使用 Secret Manager 与 Batch 保护敏感数据

本文档介绍了如何保护您想 为批处理作业指定默认值。

Secret Manager 密钥 通过加密保护敏感数据 在批处理作业中,您可以指定一个或多个现有 Secret,以安全地传递其中包含的敏感数据,这些数据可用于执行以下操作:

准备工作

  1. 如果您以前没有使用过 Batch,请参阅 Batch 使用入门 并通过填写 针对项目和用户的前提条件
  2. 创建 Secret识别密钥 为作业安全指定的敏感数据。
  3. 如需获得创建作业所需的权限,请让管理员向您授予以下 IAM 角色:

    如需详细了解如何授予角色,请参阅管理对项目、文件夹和组织的访问权限

    您也可以通过自定义角色或其他预定义角色来获取所需的权限。

  4. 为了确保作业的服务账号具备 拥有访问 Secret 的权限 请让管理员向该作业的服务账号授予 针对密钥的 Secret Manager Secret Accessor (roles/secretmanager.secretAccessor) IAM 角色。

安全地将敏感数据传递给自定义环境变量

为了安全地将敏感数据从 Secret Manager Secret 传递到自定义 环境变量,因此必须在 环境的 Secret 变量 (secretVariables) 子字段 并为每个值指定一个 Secret。 每当您在作业中指定 Secret 时,都必须将其格式设置为 Secret 版本的路径:projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION

您可以使用 gcloud CLI、Batch API、Java 或 Python 创建用于定义 Secret 变量的作业。以下示例说明了如何创建 定义并使用 Secret 变量, 所有可运行对象的环境(taskSpecenvironment 子字段)

gcloud

  1. 创建一个 JSON 文件,用于指定作业的配置详细信息 为一个或多个环境添加 secretVariables 子字段。

    例如,创建使用 Secret 的基本脚本作业 变量, 创建一个包含以下内容的 JSON 文件:

    {
      "taskGroups": [
        {
          "taskSpec": {
            "runnables": [
              {
                "script": {
                  "text": "echo This is the secret: ${SECRET_VARIABLE_NAME}"
                }
              }
            ],
            "environment": {
              "secretVariables": {
                "{SECRET_VARIABLE_NAME}": "projects/PROJECT_ID/secrets/SECRET_NAME/versions/VERSION"
              }
            }
          }
        }
      ],
      "logsPolicy": {
        "destination": "CLOUD_LOGGING"
      }
    }
    

    替换以下内容:

    • SECRET_VARIABLE_NAMEsecret 变量。 按照惯例,环境变量名称为 大写

      要从变量的 Secret Manager 密钥,请在此 作业的可运行对象在您定义 Secret 变量的同一环境中,所有可运行程序都可以访问该 Secret 变量。

    • PROJECT_ID:项目的项目 ID

    • SECRET_NAME现有 Secret Manager Secret 的名称。

    • VERSION版本 包含您想要的数据 传递给作业。这可以是版本号或 latest

  2. 如需创建并运行作业,请使用 gcloud batch jobs submit 命令

    gcloud batch jobs submit JOB_NAME \
      --location LOCATION \
      --config JSON_CONFIGURATION_FILE
    

    替换以下内容:

    • JOB_NAME:作业的名称。

    • LOCATION位置 作业的组成部分。

    • JSON_CONFIGURATION_FILE:JSON 路径 该文件包含作业的配置详情。

API

请发送 POST 请求至 jobs.create 方法 ,为一个或多个环境指定 secretVariables 子字段。

例如,创建使用 Secret 的基本脚本作业 变量, 请提出以下请求:

POST https://batch.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/jobs?job_id=JOB_NAME
{
  "taskGroups": [
    {
      "taskSpec": {
        "runnables": [
          {
            "script": {
              "text": "echo This is the secret: ${SECRET_VARIABLE_NAME}"
            }
          }
        ],
        "environment": {
          "secretVariables": {
            "{SECRET_VARIABLE_NAME}": "projects/PROJECT_ID/secrets/SECRET_NAME/versions/VERSION"
          }
        }
      }
    }
  ],
  "logsPolicy": {
    "destination": "CLOUD_LOGGING"
  }
}

