签名容器映像 Codelab

1. 概览

本 Codelab 基于 Confidential Space Codelab 构建而成。支持已签名的容器映像,并提供以下选项:使用经过证明的公钥对容器进行身份验证,而不是在工作负载身份池 (WIP) 政策中指定映像摘要。

Confidential Space 中对已签名容器映像的支持有何变化

改进了易用性:随着已签名容器映像功能的推出,我们现在可以从工作负载映像摘要方法转为容器签名方法,以便协作者/审核员授权映像。

  • 如果直接使用映像摘要,资源所有者每次授权新映像时都必须使用映像摘要更新其政策。通过使用映像签名,政策包含一个公钥指纹,其对应的私钥归协作者/审核员所有,用于对审核的映像进行签名。
  • 对于某些安全模型,引用可信的映像签名密钥比更新新映像摘要值列表更方便。

无安全性回归:与之前的映像摘要方法相比,此容器签名方法不会带来任何安全性回归,因为信任边界保持不变。在容器签名方法中,资源所有者通过在 WIP 政策中指定可信的公钥指纹来授权验证密钥,并且授权检查由证明验证器服务和 WIP 执行;证明验证器服务验证签名是否与正在运行的工作负载相关联,并且 WIP 政策检查服务断言的公钥是否已获得政策授权。

安全性高:使用容器映像签名可将一定程度的信任委托给映像签名者。通过在证明政策中指定可信签名者的公钥指纹,资源所有者可授权该签名者对符合政策的容器映像进行认可。证明验证器服务会验证签名是否与正在运行的工作负载相关联,并且政策会检查创建签名的公钥是否已获得政策授权。通过这种方式,映像签名提供的间接层可保持 Confidential Space 的强大安全保证。

这两种方法的唯一区别在于,后一种方法使用额外的间接层,通过签名密钥授权工作负载映像。这不会引入任何新的安全漏洞,因为信任边界保持不变。

学习内容

在此 Codelab 中,您将学习如何利用容器映像签名来授权对受保护资源的访问权限:

  • 如何使用 cosign 对经过审核的容器映像进行签名
  • 如何将容器映像签名上传到 OCI 注册表以进行签名发现和存储
  • 如何配置运行 Confidential Space 所需的云资源
  • 如何在支持已签名容器映像的 Confidential Space 中运行工作负载

此 Codelab 演示了如何使用 Confidential Space 对在 Google Compute Engine 上运行的由可信密钥签名的容器映像进行远程证明。

所需条件

涉及签名容器映像的 Confidential Space 中的角色

在此 Codelab 中,Primus Bank 将作为审核员和资源所有者,负责以下事项:

  1. 使用示例数据设置所需资源。
  2. 审核工作负载代码。
  3. 使用 cosign 对工作负载映像进行签名。
  4. 将签名上传到代码库。
  5. 配置 WIP 政策以保护客户数据。

Secundus Bank 将是工作负载的创建者和运维人员,负责:

  1. 设置存储结果所需的资源。
  2. 编写工作负载代码。
  3. 发布工作负载映像。
  4. 在 Confidential Space 中运行工作负载,并支持已签名的容器映像。

Secundus Bank 将开发并发布一个工作负载,用于查询存储在 Cloud Storage 存储分区中且归 Primus Bank 所有的客户数据。Primus Bank 将审核工作负载、签署容器映像,并配置 WIP 政策以允许经过批准的工作负载访问其数据。相应工作负载的执行结果将存储在 Secundus 银行拥有的 Cloud Storage 存储分区中。

Confidential Space 设置中涉及的资源

此 Codelab 引用了许多变量,您应为这些变量设置适合您 GCP 项目的值。此 Codelab 中的命令假定这些变量已设置。(例如,export PRIMUS_INPUT_STORAGE_BUCKET='my-input-bucket' 可用于设置 Primus 银行的输入存储分区的名称。)如果资源名称的变量尚未设置,则会根据 GCP 项目 ID 生成。

