|
4 | 4 | from typing import Dict, Any |
5 | 5 | from aiohttp import web |
6 | 6 | from azure.storage.blob.aio import ContainerClient, BlobServiceClient |
| 7 | +from azure.identity.aio import DefaultAzureCredential |
7 | 8 | from azure.storage.blob import generate_blob_sas, BlobSasPermissions |
8 | 9 | from azure.core.exceptions import AzureError, ResourceNotFoundError |
9 | 10 |
|
@@ -333,17 +334,61 @@ async def _get_file_url( |
333 | 334 | except Exception: |
334 | 335 | logger.debug("Credential diagnostic check failed", extra={"request_id": request_id}, exc_info=True) |
335 | 336 |
|
336 | | - user_delegation_key = await blob_service_client.get_user_delegation_key( |
337 | | - key_start_time=start_time, key_expiry_time=expiry_time |
338 | | - ) |
339 | | - sas_token = generate_blob_sas( |
340 | | - account_name=blob_client.account_name or "", |
341 | | - container_name=container_client.container_name, |
342 | | - blob_name=normalized_blob_name, |
343 | | - user_delegation_key=user_delegation_key, |
344 | | - permission=BlobSasPermissions(read=True), |
345 | | - expiry=datetime.utcnow() + timedelta(minutes=self.sas_duration_minutes), |
346 | | - ) |
| 337 | + # If the existing blob_service_client is not AAD/Bearer-capable (for example, |
| 338 | + # constructed with an account key), the Storage service will reject a |
| 339 | + # get_user_delegation_key call. In that case, create a temporary |
| 340 | + # AAD-backed BlobServiceClient using DefaultAzureCredential to request |
| 341 | + # the user delegation key. This is deliberate and only used when |
| 342 | + # auth_mode == MANAGED_IDENTITY and the provided client cannot issue |
| 343 | + # a Bearer token. We log the behavior so it is visible in production. |
| 344 | + temp_cred = None |
| 345 | + temp_blob_service_client = None |
| 346 | + try: |
| 347 | + cred = getattr(blob_service_client, 'credential', None) |
| 348 | + needs_temp_aad = not (cred is not None and hasattr(cred, 'get_token')) |
| 349 | + |
| 350 | + if needs_temp_aad: |
| 351 | + logger.info( |
| 352 | + "BlobServiceClient not AAD-capable; creating temporary AAD-backed client for user-delegation key", |
| 353 | + extra={"request_id": request_id, "blob_account": blob_client.account_name} |
| 354 | + ) |
| 355 | + temp_cred = DefaultAzureCredential() |
| 356 | + # Use the same account URL as the provided service client |
| 357 | + account_url = getattr(blob_service_client, 'url', None) |
| 358 | + if not account_url: |
| 359 | + # Fallback to constructing from account name |
| 360 | + account_name = getattr(blob_client, 'account_name', None) or getattr(blob_service_client, 'account_name', None) |
| 361 | + account_url = f"https://{account_name}.blob.core.windows.net" |
| 362 | + |
| 363 | + temp_blob_service_client = BlobServiceClient(account_url=account_url, credential=temp_cred) |
| 364 | + user_delegation_key = await temp_blob_service_client.get_user_delegation_key( |
| 365 | + key_start_time=start_time, key_expiry_time=expiry_time |
| 366 | + ) |
| 367 | + else: |
| 368 | + user_delegation_key = await blob_service_client.get_user_delegation_key( |
| 369 | + key_start_time=start_time, key_expiry_time=expiry_time |
| 370 | + ) |
| 371 | + |
| 372 | + sas_token = generate_blob_sas( |
| 373 | + account_name=blob_client.account_name or "", |
| 374 | + container_name=container_client.container_name, |
| 375 | + blob_name=normalized_blob_name, |
| 376 | + user_delegation_key=user_delegation_key, |
| 377 | + permission=BlobSasPermissions(read=True), |
| 378 | + expiry=datetime.utcnow() + timedelta(minutes=self.sas_duration_minutes), |
| 379 | + ) |
| 380 | + finally: |
| 381 | + # Close temporary clients/credentials if created to avoid leaks |
| 382 | + if temp_blob_service_client is not None: |
| 383 | + try: |
| 384 | + await temp_blob_service_client.close() |
| 385 | + except Exception: |
| 386 | + logger.debug("Failed closing temporary BlobServiceClient", extra={"request_id": request_id}, exc_info=True) |
| 387 | + if temp_cred is not None: |
| 388 | + try: |
| 389 | + await temp_cred.close() |
| 390 | + except Exception: |
| 391 | + logger.debug("Failed closing temporary DefaultAzureCredential", extra={"request_id": request_id}, exc_info=True) |
347 | 392 | except Exception as ade: |
348 | 393 | logger.error("Managed Identity auth selected but user-delegation key request failed", extra={"request_id": request_id, "error": str(ade)}) |
349 | 394 | # Surface error to caller (no silent fallback allowed) |
|
0 commit comments