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 of 3 (VMADDR_CID_HOST is 2, while the parent EC2 instance in Nitro is consistently 3).
  • Enclave CID (Dynamic or User-Defined): When starting an enclave with the nitro-cli run-enclave command, the administrator can specify a unique CID (for example, 16) using the --enclave-cid flag, or allow the Nitro CLI to assign one automatically.
  • Loopback CID (1): Represents local loopback communication (VMADDR_CID_LOCAL).
  • Wildcard CID (4294967295 or 0xFFFFFFFF): Known as VMADDR_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 443

Inside 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:8000

By 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:

  1. 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.
  2. 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) and PCR8 (signing certificate hash).
  3. Validate Input Strictly: Treat all bytes received over AF_VSOCK inside the enclave as untrusted input, applying strict schema validation, length bounds, and deserialization safeguards.