Automate workflows with Vault GitHub actions
GitHub Actions enable you automate workflows for your GitHub hosted repositories. The workflows that build, test, and deploy your code may require secrets to achieve their goal. The Vault GitHub action gives you the ability to pull secrets from Vault.
Challenge
A GitHub repository maintains a web application that requires a Docker image. The image requires the injection of secrets that you have stored in Vault. The process is typically handled either manually, or through a continuous integration (CI) service. The process requires human intervention and does not offer rich feedback alongside the GitHub repository.
Solution
Define a GitHub workflow within your repository and request the required secrets with Vault GitHub actions.
Prerequisites
This tutorial requires Vault, git, Docker, a GitHub account, and the sample web application.
Retrieve the web application and additional configuration by cloning the hashicorp-education/learn-github-actions repository from GitHub.
$ git clone https://github.com/hashicorp-education/learn-vault-github-actions
This repository holds supporting content for Vault learn tutorials. You can find the content for this hands on lab in a subdirectory.
Change into the learn-vault-github-actions/vault-github-action
directory.
$ cd learn-vault-github-actions/vault-github-action
Working directory
You should execute commands for the rest of the tutorial from this directory.
Create Docker image
Create a Docker image for the application and label it
vault-action-exampleapp
.
$ docker build . --file Dockerfile -t vault-action-exampleapp
Docker builds the image and pushes it to your local Docker repository.
During image creation a default application secret gets stored in the
application root, in a file named app_secret
.
View the contents of the app_secret
file in the Docker image.
$ docker run vault-action-exampleapp /bin/bash -c "cat ./app_secret"
UNSET_SECRET_PLEASE_OVERRIDE
The contents of the file show that the file used the default value during creation. Now, you are ready to automate the image build process while overriding the secret.
Start Vault
Vault can manage the secrets required for this application. A Vault server run in development mode automatically initializes, unseals, and enables the key-value secrets engine.
In another terminal, start a Vault dev server with
root
as the root token.$ vault server -dev -dev-root-token-id root
The Vault dev server defaults to running at
127.0.0.1:8200
. A dev mode server automatically initializes and unseals itself at startup.Insecure operation
Do not run a Vault dev server in production. You use a dev mode server in this tutorial just to simplify the unsealing process for this hands on lab.
Export an environment variable for the
vault
CLI to address the Vault server.$ export VAULT_ADDR=http://127.0.0.1:8200
Export an environment variable for the
vault
CLI to authenticate with the Vault server.$ export VAULT_TOKEN=root
The Vault server is ready to have a secret added.
Create a secret
The GitHub workflow deployed later reads a secret defined at secret/data/ci
.
You need to create this secret, a policy defined to access the secret, and a
token generated to retrieve the secret.
Create the secret at the path
secret/ci
with anapp_secret
key.$ vault kv put secret/ci app_secret=SecretProvidedByVault
Verify that the secret exists at the path
secret/ci
.$ vault kv get secret/ci
You created the secret.
Write a policy that grants the read capability for the secret path.
$ vault policy write ci-secret-reader - <<EOF path "secret/data/ci" { capabilities = ["read"] } EOF
You wrote the policy, and can now specify its attachment to the token you create next.
Export an environment variable
GITHUB_REPO_TOKEN
to capture the token value created with theci-secret-reader
policy attached.$ GITHUB_REPO_TOKEN=$(vault token create -policy=ci-secret-reader -format json | jq -r ".auth.client_token")
Retrieve the secret at the path using the
GITHUB_REPO_TOKEN
.$ VAULT_TOKEN=$GITHUB_REPO_TOKEN vault kv get secret/ci ====== Metadata ====== Key Value --- ----- created_time 2020-11-06T20:53:00.845238Z deletion_time n/a destroyed false version 1 ======== Data ======== Key Value --- ----- app_secret SecretProvidedByVault
You retrieved the secret using the token.
This token gets assigned to the GitHub repository in the next section.
Setup the GitHub self-hosted runner
You can define GitHub actions for a repository. Create a new repository associated with your user account or a GitHub organization.
Create a GitHub repository
Initialize the current directory as a git repository
$ git init
Stage all files to commit.
$ git add .
Commit all staged files.
$ git commit -m "Initial Commit"
On GitHub, in the upper-right corner of any page, use the + (plus) drop-down menu, and select New repository.
Name your repository "vault-action-exampleapp"
Click Create repository.
The view changes to show you the main page of the repository.
Follow the instructions from the …or push an existing repository from the command line section. Return to the terminal and paste those commands.
$ git remote add origin https://github.com/<YOUR_ACCOUNT_OR_ORG>/vault-action-exampleapp $ git branch -M main $ git push -u origin main
Setup Vault auth credentials with the repository
The GitHub self-hosted runner requires a token for it to authenticate with the Vault server. This token value gets defined as a repository secret.
More auth methods
The Vault GitHub Action supports several different authentication methods.
On GitHub, navigate to the main page of the repository.
Under your repository name, click Settings.
In the left sidebar, click Secrets.
Click New secret.
Enter the name
VAULT_TOKEN
for your secret in the Name input box.From the terminal, copy the token stored in the variable
GITHUB_REPO_TOKEN
$ echo $GITHUB_REPO_TOKEN | pbcopy
Paste the token as the value for the secret.
Click Add secret.
The view returns to the secrets index and displays the new secret in the list of secrets.
You configured the GitHub repository with a token that is valid, and capable of reading the secret from the Vault server.
Setup the GitHub self-hosted runner
The GitHub self-hosted runner enables you to start a runner instance on an instance that you manage. You can use your workstation if it's supported.
On GitHub, navigate to the main page of the repository.
Under your repository name, click Settings.
In the left sidebar, click Actions.
Under "Self-hosted runners," click Add runner.
Select the operating system and architecture of your self-hosted runner machine.
Follow the instructions in the Download section.
This prepares a directory for the GitHub runner and then downloads the runner.
Warning
You can download and extract the runner within this tutorial directory, but you should not check this code into your repository source as it can contain sensitive information.
Follow the instructions in the Configure section.
This configures the runner to connect to GitHub with a token it generates for the runner.
Define a workflow for the GitHub action
GitHub actions express the operations that they carry out through workflows. These workflows can trigger based on different events that take place during the lifecycle of the source code in the repository. GitHub actions automatically create workflows when it detects a configuration file within the repository.
In a terminal, within the repository, create the directory
.github/workflows
.$ mkdir -p .github/workflows
Create a workflow file named
image-builder.yml
within that directory that defines the name of the workflow and the trigger frequency.$ tee .github/workflows/image-builder.yml <<EOF name: ImageBuilder # Run this workflow every time a new commit pushed to your repository on: push EOF
The name of the Workflow determines how it will appear in the repository's actions interface. The
on
specifies that this workflow takes action when any time that the repository receives a new commit on any branch.This image builder workflow needs to:
Check out the source code
Import the secret from Vault
Build the image with the secret
A workflow defines one or more jobs. A job sets up an environment, and describes a list of steps necessary to complete an operation for the workflow.
Add a self-hosted runner job to the workflow named
build
.$ tee -a .github/workflows/image-builder.yml <<EOF jobs: build: runs-on: self-hosted steps: EOF
The
build
job may define one or more steps to complete the work of thebuild
job. The first operation for the job is to checkout the source code of the repository.Add the checkout step to the
build
job.$ tee -a .github/workflows/image-builder.yml <<EOF - uses: actions/checkout@v3 EOF
You define the steps in a YAML array. This step uses the core GitHub checkout action. After the source code check out, the job fetches secrets from the Vault server for the build step.
Add a step, named
Import Secrets
, that uses the Vault GitHub action.$ tee -a .github/workflows/image-builder.yml <<EOF - name: Import Secrets uses: hashicorp/vault-action@v2 with: url: http://127.0.0.1:8200 tlsSkipVerify: true token: \${{ secrets.VAULT_TOKEN }} secrets: | secret/data/ci app_secret EOF
This step defines its name as "Import Secrets" overriding the default name provided by the
hashicorp/vault-action@v2
step. The configuration for this step communicates with the local Vault server running in dev mode. Thetoken
used to authenticate is theVAULT_TOKEN
secret value you defined in the GitHub repository.The
secrets
section of this step defines the path of the secret,secret/data/ci
, and the key to extract that secretapp_secret
. By default, this secret value gets exported to the environment variable,APP_SECRET
, that is useful to the steps that follow this one.Add the build step to perform the Docker image build operation.
$ tee -a .github/workflows/image-builder.yml <<EOF - name: Build Docker Image run: docker build . --file Dockerfile --build-arg app_secret="\${{ env.APP_SECRET }}" -t vault-action-exampleapp EOF
This step builds a Docker image from the Dockerfile
in the repository. During the
build, the build_arg app_secret
gets populated with the secret imported
from Vault, and the Docker image stored as vault-action-exampleapp
in the local
Docker registry.
Trigger the GitHub runner
The workflow triggers on every push to any branch of this repository.
Add the unstaged files.
$ git add .
Commit the staged changes.
$ git commit -m "adds workflow to repo"
Push these changes to the remote repository.
$ git push origin main
The GitHub self-hosted runner polls GitHub for changes, and executes the runner upon detecting changes.
On GitHub, navigate to the main page of the repository.
Under your repository name, click Actions.
Under "All workflows" click the result adds workflow to repo.
This view displays the execution of the workflow for this commit.
Under the name of the workflow, click the build job.
This view displays the steps that took place for this job. Each step can be expanded to see progress, results, and errors.
Docker builds the image and stores it in the local Docker registry. The Docker image created a file named
app_secret
within the working directory and populated it with the Vault secret.View the contents of the
app_secret
file in the Docker image.$ docker run vault-action-exampleapp /bin/bash -c "cat ./app_secret" SecretProvidedByVault
The results display the Vault secret defined at the path
secret/data/ci
. The application, and other services within this Docker image can use the secret.
Next steps
You started Vault, created a secret, and a token with a policy to retrieve that secret. Learn more about creating versioned key/value secrets and policies.
You set up a GitHub action with a self-hosted runner that triggered when you pushed a commit. Learn more about defining GitHub actions and the Vault GitHub action.
The workflow accomplished a small task of creating a Docker image. Watch Vault engineers deploy an example app into a Kubernetes cluster running on AWS.