skip to content

Provider Configuration and Authentication

How Terraform gets credentials is a security question disguised as a config question. I should be able to explain why keys never belong in the provider block and what replaces them in CI.

part ofTerraformoverview, primer and where to startread it →
on this pageshow

questions

5

In a Terraform aws provider block, why should you not set access_key and secret_key, and where does the provider get credentials instead?

level: juniorimportance: must knowfreq 74%

answer

  1. code is copied; credentials must be revocable
  2. hiding the value is not removing it
  3. the provider has its own credential chain
  4. short-lived role sessions beat static keys
  5. the provider block should hold no secrets

basics

~20 s

Keys written into HCL get committed, cloned and copied, and cannot be rotated per run. Omit access_key and secret_key; the AWS provider then resolves credentials itself from environment variables, the shared AWS config files, or an attached instance or CI role.

solid answer

~50 s

Terraform configuration is source code: it is reviewed, cloned, mirrored and kept in history forever. A long-lived `access_key`/`secret_key` pair pasted into a `provider "aws"` block therefore leaks to everyone who can read the repository, survives deletion because git keeps history, and cannot be scoped or rotated per run. The fix is not to hide the value better — a `secret.auto.tfvars` file or a `sensitive` variable still ends up as a long-lived static key sitting on disks and in CI logs. The fix is to stop putting credentials in the configuration at all. Leave the provider block to non-secret settings such as `region` and `default_tags`, and let the provider resolve credentials from its own chain: `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`/`AWS_SESSION_TOKEN` environment variables, a named profile in the shared config files, or, best of all, short-lived credentials from an attached instance role or an OIDC-federated CI role.

go deeper

for a junior

Be able to say plainly that Terraform files are committed source code, so a key placed in one is shared and permanent, and that you leave the credential arguments out and supply credentials through the environment or a profile instead.

for a middle

Explain the provider's credential chain concretely — environment variables, shared config profiles, instance and container roles — and why moving the key into a variable or a gitignored file changes visibility but not the underlying long-lived-secret problem.

for a senior

Show you would treat a pushed key as compromised and rotate at the issuer rather than force-push, and describe how you get an estate to zero static keys: role sessions on the machines, federated identity in CI, per-engineer profiles locally.

for a principal

Own the policy: what identity each Terraform execution context runs as, how those identities are scoped and audited, and what control actually prevents a key from reappearing — pre-commit and CI secret scanning plus not issuing long-lived keys in the first place.

## The shape of the mistake Every Terraform provider needs credentials for the API it talks to, and every provider offers arguments to supply them directly. For the AWS provider those are `access_key`, `secret_key` and `token`. They exist, they work, and using them is almost always wrong. The reason is not that the arguments are broken; it is that Terraform configuration is *code*. It is committed, reviewed in pull requests, cloned onto laptops, mirrored to CI caches, and archived in git history. A credential placed there inherits all of those properties. It is now readable by everyone with repository access, including people who joined after the commit. It stays readable after you delete the line, because the old blob is still in history. And it is a *long-lived* credential — an IAM user access key does not expire on its own — so a copy taken today still works next year. ## The near-miss fixes that are not fixes Candidates usually reach for one of these: - **Move it to a `.tfvars` file and gitignore it.** Better than committing, but you still have a permanent static key on every developer machine and in the CI secret store, shared by everyone, rotated by nobody. - **Make it a variable marked `sensitive = true`.** `sensitive` only redacts the value in Terraform's own CLI output and plan rendering. It does not encrypt anything, does not stop the value being passed on a command line or through `TF_VAR_` environment variables, and does not change the fact that the key is long-lived. - **Delete the commit and force-push.** The blob may still exist in forks, clones, CI caches and provider-side git storage. Once a credential has been pushed, the only real remediation is revocation and rotation at the issuer. None of these change the underlying property: a static key exists, and it is portable. ## What replaces it Omit the credential arguments entirely and let the provider run its resolution chain. A healthy provider block carries only non-secret configuration: ```hcl provider "aws" { region = "eu-west-1" default_tags { tags = { ManagedBy = "terraform" } } } ``` Credentials then come from outside the repository, in roughly this order of preference: 1. **A role attached to the compute that runs Terraform.** An EC2 instance profile, an ECS task role, or an EKS pod identity. The provider fetches short-lived credentials at runtime; nothing is stored anywhere. 2. **OIDC federation from CI.** The CI platform issues a short-lived signed token identifying the job, and the provider exchanges it for a temporary AWS role session. No key exists to steal. 3. **A named profile in the shared config files** (`~/.aws/config`, `~/.aws/credentials`), typically backed by SSO or an assumed role. Convenient for humans; each engineer has their own identity, which is what you want for audit. 4. **Environment variables** (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`) — fine when they carry *temporary* session credentials, a last resort when they carry a static key. All four keep the credential out of the code and, in the first three cases, make it short-lived and attributable to a specific identity. ```bash # credentials come from outside the configuration export AWS_PROFILE=platform-dev terraform plan ``` ## The point an interviewer is listening for Two things. First, that you name the real property that makes hardcoding bad — a code artifact is copied and retained, a credential must be revocable and short-lived, and those two requirements are incompatible. Second, that your remedy is *removal*, not concealment. "Put it in a variable" is the answer of someone who thinks the problem is visibility; "let the provider pick up a role session" is the answer of someone who understands the problem is the existence of a durable secret. One related fact worth carrying: this is about the *provider's* configuration, not about Terraform state. Credentials used to authenticate are not what lands in state — but resource attributes such as generated passwords are, which is a separate concern with its own handling.

  • Someone argues the key is safe because it is in a gitignored terraform.tfvars rather than in the provider block. What do you say?
    That it improves visibility hygiene and nothing else. The credential is still a long-lived static key living on every machine that runs Terraform and in the CI secret store, shared and rarely rotated. The property that matters is lifetime and revocability, not which file holds it. Replace it with a role session the runtime obtains at execution time.
  • A credential was committed six months ago and has since been removed from the file. Is the repository clean?
    No. The blob is still reachable in git history, and likely in clones, forks and CI caches. History rewriting does not reach those copies. Treat the credential as compromised: revoke and rotate it at the issuer, then check usage logs for the period it was exposed. Cleaning history is optional tidy-up, not remediation.
  • Does marking a Terraform variable sensitive protect a credential?
    Only in Terraform's own output. `sensitive = true` suppresses the value in plan and apply rendering and in `terraform output`. It does not encrypt the value, does not stop it reaching provider API calls, and does not prevent it being visible wherever it was supplied from. It is display hygiene, not a secret-management control.

saying these in an interview costs you the question

  • It is fine because the repository is private
  • Terraform cannot authenticate unless keys are in the config
  • Putting the key in a gitignored tfvars file solves it
  • Marking the variable sensitive protects the credential
  • Deleting the commit removes the exposure

context

open as a page

What does an assume_role block inside Terraform's aws provider do, and which credentials does it need before it can work?

level: middleimportance: should knowfreq 52%

basics

~20 s

It tells the provider to take the credentials it already resolved, call AWS STS to assume the named role, and use the temporary session credentials for every API call. It needs working base credentials that the target role's trust policy permits to assume it.

open as a page

Your Terraform aws provider block sets profile = "dev". It plans fine on a laptop but fails in CI, where credentials are supplied as environment variables. Why, and in what order does the AWS provider look for credentials?

level: middleimportance: should knowfreq 56%

basics

~20 s

Arguments written in the provider block win over everything else, so profile = "dev" makes the provider look for a shared-config profile that does not exist on the CI runner and ignore the environment variables. Remove the argument and let AWS_PROFILE or the environment supply it.

open as a page

Your CI runs Terraform against AWS with no long-lived access keys stored anywhere. How does the AWS provider obtain credentials in that setup, and what has to exist for it to work?

level: seniorimportance: should knowfreq 46%

basics

~20 s

The CI platform issues the job a short-lived signed OIDC token, and the AWS provider exchanges it for a temporary role session through STS web-identity federation. It needs the CI issuer registered as an OIDC identity provider in the account and a role whose trust policy accepts tokens from that issuer.

open as a page

What does the default_tags block in Terraform's aws provider do, and what does it not cover?

level: middleimportance: nice to knowfreq 40%

basics

~20 s

It sets tags applied to every taggable resource that provider instance manages, so cost and ownership tags stop being copy-pasted per resource. It does not reach resources managed elsewhere, data sources, or tagging shapes that are not a plain tags map.

open as a page