Retrieve Vault secrets from GitHub Actions
Author: Chris Zembower
This guide details the required configuration and steps necessary to integrate a GitHub Actions workflow with HCP Vault Dedicated or Vault Enterprise using the GitHub Actions OIDC provider as a source of identity for authentication. GitHub Actions is a CI/CD pipeline tool for building and deploying applications and infrastructure. Using GitHub Actions interaction with Vault ensures that pipelines use Vault in a reliable, scalable, and secure with minimal management overhead. You can configure the GitHub Actions integrations using the API, CLI, or through Terraform. Using Terraform lets you reliably deploy IaC (infrastructure as code) and enables version-controlled infrastructure changes..
You can deliver sensitive data to GitHub Action-based CI pipelines sourcing secrets from GitHub Action secrets, environment variables, or integration with an external secrets management system. Vault centralizes secret management, is fully auditable, and has capabilities that enhance identity-based data security.
The GitHub OIDC provider can securely obtain secrets from Vault by permitting the unique identification of pipeline persona in a way that is wholly congruent with a Vault authentication construct (JWT/OIDC). If you cannot use the GitHub OIDC provider, you can use Vault Secrets Sync to deliver secrets to GitHub Actions. Currently, Secrets Sync only supports KV (static) secrets.
By integrating GitHub Action with workload identity, you gain the following benefits:
- Eliminate static credentials: Vault delivers tokens just-in-time and expire after 60 minutes.
- Simplify credential management: Store secrets in a centralized management system.
- Use principle of least privilege: Grant the minimum access rights necessary to perform tasks.
- End-to-end audibility: Ability to audit the entire secrets management stack.
- Scalability: Support many pipeline identities with only a single Vault auth mount, if desired.
Target audience
This guide references the following roles:
- Platform operator: Someone who manages your organization's platform. This may include Security and Platform teams. This is an all-encompassing role that may include the responsibilities of the Vault administrator and producer.
- For this specific guide, the platform team is also responsible for managing and configuring GitHub and GitHub Actions.
- Vault administrator: Someone who has access to configure Vault secret engines.
- Vault consumer: Someone who has access to read Vault secrets, such as Application and DevOps engineers.
Prerequisites
To follow this guide, you will need the following permissions:
Platform operator:
- Privileged access to a Vault Enterprise or HCP Vault Dedicated cluster that grants permissions to create auth mounts, configure auth roles, and create policies.
- HCP Terraform workspace for managing Vault configurations
- Permissions to configure
hashicorp/vault-action@v2
GitHub Action - Permission to issue JWT tokens for repositories (id-token: 'write')
Vault consumer:
- GitHub account and repository
- Permissions to use
hashicorp/vault-action@v2
GitHub Action - Secrets stored in or managed by Vault that the CI pipeline can consume
In addition, you need a working knowledge of:
- Platform operator: OpenID Connect (OIDC), JWT claims and audiences
- Vault consumer: Vault, git, and GitHub Actions
Background and best practices
This section contains best practices for implementing GitHub Actions integration with Vault. You will need to understand these concepts and best practices to ensure secure, efficient, and scalable management of secrets.
People and process considerations
When security teams adopt Vault to centralize secrets management, they simplify audit and governance processes and empower application teams to consume sensitive data in alignment with DevOps practices, meeting increasingly stringent compliance requirements. By extending Vault access to GitHub Actions, CI pipelines can use a variety of secrets engines and capabilities while adhering to organizational and data protection policies.
It is critical to recognize the importance of secrets management in CI, and not expect that practitioners will require access only to static secret key/value systems such as GitHub Actions secrets. Not only are the data types limited with this solution, but secret sprawl and audit activities can become unwieldy and frustrating.
Vault GitHub Action simplifies Vault access for CI jobs by abstracting the underlying tasks needed to authenticate and consume secrets from Vault. For consumers, the configuration required for each workflow definition is reduced to a few lines of code, making it easy to secure pipelines.
The following are some roles that Security and Platform teams, and consumers need to be familiar with.
Security or platform team:
- Define JWT auth method roles with the narrowest possible scope to restrict access, ensuring that only authorized pipelines can retrieve application-specific secrets.
- Leverage Terraform for Vault management to maintain a system of record, establish accountability, promote configuration transparency, and streamline daily team operations.
- Align issued token and any dynamic secret lease TTLs with CI job durations to limit excessive Vault resource consumption, particularly when implementing these patterns at scale.
- Establish an onboarding process to ensure consistency and aid application teams as they adopt Vault for this use case.
Consumers:
- Avoid persisting Vault secrets in any remote storage or logging destinations.
- Work closely with the Security or platform teams to ensure that only necessary git branches, workflows, owners, and human actors are authorized to retrieve secrets.
- Know when to use dynamic secrets engines.
Understand Vault JWT auth method
It is helpful to understand how Vault's JWT auth method works before implementing the GitHub integration. Although not required, the following section will give you a high-level overview of how Vault’s JWT system will interact with GitHub.
Vault's JWT auth method is highly flexible and can authorize access based on a range of token parameters. Auth roles may be scoped narrowly or broadly, depending on the nature and sensitivity of the secrets.
For example, if a secret should be accessible to all workflows within a GitHub organization, the role configuration only needs to specify the repository owner. If specific repositories, workflows, or actors should only access specific secrets, you should secure them with granular role definitions.
Ideally, you should configure a Vault JWT auth role for each organization, repository, branch, and workflow name combination. To avoid the risk of accidental overlap with neighboring jobs, application teams should provide the platform team with the following information during the onboarding process:
- GitHub organization or owner name
- Repository name
- Authorized branch name(s)
- GitHub Action workflow name
The Vault administrator needs a policy that grants permission to create and manage authentication mounts, roles, and policies to configure the resources outlined in this document. A minimal policy for enabling these actions within a specific namespace should include the following capabilities:
## Manage auth mounts ##
path "sys/auth/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
## Configure auth methods ##
path "auth/+/config" {
capabilities = ["read", "update"]
}
## Manage auth roles ##
path "auth/+/role/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
## Manage policies ##
path "sys/policy/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
Inspect GitHub token
The GitHub Actions OIDC provider retrieves a short-lived token from Vault, regardless of the specific role definition. Vault's JWT auth method evaluates the token against the role name specified in the authentication request. GitHub Action can inspect a token using a basic workflow. While this is not a required step, it may be beneficial during initial planning and testing to inspect a token and identify appropriate constraints.
Note
Encoded JWTs pose a security risk under certain circumstances, especially when Vault or another service authorizes access based on tokens from a specific OIDC provider. We do not recommend exposing encoded tokens outside of your security boundaries. Decoded tokens, while not secrets, may be considered sensitive by your organization due to the descriptive nature of the claims, which may reveal repository names and other environmental metadata that could present various risks.
The following action logs the token returned by the GitHub OIDC provider, visible in the GitHub user web interface. You can decode this token using freely available tools such as jwt.io. You may notice that the signature is not validated unless you also provide the GitHub OIDC provider’s public key. Validation is not a necessary step to view the parsed token.
name: inspect-jwt
on:
push:
branches:
- main
paths-ignore:
- 'terraform/**'
- '*.mdjobs:
token-job:
permissions:
contents: 'read'
id-token: 'write'
runs-on: self-hosted
name: Run Step 1
steps:
- name: Get JWT
id: auth-token
run: |
export TOKEN=$(curl -sSL -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL")
echo $TOKEN | jq .value | base64
The following is the decoded output, allowing you to inspect claims:
{
"jti": "a1fb4d9f-cce9-43f3-9de0-82e88b6a585e",
"sub": "repo:hashicorp-services/gha-vault-jwt-demo:ref:refs/heads/main",
"aud": "https://github.com/hashicorp-services",
"ref": "refs/heads/main",
"sha": "49ea14fc8f4768562ff4901450004f31d697f4ce",
"repository": "hashicorp-services/gha-vault-jwt-demo",
"repository_owner": "hashicorp-services",
"repository_owner_id": "78600379",
"run_id": "10168595028",
"run_number": "57",
"run_attempt": "1",
"repository_visibility": "private",
"repository_id": "830662526",
"actor_id": "7572811",
"actor": "user123",
"workflow": "demo-jwt-auth-workflow",
"head_ref": "",
"base_ref": "",
"event_name": "push",
"ref_protected": "false",
"ref_type": "branch",
"workflow_ref": "hashicorp-services/gha-vault-jwt-demo/.github/workflows/action.yml@refs/heads/main",
"workflow_sha": "49ea14fc8f4768562ff4901450004f31d697f4ce",
"job_workflow_ref": "hashicorp-services/gha-vault-jwt-demo/.github/workflows/action.yml@refs/heads/main",
"job_workflow_sha": "49ea14fc8f4768562ff4901450004f31d697f4ce",
"runner_environment": "self-hosted",
"enterprise_id": "290",
"enterprise": "hashicorp",
"iss": "https://token.actions.githubusercontent.com",
"nbf": 1722367375,
"exp": 1722368275,
"iat": 1722367975
}
In the role configuration, you can use all of the claims in the token, whether standard or custom, to control pipeline access to secrets. Standard JWT claims are available as first-class parameters in the Vault role configuration. Vault also supports custom claims through a bound_claims map.
With the previous example token as a reference, a corresponding Terraform HCL role definition specifies the following constraints:
resource "vault_jwt_auth_backend_role" "app1_workflow1" {
backend = vault_jwt_auth_backend.this.path
role_name = "auth.jwt.app1.workflow1"
bound_audiences = ["https://github.com/hashicorp-services"]
bound_claims_type = "string"
bound_subject = "repo:hashicorp-services/gha-vault-jwt-demo:ref:refs/heads/main"
bound_claims = {
actor = "user123"
workflow = "demo-jwt-auth-workflow"
repository_visibility = "private"
runner_environment = "self-hosted"
repository_owner = "hashicorp-services"
}
user_claim = "sub"
role_type = "jwt"
token_ttl = 300
token_type = "service"
token_policies = ["app1-workflow1"]
}
Authentication flow
The following authentication flow diagram shows how the GitHub Action uses the GitHub OIDC provider and JWT Auth role to retrieve a token and access to secrets from Vault.
Vault only grants authorization if all token claims match the specified configuration, limiting access to a particular organization, repository, branch, workflow, and actor. This role is further protected by restricting Vault access to private repositories and self-hosted GitHub Action runners. In addition to satisfying the required claims, the GitHub Action OIDC discovery endpoint verifies the token signature. Once authorized, the pipeline client receives a Vault service token with a time-to-live value of 300 seconds and the app1-workflow1 policy attached.
This level of versatility enables the creation of roles that align with your organization's governance requirements. During the initial planning phase, it may prove helpful for the Vault team to gather example tokens from a representative cross-section of repositories and pipelines that reflect the diverse workflows within your organization. We recommend that security and Vault application teams standardize on sensible and appropriate claims to ensure the desired level of security and workload isolation.
Access Vault Secrets
To configure the workflow on the consuming side, application teams require the following information, which should be established and shared during onboarding:
- Vault address
- Vault namespace (if applicable)
- Vault JWT auth mount path (interpreted to
v1/auth/${path}/login
) - Vault role name
- Path to secrets
GitHub Actions allows the pipeline author to override the audience claim when requesting a new token from the GitHub OIDC provider. If the author wants a specific audience value, this must be coordinated with the Vault team so that they can configure the role correctly, and specify it in the workflow YAML using the jwtGitHubAudience parameter.
After role configuration is complete, the application team should test Vault access from a workflow using the hashicorp/vault-action@v2 GitHub Action. The following example tests Vault access using the hashicorp/vault-action@v2
GitHub Action:
name: demo-jwt-auth-workflow
on:
push:
branches:
- main
paths-ignore:
- 'terraform/**'
- '*.md'
jobs:
get-secret:
permissions:
contents: 'read'
id-token: 'write'
runs-on: self-hosted
name: Run Step 1
steps:
- name: Retrieve Vault Secret
id: import-secrets
uses: hashicorp/vault-action@v2
with:
url: ${{ env.VAULT_ADDR }}
namespace: ${{ env.VAULT_NAMESPACE }}
path: github/jwt
role: auth.jwt.app1.workflow1
method: jwt
secrets: |
kv/data/foo bar | SECRET_BAR ;
- name: Revoke Vault Token
if: always()
run: |
curl -X POST -sv -H "X-Vault-Token: ${{ env.VAULT_TOKEN }}" \
${{ env.VAULT_ADDR }}/v1/auth/token/revoke-self
The “Import Secrets” step in this example demonstrates storing the value of the key “bar” from the KV secret at the path “kv/data/foo” in the environment variable “SECRET_BAR”, which you can subsequently use in the workflow. At the end of the workflow, the Vault token is revoked.
Validated architecture
This validated architecture describes a GitHub Action pipeline requesting access to Vault to retrieve secrets. The GitHub Action uses the hashicorp/vault-action@v2
action and JWT to authenticate and retrieve secrets from Vault. This approach ensures automation, consistency, and security.
The diagram shows the following workflow:
- A platform operator configures Vault with a JWT auth mount, role, policy, and secrets engine(s) to support access by a GitHub Actions pipeline-based client.
- A GitHub Actions pipeline requiring access to Vault uses the official Vault GitHub Action hashicorp/vault-action@v2.
- The pipeline requests a signed JWT from the GitHub Actions OIDC provider.
- The pipeline uses the signed JWT to authenticate with Vault and retrieve secrets, making those secrets available to the pipeline for further work.
It's important to note that the JWT request is transparent and automatic when using the Vault GitHub Action.
Checklist
- Ensure the GitHub administrator enables access to the Vault GitHub Action and allows workflows to request ID tokens.
- Inspect example tokens from several application workflows to determine the preferred claims for Vault authorization.
- Design an efficient and effective onboarding process, leveraging automation if possible.
- Use Terraform to manage Vault configuration.
- Define JWT auth roles and Vault policies with the appropriate level of granularity.
The platform operator (GitHub administrator) should enable GitHub organization access to hashicorp/vault-action@v2
with the following steps:
- In the upper-right corner of GitHub, select your profile photo, then click “Your organizations”.
- Next to the organization, click “Settings”.
- In the left sidebar, click “Actions”, then click “General”. Under "Policies", select “Allow select actions” and add your required actions to the list.
- Click “Save”.
If your organization globally restricts GitHub Actions, you must enable access to the Vault action. Security controls within your environment may differ, so consult appropriate parties as needed. Note that HashiCorp is a GitHub verified creator.
Configure a JWT auth mount for the GitHub Actions OIDC provider
The Vault platform operator should create a JWT auth backed for GitHub Actions.
Note
A single JWT auth mount resource may service multiple underlying roles and must only be configured once for a Vault namespace.
Terraform:
resource "vault_jwt_auth_backend" "github_actions" {
namespace = "business-unit-1"
description = "JWT Auth Backend for GitHub Actions"
path = "jwt-github-actions"
bound_issuer = "https://token.actions.githubusercontent.com"
oidc_discovery_url = "https://token.actions.githubusercontent.com"
tune {
default_lease_ttl = "300s"
}
}
API:
curl -X POST \
-H X-Vault-Token:$VAULT_TOKEN \
-H X-Vault-Namespace:$VAULT_NAMESPACE \
https://vault.example.com:8200/v1/sys/auth/jwt-github-actions -d \
'{
"type": "jwt",
"description": "JWT Auth Backend for GitHub Actions",
"default_lease_ttl": 300
}'
curl -X POST \
-H X-Vault-Token:$VAULT_TOKEN \
-H X-Vault-Namespace:$VAULT_NAMESPACE \
https://vault.example.com:8200/v1/auth/jwt-github-actions/config -d \
'{
"bound_issuer": "https://token.actions.githubusercontent.com",
"oidc_discovery_url": "https://token.actions.githubusercontent.com"
}'
CLI:
vault auth enable \
-path=jwt-github-actions \
-description="JWT Auth Backend for GitHub Actions" \
-default-lease-ttl=30 \
jwt
Configure a Vault policy for a GitHub Actions workflow
The Vault platform engineer should create a policy that grants read access to all secrets under the path kv/app1
. We recommend all Vault policies follow the principle of least privilege to limit client access to only the secrets the service requires. The policy assumes that secrets already exist on this path or that the app1
team can create and update this path.
Terraform:
resource "vault_policy" "app1_workflow1" {
name = "app1-workflow1"
policy = <<EOT
path "kv/data/app1/*" {
capabilities = ["read"]
}
EOT
}
API:
curl -X POST \
-H X-Vault-Token:$VAULT_TOKEN \
-H X-Vault-Namespace:$VAULT_NAMESPACE \
https://vault.example.com:8200/v1/sys/policy/app1-workflow1 \
-d '{"policy": "path \"kv/data/app1/*\" {\n capabilities = [\"read\"]\n}"}'
CLI:
echo 'path "kv/data/app1/*" {
capabilities = ["read"]
}
' | vault policy write app1-workflow1 -
Configure a JWT auth role for a GitHub Actions workflow
The Vault platform engineer should create a Vault JWT auth role for each unique CI workflow. This auth role may include repository and workflow information like:
- Pecific audience, subject
- Repository, branch
- Actor, workflow name
- Repository visibility setting, runner environment, and repository owner.
Terraform:
resource "vault_jwt_auth_backend_role" "app1_workflow1" {
backend = vault_jwt_auth_backend.this.path
role_name = "auth.jwt.app1.workflow1"
bound_audiences = ["https://github.com/hashicorp-services"]
bound_claims_type = "string"
bound_subject = "repo:hashicorp-services/gha-vault-jwt-demo:ref:refs/heads/main"
bound_claims = {
actor = "user123"
workflow = "demo-jwt-auth-workflow"
repository_visibility = "private"
runner_environment = "self-hosted"
repository_owner = "hashicorp-services"
}
user_claim = "sub"
role_type = "jwt"
token_ttl = 300
token_type = "service"
token_policies = [vault_policy.app1_workflow1.name]
}
API:
curl -sk -X POST \
-H X-Vault-Token:$VAULT_TOKEN \
-H X-Vault-Namespace:$VAULT_NAMESPACE \
https://vault.example.com:8200/v1/auth/jwt-github-actions/role/auth.jwt.app1.workflow1 -d \
'{
"role_type": "jwt",
"bound_audiences": ["https://github.com/hashicorp-services"],
"bound_claims_type": "string",
"bound_subject": "repo:hashicorp-services/gha-vault-jwt-demo:ref:refs/heads/main",
"user_claim": "sub",
"token_ttl": 300,
"token_type": "service",
"token_policies": ["app1-workflow1"],
"bound_claims": {
"actor": "user123",
"workflow": "demo-jwt-auth-workflow",
"repository_visibility": "private",
"runner_environment": "self-hosted",
"repository_owner": "hashicorp-services"
}
}'
CLI:
vault write auth/jwt-github-actions/role/auth.jwt.app1.workflow1 -<<EOF
{
"role_type": "jwt",
"bound_audiences": ["https://github.com/hashicorp-services"],
"bound_claims_type": "string",
"bound_subject": "repo:hashicorp-services/gha-vault-jwt-demo:ref:refs/heads/main",
"user_claim": "sub",
"token_ttl": 300,
"token_type": "service",
"token_policies": ["app1-workflow1"],
"bound_claims": {
"actor": "user123",
"workflow": "demo-jwt-auth-workflow",
"repository_visibility": "private",
"runner_environment": "self-hosted",
"repository_owner": "hashicorp-services"
}
}
EOF
Configure the workflow to integrate with Vault
The platform operator creates a GitHub Action using the official hashicop/vault-action@v2
action, consumes a secret from Vault, and stores the secret in the environment variable SECRET_API_KEY
. The following example assumes the GitHub Actions environment variable VAULT_ADDR
contains the appropriate Vault URL. .
After the platform operator sets up the integration, your workflow can now authenticate and request secrets from Vault, under the policy assigned by the role.
name: app1-workflow1-vault-secrets
on:
push:
branches:
- main
paths-ignore:
- '*.md'
jobs:
get-secret:
permissions:
contents: 'read'
id-token: 'write'
runs-on: self-hosted
name: get-secret
steps:
- name: Import Secrets
id: import-secrets
uses: hashicorp/vault-action@v2
with:
url: ${{ env.VAULT_ADDR }}
namespace: ${{ env.VAULT_NAMESPACE }}
path: jwt-github-actions
role: auth.jwt.app1.workflow1
method: jwt
secrets: |
kv/data/app1 api-key | SECRET_API_KEY ;
- name: Revoke Vault Token
if: always()
run: |
curl -X POST -sv -H "X-Vault-Token: ${{ env.VAULT_TOKEN }}" \
${{ env.VAULT_ADDR }}/v1/auth/token/revoke-self
Clean up infrastructure
You have set up the GitHub Actions integration with GitHub.
If you choose to revoke Vault access for the GitHub Action workflow, the Vault platform engineer can destroy the Terraform resources associated with this guide.
Conclusion
In this guide, you set up the GitHub Actions integration with HCP Vault to allow you to securely use secrets in your CI/CD pipelines. You have defined the responsibilities of the platform operator, Vault administrator, and Vault consumer. In the future, we recommend you automate this process and provide a scalable self-service flow that enforces specific patterns to ensure consistency.
To learn more, check out the following resources: