Join our Newsletter — 33% off our NHI Course
Home› Guides› How to federate Terraform Cloud to AWS: Replace…
Guide Cloud & Workload Identity

How to federate Terraform Cloud to AWS: Replace static AWS keys with per-run role credentials

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 11 November 2026 13 min read
On this page

A Terraform Cloud workspace holding AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as sensitive variables gives every run, and anyone who can edit that workspace, a long-lived key that usually carries near-administrator rights. HCP Terraform (the current name for Terraform Cloud) can instead mint a signed workload identity token for each plan and apply, which AWS STS exchanges for temporary role credentials that are discarded when the run ends. This page is the setup runbook: create the IAM OIDC provider, write trust policies that pin your organisation, project, workspace and run phase, set the TFC_AWS_* variables, test, roll back and delete the old keys. It is for platform engineers who own both the HCP Terraform organisation and the AWS accounts. For the wider platform hardening picture see the Terraform Cloud and HCP Security Guide; this page focuses on doing the migration.

This is an independent guide from NHI Mgmt Group, based on HashiCorp's and AWS's public documentation as checked in November 2026. It is not sponsored or reviewed by HashiCorp or AWS. Confirm settings against the current vendor documentation before changing production.

Key takeaways

  • The trust policy is the only gate. HashiCorp warns that if you do not check at least the audience and the organisation name, other HCP Terraform organisations could assume your role. Every customer shares the same issuer, https://app.terraform.io.
  • Only aud and sub are usable in AWS. AWS maps generic OIDC providers to the condition keys aud, sub, amr, email and oaud, so the rich terraform_workspace_id claims cannot be checked in the trust policy. All scoping happens in the sub string.
  • Split plan and apply. The subject ends with run_phase:plan or run_phase:apply, so a read-only plan role and a write apply role are a two-line change with TFC_AWS_PLAN_ROLE_ARN and TFC_AWS_APPLY_ROLE_ARN.
  • Names, not IDs, are in the subject. Organisation, project and workspace names can change. A rename breaks the trust, and a workspace recreated with the old name inherits it, so control who can create and rename workspaces.
  • Finish by deleting keys. The migration is done only when the IAM user's access keys are deleted and the static variables removed from every workspace and variable set.

When you need this

  • Static AWS keys sit in workspace variables or a variable set. A variable set shared across many workspaces is the worst case: one key, one blast radius, many writers. This is the long-lived secret pattern OWASP lists as NHI7 Long-Lived Secrets.
  • The Terraform IAM user has broad rights. Provisioning identities often hold AdministratorAccess because nobody scoped them. Federation is the moment to split plan from apply and scope per workspace.
  • You cannot tell which workspace changed what. With a shared IAM user every run looks the same in CloudTrail. Federated sessions record the token subject, which names the workspace and phase.
  • Rotation is manual or overdue. HashiCorp's own rationale is that static credentials carry risk even when rotated regularly; per-run credentials leave nothing to rotate.

Credentials stored in Terraform state are a separate problem that federation does not solve. See How to Find secrets in terraform state.

Before you start

ItemWhat you need
AWS permissionsRights to create an IAM OIDC identity provider and IAM roles in each target account
HCP Terraform permissionsRights to manage variables on the target workspaces or variable sets; organisation owner if you will restrict who can create workspaces
Issuer URLhttps://app.terraform.io for HCP Terraform, no trailing slash. For Terraform Enterprise, your instance URL, which AWS must be able to reach to read its discovery document and keys
Audienceaws.workload.identity by default, or your own value set with TFC_AWS_WORKLOAD_IDENTITY_AUDIENCE
Subject planOrganisation, project and workspace names for each role, and whether plan and apply need separate roles
AgentsSelf-hosted HCP Terraform agents must run v1.7.0 or later, and v1.12.0 or later for multiple configurations
Permissions planThe AWS actions each workspace really uses, from CloudTrail history for the existing IAM user

How the subject is built

For a workspace run the token's sub claim is organization:ORG_NAME:project:PROJECT_NAME:workspace:WORKSPACE_NAME:run_phase:RUN_PHASE, where the phase is plan or apply. Project names may contain spaces: the default project is literally Default Project, and the subject keeps that space. Module test runs use a different form, organization:ORG_NAME:module:MODULE_NAME:operation:test_run, and are always read-only.

Trust scopesub conditionUse it forWatch out for
One workspace, one phaseStringEquals organization:acme:project:payments:workspace:payments-prod:run_phase:applyProduction apply rolesBreaks if the workspace or project is renamed
One workspace, both phasesStringLike ending run_phase:*Low-risk workspaces where plan and apply need the same rightsPlans, which run on every speculative change, get write rights
One project, all workspacesStringLike organization:acme:project:sandbox:workspace:*:run_phase:planRead-only roles across a projectAnyone who can create a workspace in that project inherits the role
Whole organisationStringLike organization:acme:*Avoid for anything except tightly limited read rolesEvery workspace, including future ones, can assume the role

