dotnet / dotnet/SqlClient

Specifying Domain Account in ServerSPN Causes Connection Failure on Linux

Open
#3,165 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
C#
Stars
989
Forks
340
Avg merge
4d 19h
Merged PRs (30d)
72

Description

When attempting to set the `ServerSPN` attribute in a connection string to specify the account under which a SQL Server instance is running (`Username@Domain`), as described in the [SQL Server Native Client documentation](https://learn.microsoft.com/en-us/sql/relational-databases/native-client/features/service-principal-name-spn-support-in-client-connections?view=sql-server-ver15), the connection succeeds on Windows but fails on Linux with the following exception:
```
Exception thrown: 'Microsoft.Data.SqlClient.SqlException' in Microsoft.Data.SqlClient.dll
Connection failed: The target principal name is incorrect. Cannot generate SSPI context.
GenericFailure
at Microsoft.Data.SqlClient.SNI.TdsParserStateObjectManaged.GenerateSspiClientContext(Byte[] receivedBuff, UInt32 receivedLength, Byte[]& sendBuff, UInt32& sendLength, Byte[][] _sniSpnBuffer)
at Microsoft.Data.SqlClient.TdsParser.SSPIData(Byte[] receivedBuff, UInt32 receivedLength, Byte[]& sendBuff, UInt32& sendLength)
```
```
at Microsoft.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command, Boolean callerHasConnectionLock, Boolean asyncClose)
at Microsoft.Data.SqlClient.TdsParser.SSPIError(String error, String procedure)
at Microsoft.Data.SqlClient.TdsParser.SSPIData(Byte[] receivedBuff, UInt32 receivedLength, Byte[]& sendBuff, UInt32& sendLength)
at Microsoft.Data.SqlClient.TdsParser.TdsLogin(SqlLogin rec, FeatureExtension requestedFeatures, SessionData recoverySessionData, FederatedAuthenticationFeatureExtensionData fedAuthFeatureExtensionData, SqlConnectionEncryptOption encrypt)
at Microsoft.Data.SqlClient.SqlInternalConnectionTds.Login(ServerInfo server, TimeoutTimer timeout, String newPassword, SecureString newSecurePassword, SqlConnectionEncryptOption encrypt)
at Microsoft.Data.SqlClient.SqlInternalConnectionTds.AttemptOneLogin(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, TimeoutTimer timeout, Boolean withFailover)
at Microsoft.Data.SqlClient.SqlInternalConnectionTds.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString connectionOptions, SqlCredential credential, TimeoutTimer timeout)
at Microsoft.Data.SqlClient.SqlInternalConnectionTds.OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectionOptions, SqlCredential credential, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance)
at Microsoft.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, Object providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken, DbConnectionPool pool, Func`3 accessTokenCallback)
at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions)
at Microsoft.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions)
at Microsoft.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
at Microsoft.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
at Microsoft.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
at Microsoft.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection)
at Microsoft.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
at Microsoft.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
at Microsoft.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
at Microsoft.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry, SqlConnectionOverrides overrides)
at Microsoft.Data.SqlClient.SqlConnection.Open(SqlConnectionOverrides overrides)
at Microsoft.Data.SqlClient.SqlConnection.Open()
at TestCSharpConnection.Program.Main() in ....
```

### To reproduce
1. Configure a SQL Server instance to run under a specific domain account.
2. On a Linux client, setup Kerberos authentication, and set up a connection string that includes the `ServerSPN` attribute, specifying the account in the format `Username@Domain` (example: svc-myuser@REALM).
3. Attempt to establish a connection to the SQL Server instance using this connection string.

The following code snippet works on Windows but doesn't work on Linux without any single change.
```c#
using Microsoft.Data.SqlClient;

namespace TestCSharpConnection
{
internal class Program
{
static void Main()
{
// Define your connection string
var connectionString = new SqlConnectionStringBuilder
{
DataSource = "myserver.mycompany.com,1033", // Server name and port
IntegratedSecurity = true, // Enables Windows Authentication (Kerberos)
TrustServerCertificate = true, // Trust the server certificate
ServerSPN = "svc-myuser@REALM"
}.ConnectionString;

try
{
// Establish connection
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
Console.WriteLine("Connected successfully!");

// Create a command to execute a query
using (var command = new SqlCommand(
"SELECT auth_scheme, * FROM sys.dm_exec_connections WHERE session_id = @@SPID", connection))
{
// Execute the query
using (var reader = command.ExecuteReader())
{
if (reader.Read())
{
Console.WriteLine($"SQL Server Auth Scheme: {reader["auth_scheme"]}");
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Connection failed: {ex.Message}");
}
}
}
}
```
Note that if we remove ```ServerSPN = "svc-myuser@REALM"``` then the connection succeeds on both Windows and Linux using Kerberos. (This indicate that Kerbros is setup correctly on the Linux Client)

### Observed Behavior
The connection attempt results in the exception mentioned above, indicating that the target principal name is incorrect and the SSPI context cannot be generated.
- The issue does not occur when the same connection string is used on a Windows client; the connection is established successfully.

### Expected behavior
The connection should succeed in both Windows and Linux.

### Further technical details

- Microsoft.Data.SqlClient version: 5.2.2 and 6.0.1
- .NET target: .NET 8.0
- SQL Server version: SQL Server 2022 CU11 Linux and SQL Server 2022 CU11 Windows
- Client Operating Systems Tested where it is failing:
- Rocky Linux release 8.10 (Green Obsidian) with kernel version `5.4.249-1.el8.x86_64`
- WSL version `2.4.11.0` with kernel version `5.15.167.4-1` on Windows `10.0.22631.4890` running `Ubuntu 22.04`

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.