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

1. 概览

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

在此 Codelab 中,您将使用机密空间,让一家公司能够安全地与希望使用该模型的另一家公司共享其专有机器学习模型。具体而言,Company Primus 具有的机器学习模型仅发布到机密空间中运行的工作负载,这使得 Primus 能够保留对其知识产权的完全控制权。Company Secundus 将是工作负载运维人员,将在机密空间中运行机器学习工作负载。Secundus 将加载此模型,并使用 Secundus 拥有的样本数据进行推理。

在这里,Primus 既是工作负载作者,又是工作负载代码编写者,也是希望保护其知识产权不受不受信任的工作负载运营商 Secundus 的协作者。Secundus 是机器学习工作负载的工作负载运维人员。

5a86c47d935da998.jpeg

学习内容

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

所需条件

机密聊天室设置中涉及的角色

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

  1. 使用机器学习模型设置所需的云资源
  2. 编写工作负载代码
  3. 发布工作负载映像
  4. 配置工作负载身份池政策,以保护机器学习模型不受不受信任的运算符的影响

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

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

机密空间的运作方式

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

  1. 工作负载从 Workload Identity 池中为 $PRIMUS_SERVICEACCOUNT 请求常规 Google 访问令牌。它会提供带有工作负载和环境声明的证明验证程序服务令牌。
  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>
  • 为您的项目启用结算功能
  • 为这两个项目启用机密计算 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 项目中对应的现有云资源。如果未设置此变量,系统将根据项目名称生成云资源名称,并使用该名称创建新的云资源。以下是资源名称支持的变量:

$PRIMUS_INPUT_STORAGE_BUCKET

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

$PRIMUS_WORKLOAD_IDENTITY_POOL

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

$PRIMUS_WIP_PROVIDER

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

$PRIMUS_SERVICE_ACCOUNT

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

$PRIMUS_ARTIFACT_REPOSITORY

将向其推送工作负载 Docker 映像的工件代码库。

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

$SECUNDUS_INPUT_STORAGE_BUCKET

用于存储 Secundus 希望使用 Primus 提供的模型进行分类的样本图片的存储分区。

$SECUNDUS_RESULT_STORAGE_BUCKET

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

$WORKLOAD_IMAGE_NAME

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

$WORKLOAD_IMAGE_TAG

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

$WORKLOAD_SERVICE_ACCOUNT

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

  • 您需要对这两个项目拥有特定权限,您可以参阅此指南,了解如何使用 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 设置资源。系统会在脚本执行过程中创建以下资源:

  • Cloud Storage 存储分区 ($PRIMUS_INPUT_STORAGE_BUCKET),用于存储 Primus 的机器学习模型。
  • 工作负载身份池 ($PRIMUS_WORKLOAD_IDENTITY_POOL),用于根据其提供方下配置的属性条件验证声明。
  • 与上述工作负载身份池 ($PRIMUS_WORKLOAD_IDENTITY_POOL) 关联的服务账号 ($PRIMUS_SERVICEACCOUNT),具有 IAM 访问权限,可读取 Cloud Storage 存储分区中的数据(使用 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 项目中创建工作负载服务账号。此服务账号将供运行机器学习工作负载的虚拟机使用。

此工作负载服务账号 ($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)。
  • 使用必需的资源名称更新工作负载代码。
  • 构建机器学习工作负载并创建 Dockerfile,用于构建工作负载代码的 Docker 映像。此处是用于此 Codelab 的 Dockerfile。
  • 构建 Docker 映像并将其发布到 Primus 拥有的 Artifact Registry ($PRIMUS_ARTIFACT_REGISTRY)。
  • 向“$WORKLOAD_SERVICEACCOUNT”授予“$PRIMUS_ARTIFACT_REGISTRY”的读取权限。要让工作负载容器从 Artifact Registry 中拉取工作负载 Docker 映像,必须执行此操作。
./create_workload.sh

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

4. 授权和运行工作负载

为工作负载授权

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

  • 内容:经过验证的代码
  • 地点:安全的环境
  • 参与方:受信任的运营商

Primus 使用工作负载身份联合来根据这些要求强制执行访问权限政策。通过工作负载身份联合,您可以指定特性条件。这些条件会限制哪些身份可以使用工作负载身份池 (WIP) 进行身份验证。您可以将证明验证程序服务作为工作负载身份池提供方添加到 WIP,以提供衡量结果并强制执行政策。

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

  • 内容:上传到 $PRIMUS_ARTIFACT_REPOSITORY 代码库的最新 $WORKLOAD_IMAGE_NAME
  • 位置:机密空间可信执行环境在完全受支持的 Confidential Space 虚拟机映像上运行。
  • 参与者:Secundus $WORKLOAD_SERVICE_ACCOUNT 服务账号。
export WORKLOAD_IMAGE_DIGEST=$(docker images digests ${PRIMUS_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${PRIMUS_PROJECT_ID}/${PRIMUS_ARTIFACT_REPOSITORY}/${WORKLOAD_IMAGE_NAME}:${WORKLOAD_IMAGE_TAG}| awk 'NR>1{ print $3 }')
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 config set project $SECUNDUS_PROJECT_ID
gcloud compute instances create ${WORKLOAD_VM} \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --maintenance-policy=TERMINATE \
 --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 存储分区中的每张示例图片,您都会在结果中看到一个条目。该条目将包含两个关键信息:

  • 预测类别的索引:这是一个数值索引,表示模型预测图片所属的类别。
  • Top_k_predictions::此选项为图片提供最多 k 个预测,按照可能性从高到低排序。在此 Codelab 中,k 的值设置为 5,但您可以在工作负载代码中调整该值,以增加或减少预测值。

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

在此 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} 身份)和工作负载服务账号 ($WORKLOAD_SERVICEACCOUNT) 下发布了一个新的 Docker 映像,该映像已授权 Artifact Registry Reader 读取这个新的工作负载映像。这是为了确保在 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}

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

gcloud compute instances create ${WORKLOAD_VM} \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --maintenance-policy=TERMINATE \
 --scopes=cloud-platform --zone=${SECUNDUS_PROJECT_ZONE} \
 --image-project=confidential-space-images \
 --image-family=confidential-space \ 
--service-account=${WORKLOAD_SERVICE_ACCOUNT}@${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 Console
  • 选择要关停的项目,然后点击“删除”顶部:这会安排删除项目

后续操作

查看一些类似的 Codelab...