/terraform-vault-github-oidc

Terraform module to configure Vault for GitHub OIDC authentication from Action runners.

Primary LanguageHCLApache License 2.0Apache-2.0

Terraform Module: Hashicorp Vault GitHub OIDC

GitHub release (latest SemVer) OIDC Tests GitHub CII Best Practices GitHub contributors GitHub last commit

Terraform module to configure Vault for GitHub OIDC authentication from Action runners on GitHub.com or self-hosted GitHub Enterprise Server.

OIDC authentication allows us to bind GitHub repositories (and subcomponents of a repository, such as a branch, ref, or environment) to a Vault role without needing to manage actual credentials that require a lifecycle system, integration into repo-level GitHub Secrets, or other organizational glue.

Explore GitHub OIDC and HashiCorp Vault use cases with this hands-on workshop: https://github.com/artis3n/course-vault-github-oidc.

Reference documents that help with understanding the process:

Once OIDC authentication is configured on a Vault server via this module, a GitHub repository can leverage hashicorp/vault-action to retrieve secrets from Vault with GitHub OIDC authentication. No secrets or credential management needed!

e.g.

- name: Import Secrets
  uses: hashicorp/vault-action@v2
  id: secrets
  with:
    exportEnv: false
    url: https://<your-vault-URL>
    path: github-actions
    method: jwt
    role: <vault_role_name>
    secrets: |
      secret/data/foo/bar fi | MY_SECRET

- name: Access secret
  run: echo '${{steps.secrets.outputs.MY_SECRET }}' | my_command

Usage

This module simplifies the creation of the JWT auth backend on Vault for this GitHub Action OIDC use case. The module requires you to configure what repositories to bind to Vault roles and policies, and under what conditions the respective repository should be granted access. This is encapsulated by the oidc_bindings variable.

Note

v2 of this module adopts Terraform 1.3's standardized support of optional object type attributes. Therefore, Terraform 1.3+ is required to use v2.0.0 or higher.

Users with Terraform 1.2 or earlier can use v1.1.0 of this module with the module_variable_optional_attrs experimental Terraform feature enabled.

Examples

Tutorial/example repo: https://github.com/artis3n/github-oidc-vault-example.

You can find several examples leveraging this module under examples/:

Basic example - one repo, separating secrets access by nonprod and prod pipelines.

module "github-vault-oidc" {
  source = "digitalocean/github-oidc/vault"
  version = "~> 2.1.0"

  oidc_bindings = [
    {
      audience : "https://github.com/artis3n",
      vault_role_name : "oidc-dev-role",
      bound_subject : "repo:artis3n/github-oidc-vault-example:pull_request",
      vault_policies : [
        vault_policy.dev.name,
      ],
    },
    {
      audience : "https://github.com/artis3n",
      vault_role_name : "oidc-deploy-role",
      bound_subject : "repo:artis3n/github-oidc-vault-example:ref:refs/heads/main",
      vault_policies : [
        vault_policy.deployment.name,
      ],
    },
  ]
}

resource "vault_policy" "dev" {
  name   = "oidc-dev"
  policy = data.vault_policy_document.dev.hcl
}

data "vault_policy_document" "dev" {
  rule {
    path         = "secret/data/dev/foo"
    capabilities = ["read"]
  }
}

resource "vault_policy" "deployment" {
  name = "oidc-deploy"
  policy = data.vault_policy_document.deployment.hcl
}

data "vault_policy_document" "deployment" {
  rule {
    path         = "secret/data/prod/bar"
    capabilities = ["read"]
  }
}

Considerations for Enterprise Cloud organizations

Enterprise Cloud organizations should strongly consider enabling the Unique Token URL feature for their organization.

If they do so, they should set the github_identity_provider variable of this module to their enterprise's unique token URL.

Variables

oidc_bindings

This input variable must be a list of objects containing the following structure:

oidc_bindings = [
  {
    audience: '',
    vault_role_name: '',
    bound_subject: '',
    vault_policies: [''],
  }
]

There are additional, optional values you can include as well:

