Batch で Secret Manager を使用してセンシティブ データを保護する

このドキュメントでは、Secret Manager の Secret を使用して Batch ジョブに指定するセンシティブ データを保護する方法について説明します。

Secret Manager の Secret は、暗号化によってセンシティブ データを保護します。 Batch ジョブでは、1 つ以上の既存の Secret を指定して、そこに含まれるセンシティブ データを安全に渡すことができます。これを使用すると、次のことを行えます。

  • センシティブ データを含むカスタム環境変数を安全に定義します。

  • Docker Registry のログイン認証情報を安全に指定すると、ジョブの実行可能ファイルでプライベート コンテナ イメージにアクセスできます。

始める前に

  1. Batch を以前に使用したことがなかった場合は、Batch を使ってみるを確認し、プロジェクトとユーザーの前提条件を完了して Batch を有効にします。
  2. ジョブに対して安全に指定するセンシティブ データの Secret を作成するSecret を特定します。
  3. ジョブの作成に必要な権限を取得するには、次の IAM ロールを付与するよう管理者に依頼してください。

    ロールの付与については、プロジェクト、フォルダ、組織へのアクセスを管理するをご覧ください。

    必要な権限は、カスタムロールや他の事前定義ロールから取得することもできます。

  4. ジョブのサービス アカウントに Secret へのアクセスに必要な権限が付与されているようにするには、ジョブのサービス アカウントに Secret に対する Secret Manager のシークレット アクセサー roles/secretmanager.secretAccessor)の IAM ロール付与するよう管理者に依頼してください。

センシティブ データをカスタム環境変数に安全に渡す

Secret Manager の Secret からカスタム環境変数にセンシティブ データを安全に渡すには、環境の Secret 変数(secretVariables)サブフィールドで各環境変数を定義し、値ごとに Secret を指定する必要があります。ジョブで Secret を指定するたびに、Secret のバージョンの形式 projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION にする必要があります。

Secret 変数を定義するジョブを作成するには、gcloud CLI、Batch API、Java、Node.js、または Python を使用します。次の例は、すべての実行可能ファイルの環境(taskSpecenvironment サブフィールド)の Secret 変数を定義して使用するジョブの作成方法を示しています。

gcloud

  1. ジョブの構成の詳細を指定し、1 つ以上の環境の secretVariables サブフィールドを含む JSON ファイルを作成します。

    たとえば、環境ですべての実行可能ファイルに対して 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_NAME: Secret 変数の名前。慣例により、環境変数名は大文字です。

      変数の 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 リクエストを送ります。これにより、1 つ以上の環境の 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_NAME: Secret 変数の名前。慣例により、環境変数名は大文字です。

    変数の 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 にする必要があります。

非公開 Docker レジストリのコンテナ イメージを使用するジョブを作成するには、gcloud CLI または Batch API を使用します。次の例は、ユーザー名とパスワードを Secret として直接指定し、非公開 Docker レジストリのコンテナ イメージを使用するジョブを作成する方法を示しています。

gcloud

  1. ジョブの構成の詳細を指定する JSON ファイルを作成します。非公開 Docker レジストリのイメージを使用するコンテナ実行可能ファイルの場合は、username フィールドと password フィールドに、そのレジストリにアクセスするために必要な認証情報を含めます。

    たとえば、プライベート 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
      

      次のように置き換えます。

  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 レジストリのイメージを使用するコンテナ実行可能ファイルの場合は、username フィールドと password フィールドに、そのレジストリにアクセスするために必要な認証情報を含めます。

たとえば、非公開 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
    

    次のように置き換えます。

次のステップ