How Do EC2 Instances Communicate with AWS Nitro Enclaves?
An Amazon EC2 parent instance communicates with its isolated AWS Nitro Enclave using a local virtual socket interface known as vsock. Because Nitro Enclaves are hardened virtual machines without persistent storage, external network interfaces, or interactive administrator access, the vsock channel serves as the sole data pathway between the parent instance and the enclave environment. By addressing endpoints using Context Identifiers and port numbers instead of standard IP addresses, applications on the parent EC2 instance can securely exchange cryptographic payloads, KMS requests, and application state with enclave processes.
Understanding the AWS Nitro Enclaves Architecture
AWS Nitro Enclaves allow customers to carve out dedicated CPU cores and memory from an existing Amazon EC2 parent instance to create an isolated compute environment. The enclave runs an independent kernel and operates without any traditional network stack, meaning it has no Ethernet interface, no IP address, no routing tables, and no direct access to the internet or Amazon Virtual Private Cloud (VPC).
To bridge the communication gap between the parent instance and the
isolated enclave, the AWS Nitro Hypervisor exposes a virtual socket
(AF_VSOCK) device. This point-to-point communication
channel bypasses the traditional TCP/IP stack while retaining standard
POSIX socket semantics (socket, bind,
listen, accept, connect,
send, and recv).
How Vsock Addressing Works
Instead of IPv4 or IPv6 addresses, the AF_VSOCK address
family identifies endpoints using a tuple consisting of a 32-bit Context
Identifier (CID) and a 32-bit port number:
\[\text{Address} = (\text{CID}, \text{Port})\]
In an AWS Nitro Enclaves environment, specific CIDs are reserved or assigned dynamically:
- Parent EC2 Instance CID (
3): The parent instance is always assigned a static CID of3(VMADDR_CID_HOSTis2, while the parent EC2 instance in Nitro is consistently3). - Enclave CID (Dynamic or User-Defined): When
starting an enclave with the
nitro-cli run-enclavecommand, the administrator can specify a unique CID (for example,16) using the--enclave-cidflag, or allow the Nitro CLI to assign one automatically. - Loopback CID (
1): Represents local loopback communication (VMADDR_CID_LOCAL). - Wildcard CID (
4294967295or0xFFFFFFFF): Known asVMADDR_CID_ANY, allowing a listening socket to bind and accept incoming connections regardless of the local CID assignment.
Implementing Direct Vsock Communication in Python
Standard Linux programming languages with socket support—including C,
Rust, Go, and Python (version 3.7 and later)—support
socket.AF_VSOCK natively. Either the parent instance or the
enclave can act as the server or client. A common pattern is to run a
listening server inside the enclave on a specific port and connect to it
from the parent EC2 instance.
Enclave Server Implementation
Inside the enclave, the application binds to
VMADDR_CID_ANY and listens on a chosen port (such as port
5000):
import socket
PORT = 5000
def run_enclave_server():
with socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM) as server_socket:
# Bind to any CID on the specified port
server_socket.bind((socket.VMADDR_CID_ANY, PORT))
server_socket.listen(1)
print(f"Enclave listening on vsock port {PORT}...")
while True:
conn, (remote_cid, remote_port) = server_socket.accept()
with conn:
print(f"Accepted connection from CID {remote_cid}, port {remote_port}")
data = conn.recv(4096)
if data:
# Process sensitive payload inside the enclave
response = b"Processed by Enclave: " + data
conn.sendall(response)
if __name__ == "__main__":
run_enclave_server()Parent EC2 Client Implementation
On the parent EC2 instance, the client connects to the enclave's
assigned CID (for example, 16) and port
5000:
import socket
ENCLAVE_CID = 16
PORT = 5000
def send_to_enclave(payload: bytes) -> bytes:
with socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM) as client_socket:
# Connect to the enclave's CID and port
client_socket.connect((ENCLAVE_CID, PORT))
client_socket.sendall(payload)
response = client_socket.recv(4096)
return response
if __name__ == "__main__":
reply = send_to_enclave(b"Hello from EC2 parent")
print(reply.decode("utf-8"))Bridging TCP and Vsock Using Socat or Vsock-Proxy
Many existing applications and SDKs expect standard TCP/IP sockets
(AF_INET) rather than AF_VSOCK. Moreover, when
an enclave needs to communicate with an external endpoint—such as AWS
Key Management Service (AWS KMS) to verify cryptographic attestation
documents and decrypt secrets—traffic must be forwarded through the
parent EC2 instance.
Forwarding
External Traffic with vsock-proxy
AWS provides a built-in utility called vsock-proxy as
part of the Nitro Enclaves CLI package. It runs on the parent EC2
instance, listens on a local vsock port, and forwards traffic over
TCP/TLS to an allowlisted external endpoint such as AWS KMS.
To start vsock-proxy on the parent instance for AWS KMS
in us-east-1 on vsock port 8000:
vsock-proxy 8000 kms.us-east-1.amazonaws.com 443Inside the enclave, an application or local forwarder connects to CID
3 (the parent instance) on port 8000. Because
the TLS handshake occurs end-to-end between the enclave and AWS KMS, the
parent EC2 instance only passes encrypted ciphertext and cannot inspect
or tamper with the payload.
Translating Local TCP
to Vsock with socat
If an application inside the enclave only speaks standard TCP on
127.0.0.1, you can use socat (version 1.7.4 or
later with vsock support) inside the enclave to bridge local TCP
connections to the parent's vsock-proxy:
# Run inside the enclave to forward localhost:443 to parent CID 3, vsock port 8000
socat TCP4-LISTEN:443,bind=127.0.0.1,fork VSOCK-CONNECT:3:8000By combining an /etc/hosts entry inside the enclave
(127.0.0.1 kms.us-east-1.amazonaws.com) with this
socat bridge, standard AWS SDKs running inside the enclave
can make HTTPS requests to AWS KMS transparently over the virtual
socket.
Security Considerations for Vsock Channels
While the Nitro Hypervisor guarantees isolation between the parent instance and the enclave, the vsock channel itself is an unencrypted raw byte stream by default. Under the Nitro Enclaves threat model, the parent EC2 instance is considered untrusted or potentially compromised. Consequently, developers should enforce strict security practices over vsock:
- Authenticate and Encrypt Traffic: Use TLS or a mutual authentication protocol over the vsock stream when transmitting sensitive records from external clients through the parent instance into the enclave.
- Use Cryptographic Attestation: Before sending
sensitive keys or data to an enclave, verify the enclave's identity
using an attestation document signed by the Nitro Hypervisor's Platform
Configuration Registers (PCRs), such as
PCR0(enclave image hash) andPCR8(signing certificate hash). - Validate Input Strictly: Treat all bytes received
over
AF_VSOCKinside the enclave as untrusted input, applying strict schema validation, length bounds, and deserialization safeguards.