How to enable TLS endpoint to a Azure Container Instance container group

This article describes the process for enabling a TLS endpoint for an application deployed to an Azure Container Instance. At the time of this writing, ACI does not provide an out-of-the-box solution for TLS/SSL, so Microsoft recommends setting up a separate container with a Nginx (“engine-x”) web server. This approach uses a sidecar pattern to allow TLS connections for your application without changing your application code. Nginx is a web server that can be used as a reverse proxy, load balancer, and HTTP cache. It also has support for TLS/SSL, URL rewriting, and redirection.

1.First obtain a certificate. This can be a self-signed certificate for non-production environments. Production environments will require a certificate issued by a trusted authority (CA). You will need the .crt and .key files from the certificate. The .crt file is the public part of the SSL certificate. Do not commit the .key file in source control or display its contents anywhere in plain text. This is the private key and therefore highly sensitive information.

2. Save the .crt and .key files somewhere in your local file system.

3. Create a configuration file for Nginx to use TLS. Start by copying the following text into a new file named nginx.conf. Commit this file to source control. It doesn’t really matter where you put it in the repository.

# Configuration file for Nginx to use TLS
# https://docs.microsoft.com/en-us/azure/container-instances/container-instances-container-group-ssl#create-nginx-configuration-file

# Run as a less privileged user for security reasons.
user nginx;

worker_processes auto;

events {
  worker_connections 1024;
}

pid        /var/run/nginx.pid;

http {

    # Redirect to https, using 307 instead of 301 to preserve post data

    server {
        listen [::]:443 ssl;
        listen 443 ssl; # the server will only accept SSL connections on Port 443
        
        server_name _;

        # Protect against the BEAST attack by not using SSLv3 at all. If you need to support older browsers (IE6) you may need to add
        # SSLv3 to the list of protocols below.
        ssl_protocols              TLSv1.2;

        # Ciphers set to best allow protection from Beast, while providing forwarding secrecy, as defined by Mozilla - https://wiki.mozilla.org/Security/Server_Side_TLS#Nginx
        ssl_ciphers                ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:AES128:AES256:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK;
        ssl_prefer_server_ciphers  on;

        # Optimize TLS/SSL by caching session parameters for 10 minutes. This cuts down on the number of expensive TLS/SSL handshakes.
        # The handshake is the most CPU-intensive operation, and by default it is re-negotiated on every new/parallel connection.
        # By enabling a cache (of type "shared between all Nginx workers"), we tell the client to re-use the already negotiated state.
        # Further optimization can be achieved by raising keepalive_timeout, but that shouldn't be done unless you serve primarily HTTPS.
        ssl_session_cache    shared:SSL:10m; # a 1mb cache can hold about 4000 sessions, so we can hold 40000 sessions
        ssl_session_timeout  24h;


        # Use a higher keepalive timeout to reduce the need for repeated handshakes
        keepalive_timeout 300; # up from 75 secs default

        # remember the certificate for a year and automatically connect to HTTPS
        add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains';

        ssl_certificate      /etc/nginx/ssl.crt;
        ssl_certificate_key  /etc/nginx/ssl.key;

        location / {
            proxy_pass http://localhost:80; # TODO: replace port if app listens on port other than 80

            proxy_set_header Connection "";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $remote_addr;
        }
    }

    server {
        listen 80; # This instructs the system to catch all HTTP traffic on Port 80
        return 307 https://$host$request_uri; # This is a short code to specify the HTTPS version of whatever the user has typed
    }
}

The first server block allows the server to only accept SSL connections on port 443. The second server block (or rule) instructs the web server to automatically redirect server connections on port 80 (HTTP) to use HTTPS.

4. In location, be sure to set proxy_pass with the correct port for your app. In this example, we set it to port 80. Nginx will listen for traffic on port 443 and redirect it to your internal app listening on port 80.

5. The next step is to Base64-encode the Nginx configuration file, the TLS/SSL certificate, and the TLS key. Run the commands below in a shell prompt replacing for the name of your .crt and .key files (or rename your cert and key files to ssl.crt and ssl.key)

cat nginx.conf | base64 > base64-nginx.conf
cat ssl.crt | base64 > base64-ssl.crt
cat ssl.key | base64 > base64-ssl.key

6. Update the YAML file or ARM template by:

Opening port 443:

Declaring the Nginx container:

Declaring a volume of type secret and call it “nginx-config” in the volumes node. Paste in the Base64-encoded values for ssl.crt, ssl.key, and nginx.conf generated previously. Do not include the ssl.key in plain text. Instead, consider injecting it through a pipeline runtime variable.

Caption: supplying the Base64-encoded values for the certificate and nginx.conf files as a secret volume

7. Deploy the container group. Test the settings by issuing a HTTP request with Postman. You should see the request redirected to HTTPS and/or it will return an error saying that HTTP requests are not allowed.

8. Confirm the certificate is installed. Open a CLI prompt in the nginx container. Change directory to /etc/nginx and you should see the certificates installed there.

If you deploy your container group in an Azure virtual network, you can consider other options to enable a TLS endpoint for a backend container instance, including:

  • Azure Functions Proxies
  • Azure API Management
  • Azure Application Gateway

Sources:

https://docs.microsoft.com/en-us/azure/container-instances/container-instances-container-group-ssl

https://docs.microsoft.com/en-us/azure/container-instances/container-instances-quickstart-template

https://docs.microsoft.com/en-us/azure/container-instances/container-instances-volume-secret