在 Primus 项目中配置以下内容:

  • $PRIMUS_INPUT_STORAGE_BUCKET:用于存储客户数据文件的存储分区。
  • $PRIMUS_WORKLOAD_IDENTITY_POOL:验证声明的工作负载身份池 (WIP)。
  • $PRIMUS_WIP_PROVIDER:工作负载身份池提供方,其中包含用于由证明验证器服务签名的令牌的授权条件。
  • $PRIMUS_SERVICEACCOUNT$PRIMUS_WORKLOAD_IDENTITY_POOL 用于访问受保护资源的服务账号。在此步骤中,它有权查看存储在 $PRIMUS_INPUT_STORAGE_BUCKET 存储分区中的客户数据。
  • $PRIMUS_ENC_KEY:用于加密存储在 $PRIMUS_INPUT_STORAGE_BUCKET 中的数据的 KMS 密钥。

此 Codelab 新增的资源:

  • $PRIMUS_COSIGN_REPOSITORY:用于存储工作负载映像签名的 Artifact Registry。
  • $PRIMUS_SIGNING_KEY:审核员/数据协作者(在本例中为 Primus Bank)用于对工作负载映像进行签名的 KMS 密钥。

在 Secundus 项目中配置以下内容:

  • $SECUNDUS_ARTIFACT_REGISTRY:将推送工作负载 Docker 映像的制品注册表。
  • $WORKLOAD_IMAGE_NAME:工作负载 Docker 映像的名称。
  • $WORKLOAD_IMAGE_TAG:工作负载 Docker 映像的标记。
  • $WORKLOAD_SERVICEACCOUNT:有权访问运行工作负载的保密虚拟机的服务账号。
  • $SECUNDUS_RESULT_BUCKET:用于存储工作负载结果的存储分区。

其他资源:

  • primus_customer_list.csv 包含客户数据。我们将此数据上传到 $PRIMUS_INPUT_STORAGE_BUCKET,并创建一个查询此数据的工作负载。

现有工作流程

在 Confidential Space 中运行工作负载时,系统会使用配置的资源执行以下流程:

  1. 工作负载从 WIP 请求 $PRIMUS_SERVICEACCOUNT 的一般 Google 访问令牌。它提供包含工作负载和环境声明的证明验证器服务令牌。
  2. 如果证明验证器服务令牌中的工作负载衡量声明与 WIP 中的属性条件相匹配,则返回 $PRIMUS_SERVICEACCOUNT. 的访问令牌
  3. 工作负载使用与 $PRIMUS_SERVICEACCOUNT 关联的服务账号访问令牌来访问 $PRIMUS_INPUT_STORAGE_BUCKET 存储分区中的客户数据。
  4. 工作负载对该数据执行操作。
  5. 工作负载使用 $WORKLOAD_SERVICEACCOUNT 服务账号将相应操作的结果写入 $SECUNDUS_RESULT_STORAGE_BUCKET 存储分区。

支持已签名容器的新工作流

签名容器支持将集成到现有工作流程中,如下所示。在 Confidential Space 中运行支持已签名容器映像的工作负载时,系统会使用配置的资源执行以下流程:

  1. Confidential Space 会发现与当前正在运行的工作负载映像相关的任何容器签名,并将这些签名发送给证明验证器。证明验证器会验证签名,并将证明声明中的所有有效签名都纳入其中。
  2. 工作负载从 WIP 请求 $PRIMUS_SERVICEACCOUNT 的一般 Google 访问令牌。它提供包含工作负载和环境声明的证明验证器服务令牌。
  3. 如果证明验证器服务令牌中的容器签名声明与 WIP 中的属性条件相匹配,则返回 $PRIMUS_SERVICEACCOUNT 的访问令牌。
  4. 工作负载使用与 $PRIMUS_SERVICEACCOUNT 关联的服务账号访问令牌来访问 $PRIMUS_INPUT_STORAGE_BUCKET 存储分区中的客户数据。
  5. 工作负载对该数据执行操作。
  6. 工作负载使用 $WORKLOAD_SERVICEACCOUNT 将相应操作的结果写入 $SECUNDUS_RESULT_STORAGE_BUCKET 存储分区。

2. 设置 Cloud 资源

在设置 Confidential Space 的过程中,您首先需要在 Primus 和 Secundus 银行的 GCP 项目下创建所需的云资源。以下是本 Codelab 中的新资源:

在 Primus 项目中:

  • 用于在审核代码后为 Secundus 工作负载签名的 KMS 签名密钥。
  • 用于存储 Cosign 签名的制品注册库。

Secundus 项目中没有新资源。设置好这些资源后,您将为工作负载创建一个具有所需角色和权限的服务账号。然后,您将创建一个工作负载映像,而审计方 Primus 银行将对该工作负载映像进行签名。然后,数据协作者(在此 Codelab 中为 Primus 银行)将授权工作负载,工作负载运营商(在此 Codelab 中为 Secundus 银行)将运行工作负载。

在设置 Confidential Space 的过程中,您将在 Primus 和 Secundus GCP 项目中创建所需的云资源。

准备工作

  • 使用以下命令克隆 此代码库,以获取此 Codelab 中使用的必需脚本。
git clone https://github.com/GoogleCloudPlatform/confidential-space
  • 更改此 Codelab 的目录。
cd confidential-space/codelabs/signed_container_codelab/scripts
  • 确保您已设置所需的项目,如下所示。
export PRIMUS_PROJECT_ID=<GCP project id of primus bank>
export SECUNDUS_PROJECT_ID=<GCP project id of secundus bank>
  • 使用此命令设置上述资源名称的变量。您可以使用这些变量(例如 export PRIMUS_INPUT_STORAGE_BUCKET='my-input-bucket')替换资源名称
  • 运行以下脚本,根据您的项目 ID 为资源名称设置剩余的变量名称值。
source config_env.sh
  • 按照此处的说明安装 cosign。

设置 Primus 银行资源

在此步骤中,您将为 Primus 银行设置所需的云资源。运行以下脚本,为 Primus 银行设置资源。在执行这些步骤的过程中,系统将创建以下资源:

  • 用于存储 Primus 银行的加密客户数据文件的 Cloud Storage 存储分区 ($PRIMUS_INPUT_STORAGE_BUCKET)。
  • KMS 中的加密密钥 ($PRIMUS_ENC_KEY) 和密钥环 ($PRIMUS_ENC_KEYRING),用于加密 Primus 银行的数据文件。
  • 工作负载身份池 ($PRIMUS_WORKLOAD_IDENTITY_POOL),用于根据其提供方下配置的属性条件验证声明。
  • 附加到上述工作负载身份池 ($PRIMUS_WORKLOAD_IDENTITY_POOL) 的服务账号 ($PRIMUS_SERVICEACCOUNT),具有以下 IAM 访问权限:
  • roles/cloudkms.cryptoKeyDecrypter 使用 KMS 密钥解密数据。
  • objectViewer 以从 Cloud Storage 存储分区读取数据。
  • roles/iam.workloadIdentityUser,用于将此服务账号连接到工作负载身份池。
./setup_primus_bank_resources.sh

设置 Secundus 银行资源

在此步骤中,您将为 Secundus 银行设置所需的云资源。运行以下 脚本,为 Secundus 银行设置资源。在此步骤中,您将创建以下资源:

  • 用于存储 Secundus 银行执行工作负载的结果的 Cloud Storage 存储分区 ($SECUNDUS_RESULT_STORAGE_BUCKET)。
./setup_secundus_bank_resources.sh

3. 创建并签署工作负载

创建工作负载服务账号

现在,您将为工作负载创建一个具有所需角色和权限的服务账号。运行以下脚本,在 Secundus 银行项目中创建工作负载服务账号。此服务账号将由运行工作负载的虚拟机使用。

  • 此工作负载服务账号 ($WORKLOAD_SERVICEACCOUNT) 将具有以下角色:
  • confidentialcomputing.workloadUser 以获取证明令牌
  • logging.logWriter 以将日志写入 Cloud Logging。
  • objectViewer 以从 $PRIMUS_INPUT_STORAGE_BUCKET Cloud Storage 存储分区读取数据。
  • objectAdmin 将工作负载结果写入 $SECUNDUS_RESULT_STORAGE_BUCKET Cloud Storage 存储分区。
./create_workload_serviceaccount.sh

创建工作负载

在此步骤中,您将创建工作负载 Docker 映像。此 Codelab 中使用的工作负载是一个简单的基于 CLI 的 Go 应用,用于统计实参中提供的地理位置的客户(来自 Primus 银行客户数据)。运行以下脚本以创建工作负载,其中执行以下步骤:

  • 创建由 Secundus 银行拥有的 Artifact Registry($SECUNDUS_ARTIFACT_REGISTRY)。
  • 使用所需的资源名称更新工作负载代码。此处是本 Codelab 中使用的工作负载代码。
  • 构建 Go 二进制文件并创建 Dockerfile 以构建工作负载代码的 Docker 映像。此处是此 Codelab 使用的 Dockerfile。
  • 构建 Docker 映像并将其发布到 Secundus 银行拥有的 Artifact Registry ($SECUNDUS_ARTIFACT_REGISTRY)。
  • $SECUNDUS_ARTIFACT_REGISTRY 授予 $WORKLOAD_SERVICEACCOUNT 读取权限。这是工作负载容器从 Artifact Registry 中拉取工作负载 Docker 映像所必需的。
./create_workload.sh

对工作负载进行签名

我们将使用 Cosign 对工作负载映像进行签名。Cosign 默认会将签名存储在与所签名映像相同的代码库中。如需为签名指定其他代码库,您可以设置 COSIGN_REPOSITORY 环境变量。

在此处,我们以 Artifact Registry 为例。您还可以根据自己的偏好选择其他 OCI 兼容的注册表,例如 Docker Hub、AWS CodeArtifact。

  1. 创建 Artifact Registry Docker 代码库。
gcloud config set project $PRIMUS_PROJECT_ID
gcloud artifacts repositories create $PRIMUS_COSIGN_REPOSITORY \
  --repository-format=docker --location=${PRIMUS_PROJECT_REPOSITORY_REGION}
  1. 在 KMS 下创建密钥环和密钥,用于对工作负载映像进行签名。
gcloud config set project $PRIMUS_PROJECT_ID
gcloud kms keyrings create $PRIMUS_SIGNING_KEYRING \
  --location=${PRIMUS_PROJECT_LOCATION}
gcloud kms keys create $PRIMUS_SIGNING_KEY \
  --keyring=$PRIMUS_SIGNING_KEYRING \
  --purpose=asymmetric-signing \
  --default-algorithm=ec-sign-p256-sha256 \
  --location=${PRIMUS_PROJECT_LOCATION}
  1. 对于 Artifact Registry,系统会要求提供完整的映像名称,例如 $LOCATION/$PROJECT/$REPOSITORY/$IMAGE_NAME。您可以将任何容器映像上传到代码库以存储签名。
export COSIGN_REPOSITORY=us-docker.pkg.dev/${PRIMUS_PROJECT_ID}/${PRIMUS_COSIGN_REPOSITORY}/demo
  1. $WORKLOAD_SERVICEACCOUNT 服务账号授予 $PRIMUS_COSIGN_REPOSITORY 代码库的查看者角色。这样,Confidential Space 就可以发现上传到 $PRIMUS_COSIGN_REPOSITORY 的任何容器映像签名。
gcloud artifacts repositories add-iam-policy-binding ${PRIMUS_COSIGN_REPOSITORY} \
--project=${PRIMUS_PROJECT_ID} --role='roles/viewer' --location=us \
--member="serviceAccount:${WORKLOAD_SERVICEACCOUNT}@${SECUNDUS_PROJECT_ID}.iam.gserviceaccount.com"

Cosign 是一款功能强大的工具,具有多种签名功能。在我们的使用情形中,我们只需要 Cosign 使用密钥对进行签名。此已签名容器映像功能不支持 Cosign 无密钥签名。

使用密钥对进行签名时,有两种选择:

  1. 使用 Cosign 生成的本地密钥对进行签名。
  2. 使用存储在其他位置(例如 KMS 中)的密钥对进行签名。
  1. 在 Cosign 中生成密钥对(如果您还没有密钥对)。如需了解详情,请参阅使用自行管理的密钥进行签名。在此示例中,我们指定了生成密钥对并对工作负载进行签名的两种方式(本地和使用 KMS 提供程序),请按照其中一种方式对工作负载容器进行签名。
