Introduction
Managing sensitive information like passwords and API keys in modern cloud infrastructure presents significant challenges. Organizations need a secure, scalable way to store and access these secrets while maintaining security best practices and enabling automation. AWS Systems Manager Parameter Store, combined with Terraform’s infrastructure as code capabilities, offers an elegant solution to this challenge.
AWS Parameter Store provides a centralized service for configuration data management and secrets storage, while Terraform enables us to automate the creation and management of these parameters. This combination delivers a powerful approach to managing sensitive information in your AWS infrastructure.
Understanding AWS Parameter Store
What is Parameter Store?
Parameter Store is a capability of AWS Systems Manager that provides secure, hierarchical storage for configuration data and secrets management. It offers two tiers of parameters:
- Standard parameters: Free tier offering basic parameter storage
- Advanced parameters: Paid tier providing additional features like parameter policies and larger parameter values
The service is designed to store configuration data, secrets, and other sensitive information in a centralized location, making it easier to manage and maintain security compliance.
Key Features
Parameter Store supports three parameter types:
- String: Plain text values
- StringList: Comma-separated list of values
- SecureString: Encrypted values using AWS KMS
A standout feature is its hierarchical storage structure using forward slashes (/), allowing logical organization of parameters. For example:
/environment/application/component/parameter
The service also includes version tracking and seamless integration with AWS KMS for encryption of sensitive data.
Setting Up Your Terraform Environment
Prerequisites
Before getting started, ensure you have:
- AWS CLI installed and configured with appropriate credentials
- Terraform installed (version 0.12 or later)
- Basic understanding of AWS IAM permissions
Your project structure should look something like this:
.
├── main.tf
├── variables.tf
├── outputs.tf
└── providers.tf
Provider Configuration
Configure your AWS provider in providers.tf
:
provider "aws" {
region = "us-west-2"
}
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
This configuration establishes your AWS region and ensures compatibility with AWS provider version 4.x. Using version constraints helps maintain consistency across team members and CI/CD pipelines.
Creating Basic Parameters
Simple String Parameters
Here’s how to create a basic parameter:
resource "aws_ssm_parameter" "basic_parameter" {
name = "/app/dev/database/url"
type = "String"
value = "jdbc:mysql://localhost:3306/mydb"
tags = {
Environment = "development"
Application = "myapp"
}
}
This creates a plain text parameter containing a database connection string. String parameters are ideal for non-sensitive configuration values that applications need at runtime. The tags help with organization and cost allocation tracking.
Parameter Organization
Implement a clear hierarchical structure for your parameters:
/env/app-name/component/parameter-name
For example:
/prod/myapp/database/url
/dev/myapp/api/endpoint
/staging/myapp/cache/host
Creating Secure Passwords with Terraform
Using the Random Provider
Terraform’s random provider offers capabilities to generate secure passwords and other random values:
terraform {
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.5.0"
}
}
}
The random provider enables generation of secure values directly within your Terraform configuration, eliminating the need for external scripts or manual password creation.
Generating Secure Passwords
Create strong, random passwords using the random_password
resource:
resource "random_password" "db_password" {
length = 16
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
min_lower = 1
min_upper = 1
min_numeric = 1
min_special = 1
}
This resource creates a strong password that meets typical security requirements. The constraints ensure password complexity by requiring at least one character from each category. The override_special
parameter limits special characters to those that are less likely to cause escaping issues in scripts or connection strings.
Password Generation Best Practices
When generating passwords with Terraform:
- Use sufficient length (16+ characters recommended)
- Include a mix of character types
- Limit problematic special characters that might need escaping
- Set
keepers
argument for controlled rotation:
resource "random_password" "rotated_password" {
length = 20
special = true
# Password will regenerate when this value changes
keepers = {
rotation_date = "2023-06-01"
}
}
The keepers
map is a powerful feature for implementing password rotation. When the rotation_date value changes, Terraform will generate a new password. This enables scheduled credential rotation without complex custom logic – simply update the date during your maintenance window and apply the changes.
Implementing Secure Parameters
Storing Generated Passwords
Store generated passwords in Parameter Store using SecureString:
resource "aws_ssm_parameter" "ssm_db_password" {
name = "/prod/myapp/database/ssm_password"
description = "Production database password"
type = "SecureString"
value = random_password.db_password.result
tags = {
Environment = "production"
}
}
This approach keeps the generated password in Terraform state while also storing it securely in Parameter Store. The unencrypted password will be stored in the Terraform state. Read here for more information.
SecureString Parameter Type
For other sensitive data beyond generated passwords, use SecureString parameters:
resource "aws_ssm_parameter" "secure_parameter" {
name = "/prod/myapp/database/secure_password"
description = "Production database password"
type = "SecureString"
value = var.database_password
tags = {
Environment = "production"
}
}
This example stores an externally provided password (perhaps from a CI/CD pipeline secret or manual input) as a SecureString. If no key_id
is specified, AWS Parameter Store automatically uses the default AWS managed KMS key for the account.
Access Control
Create necessary IAM policies:
resource "aws_iam_policy" "parameter_store_access" {
name = "parameter-store-access"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ssm:GetParameter",
"ssm:PutParameter"
]
Resource = "arn:aws:ssm:*:*:parameter/prod/myapp/*"
}
]
})
}
This IAM policy implements the principle of least privilege by restricting parameter access to only the specific path needed (/prod/myapp/*
). The actions are limited to just reading and writing parameters, rather than granting broader permissions. This policy could be attached to roles for applications or services that need to access these specific parameters.
StringList Parameters
StringList parameters are useful for storing multiple related values:
resource "aws_ssm_parameter" "allowed_ips" {
name = "/app/network/allowed-ips"
type = "StringList"
value = join(",", ["192.168.1.1", "192.168.1.2", "192.168.1.3"])
}
StringList parameters store multiple values in a single parameter, which is ideal for allowlists, configuration options, or any collection of related values. The AWS SDKs and CLI tools automatically parse these values into arrays or lists, making them convenient for application consumption. The join
function converts the Terraform list into the comma-separated format required by Parameter Store.
Working with Multiple Environments
Environment Separation
Utilize Terraform workspaces for environment management:
locals {
environment = terraform.workspace
app_name = "myapp"
}
resource "aws_ssm_parameter" "environment_configs" {
for_each = {
database_url = var.database_url
api_endpoint = var.api_endpoint
cache_cluster_url = var.cache_cluster_url
}
name = "/${local.environment}/${local.app_name}/${each.key}"
type = "String"
value = each.value
}
This configuration leverages Terraform workspaces to dynamically set the environment prefix in parameter paths. With this approach, you can use the same Terraform code across different environments by simply switching workspaces (terraform workspace select dev|staging|prod
). The for_each
loop creates multiple parameters efficiently, reducing repetitive code.
Automation Strategies
Implement bulk parameter creation using YAML configuration:
locals {
parameters = yamldecode(file("${path.module}/parameters.yaml"))
}
resource "aws_ssm_parameter" "dynamic_parameters" {
for_each = local.parameters
name = each.value.name
type = each.value.type
value = each.value.value
description = each.value.description
}
This approach separates parameter definitions from Terraform code, allowing for easier management of large numbers of parameters. The YAML file can be updated independently of the Terraform code, and the configuration could even be generated by other tools or processes.
Integration Examples
EC2 Integration
Configure EC2 instances to access parameters:
resource "aws_launch_template" "app_server" {
user_data = base64encode(<<-EOF
#!/bin/bash
aws ssm get-parameter \
--name "/app/prod/config" \
--region ${data.aws_region.current.name} \
--with-decryption \
--output text \
--query Parameter.Value > /app/config.json
EOF
)
}
This user data script runs when the EC2 instance launches, retrieving configuration from Parameter Store and saving it to a file. Using the AWS CLI with the --with-decryption
flag allows the instance to retrieve and decrypt SecureString values, assuming it has the necessary IAM permissions.
ECS Implementation
Inject secrets into ECS tasks:
resource "aws_ecs_task_definition" "app" {
container_definitions = jsonencode([
{
name = "app"
secrets = [
{
name = "DB_PASSWORD"
valueFrom = aws_ssm_parameter.db_password.arn
}
]
}
])
}
This ECS task definition demonstrates how to inject parameters as environment variables into containers. ECS integrates directly with Parameter Store, securely providing the values to the container at runtime without exposing them in the task definition or requiring custom retrieval code.
Database Configuration
Manage database credentials securely:
resource "aws_db_instance" "database" {
password = data.aws_ssm_parameter.db_password.value
username = data.aws_ssm_parameter.db_username.value
}
This example uses Parameter Store values directly in the Terraform configuration. The data
resource fetches the parameter values during Terraform execution, allowing you to maintain sensitive values outside of version control while still using them to configure resources.
Best Practices and Common Pitfalls
Security Considerations
- Always use SecureString for sensitive data
- Implement least-privilege access through IAM
- Rotate secrets regularly using parameter versions
- Enable AWS CloudTrail for audit logging
- State file security: When generating passwords with Terraform, remember that sensitive values are stored in the state file. Ensure your state file is encrypted and access-restricted.
- Rotation strategy: Consider implementing an automated rotation system using the
keepers
argument onrandom_password
resources.
Common Issues
- Version conflicts: Use
ignore_changes
in lifecycle blocks - KMS permissions: Ensure proper key policies are in place
- Rate limiting: Implement exponential backoff in applications
Solutions and Workarounds
- Use parameter policies for automatic rotation
- Implement proper error handling in applications
- Cache parameters when possible to avoid rate limits
- Use parameter hierarchies for better organization
Conclusion
AWS Systems Manager Parameter Store, when combined with Terraform, provides a robust solution for managing sensitive information in your cloud infrastructure. Remember that proper secrets management is crucial for maintaining secure infrastructure. Regular audits, updates, and following security best practices will help ensure your sensitive information remains protected while being easily accessible to your applications and services.