Skip to content

Commit

Permalink
Merge pull request #257 from drcapulet/alexc-protocols
Browse files Browse the repository at this point in the history
Allow user-specifiable SSL / TLS protocols and ciphers to the JVM agent
  • Loading branch information
rhuss committed Jul 4, 2016
2 parents c740186 + f054d4f commit cccb2da
Show file tree
Hide file tree
Showing 8 changed files with 372 additions and 93 deletions.
47 changes: 25 additions & 22 deletions agent/jvm/src/main/java/org/jolokia/jvmagent/JolokiaServer.java
Expand Up @@ -169,7 +169,7 @@ public JolokiaServerConfig getServerConfig() {
protected final void init(JolokiaServerConfig pConfig, boolean pLazy) throws IOException {
// We manage it on our own
httpServer = createHttpServer(pConfig);
init(httpServer,pConfig,pLazy);
init(httpServer, pConfig, pLazy);
}

/**
Expand All @@ -186,7 +186,7 @@ protected final void init(HttpServer pServer, JolokiaServerConfig pConfig, boole

// Create proper context along with handler
final String contextPath = pConfig.getContextPath();
jolokiaHttpHandler = useHttps(pConfig) ?
jolokiaHttpHandler = pConfig.useHttps() ?
new JolokiaHttpsHandler(pConfig) :
new JolokiaHttpHandler(pConfig.getJolokiaConfig());
HttpContext context = pServer.createContext(contextPath, jolokiaHttpHandler);
Expand Down Expand Up @@ -240,7 +240,7 @@ private HttpServer createHttpServer(JolokiaServerConfig pConfig) throws IOExcept
InetAddress address = pConfig.getAddress();
InetSocketAddress socketAddress = new InetSocketAddress(address,port);

HttpServer server = useHttps(pConfig) ?
HttpServer server = pConfig.useHttps() ?
createHttpsServer(socketAddress, pConfig) :
HttpServer.create(socketAddress, pConfig.getBacklog());

Expand All @@ -261,13 +261,7 @@ private HttpServer createHttpServer(JolokiaServerConfig pConfig) throws IOExcept

// =========================================================================================================
// HTTPS handling

private boolean useHttps(JolokiaServerConfig pConfig) {
String protocol = pConfig.getProtocol();
return protocol.equalsIgnoreCase("https");
}

private HttpServer createHttpsServer(InetSocketAddress pSocketAddress,JolokiaServerConfig pConfig) {
private HttpServer createHttpsServer(InetSocketAddress pSocketAddress, JolokiaServerConfig pConfig) {
// initialise the HTTPS server
try {
HttpsServer server = HttpsServer.create(pSocketAddress, pConfig.getBacklog());
Expand All @@ -285,8 +279,12 @@ private HttpServer createHttpsServer(InetSocketAddress pSocketAddress,JolokiaSer
tmf.init(ks);

// setup the HTTPS context and parameters
sslContext.init(kmf.getKeyManagers(),tmf.getTrustManagers(), null);
server.setHttpsConfigurator(new JolokiaHttpsConfigurator(sslContext, pConfig.useSslClientAuthentication()));
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

// Update the config to filter out bad protocols or ciphers
pConfig.updateHTTPSSettingsFromContext(sslContext);

server.setHttpsConfigurator(new JolokiaHttpsConfigurator(sslContext, pConfig));
return server;
} catch (GeneralSecurityException e) {
throw new IllegalStateException("Cannot use keystore for https communication: " + e,e);
Expand Down Expand Up @@ -401,29 +399,34 @@ public Thread newThread(Runnable r) {

// HTTPS configurator
private static final class JolokiaHttpsConfigurator extends HttpsConfigurator {
private boolean useClientAuthentication;
private JolokiaServerConfig serverConfig;
private SSLContext context;

private JolokiaHttpsConfigurator(SSLContext pSSLContext,boolean pUseClientAuthentication) {
private JolokiaHttpsConfigurator(SSLContext pSSLContext, JolokiaServerConfig pConfig) {
super(pSSLContext);
this.context = pSSLContext;
useClientAuthentication = pUseClientAuthentication;
this.serverConfig = pConfig;
}

/** {@inheritDoc} */
public void configure(HttpsParameters params) {

// initialise the SSL context
SSLEngine engine = context.createSSLEngine();
params.setNeedClientAuth(useClientAuthentication);
params.setCipherSuites(engine.getEnabledCipherSuites());
params.setProtocols(engine.getEnabledProtocols());

// get the default parameters
SSLParameters defaultSSLParameters = context.getDefaultSSLParameters();
defaultSSLParameters.setNeedClientAuth(useClientAuthentication);
params.setSSLParameters(defaultSSLParameters);

params.setNeedClientAuth(serverConfig.useSslClientAuthentication());
defaultSSLParameters.setNeedClientAuth(serverConfig.useSslClientAuthentication());

// Cipher Suites
params.setCipherSuites(serverConfig.getSSLCipherSuites());
defaultSSLParameters.setCipherSuites(serverConfig.getSSLCipherSuites());

// Protocols
params.setProtocols(serverConfig.getSSLProtocols());
defaultSSLParameters.setProtocols(serverConfig.getSSLProtocols());

params.setSSLParameters(defaultSSLParameters);
}
}
}
Expand Down
Expand Up @@ -28,6 +28,8 @@
import java.util.regex.Pattern;

