Skip to content
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
19 changes: 18 additions & 1 deletion docs/content/configuration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,28 @@ The Druid servers [emit various metrics](../operations/metrics.html) and alerts
|`druid.emitter.http.minHttpTimeoutMillis`|If the speed of filling batches imposes timeout smaller than that, not even trying to send batch to endpoint, because it will likely fail, not being able to send the data that fast. Configure this depending based on emitter/successfulSending/minTimeMs metric. Reasonable values are 10ms..100ms.|0|
|`druid.emitter.http.recipientBaseUrl`|The base URL to emit messages to. Druid will POST JSON to be consumed at the HTTP endpoint specified by this property.|none, required config|

#### Http Emitter Module TLS Overrides

When emitting events to a TLS-enabled receiver, the Http Emitter will by default use an SSLContext obtained via the process described at [Druid's internal communication over TLS](../operations/tls-support.html#druids-internal-communication-over-tls), i.e., the same SSLContext that would be used for internal communications between Druid nodes.

In some use cases it may be desirable to have the Http Emitter use its own separate truststore configuration. For example, there may be organizational policies that prevent the TLS-enabled metrics receiver's certificate from being added to the same truststore used by Druid's internal HTTP client.

The following properties allow the Http Emitter to use its own truststore configuration when building its SSLContext.

|Property|Description|Default|
|--------|-----------|-------|
|`druid.emitter.http.ssl.useDefaultJavaContext`|If set to true, the HttpEmitter will use `SSLContext.getDefault()`, the default Java SSLContext, and all other properties below are ignored.|false|
|`druid.emitter.http.ssl.trustStorePath`|The file path or URL of the TLS/SSL Key store where trusted root certificates are stored. If this is unspecified, the Http Emitter will use the same SSLContext as Druid's internal HTTP client, as described in the beginning of this section, and all other properties below are ignored.|null|
|`druid.emitter.http.ssl.trustStoreType`|The type of the key store where trusted root certificates are stored.|`java.security.KeyStore.getDefaultType()`|
|`druid.emitter.http.ssl.trustStoreAlgorithm`|Algorithm to be used by TrustManager to validate certificate chains|`javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm()`|
|`druid.emitter.http.ssl.trustStorePassword`|The [Password Provider](../../operations/password-provider.html) or String password for the Trust Store.|none|
|`druid.emitter.http.ssl.protocol`|TLS protocol to use.|"TLSv1.2"|

#### Parametrized Http Emitter Module

`druid.emitter.parametrized.httpEmitting.*` configs correspond to the configs of Http Emitter Modules, see above.
Except `recipientBaseUrl`. E. g. `druid.emitter.parametrized.httpEmitting.flushMillis`,
`druid.emitter.parametrized.httpEmitting.flushCount`, etc.
`druid.emitter.parametrized.httpEmitting.flushCount`, `druid.emitter.parametrized.httpEmitting.ssl.trustStorePath`, etc.

The additional configs are:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,12 @@

package io.druid.https;

import com.google.common.base.Throwables;
import com.google.inject.Inject;
import com.google.inject.Provider;
import io.druid.java.util.emitter.EmittingLogger;
import io.druid.server.security.TLSUtils;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

public class SSLContextProvider implements Provider<SSLContext>
{
Expand All @@ -51,25 +43,12 @@ public SSLContext get()
{
log.info("Creating SslContext for https client using config [%s]", config);

SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance(config.getProtocol() == null ? "TLSv1.2" : config.getProtocol());
KeyStore keyStore = KeyStore.getInstance(config.getTrustStoreType() == null
? KeyStore.getDefaultType()
: config.getTrustStoreType());
keyStore.load(
new FileInputStream(config.getTrustStorePath()),
config.getTrustStorePasswordProvider().getPassword().toCharArray()
);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(config.getTrustStoreAlgorithm() == null
? TrustManagerFactory.getDefaultAlgorithm()
: config.getTrustStoreAlgorithm());
trustManagerFactory.init(keyStore);
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
}
catch (CertificateException | KeyManagementException | IOException | KeyStoreException | NoSuchAlgorithmException e) {
Throwables.propagate(e);
}
return sslContext;
return TLSUtils.createSSLContext(
config.getProtocol(),
config.getTrustStoreType(),
config.getTrustStorePath(),
config.getTrustStoreAlgorithm(),
config.getTrustStorePasswordProvider()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@
import com.google.inject.Provides;
import com.google.inject.name.Named;
import com.google.inject.util.Providers;
import io.druid.guice.LazySingleton;
import io.druid.java.util.common.logger.Logger;
import io.druid.java.util.emitter.core.Emitter;
import io.druid.java.util.emitter.core.HttpEmitterConfig;
import io.druid.java.util.emitter.core.HttpPostEmitter;
import io.druid.guice.JsonConfigProvider;
import io.druid.guice.LazySingleton;
import io.druid.guice.ManageLifecycle;
import io.druid.java.util.common.concurrent.Execs;
import io.druid.java.util.common.lifecycle.Lifecycle;
import io.druid.server.security.TLSUtils;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.JdkSslContext;
import io.netty.util.HashedWheelTimer;
Expand All @@ -49,10 +51,13 @@
*/
public class HttpEmitterModule implements Module
{
private static final Logger log = new Logger(HttpEmitterModule.class);

@Override
public void configure(Binder binder)
{
JsonConfigProvider.bind(binder, "druid.emitter.http", HttpEmitterConfig.class);
JsonConfigProvider.bind(binder, "druid.emitter.http.ssl", HttpEmitterSSLClientConfig.class);

configureSsl(binder);
}
Expand Down Expand Up @@ -90,6 +95,7 @@ static AsyncHttpClient createAsyncHttpClient(
@Named("http")
public Emitter getEmitter(
Supplier<HttpEmitterConfig> config,
Supplier<HttpEmitterSSLClientConfig> sslConfig,
@Nullable SSLContext sslContext,
Lifecycle lifecycle,
ObjectMapper jsonMapper
Expand All @@ -101,10 +107,35 @@ public Emitter getEmitter(
createAsyncHttpClient(
"HttpPostEmitter-AsyncHttpClient-%d",
"HttpPostEmitter-AsyncHttpClient-Timer-%d",
sslContext
getEffectiveSSLContext(sslConfig.get(), sslContext)
)
),
jsonMapper
);
}

public static SSLContext getEffectiveSSLContext(HttpEmitterSSLClientConfig sslConfig, SSLContext sslContext)
{
SSLContext effectiveSSLContext;
if (sslConfig.isUseDefaultJavaContext()) {
try {
effectiveSSLContext = SSLContext.getDefault();
}
catch (NoSuchAlgorithmException nsae) {
throw new RuntimeException(nsae);
}
} else if (sslConfig.getTrustStorePath() != null) {
log.info("Creating SSLContext for HttpEmitter client using config [%s]", sslConfig);
effectiveSSLContext = TLSUtils.createSSLContext(
sslConfig.getProtocol(),
sslConfig.getTrustStoreType(),
sslConfig.getTrustStorePath(),
sslConfig.getTrustStoreAlgorithm(),
sslConfig.getTrustStorePasswordProvider()
);
} else {
effectiveSSLContext = sslContext;
}
return effectiveSSLContext;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Metamarkets 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 io.druid.server.emitter;

import com.fasterxml.jackson.annotation.JsonProperty;

import io.druid.metadata.PasswordProvider;

/**
* This is kept separate from {@link io.druid.java.util.emitter.core.HttpEmitterConfig} because {@link PasswordProvider}
* is currently located in druid-api. The java-util module which contains HttpEmitterConfig cannot import
* PasswordProvider because this would introduce a circular dependence between java-util and druid-api.
*
* PasswordProvider could be moved to java-util, but PasswordProvider is annotated with
* {@link io.druid.guice.annotations.ExtensionPoint}, which would also have to be moved.
*
* It would be easier to resolve these issues and merge the TLS-related config with HttpEmitterConfig once
* https://github.com/druid-io/druid/issues/4312 is resolved, so the TLS config is kept separate for now.
*/
public class HttpEmitterSSLClientConfig
{
@JsonProperty
private String protocol;

@JsonProperty
private String trustStoreType;

@JsonProperty
private String trustStorePath;

@JsonProperty
private String trustStoreAlgorithm;

@JsonProperty("trustStorePassword")
private PasswordProvider trustStorePasswordProvider;

@JsonProperty("useDefaultJavaContext")
private boolean useDefaultJavaContext = false;

public String getProtocol()
{
return protocol;
}

public String getTrustStoreType()
{
return trustStoreType;
}

public String getTrustStorePath()
{
return trustStorePath;
}

public String getTrustStoreAlgorithm()
{
return trustStoreAlgorithm;
}

public PasswordProvider getTrustStorePasswordProvider()
{
return trustStorePasswordProvider;
}

public boolean isUseDefaultJavaContext()
{
return useDefaultJavaContext;
}

@Override
public String toString()
{
return "HttpEmitterSSLClientConfig{" +
"protocol='" + protocol + '\'' +
", trustStoreType='" + trustStoreType + '\'' +
", trustStorePath='" + trustStorePath + '\'' +
", trustStoreAlgorithm='" + trustStoreAlgorithm + '\'' +
", useDefaultJavaContext='" + useDefaultJavaContext + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.google.inject.Module;
import com.google.inject.Provides;
import com.google.inject.name.Named;
import io.druid.java.util.common.logger.Logger;
import io.druid.java.util.emitter.core.Emitter;
import io.druid.java.util.emitter.core.ParametrizedUriEmitter;
import io.druid.java.util.emitter.core.ParametrizedUriEmitterConfig;
Expand All @@ -37,30 +38,34 @@

public class ParametrizedUriEmitterModule implements Module
{
private static final Logger log = new Logger(ParametrizedUriEmitterModule.class);

@Override
public void configure(Binder binder)
{
JsonConfigProvider.bind(binder, "druid.emitter.parametrized", ParametrizedUriEmitterConfig.class);
HttpEmitterModule.configureSsl(binder);
JsonConfigProvider.bind(binder, "druid.emitter.parametrized.httpEmitting", ParametrizedUriEmitterSSLClientConfig.class);
}

@Provides
@ManageLifecycle
@Named("parametrized")
public Emitter getEmitter(
Supplier<ParametrizedUriEmitterConfig> config,
Supplier<ParametrizedUriEmitterSSLClientConfig> parametrizedSSLClientConfig,
@Nullable SSLContext sslContext,
Lifecycle lifecycle,
ObjectMapper jsonMapper
)
{
HttpEmitterSSLClientConfig sslConfig = parametrizedSSLClientConfig.get().getHttpEmittingSSLClientConfig();
return new ParametrizedUriEmitter(
config.get(),
lifecycle.addCloseableInstance(
HttpEmitterModule.createAsyncHttpClient(
"ParmetrizedUriEmitter-AsyncHttpClient-%d",
"ParmetrizedUriEmitter-AsyncHttpClient-Timer-%d",
sslContext
HttpEmitterModule.getEffectiveSSLContext(sslConfig, sslContext)
)
),
jsonMapper
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Metamarkets 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 io.druid.server.emitter;

import com.fasterxml.jackson.annotation.JsonProperty;

public class ParametrizedUriEmitterSSLClientConfig
{
private static final HttpEmitterSSLClientConfig HTTP_EMITTER_SSL_CLIENT_CONFIG = new HttpEmitterSSLClientConfig();

@JsonProperty("ssl")
private HttpEmitterSSLClientConfig httpEmittingSSLClientConfig = HTTP_EMITTER_SSL_CLIENT_CONFIG;

@Override
public String toString()
{
return "ParametrizedUriEmitterSSLClientConfig{" +
"httpEmittingSSLClientConfig='" + httpEmittingSSLClientConfig +
'}';
}

public HttpEmitterSSLClientConfig getHttpEmittingSSLClientConfig()
{
return httpEmittingSSLClientConfig;
}
}
Loading