🔐 Secrets Management: Deep Dive
Modern secret management is critical for secure automation. This deep dive explores the principles, patterns, and anti-patterns of handling sensitive data in shell scripts.
🎯 Core Principles
Zero Trust Secret Handling
Never trust that secrets are handled securely by default. Always assume:
- Environments are compromised
- Logs are monitored
- Processes can be inspected
- Memory can be dumped
Secret Lifecycle Management
- Generation: Create strong, unique secrets
- Storage: Use secure, encrypted storage
- Transmission: Encrypt in transit
- Usage: Inject at runtime, never persist
- Rotation: Regular automated rotation
- Revocation: Immediate invalidation when needed
🔥 Common Anti-Patterns
1. Hardcoded Secrets
| # ❌ NEVER DO THIS
API_KEY="sk-abc123xyz"
DATABASE_URL="postgres://user:password@host:5432/db"
# ✅ DO THIS INSTEAD
API_KEY="${API_KEY:-}"
DATABASE_URL="${DATABASE_URL:-}"
|
2. Environment Variable Exposure
| # ❌ DANGEROUS - Shows in process list
ps auxww | grep myapp
# user 12345 0.1 0.2 myapp --api-key=SECRET_HERE
# ✅ SAFE - Read from secure source
API_KEY=$(vault read -field=key secret/myapp/api)
|
3. Log Pollution
| # ❌ LEAKS SECRETS TO LOGS
echo "Connecting with API key: $API_KEY"
# ✅ SAFE LOGGING
echo "Connecting to API service"
|
🛡️ Secure Secret Patterns
1. Temporary File 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 | # Secure temporary file handling
create_secure_temp_file() {
local suffix="${1:-}"
local temp_file
temp_file=$(mktemp "${TMPDIR:-/tmp}/XXXXXXXX${suffix}" 2>/dev/null)
if [ -z "$temp_file" ]; then
echo "Failed to create temporary file" >&2
return 1
fi
# Set restrictive permissions
chmod 600 "$temp_file" || {
rm -f "$temp_file"
echo "Failed to set permissions on temporary file" >&2
return 1
}
echo "$temp_file"
return 0
}
# Usage for sensitive configs
CONFIG_FILE=$(create_secure_temp_file ".json")
echo "$SENSITIVE_JSON" > "$CONFIG_FILE"
myapp --config "$CONFIG_FILE"
rm -f "$CONFIG_FILE"
|
2. Environment Sanitization
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | # Sanitize environment before executing commands
sanitize_and_execute() {
local cmd="$1"
shift
# Backup sensitive env vars
local original_api_key="${API_KEY:-}"
local original_db_pass="${DB_PASSWORD:-}"
# Clear them for subprocess
unset API_KEY DB_PASSWORD
# Execute command
"$cmd" "$@"
local exit_code=$?
# Restore environment
export API_KEY="$original_api_key"
export DB_PASSWORD="$original_db_pass"
return $exit_code
}
|
3. Memory-Safe Secret Handling
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | # Secure secret clearing
clear_sensitive_data() {
local var_name="$1"
local var_value="${!var_name}"
if [ -n "$var_value" ]; then
# Overwrite with random data
local random_data
random_data=$(openssl rand -hex ${#var_value})
eval "$var_name=\$random_data"
# Clear again
eval "$var_name="
unset "$var_name"
fi
}
# Usage
trap 'clear_sensitive_data "API_KEY"' EXIT
|
🔧 Secret Injection Strategies
1. CLI Flag Injection
| # Secure flag passing
run_with_secrets() {
local api_key
api_key=$(get_secret "api/key")
# Pass via stdin or temporary file, not command line
echo "$api_key" | myapp --api-key-from-stdin
}
|
2. Configuration File Templates
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | # Secure config generation
generate_secure_config() {
local template="$1"
local output="$2"
# Get secrets
local db_password
db_password=$(get_secret "database/password")
# Generate config with restricted permissions
(
echo "database:"
echo " password: $db_password"
echo " host: ${DB_HOST:-localhost}"
) > "$output"
chmod 600 "$output"
}
|
3. Process Substitution for Secrets
| # Secure secret injection via process substitution
myapp --config <(echo "api_key: $(get_secret 'api/key')")
|
🏗️ Secret Store Integration Framework
Universal Secret Getter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | # Abstract secret retrieval
get_secret() {
local secret_path="$1"
case "${SECRET_BACKEND:-vault}" in
vault)
vault read -field=value "$secret_path"
;;
aws)
aws secretsmanager get-secret-value --secret-id "$secret_path" --query SecretString --output text
;;
azure)
az keyvault secret show --name "$secret_path" --query value --output tsv
;;
file)
cat "${SECRET_FILE_DIR:-/run/secrets}/$secret_path"
;;
*)
echo "Unknown secret backend: ${SECRET_BACKEND:-vault}" >&2
return 1
;;
esac
}
|
Secret Health Checking
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | # Verify secret availability
check_secret_health() {
local required_secrets=("$@")
local missing_secrets=()
for secret in "${required_secrets[@]}"; do
if ! get_secret "$secret" >/dev/null 2>&1; then
missing_secrets+=("$secret")
fi
done
if [ ${#missing_secrets[@]} -gt 0 ]; then
echo "Missing required secrets: ${missing_secrets[*]}" >&2
return 1
fi
echo "All required secrets available"
return 0
}
|
🧪 Security Testing
Secret Leak Detection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | # Scan for potential secret leaks in code
scan_for_secret_leaks() {
local scan_dir="${1:-.}"
# Common secret patterns
local patterns=(
"API_KEY="
"SECRET_KEY="
"PASSWORD="
"TOKEN="
"aws_secret"
"private_key"
)
for pattern in "${patterns[@]}"; do
if grep -r --exclude-dir=.git "$pattern" "$scan_dir"; then
echo "Potential secret leak found: $pattern" >&2
return 1
fi
done
echo "No obvious secret leaks detected"
return 0
}
|
Runtime Secret Monitoring
| # Monitor for secret exposure in process list
monitor_secret_exposure() {
local sensitive_vars=("API_KEY" "DB_PASSWORD" "SECRET_TOKEN")
for var in "${sensitive_vars[@]}"; do
if ps auxww | grep -v grep | grep -q "${!var}"; then
echo "WARNING: Potential secret exposure in process list for $var" >&2
fi
done
}
|
🧾 Summary Best Practices
✅ Never hardcode secrets in scripts or configuration files
✅ Use secure temporary files with proper permissions
✅ Sanitize environment variables before subprocess execution
✅ Implement secret rotation and health checking
✅ Monitor for secret leaks in code and process lists
✅ Use abstract secret getters for backend flexibility
✅ Clear sensitive data from memory when no longer needed
🧾 See Also