使用 Secret Manager 搭配批次作業保護機密資料

本文說明如何使用 Secret Manager 密鑰保護您要為批次工作指定的機密資料。

Secret Manager 機密可透過加密機制保護機密資料。在批次工作中,您可以指定一或多個現有機密,安全地傳遞其中所含的機密資料,以便執行下列操作:

事前準備

  1. 如果您之前未使用過 Batch,請參閱「開始使用 Batch」,並完成專案和使用者的必要條件,啟用 Batch。
  2. 建立機密資料識別機密資料,以便為工作安全地指定機密資料。
  3. 如要取得建立工作所需的權限,請要求管理員授予您下列 IAM 角色:

    如要進一步瞭解如何授予角色,請參閱「管理專案、資料夾和機構的存取權」。

    您或許還可透過自訂角色或其他預先定義的角色取得必要權限。

  4. 為確保工作服務帳戶具備存取密鑰的必要權限,請管理員授予工作服務帳戶 Secret Manager 密鑰存取者 (roles/secretmanager.secretAccessor) 的 IAM 角色。

安全地將機密資料傳遞至自訂環境變數

如要安全地將機密資料從 Secret Manager 機密資料傳遞至自訂環境變數,您必須在環境的機密變數 (secretVariables) 子欄中定義每個環境變數,並為每個值指定機密資料。在工作中指定密鑰時,必須將其格式設為密鑰版本的路徑:projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION

您可以使用 gcloud CLI、Batch API、Java、Node.js 或 Python 建立定義密鑰變數的工作。以下範例說明如何建立工作,為所有可執行項目的環境 (taskSpecenvironment 子欄) 定義及使用機密變數。

gcloud

  1. 建立 JSON 檔案,指定工作設定詳細資料,並納入一或多個環境的 secretVariables 子欄位。

    舉例來說,如要建立基本指令碼工作,並在環境中使用所有可執行項目的秘密變數,請建立 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_NAME機密變數的名稱。按照慣例,環境變數名稱會以大寫顯示。

      如要安全地存取變數的 Secret Manager 密鑰中的機密資料,請在這個工作單元的可執行項目中指定這個變數名稱。在定義密鑰變數的環境中,所有可執行項目皆可存取密鑰變數。

    • PROJECT_ID:專案的專案 ID

    • SECRET_NAME現有 Secret Manager 密鑰的名稱。

    • 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

jobs.create 方法發出 POST 要求,指定一或多個環境的 secretVariables 子欄位。

舉例來說,如要建立基本指令碼工作,並在環境中使用所有可執行項目的密鑰變數,請提出以下要求:

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_NAME機密變數的名稱。按照慣例,環境變數名稱會以大寫顯示。

    如要安全地存取變數的 Secret Manager 密鑰中的機密資料,請在工作可執行項目中指定這個變數名稱。在定義密鑰變數的環境中,所有可執行項目都能存取密鑰變數。

  • 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;
    }
  }
}

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 登錄所需的任何憑證。

您可以指定含有資訊的現有機密金鑰,而非直接定義這些欄位,藉此保護 Docker 登錄的任何機密憑證。在工作中指定密鑰時,必須將其格式設為密鑰版本的路徑:projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION

您可以使用 gcloud CLI 或 Batch API,建立使用私人 Docker 存放區容器映像檔的工作。以下範例說明如何建立工作,以便直接指定使用者名稱和密碼做為祕密,從私人 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 登錄檔的使用者名稱,可指定為密鑰或直接指定。

    • PASSWORD:私人 Docker 登錄檔的密碼,可指定為密鑰 (建議) 或直接指定。

      例如,如要將密碼指定為密鑰,請將 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 登錄檔映像檔的容器可執行項目,請在 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 登錄檔的使用者名稱,可指定為密鑰或直接指定。

  • PASSWORD:私人 Docker 登錄檔的密碼,可指定為密鑰 (建議) 或直接指定。

    例如,如要將密碼指定為密鑰,請將 PASSWORD 設為以下內容:

    projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION
    

    更改下列內容:

後續步驟