替换以下内容:

  • PROJECT_ID:项目的项目 ID

  • LOCATION位置 作业的组成部分。

  • JOB_NAME:作业的名称。

  • SECRET_VARIABLE_NAMEsecret 变量。 按照惯例,环境变量名称应采用大写形式。

    如需安全地访问变量的 Secret Manager Secret 中的敏感数据,请在此作业的可运行代码中指定此变量名称。该密钥变量可供所有 这些运行时 环境 您可以在其中定义 Secret 变量。

  • SECRET_NAME现有的 Secret Manager 密钥

  • VERSION版本 包含您想要的数据 传递给作业。这可以是版本号或 latest

Java


import com.google.cloud.batch.v1.BatchServiceClient;
import com.google.cloud.batch.v1.CreateJobRequest;
import com.google.cloud.batch.v1.Environment;
import com.google.cloud.batch.v1.Job;
import com.google.cloud.batch.v1.LogsPolicy;
import com.google.cloud.batch.v1.LogsPolicy.Destination;
import com.google.cloud.batch.v1.Runnable;
import com.google.cloud.batch.v1.Runnable.Script;
import com.google.cloud.batch.v1.TaskGroup;
import com.google.cloud.batch.v1.TaskSpec;
import com.google.protobuf.Duration;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class CreateBatchUsingSecretManager {

  public static void main(String[] args)
      throws IOException, ExecutionException, InterruptedException, TimeoutException {
    // TODO(developer): Replace these variables before running the sample.
    // Project ID or project number of the Google Cloud project you want to use.
    String projectId = "YOUR_PROJECT_ID";
    // Name of the region you want to use to run the job. Regions that are
    // available for Batch are listed on: https://cloud.google.com/batch/docs/get-started#locations
    String region = "europe-central2";
    // The name of the job that will be created.
    // It needs to be unique for each project and region pair.
    String jobName = "JOB_NAME";
    // The name of the secret variable.
    // This variable name is specified in this job's runnables
    // and is accessible to all of the runnables that are in the same environment.
    String secretVariableName = "VARIABLE_NAME";
    // The name of an existing Secret Manager secret.
    String secretName = "SECRET_NAME";
    // The version of the specified secret that contains the data you want to pass to the job.
    // This can be the version number or latest.
    String version = "VERSION";

    createBatchUsingSecretManager(projectId, region,
            jobName, secretVariableName, secretName, version);
  }

  // Create a basic script job to securely pass sensitive data.
  // The data is obtained from Secret Manager secrets
  // and set as custom environment variables in the job.
  public static Job createBatchUsingSecretManager(String projectId, String region,
                                                  String jobName, String secretVariableName,
                                                  String secretName, String version)
      throws IOException, ExecutionException, InterruptedException, TimeoutException {
    // Initialize client that will be used to send requests. This client only needs to be created
    // once, and can be reused for multiple requests.
    try (BatchServiceClient batchServiceClient = BatchServiceClient.create()) {
      // Define what will be done as part of the job.
      Runnable runnable =
          Runnable.newBuilder()
              .setScript(
                  Script.newBuilder()
                      .setText(
                          String.format("echo This is the secret: ${%s}.", secretVariableName))
                      // You can also run a script from a file. Just remember, that needs to be a
                      // script that's already on the VM that will be running the job.
                      // Using setText() and setPath() is mutually exclusive.
                      // .setPath("/tmp/test.sh")
                      .build())
              .build();

      // Construct the resource path to the secret's version.
      String secretValue = String
              .format("projects/%s/secrets/%s/versions/%s", projectId, secretName, version);

      // Set the secret as an environment variable.
      Environment.Builder environmentVariable = Environment.newBuilder()
          .putSecretVariables(secretVariableName, secretValue);

      TaskSpec task = TaskSpec.newBuilder()
          // Jobs can be divided into tasks. In this case, we have only one task.
          .addRunnables(runnable)
          .setEnvironment(environmentVariable)
          .setMaxRetryCount(2)
          .setMaxRunDuration(Duration.newBuilder().setSeconds(3600).build())
          .build();

      // Tasks are grouped inside a job using TaskGroups.
      // Currently, it's possible to have only one task group.
      TaskGroup taskGroup = TaskGroup.newBuilder()
          .setTaskSpec(task)
          .build();

      Job job =
          Job.newBuilder()
              .addTaskGroups(taskGroup)
              .putLabels("env", "testing")
              .putLabels("type", "script")
              // We use Cloud Logging as it's an out of the box available option.
              .setLogsPolicy(
                  LogsPolicy.newBuilder().setDestination(Destination.CLOUD_LOGGING))
              .build();

      CreateJobRequest createJobRequest =
          CreateJobRequest.newBuilder()
              // The job's parent is the region in which the job will run.
              .setParent(String.format("projects/%s/locations/%s", projectId, region))
              .setJob(job)
              .setJobId(jobName)
              .build();

      Job result =
          batchServiceClient
              .createJobCallable()
              .futureCall(createJobRequest)
              .get(5, TimeUnit.MINUTES);

      System.out.printf("Successfully created the job: %s", result.getName());

      return result;
    }
  }
}

