Przejdź do treści

☁️ 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

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

List Secrets with Metadata

 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