Skip to content

Deploy with OpenTofu and Terraform without cloud access keys

Long-lived cloud keys stored as workspace variables are the most common way infrastructure pipelines get breached. OIDC federation replaces them with credentials that expire minutes after a run.

The problem with static keys

The classic setup is an AWS access key, a Google Cloud service account key, or an Azure client secret saved as a sensitive variable. It works, but the key:

  • never expires unless someone remembers to rotate it;
  • usually has broad permissions, because one key serves every run;
  • can be copied out by any provider, module, or script in a run; and
  • is shared by plan and apply, so a pull request plan holds write access.

How OIDC federation works

With OpenID Connect (OIDC) federation, your cloud trusts the platform that runs your plans instead of trusting a stored secret:

  1. When a run starts, the platform issues it a signed identity token. The token says which organization, workspace, and run phase it belongs to.
  2. The run presents that token to the cloud's security token service.
  3. The cloud checks the signature and the claims against a trust policy you wrote, then returns temporary credentials for one role.
  4. The credentials expire shortly after the run ends.

Each cloud has its own name for the same idea:

CloudFeature
AWSIAM OIDC identity provider and AssumeRoleWithWebIdentity
Google CloudWorkload Identity Federation with service account impersonation
Microsoft AzureFederated identity credentials on an app registration

Scope the trust tightly

The trust policy is where the security comes from. Bind it to the claims that identify one workspace and one phase, not just to the platform:

  • One role per workspace. A run for network-production should never be able to assume the role for billing-production.
  • A read-only role for pull request plans. Speculative plans run code that hasn't been reviewed yet. Give them a role that can read, but not change, your infrastructure.
  • Least privilege for apply. Grant the apply role only the services the workspace manages.

Set it up in StackShip

StackShip calls these credential profiles. Each run gets temporary credentials, and no cloud key is stored.

  1. Go to Credentials and create a profile for the workspace. Choose AWS, Google Cloud, or Microsoft Azure.
  2. Add a version for a run phase: Plan and apply for normal runs, or Speculative plan for pull request plans.
  3. Enter the role to assume: an AWS role ARN, a Google service account and workload identity provider, or Azure client, tenant, and subscription IDs.
  4. Copy the provider trust template StackShip shows and apply it in your cloud account. It already limits the trust to this workspace and phase.
  5. Select Verify version. Runs use the version only after it verifies.

Your provider blocks don't need keys or a role ARN; the run environment supplies the temporary credentials. Revoking a version stops it from being used immediately, including by queued runs, and every change is in the audit log.

When a service has no federation

Some providers only accept an API token. For those, keep the secret write-only, scope it to the smallest permission set the provider allows, and never expose it to pull request plans. StackShip's generic environment secrets follow these rules: values can't be read back and are only given to plan-and-apply runs.

Next steps

See how StackShip isolates runs and protects state, or read the credential profile reference for every field and limit.

Get started

Run your first OpenTofu plan today.

Free for every organization. Sign up with email or Google.