Skip to content
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

Feature | Add support server SPN option #1607

Merged
merged 17 commits into from
Jun 16, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ End Module
|Encrypt|'true' in 4.0 and above<br/><br/>'false' in 3.x and below|Recognized values are:<br/>versions 1 - 4: `true`/`yes` and `false`/`no`<br/>versions 5+: `true`/`yes`/`mandatory`, `false`/`no`/`optional` and `strict`. When `true`, TLS encryption is used for all data sent between the client and server if the server has a certificate installed. When `strict`, TDS 8.0 TLS encryption is used and the `TrustServerCertificate` setting is ignored and treated as false. For more information, see [Connection String Syntax](/sql/connect/ado-net/connection-string-syntax).<br /><br /> When `Encrypt` is true or strict and `TrustServerCertificate` is false, the server name (or IP address) in a server's certificate must exactly match the server name (or IP address) specified in the connection string. Otherwise, the connection attempt will fail. For information about support for certificates whose subject starts with a wildcard character (*), see [Accepted wildcards used by server certificates for server authentication](https://support.microsoft.com/kb/258858).|
|Enlist|'true'|`true` indicates that the SQL Server connection pooler automatically enlists the connection in the creation thread's current transaction context.|
|Failover Partner|N/A|The name of the failover partner server where database mirroring is configured.<br /><br /> If the value of this key is "", then **Initial Catalog** must be present, and its value must not be "".<br /><br /> The server name can be 128 characters or less.<br /><br /> If you specify a failover partner but the failover partner server is not configured for database mirroring and the primary server (specified with the Server keyword) is not available, then the connection will fail.<br /><br /> If you specify a failover partner and the primary server is not configured for database mirroring, the connection to the primary server (specified with the Server keyword) will succeed if the primary server is available.|
|Failover Partner SPN<br /><br /> -or-<br /><br /> FailoverPartnerSPN|N/A|The SPN for the failover partner. The default value is an empty string, which causes SqlClient to use the default, driver-generated SPN.<br /><br /> (Only available in v5.0+)|
|Host Name In Certificate<br /><br /> -or-<br /><br />HostNameInCertificate|N/A|Available starting in version 5.0.<br/><br/>The host name to use when validating the server certificate. When not specified, the server name from the Data Source is used for certificate validation.|
|Initial Catalog<br /><br /> -or-<br /><br /> Database|N/A|The name of the database.<br /><br /> The database name can be 128 characters or less.|
|Integrated Security<br /><br /> -or-<br /><br /> Trusted_Connection|'false'|When `false`, User ID and Password are specified in the connection. When `true`, the current Windows account credentials are used for authentication.<br /><br /> Recognized values are `true`, `false`, `yes`, `no`, and `sspi` (strongly recommended), which is equivalent to `true`.<br /><br /> If User ID and Password are specified and Integrated Security is set to true, the User ID and Password will be ignored and Integrated Security will be used.<br /><br /> <xref:Microsoft.Data.SqlClient.SqlCredential> is a more secure way to specify credentials for a connection that uses SQL Server Authentication (`Integrated Security=false`).|
Expand All @@ -556,6 +557,7 @@ End Module
|Pool Blocking Period<br /><br /> -or-<br /><br />PoolBlockingPeriod|Auto|Sets the blocking period behavior for a connection pool. See <xref:Microsoft.Data.SqlClient.SqlConnectionStringBuilder.PoolBlockingPeriod> property for details.|
|Pooling|'true'|When the value of this key is set to true, any newly created connection will be added to the pool when closed by the application. In a next attempt to open the same connection, that connection will be drawn from the pool.<br /><br /> Connections are considered the same if they have the same connection string. Different connections have different connection strings.<br /><br /> The value of this key can be "true", "false", "yes", or "no".|
|Replication|'false'|`true` if replication is supported using the connection.|
|Server SPN<br /><br /> -or-<br /><br /> ServerSPN|N/A|The SPN for the data source. The default value is an empty string, which causes SqlClient to use the default, driver-generated SPN.<br /><br /> (Only available in v5.0+)|
|Transaction Binding|Implicit Unbind|Controls connection association with an enlisted `System.Transactions` transaction.<br /><br /> Possible values are:<br /><br /> `Transaction Binding=Implicit Unbind;`<br /><br /> `Transaction Binding=Explicit Unbind;`<br /><br /> Implicit Unbind causes the connection to detach from the transaction when it ends. After detaching, additional requests on the connection are performed in autocommit mode. The `System.Transactions.Transaction.Current` property is not checked when executing requests while the transaction is active. After the transaction has ended, additional requests are performed in autocommit mode.<br /><br /> If the system ends the transaction (in the scope of a using block) before the last command completes, it will throw <xref:System.InvalidOperationException>.<br /><br /> Explicit Unbind causes the connection to remain attached to the transaction until the connection is closed or an explicit `SqlConnection.TransactionEnlist(null)` is called. Beginning in .NET Framework 4.0, changes to Implicit Unbind make Explicit Unbind obsolete. An `InvalidOperationException` is thrown if `Transaction.Current` is not the enlisted transaction or if the enlisted transaction is not active.|
|Transparent Network IP Resolution<br /><br /> -or-<br /><br />TransparentNetworkIPResolution|See description.|When the value of this key is set to `true`, the application is required to retrieve all IP addresses for a particular DNS entry and attempt to connect with the first one in the list. If the connection is not established within 0.5 seconds, the application will try to connect to all others in parallel. When the first answers, the application will establish the connection with the respondent IP address.<br /><br /> If the `MultiSubnetFailover` key is set to `true`, `TransparentNetworkIPResolution` is ignored.<br /><br /> If the `Failover Partner` key is set, `TransparentNetworkIPResolution` is ignored.<br /><br /> The value of this key must be `true`, `false`, `yes`, or `no`.<br /><br /> A value of `yes` is treated the same as a value of `true`.<br /><br /> A value of `no` is treated the same as a value of `false`.<br /><br /> The default values are as follows:<br /><br /> <ul><li>`false` when:<br /><br /> <ul><li>Connecting to Azure SQL Database where the data source ends with:<br /><br /> <ul><li>.database.chinacloudapi.cn</li><li>.database.usgovcloudapi.net</li><li>.database.cloudapi.de</li><li>.database.windows.net</li></ul></li><li>`Authentication` is 'Active Directory Password' or 'Active Directory Integrated'</li></ul></li><li>`true` in all other cases.</li></ul>|
|Trust Server Certificate<br /><br /> -or-<br /><br />TrustServerCertificate|'false'|When set to `true`, TLS is used to encrypt the channel when bypassing walking the certificate chain to validate trust. If TrustServerCertificate is set to `true` and Encrypt is set to `false`, the channel is not encrypted. Recognized values are `true`, `false`, `yes`, and `no`. For more information, see [Connection String Syntax](/sql/connect/ado-net/connection-string-syntax).|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,25 @@ If you specify a failover partner and the primary server is not configured for d
]]></format>
</remarks>
</FailoverPartner>
<FailoverPartnerSPN>
<summary>Gets or sets the service principal name (SPN) of the failover partner for the connection.</summary>
<value>
The value of the <see cref="P:Microsoft.Data.SqlClient.SqlConnectionStringBuilder.FailoverPartnerSPN" /> property, or <see langword="String.Empty" /> if none has been supplied.
</value>
<remarks>
<format type="text/markdown">
<![CDATA[

## Remarks
This property corresponds to the "FailoverPartnerSPN" and "Failover Partner SPN" keys within the connection string.

> [!NOTE]
> This property only applies when using Integrated Security mode, otherwise it is ignored.

]]>
</format>
</remarks>
</FailoverPartnerSPN>
<GetProperties>
<param name="propertyDescriptors">To be added.</param>
<summary>To be added.</summary>
Expand Down Expand Up @@ -820,6 +839,25 @@ Database = AdventureWorks
]]></format>
</remarks>
</Replication>
<ServerSPN>
<summary>Gets or sets the service principal name (SPN) of the data source.</summary>
<value>
The value of the <see cref="P:Microsoft.Data.SqlClient.SqlConnectionStringBuilder.ServerSPN" /> property, or <see langword="String.Empty" /> if none has been supplied.
</value>
<remarks>
<format type="text/markdown">
<![CDATA[

## Remarks
This property corresponds to the "ServerSPN" and "Server SPN" keys within the connection string.

> [!NOTE]
> This property only applies when using Integrated Security mode, otherwise it is ignored.

]]>
</format>
</remarks>
</ServerSPN>
<ShouldSerialize>
<param name="keyword">The key to locate in the <see cref="T:Microsoft.Data.SqlClient.SqlConnectionStringBuilder" />.</param>
<summary>Indicates whether the specified key exists in this <see cref="T:Microsoft.Data.SqlClient.SqlConnectionStringBuilder" /> instance.</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,10 @@ public SqlConnectionStringBuilder(string connectionString) { }
[System.ComponentModel.DisplayNameAttribute("Failover Partner")]
[System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
public string FailoverPartner { get { throw null; } set { } }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml' path='docs/members[@name="SqlConnectionStringBuilder"]/FailoverPartnerSPN/*'/>
[System.ComponentModel.DisplayNameAttribute("Failover Partner SPN")]
[System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
public string FailoverPartnerSPN { get { throw null; } set { } }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml' path='docs/members[@name="SqlConnectionStringBuilder"]/InitialCatalog/*'/>
[System.ComponentModel.DisplayNameAttribute("Initial Catalog")]
[System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
Expand Down Expand Up @@ -1095,6 +1099,10 @@ public SqlConnectionStringBuilder(string connectionString) { }
[System.ComponentModel.DisplayNameAttribute("Replication")]
[System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
public bool Replication { get { throw null; } set { } }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml' path='docs/members[@name="SqlConnectionStringBuilder"]/ServerSPN/*'/>
[System.ComponentModel.DisplayNameAttribute("Server SPN")]
[System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
public string ServerSPN { get { throw null; } set { } }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml' path='docs/members[@name="SqlConnectionStringBuilder"]/TransactionBinding/*'/>
[System.ComponentModel.DisplayNameAttribute("Transaction Binding")]
[System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;

namespace Microsoft.Data.SqlClient.SNI
{
Expand Down Expand Up @@ -69,7 +68,7 @@ internal static void GenSspiClientContext(SspiClientContextStatus sspiClientCont
string[] serverSPNs = new string[serverName.Length];
for (int i = 0; i < serverName.Length; i++)
{
serverSPNs[i] = Encoding.UTF8.GetString(serverName[i]);
serverSPNs[i] = Encoding.Unicode.GetString(serverName[i]);
}
SecurityStatusPal statusCode = NegotiateStreamPal.InitializeSecurityContext(
credentialsHandle,
Expand Down Expand Up @@ -135,6 +134,7 @@ private static bool IsErrorStatus(SecurityStatusPalErrorCode errorCode)
/// <param name="timerExpire">Timer expiration</param>
/// <param name="instanceName">Instance name</param>
/// <param name="spnBuffer">SPN</param>
/// <param name="serverSPN">pre-defined SPN</param>
/// <param name="flushCache">Flush packet cache</param>
/// <param name="async">Asynchronous connection</param>
/// <param name="parallel">Attempt parallel connects</param>
Expand All @@ -151,6 +151,7 @@ internal static SNIHandle CreateConnectionHandle(
long timerExpire,
out byte[] instanceName,
ref byte[][] spnBuffer,
string serverSPN,
bool flushCache,
bool async,
bool parallel,
Expand Down Expand Up @@ -200,7 +201,7 @@ internal static SNIHandle CreateConnectionHandle(
{
try
{
spnBuffer = GetSqlServerSPNs(details);
spnBuffer = GetSqlServerSPNs(details, serverSPN);
}
catch (Exception e)
{
Expand All @@ -212,9 +213,13 @@ internal static SNIHandle CreateConnectionHandle(
return sniHandle;
}

private static byte[][] GetSqlServerSPNs(DataSource dataSource)
private static byte[][] GetSqlServerSPNs(DataSource dataSource, string serverSPN)
{
Debug.Assert(!string.IsNullOrWhiteSpace(dataSource.ServerName));
if (!string.IsNullOrWhiteSpace(serverSPN))
{
return new byte[1][] { Encoding.Unicode.GetBytes(serverSPN) };
}

string hostName = dataSource.ServerName;
string postfix = null;
Expand Down Expand Up @@ -262,12 +267,12 @@ private static byte[][] GetSqlServerSPNs(string hostNameOrAddress, string portOr
string serverSpnWithDefaultPort = serverSpn + $":{DefaultSqlServerPort}";
// Set both SPNs with and without Port as Port is optional for default instance
SqlClientEventSource.Log.TryAdvancedTraceEvent("SNIProxy.GetSqlServerSPN | Info | ServerSPNs {0} and {1}", serverSpn, serverSpnWithDefaultPort);
return new byte[][] { Encoding.UTF8.GetBytes(serverSpn), Encoding.UTF8.GetBytes(serverSpnWithDefaultPort) };
return new byte[][] { Encoding.Unicode.GetBytes(serverSpn), Encoding.Unicode.GetBytes(serverSpnWithDefaultPort) };
}
// else Named Pipes do not need to valid port

SqlClientEventSource.Log.TryAdvancedTraceEvent("SNIProxy.GetSqlServerSPN | Info | ServerSPN {0}", serverSpn);
return new byte[][] { Encoding.UTF8.GetBytes(serverSpn) };
return new byte[][] { Encoding.Unicode.GetBytes(serverSpn) };
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1552,7 +1552,7 @@ private void LoginNoFailover(ServerInfo serverInfo,
throw SQL.ROR_TimeoutAfterRoutingInfo(this);
}

serverInfo = new ServerInfo(ConnectionOptions, RoutingInfo, serverInfo.ResolvedServerName);
serverInfo = new ServerInfo(ConnectionOptions, RoutingInfo, serverInfo.ResolvedServerName, serverInfo.ServerSPN);
_timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination);
_originalClientConnectionId = _clientConnectionId;
_routingDestination = serverInfo.UserServerName;
Expand Down Expand Up @@ -1696,7 +1696,7 @@ TimeoutTimer timeout
int sleepInterval = 100; //milliseconds to sleep (back off) between attempts.
long timeoutUnitInterval;

ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost);
ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost, connectionOptions.FailoverPartnerSPN);

ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions);
if (null == ServerProvidedFailOverPartner)
Expand Down Expand Up @@ -1910,11 +1910,8 @@ private void AttemptOneLogin(
this,
ignoreSniOpenTimeout,
timeout.LegacyTimerExpire,
ConnectionOptions.Encrypt,
ConnectionOptions.TrustServerCertificate,
ConnectionOptions.IntegratedSecurity,
ConnectionOptions,
withFailover,
ConnectionOptions.Authentication,
ConnectionOptions.HostNameInCertificate);
DavoudEshtehari marked this conversation as resolved.
Show resolved Hide resolved

_timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake);
Expand Down Expand Up @@ -2797,6 +2794,7 @@ internal sealed class ServerInfo
internal string ResolvedServerName { get; private set; } // the resolved servername only
internal string ResolvedDatabaseName { get; private set; } // name of target database after resolution
internal string UserProtocol { get; private set; } // the user specified protocol
internal string ServerSPN { get; private set; } // the server SPN

// The original user-supplied server name from the connection string.
// If connection string has no Data Source, the value is set to string.Empty.
Expand All @@ -2817,10 +2815,16 @@ private set
internal readonly string PreRoutingServerName;

// Initialize server info from connection options,
internal ServerInfo(SqlConnectionString userOptions) : this(userOptions, userOptions.DataSource) { }
internal ServerInfo(SqlConnectionString userOptions) : this(userOptions, userOptions.DataSource, userOptions.ServerSPN) { }

// Initialize server info from connection options, but override DataSource and ServerSPN with given server name and server SPN
internal ServerInfo(SqlConnectionString userOptions, string serverName, string serverSPN) : this(userOptions, serverName)
{
ServerSPN = serverSPN;
}

// Initialize server info from connection options, but override DataSource with given server name
internal ServerInfo(SqlConnectionString userOptions, string serverName)
private ServerInfo(SqlConnectionString userOptions, string serverName)
{
//-----------------
// Preconditions
Expand All @@ -2839,7 +2843,7 @@ internal ServerInfo(SqlConnectionString userOptions, string serverName)


// Initialize server info from connection options, but override DataSource with given server name
internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string preRoutingServerName)
internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string preRoutingServerName, string serverSPN)
{
//-----------------
// Preconditions
Expand All @@ -2860,6 +2864,7 @@ internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string
UserProtocol = TdsEnums.TCP;
SetDerivedNames(UserProtocol, UserServerName);
ResolvedDatabaseName = userOptions.InitialCatalog;
ServerSPN = serverSPN;
}

internal void SetDerivedNames(string protocol, string serverName)
Expand Down
Loading