Przejdź do treści

🔐 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

  1. Generation: Create strong, unique secrets
  2. Storage: Use secure, encrypted storage
  3. Transmission: Encrypt in transit
  4. Usage: Inject at runtime, never persist
  5. Rotation: Regular automated rotation
  6. Revocation: Immediate invalidation when needed

🔥 Common Anti-Patterns

1. Hardcoded Secrets

1
2
3
4
5
6
7
# ❌ 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

1
2
3
4
5
6
# ❌ 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

1
2
3
4
5
# ❌ 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

1
2
3
4
5
6
7
8
# 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

1
2
# 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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 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