הגנה על מידע אישי רגיש באמצעות Secret Manager עם Batch

במאמר הזה מוסבר איך להגן על מידע אישי רגיש שרוצים לציין עבור משימה באצווה באמצעות סודות של Secret Manager.

סודות ב-Secret Manager מגנים על מידע אישי רגיש באמצעות הצפנה. במשימה באצווה, אפשר לציין סוד אחד או יותר כדי להעביר בצורה מאובטחת את המידע האישי הרגיש שהם מכילים. אפשר להשתמש בנתונים האלה כדי לבצע את הפעולות הבאות:

  • הגדרת משתני סביבה מותאמים אישית שמכילים מידע אישי רגיש בצורה מאובטחת.

  • אפשר לציין באופן מאובטח את פרטי הכניסה ל-Docker Registry כדי לאפשר לקבצים להרצה של משימה לגשת לקובצי האימג' הפרטיים של הקונטיינרים.

לפני שמתחילים

  1. אם עוד לא השתמשתם ב-Batch, כדאי לעיין במאמר תחילת העבודה עם Batch ולהפעיל את Batch על ידי השלמת הדרישות המוקדמות לפרויקטים ולמשתמשים.
  2. יוצרים סוד או מזהים סוד לנתונים הרגישים שרוצים לציין בצורה מאובטחת למשימה.
  3. כדי לקבל את ההרשאות שדרושות ליצירת משימה, אתם צריכים לבקש מהאדמין להקצות לכם את תפקידי ה-IAM הבאים:

    להסבר על מתן תפקידים, ראו איך מנהלים את הגישה ברמת הפרויקט, התיקייה והארגון.

    יכול להיות שאפשר לקבל את ההרשאות הנדרשות גם באמצעות תפקידים בהתאמה אישית או תפקידים מוגדרים מראש.

  4. כדי לוודא שלחשבון השירות של המשימה יש את ההרשאות שנדרשות לגישה לסודות, צריך לבקש מהאדמין להקצות לחשבון השירות של המשימה את תפקיד ה-IAM‏ Secret Manager Secret Accessor (roles/secretmanager.secretAccessor) בסוד.

העברה מאובטחת של מידע אישי רגיש למשתני סביבה מותאמים אישית

כדי להעביר בצורה מאובטחת נתונים רגישים מסודות ב-Secret Manager למשתני סביבה מותאמים אישית, צריך להגדיר כל משתנה סביבה בשדה המשנה של משתני הסוד (secretVariables) בסביבה ולציין סוד לכל ערך. בכל פעם שמציינים סוד בעבודת הפעלה, צריך להגדיר אותו כנתיב לגרסת סוד: projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION.

אפשר ליצור משימה שמגדירה משתני סוד באמצעות ה-CLI של gcloud,‏ Batch API,‏ Java,‏ Node.js או Python. בדוגמה הבאה מוסבר איך ליצור ג'וב שמגדיר משתנה סודי ומשתמש בו בסביבה של כל הרכיבים הניתנים להרצה (שדה המשנה environment של taskSpec).

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: מזהה הפרויקט של הפרויקט.

    • 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

שולחים בקשת POST ל-method‏ jobs.create שמציינת את שדה המשנה 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: מזהה הפרויקט של הפרויקט.

  • 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 Registry

כדי להשתמש בקובץ אימג' של קונטיינר ממאגר Docker פרטי, צריך לציין ב-Runnable פרטי כניסה שיאפשרו לו לגשת למאגר Docker הזה. בפרט, לכל קונטיינר שאפשר להפעיל עם הערך של השדה image URI (imageUri) שמוגדר לקובץ אימג' ממאגר Docker פרטי, צריך לציין את כל פרטי הכניסה שנדרשים לגישה למאגר Docker הזה באמצעות השדה username (username) והשדה password (password).

כדי להגן על פרטי כניסה רגישים במאגר Docker, אפשר לציין סודות קיימים שמכילים את המידע במקום להגדיר את השדות האלה ישירות. בכל פעם שמציינים סוד בעבודת הפעלה, צריך להגדיר אותו כנתיב לגרסת סוד: projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION.

אפשר ליצור משימה שמשתמשת בתמונות קונטיינר ממאגר Docker פרטי באמצעות ה-CLI של gcloud או Batch API. בדוגמה הבאה מוסבר איך ליצור משימה שמשתמשת בקובץ אימג' של קונטיינר ממאגר 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: ה-URI של קובץ אימג' של קונטיינר ממאגר Docker פרטי. אם התמונה הזו דורשת הגדרות אחרות של מאגר התגים, צריך לכלול גם אותן.

    • 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

שולחים בקשת POST אל ה-method‏ jobs.create. אם יש קונטיינרים שניתנים להפעלה ומשתמשים בתמונות ממאגר פרטי של 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: מזהה הפרויקט של הפרויקט.

  • LOCATION: המיקום של המשרה.

  • JOB_NAME: שם המשימה.

  • PRIVATE_IMAGE_URI: ה-URI של קובץ אימג' של קונטיינר ממאגר Docker פרטי. אם התמונה הזו דורשת הגדרות אחרות של מאגר התגים, צריך לכלול גם אותן.

  • USERNAME: שם המשתמש במאגר Docker הפרטי, שאפשר לציין אותו כסוד או ישירות.

  • PASSWORD: הסיסמה למאגר Docker הפרטי, שאפשר לציין אותה כסוד (מומלץ) או ישירות.

    לדוגמה, כדי לציין את הסיסמה כסוד, מגדירים את PASSWORD באופן הבא:

    projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION
    

    מחליפים את מה שכתוב בשדות הבאים:

המאמרים הבאים