Using Fortanix Confidential Computing Manager to Build an Enclave OS Application that Talks Over TLS from Within an Enclave

Introduction

In this example, we will describe how to create two Python Flask applications that run securely within an AWS Nitro enclave and can communicate over a secure connection using TLS. Each application will first receive a TLS certificate signed by the Fortanix Confidential Computing Manager (CCM). The corresponding private key is generated from within the enclave and is never exposed outside the enclave. The TLS connection itself is terminated within the enclave, thereby achieving end-to-end security.

Create Client/Server Applications

Let us start by creating the two applications.

Python Server Application

Create a file named server.py containing the following code:

import ssl
import uuid
from flask import jsonify, Flask
from flask_api import status

app = Flask("Python Flask TLS Server")

@app.route('/random')
def get_random_token():
    random_token = uuid.uuid4().hex[:6]
    return jsonify({
        "token": random_token
    }), status.HTTP_200_OK

if __name__ == "__main__":
    context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
    context.verify_mode = ssl.CERT_REQUIRED
    context.load_verify_locations("/app/cert/ca.crt")
    context.load_cert_chain("/app/cert/server.crt", "/app/cert/server.key")
    app.run(host="0.0.0.0", port=2222, use_reloader=False, threaded=True, ssl_context=context)

This is a very simple server listening on port 2222, accepts GET requests at path /random, and returns a random token. The server is configured to use the HTTPS protocol using a certificate at location "/app/cert/server.crt", with its corresponding key at "/app/cert/server.key". The certificate of the Certificate Authority that signed the server.crt certificate is located at "/app/cert/ca.crt". The line context.verify_mode = ssl.CERT_REQUIRED indicates that the sender of requests (in this case the client) needs to be verified using its certificate, thereby establishing a mutual TLS connection.

When this application is deployed using Enclave OS, Enclave OS will first generate the certificate key and store it at the location "/app/cert/server.key" within the enclave. It will then submit a Certificate Signing Request (CSR) to Fortanix CCM and receive back a signed certificate which will be stored at "/app/cert/server.crt".

Next, let us turn this application into a docker container. First, create a file called dockerfile.server with the following contents:

FROM python:3.7
RUN mkdir /app
RUN mkdir /app/cert
COPY server.py /app/
RUN pip3 install flask Flask-API
WORKDIR /app
ENTRYPOINT ["python3", "server.py"]

Then build the container and push it to a registry using the commands:

docker build -t fortanix/python-tls-server -f dockerfile.server .
docker push fortanix/python-tls-server

