Skip to content

Commit

Permalink
Add SslOptions (#3980)
Browse files Browse the repository at this point in the history
Similar to SslOptions in Lettuce library.

---

* Add SslOptions from Lettuce library

* Add InsecureTrustManagerFactory from Netty library

* Separate SSL socket creation in DefaultJedisSocketFactory

* Add SslOptions in JedisClientConfig

* Add SslHostnameVerifyMode

inspired by SslVerifyMode in Lettuce

* Allow system default key and trust managers and insecure trust manager

* fix jdoc

* Address review comments

* Revert back to verification mode names in Lettuce,

to avoid confusion at this moment.

* Rename insecureTrust to noTruststoreVerification;

does this reduce confusion?

* SslVerifyMode is renamed back

and INSECURE option (renamed from NONE) is added back in SslVerifyMode.

* Remove InsecureTrustManagerFactory

* Disable ALL existing SSL tests

* JedisTest with SslOptions

* SSLACLJedisTest with SslOptions

* SSLOptionsJedisSentinelPoolTest with SslOptions

* SSLJedisClusterTest with SslOptions

* TODO comment to enable existing SSL tests

* TODO command to enable existing SSL tests in csc package

* Enable existing SSL tests without impacting new ones

* Missing enable existing SSL tests without impacting new ones

* Keep only Builder pattern constructor for DefaultJedisClientConfig

* Limit HostnameVerifier only for legacy ssl config

and document as JavaDoc in JedisClientConfig

* Remove unused codes from SSLOptionsJedisTest

* Increase code reuse for LocalhostVerifier

* Individual JavaDoc for each SslVerifyMode

* Custom SocketFactory won't be supported with SslOptions

* Deprecate DefaultJedisClientConfig.copyConfig()

* Add option to set SSLContext protocol

* Remove options to set KeyManager and TrustManager algorithms

* Add File checkers

* minor user/password change

* minor update javadoc

* Allow manual HostnameVerifier with SslOptions

* Make test connectWithCustomHostNameVerifier() pass

* Better SslOptions with custom HostnameVerifier

in connectWithCustomHostNameVerifier() test

* Shorten sslContextProtocol to sslProtocol

* Use null as default password,

unlike Lettuce where it uses empty char array.

* Make an accidental private truststore builder option public

* Remove Lettuce comments

* Add JedisPooled tests

* Use char array for password

* Remove file license

* Address code review

* Merge fix

* Deprecate helper methods in DefaultJedisClientConfig
  • Loading branch information
sazzad16 authored Jan 6, 2025
1 parent 7697b6a commit 87d451c
Show file tree
Hide file tree
Showing 16 changed files with 1,164 additions and 169 deletions.
131 changes: 88 additions & 43 deletions src/main/java/redis/clients/jedis/DefaultJedisClientConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public final class DefaultJedisClientConfig implements JedisClientConfig {
private final boolean ssl;
private final SSLSocketFactory sslSocketFactory;
private final SSLParameters sslParameters;
private final SslOptions sslOptions;
private final HostnameVerifier hostnameVerifier;

private final HostAndPortMapper hostAndPortMapper;
Expand All @@ -32,29 +33,23 @@ public final class DefaultJedisClientConfig implements JedisClientConfig {

private final AuthXManager authXManager;

private DefaultJedisClientConfig(RedisProtocol protocol, int connectionTimeoutMillis,
int soTimeoutMillis, int blockingSocketTimeoutMillis,
Supplier<RedisCredentials> credentialsProvider, int database, String clientName, boolean ssl,
SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
HostnameVerifier hostnameVerifier, HostAndPortMapper hostAndPortMapper,
ClientSetInfoConfig clientSetInfoConfig, boolean readOnlyForRedisClusterReplicas,
AuthXManager authXManager) {
this.redisProtocol = protocol;
this.connectionTimeoutMillis = connectionTimeoutMillis;
this.socketTimeoutMillis = soTimeoutMillis;
this.blockingSocketTimeoutMillis = blockingSocketTimeoutMillis;
this.credentialsProvider = credentialsProvider;
this.database = database;
this.clientName = clientName;
this.ssl = ssl;
this.sslSocketFactory = sslSocketFactory;
this.sslParameters = sslParameters;
this.hostnameVerifier = hostnameVerifier;
this.hostAndPortMapper = hostAndPortMapper;
this.clientSetInfoConfig = clientSetInfoConfig;
this.readOnlyForRedisClusterReplicas = readOnlyForRedisClusterReplicas;
this.authXManager = authXManager;

private DefaultJedisClientConfig(DefaultJedisClientConfig.Builder builder) {
this.redisProtocol = builder.redisProtocol;
this.connectionTimeoutMillis = builder.connectionTimeoutMillis;
this.socketTimeoutMillis = builder.socketTimeoutMillis;
this.blockingSocketTimeoutMillis = builder.blockingSocketTimeoutMillis;
this.credentialsProvider = builder.credentialsProvider;
this.database = builder.database;
this.clientName = builder.clientName;
this.ssl = builder.ssl;
this.sslSocketFactory = builder.sslSocketFactory;
this.sslParameters = builder.sslParameters;
this.sslOptions = builder.sslOptions;
this.hostnameVerifier = builder.hostnameVerifier;
this.hostAndPortMapper = builder.hostAndPortMapper;
this.clientSetInfoConfig = builder.clientSetInfoConfig;
this.readOnlyForRedisClusterReplicas = builder.readOnlyForRedisClusterReplicas;
this.authXManager = builder.authXManager;
}

@Override
Expand Down Expand Up @@ -123,6 +118,11 @@ public SSLParameters getSslParameters() {
return sslParameters;
}

@Override
public SslOptions getSslOptions() {
return sslOptions;
}

@Override
public HostnameVerifier getHostnameVerifier() {
return hostnameVerifier;
Expand Down Expand Up @@ -164,6 +164,7 @@ public static class Builder {
private boolean ssl = false;
private SSLSocketFactory sslSocketFactory = null;
private SSLParameters sslParameters = null;
private SslOptions sslOptions = null;
private HostnameVerifier hostnameVerifier = null;

private HostAndPortMapper hostAndPortMapper = null;
Expand All @@ -172,7 +173,7 @@ public static class Builder {

private boolean readOnlyForRedisClusterReplicas = false;

private AuthXManager authXManager;
private AuthXManager authXManager = null;

private Builder() {
}
Expand All @@ -183,15 +184,13 @@ public DefaultJedisClientConfig build() {
new DefaultRedisCredentials(user, password));
}

return new DefaultJedisClientConfig(redisProtocol, connectionTimeoutMillis,
socketTimeoutMillis, blockingSocketTimeoutMillis, credentialsProvider, database,
clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMapper,
clientSetInfoConfig, readOnlyForRedisClusterReplicas, authXManager);
return new DefaultJedisClientConfig(this);
}

/**
* Shortcut to {@link redis.clients.jedis.DefaultJedisClientConfig.Builder#protocol(RedisProtocol)} with
* {@link RedisProtocol#RESP3}.
* @return this
*/
public Builder resp3() {
return protocol(RedisProtocol.RESP3);
Expand Down Expand Up @@ -268,6 +267,11 @@ public Builder sslParameters(SSLParameters sslParameters) {
return this;
}

public Builder sslOptions(SslOptions sslOptions) {
this.sslOptions = sslOptions;
return this;
}

public Builder hostnameVerifier(HostnameVerifier hostnameVerifier) {
this.hostnameVerifier = hostnameVerifier;
return this;
Expand Down Expand Up @@ -304,6 +308,7 @@ public Builder from(JedisClientConfig instance) {
this.ssl = instance.isSsl();
this.sslSocketFactory = instance.getSslSocketFactory();
this.sslParameters = instance.getSslParameters();
this.sslOptions = instance.getSslOptions();
this.hostnameVerifier = instance.getHostnameVerifier();
this.hostAndPortMapper = instance.getHostAndPortMapper();
this.clientSetInfoConfig = instance.getClientSetInfoConfig();
Expand All @@ -313,24 +318,64 @@ public Builder from(JedisClientConfig instance) {
}
}

/**
* @deprecated Use {@link redis.clients.jedis.DefaultJedisClientConfig.Builder}.
*/
@Deprecated
public static DefaultJedisClientConfig create(int connectionTimeoutMillis, int soTimeoutMillis,
int blockingSocketTimeoutMillis, String user, String password, int database,
String clientName, boolean ssl, SSLSocketFactory sslSocketFactory,
SSLParameters sslParameters, HostnameVerifier hostnameVerifier,
HostAndPortMapper hostAndPortMapper, AuthXManager authXManager) {
return new DefaultJedisClientConfig(null, connectionTimeoutMillis, soTimeoutMillis,
blockingSocketTimeoutMillis,
new DefaultRedisCredentialsProvider(new DefaultRedisCredentials(user, password)), database,
clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMapper, null,
false, authXManager);
int blockingSocketTimeoutMillis, String user, String password, int database, String clientName,
boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
HostnameVerifier hostnameVerifier, HostAndPortMapper hostAndPortMapper) {
Builder builder = builder();
builder.connectionTimeoutMillis(connectionTimeoutMillis).socketTimeoutMillis(soTimeoutMillis)
.blockingSocketTimeoutMillis(blockingSocketTimeoutMillis);
if (user != null || password != null) {
// deliberately not handling 'user != null && password == null' here
builder.credentials(new DefaultRedisCredentials(user, password));
}
builder.database(database).clientName(clientName);
builder.ssl(ssl).sslSocketFactory(sslSocketFactory).sslParameters(sslParameters).hostnameVerifier(hostnameVerifier);
builder.hostAndPortMapper(hostAndPortMapper);
return builder.build();
}

/**
* @deprecated Use
* {@link redis.clients.jedis.DefaultJedisClientConfig.Builder#from(redis.clients.jedis.JedisClientConfig)}.
*/
@Deprecated
public static DefaultJedisClientConfig copyConfig(JedisClientConfig copy) {
return new DefaultJedisClientConfig(copy.getRedisProtocol(), copy.getConnectionTimeoutMillis(),
copy.getSocketTimeoutMillis(), copy.getBlockingSocketTimeoutMillis(),
copy.getCredentialsProvider(), copy.getDatabase(), copy.getClientName(), copy.isSsl(),
copy.getSslSocketFactory(), copy.getSslParameters(), copy.getHostnameVerifier(),
copy.getHostAndPortMapper(), copy.getClientSetInfoConfig(),
copy.isReadOnlyForRedisClusterReplicas(), copy.getAuthXManager());
Builder builder = builder();
builder.protocol(copy.getRedisProtocol());
builder.connectionTimeoutMillis(copy.getConnectionTimeoutMillis());
builder.socketTimeoutMillis(copy.getSocketTimeoutMillis());
builder.blockingSocketTimeoutMillis(copy.getBlockingSocketTimeoutMillis());

Supplier<RedisCredentials> credentialsProvider = copy.getCredentialsProvider();
if (credentialsProvider != null) {
builder.credentialsProvider(credentialsProvider);
} else {
builder.user(copy.getUser());
builder.password(copy.getPassword());
}

builder.database(copy.getDatabase());
builder.clientName(copy.getClientName());

builder.ssl(copy.isSsl());
builder.sslSocketFactory(copy.getSslSocketFactory());
builder.sslParameters(copy.getSslParameters());
builder.hostnameVerifier(copy.getHostnameVerifier());
builder.sslOptions(copy.getSslOptions());
builder.hostAndPortMapper(copy.getHostAndPortMapper());

builder.clientSetInfoConfig(copy.getClientSetInfoConfig());
if (copy.isReadOnlyForRedisClusterReplicas()) {
builder.readOnlyForRedisClusterReplicas();
}

builder.authXManager(copy.getAuthXManager());

return builder.build();
}
}
70 changes: 51 additions & 19 deletions src/main/java/redis/clients/jedis/DefaultJedisSocketFactory.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package redis.clients.jedis;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
Expand All @@ -24,6 +27,7 @@ public class DefaultJedisSocketFactory implements JedisSocketFactory {
private int socketTimeout = Protocol.DEFAULT_TIMEOUT;
private boolean ssl = false;
private SSLSocketFactory sslSocketFactory = null;
private SslOptions sslOptions = null;
private SSLParameters sslParameters = null;
private HostnameVerifier hostnameVerifier = null;
private HostAndPortMapper hostAndPortMapper = null;
Expand All @@ -49,6 +53,7 @@ public DefaultJedisSocketFactory(HostAndPort hostAndPort, JedisClientConfig conf
this.ssl = config.isSsl();
this.sslSocketFactory = config.getSslSocketFactory();
this.sslParameters = config.getSslParameters();
this.sslOptions = config.getSslOptions();
this.hostnameVerifier = config.getHostnameVerifier();
this.hostAndPortMapper = config.getHostAndPortMapper();
}
Expand Down Expand Up @@ -89,25 +94,8 @@ public Socket createSocket() throws JedisConnectionException {
socket = connectToFirstSuccessfulHost(_hostAndPort);
socket.setSoTimeout(socketTimeout);

if (ssl) {
SSLSocketFactory _sslSocketFactory = this.sslSocketFactory;
if (null == _sslSocketFactory) {
_sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
}
Socket plainSocket = socket;
socket = _sslSocketFactory.createSocket(socket, _hostAndPort.getHost(), _hostAndPort.getPort(), true);

if (null != sslParameters) {
((SSLSocket) socket).setSSLParameters(sslParameters);
}
socket = new SSLSocketWrapper((SSLSocket) socket, plainSocket);

if (null != hostnameVerifier
&& !hostnameVerifier.verify(_hostAndPort.getHost(), ((SSLSocket) socket).getSession())) {
String message = String.format(
"The connection to '%s' failed ssl/tls hostname verification.", _hostAndPort.getHost());
throw new JedisConnectionException(message);
}
if (ssl || sslOptions != null) {
socket = createSslSocket(_hostAndPort, socket);
}

return socket;
Expand All @@ -122,6 +110,50 @@ public Socket createSocket() throws JedisConnectionException {
}
}

/**
* ssl enable check is done before this.
*/
private Socket createSslSocket(HostAndPort _hostAndPort, Socket socket) throws IOException, GeneralSecurityException {

Socket plainSocket = socket;

SSLSocketFactory _sslSocketFactory;
SSLParameters _sslParameters;

if (sslOptions != null) {

SSLContext _sslContext = sslOptions.createSslContext();
_sslSocketFactory = _sslContext.getSocketFactory();

_sslParameters = sslOptions.getSslParameters();

} else {

_sslSocketFactory = this.sslSocketFactory;
_sslParameters = this.sslParameters;
}

if (_sslSocketFactory == null) {
_sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
}

SSLSocket sslSocket = (SSLSocket) _sslSocketFactory.createSocket(socket,
_hostAndPort.getHost(), _hostAndPort.getPort(), true);

if (_sslParameters != null) {
sslSocket.setSSLParameters(_sslParameters);
}

// allowing HostnameVerifier for both SslOptions and legacy ssl config
if (hostnameVerifier != null && !hostnameVerifier.verify(_hostAndPort.getHost(), sslSocket.getSession())) {
String message = String.format("The connection to '%s' failed ssl/tls hostname verification.",
_hostAndPort.getHost());
throw new JedisConnectionException(message);
}

return new SSLSocketWrapper(sslSocket, plainSocket);
}

public void updateHostAndPort(HostAndPort hostAndPort) {
this.hostAndPort = hostAndPort;
}
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/redis/clients/jedis/JedisClientConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ default String getPassword() {
return null;
}

// TODO: return null
default Supplier<RedisCredentials> getCredentialsProvider() {
return new DefaultRedisCredentialsProvider(
new DefaultRedisCredentials(getUser(), getPassword()));
Expand Down Expand Up @@ -78,6 +79,16 @@ default SSLParameters getSslParameters() {
return null;
}

/**
* {@link JedisClientConfig#isSsl()}, {@link JedisClientConfig#getSslSocketFactory()} and
* {@link JedisClientConfig#getSslParameters()} will be ignored if
* {@link JedisClientConfig#getSslOptions() this} is set.
* @return ssl options
*/
default SslOptions getSslOptions() {
return null;
}

default HostnameVerifier getHostnameVerifier() {
return null;
}
Expand Down
19 changes: 12 additions & 7 deletions src/main/java/redis/clients/jedis/JedisPooled.java
Original file line number Diff line number Diff line change
Expand Up @@ -292,10 +292,12 @@ public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final S
}

public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,
final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,
final String user, final String password, final int database, final String clientName) {
this(new HostAndPort(host, port), DefaultJedisClientConfig.create(connectionTimeout, soTimeout,
infiniteSoTimeout, user, password, database, clientName, false, null, null, null, null, null),
final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout, final String user,
final String password, final int database, final String clientName) {
this(new HostAndPort(host, port),
DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(soTimeout)
.blockingSocketTimeoutMillis(infiniteSoTimeout).user(user).password(password).database(database)
.clientName(clientName).build(),
poolConfig);
}

Expand All @@ -304,9 +306,12 @@ public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final S
final String password, final int database, final String clientName, final boolean ssl,
final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
final HostnameVerifier hostnameVerifier) {
this(new HostAndPort(host, port), DefaultJedisClientConfig.create(connectionTimeout, soTimeout,
infiniteSoTimeout, user, password, database, clientName, ssl, sslSocketFactory, sslParameters,
hostnameVerifier, null, null), poolConfig);
this(new HostAndPort(host, port),
DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(soTimeout)
.blockingSocketTimeoutMillis(infiniteSoTimeout).user(user).password(password).database(database)
.clientName(clientName).ssl(ssl).sslSocketFactory(sslSocketFactory).sslParameters(sslParameters)
.hostnameVerifier(hostnameVerifier).build(),
poolConfig);
}

public JedisPooled(final URI uri) {
Expand Down
Loading

0 comments on commit 87d451c

Please sign in to comment.