Skip to content

Commit

Permalink
FIX | Adding support for sharedInstances to managed SNI (#1237)
Browse files Browse the repository at this point in the history
  • Loading branch information
Javad authored Sep 20, 2021
1 parent 96a8b05 commit 5cd0f7d
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 34 deletions.
1 change: 1 addition & 0 deletions BUILDGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ Manual Tests require the below setup to run:
|AzureKeyVaultClientId | (Optional) "Application (client) ID" of an Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL`. Requires the key permissions Get, List, Import, Decrypt, Encrypt, Unwrap, Wrap, Verify, and Sign. | _{Client Application ID}_ |
|AzureKeyVaultClientSecret | (Optional) "Client Secret" of the Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL` | _{Client Application Secret}_ |
|LocalDbAppName | (Optional) If Local Db Testing is supported, this property configures the name of Local DB App instance available in client environment. Empty string value disables Local Db testing. | Name of Local Db App to connect to.|
|LocalDbSharedInstanceName | (Optional) If LocalDB testing is supported and the instance is shared, this property configures the name of the shared instance of LocalDB to connect to. | Name of shared instance of LocalDB. |
|SupportsIntegratedSecurity | (Optional) Whether or not the USER running tests has integrated security access to the target SQL Server.| `true` OR `false`|
|FileStreamDirectory | (Optional) If File Stream is enabled on SQL Server, pass local directory path to be used for setting up File Stream enabled database. | `D:\\escaped\\absolute\\path\\to\\directory\\` |
|UseManagedSNIOnWindows | (Optional) Enables testing with Managed SNI on Windows| `true` OR `false`|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;

namespace Microsoft.Data.SqlClient.SNI
{
Expand Down Expand Up @@ -142,7 +143,7 @@ private static bool IsErrorStatus(SecurityStatusPalErrorCode errorCode)
/// <param name="cachedFQDN">Used for DNS Cache</param>
/// <param name="pendingDNSInfo">Used for DNS Cache</param>
/// <returns>SNI handle</returns>
internal static SNIHandle CreateConnectionHandle(string fullServerName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer,
internal static SNIHandle CreateConnectionHandle(string fullServerName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer,
bool flushCache, bool async, bool parallel, bool isIntegratedSecurity, SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo)
{
instanceName = new byte[1];
Expand Down Expand Up @@ -415,6 +416,7 @@ internal enum Protocol { TCP, NP, None, Admin };

private string _workingDataSource;
private string _dataSourceAfterTrimmingProtocol;

internal bool IsBadDataSource { get; private set; } = false;

internal bool IsSsrpRequired { get; private set; } = false;
Expand Down Expand Up @@ -472,31 +474,39 @@ private void PopulateProtocol()
}
}

// LocalDbInstance name always starts with (localdb)
// possible scenarios:
// (localdb)\<instance name>
// or (localdb)\. which goes to default localdb
// or (localdb)\.\<sharedInstance name>
internal static string GetLocalDBInstance(string dataSource, out bool error)
{
string instanceName = null;

string workingDataSource = dataSource.ToLowerInvariant();

string[] tokensByBackSlash = workingDataSource.Split(BackSlashCharacter);

// ReadOnlySpan is not supported in netstandard 2.0, but installing System.Memory solves the issue
ReadOnlySpan<char> input = dataSource.AsSpan().TrimStart();
error = false;

// All LocalDb endpoints are of the format host\instancename where host is always (LocalDb) (case-insensitive)
if (tokensByBackSlash.Length == 2 && LocalDbHost.Equals(tokensByBackSlash[0].TrimStart()))
// NetStandard 2.0 does not support passing a string to ReadOnlySpan<char>
if (input.StartsWith(LocalDbHost.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase))
{
if (!string.IsNullOrWhiteSpace(tokensByBackSlash[1]))
// When netcoreapp support for netcoreapp2.1 is dropped these slice calls could be converted to System.Range\System.Index
// Such ad input = input[1..];
input = input.Slice(LocalDbHost.Length);
if (!input.IsEmpty && input[0] == BackSlashCharacter)
{
instanceName = tokensByBackSlash[1].Trim();
input = input.Slice(1);
}
if (!input.IsEmpty)
{
instanceName = input.Trim().ToString();
}
else
{
SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBNoInstanceName, Strings.SNI_ERROR_51);
error = true;
return null;
}
}

return instanceName;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public static class DataTestUtility
public static readonly string AKVClientId = null;
public static readonly string AKVClientSecret = null;
public static readonly string LocalDbAppName = null;
public static readonly string LocalDbSharedInstanceName = null;
public static List<string> AEConnStrings = new List<string>();
public static List<string> AEConnStringsSetup = new List<string>();
public static bool EnclaveEnabled { get; private set; } = false;
Expand Down Expand Up @@ -84,6 +85,7 @@ static DataTestUtility()
AADServicePrincipalId = c.AADServicePrincipalId;
AADServicePrincipalSecret = c.AADServicePrincipalSecret;
LocalDbAppName = c.LocalDbAppName;
LocalDbSharedInstanceName = c.LocalDbSharedInstanceName;
SupportsIntegratedSecurity = c.SupportsIntegratedSecurity;
FileStreamDirectory = c.FileStreamDirectory;
EnclaveEnabled = c.EnclaveEnabled;
Expand Down Expand Up @@ -441,7 +443,8 @@ public static void DropDatabase(SqlConnection sqlConnection, string dbName)
cmd.ExecuteNonQuery();
}

public static bool IsLocalDBInstalled() => !string.IsNullOrEmpty(LocalDbAppName?.Trim());
public static bool IsLocalDBInstalled() => !string.IsNullOrEmpty(LocalDbAppName?.Trim()) && IsIntegratedSecuritySetup();
public static bool IsLocalDbSharedInstanceSetup() => !string.IsNullOrEmpty(LocalDbSharedInstanceName?.Trim()) && IsIntegratedSecuritySetup();

public static bool IsIntegratedSecuritySetup() => SupportsIntegratedSecurity;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,57 +1,95 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using Xunit;

namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
public static class LocalDBTest
{
private static bool IsLocalDBEnvironmentSet() => DataTestUtility.IsLocalDBInstalled();
private static bool IsLocalDbSharedInstanceSet() => DataTestUtility.IsLocalDbSharedInstanceSetup();
private static readonly string s_localDbConnectionString = @$"server=(localdb)\{DataTestUtility.LocalDbAppName}";
private static readonly string[] s_sharedLocalDbInstances = new string[] { @$"server=(localdb)\.\{DataTestUtility.LocalDbSharedInstanceName}", @$"server=(localdb)\." };
private static readonly string s_badConnectionString = $@"server=(localdb)\{DataTestUtility.LocalDbAppName};Database=DOES_NOT_EXIST;Pooling=false;";

static string LocalDbName = DataTestUtility.LocalDbAppName;
#region LocalDbTests
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
public static void LocalDBConnectionTest()
public static void SqlLocalDbConnectionTest()
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(@$"server=(localdb)\{DataTestUtility.LocalDbAppName}");
builder.IntegratedSecurity = true;
builder.ConnectTimeout = 2;
OpenConnection(builder.ConnectionString);
ConnectionTest(s_localDbConnectionString);
}

[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
public static void LocalDBMarsTest()
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(@$"server=(localdb)\{DataTestUtility.LocalDbAppName}");
builder.IntegratedSecurity = true;
builder.MultipleActiveResultSets = true;
builder.ConnectTimeout = 2;
OpenConnection(builder.ConnectionString);
ConnectionWithMarsTest(s_localDbConnectionString);
}

[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
public static void InvalidDBTest()
public static void InvalidLocalDBTest()
{
using (var connection = new SqlConnection(@$"server=(localdb)\{DataTestUtility.LocalDbAppName};Database=DOES_NOT_EXIST;Pooling=false;"))
using var connection = new SqlConnection(s_badConnectionString);
DataTestUtility.AssertThrowsWrapper<SqlException>(() => connection.Open());
}
#endregion

#region SharedLocalDb tests
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDbSharedInstanceSet))]
public static void SharedLocalDbMarsTest()
{
foreach (string connectionString in s_sharedLocalDbInstances)
{
DataTestUtility.AssertThrowsWrapper<SqlException>(() => connection.Open());
ConnectionWithMarsTest(connectionString);
}
}

