Security¶
This document covers security features and best practices for the Terraformer module.
SSH Key Management¶
Auto-Generated Keys (Recommended)¶
When ssh_key_name is not provided, the module automatically generates and rotates SSH keys.
Key Generation¶
module "terraformer" {
source = "registry.infrahouse.com/infrahouse/terraformer/aws"
version = "~> 2.0"
environment = "production"
zone_id = "Z1234567890ABC"
subnet = "subnet-abc123"
# Omit ssh_key_name for auto-generation
ssh_key_rotation_days = 90 # Optional, defaults to 90
}
What happens:
- TLS private key generated by Terraform (4096-bit RSA)
- Public key uploaded to AWS as Key Pair
- Private key stored in Secrets Manager via
infrahouse/secret/awsmodule - Key automatically rotates every 90 days (configurable)
Accessing Auto-Generated Keys¶
# Get secret ARN from Terraform output
SECRET_ARN=$(terraform output -raw ssh_key_secret_arn)
# Download private key
aws secretsmanager get-secret-value \
--secret-id "$SECRET_ARN" \
--query SecretString \
--output text > ~/.ssh/terraformer.pem
chmod 600 ~/.ssh/terraformer.pem
Controlling Access to Keys¶
Use ssh_key_readers to specify who can read the private key:
module "terraformer" {
# ...
ssh_key_readers = [
"arn:aws:iam::123456789012:role/DevOpsTeam",
"arn:aws:iam::123456789012:role/SRE",
"arn:aws:iam::123456789012:user/admin"
]
}
This creates a Secrets Manager resource policy allowing only specified principals to read the key.
User-Provided Keys¶
If you have existing key management:
Security considerations:
- You're responsible for key rotation
- Private key not stored in Terraform state
- No automatic rotation
IAM Permissions¶
Base Permissions¶
The instance profile includes minimal base permissions:
AssumeRole Capability¶
Why: Allows terraformer to assume roles in other accounts for cross-account operations.
CloudWatch Logs¶
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
],
"Resource": "arn:aws:logs:*:*:log-group:/aws/ec2/terraformer/${environment}/${dns_name}:*"
}
Why: Write operations logs for audit trail. Scoped to terraformer log group only.
CloudWatch Metrics¶
{
"Effect": "Allow",
"Action": "cloudwatch:PutMetricData",
"Resource": "*",
"Condition": {
"StringEquals": {
"cloudwatch:namespace": "terraformer"
}
}
}
Why: Publish custom metrics. Restricted to terraformer namespace only.
EC2 Tags¶
Why: Required for CloudWatch agent's ec2tagger to enrich metrics with instance tags.
Adding Permissions¶
For additional operations (e.g., S3 state access):
data "aws_iam_policy_document" "extra_permissions" {
statement {
sid = "TerraformStateAccess"
actions = [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
]
resources = [
"arn:aws:s3:::my-terraform-states/*"
]
}
statement {
sid = "DynamoDBLocking"
actions = [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem"
]
resources = [
"arn:aws:dynamodb:*:*:table/terraform-locks"
]
}
}
module "terraformer" {
# ...
extra_instance_profile_permissions = data.aws_iam_policy_document.extra_permissions.json
}
Cross-Account Access¶
Configure trust relationships in target accounts:
# In target account
resource "aws_iam_role" "terraformer_access" {
name = "terraformer-admin-access"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::SOURCE-ACCOUNT:role/terraformer-XXXXXXXXXXXX"
}
Action = "sts:AssumeRole"
Condition = {
StringEquals = {
"sts:ExternalId" = "unique-external-id"
}
}
}]
})
managed_policy_arns = [
"arn:aws:iam::aws:policy/ReadOnlyAccess"
]
}
Best practices:
- Use ExternalId for additional security
- Grant least privilege (not AdministratorAccess unless necessary)
- Enable CloudTrail in both accounts for audit
- Consider session duration limits
Auto-Recovery¶
Hardware Failure Protection¶
System Status Check Alarm:
- Metric:
StatusCheckFailed_System - Threshold: 2 consecutive failures (2 minutes)
- Action:
ec2:recover
What it does:
- Migrates instance to healthy hardware
- Preserves instance ID, private IP, Elastic IP, volumes
- Minimal downtime (~2-3 minutes)
When it triggers:
- Host hardware degradation
- Network path failures
- Power issues on underlying host
Software Failure Protection¶
Instance Status Check Alarm:
- Metric:
StatusCheckFailed_Instance - Threshold: 3 consecutive failures (3 minutes)
- Action:
ec2:reboot
What it does:
- Gracefully reboots the instance
- Reinitializes operating system
- Re-runs cloud-init and Puppet
When it triggers:
- Kernel panics
- Out of memory conditions
- Network misconfiguration
- Failed system checks
Disabling Auto-Recovery¶
Auto-recovery is always enabled (no variable to disable). This is intentional for a critical administrative instance.
If you need to disable during maintenance:
# Temporarily disable alarms
INSTANCE_ID=$(terraform output -raw instance_id)
aws cloudwatch disable-alarm-actions \
--alarm-names \
"terraformer-system-auto-recovery-$INSTANCE_ID" \
"terraformer-instance-status-check-$INSTANCE_ID"
# Perform maintenance
# Re-enable
aws cloudwatch enable-alarm-actions \
--alarm-names \
"terraformer-system-auto-recovery-$INSTANCE_ID" \
"terraformer-instance-status-check-$INSTANCE_ID"
Network Security¶
Security Group Rules¶
SSH (port 22):
- Source: VPC CIDR (always allowed)
- Additional CIDRs via
extra_ssh_cidrsvariable
ICMP (all types):
- Source: VPC CIDR only
- Allows ping, traceroute from internal networks only
Egress:
- All traffic allowed (required for AWS API, Puppet, package updates)
Subnet Options¶
Private Subnet (Recommended for Production):
- Instance gets private IP only
- DNS record points to private IP
- Access via bastion host, VPN, or AWS SSM Session Manager
Public Subnet (For Development/Testing):
module "terraformer" {
# ...
subnet = data.aws_subnet.public.id
extra_ssh_cidrs = ["203.0.113.0/24"] # Your office/home IP
}
- Instance gets public IP (if subnet has
map_public_ip_on_launch = true) - DNS record automatically uses public IP
- Direct SSH access from allowed CIDRs
VPC Endpoints (Recommended)¶
# Add VPC endpoints for AWS services
resource "aws_vpc_endpoint" "s3" {
vpc_id = data.aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.s3"
}
resource "aws_vpc_endpoint" "secretsmanager" {
vpc_id = data.aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.secretsmanager"
vpc_endpoint_type = "Interface"
subnet_ids = [data.aws_subnet.private.id]
security_group_ids = [aws_security_group.vpc_endpoints.id]
}
Metadata Security¶
IMDSv2 Enforcement¶
The instance enforces IMDSv2 (Instance Metadata Service Version 2):
Why: Protects against SSRF attacks that could retrieve IAM credentials.
Audit and Compliance¶
CloudWatch Logs¶
All operations on the instance are logged:
- Log Group:
/aws/ec2/terraformer/${environment}/${dns_name} - Retention: 365 days (ISO 27001 compliant)
- Streams: Organized by instance ID and log type
What's logged:
- System logs (syslog, auth.log)
- Application logs (terraform, aws cli)
- Puppet runs
- User sessions
Enabling Session Manager¶
For additional audit capabilities, enable AWS Systems Manager Session Manager:
data "aws_iam_policy_document" "extra_permissions" {
statement {
actions = [
"ssmmessages:CreateControlChannel",
"ssmmessages:CreateDataChannel",
"ssmmessages:OpenControlChannel",
"ssmmessages:OpenDataChannel",
"ssm:UpdateInstanceInformation"
]
resources = ["*"]
}
statement {
actions = [
"logs:CreateLogStream",
"logs:PutLogEvents"
]
resources = [
"arn:aws:logs:*:*:log-group:/aws/ssm/sessions:*"
]
}
}
module "terraformer" {
# ...
extra_instance_profile_permissions = data.aws_iam_policy_document.extra_permissions.json
}
Benefits:
- All sessions logged to CloudWatch
- No need for SSH keys
- Integration with AWS CloudTrail
Security Checklist¶
- [ ] Instance in private subnet with NAT gateway (production) or public subnet with restricted
extra_ssh_cidrs(development) - [ ] SSH access restricted to VPC CIDR and explicitly allowed CIDRs only
- [ ] Auto-generated SSH keys with rotation enabled
- [ ]
ssh_key_readersconfigured for key access control - [ ] Extra IAM permissions follow least privilege
- [ ] Cross-account roles use ExternalId
- [ ] CloudWatch Logs enabled with appropriate retention
- [ ] Auto-recovery alarms active
- [ ] VPC endpoints configured for AWS services
- [ ] CloudTrail enabled in all accounts
- [ ] Regular security audits of assumed roles