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

How to install a trusted certificate in a Docker container

How to install a trusted certificate in a Docker container

This document describes the process of installing a certificate inside a Docker container’s trusted root certificate store.

  1. The first step is to load the .crt file into the container’s file system. Have in mind that .crt is the public part of an SSL certificate. You should never store sensitive information, secrets and passwords alike, in a container or in a source control repository.
  2. Add the .crt file in the same folder as your Dockerfile
  3. Make sure the .crt file is included in your build’s output directory (e.g., the ‘bin’ folder). In Visual Studio, you can do this by right-clicking the file and enabling the “Copy to Output Directory” property.
  4. Add these lines to the bottom of your Dockerfile but before the ENTRYPOINT.
    1. COPY my-cert.crt usr/local/share/ca-certificates/my-cert.crt
    2. RUN chmod 644 /usr/local/share/ca-certificates/my-cert.crt && update-ca-certificates
  5. The COPY statement adds the certificate to the container’s trusted root certificate store which is located in usr/local/share/ca-certificates. If the certificate is not copied or if you get a “file not found” error, make sure the source path is relative to your application’s build context and the target path to the WORKDIR.
  6. The RUN statement gives read and write access to the owner in the file and updates the trusted certificates.
  7. Confirm the certificate was successfully installed by inspecting the etc/ssl/certs folder inside of the container. Your certificate should appear here with a .pem extension.

And that’s it! That’s all you should need to install a trusted certificate. You can test if the certificate was configured correctly by curling the target server:

curl --verbose https://<host>:<port>

If the connection is successful and verified by the root certificate, you should see a “ssl certificate verify ok” message in the response.

Pulling images from an Azure Container Registry in Docker Compose

Step 1: obtain credentials from ACR

1. In the Azure portal, navigate to your existing container registry
2. Go to Access Keys
3. Create an access key if there isn’t one yet
4. Copy the username and password that was generated. These are the credentials you will use to authenticate with ACR

Step 2: docker login

1. From a terminal window, run docker login using the username and password provided by ACR in step 1. If there are cached credentials, you might want to run first ‘docker logout’ and remove any existing credentials stored in KeyChain (MacOS), WinCred (Windows), or pass (Linux). By default, Docker looks for the native binary on each platform for storing credentials

Step 3: docker compose

1. Navigate to Azure portal > > Repositories > and click on the latest image tag. From here you will be able to copy the URL to pull down the image
2. Add the image in Docker Compose. For example:

services:
notificationservice:
image: myacr.azurecr.io/ordersapi:721

3. Run ‘docker compose up’ at this point. If the project fails to pull the image, try running a ‘docker pull’ command from a separate terminal and make sure you are able to pull the image using the credentials supplied by ACR