Przejdź do treści

🏦 Vault Integration Recipes

HashiCorp Vault is the industry standard for secrets management. These recipes show practical ways to integrate Vault with shell scripts for secure automation.


🎯 Vault Setup and Authentication

Basic Vault Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# Set Vault address
export VAULT_ADDR="https://vault.example.com:8200"

# Verify connectivity
vault status

# Authenticate (choose one method)
# Token auth
export VAULT_TOKEN="s.token_here"

# AppRole auth
vault write auth/approle/login \
    role_id="$ROLE_ID" \
    secret_id="$SECRET_ID"

# Kubernetes auth
vault write auth/kubernetes/login \
    role="$VAULT_ROLE" \
    jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"

Vault Health Check Function

 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
# Vault health checking
check_vault_health() {
    if ! command -v vault >/dev/null 2>&1; then
        echo "Error: Vault CLI not found" >&2
        return 1
    fi

    if [ -z "$VAULT_ADDR" ]; then
        echo "Error: VAULT_ADDR not set" >&2
        return 1
    fi

    local vault_status
    vault_status=$(vault status -format=json 2>/dev/null)

    if [ $? -ne 0 ]; then
        echo "Error: Cannot connect to Vault at $VAULT_ADDR" >&2
        return 1
    fi

    local initialized
    initialized=$(echo "$vault_status" | jq -r '.initialized')

    if [ "$initialized" != "true" ]; then
        echo "Error: Vault is not initialized" >&2
        return 1
    fi

    echo "Vault is healthy and initialized"
    return 0
}

🔧 Secret Retrieval Patterns

Simple Secret Reading

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Get a single secret value
get_vault_secret() {
    local secret_path="$1"
    local field="${2:-value}"

    if [ -z "$secret_path" ]; then
        echo "Error: Secret path required" >&2
        return 1
    fi

    local secret_value
    secret_value=$(vault kv get -field="$field" "$secret_path" 2>/dev/null)

    if [ $? -ne 0 ] || [ -z "$secret_value" ]; then
        echo "Error: Failed to retrieve secret from $secret_path" >&2
        return 1
    fi

    echo "$secret_value"
    return 0
}

# Usage
DATABASE_PASSWORD=$(get_vault_secret "database/prod" "password")

Multiple Secret Fields

 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 multiple fields from a secret
get_vault_secret_fields() {
    local secret_path="$1"
    shift
    local fields=("$@")

    local json_output
    json_output=$(vault kv get -format=json "$secret_path" 2>/dev/null)

    if [ $? -ne 0 ]; then
        echo "Error: Failed to retrieve secret from $secret_path" >&2
        return 1
    fi

    # Extract each field
    for field in "${fields[@]}"; do
        local value
        value=$(echo "$json_output" | jq -r ".data.data.$field")
        if [ "$value" != "null" ]; then
            echo "$field=$value"
        fi
    done
}

# Usage
get_vault_secret_fields "database/prod" "username" "password" "host" "port"

Dynamic Secrets

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# Get dynamic database credentials
get_dynamic_database_creds() {
    local role_name="$1"
    local ttl="${2:-1h}"

    local creds_json
    creds_json=$(vault read -format=json "database/creds/$role_name" ttl="$ttl" 2>/dev/null)

    if [ $? -ne 0 ]; then
        echo "Error: Failed to generate dynamic credentials for role $role_name" >&2
        return 1
    fi

    echo "$creds_json" | jq -r '.data | to_entries[] | "\(.key)=\(.value)"'
}

# Usage
eval "$(get_dynamic_database_creds "app-role" "2h")"
# Sets USERNAME and PASSWORD environment variables

🛡️ Secure Secret Injection

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
28
29
30
31
32
33
34
35
36
37
38
# Inject secrets into temporary config file
inject_vault_secrets_to_file() {
    local template_file="$1"
    local output_file="$2"

    # Create secure temporary file
    local temp_file
    temp_file=$(mktemp "${TMPDIR:-/tmp}/vault_config_XXXXXX") || {
        echo "Error: Failed to create temporary file" >&2
        return 1
    }

    chmod 600 "$temp_file"

    # Get secrets
    local db_password
    db_password=$(get_vault_secret "database/prod" "password")

    local api_key
    api_key=$(get_vault_secret "api/prod" "key")

    # Generate config from template
    env DB_PASSWORD="$db_password" API_KEY="$api_key" \
        envsubst < "$template_file" > "$temp_file"

    # Move to final location
    mv "$temp_file" "$output_file"
    chmod 600 "$output_file"

    echo "Secrets injected to $output_file"
}

# Template file example (config.template):
# database:
#   password: ${DB_PASSWORD}
#   host: prod-db.example.com
# api:
#   key: ${API_KEY}

