DevLab / cloud / azure-key-vault-secret-manager
CLOUD PYTHON INTERMEDIATE

Azure Key Vault Secret Manager

This tutorial provides a production-ready Python script for managing secrets in Azure Key Vault. It demonstrates how to authenticate securely using Azure I

Python3.10+
OSLinux / macOS / Windows
Lines~211
Packages2 deps
Install Dependencies
$ pip install azure-keyvault-secrets azure-identity
Usage
$ python3 keyvault_mgr.py --vault https://myvault.vault.azure.net --secret db-password
#cloud#python#intermediate#azure#azure

This tutorial provides a production-ready Python script for managing secrets in Azure Key Vault. It demonstrates how to authenticate securely using Azure Identity and perform common operations like getting, setting, and deleting secrets.

Problem Statement

In modern application development, especially within cloud environments, securely managing sensitive information like database connection strings, API keys, and service credentials is a critical challenge. Hardcoding secrets in application code, storing them in configuration files, or passing them as environment variables without proper encryption poses significant security risks, making applications vulnerable to data breaches. Developers and DevOps engineers need a robust, scalable, and secure solution to store and retrieve these secrets at runtime without exposing them.

Azure Key Vault addresses this problem by providing a centralized cloud service for securely storing and accessing secrets. It ensures that secrets are encrypted at rest and in transit, offers fine-grained access control, and integrates seamlessly with other Azure services and applications. This script acts as a command-line interface (CLI) tool, allowing administrators and automated processes to interact with Azure Key Vault to manage secrets efficiently and securely, eliminating the need for manual intervention or insecure storage practices.

How It Works

This script leverages the Azure SDK for Python, specifically the azure-identity and azure-keyvault-secrets libraries, to interact with Azure Key Vault. The core of its functionality revolves around secure authentication and authorized secret management.

  1. Authentication: The script utilizes DefaultAzureCredential from azure-identity. This is a powerful credential provider that attempts to authenticate using a sequence of mechanisms: environment variables, managed identity (if running on Azure), Azure CLI, Azure PowerShell, and Visual Studio Code. This flexibility ensures the script can run seamlessly across various development and deployment environments without explicit credential configuration in the code. Once authenticated, it obtains an access token for Azure Key Vault.
  2. Key Vault Interaction: With an authenticated credential, a SecretClient is initialized, targeting a specific Azure Key Vault URI. This client provides methods to perform secret operations.
  3. Secret Operations:
    • Get Secret: Retrieves the latest version of a specified secret.
    • Set Secret: Creates a new secret or updates an existing one with a new value.
    • Delete Secret: Initiates the deletion of a secret. Key Vault supports soft-delete by default, meaning the secret moves to a "deleted" state before permanent purging. The script offers an optional --purge flag for immediate permanent deletion, useful in development or specific compliance scenarios.

The script is designed for command-line execution, taking parameters like the Key Vault URI, secret name, desired operation, and secret value (for set operations). It includes robust error handling to catch common issues like authentication failures, secrets not found, and general Key Vault API errors, providing clear feedback to the user.

Full Script

#!/usr/bin/env python3
"""
Azure Key Vault Secret Manager

This script provides a command-line interface to interact with Azure Key Vault
secrets. It supports getting, setting, and deleting secrets securely.
Authentication is handled via DefaultAzureCredential, allowing for flexible
authentication methods (environment variables, Azure CLI, Managed Identity, etc.).
"""

import os
import argparse
import logging
import sys
from typing import Optional

from azure.identity import DefaultAzureCredential, ClientAuthenticationError
from azure.keyvault.secrets import SecretClient
from azure.core.exceptions import ResourceNotFoundError, HttpResponseError

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    stream=sys.stderr
)
logger = logging.getLogger(__name__)

# --- Helper Functions ---

