This is an automated email from the ASF dual-hosted git repository.

curth pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git


The following commit(s) were added to refs/heads/main by this push:
     new c94cd3816 feat(csharp/src/Drivers/Apache): Implement Standard SSL mode 
for Impala (#2745)
c94cd3816 is described below

commit c94cd3816444b854b452031b0ef423e90ad141c3
Author: Sudhir Reddy Emmadi <[email protected]>
AuthorDate: Fri May 2 22:25:49 2025 +0530

    feat(csharp/src/Drivers/Apache): Implement Standard SSL mode for Impala 
(#2745)
    
    Co-authored-by: Sudhir Emmadi <[email protected]>
---
 .../Drivers/Apache/Hive2/HiveServer2Parameters.cs  |  9 ++++
 .../src/Drivers/Apache/Hive2/HiveServer2TlsImpl.cs | 61 ++++++++++++++++++++--
 .../Apache/Impala/ImpalaStandardConnection.cs      | 23 ++++++--
 csharp/src/Drivers/Apache/Impala/README.md         |  9 +++-
 .../Drivers/Apache/Hive2/HiveServer2TlsImplTest.cs | 42 +++++++++++++--
 5 files changed, 132 insertions(+), 12 deletions(-)

diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2Parameters.cs 
b/csharp/src/Drivers/Apache/Hive2/HiveServer2Parameters.cs
index f1bb90f11..5d44948bf 100644
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2Parameters.cs
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2Parameters.cs
@@ -54,4 +54,13 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
         public const string TrustedCertificatePath = 
"adbc.http_options.tls.trusted_certificate_path";
         public const string DisableServerCertificateValidation = 
"adbc.http_options.tls.disable_server_certificate_validation";
     }
+
+    public static class StandardTlsOptions
+    {
+        public const string IsTlsEnabled = "adbc.standard_options.tls.enabled";
+        public const string AllowSelfSigned = 
"adbc.standard_options.tls.allow_self_signed";
+        public const string AllowHostnameMismatch = 
"adbc.standard_options.tls.allow_hostname_mismatch";
+        public const string TrustedCertificatePath = 
"adbc.standard_options.tls.trusted_certificate_path";
+        public const string DisableServerCertificateValidation = 
"adbc.standard_options.tls.disable_server_certificate_validation";
+    }
 }
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2TlsImpl.cs 
b/csharp/src/Drivers/Apache/Hive2/HiveServer2TlsImpl.cs
index 7c3d51c3f..b856d38d7 100644
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2TlsImpl.cs
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2TlsImpl.cs
@@ -37,15 +37,19 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
     {
         static internal TlsProperties 
GetHttpTlsOptions(IReadOnlyDictionary<string, string> properties)
         {
-            TlsProperties tlsProperties = new TlsProperties();
+            TlsProperties tlsProperties = new();
             if (properties.TryGetValue(AdbcOptions.Uri, out string? uri) && 
!string.IsNullOrWhiteSpace(uri))
             {
                 var uriValue = new Uri(uri);
-                tlsProperties.IsTlsEnabled = uriValue.Scheme == 
Uri.UriSchemeHttps || (properties.TryGetValue(HttpTlsOptions.IsTlsEnabled, out 
string? isSslEnabled) && bool.TryParse(isSslEnabled, out bool isSslEnabledBool) 
&& isSslEnabledBool);
+                tlsProperties.IsTlsEnabled = uriValue.Scheme == 
Uri.UriSchemeHttps || !properties.TryGetValue(HttpTlsOptions.IsTlsEnabled, out 
string? isTlsEnabled) || !bool.TryParse(isTlsEnabled, out bool 
isTlsEnabledBool) || isTlsEnabledBool;
             }
-            else if (properties.TryGetValue(HttpTlsOptions.IsTlsEnabled, out 
string? isSslEnabled) && bool.TryParse(isSslEnabled, out bool isSslEnabledBool))
+            else if (!properties.TryGetValue(HttpTlsOptions.IsTlsEnabled, out 
string? isTlsEnabled) || !bool.TryParse(isTlsEnabled, out bool 
isTlsEnabledBool))
             {
-                tlsProperties.IsTlsEnabled = isSslEnabledBool;
+                tlsProperties.IsTlsEnabled = true;
+            }
+            else
+            {
+                tlsProperties.IsTlsEnabled = isTlsEnabledBool;
             }
             if (!tlsProperties.IsTlsEnabled)
             {
@@ -97,5 +101,54 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
             }
             return httpClientHandler;
         }
+
+        static internal TlsProperties 
GetStandardTlsOptions(IReadOnlyDictionary<string, string> properties)
+        {
+            TlsProperties tlsProperties = new();
+            // tls is enabled by default
+            if (!properties.TryGetValue(StandardTlsOptions.IsTlsEnabled, out 
string? isTlsEnabled) || !bool.TryParse(isTlsEnabled, out bool 
isTlsEnabledBool))
+            {
+                tlsProperties.IsTlsEnabled = true;
+            }
+            else
+            {
+                tlsProperties.IsTlsEnabled = isTlsEnabledBool;
+            }
+            if (!tlsProperties.IsTlsEnabled)
+            {
+                return tlsProperties;
+            }
+
+            if 
(properties.TryGetValue(StandardTlsOptions.DisableServerCertificateValidation, 
out string? disableServerCertificateValidation) && 
bool.TryParse(disableServerCertificateValidation, out bool 
disableServerCertificateValidationBool) && 
disableServerCertificateValidationBool)
+            {
+                tlsProperties.DisableServerCertificateValidation = true;
+                return tlsProperties;
+            }
+            tlsProperties.DisableServerCertificateValidation = false;
+            tlsProperties.AllowHostnameMismatch = 
properties.TryGetValue(StandardTlsOptions.AllowHostnameMismatch, out string? 
allowHostnameMismatch) && bool.TryParse(allowHostnameMismatch, out bool 
allowHostnameMismatchBool) && allowHostnameMismatchBool;
+            tlsProperties.AllowSelfSigned = 
properties.TryGetValue(StandardTlsOptions.AllowSelfSigned, out string? 
allowSelfSigned) && bool.TryParse(allowSelfSigned, out bool 
allowSelfSignedBool) && allowSelfSignedBool;
+            if (tlsProperties.AllowSelfSigned)
+            {
+                if 
(!properties.TryGetValue(StandardTlsOptions.TrustedCertificatePath, out string? 
trustedCertificatePath)) return tlsProperties;
+                tlsProperties.TrustedCertificatePath = trustedCertificatePath 
!= "" && File.Exists(trustedCertificatePath) ? trustedCertificatePath : throw 
new FileNotFoundException("Trusted certificate path is invalid or file does not 
exist.");
+            }
+            return tlsProperties;
+        }
+
+        static internal RemoteCertificateValidationCallback 
GetCertificateValidator(TlsProperties tlsProperties)
+        {
+            return (object sender, X509Certificate? certificate, X509Chain? 
chain, SslPolicyErrors policyErrors) =>
+            {
+                if (policyErrors == SslPolicyErrors.None || 
tlsProperties.DisableServerCertificateValidation) return true;
+                if (string.IsNullOrEmpty(tlsProperties.TrustedCertificatePath))
+                {
+                    return
+                        
(!policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors) || 
tlsProperties.AllowSelfSigned)
+                        && 
(!policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch) || 
tlsProperties.AllowHostnameMismatch);
+                }
+
+                return false;
+            };
+        }
     }
 }
