File: AzureNetworkSecurityGroupExtensions.cs
Web Access
Project: src\src\Aspire.Hosting.Azure.Network\Aspire.Hosting.Azure.Network.csproj (Aspire.Hosting.Azure.Network)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure;
using Azure.Provisioning;
using Azure.Provisioning.Network;
 
namespace Aspire.Hosting;
 
/// <summary>
/// Provides extension methods for adding Azure Network Security Group resources to the application model.
/// </summary>
public static class AzureNetworkSecurityGroupExtensions
{
    /// <summary>
    /// Adds an Azure Network Security Group to the application model.
    /// </summary>
    /// <param name="builder">The builder for the distributed application.</param>
    /// <param name="name">The name of the Network Security Group resource.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{AzureNetworkSecurityGroupResource}"/>.</returns>
    /// <example>
    /// This example adds a Network Security Group with a security rule:
    /// <code>
    /// var nsg = builder.AddNetworkSecurityGroup("web-nsg")
    ///     .WithSecurityRule(new AzureSecurityRule
    ///     {
    ///         Name = "allow-https",
    ///         Priority = 100,
    ///         Direction = SecurityRuleDirection.Inbound,
    ///         Access = SecurityRuleAccess.Allow,
    ///         Protocol = SecurityRuleProtocol.Tcp,
    ///         DestinationPortRange = "443"
    ///     });
    /// </code>
    /// </example>
    public static IResourceBuilder<AzureNetworkSecurityGroupResource> AddNetworkSecurityGroup(
        this IDistributedApplicationBuilder builder,
        [ResourceName] string name)
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentException.ThrowIfNullOrEmpty(name);
 
        builder.AddAzureProvisioning();
 
        var resource = new AzureNetworkSecurityGroupResource(name, ConfigureNetworkSecurityGroup);
 
        if (builder.ExecutionContext.IsRunMode)
        {
            return builder.CreateResourceBuilder(resource);
        }
 
        return builder.AddResource(resource);
    }
 
    /// <summary>
    /// Adds a security rule to the Network Security Group.
    /// </summary>
    /// <param name="builder">The Network Security Group resource builder.</param>
    /// <param name="rule">The security rule configuration.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{AzureNetworkSecurityGroupResource}"/> for chaining.</returns>
    /// <example>
    /// This example adds multiple security rules to a Network Security Group:
    /// <code>
    /// var nsg = builder.AddNetworkSecurityGroup("web-nsg")
    ///     .WithSecurityRule(new AzureSecurityRule
    ///     {
    ///         Name = "allow-https",
    ///         Priority = 100,
    ///         Direction = SecurityRuleDirection.Inbound,
    ///         Access = SecurityRuleAccess.Allow,
    ///         Protocol = SecurityRuleProtocol.Tcp,
    ///         DestinationPortRange = "443"
    ///     })
    ///     .WithSecurityRule(new AzureSecurityRule
    ///     {
    ///         Name = "deny-all-inbound",
    ///         Priority = 4096,
    ///         Direction = SecurityRuleDirection.Inbound,
    ///         Access = SecurityRuleAccess.Deny,
    ///         Protocol = SecurityRuleProtocol.Asterisk,
    ///         DestinationPortRange = "*"
    ///     });
    /// </code>
    /// </example>
    public static IResourceBuilder<AzureNetworkSecurityGroupResource> WithSecurityRule(
        this IResourceBuilder<AzureNetworkSecurityGroupResource> builder,
        AzureSecurityRule rule)
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(rule);
        ArgumentException.ThrowIfNullOrEmpty(rule.Name);
 
        if (builder.Resource.SecurityRules.Any(existing => string.Equals(existing.Name, rule.Name, StringComparison.OrdinalIgnoreCase)))
        {
            throw new ArgumentException(
                $"A security rule named '{rule.Name}' already exists in Network Security Group '{builder.Resource.Name}'.",
                nameof(rule));
        }
 
        builder.Resource.SecurityRules.Add(rule);
        return builder;
    }
 
    private static void ConfigureNetworkSecurityGroup(AzureResourceInfrastructure infra)
    {
        var azureResource = (AzureNetworkSecurityGroupResource)infra.AspireResource;
 
        var nsg = AzureProvisioningResource.CreateExistingOrNewProvisionableResource(infra,
            (identifier, name) =>
            {
                var resource = NetworkSecurityGroup.FromExisting(identifier);
                resource.Name = name;
                return resource;
            },
            (infrastructure) =>
            {
                return new NetworkSecurityGroup(infrastructure.AspireResource.GetBicepIdentifier())
                {
                    Tags = { { "aspire-resource-name", infrastructure.AspireResource.Name } }
                };
            });
 
        foreach (var rule in azureResource.SecurityRules)
        {
            var ruleIdentifier = Infrastructure.NormalizeBicepIdentifier($"{nsg.BicepIdentifier}_{rule.Name}");
            var securityRule = new SecurityRule(ruleIdentifier)
            {
                Name = rule.Name,
                Priority = rule.Priority,
                Direction = rule.Direction,
                Access = rule.Access,
                Protocol = rule.Protocol,
                SourceAddressPrefix = rule.SourceAddressPrefix,
                SourcePortRange = rule.SourcePortRange,
                DestinationAddressPrefix = rule.DestinationAddressPrefix,
                DestinationPortRange = rule.DestinationPortRange,
                Parent = nsg,
            };
            infra.Add(securityRule);
        }
 
        infra.Add(new ProvisioningOutput("id", typeof(string))
        {
            Value = nsg.Id
        });
 
        infra.Add(new ProvisioningOutput("name", typeof(string))
        {
            Value = nsg.Name
        });
    }
}