Skip to main content

Secrets Found in EC2 Auto Scaling Launch Configuration

Overview

This check scans your EC2 Auto Scaling launch configurations for sensitive data embedded in User Data scripts. User Data is a bootstrap script that runs when an instance launches. If secrets like passwords, API keys, or tokens are hardcoded there, they become visible to anyone who can access the instance or its metadata.

Note: Launch configurations are deprecated. AWS recommends using Launch Templates instead.

Risk

Hardcoded secrets in User Data create serious security vulnerabilities:

  • Anyone on the instance can read them - User Data is accessible to all processes and users on the EC2 instance
  • Secrets get logged - Bootstrap scripts often appear in logs, exposing credentials
  • Attackers gain lateral movement - Exposed API keys let attackers access other AWS services or accounts
  • Credential reuse amplifies damage - One leaked key can compromise multiple systems if reused

Remediation Steps

Prerequisites

You need:

  • AWS Console access with permissions to view and modify Auto Scaling groups and launch configurations
  • A secrets management solution (AWS Secrets Manager or Parameter Store)

AWS Console Method

  1. Find the affected launch configuration

    • Open the EC2 Console
    • In the left menu, scroll to Auto Scaling and click Launch Configurations
    • Locate the flagged launch configuration
  2. Review the User Data

    • Select the launch configuration
    • Click View User Data to see the bootstrap script
    • Identify any hardcoded secrets (passwords, API keys, tokens, connection strings)
  3. Move secrets to AWS Secrets Manager

    • Open Secrets Manager
    • Click Store a new secret
    • Select Other type of secret and enter your key-value pairs
    • Name the secret (e.g., myapp/database-credentials)
    • Complete the wizard
  4. Create a new launch configuration without secrets

    • Back in the EC2 Console, select your old launch configuration
    • Click Actions > Copy launch configuration
    • Give it a new name (e.g., my-app-lc-secure)
    • In Advanced Details, edit the User Data to remove hardcoded secrets
    • Replace them with code that fetches secrets at runtime (see example below)
    • Click Create launch configuration
  5. Update the Auto Scaling group

    • Go to Auto Scaling Groups in the left menu
    • Select the group using the old launch configuration
    • Click Edit
    • Under Launch configuration, select your new secure configuration
    • Click Update
  6. Delete the old launch configuration

    • Return to Launch Configurations
    • Select the old configuration with embedded secrets
    • Click Actions > Delete launch configuration
  7. Rotate the exposed secrets

    • Treat any secrets that were in User Data as compromised
    • Generate new credentials and update them in Secrets Manager
    • Update any systems using the old credentials

Example: Fetching secrets at runtime in User Data

Instead of:

#!/bin/bash
export DB_PASSWORD="my-secret-password"

Use:

#!/bin/bash
DB_PASSWORD=$(aws secretsmanager get-secret-value \
--secret-id myapp/database-credentials \
--query SecretString --output text \
--region us-east-1 | jq -r '.password')
export DB_PASSWORD
AWS CLI Method

Step 1: List launch configurations and check for secrets

# List all launch configurations
aws autoscaling describe-launch-configurations \
--region us-east-1 \
--query 'LaunchConfigurations[*].[LaunchConfigurationName,UserData]' \
--output table

Step 2: Decode and inspect User Data for a specific configuration

# Get User Data (base64 encoded) and decode it
aws autoscaling describe-launch-configurations \
--launch-configuration-names <your-launch-config-name> \
--region us-east-1 \
--query 'LaunchConfigurations[0].UserData' \
--output text | base64 --decode

Step 3: Store secrets in Secrets Manager

# Create a secret
aws secretsmanager create-secret \
--name myapp/database-credentials \
--secret-string '{"username":"admin","password":"new-secure-password"}' \
--region us-east-1

Step 4: Create IAM role for instances to access secrets

# Create trust policy file
cat > /tmp/trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole"
}
]
}
EOF