Process Substitution for Secrets

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Secure secret injection via process substitution
run_with_vault_secrets() {
    local cmd="$1"
    shift

    # Get secrets
    local db_password
    db_password=$(get_vault_secret "database/prod" "password")

    # Inject via environment (but clear afterward)
    DB_PASSWORD="$db_password" "$cmd" "$@"
}

# Usage
run_with_vault_secrets "myapp" "--config" "config.yaml"

🔄 Secret Rotation and Renewal

Lease Renewal

 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
# Renew Vault leases
renew_vault_lease() {
    local lease_id="$1"
    local increment="${2:-3600}"  # 1 hour default

    if [ -z "$lease_id" ]; then
        echo "Error: Lease ID required" >&2
        return 1
    fi

    vault lease renew -increment="$increment" "$lease_id"
    return $?
}

# Auto-renewal background process
start_lease_renewal() {
    local lease_id="$1"
    local interval="${2:-1800}"  # 30 minutes

    (
        while true; do
            sleep "$interval"
            if ! renew_vault_lease "$lease_id"; then
                echo "Failed to renew lease $lease_id" >&2
                break
            fi
        done
    ) &

    echo "Lease renewal started for $lease_id"
}

Dynamic 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
28
29
30
31
# Rotate dynamic credentials
rotate_dynamic_credentials() {
    local role_name="$1"
    local current_lease_id="$2"

    # Revoke old credentials if they exist
    if [ -n "$current_lease_id" ]; then
        vault lease revoke "$current_lease_id" 2>/dev/null
    fi

    # Generate new credentials
    local new_creds
    new_creds=$(vault read -format=json "database/creds/$role_name" 2>/dev/null)

    if [ $? -ne 0 ]; then
        echo "Error: Failed to rotate credentials for role $role_name" >&2
        return 1
    fi

    # Extract new lease ID
    local new_lease_id
    new_lease_id=$(echo "$new_creds" | jq -r '.lease_id')

    # Export credentials
    echo "$new_creds" | jq -r '.data | to_entries[] | "export \(.key)=\(.value)"' | while read -r line; do
        eval "$line"
    done

    echo "Credentials rotated. New lease ID: $new_lease_id"
    echo "$new_lease_id"
}

🧪 Vault Integration Testing

Mock Vault for 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
# Mock Vault for local testing
mock_vault() {
    local mock_dir="${1:-/tmp/mock_vault}"

    mkdir -p "$mock_dir"

    # Create mock secrets
    cat > "$mock_dir/database_prod" << EOF
username: dbuser
password: dbpassword123
host: localhost
port: 5432
EOF

    cat > "$mock_dir/api_prod" << EOF
key: apikey123
endpoint: https://api.example.com
EOF

    # Mock vault command
    cat > "$mock_dir/vault_mock" << 'EOF'
#!/bin/bash
if [[ "$1" == "kv" && "$2" == "get" ]]; then
    local path="$4"
    local field="${6:-value}"

    # Map path to mock file
    local mock_file="/tmp/mock_vault/$(echo "$path" | tr '/' '_')"

    if [[ -f "$mock_file" ]]; then
        grep "^$field:" "$mock_file" | cut -d' ' -f2-
    else
        echo "Error: Secret not found" >&2
        exit 1
    fi
else
    echo "Mock Vault: $*" >&2
fi
EOF

    chmod +x "$mock_dir/vault_mock"

    echo "Mock Vault 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
# Vault integration tests
test_vault_integration() {
    echo "Testing Vault integration..."

    # Test connectivity
    if ! check_vault_health; then
        echo "FAIL: Vault health check failed"
        return 1
    fi

    # Test secret retrieval
    local test_secret
    test_secret=$(get_vault_secret "test/secret" 2>/dev/null)

    if [ $? -eq 0 ] && [ -n "$test_secret" ]; then
        echo "PASS: Secret retrieval successful"
    else
        echo "WARN: Could not retrieve test secret (may be expected in CI)"
    fi

    # Test dynamic credentials
    local dyn_creds
    dyn_creds=$(get_dynamic_database_creds "test-role" "1m" 2>/dev/null)

    if [ $? -eq 0 ] && [ -n "$dyn_creds" ]; then
        echo "PASS: Dynamic credential generation successful"
    else
        echo "WARN: Could not generate dynamic credentials (may be expected)"
    fi

    echo "Vault integration tests completed"
}

🧾 Summary

Secure authentication using AppRole, Kubernetes, or token methods ✅ Robust secret retrieval with proper error handling ✅ Safe secret injection via temporary files and process substitution ✅ Dynamic credential management with lease renewal ✅ Comprehensive testing with mock Vault environments


🧾 See Also