From 3368d6e071b3138bfbb89d6c3480cb25739093c3 Mon Sep 17 00:00:00 2001 From: Joshua Wilshere Date: Wed, 25 Feb 2026 02:49:02 +0000 Subject: [PATCH 1/6] Added Key Vault auth option for Redis connection since managed identity is not available in sovereign clouds. --- application/single_app/app.py | 16 +++++++++++ application/single_app/app_settings_cache.py | 24 ++++++++++++++++ application/single_app/functions_keyvault.py | 28 +++++++++++++++++++ .../single_app/route_backend_settings.py | 8 ++++++ .../static/js/admin/admin_settings.js | 23 +++++++++++++-- .../single_app/templates/admin_settings.html | 16 +++++++++-- 6 files changed, 110 insertions(+), 5 deletions(-) diff --git a/application/single_app/app.py b/application/single_app/app.py index cd04ff67..1f259bac 100644 --- a/application/single_app/app.py +++ b/application/single_app/app.py @@ -174,6 +174,22 @@ def configure_sessions(settings): socket_connect_timeout=5, socket_timeout=5 ) + elif redis_auth_type == 'key_vault': + print("Redis enabled using Key Vault Secret") + from functions_keyvault import retrieve_secret_direct + redis_key_secret_name = settings.get('redis_key', '').strip() + redis_password = retrieve_secret_direct(redis_key_secret_name) + if redis_password: + redis_password = redis_password.strip() + redis_client = Redis( + host=redis_url, + port=6380, + db=0, + password=redis_password, + ssl=True, + socket_connect_timeout=5, + socket_timeout=5 + ) else: redis_key = settings.get('redis_key', '').strip() print("Redis enabled using Access Key") diff --git a/application/single_app/app_settings_cache.py b/application/single_app/app_settings_cache.py index cf908540..b06258b9 100644 --- a/application/single_app/app_settings_cache.py +++ b/application/single_app/app_settings_cache.py @@ -36,6 +36,30 @@ def configure_app_cache(settings, redis_cache_endpoint=None): password=token.token, ssl=True ) + elif redis_auth_type == 'key_vault': + print("[ASC] Redis enabled using Key Vault Secret") + redis_key_secret_name = settings.get('redis_key', '').strip() + key_vault_name = settings.get('key_vault_name', '').strip() + key_vault_identity = settings.get('key_vault_identity', '').strip() + try: + from azure.keyvault.secrets import SecretClient + from config import KEY_VAULT_DOMAIN + kv_credential = DefaultAzureCredential(managed_identity_client_id=key_vault_identity) if key_vault_identity else DefaultAzureCredential() + kv_client = SecretClient(vault_url=f"https://{key_vault_name}{KEY_VAULT_DOMAIN}", credential=kv_credential) + redis_password = kv_client.get_secret(redis_key_secret_name).value + if redis_password: + redis_password = redis_password.strip() + print("[ASC] Redis key retrieved from Key Vault successfully") + except Exception as kv_err: + print(f"[ASC] ERROR: Failed to retrieve Redis key from Key Vault: {kv_err}") + raise + redis_client = Redis( + host=redis_url, + port=6380, + db=0, + password=redis_password, + ssl=True + ) else: redis_key = settings.get('redis_key', '').strip() print("[ASC] Redis enabled using Access Key") diff --git a/application/single_app/functions_keyvault.py b/application/single_app/functions_keyvault.py index 2094814f..cf44381c 100644 --- a/application/single_app/functions_keyvault.py +++ b/application/single_app/functions_keyvault.py @@ -111,6 +111,34 @@ def retrieve_secret_from_key_vault_by_full_name(full_secret_name): return full_secret_name +def retrieve_secret_direct(secret_name): + """ + Retrieve a secret directly from Key Vault by its exact name, bypassing source/scope name + validation and the enable_key_vault_secret_storage guard. Use this for infrastructure + secrets (e.g. Redis key) where the secret name is arbitrary and not controlled by the + scope_value--source--scope--secret_name convention. + + Args: + secret_name (str): The exact Key Vault secret name. + + Returns: + str: The secret value. + + Raises: + ValueError: If Key Vault is not configured in settings. + Exception: If the secret cannot be retrieved. + """ + settings = app_settings_cache.get_settings_cache() + key_vault_name = settings.get("key_vault_name", "").strip() + if not key_vault_name: + raise ValueError("Key Vault name is not configured in settings (key_vault_name).") + if not secret_name: + raise ValueError("secret_name must not be empty.") + key_vault_url = f"https://{key_vault_name}{KEY_VAULT_DOMAIN}" + secret_client = SecretClient(vault_url=key_vault_url, credential=get_keyvault_credential()) + retrieved = secret_client.get_secret(secret_name) + return retrieved.value + def store_secret_in_key_vault(secret_name, secret_value, scope_value, source="global", scope="global"): """ Store a secret in Key Vault using a dynamic name based on source, scope, and scope_value. diff --git a/application/single_app/route_backend_settings.py b/application/single_app/route_backend_settings.py index 7be73134..a69c554b 100644 --- a/application/single_app/route_backend_settings.py +++ b/application/single_app/route_backend_settings.py @@ -706,6 +706,14 @@ def _test_redis_connection(payload): cache_endpoint = get_redis_cache_infrastructure_endpoint(redis_hostname) token = credential.get_token(cache_endpoint) redis_password = token.token + elif redis_auth_type == 'key_vault': + if not redis_key: + return jsonify({'error': 'Key Vault secret name is required for key_vault auth'}), 400 + try: + from functions_keyvault import retrieve_secret_direct + redis_password = retrieve_secret_direct(redis_key) + except Exception as kv_err: + return jsonify({'error': f'Failed to retrieve Redis key from Key Vault: {str(kv_err)}'}), 500 else: if not redis_key: return jsonify({'error': 'Redis key is required for key auth'}), 400 diff --git a/application/single_app/static/js/admin/admin_settings.js b/application/single_app/static/js/admin/admin_settings.js index 85719128..3eae281c 100644 --- a/application/single_app/static/js/admin/admin_settings.js +++ b/application/single_app/static/js/admin/admin_settings.js @@ -1867,14 +1867,30 @@ function setupToggles() { const redisAuthType = document.getElementById('redis_auth_type'); if (redisAuthType) { const redisKeyContainer = document.getElementById('redis_key_container'); + const redisKeyLabel = document.getElementById('redis_key_label'); + + // Helper to update the label text based on auth type + function updateRedisKeyLabel(authTypeValue) { + if (!redisKeyLabel) return; + redisKeyLabel.textContent = authTypeValue === 'key_vault' ? 'Key Vault Secret Name' : 'Redis Access Key'; + } + // Set initial state on load if (redisKeyContainer) { - redisKeyContainer.style.display = (redisAuthType.value === 'key') ? 'block' : 'none'; + redisKeyContainer.style.display = (redisAuthType.value === 'key' || redisAuthType.value === 'key_vault') ? 'block' : 'none'; } + updateRedisKeyLabel(redisAuthType.value); + redisAuthType.addEventListener('change', function () { if (redisKeyContainer) { - redisKeyContainer.style.display = (this.value === 'key') ? 'block' : 'none'; + redisKeyContainer.style.display = (this.value === 'key' || this.value === 'key_vault') ? 'block' : 'none'; + } + const redisKeyVaultHint = document.getElementById('redis_key_vault_hint'); + if (redisKeyVaultHint) { + redisKeyVaultHint.style.display = (this.value === 'key_vault') ? 'block' : 'none'; } + updateRedisKeyLabel(this.value); + markFormAsModified(); }); } @@ -2179,7 +2195,8 @@ function setupTestButtons() { const payload = { test_type: 'redis', endpoint: document.getElementById('redis_url').value, - key: document.getElementById('redis_key').value + key: document.getElementById('redis_key').value, + auth_type: document.getElementById('redis_auth_type').value }; try { diff --git a/application/single_app/templates/admin_settings.html b/application/single_app/templates/admin_settings.html index 7d01f7da..3de1a9e9 100644 --- a/application/single_app/templates/admin_settings.html +++ b/application/single_app/templates/admin_settings.html @@ -2036,14 +2036,26 @@
-
- +
+
+
+ Enter the full Key Vault secret name. + Key Vault + must be configured and Enable Key Vault Secret Storage must be enabled. +
-
+
-
+
Enter the full Key Vault secret name. - Key Vault - must be configured and Enable Key Vault Secret Storage must be enabled. + Enable Key Vault for Agent and Action Secrets + must be enabled and configured.