// Set Application Default Credentials.
gcloud auth application-default login 
// Generate keys using a KMS provider.
cosign generate-key-pair --kms <provider>://<key>
// Generate keys using Cosign.
cosign generate-key-pair

在上述内容中,将 <provider>://<key> 替换为 gcpkms://projects/$PRIMUS_PROJECT_ID/locations/global/keyRings/$PRIMUS_SIGNING_KEYRING/cryptoKeys/$PRIMUS_SIGNING_KEY/cryptoKeyVersions/$PRIMUS_SIGNING_KEYVERSION

  • <provider>:指您使用的 KMS 解决方案
  • <key>:指 KMS 中的密钥路径
  1. 检索用于验证的公钥。
// For KMS providers.
cosign public-key --key <some provider>://<some key> > pub.pem

// For local key pair signing.
cosign public-key --key cosign.key > pub.pem
  1. 使用 Cosign 对工作负载进行签名。对公钥执行无填充的 base64 编码
PUB=$(cat pub.pem | openssl base64)
// Remove spaces and trailing "=" signs.
PUB=$(echo $PUB | tr -d '[:space:]' | sed 's/[=]*$//')
  1. 使用导出的公钥和附加的签名算法通过 Cosign 为工作负载签名。
IMAGE_REFERENCE=us-docker.pkg.dev/$SECUNDUS_PROJECT_ID/$SECUNDUS_ARTIFACT_REPOSITORY/$WORKLOAD_IMAGE_NAME:$WORKLOAD_IMAGE_TAG
// Sign with KMS support.
cosign sign --key <some provider>://<some key> $IMAGE_REFERENCE \
-a dev.cosignproject.cosign/sigalg=ECDSA_P256_SHA256 \
-a dev.cosignproject.cosign/pub=$PUB
// Sign with a local key pair.
cosign sign --key cosign.key $IMAGE_REFERENCE \
-a dev.cosignproject.cosign/sigalg=ECDSA_P256_SHA256 \
-a dev.cosignproject.cosign/pub=$PUB
  • --key [必需] 指定要使用的签名密钥。在引用由 KMS 提供方管理的密钥时,请遵循 Sigstore KMS 支持中的特定 URI 格式。在引用由 Cosign 生成的密钥时,请改用 cosign.key。
  • $IMAGE_REFERENCE [必需] 指定要签名的容器映像。IMAGE_REFERENCE 的格式可以通过标记或映像摘要来识别。例如:us-docker.pkg.dev/$SECUNDUS_PROJECT_ID/secundus-workloads/workload-container:latest or us-docker.pkg.dev/$SECUNDUS_PROJECT_ID/secundus-workloads/workload-container[IMAGE-digest]
  • -a [必需] 指定附加到签名载荷的注释。对于 Confidential Space 签名容器映像,必须将公钥和签名算法附加到签名载荷。
  • dev.cosignproject.cosign/sigalg 接受以下三个值:
  • RSASSA_PSS_SHA256:采用 PSS 填充的 RSASSA 算法,具有 SHA256 摘要。
  • RSASSA_PKCS1V15_SHA256:采用 PKCS#1 v1.5 填充和 SHA256 摘要的 RSASSA 算法。
  • ECDSA_P256_SHA256:具有 SHA256 摘要的 P-256 曲线上的 ECDSA。这也是 Cosign 生成的密钥对的默认签名算法。
  1. 将签名上传到 Docker 代码库

Cosign 签名会自动将签名上传到指定的 COSIGN_REPOSITORY.

4. 授权并运行工作负载

授权工作负载

在此步骤中,我们将在工作负载身份池 ($PRIMUS_WORKLOAD_IDENTITY_POOL) 下设置工作负载身份提供方。系统会为工作负载身份配置属性条件,如下所示。其中一个条件是根据签名公钥的指纹验证工作负载映像签名的指纹。借助此属性条件,当 Secundus Bank 发布新的工作负载映像时,Primus Bank 会审核工作负载代码并签署新的工作负载映像,而无需使用映像摘要更新 WIP 政策。

