Proteger datos sensibles con Secret Manager y Batch

En este documento se describe cómo proteger los datos sensibles que quieras especificar en un trabajo por lotes mediante secretos de Secret Manager.

Los secretos de Secret Manager protegen los datos sensibles mediante el cifrado. En un trabajo por lotes, puedes especificar uno o varios secretos para transferir de forma segura los datos sensibles que contienen, que puedes usar para hacer lo siguiente:

  • Define de forma segura variables de entorno personalizadas que contengan datos sensibles.

  • Especifica de forma segura las credenciales de inicio de sesión de un registro de Docker para que los elementos ejecutables de un trabajo puedan acceder a sus imágenes de contenedor privadas.

Antes de empezar

  1. Si no has usado Batch antes, consulta el artículo Empezar a usar Batch y habilita Batch completando los requisitos previos para proyectos y usuarios.
  2. Crea un secreto o identifica un secreto para los datos sensibles que quieras especificar de forma segura en un trabajo.
  3. Para obtener los permisos que necesitas para crear un trabajo, pide a tu administrador que te conceda los siguientes roles de gestión de identidades y accesos:

    Para obtener más información sobre cómo conceder roles, consulta el artículo Gestionar el acceso a proyectos, carpetas y organizaciones.

    También puedes conseguir los permisos necesarios a través de roles personalizados u otros roles predefinidos.

  4. Para asegurarte de que la cuenta de servicio del trabajo tiene los permisos necesarios para acceder a los secretos, pide a tu administrador que le conceda el rol de gestión de identidades y accesos Secret Manager Secret Accessor (roles/secretmanager.secretAccessor) en el secreto.

Transferir datos sensibles de forma segura a variables de entorno personalizadas

Para transferir de forma segura datos sensibles de secretos de Secret Manager a variables de entorno personalizadas, debes definir cada variable de entorno en el subcampo variables de secreto (secretVariables) de un entorno y especificar un secreto para cada valor. Cada vez que especifiques un secreto en un trabajo, debes darle el formato de una ruta de acceso a una versión del secreto: projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION.

Puedes crear un trabajo que defina variables secretas mediante la CLI de gcloud, la API Batch, Java, Node.js o Python. En el siguiente ejemplo se explica cómo crear un trabajo que defina y use una variable secreta para el entorno de todos los elementos ejecutables (subcampo environment de taskSpec).

gcloud

  1. Crea un archivo JSON que especifique los detalles de configuración del trabajo e incluya el subcampo secretVariables de uno o varios entornos.

    Por ejemplo, para crear un trabajo de secuencia de comandos básico que use una variable secreta en el entorno de todos los elementos ejecutables, crea un archivo JSON con el siguiente contenido:

    {
      "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"
      }
    }
    

    Haz los cambios siguientes:

    • SECRET_VARIABLE_NAME: nombre de la variable secreta. Por convención, los nombres de las variables de entorno se escriben en mayúsculas.

      Para acceder de forma segura a los datos sensibles del secreto de Secret Manager de la variable, especifique el nombre de esta variable en los elementos ejecutables de este trabajo. Se puede acceder a la variable secreta desde todos los elementos ejecutables que se encuentren en el mismo entorno en el que definas la variable secreta.

    • PROJECT_ID: el ID de proyecto de tu proyecto.

    • SECRET_NAME: el nombre de un secreto de Secret Manager.

    • VERSION: la versión del secreto especificado que contiene los datos que quieres transferir al trabajo. Puede ser el número de versión o latest.

  2. Para crear y ejecutar el trabajo, usa el comando gcloud batch jobs submit:

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

    Haz los cambios siguientes:

    • JOB_NAME: el nombre del puesto.

    • LOCATION: la ubicación del puesto.

    • JSON_CONFIGURATION_FILE: la ruta de un archivo JSON con los detalles de configuración del trabajo.

API

Envía una solicitud POST al método jobs.create que especifique el subcampo secretVariables de uno o varios entornos.

Por ejemplo, para crear una tarea de secuencia de comandos básica que use una variable secreta en el entorno de todos los elementos ejecutables, haz la siguiente solicitud:

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"
  }
}

Haz los cambios siguientes:

  • PROJECT_ID: el ID de proyecto de tu proyecto.

  • LOCATION: la ubicación del puesto.

  • JOB_NAME: el nombre del puesto.

  • SECRET_VARIABLE_NAME: nombre de la variable secreta. Por convención, los nombres de las variables de entorno se escriben en mayúsculas.

    Para acceder de forma segura a los datos sensibles del secreto de Secret Manager de la variable, especifique el nombre de esta variable en los elementos ejecutables de este trabajo. Se puede acceder a la variable secreta desde todos los elementos ejecutables que se encuentren en el mismo entorno en el que definas la variable secreta.

  • SECRET_NAME: el nombre de un secreto de Secret Manager.

  • VERSION: la versión del secreto especificado que contiene los datos que quieres transferir al trabajo. Puede ser el número de versión o 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)

