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.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.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
|
||||
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.
|
||||
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
|
||||
private String keyManagerFactoryAlgorithm;
|
||||
|
||||
@JsonProperty
|
||||
private Boolean validateHostnames;
|
||||
|
||||
public String getProtocol()
|
||||
{
|
||||
return protocol;
|
||||
|
@ -112,6 +115,11 @@ public class SSLClientConfig
|
|||
return keyManagerFactoryAlgorithm;
|
||||
}
|
||||
|
||||
public Boolean getValidateHostnames()
|
||||
{
|
||||
return validateHostnames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
@ -124,6 +132,7 @@ public class SSLClientConfig
|
|||
", keyStoreType='" + keyStoreType + '\'' +
|
||||
", certAlias='" + certAlias + '\'' +
|
||||
", keyManagerFactoryAlgorithm='" + keyManagerFactoryAlgorithm + '\'' +
|
||||
", validateHostnames='" + validateHostnames + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ package org.apache.druid.https;
|
|||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import org.apache.druid.java.util.emitter.EmittingLogger;
|
||||
import org.apache.druid.server.security.TLSCertificateChecker;
|
||||
import org.apache.druid.server.security.TLSUtils;
|
||||
|
||||
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 SSLClientConfig config;
|
||||
private TLSCertificateChecker certificateChecker;
|
||||
|
||||
@Inject
|
||||
public SSLContextProvider(SSLClientConfig config)
|
||||
public SSLContextProvider(
|
||||
SSLClientConfig config,
|
||||
TLSCertificateChecker certificateChecker
|
||||
)
|
||||
{
|
||||
this.config = config;
|
||||
this.certificateChecker = certificateChecker;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -55,6 +61,8 @@ public class SSLContextProvider implements Provider<SSLContext>
|
|||
.setCertAlias(config.getCertAlias())
|
||||
.setKeyStorePasswordProvider(config.getKeyStorePasswordProvider())
|
||||
.setKeyManagerFactoryPasswordProvider(config.getKeyManagerPasswordProvider())
|
||||
.setValidateHostnames(config.getValidateHostnames())
|
||||
.setCertificateChecker(certificateChecker)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ ADD client_tls client_tls
|
|||
# - 8083, 8283: HTTP, HTTPS (historical)
|
||||
# - 8090, 8290: HTTP, HTTPS (overlord)
|
||||
# - 8091, 8291: HTTP, HTTPS (middlemanager)
|
||||
# - 8888-8891, 9088-9091: HTTP, HTTPS (routers)
|
||||
# - 3306: MySQL
|
||||
# - 2181 2888 3888: ZooKeeper
|
||||
# - 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.
|
||||
|
||||
# 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
|
||||
docker stop $node
|
||||
docker rm $node
|
||||
|
@ -50,6 +50,9 @@ mvn -B dependency:copy-dependencies -DoutputDirectory=$SHARED_DIR/docker/lib
|
|||
# install logging config
|
||||
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
|
||||
|
||||
# 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
|
||||
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 permissiveRouterUrl;
|
||||
private String noClientAuthRouterUrl;
|
||||
private String customCertCheckRouterUrl;
|
||||
private String routerTLSUrl;
|
||||
private String brokerTLSUrl;
|
||||
private String historicalTLSUrl;
|
||||
|
@ -47,6 +48,7 @@ public class ConfigFileConfigProvider implements IntegrationTestingConfigProvide
|
|||
private String indexerTLSUrl;
|
||||
private String permissiveRouterTLSUrl;
|
||||
private String noClientAuthRouterTLSUrl;
|
||||
private String customCertCheckRouterTLSUrl;
|
||||
private String middleManagerHost;
|
||||
private String zookeeperHosts; // comma-separated list of host:port
|
||||
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"));
|
||||
}
|
||||
}
|
||||
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");
|
||||
if (brokerUrl == null) {
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCustomCertCheckRouterUrl()
|
||||
{
|
||||
return customCertCheckRouterUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCustomCertCheckRouterTLSUrl()
|
||||
{
|
||||
return customCertCheckRouterTLSUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBrokerUrl()
|
||||
{
|
||||
|
|
|
@ -102,6 +102,18 @@ public class DockerConfigProvider implements IntegrationTestingConfigProvider
|
|||
return "https://" + dockerIp + ":9090";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCustomCertCheckRouterUrl()
|
||||
{
|
||||
return "http://" + dockerIp + ":8891";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCustomCertCheckRouterTLSUrl()
|
||||
{
|
||||
return "https://" + dockerIp + ":9091";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBrokerUrl()
|
||||
{
|
||||
|
|
|
@ -45,6 +45,10 @@ public interface IntegrationTestingConfig
|
|||
|
||||
String getNoClientAuthRouterTLSUrl();
|
||||
|
||||
String getCustomCertCheckRouterUrl();
|
||||
|
||||
String getCustomCertCheckRouterTLSUrl();
|
||||
|
||||
String getBrokerUrl();
|
||||
|
||||
String getBrokerTLSUrl();
|
||||
|
|
|
@ -59,5 +59,4 @@ public class DruidTestModuleFactory implements IModuleFactory
|
|||
context.addInjector(Collections.singletonList(module), injector);
|
||||
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.response.StatusResponseHandler;
|
||||
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.testing.IntegrationTestingConfig;
|
||||
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.HttpResponseStatus;
|
||||
import org.joda.time.Duration;
|
||||
|
@ -81,6 +83,9 @@ public class ITTLSTest
|
|||
@Client
|
||||
DruidHttpClientConfig httpClientConfig;
|
||||
|
||||
@Inject
|
||||
TLSCertificateChecker certificateChecker;
|
||||
|
||||
StatusResponseHandler responseHandler = new StatusResponseHandler(StandardCharsets.UTF_8);
|
||||
|
||||
@Test
|
||||
|
@ -234,6 +239,33 @@ public class ITTLSTest
|
|||
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)
|
||||
{
|
||||
checkFailedAccess(
|
||||
|
@ -242,7 +274,8 @@ public class ITTLSTest
|
|||
url + "/status",
|
||||
"Certless",
|
||||
SSLException.class,
|
||||
"Received fatal alert: bad_certificate"
|
||||
"Received fatal alert: bad_certificate",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -254,7 +287,8 @@ public class ITTLSTest
|
|||
url + "/status",
|
||||
"Wrong hostname",
|
||||
SSLException.class,
|
||||
"Received fatal alert: certificate_unknown"
|
||||
"Received fatal alert: certificate_unknown",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -266,7 +300,8 @@ public class ITTLSTest
|
|||
url + "/status",
|
||||
"Wrong root cert",
|
||||
SSLException.class,
|
||||
"Received fatal alert: certificate_unknown"
|
||||
"Received fatal alert: certificate_unknown",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -278,7 +313,8 @@ public class ITTLSTest
|
|||
url + "/status",
|
||||
"Revoked cert",
|
||||
SSLException.class,
|
||||
"Received fatal alert: certificate_unknown"
|
||||
"Received fatal alert: certificate_unknown",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -290,7 +326,8 @@ public class ITTLSTest
|
|||
url + "/status",
|
||||
"Expired cert",
|
||||
SSLException.class,
|
||||
"Received fatal alert: certificate_unknown"
|
||||
"Received fatal alert: certificate_unknown",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -302,7 +339,8 @@ public class ITTLSTest
|
|||
url + "/status",
|
||||
"Cert signed by non-CA",
|
||||
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)
|
||||
{
|
||||
return makeCustomHttpClient(keystorePath, certAlias, certificateChecker);
|
||||
}
|
||||
|
||||
private HttpClient makeCustomHttpClient(
|
||||
String keystorePath,
|
||||
String certAlias,
|
||||
TLSCertificateChecker certificateChecker
|
||||
)
|
||||
{
|
||||
SSLContext intermediateClientSSLContext = new TLSUtils.ClientSSLContextBuilder()
|
||||
.setProtocol(sslClientConfig.getProtocol())
|
||||
|
@ -335,6 +382,7 @@ public class ITTLSTest
|
|||
.setCertAlias(certAlias)
|
||||
.setKeyStorePasswordProvider(sslClientConfig.getKeyStorePasswordProvider())
|
||||
.setKeyManagerFactoryPasswordProvider(sslClientConfig.getKeyManagerPasswordProvider())
|
||||
.setCertificateChecker(certificateChecker)
|
||||
.build();
|
||||
|
||||
final HttpClientConfig.Builder builder = getHttpClientConfigBuilder(intermediateClientSSLContext);
|
||||
|
@ -361,6 +409,7 @@ public class ITTLSTest
|
|||
.setTrustStorePath(sslClientConfig.getTrustStorePath())
|
||||
.setTrustStoreAlgorithm(sslClientConfig.getTrustStoreAlgorithm())
|
||||
.setTrustStorePasswordProvider(sslClientConfig.getTrustStorePasswordProvider())
|
||||
.setCertificateChecker(certificateChecker)
|
||||
.build();
|
||||
|
||||
final HttpClientConfig.Builder builder = getHttpClientConfigBuilder(certlessClientSSLContext);
|
||||
|
@ -385,7 +434,8 @@ public class ITTLSTest
|
|||
String url,
|
||||
String clientDesc,
|
||||
Class expectedException,
|
||||
String expectedExceptionMsg
|
||||
String expectedExceptionMsg,
|
||||
boolean useContainsMsgCheck
|
||||
)
|
||||
{
|
||||
int retries = 0;
|
||||
|
@ -411,13 +461,17 @@ public class ITTLSTest
|
|||
|
||||
Assert.assertTrue(
|
||||
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))
|
||||
);
|
||||
|
||||
Assert.assertEquals(
|
||||
rootCause.getMessage(),
|
||||
expectedExceptionMsg
|
||||
);
|
||||
if (useContainsMsgCheck) {
|
||||
Assert.assertTrue(rootCause.getMessage().contains(expectedExceptionMsg));
|
||||
} else {
|
||||
Assert.assertEquals(
|
||||
rootCause.getMessage(),
|
||||
expectedExceptionMsg
|
||||
);
|
||||
}
|
||||
|
||||
LOG.info("%s client [%s] request failed as expected when accessing [%s]", clientDesc, method, url);
|
||||
return;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# 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
|
||||
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.jetty.JettyServerModule;
|
||||
import org.apache.druid.server.metrics.MetricsModule;
|
||||
import org.apache.druid.server.security.TLSCertificateCheckerModule;
|
||||
import org.eclipse.aether.artifact.DefaultArtifact;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -369,6 +370,7 @@ public class Initialization
|
|||
new Log4jShutterDownerModule(),
|
||||
new DruidAuthModule(),
|
||||
new LifecycleModule(),
|
||||
TLSCertificateCheckerModule.class,
|
||||
EmitterModule.class,
|
||||
HttpClientModule.global(),
|
||||
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.TLSServerConfig;
|
||||
import org.apache.druid.server.metrics.DataSourceTaskIdHolder;
|
||||
import org.apache.druid.server.security.TLSCertificateChecker;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
||||
|
@ -101,7 +102,8 @@ public class ChatHandlerServerModule implements Module
|
|||
node,
|
||||
config,
|
||||
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.MetricsModule;
|
||||
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.Handler;
|
||||
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.SSLEngine;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.CRL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -159,7 +165,8 @@ public class JettyServerModule extends JerseyServletModule
|
|||
node,
|
||||
config,
|
||||
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,
|
||||
ServerConfig config,
|
||||
TLSServerConfig tlsServerConfig,
|
||||
Binding<SslContextFactory> sslContextFactoryBinding
|
||||
Binding<SslContextFactory> sslContextFactoryBinding,
|
||||
TLSCertificateChecker certificateChecker
|
||||
)
|
||||
{
|
||||
// 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());
|
||||
if (sslContextFactoryBinding == null) {
|
||||
// Never trust all certificates by default
|
||||
sslContextFactory = new SslContextFactory(false);
|
||||
sslContextFactory = new IdentityCheckOverrideSslContextFactory(tlsServerConfig, certificateChecker);
|
||||
|
||||
sslContextFactory.setKeyStorePath(tlsServerConfig.getKeyStorePath());
|
||||
sslContextFactory.setKeyStoreType(tlsServerConfig.getKeyStoreType());
|
||||
|
@ -471,4 +479,45 @@ public class JettyServerModule extends JerseyServletModule
|
|||
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.Throwables;
|
||||
import org.apache.druid.java.util.common.logger.Logger;
|
||||
import org.apache.druid.metadata.PasswordProvider;
|
||||
|
||||
import org.eclipse.jetty.util.ssl.AliasedX509ExtendedKeyManager;
|
||||
|
@ -29,8 +30,10 @@ import javax.annotation.Nullable;
|
|||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyManagementException;
|
||||
|
@ -42,6 +45,8 @@ import java.security.cert.CertificateException;
|
|||
|
||||
public class TLSUtils
|
||||
{
|
||||
private static final Logger log = new Logger(TLSUtils.class);
|
||||
|
||||
public static class ClientSSLContextBuilder
|
||||
{
|
||||
private String protocol;
|
||||
|
@ -55,6 +60,8 @@ public class TLSUtils
|
|||
private String certAlias;
|
||||
private PasswordProvider keyStorePasswordProvider;
|
||||
private PasswordProvider keyManagerFactoryPasswordProvider;
|
||||
private Boolean validateHostnames;
|
||||
private TLSCertificateChecker certificateChecker;
|
||||
|
||||
public ClientSSLContextBuilder setProtocol(String protocol)
|
||||
{
|
||||
|
@ -122,6 +129,18 @@ public class TLSUtils
|
|||
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()
|
||||
{
|
||||
Preconditions.checkNotNull(trustStorePath, "must specify a trustStorePath");
|
||||
|
@ -137,7 +156,9 @@ public class TLSUtils
|
|||
keyStoreAlgorithm,
|
||||
certAlias,
|
||||
keyStorePasswordProvider,
|
||||
keyManagerFactoryPasswordProvider
|
||||
keyManagerFactoryPasswordProvider,
|
||||
validateHostnames,
|
||||
certificateChecker
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -153,7 +174,9 @@ public class TLSUtils
|
|||
@Nullable String keyStoreAlgorithm,
|
||||
@Nullable String certAlias,
|
||||
@Nullable PasswordProvider keyStorePasswordProvider,
|
||||
@Nullable PasswordProvider keyManagerFactoryPasswordProvider
|
||||
@Nullable PasswordProvider keyManagerFactoryPasswordProvider,
|
||||
@Nullable Boolean validateHostnames,
|
||||
TLSCertificateChecker tlsCertificateChecker
|
||||
)
|
||||
{
|
||||
SSLContext sslContext = null;
|
||||
|
@ -171,7 +194,6 @@ public class TLSUtils
|
|||
: trustStoreAlgorithm);
|
||||
trustManagerFactory.init(trustStore);
|
||||
|
||||
|
||||
KeyManager[] keyManagers;
|
||||
if (keyStorePath != null) {
|
||||
KeyStore keyStore = KeyStore.getInstance(keyStoreType == null
|
||||
|
@ -195,9 +217,25 @@ public class TLSUtils
|
|||
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(
|
||||
keyManagers,
|
||||
trustManagerFactory.getTrustManagers(),
|
||||
newTrustManagers,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue