This tutorial provides a production-ready Python script for managing secrets in Azure Key Vault. It demonstrates how to authenticate securely using Azure I
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.
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.
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.
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.SecretClient is initialized, targeting a specific Azure Key Vault URI. This client provides methods to perform secret operations.--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.
#!/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()
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:
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.az login), the script will use your current CLI credentials. This is often the easiest method for local development.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:
--get: Secrets Get permission.--set: Secrets Set permission.--delete: Secrets Delete permission.--purge (with --delete): Secrets Purge permission.Example RBAC roles that might grant these permissions:
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.
The script implements robust error handling to provide informative feedback in case of issues:
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.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.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.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.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.
KeyClient) and certificates (CertificateClient). This would involve adding new command-line arguments and corresponding functions to interact with these clients.