一、概述
AWS Nitro Enclaves 是亚马逊云科技提供的 TEE 解决方案,可同时兼容 Intel,AMD x86 处理器以及 Graviton arm 架构处理器,并支持 EC2 实例和 Amazon EKS(Kubernetes)计算服务。从已有的 Blog 我们会找到一些对 Enclave 基于钱包的实现,但是我们看到的都是借用工具和基于 Python 和 Go 的实现,在这篇文章中,我们将介绍如何实现基于 Java Spring Boot Nitro Enclaves TEE 技术的钱包应用,并在 AWS EKS 容器平台上部署。
二、方案概述
本方案构建一个基于 Amazon EKS 部署的 Nitro Enclaves 加密钱包示例应用程序,实现对私钥进行加密,并存储到 DynamoDB 上的一套完整的实现过程,而且我们会借助消息中间件,基于消息触发,消费后创建私钥并且保存在 Dynamo DB 上,保证私钥的安全。
下图展示了钱包应用的部署架构,使用包括:Nitro Enclaves,Amazon EKS(Elastic Kubernetes Service),AWS KMS(Key Management Service),AWS DynamoDB,AWS MSK(Managed Streaming for Apache Kafka),AWS ECR(Elastic Container Registry)等服务。
利用 Nitro Enclaves 技术在 EKS worker 节点上提供一个隔离的计算环境(Enclave)来保护和安全地处理高度敏感的数据,例如区块链操作的私钥。Enclave 和 EKS Pods 以及外部 AWS 服务(如 AWS KMS 或 Secrets Manager)之间的通信仅限于安全的本地 vsocks 通道。
创建 MSK
创建 MSK 不是必要的过程,此处只是为了演示,可以通过消息事件来驱动整个流程,使得各个组件之间解耦。
首先我们先定义一个 Cluster 的配置配件:
auto.create.topics.enable=true
default.replication.factor=3
min.insync.replicas=2
num.io.threads=8
num.network.threads=5
num.partitions=1
num.replica.fetchers=2
replica.lag.time.max.ms=30000
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600
socket.send.buffer.bytes=102400
unclean.leader.election.enable=true
zookeeper.session.timeout.ms=18000
创建流程说明:
Step 1,
创建请根据上面的配置参数,创建一个自定义的配置文件,名字任意本文使用“CustomConfiguration”。
- 点击创建集群
- 选择创建的方法自定义创建
- Cluster 的名字,可以根据的记得需求修改,本文 msk-cluster
- Cluster 的类型,选择预制
- Cluster Kafka 的版本选择推荐
- Broker 的类型选择 t3.small
- 其他选项保持默认,配置选项,选择刚刚创建的 CustomConfiguration,并且选择 revision,点击下一步
Step 2,
网络以及安全组的设置,此处可以根据您所使用的网络环境进行配置,本文选择选择了 3 个公有子网,并且制定默认的安全组 Default,点击下一步。
Step 3,
安全的选择,本文开放了匿名的访问和 Plaintext 作为默认的加密方式,可以根据具体的业务需求选择不同的选项,并点击下一步。
Step 4,
此处保持默认即可,如果特殊的设置要求,请根据您的业务场景选择不同的选项,点击下一步。
最后一步我们对已设置的参数进行最后的审查,确定无误后点击创建按钮(15 mins 的等待时间)。
EKS 的创建
我们首先要创建 EKS 的 VPC 的网络环境,如果您已有,我们可以直接使用已有的网络环境,如果没有,请参见 https://docs.thinkwithwp.com/eks/latest/userguide/network_reqs.html。
网络架构图可以参考下面的结构:
我们可以用第三方的 Infra 的工具构建 EKS 的基础设施:
如果有需要变动的机型,VPC,区域,以及集群的版本等等,可以编辑文件,main.tf
provider "aws" {
region = "eu-west-1"
}
#####
# VPC and subnets
#####
data "aws_vpc" "default" {
default = false
}
# data.aws_vpc.default.id
data "aws_subnets" "all" {
filter {
name = "vpc-id"
values = ["vpc-064249f3d96c56deb"]
}
}
#####
# EKS Cluster
#####
resource "aws_eks_cluster" "cluster" {
enabled_cluster_log_types = ["api", "audit","authenticator","controllerManager","scheduler"]
name = "eks-module-test-cluster"
role_arn = aws_iam_role.cluster.arn
version = "1.23"
vpc_config {
subnet_ids = data.aws_subnets.all.ids
security_group_ids = []
endpoint_private_access = "true"
endpoint_public_access = "true"
}
}
resource "aws_iam_role" "cluster" {
name = "eks-cluster-role"
assume_role_policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "eks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
POLICY
}
resource "aws_iam_role_policy_attachment" "cluster_AmazonEKSClusterPolicy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
role = aws_iam_role.cluster.name
}
resource "aws_iam_role_policy_attachment" "cluster_AmazonEKSServicePolicy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSServicePolicy"
role = aws_iam_role.cluster.name
}
#####
# Launch Template with AMI
#####
data "aws_ssm_parameter" "cluster" {
name = "/aws/service/eks/optimized-ami/${aws_eks_cluster.cluster.version}/amazon-linux-2/recommended/image_id"
}
data "aws_launch_template" "cluster" {
name = aws_launch_template.cluster.name
depends_on = [aws_launch_template.cluster]
}
resource "aws_launch_template" "cluster" {
image_id = data.aws_ssm_parameter.cluster.value
instance_type = "c5.2xlarge"
name = "eks-launch-template-test"
update_default_version = true
key_name = "ec2-user"
enclave_options {
enabled = true
}
block_device_mappings {
device_name = "/dev/sda1"
ebs {
volume_size = 40
}
}
tag_specifications {
resource_type = "instance"
tags = {
Name = "eks-launch-template-test"
}
}
user_data = base64encode(templatefile("userdata.tpl", { CLUSTER_NAME = aws_eks_cluster.cluster.name, B64_CLUSTER_CA = aws_eks_cluster.cluster.certificate_authority[0].data, API_SERVER_URL = aws_eks_cluster.cluster.endpoint }))
}
#####
# EKS Node Group
#####
module "eks-node-group" {
source = "../../"
cluster_name = aws_eks_cluster.cluster.id
subnet_ids = data.aws_subnets.all.ids
desired_size = 1
min_size = 1
max_size = 1
launch_template = {
id = data.aws_launch_template.cluster.id
version = data.aws_launch_template.cluster.latest_version
}
labels = {
lifecycle = "OnDemand"
}
tags = {
"kubernetes.io/cluster/eks" = "owned"
Environment = "test"
}
depends_on = [data.aws_launch_template.cluster]
}
output "name" {
value = aws_eks_cluster.cluster.name
}
output "endpoint" {
value = aws_eks_cluster.cluster.endpoint
}
output "kubeconfig-certificate-authority-data" {
value = aws_eks_cluster.cluster.certificate_authority.0.data
}
手动的方式构建 EKS 基础设施
因为篇幅的关系,本文只介绍构建自定义 Enclave Launch Template 的过程,其余的跟正常创建 EKS 集群一样:
aws ssm get-parameter --name /aws/service/eks/optimized-ami/1.23/amazon-linux-2/recommended/image_id \
--region eu-west-1 --query "Parameter.Value" --output text
# 输出
ami-0fb932036294318ad
输出结果为基于 1.23 的 EKS 最新的 AL2 的 AMI 镜像,我们根据此 AMI ID 构建 Template。
我们要注意:
高级选项选择 Enclave 选择启用,选择自己希望的磁盘大小。
构建 Enclave Template 的关键是这个 User Data。
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="==MYBOUNDARY=="
--==MYBOUNDARY==
Content-Type: text/x-shellscript; charset="us-ascii"
#!/bin/bash
set -uxo pipefail
# Test to see if the Nitro enclaves module is loaded
lsmod | grep -q nitro_enclaves
RETURN=$?
set -e
# Setup Nitro enclave on the host if the module is available as expected.
if [ $RETURN -eq 0 ]; then
amazon-linux-extras install aws-nitro-enclaves-cli -y
yum install aws-nitro-enclaves-cli-devel -y
usermod -aG ne ec2-user
usermod -aG docker ec2-user
# If needed, install custom allocator config here: /etc/nitro_enclaves/allocator.yaml
systemctl start nitro-enclaves-allocator.service
systemctl enable nitro-enclaves-allocator.service
systemctl start docker
systemctl enable docker
#
# Note: After some testing we discovered that there is an apparent bug in the
# Nitro CLI RPM or underlying tools, that does not properly reload the
# udev rules when inside the AWS bootstrap environment. This means that we must
# manually fix the device permissions on `/dev/nitro_enclaves`, if we
# don't want to be forced to restart the instance to get everything working.
# See: https://github.com/aws/aws-nitro-enclaves-cli/issues/227
#
chgrp ne /dev/nitro_enclaves
echo "Done with AWS Nitro enclave Setup"
fi
set -ex
exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent && systemctl start amazon-ssm-agent
/etc/eks/bootstrap.sh ${CLUSTER_NAME} --b64-cluster-ca ${CA} --apiserver-endpoint ${AE}
--==MYBOUNDARY==--\
${CLUSTER_NAME} 集群的名字
${CA} 集群 CA 的 CA 证书
${AE} 集群的访问 API
截止到此,template 模版创建成功,我们在 EKS 上应用这个模版:
其余的,步骤我们选择节点组的最大值,最小值,和期望值,并且指定 VPC 的网络环境即可创建节点组了,等节点组创建的 Node 启动成功,并以 Ready 的状态呈现,我们就可以安装和部署我们的 Java 程序。
我们可以创建一个基于公有子网的跳板机,构建和整个 EKS 所需要的容器镜像。
创建容器镜像
我们使用 AWS 的 ECR,镜像仓库创建 Public(也可以创建 Private)的仓库,命名为 kafka-consumer,此镜像会运行在宿主机上,接受和消费 Kafka 的消息,并且将详细打包,通过 vsock 传给 enclave。
以相同的步骤创建 Repository enclave-w:
首先我们创建一个跳板机,选择机型和操作系统,使用 Amazon Linux 2,机型 c5.xlarge。将此跳板机至于公有子网,设置存储 40G,方便生成镜像和下载代码编译。创建成功后我们通过 Web Console 直接连接此实例。
# 切换 ec2-user 用户
sudo su - ec2-user
# 更新软件源
sudo yum update
# 安装 enclave 的客户端工具
sudo amazon-linux-extras install aws-nitro-enclaves-cli -y
sudo yum install aws-nitro-enclaves-cli-devel -y
# 安装 Git 软件方便下载代码
sudo yum install -y git
# 安装 Java 1.8 JDK,注意是 JDK 不是 JRE
# 安装 Maven,通过 Maven 打包
sudo wget http://repos.fedorapeople.org/repos/dchen/apache-maven/epel-apache-maven.repo -O /etc/yum.repos.d/epel-apache-maven.repo
sudo sed -i s/\$releasever/6/g /etc/yum.repos.d/epel-apache-maven.repo
sudo yum install -y apache-maven
# 查看刚安装的版本目前是 3.5.2
mvn --version
|
# 检出 Java Enlcave Spring Boot 项目
git clone https://github.com/sunchaoqun/spring-boot-nitro-enclaves
# 对代码进行编译
cd /home/ec2-user/spring-boot-nitro-enclaves
mvn install
cd /home/ec2-user/spring-boot-nitro-enclaves/examples/simple-echo
mvn install
# 安装 Docker 并设置 ec2-user 权限
sudo yum install -y docker
newgrp docker
sudo usermod -aG docker ec2-user
sudo systemctl start docker && sudo systemctl enable docker
# Build kafka-consumer 并上传至 ECR
cd /home/ec2-user/spring-boot-nitro-enclaves/examples/simple-echo/simple-echo-host
docker build -t kafka-consumer .
aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/s0k4s5w7
docker tag kafka-consumer:latest public.ecr.aws/s0k4s5w7/kafka-consumer:latest
docker push public.ecr.aws/s0k4s5w7/kafka-consumer:latest
# Build 基于 Java 的程序成 Image,方便后面我们将其封装成满足 Enclave 运行要求的镜像
cd /home/ec2-user/spring-boot-nitro-enclaves/examples/simple-echo/simple-echo-enclave
docker build -t enclave-wallet .
cd /home/ec2-user/spring-boot-nitro-enclaves/examples/simple-echo/simple-echo-enclave/enclave-build
# 基于我们 build 生成的 enclave-wallet 生成 EIF 文件
./generate_eif.sh
|
# 根据已生成的 eif 文件,并将其封装成 Image,方便我们通过 EKS 加载此镜像并运行在 enclave 支持的 EKS 集群中
docker build -t enclave-w .
# 默认情况下 AL2 使用的是 aws-cli 1.x 的版本,我们需要安装 aws-cli 2.x 的版本
sudo yum erase aws-cli
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
sudo ln /usr/local/bin/aws /usr/bin/aws
# 安装成功后执行发布动作,请注意 EC2 本身的权限要拥有
# 此处给的是 AmazonElasticContainerRegistryPublicFullAccess 权限
aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/s0k4s5w7
docker tag enclave-w:latest public.ecr.aws/s0k4s5w7/enclave-w:latest
docker push public.ecr.aws/s0k4s5w7/enclave-w:latest
三、应用部署
创建 Dynamo DB 的表 UserAccount 存储我们在 Enclave 中基于邮箱地址生成的公钥地址和 WIF 支持 Wallet 导入的私钥格式(注意 Partition key 是 userid 类型是 String)。
创建 Dynamo DB 的表 ConfigEnvironment 存储 Java 程序需要获取的 MSK 的地址,主要是中心化配置(注意 Partition key 是 key 类型是String)MSK_BOOTSTRAP_SERVERS 对应 MSK brokers 的地址。
1. 部署 Enclave
如上我们的准备工作已经完成,为了能够连接到 EKS 集群,我们需要安装 EKS 相关的软件。
# 设置要连接的集群更新当前配置
# 处理这步之前,我们需要设置该 EC2 拥有操作 EKS 集群的权限
aws eks --region eu-west-1 update-kubeconfig --name eks-module-test-cluster
curl -O https://s3.us-west-2.amazonaws.com/amazon-eks/1.22.17/2023-05-11/bin/linux/amd64/kubectl
chmod +x ./kubectl
mkdir -p $HOME/bin && cp ./kubectl $HOME/bin/kubectl && export PATH=$HOME/bin:$PATH
echo 'export PATH=$HOME/bin:$PATH' >> ~/.bash_profile
curl --silent --location https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz | tar xz -C /tmp
sudo mv /tmp/eksctl /usr/bin
# 如果当前的 EC2 没有权限可以访问集群,请使用集群的创建者执行
eksctl create iamidentitymapping \
--cluster eks-module-test-cluster \
--region eu-west-1 \
--arn YOUR_ROLE \
--group system:masters \
--no-duplicate-arns \
--username admin-user-blog
安装 Nitro Enclaves Kubernetes device plugin
Enclaves Kubernetes device plugin 插件使每个 worker 节点上的 pod 能够访问 Nitro Enclaves 设备驱动程序,该插件作为守护进程部署到 Kubernetes 集群。
查看集群内的节点,并为每个节点加上 enclaves enable 标签。
注意:当 eks 集群内新加入节点,需要对每个新节点添加一遍 enclaves enable 标签。
# 安装 Enclave Deployment
$ cd /home/ec2-user/spring-boot-nitro-enclaves/examples/simple-echo/simple-echo-enclave/enclave-build
$ kubectl apply -f deployment.yaml
# 我们可以根据 enclave 的程序所需的内存大小来控制,enclave 初始化的大小
# 接下来我们查看 enclave 是否启动正常启动
kubectl get all -A
# 如果发现 Pod 启动异常,我们通过查看 pod 的 log 来定位问题
kubectl logs -f pod/enclave-deployment-7cc6b77c88-n7728
届此,enclave 程序启动成功。
2. 部署 Consumer
接下来我们来部署 KafkaConsumer,这个 Consumer 在宿主机上运行,负责连接外部发来的通知,来消费,并且通知 enclave 对所需的用户创建私钥并且保存在 Dynamo DB 中。
# 安装 KafkaConsumer Deployment
$ cd /home/ec2-user/spring-boot-nitro-enclaves/examples/simple-echo/simple-echo-host
$ kubectl apply -f deployment.yaml
# 我们可以根据 enclave 的程序所需的内存大小来控制,enclave 初始化的大小
# 接下来我们查看 enclave 是否启动正常启动
kubectl get all
# 如果发现 Pod 启动异常,我们通过查看 pod 的 log 来定位问题
kubectl logs -f pod/enclave-kafka-consumer-deployment-559655647b-n652q
3. 安装 Kafka Client
我们查看一下 Enclave 的日志输出:
kubectl logs -f pod/enclave-deployment-7cc6b77c88-n7728
这里我们看到:Private Key,Private Key WIF 和 Public Address 均在控制台输出(输出只是为了测试),Dynamo DB 中存着相同的数据。
4. 环境清理
完成以上部署及功能验证后,为避免持续产生费用,请按照以下步骤停止应用或者删除所有已部署的组件:
# 删除 pod
$ kubectl get pods
$ kubectl delete pod <your-pod-name>
# 清理未完成的部署
$ kubectl delete deployment appclient
# 删除连接 EKS 的跳板机
# 删除 EKS 集群
# 删除 Enclave 的 Launch Template
5. 问题总结
如果遇到 Enclave 连接 Dynamo DB 出现异常,请登录 EKS Node 节点,手动启动 vsock-proxy,我们也可以将此进程写在 user-data 的启动脚本中。
vsock-proxy 8000 kms.eu-west-1.amazonaws.com 443 &
vsock-proxy 8001 dynamodb.eu-west-1.amazonaws.com 443 &
四、总结
本文主要介绍和演示了基于 Nitro Enclaves 实现的钱包应用,并且将其完整的部署在 EKS Cluster 上,不仅提供了一套完整的基于 Java Spring Boot 解决方案,并且将其整个流程通过具体的业务贯穿。
本篇作者