Back to Checks
[DEPRECATED] EC2 Auto Scaling launch configuration user data contains no secrets
autoscaling_find_secrets_ec2_launch_configuration
Service: autoscalingSeverity: critical
OfficialBuilt-inVerified
Categories:secrets
Resource Type:AwsAutoScalingLaunchConfiguration
[DEPRECATED] EC2 Auto Scaling launch configurations are analyzed for secrets embedded in User Data, such as passwords, tokens, or API keys in bootstrapping scripts.
Risk
Secrets in User Data erode confidentiality and integrity:
- Instance users or processes can read or log them
- Exposed keys enable unauthorized API calls, data exfiltration, and lateral movement
- Credential reuse increases blast radius across accounts and services
Run this check with Prowler
Run in Prowler CloudRun
prowler aws --checks autoscaling_find_secrets_ec2_launch_configurationRecommendation
Never place secrets in User Data.
- Use a managed secret store with an instance role to fetch at runtime
- Enforce least privilege, rotate secrets, and avoid writing secrets to logs
- Prefer short-lived, scoped credentials and layer controls for defense in depth
Remediation
Terraform
# Launch configuration with no secrets in user data
resource "aws_launch_configuration" "<example_resource_name>" {
image_id = "<AMI_ID>"
instance_type = "<INSTANCE_TYPE>"
user_data = "" # Critical: empty user data ensures no secrets are present
}NativeIaC
# CloudFormation Launch Configuration without secrets in UserData
Resources:
<example_resource_name>:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
ImageId: <AMI_ID>
InstanceType: <INSTANCE_TYPE>
UserData: '' # Critical: empty user data ensures no secrets are presentOther
1In the AWS Console, go to EC2 > Launch configurations and click Create launch configuration
2Reuse the same AMI and instance type; leave User data empty
3Go to EC2 > Auto Scaling groups, select the group using the failing launch configuration, click Edit
4Under Launch options, select the new launch configuration and Save
5After the ASG is updated, delete the old launch configuration
Check Code
import zlib
from base64 import b64decode
from prowler.config.config import encoding_format_utf_8
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.lib.logger import logger
from prowler.lib.utils.utils import (
SecretsScanError,
annotate_verified_secrets,
detect_secrets_scan_batch,
)
from prowler.providers.aws.services.autoscaling.autoscaling_client import (
autoscaling_client,
)
class autoscaling_find_secrets_ec2_launch_configuration(Check):
def execute(self):
findings = []
secrets_ignore_patterns = autoscaling_client.audit_config.get(
"secrets_ignore_patterns", []
)
validate = autoscaling_client.audit_config.get("secrets_validate", False)
configurations = list(autoscaling_client.launch_configurations.values())
# Collect the decoded User Data of each launch configuration and scan it
# all in batched Kingfisher invocations instead of one subprocess each.
# Configurations whose User Data cannot be decoded are undecodable (no report),
# matching the original per-resource behavior.
undecodable = set()
def payloads():
for index, configuration in enumerate(configurations):
if not configuration.user_data:
continue
user_data = b64decode(configuration.user_data)
try:
if user_data[0:2] == b"\x1f\x8b": # GZIP magic number
user_data = zlib.decompress(
user_data, zlib.MAX_WBITS | 32
).decode(encoding_format_utf_8)
else:
user_data = user_data.decode(encoding_format_utf_8)
except UnicodeDecodeError as error:
logger.warning(
f"{configuration.region} -- Unable to decode user data in autoscaling launch configuration {configuration.name}: {error}"
)
undecodable.add(index)
continue
except Exception as error:
logger.error(
f"{configuration.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
undecodable.add(index)
continue
yield index, user_data
scan_error = None
try:
batch_results = detect_secrets_scan_batch(
payloads(), excluded_secrets=secrets_ignore_patterns, validate=validate
)
except SecretsScanError as error:
batch_results = {}
scan_error = error
for index, configuration in enumerate(configurations):
report = Check_Report_AWS(metadata=self.metadata(), resource=configuration)
if scan_error and configuration.user_data:
report.status = "MANUAL"
report.status_extended = (
f"Could not scan autoscaling {configuration.name} User Data for "
f"secrets: {scan_error}; manual review is required."
)
findings.append(report)
continue
if index in undecodable:
report.status = "MANUAL"
report.status_extended = f"Could not decode User Data for autoscaling {configuration.name}; manual review is required to scan for secrets."
elif configuration.user_data:
has_secrets = batch_results.get(index)
if has_secrets:
report.status = "FAIL"
report.status_extended = f"Potential secret found in autoscaling {configuration.name} User Data."
annotate_verified_secrets(report, has_secrets)
else:
report.status = "PASS"
report.status_extended = f"No secrets found in autoscaling {configuration.name} User Data."
else:
report.status = "PASS"
report.status_extended = f"No secrets found in autoscaling {configuration.name} since User Data is empty."
findings.append(report)
return findings