Step-by-step

  1. Read the current state first (no changes). List existing providers with aws iam list-open-id-connect-providers; there can only be one provider per URL in an account. In HCP Terraform, list every workspace and variable set that holds AWS_ACCESS_KEY_ID, and record which IAM user each key belongs to with aws sts get-caller-identity from a controlled shell.
  2. Create the OIDC identity provider once per account.
    aws iam create-open-id-connect-provider \
      --url https://app.terraform.io \
      --client-id-list aws.workload.identity
    AWS verifies the issuer's TLS certificate against its library of trusted root certificate authorities and uses a thumbprint only as a fallback. HashiCorp's example Terraform for this step uses the aws_iam_openid_connect_provider resource with a tls_certificate data source to populate the thumbprint, which does no harm.
  3. Write the apply role trust policy. Save as trust-apply.json:
    {
      "Version": "2012-10-17",
      "Statement": [{
        "Effect": "Allow",
        "Principal": {"Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/app.terraform.io"},
        "Action": "sts:AssumeRoleWithWebIdentity",
        "Condition": {
          "StringEquals": {
            "app.terraform.io:aud": "aws.workload.identity",
            "app.terraform.io:sub": "organization:<ORG>:project:<PROJECT>:workspace:<WORKSPACE>:run_phase:apply"
          }
        }
      }]
    }
    Write trust-plan.json the same way with run_phase:plan. Note that HashiCorp's public example repository uses StringLike with run_phase:* for brevity; for production, prefer two roles with exact matches.
  4. Create the roles and attach least-privilege permissions.
    aws iam create-role --role-name tfc-payments-prod-apply \
      --assume-role-policy-document file://trust-apply.json --max-session-duration 3600
    aws iam create-role --role-name tfc-payments-prod-plan \
      --assume-role-policy-document file://trust-plan.json --max-session-duration 3600
    aws iam attach-role-policy --role-name tfc-payments-prod-plan \
      --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
    Give the apply role a policy built from the services the workspace actually manages. The AWS managed ReadOnlyAccess policy is a quick starting point for plans, but it is broad; replace it with a policy limited to the resources the refresh actually reads. See the AWS IAM Policies and Roles Guide.
  5. Set the workspace variables. As environment variables on the workspace (or a variable set scoped to exactly the workspaces the roles trust):
    VariableValueNotes
    TFC_AWS_PROVIDER_AUTHtrueRequired; without it HCP Terraform does not attempt AWS authentication
    TFC_AWS_PLAN_ROLE_ARNPlan role ARNFalls back to TFC_AWS_RUN_ROLE_ARN if unset
    TFC_AWS_APPLY_ROLE_ARNApply role ARNFalls back to TFC_AWS_RUN_ROLE_ARN if unset
    TFC_AWS_RUN_ROLE_ARNSingle role ARNRequired only if the two phase-specific variables are not both set
    TFC_AWS_WORKLOAD_IDENTITY_AUDIENCECustom audienceOptional; must match the provider client ID and the policy aud
    None of these are secrets, but mark the workspace variables as managed through code or restricted to administrators: whoever can change a role ARN can point the run at another role the subject is trusted by.
  6. Clean the provider block. Keep a region argument (or set AWS_REGION) and remove access_key, secret_key, profile and assume_role settings that existed for the static key. HashiCorp warns that other authentication arguments may interfere with dynamic credentials.
  7. Handle multiple accounts with aliases. Where one workspace deploys to several accounts, use tagged variables (for example TFC_AWS_PROVIDER_AUTH_PROD and TFC_AWS_RUN_ROLE_ARN_PROD), declare a Terraform variable named tfc_aws_dynamic_credentials, and point each aliased provider's shared_config_files at var.tfc_aws_dynamic_credentials.aliases["PROD"].shared_config_file. Each target account needs its own OIDC provider and role.
  8. Pilot on a non-production workspace. Leave the old key variables in place but rename them (for example prefix with DISABLED_) so the provider cannot find them, then queue a plan and an apply.

Verify it worked

  • Run log: the run completes without the static variables present. A temporary data "aws_caller_identity" output shows an assumed-role ARN for the plan role during plan and the apply role during apply.
  • CloudTrail: look for AssumeRoleWithWebIdentity from sts.amazonaws.com with userIdentity.type of WebIdentityUser. responseElements.subjectFromWebIdentityToken holds the full HCP Terraform subject, so you can see the workspace and phase. Global endpoint requests are logged in us-east-1, regional endpoint requests in their own region.
  • Negative tests: trigger a plan from a workspace the policy does not name, and confirm a plan cannot use the apply role. Both must fail with an AssumeRoleWithWebIdentity access denial.
  • Old identity: aws iam get-access-key-last-used on the old key shows no use after the cut-over date.
CloudTrail fieldWhat it tells you for HCP Terraform
userIdentity.identityProviderThe app.terraform.io provider ARN that vouched for the caller
responseElements.subjectFromWebIdentityTokenOrganisation, project, workspace and run phase
responseElements.audienceShould always be your configured audience
requestParameters.roleArnPlan or apply role used
additionalEventData.identityProviderConnectionVerificationMethodIAMTrustStore or Thumbprint

