Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Comment thread
NilanshBansal marked this conversation as resolved.
Outdated

@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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -261,9 +263,16 @@ public Mono<JedisPool> 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() != null
&& !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);
}
Comment thread
NilanshBansal marked this conversation as resolved.
Outdated
})
.subscribeOn(scheduler);
}
Expand Down Expand Up @@ -301,6 +310,33 @@ public Set<String> validateDatasource(DatasourceConfiguration datasourceConfigur
invalids.add(RedisErrorMessages.DS_MISSING_PASSWORD_ERROR_MSG);
}

TlsConfiguration tlsConfiguration = datasourceConfiguration.getTlsConfiguration();
Comment thread
NilanshBansal marked this conversation as resolved.
if (tlsConfiguration != null && 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);
}
}
}
Comment thread
NilanshBansal marked this conversation as resolved.

return invalids;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
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 {
Comment thread
NilanshBansal marked this conversation as resolved.

TlsConfiguration tlsConfiguration = datasourceConfiguration.getTlsConfiguration();
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;

// 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);
Comment thread
NilanshBansal marked this conversation as resolved.
Outdated
PrivateKey privateKey = loadPrivateKey(clientKey);
Comment thread
NilanshBansal marked this conversation as resolved.
Outdated

// 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)
Comment thread
NilanshBansal marked this conversation as resolved.
Outdated
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) {}
}
};
}
Comment thread
NilanshBansal marked this conversation as resolved.
Outdated

// Initialize SSL context with appropriate managers
sslContext.init(
requiresClientAuth ? keyManagerFactory.getKeyManagers() : null, trustManagers, new SecureRandom());
Comment thread
NilanshBansal marked this conversation as resolved.
Outdated

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;
}
Comment thread
NilanshBansal marked this conversation as resolved.

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);
}
Comment thread
NilanshBansal marked this conversation as resolved.
Outdated
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ 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() != null
Comment thread
NilanshBansal marked this conversation as resolved.
Outdated
&& datasourceConfiguration.getTlsConfiguration().getTlsEnabled()) {
builder.append(REDIS_SSL_SCHEME);
} else {
builder.append(REDIS_SCHEME);
}
Comment thread
NilanshBansal marked this conversation as resolved.

String uriAuth = getUriAuth(datasourceConfiguration);
builder.append(uriAuth);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Comment thread
NilanshBansal marked this conversation as resolved.
]
}
]
}
]
}
Loading