From bf884dca362eaf09c65f5d6591a39af8e36159e6 Mon Sep 17 00:00:00 2001 From: Evgeny Kotkov Date: Thu, 2 Nov 2023 20:13:57 +0100 Subject: [PATCH 1/6] http.sys: Allow configuring HTTP_AUTH_EX_FLAGs as options The native HTTP.sys API offers two extended authentication flags: - `HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING` and - `HTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL` that can be used by users to fine-tune their Windows authentication setups. For instance, the `HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING` flag can be used to avoid having to authenticate every request and make the authentication session-based, thus reducing the overall number of requests and improving the high-latency scenarios. Enabling it can be used to achieve the same behavior as with the `authPersistNonNTLM` option in IIS. (See https://github.com/dotnet/aspnetcore/issues/13634) This commit exposes both flags as options in the authentication manager. Because setting the extended flags requires a different property type (`HttpServerExtendedAuthenticationProperty`), we take additional precaution to only use that property type if we actually have the extended flags to set, and use the original `HttpServerAuthenticationProperty` otherwise. --- .../HttpSys/src/AuthenticationManager.cs | 43 +++++++++++++-- src/Servers/HttpSys/src/NativeMethods.txt | 1 + .../HttpSys/src/PublicAPI.Unshipped.txt | 4 ++ .../FunctionalTests/AuthenticationTests.cs | 54 +++++++++++++++++++ 4 files changed, 98 insertions(+), 4 deletions(-) diff --git a/src/Servers/HttpSys/src/AuthenticationManager.cs b/src/Servers/HttpSys/src/AuthenticationManager.cs index 7bf734d6ffa8..4bf7e6f9d086 100644 --- a/src/Servers/HttpSys/src/AuthenticationManager.cs +++ b/src/Servers/HttpSys/src/AuthenticationManager.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; +using Windows.Win32; using Windows.Win32.Networking.HttpServer; namespace Microsoft.AspNetCore.Server.HttpSys; @@ -63,6 +64,19 @@ public bool AllowAnonymous /// public string? AuthenticationDisplayName { get; set; } + /// + /// If true, the Kerberos authentication credentials are cached. Kerberos or Negotiate + /// authentication must be enabled. The default is false. + /// + public bool EnableKerberosCredentialCaching { get; set; } + + /// + /// If true, the server captures the caller's credentials and uses them for Kerberos + /// or Negotiate authentication. Kerberos or Negotiate authentication must be enabled. + /// The default is false. + /// + public bool CaptureCredential { get; set; } + internal void SetUrlGroupSecurity(UrlGroup urlGroup) { Debug.Assert(_urlGroup == null, "SetUrlGroupSecurity called more than once."); @@ -85,18 +99,39 @@ private unsafe void SetUrlGroupSecurity() { authInfo.AuthSchemes = (uint)_authSchemes; + authInfo.ExFlags = 0; + + if (EnableKerberosCredentialCaching) + { + authInfo.ExFlags |= (byte)PInvoke.HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING; + } + + if (CaptureCredential) + { + authInfo.ExFlags |= (byte)PInvoke.HTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL; + } + // TODO: // NTLM auth sharing (on by default?) DisableNTLMCredentialCaching - // Kerberos auth sharing (off by default?) HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING // Mutual Auth - ReceiveMutualAuth // Digest domain and realm - HTTP_SERVER_AUTHENTICATION_DIGEST_PARAMS // Basic realm - HTTP_SERVER_AUTHENTICATION_BASIC_PARAMS + HTTP_SERVER_PROPERTY property; + if (authInfo.ExFlags != 0) + { + // We need to modify extended fields such as ExFlags, set the extended auth property. + property = HTTP_SERVER_PROPERTY.HttpServerExtendedAuthenticationProperty; + } + else + { + // Otherwise set the regular auth property. + property = HTTP_SERVER_PROPERTY.HttpServerAuthenticationProperty; + } + IntPtr infoptr = new IntPtr(&authInfo); - _urlGroup.SetProperty( - HTTP_SERVER_PROPERTY.HttpServerAuthenticationProperty, - infoptr, (uint)AuthInfoSize); + _urlGroup.SetProperty(property, infoptr, (uint)AuthInfoSize); } } diff --git a/src/Servers/HttpSys/src/NativeMethods.txt b/src/Servers/HttpSys/src/NativeMethods.txt index 548306dfeade..3d6f700d6101 100644 --- a/src/Servers/HttpSys/src/NativeMethods.txt +++ b/src/Servers/HttpSys/src/NativeMethods.txt @@ -5,6 +5,7 @@ CloseHandle FILE_SKIP_COMPLETION_PORT_ON_SUCCESS FILE_SKIP_SET_EVENT_ON_HANDLE +HTTP_AUTH_EX_FLAG_* HTTP_AUTH_STATUS HTTP_BINDING_INFO HTTP_CACHE_POLICY diff --git a/src/Servers/HttpSys/src/PublicAPI.Unshipped.txt b/src/Servers/HttpSys/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..a1725c258e62 100644 --- a/src/Servers/HttpSys/src/PublicAPI.Unshipped.txt +++ b/src/Servers/HttpSys/src/PublicAPI.Unshipped.txt @@ -1 +1,5 @@ #nullable enable +Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.CaptureCredential.get -> bool +Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.CaptureCredential.set -> void +Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.EnableKerberosCredentialCaching.get -> bool +Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.EnableKerberosCredentialCaching.set -> void diff --git a/src/Servers/HttpSys/test/FunctionalTests/AuthenticationTests.cs b/src/Servers/HttpSys/test/FunctionalTests/AuthenticationTests.cs index f82e870c3e2d..1def56ca9698 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/AuthenticationTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/AuthenticationTests.cs @@ -400,6 +400,60 @@ public async Task AuthTypes_DisableAutomaticAuthentication(AuthenticationSchemes } } + [ConditionalTheory] + [InlineData(AuthenticationSchemes.Negotiate)] + [InlineData(AuthenticationSchemes.NTLM)] + [InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM)] + public async Task AuthTypes_EnableKerberosCredentialCaching(AuthenticationSchemes authType) + { + using (var server = Utilities.CreateDynamicHost(out var address, options => + { + options.Authentication.Schemes = authType; + options.Authentication.AllowAnonymous = DenyAnoymous; + options.Authentication.EnableKerberosCredentialCaching = true; + }, + httpContext => + { + // There doesn't seem to be a simple way of testing the `EnableKerberosCredentialCaching` + // setting, but at least check that the server works. + Assert.NotNull(httpContext.User); + Assert.NotNull(httpContext.User.Identity); + Assert.True(httpContext.User.Identity.IsAuthenticated); + return Task.FromResult(0); + }, LoggerFactory)) + { + var response = await SendRequestAsync(address, useDefaultCredentials: true); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + } + + [ConditionalTheory] + [InlineData(AuthenticationSchemes.Negotiate)] + [InlineData(AuthenticationSchemes.NTLM)] + [InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM)] + public async Task AuthTypes_CaptureCredential(AuthenticationSchemes authType) + { + using (var server = Utilities.CreateDynamicHost(out var address, options => + { + options.Authentication.Schemes = authType; + options.Authentication.AllowAnonymous = DenyAnoymous; + options.Authentication.CaptureCredential = true; + }, + httpContext => + { + // There doesn't seem to be a simple way of testing the `CaptureCredential` + // setting, but at least check that the server works. + Assert.NotNull(httpContext.User); + Assert.NotNull(httpContext.User.Identity); + Assert.True(httpContext.User.Identity.IsAuthenticated); + return Task.FromResult(0); + }, LoggerFactory)) + { + var response = await SendRequestAsync(address, useDefaultCredentials: true); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + } + private async Task SendRequestAsync(string uri, bool useDefaultCredentials = false) { HttpClientHandler handler = new HttpClientHandler(); From 39edd844a70e4bc990fbaefd20f05f39e1cfdcd5 Mon Sep 17 00:00:00 2001 From: Evgeny Kotkov Date: Fri, 10 Nov 2023 21:21:57 +0100 Subject: [PATCH 2/6] =?UTF-8?q?http.sys:=20Rename:=20CaptureCredential=20?= =?UTF-8?q?=E2=86=92=20CaptureCredentials?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Servers/HttpSys/src/AuthenticationManager.cs | 4 ++-- src/Servers/HttpSys/src/PublicAPI.Unshipped.txt | 4 ++-- .../HttpSys/test/FunctionalTests/AuthenticationTests.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Servers/HttpSys/src/AuthenticationManager.cs b/src/Servers/HttpSys/src/AuthenticationManager.cs index 4bf7e6f9d086..f5524678bc37 100644 --- a/src/Servers/HttpSys/src/AuthenticationManager.cs +++ b/src/Servers/HttpSys/src/AuthenticationManager.cs @@ -75,7 +75,7 @@ public bool AllowAnonymous /// or Negotiate authentication. Kerberos or Negotiate authentication must be enabled. /// The default is false. /// - public bool CaptureCredential { get; set; } + public bool CaptureCredentials { get; set; } internal void SetUrlGroupSecurity(UrlGroup urlGroup) { @@ -106,7 +106,7 @@ private unsafe void SetUrlGroupSecurity() authInfo.ExFlags |= (byte)PInvoke.HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING; } - if (CaptureCredential) + if (CaptureCredentials) { authInfo.ExFlags |= (byte)PInvoke.HTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL; } diff --git a/src/Servers/HttpSys/src/PublicAPI.Unshipped.txt b/src/Servers/HttpSys/src/PublicAPI.Unshipped.txt index a1725c258e62..f68a65d79547 100644 --- a/src/Servers/HttpSys/src/PublicAPI.Unshipped.txt +++ b/src/Servers/HttpSys/src/PublicAPI.Unshipped.txt @@ -1,5 +1,5 @@ #nullable enable -Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.CaptureCredential.get -> bool -Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.CaptureCredential.set -> void +Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.CaptureCredentials.get -> bool +Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.CaptureCredentials.set -> void Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.EnableKerberosCredentialCaching.get -> bool Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.EnableKerberosCredentialCaching.set -> void diff --git a/src/Servers/HttpSys/test/FunctionalTests/AuthenticationTests.cs b/src/Servers/HttpSys/test/FunctionalTests/AuthenticationTests.cs index 1def56ca9698..cbafac88e448 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/AuthenticationTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/AuthenticationTests.cs @@ -431,17 +431,17 @@ public async Task AuthTypes_EnableKerberosCredentialCaching(AuthenticationScheme [InlineData(AuthenticationSchemes.Negotiate)] [InlineData(AuthenticationSchemes.NTLM)] [InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM)] - public async Task AuthTypes_CaptureCredential(AuthenticationSchemes authType) + public async Task AuthTypes_CaptureCredentials(AuthenticationSchemes authType) { using (var server = Utilities.CreateDynamicHost(out var address, options => { options.Authentication.Schemes = authType; options.Authentication.AllowAnonymous = DenyAnoymous; - options.Authentication.CaptureCredential = true; + options.Authentication.CaptureCredentials = true; }, httpContext => { - // There doesn't seem to be a simple way of testing the `CaptureCredential` + // There doesn't seem to be a simple way of testing the `CaptureCredentials` // setting, but at least check that the server works. Assert.NotNull(httpContext.User); Assert.NotNull(httpContext.User.Identity); From db5b58074c51ee297c1ff5d2ec67bcf0c6a40515 Mon Sep 17 00:00:00 2001 From: Evgeny Kotkov Date: Fri, 10 Nov 2023 21:32:39 +0100 Subject: [PATCH 3/6] http.sys: Clarify description for `EnableKerberosCredentialCaching` --- src/Servers/HttpSys/src/AuthenticationManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Servers/HttpSys/src/AuthenticationManager.cs b/src/Servers/HttpSys/src/AuthenticationManager.cs index f5524678bc37..3a182ad1309f 100644 --- a/src/Servers/HttpSys/src/AuthenticationManager.cs +++ b/src/Servers/HttpSys/src/AuthenticationManager.cs @@ -65,7 +65,8 @@ public bool AllowAnonymous public string? AuthenticationDisplayName { get; set; } /// - /// If true, the Kerberos authentication credentials are cached. Kerberos or Negotiate + /// If true, the Kerberos authentication credentials are persisted per connection + /// and re-used for subsequent anonymous requests. Kerberos or Negotiate /// authentication must be enabled. The default is false. /// public bool EnableKerberosCredentialCaching { get; set; } From e2a327a6df7cec7ac7df37b59ff81e0f9309a89b Mon Sep 17 00:00:00 2001 From: Evgeny Kotkov Date: Sat, 11 Nov 2023 00:01:46 +0100 Subject: [PATCH 4/6] http.sys: Further clarify description for `EnableKerberosCredentialCaching` --- src/Servers/HttpSys/src/AuthenticationManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Servers/HttpSys/src/AuthenticationManager.cs b/src/Servers/HttpSys/src/AuthenticationManager.cs index 3a182ad1309f..62015d1c9117 100644 --- a/src/Servers/HttpSys/src/AuthenticationManager.cs +++ b/src/Servers/HttpSys/src/AuthenticationManager.cs @@ -66,8 +66,8 @@ public bool AllowAnonymous /// /// If true, the Kerberos authentication credentials are persisted per connection - /// and re-used for subsequent anonymous requests. Kerberos or Negotiate - /// authentication must be enabled. The default is false. + /// and re-used for subsequent anonymous requests on the same connection. + /// Kerberos or Negotiate authentication must be enabled. The default is false. /// public bool EnableKerberosCredentialCaching { get; set; } From 5db6406fa33f4121c5d8468757e73d1e83336247 Mon Sep 17 00:00:00 2001 From: Evgeny Kotkov Date: Mon, 5 Feb 2024 18:03:26 +0100 Subject: [PATCH 5/6] http.sys: Further clarify documentation for new options Based on the discussion in https://github.com/dotnet/aspnetcore/issues/51990 --- src/Servers/HttpSys/src/AuthenticationManager.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Servers/HttpSys/src/AuthenticationManager.cs b/src/Servers/HttpSys/src/AuthenticationManager.cs index 62015d1c9117..51e431e3c791 100644 --- a/src/Servers/HttpSys/src/AuthenticationManager.cs +++ b/src/Servers/HttpSys/src/AuthenticationManager.cs @@ -68,13 +68,18 @@ public bool AllowAnonymous /// If true, the Kerberos authentication credentials are persisted per connection /// and re-used for subsequent anonymous requests on the same connection. /// Kerberos or Negotiate authentication must be enabled. The default is false. + /// This option maps to the native HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING flag. + /// /// public bool EnableKerberosCredentialCaching { get; set; } /// - /// If true, the server captures the caller's credentials and uses them for Kerberos - /// or Negotiate authentication. Kerberos or Negotiate authentication must be enabled. - /// The default is false. + /// If true, the server captures the current caller's credentials and impersonates + /// them during Kerberos or Negotiate authentication. The credentials are captured + /// from the security context of a thread that performs the host initialization. + /// Kerberos or Negotiate authentication must be enabled. The default is false. + /// This option maps to the native HTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL flag. + /// /// public bool CaptureCredentials { get; set; } From a2885d991de74bce088652f5711dd1e8b0116ec8 Mon Sep 17 00:00:00 2001 From: Evgeny Kotkov Date: Wed, 7 Feb 2024 23:36:18 +0100 Subject: [PATCH 6/6] http.sys: Tweak documentation for `CaptureCredentials` --- src/Servers/HttpSys/src/AuthenticationManager.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Servers/HttpSys/src/AuthenticationManager.cs b/src/Servers/HttpSys/src/AuthenticationManager.cs index 51e431e3c791..f40e3ea0d4e8 100644 --- a/src/Servers/HttpSys/src/AuthenticationManager.cs +++ b/src/Servers/HttpSys/src/AuthenticationManager.cs @@ -74,9 +74,8 @@ public bool AllowAnonymous public bool EnableKerberosCredentialCaching { get; set; } /// - /// If true, the server captures the current caller's credentials and impersonates - /// them during Kerberos or Negotiate authentication. The credentials are captured - /// from the security context of a thread that performs the host initialization. + /// If true, the server captures user credentials from the thread that starts the + /// host and impersonates that user during Kerberos or Negotiate authentication. /// Kerberos or Negotiate authentication must be enabled. The default is false. /// This option maps to the native HTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL flag. ///