From 0d6fc1ddf13d1324ed54f2aa3a4746d1aedf43fa Mon Sep 17 00:00:00 2001 From: KanchanN-Microsoft Date: Thu, 13 Aug 2026 10:01:11 +0530 Subject: [PATCH 1/4] azuredefaultcredential --- .../src/libs/utils/azure_credential_utils.py | 14 ++++++++++++-- .../src/libs/utils/credential_util.py | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/ContentProcessor/src/libs/utils/azure_credential_utils.py b/src/ContentProcessor/src/libs/utils/azure_credential_utils.py index 3344379c..d8ddeb00 100644 --- a/src/ContentProcessor/src/libs/utils/azure_credential_utils.py +++ b/src/ContentProcessor/src/libs/utils/azure_credential_utils.py @@ -47,7 +47,7 @@ async def get_async_bearer_token_provider(): Returns: A callable suitable for SDK clients that accept a token provider. """ - credential = await get_async_azure_credential() + credential = get_async_azure_credential() return identity_get_async_bearer_token_provider( credential, "https://cognitiveservices.azure.com/.default" ) @@ -195,7 +195,17 @@ def get_async_azure_credential(): logging.info( "[AUTH] All async CLI credentials failed - falling back to AsyncDefaultAzureCredential" ) - return AsyncDefaultAzureCredential() + app_env = os.getenv("APP_ENV", "prod").lower() + if app_env == "dev": + print("[AUTH] Environment: DEV -> using AsyncDefaultAzureCredential") + logging.info("[AUTH] APP_ENV=dev -> using AsyncDefaultAzureCredential") + return AsyncDefaultAzureCredential() # CodeQL [SM05139] Okay use of DefaultAzureCredential as it is only used in development + else: + print(f"[AUTH] Environment: PROD (APP_ENV={app_env}) -> using AsyncManagedIdentityCredential") + logging.info( + "[AUTH] APP_ENV=%s -> using AsyncManagedIdentityCredential", app_env + ) + return AsyncManagedIdentityCredential(client_id=os.getenv("AZURE_CLIENT_ID")) def validate_azure_authentication() -> dict[str, Any]: diff --git a/src/ContentProcessor/src/libs/utils/credential_util.py b/src/ContentProcessor/src/libs/utils/credential_util.py index 791ab42c..cd03dcb9 100644 --- a/src/ContentProcessor/src/libs/utils/credential_util.py +++ b/src/ContentProcessor/src/libs/utils/credential_util.py @@ -47,7 +47,7 @@ async def get_async_bearer_token_provider(): Returns: A callable suitable for SDK clients that accept a token provider. """ - credential = await get_async_azure_credential() + credential = get_async_azure_credential() return identity_get_async_bearer_token_provider( credential, "https://cognitiveservices.azure.com/.default" ) From c97b7ba088f012e83555303b33b938fc83719575 Mon Sep 17 00:00:00 2001 From: KanchanN-Microsoft Date: Thu, 13 Aug 2026 13:22:19 +0530 Subject: [PATCH 2/4] fixed copilot comments and test cases --- infra/main.bicep | 1 + .../src/libs/utils/azure_credential_utils.py | 30 +++++++++++-------- .../utils/test_azure_credential_utils.py | 2 +- .../test_azure_credential_utils_extended.py | 6 ++-- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 3ce3db4f..df9263de 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -560,6 +560,7 @@ resource resourceGroupTags 'Microsoft.Resources/tags@2025-04-01' = { Type: enablePrivateNetworking ? 'WAF' : 'Non-WAF' CreatedBy: createdBy DeploymentName: deployment().name + SecurityControl: 'Ignore' } } } diff --git a/src/ContentProcessor/src/libs/utils/azure_credential_utils.py b/src/ContentProcessor/src/libs/utils/azure_credential_utils.py index d8ddeb00..d8f94d6f 100644 --- a/src/ContentProcessor/src/libs/utils/azure_credential_utils.py +++ b/src/ContentProcessor/src/libs/utils/azure_credential_utils.py @@ -191,21 +191,27 @@ def get_async_azure_credential(): logging.info(f"[AUTH] Using {credential_name} for local development") return credential - # Final fallback to DefaultAzureCredential - logging.info( - "[AUTH] All async CLI credentials failed - falling back to AsyncDefaultAzureCredential" - ) + # All async CLI credentials failed. Select the final credential based on the + # environment: production uses Managed Identity, while development uses + # DefaultAzureCredential. Defaults to production when APP_ENV is not set. app_env = os.getenv("APP_ENV", "prod").lower() - if app_env == "dev": - print("[AUTH] Environment: DEV -> using AsyncDefaultAzureCredential") - logging.info("[AUTH] APP_ENV=dev -> using AsyncDefaultAzureCredential") - return AsyncDefaultAzureCredential() # CodeQL [SM05139] Okay use of DefaultAzureCredential as it is only used in development - else: - print(f"[AUTH] Environment: PROD (APP_ENV={app_env}) -> using AsyncManagedIdentityCredential") + if app_env == "prod": + client_id = os.getenv("AZURE_CLIENT_ID") + if client_id: + logging.info( + "[AUTH] APP_ENV=prod -> using async user-assigned managed identity: %s", + client_id, + ) + return AsyncManagedIdentityCredential(client_id=client_id) logging.info( - "[AUTH] APP_ENV=%s -> using AsyncManagedIdentityCredential", app_env + "[AUTH] APP_ENV=prod -> using async system-assigned managed identity" ) - return AsyncManagedIdentityCredential(client_id=os.getenv("AZURE_CLIENT_ID")) + return AsyncManagedIdentityCredential() + + logging.info( + "[AUTH] APP_ENV=%s -> falling back to AsyncDefaultAzureCredential", app_env + ) + return AsyncDefaultAzureCredential() # CodeQL [SM05139] Okay use of DefaultAzureCredential as it is only used in development def validate_azure_authentication() -> dict[str, Any]: diff --git a/src/tests/ContentProcessor/utils/test_azure_credential_utils.py b/src/tests/ContentProcessor/utils/test_azure_credential_utils.py index 7f0f88a0..e684b34c 100644 --- a/src/tests/ContentProcessor/utils/test_azure_credential_utils.py +++ b/src/tests/ContentProcessor/utils/test_azure_credential_utils.py @@ -98,7 +98,7 @@ def test_returns_async_user_assigned_with_client_id(self, mock_async_managed): side_effect=Exception("no azd"), ) @patch(f"{MODULE}.AsyncAzureCliCredential", side_effect=Exception("no az")) - @patch.dict("os.environ", {}, clear=True) + @patch.dict("os.environ", {"APP_ENV": "dev"}, clear=True) def test_falls_back_to_async_default( self, mock_async_cli, mock_async_dev_cli, mock_async_default ): diff --git a/src/tests/ContentProcessor/utils/test_azure_credential_utils_extended.py b/src/tests/ContentProcessor/utils/test_azure_credential_utils_extended.py index edd735d8..8f538756 100644 --- a/src/tests/ContentProcessor/utils/test_azure_credential_utils_extended.py +++ b/src/tests/ContentProcessor/utils/test_azure_credential_utils_extended.py @@ -109,6 +109,7 @@ def test_get_async_azure_credential_cli_fallback(self, monkeypatch): for key in ["WEBSITE_SITE_NAME", "AZURE_CLIENT_ID", "MSI_ENDPOINT", "IDENTITY_ENDPOINT", "KUBERNETES_SERVICE_HOST"]: monkeypatch.delenv(key, raising=False) + monkeypatch.setenv("APP_ENV", "dev") with patch('libs.utils.azure_credential_utils.AsyncAzureCliCredential') as mock_cli, \ patch('libs.utils.azure_credential_utils.AsyncAzureDeveloperCliCredential') as mock_azd, \ @@ -149,10 +150,7 @@ async def test_get_async_bearer_token_provider_success(self, monkeypatch): """Test async bearer token provider creation""" monkeypatch.setenv("MSI_ENDPOINT", "http://localhost") - # Create an async mock - from unittest.mock import AsyncMock - - with patch('libs.utils.azure_credential_utils.get_async_azure_credential', new_callable=AsyncMock) as mock_get_cred, \ + with patch('libs.utils.azure_credential_utils.get_async_azure_credential') as mock_get_cred, \ patch('libs.utils.azure_credential_utils.identity_get_async_bearer_token_provider') as mock_provider: mock_credential = Mock() From 9aa65aa523f13278d2968def7da40e07aff31475 Mon Sep 17 00:00:00 2001 From: KanchanN-Microsoft Date: Thu, 13 Aug 2026 13:31:18 +0530 Subject: [PATCH 3/4] remove unnecessary comments --- src/ContentProcessor/src/libs/utils/azure_credential_utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ContentProcessor/src/libs/utils/azure_credential_utils.py b/src/ContentProcessor/src/libs/utils/azure_credential_utils.py index d8f94d6f..0e30c658 100644 --- a/src/ContentProcessor/src/libs/utils/azure_credential_utils.py +++ b/src/ContentProcessor/src/libs/utils/azure_credential_utils.py @@ -191,9 +191,6 @@ def get_async_azure_credential(): logging.info(f"[AUTH] Using {credential_name} for local development") return credential - # All async CLI credentials failed. Select the final credential based on the - # environment: production uses Managed Identity, while development uses - # DefaultAzureCredential. Defaults to production when APP_ENV is not set. app_env = os.getenv("APP_ENV", "prod").lower() if app_env == "prod": client_id = os.getenv("AZURE_CLIENT_ID") From 7f9355a70221324b2273bfcbb9836ecec5274515 Mon Sep 17 00:00:00 2001 From: KanchanN-Microsoft Date: Thu, 13 Aug 2026 13:41:41 +0530 Subject: [PATCH 4/4] remove SecurityControl from resourceGroupTags --- infra/main.bicep | 1 - 1 file changed, 1 deletion(-) diff --git a/infra/main.bicep b/infra/main.bicep index df9263de..3ce3db4f 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -560,7 +560,6 @@ resource resourceGroupTags 'Microsoft.Resources/tags@2025-04-01' = { Type: enablePrivateNetworking ? 'WAF' : 'Non-WAF' CreatedBy: createdBy DeploymentName: deployment().name - SecurityControl: 'Ignore' } } }