Terraform’s AWS provider is designed to be flexible in how it authenticates to your AWS account, but this flexibility can also be a source of confusion. The core issue is that Terraform needs AWS credentials to make API calls, and there are several ways to provide them, each with its own nuances.

Let’s see this in action. Imagine you have a simple Terraform configuration to create an S3 bucket:

provider "aws" {
  region = "us-east-1"
}

resource "aws_s3_bucket" "example" {
  bucket = "my-unique-terraform-bucket-12345"
  acl    = "private"
}

When you run terraform init and then terraform apply, Terraform will try to authenticate to AWS. If it fails, you’ll see an error. The question is, how is it trying to authenticate, and what might be going wrong?

Terraform, by default, searches for credentials in a specific order. It’s like a detective looking for clues:

  1. Environment Variables: This is often the most direct method. Terraform looks for AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and optionally AWS_SESSION_TOKEN. If these are set, Terraform will use them.

    • Diagnosis: Run printenv | grep AWS. If you see these variables, they are being used.
    • Fix: To set them:
      export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
      export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
      # If using temporary credentials (e.g., from STS)
      # export AWS_SESSION_TOKEN="AQoDYXdzEJr..."
      
    • Why it works: These variables directly map to the standard AWS SDK credential lookup chain, which the Terraform provider adheres to.
  2. Shared Credential File (~/.aws/credentials): If environment variables aren’t found, Terraform checks this file. It supports profiles, allowing you to manage multiple AWS accounts or roles.

    • Diagnosis: Check the contents of ~/.aws/credentials. Look for a section like [default] or a named profile like [my-profile].
    • Fix:
      • Using the default profile: Ensure ~/.aws/credentials has:
        [default]
        aws_access_key_id = AKIAIOSFODNN7EXAMPLE
        aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
        
      • Using a named profile:
        [my-profile]
        aws_access_key_id = AKIAIOSFODNN7EXAMPLE
        aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
        
        Then, tell Terraform to use it:
        provider "aws" {
          region  = "us-east-1"
          profile = "my-profile"
        }
        
    • Why it works: The AWS SDK and Terraform provider are configured to read this standard file format, allowing for organized credential management.
  3. Shared Configuration File (~/.aws/config): This file can also define profiles, especially for role assumption.

    • Diagnosis: Check ~/.aws/config for profiles.
    • Fix: To use a role with a named profile:
      [profile my-role-profile]
      role_arn = arn:aws:iam::123456789012:role/MyTerraformRole
      source_profile = default # Or another profile that has permissions to assume this role
      region = us-east-1
      
      Then, in your Terraform code:
      provider "aws" {
        region  = "us-east-1"
        profile = "my-role-profile"
      }
      
      Terraform will use the source_profile to get temporary credentials to assume role_arn.
    • Why it works: This allows Terraform to leverage AWS’s Identity and Access Management (IAM) role assumption mechanism, providing temporary, more secure credentials without long-lived access keys.
  4. EC2 Instance Metadata Service / ECS Task Role: If Terraform is running on an EC2 instance or within an ECS task, it can automatically use the IAM role attached to that resource.

    • Diagnosis: Check if your Terraform code is being executed on an EC2 instance or ECS task. If so, check the IAM role attached to that resource in the AWS console.
    • Fix: No Terraform code change is usually needed. Ensure the EC2 instance profile or ECS task role has the necessary permissions (e.g., AmazonS3FullAccess if you’re creating S3 buckets).
    • Why it works: The AWS SDK (and thus Terraform) queries the instance metadata endpoint (http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME) to retrieve temporary credentials automatically.
  5. Hardcoded in Provider Block (Discouraged): You can directly put keys in your provider block. This is highly discouraged due to security risks.

    • Diagnosis: Look for access_key and secret_key arguments directly within the provider "aws" {} block in your .tf files.
    • Fix: Remove them and use one of the above methods. If you absolutely must (e.g., for a very short-lived, isolated test), you’d do:
      provider "aws" {
        region = "us-east-1"
        access_key = "AKIAIOSFODNN7EXAMPLE"
        secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
      }
      
    • Why it works: It directly provides the credentials, but makes them visible in your code, which is a major security vulnerability.

The most common pitfalls are:

  • Incorrectly configured ~/.aws/credentials or ~/.aws/config files: Typos, missing profile names, or incorrect role_arn values.
  • Environment variables not being exported correctly: Using set instead of export in some shells, or setting them in a script that doesn’t persist them to the current shell session.
  • IAM permissions: Even if authentication succeeds, the credentials used might not have the necessary permissions to perform the requested actions (e.g., s3:CreateBucket). This will result in an AccessDenied error, not an authentication error.

Once you’ve resolved authentication, the next hurdle is often ensuring the IAM principal (user or role) has the permissions to perform the actions defined in your Terraform code.

Want structured learning?

Take the full Terraform course →