oidc_bindings = [
  {
    audience: '',
    vault_role_name: '',
    bound_subject: '',
    vault_policies: [''],
    # Optional below
    user_claim: '',
    additional_claims: [
      {
        x: '',
      }
    ],
    ttl: 0,
  }
]

Descriptions for each parameter are below:

oidc_bindings.audience

By default, the audience must be the URL of the repository owner (e.g. https://github.com/digitalocean).

The audience can be customized by configuring whatever you'd like and using the jwtGithubAudience parameter in hashicorp/vault-action. For example, from an organizational or audit perspective, you may desire to establish a naming scheme such as audience: "<company>:<org-unit>:<team-name>", e.g. digitalocean:security:product-security.

oidc_bindings.vault_role_name

The vault_role_name must be the name of the Vault role you wish to create on the JWT auth backend. Each Vault role should be configured for one repo subject - using the same Vault role with different configurations in the rest of the parameters will cause this module to fail. This is because you would otherwise silently overwrite the role configuration.

You may want to create multiple Vault roles for a single GitHub repository, e.g. a nonprod CI workflow that needs access to CI secrets, and a deployment workflow that publishes a release that needs production secrets.

oidc_bindings.bound_subject

The bound_subject must be the sub field from GitHub's OIDC token. The bound subject can be constructed from various filters, such as a branch, tag, or specific environment. See GitHub's documentation for examples.

oidc_bindings.vault_policies

vault_policies must be a list of Vault policy strings to grant to the vault_role_name Vault role being configured. These can also come from vault_policy resources.

oidc_bindings.user_claim

Optional

The user_claim is how you want Vault to uniquely identify this client. This will be used as the name for the Identity entity alias created due to a successful login. This means it will determine the auth.display_name value in Vault audit logs.

This must be a field present in the GitHub JWT token. Defaults to the value of the default_user_claim variable if not provided.

We strongly recommend you keep a consistent format for auth.display_name for monitoring of Vault's audit log. Instead of changing the user_claim for a specific role, consider modifying the default_user_claim variable to apply a format change to all roles managed through this module.

oidc_bindings.additional_claims

Optional

additional_claims must be a list of any additional claims you would like to enforce in the Vault role binding. Each key must be a field present in the GitHub JWT token.

For example, to leverage reusable workflows with OIDC, you may wish to set your bound_subject to repo:ORG_NAME/* and add an additional claim of job_workflow_ref:ORG_NAME/REPO_NAME pointing to the reusable workflow.

e.g.

oidc_bindings = [
  {
    audience: '...',
    vault_role_name: '...',
    bound_subject: "repo:digitalocean/*",
    vault_policies: ['...'],
    user_claim: '...',
    additional_claims: [
      {
        job_workflow_ref: 'digitalocean/oidc-example/.github/workflows/deployment.yml@v1',
      }
    ],
  },
]

oidc_bindings.ttl

You can also specify a custom ttl per role binding if you wish to customize beyond the default_ttl variable. This must be a number of seconds.

default_ttl

Optional

The default incremental time-to-live for generated tokens, in seconds. Since most uses of hashicorp/vault-action authenticate & retrieve secrets in one step during a CI pipeline, the default for this variable is set to 5 minutes.

If you wish to customize the TTL for all roles, modify this variable. You can also specify individual TTL requirements on individual roles that may have edge case needs for a different TTL. See oidc_bindings.ttl.

default_user_claim

Optional

This is how you want Vault to uniquely identify this client. This will be used as the name for the Identity entity alias created from a successful login. This means it will determine the auth.display_name value in Vault audit logs.

This must be a field prevent in the GitHub JWT token.

This is set to job_workflow_ref by default.

oidc_auth_backend_path

Optional

By default, this role will generate a JWT auth backend on Vault at the path /github-actions. If you wish to customize the path created by this module, modify this variable. Do not include a leading / in the variable value (e.g. use github-actions not /github-actions).

At this time, this module expects to create and manage the JWT backend leveraged for GitHub OIDC auth. You cannot pass in a Terraform reference to an existing backend.

github_identity_provider

Optional

By default, this role will communicate with github.com for an OIDC JWT (https://token.actions.githubusercontent.com).

If you are an Enterprise Cloud customer, you should configure a unique token URL and set this variable to your unique token URL.

https://token.actions.githubusercontent.com/<enterpriseSlug>

If you run GitHub Enterprise Server, you will need to configure your instance of GitHub as the identity provider and should modify this variable. This requires GitHub Enterprise Server version 3.5 or higher.

The format is: https://HOSTNAME/_services/token.

See https://docs.github.com/en/enterprise-server@latest/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-hashicorp-vault#adding-the-identity-provider-to-hashicorp-vault.

token_type

Optional

The type of Vault token that should be generated. https://developer.hashicorp.com/vault/api-docs/auth/jwt#token_type

Because of the short TTLs and frequent use intended for authentication via this module, this module generates a batch token by default.

Requirements

Name Version
terraform >= 1.3.0
vault >= 3.4.1

Providers

Name Version
vault 3.12.0

Modules

No modules.

Resources

Name Type
vault_jwt_auth_backend.github_oidc resource
vault_jwt_auth_backend_role.github_oidc_role resource

Inputs

Name Description Type Default Required
oidc_bindings A list of OIDC JWT bindings between GitHub repos and Vault roles. For each entry, you must include:

audience: By default, this must be the URL of the repository owner (e.g. https://github.com/digitalocean). This can be customized with the jwtGithubAudience parameter in hashicorp/vault-action . This is the bound audience (aud) field from GitHub's OIDC token .

vault_role_name: The name of the Vault role to generate under the OIDC auth backend.

bound_subject: This is what is set in the sub field from GitHub's OIDC token . The bound subject can be constructed from various filters, such as a branch, tag, or specific environment . See GitHub's documentation for examples.

vault_policies: A list of Vault policies you wish to grant to the generated token.

user_claim: Optional. This is how you want Vault to uniquely identify this client. This will be used as the name for the Identity entity alias created due to a successful login. This must be a field present in the GitHub JWT token . Defaults to the default_user_claim variable if not provided. Consider the impact on reusable workflows if you are thinking of changing this value from the default.

additional_claims: Optional. Any additional bound_claims to configure for this role. Claim keys must match a value in GitHub's OIDC token . Do not use this field for the sub claim. Use the bound_subject parameter instead.

ttl: Optional. The default incremental time-to-live for the generated token, in seconds. Defaults to the default_ttl value but can be individually specified per binding with this value.
list(object({
audience = string,
vault_role_name = string,
bound_subject = string,
vault_policies = set(string),
user_claim = optional(string),
additional_claims = optional(map(string)),
ttl = optional(number),
}))
n/a yes
default_ttl The default incremental time-to-live for generated tokens, in seconds. number 300 no
default_user_claim This is how you want Vault to uniquely identify this client. This will be used as the name for the Identity entity alias created due to a successful login. This must be a field present in the GitHub OIDC token . Consider the impact on reusable workflows if you are thinking of changing this value from the default. string "job_workflow_ref" no
github_identity_provider The JWT authentication URL used for the GitHub OIDC trust configuration. If you are an Enteprise Cloud account, you should configure a unique token URL and set the result on this variable. If you are an Enterprise Server organization, you should provide a URL in the format: https://HOSTNAME/_services/token. This requires GitHub Enterprise Server version 3.5 or higher. See https://docs.github.com/en/enterprise-server@latest/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-hashicorp-vault#adding-the-identity-provider-to-hashicorp-vault. string "https://token.actions.githubusercontent.com" no
oidc_auth_backend_path The path to mount the OIDC auth backend. string "github-actions" no
token_type The type of token to generate. This can be either batch or service. See https://developer.hashicorp.com/vault/api-docs/auth/jwt#token_type for more information. string "batch" no

Outputs

Name Description
auth_backend_path The path of the generated auth method. Use with a vault_auth_backend data source to retrieve any needed attributes from this resource.
oidc_bindings_names The Vault role names generated for each OIDC binding provided. This is a reflection of the vault_role_name value of each item in oidc-bindings.

Authors

This module is maintained by Ari Kalfus with help from these excellent contributors.

License

Licensed under Apache 2.0. See LICENSE for full details.