private static void OpenConnection(string connString)
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDbSharedInstanceSet))]
public static void SqlLocalDbSharedInstanceConnectionTest()
{
using (SqlConnection connection = new SqlConnection(connString))
foreach (string connectionString in s_sharedLocalDbInstances)
{
connection.Open();
using (SqlCommand command = new SqlCommand("SELECT @@SERVERNAME", connection))
{
var result = command.ExecuteScalar();
Assert.NotNull(result);
}
ConnectionTest(connectionString);
}
}
#endregion

private static void ConnectionWithMarsTest(string connectionString)
{
SqlConnectionStringBuilder builder = new(connectionString)
{
IntegratedSecurity = true,
MultipleActiveResultSets = true,
ConnectTimeout = 2,
Encrypt = false
};
OpenConnection(builder.ConnectionString);
}
private static void ConnectionTest(string connectionString)
{
SqlConnectionStringBuilder builder = new(connectionString)
{
IntegratedSecurity = true,
ConnectTimeout = 2,
Encrypt = false
};
OpenConnection(builder.ConnectionString);
}

private static void OpenConnection(string connString)
{
using SqlConnection connection = new(connString);
connection.Open();
using SqlCommand command = new SqlCommand("SELECT @@SERVERNAME", connection);
var result = command.ExecuteScalar();
Assert.NotNull(result);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class Config
public string AzureKeyVaultClientId = null;
public string AzureKeyVaultClientSecret = null;
public string LocalDbAppName = null;
public string LocalDbSharedInstanceName = null;
public bool EnclaveEnabled = false;
public bool TracingEnabled = false;
public bool SupportsIntegratedSecurity = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"AzureKeyVaultClientSecret": "",
"SupportsIntegratedSecurity": true,
"LocalDbAppName": "",
"SupportsFileStream": false,
"FileStreamDirectory": "",
"UseManagedSNIOnWindows": false,
"DNSCachingConnString": "",
Expand Down

0 comments on commit 5cd0f7d

Please sign in to comment.