make use of ports in SPN optional (#57159)
authorTomas Weinfurt <tweinfurt@yahoo.com>
Mon, 16 Aug 2021 10:16:07 +0000 (03:16 -0700)
committerGitHub <noreply@github.com>
Mon, 16 Aug 2021 10:16:07 +0000 (06:16 -0400)
* make port optional in SPN

* fix tests

* feedback from review

* Update src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs

Co-authored-by: Stephen Toub <stoub@microsoft.com>
* fix build

Co-authored-by: Stephen Toub <stoub@microsoft.com>
src/libraries/Common/tests/System/Net/EnterpriseTests/EnterpriseTestConfiguration.cs
src/libraries/Common/tests/System/Net/EnterpriseTests/setup/apacheweb/apache2.conf
src/libraries/Common/tests/System/Net/EnterpriseTests/setup/apacheweb/run.sh
src/libraries/Common/tests/System/Net/EnterpriseTests/setup/docker-compose.yml
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs
src/libraries/System.Net.Http/tests/EnterpriseTests/HttpClientAuthenticationTest.cs
src/libraries/System.Net.Http/tests/EnterpriseTests/System.Net.Http.Enterprise.Tests.csproj

index e6a9cef..f6ea2e8 100644 (file)
@@ -7,6 +7,7 @@ namespace System.Net.Test.Common
     {
         public const string Realm = "LINUX.CONTOSO.COM";
         public const string NegotiateAuthWebServer = "http://apacheweb.linux.contoso.com/auth/kerberos/";
+        public const string NegotiateAuthWebServerNotDefaultPort = "http://apacheweb.linux.contoso.com:8081/auth/kerberos/";
         public const string AlternativeService = "http://altweb.linux.contoso.com:8080/auth/kerberos/";
         public const string NtlmAuthWebServer = "http://apacheweb.linux.contoso.com:8080/auth/ntlm/";
         public const string DigestAuthWebServer = "http://apacheweb.linux.contoso.com/auth/digest/";
index 87c20f0..a26a52e 100644 (file)
@@ -54,6 +54,7 @@ Listen 8080
 </IfDefine>
 <IfDefine !ALTPORT>
 Listen 80
+Listen 8081
 </IfDefine>
 
 #
@@ -238,7 +239,7 @@ Group daemon
 # e-mailed.  This address appears on some server-generated pages, such
 # as error documents.  e.g. admin@your-domain.com
 #
-ServerAdmin you@example.com
+ServerAdmin webmaster@contoso.com
 
 #
 # ServerName gives the name and port that the server uses to identify itself.
@@ -583,11 +584,18 @@ SSLRandomSeed startup builtin
 SSLRandomSeed connect builtin
 </IfModule>
 
+<IfDefine ALTPORT>
 <VirtualHost *:8080>
-  ServerAdmin webmaster@contoso.com
   DocumentRoot "/setup/altdocs"
   ServerName altservice.contoso.com:8080
 </VirtualHost>
+</IfDefine>
+
+<IfDefine !ALTSPN>
+<VirtualHost *:8081>
+  DocumentRoot "/setup/htdocs"
+</VirtualHost>
+</IfDefine>
 
 
 <IFDefine NTLM>
index 6aa9694..0b4a615 100644 (file)
@@ -11,6 +11,7 @@ if [ "$1" == "-debug" ]; then
 fi
 
 if [ "$1" == "-DNTLM" ]; then
+  # NTLM/Winbind is aggressive and eats Negotiate so it cannot be combined with Kerberos
   ./setup-pdc.sh
   /usr/sbin/apache2 -DALTPORT "$@"
   shift
index 6aebd7e..c54adfb 100644 (file)
@@ -41,7 +41,7 @@ services:
     hostname: altweb
     domainname: linux.contoso.com
     dns_search: linux.contoso.com
-    command: -DALTPORT
+    command: "-DALTPORT -DALTSPN"
     volumes:
       - shared-volume:/SHARED
     networks:
index 8ee9130..52edbb5 100644 (file)
@@ -13,6 +13,38 @@ namespace System.Net.Http
 {
     internal static partial class AuthenticationHelper
     {
+        private const string UsePortInSpnCtxSwitch = "System.Net.Http.UsePortInSpn";
+        private const string UsePortInSpnEnvironmentVariable = "DOTNET_SYSTEM_NET_HTTP_USEPORTINSPN";
+
+        private static volatile int s_usePortInSpn = -1;
+
+        private static bool UsePortInSpn
+        {
+            get
+            {
+                int usePortInSpn = s_usePortInSpn;
+                if (usePortInSpn != -1)
+                {
+                    return usePortInSpn != 0;
+                }
+
+                // First check for the AppContext switch, giving it priority over the environment variable.
+                if (AppContext.TryGetSwitch(UsePortInSpnCtxSwitch, out bool value))
+                {
+                    s_usePortInSpn = value ? 1 : 0;
+                }
+                else
+                {
+                    // AppContext switch wasn't used. Check the environment variable.
+                   s_usePortInSpn =
+                       Environment.GetEnvironmentVariable(UsePortInSpnEnvironmentVariable) is string envVar &&
+                       (envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase)) ? 1 : 0;
+                }
+
+                return s_usePortInSpn != 0;
+            }
+        }
+
         private static Task<HttpResponseMessage> InnerSendAsync(HttpRequestMessage request, bool async, bool isProxyAuth, HttpConnectionPool pool, HttpConnection connection, CancellationToken cancellationToken)
         {
             return isProxyAuth ?
@@ -110,7 +142,7 @@ namespace System.Net.Http
                                 hostName = result.HostName;
                             }
 
-                            if (!isProxyAuth && !authUri.IsDefaultPort)
+                            if (!isProxyAuth && !authUri.IsDefaultPort && UsePortInSpn)
                             {
                                 hostName = string.Create(null, stackalloc char[128], $"{hostName}:{authUri.Port}");
                             }
index 9b93d53..c2c78d2 100644 (file)
@@ -3,7 +3,7 @@
 
 using System.Net.Test.Common;
 using System.Threading.Tasks;
-
+using Microsoft.DotNet.RemoteExecutor;
 using Xunit;
 
 namespace System.Net.Http.Enterprise.Tests
@@ -11,20 +11,31 @@ namespace System.Net.Http.Enterprise.Tests
     [ConditionalClass(typeof(EnterpriseTestConfiguration), nameof(EnterpriseTestConfiguration.Enabled))]
     public class HttpClientAuthenticationTest
     {
+        private const string AppContextSettingName = "System.Net.Http.UsePortInSpn";
+
         [Theory]
         [InlineData(EnterpriseTestConfiguration.NegotiateAuthWebServer, false)]
-        [InlineData(EnterpriseTestConfiguration.AlternativeService, false)]
+        [InlineData(EnterpriseTestConfiguration.NegotiateAuthWebServerNotDefaultPort, false)]
+        [InlineData(EnterpriseTestConfiguration.AlternativeService, false, true)]
         [InlineData(EnterpriseTestConfiguration.DigestAuthWebServer, true)]
         [InlineData(EnterpriseTestConfiguration.DigestAuthWebServer, false)]
         [InlineData(EnterpriseTestConfiguration.NtlmAuthWebServer, true)]
-        public async Task HttpClient_ValidAuthentication_Success(string url, bool useDomain)
+        public void HttpClient_ValidAuthentication_Success(string url, bool useDomain, bool useAltPort = false)
         {
-            using var handler = new HttpClientHandler();
-            handler.Credentials = useDomain ? EnterpriseTestConfiguration.ValidDomainNetworkCredentials : EnterpriseTestConfiguration.ValidNetworkCredentials;
-            using var client = new HttpClient(handler);
+            RemoteExecutor.Invoke((url, useAltPort, useDomain) =>
+            {
+                // This is safe as we have no parallel tests
+               if (!string.IsNullOrEmpty(useAltPort))
+               {
+                    AppContext.SetSwitch(AppContextSettingName, true);
+                }
+                using var handler = new HttpClientHandler();
+                handler.Credentials = string.IsNullOrEmpty(useDomain) ? EnterpriseTestConfiguration.ValidNetworkCredentials : EnterpriseTestConfiguration.ValidDomainNetworkCredentials;
+                using var client = new HttpClient(handler);
 
-            using HttpResponseMessage response = await client.GetAsync(url);
-            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+                using HttpResponseMessage response = client.GetAsync(url).GetAwaiter().GetResult();
+                Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+            }, url, useAltPort ? "true" : "" , useDomain ? "true" : "").Dispose();
         }
 
         [ActiveIssue("https://github.com/dotnet/runtime/issues/416")]
index 5d1c6c0..4af21d6 100644 (file)
@@ -1,6 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFrameworks>$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser</TargetFrameworks>
+    <IncludeRemoteExecutor>true</IncludeRemoteExecutor>
   </PropertyGroup>
   <ItemGroup>
     <Compile Include="HttpClientAuthenticationTest.cs" />
@@ -8,4 +9,4 @@
     <Compile Include="$(CommonTestPath)System\Net\EnterpriseTests\EnterpriseTestConfiguration.cs"
              Link="Common\System\Net\EnterpriseTests\EnterpriseTestConfiguration.cs" />
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>