File: Extensions\EndpointRoutingValidation.cs
Web Access
Project: src\src\Aspire.Hosting.Kubernetes\Aspire.Hosting.Kubernetes.csproj (Aspire.Hosting.Kubernetes)
// 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;
 
namespace Aspire.Hosting.Kubernetes.Extensions;
 
/// <summary>
/// Helpers that validate publish-time intent for endpoints that are being
/// routed by ingress / gateway-style resources.
/// </summary>
/// <remarks>
/// Ingress and Gateway resources expose their backing service to traffic that
/// originates outside the cluster, so it is a privacy/security footgun to
/// route an <see cref="EndpointReference"/> that the resource owner never
/// flagged as external. The check is performed during publish (when the
/// Helm chart is materialized) rather than at <c>WithPath</c> / <c>WithRoute</c> call time
/// because authoring order is not significant: a user may legitimately
/// register the path/route before calling
/// <see cref="ResourceBuilderExtensions.WithExternalHttpEndpoints{T}"/> or
/// setting <see cref="EndpointAnnotation.IsExternal"/> directly.
/// </remarks>
internal static class EndpointRoutingValidation
{
    /// <summary>
    /// Throws an <see cref="InvalidOperationException"/> when the supplied
    /// endpoint is not marked external on its <see cref="EndpointAnnotation"/>.
    /// </summary>
    /// <param name="endpoint">The routed endpoint reference.</param>
    /// <param name="routingResourceKind">The kind of routing resource (e.g., <c>"Ingress"</c> or <c>"Gateway"</c>).</param>
    /// <param name="routingResourceName">The name of the routing resource that owns the route.</param>
    public static void ThrowIfEndpointNotExternal(
        EndpointReference endpoint,
        string routingResourceKind,
        string routingResourceName)
    {
        ArgumentNullException.ThrowIfNull(endpoint);
        ArgumentException.ThrowIfNullOrEmpty(routingResourceKind);
        ArgumentException.ThrowIfNullOrEmpty(routingResourceName);
 
        // EndpointAnnotation captures publish-time intent — the endpoint
        // is external if and only if the author explicitly opted in, either
        // through .WithExternalHttpEndpoints() or by passing isExternal: true
        // when creating the endpoint annotation.
        if (endpoint.EndpointAnnotation.IsExternal)
        {
            return;
        }
 
        throw new InvalidOperationException(
            $"Resource '{endpoint.Resource.Name}' endpoint '{endpoint.EndpointName}' is not marked as external " +
            $"but is being routed by {routingResourceKind} '{routingResourceName}'. " +
            $"Call .WithExternalHttpEndpoints() on the target resource or pass isExternal: true when " +
            $"creating the endpoint annotation. {routingResourceKind} routes may only expose endpoints " +
            $"that are explicitly marked external.");
    }
}