Skip to main content

Find Secrets in SSM Documents

Overview

This check scans AWS Systems Manager (SSM) documents for hardcoded secrets such as passwords, API keys, access tokens, or private keys. SSM documents should reference secrets securely from AWS Secrets Manager or Parameter Store rather than embedding them directly in document content.

Risk

Hardcoded secrets in SSM documents create serious security vulnerabilities:

  • Credential exposure: Anyone with read access to the document can see the secrets
  • Lateral movement: Attackers or malware can harvest credentials to access other services
  • Audit gaps: Hardcoded secrets bypass rotation policies and access logging
  • Compliance violations: Storing plaintext secrets violates most security frameworks

This is classified as a critical severity finding because exposed credentials can lead to full account compromise.

Remediation Steps

Prerequisites

You need:

  • AWS Console access with permissions to view and edit SSM documents
  • Access to create secrets in Secrets Manager or Parameter Store
Required IAM permissions

To remediate this finding, your IAM user or role needs these permissions:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssm:ListDocuments",
"ssm:GetDocument",
"ssm:UpdateDocument",
"ssm:UpdateDocumentDefaultVersion"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ssm:PutParameter",
"ssm:GetParameter"
],
"Resource": "arn:aws:ssm:*:*:parameter/*"
},
{
"Effect": "Allow",
"Action": [
"secretsmanager:CreateSecret",
"secretsmanager:GetSecretValue"
],
"Resource": "*"
}
]
}

AWS Console Method

Step 1: Identify the affected document

  1. Open the AWS Systems Manager Console
  2. In the left navigation, click Documents
  3. Click the Owned by me tab to see your custom documents
  4. Find and click on the document flagged by Prowler

Step 2: Review the document content

  1. On the document details page, click the Content tab
  2. Review the document for any hardcoded secrets such as:
    • Passwords (look for fields like password, secret, credential)
    • AWS access keys (strings starting with AKIA or ASIA)
    • API tokens or bearer tokens
    • Private keys (text containing -----BEGIN)
    • Database connection strings with embedded passwords

Step 3: Store the secret securely

Move the secret to Parameter Store (simplest) or Secrets Manager (for rotation):

Using Parameter Store:

  1. Open Parameter Store
  2. Click Create parameter
  3. Enter a name like /myapp/database/password
  4. For Type, select SecureString
  5. Paste the secret value
  6. Click Create parameter

Using Secrets Manager:

  1. Open Secrets Manager
  2. Click Store a new secret
  3. Choose the appropriate secret type
  4. Enter your secret value
  5. Give it a name like myapp/database/credentials
  6. Complete the wizard and click Store

Step 4: Update the SSM document

  1. Return to your SSM document in the console
  2. Click Create new version
  3. In the document content, replace the hardcoded secret with a dynamic reference:
    • For Parameter Store: {{ssm-secure:/myapp/database/password}}
    • For Secrets Manager: {{resolve:secretsmanager:myapp/database/credentials}}
  4. Click Create new version

Step 5: Set the new version as default

  1. On the document page, click Set default version
  2. Select the new version you just created
  3. Click Set default version
AWS CLI Method

List your SSM documents

aws ssm list-documents \
--region us-east-1 \
--filters Key=Owner,Values=Self \
--query 'DocumentIdentifiers[*].[Name,DocumentVersion]' \
--output table

View document content

aws ssm get-document \
--region us-east-1 \
--name "<your-document-name>" \
--query 'Content' \
--output text | jq .

Create a SecureString parameter

aws ssm put-parameter \
--region us-east-1 \
--name "/myapp/database/password" \
--type "SecureString" \
--value "<your-secret-value>"

Create a secret in Secrets Manager

aws secretsmanager create-secret \
--region us-east-1 \
--name "myapp/database/credentials" \
--secret-string '{"username":"admin","password":"<your-password>"}'

Update the SSM document

First, save your updated document content to a file (with secrets replaced by references):

# Create updated document content
cat > updated-document.json << 'EOF'
{
"schemaVersion": "2.2",
"description": "My SSM document",
"mainSteps": [
{
"action": "aws:runShellScript",
"name": "example",
"inputs": {
"runCommand": [
"echo 'Using secure parameter reference'",
"export DB_PASSWORD={{ssm-secure:/myapp/database/password}}"
]
}
}
]
}
EOF

Then update the document:

aws ssm update-document \
--region us-east-1 \
--name "<your-document-name>" \
--content file://updated-document.json \
--document-version '$LATEST'

Set the new version as default

aws ssm update-document-default-version \
--region us-east-1 \
--name "<your-document-name>" \
--document-version "<new-version-number>"
CloudFormation Example

This example shows how to create an SSM document that references secrets securely using Secrets Manager:

AWSTemplateFormatVersion: '2010-09-09'
Description: SSM Document with secure secret references

Resources:
DatabaseSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: myapp/database/credentials
Description: Database credentials stored securely
GenerateSecretString:
SecretStringTemplate: '{"username": "admin"}'
GenerateStringKey: password
PasswordLength: 32
ExcludeCharacters: '"@/\'

