Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom TLS cert checks #6432

Merged
merged 3 commits into from
Oct 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
13 changes: 13 additions & 0 deletions docs/content/operations/tls-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ public class SSLClientConfig
@JsonProperty
private String keyManagerFactoryAlgorithm;

@JsonProperty
private Boolean validateHostnames;

public String getProtocol()
{
return protocol;
Expand Down Expand Up @@ -112,6 +115,11 @@ public String getKeyManagerFactoryAlgorithm()
return keyManagerFactoryAlgorithm;
}

public Boolean getValidateHostnames()
{
return validateHostnames;
}

@Override
public String toString()
{
Expand All @@ -124,6 +132,7 @@ public String toString()
", keyStoreType='" + keyStoreType + '\'' +
", certAlias='" + certAlias + '\'' +
", keyManagerFactoryAlgorithm='" + keyManagerFactoryAlgorithm + '\'' +
", validateHostnames='" + validateHostnames + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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;
Expand All @@ -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
Expand All @@ -55,6 +61,8 @@ public SSLContext get()
.setCertAlias(config.getCertAlias())
.setKeyStorePasswordProvider(config.getKeyStorePasswordProvider())
.setKeyManagerFactoryPasswordProvider(config.getKeyManagerPasswordProvider())
.setValidateHostnames(config.getValidateHostnames())
.setCertificateChecker(certificateChecker)
.build();
}
}
1 change: 1 addition & 0 deletions integration-tests/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
57 changes: 57 additions & 0 deletions integration-tests/docker/router-custom-check-tls.conf
Original file line number Diff line number Diff line change
@@ -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
8 changes: 7 additions & 1 deletion integration-tests/run_cluster.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@ 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;
private String coordinatorTLSUrl;
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;
Expand Down Expand Up @@ -114,6 +116,21 @@ private void loadProperties(String configFile)
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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: i find this kind of jarring since outer is var == null and then inside it's flipped to null != var, but I don't think it's a big deal

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, it follows the convention for the other nodes in that file, i'll leave this unchanged in this PR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

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"));
Expand Down Expand Up @@ -248,6 +265,18 @@ public String getNoClientAuthRouterTLSUrl()
return noClientAuthRouterTLSUrl;
}

@Override
public String getCustomCertCheckRouterUrl()
{
return customCertCheckRouterUrl;
}

@Override
public String getCustomCertCheckRouterTLSUrl()
{
return customCertCheckRouterTLSUrl;
}

@Override
public String getBrokerUrl()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@ public String getNoClientAuthRouterTLSUrl()
return "https://" + dockerIp + ":9090";
}

@Override
public String getCustomCertCheckRouterUrl()
{
return "http://" + dockerIp + ":8891";
}

@Override
public String getCustomCertCheckRouterTLSUrl()
{
return "https://" + dockerIp + ":9091";
}

@Override
public String getBrokerUrl()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ public interface IntegrationTestingConfig

String getNoClientAuthRouterTLSUrl();

String getCustomCertCheckRouterUrl();

String getCustomCertCheckRouterTLSUrl();

String getBrokerUrl();

String getBrokerTLSUrl();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,4 @@ public Module createModule(ITestContext context, Class<?> testClass)
context.addInjector(Collections.singletonList(module), injector);
return module;
}

}
Original file line number Diff line number Diff line change
@@ -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;
}
}

Original file line number Diff line number Diff line change
@@ -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.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.apache.druid.testing.guice.ITTLSCertificateCheckerModule
Loading