From d978e3387ab3c80ce4523bdb54555058d8da26d6 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Tue, 3 Oct 2023 22:01:18 +0200 Subject: [PATCH] Use frozen collections --- docs/docfx/articles/header-guidelines.md | 2 +- eng/Versions.props | 1 + src/ReverseProxy/Configuration/ConfigValidator.cs | 11 ++++++----- src/ReverseProxy/Forwarder/RequestUtilities.cs | 9 +++++---- src/ReverseProxy/Health/ActiveHealthCheckMonitor.cs | 3 ++- src/ReverseProxy/Health/ClusterDestinationsUpdater.cs | 3 ++- .../Health/PassiveHealthCheckMiddleware.cs | 3 ++- .../LoadBalancing/LoadBalancingMiddleware.cs | 3 ++- .../SessionAffinity/AffinitizeTransformProvider.cs | 3 ++- .../SessionAffinity/SessionAffinityMiddleware.cs | 5 +++-- .../Transforms/RequestHeadersAllowedTransform.cs | 5 +++-- .../Transforms/ResponseHeadersAllowedTransform.cs | 5 +++-- .../Transforms/ResponseTrailersAllowedTransform.cs | 5 +++-- src/ReverseProxy/Utilities/ServiceLookupHelper.cs | 7 ++++--- src/ReverseProxy/Yarp.ReverseProxy.csproj | 1 + 15 files changed, 40 insertions(+), 26 deletions(-) diff --git a/docs/docfx/articles/header-guidelines.md b/docs/docfx/articles/header-guidelines.md index 36c5edead..c47376cdf 100644 --- a/docs/docfx/articles/header-guidelines.md +++ b/docs/docfx/articles/header-guidelines.md @@ -9,7 +9,7 @@ Headers are a very important part of processing HTTP requests and each have thei ## YARP header filtering -YARP automatically removes request and response headers that could impact its ability to forward a request correctly, or that may be used maliciously to bypass features of the proxy. A complete list can be found [here](https://github.com/microsoft/reverse-proxy/blob/main/src/ReverseProxy/Forwarder/RequestUtilities.cs#L70), with some highlights described below. +YARP automatically removes request and response headers that could impact its ability to forward a request correctly, or that may be used maliciously to bypass features of the proxy. A complete list can be found [here](https://github.com/microsoft/reverse-proxy/blob/main/src/ReverseProxy/Forwarder/RequestUtilities.cs#L71), with some highlights described below. ### Connection, KeepAlive, Close diff --git a/eng/Versions.props b/eng/Versions.props index e90859d3d..d6d29b142 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -12,6 +12,7 @@ + 8.0.0-rc.1.23419.4 8.0.0-rc.1.23419.4 8.0.0-beta.23463.1 diff --git a/src/ReverseProxy/Configuration/ConfigValidator.cs b/src/ReverseProxy/Configuration/ConfigValidator.cs index 486cf8937..51d9c4da0 100644 --- a/src/ReverseProxy/Configuration/ConfigValidator.cs +++ b/src/ReverseProxy/Configuration/ConfigValidator.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Linq; using System.Net; @@ -31,11 +32,11 @@ internal sealed class ConfigValidator : IConfigValidator private readonly IAuthorizationPolicyProvider _authorizationPolicyProvider; private readonly IYarpRateLimiterPolicyProvider _rateLimiterPolicyProvider; private readonly ICorsPolicyProvider _corsPolicyProvider; - private readonly IDictionary _loadBalancingPolicies; - private readonly IDictionary _affinityFailurePolicies; - private readonly IDictionary _availableDestinationsPolicies; - private readonly IDictionary _activeHealthCheckPolicies; - private readonly IDictionary _passiveHealthCheckPolicies; + private readonly FrozenDictionary _loadBalancingPolicies; + private readonly FrozenDictionary _affinityFailurePolicies; + private readonly FrozenDictionary _availableDestinationsPolicies; + private readonly FrozenDictionary _activeHealthCheckPolicies; + private readonly FrozenDictionary _passiveHealthCheckPolicies; private readonly ILogger _logger; diff --git a/src/ReverseProxy/Forwarder/RequestUtilities.cs b/src/ReverseProxy/Forwarder/RequestUtilities.cs index da70a1688..e2914d4de 100644 --- a/src/ReverseProxy/Forwarder/RequestUtilities.cs +++ b/src/ReverseProxy/Forwarder/RequestUtilities.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Collections.Frozen; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -67,7 +68,7 @@ internal static bool ShouldSkipResponseHeader(string headerName) return _headersToExclude.Contains(headerName); } - private static readonly HashSet _headersToExclude = new(18, StringComparer.OrdinalIgnoreCase) + private static readonly FrozenSet _headersToExclude = new HashSet(18, StringComparer.OrdinalIgnoreCase) { HeaderNames.Connection, HeaderNames.TransferEncoding, @@ -87,11 +88,11 @@ internal static bool ShouldSkipResponseHeader(string headerName) HeaderNames.TE, HeaderNames.AltSvc, HeaderNames.StrictTransportSecurity, - }; + }.ToFrozenSet(StringComparer.OrdinalIgnoreCase); // Headers marked as HttpHeaderType.Content in // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/System/Net/Http/Headers/KnownHeaders.cs - private static readonly HashSet _contentHeaders = new(11, StringComparer.OrdinalIgnoreCase) + private static readonly FrozenSet _contentHeaders = new HashSet(11, StringComparer.OrdinalIgnoreCase) { HeaderNames.Allow, HeaderNames.ContentDisposition, @@ -104,7 +105,7 @@ internal static bool ShouldSkipResponseHeader(string headerName) HeaderNames.ContentType, HeaderNames.Expires, HeaderNames.LastModified - }; + }.ToFrozenSet(StringComparer.OrdinalIgnoreCase); /// /// Appends the given path and query to the destination prefix while avoiding duplicate '/'. diff --git a/src/ReverseProxy/Health/ActiveHealthCheckMonitor.cs b/src/ReverseProxy/Health/ActiveHealthCheckMonitor.cs index 383ff619d..cd4f2e23c 100644 --- a/src/ReverseProxy/Health/ActiveHealthCheckMonitor.cs +++ b/src/ReverseProxy/Health/ActiveHealthCheckMonitor.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Diagnostics; using System.Net.Http; @@ -17,7 +18,7 @@ namespace Yarp.ReverseProxy.Health; internal partial class ActiveHealthCheckMonitor : IActiveHealthCheckMonitor, IClusterChangeListener, IDisposable { private readonly ActiveHealthCheckMonitorOptions _monitorOptions; - private readonly IDictionary _policies; + private readonly FrozenDictionary _policies; private readonly IProbingRequestFactory _probingRequestFactory; private readonly ILogger _logger; diff --git a/src/ReverseProxy/Health/ClusterDestinationsUpdater.cs b/src/ReverseProxy/Health/ClusterDestinationsUpdater.cs index 7dd698a51..1b00c8923 100644 --- a/src/ReverseProxy/Health/ClusterDestinationsUpdater.cs +++ b/src/ReverseProxy/Health/ClusterDestinationsUpdater.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; @@ -13,7 +14,7 @@ namespace Yarp.ReverseProxy.Health; internal sealed class ClusterDestinationsUpdater : IClusterDestinationsUpdater { private readonly ConditionalWeakTable _clusterLocks = new ConditionalWeakTable(); - private readonly IDictionary _destinationPolicies; + private readonly FrozenDictionary _destinationPolicies; public ClusterDestinationsUpdater(IEnumerable destinationPolicies) { diff --git a/src/ReverseProxy/Health/PassiveHealthCheckMiddleware.cs b/src/ReverseProxy/Health/PassiveHealthCheckMiddleware.cs index c8ff54a80..801c4c2dc 100644 --- a/src/ReverseProxy/Health/PassiveHealthCheckMiddleware.cs +++ b/src/ReverseProxy/Health/PassiveHealthCheckMiddleware.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -12,7 +13,7 @@ namespace Yarp.ReverseProxy.Health; public class PassiveHealthCheckMiddleware { private readonly RequestDelegate _next; - private readonly IDictionary _policies; + private readonly FrozenDictionary _policies; public PassiveHealthCheckMiddleware(RequestDelegate next, IEnumerable policies) { diff --git a/src/ReverseProxy/LoadBalancing/LoadBalancingMiddleware.cs b/src/ReverseProxy/LoadBalancing/LoadBalancingMiddleware.cs index ec61b2c00..64830a2e2 100644 --- a/src/ReverseProxy/LoadBalancing/LoadBalancingMiddleware.cs +++ b/src/ReverseProxy/LoadBalancing/LoadBalancingMiddleware.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -17,7 +18,7 @@ namespace Yarp.ReverseProxy.LoadBalancing; internal sealed class LoadBalancingMiddleware { private readonly ILogger _logger; - private readonly IDictionary _loadBalancingPolicies; + private readonly FrozenDictionary _loadBalancingPolicies; private readonly RequestDelegate _next; public LoadBalancingMiddleware( diff --git a/src/ReverseProxy/SessionAffinity/AffinitizeTransformProvider.cs b/src/ReverseProxy/SessionAffinity/AffinitizeTransformProvider.cs index e33f6d22f..2934d8be0 100644 --- a/src/ReverseProxy/SessionAffinity/AffinitizeTransformProvider.cs +++ b/src/ReverseProxy/SessionAffinity/AffinitizeTransformProvider.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Frozen; using System.Collections.Generic; using Yarp.ReverseProxy.Transforms.Builder; using Yarp.ReverseProxy.Utilities; @@ -10,7 +11,7 @@ namespace Yarp.ReverseProxy.SessionAffinity; internal sealed class AffinitizeTransformProvider : ITransformProvider { - private readonly IDictionary _sessionAffinityPolicies; + private readonly FrozenDictionary _sessionAffinityPolicies; public AffinitizeTransformProvider(IEnumerable sessionAffinityPolicies) { diff --git a/src/ReverseProxy/SessionAffinity/SessionAffinityMiddleware.cs b/src/ReverseProxy/SessionAffinity/SessionAffinityMiddleware.cs index 043bf2c18..c72b81f78 100644 --- a/src/ReverseProxy/SessionAffinity/SessionAffinityMiddleware.cs +++ b/src/ReverseProxy/SessionAffinity/SessionAffinityMiddleware.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -18,8 +19,8 @@ namespace Yarp.ReverseProxy.SessionAffinity; internal sealed class SessionAffinityMiddleware { private readonly RequestDelegate _next; - private readonly IDictionary _sessionAffinityPolicies; - private readonly IDictionary _affinityFailurePolicies; + private readonly FrozenDictionary _sessionAffinityPolicies; + private readonly FrozenDictionary _affinityFailurePolicies; private readonly ILogger _logger; public SessionAffinityMiddleware( diff --git a/src/ReverseProxy/Transforms/RequestHeadersAllowedTransform.cs b/src/ReverseProxy/Transforms/RequestHeadersAllowedTransform.cs index 6385d5088..f02630e96 100644 --- a/src/ReverseProxy/Transforms/RequestHeadersAllowedTransform.cs +++ b/src/ReverseProxy/Transforms/RequestHeadersAllowedTransform.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; @@ -22,12 +23,12 @@ public RequestHeadersAllowedTransform(string[] allowedHeaders) } AllowedHeaders = allowedHeaders; - AllowedHeadersSet = new HashSet(allowedHeaders, StringComparer.OrdinalIgnoreCase); + AllowedHeadersSet = new HashSet(allowedHeaders, StringComparer.OrdinalIgnoreCase).ToFrozenSet(StringComparer.OrdinalIgnoreCase); } internal string[] AllowedHeaders { get; } - private HashSet AllowedHeadersSet { get; } + private FrozenSet AllowedHeadersSet { get; } /// public override ValueTask ApplyAsync(RequestTransformContext context) diff --git a/src/ReverseProxy/Transforms/ResponseHeadersAllowedTransform.cs b/src/ReverseProxy/Transforms/ResponseHeadersAllowedTransform.cs index d6334030d..f7ab2b7f9 100644 --- a/src/ReverseProxy/Transforms/ResponseHeadersAllowedTransform.cs +++ b/src/ReverseProxy/Transforms/ResponseHeadersAllowedTransform.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Diagnostics; using System.Net.Http.Headers; @@ -24,12 +25,12 @@ public ResponseHeadersAllowedTransform(string[] allowedHeaders) } AllowedHeaders = allowedHeaders; - AllowedHeadersSet = new HashSet(allowedHeaders, StringComparer.OrdinalIgnoreCase); + AllowedHeadersSet = new HashSet(allowedHeaders, StringComparer.OrdinalIgnoreCase).ToFrozenSet(StringComparer.OrdinalIgnoreCase); } internal string[] AllowedHeaders { get; } - private HashSet AllowedHeadersSet { get; } + private FrozenSet AllowedHeadersSet { get; } /// public override ValueTask ApplyAsync(ResponseTransformContext context) diff --git a/src/ReverseProxy/Transforms/ResponseTrailersAllowedTransform.cs b/src/ReverseProxy/Transforms/ResponseTrailersAllowedTransform.cs index 0ad794798..0c072227d 100644 --- a/src/ReverseProxy/Transforms/ResponseTrailersAllowedTransform.cs +++ b/src/ReverseProxy/Transforms/ResponseTrailersAllowedTransform.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Diagnostics; using System.Net.Http.Headers; @@ -25,12 +26,12 @@ public ResponseTrailersAllowedTransform(string[] allowedHeaders) } AllowedHeaders = allowedHeaders; - AllowedHeadersSet = new HashSet(allowedHeaders, StringComparer.OrdinalIgnoreCase); + AllowedHeadersSet = new HashSet(allowedHeaders, StringComparer.OrdinalIgnoreCase).ToFrozenSet(StringComparer.OrdinalIgnoreCase); } internal string[] AllowedHeaders { get; } - private HashSet AllowedHeadersSet { get; } + private FrozenSet AllowedHeadersSet { get; } /// public override ValueTask ApplyAsync(ResponseTrailersTransformContext context) diff --git a/src/ReverseProxy/Utilities/ServiceLookupHelper.cs b/src/ReverseProxy/Utilities/ServiceLookupHelper.cs index 264c0a881..95de05354 100644 --- a/src/ReverseProxy/Utilities/ServiceLookupHelper.cs +++ b/src/ReverseProxy/Utilities/ServiceLookupHelper.cs @@ -2,13 +2,14 @@ // Licensed under the MIT License. using System; +using System.Collections.Frozen; using System.Collections.Generic; namespace Yarp.ReverseProxy.Utilities; internal static class ServiceLookupHelper { - public static IDictionary ToDictionaryByUniqueId(this IEnumerable services, Func idSelector) + public static FrozenDictionary ToDictionaryByUniqueId(this IEnumerable services, Func idSelector) { if (services is null) { @@ -25,10 +26,10 @@ public static IDictionary ToDictionaryByUniqueId(this IEnumerable< } } - return result; + return result.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase); } - public static T GetRequiredServiceById(this IDictionary services, string? id, string defaultId) + public static T GetRequiredServiceById(this FrozenDictionary services, string? id, string defaultId) { var lookup = id; if (string.IsNullOrEmpty(lookup)) diff --git a/src/ReverseProxy/Yarp.ReverseProxy.csproj b/src/ReverseProxy/Yarp.ReverseProxy.csproj index b6f447e10..e45cf4b00 100644 --- a/src/ReverseProxy/Yarp.ReverseProxy.csproj +++ b/src/ReverseProxy/Yarp.ReverseProxy.csproj @@ -13,6 +13,7 @@ +