mirror of https://github.com/apache/druid.git
Allow custom TLS cert checks (#6432)
* Allow custom TLS cert checks * PR comment * Checkstyle, PR comment
This commit is contained in:
parent
601183b4c7
commit
b2d9b6f23d
|
@ -28,6 +28,7 @@ The following table contains optional parameters for supporting client certifica
|
||||||
|`druid.client.https.keyStorePassword`|The [Password Provider](../operations/password-provider.html) or String password for the Key Store.|none|no|
|
|`druid.client.https.keyStorePassword`|The [Password Provider](../operations/password-provider.html) or String password for the Key Store.|none|no|
|
||||||
|`druid.client.https.keyManagerFactoryAlgorithm`|Algorithm to use for creating KeyManager, more details [here](https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#KeyManager).|`javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm()`|no|
|
|`druid.client.https.keyManagerFactoryAlgorithm`|Algorithm to use for creating KeyManager, more details [here](https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#KeyManager).|`javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm()`|no|
|
||||||
|`druid.client.https.keyManagerPassword`|The [Password Provider](../operations/password-provider.html) or String password for the Key Manager.|none|no|
|
|`druid.client.https.keyManagerPassword`|The [Password Provider](../operations/password-provider.html) or String password for the Key Manager.|none|no|
|
||||||
|
|`druid.client.https.validateHostnames`|Validate the hostname of the server. This should not be disabled unless you are using [custom TLS certificate checks](../../operations/tls-support.html#custom-tls-certificate-checks) and know that standard hostname validation is not needed.|true|no|
|
||||||
|
|
||||||
This [document](http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html) lists all the possible
|
This [document](http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html) lists all the possible
|
||||||
values for the above mentioned configs among others provided by Java implementation.
|
values for the above mentioned configs among others provided by Java implementation.
|
|
@ -71,3 +71,16 @@ to create your own extension.
|
||||||
When Druid Coordinator/Overlord have both HTTP and HTTPS enabled and Client sends request to non-leader node, then Client is always redirected to the HTTPS endpoint on leader node.
|
When Druid Coordinator/Overlord have both HTTP and HTTPS enabled and Client sends request to non-leader node, then Client is always redirected to the HTTPS endpoint on leader node.
|
||||||
So, Clients should be first upgraded to be able to handle redirect to HTTPS. Then Druid Overlord/Coordinator should be upgraded and configured to run both HTTP and HTTPS ports. Then Client configuration should be changed to refer to Druid Coordinator/Overlord via the HTTPS endpoint and then HTTP port on Druid Coordinator/Overlord should be disabled.
|
So, Clients should be first upgraded to be able to handle redirect to HTTPS. Then Druid Overlord/Coordinator should be upgraded and configured to run both HTTP and HTTPS ports. Then Client configuration should be changed to refer to Druid Coordinator/Overlord via the HTTPS endpoint and then HTTP port on Druid Coordinator/Overlord should be disabled.
|
||||||
|
|
||||||
|
# Custom TLS certificate checks
|
||||||
|
|
||||||
|
Druid supports custom certificate check extensions. Please refer to the `org.apache.druid.server.security.TLSCertificateChecker` interface for details on the methods to be implemented.
|
||||||
|
|
||||||
|
To use a custom TLS certificate checker, specify the following property:
|
||||||
|
|
||||||
|
|Property|Description|Default|Required|
|
||||||
|
|--------|-----------|-------|--------|
|
||||||
|
|`druid.tls.certificateChecker`|Type name of custom TLS certificate checker, provided by extensions. Please refer to extension documentation for the type name that should be specified.|"default"|no|
|
||||||
|
|
||||||
|
The default checker delegates to the standard trust manager and performs no additional actions or checks.
|
||||||
|
|
||||||
|
If using a non-default certificate checker, please refer to the extension documentation for additional configuration properties needed.
|
||||||
|
|
|
@ -57,6 +57,9 @@ public class SSLClientConfig
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private String keyManagerFactoryAlgorithm;
|
private String keyManagerFactoryAlgorithm;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private Boolean validateHostnames;
|
||||||
|
|
||||||
public String getProtocol()
|
public String getProtocol()
|
||||||
{
|
{
|
||||||
return protocol;
|
return protocol;
|
||||||
|
@ -112,6 +115,11 @@ public class SSLClientConfig
|
||||||
return keyManagerFactoryAlgorithm;
|
return keyManagerFactoryAlgorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getValidateHostnames()
|
||||||
|
{
|
||||||
|
return validateHostnames;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
@ -124,6 +132,7 @@ public class SSLClientConfig
|
||||||
", keyStoreType='" + keyStoreType + '\'' +
|
", keyStoreType='" + keyStoreType + '\'' +
|
||||||
", certAlias='" + certAlias + '\'' +
|
", certAlias='" + certAlias + '\'' +
|
||||||
", keyManagerFactoryAlgorithm='" + keyManagerFactoryAlgorithm + '\'' +
|
", keyManagerFactoryAlgorithm='" + keyManagerFactoryAlgorithm + '\'' +
|
||||||
|
", validateHostnames='" + validateHostnames + '\'' +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ package org.apache.druid.https;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
import org.apache.druid.java.util.emitter.EmittingLogger;
|
import org.apache.druid.java.util.emitter.EmittingLogger;
|
||||||
|
import org.apache.druid.server.security.TLSCertificateChecker;
|
||||||
import org.apache.druid.server.security.TLSUtils;
|
import org.apache.druid.server.security.TLSUtils;
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
@ -31,11 +32,16 @@ public class SSLContextProvider implements Provider<SSLContext>
|
||||||
private static final EmittingLogger log = new EmittingLogger(SSLContextProvider.class);
|
private static final EmittingLogger log = new EmittingLogger(SSLContextProvider.class);
|
||||||
|
|
||||||
private SSLClientConfig config;
|
private SSLClientConfig config;
|
||||||
|
private TLSCertificateChecker certificateChecker;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SSLContextProvider(SSLClientConfig config)
|
public SSLContextProvider(
|
||||||
|
SSLClientConfig config,
|
||||||
|
TLSCertificateChecker certificateChecker
|
||||||
|
)
|
||||||
{
|
{
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
this.certificateChecker = certificateChecker;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -55,6 +61,8 @@ public class SSLContextProvider implements Provider<SSLContext>
|
||||||
.setCertAlias(config.getCertAlias())
|
.setCertAlias(config.getCertAlias())
|
||||||
.setKeyStorePasswordProvider(config.getKeyStorePasswordProvider())
|
.setKeyStorePasswordProvider(config.getKeyStorePasswordProvider())
|
||||||
.setKeyManagerFactoryPasswordProvider(config.getKeyManagerPasswordProvider())
|
.setKeyManagerFactoryPasswordProvider(config.getKeyManagerPasswordProvider())
|
||||||
|
.setValidateHostnames(config.getValidateHostnames())
|
||||||
|
.setCertificateChecker(certificateChecker)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ ADD client_tls client_tls
|
||||||
# - 8083, 8283: HTTP, HTTPS (historical)
|
# - 8083, 8283: HTTP, HTTPS (historical)
|
||||||
# - 8090, 8290: HTTP, HTTPS (overlord)
|
# - 8090, 8290: HTTP, HTTPS (overlord)
|
||||||
# - 8091, 8291: HTTP, HTTPS (middlemanager)
|
# - 8091, 8291: HTTP, HTTPS (middlemanager)
|
||||||
|
# - 8888-8891, 9088-9091: HTTP, HTTPS (routers)
|
||||||
# - 3306: MySQL
|
# - 3306: MySQL
|
||||||
# - 2181 2888 3888: ZooKeeper
|
# - 2181 2888 3888: ZooKeeper
|
||||||
# - 8100 8101 8102 8103 8104 8105 : peon ports
|
# - 8100 8101 8102 8103 8104 8105 : peon ports
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
[program:druid-router-custom-check-tls]
|
||||||
|
command=java
|
||||||
|
-server
|
||||||
|
-Xmx128m
|
||||||
|
-XX:+UseConcMarkSweepGC
|
||||||
|
-XX:+PrintGCDetails
|
||||||
|
-XX:+PrintGCTimeStamps
|
||||||
|
-Duser.timezone=UTC
|
||||||
|
-Dfile.encoding=UTF-8
|
||||||
|
-Ddruid.host=%(ENV_HOST_IP)s
|
||||||
|
-Ddruid.plaintextPort=8891
|
||||||
|
-Ddruid.tlsPort=9091
|
||||||
|
-Ddruid.zk.service.host=druid-zookeeper-kafka
|
||||||
|
-Ddruid.server.http.numThreads=100
|
||||||
|
-Ddruid.lookup.numLookupLoadingThreads=1
|
||||||
|
-Ddruid.router.managementProxy.enabled=true
|
||||||
|
-Ddruid.auth.authenticatorChain="[\"basic\"]"
|
||||||
|
-Ddruid.auth.authenticator.basic.type=basic
|
||||||
|
-Ddruid.auth.authenticator.basic.initialAdminPassword=priest
|
||||||
|
-Ddruid.auth.authenticator.basic.initialInternalClientPassword=warlock
|
||||||
|
-Ddruid.auth.authenticator.basic.authorizerName=basic
|
||||||
|
-Ddruid.auth.basic.common.cacheDirectory=/tmp/authCache/router-custom-check-tls
|
||||||
|
-Ddruid.escalator.type=basic
|
||||||
|
-Ddruid.escalator.internalClientUsername=druid_system
|
||||||
|
-Ddruid.escalator.internalClientPassword=warlock
|
||||||
|
-Ddruid.escalator.authorizerName=basic
|
||||||
|
-Ddruid.auth.authorizers="[\"basic\"]"
|
||||||
|
-Ddruid.auth.authorizer.basic.type=basic
|
||||||
|
-Ddruid.sql.enable=true
|
||||||
|
-Ddruid.sql.avatica.enable=true
|
||||||
|
-Ddruid.enableTlsPort=true
|
||||||
|
-Ddruid.server.https.keyStorePath=/tls/server.jks
|
||||||
|
-Ddruid.server.https.keyStoreType=jks
|
||||||
|
-Ddruid.server.https.certAlias=druid
|
||||||
|
-Ddruid.server.https.keyManagerPassword=druid123
|
||||||
|
-Ddruid.server.https.keyStorePassword=druid123
|
||||||
|
-Ddruid.server.https.requireClientCertificate=true
|
||||||
|
-Ddruid.server.https.trustStorePath=/tls/truststore.jks
|
||||||
|
-Ddruid.server.https.trustStorePassword=druid123
|
||||||
|
-Ddruid.server.https.trustStoreAlgorithm=PKIX
|
||||||
|
-Ddruid.server.https.validateHostnames=true
|
||||||
|
-Ddruid.client.https.trustStoreAlgorithm=PKIX
|
||||||
|
-Ddruid.client.https.protocol=TLSv1.2
|
||||||
|
-Ddruid.client.https.trustStorePath=/tls/truststore.jks
|
||||||
|
-Ddruid.client.https.trustStorePassword=druid123
|
||||||
|
-Ddruid.client.https.keyStorePath=/tls/server.jks
|
||||||
|
-Ddruid.client.https.certAlias=druid
|
||||||
|
-Ddruid.client.https.keyManagerPassword=druid123
|
||||||
|
-Ddruid.client.https.keyStorePassword=druid123
|
||||||
|
-Ddruid.client.https.validateHostnames=false
|
||||||
|
-Ddruid.tls.certificateChecker=integration-test
|
||||||
|
-cp /shared/docker/lib/*
|
||||||
|
org.apache.druid.cli.Main server router
|
||||||
|
redirect_stderr=true
|
||||||
|
priority=100
|
||||||
|
autorestart=false
|
||||||
|
stdout_logfile=/shared/logs/router-custom-check-tls.log
|
|
@ -15,7 +15,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
# cleanup
|
# cleanup
|
||||||
for node in druid-historical druid-coordinator druid-overlord druid-router druid-router-permissive-tls druid-router-no-client-auth-tls druid-broker druid-middlemanager druid-zookeeper-kafka druid-metadata-storage;
|
for node in druid-historical druid-coordinator druid-overlord druid-router druid-router-permissive-tls druid-router-no-client-auth-tls druid-router-custom-check-tls druid-broker druid-middlemanager druid-zookeeper-kafka druid-metadata-storage;
|
||||||
do
|
do
|
||||||
docker stop $node
|
docker stop $node
|
||||||
docker rm $node
|
docker rm $node
|
||||||
|
@ -50,6 +50,9 @@ mvn -B dependency:copy-dependencies -DoutputDirectory=$SHARED_DIR/docker/lib
|
||||||
# install logging config
|
# install logging config
|
||||||
cp src/main/resources/log4j2.xml $SHARED_DIR/docker/lib/log4j2.xml
|
cp src/main/resources/log4j2.xml $SHARED_DIR/docker/lib/log4j2.xml
|
||||||
|
|
||||||
|
# copy the integration test jar, it provides test-only extension implementations
|
||||||
|
cp target/druid-integration-tests*.jar $SHARED_DIR/docker/lib
|
||||||
|
|
||||||
docker network create --subnet=172.172.172.0/24 druid-it-net
|
docker network create --subnet=172.172.172.0/24 druid-it-net
|
||||||
|
|
||||||
# Build Druid Cluster Image
|
# Build Druid Cluster Image
|
||||||
|
@ -84,3 +87,6 @@ docker run -d --privileged --net druid-it-net --ip 172.172.172.10 --name druid-r
|
||||||
|
|
||||||
# Start Router with TLS but no client auth
|
# Start Router with TLS but no client auth
|
||||||
docker run -d --privileged --net druid-it-net --ip 172.172.172.11 --name druid-router-no-client-auth-tls -p 8890:8890 -p 9090:9090 -v $SHARED_DIR:/shared -v $DOCKERDIR/router-no-client-auth-tls.conf:$SUPERVISORDIR/router-no-client-auth-tls.conf --link druid-zookeeper-kafka:druid-zookeeper-kafka --link druid-coordinator:druid-coordinator --link druid-broker:druid-broker druid/cluster
|
docker run -d --privileged --net druid-it-net --ip 172.172.172.11 --name druid-router-no-client-auth-tls -p 8890:8890 -p 9090:9090 -v $SHARED_DIR:/shared -v $DOCKERDIR/router-no-client-auth-tls.conf:$SUPERVISORDIR/router-no-client-auth-tls.conf --link druid-zookeeper-kafka:druid-zookeeper-kafka --link druid-coordinator:druid-coordinator --link druid-broker:druid-broker druid/cluster
|
||||||
|
|
||||||
|
# Start Router with custom TLS cert checkers
|
||||||
|
docker run -d --privileged --net druid-it-net --ip 172.172.172.12 --hostname druid-router-custom-check-tls --name druid-router-custom-check-tls -p 8891:8891 -p 9091:9091 -v $SHARED_DIR:/shared -v $DOCKERDIR/router-custom-check-tls.conf:$SUPERVISORDIR/router-custom-check-tls.conf --link druid-zookeeper-kafka:druid-zookeeper-kafka --link druid-coordinator:druid-coordinator --link druid-broker:druid-broker druid/cluster
|
||||||
|
|
|
@ -40,6 +40,7 @@ public class ConfigFileConfigProvider implements IntegrationTestingConfigProvide
|
||||||
private String indexerUrl;
|
private String indexerUrl;
|
||||||
private String permissiveRouterUrl;
|
private String permissiveRouterUrl;
|
||||||
private String noClientAuthRouterUrl;
|
private String noClientAuthRouterUrl;
|
||||||
|
private String customCertCheckRouterUrl;
|
||||||
private String routerTLSUrl;
|
private String routerTLSUrl;
|
||||||
private String brokerTLSUrl;
|
private String brokerTLSUrl;
|
||||||
private String historicalTLSUrl;
|
private String historicalTLSUrl;
|
||||||
|
@ -47,6 +48,7 @@ public class ConfigFileConfigProvider implements IntegrationTestingConfigProvide
|
||||||
private String indexerTLSUrl;
|
private String indexerTLSUrl;
|
||||||
private String permissiveRouterTLSUrl;
|
private String permissiveRouterTLSUrl;
|
||||||
private String noClientAuthRouterTLSUrl;
|
private String noClientAuthRouterTLSUrl;
|
||||||
|
private String customCertCheckRouterTLSUrl;
|
||||||
private String middleManagerHost;
|
private String middleManagerHost;
|
||||||
private String zookeeperHosts; // comma-separated list of host:port
|
private String zookeeperHosts; // comma-separated list of host:port
|
||||||
private String kafkaHost;
|
private String kafkaHost;
|
||||||
|
@ -114,6 +116,21 @@ public class ConfigFileConfigProvider implements IntegrationTestingConfigProvide
|
||||||
noClientAuthRouterTLSUrl = StringUtils.format("https://%s:%s", noClientAuthRouterHost, props.get("router_no_client_auth_tls_port"));
|
noClientAuthRouterTLSUrl = StringUtils.format("https://%s:%s", noClientAuthRouterHost, props.get("router_no_client_auth_tls_port"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
customCertCheckRouterUrl = props.get("router_no_client_auth_url");
|
||||||
|
if (customCertCheckRouterUrl == null) {
|
||||||
|
String customCertCheckRouterHost = props.get("router_no_client_auth_host");
|
||||||
|
if (null != customCertCheckRouterHost) {
|
||||||
|
customCertCheckRouterUrl = StringUtils.format("http://%s:%s", customCertCheckRouterHost, props.get("router_no_client_auth_port"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customCertCheckRouterTLSUrl = props.get("router_no_client_auth_tls_url");
|
||||||
|
if (customCertCheckRouterTLSUrl == null) {
|
||||||
|
String customCertCheckRouterHost = props.get("router_no_client_auth_host");
|
||||||
|
if (null != customCertCheckRouterHost) {
|
||||||
|
customCertCheckRouterTLSUrl = StringUtils.format("https://%s:%s", customCertCheckRouterHost, props.get("router_no_client_auth_tls_port"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
brokerUrl = props.get("broker_url");
|
brokerUrl = props.get("broker_url");
|
||||||
if (brokerUrl == null) {
|
if (brokerUrl == null) {
|
||||||
brokerUrl = StringUtils.format("http://%s:%s", props.get("broker_host"), props.get("broker_port"));
|
brokerUrl = StringUtils.format("http://%s:%s", props.get("broker_host"), props.get("broker_port"));
|
||||||
|
@ -248,6 +265,18 @@ public class ConfigFileConfigProvider implements IntegrationTestingConfigProvide
|
||||||
return noClientAuthRouterTLSUrl;
|
return noClientAuthRouterTLSUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCustomCertCheckRouterUrl()
|
||||||
|
{
|
||||||
|
return customCertCheckRouterUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCustomCertCheckRouterTLSUrl()
|
||||||
|
{
|
||||||
|
return customCertCheckRouterTLSUrl;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getBrokerUrl()
|
public String getBrokerUrl()
|
||||||
{
|
{
|
||||||
|
|
|
@ -102,6 +102,18 @@ public class DockerConfigProvider implements IntegrationTestingConfigProvider
|
||||||
return "https://" + dockerIp + ":9090";
|
return "https://" + dockerIp + ":9090";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCustomCertCheckRouterUrl()
|
||||||
|
{
|
||||||
|
return "http://" + dockerIp + ":8891";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCustomCertCheckRouterTLSUrl()
|
||||||
|
{
|
||||||
|
return "https://" + dockerIp + ":9091";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getBrokerUrl()
|
public String getBrokerUrl()
|
||||||
{
|
{
|
||||||
|
|
|
@ -45,6 +45,10 @@ public interface IntegrationTestingConfig
|
||||||
|
|
||||||
String getNoClientAuthRouterTLSUrl();
|
String getNoClientAuthRouterTLSUrl();
|
||||||
|
|
||||||
|
String getCustomCertCheckRouterUrl();
|
||||||
|
|
||||||
|
String getCustomCertCheckRouterTLSUrl();
|
||||||
|
|
||||||
String getBrokerUrl();
|
String getBrokerUrl();
|
||||||
|
|
||||||
String getBrokerTLSUrl();
|
String getBrokerTLSUrl();
|
||||||
|
|
|
@ -59,5 +59,4 @@ public class DruidTestModuleFactory implements IModuleFactory
|
||||||
context.addInjector(Collections.singletonList(module), injector);
|
context.addInjector(Collections.singletonList(module), injector);
|
||||||
return module;
|
return module;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.testing.guice;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.Module;
|
||||||
|
import com.google.inject.Binder;
|
||||||
|
|
||||||
|
import com.google.inject.name.Names;
|
||||||
|
import org.apache.druid.initialization.DruidModule;
|
||||||
|
import org.apache.druid.server.security.TLSCertificateChecker;
|
||||||
|
import org.apache.druid.testing.utils.ITTLSCertificateChecker;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ITTLSCertificateCheckerModule implements DruidModule
|
||||||
|
{
|
||||||
|
private final ITTLSCertificateChecker INSTANCE = new ITTLSCertificateChecker();
|
||||||
|
|
||||||
|
public static final String IT_CHECKER_TYPE = "integration-test";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(Binder binder)
|
||||||
|
{
|
||||||
|
binder.bind(TLSCertificateChecker.class)
|
||||||
|
.annotatedWith(Names.named(IT_CHECKER_TYPE))
|
||||||
|
.toInstance(INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<? extends Module> getJacksonModules()
|
||||||
|
{
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.testing.utils;
|
||||||
|
|
||||||
|
import org.apache.druid.java.util.common.logger.Logger;
|
||||||
|
import org.apache.druid.server.security.TLSCertificateChecker;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
public class ITTLSCertificateChecker implements TLSCertificateChecker
|
||||||
|
{
|
||||||
|
private static final Logger log = new Logger(ITTLSCertificateChecker.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkClient(
|
||||||
|
X509Certificate[] chain,
|
||||||
|
String authType,
|
||||||
|
SSLEngine engine,
|
||||||
|
X509ExtendedTrustManager baseTrustManager
|
||||||
|
) throws CertificateException
|
||||||
|
{
|
||||||
|
// only the integration test client with "thisisprobablynottherighthostname" cert is allowed to talk to me
|
||||||
|
if (!chain[0].toString().contains("thisisprobablynottherighthostname") || !engine.getPeerHost().contains("172.172.172.1")) {
|
||||||
|
throw new CertificateException("Custom check rejected request from client.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkServer(
|
||||||
|
X509Certificate[] chain,
|
||||||
|
String authType,
|
||||||
|
SSLEngine engine,
|
||||||
|
X509ExtendedTrustManager baseTrustManager
|
||||||
|
) throws CertificateException
|
||||||
|
{
|
||||||
|
baseTrustManager.checkServerTrusted(chain, authType, engine);
|
||||||
|
|
||||||
|
// fail intentionally when trying to talk to the broker
|
||||||
|
if (chain[0].toString().contains("172.172.172.8")) {
|
||||||
|
throw new CertificateException("Custom check intentionally terminated request to broker.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
org.apache.druid.testing.guice.ITTLSCertificateCheckerModule
|
|
@ -38,9 +38,11 @@ import org.apache.druid.java.util.http.client.Request;
|
||||||
import org.apache.druid.java.util.http.client.auth.BasicCredentials;
|
import org.apache.druid.java.util.http.client.auth.BasicCredentials;
|
||||||
import org.apache.druid.java.util.http.client.response.StatusResponseHandler;
|
import org.apache.druid.java.util.http.client.response.StatusResponseHandler;
|
||||||
import org.apache.druid.java.util.http.client.response.StatusResponseHolder;
|
import org.apache.druid.java.util.http.client.response.StatusResponseHolder;
|
||||||
|
import org.apache.druid.server.security.TLSCertificateChecker;
|
||||||
import org.apache.druid.server.security.TLSUtils;
|
import org.apache.druid.server.security.TLSUtils;
|
||||||
import org.apache.druid.testing.IntegrationTestingConfig;
|
import org.apache.druid.testing.IntegrationTestingConfig;
|
||||||
import org.apache.druid.testing.guice.DruidTestModuleFactory;
|
import org.apache.druid.testing.guice.DruidTestModuleFactory;
|
||||||
|
import org.apache.druid.testing.utils.ITTLSCertificateChecker;
|
||||||
import org.jboss.netty.handler.codec.http.HttpMethod;
|
import org.jboss.netty.handler.codec.http.HttpMethod;
|
||||||
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
|
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import org.joda.time.Duration;
|
import org.joda.time.Duration;
|
||||||
|
@ -81,6 +83,9 @@ public class ITTLSTest
|
||||||
@Client
|
@Client
|
||||||
DruidHttpClientConfig httpClientConfig;
|
DruidHttpClientConfig httpClientConfig;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
TLSCertificateChecker certificateChecker;
|
||||||
|
|
||||||
StatusResponseHandler responseHandler = new StatusResponseHandler(StandardCharsets.UTF_8);
|
StatusResponseHandler responseHandler = new StatusResponseHandler(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -234,6 +239,33 @@ public class ITTLSTest
|
||||||
makeRequest(notCAClient, HttpMethod.GET, config.getNoClientAuthRouterTLSUrl() + "/status", null);
|
makeRequest(notCAClient, HttpMethod.GET, config.getNoClientAuthRouterTLSUrl() + "/status", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkAccessWithCustomCertificateChecks()
|
||||||
|
{
|
||||||
|
LOG.info("---------Testing TLS resource access with custom certificate checks---------");
|
||||||
|
HttpClient wrongHostnameClient = makeCustomHttpClient(
|
||||||
|
"client_tls/invalid_hostname_client.jks",
|
||||||
|
"invalid_hostname_client",
|
||||||
|
new ITTLSCertificateChecker()
|
||||||
|
);
|
||||||
|
|
||||||
|
checkFailedAccessWrongHostname(httpClient, HttpMethod.GET, config.getCustomCertCheckRouterTLSUrl());
|
||||||
|
|
||||||
|
makeRequest(wrongHostnameClient, HttpMethod.GET, config.getCustomCertCheckRouterTLSUrl() + "/status", null);
|
||||||
|
|
||||||
|
checkFailedAccess(
|
||||||
|
wrongHostnameClient,
|
||||||
|
HttpMethod.POST,
|
||||||
|
config.getCustomCertCheckRouterTLSUrl() + "/druid/v2",
|
||||||
|
"Custom cert check",
|
||||||
|
ISE.class,
|
||||||
|
"Error while making request to url[https://127.0.0.1:9091/druid/v2] status[400 Bad Request] content[{\"error\":\"No content to map due to end-of-input",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
makeRequest(wrongHostnameClient, HttpMethod.GET, config.getCustomCertCheckRouterTLSUrl() + "/druid/coordinator/v1/leader", null);
|
||||||
|
}
|
||||||
|
|
||||||
private void checkFailedAccessNoCert(HttpClient httpClient, HttpMethod method, String url)
|
private void checkFailedAccessNoCert(HttpClient httpClient, HttpMethod method, String url)
|
||||||
{
|
{
|
||||||
checkFailedAccess(
|
checkFailedAccess(
|
||||||
|
@ -242,7 +274,8 @@ public class ITTLSTest
|
||||||
url + "/status",
|
url + "/status",
|
||||||
"Certless",
|
"Certless",
|
||||||
SSLException.class,
|
SSLException.class,
|
||||||
"Received fatal alert: bad_certificate"
|
"Received fatal alert: bad_certificate",
|
||||||
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,7 +287,8 @@ public class ITTLSTest
|
||||||
url + "/status",
|
url + "/status",
|
||||||
"Wrong hostname",
|
"Wrong hostname",
|
||||||
SSLException.class,
|
SSLException.class,
|
||||||
"Received fatal alert: certificate_unknown"
|
"Received fatal alert: certificate_unknown",
|
||||||
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,7 +300,8 @@ public class ITTLSTest
|
||||||
url + "/status",
|
url + "/status",
|
||||||
"Wrong root cert",
|
"Wrong root cert",
|
||||||
SSLException.class,
|
SSLException.class,
|
||||||
"Received fatal alert: certificate_unknown"
|
"Received fatal alert: certificate_unknown",
|
||||||
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,7 +313,8 @@ public class ITTLSTest
|
||||||
url + "/status",
|
url + "/status",
|
||||||
"Revoked cert",
|
"Revoked cert",
|
||||||
SSLException.class,
|
SSLException.class,
|
||||||
"Received fatal alert: certificate_unknown"
|
"Received fatal alert: certificate_unknown",
|
||||||
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,7 +326,8 @@ public class ITTLSTest
|
||||||
url + "/status",
|
url + "/status",
|
||||||
"Expired cert",
|
"Expired cert",
|
||||||
SSLException.class,
|
SSLException.class,
|
||||||
"Received fatal alert: certificate_unknown"
|
"Received fatal alert: certificate_unknown",
|
||||||
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,7 +339,8 @@ public class ITTLSTest
|
||||||
url + "/status",
|
url + "/status",
|
||||||
"Cert signed by non-CA",
|
"Cert signed by non-CA",
|
||||||
SSLException.class,
|
SSLException.class,
|
||||||
"Received fatal alert: certificate_unknown"
|
"Received fatal alert: certificate_unknown",
|
||||||
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,6 +360,15 @@ public class ITTLSTest
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpClient makeCustomHttpClient(String keystorePath, String certAlias)
|
private HttpClient makeCustomHttpClient(String keystorePath, String certAlias)
|
||||||
|
{
|
||||||
|
return makeCustomHttpClient(keystorePath, certAlias, certificateChecker);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpClient makeCustomHttpClient(
|
||||||
|
String keystorePath,
|
||||||
|
String certAlias,
|
||||||
|
TLSCertificateChecker certificateChecker
|
||||||
|
)
|
||||||
{
|
{
|
||||||
SSLContext intermediateClientSSLContext = new TLSUtils.ClientSSLContextBuilder()
|
SSLContext intermediateClientSSLContext = new TLSUtils.ClientSSLContextBuilder()
|
||||||
.setProtocol(sslClientConfig.getProtocol())
|
.setProtocol(sslClientConfig.getProtocol())
|
||||||
|
@ -335,6 +382,7 @@ public class ITTLSTest
|
||||||
.setCertAlias(certAlias)
|
.setCertAlias(certAlias)
|
||||||
.setKeyStorePasswordProvider(sslClientConfig.getKeyStorePasswordProvider())
|
.setKeyStorePasswordProvider(sslClientConfig.getKeyStorePasswordProvider())
|
||||||
.setKeyManagerFactoryPasswordProvider(sslClientConfig.getKeyManagerPasswordProvider())
|
.setKeyManagerFactoryPasswordProvider(sslClientConfig.getKeyManagerPasswordProvider())
|
||||||
|
.setCertificateChecker(certificateChecker)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
final HttpClientConfig.Builder builder = getHttpClientConfigBuilder(intermediateClientSSLContext);
|
final HttpClientConfig.Builder builder = getHttpClientConfigBuilder(intermediateClientSSLContext);
|
||||||
|
@ -361,6 +409,7 @@ public class ITTLSTest
|
||||||
.setTrustStorePath(sslClientConfig.getTrustStorePath())
|
.setTrustStorePath(sslClientConfig.getTrustStorePath())
|
||||||
.setTrustStoreAlgorithm(sslClientConfig.getTrustStoreAlgorithm())
|
.setTrustStoreAlgorithm(sslClientConfig.getTrustStoreAlgorithm())
|
||||||
.setTrustStorePasswordProvider(sslClientConfig.getTrustStorePasswordProvider())
|
.setTrustStorePasswordProvider(sslClientConfig.getTrustStorePasswordProvider())
|
||||||
|
.setCertificateChecker(certificateChecker)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
final HttpClientConfig.Builder builder = getHttpClientConfigBuilder(certlessClientSSLContext);
|
final HttpClientConfig.Builder builder = getHttpClientConfigBuilder(certlessClientSSLContext);
|
||||||
|
@ -385,7 +434,8 @@ public class ITTLSTest
|
||||||
String url,
|
String url,
|
||||||
String clientDesc,
|
String clientDesc,
|
||||||
Class expectedException,
|
Class expectedException,
|
||||||
String expectedExceptionMsg
|
String expectedExceptionMsg,
|
||||||
|
boolean useContainsMsgCheck
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
int retries = 0;
|
int retries = 0;
|
||||||
|
@ -411,13 +461,17 @@ public class ITTLSTest
|
||||||
|
|
||||||
Assert.assertTrue(
|
Assert.assertTrue(
|
||||||
expectedException.isInstance(rootCause),
|
expectedException.isInstance(rootCause),
|
||||||
StringUtils.format("Expected %s but found %s instead.", expectedException, rootCause)
|
StringUtils.format("Expected %s but found %s instead.", expectedException, Throwables.getStackTraceAsString(rootCause))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (useContainsMsgCheck) {
|
||||||
|
Assert.assertTrue(rootCause.getMessage().contains(expectedExceptionMsg));
|
||||||
|
} else {
|
||||||
Assert.assertEquals(
|
Assert.assertEquals(
|
||||||
rootCause.getMessage(),
|
rootCause.getMessage(),
|
||||||
expectedExceptionMsg
|
expectedExceptionMsg
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
LOG.info("%s client [%s] request failed as expected when accessing [%s]", clientDesc, method, url);
|
LOG.info("%s client [%s] request failed as expected when accessing [%s]", clientDesc, method, url);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
for node in druid-historical druid-coordinator druid-overlord druid-router druid-router-permissive-tls druid-router-no-client-auth-tls druid-broker druid-middlemanager druid-zookeeper-kafka druid-metadata-storage;
|
for node in druid-historical druid-coordinator druid-overlord druid-router druid-router-permissive-tls druid-router-no-client-auth-tls druid-router-custom-check-tls druid-broker druid-middlemanager druid-zookeeper-kafka druid-metadata-storage;
|
||||||
|
|
||||||
do
|
do
|
||||||
docker stop $node
|
docker stop $node
|
||||||
|
|
|
@ -70,6 +70,7 @@ import org.apache.druid.server.initialization.AuthenticatorMapperModule;
|
||||||
import org.apache.druid.server.initialization.AuthorizerMapperModule;
|
import org.apache.druid.server.initialization.AuthorizerMapperModule;
|
||||||
import org.apache.druid.server.initialization.jetty.JettyServerModule;
|
import org.apache.druid.server.initialization.jetty.JettyServerModule;
|
||||||
import org.apache.druid.server.metrics.MetricsModule;
|
import org.apache.druid.server.metrics.MetricsModule;
|
||||||
|
import org.apache.druid.server.security.TLSCertificateCheckerModule;
|
||||||
import org.eclipse.aether.artifact.DefaultArtifact;
|
import org.eclipse.aether.artifact.DefaultArtifact;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -369,6 +370,7 @@ public class Initialization
|
||||||
new Log4jShutterDownerModule(),
|
new Log4jShutterDownerModule(),
|
||||||
new DruidAuthModule(),
|
new DruidAuthModule(),
|
||||||
new LifecycleModule(),
|
new LifecycleModule(),
|
||||||
|
TLSCertificateCheckerModule.class,
|
||||||
EmitterModule.class,
|
EmitterModule.class,
|
||||||
HttpClientModule.global(),
|
HttpClientModule.global(),
|
||||||
HttpClientModule.escalatedGlobal(),
|
HttpClientModule.escalatedGlobal(),
|
||||||
|
|
|
@ -36,6 +36,7 @@ import org.apache.druid.server.DruidNode;
|
||||||
import org.apache.druid.server.initialization.ServerConfig;
|
import org.apache.druid.server.initialization.ServerConfig;
|
||||||
import org.apache.druid.server.initialization.TLSServerConfig;
|
import org.apache.druid.server.initialization.TLSServerConfig;
|
||||||
import org.apache.druid.server.metrics.DataSourceTaskIdHolder;
|
import org.apache.druid.server.metrics.DataSourceTaskIdHolder;
|
||||||
|
import org.apache.druid.server.security.TLSCertificateChecker;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
|
||||||
|
@ -101,7 +102,8 @@ public class ChatHandlerServerModule implements Module
|
||||||
node,
|
node,
|
||||||
config,
|
config,
|
||||||
TLSServerConfig,
|
TLSServerConfig,
|
||||||
injector.getExistingBinding(Key.get(SslContextFactory.class))
|
injector.getExistingBinding(Key.get(SslContextFactory.class)),
|
||||||
|
injector.getInstance(TLSCertificateChecker.class)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,8 @@ import org.apache.druid.server.initialization.TLSServerConfig;
|
||||||
import org.apache.druid.server.metrics.DataSourceTaskIdHolder;
|
import org.apache.druid.server.metrics.DataSourceTaskIdHolder;
|
||||||
import org.apache.druid.server.metrics.MetricsModule;
|
import org.apache.druid.server.metrics.MetricsModule;
|
||||||
import org.apache.druid.server.metrics.MonitorsConfig;
|
import org.apache.druid.server.metrics.MonitorsConfig;
|
||||||
|
import org.apache.druid.server.security.CustomCheckX509TrustManager;
|
||||||
|
import org.apache.druid.server.security.TLSCertificateChecker;
|
||||||
import org.eclipse.jetty.server.ConnectionFactory;
|
import org.eclipse.jetty.server.ConnectionFactory;
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.HttpConfiguration;
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
|
@ -75,10 +77,14 @@ import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
|
||||||
|
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
|
import java.security.cert.CRL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -159,7 +165,8 @@ public class JettyServerModule extends JerseyServletModule
|
||||||
node,
|
node,
|
||||||
config,
|
config,
|
||||||
TLSServerConfig,
|
TLSServerConfig,
|
||||||
injector.getExistingBinding(Key.get(SslContextFactory.class))
|
injector.getExistingBinding(Key.get(SslContextFactory.class)),
|
||||||
|
injector.getInstance(TLSCertificateChecker.class)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +194,8 @@ public class JettyServerModule extends JerseyServletModule
|
||||||
DruidNode node,
|
DruidNode node,
|
||||||
ServerConfig config,
|
ServerConfig config,
|
||||||
TLSServerConfig tlsServerConfig,
|
TLSServerConfig tlsServerConfig,
|
||||||
Binding<SslContextFactory> sslContextFactoryBinding
|
Binding<SslContextFactory> sslContextFactoryBinding,
|
||||||
|
TLSCertificateChecker certificateChecker
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// adjusting to make config.getNumThreads() mean, "number of threads
|
// adjusting to make config.getNumThreads() mean, "number of threads
|
||||||
|
@ -235,7 +243,7 @@ public class JettyServerModule extends JerseyServletModule
|
||||||
log.info("Creating https connector with port [%d]", node.getTlsPort());
|
log.info("Creating https connector with port [%d]", node.getTlsPort());
|
||||||
if (sslContextFactoryBinding == null) {
|
if (sslContextFactoryBinding == null) {
|
||||||
// Never trust all certificates by default
|
// Never trust all certificates by default
|
||||||
sslContextFactory = new SslContextFactory(false);
|
sslContextFactory = new IdentityCheckOverrideSslContextFactory(tlsServerConfig, certificateChecker);
|
||||||
|
|
||||||
sslContextFactory.setKeyStorePath(tlsServerConfig.getKeyStorePath());
|
sslContextFactory.setKeyStorePath(tlsServerConfig.getKeyStorePath());
|
||||||
sslContextFactory.setKeyStoreType(tlsServerConfig.getKeyStoreType());
|
sslContextFactory.setKeyStoreType(tlsServerConfig.getKeyStoreType());
|
||||||
|
@ -471,4 +479,45 @@ public class JettyServerModule extends JerseyServletModule
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class IdentityCheckOverrideSslContextFactory extends SslContextFactory
|
||||||
|
{
|
||||||
|
private final TLSServerConfig tlsServerConfig;
|
||||||
|
private final TLSCertificateChecker certificateChecker;
|
||||||
|
|
||||||
|
public IdentityCheckOverrideSslContextFactory(
|
||||||
|
TLSServerConfig tlsServerConfig,
|
||||||
|
TLSCertificateChecker certificateChecker
|
||||||
|
)
|
||||||
|
{
|
||||||
|
super(false);
|
||||||
|
this.tlsServerConfig = tlsServerConfig;
|
||||||
|
this.certificateChecker = certificateChecker;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TrustManager[] getTrustManagers(
|
||||||
|
KeyStore trustStore,
|
||||||
|
Collection<? extends CRL> crls
|
||||||
|
) throws Exception
|
||||||
|
{
|
||||||
|
TrustManager[] trustManagers = super.getTrustManagers(trustStore, crls);
|
||||||
|
TrustManager[] newTrustManagers = new TrustManager[trustManagers.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < trustManagers.length; i++) {
|
||||||
|
if (trustManagers[i] instanceof X509ExtendedTrustManager) {
|
||||||
|
newTrustManagers[i] = new CustomCheckX509TrustManager(
|
||||||
|
(X509ExtendedTrustManager) trustManagers[i],
|
||||||
|
certificateChecker,
|
||||||
|
tlsServerConfig.isValidateHostnames()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
newTrustManagers[i] = trustManagers[i];
|
||||||
|
log.info("Encountered non-X509ExtendedTrustManager: " + trustManagers[i].getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newTrustManagers;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.server.security;
|
||||||
|
|
||||||
|
import org.apache.druid.java.util.common.logger.Logger;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
import javax.net.ssl.SSLParameters;
|
||||||
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
public class CustomCheckX509TrustManager extends X509ExtendedTrustManager implements X509TrustManager
|
||||||
|
{
|
||||||
|
private static final Logger log = new Logger(CustomCheckX509TrustManager.class);
|
||||||
|
|
||||||
|
private final X509ExtendedTrustManager delegate;
|
||||||
|
private final boolean validateServerHostnames;
|
||||||
|
private final TLSCertificateChecker certificateChecker;
|
||||||
|
|
||||||
|
public CustomCheckX509TrustManager(
|
||||||
|
final X509ExtendedTrustManager delegate,
|
||||||
|
final TLSCertificateChecker certificateChecker,
|
||||||
|
final boolean validateServerHostnames
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.validateServerHostnames = validateServerHostnames;
|
||||||
|
this.certificateChecker = certificateChecker;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException
|
||||||
|
{
|
||||||
|
delegate.checkClientTrusted(chain, authType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException
|
||||||
|
{
|
||||||
|
delegate.checkServerTrusted(chain, authType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public X509Certificate[] getAcceptedIssuers()
|
||||||
|
{
|
||||||
|
return delegate.getAcceptedIssuers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException
|
||||||
|
{
|
||||||
|
delegate.checkClientTrusted(chain, authType, socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException
|
||||||
|
{
|
||||||
|
delegate.checkServerTrusted(chain, authType, socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException
|
||||||
|
{
|
||||||
|
certificateChecker.checkClient(chain, authType, engine, delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException
|
||||||
|
{
|
||||||
|
// The Netty client we use for the internal client does not provide an option to disable the standard hostname
|
||||||
|
// validation. When using custom certificate checks, we want to allow that option, so we change the endpoint
|
||||||
|
// identification algorithm here. This is not needed for the server-side, since the Jetty server does provide
|
||||||
|
// an option for enabling/disabling standard hostname validation.
|
||||||
|
if (!validateServerHostnames) {
|
||||||
|
SSLParameters params = engine.getSSLParameters();
|
||||||
|
params.setEndpointIdentificationAlgorithm(null);
|
||||||
|
engine.setSSLParameters(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
certificateChecker.checkServer(chain, authType, engine, delegate);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.server.security;
|
||||||
|
|
||||||
|
import org.apache.druid.java.util.common.logger.Logger;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
public class DefaultTLSCertificateChecker implements TLSCertificateChecker
|
||||||
|
{
|
||||||
|
private static final Logger log = new Logger(DefaultTLSCertificateChecker.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkClient(
|
||||||
|
X509Certificate[] chain,
|
||||||
|
String authType,
|
||||||
|
SSLEngine engine,
|
||||||
|
X509ExtendedTrustManager baseTrustManager
|
||||||
|
) throws CertificateException
|
||||||
|
{
|
||||||
|
baseTrustManager.checkClientTrusted(chain, authType, engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkServer(
|
||||||
|
X509Certificate[] chain,
|
||||||
|
String authType,
|
||||||
|
SSLEngine engine,
|
||||||
|
X509ExtendedTrustManager baseTrustManager
|
||||||
|
) throws CertificateException
|
||||||
|
{
|
||||||
|
baseTrustManager.checkServerTrusted(chain, authType, engine);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.server.security;
|
||||||
|
|
||||||
|
import com.google.inject.Binder;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
|
||||||
|
import com.google.inject.name.Names;
|
||||||
|
|
||||||
|
public class DefaultTLSCertificateCheckerModule implements Module
|
||||||
|
{
|
||||||
|
private final DefaultTLSCertificateChecker INSTANCE = new DefaultTLSCertificateChecker();
|
||||||
|
|
||||||
|
public static final String DEFAULT_CHECKER_TYPE = "default";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(Binder binder)
|
||||||
|
{
|
||||||
|
binder.bind(TLSCertificateChecker.class)
|
||||||
|
.annotatedWith(Names.named(DEFAULT_CHECKER_TYPE))
|
||||||
|
.toInstance(INSTANCE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.server.security;
|
||||||
|
|
||||||
|
import org.apache.druid.guice.annotations.ExtensionPoint;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This extension point allows developers to replace the standard TLS certificate checks with custom checks.
|
||||||
|
* By default, a {@link DefaultTLSCertificateChecker} is used, which simply delegates to the
|
||||||
|
* base {@link X509ExtendedTrustManager}.
|
||||||
|
*/
|
||||||
|
@ExtensionPoint
|
||||||
|
public interface TLSCertificateChecker
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This method allows an extension to replace the standard
|
||||||
|
* {@link X509ExtendedTrustManager#checkClientTrusted(X509Certificate[], String, SSLEngine)} method.
|
||||||
|
*
|
||||||
|
* This controls the certificate check used by Druid's server, checking certificates for internal requests made
|
||||||
|
* by other Druid services and user-submitted requests.
|
||||||
|
*
|
||||||
|
* @param chain See docs for {@link X509ExtendedTrustManager#checkClientTrusted(X509Certificate[], String, SSLEngine)}.
|
||||||
|
* @param authType See docs for {@link X509ExtendedTrustManager#checkClientTrusted(X509Certificate[], String, SSLEngine)}.
|
||||||
|
* @param engine See docs for {@link X509ExtendedTrustManager#checkClientTrusted(X509Certificate[], String, SSLEngine)}.
|
||||||
|
* @param baseTrustManager The base trust manager. An extension should call
|
||||||
|
* baseTrustManager.checkClientTrusted(chain, authType, engine) if/when it wishes
|
||||||
|
* to use the standard check in addition to custom checks.
|
||||||
|
* @throws CertificateException
|
||||||
|
*/
|
||||||
|
void checkClient(
|
||||||
|
X509Certificate[] chain,
|
||||||
|
String authType,
|
||||||
|
SSLEngine engine,
|
||||||
|
X509ExtendedTrustManager baseTrustManager
|
||||||
|
) throws CertificateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method allows an extension to replace the standard
|
||||||
|
* {@link X509ExtendedTrustManager#checkServerTrusted(X509Certificate[], String, SSLEngine)} method.
|
||||||
|
*
|
||||||
|
* This controls the certificate check used by Druid's internal client, used to validate the certificates of other Druid services.
|
||||||
|
*
|
||||||
|
* @param chain See docs for {@link X509ExtendedTrustManager#checkServerTrusted(X509Certificate[], String, SSLEngine)}.
|
||||||
|
* @param authType See docs for {@link X509ExtendedTrustManager#checkServerTrusted(X509Certificate[], String, SSLEngine)}.
|
||||||
|
* @param engine See docs for {@link X509ExtendedTrustManager#checkServerTrusted(X509Certificate[], String, SSLEngine)}.
|
||||||
|
* @param baseTrustManager The base trust manager. An extension should call
|
||||||
|
* baseTrustManager.checkServerTrusted(chain, authType, engine) if/when it wishes
|
||||||
|
* to use the standard check in addition to custom checks.
|
||||||
|
* @throws CertificateException
|
||||||
|
*/
|
||||||
|
void checkServer(
|
||||||
|
X509Certificate[] chain,
|
||||||
|
String authType,
|
||||||
|
SSLEngine engine,
|
||||||
|
X509ExtendedTrustManager baseTrustManager
|
||||||
|
) throws CertificateException;
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.server.security;
|
||||||
|
|
||||||
|
import com.google.inject.Binder;
|
||||||
|
import com.google.inject.Binding;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import com.google.inject.TypeLiteral;
|
||||||
|
import com.google.inject.name.Names;
|
||||||
|
import org.apache.druid.guice.LazySingleton;
|
||||||
|
import org.apache.druid.java.util.common.IAE;
|
||||||
|
import org.apache.druid.java.util.common.ISE;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public class TLSCertificateCheckerModule implements Module
|
||||||
|
{
|
||||||
|
private static final String CHECKER_TYPE_PROPERTY = "druid.tls.certificateChecker";
|
||||||
|
|
||||||
|
private final Properties props;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public TLSCertificateCheckerModule(
|
||||||
|
Properties props
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.props = props;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(Binder binder)
|
||||||
|
{
|
||||||
|
String checkerType = props.getProperty(CHECKER_TYPE_PROPERTY, DefaultTLSCertificateCheckerModule.DEFAULT_CHECKER_TYPE);
|
||||||
|
|
||||||
|
binder.install(new DefaultTLSCertificateCheckerModule());
|
||||||
|
|
||||||
|
binder.bind(TLSCertificateChecker.class)
|
||||||
|
.toProvider(new TLSCertificateCheckerProvider(checkerType))
|
||||||
|
.in(LazySingleton.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TLSCertificateCheckerProvider implements Provider<TLSCertificateChecker>
|
||||||
|
{
|
||||||
|
private final String checkerType;
|
||||||
|
|
||||||
|
private TLSCertificateChecker checker = null;
|
||||||
|
|
||||||
|
public TLSCertificateCheckerProvider(
|
||||||
|
String checkerType
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.checkerType = checkerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public void inject(Injector injector)
|
||||||
|
{
|
||||||
|
final List<Binding<TLSCertificateChecker>> checkerBindings = injector.findBindingsByType(new TypeLiteral<TLSCertificateChecker>(){});
|
||||||
|
|
||||||
|
checker = findChecker(checkerType, checkerBindings);
|
||||||
|
if (checker == null) {
|
||||||
|
throw new IAE("Could not find certificate checker with type: " + checkerType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TLSCertificateChecker get()
|
||||||
|
{
|
||||||
|
if (checker == null) {
|
||||||
|
throw new ISE("Checker was null, that's bad!");
|
||||||
|
}
|
||||||
|
return checker;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TLSCertificateChecker findChecker(
|
||||||
|
String checkerType,
|
||||||
|
List<Binding<TLSCertificateChecker>> checkerBindings
|
||||||
|
)
|
||||||
|
{
|
||||||
|
for (Binding<TLSCertificateChecker> binding : checkerBindings) {
|
||||||
|
if (Names.named(checkerType).equals(binding.getKey().getAnnotation())) {
|
||||||
|
return binding.getProvider().get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ package org.apache.druid.server.security;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
|
import org.apache.druid.java.util.common.logger.Logger;
|
||||||
import org.apache.druid.metadata.PasswordProvider;
|
import org.apache.druid.metadata.PasswordProvider;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.ssl.AliasedX509ExtendedKeyManager;
|
import org.eclipse.jetty.util.ssl.AliasedX509ExtendedKeyManager;
|
||||||
|
@ -29,8 +30,10 @@ import javax.annotation.Nullable;
|
||||||
import javax.net.ssl.KeyManager;
|
import javax.net.ssl.KeyManager;
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
import javax.net.ssl.X509ExtendedKeyManager;
|
import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
|
@ -42,6 +45,8 @@ import java.security.cert.CertificateException;
|
||||||
|
|
||||||
public class TLSUtils
|
public class TLSUtils
|
||||||
{
|
{
|
||||||
|
private static final Logger log = new Logger(TLSUtils.class);
|
||||||
|
|
||||||
public static class ClientSSLContextBuilder
|
public static class ClientSSLContextBuilder
|
||||||
{
|
{
|
||||||
private String protocol;
|
private String protocol;
|
||||||
|
@ -55,6 +60,8 @@ public class TLSUtils
|
||||||
private String certAlias;
|
private String certAlias;
|
||||||
private PasswordProvider keyStorePasswordProvider;
|
private PasswordProvider keyStorePasswordProvider;
|
||||||
private PasswordProvider keyManagerFactoryPasswordProvider;
|
private PasswordProvider keyManagerFactoryPasswordProvider;
|
||||||
|
private Boolean validateHostnames;
|
||||||
|
private TLSCertificateChecker certificateChecker;
|
||||||
|
|
||||||
public ClientSSLContextBuilder setProtocol(String protocol)
|
public ClientSSLContextBuilder setProtocol(String protocol)
|
||||||
{
|
{
|
||||||
|
@ -122,6 +129,18 @@ public class TLSUtils
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ClientSSLContextBuilder setValidateHostnames(Boolean validateHostnames)
|
||||||
|
{
|
||||||
|
this.validateHostnames = validateHostnames;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientSSLContextBuilder setCertificateChecker(TLSCertificateChecker certificateChecker)
|
||||||
|
{
|
||||||
|
this.certificateChecker = certificateChecker;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public SSLContext build()
|
public SSLContext build()
|
||||||
{
|
{
|
||||||
Preconditions.checkNotNull(trustStorePath, "must specify a trustStorePath");
|
Preconditions.checkNotNull(trustStorePath, "must specify a trustStorePath");
|
||||||
|
@ -137,7 +156,9 @@ public class TLSUtils
|
||||||
keyStoreAlgorithm,
|
keyStoreAlgorithm,
|
||||||
certAlias,
|
certAlias,
|
||||||
keyStorePasswordProvider,
|
keyStorePasswordProvider,
|
||||||
keyManagerFactoryPasswordProvider
|
keyManagerFactoryPasswordProvider,
|
||||||
|
validateHostnames,
|
||||||
|
certificateChecker
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,7 +174,9 @@ public class TLSUtils
|
||||||
@Nullable String keyStoreAlgorithm,
|
@Nullable String keyStoreAlgorithm,
|
||||||
@Nullable String certAlias,
|
@Nullable String certAlias,
|
||||||
@Nullable PasswordProvider keyStorePasswordProvider,
|
@Nullable PasswordProvider keyStorePasswordProvider,
|
||||||
@Nullable PasswordProvider keyManagerFactoryPasswordProvider
|
@Nullable PasswordProvider keyManagerFactoryPasswordProvider,
|
||||||
|
@Nullable Boolean validateHostnames,
|
||||||
|
TLSCertificateChecker tlsCertificateChecker
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
SSLContext sslContext = null;
|
SSLContext sslContext = null;
|
||||||
|
@ -171,7 +194,6 @@ public class TLSUtils
|
||||||
: trustStoreAlgorithm);
|
: trustStoreAlgorithm);
|
||||||
trustManagerFactory.init(trustStore);
|
trustManagerFactory.init(trustStore);
|
||||||
|
|
||||||
|
|
||||||
KeyManager[] keyManagers;
|
KeyManager[] keyManagers;
|
||||||
if (keyStorePath != null) {
|
if (keyStorePath != null) {
|
||||||
KeyStore keyStore = KeyStore.getInstance(keyStoreType == null
|
KeyStore keyStore = KeyStore.getInstance(keyStoreType == null
|
||||||
|
@ -195,9 +217,25 @@ public class TLSUtils
|
||||||
keyManagers = null;
|
keyManagers = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
||||||
|
TrustManager[] newTrustManagers = new TrustManager[trustManagers.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < trustManagers.length; i++) {
|
||||||
|
if (trustManagers[i] instanceof X509ExtendedTrustManager) {
|
||||||
|
newTrustManagers[i] = new CustomCheckX509TrustManager(
|
||||||
|
(X509ExtendedTrustManager) trustManagers[i],
|
||||||
|
tlsCertificateChecker,
|
||||||
|
validateHostnames == null ? true : validateHostnames
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
newTrustManagers[i] = trustManagers[i];
|
||||||
|
log.info("Encountered non-X509ExtendedTrustManager: " + trustManagers[i].getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sslContext.init(
|
sslContext.init(
|
||||||
keyManagers,
|
keyManagers,
|
||||||
trustManagerFactory.getTrustManagers(),
|
newTrustManagers,
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue