签名容器映像 Codelab

1. 概览

此 Codelab 以机密空间 Codelab 为基础。支持签名容器映像,可以选择使用经过认证的公钥对容器进行身份验证,而不是在工作负载身份池 (WIP) 政策中指定映像摘要。

机密 Space 支持签名容器映像的变化如下

提高易用性:随着已签名的容器映像功能的推出,我们现在可以将工作负载映像摘要方法转为容器签名方法,以供对映像授权的协作者/审核人员使用。

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

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

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

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

学习内容

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

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

此 Codelab 介绍了如何使用机密空间远程证明由在 Google Compute Engine 上运行的可信密钥签名的容器映像。

所需条件

带有签名容器映像的机密空间中涉及的角色

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

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

Secundus Bank 是工作负载的编写和运营者,并负责:

  1. 设置所需的资源以存储结果。
  2. 编写工作负载代码。
  3. 发布工作负载映像。
  4. 在支持签名容器映像的机密空间中运行工作负载。

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

机密聊天室设置中涉及的资源

此 Codelab 引用了一些变量,您应该为 GCP 项目设置适当的值。此 Codelab 中的命令假定已设置这些变量。(例如,export PRIMUS_INPUT_STORAGE_BUCKET='my-input-bucket' 可用于设置 Primus bank 的输入存储分区的名称。)如果尚未设置资源名称变量,系统将根据 GCP project-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 映像的 Artifact Registry。
  • $WORKLOAD_IMAGE_NAME:工作负载 Docker 映像的名称。
  • $WORKLOAD_IMAGE_TAG:工作负载 Docker 映像的标记。
  • $WORKLOAD_SERVICEACCOUNT:有权访问运行工作负载的机密虚拟机的服务账号。
  • $SECUNDUS_RESULT_BUCKET:用于存储工作负载结果的存储分区。

其他资源:

  • primus_customer_list.csv 包含客户数据。我们会将这些数据上传到 $PRIMUS_INPUT_STORAGE_BUCKET,并创建用于查询这些数据的工作负载。

现有工作流

在机密空间中运行工作负载时,系统会使用配置的资源完成以下过程:

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

支持签名容器的新工作流

对已签名容器的支持将集成到现有工作流中,如下突出显示。当您通过签名容器映像支持在机密空间中运行工作负载时,系统将使用配置的资源完成以下过程:

  1. 机密空间会发现与当前运行的工作负载映像相关的任何容器签名,并将这些签名发送到证明验证程序。认证验证程序会验证签名,并在认证声明中包含所有有效签名。
  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 资源

在设置机密空间时,首先您需要在 Primus 和 Secundus 银行的 GCP 项目下创建所需的云资源。以下是此 Codelab 的新增资源:

在 Primus 项目中:

  • KMS 签名密钥,用于在审核代码后为 Secundus 工作负载签名。
  • 用于存储 Cosign 签名的 Artifact Registry 仓库。

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

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

准备工作

  • 使用以下命令克隆 此代码库,以获取此 Codelab 中使用的必需脚本。
$ git clone https://github.com/GoogleCloudPlatform/confidential-space
  • 确保您已按如下所示设置所需的项目。
$ 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
  • 按照此处的说明安装协同签名。

设置 Primus 银行资源

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

  • Cloud Storage 存储分区 ($PRIMUS_INPUT_STORAGE_BUCKET),用于存储 Primus 银行的加密客户数据文件。
  • 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 银行设置资源。在此步骤中,系统会创建下述资源:

  • Cloud Storage 存储分区 ($SECUNDUS_RESULT_STORAGE_BUCKET),用于存储 Secundus 银行的工作负载执行结果。
$ ./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)。
  • 向“$WORKLOAD_SERVICEACCOUNT”授予“$SECUNDUS_ARTIFACT_REGISTRY”的读取权限。要让工作负载容器从 Artifact Registry 中拉取工作负载 Docker 映像,必须执行此操作。
$ ./create_workload.sh

为工作负载签名

我们将使用 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=us
  1. 在 KMS 下创建密钥环和密钥,以便为工作负载映像签名。
$ gcloud config set project $PRIMUS_PROJECT_ID

$ gcloud kms keyrings create $PRIMUS_SIGNING_KEYRING \
  --location=global

$ gcloud kms keys create $PRIMUS_SIGNING_KEY \
  --keyring=$PRIMUS_SIGNING_KEYRING \
  --purpose=asymmetric-signing \
  --default-algorithm=ec-sign-p256-sha256
  --location=us
  1. 对于 Artifact Registry,需要使用完整的映像名称,例如 $LOCATION/$PROJECT/$REPOSITORY/$IMAGE_NAME。您可以将任何容器映像上传到存储库以进行签名存储。
$ export COSIGN_REPOSITORY=us-docker.pkg.dev/${PRIMUS_PROJECT_ID}/${PRIMUS_COSIGN_REPOSITORY}/demo
  1. $PRIMUS_COSIGN_REPOSITORY 代码库的 Viewer 角色授予 $WORKLOAD_SERVICEACCOUNT 服务账号。这样一来,机密空间便可发现上传到 $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 使用密钥对签名。此已签名的容器映像功能不支持协同签名无密钥签名。

使用密钥对签名时,有两个选项:

  1. 使用 Cosign 生成的本地密钥对签名。
  2. 使用存储在其他位置(例如 KMS 中)的密钥对签名。
  1. 如果您没有密钥对,请在 Cosign 中生成一个。如需了解详情,请参阅使用自行管理的密钥签名
// 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

  • &lt;provider&gt;:是指您正在使用的 KMS 解决方案
  • &lt;key&gt;: 指 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 [必需] 指定附加到签名负载的注释。对于 Encrypt Space 签名的容器映像,需要将公钥和签名算法附加到签名载荷。
  • dev.cosignproject.cosign/sigalg ONLY 接受三个值:
  • RSASSA_PSS_SHA256:使用 SHA256 摘要进行 PSS 填充的 RSASSA 算法。
  • RSASSA_PKCS1V15_SHA256:使用 SHA256 摘要通过 PKCS#1 v1.5 填充的 RSASSA 算法。
  • ECDSA_P256_SHA256:具有 SHA256 摘要的 P-256 曲线上的 ECDSA。这也是协同签名生成的密钥对的默认签名算法。
  1. 将签名上传到 Docker 代码库

协同签名会自动将签名上传到指定的 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 config set project $SECUNDUS_PROJECT_ID

$ gcloud compute instances create signed-container-vm \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --maintenance-policy=TERMINATE \
 --scopes=cloud-platform --zone=us-west1-b \
 --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 文件中列出了来自西雅图的人数!

5. 清理

此处是一个脚本,可用于清理我们在此 Codelab 中创建的资源。在此次清理过程中,系统将删除以下资源:

  • Primus 银行的输入存储分区 ($PRIMUS_INPUT_STORAGE_BUCKET)。
  • Primus 银行服务账号 ($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) 的 Artifact Registry。
// run the clean up script to delete the resources created as part of this codelab.
$ ./cleanup.sh

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

  • 转到 Cloud Platform Console
  • 选择要关停的项目,然后点击“删除”顶部:这会安排删除项目

恭喜

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

您学习了如何利用已签名的容器映像功能来提高机密空间的易用性。

后续操作

查看一些类似的 Codelab...

深入阅读