diff --git a/Microsoft.Azure.Cosmos/src/CosmosClient.cs b/Microsoft.Azure.Cosmos/src/CosmosClient.cs index 7b143196fe..89c628635f 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClient.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClient.cs @@ -183,7 +183,9 @@ protected CosmosClient() /// /// /// - /// The returned reference doesn't guarantee credentials or connectivity validations because creation doesn't make any network calls. + /// Emulator: To ignore SSL Certificate please suffix connectionstring with "DisableServerCertificateValidation=True;". + /// When CosmosClientOptions.HttpClientFactory is used, SSL certificate needs to be handled appropriately. + /// NOTE: DO NOT use this flag in production (only for emulator) /// /// /// @@ -195,7 +197,7 @@ public CosmosClient( : this( CosmosClientOptions.GetAccountEndpoint(connectionString), CosmosClientOptions.GetAccountKey(connectionString), - clientOptions) + CosmosClientOptions.GetCosmosClientOptionsWithCertificateFlag(connectionString, clientOptions)) { } @@ -495,6 +497,11 @@ public static async Task CreateAndInitializeAsync(string accountEn /// ]]> /// /// + /// + /// Emulator: To ignore SSL Certificate please suffix connectionstring with "DisableServerCertificateValidation=True;". + /// When CosmosClientOptions.HttpClientFactory is used, SSL certificate needs to be handled appropriately. + /// NOTE: DO NOT use this flag in production (only for emulator) + /// public static async Task CreateAndInitializeAsync(string connectionString, IReadOnlyList<(string databaseId, string containerId)> containers, CosmosClientOptions cosmosClientOptions = null, @@ -504,6 +511,7 @@ public static async Task CreateAndInitializeAsync(string connectio { throw new ArgumentNullException(nameof(containers)); } + cosmosClientOptions = CosmosClientOptions.GetCosmosClientOptionsWithCertificateFlag(connectionString, cosmosClientOptions); CosmosClient cosmosClient = new CosmosClient(connectionString, cosmosClientOptions); diff --git a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs index c49183e435..1fd66c3169 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs @@ -51,6 +51,7 @@ public class CosmosClientOptions private const string ConnectionStringAccountEndpoint = "AccountEndpoint"; private const string ConnectionStringAccountKey = "AccountKey"; + private const string ConnectionStringDisableServerCertificateValidation = "DisableServerCertificateValidation"; private const ApiType DefaultApiType = ApiType.None; @@ -651,7 +652,9 @@ internal Protocol ConnectionProtocol /// /// /// - /// Customizing SSL verification is not recommended in production environments. + /// Emulator: To ignore SSL Certificate please suffix connectionstring with "DisableServerCertificateValidation=True;". + /// When CosmosClientOptions.HttpClientFactory is used, SSL certificate needs to be handled appropriately. + /// NOTE: DO NOT use this flag in production (only for emulator) /// /// public Func ServerCertificateCustomValidationCallback { get; set; } @@ -843,34 +846,62 @@ internal virtual ConnectionPolicy GetConnectionPolicy(int clientId) return (Documents.ConsistencyLevel)this.ConsistencyLevel.Value; } - internal static string GetAccountEndpoint(string connectionString) - { - return CosmosClientOptions.GetValueFromConnectionString(connectionString, CosmosClientOptions.ConnectionStringAccountEndpoint); - } - - internal static string GetAccountKey(string connectionString) - { - return CosmosClientOptions.GetValueFromConnectionString(connectionString, CosmosClientOptions.ConnectionStringAccountKey); - } - - private static string GetValueFromConnectionString(string connectionString, string keyName) - { - if (connectionString == null) - { - throw new ArgumentNullException(nameof(connectionString)); - } - - DbConnectionStringBuilder builder = new DbConnectionStringBuilder { ConnectionString = connectionString }; - if (builder.TryGetValue(keyName, out object value)) - { - string keyNameValue = value as string; - if (!string.IsNullOrEmpty(keyNameValue)) - { - return keyNameValue; - } - } - - throw new ArgumentException("The connection string is missing a required property: " + keyName); + internal static string GetAccountEndpoint(string connectionString) + { + return CosmosClientOptions.GetValueFromConnectionString(connectionString, CosmosClientOptions.ConnectionStringAccountEndpoint, null); + } + + internal static string GetAccountKey(string connectionString) + { + return CosmosClientOptions.GetValueFromConnectionString(connectionString, CosmosClientOptions.ConnectionStringAccountKey, null); + } + + internal static bool IsConnectionStringDisableServerCertificateValidationFlag(string connectionString) + { + return Convert.ToBoolean(CosmosClientOptions.GetValueFromConnectionString(connectionString, CosmosClientOptions.ConnectionStringDisableServerCertificateValidation, false)); + } + + internal static CosmosClientOptions GetCosmosClientOptionsWithCertificateFlag(string connectionString, CosmosClientOptions clientOptions) + { + clientOptions ??= new CosmosClientOptions(); + if (CosmosClientOptions.IsConnectionStringDisableServerCertificateValidationFlag(connectionString)) + { + clientOptions.ServerCertificateCustomValidationCallback = (_, _, _) => true; + } + + return clientOptions; + } + + private static T GetValueFromConnectionString(string connectionString, string keyName, T defaultValue) + { + if (connectionString == null) + { + throw new ArgumentNullException(nameof(connectionString)); + } + + DbConnectionStringBuilder builder = new DbConnectionStringBuilder { ConnectionString = connectionString }; + if (builder.TryGetValue(keyName, out object value)) + { + string keyNameValue = value as string; + if (!string.IsNullOrEmpty(keyNameValue)) + { + try + { + return (T)Convert.ChangeType(value, typeof(T)); + } + catch (InvalidCastException) + { + throw new ArgumentException("The connection string contains invalid property: " + keyName); + } + } + } + + if (defaultValue != null) + { + return defaultValue; + } + + throw new ArgumentException("The connection string is missing a required property: " + keyName); } private void ValidateLimitToEndpointSettings() diff --git a/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs b/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs index 5258987a35..e030411338 100644 --- a/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs @@ -124,6 +124,11 @@ public CosmosClientBuilder( /// /// "AccountEndpoint=https://mytestcosmosaccount.documents.azure.com:443/;AccountKey={SecretAccountKey};" /// The connection string must contain AccountEndpoint and AccountKey or ResourceToken. + /// + /// Emulator: To ignore SSL Certificate please suffix connectionstring with "DisableServerCertificateValidation=True;". + /// When CosmosClientOptions.HttpClientFactory is used, SSL certificate needs to be handled appropriately. + /// NOTE: DO NOT use this flag in production (only for emulator) + /// public CosmosClientBuilder(string connectionString) { if (connectionString == null) @@ -133,6 +138,8 @@ public CosmosClientBuilder(string connectionString) this.accountEndpoint = CosmosClientOptions.GetAccountEndpoint(connectionString); this.accountKey = CosmosClientOptions.GetAccountKey(connectionString); + + this.clientOptions = CosmosClientOptions.GetCosmosClientOptionsWithCertificateFlag(connectionString, this.clientOptions); } /// diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs index b625858c09..51aee132ae 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs @@ -7,10 +7,12 @@ namespace Microsoft.Azure.Cosmos.Tests using System; using System.Collections; using System.Collections.Generic; - using System.Collections.ObjectModel; using System.Linq; using System.Net; - using System.Net.Http; + using System.Net.Http; + using System.Net.Security; + using System.Security.Cryptography; + using System.Security.Cryptography.X509Certificates; using global::Azure.Core; using Microsoft.Azure.Cosmos.Fluent; using Microsoft.Azure.Documents; @@ -885,7 +887,32 @@ public void InvalidApplicationNameCatchTest() ApplicationName = illegal }); } - } + } + + [TestMethod] + [DataRow(ConnectionString, false)] + [DataRow(ConnectionString + "DisableServerCertificateValidation=true;", true)] + public void TestServerCertificatesValidationCallback(string connStr, bool expectedIgnoreCertificateFlag) + { + //Arrange + X509Certificate2 x509Certificate2 = new CertificateRequest("cn=www.test", ECDsa.Create(), HashAlgorithmName.SHA256).CreateSelfSigned(DateTime.Now, DateTime.Now.AddYears(1)); + X509Chain x509Chain = new X509Chain(); + SslPolicyErrors sslPolicyErrors = new SslPolicyErrors(); + + CosmosClient cosmosClient = new CosmosClient(connStr); + + if (expectedIgnoreCertificateFlag) + { + Assert.IsNotNull(cosmosClient.ClientOptions.ServerCertificateCustomValidationCallback); + Assert.IsTrue(cosmosClient + .ClientOptions + .ServerCertificateCustomValidationCallback(x509Certificate2, x509Chain, sslPolicyErrors)); + } + else + { + Assert.IsNull(cosmosClient.ClientOptions.ServerCertificateCustomValidationCallback); + } + } private class TestWebProxy : IWebProxy {