Skip to content

fix(azure_sentinel): respect AZURE_AUTHORITY_HOST and derive the Azure Monitor audience per cloud - #36137

Merged
yucheng-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_lit5293_azure_sentinel_sovereign_cloud
Aug 7, 2026
Merged

fix(azure_sentinel): respect AZURE_AUTHORITY_HOST and derive the Azure Monitor audience per cloud#36137
yucheng-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_lit5293_azure_sentinel_sovereign_cloud

Conversation

@yucheng-berri

@yucheng-berri yucheng-berri commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • azure_sentinel hardcoded the commercial Entra authority (https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token) and the commercial Azure Monitor audience (https://monitor.azure.com/.default), so Log Analytics ingestion could not work in Azure Government even with AZURE_SENTINEL_ENDPOINT pointed at a sovereign Data Collection Endpoint
  • A customer hit exactly this and asked whether the codepath could respect AZURE_AUTHORITY_HOST

How it solves it:

  • The Entra authority now resolves from the existing AZURE_AUTHORITY_HOST, defaulting to the Azure Public Cloud authority when unset
  • The Logs Ingestion audience is derived from that authority. Moving only the token URL is not enough: sovereign Entra would then be asked for a token scoped to the commercial audience, which the sovereign endpoint rejects. The audience values come from the Azure SDK's KnownMonitorAudience enum in Azure/azure-sdk-for-js, sdk/monitor/monitor-ingestion/src/models/models.ts
  • No new environment variables. One file changed, plus its tests
  • THIS IS A BREAKING CHANGE FOR DEPLOYMENTS THAT ALREADY SET AZURE_AUTHORITY_HOST AND KEEP THEIR SENTINEL WORKSPACE IN THE COMMERCIAL CLOUD. SEE BEHAVIOR CHANGES BELOW.

Relevant issues

Linear ticket

Resolves LIT-5293

Behavior changes

BREAKING CHANGE FOR ONE CONFIGURATION

IF YOU SET AZURE_AUTHORITY_HOST TO A SOVEREIGN AUTHORITY AND YOUR AZURE SENTINEL WORKSPACE IS IN THE COMMERCIAL CLOUD, THIS PR WILL STOP YOUR SENTINEL LOGS FROM BEING DELIVERED.

SENTINEL PREVIOUSLY IGNORED THAT VARIABLE AND ALWAYS USED THE COMMERCIAL CLOUD. IT NOW FOLLOWS IT. THERE IS CURRENTLY NO PER INTEGRATION OPT OUT, BECAUSE THE PROXY CONSTRUCTS THE LOGGER WITH NO ARGUMENTS, SO THE authority_host CONSTRUCTOR PARAMETER IS REACHABLE ONLY FROM THE SDK.

REQUESTS, BILLING AND ALL OTHER CALLBACKS ARE UNAFFECTED. DEPLOYMENTS THAT DO NOT SET AZURE_AUTHORITY_HOST SEE NO CHANGE AT ALL.

Azure Sentinel was the only subsystem minting an Entra token for an Azure resource that did not follow AZURE_AUTHORITY_HOST. Azure OpenAI, the azure_storage logger, Redis AAD auth, Key Vault and the Azure Blob backend all construct azure-identity credentials without an explicit authority=, so they inherit it already, and the Azure OpenAI OIDC path reads it directly at litellm/llms/azure/common_utils.py:174. The Graph-facing callers, the Purview guardrail and Microsoft SSO, still hardcode login.microsoftonline.com and are out of scope here.

The audience has to move with the authority because Azure Monitor publishes a different one per cloud, monitor.azure.us in Government. Azure Storage is the instructive contrast: Microsoft documents its https://storage.azure.com/.default audience as identical in every cloud, which is why azure_storage needs only an endpoint-suffix setting and no audience mapping. The two axes move independently, since storage hostnames do change per cloud even though the audience does not.

Who this breaks, stated plainly, because it is narrower than it sounds but genuinely reachable. The sibling integrations mostly default to non-Entra auth: azure_storage branches to account-key auth and never touches Entra, Redis AAD is opt-in behind REDIS_AZURE_AD_TOKEN, Key Vault loads only when enabled, and enable_azure_ad_token_refresh defaults to false. So a deployment can be perfectly healthy today with a sovereign AZURE_AUTHORITY_HOST used only for Azure OpenAI, alongside a commercial Sentinel workspace. That configuration works now, stops working after this PR, and the operator has no way to opt out. Measured base-vs-head against a real Azure Monitor endpoint: base delivered and stored the row, head did not attempt ingestion, and requests and spend logging were identical on both sides.

Pre-Submission checklist

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review. 5/5, anchored to the current head c593445c

Screenshots / Proof of Fix

No stubs. Real proxy, real Postgres, real Gemini call driving the callback, real Microsoft Entra, and a real Azure Monitor Data Collection Endpoint backed by a real Log Analytics workspace, built for this run in a commercial subscription. Tenant id redacted.

export AZURE_SENTINEL_DCR_IMMUTABLE_ID="dcr-e3a8a133f5004c89b6ab2d76b50a1b68"
export AZURE_SENTINEL_ENDPOINT="https://litellm-lit5293-dce-yv7r.eastus-1.ingest.monitor.azure.com"
export AZURE_SENTINEL_TENANT_ID=...  AZURE_SENTINEL_CLIENT_ID=...  AZURE_SENTINEL_CLIENT_SECRET=...

python -m litellm.proxy.proxy_cli --config config.yaml --port 20297 --use_prisma_db_push --detailed_debug

curl -sS http://127.0.0.1:20297/v1/chat/completions \
  -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" \
  -d '{"model":"gemini-flash","messages":[{"role":"user","content":"Reply with exactly: <marker>"}]}'
leg tree AZURE_AUTHORITY_HOST Entra token Azure ingestion row in Log Analytics
1 head unset 200 204 stored
2 base unset 200 204 stored
3 base gov 200, setting ignored 204 stored
4 head gov 400 at login.microsoftonline.us not attempted absent

Leg 1 against leg 2 is the backward-compatibility result: with no authority host configured, head behaves exactly like base and the log still lands.

Leg 3 against leg 4 is the fix, confirmed at the destination rather than inferred from a status code. Base ignores the sovereign authority and quietly ships to the commercial cloud; head routes where it was told and, because this service principal lives in a commercial tenant, gets AADSTS90038: Tenant ... request is being redirected to the National Cloud 'MicrosoftOnline.COM'. A genuine Azure Government tenant would get a token.

az monitor log-analytics query -w <workspace-id> --analytics-query \
  "Custom_LiteLLM_CL | project TimeGenerated, Model, Status, TotalTokens, Msg=tostring(Messages) | order by TimeGenerated asc"
2026-08-07T01:15:00  model=gemini/gemini-3.5-flash status=success tokens=173  leg=2 (base, default)
2026-08-07T01:15:31  model=gemini/gemini-3.5-flash status=success tokens=149  leg=3 (base, gov)
2026-08-07T02:42:41  model=gemini/gemini-3.5-flash status=success tokens=119  leg=1 (head, default)

leg 4 present: False

Both head legs were re-run against the exact code in this PR after it was cut back; the two base legs are unchanged upstream code and were not affected by that. Leg 4 is absent by design, which is the whole point of the differential.

What this evidence does not cover

Only commercial (AzureCloud) subscriptions were available, so nothing here demonstrates a sovereign Data Collection Endpoint accepting a token minted with a sovereign audience; that needs an Azure Government tenant. What is proven is that the authority and the audience follow the configured cloud, that the commercial path is unchanged, and that the audience values match the Azure SDK's own enum. Requesting the gov audience from commercial Entra returns AADSTS500011: The resource principal named https://monitor.azure.us was not found in the tenant, which echoes the requested scope back and confirms it reaches the wire.

Type

🐛 Bug Fix

Changes

litellm/integrations/azure_sentinel/azure_sentinel.py gains a two-entry authority-to-audience table, two pure static helpers, and an optional authority_host constructor parameter mirroring the existing settings. The token URL is built from the resolved authority instead of a literal.

Azure China is not mapped. Its audience is known and the entry would be one line, but no one has asked for it, so it is left out rather than shipped untested.

QA runbook

  1. Set AZURE_SENTINEL_* for a real workspace, add azure_sentinel to success_callback, leave AZURE_AUTHORITY_HOST unset, send a chat completion, confirm the record lands in Log Analytics exactly as before
  2. Set AZURE_AUTHORITY_HOST=https://login.microsoftonline.us and confirm the proxy's token request goes to login.microsoftonline.us with the https://monitor.azure.us/.default audience

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@greptile-apps

greptile-apps Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR makes Azure Sentinel token acquisition honor AZURE_AUTHORITY_HOST and derives the Azure Monitor OAuth audience from the selected cloud.

  • Adds authority normalization and public/US Government Monitor audience selection.
  • Uses the resolved authority when constructing the OAuth token endpoint.
  • Adds unit coverage for defaults, environment configuration, normalization, and the outbound sovereign-cloud token request.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
litellm/integrations/azure_sentinel/azure_sentinel.py Resolves the configured Azure authority, selects the corresponding Monitor scope, and uses both values during token acquisition.
tests/test_litellm/integrations/test_azure_sentinel.py Adds mocked coverage for authority resolution, environment configuration, normalization, and token-request behavior without making real network calls.

Reviews (4): Last reviewed commit: "fix(azure_sentinel): respect AZURE_AUTHO..." | Re-trigger Greptile

greptile-apps[bot]

This comment was marked as resolved.

@yucheng-berri

Copy link
Copy Markdown
Contributor Author

Moved the authority and audience constants into litellm/constants.py next to the other Azure ones. Resolution helpers stay put: llms/azure/common_utils.py pulls in openai, and litellm_logging imports this logger at import time. Please re-review the current head 1a48ee2

@yucheng-berri

Copy link
Copy Markdown
Contributor Author

@greptileai please review the current head 1a48ee2

@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_lit5293_azure_sentinel_sovereign_cloud (c593445) with litellm_internal_staging (4e5495e)

Open in CodSpeed

@yucheng-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

Comment thread litellm/integrations/azure_sentinel/azure_sentinel.py Outdated
@yucheng-berri
yucheng-berri force-pushed the litellm_lit5293_azure_sentinel_sovereign_cloud branch from 1a48ee2 to 768c866 Compare August 7, 2026 02:02
devin-ai-integration[bot]

This comment was marked as resolved.

@yucheng-berri

Copy link
Copy Markdown
Contributor Author

@greptileai review latest head

@yucheng-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@yucheng-berri
yucheng-berri force-pushed the litellm_lit5293_azure_sentinel_sovereign_cloud branch from 768c866 to 35213d7 Compare August 7, 2026 02:22

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

1 issue from previous review remains unresolved.

Fix All in Cursor

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 768c866. Configure here.

… and audience

The Azure Sentinel logger hardcoded the commercial Entra authority and the
commercial Azure Monitor audience, so Log Analytics ingestion could not work in
Azure Government even when the ingestion endpoint pointed at a sovereign Data
Collection Endpoint.

Resolve the authority from AZURE_AUTHORITY_HOST and derive the matching Logs
Ingestion audience from it. Moving only the token URL is not enough: sovereign
Entra would then be asked for a token scoped to the commercial audience, which
the sovereign endpoint rejects.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 new potential issues.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment on lines +165 to +172
@staticmethod
def _resolve_oauth_scope(authority_host: str) -> str:
"""
Map an authority host to the Azure Monitor Logs Ingestion audience for the same cloud,
falling back to the Azure Public Cloud audience for an unrecognized host.
"""
host: Final = urlparse(authority_host).hostname or ""
return MONITOR_SCOPE_BY_AUTHORITY_HOST.get(host, DEFAULT_AZURE_MONITOR_SCOPE)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Logs still fail for Azure China and other sovereign clouds because the wrong audience is silently used

An authority host that is not one of the two hard-coded entries falls back to the commercial Azure Monitor audience (MONITOR_SCOPE_BY_AUTHORITY_HOST.get(host, DEFAULT_AZURE_MONITOR_SCOPE) at litellm/integrations/azure_sentinel/azure_sentinel.py:172) with no way to override it, so log delivery in clouds such as Azure China keeps failing silently.
Impact: Operators in unsupported sovereign clouds get no logs and no message explaining why.

Unrecognized authority hosts silently map to the commercial audience

MONITOR_SCOPE_BY_AUTHORITY_HOST (litellm/integrations/azure_sentinel/azure_sentinel.py:36-41) only contains login.microsoftonline.com and login.microsoftonline.us. For Azure China (login.partner.microsoftonline.cn or the legacy login.chinacloudapi.cn) the lookup misses and the commercial audience https://monitor.azure.com/.default is requested from the sovereign Entra authority, which rejects it (or produces a token the sovereign ingestion endpoint rejects). There is no scope override parameter or env var, so the misconfiguration cannot be worked around and nothing is logged to warn the operator. The PR description states China is mapped and that an AZURE_SENTINEL_OAUTH_SCOPE override plus startup warnings exist, but none of that is present in the code.

Prompt for agents
In litellm/integrations/azure_sentinel/azure_sentinel.py, MONITOR_SCOPE_BY_AUTHORITY_HOST only covers Azure Public and Azure Government, and _resolve_oauth_scope silently falls back to the commercial audience for anything else. A deployment pointed at Azure China (login.partner.microsoftonline.cn, or the legacy login.chinacloudapi.cn that azure-identity still exposes) will therefore ask a sovereign authority for the commercial Azure Monitor audience and get no logs, with no diagnostic. Consider adding the China entries to the mapping, emitting a warning when the resolved authority host has no known audience, and providing an explicit scope override (constructor arg plus AZURE_SENTINEL_OAUTH_SCOPE env var) so unlisted clouds can be configured.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deliberate scope cut; description now says China is unmapped. Unlisted clouds get the commercial audience exactly as before this PR, so no regression.

Comment on lines +97 to +99
resolved_authority_host: Final = self._normalize_authority_host(
authority_host or os.getenv("AZURE_AUTHORITY_HOST") or DEFAULT_AZURE_AUTHORITY_HOST
)

This comment was marked as off-topic.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deliberately cut as speculative after review. Mixed sovereign-identity plus commercial-Sentinel deployments aren't a shape we're solving here. Documented under Behavior changes.

Comment on lines +154 to +163
@staticmethod
def _normalize_authority_host(authority_host: str) -> str:
"""
Normalize an authority host into an absolute URL with no trailing slash.

Accepts the scheme-qualified form litellm documents ("https://login.microsoftonline.us")
and the bare-host form the azure-identity AzureAuthorityHosts constants use.
"""
stripped: Final = authority_host.strip().rstrip("/")
return stripped if "://" in stripped else f"https://{stripped}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 Authority host is used unvalidated to build the token URL, allowing client secrets over cleartext or to an arbitrary host

_normalize_authority_host (litellm/integrations/azure_sentinel/azure_sentinel.py:154-163) accepts any string containing :// verbatim, and that value is interpolated directly into the token URL (litellm/integrations/azure_sentinel/azure_sentinel.py:195) where the Azure client id and client secret are POSTed. A value like http://login.microsoftonline.us (a plausible copy-paste) sends the client secret in cleartext, and a value with a path or an unexpected host silently redirects the credential to a different destination. No scheme or path validation is performed.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declined, same as the Cursor finding. Authority host is proxy-admin-only via env, and that admin already holds the client secret.

@yucheng-berri

Copy link
Copy Markdown
Contributor Author

@greptileai review latest head

@yucheng-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit c593445. Configure here.

@mateo-berri mateo-berri left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks!

@yucheng-berri
yucheng-berri merged commit d59a492 into litellm_internal_staging Aug 7, 2026
85 checks passed
@yucheng-berri
yucheng-berri deleted the litellm_lit5293_azure_sentinel_sovereign_cloud branch August 7, 2026 03:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants