AWS Provider — Credentials and Permissions
When you use the AWS provider in Terraform, Terraform must call the AWS API to create, update, and delete resources. Those API calls are authorized using AWS credentials.
Terraform does not have its own credential system — the AWS provider uses the same credential chain as the AWS CLI and the AWS SDK (environment variables, shared credentials file, IAM roles, etc.).
Whoever or whatever is running terraform plan and terraform apply must have an IAM identity (user or role) with permissions to perform the actions your configuration requires.
How Terraform Gets AWS Credentials
Section titled “How Terraform Gets AWS Credentials”The AWS provider resolves credentials in this order (same as the AWS CLI):
| Source | When it’s used |
|---|---|
| Environment variables | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and optionally AWS_SESSION_TOKEN (for temporary credentials). |
| Shared credentials file | ~/.aws/credentials (and the profile specified by AWS_PROFILE or the profile argument in the provider block). |
| Shared config file | ~/.aws/config — used for SSO, assumed roles, and other profile settings. |
| IAM role for EC2/ECS/Lambda | When Terraform runs on an EC2 instance (instance profile), in ECS (task role), or in Lambda (execution role), the provider uses the role attached to that environment. No keys needed. |
| Other mechanisms | The provider can use IAM Roles Anywhere, web identity tokens (e.g. OIDC in CI), or custom credential logic via the provider configuration. |
Example: configuring the provider with a region and optional profile:
provider "aws" { region = "us-east-1" profile = "my-profile" # optional; uses default profile if omitted}If you do not set profile, the provider uses the default profile from ~/.aws/credentials or, if no credentials file is present, the next source in the chain (e.g. environment variables or an IAM role).
IAM Permissions Terraform Needs
Section titled “IAM Permissions Terraform Needs”Terraform can only create or change resources for which the active credentials have IAM permissions. The identity (user or role) must be allowed to perform the API actions that your resources use. For example:
aws_instance→ec2:RunInstances,ec2:Describe*,ec2:CreateTags, etc.aws_s3_bucket→s3:CreateBucket,s3:PutBucketVersioning, etc.aws_iam_role→iam:CreateRole,iam:PutRolePolicy,iam:PassRole, etc.
There is no single “Terraform” managed policy that covers every possible resource. In practice you either:
- Grant broad but scoped permissions (e.g. a policy that allows common EC2, VPC, S3, IAM actions for the accounts/regions you use), or
- Build a custom IAM policy that allows only the actions and resources your Terraform config actually uses (least privilege).
Best practice: use a dedicated IAM user or role for Terraform (e.g. in CI or a separate “terraform” user) with a policy that limits which services and actions Terraform can use, and avoid using long-lived access keys when you can use roles (e.g. OIDC in GitHub Actions, or an EC2/ECS role when Terraform runs in AWS).
Running Terraform in CI (GitHub Actions, etc.)
Section titled “Running Terraform in CI (GitHub Actions, etc.)”In CI, do not commit access keys. Prefer:
- OIDC (OpenID Connect) — Let the CI provider (e.g. GitHub Actions) assume an IAM role with no long-lived secrets. The AWS provider picks up the temporary credentials from the environment.
- Short-lived credentials — If you must use keys, store them as CI secrets and inject them as
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY, andAWS_SESSION_TOKEN(if using a role assumption) in the job environment.
The provider block stays the same; only the source of the credentials changes (OIDC or env vars from secrets). For more on CI/CD layout and storing secrets, see Best Practices.
Using AWS Secrets with Terraform and External Secrets Operator
Section titled “Using AWS Secrets with Terraform and External Secrets Operator”External Secrets Operator (ESO) runs in Kubernetes and syncs secrets from external stores (e.g. AWS Secrets Manager, AWS Systems Manager Parameter Store) into Kubernetes Secret resources. Terraform and ESO work together like this:
1. Terraform creates the secret in AWS
Section titled “1. Terraform creates the secret in AWS”Use the AWS provider to create and populate the secret in AWS. ESO does not replace this — it only reads from AWS and writes into the cluster.
AWS Secrets Manager:
resource "aws_secretsmanager_secret" "app_db" { name = "prod/app/db-password"}
resource "aws_secretsmanager_secret_version" "app_db" { secret_id = aws_secretsmanager_secret.app_db.id secret_string = var.db_password # pass via TF_VAR_db_password or CI secret}SSM Parameter Store (SecureString):
resource "aws_ssm_parameter" "app_db" { name = "/prod/app/db-password" type = "SecureString" value = var.db_password description = "Database password for app"}Store the value in a variable marked sensitive = true and set it via environment or CI secrets — never in .tfvars or code.
2. ESO syncs from AWS into Kubernetes
Section titled “2. ESO syncs from AWS into Kubernetes”ESO runs in your cluster and uses a SecretStore or ClusterSecretStore (pointing at AWS via IAM role for service account, or credentials) and an ExternalSecret that references the AWS secret by name/ARN. ESO then creates or updates a Kubernetes Secret. Terraform does not run inside the cluster; ESO does the sync continuously or on a schedule.
So the flow is: Terraform → creates/updates secret in AWS → ESO → reads from AWS and writes a K8s Secret.
3. Optionally manage ESO resources with Terraform
Section titled “3. Optionally manage ESO resources with Terraform”You can keep the ExternalSecret and SecretStore definitions in Git and apply them with Terraform using the Kubernetes provider:
# Requires: Kubernetes provider + ESO CRDs installed in clusterresource "kubernetes_manifest" "cluster_secret_store" { manifest = { apiVersion = "external-secrets.io/v1beta1" kind = "ClusterSecretStore" metadata = { name = "aws-secrets" } spec = { provider = { aws = { service = "SecretsManager" region = "us-east-1" auth = { jwt = { serviceAccountRef = { name = "external-secrets-sa", namespace = "external-secrets" } } } } } } }}
resource "kubernetes_manifest" "external_secret" { manifest = { apiVersion = "external-secrets.io/v1beta1" kind = "ExternalSecret" metadata = { name = "app-db", namespace = "app" } spec = { refreshInterval = "1h" secretStoreRef = { name = "aws-secrets", kind = "ClusterSecretStore" } target = { name = "app-db-secret" } data = [{ secretKey = "password" remoteRef = { key = aws_secretsmanager_secret.app_db.name } }] } }}Then Terraform manages both the AWS secret and the ESO resources; the cluster’s ESO controller does the actual sync.
4. Using a secret value inside Terraform (no ESO)
Section titled “4. Using a secret value inside Terraform (no ESO)”If you need a secret inside Terraform (e.g. to set an RDS password or inject into user data), use a data source to read from AWS. This does not involve ESO:
data "aws_secretsmanager_secret_version" "existing" { secret_id = "prod/app/some-secret"}
# Use in a resource; mark any output as sensitiveresource "aws_db_instance" "main" { password = data.aws_secretsmanager_secret_version.existing.secret_string # ...}Summary: Terraform provisions secrets in AWS (Secrets Manager or SSM); ESO syncs those into Kubernetes Secrets. You can manage the ESO CRDs with Terraform via the Kubernetes provider, or manage them separately (Helm, kubectl). For Terraform itself needing a secret value, use aws_secretsmanager_secret_version (or SSM data source) instead of ESO.
Summary
Section titled “Summary”- Terraform’s permissions with AWS come from the credentials used by the AWS provider — same chain as the AWS CLI (env vars,
~/.aws/credentials, IAM roles for EC2/ECS/Lambda, etc.). - The IAM identity (user or role) behind those credentials must have permissions for the API actions your resources need; there is no single “Terraform” policy.
- Use a dedicated identity for Terraform and least privilege where possible; in CI, prefer OIDC or short-lived credentials over long-lived keys.