-
Notifications
You must be signed in to change notification settings - Fork 330
Fix | Linux SPN port number using named instance and Kerberos authentication does not return port# #2240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Fix | Linux SPN port number using named instance and Kerberos authentication does not return port# #2240
Changes from 15 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
e8b6a5f
Push unit test to continue development in VM.
arellegue ce2481d
Added unit test for SPN in managed SNI and tested in VMs in sqldrv.ad…
arellegue 60f061f
Save to my branch all changes
arellegue e77bfb6
Implemented Unit Test using Reflections.
arellegue ed15e37
Change TimeourTimer from 100000 to just 30 seconds.
arellegue d671acd
Removed and sort usings.
arellegue d62c9e1
Fix backwards DataTestUtility.IsNotLocalhost logic.
arellegue 8de6014
Removed DataTestUtility.IsManagedSNI as there is already DataTestUtil…
arellegue 2d3ae85
Add Type suffix to reflected types.
arellegue 5907497
Use meaningful variable names per Davoud.
arellegue 589d5bd
Use object type instead of var type to make code easier to understand…
arellegue a7bd4ed
Implement suggestions from PR Review.
arellegue 94457f9
Removed all instance of top secret domain name.
arellegue 6c6f6dc
Moved IsManagedKerberos function from DataTestUtility class to Instan…
arellegue 030d8f7
Applied suggestions by Davoud "Even though this PR suggests an update…
arellegue 4a92bc7
Disable unit test for Linux SPN Port# issue using ActiveIssue 27824.
arellegue 2c8e139
Changed comment for ActiveIssue 27824.
arellegue File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| using System; | ||
| using System.Net; | ||
| using System.Net.Sockets; | ||
| using System.Reflection; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
| using Xunit; | ||
|
|
@@ -83,6 +84,131 @@ public static void ConnectManagedWithInstanceNameTest(bool useMultiSubnetFailove | |
| } | ||
| } | ||
|
|
||
| // Note: This Unit test was tested in a domain-joined VM connecting to a remote | ||
| // SQL Server using Kerberos in the same domain. | ||
| [ConditionalFact(nameof(IsKerberos))] | ||
| public static void PortNumberInSPNTest() | ||
| { | ||
| string connStr = DataTestUtility.TCPConnectionString; | ||
| // If config.json.SupportsIntegratedSecurity = true, replace all keys defined below with Integrated Security=true | ||
| if (DataTestUtility.IsIntegratedSecuritySetup()) | ||
| { | ||
| string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD", "Trusted_Connection" }; | ||
| connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.TCPConnectionString, removeKeys) + $"Integrated Security=true"; | ||
| } | ||
|
|
||
| SqlConnectionStringBuilder builder = new(connStr); | ||
|
|
||
| Assert.True(DataTestUtility.ParseDataSource(builder.DataSource, out string hostname, out _, out string instanceName)); | ||
|
arellegue marked this conversation as resolved.
Outdated
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With specifying instance name and port number, this method call always returns false! |
||
|
|
||
| if (IsBrowserAlive(hostname) && IsValidInstance(hostname, instanceName)) | ||
|
arellegue marked this conversation as resolved.
Outdated
|
||
| { | ||
| using SqlConnection connection = new(builder.ConnectionString); | ||
| connection.Open(); | ||
| using SqlCommand command = new("SELECT auth_scheme, local_tcp_port from sys.dm_exec_connections where session_id = @@spid", connection); | ||
| using SqlDataReader reader = command.ExecuteReader(); | ||
| Assert.True(reader.Read(), "Expected to receive one row data"); | ||
| Assert.Equal("KERBEROS", reader.GetString(0)); | ||
| int Port = reader.GetInt32(1); | ||
|
|
||
| int port = -1; | ||
|
arellegue marked this conversation as resolved.
Outdated
|
||
| string spnInfo = GetSPNInfo(builder.DataSource, out port); | ||
|
|
||
| // sample output to validate = MSSQLSvc/machine.domain.tld:port" | ||
| Assert.Contains($"MSSQLSvc/{hostname}", spnInfo); | ||
| // the local_tcp_port Port is the same as the inferred port from instance name | ||
| Assert.Equal(Port, port); | ||
| } | ||
| } | ||
|
|
||
| private static string GetSPNInfo(string datasource, out int out_port) | ||
| { | ||
| Assembly sqlConnectionAssembly = Assembly.GetAssembly(typeof(SqlConnection)); | ||
|
|
||
| // Get all required types using reflection | ||
| Type sniProxyType = sqlConnectionAssembly.GetType("Microsoft.Data.SqlClient.SNI.SNIProxy"); | ||
| Type ssrpType = sqlConnectionAssembly.GetType("Microsoft.Data.SqlClient.SNI.SSRP"); | ||
| Type dataSourceType = sqlConnectionAssembly.GetType("Microsoft.Data.SqlClient.SNI.DataSource"); | ||
| Type timeoutTimerType = sqlConnectionAssembly.GetType("Microsoft.Data.ProviderBase.TimeoutTimer"); | ||
|
|
||
| // Used in Datasource constructor param type array | ||
| Type[] dataSourceConstructorTypesArray = new Type[] { typeof(string) }; | ||
|
|
||
| // Used in GetSqlServerSPNs function param types array | ||
| Type[] getSqlServerSPNsTypesArray = new Type[] { dataSourceType, typeof(string) }; | ||
|
|
||
| // GetPortByInstanceName parameters array | ||
| Type[] getPortByInstanceNameTypesArray = new Type[] { typeof(string), typeof(string), timeoutTimerType, typeof(bool), typeof(Microsoft.Data.SqlClient.SqlConnectionIPAddressPreference) }; | ||
|
|
||
| // TimeoutTimer.StartSecondsTimeout params | ||
| Type[] startSecondsTimeoutTypesArray = new Type[] { typeof(int) }; | ||
|
|
||
| // Get all types constructors | ||
| ConstructorInfo sniProxyCtor = sniProxyType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, Type.EmptyTypes, null); | ||
| ConstructorInfo SSRPCtor = ssrpType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, Type.EmptyTypes, null); | ||
| ConstructorInfo dataSourceCtor = dataSourceType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, dataSourceConstructorTypesArray , null); | ||
| ConstructorInfo timeoutTimerCtor = timeoutTimerType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, Type.EmptyTypes, null); | ||
|
|
||
| // Instantiate SNIProxy | ||
| object sniProxy = sniProxyCtor.Invoke(new object[] { }); | ||
|
arellegue marked this conversation as resolved.
Outdated
|
||
|
|
||
| // Instantiate datasource | ||
| object details = dataSourceCtor.Invoke(new object[] { datasource }); | ||
|
arellegue marked this conversation as resolved.
Outdated
|
||
|
|
||
| // Instantiate SSRP | ||
| object ssrp = SSRPCtor.Invoke(new object[] { }); | ||
|
arellegue marked this conversation as resolved.
Outdated
|
||
|
|
||
| // Instantiate TimeoutTimer | ||
| object timeoutTimer = timeoutTimerCtor.Invoke(new object[] { }); | ||
|
|
||
| // Get TimeoutTimer.StartSecondsTimeout Method | ||
| MethodInfo startSecondsTimeout = timeoutTimer.GetType().GetMethod("StartSecondsTimeout", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, startSecondsTimeoutTypesArray, null); | ||
| // Create a timeoutTimer that expires in 30 seconds | ||
| timeoutTimer = startSecondsTimeout.Invoke(details, new object[] { 30 }); | ||
|
|
||
| // Parse the datasource to separate the server name and instance name | ||
| MethodInfo ParseServerName = details.GetType().GetMethod("ParseServerName", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, dataSourceConstructorTypesArray, null); | ||
| object dataSrcInfo = ParseServerName.Invoke(details, new object[] { datasource }); | ||
|
arellegue marked this conversation as resolved.
Outdated
|
||
|
|
||
| // Get the GetPortByInstanceName method of SSRP | ||
| MethodInfo getPortByInstanceName = ssrp.GetType().GetMethod("GetPortByInstanceName", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, getPortByInstanceNameTypesArray, null); | ||
|
|
||
| // Get the server name | ||
| PropertyInfo serverInfo = dataSrcInfo.GetType().GetProperty("ServerName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); | ||
| string serverName = serverInfo.GetValue(dataSrcInfo, null).ToString(); | ||
|
|
||
| // Get the instance name | ||
| PropertyInfo instanceNameInfo = dataSrcInfo.GetType().GetProperty("InstanceName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); | ||
| string instanceName = instanceNameInfo.GetValue(dataSrcInfo, null).ToString(); | ||
|
|
||
| // Get the port number using the GetPortByInstanceName method of SSRP | ||
| object port = getPortByInstanceName.Invoke(ssrp, parameters: new object[] { serverName, instanceName, timeoutTimer, false, 0 } ); | ||
|
|
||
| // Set the resolved port property of datasource | ||
| PropertyInfo resolvedPortInfo = dataSrcInfo.GetType().GetProperty("ResolvedPort", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); | ||
| resolvedPortInfo.SetValue(dataSrcInfo, (int)port, null); | ||
|
|
||
| // Prepare the GetSqlServerSPNs method | ||
| string serverSPN = ""; | ||
| MethodInfo getSqlServerSPNs = sniProxy.GetType().GetMethod("GetSqlServerSPNs", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, getSqlServerSPNsTypesArray, null); | ||
|
|
||
| // Finally call GetSqlServerSPNs | ||
| // Use dynamic type for indexing to work at design time | ||
| dynamic result = getSqlServerSPNs.Invoke(sniProxy, new object[] { dataSrcInfo, serverSPN }); | ||
|
arellegue marked this conversation as resolved.
Outdated
|
||
|
|
||
| // Example result: MSSQLSvc/machine.domain.tld:port" | ||
| string spnInfo = Encoding.Unicode.GetString(result[0]); | ||
|
|
||
| out_port = (int)port; | ||
|
|
||
| return spnInfo; | ||
| } | ||
|
|
||
| private static bool IsKerberos() | ||
| { | ||
| return (DataTestUtility.AreConnStringsSetup() && DataTestUtility.IsNotLocalhost() && DataTestUtility.IsKerberosTest && DataTestUtility.IsNotAzureServer() && DataTestUtility.IsNotAzureSynapse()); | ||
| } | ||
|
arellegue marked this conversation as resolved.
|
||
|
|
||
| private static bool IsBrowserAlive(string browserHostname) | ||
| { | ||
| const byte ClntUcastEx = 0x03; | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.