Brigade

Brigade - Event-driven scripting for Kubernetes.

Secret Management

Secret Management

Brigade provides tools for storing sensitive data outside of your brigade.js scripts, and then passing that information into the jobs that need them. Brigade accomplishes this by making use of Kubernetes secrets.

Adding a Secret to Your Project

Imagine a case where we need to pass a sensitive piece of information to one of the jobs in our brigade.js. For example, we might need to pass an authentication token to a job that must authenticate to a remote service.

We do this by storing the secret inside of the project definition.

Project definitions are typically managed by brig. As you may recall from the installation manual, a new project is created like via brig project create.

During the creation process, we are able to add secrets when prompted. If we’d forgotten to add a secret and/or additional secrets need to be added to this existing project, we can do so via brig project create --replace -p <existing project name>.

Here we add a new secret with key dbPassword and value supersecret to our existing brigadecore/empty-testbed project:

$ brig project create --replace -p brigadecore/empty-testbed
? Existing Project Name brigadecore/empty-testbed
? Full repository name github.com/brigadecore/empty-testbed
? Clone URL (https://github.com/your/repo.git) https://github.com/brigadecore/empty-testbed.git
? Add secrets? Yes
? 	Secret 1 dbPassword
? 	Value supersecret
? ===> Add another? No
Auto-generated a Shared Secret: "9qQvuplBx39r04Wd9EmyxjGA"
? Configure GitHub Access? No
? Configure advanced options No
Project ID: brigade-830c16d4aaf6f5490937ad719afd8490a5bcbef064d397411043ac

Accessing a Secret within brigade.js

Within the brigade.js file, we can access any of the secrets defined on our project.

// THIS IS NOT SAFE! IT LEAKS YOUR SECRET INTO THE LOGS.
const {events, Job, Group} = require("brigadier")

events.on("push", function(e, project) {
  console.log("My DB password is " + project.secrets.dbPassword)
})

Secrets can be selectively passed to jobs by setting environment variables.

// THIS IS NOT SAFE! IT LEAKS YOUR SECRET INTO THE LOGS.
const {events, Job, Group} = require("brigadier")

events.on("push", function(e, project) {
  var j1 = new Job("secrets", "alpine:3.4")

  // Send the password to an environment variable.
  j1.env = {
    "DB_PASSWORD": project.secrets.dbPassword
  }

  // Print the env var to the log.
  j1.tasks = [
    "echo $DB_PASSWORD"
  ]

  j1.run()
}

In this case, we retrieve the secret from the project, and we pass it into the new Job as an environment variable. When the job executes, it can access the dbPassword as $DB_PASSWORD.

Note that behind the scenes, Brigade is storing the environment variables in another Job-specific secret.

FAQ

Why don’t all jobs get access to all of the secrets? Why do I have to pass them to the Job.env?

Brigade is designed to use off-the-shelf Docker images. In the examples above, we used the alpine:3.4 image straight from DockerHub. We wouldn’t want to just automatically pass all of our information straight into that container. For starters, doing so might inadvertently override an existing environment variable of the same name. More importantly, the data might get misused or unintentionally exposed by the container.

So we err on the side of safety.

Can I encrypt my secrets?

We use Kubernetes Secrets for holding sensitive data. As encrypted Secrets are adopted into Kubernetes, we plan to support them. However, the present stable version of Kubernetes Secrets only Base64-encodes data.

Our present recommendation is for Brigade developers to fetch the secret directly from a trusted key store such as Vault. See the example below.

Alternatively, you could use secretKeyRef to reference existing secrets already in your Kubernetes cluster. See the example below.

I don’t want to use Helm to manage my project/secrets. Can I do it manually?

Yes. Helm is there to make your life easier, but you can manage project secrets manually. You cannot, however, make manual modifications and then go back to using Helm. Doing so may result in lost data.

Examples


Fetching secrets from a remote store

Here is an example where we fetch secrets from a remote store (Azure Key Vault) for use by one or more Jobs.

To do so, we first run a Job to authenticate with the remote store, fetch all needed secrets and save these secret values to a shared directory. Then, we run a Job that consumes these values.

Here’s what our brigade.js file looks like:

const { events, Job, Group } = require("brigadier");

const sharedMountPrefix = `/mnt/brigade/share`;
const secrets = [
  "foo",
  "bar"
]

events.on("exec", (event, project) => {
  // Create Job to fetch secrets
  secretfetcher = new Job("secretfetcher", "microsoft/azure-cli:latest");
  secretfetcher.storage.enabled = true;

  // Login as an Azure Service Principal
  secretfetcher.tasks.push(
    `az login --service-principal \
    -u ${project.secrets.spID} \
    -p '${project.secrets.spPW}' \
    --tenant ${project.secrets.spTenant}`)

  // Fetch all secrets from the Azure Key Vault
  for (i in secrets) {
    secretfetcher.tasks.push(
      `az keyvault secret show --vault-name ${project.secrets.keyvault} \
      -n ${secrets[i]} --query value > ${sharedMountPrefix}/${secrets[i]}`);
  }

  // Create Job to consume secrets
  secretconsumer = new Job("secretconsumer", "alpine");
  secretconsumer.storage.enabled = true;

  // Consume all secrets
  for (i in secrets) {
    secretconsumer.tasks.push(
      `ls -haltr ${sharedMountPrefix}/${secrets[i]}`);
  }

  // Run jobs sequentially
  Group.runEach([secretfetcher, secretconsumer]);
})

To run this example, we replace BRIGADE_PROJECT_NAME with our Brigade project name and run:

brig run BRIGADE_PROJECT_NAME -f brigade.js

When we check the logs of the secretconsumer job pod, we see:

 $ kubectl logs secretconsumer-01d23ch2n6d1847ys6x48x1rez
-rwxrwxrwx    1 root     root           6 Jan 25 20:52 /mnt/brigade/share/foo
-rwxrwxrwx    1 root     root           6 Jan 25 20:52 /mnt/brigade/share/bar

Using Kubernetes secrets

Kubernetes secrets can also be used for consuming secret data.

For this example, we first create the Kubernetes secret:

kubectl create secret generic mysecret --from-literal=hello=world

This Kubernetes Secret has a name of mysecret and a single key-value pair as its data (key: hello, value: world).

Then, we create a brigade.js file with the following contents:

const { events, Job } = require("brigadier");

events.on("exec", () => {
  var job = new Job("echo", "alpine");

  job.env = {
    mySecretReference: {
      secretKeyRef: {
        name: "mysecret",
        key: "hello"
      }
    }
  };

  job.tasks = [
    "echo hello ${mySecretReference}"
  ];

  job.run();
});

To recap, mySecretReference is the name of the variable in the job’s environment, secretKeyRef.name is the name of the Kubernetes secret, and secretKeyRef.key is the key pointing to the actual secret value (world in this example.)

As a result, mySecretReference points directly to this secret value in this job’s environment.

To run this example, we replace BRIGADE_PROJECT_NAME with our Brigade project name and run:

brig run BRIGADE_PROJECT_NAME -f brigade.js

We will then see "hello world" displayed in the logs.