Terraform
Authenticate a Stack
You can authenticate a Stack in two ways: by using OIDC to dynamically authenticate with the built-in identity_token
block or by accessing existing authentication credentials with the store
block. We recommend authenticating your Stack with OIDC because using static credentials to authenticate providers presents a security risk, even if you rotate your credentials regularly.
Authenticate with OIDC
OpenID Connect (OIDC) is an identity layer on top of the OAuth 2.0 protocol. You can use HCP Terraform’s Workload identity tokens, built on the OpenID Connect protocol, to securely connect and authenticate your Stacks with cloud providers.
Hands on: Walk through setting up OIDC for a Stack in the Deploy a Stack with HCP Terraform tutorial.
Stacks have a built-in identity_token
block that creates workload identity tokens, also known as JWT tokens. You can use these tokens to authenticate Stacks with Terraform providers securely.
This guide covers how to set up OIDC for Stacks using the identity_token
block.
Overview
The details of Stack authentication differ based on which cloud provider you are setting up, but the basic steps remain the same:
- Set up the trust configuration between your cloud provider and HCP Terraform, which usually includes creating roles and policies for your cloud provider.
- Add an
identity_token
block to your Stack’s deployment file using the audience and roles you created in the previous step.
Your deployments can reference the value of the identity_token
block to pass the trust relationship role to your Stack’s operations.
Configure trust configuration
In this example, you will set up an example trust policy with AWS. For examples of setting up trust policies with other providers, refer to our repository of example configurations.
We recommend using Terraform to create and configure the trust relationship and permissions for the cloud provider you want to authenticate with. Using the following Terraform configuration, create a new workspace and configure the aws_region
, tf_organization
, tf_project,
and tf_stack
variables for your specific set up.
# main.tf
variable "aws_region" {
type = string
description = "The AWS region to create the role in."
}
variable "tf_organization" {
type = string
description = "The name of the organization that this workspace and Stack live in."
}
variable "tf_project" {
type = string
description = "The name of the project that this workspace and Stack live in."
}
variable "tf_stack" {
type = string
description = "The name of the Stack you will you use this token in."
}
provider "aws" {
region = var.aws_region
}
resource "aws_iam_openid_connect_provider" "stacks_openid_provider" {
url = "https://app.terraform.io"
client_id_list = ["aws.workload.identity"]
# This is the thumbprint of https://app.terraform.io as of 2024/08/07
# Refer to "Adjust access of trust" to learn how to update this thumbprint
thumbprint_list = ["9e99a48a9960b14926bb7f3b02e22da2b0ab7280"]
}
resource "aws_iam_role" "stacks_role" {
name = "stacks-${var.tf_organization}-${var.tf_project}-${var.tf_stack}"
assume_role_policy = data.aws_iam_policy_document.stacks_role_policy.json
}
data "aws_iam_policy_document" "stacks_role_policy" {
statement {
effect = "Allow"
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.stacks_openid_provider.arn]
}
actions = ["sts:AssumeRoleWithWebIdentity"]
condition {
test = "StringEquals"
variable = "app.terraform.io:aud"
values = ["aws.workload.identity"]
}
condition {
test = "StringLike"
variable = "app.terraform.io:sub"
# This value dictates which HCP Terraform organizations, projects,
# and stacks can assume the new role you are creating.
#
# You can widen access to an entire organization or project by
# tweaking the value below. You can also restrict access to specific
# deployments or operations. Refer to Configure trust for more information.
values = ["organization:${var.tf_organization}:project:${var.tf_project}:stack:${var.tf_stack}:*"]
}
}
}
# Now, you give the new role access to things you want to manage in your Stack.
#
# The policies below are too broad for a production use case, but you set them
# broadly for now to ensure this Stacks can do anything during development and
# testing. In practice, only give your Stack access to what it needs to manage.
resource "aws_iam_role_policy_attachment" "iam" {
role = aws_iam_role.stacks_role.name
policy_arn = "arn:aws:iam::aws:policy/IAMFullAccess"
}
resource "aws_iam_role_policy_attachment" "sudo" {
role = aws_iam_role.stacks_role.name
policy_arn = "arn:aws:iam::aws:policy/PowerUserAccess"
}
# Your workspace returns this output role, which you use to configure your
# deployments.
output "role_arn" {
value = aws_iam_role.stacks_role.arn
}
After setting up your workspace in HCP Terraform, perform a plan and apply operation. HCP Terraform will create your OpenID provider, policy, and role before outputting your new role’s ARN with the role_arn
output. Copy your new AWS ARN role because this is the value you use to configure your cloud provider with your Stack and HCP Terraform.
Adjust access of trust
Workload identity tokens contain useful metadata in their payloads, known as claims. Once a cloud platform verifies a token using HCP Terraform’s public key, it can look at the identity token's claims to match it to the correct permissions or reject it.
You do not need to understand the full token specification or what every claim means, but you can use the claims to adjust your trust relationship.
Workload identity tokens commonly reference the following claims.
Claim | Explanation |
---|---|
jti (JWT ID) | A unique identifier for each token, which is a UUID. |
iss (issuer) | Full URL of the HCP Terraform instance that signed the token. For example, “https://app.terraform.io”. |
iat (issued at) | Unix timestamp when the token was issued. May be required by certain relying parties. |
nbf (not before) | Unix Timestamp when the token can start being used. Should be the same as iat for your purposes and may be required by certain relying parties. |
aud (audience) | Intended audience for the token. For example, “api://AzureADTokenExchange” for Azure. |
exp (expiration) | Unix timestamp based on the timeout of the operation phase that it was issued for. |
sub (subject) | Fully qualified path to a Stack deployment, followed by the operation type:organization:<organization_name>:project:<project_name>:stack:<stack_name>:deployment:my-deployment-name:operation:<operation_type> For example: organization:My_Org:project:My_Project:stack:My_Stack:deployment:staging:operation:apply |
You can further customize your token’s permissions using the following custom claims.
Claim | Explanation |
---|---|
terraform_operation ("run phase") | The Terraform operation this token was issued for. For example, “plan” or “apply”. |
terraform_stack_deployment_name | Name of the deployment that the operation is for. |
terraform_stack_id (stack ID) | External ID of the Stack that the operation is for. |
terraform_stack_name (stack name) | Name of the Stack that the operation is for. |
terraform_project_id (project ID) | External ID of the project that the operation is taking place in. Useful if you want to use the same role across Stacks in a given project. |
terraform_project_name (project name) | Name of the project that the operation is taking place in. |
terraform_organization_id (organization ID) | External ID of the HCP Terraform organization that the operation is taking place in. Useful if you are in a large company that may have more than one organization or if you want to use the same role across your entire organization. |
terraform_organization_name (organization name) | Name of the HCP Terraform organization that the operation is taking place in. |
terraform_plan_id (plan ID) | External ID of the plan that this token is being used for creating or applying. This might be used to track down where a token has been leaked from or to block a specific token if it has been leaked. |
You can also update your configuration to use the current thumbprint for HCP Terraform by running the following command and removing the colons from the value it returns.
$ echo | openssl s_client -showcerts -servername app.terraform.io -connect app.terraform.io:443 2>/dev/null | openssl x509 -fingerprint -noout -sha1
sha1 Fingerprint=FD:88:23:AE:FB:96:13:28:A1:34:70:6D:C8:57:5A:17:0F:0B:B3:7C
Configure HCP Terraform
Stacks use the identity_token
block to define a JSON Web Token (JWT) for a specific deployment. This token enables OIDC-based authentication, allowing your Stack deployments to securely connect to cloud providers like AWS, Azure, and GCP.
When you define the identity_token
block, you specify its audience. For example, the example Stack deployment configuration defines an identity_token
block specifying the AWS audience.
identity_token "aws" {
audience = ["aws.workload.identity"]
}
Once defined, you can reference an identity token in a deployment's input variables and reference that token in a provider’s configuration.
You also need to add your trust relationship, your role ARN, to configure your token with the trust relationship to authenticate AWS resources.
# deployments.tfdeploy.hcl
identity_token "aws" {
audience = ["aws.workload.identity"]
}
deployment "development" {
inputs = {
role_arn = "<YOUR_ROLE_ARN>"
identity_token = identity_token.aws.jwt
}
}
Now, the deployments are authenticated with AWS and can pass the trust configuration to your Stack’s providers for authentication.
# variables.tfstack.hcl
variable "role_arn" {
type = string
}
variable "identity_token" {
type = string
ephemeral = true
}
# providers.tfstack.hcl
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.7.0"
}
}
provider "aws" "this" {
config {
region = var.region
assume_role_with_web_identity {
role_arn = var.aws_role
web_identity_token = var.aws_token
}
}
}
With this setup, each of your deployments pass the ARN of your trust relationship role and a JWT token generated by AWS each time you plan or apply your Stack. Each of your deployments uses their own role, configured with the specific permissions you require.
For more examples of setting up OIDC with different providers, refer to our repository of example configurations.
Authenticate with existing credentials
You can use the store
block to define key-value secrets in your deployment configuration and then access those values in your deployments.
You can use the store
block to access credentials stored in an HCP Terraform variable set. This example uses a store
block with a variable set to access credentials stored in HCP Terraform. For more information about using store
blocks, refer to the Deployment configuration reference.
Before writing any configuration, ensure your Stack can access the variable set you are targeting by allowing your project to access the set or making the set globally available.
Next, add a store
block to your deployment configuration file to access your variable set. Once defined, your deployments can access your variable set’s values.
# deployments.tfdeploy.hcl
# Source environment secrets from your HCP Terraform variable set
store "varset" "tokens" {
id = "varset-<variables-set-id>"
category = "env"
}
# Access your variable set's value using your store and pass them into your
# deployment's inputs.
deployment "test" {
inputs = {
access_key = store.varset.tokens.AWS_ACCESS_KEY_ID
secret_key = store.varset.tokens.AWS_SECRET_ACCESS_KEY
session_token = store.varset.tokens.AWS_SESSION_TOKEN
}
}
For example, if your test
deployment can use your store
to access your variable set’s keys, and therefore, your variable set’s AWS provider credentials.
Your test
deployment can define your AWS credentials as inputs
, allowing your providers
to access those credentials.
# providers.tfstack.hcl
variable "access_key" {
description = "AWS access key"
type = string
ephemeral = true
}
variable "secret_key" {
description = "AWS sensitive secret key."
type = string
sensitive = true
ephemeral = true
}
variable "session_token" {
description = "AWS session token."
type = string
sensitive = true
ephemeral = true
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.7.0"
}
}
provider "aws" "this" {
config {
access_key = var.access_key
secret_key = var.secret_key
token = var.session_token
}
}
This example allows your aws.this
provider to authenticate to AWS using the credentials from a variable set stored in HCP Terraform. For more information about using store
blocks, refer to the Deployment configuration reference.