In the above commands, you can adjust the tag to point to your own container registry, or simply skip this step (you do not have permission to push to the Fortanix docker hub registry) since this container is already pushed to the Fortanix public registry (https://hub.docker.com/r/fortanix/python-tls-server).

Python Client Application

Similar to the server application above, the client application will receive a signed certificate from Fortanix CCM. In response to receiving requests from a user, the client application uses this certificate to establish a mutual TLS connection with the server and submit a request to the server. The server response, which is a random token in our simple example, will be sent back to the user.

Create a file called client.py containing the following code:

import os
import requests
import ssl
from flask import Flask

SERVER_DOMAIN = "flask-server.domain"
ENDPOINT = "https://" + SERVER_DOMAIN + ":2222"
app = Flask("Python Flask TLS Client")

@app.route("/submit")
def submit_request():
    path = ENDPOINT + "/random"
    req = requests.get(url=path, verify='/app/cert/ca.crt', cert=("/app/cert/client.crt", "/app/cert/client.key"))
    return req.content

if __name__ == '__main__':
    os.system("echo '0.0.0.0 " + SERVER_DOMAIN + "' >> /etc/hosts")
    context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
    context.load_verify_locations("/app/cert/ca.crt")
    context.load_cert_chain("/app/cert/client.crt", "/app/cert/client.key")
    app.run(host="0.0.0.0", port=3333, use_reloader=False, threaded=True, ssl_context=context)

Next, create a docker file named dockerfile.client with these contents:

FROM python:3.7
RUN mkdir /app
RUN mkdir /app/cert
COPY client.py /app/
RUN pip3 install flask Requests
WORKDIR /app
ENTRYPOINT ["python3", "client.py"]

Build and push the client container using the commands:

docker build -t fortanix/python-tls-client -f dockerfile.client .
docker push fortanix/python-tls-client

The client container is already available here: https://hub.docker.com/r/fortanix/python-tls-client.

Create Fortanix CCM applications

Now that we have the containers of both applications available to a registry, let us create Fortanix CCM applications for both the client and the server, so we can convert the containers into ones that run within enclaves, using Fortanix Enclave OS.

Follow the same steps described in this example to issue REST API request to:

  1. Authenticate to Fortanix CCM.

  2. Select an account.

  3. Recall that session tokens are short-lived and you will occasionally have to issue a refresh API request whenever you get {"message": "Forbidden", "code": "FORBIDDEN"}.

When you reach the step for creating an application, use the following server.json (replace the output_image_name with your private registry) file shown below with the API request:

curl -b $cpath -c $cpath -H "X-CSRF-Header:true" -H "Content-Type: application/json" -d @server.json -X POST https://ccm.fortanix.com/v1/apps
{
    "name": "Python TLS Server",
    "description": "A python server using Flask and a CCM certificate",
    "input_image_name": "fortanix/python-tls-server",
    "output_image_name": "fortanix-private/python-tls-server-nitro",
    "default_build_settings": {
        "sgx": {},
        "nitro_enclaves": {
            "cpu_count": 2,
            "mem_size": 1024,
            "enable_overlay_filesystem_persistence": true
        }
    },
    "group_id": "a8e8395e-096d-4eb8-9017-2098f2ab8327",
    "allowed_domains": [
        "flask-server.domain"
    ],
    "advanced_settings": {
        "entrypoint": [],
        "manifestEnv": [],
        "encryptedDirs": [],
        "certificate": {
            "issuer": "MANAGER_CA",
            "subject": "flask-server.domain",
            "keyType": "RSA",
            "keyParam": {
                "size": 2048
            },
            "keyPath": "/app/cert/server.key",
            "certPath": "/app/cert/server.crt"
        },
        "caCertificate": {
            "caPath": "/app/cert/ca.crt",
            "system": "false"
        },
        "rw_dirs": [
            "/app/cert"
        ]
    },
 "custom_metadata": {
        "app_type": "ENCLAVE_OS"
    }
}

Make a note of the <app_id> and the (pending_task_id) in the response since we will be using these in the next two requests.

{
    "name":"Python TLS Server",
    "app_id":"1ef7d352-28b6-4560-a714-a503435455bc",
    "pending_task_id":"d5550480-697e-4782-9bc5-58aaaa97e712",
    "domains_added":["flask-server.domain"],
    ...
}

Since this application contains a certificate with a domain, you will need to whitelist the application domain:

curl -b $cpath -c $cpath -H "X-CSRF-Header:true" -H "Content-Type: application/json" -d '{"status":"APPROVED"}' -X PATCH https://ccm.fortanix.com/v1/tasks/<task_id>

Finally, follow the steps in this example to create and whitelist an image. You will need to use the <app_id> as part of these steps.

Follow the exact same steps to create a Fortanix CCM application for the client application, as well as create and whitelist an image. Use the client.json file when creating your client application:

{
    "name": "Python TLS Client",
    "description": "A python client using Flask and a CCM certificate",
    "input_image_name": "fortanix/python-tls-client",
    "output_image_name": "fortanix-private/python-tls-client-nitro",
    "default_build_settings": {
        "sgx": {},
        "nitro_enclaves": {
            "cpu_count": 2,
            "mem_size": 1024,
            "enable_overlay_filesystem_persistence": true
        }
    },
    "group_id": "a8e8395e-096d-4eb8-9017-2098f2ab8327",
    "allowed_domains": [
        "flask-client.domain"
    ],
    "advanced_settings": {
        "entrypoint": [],
        "manifestEnv": [],
        "encryptedDirs": [],
        "certificate": {
            "issuer": "MANAGER_CA",
            "subject": "flask-client.domain",
            "keyType": "RSA",
            "keyParam": {
                "size": 2048
            },
            "keyPath": "/app/cert/client.key",
            "certPath": "/app/cert/client.crt"
        },
        "caCertificate": {
            "caPath": "/app/cert/ca.crt",
            "system": "false"
        },
        "rw_dirs": [
            "/app/cert"
        ]
    },
 "custom_metadata": {
        "app_type": "ENCLAVE_OS"
    }
}

Run the converted applications

At this point, we have everything we need to deploy and test our converted applications on a compute node that has been enrolled in CCM that will run securely within an enclave.

Let us start by running the server application using the following command, replacing the <ip_addr> with the IP address of your compute node and the image name to your image in your private registry:

docker run --privileged --volume /dev:/dev -v /run/nitro_enclaves:/run/nitro_enclaves -e NODE_AGENT_BASE_URL=http://<ip_addr>:9092/v1 -p 2222:2222 fortanix-private/python-tls-server-nitro:latest

On the same compute node, run the client application on a separate terminal window using:

docker run --privileged --volume /dev:/dev -v /run/nitro_enclaves:/run/nitro_enclaves -e NODE_AGENT_BASE_URL=http://:9092/v1 -p 3333:3333 --network host fortanix-private/python-tls-client-nitro:latest

Now that both the server and the client are running, we can use yet another terminal window to submit a request using curl:

curl -k --cacert ca.pem https://localhost:3333/submit

ca.pem is the certificate of the authority that signed the server.crt and client.crt certificates delivered to the applications. The ca.pem file is also called the Zone Certificate and it is specific to your Fortanix CCM account. All applications under the same account receive certificates signed by the same Zone certificate.

To get the CA certificate, you can use the attached script: fetch_ca.sh

Conclusion

In this example, we have created two applications that run securely within an enclave. Both applications receive TLS certificates from the Fortanix Confidential Computing Manager which they then use to establish secure channels through which they can communicate with each other but also with external users.