Acceder de forma segura a imágenes de contenedor que requieren credenciales de registro de Docker

Para usar una imagen de contenedor de un registro de Docker privado, un elemento ejecutable debe especificar las credenciales de inicio de sesión que le permitan acceder a ese registro de Docker. En concreto, para cualquier contenedor que se pueda ejecutar con el campo URI de la imagen (imageUri) definido en una imagen de un registro de Docker privado, debes especificar las credenciales necesarias para acceder a ese registro de Docker mediante los campos nombre de usuario (username) y contraseña (password).

Puede proteger las credenciales sensibles de un registro de Docker especificando secretos que contengan la información en lugar de definir estos campos directamente. Cada vez que especifiques un secreto en un trabajo, debes darle el formato de una ruta de acceso a una versión del secreto: projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION.

Puedes crear un trabajo que use imágenes de contenedor de un registro de Docker privado mediante la CLI de gcloud o la API de Batch. En el siguiente ejemplo se explica cómo crear un trabajo que use una imagen de contenedor de un registro de Docker privado especificando el nombre de usuario directamente y la contraseña como secreto.

gcloud

  1. Crea un archivo JSON que especifique los detalles de configuración del trabajo. En el caso de los contenedores ejecutables que usen imágenes de un registro de Docker privado, incluye las credenciales necesarias para acceder a él en los campos username y password.

    Por ejemplo, para crear un trabajo de contenedor básico que especifique una imagen de un registro de Docker privado, crea un archivo JSON con el siguiente contenido:

    {
      "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"
      }
    }
    

    Haz los cambios siguientes:

    • PRIVATE_IMAGE_URI: el URI de la imagen de un contenedor de un registro de Docker privado. Si esta imagen requiere otros ajustes de contenedor, también debes incluirlos.

    • USERNAME: el nombre de usuario del registro de Docker privado, que se puede especificar como secreto o directamente.

    • PASSWORD: la contraseña del registro de Docker privado, que se puede especificar como secreto (opción recomendada) o directamente.

      Por ejemplo, para especificar la contraseña como secreto, asigna el siguiente valor a PASSWORD:

      projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION
      

      Haz los cambios siguientes:

      • PROJECT_ID: el ID de proyecto de tu proyecto.

      • SECRET_NAME: el nombre de un secreto de Secret Manager.

      • VERSION: la versión del secreto especificado que contiene los datos que quieres transferir al trabajo. Puede ser el número de versión o latest.

  2. Para crear y ejecutar el trabajo, usa el comando gcloud batch jobs submit:

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

    Haz los cambios siguientes:

    • JOB_NAME: el nombre del puesto.

    • LOCATION: la ubicación del puesto.

    • JSON_CONFIGURATION_FILE: la ruta de un archivo JSON con los detalles de configuración del trabajo.

API

Realiza una solicitud POST al método jobs.create. En el caso de los contenedores ejecutables que usen imágenes de un registro de Docker privado, incluye las credenciales necesarias para acceder a él en los campos username y password.

Por ejemplo, para crear un trabajo de contenedor básico que especifique una imagen de un registro de Docker privado, haz la siguiente solicitud:

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"
  }
}

Haz los cambios siguientes:

  • PROJECT_ID: el ID de proyecto de tu proyecto.

  • LOCATION: la ubicación del puesto.

  • JOB_NAME: el nombre del puesto.

  • PRIVATE_IMAGE_URI: el URI de la imagen de un contenedor de un registro de Docker privado. Si esta imagen requiere otros ajustes de contenedor, también debes incluirlos.

  • USERNAME: el nombre de usuario del registro de Docker privado, que se puede especificar como secreto o directamente.

  • PASSWORD: la contraseña del registro de Docker privado, que se puede especificar como secreto (opción recomendada) o directamente.

    Por ejemplo, para especificar la contraseña como secreto, asigna el siguiente valor a PASSWORD:

    projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION
    

    Haz los cambios siguientes:

    • PROJECT_ID: el ID de proyecto de tu proyecto.

    • SECRET_NAME: el nombre de un secreto de Secret Manager.

    • VERSION: la versión del secreto especificado que contiene los datos que quieres transferir al trabajo. Puede ser el número de versión o latest.

Siguientes pasos