☁️ AWS Secrets Manager in Shell
Amazon Web Services Secrets Manager provides secure storage and rotation of secrets for AWS environments. This guide shows how to integrate AWS Secrets Manager with shell scripts.
🎯 AWS Setup and Authentication
AWS CLI Configuration
| # Configure AWS CLI
aws configure
# Or use environment variables (recommended for automation)
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
export AWS_DEFAULT_REGION="us-east-1"
# Or use IAM roles (best practice for EC2/ECS/Lambda)
# No explicit credentials needed - IAM role provides them
|
AWS Secrets Manager Health Check
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | # Verify AWS Secrets Manager connectivity
check_aws_secrets_health() {
if ! command -v aws >/dev/null 2>&1; then
echo "Error: AWS CLI not found" >&2
return 1
fi
if [ -z "$AWS_DEFAULT_REGION" ]; then
echo "Error: AWS_DEFAULT_REGION not set" >&2
return 1
fi
# Test connectivity
if aws secretsmanager list-secrets --max-results 1 >/dev/null 2>&1; then
echo "AWS Secrets Manager is accessible"
return 0
else
echo "Error: Cannot access AWS Secrets Manager" >&2
return 1
fi
}
|
🔧 Secret Retrieval Patterns
Simple Secret String Retrieval
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 | # Get a simple secret string
get_aws_secret_string() {
local secret_name="$1"
if [ -z "$secret_name" ]; then
echo "Error: Secret name required" >&2
return 1
fi
local secret_value
secret_value=$(aws secretsmanager get-secret-value \
--secret-id "$secret_name" \
--query SecretString \
--output text 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$secret_value" ]; then
echo "Error: Failed to retrieve secret $secret_name" >&2
return 1
fi
echo "$secret_value"
return 0
}
# Usage
DATABASE_PASSWORD=$(get_aws_secret_string "prod/database/password")
|
JSON Secret Parsing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | # Get and parse JSON secret
get_aws_secret_json() {
local secret_name="$1"
local field="$2"
if [ -z "$secret_name" ]; then
echo "Error: Secret name required" >&2
return 1
fi
local secret_json
secret_json=$(aws secretsmanager get-secret-value \
--secret-id "$secret_name" \
--query SecretString \
--output text 2>/dev/null)
if [ $? -ne 0 ]; then
echo "Error: Failed to retrieve secret $secret_name" >&2
return 1
fi
if [ -n "$field" ]; then
# Extract specific field
echo "$secret_json" | jq -r ".$field" 2>/dev/null
else
# Return full JSON
echo "$secret_json"
fi
}
# Usage
DATABASE_CREDENTIALS=$(get_aws_secret_json "prod/database/credentials")
DB_USERNAME=$(get_aws_secret_json "prod/database/credentials" "username")
DB_PASSWORD=$(get_aws_secret_json "prod/database/credentials" "password")
|
Binary Secret Retrieval
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 | # Get binary secret
get_aws_secret_binary() {
local secret_name="$1"
local output_file="$2"
if [ -z "$secret_name" ] || [ -z "$output_file" ]; then
echo "Error: Secret name and output file required" >&2
return 1
fi
# Get base64-encoded binary secret
local encoded_secret
encoded_secret=$(aws secretsmanager get-secret-value \
--secret-id "$secret_name" \
--query SecretBinary \
--output text 2>/dev/null)
if [ $? -ne 0 ]; then
echo "Error: Failed to retrieve binary secret $secret_name" >&2
return 1
fi
# Decode and save
echo "$encoded_secret" | base64 -d > "$output_file"
if [ $? -ne 0 ]; then
echo "Error: Failed to decode binary secret" >&2
rm -f "$output_file"
return 1
fi
echo "Binary secret saved to $output_file"
return 0
}
# Usage
get_aws_secret_binary "prod/ssl/certificate" "/tmp/cert.pem"
|
🛡️ Secure Secret Injection
Environment Variable Injection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 | # Inject secrets into environment variables
inject_aws_secrets_to_env() {
local secrets_map="$1" # JSON mapping of env_var:secret_name
if [ -z "$secrets_map" ]; then
echo "Error: Secrets map required" >&2
return 1
fi
echo "$secrets_map" | jq -r 'to_entries[] | "\(.key)=\(.value)"' | while read -r mapping; do
local env_var="${mapping%=*}"
local secret_name="${mapping#*=}"
local secret_value
secret_value=$(get_aws_secret_string "$secret_name")
if [ $? -eq 0 ] && [ -n "$secret_value" ]; then
export "$env_var"="$secret_value"
echo "Exported $env_var from secret $secret_name"
else
echo "Warning: Failed to export $env_var" >&2
fi
done
}
# Usage
SECRETS_MAP='{
"DB_PASSWORD": "prod/database/password",
"API_KEY": "prod/api/key",
"SMTP_PASSWORD": "prod/smtp/password"
}'
inject_aws_secrets_to_env "$SECRETS_MAP"
|
Temporary File Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 | # Create secure config file from AWS secrets
create_secure_config_from_aws_secrets() {
local config_template="$1"
local output_file="$2"
local secrets_mapping="$3" # JSON mapping of placeholders to secret names
# Create secure temporary file
local temp_file
temp_file=$(mktemp "${TMPDIR:-/tmp}/aws_config_XXXXXX") || {
echo "Error: Failed to create temporary file" >&2
return 1
}
chmod 600 "$temp_file"
# Build environment for substitution
local env_vars=()
echo "$secrets_mapping" | jq -r 'to_entries[] | "\(.key)=\(.value)"' | while read -r mapping; do
local placeholder="${mapping%=*}"
local secret_name="${mapping#*=}"
local secret_value
secret_value=$(get_aws_secret_string "$secret_name")
if [ $? -eq 0 ] && [ -n "$secret_value" ]; then
env_vars+=("$placeholder=$secret_value")
else
echo "Warning: Failed to get secret for $placeholder" >&2
fi
done
# Generate config with substituted values
env "${env_vars[@]}" envsubst < "$config_template" > "$temp_file"
# Move to final location
mv "$temp_file" "$output_file"
chmod 600 "$output_file"
echo "Secure config created: $output_file"
}
# Template file example (app.conf.template):
# database:
# password: ${DB_PASSWORD}
# host: prod-db.cluster.amazonaws.com
# api:
# key: ${API_KEY}
# endpoint: https://api.example.com
# Usage
SECRETS_MAPPING='{
"DB_PASSWORD": "prod/database/password",
"API_KEY": "prod/api/key"
}'
create_secure_config_from_aws_secrets "app.conf.template" "/tmp/app.conf" "$SECRETS_MAPPING"
|
🔄 Secret Rotation and Management
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | # List secrets with rotation information
list_aws_secrets_with_rotation() {
local max_results="${1:-50}"
aws secretsmanager list-secrets \
--max-results "$max_results" \
--query 'SecretList[].{
Name: Name,
Description: Description,
LastChangedDate: LastChangedDate,
RotationEnabled: RotationEnabled,
RotationLambdaARN: RotationLambdaARN,
RotationRules: RotationRules
}' \
--output table
}
# Usage
list_aws_secrets_with_rotation 100
|
Enable Automatic Rotation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 | # Enable automatic rotation for a secret
enable_secret_rotation() {
local secret_name="$1"
local lambda_arn="$2"
local rotation_interval="${3:-30}" # Days
if [ -z "$secret_name" ] || [ -z "$lambda_arn" ]; then
echo "Error: Secret name and Lambda ARN required" >&2
return 1
fi
aws secretsmanager rotate-secret \
--secret-id "$secret_name" \
--rotation-lambda-arn "$lambda_arn" \
--rotation-rules "AutomaticallyAfterDays=$rotation_interval"
if [ $? -eq 0 ]; then
echo "Automatic rotation enabled for $secret_name"
return 0
else
echo "Error: Failed to enable rotation for $secret_name" >&2
return 1
fi
}
# Usage
enable_secret_rotation "prod/database/password" "arn:aws:lambda:us-east-1:123456789012:function:RotateDBPassword" 30
|
Manual Secret Rotation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 | # Manually rotate a secret
rotate_secret_manually() {
local secret_name="$1"
local new_value="$2"
if [ -z "$secret_name" ] || [ -z "$new_value" ]; then
echo "Error: Secret name and new value required" >&2
return 1
fi
# Put new secret value
aws secretsmanager put-secret-value \
--secret-id "$secret_name" \
--secret-string "$new_value"
if [ $? -eq 0 ]; then
echo "Secret $secret_name rotated successfully"
return 0
else
echo "Error: Failed to rotate secret $secret_name" >&2
return 1
fi
}
# Usage
NEW_PASSWORD=$(openssl rand -base64 32)
rotate_secret_manually "prod/database/password" "$NEW_PASSWORD"
|
🧪 AWS Secrets Manager Testing
Mock AWS for Local Testing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 | # Mock AWS Secrets Manager for local development
mock_aws_secrets() {
local mock_dir="${1:-/tmp/mock_aws}"
mkdir -p "$mock_dir/secrets"
# Create mock secrets
echo '{"username":"dbuser","password":"dbpass123","host":"localhost","port":5432}' > "$mock_dir/secrets/prod/database/credentials"
echo "apikey123" > "$mock_dir/secrets/prod/api/key"
echo "smtppass123" > "$mock_dir/secrets/prod/smtp/password"
# Create mock AWS CLI script
cat > "$mock_dir/aws_mock" << 'EOF'
#!/bin/bash
if [[ "$1" == "secretsmanager" && "$2" == "get-secret-value" ]]; then
# Extract secret-id
local secret_id=""
for ((i=3; i<=$#; i++)); do
if [[ "${!i}" == "--secret-id" ]]; then
secret_id="${!$((i+1))}"
break
fi
done
if [[ -n "$secret_id" ]]; then
# Convert secret-id to file path
local secret_file="/tmp/mock_aws/secrets/$(echo "$secret_id" | tr ':' '_')"
if [[ -f "$secret_file" ]]; then
echo '{"SecretString":"'$(cat "$secret_file" | sed 's/"/\\"/g')'"}'
else
echo "Error: Secret not found" >&2
exit 1
fi
else
echo "Error: Missing --secret-id" >&2
exit 1
fi
else
echo "Mock AWS: $*" >&2
fi
EOF
chmod +x "$mock_dir/aws_mock"
echo "Mock AWS Secrets Manager created in $mock_dir"
echo "Use: export PATH=\"$mock_dir:\$PATH\" for testing"
}
|
Integration Test Suite
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 | # AWS Secrets Manager integration tests
test_aws_secrets_integration() {
echo "Testing AWS Secrets Manager integration..."
# Test connectivity
if ! check_aws_secrets_health; then
echo "FAIL: AWS Secrets Manager health check failed"
return 1
fi
# Test secret retrieval
local test_secret
test_secret=$(get_aws_secret_string "test/secret" 2>/dev/null)
if [ $? -eq 0 ]; then
echo "PASS: Secret retrieval successful"
else
echo "WARN: Could not retrieve test secret (may be expected in CI)"
fi
# Test JSON parsing
local json_secret
json_secret=$(get_aws_secret_json "test/json-secret" 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$json_secret" ]; then
echo "PASS: JSON secret parsing successful"
else
echo "WARN: Could not parse JSON secret (may be expected)"
fi
echo "AWS Secrets Manager integration tests completed"
}
|
🧾 Summary
✅ Secure authentication using IAM roles, access keys, or temporary credentials
✅ Flexible secret retrieval for strings, JSON, and binary data
✅ Safe secret injection via environment variables and temporary files
✅ Automatic rotation with Lambda functions
✅ Comprehensive testing with mock AWS environments
🧾 See Also