def get_keyvault_client(vault_uri: str) -> SecretClient:
    """
    Authenticates with Azure and returns a SecretClient for the specified Key Vault.

    Args:
        vault_uri: The URI of the Azure Key Vault (e.g., "https://myvault.vault.azure.net").

    Returns:
        An initialized SecretClient object.

    Raises:
        ClientAuthenticationError: If authentication fails.
        Exception: For other errors during client initialization.
    """
    try:
        # DefaultAzureCredential attempts to authenticate via various methods:
        # environment variables, managed identity, Azure CLI, etc.
        credential = DefaultAzureCredential()
        client = SecretClient(vault_url=vault_uri, credential=credential)
        logger.info(f"Successfully connected to Azure Key Vault: {vault_uri}")
        return client
    except ClientAuthenticationError as e:
        logger.error(f"Authentication failed: {e}")
        logger.error("Please ensure you are logged in to Azure CLI (az login) "
                     "or have appropriate environment variables set.")
        raise
    except Exception as e:
        logger.error(f"Failed to initialize Key Vault client: {e}")
        raise

def get_secret_value(client: SecretClient, secret_name: str) -> str:
    """
    Retrieves the value of a secret from Azure Key Vault.

    Args:
        client: An authenticated SecretClient object.
        secret_name: The name of the secret to retrieve.

    Returns:
        The value of the secret.

    Raises:
        ResourceNotFoundError: If the secret does not exist.
        HttpResponseError: For other Key Vault API errors.
    """
    try:
        secret = client.get_secret(secret_name)
        logger.info(f"Successfully retrieved secret '{secret_name}'.")
        return secret.value
    except ResourceNotFoundError:
        logger.error(f"Secret '{secret_name}' not found in Key Vault.")
        raise
    except HttpResponseError as e:
        logger.error(f"Error retrieving secret '{secret_name}': {e}")
        raise
    except Exception as e:
        logger.error(f"An unexpected error occurred while getting secret '{secret_name}': {e}")
        raise

def set_secret_value(
    client: SecretClient,
    secret_name: str,
    secret_value: str,
    content_type: Optional[str] = None
) -> None:
    """
    Sets or updates a secret in Azure Key Vault.

    Args:
        client: An authenticated SecretClient object.
        secret_name: The name of the secret to set/update.
        secret_value: The value of the secret.
        content_type: Optional content type for the secret (e.g., 'text/plain').

    Raises:
        HttpResponseError: For Key Vault API errors.
    """
    try:
        client.set_secret(secret_name, secret_value, content_type=content_type)
        logger.info(f"Secret '{secret_name}' successfully set/updated.")
    except HttpResponseError as e:
        logger.error(f"Error setting secret '{secret_name}': {e}")
        raise
    except Exception as e:
        logger.error(f"An unexpected error occurred while setting secret '{secret_name}': {e}")
        raise

def delete_secret_value(client: SecretClient, secret_name: str, purge: bool = False) -> None:
    """
    Deletes a secret from Azure Key Vault. If purge is True, it attempts to purge
    the secret immediately after deletion (requires specific permissions).

    Args:
        client: An authenticated SecretClient object.
        secret_name: The name of the secret to delete.
        purge: If True, attempts to permanently purge the secret immediately.

    Raises:
        HttpResponseError: For Key Vault API errors.
    """
    try:
        # Soft delete is the default behavior in Azure Key Vault
        logger.info(f"Attempting to delete secret '{secret_name}'...")
        poller = client.begin_delete_secret(secret_name)
        poller.wait() # Wait for the delete operation to complete
        logger.info(f"Secret '{secret_name}' successfully moved to soft-delete state.")

        if purge:
            logger.warning(f"Attempting to purge secret '{secret_name}' permanently. "
                           "This action is irreversible and requires 'Purge' permission.")
            poller = client.begin_purge_deleted_secret(secret_name)
            poller.wait() # Wait for the purge operation to complete
            logger.info(f"Secret '{secret_name}' successfully purged permanently.")
    except ResourceNotFoundError:
        logger.warning(f"Secret '{secret_name}' not found. Cannot delete/purge.")
    except HttpResponseError as e:
        logger.error(f"Error deleting/purging secret '{secret_name}': {e}")
        raise
    except Exception as e:
        logger.error(f"An unexpected error occurred while deleting secret '{secret_name}': {e}")
        raise

# --- Main Logic ---

