diff --git a/eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml b/eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml index f0c27e6152..4abe1c9a5f 100644 --- a/eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml +++ b/eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml @@ -195,22 +195,49 @@ steps: displayName: 'Setup SQL Alias [Win]' condition: ${{parameters.condition }} +- powershell: | + # Create Certificate + $computerDnsName = [System.Net.Dns]::Resolve($null).HostName + $certificate = New-SelfSignedCertificate -DnsName $computerDnsName,localhost -CertStoreLocation cert:\LocalMachine\My -FriendlyName test99 -KeySpec KeyExchange + + # Get path to Private key (used later) + $keyPath = $certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName + $machineKeyPath = "$env:ProgramData\Microsoft\Crypto\RSA\MachineKeys\$keyPath" + + # Add certificate to trusted roots + $store = new-object System.Security.Cryptography.X509Certificates.X509Store( + [System.Security.Cryptography.X509Certificates.StoreName]::Root, + "localmachine" + ) + + $store.open("MaxAllowed") + $store.add($certificate) + $store.close() + + # Get SQL Server instances and add the Certificate + $instances = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' + foreach ($instance in $instances){ + $instance | ForEach-Object { + $_.PSObject.Properties | Where-Object { $_.Name -notmatch '^PS.*' } | ForEach-Object { + Write-Output "Configuring instance $($_.Name) (Value: $($_.Value))" + Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($_.Value)\MSSQLServer\SuperSocketNetLib" -Name Certificate -Value $certificate.Thumbprint.ToLower() + + # Grant read access to Private Key for SQL Service Account + if ($($_.Name) -eq "MSSQLSERVER") { + icacls $machineKeyPath /grant "NT Service\MSSQLSERVER:R" + } else { + icacls $machineKeyPath /grant "NT Service\MSSQL`$$($_.Name):R" + } + } + } + } + displayName: 'Add SQL Certificate [Win]' + condition: ${{parameters.condition }} + - powershell: | # You need to restart SQL Server for the change to persist # -Force takes care of any dependent services, like SQL Agent. - # Note: if the instance is named, replace MSSQLSERVER with MSSQL$ followed by - # the name of the instance (e.g. MSSQL$MYINSTANCE) - - $serviceName = "${{parameters.instanceName }}" - $InstancePrefix = 'MSSQL$' - - if ( "${{parameters.instanceName }}" -ne "MSSQLSERVER" ) - { - $serviceName = $InstancePrefix+"${{parameters.instanceName }}" - } - - Restart-Service -Name "$serviceName" -Force - + Get-Service MSSQL* | Restart-Service -Force displayName: 'Restart SQL Server [Win]' condition: ${{parameters.condition }} diff --git a/eng/pipelines/common/templates/steps/run-all-tests-step.yml b/eng/pipelines/common/templates/steps/run-all-tests-step.yml index f0eb2eff66..4f038a18ca 100644 --- a/eng/pipelines/common/templates/steps/run-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/run-all-tests-step.yml @@ -55,6 +55,7 @@ steps: - ${{if eq(parameters.operatingSystem, 'Windows')}}: - task: MSBuild@1 displayName: 'Run Functional Tests ${{parameters.msbuildArchitecture }}' + enabled: false inputs: solution: build.proj msbuildArchitecture: ${{parameters.msbuildArchitecture }} @@ -84,6 +85,7 @@ steps: - ${{ else }}: # Linux or macOS - task: DotNetCoreCLI@2 displayName: 'Run Functional Tests' + enabled: false inputs: command: custom projects: build.proj diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index 41a2a25416..66432123db 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -12,12 +12,12 @@ parameters: - name: targetFrameworks displayName: 'Target Frameworks on Windows' type: object - default: [net462, net8.0, net9.0] + default: [net462, net8.0] - name: targetFrameworksLinux displayName: 'Target Frameworks on Non-Windows' type: object - default: [net8.0, net9.0] + default: [net8.0] - name: netcoreVersionTestUtils displayName: 'Netcore Version for Test Utilities' @@ -32,7 +32,7 @@ parameters: - name: testSets displayName: 'Test Sets' type: object - default: [1, 2, 3] + default: [2] - name: useManagedSNI displayName: | diff --git a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml index 4956b15c89..9bd73fb9cc 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml @@ -5,37 +5,6 @@ ################################################################################# name: $(DayOfYear)$(Rev:rr) -trigger: - batch: true - branches: - include: - - main - - internal/main - paths: - include: - - src\Microsoft.Data.SqlClient\netcore\ref - - src\Microsoft.Data.SqlClient\netfx\ref - - src\Microsoft.Data.SqlClient\ref - - eng - - tools - - .config - - Nuget.config - -schedules: -- cron: '0 4 * * Fri' - displayName: Weekly Thursday 9:00 PM (UTC - 7) Build - branches: - include: - - internal/main - always: true - -- cron: '0 0 * * Mon-Fri' - displayName: Daily build 5:00 PM (UTC - 7) Build - branches: - include: - - main - always: true - parameters: # parameters are shown up in ADO UI in a build queue time - name: 'debug' displayName: 'Enable debug output' diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 54058a9218..84da2d86c4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -361,10 +361,8 @@ public static bool IsAdmin { get { -#if !NETFRAMEWORK - System.Diagnostics.Debug.Assert(OperatingSystem.IsWindows()); -#endif - return new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); + return !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + || new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); } } @@ -454,7 +452,18 @@ public static bool IsAADAuthorityURLSetup() public static bool IsNotAzureServer() { - return !AreConnStringsSetup() || !Utils.IsAzureSqlServer(new SqlConnectionStringBuilder((TCPConnectionString)).DataSource); + return !AreConnStringsSetup() || !Utils.IsAzureSqlServer(new SqlConnectionStringBuilder(TCPConnectionString).DataSource); + } + + public static bool IsNotNamedInstance() + { + return !AreConnStringsSetup() || !new SqlConnectionStringBuilder(TCPConnectionString).DataSource.Contains(@"\"); + } + + public static bool IsLocalHost() + { + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); + return ParseDataSource(builder.DataSource, out string hostname, out _, out _) && string.Equals("localhost", hostname, StringComparison.OrdinalIgnoreCase); } // Synapse: Always Encrypted is not supported with Azure Synapse. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTest.cs index d500391227..0d1714b1c8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTest.cs @@ -9,12 +9,14 @@ using System.Linq; using System.Net; using System.Net.Sockets; +using System.Runtime.InteropServices; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.ServiceProcess; using System.Text; using Microsoft.Win32; using Xunit; +using Xunit.Abstractions; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { @@ -38,6 +40,8 @@ public class CertificateTest : IDisposable // SlashInstance is used to override IPV4 and IPV6 defined about so it includes an instance name private static string SlashInstanceName = ""; + private readonly ITestOutputHelper _testOutputHelper; + private static string ForceEncryptionRegistryPath { get @@ -59,8 +63,9 @@ private static string ForceEncryptionRegistryPath } #endregion - public CertificateTest() + public CertificateTest(ITestOutputHelper testOutputHelper) { + _testOutputHelper = testOutputHelper; SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); Assert.True(DataTestUtility.ParseDataSource(builder.DataSource, out string hostname, out _, out string instanceName)); if (!LocalHost.Equals(hostname, StringComparison.OrdinalIgnoreCase)) @@ -166,8 +171,7 @@ public void OpeningConnectionWitHNICTest() } } - [ActiveIssue("31754")] - [ConditionalFact(nameof(AreConnStringsSetup), nameof(UseManagedSNIOnWindows), nameof(IsNotAzureServer), nameof(IsLocalHost))] + [ConditionalFact(nameof(AreConnStringsSetup), nameof(IsNotAzureServer), nameof(IsLocalHost), nameof(UseManagedSNIOnWindows))] [PlatformSpecific(TestPlatforms.Windows)] public void RemoteCertificateNameMismatchErrorTest() { @@ -175,15 +179,21 @@ public void RemoteCertificateNameMismatchErrorTest() { DataSource = GetLocalIpAddress(), Encrypt = SqlConnectionEncryptOption.Mandatory, + TrustServerCertificate = false, HostNameInCertificate = "BadHostName" }; using SqlConnection connection = new(builder.ConnectionString); SqlException exception = Assert.Throws(() => connection.Open()); - Assert.StartsWith("A connection was successfully established with the server, but then an error occurred during the pre-login handshake. (provider: TCP Provider, error: 35 - An internal exception was caught)", exception.Message); + + _testOutputHelper.WriteLine("Actual exception:"); + _testOutputHelper.WriteLine(exception.ToString()); + + _testOutputHelper.WriteLine("Actual inner exception:"); + _testOutputHelper.WriteLine(exception.InnerException?.ToString() ?? "None"); Assert.Equal(20, exception.Class); + Assert.StartsWith("A connection was successfully established with the server, but then an error occurred during the pre-login handshake. (provider: TCP Provider, error: 35 - An internal exception was caught)", exception.Message); Assert.IsType(exception.InnerException); Assert.StartsWith("Certificate name mismatch. The provided 'DataSource' or 'HostNameInCertificate' does not match the name in the certificate.", exception.InnerException.Message); - Console.WriteLine(exception.Message); } private static void CreateValidCertificate(string script) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SplitPacketTest/SplitPacketTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SplitPacketTest/SplitPacketTest.cs index 1147e704ca..4cfd148d72 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SplitPacketTest/SplitPacketTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SplitPacketTest/SplitPacketTest.cs @@ -11,79 +11,83 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { - [ActiveIssue("5538")] // Only testable on localhost - public class SplitPacketTest + public class SplitPacketTest : IDisposable { - private int Port = -1; - private int SplitPacketSize = 1; - private string BaseConnString; + private int _port = -1; + private int _splitPacketSize = 1; + private string _baseConnString; + private TcpListener _listener; + private CancellationTokenSource _cts = new CancellationTokenSource(); public SplitPacketTest() { - string actualHost; - int actualPort; - SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString); - GetTcpInfoFromDataSource(builder.DataSource, out actualHost, out actualPort); + DataSourceBuilder dataSourceBuilder = new DataSourceBuilder(builder.DataSource); - Task.Factory.StartNew(() => { SetupProxy(actualHost, actualPort); }); + Task.Factory.StartNew(() => { SetupProxy(dataSourceBuilder.ServerName, dataSourceBuilder.Port ?? 1433, _cts.Token); }); - for (int i = 0; i < 10 && Port == -1; i++) + for (int i = 0; i < 10 && _port == -1; i++) { Thread.Sleep(500); } - if (Port == -1) + if (_port == -1) throw new InvalidOperationException("Proxy local port not defined!"); - builder.DataSource = "tcp:127.0.0.1," + Port; - BaseConnString = builder.ConnectionString; + builder.DataSource = "tcp:127.0.0.1," + _port; + _baseConnString = builder.ConnectionString; } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsTCPConnStringSetup), nameof(DataTestUtility.IsLocalHost), nameof(DataTestUtility.IsNotNamedInstance))] public void OneByteSplitTest() { - SplitPacketSize = 1; + _splitPacketSize = 1; OpenConnection(); + Assert.True(true); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsTCPConnStringSetup), nameof(DataTestUtility.IsLocalHost), nameof(DataTestUtility.IsNotNamedInstance))] public void AlmostFullHeaderTest() { - SplitPacketSize = 7; + _splitPacketSize = 7; OpenConnection(); + Assert.True(true); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsTCPConnStringSetup), nameof(DataTestUtility.IsLocalHost), nameof(DataTestUtility.IsNotNamedInstance))] public void FullHeaderTest() { - SplitPacketSize = 8; + _splitPacketSize = 8; OpenConnection(); + Assert.True(true); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsTCPConnStringSetup), nameof(DataTestUtility.IsLocalHost), nameof(DataTestUtility.IsNotNamedInstance))] public void HeaderPlusOneTest() { - SplitPacketSize = 9; + _splitPacketSize = 9; OpenConnection(); + Assert.True(true); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsTCPConnStringSetup), nameof(DataTestUtility.IsLocalHost), nameof(DataTestUtility.IsNotNamedInstance))] public void MARSSplitTest() { - SplitPacketSize = 1; + _splitPacketSize = 1; OpenMarsConnection("select * from Orders"); + Assert.True(true); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsTCPConnStringSetup), nameof(DataTestUtility.IsLocalHost), nameof(DataTestUtility.IsNotNamedInstance))] public void MARSReplicateTest() { - SplitPacketSize = 1; + _splitPacketSize = 1; OpenMarsConnection("select REPLICATE('A', 10000)"); + Assert.True(true); } private void OpenMarsConnection(string cmdText) { - using (SqlConnection conn = new SqlConnection((new SqlConnectionStringBuilder(BaseConnString) { MultipleActiveResultSets = true }).ConnectionString)) + using (SqlConnection conn = new SqlConnection((new SqlConnectionStringBuilder(_baseConnString) { MultipleActiveResultSets = true }).ConnectionString)) { conn.Open(); using (SqlCommand cmd1 = new SqlCommand(cmdText, conn)) @@ -102,7 +106,7 @@ private void OpenMarsConnection(string cmdText) private void OpenConnection() { - using (SqlConnection conn = new SqlConnection(BaseConnString)) + using (SqlConnection conn = new SqlConnection(_baseConnString)) { conn.Open(); using (SqlCommand cmd = new SqlCommand("select * from Orders", conn)) @@ -114,23 +118,23 @@ private void OpenConnection() } } - private void SetupProxy(string actualHost, int actualPort) + private void SetupProxy(string actualHost, int actualPort, CancellationToken cancellationToken) { - TcpListener listener = new TcpListener(IPAddress.Loopback, 0); - listener.Start(); - Port = ((IPEndPoint)listener.LocalEndpoint).Port; - var client = listener.AcceptTcpClientAsync().GetAwaiter().GetResult(); + _listener = new TcpListener(IPAddress.Loopback, 0); + _listener.Start(); + _port = ((IPEndPoint)_listener.LocalEndpoint).Port; + var client = _listener.AcceptTcpClientAsync().GetAwaiter().GetResult(); var sqlClient = new TcpClient(); - sqlClient.ConnectAsync(actualHost, actualPort).Wait(); + sqlClient.ConnectAsync(actualHost, actualPort).Wait(cancellationToken); - Task.Factory.StartNew(() => { ForwardToSql(client, sqlClient); }); - Task.Factory.StartNew(() => { ForwardToClient(client, sqlClient); }); + Task.Factory.StartNew(() => { ForwardToSql(client, sqlClient, cancellationToken); }, cancellationToken); + Task.Factory.StartNew(() => { ForwardToClient(client, sqlClient, cancellationToken); }, cancellationToken); } - private void ForwardToSql(TcpClient ourClient, TcpClient sqlClient) + private void ForwardToSql(TcpClient ourClient, TcpClient sqlClient, CancellationToken cancellationToken) { - while (true) + while (!cancellationToken.IsCancellationRequested) { byte[] buffer = new byte[1024]; int bytesRead = ourClient.GetStream().Read(buffer, 0, buffer.Length); @@ -139,11 +143,11 @@ private void ForwardToSql(TcpClient ourClient, TcpClient sqlClient) } } - private void ForwardToClient(TcpClient ourClient, TcpClient sqlClient) + private void ForwardToClient(TcpClient ourClient, TcpClient sqlClient, CancellationToken cancellationToken) { - while (true) + while (!cancellationToken.IsCancellationRequested) { - byte[] buffer = new byte[SplitPacketSize]; + byte[] buffer = new byte[_splitPacketSize]; int bytesRead = sqlClient.GetStream().Read(buffer, 0, buffer.Length); ourClient.GetStream().Write(buffer, 0, bytesRead); @@ -155,22 +159,24 @@ private void ForwardToClient(TcpClient ourClient, TcpClient sqlClient) } } - private static void GetTcpInfoFromDataSource(string dataSource, out string hostName, out int port) + public void Dispose() { - string[] dataSourceParts = dataSource.Split(','); - if (dataSourceParts.Length == 1) - { - hostName = dataSourceParts[0].Replace("tcp:", ""); - port = 1433; - } - else if (dataSourceParts.Length == 2) - { - hostName = dataSourceParts[0].Replace("tcp:", ""); - port = int.Parse(dataSourceParts[1]); - } - else + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) { - throw new InvalidOperationException("TCP Connection String not in correct format!"); + _cts.Cancel(); + _cts.Dispose(); + _listener?.Server.Dispose(); +#if NETFRAMEWORK + _listener?.Stop(); +#else + _listener?.Dispose(); +#endif } } }