diff --git a/playground/AzureVirtualNetworkEndToEnd/AzureVirtualNetworkEndToEnd.AppHost/Program.cs b/playground/AzureVirtualNetworkEndToEnd/AzureVirtualNetworkEndToEnd.AppHost/Program.cs
index f7dcb60b2b4..81ad596b9e8 100644
--- a/playground/AzureVirtualNetworkEndToEnd/AzureVirtualNetworkEndToEnd.AppHost/Program.cs
+++ b/playground/AzureVirtualNetworkEndToEnd/AzureVirtualNetworkEndToEnd.AppHost/Program.cs
@@ -3,6 +3,7 @@
#pragma warning disable AZPROVISION001 // Azure.Provisioning.Network is experimental
+using Aspire.Hosting.Azure;
using Azure.Provisioning.Network;
var builder = DistributedApplication.CreateBuilder(args);
@@ -13,17 +14,17 @@
var vnet = builder.AddAzureVirtualNetwork("vnet");
var containerAppsSubnet = vnet.AddSubnet("container-apps", "10.0.0.0/23")
- .AllowInbound(port: "443", from: "AzureLoadBalancer", protocol: SecurityRuleProtocol.Tcp)
- .DenyInbound(from: "VirtualNetwork")
- .DenyInbound(from: "Internet");
+ .AllowInbound(port: "443", from: AzureServiceTags.AzureLoadBalancer, protocol: SecurityRuleProtocol.Tcp)
+ .DenyInbound(from: AzureServiceTags.VirtualNetwork)
+ .DenyInbound(from: AzureServiceTags.Internet);
// Create a NAT Gateway for deterministic outbound IP on the ACA subnet
var natGateway = builder.AddNatGateway("nat");
containerAppsSubnet.WithNatGateway(natGateway);
var privateEndpointsSubnet = vnet.AddSubnet("private-endpoints", "10.0.2.0/27")
- .AllowInbound(port: "443", from: "VirtualNetwork", protocol: SecurityRuleProtocol.Tcp)
- .DenyInbound(from: "Internet");
+ .AllowInbound(port: "443", from: AzureServiceTags.VirtualNetwork, protocol: SecurityRuleProtocol.Tcp)
+ .DenyInbound(from: AzureServiceTags.Internet);
// Configure the Container App Environment to use the VNet
builder.AddAzureContainerAppEnvironment("env")
diff --git a/src/Aspire.Hosting.Azure.Network/AzureServiceTags.cs b/src/Aspire.Hosting.Azure.Network/AzureServiceTags.cs
new file mode 100644
index 00000000000..d09d8b61ec6
--- /dev/null
+++ b/src/Aspire.Hosting.Azure.Network/AzureServiceTags.cs
@@ -0,0 +1,111 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Aspire.Hosting.Azure;
+
+///
+/// Provides well-known Azure service tags that can be used as source or destination address prefixes
+/// in network security group rules.
+///
+///
+///
+/// Service tags represent a group of IP address prefixes from a given Azure service. Microsoft manages the
+/// address prefixes encompassed by each tag and automatically updates them as addresses change.
+///
+///
+/// These tags can be used with the from and to parameters of methods such as
+/// , ,
+/// , ,
+/// or with the and properties.
+///
+///
+///
+/// Use service tags when configuring network security rules:
+///
+/// var subnet = vnet.AddSubnet("web", "10.0.1.0/24")
+/// .AllowInbound(port: "443", from: AzureServiceTags.AzureLoadBalancer, protocol: SecurityRuleProtocol.Tcp)
+/// .DenyInbound(from: AzureServiceTags.Internet);
+///
+///
+public static class AzureServiceTags
+{
+ ///
+ /// Represents the Internet address space, including all publicly routable IP addresses.
+ ///
+ public const string Internet = nameof(Internet);
+
+ ///
+ /// Represents the address space for the virtual network, including all connected address spaces,
+ /// all connected on-premises address spaces, and peered virtual networks.
+ ///
+ public const string VirtualNetwork = nameof(VirtualNetwork);
+
+ ///
+ /// Represents the Azure infrastructure load balancer. This tag is commonly used to allow
+ /// health probe traffic from Azure.
+ ///
+ public const string AzureLoadBalancer = nameof(AzureLoadBalancer);
+
+ ///
+ /// Represents Azure Traffic Manager probe IP addresses.
+ ///
+ public const string AzureTrafficManager = nameof(AzureTrafficManager);
+
+ ///
+ /// Represents the Azure Storage service. This tag does not include specific Storage accounts;
+ /// it covers all Azure Storage IP addresses.
+ ///
+ public const string Storage = nameof(Storage);
+
+ ///
+ /// Represents Azure SQL Database, Azure Database for MySQL, Azure Database for PostgreSQL,
+ /// Azure Database for MariaDB, and Azure Synapse Analytics.
+ ///
+ public const string Sql = nameof(Sql);
+
+ ///
+ /// Represents Azure Cosmos DB service addresses.
+ ///
+ public const string AzureCosmosDB = nameof(AzureCosmosDB);
+
+ ///
+ /// Represents Azure Key Vault service addresses.
+ ///
+ public const string AzureKeyVault = nameof(AzureKeyVault);
+
+ ///
+ /// Represents Azure Event Hubs service addresses.
+ ///
+ public const string EventHub = nameof(EventHub);
+
+ ///
+ /// Represents Azure Service Bus service addresses.
+ ///
+ public const string ServiceBus = nameof(ServiceBus);
+
+ ///
+ /// Represents Azure Container Registry service addresses.
+ ///
+ public const string AzureContainerRegistry = nameof(AzureContainerRegistry);
+
+ ///
+ /// Represents Azure App Service and Azure Functions service addresses.
+ ///
+ public const string AppService = nameof(AppService);
+
+ ///
+ /// Represents Microsoft Entra ID (formerly Azure Active Directory) service addresses.
+ ///
+ public const string AzureActiveDirectory = nameof(AzureActiveDirectory);
+
+ ///
+ /// Represents Azure Monitor service addresses, including Log Analytics, Application Insights,
+ /// and Azure Monitor metrics.
+ ///
+ public const string AzureMonitor = nameof(AzureMonitor);
+
+ ///
+ /// Represents the Gateway Manager service, used for VPN Gateway and Application Gateway management traffic.
+ ///
+ public const string GatewayManager = nameof(GatewayManager);
+}
diff --git a/src/Aspire.Hosting.Azure.Network/AzureVirtualNetworkExtensions.cs b/src/Aspire.Hosting.Azure.Network/AzureVirtualNetworkExtensions.cs
index 4e15aa7ad3f..3d5d18475c0 100644
--- a/src/Aspire.Hosting.Azure.Network/AzureVirtualNetworkExtensions.cs
+++ b/src/Aspire.Hosting.Azure.Network/AzureVirtualNetworkExtensions.cs
@@ -361,8 +361,8 @@ public static IResourceBuilder WithNetworkSecurityGroup(
/// This example allows HTTPS traffic from the Azure Load Balancer:
///
/// var subnet = vnet.AddSubnet("web", "10.0.1.0/24")
- /// .AllowInbound(port: "443", from: "AzureLoadBalancer", protocol: SecurityRuleProtocol.Tcp)
- /// .DenyInbound(from: "Internet");
+ /// .AllowInbound(port: "443", from: AzureServiceTags.AzureLoadBalancer, protocol: SecurityRuleProtocol.Tcp)
+ /// .DenyInbound(from: AzureServiceTags.Internet);
///
///
public static IResourceBuilder AllowInbound(
diff --git a/src/Aspire.Hosting.Azure.Network/README.md b/src/Aspire.Hosting.Azure.Network/README.md
index 90975e8bbc6..3590fb1e102 100644
--- a/src/Aspire.Hosting.Azure.Network/README.md
+++ b/src/Aspire.Hosting.Azure.Network/README.md
@@ -90,8 +90,8 @@ Add security rules to control traffic flow on subnets using shorthand methods:
```csharp
var vnet = builder.AddAzureVirtualNetwork("vnet");
var subnet = vnet.AddSubnet("web", "10.0.1.0/24")
- .AllowInbound(port: "443", from: "AzureLoadBalancer", protocol: SecurityRuleProtocol.Tcp)
- .DenyInbound(from: "Internet");
+ .AllowInbound(port: "443", from: AzureServiceTags.AzureLoadBalancer, protocol: SecurityRuleProtocol.Tcp)
+ .DenyInbound(from: AzureServiceTags.Internet);
```
An NSG is automatically created when shorthand methods are used. Priority auto-increments (100, 200, 300...) and rule names are auto-generated.
diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureNetworkSecurityGroupExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureNetworkSecurityGroupExtensionsTests.cs
index 964a030b6a3..79ba00243ac 100644
--- a/tests/Aspire.Hosting.Azure.Tests/AzureNetworkSecurityGroupExtensionsTests.cs
+++ b/tests/Aspire.Hosting.Azure.Tests/AzureNetworkSecurityGroupExtensionsTests.cs
@@ -247,7 +247,7 @@ public async Task MultipleNSGs_WithSameRuleName_GeneratesDistinctBicepIdentifier
Direction = SecurityRuleDirection.Inbound,
Access = SecurityRuleAccess.Allow,
Protocol = SecurityRuleProtocol.Tcp,
- SourceAddressPrefix = "VirtualNetwork",
+ SourceAddressPrefix = AzureServiceTags.VirtualNetwork,
SourcePortRange = "*",
DestinationAddressPrefix = "*",
DestinationPortRange = "443"
@@ -271,7 +271,7 @@ public void WithNetworkSecurityGroup_AfterShorthand_Throws()
var vnet = builder.AddAzureVirtualNetwork("myvnet");
var nsg = builder.AddNetworkSecurityGroup("web-nsg");
var subnet = vnet.AddSubnet("web-subnet", "10.0.1.0/24")
- .AllowInbound(port: "443", from: "AzureLoadBalancer");
+ .AllowInbound(port: "443", from: AzureServiceTags.AzureLoadBalancer);
var exception = Assert.Throws(() => subnet.WithNetworkSecurityGroup(nsg));
diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureVirtualNetworkExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureVirtualNetworkExtensionsTests.cs
index 08fafbde1a3..1747a3b1f96 100644
--- a/tests/Aspire.Hosting.Azure.Tests/AzureVirtualNetworkExtensionsTests.cs
+++ b/tests/Aspire.Hosting.Azure.Tests/AzureVirtualNetworkExtensionsTests.cs
@@ -361,6 +361,66 @@ await Verify(vnetManifest.BicepText, extension: "bicep")
.AppendContentAsFile(nsgManifest.BicepText, "bicep", "nsg");
}
+ [Fact]
+ public void ServiceTags_CanBeUsedAsFromAndToParameters()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var vnet = builder.AddAzureVirtualNetwork("myvnet");
+ var subnet = vnet.AddSubnet("web", "10.0.1.0/24")
+ .AllowInbound(port: "443", from: AzureServiceTags.AzureLoadBalancer, protocol: SecurityRuleProtocol.Tcp)
+ .DenyInbound(from: AzureServiceTags.Internet)
+ .AllowOutbound(port: "443", to: AzureServiceTags.Storage)
+ .DenyOutbound(to: AzureServiceTags.VirtualNetwork);
+
+ var rules = subnet.Resource.NetworkSecurityGroup!.SecurityRules;
+ Assert.Equal(4, rules.Count);
+
+ Assert.Equal("AzureLoadBalancer", rules[0].SourceAddressPrefix);
+ Assert.Equal("Internet", rules[1].SourceAddressPrefix);
+ Assert.Equal("Storage", rules[2].DestinationAddressPrefix);
+ Assert.Equal("VirtualNetwork", rules[3].DestinationAddressPrefix);
+ }
+
+ [Fact]
+ public void ServiceTags_CanBeUsedInSecurityRuleProperties()
+ {
+ var rule = new AzureSecurityRule
+ {
+ Name = "allow-https-from-lb",
+ Priority = 100,
+ Direction = SecurityRuleDirection.Inbound,
+ Access = SecurityRuleAccess.Allow,
+ Protocol = SecurityRuleProtocol.Tcp,
+ SourceAddressPrefix = AzureServiceTags.AzureLoadBalancer,
+ DestinationAddressPrefix = AzureServiceTags.VirtualNetwork,
+ DestinationPortRange = "443"
+ };
+
+ Assert.Equal("AzureLoadBalancer", rule.SourceAddressPrefix);
+ Assert.Equal("VirtualNetwork", rule.DestinationAddressPrefix);
+ }
+
+ [Fact]
+ public void ServiceTags_HaveExpectedValues()
+ {
+ Assert.Equal("Internet", AzureServiceTags.Internet);
+ Assert.Equal("VirtualNetwork", AzureServiceTags.VirtualNetwork);
+ Assert.Equal("AzureLoadBalancer", AzureServiceTags.AzureLoadBalancer);
+ Assert.Equal("AzureTrafficManager", AzureServiceTags.AzureTrafficManager);
+ Assert.Equal("Storage", AzureServiceTags.Storage);
+ Assert.Equal("Sql", AzureServiceTags.Sql);
+ Assert.Equal("AzureCosmosDB", AzureServiceTags.AzureCosmosDB);
+ Assert.Equal("AzureKeyVault", AzureServiceTags.AzureKeyVault);
+ Assert.Equal("EventHub", AzureServiceTags.EventHub);
+ Assert.Equal("ServiceBus", AzureServiceTags.ServiceBus);
+ Assert.Equal("AzureContainerRegistry", AzureServiceTags.AzureContainerRegistry);
+ Assert.Equal("AppService", AzureServiceTags.AppService);
+ Assert.Equal("AzureActiveDirectory", AzureServiceTags.AzureActiveDirectory);
+ Assert.Equal("AzureMonitor", AzureServiceTags.AzureMonitor);
+ Assert.Equal("GatewayManager", AzureServiceTags.GatewayManager);
+ }
+
[Fact]
public void AllFourDirectionAccessCombos_SetCorrectly()
{