SecureSSMDocument:
Type: AWS::SSM::Document
Properties:
DocumentType: Command
DocumentFormat: YAML
Content:
schemaVersion: '2.2'
description: Example document using secure secret references
parameters:
DatabaseEndpoint:
type: String
description: Database endpoint
mainSteps:
- action: aws:runShellScript
name: ConnectToDatabase
inputs:
runCommand:
- |
# Password is retrieved securely at runtime from Secrets Manager
DB_PASSWORD=$(aws secretsmanager get-secret-value \
--secret-id myapp/database/credentials \
--query 'SecretString' \
--output text | jq -r '.password')

# Use the password securely
mysql -h {{ DatabaseEndpoint }} -u admin -p"$DB_PASSWORD" -e "SELECT 1"

Note: CloudFormation does not support creating SecureString SSM parameters directly. Use Secrets Manager for secrets, or create SecureString parameters via the AWS CLI or console.

Terraform Example

This example shows how to create an SSM document that references secrets securely:

# Store the secret in Secrets Manager
resource "aws_secretsmanager_secret" "db_password" {
name = "myapp/database/password"
description = "Database password for myapp"
}

resource "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret.db_password.id
secret_string = var.database_password
}

# Create a SecureString parameter that references the secret
resource "aws_ssm_parameter" "db_password" {
name = "/myapp/database/password"
description = "Database password reference"
type = "SecureString"
value = var.database_password

tags = {
Environment = "production"
}
}

# SSM Document that uses secure references instead of hardcoded secrets
resource "aws_ssm_document" "secure_example" {
name = "SecureExampleDocument"
document_type = "Command"
document_format = "YAML"

content = <<-DOC
schemaVersion: '2.2'
description: Example document using secure parameter references
parameters:
DatabaseEndpoint:
type: String
description: Database endpoint
mainSteps:
- action: aws:runShellScript
name: ConnectToDatabase
inputs:
runCommand:
- |
# Password is retrieved securely at runtime
DB_PASSWORD=$(aws ssm get-parameter \
--name /myapp/database/password \
--with-decryption \
--query 'Parameter.Value' \
--output text)

# Use the password securely
mysql -h {{ DatabaseEndpoint }} -u admin -p"$DB_PASSWORD" -e "SELECT 1"
DOC
}

# Variable for the password (should be provided via tfvars or environment)
variable "database_password" {
description = "Database password - provide via TF_VAR_database_password environment variable"
type = string
sensitive = true
}

Important: Never hardcode the actual secret value in your Terraform files. Pass it via:

  • Environment variable: TF_VAR_database_password
  • A .tfvars file that is gitignored
  • HashiCorp Vault or another secrets management tool

Verification

After remediation, verify the fix:

  1. Review the document: Open the updated SSM document and confirm no hardcoded secrets remain
  2. Check for dynamic references: Verify secrets are referenced using {{ssm-secure:...}} or {{resolve:secretsmanager:...}} syntax
  3. Test execution: Run the document on a test instance to confirm it can retrieve secrets at runtime
  4. Re-run Prowler: Execute the check again to confirm the finding is resolved
prowler aws --check ssm_document_secrets --region us-east-1
Script to scan all documents for secrets

Use this script to identify all documents that may contain secrets:

#!/bin/bash
# Scan all owned SSM documents for potential secrets

REGION="us-east-1"

# Patterns that might indicate hardcoded secrets
SECRET_PATTERNS="password|secret|api[_-]?key|access[_-]?key|token|credential|private[_-]?key|AKIA|ASIA|BEGIN.*PRIVATE"

echo "Scanning SSM documents for potential secrets..."
echo "================================================"

for doc in $(aws ssm list-documents --region $REGION --filters Key=Owner,Values=Self --query 'DocumentIdentifiers[*].Name' --output text); do
content=$(aws ssm get-document --region $REGION --name "$doc" --query 'Content' --output text 2>/dev/null)

if echo "$content" | grep -iE "$SECRET_PATTERNS" > /dev/null 2>&1; then
echo ""
echo "POTENTIAL SECRETS FOUND IN: $doc"
echo "$content" | grep -inE "$SECRET_PATTERNS" | head -5
fi
done

echo ""
echo "Scan complete."

Additional Resources

Notes

  • Document versions: Updating a document creates a new version. Old versions with secrets may still exist. Consider deleting old versions after confirming the new version works.
  • Execution role permissions: The IAM role used to execute the document needs ssm:GetParameter with WithDecryption for SecureString parameters, or secretsmanager:GetSecretValue for Secrets Manager secrets.
  • Audit trail: Using Secrets Manager or Parameter Store provides CloudTrail logging of secret access, which hardcoded secrets do not offer.
  • Secret rotation: Secrets Manager supports automatic rotation. Consider enabling rotation for database credentials and API keys.
  • AWS-owned documents: You cannot modify AWS-owned documents. If an AWS-owned document contains what appears to be a secret pattern, it may be a false positive (e.g., example placeholders).