import com.sun.net.httpserver.Authenticator;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import org.jolokia.config.ConfigKey;
import org.jolokia.config.Configuration;
import org.jolokia.jvmagent.security.*;
Expand Down Expand Up @@ -68,6 +70,8 @@ public class JolokiaServerConfig {
private String serverKeyAlgorithm;
private List<String> clientPrincipals;
private boolean extendedClientCheck;
private String[] sslProtocols;
private String[] sslCipherSuites;

/**
* Constructor which prepares the server configuration from a map
Expand Down Expand Up @@ -136,6 +140,15 @@ public String getProtocol() {
return protocol;
}

/**
* Whether or not to use https as the procol
*
* @return true when using https as the protocol
*/
public boolean useHttps() {
return protocol.equalsIgnoreCase("https");
}

/**
* Address to bind to, which is either used from the configuration option
* "host" or by default from {@link InetAddress#getLocalHost()}
Expand Down Expand Up @@ -261,12 +274,73 @@ public String getServerKey() {
/**
* The algorithm to use for extracting the private server key.
*
* @return the server keyl algoritm
* @return the server key algorithm
*/
public String getServerKeyAlgorithm() {
return serverKeyAlgorithm;
}

/**
* The list of enabled SSL / TLS protocols to serve with
*
* @return the list of enabled protocols
*/
public String[] getSSLProtocols() { return sslProtocols; }

/**
* The list of enabled SSL / TLS cipher suites
*
* @return the list of cipher suites
*/
public String[] getSSLCipherSuites() {
return sslCipherSuites;
}

/**
* Filter the list of protocols and ciphers to those supported by the given SSLContext
*
* @param sslContext the SSLContext to pull information from
*/
public void updateHTTPSSettingsFromContext(SSLContext sslContext) {
SSLParameters parameters = sslContext.getSupportedSSLParameters();

// Protocols
if (sslProtocols == null) {
sslProtocols = parameters.getProtocols();
} else {
List<String> supportedProtocols = Arrays.asList(parameters.getProtocols());
List<String> sslProtocolsList = new ArrayList<String>(Arrays.asList(sslProtocols));

Iterator<String> pit = sslProtocolsList.iterator();
while (pit.hasNext()) {
String protocol = pit.next();
if (!supportedProtocols.contains(protocol)) {
System.out.println("Jolokia: Discarding unsupported protocol: " + protocol);
pit.remove();
}
}
sslProtocols = sslProtocolsList.toArray(new String[0]);
}

// Cipher Suites
if (sslCipherSuites == null) {
sslCipherSuites = parameters.getCipherSuites();
} else {
List<String> supportedCipherSuites = Arrays.asList(parameters.getCipherSuites());
List<String> sslCipherSuitesList = new ArrayList<String>(Arrays.asList(sslCipherSuites));

Iterator<String> cit = sslCipherSuitesList.iterator();
while (cit.hasNext()) {
String cipher = cit.next();
if (!supportedCipherSuites.contains(cipher)) {
System.out.println("Jolokia: Discarding unsupported cipher suite: " + cipher);
cit.remove();
}
}
sslCipherSuites = sslCipherSuitesList.toArray(new String[0]);
}
}

// Initialise and validate early in order to fail fast in case of an configuration error
protected void initConfigAndValidate(Map<String,String> agentConfig) {
initContext();
Expand Down Expand Up @@ -401,9 +475,18 @@ private void initHttpsRelatedSettings(Map<String, String> agentConfig) {
keystorePassword = password != null ? decipherPasswordIfNecessary(password) : new char[0];

serverKeyAlgorithm = agentConfig.get("serverKeyAlgorithm");
clientPrincipals = extractList(agentConfig,"clientPrincipal");
clientPrincipals = extractList(agentConfig, "clientPrincipal");
String xCheck = agentConfig.get("extendedClientCheck");
extendedClientCheck = xCheck != null && Boolean.valueOf(xCheck);

List<String> sslProtocolsList = extractList(agentConfig, "sslProtocol");
if (sslProtocolsList != null) {
sslProtocols = sslProtocolsList.toArray(new String[0]);
}
List<String> sslCipherSuitesList = extractList(agentConfig, "sslCipherSuite");
if (sslCipherSuitesList != null) {
sslCipherSuites = sslCipherSuitesList.toArray(new String[0]);
}
}

private char[] decipherPasswordIfNecessary(String password) {
Expand Down
4 changes: 2 additions & 2 deletions agent/jvm/src/main/java/org/jolokia/jvmagent/JvmAgent.java
Expand Up @@ -64,7 +64,7 @@ private JvmAgent() {}
* @param agentArgs arguments as given on the command line
*/
public static void premain(String agentArgs) {
startAgent(new JvmAgentConfig(agentArgs),true /* register and detect lazy */);
startAgent(new JvmAgentConfig(agentArgs), true /* register and detect lazy */);
}

/**
Expand All @@ -82,7 +82,7 @@ public static void agentmain(String agentArgs) {
}
}

private static void startAgent(JvmAgentConfig pConfig,boolean pLazy) {
private static void startAgent(JvmAgentConfig pConfig, boolean pLazy) {
try {
server = new JolokiaServer(pConfig,pLazy);

Expand Down
Expand Up @@ -100,9 +100,12 @@ static void printUsage() {
" --serverKey <path> Path to a PEM encoded server key file (https only)\n" +
" --serverKeyAlgorithm <algo> Algorithm to use for decrypting the server key (https only, default: RSA)\n" +
" --clientPrincipal <principal> Allow only this principal in the client cert (https & sslClientAuth only)\n" +
" If supplied multiple times, any one of the clientPrincipals must match\n" +
" --extendedClientCheck <t|f> Additional validation of client certs for the proper key usage (https & sslClientAuth only)\n" +
" --discoveryEnabled <t|f> Enable/Disable discovery multicast responses (default: true)\n" +
" --discoveryAgentUrl <url> The URL to use for answering discovery requests. Will be autodetected if not given.\n" +
" --sslProtocol <protocol> SSL / TLS protocol to enable, can be provided multiple times\n" +
" --sslCipherSuite <suite> SSL / TLS cipher suite to enable, can be provided multiple times\n" +
" --debug Switch on agent debugging\n" +
" --debugMaxEntries <nr> Number of debug entries to keep in memory which can be fetched from the Jolokia MBean\n" +
" --maxDepth <depth> Maximum number of levels for serialization of beans\n" +
Expand All @@ -128,7 +131,7 @@ static void printUsage() {
"is printed\n" +
"\n" +
"There are several possible reasons, why attaching to a process can fail:\n" +
" * The UID of this launcher must be the very *same*as the process to attach too. It not sufficient to be root.\n" +
" * The UID of this launcher must be the very *same* as the process to attach too. It not sufficient to be root.\n" +
" * The JVM must have HotSpot enabled and be a JVM 1.6 or larger.\n" +
" * It must be a Java process ;-)\n" +
"\n" +
Expand Down
Expand Up @@ -47,6 +47,7 @@ public final class OptionsAndArgs {
"keystore", "keystorePassword", "useSslClientAuthentication!",
"secureSocketProtocol", "keyStoreType", "keyManagerAlgorithm", "trustManagerAlgorithm",
"caCert", "serverCert", "serverKey", "serverKeyAlgorithm", "clientPrincipal", "extractClientCheck",
"sslProtocol", "sslCipherSuite",
// Jolokia options:
"historyMaxEntries", "debug!", "debugMaxEntries",
"dispatcherClasses", "maxDepth", "maxCollectionSize",
Expand All @@ -57,7 +58,7 @@ public final class OptionsAndArgs {
"config", "help!"));

private static final Set<String> LIST_OPTIONS = new HashSet<String>(Arrays.asList(
"clientPrincipal"));
"clientPrincipal", "sslProtocol", "sslCipherSuite"));

static {
String shortOptsDef[] = {
Expand Down

0 comments on commit cccb2da

Please sign in to comment.