Why Hardcoded Credentials Are Dangerous
Every time you commit hardcoded credentials to version control, you create a permanent security vulnerability. Even if you later remove the credentials, they remain in Git history forever. Version control systems like GitHub, GitLab, and Bitbucket are actively scanned by automated tools seeking exposed credentials.
The Three Deadly Sins of Credential Management
Source Code Exposure
Credentials committed to Git remain in history forever. Automated tools constantly scan public repositories for exposed API keys, database passwords, and access tokens.
Rotation Impossibility
Hardcoded credentials make rotation nearly impossible. Changing a password requires updating every instance across your codebase, redeploying applications, and coordinating timing across services.
Privilege Escalation Risk
Hardcoded credentials often grant broader access than necessary because developers use convenient, high-privilege accounts. A single compromised credential can provide administrative access.
// NEVER DO THIS - Hardcoded credentials
const config = {
database_url: "postgresql://admin:EMAIL ADDRESS/main",
api_key: "sk-1234567890abcdef",
jwt_secret: "my-super-secret-key"
};
Secrets Manager vs. Parameter Store
- Secrets Manager: Built-in automatic rotation, KMS encryption, cross-region replication, ~$0.40/secret/month. Best for database credentials, API keys, OAuth tokens.
- Parameter Store: No automatic rotation, encrypted at rest, regional only, ~$0.05/10K requests. Best for application configuration, non-sensitive data.
- Hardcoding: Never use for credentials. Git history exposure, impossible rotation, no audit logging.
Create Your First Secret
~5 minutes
AWS Secrets Manager stores your credentials securely with encryption at rest using AWS KMS. You can create secrets for database credentials, API keys, or any sensitive configuration.
Prerequisites
- AWS CLI configured with appropriate IAM permissions
- Access to AWS Management Console
- Existing database or service credentials to migrate
Console Steps
1.1 Navigate to Secrets Manager
- Open the AWS Management Console
- Search for "Secrets Manager" in the services search bar
- Click on "AWS Secrets Manager"
1.2 Create New Secret
- Click
Store a new secret - Select secret type:
- Credentials for Amazon RDS database: For database credentials with automatic rotation
- Other type of secret: For API keys, OAuth tokens, or custom credentials
- Enter your credentials in key/value pairs or as JSON
1.3 Configure Secret Details
- Secret name:
prod/database/postgresql - Description: "Production database credentials for main application"
- KMS encryption key: Choose
aws/secretsmanager(default) or custom key - Add tags for environment, team, and cost allocation
# Create a database secret with JSON credentials
aws secretsmanager create-secret \
--name "prod/database/postgresql" \
--description "Production PostgreSQL credentials" \
--secret-string '{
"username": "app_user",
"password": "YourSecurePassword123!",
"host": "prod-db.cluster-xyz.us-west-2.rds.amazonaws.com",
"port": 5432,
"dbname": "production"
}' \
--tags '[
{"Key": "Environment", "Value": "production"},
{"Key": "Team", "Value": "backend"},
{"Key": "Application", "Value": "main-app"}
]'
# Create an API key secret
aws secretsmanager create-secret \
--name "prod/external-api/stripe" \
--description "Stripe API keys for payment processing" \
--secret-string '{
"publishable_key": "pk_live_abc123...",
"secret_key": "sk_live_xyz789...",
"webhook_secret": "whsec_def456..."
}'
Migrate from Hardcoded Credentials
~8 minutes
Replace hardcoded credentials in your application with secure calls to Secrets Manager. This involves updating your application code and deployment configuration.
Install AWS SDK
# Node.js
npm install @aws-sdk/client-secrets-manager
# Python
pip install boto3
# Go
go get github.com/aws/aws-sdk-go-v2/service/secretsmanager
# Java (Gradle)
implementation 'software.amazon.awssdk:secretsmanager'
Update Application Code
// SECURE: Dynamic credential retrieval
const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');
async function getDbCredentials() {
const client = new SecretsManagerClient({ region: 'us-west-2' });
try {
const response = await client.send(
new GetSecretValueCommand({
SecretId: 'prod/database/postgresql'
})
);
return JSON.parse(response.SecretString);
} catch (error) {
console.error('Failed to retrieve credentials:', error);
throw error;
}
}
// Usage in your application
async function initializeDatabase() {
const credentials = await getDbCredentials();
const pool = new Pool({
host: credentials.host,
user: credentials.username,
password: credentials.password,
database: credentials.dbname,
port: credentials.port
});
return pool;
}
# SECURE: Python implementation
import boto3
import json
from botocore.exceptions import ClientError
def get_secret(secret_name, region_name='us-west-2'):
"""Retrieve secret from AWS Secrets Manager."""
session = boto3.session.Session()
client = session.client(
service_name='secretsmanager',
region_name=region_name
)
try:
response = client.get_secret_value(SecretId=secret_name)
except ClientError as e:
raise e
return json.loads(response['SecretString'])
# Usage
credentials = get_secret('prod/database/postgresql')
connection_string = (
f"postgresql://{credentials['username']}:{credentials['password']}"
f"@{credentials['host']}:{credentials['port']}/{credentials['dbname']}"
)
#!/bin/bash
# Deployment script that retrieves secrets
# Retrieve secret and extract values
SECRET_JSON=$(aws secretsmanager get-secret-value \
--secret-id "prod/database/postgresql" \
--query 'SecretString' \
--output text)
# Parse JSON and export as environment variables
export DB_HOST=$(echo $SECRET_JSON | jq -r '.host')
export DB_USER=$(echo $SECRET_JSON | jq -r '.username')
export DB_PASS=$(echo $SECRET_JSON | jq -r '.password')
export DB_NAME=$(echo $SECRET_JSON | jq -r '.dbname')
# Start your application
node app.js
Set Up Automatic Rotation
~4 minutes
Automatic rotation is one of Secrets Manager's most powerful features. It regularly updates your credentials without requiring application downtime or manual intervention.
Console Steps
3.1 Enable Rotation for RDS Database
- Open your secret in the Secrets Manager console
- Click
Edit rotation - Enable
Automatic rotation - Set rotation interval (recommended: 30-90 days)
- Choose rotation strategy:
- Single user: One set of credentials, brief downtime during rotation
- Alternating users: Two sets of credentials, zero downtime
# Enable automatic rotation for RDS database secret
aws secretsmanager rotate-secret \
--secret-id "prod/database/postgresql" \
--rotation-rules '{"AutomaticallyAfterDays": 30}'
# Verify rotation configuration
aws secretsmanager describe-secret \
--secret-id "prod/database/postgresql" \
--query '{RotationEnabled: RotationEnabled, RotationRules: RotationRules}'
3.2 Custom Rotation for API Keys
For non-database secrets like API keys, create a custom Lambda rotation function:
# Lambda function for custom API key rotation
import json
import boto3
import requests
def lambda_handler(event, context):
"""Handle rotation steps for custom secrets."""
secret_arn = event['SecretId']
step = event['Step']
secrets_client = boto3.client('secretsmanager')
if step == 'createSecret':
# Generate new API key from service
new_api_key = generate_new_api_key()
# Store pending version
secrets_client.put_secret_value(
SecretId=secret_arn,
SecretString=json.dumps({'api_key': new_api_key}),
VersionStages=['AWSPENDING']
)
elif step == 'setSecret':
# Activate new API key with external service
activate_api_key(secret_arn)
elif step == 'testSecret':
# Test new API key functionality
test_api_key(secret_arn)
elif step == 'finishSecret':
# Move AWSPENDING to AWSCURRENT
finish_rotation(secrets_client, secret_arn)
def generate_new_api_key():
"""Call your service's API to generate new key."""
response = requests.post(
'https://api.yourservice.com/keys',
headers={'Authorization': 'Bearer admin_token'}
)
return response.json()['api_key']
Production-Ready Integration
~3 minutes
For production applications, implement caching, error handling, and retry logic to ensure reliable access to secrets while minimizing AWS API calls.
Implement Secret Caching
// Production-ready secrets manager client with caching
const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');
class SecretsCache {
constructor(region, ttlMinutes = 5) {
this.client = new SecretsManagerClient({ region });
this.cache = new Map();
this.ttl = ttlMinutes * 60 * 1000; // Convert to milliseconds
}
async getSecret(secretId, forceRefresh = false) {
const cached = this.cache.get(secretId);
// Return cached value if valid and not forcing refresh
if (!forceRefresh && cached && (Date.now() - cached.timestamp) < this.ttl) {
return cached.value;
}
// Fetch fresh value from AWS
try {
const response = await this.client.send(
new GetSecretValueCommand({ SecretId: secretId })
);
const secret = JSON.parse(response.SecretString);
// Cache the result
this.cache.set(secretId, {
value: secret,
timestamp: Date.now()
});
return secret;
} catch (error) {
// Return stale cache if AWS call fails
if (cached) {
console.warn('Using stale cache due to AWS error:', error.message);
return cached.value;
}
throw error;
}
}
// Force refresh all secrets (call during rotation events)
async refreshAll() {
const promises = Array.from(this.cache.keys()).map(secretId =>
this.getSecret(secretId, true)
);
await Promise.all(promises);
}
}
// Global instance with singleton pattern
const secretsCache = new SecretsCache('us-west-2', 5);
module.exports = secretsCache;
IAM Permissions
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": [
"arn:aws:secretsmanager:us-west-2:*:secret:prod/database/*",
"arn:aws:secretsmanager:us-west-2:*:secret:prod/external-api/*"
]
},
{
"Effect": "Allow",
"Action": [
"kms:Decrypt"
],
"Resource": "arn:aws:kms:us-west-2:*:key/*",
"Condition": {
"StringEquals": {
"kms:ViaService": "secretsmanager.us-west-2.amazonaws.com"
}
}
}
]
}
Docker Integration
#!/bin/bash
# start.sh - Startup script for containerized applications
set -e
# Wait for AWS metadata service (for ECS/EC2)
until aws sts get-caller-identity > /dev/null 2>&1; do
echo "Waiting for AWS credentials..."
sleep 2
done
# Retrieve and export database credentials
echo "Retrieving database credentials..."
DB_SECRET=$(aws secretsmanager get-secret-value \
--secret-id "prod/database/postgresql" \
--query 'SecretString' \
--output text)
export DB_HOST=$(echo "$DB_SECRET" | jq -r '.host')
export DB_USER=$(echo "$DB_SECRET" | jq -r '.username')
export DB_PASS=$(echo "$DB_SECRET" | jq -r '.password')
export DB_NAME=$(echo "$DB_SECRET" | jq -r '.dbname')
echo "Starting application..."
exec node app.js
Cross-Region Replication
# Replicate critical secrets to multiple regions for disaster recovery
aws secretsmanager replicate-secret-to-regions \
--secret-id "prod/database/postgresql" \
--add-replica-regions '[
{
"Region": "us-east-1",
"KmsKeyId": "alias/aws/secretsmanager"
},
{
"Region": "eu-west-1",
"KmsKeyId": "alias/aws/secretsmanager"
}
]'
Validate Your Configuration
Complete these checks to ensure your secrets management is working correctly and securely:
Validation Script
#!/bin/bash
# Secrets Manager Implementation Validation Script
echo "=== Secrets Manager Validation ==="
echo ""
# Test 1: Verify secret exists and is accessible
echo "Testing secret accessibility..."
SECRET_ARN=$(aws secretsmanager describe-secret \
--secret-id "prod/database/postgresql" \
--query 'ARN' \
--output text 2>/dev/null)
if [ -n "$SECRET_ARN" ]; then
echo "✓ Secret accessible: $SECRET_ARN"
else
echo "✗ Cannot access secret!"
exit 1
fi
echo ""
# Test 2: Verify rotation configuration
echo "Checking rotation configuration..."
ROTATION_ENABLED=$(aws secretsmanager describe-secret \
--secret-id "prod/database/postgresql" \
--query 'RotationEnabled' \
--output text)
if [ "$ROTATION_ENABLED" == "True" ]; then
echo "✓ Automatic rotation is enabled"
ROTATION_DAYS=$(aws secretsmanager describe-secret \
--secret-id "prod/database/postgresql" \
--query 'RotationRules.AutomaticallyAfterDays' \
--output text)
echo " Rotation interval: $ROTATION_DAYS days"
else
echo "⚠ Consider enabling automatic rotation"
fi
echo ""
# Test 3: Check encryption configuration
echo "Verifying encryption..."
KMS_KEY=$(aws secretsmanager describe-secret \
--secret-id "prod/database/postgresql" \
--query 'KmsKeyId' \
--output text)
if [ -n "$KMS_KEY" ] && [ "$KMS_KEY" != "None" ]; then
echo "✓ Encrypted with KMS key: $KMS_KEY"
else
echo "✓ Using default AWS managed key"
fi
echo ""
# Test 4: Validate secret format
echo "Validating secret format..."
SECRET_VALUE=$(aws secretsmanager get-secret-value \
--secret-id "prod/database/postgresql" \
--query 'SecretString' \
--output text)
if echo "$SECRET_VALUE" | jq -e '.username and .password and .host' > /dev/null 2>&1; then
echo "✓ Secret contains required database fields"
else
echo "✗ Secret format is invalid or incomplete"
fi
echo ""
# Test 5: Check for hardcoded credentials in codebase
echo "Scanning for hardcoded credentials..."
if [ -d "." ]; then
HARDCODED_FOUND=$(grep -r -i -E "(password|secret|token|api_key)\s*[:=]\s*['\"][^'\"]+['\"]" . \
--include="*.js" --include="*.py" --include="*.java" --include="*.ts" \
--exclude-dir=node_modules --exclude-dir=.git --exclude-dir=venv 2>/dev/null | wc -l)
if [ "$HARDCODED_FOUND" -eq 0 ]; then
echo "✓ No obvious hardcoded credentials found"
else
echo "⚠ Found $HARDCODED_FOUND potential hardcoded credentials - review manually"
fi
fi
echo ""
echo "Secrets Manager validation complete!"
Common Mistakes to Avoid
Hardcoding credentials "temporarily." Temporary hardcoded credentials often become permanent. Use Secrets Manager from the start, even in development.
Not implementing caching. Calling Secrets Manager on every request increases latency and costs. Implement caching with appropriate TTL.
Overly broad IAM permissions. Grant access only to specific secrets your application needs, not all secrets in the account.
Skipping automatic rotation. Manual rotation rarely happens. Enable automatic rotation for all database credentials.
No error handling for rotation. Applications must handle credential refresh during rotation events gracefully.
Inconsistent naming conventions. Use consistent patterns like environment/service/resource for easy management and IAM policies.
Stop Managing Secrets Manually
Tracking secrets across multiple environments, applications, and teams is complex. AWSight automatically monitors your secrets management posture, detects hardcoded credentials, tracks rotation compliance, and alerts you to exposed secrets before they become breaches.