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.yamlfile. (See proposed example from https://github.com/prometheus/jmx_exporter/blob/new-config/collector/src/test/resources/test-config-new.yaml) -
The
jmx_exportershould be able to use a different keystore than the one that is configured for the whole JVM, if defined in theconfig.yamlfile. Using the-Djavax.net.ssl.keyStoreand-Djavax.net.ssl.keyStore.passphraseshould work as a backup, but should be discouraged for configuring thejmx_exporter. -
An
sslKeyAliasis required to build the correctSSLContextin the scenario there are multiple certificates in the keystore. -
The
HTTPServer.Builderinclient_javashould 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
HttpsConfiguratorused by theHTTPServer.Buildershould 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
InetSocketAddressshould not be created. This can be handled by theHTTPServer.BuilderwithHostname(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