亚马逊AWS官方博客

在 AWS Nitro Enclaves 中运行传统 Web 应用迁移实践

简介

在今天,数据安全和隐私越来越被重视。尤其在一些涉及处理用户隐私数据的行业,比如金融、医疗等行业里面,对隐私的、隔离的计算需求越来越强烈。非常多的组织已经在云上去构建他们的业务系统,可以利用 AWS 提供的各种安全服务功能,实现敏感数据存储时的加密与传输过程中的加密。但是,当对这些敏感数据进行处理时,还是需要对这些数据进行解密,并在 Amazon EC2 中去处理。如何去保护这些已经被解密的数据呢?这就需要一个隔离的计算环境来处理敏感的数据。 AWS Nitro Enclaves 提供的功能就是帮助客户在云上的环境里去创建一个隔离的计算空间,这样客户就可以进一步保护和处理高度敏感的数据。

Nitro Enclaves 使用 Nitro Hypervisor 将 CPU 和内存与 EC2 父实例隔离。他没有持久存储,不提供交互访问,也没有外部网络连接,只能使用 vsock 本地通道与父级 EC2 实例进行通信。vsock 是一种 socket interface,通过指定的 Context ID(CID)与端口号进行互相通信。vsock 也提供符合 POSIX 规范的 Socket API。应用程序需要使用 vsock API 进行网络通信,这就需要对代码进行改造才可以让运行在 Enclaves 中的 app 发送 HTTP 请求或做为服务器接受 HTTP 请求。在应用迁移过程中,有时由于各种原因难以进行业务代码的改造。

本文介绍利用 vsock proxy 方案,避免对业务进行侵入式的代码改造。让原有的传统服务器应用,直接运行在 Enclaves 内。业务即可做为 Client 端往外发送 HTTP 请求,又可作为 Server 端让外部通过 HTTP 进行访问。

架构

  • 部署在 Enclaves 内的应用监听端口,外部会发起请求。需要在父实例部署 proxy 进行 http 监听并做 vsock 转发。
  • 在 Enclaves 内部署 proxy 进行 vsock 的监听并转发 http 请求到 server app。
  • Enclaves 内运行的应用只需正常监听 http 即可,不需要进行代码侵入性的改造。
  • Server app 收到 client 发来的请求后,会发起一个 Http Request 到外部系统,用于获取数据,并把数据返回给 client。
  • Server app 利用 Enclaves 的 lo 回环网络把请求送到 127.0.0.1:443。Enclaves 内部署 proxy,监听 127.0.0.1 上的 http,并做 vsock 转发到父实例。
  • 父实例运行 proxy,监听 vsock 并发起 http 请求到外部系统获取数据。

安装和配置 Nitro Enclaves CLI 与工具,可参考文档

1. 为了能运行 Enclaves,需要安装 Nitro Enclaves CLI

sudo amazon-linux-extras install aws-nitro-enclaves-cli -y

2. 安装 Nitro Enclaves 开发者工具

sudo yum install aws-nitro-enclaves-cli-devel -y

3. 配置用户权限

sudo usermod -aG ne $USER
sudo usermod -aG docker $USER

4. 验证 Nitro Enclaves CLI 已经被正确安装

nitro-cli –version

应该输出版本号

5. 启动 Nitro Enclaves allocator services

sudo systemctl start nitro-enclaves-allocator.service && sudo systemctl enable nitro-enclaves-allocator.service

6. 启动 Docker service

sudo systemctl start docker && sudo systemctl enable docker

构建 Enclaves 的 Image 文件

应用程序首先打包成 Docker Image,然后通过 Nitro-CLI 打包为 Enclaves 镜像。

应用程序的目录结构

ServerApp

—–main.go

—–go.mod

—–go.sum

—–run.sh

—–Dockerfile

main.go

package main

import (
	"io"
	"log"
	"net/http"
)

const PORT = ":8888"

func main() {
	handler := func(w http.ResponseWriter, req *http.Request) {
		io.WriteString(w, "Sending Http Request:\\n")
		resp, err := http.Get("https://dynamodb.us-west-2.amazonaws.com")
		if err != nil {
			log.Fatal(err)
		}

		defer resp.Body.Close()

		if resp.StatusCode == http.StatusOK {
			bodyBytes, err := io.ReadAll(resp.Body)
			if err != nil {
				log.Fatal(err)
			}
			bodyString := string(bodyBytes)
			io.WriteString(w, bodyString)
		}

	}

	http.HandleFunc("/", handler)
	log.Println("listening on", PORT)
	log.Fatal(http.ListenAndServe(PORT, nil))
}

Dockerfile

FROM golang:1.18-alpine

ARG VERSION=1.7.4.3-r0
RUN apk --no-cache add socat=${VERSION}

WORKDIR /app


COPY run.sh ./
COPY go.mod ./
COPY go.sum ./
RUN go mod download
COPY *.go ./

RUN go build -o /httptest

EXPOSE 8888