To trace a session back to a run, match the CloudTrail event time and subject with the run in HCP Terraform; the token also carries a terraform_run_id claim (for example run-X3n1AUXNGWbfECsJ) that HashiCorp recommends for logging, though AWS does not surface custom claims in the event.

Rollback and common errors

SymptomLikely causeFix
"Not authorized to perform sts:AssumeRoleWithWebIdentity"sub mismatch: project name wrong (often Default Project), workspace renamed, or phase not allowedCompare subjectFromWebIdentityToken from a failed attempt with the policy, character for character
Provider asks for credentials or uses an unexpected identityTFC_AWS_PROVIDER_AUTH missing or not true, or leftover static keys or profile settingsSet the variable; remove other auth settings from the provider block and workspace
Audience errorProvider client ID, policy aud and TFC_AWS_WORKLOAD_IDENTITY_AUDIENCE disagreeMake all three the same value
Works on HCP-hosted runs, fails on agentsAgent older than v1.7.0, or older than v1.12.0 with aliasesUpgrade the agent pool

Rollback: during the pilot, rename the disabled key variables back and remove TFC_AWS_PROVIDER_AUTH; nothing in AWS needs undoing. After cut-over, deactivate the old IAM user's keys (aws iam update-access-key --status Inactive), keep them inactive for at least one full release cycle, then delete the keys, the variables and, if nothing else uses it, the IAM user. See How to find unused AWS IAM roles and access keys. To withdraw access from a workspace, edit or delete the role's trust policy; sessions already issued last until they expire.

Choosing a role layout

LayoutBest forTrade-off
One plan and one apply role per workspaceProduction, regulated accountsMore roles to manage; clearest audit trail and smallest blast radius
One role per workspace, both phasesSmall teams with few workspacesEvery speculative plan, including those from pull requests, holds write rights
Shared read role per project, per-workspace apply rolesLarge estates with many similar workspacesProject membership becomes part of the trust; control workspace creation
Vault-backed dynamic credentialsOrganisations already brokering AWS access through VaultAdds Vault as a dependency; AWS trust is to Vault rather than to HCP Terraform

Small organisations can start with one role per workspace and split phases later. Large organisations should manage the roles themselves in a bootstrap workspace with its own tightly held apply role, so that no ordinary workspace can edit the trust policies that govern it.

Hardening and monitoring afterwards

  • Protect names. Restrict who can create, rename or move workspaces and projects, because the subject trusts names. Alert when a workspace whose name appears in a trust policy is deleted or renamed.
  • Use StringEquals for apply roles. Keep StringLike wildcards for read-only roles, and never wildcard the organisation segment.
  • Keep the bootstrap separate. The workspace that manages OIDC providers and CI roles should not be trusted by any role it creates.
  • Alert on drift: UpdateAssumeRolePolicy on tfc-* roles, CreateOpenIDConnectProvider or AddClientIDToOpenIDConnectProvider outside change windows, and AssumeRoleWithWebIdentity with an unexpected subject.
  • Review variable sets. A variable set applied globally that sets TFC_AWS_RUN_ROLE_ARN hands the same role to every workspace; scope sets explicitly.
MetricTarget
Workspaces or variable sets holding AWS_SECRET_ACCESS_KEYZero
Apply roles whose sub uses StringLikeZero, or documented exceptions
Workspaces with separate plan and apply rolesAll production workspaces
Trust policy changes without a linked change recordZero
Federated sessions with an unexpected subjectZero; investigate every one

Framework mapping

FrameworkReferenceRelevance
MITRE ATT&CKT1552.001 Credentials In Files; T1078.004 Cloud Accounts; T1098.003 Additional Cloud RolesStatic keys exposed in variables or code; abuse of valid cloud roles; tampering with role trust
NIST SP 800-53 Rev. 5IA-5, IA-5(7), AC-6, AU-6Authenticator management, no embedded static authenticators, least privilege, log review
NIST CSF 2.0PR.AA-01, PR.AA-05, DE.CM-03Credential management, least privilege, monitoring
OWASP NHI Top 10 2025NHI6 Insecure Cloud Deployment Configurations; NHI7 Long-Lived SecretsStatic CI credentials and loose OIDC conditions

Practitioner checklist

  • List every workspace and variable set holding AWS keys and the IAM users behind them.
  • Create the app.terraform.io OIDC provider in each target account with the agreed audience.
  • Create separate plan and apply roles, with StringEquals on aud and an exact sub for apply.
  • Check the project name in every subject, including Default Project.
  • Set TFC_AWS_PROVIDER_AUTH and the role ARN variables; remove other auth settings from provider blocks.
  • Upgrade self-hosted agents to a version that supports dynamic credentials and aliases.
  • Run positive and negative tests and confirm the subject in CloudTrail.
  • Deactivate, then delete, the old keys, variables and IAM user.
  • Restrict workspace creation and renaming, and alert on trust policy changes.

Standards and references

Related NHI Mgmt Group resources: Terraform and IaC Pipeline Credential Security Guide · How to set up workload identity federation for GitHub Actions to AWS · How to federate Bitbucket Pipelines to AWS · Multi-Cloud Workload Identity Troubleshooting Guide · AWS STS and AssumeRole Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 11 November 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org