@@ -18,6 +18,8 @@ namespace ServiceControl.Transports.ASBS;
1818using Azure . ResourceManager . Resources ;
1919using Azure . ResourceManager . ServiceBus ;
2020using BrokerThroughput ;
21+ using DnsClient ;
22+ using DnsClient . Protocol ;
2123using Microsoft . Extensions . Logging ;
2224
2325public class AzureQuery ( ILogger < AzureQuery > logger , TimeProvider timeProvider , TransportSettings transportSettings )
@@ -27,6 +29,7 @@ public class AzureQuery(ILogger<AzureQuery> logger, TimeProvider timeProvider, T
2729 MetricsQueryClient ? client ;
2830 ArmClient ? armClient ;
2931 string ? resourceId ;
32+ ArmEnvironment armEnvironment ;
3033
3134 protected override void InitializeCore ( ReadOnlyDictionary < string , string > settings )
3235 {
@@ -99,11 +102,11 @@ protected override void InitializeCore(ReadOnlyDictionary<string, string> settin
99102 Diagnostics . AppendLine ( "Client secret set" ) ;
100103 }
101104
102- ( ArmEnvironment armEnvironment , MetricsQueryAudience metricsQueryAudience ) environment = GetEnvironment ( ) ;
105+ ( armEnvironment , var metricsQueryAudience ) = GetEnvironment ( ) ;
103106
104107 if ( managementUrl == null )
105108 {
106- Diagnostics . AppendLine ( $ "Management Url not set, defaulted to \" { environment . armEnvironment . Endpoint } \" ") ;
109+ Diagnostics . AppendLine ( $ "Management Url not set, defaulted to \" { armEnvironment . Endpoint } \" ") ;
107110 }
108111 else
109112 {
@@ -126,10 +129,10 @@ protected override void InitializeCore(ReadOnlyDictionary<string, string> settin
126129 clientCredentials = new ClientSecretCredential ( tenantId , clientId , clientSecret ) ;
127130 }
128131
129- client = new MetricsQueryClient ( environment . armEnvironment . Endpoint , clientCredentials ,
132+ client = new MetricsQueryClient ( armEnvironment . Endpoint , clientCredentials ,
130133 new MetricsQueryClientOptions
131134 {
132- Audience = environment . metricsQueryAudience ,
135+ Audience = metricsQueryAudience ,
133136 Transport = new HttpClientTransport (
134137 new HttpClient ( new SocketsHttpHandler
135138 {
@@ -139,7 +142,7 @@ protected override void InitializeCore(ReadOnlyDictionary<string, string> settin
139142 armClient = new ArmClient ( clientCredentials , subscriptionId ,
140143 new ArmClientOptions
141144 {
142- Environment = environment . armEnvironment ,
145+ Environment = armEnvironment ,
143146 Transport = new HttpClientTransport (
144147 new HttpClient ( new SocketsHttpHandler
145148 {
@@ -263,14 +266,16 @@ async Task<IReadOnlyList<MetricValue>> GetMetrics(string queueName, DateOnly sta
263266 public override async IAsyncEnumerable < IBrokerQueue > GetQueueNames (
264267 [ EnumeratorCancellation ] CancellationToken cancellationToken = default )
265268 {
269+ var validNamespaces = await GetValidNamespaceNames ( cancellationToken ) ;
270+
266271 SubscriptionResource ? subscription = await armClient ! . GetDefaultSubscriptionAsync ( cancellationToken ) ;
267272 var namespaces =
268273 subscription . GetServiceBusNamespacesAsync ( cancellationToken ) ;
269274
270275 await foreach ( var serviceBusNamespaceResource in namespaces . WithCancellation (
271276 cancellationToken ) )
272277 {
273- if ( serviceBusNamespaceResource . Data . Name == serviceBusName )
278+ if ( validNamespaces . Contains ( serviceBusNamespaceResource . Data . Name ) )
274279 {
275280 resourceId = serviceBusNamespaceResource . Id ;
276281 await foreach ( var queue in serviceBusNamespaceResource . GetServiceBusQueues ( )
@@ -286,6 +291,43 @@ public override async IAsyncEnumerable<IBrokerQueue> GetQueueNames(
286291 throw new Exception ( $ "Could not find a ServiceBus named \" { serviceBusName } \" ") ;
287292 }
288293
294+ // ArmEnvironment Audience Values: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/resourcemanager/Azure.ResourceManager/src/ArmEnvironment.cs
295+ // Service Bus Domains: https://learn.microsoft.com/en-us/rest/api/servicebus/
296+ static readonly Dictionary < ArmEnvironment , string > ServiceBusDomains = new ( )
297+ {
298+ { ArmEnvironment . AzurePublicCloud , "servicebus.windows.net" } ,
299+ { ArmEnvironment . AzureGovernment , "servicebus.usgovcloudapi.net" } ,
300+ { ArmEnvironment . AzureGermany , "servicebus.cloudapi.de" } ,
301+ { ArmEnvironment . AzureChina , "servicebus.chinacloudapi.cn" } ,
302+ } ;
303+
304+ async Task < HashSet < string > > GetValidNamespaceNames ( CancellationToken cancellationToken = default )
305+ {
306+ var validNamespaces = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) { serviceBusName } ;
307+
308+ if ( ! ServiceBusDomains . TryGetValue ( armEnvironment , out var serviceBusCloudDomain ) )
309+ {
310+ // Worst case: the DNS lookup finds nothing additional to match
311+ serviceBusCloudDomain = "servicebus.windows.net" ;
312+ }
313+
314+ var queryDomain = $ "{ serviceBusName } .{ serviceBusCloudDomain } ";
315+ var validDomainTail = $ ".{ serviceBusCloudDomain } .";
316+
317+ var dnsLookup = new LookupClient ( ) ;
318+ var dnsResult = await dnsLookup . QueryAsync ( queryDomain , QueryType . CNAME , cancellationToken : cancellationToken ) ;
319+ var domain = ( dnsResult . Answers . FirstOrDefault ( ) as CNameRecord ) ? . CanonicalName . Value ;
320+ if ( domain is not null && domain . EndsWith ( validDomainTail ) )
321+ {
322+ // In some cases, like private networking access, result might be something like `namespacename.private` with a dot in the middle
323+ // which is not a big deal because that will not actually match a namespace name in metrics
324+ var otherName = domain [ ..^ validDomainTail . Length ] ;
325+ validNamespaces . Add ( otherName ) ;
326+ }
327+
328+ return validNamespaces ;
329+ }
330+
289331 public override string SanitizedEndpointNameCleanser ( string endpointName ) => endpointName . ToLower ( ) ;
290332
291333 public override KeyDescriptionPair [ ] Settings =>
0 commit comments