RUN chmod +x /app/run.sh

CMD ["/app/run.sh"]

run.sh

# Assign an IP address to local loopback 
ip addr add 127.0.0.1/32 dev lo

ip link set dev lo up

# Add a hosts record, pointing target site calls to local loopback
echo "127.0.0.1   dynamodb.us-west-2.amazonaws.com" >> /etc/hosts

/httptest &
socat VSOCK-LISTEN:8001,fork,reuseaddr TCP:127.0.0.1:8888 &
socat TCP-LISTEN:443,fork,reuseaddr VSOCK-CONNECT:3:8002

在 Enclaves 中,运行 Server App 和 ProxyServer,SOCAT 作为 ProxyServer。SOCAT 是一个基于命令行的 Linux 实用程序,用于建立两个双向字节流并在它们之间传输数据。SOCAT 从 1.7.4 版本开始支持 vsock 协议,可通过 VSOCK-LISTEN:<port>监听 vsock 连接,通过 VSOCK-CONNECT:<cid>:<port>发起连接。SOCAT 也支持 fork 选项用于多连接的服务器模式。

由于 Enclaves 中不提供对外网络连接,所以需要利用 local loopback 回环网络并分配 127.0.0.1 到 lo 上。如果 app 需要访问 External URL,则通过 host 把 url 对应到 127.0.0.1,这样发起的请求会被 tcp->vsock的proxyServer 所处理从而转发到外部网络。

进行 Docker 打包

docker build ./ -t httptest

Output Example

Successfully tagged httptest:latest

Nitro Enclaves Image 打包

nitro-cli build-enclave --docker-uri httptest:latest --output-file httptest.eif

Output Example

Using the locally available Docker image...
Enclave Image successfully created.

运行 Enclave

nitro-cli run-enclave --cpu-count 2 --memory 6144 --eif-path httptest.eif --debug-mode

Output Example

Started enclave with enclave-cid: 16, memory: 6144 MiB, cpu-ids: [1, 9]
{
  "EnclaveName": "httptest",
  "EnclaveID": "i-0d79d749bxxxxxxx-enc1878b6d5xxxxxxx3",
  "ProcessID": 15404,
  "EnclaveCID": 16,
  "NumberOfCPUs": 2,
  "CPUIDs": [
    1,
    9
  ],
  "MemoryMiB": 6144
}

查看 Enclaves 运行日志

ENCLAVE_ID=$(nitro-cli describe-enclaves | jq -r ".[0].EnclaveID")
[ "$ENCLAVE_ID" != "null" ] && nitro-cli console --enclave-id ${ENCLAVE_ID}

Example Output

listening on :8888

在父实例运行 http->vsock socat proxy

ENCLAVE_CID=$(nitro-cli describe-enclaves | jq -r ".[0].EnclaveCID")
docker run -p 8001:8001 alpine/socat TCP-LISTEN:8001,fork,reuseaddr VSOCK-CONNECT:${ENCLAVE_CID}:8001

在父实例运行 vsock->http socat proxy,这个 proxy 提供访问 External URL 功能,参数需要指定要访问的 URL。

docker run -p 8002:8002 alpine/socat VSOCK-LISTEN:8002,fork,reuseaddr TCP:dynamodb.us-west-2.amazonaws.com:443

客户端发起 http 请求,请求发给父实例运行的 http->vsock proxy,这样请求会被转发到 Enclaves 内运行的 Server App。

curl http://localhost:8001

Example Output

Sending Http Request:\nhealthy: dynamodb.us-west-2.amazonaws.com

可以看到,客户端发起的请求,已经被 Enclaves 内的 Server 所处理。Server 也能从 External URL 获得数据并返回给客户端。

停止应用

ENCLAVE_ID=$(nitro-cli describe-enclaves | jq -r ".[0].EnclaveID")
[ "$ENCLAVE_ID" != "null" ] && nitro-cli terminate-enclave --enclave-id ${ENCLAVE_ID}

Example Output

Successfully terminated enclave i-0d79d7xxxxxxxxx-enc18xxxxxxxxxx.
{
  "EnclaveName": "httptest",
  "EnclaveID": "i-0d79d74xxxxxxxe-enc1878b6xxxxxxx",
  "Terminated": true
}

总结

本文介绍了如何通过 vsock proxy 的方式,在 Nitro Enclaves 内部署传统 Web 应用,演示了完整的部署过程,并提供了示例代码。按照这种模式,客户无需对业务进行侵入性的代码改造即可在 Enclaves 内运行可对外发起请求的,同时也能让外部系统连接监听的 Server App。客户可以根据本文所示范的原理进行业务系统的搭建,也可以通过编程的方式,自主实现更匹配业务要求的 vsock 转发服务器。

本篇作者

秦镜高

亚马逊云科技资深解决方案架构师,负责基于 AWS 云计算方案的咨询与架构设计,帮助客户利用先进的云服务技术构建更具创新性的应用。