Fix SPN using instance name instead of resolved port for Protocol.None and Protocol.Admin#4180
Fix SPN using instance name instead of resolved port for Protocol.None and Protocol.Admin#4180paulmedynski wants to merge 3 commits intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes SPN generation for named-instance connections when the connection string omits a protocol prefix (Protocol.None) or uses DAC (Protocol.Admin), ensuring TCP-like protocols use the SSRP-resolved port rather than the instance name in the SPN.
Changes:
- Updated
SniProxy.GetSqlServerSPNs(DataSource, string)to use the instance name only for Named Pipes (NP); all other protocols use the SSRP-resolved port. - Changed
GetSqlServerSPNs(DataSource, string)visibility fromprivatetointernalto enable direct unit testing. - Added unit tests covering Protocol.None/TCP/Admin and custom SPN passthrough behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs | Adjusts SPN postfix selection logic so TCP-like protocols use SSRP-resolved port; exposes method for unit tests. |
| src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/SniProxyGetSqlServerSPNsTest.cs | Adds regression tests for SPN formatting across protocols and custom SPN passthrough. |
Comments suppressed due to low confidence (1)
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs:144
- The trace event logs
dataSource.Portbut the postfix/SPN decision for named instances now depends ondataSource.ResolvedPort. Consider loggingResolvedPortas well (or instead) so diagnostics reflect the value actually used to build the SPN.
postfix = dataSource.ResolvedProtocol == DataSource.Protocol.NP ? dataSource.InstanceName : dataSource.ResolvedPort.ToString();
}
SqlClientEventSource.Log.TryTraceEvent("SNIProxy.GetSqlServerSPN | Info | ServerName {0}, InstanceName {1}, Port {2}, postfix {3}", dataSource?.ServerName, dataSource?.InstanceName, dataSource?.Port, postfix);
return GetSqlServerSPNs(hostName, postfix, dataSource.ResolvedProtocol);
da6afed to
f027453
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4180 +/- ##
==========================================
- Coverage 65.99% 64.36% -1.63%
==========================================
Files 277 272 -5
Lines 42988 65785 +22797
==========================================
+ Hits 28370 42345 +13975
- Misses 14618 23440 +8822
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| { | ||
| postfix = dataSource.ResolvedProtocol == DataSource.Protocol.TCP ? dataSource.ResolvedPort.ToString() : dataSource.InstanceName; | ||
| // Named Pipes use the instance name in the SPN (MSSQLSvc/host:instance). | ||
| // All other protocols (TCP, None, Admin) use the port resolved by SSRP |
There was a problem hiding this comment.
Not necessarily, both Port and Instance Name can be used interchangeably. Refer to Microsoft Docs They both point to the same SQL Server instance.
Protocol.None points the driver to use Shared Memory protocol when establishing connection - which is supported on Windows, but not on Unix.
| // (MSSQLSvc/host:port). Protocol.None is the default when no prefix is | ||
| // specified in the data source (e.g. "server\instance"), and it is treated | ||
| // as TCP for connection purposes. See GitHub issue #3566. | ||
| postfix = dataSource.ResolvedProtocol == DataSource.Protocol.NP ? dataSource.InstanceName : dataSource.ResolvedPort.ToString(); |
There was a problem hiding this comment.
Has this been tested in Kerberos test suite to verify it doesn't cause any regression in any environment?
f027453 to
f5942fd
Compare
…ecific SPN handling - Add XML class and method documentation to SniProxyGetSqlServerSPNsTest explaining: * Purpose: regression tests for SPN protocol-specific behavior (issue #3566) * Protocol semantics: TCP-like protocols use port, Named Pipes uses instance name * Reference to official Microsoft Learn SPN format documentation * Specific test intentions and assertions - Add inline comments in unit tests clarifying: * DataSource parsing and Protocol.None/TCP/NP/Admin differentiation * SSRP port simulation behavior * Why each assertion validates the correct SPN format per protocol - Add 5 integration tests to KerberosTest.cs for end-to-end Kerberos validation: * ProtocolNone_NamedInstanceWithSsrpResolution - Tests issue #3566 (SSRP-resolved port in SPN) * ProtocolTcp_NamedInstanceWithExplicitPort - Tests TCP protocol with named instances * CustomServerSPN_BypassesAutoGeneration - Tests explicit SPN overrides for custom environments * ProtocolAdmin_DedicatedAdminConnection - Tests DAC protocol SPN behavior * Plus enhanced documentation explaining environment setup requirements Tests verify that Kerberos authentication succeeds by checking auth_scheme='KERBEROS' in sys.dm_exec_connections, confirming that protocol-specific SPN generation enables successful authentication across all protocol types. Addresses Cheena's review concerns about protocol semantics and Kerberos regression testing.
…robustness SniProxy.netcore.cs: - Guard against ResolvedPort <= 0 (e.g. -1 before SSRP resolves) to avoid producing malformed SPNs like ':-1' in error paths. Fall back to instance name when port is not yet available. SniProxyGetSqlServerSPNsTest.cs: - Replace generic 'server' hostnames with 'localhost' in all ParseServerName calls and the NP direct-call test. This avoids real DNS lookups that could cause flakiness in restricted-DNS environments. KerberosTest.cs: - Protocol.None test: build connection string from SqlConnectionStringBuilder(tcpConnStr) instead of from scratch to preserve Encrypt, TrustServerCertificate, etc. - TCP test: skip if no named instance; omit port fallback to 1433 (which disables SSRP and can produce invalid 'host\,port' strings). - Custom SPN test: require explicit port and use TCP-format SPN (host:port) rather than instance name, which is wrong for port-based SPN registrations. - Admin test: build from tcpConnStr base; require named instance; remove fragile catch-by-message-substring that could hide failures. DAC unavailability now correctly surfaces as a test failure instead of silent pass.
c2710f0 to
d485228
Compare
| // settings. Do NOT fall back to port 1433 — the DAC port is separate from the regular | ||
| // SQL Server port and must be discovered via SSRP if not explicitly known. | ||
| string newDataSource = port > 0 | ||
| ? $"admin:{hostname}\\{instanceName},{port}" | ||
| : $"admin:{hostname}\\{instanceName}"; |
| /// Environment: Requires a named instance with an explicitly specified or SSRP-resolvable port. | ||
| /// </summary> | ||
| [PlatformSpecific(TestPlatforms.Linux)] | ||
| [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsKerberosTest))] | ||
| public void KerberosTest_ProtocolTcp_NamedInstanceWithExplicitPort() |
Fix
Fixes #3566.
Depends on #4252.
Problem
When connecting to a named instance without a tcp: prefix (for example, Data Source=server\instance), DataSource.ResolvedProtocol is Protocol.None. SPN generation only used resolved port for Protocol.TCP, so Protocol.None and Protocol.Admin could incorrectly use instance name in the SPN:
MSSQLSvc/server.fqdn:instancename
instead of using the SSRP-resolved port:
MSSQLSvc/server.fqdn:12345
Why this addresses the review concern
@cheenamalhotra correctly noted that port and instance name can both map to the same SQL Server instance. This change does not claim they are always semantically different targets.
The driver behavior here is protocol-specific for SPN construction:
This gives deterministic SPN formatting for integrated auth on Unix/Linux SSRP flows while preserving NP behavior and explicit ServerSPN overrides.
Code changes
Testing performed
Kerberos environment validation status
I will run our existing suite of Kerberos tests and provide results here.