Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Implement mutual auth for the REST endpoints #267

Closed
Closed
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
9 changes: 7 additions & 2 deletions pa_config/performance-analyzer.properties
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@ metrics-db-file-prefix-path = /tmp/metricsdb_

https-enabled = false

#Setup the correct path for certificates
# Setup the correct path for server certificates
certificate-file-path = specify_path

private-key-file-path = specify_path
trusted-cas-file-path = specify_path

# Setup the correct path for client certificates (by default, the client will just use the server certificates)
#client-certificate-file-path = specify_path
#client-private-key-file-path = specify_path
#client-trusted-cas-file-path = specify_path

# WebService bind host; default only to local interface
#webservice-bind-host =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import javax.annotation.Nullable;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
Expand All @@ -31,12 +37,18 @@

public class CertificateUtils {

public static final String ALIAS_PRIVATE = "private";
public static final String ALIAS_IDENTITY = "identity";
public static final String ALIAS_CERT = "cert";
// The password is not used to encrypt keys on disk.
public static final String IN_MEMORY_PWD = "opendistro";
private static final String CERTIFICATE_FILE_PATH = "certificate-file-path";
private static final String PRIVATE_KEY_FILE_PATH = "private-key-file-path";
public static final String CERTIFICATE_FILE_PATH = "certificate-file-path";
public static final String PRIVATE_KEY_FILE_PATH = "private-key-file-path";
public static final String TRUSTED_CAS_FILE_PATH = "trusted-cas-file-path";
public static final String CLIENT_PREFIX = "client-";
public static final String CLIENT_CERTIFICATE_FILE_PATH = CLIENT_PREFIX + CERTIFICATE_FILE_PATH;
public static final String CLIENT_PRIVATE_KEY_FILE_PATH = CLIENT_PREFIX + PRIVATE_KEY_FILE_PATH;
public static final String CLIENT_TRUSTED_CAS_FILE_PATH = CLIENT_PREFIX + TRUSTED_CAS_FILE_PATH;

private static final Logger LOGGER = LogManager.getLogger(CertificateUtils.class);

public static Certificate getCertificate(final FileReader certReader) throws Exception {
Expand All @@ -58,14 +70,46 @@ public static PrivateKey getPrivateKey(final FileReader keyReader) throws Except
public static KeyStore createKeyStore() throws Exception {
String certFilePath = PluginSettings.instance().getSettingValue(CERTIFICATE_FILE_PATH);
String keyFilePath = PluginSettings.instance().getSettingValue(PRIVATE_KEY_FILE_PATH);
KeyStore.ProtectionParameter protParam = new KeyStore.PasswordProtection(
CertificateUtils.IN_MEMORY_PWD.toCharArray());
PrivateKey pk = getPrivateKey(new FileReader(keyFilePath));
KeyStore ks = createEmptyStore();
Certificate certificate = getCertificate(new FileReader(certFilePath));
ks.setCertificateEntry(ALIAS_CERT, certificate);
ks.setKeyEntry(ALIAS_PRIVATE, pk, IN_MEMORY_PWD.toCharArray(), new Certificate[] {certificate});
ks.setEntry(ALIAS_IDENTITY, new KeyStore.PrivateKeyEntry(pk, new Certificate[]{certificate}), protParam);
return ks;
}

public static TrustManager[] getTrustManagers(boolean forServer) throws Exception {
// If a certificate authority is specified, create an authenticating trust manager
String certificateAuthority;
if (forServer) {
certificateAuthority = PluginSettings.instance().getSettingValue(TRUSTED_CAS_FILE_PATH);
} else {
certificateAuthority = PluginSettings.instance().getSettingValue(CLIENT_TRUSTED_CAS_FILE_PATH);
}
if (certificateAuthority != null && !certificateAuthority.isEmpty()) {
KeyStore ks = createEmptyStore();
Certificate certificate = getCertificate(new FileReader(certificateAuthority));
ks.setCertificateEntry(ALIAS_CERT, certificate);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
return tmf.getTrustManagers();
}
// Otherwise, return an all-trusting TrustManager
return new TrustManager[] {
new X509TrustManager() {

public X509Certificate[] getAcceptedIssuers() {
return null;
}

public void checkClientTrusted(X509Certificate[] certs, String authType) {}

public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}
};
}

public static KeyStore createEmptyStore() throws Exception {
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(null, IN_MEMORY_PWD.toCharArray());
Expand All @@ -81,4 +125,39 @@ public static File getPrivateKeyFile() {
String privateKeyPath = PluginSettings.instance().getSettingValue(PRIVATE_KEY_FILE_PATH);
return new File(privateKeyPath);
}

@Nullable
public static File getTrustedCasFile() {
String trustedCasPath = PluginSettings.instance().getSettingValue(TRUSTED_CAS_FILE_PATH);
if (trustedCasPath == null || trustedCasPath.isEmpty()) {
return null;
}
return new File(trustedCasPath);
}

public static File getClientCertificateFile() {
String certFilePath = PluginSettings.instance().getSettingValue(CLIENT_CERTIFICATE_FILE_PATH);
if (certFilePath == null || certFilePath.isEmpty()) {
return getCertificateFile();
}
return new File(certFilePath);
}

public static File getClientPrivateKeyFile() {
String privateKeyPath = PluginSettings.instance().getSettingValue(CLIENT_PRIVATE_KEY_FILE_PATH);
if (privateKeyPath == null || privateKeyPath.isEmpty()) {
return getPrivateKeyFile();
}
return new File(privateKeyPath);
}

@Nullable
public static File getClientTrustedCasFile() {
String trustedCasPath = PluginSettings.instance().getSettingValue(CLIENT_TRUSTED_CAS_FILE_PATH);
// By default, use the same CA as the server
if (trustedCasPath == null || trustedCasPath.isEmpty()) {
return getTrustedCasFile();
}
return new File(trustedCasPath);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,25 @@
package com.amazon.opendistro.elasticsearch.performanceanalyzer;

import com.amazon.opendistro.elasticsearch.performanceanalyzer.config.PluginSettings;
import com.google.common.annotations.VisibleForTesting;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsParameters;
import com.sun.net.httpserver.HttpsServer;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.concurrent.Executors;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
Expand All @@ -40,11 +43,15 @@ public class PerformanceAnalyzerWebServer {

private static final Logger LOG = LogManager.getLogger(PerformanceAnalyzerWebServer.class);
private static final int WEBSERVICE_DEFAULT_PORT = 9600;
private static final String WEBSERVICE_PORT_CONF_NAME = "webservice-listener-port";
private static final String WEBSERVICE_BIND_HOST_NAME = "webservice-bind-host";
// Use system default for max backlog.
private static final int INCOMING_QUEUE_LENGTH = 1;

@VisibleForTesting
public static final String WEBSERVICE_BIND_HOST_NAME = "webservice-bind-host";
@VisibleForTesting
public static final String WEBSERVICE_PORT_CONF_NAME = "webservice-listener-port";


public static HttpServer createInternalServer(PluginSettings settings) {
int internalPort = getPortNumber();
try {
Expand All @@ -67,8 +74,34 @@ public static HttpServer createInternalServer(PluginSettings settings) {
return null;
}

/**
* ClientAuthConfigurator makes the server perform client authentication if the user has set up a
* certificate authority
*/
private static class ClientAuthConfigurator extends HttpsConfigurator {
public ClientAuthConfigurator(SSLContext sslContext) {
super(sslContext);
}

@Override
public void configure(HttpsParameters params) {
final SSLParameters sslParams = getSSLContext().getDefaultSSLParameters();
if (CertificateUtils.getTrustedCasFile() != null) {
LOG.debug("Enabling client auth");
final SSLEngine sslEngine = getSSLContext().createSSLEngine();
sslParams.setNeedClientAuth(true);
sslParams.setCipherSuites(sslEngine.getEnabledCipherSuites());
sslParams.setProtocols(sslEngine.getEnabledProtocols());
params.setSSLParameters(sslParams);
} else {
LOG.debug("Not enabling client auth");
super.configure(params);
}
}
}

private static HttpServer createHttpsServer(int readerPort) throws Exception {
HttpsServer server = null;
HttpsServer server;
String bindHost = getBindHost();
if (bindHost != null && !bindHost.trim().isEmpty()) {
LOG.info("Binding to Interface: {}", bindHost);
Expand All @@ -83,38 +116,30 @@ private static HttpServer createHttpsServer(int readerPort) throws Exception {
server = HttpsServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), readerPort), INCOMING_QUEUE_LENGTH);
}

TrustManager[] trustAllCerts =
new TrustManager[] {
new X509TrustManager() {

public X509Certificate[] getAcceptedIssuers() {
return null;
}

public void checkClientTrusted(X509Certificate[] certs, String authType) {}

public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}
};

HostnameVerifier allHostsValid =
new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};

// Install the all-trusting trust manager
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");

KeyStore ks = CertificateUtils.createKeyStore();
KeyManagerFactory kmf = KeyManagerFactory.getInstance("NewSunX509");
kmf.init(ks, CertificateUtils.IN_MEMORY_PWD.toCharArray());
sslContext.init(kmf.getKeyManagers(), trustAllCerts, null);
sslContext.init(kmf.getKeyManagers(), CertificateUtils.getTrustManagers(true), null);
server.setHttpsConfigurator(new ClientAuthConfigurator(sslContext));


// TODO ask ktkrg why this is necessary
// Try to set HttpsURLConnection defaults, our webserver can still run even if this block fails
try {
LOG.debug("Setting default SSLSocketFactory...");
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
LOG.debug("Default SSLSocketFactory set successfully");
HostnameVerifier allHostsValid = (hostname, session) -> true;
LOG.debug("Setting default HostnameVerifier...");
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
LOG.debug("Default HostnameVerifier set successfully");
} catch (Exception e) { // Usually AccessControlException
LOG.warn("Exception while trying to set URLConnection defaults", e);
}

HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
server.setHttpsConfigurator(new HttpsConfigurator(sslContext));
return server;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@

import com.amazon.opendistro.elasticsearch.performanceanalyzer.ConfigStatus;
import com.amazon.opendistro.elasticsearch.performanceanalyzer.core.Util;
import com.google.common.annotations.VisibleForTesting;

import java.io.File;
import java.util.Properties;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
Expand Down Expand Up @@ -100,6 +103,16 @@ public boolean getHttpsEnabled() {
return this.httpsEnabled;
}

@VisibleForTesting
public void setHttpsEnabled(boolean httpsEnabled) {
this.httpsEnabled = httpsEnabled;
}

@VisibleForTesting
public void overrideProperty(String key, String value) {
settings.setProperty(key, value);
}

public boolean shouldCleanupMetricsDBFiles() {
return shouldCleanupMetricsDBFiles;
}
Expand Down
Loading