亚马逊AWS官方博客

基于 Java Spring Boot Nitro Enclaves + AWS EKS 加密钱包应用

一、概述

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”。

  1. 点击创建集群
  2. 选择创建的方法自定义创建
  3. Cluster 的名字,可以根据的记得需求修改,本文 msk-cluster
  4. Cluster 的类型,选择预制
  5. Cluster Kafka 的版本选择推荐
  6. Broker 的类型选择 t3.small
  7. 其他选项保持默认,配置选项,选择刚刚创建的 CustomConfiguration,并且选择 revision,点击下一步

Step 2,

网络以及安全组的设置,此处可以根据您所使用的网络环境进行配置,本文选择选择了 3 个公有子网,并且制定默认的安全组 Default,点击下一步。

Step 3,

安全的选择,本文开放了匿名的访问和 Plaintext 作为默认的加密方式,可以根据具体的业务需求选择不同的选项,并点击下一步。

Step 4,

此处保持默认即可,如果特殊的设置要求,请根据您的业务场景选择不同的选项,点击下一步。

最后一步我们对已设置的参数进行最后的审查,确定无误后点击创建按钮(15 mins 的等待时间)。

EKS 的创建

我们首先要创建 EKS 的 VPC 的网络环境,如果您已有,我们可以直接使用已有的网络环境,如果没有,请参见 https://docs.aws.amazon.com/eks/latest/userguide/network_reqs.html

网络架构图可以参考下面的结构:

我们可以用第三方的 Infra 的工具构建 EKS 的基础设施

git clone https://github.com/sunchaoqun/terraform-aws-eks-node-group

cd terraform-aws-eks-node-group/examples/single-node-group-with-launch-template

terraform init

terraform plan

terraform apply -y

如果有需要变动的机型,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

wget https://archive.apache.org/dist/kafka/2.8.1/kafka_2.12-2.8.1.tgz

tar -xzvf kafka_2.12-2.8.1.tgz

cd kafka_2.12-2.8.1/

./bin/kafka-console-producer.sh —broker-list \
"b-1.mskclusterbtc.6lz2fu.c5.kafka.eu-west-1.amazonaws.com:9092,b-2.mskclusterbtc.6lz2fu.c5.kafka.eu-west-1.amazonaws.com:9092,b-3.mskclusterbtc.6lz2fu.c5.kafka.eu-west-1.amazonaws.com:9092" \
—topic "YOUR_TOPIC"

# 输入 Json 消息来触发事件

{"email":"sunchaoqun@126.com","action":"create_xxx_address"}

我们查看一下 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 解决方案,并且将其整个流程通过具体的业务贯穿。

本篇作者

孙超群

AWS 解决方案架构师,负责基于 AWS 云计算方案的架构咨询,设计实现并以技术赋能助力企业成长,同时热衷于为客户设计和构建端到端的区块链解决方案。