You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by am...@apache.org on 2023/02/17 10:27:30 UTC
[ignite-3] branch main updated: IGNITE-18790 Add SSL support for JDBC driver (#1680)
This is an automated email from the ASF dual-hosted git repository.
amashenkov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new be6c8b2908 IGNITE-18790 Add SSL support for JDBC driver (#1680)
be6c8b2908 is described below
commit be6c8b290894dbd6f88eaaa2a2aafc3eff300855
Author: Aleksandr <Sa...@icloud.com>
AuthorDate: Fri Feb 17 14:27:25 2023 +0400
IGNITE-18790 Add SSL support for JDBC driver (#1680)
---
.../ignite/internal/jdbc/ConnectionProperties.java | 113 +++++++++++
.../internal/jdbc/ConnectionPropertiesImpl.java | 206 ++++++++++++++++++++-
.../ignite/internal/jdbc/JdbcConnection.java | 22 ++-
modules/runner/build.gradle | 1 +
.../org/apache/ignite/internal/ssl/ItSslTest.java | 125 ++++++++++---
5 files changed, 443 insertions(+), 24 deletions(-)
diff --git a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionProperties.java b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionProperties.java
index f14547d9ba..ce29e5e572 100644
--- a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionProperties.java
+++ b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionProperties.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.jdbc;
import java.sql.SQLException;
+import org.apache.ignite.client.ClientAuthenticationMode;
import org.apache.ignite.internal.client.HostAndPortRange;
/**
@@ -126,4 +127,116 @@ public interface ConnectionProperties {
* @throws SQLException On error.
*/
void setConnectionTimeout(Integer connTimeout) throws SQLException;
+
+ /**
+ * SSL enabled.
+ *
+ * @return true if SSL is enabled.
+ */
+ boolean isSslEnabled();
+
+ /**
+ * Enable/disable ssl.
+ *
+ * @param enabled true if SSL is enabled.
+ */
+ void setSslEnabled(boolean enabled);
+
+ /**
+ * SSL client authentication. Can be NONE, REQUIRE, and OPTIONAL.
+ *
+ * @param clientAuth SSL client authentication.
+ */
+ void setClientAuth(ClientAuthenticationMode clientAuth);
+
+ /**
+ * SSL client authentication.
+ *
+ * @return SSL client authentication.
+ */
+ ClientAuthenticationMode getClientAuth();
+
+ /**
+ * Set trust store path that will be used to setup SSL connection.
+ *
+ * @param trustStorePath Trust store path.
+ */
+ void setTrustStorePath(String trustStorePath);
+
+ /**
+ * Set trust store password.
+ *
+ * @param password Trust store password.
+ */
+ void setTrustStorePassword(String password);
+
+ /**
+ * Set keystore type. For example, PKSC12 or JKS.
+ *
+ * @param type Truststore type.
+ */
+ void setTrustStoreType(String type);
+
+ /**
+ * Trust store path.
+ *
+ * @return Truststore path.
+ */
+ String getTrustStorePath();
+
+ /**
+ * Truststore password.
+ *
+ * @return Truststore password.
+ */
+ String getTrustStorePassword();
+
+ /**
+ * Truststore type.
+ *
+ * @return Truststore type.
+ */
+ String getTrustStoreType();
+
+ /**
+ * Set keystore type. For example, PKSC12 or JKS.
+ *
+ * @param type Keystore type.
+ */
+ void setKeyStoreType(String type);
+
+ /**
+ * Set key store path that will be used to setup SSL connection.
+ *
+ * @param keyStorePath Key store path.
+ */
+ void setKeyStorePath(String keyStorePath);
+
+ /**
+ * Set key store password.
+ *
+ * @param password Key store password.
+ */
+ void setKeyStorePassword(String password);
+
+ /**
+ * Key store path.
+ *
+ * @return Keystore path.
+ */
+ String getKeyStorePath();
+
+ /**
+ * Keystore password.
+ *
+ * @return Keystore password.
+ */
+ String getKeyStorePassword();
+
+ /**
+ * Keystore type.
+ *
+ * @return Keytore type.
+ */
+ String getKeyStoreType();
}
diff --git a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionPropertiesImpl.java b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionPropertiesImpl.java
index f3b5c03344..385008f74f 100644
--- a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionPropertiesImpl.java
+++ b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionPropertiesImpl.java
@@ -23,11 +23,14 @@ import java.sql.SQLException;
import java.util.Arrays;
import java.util.Properties;
import java.util.StringTokenizer;
+import java.util.stream.Collectors;
+import org.apache.ignite.client.ClientAuthenticationMode;
import org.apache.ignite.client.IgniteClientConfiguration;
import org.apache.ignite.internal.client.HostAndPortRange;
import org.apache.ignite.internal.jdbc.proto.SqlStateCode;
import org.apache.ignite.internal.util.ArrayUtils;
import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -78,8 +81,52 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa
"Sets the reconnect throttling retries. Zero means there is no limits.",
IgniteClientConfiguration.DFLT_RECONNECT_THROTTLING_RETRIES, false, 0, Integer.MAX_VALUE);
+ /** Path to the truststore. */
+ private final StringProperty trustStorePath = new StringProperty("trustStorePath",
+ "Path to trust store", null, null, false, null);
+
+ /** Truststore password. */
+ private final StringProperty trustStorePassword = new StringProperty("trustStorePassword",
+ "Trust store password", null, null, false, null);
+
+ /** Type of the truststore. */
+ private final StringProperty trustStoreType = new StringProperty("trustStoreType",
+ "Type of the trust store", "PKCS12", null, false, null);
+
+ /** Path to the keystore. */
+ private final StringProperty keyStorePath = new StringProperty("keyStorePath",
+ "Path to key store", null, null, false, null);
+
+ /** Keystore password. */
+ private final StringProperty keyStorePassword = new StringProperty("keyStorePassword",
+ "Key store password", null, null, false, null);
+
+ /** Type of the keystore. */
+ private final StringProperty keyStoreType = new StringProperty("keyStoreType",
+ "Type of the key store", "PKCS12", null, false, null);
+
+ /** SSL client authentication. */
+ private final StringProperty clientAuth = new StringProperty("clientAuth",
+ "SSL client authentication", "none", clientAuthValues(), false, null);
+
+ @NotNull
+ private static String[] clientAuthValues() {
+ return Arrays.stream(ClientAuthenticationMode.values())
+ .map(Enum::name)
+ .map(String::toLowerCase)
+ .collect(Collectors.toList())
+ .toArray(String[]::new);
+ }
+
+ /** Enable SSL. */
+ private final BooleanProperty sslEnabled = new BooleanProperty("sslEnabled",
+ "Enable ssl", false, null, false, null);
+
/** Properties array. */
- private final ConnectionProperty[] propsArray = {qryTimeout, connTimeout};
+ private final ConnectionProperty[] propsArray = {
+ qryTimeout, connTimeout, trustStorePath, trustStorePassword, trustStoreType,
+ sslEnabled, clientAuth, keyStorePath, keyStorePassword, keyStoreType
+ };
/** {@inheritDoc} */
@Override
@@ -191,6 +238,102 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa
connTimeout.setValue(timeout);
}
+ /** {@inheritDoc} */
+ @Override
+ public void setTrustStorePath(String trustStorePath) {
+ this.trustStorePath.setValue(trustStorePath);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setTrustStorePassword(String password) {
+ this.trustStorePassword.setValue(password);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getTrustStorePath() {
+ return trustStorePath.value();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getTrustStorePassword() {
+ return trustStorePassword.value();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getTrustStoreType() {
+ return trustStoreType.value();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setKeyStoreType(String type) {
+ keyStoreType.setValue(type);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setKeyStorePath(String keyStorePath) {
+ this.keyStorePath.setValue(keyStorePath);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setKeyStorePassword(String password) {
+ this.keyStorePassword.setValue(password);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getKeyStorePath() {
+ return keyStorePath.value();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getKeyStorePassword() {
+ return keyStorePassword.value();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getKeyStoreType() {
+ return keyStoreType.value();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setTrustStoreType(String type) {
+ trustStoreType.setValue(type);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isSslEnabled() {
+ return sslEnabled.value();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setSslEnabled(boolean enabled) {
+ sslEnabled.setValue(enabled);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setClientAuth(ClientAuthenticationMode clientAuth) {
+ this.clientAuth.setValue(clientAuth.name().toLowerCase());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ClientAuthenticationMode getClientAuth() {
+ return ClientAuthenticationMode.valueOf(this.clientAuth.value().toUpperCase());
+ }
+
/**
* Init connection properties.
*
@@ -835,6 +978,67 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa
}
}
+ /**
+ * Boolean property.
+ */
+ private static class BooleanProperty extends ConnectionProperty {
+ /** Serial version uid. */
+ private static final long serialVersionUID = 0L;
+
+ /** Value. */
+ private boolean val;
+
+ /**
+ * Constructor.
+ *
+ * @param name Name.
+ * @param desc Description.
+ * @param dfltVal Default value.
+ * @param required {@code true} if the property is required.
+ * @param validator Property value validator.
+ */
+ BooleanProperty(String name, String desc, boolean dfltVal, String[] choices, boolean required,
+ PropertyValidator validator) {
+ super(name, desc, dfltVal, choices, required, validator);
+
+ val = dfltVal;
+ }
+
+ /**
+ * Set the property value.
+ *
+ * @param val Property value.
+ */
+ void setValue(boolean val) {
+ this.val = val;
+ }
+
+ /**
+ * Get the property value.
+ *
+ * @return Property value.
+ */
+ boolean value() {
+ return val;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ void init(String str) throws SQLException {
+ if (validator != null) {
+ validator.validate(str);
+ }
+
+ val = Boolean.parseBoolean(str);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ String valueObject() {
+ return String.valueOf(val);
+ }
+ }
+
/**
* Get the driver properties.
*
diff --git a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
index 724debb087..433f1d9dae 100644
--- a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
+++ b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
@@ -51,6 +51,7 @@ import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Executor;
import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.client.SslConfiguration;
import org.apache.ignite.internal.client.HostAndPortRange;
import org.apache.ignite.internal.client.TcpIgniteClient;
import org.apache.ignite.internal.jdbc.proto.JdbcQueryEventHandler;
@@ -149,12 +150,12 @@ public class JdbcConnection implements Connection {
int reconnectThrottlingRetries = connProps.getReconnectThrottlingRetries();
try {
- client = ((TcpIgniteClient) IgniteClient
- .builder()
+ client = ((TcpIgniteClient) IgniteClient.builder()
.addresses(addrs)
.connectTimeout(netTimeout)
.reconnectThrottlingPeriod(reconnectThrottlingPeriod)
.reconnectThrottlingRetries(reconnectThrottlingRetries)
+ .ssl(extractSslConfiguration(connProps))
.build());
} catch (Exception e) {
@@ -170,6 +171,23 @@ public class JdbcConnection implements Connection {
holdability = HOLD_CURSORS_OVER_COMMIT;
}
+ private @Nullable SslConfiguration extractSslConfiguration(ConnectionProperties connProps) {
+ if (connProps.isSslEnabled()) {
+ return SslConfiguration.builder()
+ .enabled(true)
+ .trustStoreType(connProps.getTrustStoreType())
+ .trustStorePath(connProps.getTrustStorePath())
+ .trustStorePassword(connProps.getTrustStorePassword())
+ .clientAuth(connProps.getClientAuth())
+ .keyStoreType(connProps.getKeyStoreType())
+ .keyStorePath(connProps.getKeyStorePath())
+ .keyStorePassword(connProps.getKeyStorePassword())
+ .build();
+ } else {
+ return null;
+ }
+ }
+
/** {@inheritDoc} */
@Override
public Statement createStatement() throws SQLException {
diff --git a/modules/runner/build.gradle b/modules/runner/build.gradle
index a25bd533fd..1316da241d 100644
--- a/modules/runner/build.gradle
+++ b/modules/runner/build.gradle
@@ -114,6 +114,7 @@ dependencies {
integrationTestImplementation project(':ignite-metastorage')
integrationTestImplementation project(':ignite-table')
integrationTestImplementation project(':ignite-transactions')
+ integrationTestImplementation project(':ignite-jdbc')
integrationTestImplementation(testFixtures(project(':ignite-core')))
integrationTestImplementation(testFixtures(project(':ignite-configuration')))
integrationTestImplementation(testFixtures(project(':ignite-schema')))
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/ssl/ItSslTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/ssl/ItSslTest.java
index 9d4054eb73..4d19136f88 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/ssl/ItSslTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/ssl/ItSslTest.java
@@ -24,6 +24,9 @@ import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.nio.file.Path;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
import org.apache.ignite.client.IgniteClient;
import org.apache.ignite.client.IgniteClientConnectionException;
import org.apache.ignite.client.SslConfiguration;
@@ -38,8 +41,6 @@ import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
-import org.junit.jupiter.api.TestInstance;
-import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExtendWith;
/** SSL support integration test. */
@@ -60,14 +61,9 @@ public class ItSslTest {
}
@Nested
- @TestInstance(Lifecycle.PER_CLASS)
+ @DisplayName("Given SSL disabled on the cluster")
class ClusterWithoutSsl {
- @WorkDirectory
- private Path workDir;
-
- private Cluster cluster;
-
@Language("JSON")
String sslDisabledBoostrapConfig = "{\n"
+ " network: {\n"
@@ -80,6 +76,10 @@ public class ItSslTest {
+ " }\n"
+ "}";
+ @WorkDirectory
+ private Path workDir;
+ private Cluster cluster;
+
@BeforeEach
void setUp(TestInfo testInfo) {
cluster = new Cluster(testInfo, workDir, sslDisabledBoostrapConfig);
@@ -104,17 +104,53 @@ public class ItSslTest {
assertThat(client.clusterNodes(), hasSize(2));
}
}
+
+ @Test
+ @DisplayName("Client can not connect with ssl configured when ssl disabled on the cluster")
+ void clientCanNotConnectWithoutSsl() {
+ var sslConfiguration =
+ SslConfiguration.builder()
+ .enabled(true)
+ .trustStoreType("JKS")
+ .trustStorePath(trustStorePath)
+ .trustStorePassword(password)
+ .build();
+
+ assertThrows(
+ IgniteClientConnectionException.class,
+ () -> IgniteClient.builder()
+ .addresses("localhost:10800")
+ .ssl(sslConfiguration)
+ .build()
+ );
+ }
+
+ @Test
+ @DisplayName("Jdbc driver could establish the connection when SSL disabled")
+ void jdbcCouldConnectWithoutSsl() throws SQLException {
+ var url = "jdbc:ignite:thin://127.0.0.1:10800";
+ try (Connection conn = DriverManager.getConnection(url)) {
+ // No-op.
+ }
+ }
+
+ @Test
+ @DisplayName("Jdbc client can not connect with SSL configured when SSL disabled on the server")
+ void jdbcCanNotConnectWithSsl() {
+ var url =
+ "jdbc:ignite:thin://127.0.0.1:10800"
+ + "?sslEnabled=true"
+ + "&trustStorePath=" + trustStorePath
+ + "&trustStoreType=JKS"
+ + "&trustStorePassword=" + password;
+ assertThrows(SQLException.class, () -> DriverManager.getConnection(url));
+ }
}
@Nested
- @TestInstance(Lifecycle.PER_CLASS)
+ @DisplayName("Given SSL enabled on the cluster")
class ClusterWithSsl {
- @WorkDirectory
- private Path workDir;
-
- private Cluster cluster;
-
@Language("JSON")
String sslEnabledBoostrapConfig = "{\n"
+ " network: {\n"
@@ -144,6 +180,10 @@ public class ItSslTest {
+ " }\n"
+ "}";
+ @WorkDirectory
+ private Path workDir;
+ private Cluster cluster;
+
@BeforeEach
void setUp(TestInfo testInfo) {
cluster = new Cluster(testInfo, workDir, sslEnabledBoostrapConfig);
@@ -171,6 +211,12 @@ public class ItSslTest {
});
}
+ @Test
+ void jdbcCannotConnectWithoutSsl() {
+ var url = "jdbc:ignite:thin://127.0.0.1:10800";
+ assertThrows(SQLException.class, () -> DriverManager.getConnection(url));
+ }
+
@Test
@DisplayName("Client can connect with SSL configured")
void clientCanConnectWithSsl() throws Exception {
@@ -190,17 +236,26 @@ public class ItSslTest {
assertThat(client.clusterNodes(), hasSize(2));
}
}
+
+ @Test
+ @DisplayName("Jdbc client can connect with SSL configured")
+ void jdbcCanConnectWithSsl() throws SQLException {
+ var url =
+ "jdbc:ignite:thin://127.0.0.1:10800"
+ + "?sslEnabled=true"
+ + "&trustStorePath=" + trustStorePath
+ + "&trustStoreType=JKS"
+ + "&trustStorePassword=" + password;
+ try (Connection conn = DriverManager.getConnection(url)) {
+ // No-op.
+ }
+ }
}
@Nested
- @TestInstance(Lifecycle.PER_CLASS)
+ @DisplayName("Given SSL enabled client auth is set to require on the cluster")
class ClusterWithSslAndClientAuth {
- @WorkDirectory
- private Path workDir;
-
- private Cluster cluster;
-
@Language("JSON")
String sslEnabledBoostrapConfig = "{\n"
+ " network: {\n"
@@ -233,10 +288,14 @@ public class ItSslTest {
+ " type: JKS,"
+ " password: \"" + password + "\","
+ " path: \"" + trustStorePath + "\""
- + " },\n"
+ + " }\n"
+ " }\n"
+ "}";
+ @WorkDirectory
+ private Path workDir;
+ private Cluster cluster;
+
@BeforeEach
void setUp(TestInfo testInfo) {
cluster = new Cluster(testInfo, workDir, sslEnabledBoostrapConfig);
@@ -264,6 +323,12 @@ public class ItSslTest {
});
}
+ @Test
+ void jdbcCannotConnectWithoutSsl() {
+ var url = "jdbc:ignite:thin://127.0.0.1:10800";
+ assertThrows(SQLException.class, () -> DriverManager.getConnection(url));
+ }
+
@Test
@DisplayName("Client can not connect without client authentication configured")
void clientCanNotConnectWithoutClientAuth() {
@@ -305,5 +370,23 @@ public class ItSslTest {
assertThat(client.clusterNodes(), hasSize(2));
}
}
+
+ @Test
+ @DisplayName("Jdbc client can connect with SSL configured")
+ void jdbcCanConnectWithSslAndClientAuth() throws SQLException {
+ var url =
+ "jdbc:ignite:thin://127.0.0.1:10800"
+ + "?sslEnabled=true"
+ + "&trustStorePath=" + trustStorePath
+ + "&trustStoreType=JKS"
+ + "&trustStorePassword=" + password
+ + "&clientAuth=require"
+ + "&keyStorePath=" + keyStorePath
+ + "&keyStoreType=PKCS12"
+ + "&keyStorePassword=" + password;
+ try (Connection conn = DriverManager.getConnection(url)) {
+ // No-op.
+ }
+ }
}
}