def main() -> None:
    """
    Parses command-line arguments and performs the requested Key Vault operation.
    """
    parser = argparse.ArgumentParser(
        description="Azure Key Vault Secret Manager for getting, setting, and deleting secrets.",
        formatter_class=argparse.RawTextHelpFormatter
    )

    # Required arguments
    parser.add_argument(
        '--vault',
        type=str,
        required=True,
        help="The URI of the Azure Key Vault (e.g., 'https://myvault.vault.azure.net')."
    )
    parser.add_argument(
        '--secret',
        type=str,
        required=True,
        dest='secret_name',
        help="The name of the secret to manage."
    )

    # Mutually exclusive group for operations
    operation_group = parser.add_mutually_exclusive_group(required=True)
    operation_group.add_argument(
        '--get',
        action='store_true',
        help="Retrieve the value of the specified secret."
    )
    operation_group.add_argument(
        '--set',
        type=str,
        metavar='VALUE',
        help="Set or update the value of the specified secret. "
             "Provide the secret value directly (e.g., --set 'my_new_password')."
    )
    operation_group.add_argument(
        '--delete',
        action='store_true',
        help="Delete the specified secret. By default, secrets are soft-deleted."
    )

    # Optional arguments
    parser.add_argument(
        '--purge',
        action='store_true',
        help="Used with --delete: Permanently purges a soft-deleted secret. "
             "This action is irreversible and requires 'Purge' permission."
    )
    parser.add_argument(
        '--content-type',
        type=str,
        default='text/plain',
        help="Content type for the secret (e.g., 'application/json'). "
             "Only applicable with --set. Default: 'text/plain'."
    )

    args = parser.parse_args()

    # Input validation for --purge with --delete
    if args.purge and not args.delete:
        parser.error("--purge can only be used in conjunction with --delete.")

    try:
        # Initialize Key Vault client
        client = get_keyvault_client(args.vault)

        if args.get:
            secret_value = get_secret_value(client, args.secret_name)
            # Print to stdout for easy piping, but log to stderr for operational info
            print(secret_value)
        elif args.set:
            set_secret_value(client, args.secret_name, args.set, args.content_type)
        elif args.delete:
            delete_secret_value(client, args.secret_name, args.purge)

    except (ClientAuthenticationError, ResourceNotFoundError, HttpResponseError) as e:
        # Specific errors are already logged by helper functions
        logger.error(f"Operation failed: {e}")
        sys.exit(1)
    except Exception as e:
        logger.critical(f"An unhandled error occurred: {e}", exc_info=True)
        sys.exit(1)

if __name__ == "__main__":
    main()

Configuration

The script primarily relies on command-line arguments for immediate configuration. However, for authentication, it uses DefaultAzureCredential, which offers a flexible approach. This credential provider attempts to authenticate in the following order:

  1. Environment Variables: If AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET are set, it will attempt to authenticate as a Service Principal.
    • AZURE_TENANT_ID: The Azure Active Directory tenant (directory) ID.
    • AZURE_CLIENT_ID: The client (application) ID of the Service Principal.
    • AZURE_CLIENT_SECRET: The client secret of the Service Principal.
  2. Managed Identity: If the script is running within an Azure service (like an Azure VM, App Service, Function App, or AKS) that has a Managed Identity enabled and configured, it will automatically use that identity. No specific environment variables are needed for this.
  3. Azure CLI: If you have the Azure CLI installed and are logged in (az login), the script will use your current CLI credentials. This is often the easiest method for local development.
  4. Azure PowerShell: Similar to Azure CLI, if you are logged in via Azure PowerShell.
  5. Visual Studio Code: If you are authenticated through the Azure Account extension in VS Code.

Required Permissions: The identity used to run this script (whether Service Principal, Managed Identity, or your user account) must have the necessary permissions on the target Azure Key Vault. These are typically granted via Azure RBAC (Role-Based Access Control) or Key Vault access policies:

  • For --get: Secrets Get permission.
  • For --set: Secrets Set permission.
  • For --delete: Secrets Delete permission.
  • For --purge (with --delete): Secrets Purge permission.

Example RBAC roles that might grant these permissions:

  • Key Vault Secrets User: Allows reading secrets.
  • Key Vault Secrets Officer: Allows reading, writing, and deleting secrets.
  • Key Vault Contributor: A broader role that includes managing Key Vaults and their secrets.

Usage Examples

First, ensure you have the dependencies installed: pip install azure-keyvault-secrets azure-identity

Make sure you're authenticated to Azure, e.g., by running az login.

1. Set a new secret or update an existing one:

python3 keyvault_mgr.py \
    --vault https://my-dev-vault.vault.azure.net \
    --secret MyAppDbPassword \
    --set "SuperSecurePassword#123" \
    --content-type "text/plain"

