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, 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
- Configure a SQL Server instance to run under a specific domain account.
- On a Linux client, setup Kerberos authentication, and set up a connection string that includes the
ServerSPN
attribute, specifying the account in the formatUsername@Domain
(example: svc-myuser@REALM). - 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.
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 version5.15.167.4-1
on Windows10.0.22631.4890
runningUbuntu 22.04
- Rocky Linux release 8.10 (Green Obsidian) with kernel version