Running Nginx in Nitro

Nginx is a good example of an application that would benefit from running in a 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, we simplify the setup and ignore the TLS configuration, and instead focus on running Nginx as a HTTP server to learn how to configure the Nitro Enclave to allow HTTP clients to connect to Nginx, even though it is running in the 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 to an Enclave Image File (EIF),

  • configure the network interfaces on the parent instance

  • start the Anjuna Nitro Network proxy

  • start a Nitro Enclave using the previously create 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 Nitro, which is the VSOCK interface. Without this deamon, the enclave would have no way to send/receive data from clients that are not in the enclave.

To support anjuna-nitro-netd-parent, a virtual tunnel interface (anjuna0) is created and used to send and receive the network packets between the parent instance and the enclave.

On this virtual interface, the enclave is assigned with private IP 192.168.0.1 and the parent instance is assigned with private IP 192.168.0.2.

These IP addresses can be used for:

  • the parent host to communicate with the enclave: on the parent host, the command curl 192.168.0.1:80 would connect to the application listening on port 80 inside the enclave.

  • the enclave to communicate with the parent host: in the enclave, the command curl 192.168.0.2:80 would connect to the application listening on port 80 on the parent instance.

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

Let’s get started!

Identify a Simple Docker Image

We will use the Official Nginx docker image on Dockerhub: https://hub.docker.com/_/nginx

Build an enclave image file (EIF)

Since the Docker image is already built and published in Dockerhub, we can just convert it into a Enclave Image File. The following command will automatically pull the Nginx Docker image from Dockerhub and convert it into an EIF.

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

Configure the network interfaces on the parent instance

Run the following command:

sudo /opt/anjuna/nitro/bin/routing-setup.sh

This command will create the virtual network interface anjuna0 and update the IP tables rules to route traffic in/out of the enclave.

This command needs to be run once (it’s not necessary to run the command every time an enclave is created), but since the changes are not persistent, if the parent host is rebooted, it should be run again.

It does not hurt to run the command multiple times. If the virtual network interfaces and IP table rules are already configured, the operation leaves the configuration in the same state.

Start the Anjuna Nitro Network proxy

Run the following command:

sudo /opt/anjuna/nitro/bin/anjuna-nitro-netd-parent --daemonize

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

Similarly to the routing-setup.sh command, this command needs to be executed only once (unless the system is rebooted).

It does not hurt to run the command multiple times. If anjuna-nitro-netd-parent is already running, the command will be ignored.

Start the Nitro Enclave

$ anjuna-nitro-cli run-enclave --cpu-count 2 --memory 1024 --eif-path nginx.eif --debug-mode

Verify that Nginx is running

$ curl 192.168.0.1: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 Nitro Enclave.

Exposing the Nitro Enclave to external hosts

In the previous section, you started a Nitro Enclave running Nginx and connected to it from the parent instance, using the IP address 192.168.0.1 on the anjuna0 virtual interface.

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

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

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

To allow another host to connect to the enclave, the setup-routing.sh tool must be used. In this example, run the following command:

$ sudo /opt/anjuna/nitro/bin/routing-setup.sh --fwd-port 80
$ sudo /opt/anjuna/nitro/bin/anjuna-nitro-netd-parent --daemonize

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 would become:

$ sudo /opt/anjuna/nitro/bin/routing-setup.sh --fwd-port 443
$ sudo /opt/anjuna/nitro/bin/anjuna-nitro-netd-parent --daemonize

If your application exposes multiple ports (for example port 80 and port 443), you can call /opt/anjuna/nitro/bin/routing-setup.sh to expose each port.

After you exposed port 80, you can terminate and restart the Nitro Enclave running Nginx, and it will become reachable through the host’s external IP address.

$ anjuna-nitro-cli terminate-enclave --all
$ anjuna-nitro-cli run-enclave --cpu-count 2 --memory 1024 --eif-path nginx.eif --debug-mode

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

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