Skip to content

Referencing Secrets

This page explains how to reference existing AWS Secrets Manager secrets in your Terraform configuration.

Prerequisites

  • Secret must already exist in AWS Secrets Manager (created manually or by customer)
  • You must have the secret ARN
  • Terraform execution role must have secretsmanager:GetSecretValue permission

Basic Configuration

Step 1: Define the Secret Reference

Add the secret reference to your secretsmanager_secrets variable:

1
2
3
4
5
secretsmanager_secrets = {
  domain_join_creds = {
    arn = "arn:aws:secretsmanager:us-east-2:271851283454:secret:sapphy/domainjoin-Z9EFWO"
  }
}

Parameters:

Parameter Type Required Description
arn String Yes Full ARN of the existing secret in Secrets Manager
version_stage String No Version stage to retrieve (e.g., "AWSCURRENT", "AWSPENDING")
version_id String No Specific version ID to retrieve

Step 2: Use the Secret in Other Resources

The module makes the secret available for reference by other Terraform resources. The most common use case is domain join:

domain_join = {
  SelfManaged_AD_Domain_Join = {
    ad_type = "self-managed"
    content = {
      mainSteps = [{
        inputs = {
          directoryName  = "example.com"
          dnsIpAddresses = ["10.0.1.10"]
          secretArn      = "arn:aws:secretsmanager:us-east-2:271851283454:secret:sapphy/domainjoin-Z9EFWO"
        }
      }]
    }
  }
}

Version Management

Using Current Version (Default)

If no version is specified, the module retrieves the current version:

secretsmanager_secrets = {
  my_secret = {
    arn = "arn:aws:secretsmanager:us-east-2:123456789:secret:app/config-ABC123"
  }
}

Using Specific Version Stage

Version stages are labels like "AWSCURRENT" or "AWSPENDING" that AWS manages:

secretsmanager_secrets = {
  my_secret = {
    arn           = "arn:aws:secretsmanager:us-east-2:123456789:secret:app/config-ABC123"
    version_stage = "AWSPENDING"  # For testing new credential rotation
  }
}

Using Specific Version ID

For precise control, use a specific version UUID:

secretsmanager_secrets = {
  my_secret = {
    arn        = "arn:aws:secretsmanager:us-east-2:123456789:secret:app/config-ABC123"
    version_id = "1234abcd-12ab-34cd-56ef-1234567890ab"
  }
}

Multiple Secrets

You can reference multiple secrets in the same configuration:

secretsmanager_secrets = {
  domain_join_prod = {
    arn = "arn:aws:secretsmanager:us-east-2:123456789:secret:domain/prod-ABC123"
  }
  domain_join_nonprod = {
    arn = "arn:aws:secretsmanager:us-east-2:123456789:secret:domain/nonprod-XYZ789"
  }
  api_key = {
    arn = "arn:aws:secretsmanager:us-east-2:123456789:secret:app/apikey-DEF456"
  }
}

Required IAM Permissions

The Terraform execution role needs permission to read the secret:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret"
      ],
      "Resource": "arn:aws:secretsmanager:us-east-2:271851283454:secret:sapphy/domainjoin-*"
    }
  ]
}

Least Privilege Example

Restrict to specific secrets using wildcards:

{
  "Effect": "Allow",
  "Action": "secretsmanager:GetSecretValue",
  "Resource": [
    "arn:aws:secretsmanager:us-east-2:271851283454:secret:domain/*",
    "arn:aws:secretsmanager:us-east-2:271851283454:secret:app/config-*"
  ]
}

Tag-Based Access Control

Restrict based on secret tags:

{
  "Effect": "Allow",
  "Action": "secretsmanager:GetSecretValue",
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "aws:ResourceTag/TerraformManaged": "true"
    }
  }
}

Security Best Practices

1. Never Store Secret Values in Terraform State

This module is designed to only reference secret ARNs, not retrieve values into state. The actual secret retrieval happens at runtime by EC2 instances or other AWS services.

Don't do this:

# BAD - Exposes secret value in Terraform state
locals {
  password = data.aws_secretsmanager_secret_version.my_secret.secret_string
}

Do this instead:

# GOOD - Only reference the ARN, let services retrieve at runtime
domain_join = {
  example = {
    content = {
      mainSteps = [{
        inputs = {
          secretArn = "arn:aws:secretsmanager:..."
        }
      }]
    }
  }
}

2. Use Separate Secrets Per Environment

secretsmanager_secrets = {
  domain_join_dev = {
    arn = "arn:aws:secretsmanager:us-east-2:111111111:secret:domain/dev-ABC"
  }
  domain_join_prod = {
    arn = "arn:aws:secretsmanager:us-west-2:222222222:secret:domain/prod-XYZ"
  }
}

3. Limit Terraform Role Permissions

Grant only the minimum required permissions:

  • secretsmanager:DescribeSecret - To get secret metadata
  • secretsmanager:GetSecretValue - Only if absolutely necessary (avoid if possible)

For domain join, EC2 instances retrieve secrets, not Terraform—so Terraform role doesn't need GetSecretValue.

4. Enable CloudTrail Logging

Monitor secret access with CloudTrail:

# Example CloudWatch alarm for unexpected secret access
resource "aws_cloudwatch_metric_alarm" "secret_access" {
  alarm_name          = "secrets-manager-unusual-access"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = "1"
  metric_name         = "SecretAccessCount"
  namespace           = "AWS/SecretsManager"
  period              = "300"
  statistic           = "Sum"
  threshold           = "10"
}

Troubleshooting

Secret Not Found

Error: ResourceNotFoundException: Secrets Manager can't find the specified secret

Solutions: - Verify the ARN is correct (check for typos) - Ensure the secret exists in the specified region - Check that you're using the correct AWS account

Access Denied

Error: AccessDeniedException: User is not authorized to perform: secretsmanager:GetSecretValue

Solutions: - Verify Terraform execution role has secretsmanager:GetSecretValue permission - Check IAM policy resource ARN matches the secret ARN - Ensure there are no deny policies blocking access

Version Not Found

Error: ResourceNotFoundException: You provided a VersionId that does not exist

Solutions: - Verify the version ID exists: aws secretsmanager list-secret-version-ids --secret-id <arn> - Check version stage spelling (e.g., "AWSCURRENT" not "awscurrent") - Ensure version hasn't been deleted

Common Patterns

Pattern 1: Domain Join (Primary Use Case)

# Reference the secret
secretsmanager_secrets = {
  domain_creds = {
    arn = "arn:aws:secretsmanager:us-east-2:123456789:secret:domain/join-ABC"
  }
}

# Use in domain join
domain_join = {
  CustomerDomain = {
    ad_type = "self-managed"
    content = {
      mainSteps = [{
        inputs = {
          directoryName  = "corp.example.com"
          dnsIpAddresses = ["10.0.1.10", "10.0.1.11"]
          secretArn      = "arn:aws:secretsmanager:us-east-2:123456789:secret:domain/join-ABC"
        }
      }]
    }
  }
}

Pattern 2: Multi-Account Setup

# Secrets in different accounts for different environments
secretsmanager_secrets = {
  dev_domain = {
    arn = "arn:aws:secretsmanager:us-east-2:111111111:secret:domain/dev-ABC"
  }
  prod_domain = {
    arn = "arn:aws:secretsmanager:us-west-2:222222222:secret:domain/prod-XYZ"
  }
}

Pattern 3: Rotation Support

# Use AWSPENDING to test rotated credentials before promotion
secretsmanager_secrets = {
  active_creds = {
    arn           = "arn:aws:secretsmanager:us-east-2:123456789:secret:app/db-ABC"
    version_stage = "AWSCURRENT"
  }
  test_rotated_creds = {
    arn           = "arn:aws:secretsmanager:us-east-2:123456789:secret:app/db-ABC"
    version_stage = "AWSPENDING"
  }
}