Running Nginx in AWS Nitro

Nginx is a good example of an application that would benefit from running in an AWS Nitro Enclave. In a production environment, it needs to have access to sensitive data (the TLS certificate), and it can serve web applications that connect to other services. Being able to completely isolate and protect the Nginx application from malicious users (even those who might have elevated privileges to the parent instance) is very valuable.

In this example, you simplify the setup and ignore the TLS configuration, and instead focus on running Nginx as an HTTP server to learn how to configure the AWS Nitro Enclave to allow HTTP clients to connect to Nginx, even though it is running in the AWS Nitro Enclave.

The steps to run Nginx in an enclave are similar to the ones in the previous section, except that there a couple of new steps (indicated in bold):

  • identify a Docker image that contains an application,

  • convert the Docker image into an Enclave Image File (EIF),

  • configure the network interfaces on the parent instance

  • start the Anjuna Nitro Network Proxy

  • start an AWS Nitro Enclave using the previously created Enclave Image File.

For Nginx to be able to communicate with clients outside the enclave, the anjuna-nitro-netd-parent is used to forward the network traffic to/from the enclave through the only data-exchange interface available in AWS Nitro, which is the vsock interface. Without this daemon, the enclave would not have a method to send/receive data from clients that are not in the enclave.

On this virtual interface, the enclave and the parent instance are assigned the same IP address. When anjuna-nitro-netd-parent is running, the parent instance can communicate with the enclave using localhost. For example, requests to localhost:80 on the parent instance are directed to the application listening on port 80 inside the enclave.

In this example, you will start an Nginx server in the enclave, configured to listen on port 80 (HTTP), and you will verify that the Nginx server is running by sending a curl command from the parent instance.

Identify a simple Docker image

You will use the official Nginx Docker image on Docker Hub.

Build an enclave image file (EIF)

Since the Docker image is already built and published in Docker Hub, you can convert it into an Enclave Image File.

If you have not already done so, use your license by putting the license file at /opt/anjuna/license.yaml.

Then, run the following command to automatically pull the Nginx Docker image from Docker Hub and convert it into an EIF.

$ anjuna-nitro-cli build-enclave --docker-uri nginx:latest --output-file nginx.eif

Start the Anjuna Nitro Network Proxy

Run the following command:

$ anjuna-nitro-netd-parent --enclave-name nginx --expose 80 --daemonize

This command is responsible for moving the network packets between the AWS Nitro Enclave and the parent instance, and must be running for the enclave to communicate with the outside world.

If your application exposes multiple ports (for example port 80 and port 8080), you can use the --expose parameter multiple times.

The enclave name is a user-provided identifier that the Anjuna Nitro Runtime uses to connect running enclaves with supporting services (networking, persistent storage, etc). It is important that the same enclave name is provided to the network proxy and to the run-enclave command described below, or the enclave will not work as expected.

This command starts the Anjuna Nitro Network Proxy as a background process. If you need to expose a different set of ports, you need to kill the Network Proxy process and run it again with the new set of ports. For example, to expose both port 80 and 443, run the following command:

$ pkill -f 'anjuna-nitro-netd-parent --enclave-name nginx'  # stops the proxy exposing just port 80
$ anjuna-nitro-netd-parent --enclave-name nginx --expose 80 --expose 443 --daemonize
If the enclave is running and you want to expose additional ports, you will need to terminate the enclave before killing the Network Proxy process.
Make sure that you kill the correct Network Proxy process that was launched with the same enclave name that your Nginx enclave will use.

Start the AWS Nitro Enclave

$ anjuna-nitro-cli run-enclave --enclave-name nginx --cpu-count 2 --memory 1024 --eif-path nginx.eif --debug-mode
The number of vCPU cores must be an even number due to hyperthreading.

Verify that Nginx is running

$ curl localhost:80

You should see the following output:

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Congratulations! Nginx is running and is accessible from outside the AWS Nitro Enclave.

Exposing the AWS Nitro Enclave to external hosts

In the previous section, you started an AWS Nitro Enclave running Nginx and connected to it from the parent instance, using the IP address localhost.

In a typical scenario, Nginx should be reachable from other hosts:

  • If the AWS Nitro parent instance is on an AWS VPC, other hosts on this VPC might try to connect to Nginx.

  • This AWS Nitro parent instance might be reachable from the internet (your web browser for example).

Whenever you terminate an AWS Nitro Enclave, you should also terminate its Network Proxy to clean up its resources. Run the following commands to terminate and restart the AWS Nitro Enclave running Nginx and the Network Proxy. Nginx will become reachable through the host’s external IP address.

$ anjuna-nitro-cli terminate-enclave --enclave-name nginx
$ pkill -f 'anjuna-nitro-netd-parent --enclave-name nginx'
$ anjuna-nitro-netd-parent --enclave-name nginx --expose 80 --daemonize
$ anjuna-nitro-cli run-enclave --enclave-name nginx --cpu-count 2 --memory 1024 --eif-path nginx.eif --debug-mode

This is only an example for going through this tutorial. In most cases, you should never expose the port 80 to serve HTTP content, and you should use TLS on port 443. In that case, the command will become:

$ anjuna-nitro-netd-parent --enclave-name nginx --expose 443 --daemonize

Run the following command on another host (replace <nitro-instance-ip> with the actual IP address of your AWS Nitro parent instance):

$ curl <nitro-instance-ip>:80
You might need to update the security rules (inbound rules) to allow traffic to be accepted by the AWS Nitro host.