Introduction to Nitro
AWS Nitro System
In short AWS Nitro is the underlying platform for the next generation of EC2 instances.
With claims of having:
- Faster innovation
- Enhanced security
- Better performance and price
These claims are great for us. Who doesn't love an increase in these areas!
Enough about Nitro here's how Nitro Enclaves come into play. ⬇️
Use case overview
Recently I have been working with a public sector organisation who requires maximum security as you can imagine. Enclaves are a great fit for this project as they allow you to create an isolated VM (Virtual Machine) that has its own Kernel, CPU, and memory.
You can only talk to said enclave using a local channel in the form of a vSocket. Meaning if an attacker managed to get on to the host machine they wouldn't be able to touch the enclave.
Key benefits of an enclave
- Cryptographic attestation
- Additional isolation and security
Enclaves are great for processing sensitive data as they are fully isolated from the parent. They can also integrate with AWS KMS (Key Management Service). Meaning only attested enclaves are allowed to decrypt with the KMS key. Providing them extra layers of security.
In this example I will show you how to set up an enclave and how to talk to it via the ec2 host using a client server model. The client will connect to the server running in the enclave. By using the enclaves
CID (Context Identifier) once connected it will send the string
hi from client into the enclave though the vSocket which will then be returned back to the client as
HI FROM CLIENT.
Server / Enclave setup guide
- You first need to create an ec2 instance that is using the nitro system. Under configure instance you can tick to enable enclaves.
- Once connected to the ec2 first setup the
nitro-cliusing this guide here.
- To setup the enclave copy over to the ec2 the below Dockerfile, run.sh and server.py files into a folder called 'server'.
Note: For this example application I am using python so we are using the
python:3.9.1-slim base image.
FROM python:3.9.1-slim WORKDIR /app COPY server.py ./ COPY run.sh ./ RUN chmod +x run.sh CMD ["/app/run.sh"]
Save this as
#!/bin/sh # Run the app python3 /app/server.py
Save this as
Server Python code
import socket import datetime def server(): print('Hello from enclave server!') port = 5000 s = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM) cid = socket.VMADDR_CID_ANY s.bind((cid, port)) s.listen() client_socket, address = s.accept() print("Connection from: " + str(address)) while True: data = client_socket.recv(1024).decode('utf-8') if not data: break print('From online user: ' + data + ' | At: ' + str(datetime.datetime.now())) # Send msg back to client but uppercase. data = data.upper() client_socket.send(data.encode('utf-8')) client_socket.close() if __name__ == '__main__': server()
Save this as
- Now to build the enclave you use:
sudo nitro-cli build-enclave --docker-path . --output-file server.eif
- To run the built enclave image use:
sudo nitro-cli run-enclave --cpu-count 2 --memory 512 --eif-path server.eif --debug-mode
- To view the read only console of the enclave use:
ENCLAVE_ID=$(nitro-cli describe-enclaves | jq -r ..EnclaveID) nitro-cli console --enclave-id $ENCLAVE_ID
Client setup guide
- Now for the client. I suggest opening another ssh connection to your ec2 so you can clearly see both outputs from the server and client easily.
- Copy over the below file on to the host.
Client python code
import socket import time import sys def client(): port = 5000 connected = False s = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM) cid = int(sys.argv) message = 'hi from client' while not connected: try: s.connect((cid, port)) connected = True print('Connected to server... ') # send msg via socket s.send(message.encode('utf-8')) # get back msg from server data = s.recv(1024).decode('utf-8') print('Received from server: ' + data) except ConnectionError: print('Server not available yet, trying again in 2 seconds...') time.sleep(2) if __name__ == '__main__': client()
Save this as
- Now to run the client. Here we are grabbing the CID of the enclave and passing it in to the client script.
CID=$(nitro-cli describe-enclaves | jq -r ..EnclaveCID) python3 ./client.py $CID
- Now you can see the output from both the enclave and ec2 host.
You have successfully talked to the enclave over vSocket! Let your imagination take you to the next level on what you can do with this!
I only scratched the surface here at what enclaves are capable of but im excited what the future holds for them in regards to code security!
Heres some issues i found during my time using nitro enclaves, since they are so new I will aim to update this over time:
- 'Could not open /env file: No such file or directory' This error message appeared a lot and was not clear what was going on it turns out that this is caused by when the enclave runs out of memory this can be fixed by increasing the hugepages size.
echo "vm.nr_hugepages=1536" | sudo tee /etc/sysctl.d/99-nitro.conf; sudo sysctl -p /etc/sysctl.d/99-nitro.conf