Przejdź do treści

☁️ Azure Key Vault in Shell

Microsoft Azure Key Vault provides secure storage and management of secrets, keys, and certificates for Azure environments. This guide demonstrates how to integrate Azure Key Vault with shell scripts.


🎯 Azure Setup and Authentication

Azure CLI Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Login to Azure
az login

# Or use service principal for automation
az login --service-principal \
    --username "$AZURE_CLIENT_ID" \
    --password "$AZURE_CLIENT_SECRET" \
    --tenant "$AZURE_TENANT_ID"

# Set subscription
az account set --subscription "your-subscription-id"

# Verify access
az keyvault list

Azure Key Vault Health Check

 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
# Verify Azure Key Vault connectivity
check_azure_keyvault_health() {
    if ! command -v az >/dev/null 2>&1; then
        echo "Error: Azure CLI not found" >&2
        return 1
    fi

    # Test connectivity
    if az account show >/dev/null 2>&1; then
        echo "Azure authentication successful"
    else
        echo "Error: Azure authentication failed" >&2
        return 1
    fi

    # Test Key Vault access
    local test_vault="${AZURE_KEYVAULT_NAME:-}"
    if [ -n "$test_vault" ]; then
        if az keyvault secret list --vault-name "$test_vault" --query "[0].id" -o tsv >/dev/null 2>&1; then
            echo "Azure Key Vault $test_vault is accessible"
            return 0
        else
            echo "Error: Cannot access Azure Key Vault $test_vault" >&2
            return 1
        fi
    else
        echo "Warning: AZURE_KEYVAULT_NAME not set, skipping vault test"
        return 0
    fi
}

🔧 Secret Retrieval Patterns

Simple 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
# Get a simple secret value
get_azure_secret() {
    local secret_name="$1"
    local vault_name="${2:-$AZURE_KEYVAULT_NAME}"

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

    if [ -z "$vault_name" ]; then
        echo "Error: Vault name required" >&2
        return 1
    fi

    local secret_value
    secret_value=$(az keyvault secret show \
        --name "$secret_name" \
        --vault-name "$vault_name" \
        --query value \
        --output tsv 2>/dev/null)

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

    echo "$secret_value"
    return 0
}

# Usage
DATABASE_PASSWORD=$(get_azure_secret "database-password")
API_KEY=$(get_azure_secret "api-key" "my-keyvault")

Secret with Version Management

 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
# Get specific version of a secret
get_azure_secret_version() {
    local secret_name="$1"
    local version="$2"
    local vault_name="${3:-$AZURE_KEYVAULT_NAME}"

    if [ -z "$secret_name" ] || [ -z "$version" ]; then
        echo "Error: Secret name and version required" >&2
        return 1
    fi

    local secret_value
    secret_value=$(az keyvault secret show \
        --name "$secret_name" \
        --version "$version" \
        --vault-name "$vault_name" \
        --query value \
        --output tsv 2>/dev/null)

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

    echo "$secret_value"
    return 0
}

# List available versions
list_azure_secret_versions() {
    local secret_name="$1"
    local vault_name="${2:-$AZURE_KEYVAULT_NAME}"

    az keyvault secret versions \
        --name "$secret_name" \
        --vault-name "$vault_name" \
        --query "[].{Version: id, Enabled: attributes.enabled, Created: attributes.created}" \
        --output table
}

Certificate 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
# Get certificate (PEM format)
get_azure_certificate_pem() {
    local cert_name="$1"
    local vault_name="${2:-$AZURE_KEYVAULT_NAME}"
    local output_file="$3"

    if [ -z "$cert_name" ] || [ -z "$vault_name" ]; then
        echo "Error: Certificate name and vault name required" >&2
        return 1
    fi

    # Get certificate in PEM format
    az keyvault certificate download \
        --name "$cert_name" \
        --vault-name "$vault_name" \
        --file "$output_file" \
        --encoding PEM 2>/dev/null

    if [ $? -eq 0 ]; then
        echo "Certificate downloaded to $output_file"
        return 0
    else
        echo "Error: Failed to download certificate $cert_name" >&2
        return 1
    fi
}

# Get certificate secret (for private key access)
get_azure_certificate_secret() {
    local cert_name="$1"
    local vault_name="${2:-$AZURE_KEYVAULT_NAME}"

    # Certificates are stored as secrets with the same name
    get_azure_secret "$cert_name" "$vault_name"
}

🛡️ 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
34
# Inject Azure secrets into environment variables
inject_azure_secrets_to_env() {
    local secrets_map="$1"  # JSON mapping of env_var:secret_name
    local vault_name="${2:-$AZURE_KEYVAULT_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_azure_secret "$secret_name" "$vault_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": "database-password",
    "API_KEY": "api-key",
    "SMTP_PASSWORD": "smtp-password"
}'

inject_azure_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
58
# Create secure config file from Azure secrets
create_secure_config_from_azure_secrets() {
    local config_template="$1"
    local output_file="$2"
    local secrets_mapping="$3"
    local vault_name="${4:-$AZURE_KEYVAULT_NAME}"

    # Create secure temporary file
    local temp_file
    temp_file=$(mktemp "${TMPDIR:-/tmp}/azure_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_azure_secret "$secret_name" "$vault_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.vault.azure.net
# api:
#   key: ${API_KEY}
#   endpoint: https://api.example.com

# Usage
SECRETS_MAPPING='{
    "DB_PASSWORD": "database-password",
    "API_KEY": "api-key"
}'

create_secure_config_from_azure_secrets "app.conf.template" "/tmp/app.conf" "$SECRETS_MAPPING"

🔄 Secret Management and Rotation

List Secrets with Metadata

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# List secrets with metadata
list_azure_secrets_with_metadata() {
    local vault_name="${1:-$AZURE_KEYVAULT_NAME}"
    local max_results="${2:-25}"

    az keyvault secret list \
        --vault-name "$vault_name" \
        --maxresults "$max_results" \
        --query "[].{
            Name: name,
            ContentType: contentType,
            Enabled: attributes.enabled,
            Created: attributes.created,
            Updated: attributes.updated,
            Expires: attributes.expires
        }" \
        --output table
}

