Integrate CircleCI with HashiCorp Vault using OIDC
Solutions Engineer
Well-designed secrets management is a delicate balancing act between security and usability. Secrets must be easily accessible to the right users when building and deploying, but they must also be well-secured and easy to rotate. This article will cover how to maintain this balance by integrating CircleCI with HashiCorp Vault and retrieving secrets using short-lived OpenID Connect (OIDC) authentication tokens.
Secrets management antipatterns
In the past, many teams chose usability over security. They embedded secrets in their CI/CD platform configuration or even checked them into version control, often in a “set and forget” way. This required very little effort and was often done as a temporary measure. But temporary measures tend to become permanent, and these secrets often remained in place far longer than intended. They became a potential attack vector – how quickly could they be rotated if source code were leaked or an engineer left the company? Even on teams with regular manual secret rotation schedules, rotations were often late, incomplete, or simply never happened at all. Though the secrets were conveniently accessible, the company’s security posture was dangerously compromised.
Read more: The Path to Platform Engineering.
At the other end of the spectrum, some teams locked down all access to secrets behind password managers that could only be accessed manually or manual MFA for every interaction with a service. Not only did these inconvenient solutions slow down development speed, but they could actually weaken> security. Many engineers simply copied credentials locally or used backdoor accounts to circumvent the cumbersome official process. Though secrets were in theory quite secure, in practice they were often illicitly stored in insecure locations and developer agility was hindered.
OIDC authentication offers a middle way between these two extremes that preserves both usability and platform security. CircleCI’s OIDC support allows developers to use ephemeral authentication tokens in their build, test, and deploy jobs, eliminating the risk inherent in long-lived credentials. Implementing OIDC enhances security and reduces friction across a project’s entire CI/CD pipeline, leading to faster and more efficient development. We’ve covered the basics of OIDC and how to authenticate with AWS and Google Cloud using CircleCI OIDC tokens in a previous blog post. In this article we’ll cover how to use OIDC to authenticate with HashiCorp Vault.
What is HashiCorp Vault?
HashiCorp Vault is an identity-based secrets and encryption management system. As technical organizations move towards GitOps and Infrastructure as Code (IaC) practices, they often run into the difficult question of how to securely store and access information that is too sensitive to check in to version control and too widely-used to leave locked up completely. Vault enables developers and platform engineers to conveniently store, access, and rotate secrets in a secure, centralized system. Using OIDC to authenticate from CircleCI to Vault adds a further layer of security because your engineers no longer need to store long-lived credentials outside of Vault itself.
In this article, we will cover how to authenticate to your existing Vault cluster using a CircleCI’s OIDC token.
Prerequisites
You will need a few things before you get started:
- A HashiCorp Vault instance
- A CircleCI account
- A GitHub, GitLab, or Bitbucket project that has been set up in CircleCI
- The CircleCI CLI installed on your workstation
From the CircleCI UI gather this information:
- Your organization Name and ID: In the CircleCI web app, go to Organization Settings > Overview to find both.
- Your project ID: In the CircleCI web app, go to your project’s page. Click Project Settings > Overview.
- A personal access token: Learn how to create one here.
Note: CircleCI must be able to connect to your Vault instance. If the instance is publicly accessible, you can restrict access by whitelisting CircleCI’s IP ranges so that only CircleCI machines can send traffic. If the instance is on a private network, you could run your Vault authentication job on a CircleCI Runner inside the private network.
Step 1: Configuring Vault
You will need to enable the JWT authentication method in your Vault instance. Log into Vault and enable the JWT auth method:
export VAULT_ADDR="your vault instance address"
export VAULT_TOKEN="your vault token"
vault login $VAULT_TOKEN
vault auth enable jwt
Next, configure the JWT auth method to accept OIDC tokens from your CircleCI organization:
vault write auth/jwt/config \
oidc_discovery_url="https://oidc.circleci.com/org/<your org id>" \
bound_issuer="https://oidc.circleci.com/org/<your org id>"
Verify the config with this command:
curl --header "X-Vault-Token: $VAULT_TOKEN" "$VAULT_ADDR/v1/auth/jwt/config" | jq
Next, create a Vault policy for your Vault role. For this tutorial, you will grant CircleCI read access to all secrets under the path secret/circleci-demo/:
vault policy write circleci-demo-policy - <<EOF
# Grant CircleCI project <your project name> RO access to secrets under the 'secret/data/circleci-demo/*' path
path "secret/data/circleci-demo/*" {
capabilities = ["read", "list"]
}
EOF
Verify the policy creation:
vault policy read circleci-demo-policy
Create a Vault role under the JWT auth method named circleci-demo for CircleCI to assume. The attributes of this role tell Vault where the OIDC token will come from, what permissions should be granted to the token bearer, and any additional claims that should be included in the token. These additional claims can be used in the Vault role’s bound_claims field to restrict role access to specific projects and/or jobs with access to specific contexts.
In this tutorial, you will use the project_id additional claim to restrict access to only your tutorial project. You can find more detail on CircleCI OIDC token claims in our OIDC documentation.
Run:
vault write auth/jwt/role/circleci-demo -<<EOF
{
"role_type": "jwt",
"user_claim": "sub",
"user_claim_json_pointer": "true",
"bound_claims": {
"oidc.circleci.com/project-id": "<your project id>"
},
"policies": ["default", "circleci-demo-policy"],
"ttl": "10m"
}
EOF
Verify the role creation with this command:
vault read auth/jwt/role/circleci-demo
Now, create some secrets to access from CircleCI.
Enable a new secrets engine mounted at secret/:
vault secrets enable -version=2 -path=secret kv
Next, create some secrets:
vault kv put secret/circleci-demo/demo-secrets \
username="paul.atreides" \
password="lis@n-al-g4ib"
Verify that the new secrets were created with this command:
vault kv get secret/circleci-demo/demo-secrets
Step 2: Configuring CircleCI
To configure CircleCI you need to:
- Create a CircleCI Context for Vault Connection Secrets
- Write a CircleCI config that uses OIDC to authenticate with Vault
- Create a templated Vault agent config file
- Create a Consul template
Creating a CircleCI Context for Vault Connection Secrets
Instead of checking your Vault endpoint and role name into version control where they could be leaked, you can store them securely in a CircleCI context. In this tutorial, you will create and populate a context using the CircleCI CLI.
Note: If you are using a CircleCI standalone org (e.g. if you are using GitLab as your VCS), you will need to create a context and populate the variables using the CircleCI UI instead of the CLI.
To set up the CircleCI CLI, run this command and enter your personal access token when prompted:
circleci setup
Your VCS should be either gh (Github) or bb (Bitbucket). Create a context named circleci-vault-demo using this command:
export CIRCLE_VCS=<your vcs>
export CIRCLE_ORG_NAME=<your org name>
circleci context create $CIRCLE_VCS $CIRCLE_ORG_NAME circleci-vault-demo
Verify that the new context was created:
circleci context list $CIRCLE_VCS $CIRCLE_ORG_NAME
Next, add the Vault connection secrets to your new context using the commands below. You will be prompted to enter the secret after each command, so be sure to have the values ready. Example values are shown in the table below.
circleci context store-secret $CIRCLE_VCS $CIRCLE_ORG_NAME circleci-vault-demo VAULT_ADDR
circleci context store-secret $CIRCLE_VCS $CIRCLE_ORG_NAME circleci-vault-demo VAULT_ROLE
| Context variable name | Value |
|---|---|
VAULT_ADDR |
URL of your vault instance, including port (e.g. https://vault.example.com:8200) |
VAULT_ROLE |
Vault role that CircleCI will assume (this will be circleci-demo if you followed the steps above) |
Verify that the secrets have been created with this command:
circleci context show $CIRCLE_VCS $CIRCLE_ORG_NAME circleci-vault-demo
Write a CircleCI config that uses OIDC to authenticate with Vault
Now we’ll write a CircleCI config file that uses a CircleCI OIDC token to authenticate with Vault, uses Vault’s auto-auth functionality to retrieve the secrets you created, and then finally exports them as environment variables for use within a CircleCI job.
In the repository you created for this tutorial, create a file at .circleci/config.yml and add the code below.
version: 2.1
commands:
install-vault:
steps:
- run:
name: Install Vault and prereqs
command: |
vault -h && exit 0 || echo "Installing vault"
# only runs if vault command above fails
cd /tmp
wget https://releases.hashicorp.com/vault/1.21.0/vault_1.21.0_linux_amd64.zip
unzip vault_1.21.0_linux_amd64.zip
sudo mv vault /usr/local/bin
vault -h
vault-auto-auth:
description: "Use Vault auto auth to load secrets"
steps:
- run:
name: Auto-authenticate with Vault
command: |
# Write the CircleCI provided value to a file read by Vault
echo $CIRCLE_OIDC_TOKEN > .circleci/vault/token.json
# Substitute the env vars in our context to render the Vault config file
sudo apt update && sudo apt install -y gettext-base
envsubst < .circleci/vault/agent.hcl.tpl > .circleci/vault/agent.hcl
# This config indicates which secrets to collect and how to authenticate
vault agent -config=.circleci/vault/agent.hcl
- run:
name: Set Environment Variables from Vault
command: |
# In order to properly expose values in Environment, we _source_ the shell values written by agent
source .circleci/vault/setenv
jobs:
setup-vault-and-load-secrets:
docker:
- image: cimg/base:2025.10
steps:
- checkout
- install-vault
- vault-auto-auth
- run:
name: Use secrets retrieved from Vault in a subsequent step
command: |
echo "Username is $SECRET_DEMO_USERNAME, password is $SECRET_DEMO_PASSWORD"
workflows:
vault:
jobs:
- setup-vault-and-load-secrets:
context:
- circleci-vault-demo
# VS Code Extension Version: 1.5.1
Next, create a templated Vault agent config file at .circleci/vault/agent.hcl.tpl. This file tells the Vault agent which Vault server to connect to and how it should authenticate. Enter this code into the file:
pid_file = "./pidfile"
exit_after_auth = true
vault {
address = "${VAULT_ADDR}"
retry {
num_retries = -1
}
}
auto_auth {
method "jwt" {
exit_on_err = true
config = {
role = "${VAULT_ROLE}"
path = ".circleci/vault/token.json"
remove_jwt_after_reading = false
}
}
sink "file" {
config = {
path = "/tmp/vault-token"
}
}
}
template_config {
exit_on_retry_failure = true
}
template {
source = ".circleci/vault/secrets.ctmpl"
destination = ".circleci/vault/setenv"
}
Now, create a Consul template that will tell the Vault agent what to do with the secrets it retrieves. In this tutorial, you can export them as environment variables to use in your pipeline. Create a file at .circleci/vault/secrets.ctmpl and enter this code:
# Export the secrets as env vars for use in this job
# Also writes them to $BASH_ENV so that they'll be available as env vars in subsequent jobs for this pipeline
{{ with secret "secret/circleci-demo/demo-secrets" }}
export SECRET_DEMO_USERNAME="{{ .Data.data.username }}"
export SECRET_DEMO_PASSWORD="{{ .Data.data.password }}"
echo "export SECRET_DEMO_USERNAME=\"{{ .Data.data.username }}\"" >> $BASH_ENV
echo "export SECRET_DEMO_PASSWORD=\"{{ .Data.data.password }}\"" >> $BASH_ENV
{{ end }}
Push your changes. This will trigger a pipeline in CircleCI. From your local repo directory, run:
circleci open
This will open your project in the CircleCI webapp. If everything has been configured correctly, you the final step will print the secrets that were retrieved from Vault.
Conclusion
In this article, you learned how to use OIDC authentication to securely store and access secrets in HashiCorp Vault from CircleCI. OIDC authentication eliminates the need to store long-lived credentials outside of a secure system, strengthening your organization’s security posture without hindering development speed. To learn more about OIDC authentication on CircleCI, visit our documentation or check out the following articles: