Build a Secure CI/CD Pipeline for Kubernetes Using GitHub Actions and Hashicorp Vault

In this tutorial, you will learn how to use Hashicorp's Vaultarrow-up-right to build CI/CD pipelines in GitHub Actionsarrow-up-right 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 Vault GitHub actionarrow-up-right gives the ability to pull secrets from Vault in GitHub Action workflows. This tutorial uses Helmarrow-up-right to manage the Kubernetes application.

Objectives:

In this tutorial, you will go through the following steps:

1

Store sensitive pipeline secrets on Vault.

2

Create Vault token and Vault policy used by GitHub Actions to authenticate to Vault.

3

Configure GitHub Actions to use secrets stored in Vault and orchestrate K8s deployments.

4

Once GitHub Action workflow is configured, trigger the Action and test if the Action works as desired.

5

Troubleshoot common issues and errors encountered while using Vault and GitHub Actions.

Prerequisites

Before starting this tutorial, you will need:

chevron-rightNeed a new cluster?hashtag

Try our guide <link> to running confidential K8s cluster using enclaive Dynemees.


Getting Started

Store secrets on Vault

circle-exclamation

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.

Secret Path
Key
Description

token

registry_token

GitHub Personal Access Tokenarrow-up-right to access GitHub Container Registryarrow-up-right for pushing and pulling Docker images. This will be passed to the GitHub Action to push images to GCR.

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.

chevron-rightCreating GitHub Personal Access Tokenhashtag

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 these stepsarrow-up-right. Make sure to copy your GitHub Personal Access Token so you can store it in Vault.

  1. Create the registry_token key-value secret at secret/token path using Vault KV Secrets Enginearrow-up-right .

  1. Similarly, create the kubeconfig key-value secret at secret/config path.

triangle-exclamation
  1. Verify the secrets were created.

  1. 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.

  1. Export an environment variable GITHUB_REPO_TOKEN to capture the token value created using vault token createarrow-up-right with the ci-secret-reader (policy name) policy attached. You will assign this token to the GitHub Repository in the next section.

Authenticate GitHub to access Vault

In this step, you will be using a Vault tokenarrow-up-right and store it as a GitHub repository secretarrow-up-right for GitHub Action to authenticate to Vault.

circle-info

More auth methods

The Vault GitHub Action supports several different authenticationarrow-up-right methods.

  1. To create a repository secret in your GitHub repository, follow these stepsarrow-up-right.

  2. In the Name field of the repository secret, type VAULT_TOKEN .

circle-info

You will use the repository secret defined here in the GitHub Actions Workflow so make sure you are using the same name in your GitHub Actions Workflow as in the repository secret.

  1. 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.

Configure GitHub Actions

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:

circle-info

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 GitHub-Hosted runnerarrow-up-right.

The above workflow contains 2 jobs:

chevron-rightbuild jobhashtag

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 Docker login-actionarrow-up-right. 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).

You created a GitHub PAT earlier and stored it as registry_token secret in Vault. This job will use that secret, stored in Vault, to login to GHCR and push images to it.

chevron-rightdeploy jobhashtag

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 Azure k8s-set-contextarrow-up-right action for setting context and retrieving Kubeconfig before deploying to K8s.

The sample app uses Helm to apply K8s manifests declaratively to the K8s cluster. Helm chart will deploy a Deployment (the NodeJS app container) and a LoadBalancer Service to expose the NodeJS application.

Similar to the build job, thedeploy job also uses the registry_token secret in Vault to retrieve the Docker image stored in GHCR.

For the GitHub Action to extract secrets from Vault, you will use the vault-actionarrow-up-right step. In the sample GHA workflow, the step is configured as follows:

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.

Test the CI/CD pipeline

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.

Trigger the GitHub Action

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:

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 troubleshooting section.

Verifying Deployment to Kubernetes

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

circle-exclamation

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.

Troubleshooting

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.

  • Vault is unsealed. Read morearrow-up-right about sealing/unsealing Vault.

  • Secrets are defined in the right path in Vault. Read morearrow-up-right about KV Secret Engine API.

  • Check for the correctness of Vault token, GitHub Personal Access Token and your Kubeconfig.

Conclusion

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.

Last updated