Secret Creation and Updates

 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
# Create or update a secret
set_azure_secret() {
    local secret_name="$1"
    local secret_value="$2"
    local vault_name="${3:-$AZURE_KEYVAULT_NAME}"
    local content_type="${4:-}"

    if [ -z "$secret_name" ] || [ -z "$secret_value" ]; then
        echo "Error: Secret name and value required" >&2
        return 1
    fi

    local extra_args=()
    if [ -n "$content_type" ]; then
        extra_args+=(--contentType "$content_type")
    fi

    az keyvault secret set \
        --name "$secret_name" \
        --value "$secret_value" \
        --vault-name "$vault_name" \
        "${extra_args[@]}" \
        --output none

    if [ $? -eq 0 ]; then
        echo "Secret $secret_name set successfully in vault $vault_name"
        return 0
    else
        echo "Error: Failed to set secret $secret_name" >&2
        return 1
    fi
}

# Delete a secret
delete_azure_secret() {
    local secret_name="$1"
    local vault_name="${2:-$AZURE_KEYVAULT_NAME}"

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

    az keyvault secret delete \
        --name "$secret_name" \
        --vault-name "$vault_name" \
        --output none

    if [ $? -eq 0 ]; then
        echo "Secret $secret_name deleted from vault $vault_name"
        return 0
    else
        echo "Error: Failed to delete secret $secret_name" >&2
        return 1
    fi
}

Backup and Restore

 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
# Backup a secret
backup_azure_secret() {
    local secret_name="$1"
    local backup_file="$2"
    local vault_name="${3:-$AZURE_KEYVAULT_NAME}"

    if [ -z "$secret_name" ] || [ -z "$backup_file" ]; then
        echo "Error: Secret name and backup file required" >&2
        return 1
    fi

    az keyvault secret backup \
        --name "$secret_name" \
        --vault-name "$vault_name" \
        --file "$backup_file"

    if [ $? -eq 0 ]; then
        echo "Secret $secret_name backed up to $backup_file"
        return 0
    else
        echo "Error: Failed to backup secret $secret_name" >&2
        return 1
    fi
}

# Restore a secret
restore_azure_secret() {
    local backup_file="$1"
    local vault_name="${2:-$AZURE_KEYVAULT_NAME}"

    if [ -z "$backup_file" ]; then
        echo "Error: Backup file required" >&2
        return 1
    fi

    az keyvault secret restore \
        --vault-name "$vault_name" \
        --file "$backup_file"

    if [ $? -eq 0 ]; then
        echo "Secret restored from $backup_file to vault $vault_name"
        return 0
    else
        echo "Error: Failed to restore secret from $backup_file" >&2
        return 1
    fi
}

🧪 Azure Key Vault Testing

Mock Azure 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
48
49
50
51
# Mock Azure Key Vault for local development
mock_azure_keyvault() {
    local mock_dir="${1:-/tmp/mock_azure}"

    mkdir -p "$mock_dir/secrets"

    # Create mock secrets
    echo "dbpassword123" > "$mock_dir/secrets/database-password"
    echo "apikey123" > "$mock_dir/secrets/api-key"
    echo "smtppass123" > "$mock_dir/secrets/smtp-password"

    # Create mock Azure CLI script
    cat > "$mock_dir/az_mock" << 'EOF'
#!/bin/bash
if [[ "$1" == "keyvault" && "$2" == "secret" && "$3" == "show" ]]; then
    # Extract name and vault-name
    local secret_name=""
    local vault_name=""
    for ((i=4; i<=$#; i++)); do
        if [[ "${!i}" == "--name" ]]; then
            secret_name="${!$((i+1))}"
        elif [[ "${!i}" == "--vault-name" ]]; then
            vault_name="${!$((i+1))}"
        fi
    done

    if [[ -n "$secret_name" ]]; then
        # Convert secret name to file path
        local secret_file="/tmp/mock_azure/secrets/$secret_name"
        if [[ -f "$secret_file" ]]; then
            echo '{"value":"'$(cat "$secret_file")'"}'
        else
            echo "Error: Secret not found" >&2
            exit 1
        fi
    else
        echo "Error: Missing --name" >&2
        exit 1
    fi
elif [[ "$1" == "account" && "$2" == "show" ]]; then
    echo '{"id":"mock-account","user":{"name":"Mock User"}}'
else
    echo "Mock Azure: $*" >&2
fi
EOF

    chmod +x "$mock_dir/az_mock"

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

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

    # Test secret retrieval
    local test_secret
    test_secret=$(get_azure_secret "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

    echo "Azure Key Vault integration tests completed"
}

🧾 Summary

Secure authentication using Azure CLI, service principals, or managed identities ✅ Flexible secret retrieval for simple values, versions, and certificates ✅ Safe secret injection via environment variables and temporary files ✅ Complete secret lifecycle management including backup/restore ✅ Comprehensive testing with mock Azure environments


🧾 See Also