diff --git a/csharp/src/Drivers/Apache/Impala/ImpalaStandardConnection.cs 
b/csharp/src/Drivers/Apache/Impala/ImpalaStandardConnection.cs
index f5c84e2cd..c1cc1320e 100644
--- a/csharp/src/Drivers/Apache/Impala/ImpalaStandardConnection.cs
+++ b/csharp/src/Drivers/Apache/Impala/ImpalaStandardConnection.cs
@@ -18,6 +18,7 @@
 using System;
 using System.Collections.Generic;
 using System.Net;
+using System.Security.Cryptography.X509Certificates;
 using System.Threading;
 using System.Threading.Tasks;
 using Apache.Arrow.Adbc.Drivers.Apache.Hive2;
@@ -95,7 +96,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Impala
         {
             Properties.TryGetValue(ImpalaParameters.DataTypeConv, out string? 
dataTypeConv);
             DataTypeConversion = DataTypeConversionParser.Parse(dataTypeConv);
-            Properties.TryGetValue(ImpalaParameters.TLSOptions, out string? 
tlsOptions);
+            TlsOptions = HiveServer2TlsImpl.GetStandardTlsOptions(Properties);
         }
 
         protected override TTransport CreateTransport()
@@ -106,8 +107,24 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Impala
 
             // Delay the open connection until later.
             bool connectClient = false;
-            TSocketTransport transport = new(hostName!, int.Parse(port!), 
connectClient, config: new());
-            TBufferedTransport bufferedTransport = new 
TBufferedTransport(transport);
+            TTransport transport;
+            if (TlsOptions.IsTlsEnabled)
+            {
+                if (IPAddress.TryParse(hostName!, out var address))
+                {
+                    transport = new TTlsSocketTransport(address!, 
int.Parse(port!), config: new(), 0, 
!string.IsNullOrEmpty(TlsOptions.TrustedCertificatePath) ? new 
X509Certificate2(TlsOptions.TrustedCertificatePath!) : null, certValidator: 
HiveServer2TlsImpl.GetCertificateValidator(TlsOptions));
+                }
+                else
+                {
+                    transport = new TTlsSocketTransport(hostName!, 
int.Parse(port!), config: new(), 0, 
!string.IsNullOrEmpty(TlsOptions.TrustedCertificatePath) ? new 
X509Certificate2(TlsOptions.TrustedCertificatePath!) : null, certValidator: 
HiveServer2TlsImpl.GetCertificateValidator(TlsOptions));
+                }
+            }
+            else
+            {
+                transport = new TSocketTransport(hostName!, int.Parse(port!), 
connectClient, config: new());
+            }
+
+            TBufferedTransport bufferedTransport = new(transport);
             return bufferedTransport;
         }
 
diff --git a/csharp/src/Drivers/Apache/Impala/README.md 
b/csharp/src/Drivers/Apache/Impala/README.md
index 0b287f7fe..315dbee23 100644
--- a/csharp/src/Drivers/Apache/Impala/README.md
+++ b/csharp/src/Drivers/Apache/Impala/README.md
@@ -48,11 +48,16 @@ but can also be passed in the call to 
`AdbcDatabase.Connect`. Options beginning
 | `adbc.get_metadata.foreign_target_catalog` | The foreign (i.e., child) 
catalog name (or pattern) when used with a metadata command query. 
<br><br>Supported metadata commands include: `GetCrossReference`. | |
 | `adbc.get_metadata.foreign_target_db_schema` | The foreign (i.e., child) 
schema name (or pattern) when used with a metadata command query. 
<br><br>Supported metadata commands include: `GetCrossReference`. | |
 | `adbc.get_metadata.foreign_target_table` | The foreign (i.e., child) table 
name (or pattern) when used with a metadata command query. <br><br>Supported 
metadata commands include: `GetCrossReference`. | |
-| `adbc.http_options.tls.enabled` | If tls needs to enabled or not. One of 
`True`, `False` | `False` |
+| `adbc.http_options.tls.enabled` | If tls needs to enabled or not for http 
transport type. One of `True`, `False` | `True` |
 | `adbc.http_options.tls.disable_server_certificate_validation` | If tls/ssl 
server certificate validation needs to enabled or not. One of `True`, `False`. 
If set to True, all certificate validation errors are ignored | `False` |
 | `adbc.http_options.tls.allow_self_signed` | If self signed tls/ssl 
certificate needs to be allowed or not. One of `True`, `False` | `False` |
 | `adbc.http_options.tls.allow_hostname_mismatch` | If hostname mismatch is 
allowed for ssl. One of `True`, `False` | `False` |
-| `adbc.http_options.tls.trusted_certificate_path` | The full path of the 
tls/ssl certificate .pem file containing custom CA certificates for verifying 
the server when connecting over TLS | `` |
+| `adbc.http_options.tls.trusted_certificate_path` | The full path of the 
tls/ssl certificate .pem file containing custom CA certificates for verifying 
the server when connecting over TLS | |
+| `adbc.standard_options.tls.enabled` | If tls needs to enabled or not for 
standard transport type. One of `True`, `False` | `True` |
+| `adbc.standard_options.tls.disable_server_certificate_validation` | If 
tls/ssl server certificate validation needs to enabled or not. One of `True`, 
`False`. If set to True, all certificate validation errors are ignored | 
`False` |
+| `adbc.standard_options.tls.allow_self_signed` | If self signed tls/ssl 
certificate needs to be allowed or not. One of `True`, `False` | `False` |
+| `adbc.standard_options.tls.allow_hostname_mismatch` | If hostname mismatch 
is allowed for ssl. One of `True`, `False` | `False` |
+| `adbc.standard_options.tls.trusted_certificate_path` | The full path of the 
tls/ssl certificate .pem file containing custom CA certificates for verifying 
the server when connecting over TLS | |
 
 ## Timeout Configuration
 
diff --git a/csharp/test/Drivers/Apache/Hive2/HiveServer2TlsImplTest.cs 
b/csharp/test/Drivers/Apache/Hive2/HiveServer2TlsImplTest.cs
index 70d5534e6..f707b992f 100644
--- a/csharp/test/Drivers/Apache/Hive2/HiveServer2TlsImplTest.cs
+++ b/csharp/test/Drivers/Apache/Hive2/HiveServer2TlsImplTest.cs
@@ -26,8 +26,8 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Apache.Hive2
     public class HiveServer2TlsImplTest
     {
         [SkippableTheory]
-        [MemberData(nameof(GetSslOptionsTestData))]
-        internal void TestValidateTlsOptions(Dictionary<string, string>? 
dataTypeConversion, TlsProperties expected, Type? exceptionType = default)
+        [MemberData(nameof(GetHttpTlsOptionsTestData))]
+        internal void TestValidateHttpTlsOptions(Dictionary<string, string>? 
dataTypeConversion, TlsProperties expected, Type? exceptionType = default)
         {
             if (exceptionType == default)
                 Assert.Equivalent(expected, 
HiveServer2TlsImpl.GetHttpTlsOptions(dataTypeConversion ?? new 
Dictionary<string, string>()));
@@ -35,8 +35,22 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Apache.Hive2
                 Assert.Throws(exceptionType, () => 
HiveServer2TlsImpl.GetHttpTlsOptions(dataTypeConversion ?? new 
Dictionary<string, string>()));
         }
 
-        public static IEnumerable<object?[]> GetSslOptionsTestData()
+        [SkippableTheory]
+        [MemberData(nameof(GetStandardTlsOptionsTestData))]
+        internal void TestValidateStandardTlsOptions(Dictionary<string, 
string>? dataTypeConversion, TlsProperties expected, Type? exceptionType = 
default)
+        {
+            if (exceptionType == default)
+                Assert.Equivalent(expected, 
HiveServer2TlsImpl.GetStandardTlsOptions(dataTypeConversion ?? new 
Dictionary<string, string>()));
+            else
+                Assert.Throws(exceptionType, () => 
HiveServer2TlsImpl.GetStandardTlsOptions(dataTypeConversion ?? new 
Dictionary<string, string>()));
+        }
+
+        public static IEnumerable<object?[]> GetHttpTlsOptionsTestData()
         {
+            // Tls is enabled by default
+            yield return new object?[] { new Dictionary<string, string> {  }, 
new TlsProperties { IsTlsEnabled = true } };
+            yield return new object?[] { new Dictionary<string, string> { { 
HttpTlsOptions.IsTlsEnabled, "abc" } }, new TlsProperties { IsTlsEnabled = true 
} };
+
             yield return new object?[] { new Dictionary<string, string> { { 
HttpTlsOptions.IsTlsEnabled, "False" } }, new TlsProperties { IsTlsEnabled = 
false } };
             yield return new object?[] { new Dictionary<string, string> { { 
AdbcOptions.Uri, "https://arrow.apache.org"; } }, new TlsProperties { 
IsTlsEnabled = true, DisableServerCertificateValidation = false } };
             // uri takes precedence over ssl option
@@ -55,5 +69,27 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Apache.Hive2
             // invalid certificate path
             yield return new object?[] { new Dictionary<string, string> { { 
HttpTlsOptions.IsTlsEnabled, "True" }, { HttpTlsOptions.AllowSelfSigned, "True" 
}, { HttpTlsOptions.AllowHostnameMismatch, "True" }, { 
HttpTlsOptions.TrustedCertificatePath, "" } }, null, 
typeof(FileNotFoundException) };
         }
+
+        public static IEnumerable<object?[]> GetStandardTlsOptionsTestData()
+        {
+            // Tls is enabled by default
+            yield return new object?[] { new Dictionary<string, string> { }, 
new TlsProperties { IsTlsEnabled = true } };
+            yield return new object?[] { new Dictionary<string, string> { { 
StandardTlsOptions.IsTlsEnabled, "abc" } }, new TlsProperties { IsTlsEnabled = 
true } };
+
+            yield return new object?[] { new Dictionary<string, string> { { 
StandardTlsOptions.IsTlsEnabled, "False" } }, new TlsProperties { IsTlsEnabled 
= false } };
+            // other ssl options are ignored if 
disableServerCertificateValidation is set to true
+            yield return new object?[] { new Dictionary<string, string> { { 
StandardTlsOptions.IsTlsEnabled, "True" }, { 
StandardTlsOptions.DisableServerCertificateValidation, "True" }, { 
StandardTlsOptions.AllowSelfSigned, "True" }, { 
StandardTlsOptions.AllowHostnameMismatch, "True" } }, new TlsProperties { 
IsTlsEnabled = true, DisableServerCertificateValidation = true } };
+            // other ssl options are ignored if ssl is disabled
+            yield return new object?[] { new Dictionary<string, string> { { 
StandardTlsOptions.IsTlsEnabled, "False" }, { 
StandardTlsOptions.AllowSelfSigned, "True" }, { 
StandardTlsOptions.AllowHostnameMismatch, "True" } }, new TlsProperties { 
IsTlsEnabled = false } };
+            // case insensitive boolean string parsing
+            yield return new object?[] { new Dictionary<string, string> { { 
StandardTlsOptions.IsTlsEnabled, "false" } }, new TlsProperties { IsTlsEnabled 
= false } };
+            yield return new object?[] { new Dictionary<string, string> { { 
StandardTlsOptions.IsTlsEnabled, "True" } }, new TlsProperties { IsTlsEnabled = 
true, DisableServerCertificateValidation = false, AllowSelfSigned = false, 
AllowHostnameMismatch = false } };
+            yield return new object?[] { new Dictionary<string, string> { { 
StandardTlsOptions.IsTlsEnabled, "tRUe" }, { 
StandardTlsOptions.AllowSelfSigned, "true" } }, new TlsProperties { 
IsTlsEnabled = true, DisableServerCertificateValidation = false, 
AllowSelfSigned = true, AllowHostnameMismatch = false } };
+            yield return new object?[] { new Dictionary<string, string> { { 
StandardTlsOptions.IsTlsEnabled, "TruE" }, { 
StandardTlsOptions.AllowSelfSigned, "True" }, { 
StandardTlsOptions.AllowHostnameMismatch, "True" } }, new TlsProperties { 
IsTlsEnabled = true, DisableServerCertificateValidation = false, 
AllowSelfSigned = true, AllowHostnameMismatch = true } };
+            // certificate path is ignored if self signed is not allowed
+            yield return new object?[] { new Dictionary<string, string> { { 
StandardTlsOptions.IsTlsEnabled, "True" }, { 
StandardTlsOptions.AllowSelfSigned, "False" }, { 
StandardTlsOptions.AllowHostnameMismatch, "True" }, { 
StandardTlsOptions.TrustedCertificatePath, "" } }, new TlsProperties { 
IsTlsEnabled = true, DisableServerCertificateValidation = false, 
AllowSelfSigned = false, AllowHostnameMismatch = true } };
+            // invalid certificate path
+            yield return new object?[] { new Dictionary<string, string> { { 
StandardTlsOptions.IsTlsEnabled, "True" }, { 
StandardTlsOptions.AllowSelfSigned, "True" }, { 
StandardTlsOptions.AllowHostnameMismatch, "True" }, { 
StandardTlsOptions.TrustedCertificatePath, "" } }, null, 
typeof(FileNotFoundException) };
+        }
     }
 }

Reply via email to