Categories AWS DevOps

Storing Sensitive Information in AWS Parameter Store with Terraform

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 on random_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.

You May Also Like