Build a Secure CI/CD Pipeline for Kubernetes Using GitHub Actions and Hashicorp Vault
Last updated
Last updated
In this tutorial, you will learn how to use Hashicorp's to build CI/CD pipelines in to securely build, push, and deploy Docker images to Kubernetes.
You will see how Vault can be used to securely store pipeline secrets like access tokens, API keys, etc. You will then configure a CI/CD pipeline using GitHub Actions to access these secrets stored in Vault and deploy the changes to a Kubernetes cluster. The gives the ability to pull secrets from Vault in GitHub Action workflows. This tutorial uses to manage the Kubernetes application.
In this tutorial, you will go through the following steps:
Before starting this tutorial, you will need:
The GitHub Actions workflow deployed later reads 2 secrets defined at secret/data/<secret_path>
. You need to create these Vault key-value secrets, a Vault policy defined to access the secrets, and a Vault token to retrieve the secrets.
token
registry_token
config
kubeconfig
Contents of your Kubeconfig - containing connection details of an existing K8s cluster. This will be passed to the GitHub Action to connect to K8s cluster.
Similarly, create the kubeconfig
key-value secret at secret/config
path.
Avoid Kubeconfig configuration with credentials belonging to a cluster-admin
user as this would allow a compromised workflow to perform any action on your cluster.
Verify the secrets were created.
In the sample repository, you will find policy.hcl
file. You will use this file to create a new Vault policy. This policy grants read capability for paths where the secrets are stored. The policy will be applied to the auth token used by GitHub Action to access the secrets stored in Vault.
In the Name field of the repository secret, type VAULT_TOKEN
.
From your terminal, copy the token value stored in the GITHUB_REPO_TOKEN
variable created earlier and paste that as the value for the VAULT_TOKEN
secret. Then click Add Secret
.
Now you have configured a GitHub repository with a valid token that can read secrets from Vault server.
In the sample repository, you will find .github/workflows/kubernetes.yaml
file which defines a GitHub Action workflow. Below are the contents of the file:
The above workflow contains 2 jobs:
The Import Secrets
step is used in both build
and deploy
jobs as both the jobs require access to secrets stored in Vault. The example step is authenticating GitHub to access Vault running locally at http://127.0.0.1:8200
. You can change this to wherever your vault is running. For example:
Once GitHub is authenticated to Vault, you can fetch secrets from the Vault server. The secrets
section of the Import Secrets
step defines the path where the secrets are stored ( secret/data/token
, secret/data/config
) and the key to extract the secrets - registry_token
and kubeconfig
. By default, this secret value gets exported to GitHub environment variables, REGISTRY_TOKEN
and KUBECONFIG
, that is useful to the steps that follow.
Once the workflow has been configured, you can now test the pipeline by performing a source code change as the GitHub Action is configured to run every time a push is made to the main
branch.
The first step to test the CI/CD pipeline is triggering the GitHub Action. To trigger the GitHub Action, edit the main.js
file. Delete the old contents from main.js
and paste the below contents:
Next, commit your changes, and then push them to GitHub:
a. Stage all modified file:
b. Commit the changes to the local repository:
c. Push the changes to GitHub:
You will use kubectl
to verify the Deployment
and the LoadBalancer Service
have been deployed and are ready to receive traffic.
a. Check K8s Deployments
b. Check K8s LoadBalancer Service and obtain the external-IP
The sample app used Helm to dynamically describe name and namespace for the Deployment and the LoadBalancer Service. Make sure to use the name of your GitHub repository where indicated.
Send a curl request to the external IP to verify your app has been successfully deployed:
You can make another change to your repository which will trigger the GitHub Action. A new run of the GitHub Action will start and deploy the changes to your K8s cluster.
If your GitHub Action run is failing, below are a few things to check:
Vault is up and running at the address provided in the GitHub Action workflow.
Check for the correctness of Vault token, GitHub Personal Access Token and your Kubeconfig.
One common challenge with GitHub Actions is managing pipeline secrets securely, especially when workflows need access to sensitive data for continuous deployment. In this tutorial, you configured a secured CI/CD pipeline using GitHub Actions and Vault to deploy securely to Kubernetes. Using Vault to store sensitive secrets enhances security while ensuring workflows have the access they need to perform tasks effectively.
Before you are able to store/retrieve Vault secrets, make sure your Vault is unsealed. Find out more about sealing/unsealing Vault .
to access for pushing and pulling Docker images. This will be passed to the GitHub Action to push images to GCR.
You will need a GitHub Personal Access Token, with write:packages
scope, which will be passed to the GitHub Action. To create a GitHub Personal Access token, follow . Make sure to copy your GitHub Personal Access Token so you can store it in Vault.
Create the registry_token
key-value secret at secret/token
path using .
Export an environment variable GITHUB_REPO_TOKEN
to capture the token value created using with the ci-secret-reader
(policy name) policy attached. You will assign this token to the GitHub Repository in the next section.
In this step, you will be using a and store it as a for GitHub Action to authenticate to Vault.
The Vault GitHub Action supports several different methods.
To create a repository secret in your GitHub repository, follow .
This GitHub Action is running on a self-hosted runner, indicated by runs-on: self-hosted
on line 8. You can alternatively also use a .
This job builds and pushes the sample app's Docker image to GitHub Container Registry. To push the Docker image to GHCR, you will use . This action requires username and a password to log-in to a container registry (the example uses GHCR, but you ca use any other container registry). For password you will need a GitHub Personal Access Token (PAT).
This job deploys the sample NodeJS app to the Kubernetes cluster defined in the Kubeconfig passed to the kubeconfig
secret key in Vault. This job uses action for setting context and retrieving Kubeconfig before deploying to K8s.
For the GitHub Action to extract secrets from Vault, you will use the step. In the sample GHA workflow, the step is configured as follows:
The GitHub-hosted or self-hosted runner polls GitHub for changes, and executes the runner upon detecting changes. Navigate to GitHub's Actions tab and you'll see a new workflow run beginning. After a few seconds, it should show as successfully completed. If your action has failed, refer to the .
Vault is unsealed. about sealing/unsealing Vault.
Secrets are defined in the right path in Vault. about KV Secret Engine API.