Back to Checks
aws

[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 Cloud
Run
prowler aws --checks autoscaling_find_secrets_ec2_launch_configuration

Recommendation

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 present

Other

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