Python

from typing import Dict, Optional

from google.cloud import batch_v1


def create_with_secret_manager(
    project_id: str,
    region: str,
    job_name: str,
    secrets: Dict[str, str],
    service_account_email: Optional[str] = None,
) -> batch_v1.Job:
    """
    This method shows how to create a sample Batch Job that will run
    a simple command on Cloud Compute instances with passing secrets from secret manager.
    Note: Job's service account should have the permissions to access secrets.
        - Secret Manager Secret Accessor (roles/secretmanager.secretAccessor) IAM role.

    Args:
        project_id: project ID or project number of the Cloud project you want to use.
        region: name of the region you want to use to run the job. Regions that are
            available for Batch are listed on: https://cloud.google.com/batch/docs/get-started#locations
        job_name: the name of the job that will be created.
            It needs to be unique for each project and region pair.
        secrets: secrets, which should be passed to the job. Environment variables should be capitalized
        by convention https://google.github.io/styleguide/shellguide.html#constants-and-environment-variable-names
            The format should look like:
                - {'SECRET_NAME': 'projects/{project_id}/secrets/{SECRET_NAME}/versions/{version}'}
            version can be set to 'latest'.
        service_account_email (optional): custom service account email

    Returns:
        A job object representing the job created.
    """
    client = batch_v1.BatchServiceClient()

    # Define what will be done as part of the job.
    task = batch_v1.TaskSpec()
    runnable = batch_v1.Runnable()
    runnable.script = batch_v1.Runnable.Script()
    runnable.script.text = (
        "echo Hello world! from task ${BATCH_TASK_INDEX}."
        + f" ${next(iter(secrets.keys()))} is the value of the secret."
    )
    task.runnables = [runnable]
    task.max_retry_count = 2
    task.max_run_duration = "3600s"

    envable = batch_v1.Environment()
    envable.secret_variables = secrets
    task.environment = envable

    # Tasks are grouped inside a job using TaskGroups.
    # Currently, it's possible to have only one task group.
    group = batch_v1.TaskGroup()
    group.task_count = 4
    group.task_spec = task

    # Policies are used to define on what kind of virtual machines the tasks will run on.
    # Read more about local disks here: https://cloud.google.com/compute/docs/disks/persistent-disks
    policy = batch_v1.AllocationPolicy.InstancePolicy()
    policy.machine_type = "e2-standard-4"
    instances = batch_v1.AllocationPolicy.InstancePolicyOrTemplate()
    instances.policy = policy
    allocation_policy = batch_v1.AllocationPolicy()
    allocation_policy.instances = [instances]

    service_account = batch_v1.ServiceAccount()
    service_account.email = service_account_email
    allocation_policy.service_account = service_account

    job = batch_v1.Job()
    job.task_groups = [group]
    job.allocation_policy = allocation_policy
    job.labels = {"env": "testing", "type": "script"}
    # We use Cloud Logging as it's an out of the box available option
    job.logs_policy = batch_v1.LogsPolicy()
    job.logs_policy.destination = batch_v1.LogsPolicy.Destination.CLOUD_LOGGING

    create_request = batch_v1.CreateJobRequest()
    create_request.job = job
    create_request.job_id = job_name
    # The job's parent is the region in which the job will run
    create_request.parent = f"projects/{project_id}/locations/{region}"

    return client.create_job(create_request)

