使用机密空间保护机器学习模型和知识产权

1. 概览

Confidential Space 可为多方提供安全的协作环境。此 Codelab 演示了如何使用 Confidential Space 保护敏感知识产权,例如机器学习模型。

在此 Codelab 中,您将使用 Confidential Space 让一家公司能够安全地与另一家想要使用该模型的公司共享其专有机器学习模型。具体而言,公司 Primus 拥有一个机器学习模型,该模型只会发布到在 Confidential Space 中运行的工作负载,从而使 Primus 能够完全控制其知识产权。公司 Secundus 将成为工作负载操作员,并将在 Confidential Space 中运行机器学习工作负载。Secundus 将加载此模型,并使用 Secundus 拥有的示例数据运行推理。

在这里,Primus 是工作负载作者,负责编写工作负载代码,也是协作者,希望保护其知识产权免受不受信任的工作负载操作员 Secundus 的影响。Secundus 是机器学习工作负载的工作负载运算符。

5a86c47d935da998.jpeg

学习内容

  • 如何配置环境,让一方可以与另一方共享其专有机器学习模型,而不会失去对其知识产权的控制。

所需条件

Confidential Space 设置中涉及的角色

在此 Codelab 中,Company Primus 将是资源所有者和工作负载作者,负责以下事项:

  1. 使用机器学习模型设置所需的云资源
  2. 编写工作负载代码
  3. 发布工作负载映像
  4. 配置 Workload Identity 池政策以防范不可信的操作者攻击机器学习模型

Secundus 公司将成为运营商,并负责:

  1. 设置所需的云资源以存储工作负载使用的示例图片和结果
  2. 使用 Primus 提供的模型在 Confidential Space 中运行机器学习工作负载

Confidential Space 的运作方式

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

  1. 工作负载从工作负载身份池请求 $PRIMUS_SERVICEACCOUNT 的通用 Google 访问令牌。它提供包含工作负载和环境声明的 Attestation Verifier 服务令牌。
  2. 如果证明验证器服务令牌中的工作负载衡量声明与 WIP 中的属性条件匹配,则会返回 $PRIMUS_SERVICEACCOUNT. 的访问令牌
  3. 该工作负载使用与 $PRIMUS_SERVICEACCOUNT 关联的服务账号访问令牌来访问存储在 $PRIMUS_INPUT_STORAGE_BUCKET 存储分区中的机器学习模型。
  4. 该工作负载会对 Secundus 拥有的数据执行操作,并且该工作负载由 Secundus 在其项目中操作和运行。
  5. 工作负载使用 $WORKLOAD_SERVICEACCOUNT 服务账号将该操作的结果写入 $SECUNDUS_RESULT_STORAGE_BUCKET 存储分区。

2. 设置 Cloud 资源

准备工作

  • 使用以下命令克隆 此代码库,以获取此 Codelab 中所需的脚本。
git clone https://github.com/GoogleCloudPlatform/confidential-space.git
  • 更改此 Codelab 的目录。
cd confidential-space/codelabs/ml_model_protection/scripts
  • 确保您已设置所需的项目环境变量,如下所示。如需详细了解如何设置 GCP 项目,请参阅 此 Codelab。您可以参阅此处,详细了解如何检索项目 ID,以及项目 ID 与项目名称和项目编号的区别。
export PRIMUS_PROJECT_ID=<GCP project id of Primus>
export SECUNDUS_PROJECT_ID=<GCP project id of Secundus>
  • 为您的项目启用结算功能
  • 为这两个项目启用 Confidential Computing API 和以下 API。
