Authenticate Terraform Cloud to access secrets from HashiCorp Vault


Bicycle

Secrets Distribution

The problem domain of secrets distribution is a common one. You have a secret, you want to distribute it to a service, and you want to implement this action in a secure way. The secret could be a password, an API key, a TLS certificate, or anything else that you want to keep secret.

Also a common problem within secrets distribution is the need to rotate secrets. This is a security best practice, and it's often required by compliance standards. Rotating secrets can be a complex process, especially when you must do it across multiple services.

And finally, you might also be aware of the problem that distributed secrets can be hard to manage. You might have secrets stored in multiple places, and it can be hard to keep track of where they are and who has access to them.

Authenticate Terraform Cloud to access secrets from HashiCorp Vault

HashiCorp Vault is a secrets management tool that can help you solve these problems. Vault can store secrets, distribute them to services, and rotate them. It can also help you manage access to secrets, so you can manage access permissions effectively.

You can configure Terraform HCP or Terraform Enterprise to authenticate against HashiCorp Vault to retrieve necessary secrets without duplication of data. This way you can store your secrets in a central place and fetch them from your services in a secure way.

Json Web Token Authentication (JWT)

HashiCorp provides multiple setups to authenticate clients, e.g. AppRole as authentication if you have no other credential data available. Terraform Enterprise but also Terraform HCP can authenticate against HashiCorp Vault by the JWT authentication method. This method allows you to authenticate with Vault using a signed JWT token.

1resource "vault_jwt_auth_backend" "tfc_jwt_vault" {
2  description        = "Hashicorp tfc auth backend"
3  path               = "tfc_jwt_vault"
4  oidc_discovery_url = "https://app.terraform.io"
5  bound_issuer       = "https://app.terraform.io"
6  default_role       = "tfc_vault_config"
7}

Initially you must define a JWT auth backend and a role. The role defines the policies and claims that are required to authenticate against Vault. The role is bound to the JWT auth backend.

 1resource "vault_jwt_auth_backend_role" "tfc_vault_config" {
 2
 3  backend = "tfc_vault_config"
 4
 5  role_name         = "tfc_jwt_all_projects"
 6  token_policies    = [ "tfc_all_projects" ]
 7  user_claim        = "terraform_full_workspace"
 8  role_type         = "jwt"
 9  bound_audiences   = ["vault.workload.identity"]
10  bound_claims_type = "glob"
11  token_ttl         = 20 * 60
12
13  bound_claims = {
14    sub = "organization:MyOrganization:project:*:workspace:*:run_phase:*"
15  }
16}

By defining the role, you can specify the policies and claims that are required to authenticate against Vault. The bound_claims attribute specifies the claims that are required to authenticate against Vault. So, in this example the sub claim must match the given value, otherwise the authentication will fail. In this example this role can be used over all projects in the organization MyOrganization for all workspaces and run phases.

 1resource "vault_jwt_auth_backend_role" "tfc_jwt_my_project" {
 2
 3  backend = "tfc_jwt_vault"
 4
 5  role_name         = "tfc_jwt_my_project"
 6  token_policies    = [ "tfc_all_projects" ]
 7  user_claim        = "terraform_full_workspace"
 8  role_type         = "jwt"
 9  bound_audiences   = ["vault.workload.identity"]
10  bound_claims_type = "glob"
11  token_ttl         = 20 * 60
12
13  bound_claims = {
14    sub = "organization:MyOrganization:project:MyProject:workspace:*:run_phase:*"
15  }
16}

If you need to restrict the access to a specific project, you can define a role for this project. In this example the role tfc_jwt_my_project can only be used for the project MyProject in the organization MyOrganization, but all workspaces and run phases within this project.

JWT Authentification on Terraform

When having to define an authentication against HashiCorp Vault in combination with AppRole authentication, you must find a solution to request the SecretID and pass this informaition to the client location. When using JWT based authentication you just need to define the Vault JWT auth backend and the role in your Terraform configuration. Terraform Cloud will handle the rest.

 1variable "tfc_vault_dynamic_credentials" {
 2  description = "Object containing Vault dynamic credentials configuration"
 3  type = object({
 4    default = object({
 5      token_filename = string
 6      address        = string
 7      namespace      = string
 8      ca_cert_file   = string
 9    })
10    aliases = map(object({
11      token_filename = string
12      address        = string
13      namespace      = string
14      ca_cert_file   = string
15    }))
16  })
17}
18
19provider "vault" {
20  skip_child_token = true
21  address          = var.tfc_vault_dynamic_credentials.default.address
22  namespace        = var.tfc_vault_dynamic_credentials.default.namespace
23
24  auth_login_token_file {
25    filename = var.tfc_vault_dynamic_credentials.default.token_filename
26  }
27}

This block defines the Vault provider in Terraform. The skip_child_token attribute must be set to true as Terraform HCP manages the token lifecycle. The address and namespace attributes are set to the values from the tfc_vault_dynamic_credentials variable. The auth_login_token_file block specifies the token file that is used to authenticate against Vault. This token file is managed by Terraform HCP. On every run Terraform HCP will authenticate against Vault using the JWT token and fetch the secrets that are required for the run.

Finally you need to define following environment variables in your Terraform configuration - this might be done in the workspace settings in Terraform HCP by using Variable Sets: TFC_VAULT_ADDR, TFC_VAULT_AUTH_PATH, TFC_VAULT_NAMESPACE, TFC_VAULT_PROVIDER_AUTH. The last variable is a boolean flag that must be set to true to enable the Vault provider as needed in Terraform. All other variables are defined/created by the previous Terraform configuration.

Have fun trying it yourself!

Go Back explore our courses

We are here for you

You are interested in our courses or you simply have a question that needs answering? You can contact us at anytime! We will do our best to answer all your questions.

Contact us