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
- Open the AWS Systems Manager Console
- In the left navigation, click Documents
- Click the Owned by me tab to see your custom documents
- Find and click on the document flagged by Prowler
Step 2: Review the document content
- On the document details page, click the Content tab
- Review the document for any hardcoded secrets such as:
- Passwords (look for fields like
password,secret,credential) - AWS access keys (strings starting with
AKIAorASIA) - API tokens or bearer tokens
- Private keys (text containing
-----BEGIN) - Database connection strings with embedded passwords
- Passwords (look for fields like
Step 3: Store the secret securely
Move the secret to Parameter Store (simplest) or Secrets Manager (for rotation):
Using Parameter Store:
- Open Parameter Store
- Click Create parameter
- Enter a name like
/myapp/database/password - For Type, select SecureString
- Paste the secret value
- Click Create parameter
Using Secrets Manager:
- Open Secrets Manager
- Click Store a new secret
- Choose the appropriate secret type
- Enter your secret value
- Give it a name like
myapp/database/credentials - Complete the wizard and click Store
Step 4: Update the SSM document
- Return to your SSM document in the console
- Click Create new version
- 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}}
- For Parameter Store:
- Click Create new version
Step 5: Set the new version as default
- On the document page, click Set default version
- Select the new version you just created
- 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
.tfvarsfile that is gitignored - HashiCorp Vault or another secrets management tool
Verification
After remediation, verify the fix:
- Review the document: Open the updated SSM document and confirm no hardcoded secrets remain
- Check for dynamic references: Verify secrets are referenced using
{{ssm-secure:...}}or{{resolve:secretsmanager:...}}syntax - Test execution: Run the document on a test instance to confirm it can retrieve secrets at runtime
- 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
- AWS Systems Manager Documents
- Using Secrets Manager with SSM
- Parameter Store SecureString Parameters
- Dynamic References in CloudFormation
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:GetParameterwithWithDecryptionfor SecureString parameters, orsecretsmanager:GetSecretValuefor 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).