diff options
author | 2018-09-26 11:58:30 +0100 | |
---|---|---|
committer | 2018-09-26 12:02:22 +0100 | |
commit | 392d41cdfc989d08cf5b79ea9a20e1f82665b447 (patch) | |
tree | 146df533a232f12705ef0c59ab1bd10cabdb7b9d /src/main/java | |
parent | ecd35f117eb0118f9cbd96fe132a1c359214bfc7 (diff) |
Implement client authentication to ElasticSearch
Add configuration to the existing Elastic Search properties to allow
Basic Authentication and/or TLS (SSL) connectivity using HTTPS.
The new configuration is optional and this commit is intended to be
backwards compatible with existing deployments.
Change-Id: I19ec3da9ff810c3f6eabd6f5faf71adde182c861
Issue-ID: AAI-1650
Signed-off-by: mark.j.leonard <mark.j.leonard@gmail.com>
Diffstat (limited to 'src/main/java')
3 files changed, 285 insertions, 6 deletions
diff --git a/src/main/java/org/onap/aai/sa/searchdbabstraction/elasticsearch/config/ElasticSearchConfig.java b/src/main/java/org/onap/aai/sa/searchdbabstraction/elasticsearch/config/ElasticSearchConfig.java index 1bf1db7..f5cb9da 100644 --- a/src/main/java/org/onap/aai/sa/searchdbabstraction/elasticsearch/config/ElasticSearchConfig.java +++ b/src/main/java/org/onap/aai/sa/searchdbabstraction/elasticsearch/config/ElasticSearchConfig.java @@ -20,9 +20,22 @@ */ package org.onap.aai.sa.searchdbabstraction.elasticsearch.config; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Optional; import java.util.Properties; +import org.eclipse.jetty.util.security.Password; +import org.onap.aai.sa.searchdbabstraction.util.SearchDbConstants; public class ElasticSearchConfig { + + private String uriScheme; + private String trustStore; + private String trustStorePassword; + private String keyStore; + private String keyStorePassword; + private String authUser; + private String authPassword; private String ipAddress; private String httpPort; private String javaApiPort; @@ -31,15 +44,33 @@ public class ElasticSearchConfig { public static final String ES_CLUSTER_NAME = "es.cluster-name"; public static final String ES_IP_ADDRESS = "es.ip-address"; public static final String ES_HTTP_PORT = "es.http-port"; - + public static final String ES_URI_SCHEME = "es.uri-scheme"; + public static final String ES_TRUST_STORE = "es.trust-store"; + public static final String ES_TRUST_STORE_ENC = "es.trust-store-password"; + public static final String ES_KEY_STORE = "es.key-store"; + public static final String ES_KEY_STORE_ENC = "es.key-store-password"; + public static final String ES_AUTH_USER = "es.auth-user"; + public static final String ES_AUTH_ENC = "es.auth-password"; + + private static final String DEFAULT_URI_SCHEME = "http"; private static final String JAVA_API_PORT_DEFAULT = "9300"; + private String authValue; public ElasticSearchConfig(Properties props) { - + setUriScheme(props.getProperty(ES_URI_SCHEME)); + if (getUriScheme().equals("https")) { + initializeHttpsProperties(props); + } setClusterName(props.getProperty(ES_CLUSTER_NAME)); setIpAddress(props.getProperty(ES_IP_ADDRESS)); setHttpPort(props.getProperty(ES_HTTP_PORT)); setJavaApiPort(JAVA_API_PORT_DEFAULT); + initializeAuthValues(props); + } + + + public String getUriScheme() { + return this.uriScheme; } public String getIpAddress() { @@ -74,10 +105,105 @@ public class ElasticSearchConfig { this.clusterName = clusterName; } + public void setKeyStore(String keyStore) { + this.keyStore = keyStore; + } + + public void setKeyStorePassword(String keyStorePassword) { + this.keyStorePassword = keyStorePassword; + } + + public String getKeyStorePath() { + return keyStore; + } + + public String getKeyStorePassword() { + return keyStorePassword; + } + + public String getTrustStorePath() { + return trustStore; + } + + public void setTrustStore(String trustStore) { + this.trustStore = trustStore; + } + + public void setTrustStorePassword(String trustStorePassword) { + this.trustStorePassword = trustStorePassword; + } + + public String getTrustStorePassword() { + return trustStorePassword; + } + + public void setAuthUser(String authUser) { + this.authUser = authUser; + } + + public String getAuthUser() { + return authUser; + } + + public void setAuthPassword(String authPassword) { + this.authPassword = authPassword; + } + + public String getAuthPassword() { + return authPassword; + } + + public boolean useAuth() { + return getAuthUser() != null || getAuthPassword() != null; + } + + public String getAuthValue() { + return authValue; + } + @Override public String toString() { - return "ElasticSearchConfig [ipAddress=" + ipAddress + ", httpPort=" + httpPort + ", javaApiPort=" + javaApiPort - + ", clusterName=" + clusterName + "]"; + return String.format( + "%s://%s:%s (cluster=%s) (API port=%s)%nauth=%s%ntrustStore=%s (passwd %s)%nkeyStore=%s (passwd %s)", + uriScheme, ipAddress, httpPort, clusterName, javaApiPort, useAuth(), trustStore, + trustStorePassword != null, keyStore, keyStorePassword != null); } + private void initializeAuthValues(Properties props) { + setAuthUser(props.getProperty(ES_AUTH_USER)); + Optional<String> passwordValue = Optional.ofNullable(props.getProperty(ES_AUTH_ENC)); + if (passwordValue.isPresent()) { + setAuthPassword(Password.deobfuscate(passwordValue.get())); + } + if (useAuth()) { + authValue = "Basic " + Base64.getEncoder() + .encodeToString((getAuthUser() + ":" + getAuthPassword()).getBytes(StandardCharsets.UTF_8)); + } + } + + private void initializeHttpsProperties(Properties props) { + Optional<String> trustStoreFile = Optional.ofNullable(props.getProperty(ES_TRUST_STORE)); + if (trustStoreFile.isPresent()) { + setTrustStore(SearchDbConstants.SDB_SPECIFIC_CONFIG + trustStoreFile.get()); + } + + Optional<String> passwordValue = Optional.ofNullable(props.getProperty(ES_TRUST_STORE_ENC)); + if (passwordValue.isPresent()) { + setTrustStorePassword(Password.deobfuscate(passwordValue.get())); + } + + Optional<String> keyStoreFile = Optional.ofNullable(props.getProperty(ES_KEY_STORE)); + if (keyStoreFile.isPresent()) { + setKeyStore(SearchDbConstants.SDB_SPECIFIC_CONFIG + keyStoreFile.get()); + } + + passwordValue = Optional.ofNullable(props.getProperty(ES_KEY_STORE_ENC)); + if (passwordValue.isPresent()) { + setKeyStorePassword(Password.deobfuscate(passwordValue.get())); + } + } + + private void setUriScheme(String uriScheme) { + this.uriScheme = Optional.ofNullable(uriScheme).orElse(DEFAULT_URI_SCHEME); + } } diff --git a/src/main/java/org/onap/aai/sa/searchdbabstraction/elasticsearch/dao/ElasticSearchHttpController.java b/src/main/java/org/onap/aai/sa/searchdbabstraction/elasticsearch/dao/ElasticSearchHttpController.java index 6d08c1d..6087488 100644 --- a/src/main/java/org/onap/aai/sa/searchdbabstraction/elasticsearch/dao/ElasticSearchHttpController.java +++ b/src/main/java/org/onap/aai/sa/searchdbabstraction/elasticsearch/dao/ElasticSearchHttpController.java @@ -132,13 +132,15 @@ public class ElasticSearchHttpController implements DocumentStoreInterface { protected AnalysisConfiguration analysisConfig; - public ElasticSearchHttpController(ElasticSearchConfig config) { this.config = config; analysisConfig = new AnalysisConfiguration(); String rootUrl = null; try { + if ("https".equals(config.getUriScheme())) { + new ElasticSearchHttpsController(config); + } rootUrl = buildUrl(createUriBuilder("")).toString(); logger.info(SearchDbMsgs.ELASTIC_SEARCH_CONNECTION_ATTEMPT, rootUrl); checkConnection(); @@ -728,6 +730,9 @@ public class ElasticSearchHttpController implements DocumentStoreInterface { conn = (HttpURLConnection) url.openConnection(); conn.setRequestProperty(CONTENT_TYPE, APPLICATION_JSON); conn.setDoOutput(true); + if (config.useAuth()) { + conn.setRequestProperty("Authorization", config.getAuthValue()); + } } catch (IOException e) { shutdownConnection(conn); throw new DocumentStoreOperationException("Failed to open connection to URL " + url, e); @@ -849,7 +854,7 @@ public class ElasticSearchHttpController implements DocumentStoreInterface { builder.host(config.getIpAddress()); String port = Optional.ofNullable(config.getHttpPort()).orElse("0"); builder.port(Integer.valueOf(port)); - builder.scheme("http"); + builder.scheme(config.getUriScheme()); return builder; } diff --git a/src/main/java/org/onap/aai/sa/searchdbabstraction/elasticsearch/dao/ElasticSearchHttpsController.java b/src/main/java/org/onap/aai/sa/searchdbabstraction/elasticsearch/dao/ElasticSearchHttpsController.java new file mode 100644 index 0000000..51b8952 --- /dev/null +++ b/src/main/java/org/onap/aai/sa/searchdbabstraction/elasticsearch/dao/ElasticSearchHttpsController.java @@ -0,0 +1,148 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017-2018 Amdocs + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ +package org.onap.aai.sa.searchdbabstraction.elasticsearch.dao; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.sa.searchdbabstraction.elasticsearch.config.ElasticSearchConfig; + +/** + * HTTPS (TLS) specific configuration. + */ +public class ElasticSearchHttpsController { + + private static final Logger logger = + LoggerFactory.getInstance().getLogger(ElasticSearchHttpsController.class.getName()); + + private static final String SSL_PROTOCOL = "TLS"; + private static final String KEYSTORE_ALGORITHM = "SunX509"; + private static final String KEYSTORE_TYPE = "PKCS12"; + + public ElasticSearchHttpsController(ElasticSearchConfig config) throws NoSuchAlgorithmException, KeyStoreException, + CertificateException, IOException, KeyManagementException, UnrecoverableKeyException { + logger.debug("Initialising HTTPS configuration"); + + SSLContext ctx = SSLContext.getInstance(SSL_PROTOCOL); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEYSTORE_ALGORITHM); + KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE); + + String clientCertPassword = config.getKeyStorePassword(); + + char[] pwd = null; + if (clientCertPassword != null) { + pwd = clientCertPassword.toCharArray(); + } else { + logger.debug("No key store password is defined"); + } + + TrustManager[] trustManagers = getTrustManagers(config); + KeyManager[] keyManagers = null; + + String clientCertFileName = config.getKeyStorePath(); + if (clientCertFileName != null) { + InputStream fin = Files.newInputStream(Paths.get(clientCertFileName)); + keyStore.load(fin, pwd); + kmf.init(keyStore, pwd); + keyManagers = kmf.getKeyManagers(); + } + + ctx.init(keyManagers, trustManagers, null); + logger.debug("Initialised SSL context"); + + HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory()); + HttpsURLConnection.setDefaultHostnameVerifier((host, session) -> host.equalsIgnoreCase(session.getPeerHost())); + } + + private TrustManager[] getTrustManagers(ElasticSearchConfig config) + throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException { + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + // Using null here initializes the TMF with the default trust store. + tmf.init((KeyStore) null); + + // Find the default trust manager. + final X509TrustManager defaultTrustManager = findX509TrustManager(tmf); + + String trustStoreFile = config.getTrustStorePath(); + if (trustStoreFile == null) { + logger.debug("No trust store defined"); + return new TrustManager[] {defaultTrustManager}; + } + + // Create a new Trust Manager from the local trust store. + try (InputStream myKeys = Files.newInputStream(Paths.get(trustStoreFile))) { + KeyStore myTrustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + char[] pwdArray = null; + if (config.getTrustStorePassword() != null) { + pwdArray = config.getTrustStorePassword().toCharArray(); + } + myTrustStore.load(myKeys, pwdArray); + tmf.init(myTrustStore); + } + + // Create a custom trust manager that wraps both our trust store and the default. + final X509TrustManager finalLocalTm = findX509TrustManager(tmf); + + return new TrustManager[] {new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return defaultTrustManager.getAcceptedIssuers(); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + try { + finalLocalTm.checkServerTrusted(chain, authType); + } catch (CertificateException e) { + defaultTrustManager.checkServerTrusted(chain, authType); + } + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + defaultTrustManager.checkClientTrusted(chain, authType); + } + }}; + } + + private X509TrustManager findX509TrustManager(TrustManagerFactory tmf) { + return (X509TrustManager) Arrays.asList(tmf.getTrustManagers()).stream() + .filter(tm -> tm instanceof X509TrustManager).findFirst().orElse(null); + } +} |