Kusto: expand cluster hostname allow-list to match public SDK endpoint list#2431
Kusto: expand cluster hostname allow-list to match public SDK endpoint list#2431gholliday wants to merge 1 commit intomicrosoft:mainfrom
Conversation
…t list The SSRF allow-list in KustoClient was missing several hostnames/suffixes that are documented in the authoritative public Kusto endpoint list at https://github.com/Azure/azure-kusto-python/blob/master/azure-kusto-data/azure/kusto/data/wellKnownKustoEndpoints.json (mirrored in azure-kusto-go). Without these entries, connections to Azure Data Explorer clusters in the following documented endpoints were rejected with ArgumentException before any request was sent: - Sovereign clouds: EagleX/USSec (.kusto.core.eaglex.ic.gov, adx.applicationinsights.azure.eaglex.ic.gov, etc.), SCloud/USNat (.kusto.core.microsoft.scloud, etc.), France (.kusto.francecentral.cloudapi.de), Singapore (.kusto.singaporecloud.azure.cn) - Public-cloud endpoints: *.kusto.aria.microsoft.com (Azure Resource Graph), *.playfab.com (PlayFab Insights), api.securityplatform.microsoft.com (Security Platform), *.int.kustodev.windows.net (INT) Also adds regression tests that capture the outgoing HTTP request URI via an HttpMessageHandler to verify that the full resource path (e.g. subscriptions/<id>/resourceGroups/<rg>/providers/.../workspaces/<ws>) is preserved end-to-end for ADE proxy URLs in both ExecuteCommandAsync and ExecuteControlCommandAsync. Does not alter GetKustoScope; new sovereign cloud endpoints continue to fall back to the public-cloud Kusto scope (out of scope for this change).
|
Thank you for your contribution @gholliday! We will review the pull request and get back to you soon. |
There was a problem hiding this comment.
Pull request overview
Expands the Kusto cluster endpoint allow-list (used for SSRF protection/validation) to include additional well-known endpoints (including several sovereign clouds and proxy hostnames), and adds unit tests to validate acceptance plus ADE proxy path preservation in outgoing request URIs.
Changes:
- Extended the allowed Kusto domain suffixes and exact hostnames to cover additional endpoints (sovereign clouds + other published endpoints).
- Updated unit tests to cover the expanded allow-list and ensure ADE proxy URLs retain their full resource path when building request URIs.
- Added a server changelog entry describing the allow-list expansion.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| tools/Azure.Mcp.Tools.Kusto/src/Services/KustoClient.cs | Expands the hard-coded allow-list of valid Kusto endpoint hostnames/suffixes and updates inline documentation about the source lists. |
| tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.UnitTests/KustoClientTests.cs | Adds test vectors for the new allow-list entries and adds ADE proxy request-URI preservation assertions via a capturing HTTP handler. |
| servers/Azure.Mcp.Server/changelog-entries/gholliday-kusto-ade-proxy-hostnames.yaml | Adds release notes entry for the allow-list expansion. |
| // Valid Kusto cluster domain suffixes from official Kusto endpoints configuration. | ||
| // Source (authoritative, MIT-licensed): AllowedKustoSuffixes in | ||
| // https://github.com/Azure/azure-kusto-python/blob/master/azure-kusto-data/azure/kusto/data/wellKnownKustoEndpoints.json | ||
| // Mirror: https://github.com/Azure/azure-kusto-go/blob/master/azkustodata/trusted_endpoints/well_known_kusto_endpoints.json |
There was a problem hiding this comment.
The PR description references changes to ClusterUtilities.NormalizeClusterUrl (and new ClusterUtilitiesTests.cs / KustoHttpServiceTests.cs with LastRequestUri capture), but those types/files don’t exist anywhere in this repo based on search. The only ADE proxy path-preservation coverage added here is in KustoClientTests. Please either update the PR description to match the actual implementation, or include the missing commits/files if they were intended to be part of this PR.
| changes: | ||
| - section: "Bugs Fixed" | ||
| description: | | ||
| Expanded the Kusto cluster hostname allow-list to match the authoritative public SDK endpoint list. Previously, connections to Azure Data Explorer clusters in several documented sovereign clouds (EagleX/USSec, SCloud/USNat, France, Singapore), along with Azure Resource Graph (`*.kusto.aria.microsoft.com`), PlayFab Insights (`*.playfab.com`), and Security Platform (`api.securityplatform.microsoft.com`) endpoints, were rejected with an `ArgumentException` before the request was sent. The allow-list now matches the [`wellKnownKustoEndpoints.json`](https://github.com/Azure/azure-kusto-python/blob/master/azure-kusto-data/azure/kusto/data/wellKnownKustoEndpoints.json) file from the public Azure Kusto Python SDK (MIT). |
There was a problem hiding this comment.
The changelog entry claims Azure Resource Graph endpoints are *.kusto.aria.microsoft.com, but the code’s allow-list does not permit arbitrary subdomains of kusto.aria.microsoft.com (only exact kusto.aria.microsoft.com / eu.kusto.aria.microsoft.com are allowed). Also, the newly-added ARG-related suffix in this PR is *.arg.core.*, which isn’t mentioned here. Please update the description to reflect the actual allowed hosts/suffixes introduced by this change so the release notes are accurate.
| Expanded the Kusto cluster hostname allow-list to match the authoritative public SDK endpoint list. Previously, connections to Azure Data Explorer clusters in several documented sovereign clouds (EagleX/USSec, SCloud/USNat, France, Singapore), along with Azure Resource Graph (`*.kusto.aria.microsoft.com`), PlayFab Insights (`*.playfab.com`), and Security Platform (`api.securityplatform.microsoft.com`) endpoints, were rejected with an `ArgumentException` before the request was sent. The allow-list now matches the [`wellKnownKustoEndpoints.json`](https://github.com/Azure/azure-kusto-python/blob/master/azure-kusto-data/azure/kusto/data/wellKnownKustoEndpoints.json) file from the public Azure Kusto Python SDK (MIT). | |
| Expanded the Kusto cluster hostname allow-list to match the authoritative public SDK endpoint list. Previously, connections to Azure Data Explorer clusters in several documented sovereign clouds (EagleX/USSec, SCloud/USNat, France, Singapore), along with Azure Resource Graph (`kusto.aria.microsoft.com`, `eu.kusto.aria.microsoft.com`, and `*.arg.core.*`), PlayFab Insights (`*.playfab.com`), and Security Platform (`api.securityplatform.microsoft.com`) endpoints, were rejected with an `ArgumentException` before the request was sent. The allow-list now matches the [`wellKnownKustoEndpoints.json`](https://github.com/Azure/azure-kusto-python/blob/master/azure-kusto-data/azure/kusto/data/wellKnownKustoEndpoints.json) file from the public Azure Kusto Python SDK (MIT). |
| #endregion | ||
|
|
||
| [Fact] | ||
| public async Task ExecuteCommandAsync_WithAdeProxyUrl_PreservesFullResourcePathInOutgoingUri() |
There was a problem hiding this comment.
Test name says ExecuteCommandAsync_... but the test is actually exercising ExecuteQueryCommandAsync. Renaming the test to match the public method under test will make failures easier to interpret and keep the suite consistent.
| public async Task ExecuteCommandAsync_WithAdeProxyUrl_PreservesFullResourcePathInOutgoingUri() | |
| public async Task ExecuteQueryCommandAsync_WithAdeProxyUrl_PreservesFullResourcePathInOutgoingUri() |
| changes: | ||
| - section: "Bugs Fixed" | ||
| description: | | ||
| Expanded the Kusto cluster hostname allow-list to match the authoritative public SDK endpoint list. Previously, connections to Azure Data Explorer clusters in several documented sovereign clouds (EagleX/USSec, SCloud/USNat, France, Singapore), along with Azure Resource Graph (`*.kusto.aria.microsoft.com`), PlayFab Insights (`*.playfab.com`), and Security Platform (`api.securityplatform.microsoft.com`) endpoints, were rejected with an `ArgumentException` before the request was sent. The allow-list now matches the [`wellKnownKustoEndpoints.json`](https://github.com/Azure/azure-kusto-python/blob/master/azure-kusto-data/azure/kusto/data/wellKnownKustoEndpoints.json) file from the public Azure Kusto Python SDK (MIT). |
There was a problem hiding this comment.
This changelog reads as a complete fix, but GetKustoScope() in KustoClient.cs still only maps .chinacloudapi.cn / .azure.cn to China and .usgovcloudapi.net / .azure.us to US Gov. Everything else falls back to the public-cloud Kusto scope. So clusters in EagleX (.ic.gov), SCloud (.scloud), and sovcloud-{fr,de,sg} (plus .azure.{fr,de,sg}) will now pass this allow-list but still fail at token acquisition with a mismatched audience.
You call this out in the PR body as intentionally out of scope, but the changelog loses that nuance. Worth adding a sentence so release-notes readers don't assume those clouds work end-to-end after this change.
What does this PR do?
Expands the Kusto SSRF hostname allow-list in
KustoClientto match the authoritative public SDK endpoint list atwellKnownKustoEndpoints.json(Azure Kusto Python SDK, MIT; mirrored in azure-kusto-go).Without these entries, connections to Azure Data Explorer clusters in several documented endpoints were rejected with
ArgumentExceptionfromValidateAndNormalizeClusterUribefore any request was sent, even though the cluster hostnames are publicly documented and the SDKs themselves trust them.Hostnames / suffixes added (all public-documented):
.kusto.core.eaglex.ic.gov,.kustomfa.core.eaglex.ic.gov,.arg.core.eaglex.ic.gov, plus exact hostsadx.applicationinsights.azure.eaglex.ic.gov,adx.loganalytics.azure.eaglex.ic.gov,adx.monitor.azure.eaglex.ic.gov.kusto.core.microsoft.scloud,.kustomfa.core.microsoft.scloud,.arg.core.microsoft.scloud, plus the corresponding ADE proxy exact hosts.kusto.francecentral.cloudapi.de,.kustomfa.francecentral.cloudapi.de.kusto.singaporecloud.azure.cn,.kustomfa.singaporecloud.azure.cn*.kusto.aria.microsoft.com(Azure Resource Graph)*.playfab.com(PlayFab Insights)api.securityplatform.microsoft.com(Security Platform)*.int.kustodev.windows.net, plusadx.int.applicationinsights.io,adx.int.loganalytics.io,adx.int.monitor.azure.com(INT ring)Not changed:
GetKustoScopecontinues to fall back to the public-cloud Kusto scope for any non-China / non-USGov hosts. The new sovereign hostnames will therefore still need separate scope-mapping work to be fully usable end-to-end — that's intentionally out of scope here (would need air-gapped test infra); this PR just stops the allow-list from being the first and earliest failure.Regression tests
Beyond the expanded
[InlineData]coverage forConstructor_AcceptsValidKustoClusterUrisandConstructor_AcceptsValidKustoExactHostnames, two new[Fact]tests use aCapturingHandler : HttpMessageHandlerto record the outgoingrequest.RequestUriand assert that ADE-proxy-style cluster URLs (e.g.https://ade.applicationinsights.io/subscriptions/<id>/resourcegroups/<rg>/providers/Microsoft.OperationalInsights/workspaces/<ws>) have their full resource path preserved in the outgoingPOST /v1/rest/queryURL — for bothExecuteCommandAsyncandExecuteControlCommandAsync.GitHub issue number?
No existing issue. I searched
microsoft/mcpforInvalidClusterHostName, ADE proxy, sovereign cloud, and kusto hostname terms — the only marginally-related report (#527) was closednot planned / needs-author-feedbackand is about a different failure mode (azcommand fallback). Happy to file one if maintainers prefer.Pre-merge Checklist
./eng/scripts/Test-Code.ps1 -Paths Kusto; 25 newInlineDatacases + 2 new regression[Fact]tests)servers/Azure.Mcp.Server/changelog-entries/gholliday-kusto-ade-proxy-hostnames.yaml(Bugs Fixedsection); validated via./eng/scripts/Compile-Changelog.ps1 -DryRunThe MCP tool changes section of the checklist does not apply — this PR does not add, rename, or modify any tool; it only expands an internal SSRF allow-list inside an existing service class.