Background

Objectives

  1. Setup the Vault server for JWT authentication method to trust the GitHub token issuer.
  2. Create the Vault policy for the secret.
  3. Create the JWT role to map the GitHub token to the Vault policy.
  4. Create the GitHub actions workflow to access the Vault secret.

Interaction of GitHub and Vault in accessing Vault secrets

@startuml

rectangle vault_server as "Vault server" {
    rectangle jwt_auth as "JWT auth method (GitHub)" {
        collections jwt_roles as "JWT roles"
        file jwt_role as "JWT role"
        jwt_roles . jwt_role
    }

    collections vault_policies as "Vault policies"
    file vault_policy as "Vault policy"
    vault_policies . vault_policy

    collections vault_secrets as "Vault secrets"
    file vault_secret as "Vault secret"
    vault_secrets . vault_secret
    vault_policy --> vault_secret : allow read

}

rectangle github as "GitHub infra" {
    file workflow as "Actions workflow"
    node runner as "Actions runner"

    database token_issuer as "Token issuer"
    workflow --> runner
    workflow -> token_issuer

    circle token as "GitHub token"

    token_issuer .> token
    runner -> token
}

jwt_role <-- token : match on claims
jwt_role --> vault_policy : map to policy

@enduml


Procedure

Setting up the Vault server for JWT authentication

Enabling the JWT authentication method


Steps
1Login to the Vault UI with permissions to add new authentication method.
2

Navigate to Access → Auth methods → Enable a new Auth method.

3

Select the JWT option and click Next.

Keep the default jwt path, otherwise the Hashicorp Vault Secrets action has trouble logging in.


4

Set the OIDC discovery URL to: https://token.actions.githubusercontent.com

5

Expand the JWT Options section.

Set the Bound issuer to: https://token.actions.githubusercontent.com

6

Click Save.

Creating the policy for the secret access


Steps
1Login to the Vault UI  with permissions to add new policies.
2Navigate to Policies → Create ACL policy.
3Provide a name to the policy.  This will be referenced in the JWT role created in a later step.
4

Provide the policy block, which references the secrets and their capabilities.

path "secret/data/mysecret" 
{
  capabilities = ["read"] 
}


5

Click Save.

Create the JWT role

These steps can only be done through the Vault CLI tool.


Steps
1

Login to Vault CLI with permissions to add JWT roles.

Run vault login -method=github

Provide your GitHub personal access token at the prompt.

2

Prepare the Vault role.  See Vault documentation on all the fields.

{
  "role_type": "jwt",
  "policies": ["read_secret_mysecret" ],
  "bound_audiences": "https://github.com/tenzin-io",
  "user_claim": "repository",
  "bound_claims_type": "string",
  "bound_claims":{
    "repository": "tenzin-io/test-vault"
  }
}

See the GitHub documentation for all the possible fields to use in bound_claims to perform the role match.

3

Write the Vault role to the authentication method.

vault write auth/jwt/role/test-vault - < test-vault.json

The role name in this example is test-vault and the role payload is in the JSON file. 

This role name needs to be referenced in the GitHub actions workflow file.

Setting up GitHub workflow to access Vault secrets


Steps
1

Create the workflows YAML file in the GitHub repository.

In the above example the repository is:  tenzin-io/test-vault

2

The example YAML file below:

name: Testing vault

on:
  # Triggers the workflow on push or pull request events but only for the "main" branch
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]
  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

jobs:
  build:
    # The type of runner that the job will run on
    runs-on: [ self-hosted, Linux, ARM64 ]
    permissions:
        contents: read
        id-token: write
    steps:
      - uses: actions/[email protected]

      - name: Import Secrets
        uses: hashicorp/[email protected]
        id: secrets
        with:
          url: https://vault.tenzin.io
          method: jwt
          role: test-vault
          secrets: |
              secret/data/mysecret secret_one | SECRET_ONE ;

      - name: Sensitive Operation
        run: "echo '${{ steps.secrets.outputs.SECRET_ONE }}'"

Source:  https://github.com/tenzin-io/test-vault/tree/main/.github/workflows

3

The permissions block modifies the default permissions on the GITHUB_TOKEN.

jobs:
  build:
    runs-on: [ self-hosted, Linux, ARM64 ]
    permissions:
        contents: read
        id-token: write


4

Add the step that uses the hashicorp/vault-action GitHub action.  See more details on the action documentation

- name: Import Secrets
  uses: hashicorp/[email protected]
  id: secrets
  with:
    url: https://vault.tenzin.io
    method: jwt
    role: test-vault
    secrets: |
      secret/data/mysecret secret_one | SECRET_ONE ; 

The role name should correspond to the Vault role name that was created on the JWT auth path. 

See the vault write auth/jwt/role/...  command from an earlier step.

5

Perform the commit, which should automatically dispatch the workflow to a GitHub actions runner.

Example workflow output:

Appendix

Vault troubleshooting

# this enables the audit logging
## option log_raw=true is very important, this decodes the request and response data field,
## otherwise the error messages are in some unhelpful encrypted form
vault audit enable file file_path=/vault/logs/audit.log log_raw=true

# the log file will be found on the vault server itself,
# not on the machine from which you ran the vault command.