From 82d19554d281a52712a75523c15ce5bddf524461 Mon Sep 17 00:00:00 2001 From: AnnaHariprasad5123 Date: Fri, 25 Oct 2024 18:09:12 +0530 Subject: [PATCH 1/8] Added TLS support for Redis Datasource --- .../models/DatasourceConfiguration.java | 3 + .../external/models/TlsConfiguration.java | 36 +++++ .../com/external/plugins/RedisPlugin.java | 39 +++++- .../exceptions/RedisErrorMessages.java | 15 +++ .../com/external/utils/RedisTLSManager.java | 126 ++++++++++++++++++ .../com/external/utils/RedisURIUtils.java | 9 +- .../redisPlugin/src/main/resources/form.json | 72 ++++++++++ .../com/external/plugins/RedisPluginTest.java | 83 +++++++++++- 8 files changed, 378 insertions(+), 5 deletions(-) create mode 100644 app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/TlsConfiguration.java create mode 100644 app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceConfiguration.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceConfiguration.java index 637879eb75b7..580182c2ea6b 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceConfiguration.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceConfiguration.java @@ -34,6 +34,9 @@ public class DatasourceConfiguration implements AppsmithDomain { @JsonView({Views.Public.class, FromRequest.class}) AuthenticationDTO authentication; + @JsonView({Views.Public.class, FromRequest.class}) + TlsConfiguration tlsConfiguration; + @JsonView({Views.Public.class, FromRequest.class}) SSHConnection sshProxy; diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/TlsConfiguration.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/TlsConfiguration.java new file mode 100644 index 000000000000..1c91ab2b8498 --- /dev/null +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/TlsConfiguration.java @@ -0,0 +1,36 @@ +package com.appsmith.external.models; + +import com.appsmith.external.views.FromRequest; +import com.appsmith.external.views.Views; +import com.fasterxml.jackson.annotation.JsonView; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class TlsConfiguration { + + @JsonView({Views.Public.class, FromRequest.class}) + Boolean tlsEnabled; + + @JsonView({Views.Public.class, FromRequest.class}) + Boolean verifyTlsCertificate; + + @JsonView({Views.Public.class, FromRequest.class}) + UploadedFile caCertificateFile; + + @JsonView({Views.Public.class, FromRequest.class}) + Boolean requiresClientAuth; + + @JsonView({Views.Public.class, FromRequest.class}) + UploadedFile clientCertificateFile; + + @JsonView({Views.Public.class, FromRequest.class}) + UploadedFile clientKeyFile; +} diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java index f0675168dc24..eeb3f926d027 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java @@ -10,6 +10,7 @@ import com.appsmith.external.models.DatasourceTestResult; import com.appsmith.external.models.Endpoint; import com.appsmith.external.models.RequestParamDTO; +import com.appsmith.external.models.TlsConfiguration; import com.appsmith.external.plugins.BasePlugin; import com.appsmith.external.plugins.PluginExecutor; import com.external.plugins.exceptions.RedisErrorMessages; @@ -45,6 +46,7 @@ import java.util.stream.Collectors; import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_BODY; +import static com.external.utils.RedisTLSManager.createJedisPoolWithTLS; import static org.apache.commons.lang3.StringUtils.isBlank; @Slf4j @@ -261,9 +263,13 @@ public Mono datasourceCreate(DatasourceConfiguration datasourceConfig int timeout = (int) Duration.ofSeconds(CONNECTION_TIMEOUT).toMillis(); URI uri = RedisURIUtils.getURI(datasourceConfiguration); - JedisPool jedisPool = new JedisPool(poolConfig, uri, timeout); - log.debug(Thread.currentThread().getName() + ": Created Jedis pool."); - return jedisPool; + if (!datasourceConfiguration.getTlsConfiguration().getTlsEnabled()) { + JedisPool jedisPool = new JedisPool(poolConfig, uri, timeout); + log.debug(Thread.currentThread().getName() + ": Created Jedis pool."); + return jedisPool; + } else { + return createJedisPoolWithTLS(poolConfig, uri, timeout, datasourceConfiguration); + } }) .subscribeOn(scheduler); } @@ -301,6 +307,33 @@ public Set validateDatasource(DatasourceConfiguration datasourceConfigur invalids.add(RedisErrorMessages.DS_MISSING_PASSWORD_ERROR_MSG); } + TlsConfiguration tlsConfiguration = datasourceConfiguration.getTlsConfiguration(); + if (tlsConfiguration.getTlsEnabled()) { + // Check for CA certificate if TLS verification is enabled + if (tlsConfiguration.getVerifyTlsCertificate() + && (tlsConfiguration.getCaCertificateFile() == null + || StringUtils.isNullOrEmpty( + tlsConfiguration.getCaCertificateFile().getBase64Content()))) { + invalids.add(RedisErrorMessages.CA_CERTIFICATE_MISSING_ERROR_MSG); + } + + // Check for client certificate and key if client authentication is required + if (tlsConfiguration.getRequiresClientAuth()) { + if (tlsConfiguration.getClientCertificateFile() == null + || StringUtils.isNullOrEmpty( + tlsConfiguration.getClientCertificateFile().getBase64Content())) { + invalids.add( + RedisErrorMessages.TLS_CLIENT_AUTH_ENABLED_BUT_CLIENT_CERTIFICATE_MISSING_ERROR_MSG); + } + + if (tlsConfiguration.getClientKeyFile() == null + || StringUtils.isNullOrEmpty( + tlsConfiguration.getClientKeyFile().getBase64Content())) { + invalids.add(RedisErrorMessages.TLS_CLIENT_AUTH_ENABLED_BUT_CLIENT_KEY_MISSING_ERROR_MSG); + } + } + } + return invalids; } diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/exceptions/RedisErrorMessages.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/exceptions/RedisErrorMessages.java index f453323f31d4..58c1d7c38f46 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/exceptions/RedisErrorMessages.java +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/exceptions/RedisErrorMessages.java @@ -29,4 +29,19 @@ public class RedisErrorMessages extends BasePluginErrorMessages { public static final String DS_MISSING_PASSWORD_ERROR_MSG = "Could not find password. Please edit the 'Password' field to provide the password."; + + /* + ************************************************************************************************************************************************ + Error messages related to TLS configuration. + ************************************************************************************************************************************************ + */ + + public static final String CA_CERTIFICATE_MISSING_ERROR_MSG = + "CA certificate is missing. Please upload the CA certificate."; + + public static final String TLS_CLIENT_AUTH_ENABLED_BUT_CLIENT_CERTIFICATE_MISSING_ERROR_MSG = + "Client authentication is enabled but the client certificate is missing. Please upload the client certificate."; + + public static final String TLS_CLIENT_AUTH_ENABLED_BUT_CLIENT_KEY_MISSING_ERROR_MSG = + "Client authentication is enabled but the client key is missing. Please upload the client key."; } diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java new file mode 100644 index 000000000000..637dd1abf094 --- /dev/null +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java @@ -0,0 +1,126 @@ +package com.external.utils; + +import com.appsmith.external.models.DatasourceConfiguration; +import com.appsmith.external.models.TlsConfiguration; +import lombok.extern.slf4j.Slf4j; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; + +@Slf4j +public class RedisTLSManager { + + public static JedisPool createJedisPoolWithTLS( + JedisPoolConfig poolConfig, URI uri, int timeout, DatasourceConfiguration datasourceConfiguration) + throws Exception { + + TlsConfiguration tlsConfiguration = datasourceConfiguration.getTlsConfiguration(); + boolean verifyTlsCertificate = tlsConfiguration.getVerifyTlsCertificate(); + boolean requiresClientAuth = tlsConfiguration.getRequiresClientAuth(); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + KeyManagerFactory keyManagerFactory = null; + + // Handle client authentication if required, regardless of certificate verification + if (requiresClientAuth) { + + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + + // Load client certificate + String clientCertContent = + new String(tlsConfiguration.getClientCertificateFile().getDecodedContent(), StandardCharsets.UTF_8); + X509Certificate clientCert = (X509Certificate) + certificateFactory.generateCertificate(new ByteArrayInputStream(clientCertContent.getBytes())); + + // Load client private key + String clientKey = + new String(tlsConfiguration.getClientKeyFile().getDecodedContent(), StandardCharsets.UTF_8); + PrivateKey privateKey = loadPrivateKey(clientKey); + + // KeyStore for client authentication + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, null); + keyStore.setKeyEntry("client-key", privateKey, null, new X509Certificate[] {clientCert}); + + keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, null); + } + + // Handle server certificate verification + TrustManager[] trustManagers; + if (verifyTlsCertificate) { + + // CA certificate verification + String caCertContent = + new String(tlsConfiguration.getCaCertificateFile().getDecodedContent(), StandardCharsets.UTF_8); + + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + X509Certificate caCert = (X509Certificate) + certificateFactory.generateCertificate(new ByteArrayInputStream(caCertContent.getBytes())); + + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(null, null); + trustStore.setCertificateEntry("ca-cert", caCert); + + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(trustStore); + trustManagers = trustManagerFactory.getTrustManagers(); + } else { + trustManagers = new TrustManager[] { + new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) {} + + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) {} + } + }; + } + + // Initialize SSL context with appropriate managers + sslContext.init( + requiresClientAuth ? keyManagerFactory.getKeyManagers() : null, trustManagers, new SecureRandom()); + + SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + + // Create and return JedisPool with TLS + JedisPool jedisPool = new JedisPool(poolConfig, uri, timeout, sslSocketFactory, null, null); + log.debug(Thread.currentThread().getName() + ": Created Jedis pool with TLS."); + return jedisPool; + } + + private static PrivateKey loadPrivateKey(String clientKey) throws Exception { + clientKey = clientKey + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s", ""); + + byte[] keyBytes = Base64.getDecoder().decode(clientKey); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePrivate(spec); + } +} diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java index 139ecb452d43..597cff7441f9 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java @@ -13,9 +13,16 @@ public class RedisURIUtils { public static final Long DEFAULT_PORT = 6379L; private static final String REDIS_SCHEME = "redis://"; + private static final String REDIS_SSL_SCHEME = "rediss://"; + public static URI getURI(DatasourceConfiguration datasourceConfiguration) throws URISyntaxException { StringBuilder builder = new StringBuilder(); - builder.append(REDIS_SCHEME); + + if (datasourceConfiguration.getTlsConfiguration().getTlsEnabled()) { + builder.append(REDIS_SSL_SCHEME); + } else { + builder.append(REDIS_SCHEME); + } String uriAuth = getUriAuth(datasourceConfiguration); builder.append(uriAuth); diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/resources/form.json b/app/server/appsmith-plugins/redisPlugin/src/main/resources/form.json index d2e12db10d5f..c93e85a35cb4 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/resources/form.json +++ b/app/server/appsmith-plugins/redisPlugin/src/main/resources/form.json @@ -58,6 +58,78 @@ ] } ] + }, + { + "sectionName": "TLS Configuration", + "id": 3, + "children": [ + { + "sectionName": null, + "children": [ + { + "label": "Enable TLS", + "configProperty": "datasourceConfiguration.tlsConfiguration.tlsEnabled", + "controlType": "CHECKBOX", + "initialValue": false + }, + { + "label": "Verify TLS Certificate", + "configProperty": "datasourceConfiguration.tlsConfiguration.verifyTlsCertificate", + "controlType": "CHECKBOX", + "initialValue": false, + "hidden": { + "path": "datasourceConfiguration.tlsConfiguration.tlsEnabled", + "comparison": "NOT_EQUALS", + "value": true + } + }, + { + "label": "Upload CA Certificate", + "configProperty": "datasourceConfiguration.tlsConfiguration.caCertificateFile", + "controlType": "FILE_PICKER", + "encrypted": true, + "hidden": { + "path": "datasourceConfiguration.tlsConfiguration.tlsEnabled", + "comparison": "NOT_EQUALS", + "value": true + } + }, + { + "label": "Requires TLS Client Authentication", + "configProperty": "datasourceConfiguration.tlsConfiguration.requiresClientAuth", + "controlType": "CHECKBOX", + "initialValue": false, + "hidden": { + "path": "datasourceConfiguration.tlsConfiguration.tlsEnabled", + "comparison": "NOT_EQUALS", + "value": true + } + }, + { + "label": "Upload Client Certificate", + "configProperty": "datasourceConfiguration.tlsConfiguration.clientCertificateFile", + "controlType": "FILE_PICKER", + "encrypted": true, + "hidden": { + "path": "datasourceConfiguration.tlsConfiguration.requiresClientAuth", + "comparison": "EQUALS", + "value": false + } + }, + { + "label": "Upload Client Key", + "configProperty": "datasourceConfiguration.tlsConfiguration.clientKeyFile", + "controlType": "FILE_PICKER", + "encrypted": true, + "hidden": { + "path": "datasourceConfiguration.tlsConfiguration.requiresClientAuth", + "comparison": "EQUALS", + "value": false + } + } + ] + } + ] } ] } diff --git a/app/server/appsmith-plugins/redisPlugin/src/test/java/com/external/plugins/RedisPluginTest.java b/app/server/appsmith-plugins/redisPlugin/src/test/java/com/external/plugins/RedisPluginTest.java index 9cbfbef3716c..7be729da9af7 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/test/java/com/external/plugins/RedisPluginTest.java +++ b/app/server/appsmith-plugins/redisPlugin/src/test/java/com/external/plugins/RedisPluginTest.java @@ -8,6 +8,8 @@ import com.appsmith.external.models.DatasourceTestResult; import com.appsmith.external.models.Endpoint; import com.appsmith.external.models.RequestParamDTO; +import com.appsmith.external.models.TlsConfiguration; +import com.appsmith.external.models.UploadedFile; import com.external.plugins.exceptions.RedisErrorMessages; import com.external.plugins.exceptions.RedisPluginError; import com.fasterxml.jackson.databind.JsonNode; @@ -62,7 +64,9 @@ private DatasourceConfiguration createDatasourceConfiguration() { DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); - + TlsConfiguration tlsConfiguration = new TlsConfiguration(); + tlsConfiguration.setTlsEnabled(false); + datasourceConfiguration.setTlsConfiguration(tlsConfiguration); return datasourceConfiguration; } @@ -79,6 +83,9 @@ public void itShouldCreateDatasource() { @Test public void itShouldValidateDatasourceWithNoEndpoints() { DatasourceConfiguration invalidDatasourceConfiguration = new DatasourceConfiguration(); + TlsConfiguration tlsConfiguration = new TlsConfiguration(); + tlsConfiguration.setTlsEnabled(false); + invalidDatasourceConfiguration.setTlsConfiguration(tlsConfiguration); assertEquals( Set.of(RedisErrorMessages.DS_MISSING_HOST_ADDRESS_ERROR_MSG), @@ -92,6 +99,10 @@ public void itShouldValidateDatasourceWithInvalidEndpoint() { Endpoint endpoint = new Endpoint(); invalidDatasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); + TlsConfiguration tlsConfiguration = new TlsConfiguration(); + tlsConfiguration.setTlsEnabled(false); + invalidDatasourceConfiguration.setTlsConfiguration(tlsConfiguration); + assertEquals( Set.of(RedisErrorMessages.DS_MISSING_HOST_ADDRESS_ERROR_MSG), pluginExecutor.validateDatasource(invalidDatasourceConfiguration)); @@ -105,6 +116,10 @@ public void itShouldValidateDatasourceWithEmptyPort() { endpoint.setHost("test-host"); invalidDatasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); + TlsConfiguration tlsConfiguration = new TlsConfiguration(); + tlsConfiguration.setTlsEnabled(false); + invalidDatasourceConfiguration.setTlsConfiguration(tlsConfiguration); + // Since default port is picked, set of invalids should be empty. assertEquals(pluginExecutor.validateDatasource(invalidDatasourceConfiguration), Set.of()); } @@ -121,6 +136,10 @@ public void itShouldValidateDatasourceWithInvalidAuth() { invalidDatasourceConfiguration.setAuthentication(invalidAuth); invalidDatasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); + TlsConfiguration tlsConfiguration = new TlsConfiguration(); + tlsConfiguration.setTlsEnabled(false); + invalidDatasourceConfiguration.setTlsConfiguration(tlsConfiguration); + assertEquals( Set.of(RedisErrorMessages.DS_MISSING_PASSWORD_ERROR_MSG), pluginExecutor.validateDatasource(invalidDatasourceConfiguration)); @@ -141,6 +160,10 @@ public void itShouldValidateDatasource() { datasourceConfiguration.setAuthentication(auth); datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); + TlsConfiguration tlsConfiguration = new TlsConfiguration(); + tlsConfiguration.setTlsEnabled(false); + datasourceConfiguration.setTlsConfiguration(tlsConfiguration); + assertTrue(pluginExecutor.validateDatasource(datasourceConfiguration).isEmpty()); } @@ -157,6 +180,64 @@ public void itShouldTestDatasource() { .verifyComplete(); } + @Test + public void itShouldValidateDatasourceWithTlsEnabledAndMissingCACertificate() { + DatasourceConfiguration datasourceConfiguration = createDatasourceConfiguration(); + TlsConfiguration tlsConfiguration = new TlsConfiguration(); + tlsConfiguration.setTlsEnabled(true); + tlsConfiguration.setVerifyTlsCertificate(true); + tlsConfiguration.setRequiresClientAuth(false); + datasourceConfiguration.setTlsConfiguration(tlsConfiguration); + datasourceConfiguration.getTlsConfiguration().setVerifyTlsCertificate(true); + + assertEquals( + Set.of(RedisErrorMessages.CA_CERTIFICATE_MISSING_ERROR_MSG), + pluginExecutor.validateDatasource(datasourceConfiguration)); + } + + @Test + public void itShouldValidateDatasourceWithTlsEnabledAndMissingClientCertificate() { + DatasourceConfiguration datasourceConfiguration = createDatasourceConfiguration(); + TlsConfiguration tlsConfiguration = new TlsConfiguration(); + tlsConfiguration.setTlsEnabled(true); + tlsConfiguration.setVerifyTlsCertificate(false); + tlsConfiguration.setRequiresClientAuth(true); + tlsConfiguration.setClientKeyFile(new UploadedFile("client-key", "base64Key")); + datasourceConfiguration.setTlsConfiguration(tlsConfiguration); + assertEquals( + Set.of(RedisErrorMessages.TLS_CLIENT_AUTH_ENABLED_BUT_CLIENT_CERTIFICATE_MISSING_ERROR_MSG), + pluginExecutor.validateDatasource(datasourceConfiguration)); + } + + @Test + public void itShouldValidateDatasourceWithTlsEnabledAndMissingKey() { + DatasourceConfiguration datasourceConfiguration = createDatasourceConfiguration(); + TlsConfiguration tlsConfiguration = new TlsConfiguration(); + tlsConfiguration.setTlsEnabled(true); + tlsConfiguration.setVerifyTlsCertificate(false); + tlsConfiguration.setRequiresClientAuth(true); + tlsConfiguration.setClientCertificateFile(new UploadedFile("client-cert", "base64Key")); + datasourceConfiguration.setTlsConfiguration(tlsConfiguration); + assertEquals( + Set.of(RedisErrorMessages.TLS_CLIENT_AUTH_ENABLED_BUT_CLIENT_KEY_MISSING_ERROR_MSG), + pluginExecutor.validateDatasource(datasourceConfiguration)); + } + + @Test + public void itShouldPassValidationWithTlsEnabledAndValidCertificates() { + DatasourceConfiguration datasourceConfiguration = createDatasourceConfiguration(); + TlsConfiguration tlsConfiguration = new TlsConfiguration(); + tlsConfiguration.setTlsEnabled(true); + tlsConfiguration.setVerifyTlsCertificate(true); + tlsConfiguration.setRequiresClientAuth(true); + tlsConfiguration.setCaCertificateFile(new UploadedFile("ca-cert", "base64Key")); + tlsConfiguration.setClientCertificateFile(new UploadedFile("client-cert", "base64Key")); + tlsConfiguration.setClientKeyFile(new UploadedFile("client-key", "base64Key")); + datasourceConfiguration.setTlsConfiguration(tlsConfiguration); + + assertTrue(pluginExecutor.validateDatasource(datasourceConfiguration).isEmpty()); + } + @Test public void itShouldThrowErrorIfHostnameIsInvalid() { From 2abfee64d9316c9c6eaf5638c0a0d598c40dcc4f Mon Sep 17 00:00:00 2001 From: AnnaHariprasad5123 Date: Mon, 28 Oct 2024 15:33:44 +0530 Subject: [PATCH 2/8] fix: some changes --- .../com/external/plugins/RedisPlugin.java | 7 ++-- .../com/external/utils/RedisTLSManager.java | 11 ++++-- .../com/external/utils/RedisURIUtils.java | 3 +- .../com/external/plugins/RedisPluginTest.java | 36 +++++++------------ 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java index eeb3f926d027..25652a19516f 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java @@ -263,7 +263,10 @@ public Mono datasourceCreate(DatasourceConfiguration datasourceConfig int timeout = (int) Duration.ofSeconds(CONNECTION_TIMEOUT).toMillis(); URI uri = RedisURIUtils.getURI(datasourceConfiguration); - if (!datasourceConfiguration.getTlsConfiguration().getTlsEnabled()) { + if (datasourceConfiguration.getTlsConfiguration() != null + && !datasourceConfiguration + .getTlsConfiguration() + .getTlsEnabled()) { JedisPool jedisPool = new JedisPool(poolConfig, uri, timeout); log.debug(Thread.currentThread().getName() + ": Created Jedis pool."); return jedisPool; @@ -308,7 +311,7 @@ public Set validateDatasource(DatasourceConfiguration datasourceConfigur } TlsConfiguration tlsConfiguration = datasourceConfiguration.getTlsConfiguration(); - if (tlsConfiguration.getTlsEnabled()) { + if (tlsConfiguration != null && tlsConfiguration.getTlsEnabled()) { // Check for CA certificate if TLS verification is enabled if (tlsConfiguration.getVerifyTlsCertificate() && (tlsConfiguration.getCaCertificateFile() == null diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java index 637dd1abf094..509ae76b8cee 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java @@ -32,9 +32,14 @@ public static JedisPool createJedisPoolWithTLS( throws Exception { TlsConfiguration tlsConfiguration = datasourceConfiguration.getTlsConfiguration(); - boolean verifyTlsCertificate = tlsConfiguration.getVerifyTlsCertificate(); - boolean requiresClientAuth = tlsConfiguration.getRequiresClientAuth(); - + if (tlsConfiguration == null) { + throw new IllegalArgumentException("TLS configuration is missing"); + } + Boolean verifyTlsCertificate = tlsConfiguration.getVerifyTlsCertificate(); + Boolean requiresClientAuth = tlsConfiguration.getRequiresClientAuth(); + if (verifyTlsCertificate == null || requiresClientAuth == null) { + throw new IllegalArgumentException("TLS configuration flags cannot be null"); + } SSLContext sslContext = SSLContext.getInstance("TLS"); KeyManagerFactory keyManagerFactory = null; diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java index 597cff7441f9..e002df0ae18a 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java @@ -18,7 +18,8 @@ public class RedisURIUtils { public static URI getURI(DatasourceConfiguration datasourceConfiguration) throws URISyntaxException { StringBuilder builder = new StringBuilder(); - if (datasourceConfiguration.getTlsConfiguration().getTlsEnabled()) { + if (datasourceConfiguration.getTlsConfiguration() != null + && datasourceConfiguration.getTlsConfiguration().getTlsEnabled()) { builder.append(REDIS_SSL_SCHEME); } else { builder.append(REDIS_SCHEME); diff --git a/app/server/appsmith-plugins/redisPlugin/src/test/java/com/external/plugins/RedisPluginTest.java b/app/server/appsmith-plugins/redisPlugin/src/test/java/com/external/plugins/RedisPluginTest.java index 7be729da9af7..86f026f0a689 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/test/java/com/external/plugins/RedisPluginTest.java +++ b/app/server/appsmith-plugins/redisPlugin/src/test/java/com/external/plugins/RedisPluginTest.java @@ -57,6 +57,12 @@ public static void setup() { port = redis.getFirstMappedPort(); } + private TlsConfiguration addTLSConfiguration(DatasourceConfiguration datasourceConfiguration) { + TlsConfiguration tlsConfiguration = new TlsConfiguration(); + tlsConfiguration.setTlsEnabled(false); + return tlsConfiguration; + } + private DatasourceConfiguration createDatasourceConfiguration() { Endpoint endpoint = new Endpoint(); endpoint.setHost(host); @@ -64,9 +70,8 @@ private DatasourceConfiguration createDatasourceConfiguration() { DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); - TlsConfiguration tlsConfiguration = new TlsConfiguration(); - tlsConfiguration.setTlsEnabled(false); - datasourceConfiguration.setTlsConfiguration(tlsConfiguration); + + datasourceConfiguration.setTlsConfiguration(addTLSConfiguration(datasourceConfiguration)); return datasourceConfiguration; } @@ -83,9 +88,7 @@ public void itShouldCreateDatasource() { @Test public void itShouldValidateDatasourceWithNoEndpoints() { DatasourceConfiguration invalidDatasourceConfiguration = new DatasourceConfiguration(); - TlsConfiguration tlsConfiguration = new TlsConfiguration(); - tlsConfiguration.setTlsEnabled(false); - invalidDatasourceConfiguration.setTlsConfiguration(tlsConfiguration); + invalidDatasourceConfiguration.setTlsConfiguration(addTLSConfiguration(invalidDatasourceConfiguration)); assertEquals( Set.of(RedisErrorMessages.DS_MISSING_HOST_ADDRESS_ERROR_MSG), @@ -98,10 +101,7 @@ public void itShouldValidateDatasourceWithInvalidEndpoint() { Endpoint endpoint = new Endpoint(); invalidDatasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); - - TlsConfiguration tlsConfiguration = new TlsConfiguration(); - tlsConfiguration.setTlsEnabled(false); - invalidDatasourceConfiguration.setTlsConfiguration(tlsConfiguration); + invalidDatasourceConfiguration.setTlsConfiguration(addTLSConfiguration(invalidDatasourceConfiguration)); assertEquals( Set.of(RedisErrorMessages.DS_MISSING_HOST_ADDRESS_ERROR_MSG), @@ -115,10 +115,7 @@ public void itShouldValidateDatasourceWithEmptyPort() { Endpoint endpoint = new Endpoint(); endpoint.setHost("test-host"); invalidDatasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); - - TlsConfiguration tlsConfiguration = new TlsConfiguration(); - tlsConfiguration.setTlsEnabled(false); - invalidDatasourceConfiguration.setTlsConfiguration(tlsConfiguration); + invalidDatasourceConfiguration.setTlsConfiguration(addTLSConfiguration(invalidDatasourceConfiguration)); // Since default port is picked, set of invalids should be empty. assertEquals(pluginExecutor.validateDatasource(invalidDatasourceConfiguration), Set.of()); @@ -135,10 +132,7 @@ public void itShouldValidateDatasourceWithInvalidAuth() { invalidAuth.setUsername("username"); // skip password invalidDatasourceConfiguration.setAuthentication(invalidAuth); invalidDatasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); - - TlsConfiguration tlsConfiguration = new TlsConfiguration(); - tlsConfiguration.setTlsEnabled(false); - invalidDatasourceConfiguration.setTlsConfiguration(tlsConfiguration); + invalidDatasourceConfiguration.setTlsConfiguration(addTLSConfiguration(invalidDatasourceConfiguration)); assertEquals( Set.of(RedisErrorMessages.DS_MISSING_PASSWORD_ERROR_MSG), @@ -159,10 +153,7 @@ public void itShouldValidateDatasource() { endpoint.setPort(Long.valueOf(port)); datasourceConfiguration.setAuthentication(auth); datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); - - TlsConfiguration tlsConfiguration = new TlsConfiguration(); - tlsConfiguration.setTlsEnabled(false); - datasourceConfiguration.setTlsConfiguration(tlsConfiguration); + datasourceConfiguration.setTlsConfiguration(addTLSConfiguration(datasourceConfiguration)); assertTrue(pluginExecutor.validateDatasource(datasourceConfiguration).isEmpty()); } @@ -188,7 +179,6 @@ public void itShouldValidateDatasourceWithTlsEnabledAndMissingCACertificate() { tlsConfiguration.setVerifyTlsCertificate(true); tlsConfiguration.setRequiresClientAuth(false); datasourceConfiguration.setTlsConfiguration(tlsConfiguration); - datasourceConfiguration.getTlsConfiguration().setVerifyTlsCertificate(true); assertEquals( Set.of(RedisErrorMessages.CA_CERTIFICATE_MISSING_ERROR_MSG), From 148a41d6a8e92583bd167870763b9cea72e00080 Mon Sep 17 00:00:00 2001 From: AnnaHariprasad5123 Date: Mon, 11 Nov 2024 14:13:40 +0530 Subject: [PATCH 3/8] fix: add-try/catch-blocks --- .../com/external/utils/RedisTLSManager.java | 175 ++++++++++-------- 1 file changed, 95 insertions(+), 80 deletions(-) diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java index 509ae76b8cee..402ba178d1f0 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java @@ -13,12 +13,14 @@ import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.net.URI; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.KeyStore; import java.security.PrivateKey; import java.security.SecureRandom; +import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.PKCS8EncodedKeySpec; @@ -42,90 +44,103 @@ public static JedisPool createJedisPoolWithTLS( } SSLContext sslContext = SSLContext.getInstance("TLS"); KeyManagerFactory keyManagerFactory = null; - - // Handle client authentication if required, regardless of certificate verification - if (requiresClientAuth) { - - CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - - // Load client certificate - String clientCertContent = - new String(tlsConfiguration.getClientCertificateFile().getDecodedContent(), StandardCharsets.UTF_8); - X509Certificate clientCert = (X509Certificate) - certificateFactory.generateCertificate(new ByteArrayInputStream(clientCertContent.getBytes())); - - // Load client private key - String clientKey = - new String(tlsConfiguration.getClientKeyFile().getDecodedContent(), StandardCharsets.UTF_8); - PrivateKey privateKey = loadPrivateKey(clientKey); - - // KeyStore for client authentication - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - keyStore.load(null, null); - keyStore.setKeyEntry("client-key", privateKey, null, new X509Certificate[] {clientCert}); - - keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, null); - } - - // Handle server certificate verification - TrustManager[] trustManagers; - if (verifyTlsCertificate) { - - // CA certificate verification - String caCertContent = - new String(tlsConfiguration.getCaCertificateFile().getDecodedContent(), StandardCharsets.UTF_8); - - CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - X509Certificate caCert = (X509Certificate) - certificateFactory.generateCertificate(new ByteArrayInputStream(caCertContent.getBytes())); - - KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); - trustStore.load(null, null); - trustStore.setCertificateEntry("ca-cert", caCert); - - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(trustStore); - trustManagers = trustManagerFactory.getTrustManagers(); - } else { - trustManagers = new TrustManager[] { - new X509TrustManager() { - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; + try { + // Handle client authentication if required, regardless of certificate verification + if (requiresClientAuth) { + + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + + // Load client certificate + String clientCertContent = new String( + tlsConfiguration.getClientCertificateFile().getDecodedContent(), StandardCharsets.UTF_8); + X509Certificate clientCert = (X509Certificate) + certificateFactory.generateCertificate(new ByteArrayInputStream(clientCertContent.getBytes())); + + // Load client private key + String clientKey = + new String(tlsConfiguration.getClientKeyFile().getDecodedContent(), StandardCharsets.UTF_8); + PrivateKey privateKey = loadPrivateKey(clientKey); + + // KeyStore for client authentication + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, null); + keyStore.setKeyEntry("client-key", privateKey, null, new X509Certificate[] {clientCert}); + + keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, null); + } + + // Handle server certificate verification + TrustManager[] trustManagers; + if (verifyTlsCertificate) { + + // CA certificate verification + String caCertContent = + new String(tlsConfiguration.getCaCertificateFile().getDecodedContent(), StandardCharsets.UTF_8); + + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + X509Certificate caCert = (X509Certificate) + certificateFactory.generateCertificate(new ByteArrayInputStream(caCertContent.getBytes())); + + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(null, null); + trustStore.setCertificateEntry("ca-cert", caCert); + + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(trustStore); + trustManagers = trustManagerFactory.getTrustManagers(); + } else { + trustManagers = new TrustManager[] { + new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) {} + + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) {} } - - @Override - public void checkClientTrusted(X509Certificate[] certs, String authType) {} - - @Override - public void checkServerTrusted(X509Certificate[] certs, String authType) {} - } - }; + }; + } + + // Initialize SSL context with appropriate managers + sslContext.init( + requiresClientAuth ? keyManagerFactory.getKeyManagers() : null, trustManagers, new SecureRandom()); + + SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + + // Create and return JedisPool with TLS + JedisPool jedisPool = new JedisPool(poolConfig, uri, timeout, sslSocketFactory, null, null); + log.debug(Thread.currentThread().getName() + ": Created Jedis pool with TLS."); + return jedisPool; + } catch (CertificateException | IOException e) { + log.error("Error occurred during TLS setup (Certificate or I/O issue): {}", e.getMessage(), e); + throw e; + } catch (Exception e) { + log.error("Unexpected error occurred while creating Jedis pool with TLS: {}", e.getMessage(), e); + throw e; } - - // Initialize SSL context with appropriate managers - sslContext.init( - requiresClientAuth ? keyManagerFactory.getKeyManagers() : null, trustManagers, new SecureRandom()); - - SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); - - // Create and return JedisPool with TLS - JedisPool jedisPool = new JedisPool(poolConfig, uri, timeout, sslSocketFactory, null, null); - log.debug(Thread.currentThread().getName() + ": Created Jedis pool with TLS."); - return jedisPool; } private static PrivateKey loadPrivateKey(String clientKey) throws Exception { - clientKey = clientKey - .replace("-----BEGIN PRIVATE KEY-----", "") - .replace("-----END PRIVATE KEY-----", "") - .replaceAll("\\s", ""); - - byte[] keyBytes = Base64.getDecoder().decode(clientKey); - PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); - KeyFactory kf = KeyFactory.getInstance("RSA"); - return kf.generatePrivate(spec); + try { + clientKey = clientKey + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s", ""); + + byte[] keyBytes = Base64.getDecoder().decode(clientKey); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePrivate(spec); + } catch (Exception e) { + // Catch all other exceptions + log.error("Unexpected error while loading private key: {}", e.getMessage(), e); + throw e; + } } } From 75f8f3cd3c637ba92f8f6ba4768007badf16200b Mon Sep 17 00:00:00 2001 From: AnnaHariprasad5123 Date: Mon, 18 Nov 2024 15:52:18 +0530 Subject: [PATCH 4/8] fix: resolved comments --- .../com/external/plugins/RedisPlugin.java | 4 +- .../com/external/utils/RedisTLSManager.java | 61 +++++++++++++------ .../com/external/utils/RedisURIUtils.java | 6 ++ .../com/external/plugins/RedisPluginTest.java | 58 ++++++++++++++++++ 4 files changed, 108 insertions(+), 21 deletions(-) diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java index 25652a19516f..d8be2b1963fc 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java @@ -263,8 +263,8 @@ public Mono datasourceCreate(DatasourceConfiguration datasourceConfig int timeout = (int) Duration.ofSeconds(CONNECTION_TIMEOUT).toMillis(); URI uri = RedisURIUtils.getURI(datasourceConfiguration); - if (datasourceConfiguration.getTlsConfiguration() != null - && !datasourceConfiguration + if (datasourceConfiguration.getTlsConfiguration() == null + || !datasourceConfiguration .getTlsConfiguration() .getTlsEnabled()) { JedisPool jedisPool = new JedisPool(poolConfig, uri, timeout); diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java index 402ba178d1f0..482f526ea33c 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java @@ -50,16 +50,28 @@ public static JedisPool createJedisPoolWithTLS( CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + X509Certificate clientCert; // Load client certificate - String clientCertContent = new String( - tlsConfiguration.getClientCertificateFile().getDecodedContent(), StandardCharsets.UTF_8); - X509Certificate clientCert = (X509Certificate) - certificateFactory.generateCertificate(new ByteArrayInputStream(clientCertContent.getBytes())); - + byte[] clientCertBytes = + tlsConfiguration.getClientCertificateFile().getDecodedContent(); + try (ByteArrayInputStream certInputStream = new ByteArrayInputStream(clientCertBytes)) { + clientCert = (X509Certificate) certificateFactory.generateCertificate(certInputStream); + // Use the clientCert object as needed + } finally { + // Clear sensitive data from memory + java.util.Arrays.fill(clientCertBytes, (byte) 0); + } + + PrivateKey privateKey; // Load client private key - String clientKey = - new String(tlsConfiguration.getClientKeyFile().getDecodedContent(), StandardCharsets.UTF_8); - PrivateKey privateKey = loadPrivateKey(clientKey); + byte[] clientKeyBytes = tlsConfiguration.getClientKeyFile().getDecodedContent(); + try { + privateKey = loadPrivateKey(clientKeyBytes); + // Use the privateKey object as needed + } finally { + // Clear sensitive data from memory + java.util.Arrays.fill(clientKeyBytes, (byte) 0); + } // KeyStore for client authentication KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); @@ -108,8 +120,9 @@ public void checkServerTrusted(X509Certificate[] certs, String authType) {} } // Initialize SSL context with appropriate managers + SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); sslContext.init( - requiresClientAuth ? keyManagerFactory.getKeyManagers() : null, trustManagers, new SecureRandom()); + requiresClientAuth ? keyManagerFactory.getKeyManagers() : null, trustManagers, secureRandom); SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); @@ -126,21 +139,31 @@ public void checkServerTrusted(X509Certificate[] certs, String authType) {} } } - private static PrivateKey loadPrivateKey(String clientKey) throws Exception { + private static PrivateKey loadPrivateKey(byte[] keyBytes) throws Exception { + byte[] decodedKey = null; try { - clientKey = clientKey - .replace("-----BEGIN PRIVATE KEY-----", "") - .replace("-----END PRIVATE KEY-----", "") - .replaceAll("\\s", ""); - - byte[] keyBytes = Base64.getDecoder().decode(clientKey); - PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); - KeyFactory kf = KeyFactory.getInstance("RSA"); + String keyString = new String(keyBytes); + + String keyType = "RSA"; + if (keyString.contains("BEGIN EC PRIVATE KEY")) { + keyType = "EC"; + } + + String cleanKey = + keyString.replaceAll("-----(?:BEGIN|END)[^-]+-----", "").replaceAll("\\s", ""); + + decodedKey = Base64.getDecoder().decode(cleanKey); + + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decodedKey); + KeyFactory kf = KeyFactory.getInstance(keyType); return kf.generatePrivate(spec); } catch (Exception e) { - // Catch all other exceptions log.error("Unexpected error while loading private key: {}", e.getMessage(), e); throw e; + } finally { + if (decodedKey != null) { + java.util.Arrays.fill(decodedKey, (byte) 0); + } } } } diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java index e002df0ae18a..fb7aa91e934f 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java @@ -3,12 +3,14 @@ import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.Endpoint; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.ObjectUtils; import org.pf4j.util.StringUtils; import java.net.URI; import java.net.URISyntaxException; +@Slf4j public class RedisURIUtils { public static final Long DEFAULT_PORT = 6379L; private static final String REDIS_SCHEME = "redis://"; @@ -21,6 +23,10 @@ public static URI getURI(DatasourceConfiguration datasourceConfiguration) throws if (datasourceConfiguration.getTlsConfiguration() != null && datasourceConfiguration.getTlsConfiguration().getTlsEnabled()) { builder.append(REDIS_SSL_SCHEME); + Endpoint endpoint = datasourceConfiguration.getEndpoints().get(0); + if (endpoint.getPort() != null && endpoint.getPort() == DEFAULT_PORT) { + log.warn("Using default non-TLS port {} with TLS enabled", DEFAULT_PORT); + } } else { builder.append(REDIS_SCHEME); } diff --git a/app/server/appsmith-plugins/redisPlugin/src/test/java/com/external/plugins/RedisPluginTest.java b/app/server/appsmith-plugins/redisPlugin/src/test/java/com/external/plugins/RedisPluginTest.java index 86f026f0a689..7182a8c8a79a 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/test/java/com/external/plugins/RedisPluginTest.java +++ b/app/server/appsmith-plugins/redisPlugin/src/test/java/com/external/plugins/RedisPluginTest.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Set; @@ -48,6 +49,14 @@ public class RedisPluginTest { private static String host; private static Integer port; + private static final String MOCK_CA_CERTIFICATE = + "-----BEGIN CERTIFICATE-----\n" + "MIIC...mock-ca-cert...\n" + "-----END CERTIFICATE-----"; + + private static final String MOCK_CLIENT_CERTIFICATE = + "-----BEGIN CERTIFICATE-----\n" + "MIIC...mock-client-cert...\n" + "-----END CERTIFICATE-----"; + + private static final String MOCK_PRIVATE_KEY = + "-----BEGIN PRIVATE KEY-----\n" + "MIIEv...mock-private-key...\n" + "-----END PRIVATE KEY-----"; private RedisPlugin.RedisPluginExecutor pluginExecutor = new RedisPlugin.RedisPluginExecutor(); @@ -57,6 +66,17 @@ public static void setup() { port = redis.getFirstMappedPort(); } + public static UploadedFile createUploadedFile(String fileName, String content, String mimeType) { + if (fileName == null || content == null || mimeType == null) { + throw new IllegalArgumentException("File name, content, and MIME type cannot be null"); + } + + String base64Content = + "data:" + mimeType + ";base64," + Base64.getEncoder().encodeToString(content.getBytes()); + + return new UploadedFile(fileName, base64Content); + } + private TlsConfiguration addTLSConfiguration(DatasourceConfiguration datasourceConfiguration) { TlsConfiguration tlsConfiguration = new TlsConfiguration(); tlsConfiguration.setTlsEnabled(false); @@ -75,6 +95,32 @@ private DatasourceConfiguration createDatasourceConfiguration() { return datasourceConfiguration; } + private DatasourceConfiguration createDatasourceConfigurationWithTLSEnabled() { + Endpoint endpoint = new Endpoint(); + endpoint.setHost(host); + endpoint.setPort(Long.valueOf(port)); + + DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); + + String base64CaCert = Base64.getEncoder().encodeToString(MOCK_CA_CERTIFICATE.getBytes()); + String base64ClientCert = Base64.getEncoder().encodeToString(MOCK_CLIENT_CERTIFICATE.getBytes()); + String base64PrivateKey = Base64.getEncoder().encodeToString(MOCK_PRIVATE_KEY.getBytes()); + + TlsConfiguration tlsConfiguration = new TlsConfiguration(); + tlsConfiguration.setTlsEnabled(true); + tlsConfiguration.setVerifyTlsCertificate(true); + tlsConfiguration.setCaCertificateFile( + createUploadedFile("ca-cert.crt", base64CaCert, "application/x-x509-ca-cert")); + tlsConfiguration.setRequiresClientAuth(true); + tlsConfiguration.setClientCertificateFile( + createUploadedFile("client-cert.crt", base64ClientCert, "application/x-x509-ca-cert")); + tlsConfiguration.setClientKeyFile( + createUploadedFile("client-key.key", base64PrivateKey, "application/x-pem-file")); + datasourceConfiguration.setTlsConfiguration(tlsConfiguration); + return datasourceConfiguration; + } + @Test public void itShouldCreateDatasource() { DatasourceConfiguration datasourceConfiguration = createDatasourceConfiguration(); @@ -228,6 +274,18 @@ public void itShouldPassValidationWithTlsEnabledAndValidCertificates() { assertTrue(pluginExecutor.validateDatasource(datasourceConfiguration).isEmpty()); } + @Test + public void itShouldTestDatasourceWithTlsEnabledAndValidCertificates() { + DatasourceConfiguration datasourceConfiguration = createDatasourceConfigurationWithTLSEnabled(); + Mono datasourceTestResultMono = pluginExecutor.testDatasource(datasourceConfiguration); + + StepVerifier.create(datasourceTestResultMono) + .assertNext(datasourceTestResult -> { + assertNotNull(datasourceTestResult); + }) + .verifyComplete(); + } + @Test public void itShouldThrowErrorIfHostnameIsInvalid() { From d43611d56ba07e6cd5b73e540b78473a4e3fec5c Mon Sep 17 00:00:00 2001 From: AnnaHariprasad5123 Date: Mon, 18 Nov 2024 16:14:03 +0530 Subject: [PATCH 5/8] fix: added catch block --- .../com/external/utils/RedisTLSManager.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java index 482f526ea33c..6f62d766339b 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java @@ -50,26 +50,29 @@ public static JedisPool createJedisPoolWithTLS( CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - X509Certificate clientCert; - // Load client certificate + X509Certificate clientCert = null; + byte[] clientCertBytes = tlsConfiguration.getClientCertificateFile().getDecodedContent(); + try (ByteArrayInputStream certInputStream = new ByteArrayInputStream(clientCertBytes)) { clientCert = (X509Certificate) certificateFactory.generateCertificate(certInputStream); - // Use the clientCert object as needed + + } catch (CertificateException e) { + log.error("Error occurred while parsing client certificate: " + e.getMessage()); } finally { - // Clear sensitive data from memory java.util.Arrays.fill(clientCertBytes, (byte) 0); } - PrivateKey privateKey; - // Load client private key + PrivateKey privateKey = null; + byte[] clientKeyBytes = tlsConfiguration.getClientKeyFile().getDecodedContent(); + try { privateKey = loadPrivateKey(clientKeyBytes); - // Use the privateKey object as needed + } catch (Exception e) { + log.error("Error occurred while parsing private key: " + e.getMessage()); } finally { - // Clear sensitive data from memory java.util.Arrays.fill(clientKeyBytes, (byte) 0); } @@ -86,7 +89,6 @@ public static JedisPool createJedisPoolWithTLS( TrustManager[] trustManagers; if (verifyTlsCertificate) { - // CA certificate verification String caCertContent = new String(tlsConfiguration.getCaCertificateFile().getDecodedContent(), StandardCharsets.UTF_8); @@ -120,10 +122,8 @@ public void checkServerTrusted(X509Certificate[] certs, String authType) {} } // Initialize SSL context with appropriate managers - SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); sslContext.init( - requiresClientAuth ? keyManagerFactory.getKeyManagers() : null, trustManagers, secureRandom); - + requiresClientAuth ? keyManagerFactory.getKeyManagers() : null, trustManagers, new SecureRandom()); SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); // Create and return JedisPool with TLS From faae2494eb7b94c128e5477a966a9993954ae279 Mon Sep 17 00:00:00 2001 From: AnnaHariprasad5123 Date: Mon, 18 Nov 2024 17:08:47 +0530 Subject: [PATCH 6/8] added throw from catch blocks --- .../src/main/java/com/external/utils/RedisTLSManager.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java index 6f62d766339b..5d27464c132f 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisTLSManager.java @@ -60,6 +60,7 @@ public static JedisPool createJedisPoolWithTLS( } catch (CertificateException e) { log.error("Error occurred while parsing client certificate: " + e.getMessage()); + throw e; } finally { java.util.Arrays.fill(clientCertBytes, (byte) 0); } @@ -72,6 +73,7 @@ public static JedisPool createJedisPoolWithTLS( privateKey = loadPrivateKey(clientKeyBytes); } catch (Exception e) { log.error("Error occurred while parsing private key: " + e.getMessage()); + throw e; } finally { java.util.Arrays.fill(clientKeyBytes, (byte) 0); } From 8b87bbf7debaee7e800e26acf6f1380fbf1d5a71 Mon Sep 17 00:00:00 2001 From: AnnaHariprasad5123 Date: Tue, 19 Nov 2024 07:47:44 +0530 Subject: [PATCH 7/8] removed-unused-parameter --- .../java/com/external/utils/RedisURIUtils.java | 3 ++- .../java/com/external/plugins/RedisPluginTest.java | 14 +++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java index fb7aa91e934f..ec4b1c348acd 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java @@ -20,7 +20,8 @@ public class RedisURIUtils { public static URI getURI(DatasourceConfiguration datasourceConfiguration) throws URISyntaxException { StringBuilder builder = new StringBuilder(); - if (datasourceConfiguration.getTlsConfiguration() != null + if (datasourceConfiguration != null + && datasourceConfiguration.getTlsConfiguration() != null && datasourceConfiguration.getTlsConfiguration().getTlsEnabled()) { builder.append(REDIS_SSL_SCHEME); Endpoint endpoint = datasourceConfiguration.getEndpoints().get(0); diff --git a/app/server/appsmith-plugins/redisPlugin/src/test/java/com/external/plugins/RedisPluginTest.java b/app/server/appsmith-plugins/redisPlugin/src/test/java/com/external/plugins/RedisPluginTest.java index 7182a8c8a79a..89635d4e4126 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/test/java/com/external/plugins/RedisPluginTest.java +++ b/app/server/appsmith-plugins/redisPlugin/src/test/java/com/external/plugins/RedisPluginTest.java @@ -77,7 +77,7 @@ public static UploadedFile createUploadedFile(String fileName, String content, S return new UploadedFile(fileName, base64Content); } - private TlsConfiguration addTLSConfiguration(DatasourceConfiguration datasourceConfiguration) { + private TlsConfiguration addTLSConfiguration() { TlsConfiguration tlsConfiguration = new TlsConfiguration(); tlsConfiguration.setTlsEnabled(false); return tlsConfiguration; @@ -91,7 +91,7 @@ private DatasourceConfiguration createDatasourceConfiguration() { DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); - datasourceConfiguration.setTlsConfiguration(addTLSConfiguration(datasourceConfiguration)); + datasourceConfiguration.setTlsConfiguration(addTLSConfiguration()); return datasourceConfiguration; } @@ -134,7 +134,7 @@ public void itShouldCreateDatasource() { @Test public void itShouldValidateDatasourceWithNoEndpoints() { DatasourceConfiguration invalidDatasourceConfiguration = new DatasourceConfiguration(); - invalidDatasourceConfiguration.setTlsConfiguration(addTLSConfiguration(invalidDatasourceConfiguration)); + invalidDatasourceConfiguration.setTlsConfiguration(addTLSConfiguration()); assertEquals( Set.of(RedisErrorMessages.DS_MISSING_HOST_ADDRESS_ERROR_MSG), @@ -147,7 +147,7 @@ public void itShouldValidateDatasourceWithInvalidEndpoint() { Endpoint endpoint = new Endpoint(); invalidDatasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); - invalidDatasourceConfiguration.setTlsConfiguration(addTLSConfiguration(invalidDatasourceConfiguration)); + invalidDatasourceConfiguration.setTlsConfiguration(addTLSConfiguration()); assertEquals( Set.of(RedisErrorMessages.DS_MISSING_HOST_ADDRESS_ERROR_MSG), @@ -161,7 +161,7 @@ public void itShouldValidateDatasourceWithEmptyPort() { Endpoint endpoint = new Endpoint(); endpoint.setHost("test-host"); invalidDatasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); - invalidDatasourceConfiguration.setTlsConfiguration(addTLSConfiguration(invalidDatasourceConfiguration)); + invalidDatasourceConfiguration.setTlsConfiguration(addTLSConfiguration()); // Since default port is picked, set of invalids should be empty. assertEquals(pluginExecutor.validateDatasource(invalidDatasourceConfiguration), Set.of()); @@ -178,7 +178,7 @@ public void itShouldValidateDatasourceWithInvalidAuth() { invalidAuth.setUsername("username"); // skip password invalidDatasourceConfiguration.setAuthentication(invalidAuth); invalidDatasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); - invalidDatasourceConfiguration.setTlsConfiguration(addTLSConfiguration(invalidDatasourceConfiguration)); + invalidDatasourceConfiguration.setTlsConfiguration(addTLSConfiguration()); assertEquals( Set.of(RedisErrorMessages.DS_MISSING_PASSWORD_ERROR_MSG), @@ -199,7 +199,7 @@ public void itShouldValidateDatasource() { endpoint.setPort(Long.valueOf(port)); datasourceConfiguration.setAuthentication(auth); datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); - datasourceConfiguration.setTlsConfiguration(addTLSConfiguration(datasourceConfiguration)); + datasourceConfiguration.setTlsConfiguration(addTLSConfiguration()); assertTrue(pluginExecutor.validateDatasource(datasourceConfiguration).isEmpty()); } From 6da938537086bdafbbab7321db7ecf7321b25325 Mon Sep 17 00:00:00 2001 From: AnnaHariprasad5123 Date: Tue, 19 Nov 2024 08:26:10 +0530 Subject: [PATCH 8/8] added-condition-for-endpoint --- .../src/main/java/com/external/utils/RedisURIUtils.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java index ec4b1c348acd..7f68ad8991a8 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/utils/RedisURIUtils.java @@ -24,9 +24,12 @@ public static URI getURI(DatasourceConfiguration datasourceConfiguration) throws && datasourceConfiguration.getTlsConfiguration() != null && datasourceConfiguration.getTlsConfiguration().getTlsEnabled()) { builder.append(REDIS_SSL_SCHEME); - Endpoint endpoint = datasourceConfiguration.getEndpoints().get(0); - if (endpoint.getPort() != null && endpoint.getPort() == DEFAULT_PORT) { - log.warn("Using default non-TLS port {} with TLS enabled", DEFAULT_PORT); + if (datasourceConfiguration.getEndpoints() != null + && !datasourceConfiguration.getEndpoints().isEmpty()) { + Endpoint endpoint = datasourceConfiguration.getEndpoints().get(0); + if (endpoint.getPort() != null && endpoint.getPort() == DEFAULT_PORT) { + log.warn("Using default non-TLS port {} with TLS enabled", DEFAULT_PORT); + } } } else { builder.append(REDIS_SCHEME);