jmx_exporter
jmx_exporter copied to clipboard
add tls support
Signed-off-by: lucian [email protected] @fstab Adding TLS support for the HTTP endpoint.
Thanks a lot! I started working on a new config file format (no worries, jmx_exporter
will still be able to read the old config for backwards compatibility). It would be good if we could make SSL configurable in the config file in addition to standard command line options. Please give me a couple of days to finalize the config and then get back to this PR. Feel free to comment on the new config file, it's still work in progress: https://github.com/prometheus/jmx_exporter/blob/new-config/collector/src/test/resources/test-config-new.yaml
After looking at the PR a few thoughts/opinions...
-
We should not introduce a command-line argument to enable TLS. This should be part of the
config.yaml
file. (See proposed example from https://github.com/prometheus/jmx_exporter/blob/new-config/collector/src/test/resources/test-config-new.yaml) -
The
jmx_exporter
should be able to use a different keystore than the one that is configured for the whole JVM, if defined in theconfig.yaml
file. Using the-Djavax.net.ssl.keyStore
and-Djavax.net.ssl.keyStore.passphrase
should work as a backup, but should be discouraged for configuring thejmx_exporter
. -
An
sslKeyAlias
is required to build the correctSSLContext
in the scenario there are multiple certificates in the keystore. -
The
HTTPServer.Builder
inclient_java
should be used to build theHTTPServer
(https://github.com/prometheus/client_java/blob/c83877ab01539a34f172d72405db0f1b9b29cc60/simpleclient_httpserver/src/main/java/io/prometheus/client/exporter/HTTPServer.java#L310). -
The creation of the
HttpsConfigurator
used by theHTTPServer.Builder
should be defined in a static method (similar to https://github.com/prometheus/client_java/blob/c83877ab01539a34f172d72405db0f1b9b29cc60/simpleclient_httpserver/src/test/java/io/prometheus/client/exporter/TestHTTPServer.java#L543). -
The
InetSocketAddress
should not be created. This can be handled by theHTTPServer.Builder
withHostname(String hostname)
andwithPort(int port)
methods.
Pseudocode...
public class JavaAgent {
static HTTPServer server;
public static void agentmain(String agentArgument, Instrumentation instrumentation) throws Exception {
premain(agentArgument, instrumentation);
}
public static void premain(String agentArgument, Instrumentation instrumentation) throws Exception {
// Bind to all interfaces by default (this includes IPv6).
String hostname = "0.0.0.0";
try {
Config config = parseConfig(agentArgument, hostname);
new BuildInfoCollector().register();
new JmxCollector(new File(config.file), JmxCollector.Mode.AGENT).register();
DefaultExports.initialize();
HTTPServer.Builder builder = new HTTPServer.Builder()
.withHostname(config.hostname)
.withPort(config.port)
.withDaemonThreads(true);
if (config.getHTTPServerConfig().sslEnabled) {
builder.withHttpsConfigurator(
createHttpsConfigurator(
createSSLContext(config)));
}
server = builder.build();
}
catch (IllegalArgumentException e) {
System.err.println("Usage: -javaagent:/path/to/JavaAgent.jar=[host:]<port>:<yaml configuration file>" + e.getMessage());
System.exit(1);
}
}
/**
* Parse the Java Agent configuration. The arguments are typically specified to the JVM as a javaagent as
* {@code -javaagent:/path/to/agent.jar=<CONFIG>}. This method parses the {@code <CONFIG>} portion.
* @param args provided agent args
* @param ifc default bind interface
* @return configuration to use for our application
*/
public static Config parseConfig(String args, String ifc) {
Pattern pattern = Pattern.compile(
"^(?:((?:[\\w.-]+)|(?:\\[.+])):)?" + // host name, or ipv4, or ipv6 address in brackets
"(\\d{1,5}):" + // port
"(.+)"); // config file
Matcher matcher = pattern.matcher(args);
if (!matcher.matches()) {
throw new IllegalArgumentException("Malformed arguments - " + args);
}
String hostname = matcher.group(1);
String port = matcher.group(2);
String configFile = matcher.group(3);
return new Config(hostname, Integer.parseInt(port), configFile);
}
/**
* Creates an HttpsConfiguration
*
* @param sslContext
* @return HttpsConfigurator
*/
private static HttpsConfigurator createHttpsConfigurator(SSLContext sslContext) {
return new HttpsConfigurator(sslContext) {
@Override
public void configure(HttpsParameters params) {
try {
SSLContext c = getSSLContext();
SSLEngine engine = c.createSSLEngine();
params.setNeedClientAuth(false);
params.setCipherSuites(engine.getEnabledCipherSuites());
params.setProtocols(engine.getEnabledProtocols());
SSLParameters sslParameters = c.getSupportedSSLParameters();
params.setSSLParameters(sslParameters);
} catch (Exception e) {
throw new RuntimeException("Exception creating HttpsConfigurator", e);
}
}
};
}
/**
* Create an SSLContext
*
* @return SSLContext
* @throws GeneralSecurityException
* @throws IOException
*/
private final static SSLContext createSSLContext(Config config)
throws GeneralSecurityException, IOException {
SSLConfig sslConfig = config.getSSLConfig();
SSLContext sslContext = null;
FileInputStream fileInputStream = null;
try {
File file = new File(sslConfig.keyStorePath);
if ((file.exists() == false) || (file.isFile() == false) || (file.canRead() == false)) {
throw new IllegalArgumentException("cannot read 'keyStorePath', path = [" + file.getAbsolutePath() + "]");
}
fileInputStream = new FileInputStream(sslConfig.keyStorePath);
KeyStore keyStore = KeyStore.getInstance(sslConfig.keyStoreType);
keyStore.load(fileInputStream, sslConfig.keyStorePassword.toCharArray());
KeyManagerFactory keyManagerFactor = KeyManagerFactory.getInstance("SunX509");
keyManagerFactor.init(keyStore, sslConfig.keyStorePassword.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
trustManagerFactory.init(keyStore);
sslContext = SSLContext.getInstance(sslConfig.sslContextType);
// TODO code to hook the SSL certificate alias
sslContext.init(keyManagerFactor.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
// IGNORE
}
}
}
return sslContext;
}
static class Config {
String hostname;
int port;
String file;
Config(String hostname, int port, String file) {
this.hostname = hostname;
this.port = port;
this.file = file;
}
public HTTPServerConfig getHTTPServerConfig() {
return new HTTPServerConfig(this);
}
public SSLConfig getSSLConfig() {
return new SSLConfig(this);
}
}
static class HTTPServerConfig {
boolean sslEnabled;
public HTTPServerConfig(Config config) {
// TODO load/parse HTTPServer subsection
}
}
static class SSLConfig {
String sslContextType;
String keyStoreType;
String keyStorePath;
String keyStorePassword;
public SSLConfig(Config config) {
// TODO load/parse SSL configuration section
}
}
@fstab any updates on this one?
This would be really useful
@fstab any update on this feature?
@fstab Asking again, any updates?
@lucian-vanghele @mlhmz SSL (TLS) support for exposing metrics is coming in the next release, targeted within the month.
@lucian-vanghele @mlhmz SSL (TLS) support for exposing metrics is coming in the next release, targeted within the month.
is this a thing or not yet?
Resolved with the 0.19.0 release