Skip to content

Commit 2333426

Browse files
Better support for deploying to non-home tenant (#1964)
* Better support for multitenant * Update app/backend/app.py Co-authored-by: Anthony Shaw <[email protected]> * Better types * Update the mock * Fix Bicep workflow * Add process timeout in both places --------- Co-authored-by: Anthony Shaw <[email protected]>
1 parent fe229cc commit 2333426

File tree

5 files changed

+33
-19
lines changed

5 files changed

+33
-19
lines changed

.github/workflows/azure-dev-validation.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ jobs:
2222
- name: Build Bicep for linting
2323
uses: azure/CLI@v2
2424
with:
25-
inlineScript: az config set bicep.use_binary_from_path=false && az bicep build -f infra/main.bicep --stdout
25+
inlineScript: |
26+
export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
27+
az config set bicep.use_binary_from_path=false && az bicep build -f infra/main.bicep --stdout
2628
2729
psrule:
2830
runs-on: ubuntu-latest
@@ -42,7 +44,7 @@ jobs:
4244
outputPath: reports/ps-rule-results.sarif
4345
summary: true
4446
continue-on-error: true
45-
47+
4648
env:
4749
PSRULE_CONFIGURATION_AZURE_BICEP_FILE_EXPANSION: 'true'
4850
PSRULE_CONFIGURATION_AZURE_BICEP_FILE_EXPANSION_TIMEOUT: '30'

app/backend/app.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616
SpeechSynthesizer,
1717
)
1818
from azure.core.exceptions import ResourceNotFoundError
19-
from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider
19+
from azure.identity.aio import (
20+
AzureDeveloperCliCredential,
21+
ManagedIdentityCredential,
22+
get_bearer_token_provider,
23+
)
2024
from azure.monitor.opentelemetry import configure_azure_monitor
2125
from azure.search.documents.aio import SearchClient
2226
from azure.search.documents.indexes.aio import SearchIndexClient
@@ -436,11 +440,21 @@ async def setup_clients():
436440
USE_SPEECH_OUTPUT_BROWSER = os.getenv("USE_SPEECH_OUTPUT_BROWSER", "").lower() == "true"
437441
USE_SPEECH_OUTPUT_AZURE = os.getenv("USE_SPEECH_OUTPUT_AZURE", "").lower() == "true"
438442

439-
# Use the current user identity to authenticate with Azure OpenAI, AI Search and Blob Storage (no secrets needed,
440-
# just use 'az login' locally, and managed identity when deployed on Azure). If you need to use keys, use separate AzureKeyCredential instances with the
441-
# keys for each service
442-
# If you encounter a blocking error during a DefaultAzureCredential resolution, you can exclude the problematic credential by using a parameter (ex. exclude_shared_token_cache_credential=True)
443-
azure_credential = DefaultAzureCredential(exclude_shared_token_cache_credential=True)
443+
# Use the current user identity for keyless authentication to Azure services.
444+
# This assumes you use 'azd auth login' locally, and managed identity when deployed on Azure.
445+
# The managed identity is setup in the infra/ folder.
446+
azure_credential: Union[AzureDeveloperCliCredential, ManagedIdentityCredential]
447+
if os.getenv("WEBSITE_HOSTNAME"): # Environment variable set on Azure Web Apps
448+
current_app.logger.info("Setting up Azure credential using ManagedIdentityCredential")
449+
azure_credential = ManagedIdentityCredential()
450+
elif AZURE_TENANT_ID:
451+
current_app.logger.info(
452+
"Setting up Azure credential using AzureDeveloperCliCredential with tenant_id %s", AZURE_TENANT_ID
453+
)
454+
azure_credential = AzureDeveloperCliCredential(tenant_id=AZURE_TENANT_ID, process_timeout=60)
455+
else:
456+
current_app.logger.info("Setting up Azure credential using AzureDeveloperCliCredential for home tenant")
457+
azure_credential = AzureDeveloperCliCredential(process_timeout=60)
444458

445459
# Set up clients for AI Search and Storage
446460
search_client = SearchClient(

app/backend/prepdocs.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -381,11 +381,12 @@ async def main(strategy: Strategy, setup_index: bool = True):
381381
use_int_vectorization = args.useintvectorization and args.useintvectorization.lower() == "true"
382382

383383
# Use the current user identity to connect to Azure services unless a key is explicitly set for any of them
384-
azd_credential = (
385-
AzureDeveloperCliCredential()
386-
if args.tenantid is None
387-
else AzureDeveloperCliCredential(tenant_id=args.tenantid, process_timeout=60)
388-
)
384+
if args.tenantid:
385+
logger.info("Connecting to Azure services using the azd credential for tenant %s", args.tenantid)
386+
azd_credential = AzureDeveloperCliCredential(tenant_id=args.tenantid, process_timeout=60)
387+
else:
388+
logger.info("Connecting to Azure services using the azd credential for home tenant")
389+
azd_credential = AzureDeveloperCliCredential(process_timeout=60)
389390

390391
if args.removeall:
391392
document_action = DocumentAction.RemoveAll

infra/main.parameters.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,6 @@
191191
"enableUnauthenticatedAccess": {
192192
"value": "${AZURE_ENABLE_UNAUTHENTICATED_ACCESS=false}"
193193
},
194-
"tenantId": {
195-
"value": "${AZURE_TENANT_ID}"
196-
},
197194
"authTenantId": {
198195
"value": "${AZURE_AUTH_TENANT_ID}"
199196
},

tests/conftest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ def mock_env(monkeypatch, request):
314314
if os.getenv("AZURE_USE_AUTHENTICATION") is not None:
315315
monkeypatch.delenv("AZURE_USE_AUTHENTICATION")
316316

317-
with mock.patch("app.DefaultAzureCredential") as mock_default_azure_credential:
317+
with mock.patch("app.AzureDeveloperCliCredential") as mock_default_azure_credential:
318318
mock_default_azure_credential.return_value = MockAzureCredential()
319319
yield
320320

@@ -383,7 +383,7 @@ async def auth_client(
383383
for key, value in request.param.items():
384384
monkeypatch.setenv(key, value)
385385

386-
with mock.patch("app.DefaultAzureCredential") as mock_default_azure_credential:
386+
with mock.patch("app.AzureDeveloperCliCredential") as mock_default_azure_credential:
387387
mock_default_azure_credential.return_value = MockAzureCredential()
388388
quart_app = app.create_app()
389389

@@ -422,7 +422,7 @@ async def auth_public_documents_client(
422422
for key, value in request.param.items():
423423
monkeypatch.setenv(key, value)
424424

425-
with mock.patch("app.DefaultAzureCredential") as mock_default_azure_credential:
425+
with mock.patch("app.AzureDeveloperCliCredential") as mock_default_azure_credential:
426426
mock_default_azure_credential.return_value = MockAzureCredential()
427427
quart_app = app.create_app()
428428

0 commit comments

Comments
 (0)