gcloud services enable \
    cloudapis.googleapis.com \
    cloudresourcemanager.googleapis.com \
    cloudshell.googleapis.com \
    container.googleapis.com \
    containerregistry.googleapis.com \
    iam.googleapis.com \
    confidentialcomputing.googleapis.com
  • 使用以下命令为上述资源名称的变量分配值。借助这些变量,您可以根据需要自定义资源名称,还可以使用已创建的现有资源。(例如 export PRIMUS_INPUT_STORAGE_BUCKET='my-input-bucket'
  1. 您可以在 Primus 项目中使用现有云资源名称设置以下变量。如果设置了该变量,则系统会使用 Primus 项目中的相应现有云资源。如果未设置该变量,系统会根据 project-name 生成云资源名称,并使用该名称创建新的 cloud-resource。以下是资源名称支持的变量:

$PRIMUS_INPUT_STORAGE_BUCKET

用于存储 Primus 机器学习模型的存储分区。

$PRIMUS_WORKLOAD_IDENTITY_POOL

Primus 的工作负载身份池 (WIP),用于验证声明。

$PRIMUS_WIP_PROVIDER

Primus 的工作负载身份池提供程序,其中包含要用于由 Attestation Verifier 服务签名的令牌的授权条件。

$PRIMUS_SERVICE_ACCOUNT

Primus 服务账号,$PRIMUS_WORKLOAD_IDENTITY_POOL 使用该账号访问受保护的资源(此 Codelab 中的机器学习模型)。在此步骤中,它有权读取存储在 $PRIMUS_INPUT_STORAGE_BUCKET 存储分区中的机器学习模型。

$PRIMUS_ARTIFACT_REPOSITORY

将推送工作负载 Docker 映像的 artifact 代码库。

  1. 您可以使用 Secundus 项目中的现有云资源名称设置以下变量。如果设置了该变量,则系统会使用 Secundus 项目中的相应现有云资源。如果未设置该变量,系统会根据项目名称生成云资源名称,并使用该名称创建新的云资源。以下是资源名称支持的变量:

$SECUNDUS_INPUT_STORAGE_BUCKET

存储 Secundus 想要使用 Primus 提供的模型进行分类的示例图片的存储分区。

$SECUNDUS_RESULT_STORAGE_BUCKET

用于存储工作负载结果的存储分区。

$WORKLOAD_IMAGE_NAME

工作负载容器映像的名称。

$WORKLOAD_IMAGE_TAG

工作负载容器映像的标记。

$WORKLOAD_SERVICE_ACCOUNT

有权访问运行工作负载的 Confidential 虚拟机的服务账号。

  • 您需要对这两个项目拥有特定权限,可以参阅本指南,了解如何使用 GCP 控制台授予 IAM 角色:
  • 对于 $PRIMUS_PROJECT_ID,您需要具备 Storage Admin、Artifact Registry Administrator、Service Account Admin、IAM Workload Identity Pool Admin 角色。
  • 对于 $SECUNDUS_PROJECT_ID,您需要具备 Compute Admin、Storage Admin、Service Account Admin、IAM Workload Identity Pool Admin、Security Admin(可选)角色。
  • 运行以下脚本,根据资源名称的项目 ID 将其余变量名称设置为值。
source config_env.sh

设置 Primus 公司资源

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

  • 用于存储 Primus 机器学习模型的 Cloud Storage 存储分区 ($PRIMUS_INPUT_STORAGE_BUCKET)。
  • 工作负载身份池 ($PRIMUS_WORKLOAD_IDENTITY_POOL),用于根据其提供方下配置的属性条件验证声明。
  • 附加到上述工作负载身份池 ($PRIMUS_WORKLOAD_IDENTITY_POOL) 的服务账号 ($PRIMUS_SERVICEACCOUNT),具有从云端存储分区读取数据的 IAM 访问权限(使用 objectViewer 角色),以及将此服务账号连接到工作负载身份池的权限(使用 roles/iam.workloadIdentityUser 角色)。

在设置这些云资源的过程中,我们将使用 TensorFlow 模型。我们可以将包含模型架构、权重和训练配置的整个模型保存在 ZIP 归档文件中。在此 Codelab 中,我们将使用基于 此处提供的 ImageNet 数据集训练的 MobileNet V1 模型。

./setup_primus_company_resources.sh

上述脚本将设置云资源,我们现在将下载模型并将其发布到脚本创建的 Cloud Storage 存储分区。

  1. 点击此处下载预训练模型。
  2. 下载完成后,将下载的 tar 文件重命名为 model.tar.gz
  3. 在包含 model.tar.gz 文件的目录中,使用以下命令将 model.tar.gz 文件发布到 Cloud Storage 存储分区。
gsutil cp model.tar.gz gs://${PRIMUS_INPUT_STORAGE_BUCKET}/

设置 Secundus 公司资源

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

  • 用于存储示例图片的 Cloud Storage 存储分区 ($SECUNDUS_INPUT_STORAGE_BUCKET),以便 Secundus 运行推理。
  • Cloud Storage 存储分区 ($SECUNDUS_RESULT_STORAGE_BUCKET),用于存储 Secundus 执行机器学习工作负载的结果。

此 Codelab 提供了一些示例图片,您可以点击此处查看。

./setup_secundus_company_resources.sh

3. 创建工作负载

创建工作负载服务账号

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

此工作负载服务账号 ($WORKLOAD_SERVICEACCOUNT) 将具有以下角色:

  • confidentialcomputing.workloadUser 以获取认证令牌
  • logging.logWriter 将日志写入 Cloud Logging。
  • objectViewer 用于从 $SECUNDUS_INPUT_STORAGE_BUCKET Cloud Storage 存储分区读取数据。
  • objectUser 将工作负载结果写入 $SECUNDUS_RESULT_STORAGE_BUCKET Cloud Storage 存储分区。
./create_workload_service_account.sh

创建工作负载

在此步骤中,您将创建工作负载 Docker 映像。工作负载将由 Primus 编写。此 Codelab 中使用的作业是机器学习 Python 代码,用于访问存储在 Primus 存储分区中的机器学习模型,并对存储在存储分区中的示例图片运行推理。

只有满足所需属性条件的工作负载才能访问存储在 Primus 存储分区中的机器学习模型。下一部分将详细介绍这些属性条件。

下面是将在此 Codelab 中创建并使用的工作负载的 run_inference() 方法。您可以在此处找到完整的工作负载代码。

def run_inference(image_path, model):
  try:
    # Read and preprocess the image
    image = tf.image.decode_image(tf.io.read_file(image_path), channels=3)
    image = tf.image.resize(image, (128, 128))
    image = tf.image.convert_image_dtype(image, tf.float32)
    image = tf.expand_dims(image, axis=0)

    # Get predictions from the model
    predictions = model(image)
    predicted_class = np.argmax(predictions)

    top_k = 5
    top_indices = np.argsort(predictions[0])[-top_k:][::-1]

    # Convert top_indices to a TensorFlow tensor
    top_indices_tensor = tf.convert_to_tensor(top_indices, dtype=tf.int32)

    # Use TensorFlow tensor for indexing
    top_scores = tf.gather(predictions[0], top_indices_tensor)

    return {
        "predicted_class": int(predicted_class),
        "top_k_predictions": [
            {"class_index": int(idx), "score": float(score)}
            for idx, score in zip(top_indices, top_scores)
        ],
    }
  except Exception as e:
    return {"error": str(e)}

运行以下脚本,创建一个执行以下步骤的工作负载:

  • 创建 Primus 拥有的 Artifact Registry($PRIMUS_ARTIFACT_REGISTRY)。
  • 使用所需的资源名称更新工作负载代码。
  • 构建 ML 工作负载并创建 Dockerfile,以构建工作负载代码的 Docker 映像。此处提供了此 Codelab 使用的 Dockerfile。
  • 构建 Docker 映像并将其发布到 Primus 拥有的 Artifact Registry ($PRIMUS_ARTIFACT_REGISTRY)。
  • $WORKLOAD_SERVICEACCOUNT 授予对 $PRIMUS_ARTIFACT_REGISTRY 的读取权限。工作负载容器需要此权限才能从 Artifact Registry 拉取工作负载 Docker 映像。
./create_workload.sh

此外,您还可以对工作负载进行编码,以便在使用机器学习模型之前检查其哈希值或签名,确保其加载的是预期版本的机器学习模型。此类额外检查的优势在于,它可确保机器学习模型的完整性。这样一来,当工作负载预计要使用不同版本的机器学习模型时,工作负载运维者还需要更新工作负载映像或其参数。

4. 授权和运行工作负载

为工作负载授权

Primus 希望根据以下资源的属性授权工作负载访问其机器学习模型:

  • 什么:已通过验证的代码
  • 位置:安全的环境
  • Who:受信任的运营商

Primus 使用工作负载身份联合根据这些要求强制执行访问权限政策。借助工作负载身份联合,您可以指定属性条件。这些条件会限制哪些身份可以使用工作负载身份池 (WIP) 进行身份验证。您可以将 Attestation Verifier Service 作为工作负载身份池提供方添加到 WIP,以显示测量结果并强制执行政策。

工作负载身份池已在之前的云资源设置步骤中创建。现在,Primus 将创建新的 OIDC 工作负载身份池提供方。指定的 --attribute-condition 会授权访问工作负载容器。它需要:

  • 内容:上传到 $PRIMUS_ARTIFACT_REPOSITORY 代码库的最新 $WORKLOAD_IMAGE_NAME
  • 条件:Confidential Space 可信执行环境在完全受支持的 Confidential Space VM 映像上运行。
  • 操作者:Secundus $WORKLOAD_SERVICE_ACCOUNT 服务账号。
export WORKLOAD_IMAGE_DIGEST=$(gcloud artifacts docker images describe ${PRIMUS_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/$PRIMUS_PROJECT_ID/$PRIMUS_ARTIFACT_REPOSITORY/$WORKLOAD_IMAGE_NAME:$WORKLOAD_IMAGE_TAG  --format="value(image_summary.digest)" --project ${PRIMUS_PROJECT_ID})
gcloud config set project $PRIMUS_PROJECT_ID
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 && 
assertion.submods.container.image_digest == '${WORKLOAD_IMAGE_DIGEST}' &&
 assertion.submods.container.image_reference == '${PRIMUS_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/$PRIMUS_PROJECT_ID/$PRIMUS_ARTIFACT_REPOSITORY/$WORKLOAD_IMAGE_NAME:$WORKLOAD_IMAGE_TAG' && 
'$WORKLOAD_SERVICEACCOUNT@$SECUNDUS_PROJECT_ID.iam.gserviceaccount.com' in assertion.google_service_accounts"

运行工作负载

在此步骤中,我们将在 Confidential Space 虚拟机中运行工作负载。系统会使用元数据标志传递所需的 TEE 参数。工作负载容器的参数使用标志的“tee-cmd”部分传递。工作负载执行结果将发布到 $SECUNDUS_RESULT_STORAGE_BUCKET

gcloud compute instances create ${WORKLOAD_VM} \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --project=${SECUNDUS_PROJECT_ID} \
 --maintenance-policy=MIGRATE \
 --scopes=cloud-platform --zone=${SECUNDUS_PROJECT_ZONE} \
 --image-project=confidential-space-images \
 --image-family=confidential-space \
 --service-account=${WORKLOAD_SERVICEACCOUNT}@${SECUNDUS_PROJECT_ID}.iam.gserviceaccount.com \
 --metadata  ^~^tee-image-reference=${PRIMUS_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${PRIMUS_PROJECT_ID}/${PRIMUS_ARTIFACT_REPOSITORY}/${WORKLOAD_IMAGE_NAME}:${WORKLOAD_IMAGE_TAG}

查看结果

工作负载成功完成后,机器学习工作负载的结果将发布到 $SECUNDUS_RESULT_STORAGE_BUCKET

gsutil cat gs://$SECUNDUS_RESULT_STORAGE_BUCKET/result

以下是一些示例图片的推理结果:

Image: sample_image_1.jpeg, Response: {'predicted_class': 531, 'top_k_predictions': [{'class_index': 531, 'score': 12.08437442779541}, {'class_index': 812, 'score': 10.269512176513672}, {'class_index': 557, 'score': 9.202644348144531}, {'class_index': 782, 'score': 9.08737564086914}, {'class_index': 828, 'score': 8.912498474121094}]}

Image: sample_image_2.jpeg, Response: {'predicted_class': 905, 'top_k_predictions': [{'class_index': 905, 'score': 9.53619384765625}, {'class_index': 557, 'score': 7.928380966186523}, {'class_index': 783, 'score': 7.70129919052124}, {'class_index': 531, 'score': 7.611623287200928}, {'class_index': 906, 'score': 7.021416187286377}]}

Image: sample_image_3.jpeg, Response: {'predicted_class': 905, 'top_k_predictions': [{'class_index': 905, 'score': 6.09878396987915}, {'class_index': 447, 'score': 5.992854118347168}, {'class_index': 444, 'score': 5.9582319259643555}, {'class_index': 816, 'score': 5.502010345458984}, {'class_index': 796, 'score': 5.450454235076904}]}

对于 Secundus 存储分区中的每张示例图片,您都会在结果中看到一个条目。此条目将包含以下两条关键信息:

  • predicted_class 的索引:这是一个数值索引,表示模型预测图片所属的类别。
  • Top_k_predictions::此参数最多可为图片提供 k 个预测结果,并按可能性从高到低进行排名。在此 Codelab 中,k 的值设为 5,但您可以在工作负载代码中调整该值,以获得更多或更少的预测结果。

如需将类别索引转换为直观易懂的类别名称,请参阅此处提供的标签列表。例如,如果您看到类索引为 2,则它对应于标签列表中的类标签“鲶鱼”。

在此 Codelab 中,我们演示了 Primus 拥有的模型仅发布到在 TEE 中运行的工作负载。Secundus 在 TEE 中运行机器学习工作负载,此工作负载能够使用 Primus 拥有的模型,而 Primus 保留对该模型的完全控制权。

运行未经授权的工作负载

Secundus 通过从其自己的工件仓库中提取 Primus 未授权的其他工作负载映像来更改工作负载映像。Primus 的工作负载身份池仅授权了 ${PRIMUS_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/$PRIMUS_PROJECT_ID/$PRIMUS_ARTIFACT_REPOSITORY/$WORKLOAD_IMAGE_NAME:$WORKLOAD_IMAGE_TAG 工作负载映像。

重新运行工作负载

当 Secundus 尝试使用此新的工作负载映像运行原始工作负载时,将会失败。如需查看错误,请删除原始结果文件和虚拟机实例,然后尝试再次运行工作负载。

请确保在 Secundus 的 artifact registry(标记为 us-docker.pkg.dev/${SECUNDUS_PROJECT_ID}/custom-image/${WORKLOAD_IMAGE_NAME}:${WORKLOAD_IMAGE_TAG})下发布了新的 Docker 映像,并且工作负载服务账号 ($WORKLOAD_SERVICEACCOUNT) 已向 artifact registry 读取器授予读取此新工作负载映像的权限。这是为了确保在 Primus 的 WIP 政策拒绝工作负载提供的令牌之前,工作负载不会退出。

删除现有结果文件和虚拟机实例

  1. 将项目设置为 $SECUNDUS_PROJECT_ID 项目。
gcloud config set project $SECUNDUS_PROJECT_ID
  1. 删除结果文件。
gsutil rm gs://$SECUNDUS_RESULT_STORAGE_BUCKET/result
  1. 删除机密虚拟机实例。
gcloud compute instances delete ${WORKLOAD_VM} --zone=${SECUNDUS_PROJECT_ZONE}

运行未经授权的工作负载:

gcloud compute instances create ${WORKLOAD_VM} \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --maintenance-policy=MIGRATE \
 --scopes=cloud-platform --zone=${SECUNDUS_PROJECT_ZONE} \
 --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}/custom-image/${WORKLOAD_IMAGE_NAME}:${WORKLOAD_IMAGE_TAG}

查看错误

您会看到错误 (The given credential is rejected by the attribute condition),而不是工作负载的结果。

gsutil cat gs://$SECUNDUS_RESULT_STORAGE_BUCKET/result

5. 清理

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

  • Primus 的输入存储分区 ($PRIMUS_INPUT_STORAGE_BUCKET)
  • Primus 服务账号 ($PRIMUS_SERVICEACCOUNT)。
  • Primus ($PRIMUS_ARTIFACT_REPOSITORY) 的制品库。
  • Primus 工作负载身份池 ($PRIMUS_WORKLOAD_IDENTITY_POOL)。
  • Secundus 的工作负载服务账号 ($WORKLOAD_SERVICEACCOUNT)。
  • Secundus 的输入存储分区 ($SECUNDUS_INPUT_STORAGE_BUCKET)
  • 工作负载计算实例。
  • Secundus 的结果存储分区 ($SECUNDUS_RESULT_STORAGE_BUCKET)。
$ ./cleanup.sh

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

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

后续操作

查看下列类似 Codelab…