Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions infra/scripts/post_deployment.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,25 @@ if ($CU_ACCOUNT_NAME) {
Write-Host " az error: $UpdateOutputStr"
}
}

# --- Grant the deploying user Container Apps Contributor on the resource group ---
# Direct (non-group) User assignment so Easy Auth's on-behalf listSecrets validation
# can resolve RBAC when the Microsoft identity provider is added (avoids group token-overage).
Write-Host ""
Write-Host "Granting Container Apps Contributor to the deploying user on the resource group..."

$DeployerObjectId = az ad signed-in-user show --query id -o tsv 2>$null
# azd env may not populate AZURE_SUBSCRIPTION_ID in every shell; fall back to the CLI context.
if (-not $SUBSCRIPTION_ID) { $SUBSCRIPTION_ID = az account show --query id -o tsv 2>$null }
if (-not $DeployerObjectId -or -not $SUBSCRIPTION_ID -or -not $RESOURCE_GROUP) {
Write-Host " [Warn] Missing signed-in user id, subscription id, or resource group. Skipping role assignment."
} else {
$RgScope = "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP"
$RoleOutput = az role assignment create --assignee-object-id $DeployerObjectId --assignee-principal-type User `
--role "358470bc-b998-42bd-ab17-a7e34c199c0f" --scope $RgScope --output none 2>&1
if ($LASTEXITCODE -eq 0 -or ($RoleOutput | Out-String) -match '(?i)RoleAssignmentExists|already exists') {
Write-Host " [OK] Container Apps Contributor granted to the deploying user."
} else {
Write-Host " [Warn] Could not create the role assignment (non-fatal). az error: $(($RoleOutput | Out-String).Trim())"
}
}
28 changes: 28 additions & 0 deletions infra/scripts/post_deployment.sh
Original file line number Diff line number Diff line change
Expand Up @@ -337,3 +337,31 @@ if [ -n "$CU_ACCOUNT_NAME" ]; then
echo " az error: $CU_UPDATE_ERR"
fi
fi

# --- Grant the deploying user Container Apps Contributor on the resource group ---
# Direct (non-group) User assignment so Easy Auth's on-behalf listSecrets validation
# can resolve RBAC when the Microsoft identity provider is added (avoids group token-overage).
echo ""
echo "Granting Container Apps Contributor to the deploying user on the resource group..."

DEPLOYER_OBJECT_ID=$(az ad signed-in-user show --query id -o tsv 2>/dev/null || true)
# azd env may not populate AZURE_SUBSCRIPTION_ID in every shell; fall back to the CLI context.
if [ -z "$SUBSCRIPTION_ID" ]; then
SUBSCRIPTION_ID=$(az account show --query id -o tsv 2>/dev/null || true)
fi

if [ -z "$DEPLOYER_OBJECT_ID" ] || [ -z "$SUBSCRIPTION_ID" ] || [ -z "$RESOURCE_GROUP" ]; then
echo " ⚠️ Missing signed-in user id, subscription id, or resource group. Skipping role assignment."
else
RG_SCOPE="/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP"
set +e # set -e (top of script) would abort on a non-zero az exit
ROLE_ERR=$(az role assignment create --assignee-object-id "$DEPLOYER_OBJECT_ID" --assignee-principal-type User \
--role "358470bc-b998-42bd-ab17-a7e34c199c0f" --scope "$RG_SCOPE" --output none 2>&1)
ROLE_EC=$?
set -e
if [ $ROLE_EC -eq 0 ] || echo "$ROLE_ERR" | grep -qiE 'RoleAssignmentExists|already exists'; then
echo " ✅ Container Apps Contributor granted to the deploying user."
else
echo " ⚠️ Could not create the role assignment (non-fatal). az error: $ROLE_ERR"
fi
fi
21 changes: 17 additions & 4 deletions src/ContentProcessor/src/libs/utils/azure_credential_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -191,11 +191,24 @@ def get_async_azure_credential():
logging.info(f"[AUTH] Using {credential_name} for local development")
return credential

# Final fallback to DefaultAzureCredential
app_env = os.getenv("APP_ENV", "prod").lower()
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=prod -> using async system-assigned managed identity"
)
return AsyncManagedIdentityCredential()
Comment thread
PadhiAjit-Microsoft marked this conversation as resolved.

logging.info(
"[AUTH] All async CLI credentials failed - falling back to AsyncDefaultAzureCredential"
"[AUTH] APP_ENV=%s -> falling back to AsyncDefaultAzureCredential", app_env
)
return AsyncDefaultAzureCredential()
return AsyncDefaultAzureCredential() # CodeQL [SM05139] Okay use of DefaultAzureCredential as it is only used in development


def validate_azure_authentication() -> dict[str, Any]:
Expand Down
2 changes: 1 addition & 1 deletion src/ContentProcessor/src/libs/utils/credential_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Comment thread
PadhiAjit-Microsoft marked this conversation as resolved.
return identity_get_async_bearer_token_provider(
credential, "https://cognitiveservices.azure.com/.default"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment thread
PadhiAjit-Microsoft marked this conversation as resolved.
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, \
Expand Down Expand Up @@ -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()
Expand Down