安全地访问需要 Docker 注册表凭据的容器映像

要使用私有 Docker 注册表中的容器映像,必须有一个可运行容器 指定允许其访问该 Docker 注册表的登录凭据。 具体而言,对于可通过将映像 URI (imageUri) 字段设置为私有 Docker 注册库中的映像来运行的任何容器,您必须使用用户名 (username) 字段密码 (password) 字段 指定访问该 Docker 注册库所需的所有凭据。

您可以通过指定 包含相关信息的现有密钥,而不是定义这些密钥, 字段。 在作业中指定 Secret 时,必须将其格式设置为路径 密钥版本: projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION

您可以使用 gcloud CLI 或 Batch API 创建使用专用 Docker 注册库中的容器映像的作业。以下示例说明了如何创建使用容器的作业 从私有 Docker 注册表导入映像 将密码设为 Secret

gcloud

  1. 创建一个 JSON 文件,用于指定作业的配置详细信息。 对于任何使用私有容器映像的 Docker Registry,添加访问它所需的任何凭据 (位于 usernamepassword 字段中)。

    例如,要创建用于指定某张图片的基本容器作业, 从私有 Docker 注册表运行 创建一个包含以下内容的 JSON 文件:

    {
      "taskGroups": [
        {
          "taskSpec": {
            "runnables": [
              {
                "container": {
                  "imageUri": "PRIVATE_IMAGE_URI",
                  "commands": [
                    "-c",
                    "echo This runnable uses a private image."
                  ],
                  "username": "USERNAME",
                  "password": "PASSWORD"
                }
              }
            ],
          }
        }
      ],
      "logsPolicy": {
        "destination": "CLOUD_LOGGING"
      }
    }
    

    替换以下内容:

    • PRIVATE_IMAGE_URI: 从私有 Docker 注册表获取容器映像如果此映像需要任何其他容器设置,您也必须添加这些设置。

    • USERNAME: 私有 Docker 注册表的用户名,该用户名可以指定 作为 Secret 或直接。

    • PASSWORD:私有 Docker 注册表的密码,可以作为 Secret(推荐)或直接指定。

      例如,要将密码指定为 Secret,可将密码设置为 PASSWORD 更改为以下内容:

      projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION
      

      替换以下内容:

  2. 如需创建并运行作业,请使用 gcloud batch jobs submit 命令

    gcloud batch jobs submit JOB_NAME \
      --location LOCATION \
      --config JSON_CONFIGURATION_FILE
    

    替换以下内容:

    • JOB_NAME:作业的名称。

    • LOCATION位置 作业的组成部分。

    • JSON_CONFIGURATION_FILE:JSON 路径 该文件包含作业的配置详情。

API

jobs.create 方法发出 POST 请求。对于任何使用私有容器映像的 Docker Registry,添加访问它所需的任何凭据 (位于 usernamepassword 字段中)。

例如,要创建用于指定某张图片的基本容器作业, 从私有 Docker 注册表运行 请提出以下请求:

POST https://batch.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/jobs?job_id=JOB_NAME
{
  "taskGroups": [
    {
      "taskSpec": {
        "runnables": [
          {
            "container": {
              "imageUri": "PRIVATE_IMAGE_URI",
                "commands": [
                  "-c",
                  "echo This runnable uses a private image."
                ],
                "username": "USERNAME",
                "password": "PASSWORD"
            }
          }
        ],
      }
    }
  ],
  "logsPolicy": {
    "destination": "CLOUD_LOGGING"
  }
}

替换以下内容:

  • PROJECT_ID:您的项目 ID

  • LOCATION位置 作业的组成部分。

  • JOB_NAME:作业的名称。

  • PRIVATE_IMAGE_URI: 从私有 Docker 注册表获取容器映像如果这张图片 需要进行任何其他容器设置, 您必须也添加这些代码

  • USERNAME: 私有 Docker 注册表的用户名,该用户名可以指定 作为 Secret 或直接。

  • PASSWORD:私有 Docker 注册表的密码,可以作为 Secret(推荐)或直接指定。

    例如,要将密码指定为 Secret,可将密码设置为 PASSWORD 更改为以下内容:

    projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION
    

    替换以下内容:

后续步骤