# Create the role
aws iam create-role \
--role-name EC2SecretsAccessRole \
--assume-role-policy-document file:///tmp/trust-policy.json

# Attach policy for Secrets Manager access
aws iam put-role-policy \
--role-name EC2SecretsAccessRole \
--policy-name SecretsManagerReadAccess \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": "arn:aws:secretsmanager:us-east-1:*:secret:myapp/*"
}]
}'

# Create instance profile
aws iam create-instance-profile \
--instance-profile-name EC2SecretsAccessProfile

# Add role to profile
aws iam add-role-to-instance-profile \
--instance-profile-name EC2SecretsAccessProfile \
--role-name EC2SecretsAccessRole

Step 5: Create new launch configuration without secrets

# Prepare secure User Data script
cat > /tmp/secure-userdata.sh << 'USERDATA'
#!/bin/bash
# Fetch secrets at runtime instead of hardcoding them
DB_CREDS=$(aws secretsmanager get-secret-value \
--secret-id myapp/database-credentials \
--query SecretString --output text \
--region us-east-1)
export DB_USERNAME=$(echo $DB_CREDS | jq -r '.username')
export DB_PASSWORD=$(echo $DB_CREDS | jq -r '.password')
# Continue with application startup...
USERDATA

# Create new launch configuration
aws autoscaling create-launch-configuration \
--launch-configuration-name my-app-lc-secure \
--image-id <your-ami-id> \
--instance-type t3.medium \
--iam-instance-profile EC2SecretsAccessProfile \
--security-groups <your-security-group-id> \
--user-data file:///tmp/secure-userdata.sh \
--region us-east-1

Step 6: Update Auto Scaling group

aws autoscaling update-auto-scaling-group \
--auto-scaling-group-name <your-asg-name> \
--launch-configuration-name my-app-lc-secure \
--region us-east-1

Step 7: Delete old launch configuration

aws autoscaling delete-launch-configuration \
--launch-configuration-name <old-launch-config-name> \
--region us-east-1
CloudFormation Template

This template creates a secure launch configuration that fetches secrets from Secrets Manager at runtime.

AWSTemplateFormatVersion: '2010-09-09'
Description: Secure Auto Scaling Launch Configuration without hardcoded secrets

Parameters:
AmiId:
Type: AWS::EC2::Image::Id
Description: AMI ID for the EC2 instances

InstanceType:
Type: String
Default: t3.medium
Description: EC2 instance type

SecurityGroupId:
Type: AWS::EC2::SecurityGroup::Id
Description: Security group for the instances

SecretArn:
Type: String
Description: ARN of the secret in Secrets Manager

Resources:
# IAM Role for EC2 instances to access Secrets Manager
EC2SecretsRole:
Type: AWS::IAM::Role
Properties:
RoleName: EC2SecretsAccessRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: SecretsManagerReadAccess
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource: !Ref SecretArn

# Instance Profile
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: EC2SecretsAccessProfile
Roles:
- !Ref EC2SecretsRole

# Secure Launch Configuration
SecureLaunchConfiguration:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
LaunchConfigurationName: my-app-lc-secure
ImageId: !Ref AmiId
InstanceType: !Ref InstanceType
IamInstanceProfile: !Ref EC2InstanceProfile
SecurityGroups:
- !Ref SecurityGroupId
UserData:
Fn::Base64: !Sub |
#!/bin/bash
# Fetch secrets at runtime - no hardcoded credentials
DB_CREDS=$(aws secretsmanager get-secret-value \
--secret-id ${SecretArn} \
--query SecretString --output text \
--region ${AWS::Region})
export DB_USERNAME=$(echo $DB_CREDS | jq -r '.username')
export DB_PASSWORD=$(echo $DB_CREDS | jq -r '.password')
# Continue with application startup...

Outputs:
LaunchConfigurationName:
Description: Name of the secure launch configuration
Value: !Ref SecureLaunchConfiguration

Deployment:

aws cloudformation create-stack \
--stack-name secure-launch-config \
--template-body file://template.yaml \
--parameters \
ParameterKey=AmiId,ParameterValue=ami-0123456789abcdef0 \
ParameterKey=SecurityGroupId,ParameterValue=sg-0123456789abcdef0 \
ParameterKey=SecretArn,ParameterValue=arn:aws:secretsmanager:us-east-1:123456789012:secret:myapp/database-credentials-AbCdEf \
--capabilities CAPABILITY_NAMED_IAM \
--region us-east-1
Terraform Configuration
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}

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

variable "ami_id" {
description = "AMI ID for the EC2 instances"
type = string
}

variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.medium"
}

variable "security_group_id" {
description = "Security group for the instances"
type = string
}

variable "secret_arn" {
description = "ARN of the secret in Secrets Manager"
type = string
}

# IAM Role for EC2 instances to access Secrets Manager
resource "aws_iam_role" "ec2_secrets_role" {
name = "EC2SecretsAccessRole"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
Action = "sts:AssumeRole"
}
]
})
}

resource "aws_iam_role_policy" "secrets_manager_access" {
name = "SecretsManagerReadAccess"
role = aws_iam_role.ec2_secrets_role.id

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue"
]
Resource = var.secret_arn
}
]
})
}

resource "aws_iam_instance_profile" "ec2_secrets_profile" {
name = "EC2SecretsAccessProfile"
role = aws_iam_role.ec2_secrets_role.name
}

# Secure Launch Configuration
resource "aws_launch_configuration" "secure_lc" {
name_prefix = "my-app-lc-secure-"
image_id = var.ami_id
instance_type = var.instance_type
iam_instance_profile = aws_iam_instance_profile.ec2_secrets_profile.name
security_groups = [var.security_group_id]

user_data = <<-USERDATA
#!/bin/bash
# Fetch secrets at runtime - no hardcoded credentials
DB_CREDS=$(aws secretsmanager get-secret-value \
--secret-id ${var.secret_arn} \
--query SecretString --output text \
--region us-east-1)
export DB_USERNAME=$(echo $DB_CREDS | jq -r '.username')
export DB_PASSWORD=$(echo $DB_CREDS | jq -r '.password')
# Continue with application startup...
USERDATA

lifecycle {
create_before_destroy = true
}
}

output "launch_configuration_name" {
description = "Name of the secure launch configuration"
value = aws_launch_configuration.secure_lc.name
}

Deployment:

terraform init
terraform plan -var="ami_id=ami-0123456789abcdef0" \
-var="security_group_id=sg-0123456789abcdef0" \
-var="secret_arn=arn:aws:secretsmanager:us-east-1:123456789012:secret:myapp/database-credentials-AbCdEf"
terraform apply

Verification

After remediation, verify the fix:

  1. Re-run the Prowler check

    prowler aws --check autoscaling_find_secrets_ec2_launch_configuration -r us-east-1
  2. Manually verify in AWS Console

    • Go to EC2 > Launch Configurations
    • Select your new launch configuration
    • Click View User Data
    • Confirm no secrets are visible in the script
  3. Test that instances can still access secrets

    • Launch a test instance from the new configuration
    • SSH into the instance and verify the application can retrieve credentials from Secrets Manager

Additional Resources

Notes

  • Launch configurations are deprecated - AWS recommends migrating to Launch Templates, which offer more features and better integration with modern AWS services
  • Rotate compromised secrets immediately - Any secrets found in User Data should be considered compromised and replaced
  • Use least privilege for IAM roles - Only grant access to the specific secrets the application needs
  • Consider using EC2 Instance Connect or SSM Session Manager - Avoid storing SSH keys in User Data
  • Enable secret rotation - AWS Secrets Manager supports automatic rotation for many secret types