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

本文档介绍了如何使用 Secret Manager 密钥保护您要为批处理作业指定的敏感数据。

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

准备工作

  1. 如果您之前未使用过批处理功能,请参阅开始使用批处理,并完成适用于项目和用户的前提条件,以启用批处理功能。
  2. 为您要为作业安全指定的敏感数据创建密文指定密文
  3. 如需获得创建作业所需的权限,请让您的管理员为您授予以下 IAM 角色:

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

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

  4. 如需确保作业的服务账号具有访问 Secret 所需的权限,请让您的管理员为作业的服务账号授予针对 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、Node.js 或 Python 创建用于定义 Secret 变量的作业。以下示例介绍了如何创建一个作业,用于为所有可运行项的环境(taskSpecenvironment 子字段)定义和使用 Secret 变量。

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 变量的同一环境中,所有可运行程序都可以访问该 Secret 变量。

    • PROJECT_ID:您的项目的项目 ID

    • SECRET_NAME现有 Secret Manager Secret 的名称。

    • VERSION:指定 Secret 的版本,其中包含要传递给作业的数据。此值可以是版本号或 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

jobs.create 方法发出 POST 请求,为一个或多个环境指定 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 变量。

  • SECRET_NAME现有 Secret Manager Secret 的名称。

  • VERSION:指定 Secret 的版本,其中包含要传递给作业的数据。此值可以是版本号或 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;
    }
  }
}

Node.js

// Imports the Batch library
const batchLib = require('@google-cloud/batch');
const batch = batchLib.protos.google.cloud.batch.v1;

// Instantiates a client
const batchClient = new batchLib.v1.BatchServiceClient();

/**
 * TODO(developer): Update these variables before running the sample.
 */
// Project ID or project number of the Google Cloud project you want to use.
const projectId = await batchClient.getProjectId();
// 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
const region = 'europe-central2';
// The name of the job that will be created.
// It needs to be unique for each project and region pair.
const jobName = 'batch-job-secret-manager';
// 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.
const secretVariableName = 'secretVariableName';
// The name of an existing Secret Manager secret.
const secretName = 'secretName';
// 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.
const version = 'version';

// Define what will be done as part of the job.
const runnable = new batch.Runnable({
  script: new batch.Runnable.Script({
    commands: ['-c', `echo This is the secret: ${secretVariableName}`],
  }),
});

// Construct the resource path to the secret's version.
const secretValue = `projects/${projectId}/secrets/${secretName}/versions/${version}`;

// Set the secret as an environment variable.
const environment = new batch.Environment();
environment.secretVariables[secretVariableName] = secretValue;

const task = new batch.TaskSpec({
  runnables: [runnable],
  environment,
  maxRetryCount: 2,
  maxRunDuration: {seconds: 3600},
});

// Tasks are grouped inside a job using TaskGroups.
const group = new batch.TaskGroup({
  taskCount: 3,
  taskSpec: task,
});

const job = new batch.Job({
  name: jobName,
  taskGroups: [group],
  labels: {env: 'testing', type: 'script'},
  // We use Cloud Logging as it's an option available out of the box
  logsPolicy: new batch.LogsPolicy({
    destination: batch.LogsPolicy.Destination.CLOUD_LOGGING,
  }),
});

// The job's parent is the project and region in which the job will run
const parent = `projects/${projectId}/locations/${region}`;

async function callCreateUsingSecretManager() {
  // Construct request
  const request = {
    parent,
    jobId: jobName,
    job,
  };

  // Run request
  const [response] = await batchClient.createJob(request);
  console.log(JSON.stringify(response));
}

await callCreateUsingSecretManager();

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(而不是直接定义这些字段),保护 Docker 注册库的任何敏感凭据。每当您在作业中指定 Secret 时,都必须将其格式设置为 Secret 版本的路径:projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION

您可以使用 gcloud CLI 或 Batch API 创建使用专用 Docker 注册库中的容器映像的作业。以下示例介绍了如何创建一个作业,通过直接指定用户名和将密码作为 Secret 来使用私有 Docker 注册表中的容器映像。

gcloud

  1. 创建一个 JSON 文件,用于指定作业的配置详细信息。对于使用私有 Docker 注册表中的映像的任何容器可运行项,请在 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 注册表中容器映像的映像 URI。如果此映像需要任何其他容器设置,您也必须添加这些设置。

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

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

      例如,如需将密码指定为 Secret,请将 PASSWORD 设置为以下内容:

      projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION
      

      替换以下内容:

      • PROJECT_ID:您的项目的项目 ID

      • SECRET_NAME现有 Secret Manager Secret 的名称。

      • VERSION:指定 Secret 的版本,其中包含要传递给作业的相关数据。此值可以是版本号或 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

jobs.create 方法发出 POST 请求。对于使用私有 Docker 注册表中的映像的任何容器可运行项,请在 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 注册表中容器映像的映像 URI。如果此映像需要任何其他容器设置,您也必须添加这些设置。

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

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

    例如,如需将密码指定为 Secret,请将 PASSWORD 设置为以下内容:

    projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION
    

    替换以下内容:

    • PROJECT_ID:您的项目的项目 ID

    • SECRET_NAME现有 Secret Manager Secret 的名称。

    • VERSION:指定 Secret 的版本,其中包含要传递给作业的相关数据。此值可以是版本号或 latest

后续步骤