gcloud config set project $PRIMUS_PROJECT_ID
PUBLIC_KEY_FINGERPRINT=$(openssl pkey -pubin -in pub.pem -outform DER | openssl sha256 | cut -d' ' -f2)
gcloud iam workload-identity-pools providers create-oidc ${PRIMUS_WIP_PROVIDER} \
   --location="global" \
   --workload-identity-pool="${PRIMUS_WORKLOAD_IDENTITY_POOL}" \
   --issuer-uri="https://confidentialcomputing.googleapis.com/" \
   --allowed-audiences="https://sts.googleapis.com" \
   --attribute-mapping="google.subject='assertion.sub'" \
   --attribute-condition="assertion.swname == 'CONFIDENTIAL_SPACE' &&
  'STABLE' in assertion.submods.confidential_space.support_attributes
     && '${WORKLOAD_SERVICEACCOUNT}@${SECUNDUS_PROJECT_ID}.iam.gserviceaccount.com' in
     assertion.google_service_accounts
     && ['ECDSA_P256_SHA256:${PUBLIC_KEY_FINGERPRINT}']
       .exists(fingerprint, fingerprint in assertion.submods.container.image_signatures.map(sig,sig.signature_algorithm+':'+sig.key_id))"

运行工作负载

在此步骤中,我们将在机密虚拟机上运行工作负载。必需的 TEE 实参通过元数据标志传递。工作负载容器的实参通过标志的“tee-cmd”部分传递。工作负载已编码为将其结果发布到 $SECUNDUS_RESULT_STORAGE_BUCKET

gcloud compute instances create ${WORKLOAD_VM} \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --maintenance-policy=MIGRATE \
 --scopes=cloud-platform \
 --zone=${SECUNDUS_PROJECT_ZONE} \
 --project=${SECUNDUS_PROJECT_ID} \
 --image-project=confidential-space-images \
 --image-family=confidential-space \
 --service-account=${WORKLOAD_SERVICEACCOUNT}@${SECUNDUS_PROJECT_ID}.iam.gserviceaccount.com \
 --metadata "^~^tee-image-reference=us-docker.pkg.dev/${SECUNDUS_PROJECT_ID}/${SECUNDUS_ARTIFACT_REPOSITORY}/${WORKLOAD_IMAGE_NAME}:${WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-cmd="[\"count-location\",\"Seattle\",\"gs://${SECUNDUS_RESULT_STORAGE_BUCKET}/seattle-result\"]"~tee-signed-image-repos=us-docker.pkg.dev/${PRIMUS_PROJECT_ID}/${PRIMUS_COSIGN_REPOSITORY}/demo"

查看结果

在 Secundus 项目中,查看工作负载的结果。

gcloud config set project $SECUNDUS_PROJECT_ID
gsutil cat gs://$SECUNDUS_RESULT_STORAGE_BUCKET/seattle-result

结果应为 3,因为 primus_customer_list.csv 文件中列出了 3 位来自西雅图的人员!

5. 清理

点击此处可查看用于清理我们在本 Codelab 中创建的资源的脚本。在此清理过程中,系统将删除以下资源:

  • Primus Bank 的输入存储分区 ($PRIMUS_INPUT_STORAGE_BUCKET)。
  • Primus bank service-account ($PRIMUS_SERVICEACCOUNT)。
  • Primus Bank 制品注册表,用于保存映像签名 ($PRIMUS_COSIGN_REPOSITORY)。
  • Primus Bank 工作负载身份池 ($PRIMUS_WORKLOAD_IDENTITY_POOL)。
  • Secundus Bank 的工作负载服务账号 ($WORKLOAD_SERVICEACCOUNT)。
  • 工作负载计算实例。
  • Secundus Bank 的结果存储分区 ($SECUNDUS_RESULT_STORAGE_BUCKET)。
  • Secundus Bank 的制品注册表 ($SECUNDUS_ARTIFACT_REGISTRY)。
  • Secundus Bank 的工作负载虚拟机 ($WORKLOAD_VM)。
./cleanup.sh

如果您已完成探索,请考虑删除项目。

  • 前往 Cloud Platform 控制台
  • 选择要关停的项目,然后点击顶部的“删除”:系统会安排删除该项目

恭喜

恭喜,您已成功完成此 Codelab!

您已了解如何利用签名容器映像功能来提高 Confidential Space 的易用性。

后续操作

不妨查看以下类似的 Codelab…

深入阅读