Expected Output (to stderr):

2023-10-27 10:30:00,123 - INFO - Successfully connected to Azure Key Vault: https://my-dev-vault.vault.azure.net
2023-10-27 10:30:00,456 - INFO - Secret 'MyAppDbPassword' successfully set/updated.

2. Get the value of a secret:

python3 keyvault_mgr.py \
    --vault https://my-dev-vault.vault.azure.net \
    --secret MyAppDbPassword \
    --get

Expected Output (secret value to stdout, logs to stderr):

SuperSecurePassword#123

(followed by logs to stderr similar to):

2023-10-27 10:31:00,123 - INFO - Successfully connected to Azure Key Vault: https://my-dev-vault.vault.azure.net
2023-10-27 10:31:00,456 - INFO - Successfully retrieved secret 'MyAppDbPassword'.

3. Delete a secret (soft-delete by default):

python3 keyvault_mgr.py \
    --vault https://my-dev-vault.vault.azure.net \
    --secret MyAppDbPassword \
    --delete

Expected Output (to stderr):

2023-10-27 10:32:00,123 - INFO - Successfully connected to Azure Key Vault: https://my-dev-vault.vault.azure.net
2023-10-27 10:32:00,456 - INFO - Attempting to delete secret 'MyAppDbPassword'...
2023-10-27 10:32:01,789 - INFO - Secret 'MyAppDbPassword' successfully moved to soft-delete state.

4. Delete and permanently purge a secret:

python3 keyvault_mgr.py \
    --vault https://my-dev-vault.vault.azure.net \
    --secret MyAppDbPassword \
    --delete \
    --purge

Expected Output (to stderr):

2023-10-27 10:33:00,123 - INFO - Successfully connected to Azure Key Vault: https://my-dev-vault.vault.azure.net
2023-10-27 10:33:00,456 - INFO - Attempting to delete secret 'MyAppDbPassword'...
2023-10-27 10:33:01,789 - INFO - Secret 'MyAppDbPassword' successfully moved to soft-delete state.
2023-10-27 10:33:01,800 - WARNING - Attempting to purge secret 'MyAppDbPassword' permanently. This action is irreversible and requires 'Purge' permission.
2023-10-27 10:33:02,500 - INFO - Secret 'MyAppDbPassword' successfully purged permanently.

Error Handling

The script implements robust error handling to provide informative feedback in case of issues:

  • Authentication Errors (ClientAuthenticationError): If DefaultAzureCredential fails to authenticate (e.g., az login not performed, incorrect environment variables, or insufficient permissions for the identity), a detailed error message will be logged to stderr, guiding the user to check their authentication setup. The script will then exit with a non-zero status code.
  • Secret Not Found (ResourceNotFoundError): When attempting to retrieve (--get) or delete (--delete) a secret that does not exist in the specified Key Vault, this specific exception is caught. An ERROR message indicating the secret was not found is logged, and the script exits.
  • Key Vault API Errors (HttpResponseError): Any other issues during interaction with the Key Vault service (e.g., network issues, permission problems for specific operations like set or purge, or internal Key Vault errors) are caught as HttpResponseError. A general error message along with the specific HTTP error details from Azure will be logged.
  • Invalid Usage: The argparse module handles incorrect command-line arguments (e.g., missing required arguments, mutually exclusive arguments used together) by printing usage information and an error message to stderr, then exiting.
  • Generic Exceptions: A broad Exception handler is included in the main function to catch any unforeseen issues, ensuring the script terminates gracefully and logs a critical error message with traceback information.

All informational messages and errors are logged to sys.stderr, while the actual secret value (when using --get) is printed to sys.stdout. This separation allows for easy piping of the secret value to other commands or scripts without mixing it with log output.

Extending Ideas

  1. Support for Other Key Vault Object Types: Extend the script to manage other Key Vault objects like keys (KeyClient) and certificates (CertificateClient). This would involve adding new command-line arguments and corresponding functions to interact with these clients.
  2. Secret Versioning and Listing: Add options to list all versions of a secret or retrieve a specific version by its version ID. This would be useful for auditing, rollback, or managing secret rotation.
  3. Integration with Configuration Management: Adapt the script to be part of a larger configuration management system. For instance
// RELATED_SCRIPTS
← Back to DevLab valtersit.com