diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/XPackPlugin.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/XPackPlugin.java index 6316c164243..3b8830a96cc 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/XPackPlugin.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/XPackPlugin.java @@ -5,10 +5,12 @@ */ package org.elasticsearch.xpack; +import org.bouncycastle.operator.OperatorCreationException; import org.elasticsearch.SpecialPermission; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.support.ActionFilter; +import org.elasticsearch.bootstrap.BootstrapCheck; import org.elasticsearch.client.Client; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; @@ -89,15 +91,21 @@ import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.SecurityFeatureSet; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; +import org.elasticsearch.xpack.ssl.SSLBootstrapCheck; import org.elasticsearch.xpack.ssl.SSLConfigurationReloader; import org.elasticsearch.xpack.ssl.SSLService; import org.elasticsearch.xpack.watcher.Watcher; import org.elasticsearch.xpack.watcher.WatcherFeatureSet; +import javax.security.auth.DestroyFailedException; import java.io.IOException; import java.nio.file.Path; import java.security.AccessController; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.security.PrivilegedAction; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; import java.time.Clock; import java.util.ArrayList; import java.util.Collection; @@ -176,7 +184,8 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I protected Watcher watcher; protected Graph graph; - public XPackPlugin(Settings settings) throws IOException { + public XPackPlugin(Settings settings) throws IOException, CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, + KeyStoreException, DestroyFailedException, OperatorCreationException { this.settings = settings; this.transportClientMode = transportClientMode(settings); this.env = transportClientMode ? null : new Environment(settings); @@ -498,4 +507,9 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I public UnaryOperator getRestHandlerWrapper(ThreadContext threadContext) { return security.getRestHandlerWrapper(threadContext); } + + @Override + public List getBootstrapChecks() { + return security.getBootstrapChecks(); + } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/XPackSettings.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/XPackSettings.java index 05fa47e12b5..e8888035c8a 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/XPackSettings.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/XPackSettings.java @@ -5,7 +5,9 @@ */ package org.elasticsearch.xpack; +import org.elasticsearch.common.Booleans; import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.ssl.SSLClientAuth; @@ -45,8 +47,20 @@ public class XPackSettings { /** Setting for enabling or disabling document/field level security. Defaults to true. */ public static final Setting DLS_FLS_ENABLED = enabledSetting(XPackPlugin.SECURITY + ".dls_fls", true); - /** Setting for enabling or disabling transport ssl. Defaults to false. */ - public static final Setting TRANSPORT_SSL_ENABLED = enabledSetting(XPackPlugin.SECURITY + ".transport.ssl", false); + /** + * Legacy setting for enabling or disabling transport ssl. Defaults to true. This is just here to make upgrading easier since the + * user needs to set this setting in 5.x to upgrade + */ + private static final Setting TRANSPORT_SSL_ENABLED = + new Setting<>(XPackPlugin.featureSettingPrefix(XPackPlugin.SECURITY) + ".transport.ssl.enabled", (s) -> Boolean.toString(true), + (s) -> { + final boolean parsed = Booleans.parseBoolean(s); + if (parsed == false) { + throw new IllegalArgumentException("transport ssl cannot be disabled. Remove setting [" + + XPackPlugin.featureSettingPrefix(XPackPlugin.SECURITY) + ".transport.ssl.enabled]"); + } + return true; + }, Property.NodeScope, Property.Deprecated); /** Setting for enabling or disabling http ssl. Defaults to false. */ public static final Setting HTTP_SSL_ENABLED = enabledSetting(XPackPlugin.SECURITY + ".http.ssl", false); @@ -86,6 +100,7 @@ public class XPackSettings { ALL_SETTINGS.addAll(GLOBAL_SSL.getAllSettings()); ALL_SETTINGS.addAll(HTTP_SSL.getAllSettings()); ALL_SETTINGS.addAll(TRANSPORT_SSL.getAllSettings()); + ALL_SETTINGS.add(TRANSPORT_SSL_ENABLED); } /** diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/Security.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/Security.java index 0e367bbde25..8f7e68d24c4 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -11,6 +11,7 @@ import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.support.ActionFilter; import org.elasticsearch.action.support.DestructiveOperations; +import org.elasticsearch.bootstrap.BootstrapCheck; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.service.ClusterService; @@ -125,6 +126,7 @@ import org.elasticsearch.xpack.security.transport.filter.IPFilter; import org.elasticsearch.xpack.security.transport.netty4.SecurityNetty4HttpServerTransport; import org.elasticsearch.xpack.security.transport.netty4.SecurityNetty4Transport; import org.elasticsearch.xpack.security.user.AnonymousUser; +import org.elasticsearch.xpack.ssl.SSLBootstrapCheck; import org.elasticsearch.xpack.ssl.SSLService; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -448,6 +450,14 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin { return settingsFilter; } + public List getBootstrapChecks() { + if (enabled) { + return Collections.singletonList(new SSLBootstrapCheck(sslService, settings, env)); + } else { + return Collections.emptyList(); + } + } + public void onIndexModule(IndexModule module) { if (enabled == false) { return; diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/SecurityFeatureSet.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/SecurityFeatureSet.java index aa593e91237..8ba96c5c166 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/SecurityFeatureSet.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/SecurityFeatureSet.java @@ -7,7 +7,6 @@ package org.elasticsearch.xpack.security; import java.io.IOException; import java.util.Collections; -import java.util.HashMap; import java.util.Map; import org.elasticsearch.common.Nullable; @@ -28,7 +27,6 @@ import org.elasticsearch.xpack.security.transport.filter.IPFilter; import org.elasticsearch.xpack.security.user.AnonymousUser; import static org.elasticsearch.xpack.XPackSettings.HTTP_SSL_ENABLED; -import static org.elasticsearch.xpack.XPackSettings.TRANSPORT_SSL_ENABLED; /** * Indicates whether the features of Security are currently in use @@ -106,10 +104,7 @@ public class SecurityFeatureSet implements XPackFeatureSet { } static Map sslUsage(Settings settings) { - Map map = new HashMap<>(2); - map.put("http", Collections.singletonMap("enabled", HTTP_SSL_ENABLED.get(settings))); - map.put("transport", Collections.singletonMap("enabled", TRANSPORT_SSL_ENABLED.get(settings))); - return map; + return Collections.singletonMap("http", Collections.singletonMap("enabled", HTTP_SSL_ENABLED.get(settings))); } static Map auditUsage(@Nullable AuditTrailService auditTrailService) { diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/user/ChangePasswordRequestBuilder.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/user/ChangePasswordRequestBuilder.java index a18f8b8c00a..cc812ca263c 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/user/ChangePasswordRequestBuilder.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/user/ChangePasswordRequestBuilder.java @@ -22,7 +22,7 @@ import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.common.xcontent.XContentUtils; import java.io.IOException; -import java.util.Arrays; +import java.nio.CharBuffer; /** * Request to change a user's password. @@ -44,15 +44,17 @@ public class ChangePasswordRequestBuilder return this; } + /** + * Sets the password. Note: the char[] passed to this method will be cleared. + */ public ChangePasswordRequestBuilder password(char[] password) { - Validation.Error error = Validation.Users.validatePassword(password); - if (error != null) { - ValidationException validationException = new ValidationException(); - validationException.addValidationError(error.toString()); - throw validationException; - } - try (SecuredString securedString = new SecuredString(password)) { + Validation.Error error = Validation.Users.validatePassword(password); + if (error != null) { + ValidationException validationException = new ValidationException(); + validationException.addValidationError(error.toString()); + throw validationException; + } request.passwordHash(Hasher.BCRYPT.hash(securedString)); } return this; @@ -82,10 +84,10 @@ public class ChangePasswordRequestBuilder } else if (User.Fields.PASSWORD.match(currentFieldName)) { if (token == XContentParser.Token.VALUE_STRING) { String password = parser.text(); - char[] passwordChars = password.toCharArray(); + final char[] passwordChars = password.toCharArray(); password(passwordChars); - password = null; - Arrays.fill(passwordChars, (char) 0); + assert CharBuffer.wrap(passwordChars).chars().noneMatch((i) -> (char) i != (char) 0) : "expected password to " + + "clear the char[] but it did not!"; } else { throw new ElasticsearchParseException( "expected field [{}] to be of type string, but found [{}] instead", currentFieldName, token); diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrail.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrail.java index be01a21e017..0189f79c57f 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrail.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrail.java @@ -42,6 +42,7 @@ import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.node.Node; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportMessage; @@ -777,7 +778,7 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl return queueConsumer.peek(); } - private static Client initializeRemoteClient(Settings settings, Logger logger) { + Client initializeRemoteClient(Settings settings, Logger logger) { Settings clientSettings = REMOTE_CLIENT_SETTINGS.get(settings); String[] hosts = clientSettings.getAsArray("hosts"); if (hosts.length == 0) { @@ -807,7 +808,7 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl final Settings theClientSetting = clientSettings.filter((s) -> s.startsWith("hosts") == false); // hosts is not a valid setting final TransportClient transportClient = new TransportClient(Settings.builder() .put("node.name", DEFAULT_CLIENT_NAME + "-" + Node.NODE_NAME_SETTING.get(settings)) - .put(theClientSetting).build(), Settings.EMPTY, Collections.singletonList(XPackPlugin.class), null) {}; + .put(theClientSetting).build(), Settings.EMPTY, remoteTransportClientPlugins(), null) {}; for (Tuple pair : hostPortPairs) { try { transportClient.addTransportAddress(new TransportAddress(InetAddress.getByName(pair.v1()), pair.v2())); @@ -965,6 +966,11 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl } } + // method for testing to allow different plugins such as mock transport... + List> remoteTransportClientPlugins() { + return Collections.singletonList(XPackPlugin.class); + } + public static void registerSettings(List> settings) { settings.add(INDEX_SETTINGS); settings.add(EXCLUDE_EVENT_SETTINGS); diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/pki/PkiRealm.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/pki/PkiRealm.java index 5adde48ed5e..1cbc2d23c77 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/pki/PkiRealm.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/pki/PkiRealm.java @@ -41,7 +41,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import static org.elasticsearch.xpack.XPackSettings.HTTP_SSL_ENABLED; -import static org.elasticsearch.xpack.XPackSettings.TRANSPORT_SSL_ENABLED; import static org.elasticsearch.xpack.security.Security.setting; public class PkiRealm extends Realm { @@ -199,6 +198,7 @@ public class PkiRealm extends Realm { * @param config this realm's configuration * @param sslService the SSLService to use for ssl configurations */ + // TODO move this to a Bootstrap check! static void checkSSLEnabled(RealmConfig config, SSLService sslService) { Settings settings = config.globalSettings(); @@ -211,10 +211,9 @@ public class PkiRealm extends Realm { } // Default Transport - final boolean ssl = TRANSPORT_SSL_ENABLED.get(settings); final Settings transportSSLSettings = settings.getByPrefix(setting("transport.ssl.")); final boolean clientAuthEnabled = sslService.isSSLClientAuthEnabled(transportSSLSettings); - if (ssl && clientAuthEnabled) { + if (clientAuthEnabled) { return; } @@ -222,9 +221,7 @@ public class PkiRealm extends Realm { Map groupedSettings = settings.getGroups("transport.profiles."); for (Map.Entry entry : groupedSettings.entrySet()) { Settings profileSettings = entry.getValue().getByPrefix(Security.settingPrefix()); - if (SecurityNetty4Transport.PROFILE_SSL_SETTING.get(profileSettings) - && sslService.isSSLClientAuthEnabled( - SecurityNetty4Transport.profileSslSettings(profileSettings), transportSSLSettings)) { + if (sslService.isSSLClientAuthEnabled(SecurityNetty4Transport.profileSslSettings(profileSettings), transportSSLSettings)) { return; } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/support/SecuredString.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/support/SecuredString.java index 56175f52b7c..4f6220c56b5 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/support/SecuredString.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/support/SecuredString.java @@ -9,6 +9,7 @@ import org.elasticsearch.ElasticsearchException; import java.nio.CharBuffer; import java.util.Arrays; +import java.util.Objects; /** * This is not a string but a CharSequence that can be cleared of its memory. Important for handling passwords. @@ -26,11 +27,11 @@ public class SecuredString implements CharSequence, AutoCloseable { private boolean cleared = false; /** - * Note: the passed in chars are duplicated + * Creates a new SecuredString from the chars. These chars will be cleared when the SecuredString is closed so they should not be + * used directly outside of using the SecuredString */ public SecuredString(char[] chars) { - this.chars = new char[chars.length]; - System.arraycopy(chars, 0, this.chars, 0, chars.length); + this.chars = Objects.requireNonNull(chars, "chars must not be null!"); } /** @@ -94,6 +95,7 @@ public class SecuredString implements CharSequence, AutoCloseable { * @return A copy of the internal characters. May be used for caching. */ public char[] copyChars() { + throwIfCleared(); return Arrays.copyOf(chars, chars.length); } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/client/SecurityClient.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/client/SecurityClient.java index 972d5fa578e..66c53b29bfb 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/client/SecurityClient.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/client/SecurityClient.java @@ -156,6 +156,10 @@ public class SecurityClient { client.execute(PutUserAction.INSTANCE, request, listener); } + /** + * Populates the {@link ChangePasswordRequest} with the username and password. Note: the passed in char[] will be cleared by this + * method. + */ public ChangePasswordRequestBuilder prepareChangePassword(String username, char[] password) { return new ChangePasswordRequestBuilder(client).username(username).password(password); } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java index 37eb0e52727..0417a70de1b 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java @@ -44,7 +44,6 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; -import static org.elasticsearch.xpack.XPackSettings.TRANSPORT_SSL_ENABLED; import static org.elasticsearch.xpack.security.Security.setting; public class SecurityServerTransportInterceptor extends AbstractComponent implements TransportInterceptor { @@ -142,10 +141,8 @@ public class SecurityServerTransportInterceptor extends AbstractComponent implem final Settings transportSSLSettings = settings.getByPrefix(setting("transport.ssl.")); for (Map.Entry entry : profileSettingsMap.entrySet()) { Settings profileSettings = entry.getValue(); - final boolean profileSsl = SecurityNetty4Transport.PROFILE_SSL_SETTING.get(profileSettings); final Settings profileSslSettings = SecurityNetty4Transport.profileSslSettings(profileSettings); - final boolean clientAuth = sslService.isSSLClientAuthEnabled(profileSslSettings, transportSSLSettings); - final boolean extractClientCert = profileSsl && clientAuth; + final boolean extractClientCert = sslService.isSSLClientAuthEnabled(profileSslSettings, transportSSLSettings); String type = entry.getValue().get(SETTING_NAME, "node"); switch (type) { case "client": @@ -161,9 +158,7 @@ public class SecurityServerTransportInterceptor extends AbstractComponent implem } if (!profileFilters.containsKey(TransportSettings.DEFAULT_PROFILE)) { - final boolean profileSsl = TRANSPORT_SSL_ENABLED.get(settings); - final boolean clientAuth = sslService.isSSLClientAuthEnabled(transportSSLSettings); - final boolean extractClientCert = profileSsl && clientAuth; + final boolean extractClientCert = sslService.isSSLClientAuthEnabled(transportSSLSettings); profileFilters.put(TransportSettings.DEFAULT_PROFILE, new ServerTransportFilter.NodeProfile(authcService, authzService, threadPool.getThreadContext(), extractClientCert, destructiveOperations, reservedRealmEnabled, securityContext)); } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransport.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransport.java index c9706771627..24e89cac4c6 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransport.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransport.java @@ -92,7 +92,7 @@ public class SecurityNetty4HttpServerTransport extends Netty4HttpServerTransport HttpSslChannelHandler(Netty4HttpServerTransport transport) { super(transport, detailedErrorsEnabled, threadPool.getThreadContext()); this.sslSettings = SSLService.getHttpTransportSSLSettings(settings); - if (ssl && sslService.isConfigurationValidForServerUsage(sslSettings, Settings.EMPTY) == false) { + if (ssl && sslService.isConfigurationValidForServerUsage(sslSettings, false) == false) { throw new IllegalArgumentException("a key must be provided to run as a server. the key should be configured using the " + "[xpack.security.http.ssl.key] or [xpack.security.http.ssl.keystore.path] setting"); } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4Transport.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4Transport.java index 36f3ec748b8..a22e1a6fb99 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4Transport.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4Transport.java @@ -15,12 +15,10 @@ import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.internal.Nullable; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.network.NetworkService; -import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.transport.TransportSettings; import org.elasticsearch.transport.netty4.Netty4Transport; import org.elasticsearch.xpack.ssl.SSLService; import org.elasticsearch.xpack.security.transport.filter.IPFilter; @@ -31,7 +29,6 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import static org.elasticsearch.xpack.security.Security.setting; -import static org.elasticsearch.xpack.XPackSettings.TRANSPORT_SSL_ENABLED; /** @@ -39,12 +36,9 @@ import static org.elasticsearch.xpack.XPackSettings.TRANSPORT_SSL_ENABLED; */ public class SecurityNetty4Transport extends Netty4Transport { - public static final Setting PROFILE_SSL_SETTING = Setting.boolSetting(setting("ssl.enabled"), false); - private final SSLService sslService; @Nullable private final IPFilter authenticator; private final Settings transportSSLSettings; - private final boolean ssl; @Inject public SecurityNetty4Transport(Settings settings, ThreadPool threadPool, NetworkService networkService, BigArrays bigArrays, @@ -52,7 +46,6 @@ public class SecurityNetty4Transport extends Netty4Transport { @Nullable IPFilter authenticator, SSLService sslService) { super(settings, threadPool, networkService, bigArrays, namedWriteableRegistry, circuitBreakerService); this.authenticator = authenticator; - this.ssl = TRANSPORT_SSL_ENABLED.get(settings); this.sslService = sslService; this.transportSSLSettings = settings.getByPrefix(setting("transport.ssl.")); } @@ -77,32 +70,22 @@ public class SecurityNetty4Transport extends Netty4Transport { class SecurityServerChannelInitializer extends ServerChannelInitializer { - private final boolean sslEnabled; private final Settings securityProfileSettings; SecurityServerChannelInitializer(String name, Settings profileSettings) { super(name, profileSettings); - this.sslEnabled = PROFILE_SSL_SETTING.exists(profileSettings) ? PROFILE_SSL_SETTING.get(profileSettings) : ssl; this.securityProfileSettings = profileSettings.getByPrefix(setting("ssl.")); - if (sslEnabled && sslService.isConfigurationValidForServerUsage(securityProfileSettings, transportSSLSettings) == false) { - if (TransportSettings.DEFAULT_PROFILE.equals(name)) { - throw new IllegalArgumentException("a key must be provided to run as a server. the key should be configured using the " - + "[xpack.security.transport.ssl.key] or [xpack.security.transport.ssl.keystore.path] setting"); - } - throw new IllegalArgumentException("a key must be provided to run as a server. the key should be configured using the " - + "[transport.profiles." + name + ".xpack.security.ssl.key] or [transport.profiles." + name - + ".xpack.security.ssl.keystore.path] setting"); - } + assert sslService.isConfigurationValidForServerUsage(securityProfileSettings, true) : + "the ssl configuration is not valid for server use but we are running as a server. this should have been caught by" + + " the key config validation"; } @Override protected void initChannel(Channel ch) throws Exception { super.initChannel(ch); - if (sslEnabled) { - SSLEngine serverEngine = sslService.createSSLEngine(securityProfileSettings, transportSSLSettings); - serverEngine.setUseClientMode(false); - ch.pipeline().addFirst(new SslHandler(serverEngine)); - } + SSLEngine serverEngine = sslService.createSSLEngine(securityProfileSettings, transportSSLSettings); + serverEngine.setUseClientMode(false); + ch.pipeline().addFirst(new SslHandler(serverEngine)); if (authenticator != null) { ch.pipeline().addFirst(new IpFilterRemoteAddressFilter(authenticator, name)); } @@ -121,9 +104,7 @@ public class SecurityNetty4Transport extends Netty4Transport { @Override protected void initChannel(Channel ch) throws Exception { super.initChannel(ch); - if (ssl) { - ch.pipeline().addFirst(new ClientSslHandlerInitializer(transportSSLSettings, sslService, hostnameVerificationEnabled)); - } + ch.pipeline().addFirst(new ClientSslHandlerInitializer(transportSSLSettings, sslService, hostnameVerificationEnabled)); } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/CertUtils.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/CertUtils.java index 3b99b81bee0..498cc17b470 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/CertUtils.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/CertUtils.java @@ -14,7 +14,9 @@ import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.ExtensionsGenerator; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.asn1.x509.Time; +import org.bouncycastle.cert.CertIOException; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; @@ -27,6 +29,7 @@ import org.bouncycastle.openssl.X509TrustedCertificateBlock; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.pkcs.PKCS10CertificationRequest; import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; @@ -48,19 +51,26 @@ import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedTrustManager; import javax.security.auth.x500.X500Principal; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.math.BigInteger; import java.net.InetAddress; +import java.net.SocketException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; +import java.security.PublicKey; import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; +import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -96,7 +106,8 @@ public class CertUtils { /** * Returns a {@link X509ExtendedKeyManager} that is built from the provided private key and certificate chain */ - static X509ExtendedKeyManager keyManager(Certificate[] certificateChain, PrivateKey privateKey, char[] password) throws Exception { + static X509ExtendedKeyManager keyManager(Certificate[] certificateChain, PrivateKey privateKey, char[] password) + throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, IOException, CertificateException { KeyStore keyStore = KeyStore.getInstance("jks"); keyStore.load(null, null); // password must be non-null for keystore... @@ -107,7 +118,8 @@ public class CertUtils { /** * Returns a {@link X509ExtendedKeyManager} that is built from the provided keystore */ - static X509ExtendedKeyManager keyManager(KeyStore keyStore, char[] password, String algorithm) throws Exception { + static X509ExtendedKeyManager keyManager(KeyStore keyStore, char[] password, String algorithm) + throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException { KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm); kmf.init(keyStore, password); KeyManager[] keyManagers = kmf.getKeyManagers(); @@ -123,9 +135,9 @@ public class CertUtils { * Creates a {@link X509ExtendedTrustManager} based on the provided certificates * @param certificates the certificates to trust * @return a trust manager that trusts the provided certificates - * @throws Exception if there is an error loading the certificates or trust manager */ - public static X509ExtendedTrustManager trustManager(Certificate[] certificates) throws Exception { + public static X509ExtendedTrustManager trustManager(Certificate[] certificates) + throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, IOException, CertificateException { KeyStore store = KeyStore.getInstance("jks"); store.load(null, null); int counter = 0; @@ -143,23 +155,24 @@ public class CertUtils { * @param trustStoreAlgorithm the algorithm to use for the truststore * @param env the environment to use for file resolution. May be {@code null} * @return a trust manager with the trust material from the store - * @throws Exception if an error occurs when loading the truststore or the trust manager */ public static X509ExtendedTrustManager trustManager(String trustStorePath, String trustStorePassword, String trustStoreAlgorithm, - @Nullable Environment env) throws Exception { + @Nullable Environment env) + throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, IOException, CertificateException { try (InputStream in = Files.newInputStream(resolvePath(trustStorePath, env))) { // TODO remove reliance on JKS since we can PKCS12 stores... KeyStore trustStore = KeyStore.getInstance("jks"); assert trustStorePassword != null; trustStore.load(in, trustStorePassword.toCharArray()); - return CertUtils.trustManager(trustStore, trustStoreAlgorithm); + return trustManager(trustStore, trustStoreAlgorithm); } } /** * Creates a {@link X509ExtendedTrustManager} based on the trust material in the provided {@link KeyStore} */ - static X509ExtendedTrustManager trustManager(KeyStore keyStore, String algorithm) throws Exception { + static X509ExtendedTrustManager trustManager(KeyStore keyStore, String algorithm) + throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, IOException, CertificateException { TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm); tmf.init(keyStore); TrustManager[] trustManagers = tmf.getTrustManagers(); @@ -176,9 +189,9 @@ public class CertUtils { * @param certPaths the paths to the PEM encoded certificates * @param environment the environment to resolve files against. May be {@code null} * @return an array of {@link Certificate} objects - * @throws Exception if an error occurs reading a file or parsing a certificate */ - public static Certificate[] readCertificates(List certPaths, @Nullable Environment environment) throws Exception { + public static Certificate[] readCertificates(List certPaths, @Nullable Environment environment) + throws CertificateException, IOException { List certificates = new ArrayList<>(certPaths.size()); CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); for (String path : certPaths) { @@ -192,7 +205,8 @@ public class CertUtils { /** * Reads the certificates from the provided reader */ - static void readCertificates(Reader reader, List certificates, CertificateFactory certFactory) throws Exception { + static void readCertificates(Reader reader, List certificates, CertificateFactory certFactory) + throws IOException, CertificateException { try (PEMParser pemParser = new PEMParser(reader)) { Object parsed = pemParser.readObject(); @@ -220,40 +234,31 @@ public class CertUtils { /** * Reads the private key from the reader and optionally uses the password supplier to retrieve a password if the key is encrypted */ - static PrivateKey readPrivateKey(Reader reader, Supplier passwordSupplier) throws Exception { + static PrivateKey readPrivateKey(Reader reader, Supplier passwordSupplier) throws IOException { try (PEMParser parser = new PEMParser(reader)) { - Object parsed; - List list = new ArrayList<>(1); - do { - parsed = parser.readObject(); - if (parsed != null) { - list.add(parsed); - } - } while (parsed != null); - - if (list.size() != 1) { - throw new IllegalStateException("key file contained [" + list.size() + "] entries, expected one"); + final Object parsed = parser.readObject(); + if (parser.readObject() != null) { + throw new IllegalStateException("key file contained more that one entry"); } PrivateKeyInfo privateKeyInfo; - Object parsedObject = list.get(0); - if (parsedObject instanceof PEMEncryptedKeyPair) { + if (parsed instanceof PEMEncryptedKeyPair) { char[] keyPassword = passwordSupplier.get(); if (keyPassword == null) { throw new IllegalArgumentException("cannot read encrypted key without a password"); } // we have an encrypted key pair so we need to decrypt it - PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) parsedObject; + PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) parsed; privateKeyInfo = encryptedKeyPair .decryptKeyPair(new JcePEMDecryptorProviderBuilder().setProvider(BC_PROV).build(keyPassword)) .getPrivateKeyInfo(); - } else if (parsedObject instanceof PEMKeyPair) { - privateKeyInfo = ((PEMKeyPair) parsedObject).getPrivateKeyInfo(); - } else if (parsedObject instanceof PrivateKeyInfo) { - privateKeyInfo = (PrivateKeyInfo) parsedObject; + } else if (parsed instanceof PEMKeyPair) { + privateKeyInfo = ((PEMKeyPair) parsed).getPrivateKeyInfo(); + } else if (parsed instanceof PrivateKeyInfo) { + privateKeyInfo = (PrivateKeyInfo) parsed; } else { throw new IllegalArgumentException("parsed an unsupported object [" + - parsedObject.getClass().getSimpleName() + "]"); + parsed.getClass().getSimpleName() + "]"); } JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); @@ -264,7 +269,8 @@ public class CertUtils { /** * Generates a CA certificate */ - static X509Certificate generateCACertificate(X500Principal x500Principal, KeyPair keyPair, int days) throws Exception { + static X509Certificate generateCACertificate(X500Principal x500Principal, KeyPair keyPair, int days) + throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException { return generateSignedCertificate(x500Principal, null, keyPair, null, null, true, days); } @@ -272,7 +278,8 @@ public class CertUtils { * Generates a signed certificate using the provided CA private key and information from the CA certificate */ public static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair, - X509Certificate caCert, PrivateKey caPrivKey, int days) throws Exception { + X509Certificate caCert, PrivateKey caPrivKey, int days) + throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException { return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false, days); } @@ -286,11 +293,10 @@ public class CertUtils { * @param caPrivKey the CA private key. If {@code null}, this results in a self signed certificate * @param isCa whether or not the generated certificate is a CA * @return a signed {@link X509Certificate} - * @throws Exception if an error occurs during the certificate creation */ private static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair, X509Certificate caCert, PrivateKey caPrivKey, boolean isCa, int days) - throws Exception { + throws NoSuchAlgorithmException, CertificateException, CertIOException, OperatorCreationException { final DateTime notBefore = new DateTime(DateTimeZone.UTC); if (days < 1) { throw new IllegalArgumentException("the certificate must be valid for at least one day"); @@ -338,9 +344,9 @@ public class CertUtils { * @param sanList the subject alternative names that should be added to the certificate as an X509v3 extension. May be * {@code null} * @return a certificate signing request - * @throws Exception if an error occurs generating or signing the CSR */ - static PKCS10CertificationRequest generateCSR(KeyPair keyPair, X500Principal principal, GeneralNames sanList) throws Exception { + static PKCS10CertificationRequest generateCSR(KeyPair keyPair, X500Principal principal, GeneralNames sanList) + throws IOException, OperatorCreationException { JcaPKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(principal, keyPair.getPublic()); if (sanList != null) { ExtensionsGenerator extGen = new ExtensionsGenerator(); @@ -364,7 +370,7 @@ public class CertUtils { /** * Generates a RSA key pair with the provided key size (in bits) */ - static KeyPair generateKeyPair(int keysize) throws Exception { + static KeyPair generateKeyPair(int keysize) throws NoSuchAlgorithmException { // generate a private key KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(keysize); @@ -374,7 +380,7 @@ public class CertUtils { /** * Converts the {@link InetAddress} objects into a {@link GeneralNames} object that is used to represent subject alternative names. */ - static GeneralNames getSubjectAlternativeNames(boolean resolveName, Set addresses) throws Exception { + static GeneralNames getSubjectAlternativeNames(boolean resolveName, Set addresses) throws SocketException { Set generalNameList = new HashSet<>(); for (InetAddress address : addresses) { if (address.isAnyLocalAddress()) { diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/DefaultJDKTrustConfig.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/DefaultJDKTrustConfig.java index 29197ab8773..e6395a03393 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/DefaultJDKTrustConfig.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/DefaultJDKTrustConfig.java @@ -13,6 +13,7 @@ import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedTrustManager; import java.nio.file.Path; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -61,68 +62,10 @@ class DefaultJDKTrustConfig extends TrustConfig { * @return a {@link TrustConfig} that represents a combination of both trust configurations */ static TrustConfig merge(TrustConfig trustConfig) { - return new CombiningTrustConfig(trustConfig); - } - - /** - * A trust configuration that is a combination of a trust configuration with the default JDK trust configuration. This trust - * configuration returns a trust manager verifies certificates against both the default JDK trusted configurations and the specific - * {@link TrustConfig} provided. - */ - static class CombiningTrustConfig extends TrustConfig { - - private final TrustConfig trustConfig; - - private CombiningTrustConfig(TrustConfig trustConfig) { - this.trustConfig = trustConfig; - } - - @Override - X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) { - X509ExtendedTrustManager trustManager = trustConfig.createTrustManager(environment); - X509ExtendedTrustManager defaultTrustManager = INSTANCE.createTrustManager(environment); - if (trustManager == null) { - return defaultTrustManager; - } - - X509Certificate[] firstIssuers = trustManager.getAcceptedIssuers(); - X509Certificate[] secondIssuers = defaultTrustManager.getAcceptedIssuers(); - X509Certificate[] acceptedIssuers = new X509Certificate[firstIssuers.length + secondIssuers.length]; - System.arraycopy(firstIssuers, 0, acceptedIssuers, 0, firstIssuers.length); - System.arraycopy(secondIssuers, 0, acceptedIssuers, firstIssuers.length, secondIssuers.length); - try { - return CertUtils.trustManager(acceptedIssuers); - } catch (Exception e) { - throw new ElasticsearchException("failed to create trust manager", e); - } - } - - @Override - List filesToMonitor(@Nullable Environment environment) { - return trustConfig.filesToMonitor(environment); - } - - @Override - public String toString() { - return "Combining Trust Config{first=[" + trustConfig.toString() + "], second=[" + INSTANCE.toString() + "]}"; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof CombiningTrustConfig)) { - return false; - } - - CombiningTrustConfig that = (CombiningTrustConfig) o; - return trustConfig.equals(that.trustConfig); - } - - @Override - public int hashCode() { - return trustConfig.hashCode(); + if (trustConfig == null) { + return INSTANCE; + } else { + return new CombiningTrustConfig(Arrays.asList(INSTANCE, trustConfig)); } } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/GeneratedKeyConfig.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/GeneratedKeyConfig.java new file mode 100644 index 00000000000..81de52b5638 --- /dev/null +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/GeneratedKeyConfig.java @@ -0,0 +1,211 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.ssl; + +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.operator.OperatorCreationException; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.hash.MessageDigests; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.env.Environment; +import org.elasticsearch.node.Node; + +import javax.net.ssl.X509ExtendedKeyManager; +import javax.net.ssl.X509ExtendedTrustManager; +import javax.security.auth.DestroyFailedException; +import javax.security.auth.x500.X500Principal; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.KeyStoreException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * Represents a {@link KeyConfig} that is automatically generated on node startup if necessary. This helps with the default user experience + * so that the user does not need to have any knowledge about SSL setup to start a node + */ +final class GeneratedKeyConfig extends KeyConfig { + + // these values have been generated using openssl + // For private key: openssl pkcs8 -in private.pem -inform PEM -nocrypt -topk8 -outform DER | openssl dgst -sha256 -hex + // For certificate: openssl x509 -in ca.pem -noout -fingerprint -sha256 + private static final String PRIVATE_KEY_SHA256 = "eec5bdb422c17c75d3850ffc64a724e52a99ec64366677da2fe4e782d7426e9f"; + private static final String CA_CERT_FINGERPRINT_SHA256 = "A147166C71EB8B61DADFC5B19ECAC8443BE2DB32A56FC1A73BC1623250738598"; + + private final X509ExtendedKeyManager keyManager; + private final X509ExtendedTrustManager trustManager; + + GeneratedKeyConfig(Settings settings) throws NoSuchAlgorithmException, IOException, CertificateException, OperatorCreationException, + UnrecoverableKeyException, KeyStoreException { + final KeyPair keyPair = CertUtils.generateKeyPair(2048); + final X500Principal principal = new X500Principal("CN=" + Node.NODE_NAME_SETTING.get(settings)); + final Certificate caCert = readCACert(); + final PrivateKey privateKey = readPrivateKey(); + final GeneralNames generalNames = CertUtils.getSubjectAlternativeNames(false, getLocalAddresses()); + X509Certificate certificate = + CertUtils.generateSignedCertificate(principal, generalNames, keyPair, (X509Certificate) caCert, privateKey, 365); + try { + privateKey.destroy(); + } catch (DestroyFailedException e) { + // best effort attempt. This is known to fail for RSA keys on the oracle JDK but maybe they'll fix it in ten years or so... + } + keyManager = CertUtils.keyManager(new Certificate[] { certificate, caCert }, keyPair.getPrivate(), new char[0]); + trustManager = CertUtils.trustManager(new Certificate[] { caCert }); + } + + @Override + X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) { + return trustManager; + } + + @Override + List filesToMonitor(@Nullable Environment environment) { + // no files to watch + return Collections.emptyList(); + } + + @Override + public String toString() { + return "Generated Key Config. DO NOT USE IN PRODUCTION"; + } + + @Override + public boolean equals(Object o) { + return this == o; + } + + @Override + public int hashCode() { + return Objects.hash(keyManager, trustManager); + } + + @Override + X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) { + return keyManager; + } + + @Override + List privateKeys(@Nullable Environment environment) { + try { + return Collections.singletonList(readPrivateKey()); + } catch (IOException e) { + throw new UncheckedIOException("failed to read key", e); + } + } + + /** + * Enumerates all of the loopback and link local addresses so these can be used as SubjectAlternativeNames inside the certificate for + * a good out of the box experience with TLS + */ + private Set getLocalAddresses() throws SocketException { + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + Set inetAddresses = new HashSet<>(); + while (networkInterfaces.hasMoreElements()) { + NetworkInterface intf = networkInterfaces.nextElement(); + if (intf.isUp()) { + if (intf.isLoopback()) { + inetAddresses.addAll(Collections.list(intf.getInetAddresses())); + } else { + Enumeration inetAddressEnumeration = intf.getInetAddresses(); + while (inetAddressEnumeration.hasMoreElements()) { + InetAddress inetAddress = inetAddressEnumeration.nextElement(); + if (inetAddress.isLoopbackAddress() || inetAddress.isLinkLocalAddress()) { + inetAddresses.add(inetAddress); + } + } + } + } + } + return inetAddresses; + } + + /** + * Reads the bundled CA private key. This key is used for signing a automatically generated certificate that allows development nodes + * to talk to each other on the same machine. + * + * This private key is the same for every distribution and is only here for a nice out of the box experience. Once in production mode + * this key should not be used! + */ + static PrivateKey readPrivateKey() throws IOException { + try (InputStream inputStream = GeneratedKeyConfig.class.getResourceAsStream("private.pem"); + Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + PrivateKey privateKey = CertUtils.readPrivateKey(reader, () -> null); + MessageDigest md = MessageDigests.sha256(); + final byte[] privateKeyBytes = privateKey.getEncoded(); + try { + final byte[] digest = md.digest(privateKeyBytes); + final byte[] expected = hexStringToByteArray(PRIVATE_KEY_SHA256); + if (Arrays.equals(digest, expected) == false) { + throw new IllegalStateException("private key hash does not match the expected value!"); + } + } finally { + Arrays.fill(privateKeyBytes, (byte) 0); + } + return privateKey; + } + } + + /** + * Reads the bundled CA certificate + */ + static Certificate readCACert() throws IOException, CertificateException { + try (InputStream inputStream = GeneratedKeyConfig.class.getResourceAsStream("ca.pem"); + Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + List certificateList = new ArrayList<>(1); + CertUtils.readCertificates(reader, certificateList, certificateFactory); + if (certificateList.size() != 1) { + throw new IllegalStateException("expected [1] default CA certificate but found [" + certificateList.size() + "]"); + } + Certificate certificate = certificateList.get(0); + final byte[] encoded = MessageDigests.sha256().digest(certificate.getEncoded()); + final byte[] expected = hexStringToByteArray(CA_CERT_FINGERPRINT_SHA256); + if (Arrays.equals(encoded, expected) == false) { + throw new IllegalStateException("CA certificate fingerprint does not match!"); + } + return certificateList.get(0); + } + } + + private static byte[] hexStringToByteArray(String hexString) { + if (hexString.length() % 2 != 0) { + throw new IllegalArgumentException("String must be an even length"); + } + final int numBytes = hexString.length() / 2; + final byte[] data = new byte[numBytes]; + + for(int i = 0; i < numBytes; i++) { + final int index = i * 2; + final int index2 = index + 1; + data[i] = (byte) ((Character.digit(hexString.charAt(index), 16) << 4) + Character.digit(hexString.charAt(index2), 16)); + } + + return data; + } +} diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/KeyConfig.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/KeyConfig.java index 49657d6c66f..52275ed2e58 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/KeyConfig.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/KeyConfig.java @@ -11,6 +11,7 @@ import org.elasticsearch.env.Environment; import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedTrustManager; import java.nio.file.Path; +import java.security.PrivateKey; import java.util.Collections; import java.util.List; @@ -46,7 +47,14 @@ abstract class KeyConfig extends TrustConfig { public int hashCode() { return System.identityHashCode(this); } + + @Override + List privateKeys(@Nullable Environment environment) { + return Collections.emptyList(); + } }; abstract X509ExtendedKeyManager createKeyManager(@Nullable Environment environment); + + abstract List privateKeys(@Nullable Environment environment); } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/PEMKeyConfig.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/PEMKeyConfig.java index 7604f654b9b..ce78cfae1c9 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/PEMKeyConfig.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/PEMKeyConfig.java @@ -8,15 +8,22 @@ package org.elasticsearch.xpack.ssl; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.Nullable; import org.elasticsearch.env.Environment; +import org.elasticsearch.xpack.security.authc.support.SecuredString; import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedTrustManager; +import java.io.IOException; import java.io.Reader; +import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; +import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; +import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -46,30 +53,40 @@ class PEMKeyConfig extends KeyConfig { @Override X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) { - // password must be non-null for keystore... - char[] password = keyPassword == null ? new char[0] : keyPassword.toCharArray(); try { PrivateKey privateKey = readPrivateKey(CertUtils.resolvePath(keyPath, environment)); + if (privateKey == null) { + throw new IllegalArgumentException("private key [" + keyPath + "] could not be loaded"); + } Certificate[] certificateChain = CertUtils.readCertificates(Collections.singletonList(certPath), environment); // password must be non-null for keystore... - return CertUtils.keyManager(certificateChain, privateKey, password); - } catch (Exception e) { - throw new ElasticsearchException("failed to initialize a KeyManagerFactory", e); - } finally { - if (password != null) { - Arrays.fill(password, (char) 0); + try (SecuredString securedKeyPasswordChars = new SecuredString(keyPassword == null ? new char[0] : keyPassword.toCharArray())) { + return CertUtils.keyManager(certificateChain, privateKey, securedKeyPasswordChars.internalChars()); } + } catch (IOException | UnrecoverableKeyException | NoSuchAlgorithmException | CertificateException | KeyStoreException e) { + throw new ElasticsearchException("failed to initialize a KeyManagerFactory", e); } } - private PrivateKey readPrivateKey(Path keyPath) throws Exception { - char[] password = keyPassword == null ? null : keyPassword.toCharArray(); - try (Reader reader = Files.newBufferedReader(keyPath, StandardCharsets.UTF_8)) { - return CertUtils.readPrivateKey(reader, () -> password); - } finally { - if (password != null) { - Arrays.fill(password, (char) 0); - } + @Override + List privateKeys(@Nullable Environment environment) { + try { + return Collections.singletonList(readPrivateKey(CertUtils.resolvePath(keyPath, environment))); + } catch (IOException e) { + throw new UncheckedIOException("failed to read key", e); + } + } + + private PrivateKey readPrivateKey(Path keyPath) throws IOException { + try (Reader reader = Files.newBufferedReader(keyPath, StandardCharsets.UTF_8); + SecuredString securedString = new SecuredString(keyPassword == null ? new char[0] : keyPassword.toCharArray())) { + return CertUtils.readPrivateKey(reader, () -> { + if (keyPassword == null) { + return null; + } else { + return securedString.internalChars(); + } + }); } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLBootstrapCheck.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLBootstrapCheck.java new file mode 100644 index 00000000000..909dd7228fb --- /dev/null +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLBootstrapCheck.java @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.ssl; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.bootstrap.BootstrapCheck; +import org.elasticsearch.common.inject.internal.Nullable; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.env.Environment; +import org.elasticsearch.xpack.XPackSettings; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.cert.CertificateException; +import java.util.Arrays; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * Bootstrap check to ensure that we only use the generated key config in non-production situations. This class is currently public because + * {@link org.elasticsearch.xpack.security.Security} is in a different package and we use package private accessors of the + * {@link SSLService} to get the configuration for the node to node transport + */ +public final class SSLBootstrapCheck implements BootstrapCheck { + + private final SSLService sslService; + private final Settings settings; + private final Environment environment; + + public SSLBootstrapCheck(SSLService sslService, Settings settings, @Nullable Environment environment) { + this.sslService = sslService; + this.settings = settings; + this.environment = environment; + } + + @Override + public boolean check() { + final Settings transportSSLSettings = settings.getByPrefix(XPackSettings.TRANSPORT_SSL_PREFIX); + return sslService.sslConfiguration(transportSSLSettings).keyConfig() == KeyConfig.NONE + || isDefaultCACertificateTrusted() || isDefaultPrivateKeyUsed(); + } + + /** + * Looks at all of the trusted certificates to ensure the default CA is not being trusted. We cannot let this happen in production mode + */ + private boolean isDefaultCACertificateTrusted() { + final PublicKey publicKey; + try { + publicKey = GeneratedKeyConfig.readCACert().getPublicKey(); + } catch (IOException | CertificateException e) { + throw new ElasticsearchException("failed to check default CA", e); + } + return sslService.getLoadedSSLConfigurations().stream() + .flatMap(config -> Stream.of(config.keyConfig().createTrustManager(environment), + config.trustConfig().createTrustManager(environment))) + .filter(Objects::nonNull) + .flatMap((tm) -> Arrays.stream(tm.getAcceptedIssuers())) + .anyMatch((cert) -> { + try { + cert.verify(publicKey); + return true; + } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException + | SignatureException e) { + // just ignore these + return false; + } + }); + } + + /** + * Looks at all of the private keys and if there is a key that is equal to the default CA key then we should bail out + */ + private boolean isDefaultPrivateKeyUsed() { + final PrivateKey defaultPrivateKey; + try { + defaultPrivateKey = GeneratedKeyConfig.readPrivateKey(); + } catch (IOException e) { + throw new UncheckedIOException("failed to read key", e); + } + + return sslService.getLoadedSSLConfigurations().stream() + .flatMap(sslConfiguration -> sslConfiguration.keyConfig().privateKeys(environment).stream()) + .anyMatch(defaultPrivateKey::equals); + } + + @Override + public String errorMessage() { + return "Default SSL key and certificate do not provide security; please generate keys and certificates"; + } +} diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLService.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLService.java index 3c041bf7898..5b029461b23 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLService.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLService.java @@ -7,7 +7,10 @@ package org.elasticsearch.xpack.ssl; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; +import org.apache.lucene.util.SetOnce; +import org.bouncycastle.operator.OperatorCreationException; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.Nullable; import org.elasticsearch.common.CheckedSupplier; import org.elasticsearch.common.Strings; import org.elasticsearch.common.component.AbstractComponent; @@ -27,13 +30,17 @@ import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedTrustManager; +import javax.security.auth.DestroyFailedException; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; +import java.nio.file.Path; import java.security.KeyManagementException; +import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.PrivateKey; +import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -56,26 +63,36 @@ public class SSLService extends AbstractComponent { private final Map sslContexts; private final SSLConfiguration globalSSLConfiguration; + private final SetOnce transportSSLConfiguration = new SetOnce<>(); private final Environment env; /** * Create a new SSLService that parses the settings for the ssl contexts that need to be created, creates them, and then caches them * for use later */ - public SSLService(Settings settings, Environment environment) { + public SSLService(Settings settings, Environment environment) throws CertificateException, UnrecoverableKeyException, + NoSuchAlgorithmException, IOException, DestroyFailedException, KeyStoreException, OperatorCreationException { super(settings); this.env = environment; this.globalSSLConfiguration = new SSLConfiguration(settings.getByPrefix(XPackSettings.GLOBAL_SSL_PREFIX)); this.sslContexts = loadSSLConfigurations(); } + private SSLService(Settings settings, Environment environment, SSLConfiguration globalSSLConfiguration, + Map sslContexts) { + super(settings); + this.env = environment; + this.globalSSLConfiguration = globalSSLConfiguration; + this.sslContexts = sslContexts; + } + /** * Creates a new SSLService that supports dynamic creation of SSLContext instances. Instances created by this service will not be * cached and will not be monitored for reloading. This dynamic server does have access to the cached and monitored instances that * have been created during initialization */ public SSLService createDynamicSSLService() { - return new SSLService(settings, env) { + return new SSLService(settings, env, globalSSLConfiguration, sslContexts) { @Override Map loadSSLConfigurations() { @@ -87,11 +104,12 @@ public class SSLService extends AbstractComponent { * Returns the existing {@link SSLContextHolder} for the configuration * @throws IllegalArgumentException if not found */ + @Override SSLContextHolder sslContextHolder(SSLConfiguration sslConfiguration) { - SSLContextHolder holder = SSLService.this.sslContexts.get(sslConfiguration); + SSLContextHolder holder = sslContexts.get(sslConfiguration); if (holder == null) { // normally we'd throw here but let's create a new one that is not cached and will not be monitored for changes! - holder = SSLService.this.createSslContext(sslConfiguration); + holder = createSslContext(sslConfiguration); } return holder; } @@ -218,13 +236,16 @@ public class SSLService extends AbstractComponent { /** * Returns whether the provided settings results in a valid configuration that can be used for server connections * @param settings the settings used to identify the ssl configuration, typically under a *.ssl. prefix - * @param fallback the settings that should be used for the fallback of the SSLConfiguration. Using {@link Settings#EMPTY} - * results in a fallback to the global configuration + * @param useTransportFallback if {@code true} this will use the transport configuration for fallback, otherwise the global + * configuration will be used */ - public boolean isConfigurationValidForServerUsage(Settings settings, Settings fallback) { - SSLConfiguration sslConfiguration = sslConfiguration(settings, fallback); - return sslConfiguration.keyConfig() != KeyConfig.NONE; + public boolean isConfigurationValidForServerUsage(Settings settings, boolean useTransportFallback) { + SSLConfiguration fallback = useTransportFallback ? transportSSLConfiguration.get() : globalSSLConfiguration; + SSLConfiguration sslConfiguration = new SSLConfiguration(settings, fallback); + return sslConfiguration.keyConfig() != KeyConfig.NONE + || (useTransportFallback && transportSSLConfiguration.get().keyConfig() == KeyConfig.NONE); } + /** * Indicates whether client authentication is enabled for a particular configuration * @param settings the settings used to identify the ssl configuration, typically under a *.ssl. prefix. The global configuration @@ -368,7 +389,18 @@ public class SSLService extends AbstractComponent { new ReloadableTrustManager(sslConfiguration.trustConfig().createTrustManager(env), sslConfiguration.trustConfig()); ReloadableX509KeyManager keyManager = new ReloadableX509KeyManager(sslConfiguration.keyConfig().createKeyManager(env), sslConfiguration.keyConfig()); + return createSslContext(keyManager, trustManager, sslConfiguration); + } + /** + * Creates an {@link SSLContext} based on the provided configuration and trust/key managers + * @param sslConfiguration the configuration to use for context creation + * @param keyManager the key manager to use + * @param trustManager the trust manager to use + * @return the created SSLContext + */ + private SSLContextHolder createSslContext(ReloadableX509KeyManager keyManager, ReloadableTrustManager trustManager, + SSLConfiguration sslConfiguration) { // Initialize sslContext try { SSLContext sslContext = SSLContext.getInstance(sslContextAlgorithm(sslConfiguration.supportedProtocols())); @@ -386,32 +418,82 @@ public class SSLService extends AbstractComponent { /** * Parses the settings to load all SSLConfiguration objects that will be used. */ - Map loadSSLConfigurations() { + Map loadSSLConfigurations() throws CertificateException, + UnrecoverableKeyException, NoSuchAlgorithmException, IOException, DestroyFailedException, KeyStoreException, + OperatorCreationException { Map sslConfigurations = new HashMap<>(); sslConfigurations.put(globalSSLConfiguration, createSslContext(globalSSLConfiguration)); final Settings transportSSLSettings = settings.getByPrefix(XPackSettings.TRANSPORT_SSL_PREFIX); List sslSettingsList = new ArrayList<>(); - sslSettingsList.add(transportSSLSettings); sslSettingsList.add(getHttpTransportSSLSettings(settings)); sslSettingsList.add(settings.getByPrefix("xpack.http.ssl.")); sslSettingsList.addAll(getRealmsSSLSettings(settings)); sslSettingsList.addAll(getMonitoringExporterSettings(settings)); - for (Settings sslSettings : sslSettingsList) { - SSLConfiguration sslConfiguration = new SSLConfiguration(sslSettings, globalSSLConfiguration); - if (sslConfigurations.containsKey(sslConfiguration) == false) { - sslConfigurations.put(sslConfiguration, createSslContext(sslConfiguration)); - } - } + sslSettingsList.forEach((sslSettings) -> + sslConfigurations.computeIfAbsent(new SSLConfiguration(sslSettings, globalSSLConfiguration), this::createSslContext)); - // transport profiles are special since they fallback is to the transport settings which in turn falls back to global. - SSLConfiguration transportSSLConfiguration = new SSLConfiguration(transportSSLSettings, globalSSLConfiguration); - for (Settings profileSettings : getTransportProfileSSLSettings(settings)) { - SSLConfiguration sslConfiguration = new SSLConfiguration(profileSettings, transportSSLConfiguration); - if (sslConfigurations.containsKey(sslConfiguration) == false) { - sslConfigurations.put(sslConfiguration, createSslContext(sslConfiguration)); - } + // transport is special because we want to use a auto-generated key when there isn't one + final SSLConfiguration transportSSLConfiguration = new SSLConfiguration(transportSSLSettings, globalSSLConfiguration); + this.transportSSLConfiguration.set(transportSSLConfiguration); + List profileSettings = getTransportProfileSSLSettings(settings); + + // if no key is provided for transport we can auto-generate a key with a signed certificate for development use only. There is a + // bootstrap check that prevents this configuration from being use in production (SSLBootstrapCheck) + if (transportSSLConfiguration.keyConfig() == KeyConfig.NONE) { + // lazily generate key to avoid slowing down startup where we do not need it + final GeneratedKeyConfig generatedKeyConfig = new GeneratedKeyConfig(settings); + final TrustConfig trustConfig = + new TrustConfig.CombiningTrustConfig(Arrays.asList(transportSSLConfiguration.trustConfig(), new TrustConfig() { + @Override + X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) { + return generatedKeyConfig.createTrustManager(environment); + } + + @Override + List filesToMonitor(@Nullable Environment environment) { + return Collections.emptyList(); + } + + @Override + public String toString() { + return "Generated Trust Config. DO NOT USE IN PRODUCTION"; + } + + @Override + public boolean equals(Object o) { + return this == o; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + })); + X509ExtendedTrustManager extendedTrustManager = trustConfig.createTrustManager(env); + ReloadableTrustManager trustManager = new ReloadableTrustManager(extendedTrustManager, trustConfig); + ReloadableX509KeyManager keyManager = + new ReloadableX509KeyManager(generatedKeyConfig.createKeyManager(env), generatedKeyConfig); + sslConfigurations.put(transportSSLConfiguration, createSslContext(keyManager, trustManager, transportSSLConfiguration)); + profileSettings.forEach((profileSetting) -> { + SSLConfiguration configuration = new SSLConfiguration(profileSetting, transportSSLConfiguration); + if (configuration.keyConfig() == KeyConfig.NONE) { + sslConfigurations.compute(configuration, (conf, holder) -> { + if (holder != null && holder.keyManager == keyManager && holder.trustManager == trustManager) { + return holder; + } else { + return createSslContext(keyManager, trustManager, configuration); + } + }); + } else { + sslConfigurations.computeIfAbsent(configuration, this::createSslContext); + } + }); + } else { + sslConfigurations.computeIfAbsent(transportSSLConfiguration, this::createSslContext); + profileSettings.forEach((profileSetting) -> + sslConfigurations.computeIfAbsent(new SSLConfiguration(profileSetting, transportSSLConfiguration), this::createSslContext)); } return Collections.unmodifiableMap(sslConfigurations); } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/StoreKeyConfig.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/StoreKeyConfig.java index d0b5d4761ae..4a805867b96 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/StoreKeyConfig.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/StoreKeyConfig.java @@ -8,14 +8,24 @@ package org.elasticsearch.xpack.ssl; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.Nullable; import org.elasticsearch.env.Environment; +import org.elasticsearch.xpack.security.authc.support.SecuredString; import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedTrustManager; +import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.security.Key; import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.ArrayList; import java.util.Collections; +import java.util.Enumeration; import java.util.List; import java.util.Objects; @@ -49,13 +59,13 @@ class StoreKeyConfig extends KeyConfig { @Override X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) { - try (InputStream in = Files.newInputStream(CertUtils.resolvePath(keyStorePath, environment))) { - // TODO remove reliance on JKS since we can use PKCS12 stores in JDK8+... - KeyStore ks = KeyStore.getInstance("jks"); - assert keyStorePassword != null; - ks.load(in, keyStorePassword.toCharArray()); - return CertUtils.keyManager(ks, keyPassword.toCharArray(), keyStoreAlgorithm); - } catch (Exception e) { + try { + KeyStore ks = getKeyStore(environment); + checkKeyStore(ks); + try (SecuredString keyPasswordSecuredString = new SecuredString(keyPassword.toCharArray())) { + return CertUtils.keyManager(ks, keyPasswordSecuredString.internalChars(), keyStoreAlgorithm); + } + } catch (IOException | CertificateException | NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) { throw new ElasticsearchException("failed to initialize a KeyManagerFactory", e); } } @@ -74,6 +84,54 @@ class StoreKeyConfig extends KeyConfig { return Collections.singletonList(CertUtils.resolvePath(keyStorePath, environment)); } + @Override + List privateKeys(@Nullable Environment environment) { + try { + KeyStore keyStore = getKeyStore(environment); + try (SecuredString keyPasswordSecuredString = new SecuredString(keyPassword.toCharArray())) { + List privateKeys = new ArrayList<>(); + for (Enumeration e = keyStore.aliases(); e.hasMoreElements(); ) { + final String alias = e.nextElement(); + if (keyStore.isKeyEntry(alias)) { + Key key = keyStore.getKey(alias, keyPasswordSecuredString.internalChars()); + if (key instanceof PrivateKey) { + privateKeys.add((PrivateKey) key); + } + } + } + return privateKeys; + } + } catch (Exception e) { + throw new ElasticsearchException("failed to list keys", e); + } + } + + private KeyStore getKeyStore(@Nullable Environment environment) + throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { + try (InputStream in = Files.newInputStream(CertUtils.resolvePath(keyStorePath, environment))) { + // TODO remove reliance on JKS since we can use PKCS12 stores in JDK8+... + KeyStore ks = KeyStore.getInstance("jks"); + if (keyStorePassword == null) { + throw new IllegalArgumentException("keystore password may not be null"); + } + try (SecuredString keyStorePasswordSecuredString = new SecuredString(keyStorePassword.toCharArray())) { + ks.load(in, keyStorePasswordSecuredString.internalChars()); + } + return ks; + } + } + + private void checkKeyStore(KeyStore keyStore) throws KeyStoreException { + Enumeration aliases = keyStore.aliases(); + while (aliases.hasMoreElements()) { + String alias = aliases.nextElement(); + if (keyStore.isKeyEntry(alias)) { + return; + } + } + throw new IllegalArgumentException("the keystore [" + keyStorePath + "] does not contain a private key entry"); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/TrustConfig.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/TrustConfig.java index 0e0b6bb1c4e..2f06c5e55ef 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/TrustConfig.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/TrustConfig.java @@ -5,12 +5,18 @@ */ package org.elasticsearch.xpack.ssl; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.Nullable; import org.elasticsearch.env.Environment; import javax.net.ssl.X509ExtendedTrustManager; import java.nio.file.Path; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; /** * The configuration of trust material for SSL usage @@ -43,4 +49,63 @@ abstract class TrustConfig { * {@inheritDoc}. Declared as abstract to force implementors to provide a custom implementation */ public abstract int hashCode(); + + /** + * A trust configuration that is a combination of a trust configuration with the default JDK trust configuration. This trust + * configuration returns a trust manager verifies certificates against both the default JDK trusted configurations and the specific + * {@link TrustConfig} provided. + */ + static class CombiningTrustConfig extends TrustConfig { + + private final List trustConfigs; + + CombiningTrustConfig(List trustConfig) { + this.trustConfigs = Collections.unmodifiableList(trustConfig); + } + + @Override + X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) { + Optional matchAll = trustConfigs.stream().filter(TrustAllConfig.INSTANCE::equals).findAny(); + if (matchAll.isPresent()) { + return matchAll.get().createTrustManager(environment); + } + + try { + return CertUtils.trustManager(trustConfigs.stream() + .flatMap((tc) -> Arrays.stream(tc.createTrustManager(environment).getAcceptedIssuers())) + .collect(Collectors.toList()) + .toArray(new X509Certificate[0])); + } catch (Exception e) { + throw new ElasticsearchException("failed to create trust manager", e); + } + } + + @Override + List filesToMonitor(@Nullable Environment environment) { + return trustConfigs.stream().flatMap((tc) -> tc.filesToMonitor(environment).stream()).collect(Collectors.toList()); + } + + @Override + public String toString() { + return "Combining Trust Config{" + trustConfigs.stream().map(TrustConfig::toString).collect(Collectors.joining(", ")) + "}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CombiningTrustConfig)) { + return false; + } + + CombiningTrustConfig that = (CombiningTrustConfig) o; + return trustConfigs.equals(that.trustConfigs); + } + + @Override + public int hashCode() { + return trustConfigs.hashCode(); + } + } } diff --git a/elasticsearch/src/main/resources/org/elasticsearch/xpack/ssl/ca.pem b/elasticsearch/src/main/resources/org/elasticsearch/xpack/ssl/ca.pem new file mode 100644 index 00000000000..eff9fc52273 --- /dev/null +++ b/elasticsearch/src/main/resources/org/elasticsearch/xpack/ssl/ca.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDWDCCAkCgAwIBAgIJANRlkT/I8aROMA0GCSqGSIb3DQEBCwUAMCYxJDAiBgNV +BAMTG3hwYWNrIHB1YmxpYyBkZXZlbG9wbWVudCBjYTAeFw0xNzAxMDUxNDUyMDNa +Fw00NDA1MjMxNDUyMDNaMCYxJDAiBgNVBAMTG3hwYWNrIHB1YmxpYyBkZXZlbG9w +bWVudCBjYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALBfQEQYZmPW +cAw939i8RRsa27+qxd32ysJu9aKgSEiIDFKU0JwFh6pog1l8frICM4jF0TqILGHv ++QbQYsD2e3jYp0cj8dy2+YN6jgTXMf1N8yh6GYXEzRrEKYhqVTHLpZgbhxEFxsws +gZiEMHiVxn6h5i4uWDmkp6zt4kHlKgvjtIEzZ1xiXWcS7jJvVPb8r0xUFPDu8Qij +BhjxkbkXprzjGEtt4bKqZ8/R+pr+eUuvmApMSMB38dZxDRXxyavbmbJcGDJX+ZKN +4OcECH55B/EtxhPxpfFXmX+y5Lh597vkhgitw8Qhayaa8gF16tt4rUgYude9kGSi +m3hs6Q9mWM8CAwEAAaOBiDCBhTAdBgNVHQ4EFgQUM6+ZLgmnj1FXHEPejFcpiRR+ +ANIwVgYDVR0jBE8wTYAUM6+ZLgmnj1FXHEPejFcpiRR+ANKhKqQoMCYxJDAiBgNV +BAMTG3hwYWNrIHB1YmxpYyBkZXZlbG9wbWVudCBjYYIJANRlkT/I8aROMAwGA1Ud +EwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABqgr2p+Ivb3myF56BuiJYYz55Wa +ncm4Aqdw6p/A5qkl3pSXu2zbgSfyFvux7Q1+lowIvw4fAOTBcQQpQkYWJmObkCLg +HMiKbBreFVqPOqScjTBk6t1g/mOdJXfOognc6QRwfunEBqevNVDT2w3sGlNHooEM +3XUPBgyuznE1Olqt7U0tMGsENyBgZv51bUg7ZZCLrV2sdgqc3XYZUqBnttvbBDyU +tozgDMoCXLvVHcpWcKsA+ONd0szbSAu1uF0ZfqgaoSslM1ph9ydPbXEvnD5AFO6Y +VBTW3v4cnluhrxO6TwRqNo43L5ENqZhtX9gVtzQ54exQsuoKzZ8NO5X1uIA= +-----END CERTIFICATE----- diff --git a/elasticsearch/src/main/resources/org/elasticsearch/xpack/ssl/private.pem b/elasticsearch/src/main/resources/org/elasticsearch/xpack/ssl/private.pem new file mode 100644 index 00000000000..19306bffb51 --- /dev/null +++ b/elasticsearch/src/main/resources/org/elasticsearch/xpack/ssl/private.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAsF9ARBhmY9ZwDD3f2LxFGxrbv6rF3fbKwm71oqBISIgMUpTQ +nAWHqmiDWXx+sgIziMXROogsYe/5BtBiwPZ7eNinRyPx3Lb5g3qOBNcx/U3zKHoZ +hcTNGsQpiGpVMculmBuHEQXGzCyBmIQweJXGfqHmLi5YOaSnrO3iQeUqC+O0gTNn +XGJdZxLuMm9U9vyvTFQU8O7xCKMGGPGRuRemvOMYS23hsqpnz9H6mv55S6+YCkxI +wHfx1nENFfHJq9uZslwYMlf5ko3g5wQIfnkH8S3GE/Gl8VeZf7LkuHn3u+SGCK3D +xCFrJpryAXXq23itSBi5172QZKKbeGzpD2ZYzwIDAQABAoIBADRpKbzSj2Ktr4BD +xsguMk76rUCIq+Ho25npxT69aJ19KERGCrPChO0jv5yQ/UlClDPZrPI60w2LdTIM +LLxwwoJHx3XBfbb7/KuQeLGBjU5bop1tozX4JIcGsdzi1ExG2v+XdoydbdTwiNZc +udark1/AFpm0le0TO+yMiEbSpasAUetmwmBLl0ld1qOoEFNM4ueLtM0/JE4kQHJC +a6a0fS1D+TQsPCdziW80X2hpwCIbg4CF3LqR521SfwIzRscbaXzCzeBNCShJE8Nm +Qun91Szze80aaFBBIwMKbppEx5iYCCKeTyO3yswRuZ44+iBe/piB3F/qRKnjBwNS +LeL9NOECgYEA4xMUueF8HN23QeC/KZ/6LALwyNBtT7JP7YbW6dvo+3F0KSPxbDL1 +nMmeOTV8suAlGslsE+GuvPU6M9fUCxpbVnbYH5hEuh0v25WRekFv14H/yEpVF26o +OHeilUIzpRTUOndgkmN8cXNp2xkzs2Yp7F2RSlog2kXQOYgC91YmvjECgYEAxtbC +OzxUUjebeqnolo8wYur42BievUDqyD9pCnaix+2F59SdLDgirJIOEtv31jjpLaIh +nO8akxMCPNwhEgVzelI2k+jJ+Kermi3+tEAnlBBDf/tMEGNav2XE3MnYkDt2jdza +fganfhKQwAufyq2lUHC/Slh+xcLPepTef6zFxv8CgYB6ZEJ7ninDdU3dWEIxMWUq +a7tUweLpXfbu1Arqqfl97bzqn9D0vNLd215I/6di0qWtNnvmi3Ifrx3b660C/wXU +KOJ8xRnmJu0wsgFjn/mkcxFm54nNw3swVGtxf+lORVfO26FVxgHBNLANxBu1yo82 +M4ioRsQGYjLFj6XpoqnnQQKBgE8RpYlCs1FCdZxwpmIArMAZKj1chPtDLlnVBWM4 +zABuzpni7WFhLUCsj9YmDMbuOKOB3pX2av3jSDeFXc05x7LzsGpe3rn3iwCzm554 +CIUTdpQVDSlTKQoFYSRfS7QHQVymX2hQIxi6Lz9/H9rL9Hopa5gX2smvbywSuOvS +e49nAoGAM7TQ9iFBsygXxbxh2EL47nw/LBxbDm86TazpSKrHd9pV6Z/Xv870QEf7 +cZJ9T/KRGkxlK8L6B7uzeckpk4uMWuDRiymnbg2pqk94oELKkh0iLnlGSMf3IPO8 +qIRFQsQfA3PaU6SG/izaB1lquBRtIj5kAW2ZXI4O9l5V39Y5/n4= +-----END RSA PRIVATE KEY----- diff --git a/elasticsearch/src/main/resources/org/elasticsearch/xpack/ssl/public.pem b/elasticsearch/src/main/resources/org/elasticsearch/xpack/ssl/public.pem new file mode 100644 index 00000000000..2b46db3019e --- /dev/null +++ b/elasticsearch/src/main/resources/org/elasticsearch/xpack/ssl/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsF9ARBhmY9ZwDD3f2LxF +Gxrbv6rF3fbKwm71oqBISIgMUpTQnAWHqmiDWXx+sgIziMXROogsYe/5BtBiwPZ7 +eNinRyPx3Lb5g3qOBNcx/U3zKHoZhcTNGsQpiGpVMculmBuHEQXGzCyBmIQweJXG +fqHmLi5YOaSnrO3iQeUqC+O0gTNnXGJdZxLuMm9U9vyvTFQU8O7xCKMGGPGRuRem +vOMYS23hsqpnz9H6mv55S6+YCkxIwHfx1nENFfHJq9uZslwYMlf5ko3g5wQIfnkH +8S3GE/Gl8VeZf7LkuHn3u+SGCK3DxCFrJpryAXXq23itSBi5172QZKKbeGzpD2ZY +zwIDAQAB +-----END PUBLIC KEY----- diff --git a/elasticsearch/src/test/java/org/elasticsearch/integration/ClearRealmsCacheTests.java b/elasticsearch/src/test/java/org/elasticsearch/integration/ClearRealmsCacheTests.java index 3cb217375f4..b1f12d9e05f 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/integration/ClearRealmsCacheTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/integration/ClearRealmsCacheTests.java @@ -179,10 +179,6 @@ public class ClearRealmsCacheTests extends SecurityIntegTestCase { .put(NetworkModule.HTTP_ENABLED.getKey(), true) .build(); } - @Override - public boolean sslTransportEnabled() { - return false; - } @Override protected String configRoles() { diff --git a/elasticsearch/src/test/java/org/elasticsearch/integration/ldap/AbstractAdLdapRealmTestCase.java b/elasticsearch/src/test/java/org/elasticsearch/integration/ldap/AbstractAdLdapRealmTestCase.java index 1e61fa903cf..9be702d22f9 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/integration/ldap/AbstractAdLdapRealmTestCase.java +++ b/elasticsearch/src/test/java/org/elasticsearch/integration/ldap/AbstractAdLdapRealmTestCase.java @@ -54,13 +54,11 @@ public abstract class AbstractAdLdapRealmTestCase extends SecurityIntegTestCase protected static RealmConfig realmConfig; protected static boolean useGlobalSSL; - protected static boolean sslEnabled; @BeforeClass public static void setupRealm() { realmConfig = randomFrom(RealmConfig.values()); useGlobalSSL = randomBoolean(); - sslEnabled = randomBoolean(); ESLoggerFactory.getLogger("test").info("running test with realm configuration [{}], with direct group to role mapping [{}]. " + "Settings [{}]", realmConfig, realmConfig.mapGroupsAsRoles, realmConfig.settings.getAsMap()); } @@ -74,20 +72,34 @@ public abstract class AbstractAdLdapRealmTestCase extends SecurityIntegTestCase protected Settings nodeSettings(int nodeOrdinal) { Path nodeFiles = createTempDir(); Path store = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"); - Settings.Builder builder = Settings.builder() - .put(super.nodeSettings(nodeOrdinal)) - .put(realmConfig.buildSettings(store, "testnode")) + Settings.Builder builder = Settings.builder(); + if (useGlobalSSL) { + builder.put(super.nodeSettings(nodeOrdinal).filter((s) -> s.startsWith("xpack.ssl.") == false)) + .put(sslSettingsForStore(store, "testnode")); + } else { + builder.put(super.nodeSettings(nodeOrdinal)); + } + builder.put(realmConfig.buildSettings(store, "testnode")) .put(XPACK_SECURITY_AUTHC_REALMS_EXTERNAL + ".files.role_mapping", writeFile(nodeFiles, "role_mapping.yml", configRoleMappings())); - if (sslEnabled == false && useGlobalSSL) { - builder.put(sslSettingsForStore(store, "testnode")); - } return builder.build(); } @Override - protected boolean sslTransportEnabled() { - return sslEnabled; + protected Settings transportClientSettings() { + if (useGlobalSSL) { + Path store = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"); + return Settings.builder() + .put(super.transportClientSettings().filter((s) -> s.startsWith("xpack.ssl.") == false)) + .put(sslSettingsForStore(store, "testnode")) + .build(); + } else { + return super.transportClientSettings(); + } + } + @Override + protected boolean useGeneratedSSLConfig() { + return useGlobalSSL == false; } protected String configRoleMappings() { diff --git a/elasticsearch/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java b/elasticsearch/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java index d8f8061e78d..0b382377129 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java +++ b/elasticsearch/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java @@ -145,12 +145,12 @@ public abstract class SecurityIntegTestCase extends ESIntegTestCase { case SUITE: if (customSecuritySettingsSource == null) { customSecuritySettingsSource = - new CustomSecuritySettingsSource(sslTransportEnabled(), createTempDir(), currentClusterScope); + new CustomSecuritySettingsSource(useGeneratedSSLConfig(), createTempDir(), currentClusterScope); } break; case TEST: customSecuritySettingsSource = - new CustomSecuritySettingsSource(sslTransportEnabled(), createTempDir(), currentClusterScope); + new CustomSecuritySettingsSource(useGeneratedSSLConfig(), createTempDir(), currentClusterScope); break; } } @@ -291,11 +291,9 @@ public abstract class SecurityIntegTestCase extends ESIntegTestCase { } /** - * Allows to control whether ssl is enabled or not on the transport layer when the - * {@link org.elasticsearch.test.ESIntegTestCase.ClusterScope} is set to - * {@link org.elasticsearch.test.ESIntegTestCase.Scope#SUITE} or {@link org.elasticsearch.test.ESIntegTestCase.Scope#TEST} + * Allows to control whether ssl key information is auto generated or not on the transport layer */ - protected boolean sslTransportEnabled() { + protected boolean useGeneratedSSLConfig() { return randomBoolean(); } @@ -309,8 +307,8 @@ public abstract class SecurityIntegTestCase extends ESIntegTestCase { private class CustomSecuritySettingsSource extends SecuritySettingsSource { - private CustomSecuritySettingsSource(boolean sslTransportEnabled, Path configDir, Scope scope) { - super(maxNumberOfNodes(), sslTransportEnabled, configDir, scope); + private CustomSecuritySettingsSource(boolean useGeneratedSSLConfig, Path configDir, Scope scope) { + super(maxNumberOfNodes(), useGeneratedSSLConfig, configDir, scope); } @Override diff --git a/elasticsearch/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java b/elasticsearch/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java index adf8fbaf11f..de29722aac5 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java +++ b/elasticsearch/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java @@ -74,36 +74,36 @@ public class SecuritySettingsSource extends ClusterDiscoveryConfiguration.Unicas private final Path parentFolder; private final String subfolderPrefix; private final byte[] systemKey; - private final boolean sslTransportEnabled; + private final boolean useGeneratedSSLConfig; private final boolean hostnameVerificationEnabled; /** * Creates a new {@link org.elasticsearch.test.NodeConfigurationSource} for the security configuration. * * @param numOfNodes the number of nodes for proper unicast configuration (can be more than actually available) - * @param sslTransportEnabled whether ssl should be enabled on the transport layer or not + * @param useGeneratedSSLConfig whether ssl key/cert should be auto-generated * @param parentFolder the parent folder that will contain all of the configuration files that need to be created * @param scope the scope of the test that is requiring an instance of SecuritySettingsSource */ - public SecuritySettingsSource(int numOfNodes, boolean sslTransportEnabled, Path parentFolder, Scope scope) { - this(numOfNodes, sslTransportEnabled, generateKey(), parentFolder, scope); + public SecuritySettingsSource(int numOfNodes, boolean useGeneratedSSLConfig, Path parentFolder, Scope scope) { + this(numOfNodes, useGeneratedSSLConfig, generateKey(), parentFolder, scope); } /** * Creates a new {@link org.elasticsearch.test.NodeConfigurationSource} for the security configuration. * * @param numOfNodes the number of nodes for proper unicast configuration (can be more than actually available) - * @param sslTransportEnabled whether ssl should be enabled on the transport layer or not + * @param useGeneratedSSLConfig whether ssl key/cert should be auto-generated * @param systemKey the system key that all of the nodes will use to sign messages * @param parentFolder the parent folder that will contain all of the configuration files that need to be created * @param scope the scope of the test that is requiring an instance of SecuritySettingsSource */ - public SecuritySettingsSource(int numOfNodes, boolean sslTransportEnabled, byte[] systemKey, Path parentFolder, Scope scope) { + public SecuritySettingsSource(int numOfNodes, boolean useGeneratedSSLConfig, byte[] systemKey, Path parentFolder, Scope scope) { super(numOfNodes, DEFAULT_SETTINGS); this.systemKey = systemKey; this.parentFolder = parentFolder; this.subfolderPrefix = scope.name(); - this.sslTransportEnabled = sslTransportEnabled; + this.useGeneratedSSLConfig = useGeneratedSSLConfig; this.hostnameVerificationEnabled = randomBoolean(); } @@ -211,10 +211,10 @@ public class SecuritySettingsSource extends ClusterDiscoveryConfiguration.Unicas "/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt", "/org/elasticsearch/xpack/security/transport/ssl/certs/simple/openldap.crt", "/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), - sslTransportEnabled, hostnameVerificationEnabled, false); + useGeneratedSSLConfig, hostnameVerificationEnabled, false); } return getSSLSettingsForStore("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks", "testnode", - sslTransportEnabled, hostnameVerificationEnabled, false); + useGeneratedSSLConfig, hostnameVerificationEnabled, false); } public Settings getClientSSLSettings() { @@ -223,11 +223,11 @@ public class SecuritySettingsSource extends ClusterDiscoveryConfiguration.Unicas "/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt", Arrays.asList("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt", "/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), - sslTransportEnabled, hostnameVerificationEnabled, true); + useGeneratedSSLConfig, hostnameVerificationEnabled, true); } return getSSLSettingsForStore("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.jks", "testclient", - sslTransportEnabled, hostnameVerificationEnabled, true); + useGeneratedSSLConfig, hostnameVerificationEnabled, true); } /** @@ -238,26 +238,26 @@ public class SecuritySettingsSource extends ClusterDiscoveryConfiguration.Unicas * @return the configuration settings */ public static Settings getSSLSettingsForStore(String resourcePathToStore, String password) { - return getSSLSettingsForStore(resourcePathToStore, password, true, true, true); + return getSSLSettingsForStore(resourcePathToStore, password, false, true, true); } - private static Settings getSSLSettingsForStore(String resourcePathToStore, String password, boolean sslTransportEnabled, + private static Settings getSSLSettingsForStore(String resourcePathToStore, String password, boolean useGeneratedSSLConfig, boolean hostnameVerificationEnabled, boolean transportClient) { Path store = resolveResourcePath(resourcePathToStore); - Settings.Builder builder = Settings.builder().put(XPackSettings.TRANSPORT_SSL_ENABLED.getKey(), sslTransportEnabled); + Settings.Builder builder = Settings.builder(); if (transportClient == false) { builder.put("xpack.security.http.ssl.enabled", false); } - if (sslTransportEnabled) { + builder.put("xpack.ssl.verification_mode", hostnameVerificationEnabled ? "full" : "certificate"); + if (useGeneratedSSLConfig == false) { builder.put("xpack.ssl.keystore.path", store) - .put("xpack.ssl.keystore.password", password) - .put("xpack.ssl.verification_mode", hostnameVerificationEnabled ? "full" : "certificate"); + .put("xpack.ssl.keystore.password", password); } - if (sslTransportEnabled && randomBoolean()) { + if (useGeneratedSSLConfig == false && randomBoolean()) { builder.put("xpack.ssl.truststore.path", store) .put("xpack.ssl.truststore.password", password); } @@ -265,20 +265,19 @@ public class SecuritySettingsSource extends ClusterDiscoveryConfiguration.Unicas } private static Settings getSSLSettingsForPEMFiles(String keyPath, String password, String certificatePath, - List trustedCertificates, boolean sslTransportEnabled, + List trustedCertificates, boolean useGeneratedSSLConfig, boolean hostnameVerificationEnabled, boolean transportClient) { Settings.Builder builder = Settings.builder(); - builder.put(XPackSettings.TRANSPORT_SSL_ENABLED.getKey(), sslTransportEnabled); if (transportClient == false) { builder.put("xpack.security.http.ssl.enabled", false); } - if (sslTransportEnabled) { + builder.put("xpack.ssl.verification_mode", hostnameVerificationEnabled ? "full" : "certificate"); + if (useGeneratedSSLConfig == false) { builder.put("xpack.ssl.key", resolveResourcePath(keyPath)) .put("xpack.ssl.key_passphrase", password) - .put("xpack.ssl.certificate", resolveResourcePath(certificatePath)) - .put("xpack.ssl.verification_mode", hostnameVerificationEnabled ? "full" : "certificate"); + .put("xpack.ssl.certificate", resolveResourcePath(certificatePath)); if (trustedCertificates.isEmpty() == false) { builder.put("xpack.ssl.certificate_authorities", diff --git a/elasticsearch/src/test/java/org/elasticsearch/test/SettingsFilterTests.java b/elasticsearch/src/test/java/org/elasticsearch/test/SettingsFilterTests.java index 41b8baa7281..69b060f7aed 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/test/SettingsFilterTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/test/SettingsFilterTests.java @@ -18,7 +18,6 @@ import org.hamcrest.Matcher; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -32,7 +31,7 @@ public class SettingsFilterTests extends ESTestCase { private Settings.Builder configuredSettingsBuilder = Settings.builder(); private Map settingsMatcherMap = new HashMap<>(); - public void testFiltering() throws IOException { + public void testFiltering() throws Exception { configureUnfilteredSetting("xpack.security.authc.realms.file.type", "file"); // ldap realm filtering diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/TimeWarpedXPackPlugin.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/TimeWarpedXPackPlugin.java index 12ddead9e2b..1933b8db770 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/TimeWarpedXPackPlugin.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/TimeWarpedXPackPlugin.java @@ -5,17 +5,24 @@ */ package org.elasticsearch.xpack; +import org.bouncycastle.operator.OperatorCreationException; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.xpack.support.clock.ClockMock; import org.elasticsearch.xpack.watcher.test.TimeWarpedWatcher; +import javax.security.auth.DestroyFailedException; import java.io.IOException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; import java.time.Clock; public class TimeWarpedXPackPlugin extends XPackPlugin { private final ClockMock clock = new ClockMock(); - public TimeWarpedXPackPlugin(Settings settings) throws IOException { + public TimeWarpedXPackPlugin(Settings settings) throws IOException, CertificateException, UnrecoverableKeyException, + NoSuchAlgorithmException, KeyStoreException, DestroyFailedException, OperatorCreationException { super(settings); watcher = new TimeWarpedWatcher(settings); } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/common/http/HttpClientTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/common/http/HttpClientTests.java index 936cf6f8a9d..48c8407efb8 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/common/http/HttpClientTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/common/http/HttpClientTests.java @@ -9,7 +9,6 @@ import com.carrotsearch.randomizedtesting.generators.RandomStrings; import org.apache.http.client.ClientProtocolException; import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.util.Supplier; -import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.env.Environment; diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/SecurityFeatureSetTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/SecurityFeatureSetTests.java index cb7e0438a52..822e1b82961 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/SecurityFeatureSetTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/SecurityFeatureSetTests.java @@ -104,8 +104,6 @@ public class SecurityFeatureSetTests extends ESTestCase { final boolean httpSSLEnabled = randomBoolean(); settings.put("xpack.security.http.ssl.enabled", httpSSLEnabled); - final boolean transportSSLEnabled = randomBoolean(); - settings.put("xpack.security.transport.ssl.enabled", transportSSLEnabled); final boolean auditingEnabled = randomBoolean(); final String[] auditOutputs = randomFrom(new String[] {"logfile"}, new String[] {"index"}, new String[] {"logfile", "index"}); when(auditTrail.usageStats()) @@ -171,9 +169,8 @@ public class SecurityFeatureSetTests extends ESTestCase { assertThat(source.getValue("realms"), is(notNullValue())); } - // check SSL + // check http SSL assertThat(source.getValue("ssl.http.enabled"), is(httpSSLEnabled)); - assertThat(source.getValue("ssl.transport.enabled"), is(transportSSLEnabled)); // auditing assertThat(source.getValue("audit.enabled"), is(auditingEnabled)); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/SecurityTribeIT.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/SecurityTribeIT.java index 7a0bd76e076..ce16c1c719f 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/SecurityTribeIT.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/SecurityTribeIT.java @@ -58,14 +58,14 @@ public class SecurityTribeIT extends NativeRealmIntegTestCase { private static final String SECOND_CLUSTER_NODE_PREFIX = "node_cluster2_"; private static InternalTestCluster cluster2; - private static boolean useSSL; + private static boolean useGeneratedSSL; private Node tribeNode; private Client tribeClient; @BeforeClass public static void setupSSL() { - useSSL = randomBoolean(); + useGeneratedSSL = randomBoolean(); } @Override @@ -73,7 +73,7 @@ public class SecurityTribeIT extends NativeRealmIntegTestCase { super.setUp(); if (cluster2 == null) { SecuritySettingsSource cluster2SettingsSource = - new SecuritySettingsSource(defaultMaxNumberOfNodes(), useSSL, systemKey(), createTempDir(), Scope.SUITE); + new SecuritySettingsSource(defaultMaxNumberOfNodes(), useGeneratedSSL, systemKey(), createTempDir(), Scope.SUITE); cluster2 = new InternalTestCluster(randomLong(), createTempDir(), true, true, 1, 2, UUIDs.randomBase64UUID(random()), cluster2SettingsSource, 0, false, SECOND_CLUSTER_NODE_PREFIX, getMockPlugins(), getClientWrapper()); @@ -82,6 +82,11 @@ public class SecurityTribeIT extends NativeRealmIntegTestCase { } } + @Override + public boolean useGeneratedSSLConfig() { + return useGeneratedSSL; + } + @AfterClass public static void tearDownSecondCluster() { if (cluster2 != null) { @@ -123,18 +128,14 @@ public class SecurityTribeIT extends NativeRealmIntegTestCase { } } - @Override - public boolean sslTransportEnabled() { - return useSSL; - } - @Override protected boolean ignoreExternalCluster() { return true; } private void setupTribeNode(Settings settings) throws NodeValidationException, InterruptedException { - SecuritySettingsSource cluster2SettingsSource = new SecuritySettingsSource(1, useSSL, systemKey(), createTempDir(), Scope.TEST); + SecuritySettingsSource cluster2SettingsSource = + new SecuritySettingsSource(1, useGeneratedSSL, systemKey(), createTempDir(), Scope.TEST); Map asMap = new HashMap<>(cluster2SettingsSource.nodeSettings(0).getAsMap()); asMap.remove(NodeEnvironment.MAX_LOCAL_STORAGE_NODES_SETTING.getKey()); Settings.Builder tribe1Defaults = Settings.builder(); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrailTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrailTests.java index cf8d58d01aa..e8c96c36fc5 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrailTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrailTests.java @@ -37,6 +37,7 @@ import org.elasticsearch.transport.MockTcpTransportPlugin; import org.elasticsearch.transport.TransportInfo; import org.elasticsearch.transport.TransportMessage; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.XPackPlugin; import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.xpack.security.audit.index.IndexAuditTrail.Message; import org.elasticsearch.xpack.security.authc.AuthenticationToken; @@ -56,6 +57,7 @@ import org.junit.BeforeClass; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -132,10 +134,11 @@ public class IndexAuditTrailTests extends SecurityIntegTestCase { // Setup a second test cluster with randomization for number of nodes, security enabled, and SSL final int numNodes = randomIntBetween(1, 2); final boolean useSecurity = randomBoolean(); - final boolean useSSL = useSecurity && randomBoolean(); - logger.info("--> remote indexing enabled. security enabled: [{}], SSL enabled: [{}], nodes: [{}]", useSecurity, useSSL, numNodes); + final boolean useGeneratedSSL = useSecurity && randomBoolean(); + logger.info("--> remote indexing enabled. security enabled: [{}], SSL enabled: [{}], nodes: [{}]", useSecurity, useGeneratedSSL, + numNodes); SecuritySettingsSource cluster2SettingsSource = - new SecuritySettingsSource(numNodes, useSSL, systemKey(), createTempDir(), Scope.SUITE) { + new SecuritySettingsSource(numNodes, useGeneratedSSL, systemKey(), createTempDir(), Scope.SUITE) { @Override public Settings nodeSettings(int nodeOrdinal) { Settings.Builder builder = Settings.builder() @@ -146,6 +149,21 @@ public class IndexAuditTrailTests extends SecurityIntegTestCase { } return builder.build(); } + + @Override + public Settings transportClientSettings() { + if (useSecurity) { + return super.transportClientSettings(); + } else { + Settings.Builder builder = Settings.builder() + .put(XPackSettings.SECURITY_ENABLED.getKey(), false) + .put(super.transportClientSettings()); + if (builder.get(NetworkModule.TRANSPORT_TYPE_KEY) == null) { + builder.put(NetworkModule.TRANSPORT_TYPE_KEY, MockTcpTransportPlugin.MOCK_TCP_TRANSPORT_NAME); + } + return builder.build(); + } + } }; @@ -163,17 +181,19 @@ public class IndexAuditTrailTests extends SecurityIntegTestCase { TransportAddress inet = info.address().publishAddress(); Settings.Builder builder = Settings.builder() - .put(XPackSettings.SECURITY_ENABLED.getKey(), useSecurity) + .put("xpack.security.audit.index.client." + XPackSettings.SECURITY_ENABLED.getKey(), useSecurity) .put(remoteSettings(NetworkAddress.format(inet.address().getAddress()), inet.address().getPort(), cluster2Name)) .put("xpack.security.audit.index.client.xpack.security.user", SecuritySettingsSource.DEFAULT_USER_NAME + ":" + SecuritySettingsSource.DEFAULT_PASSWORD); - if (useSSL) { + if (useGeneratedSSL == false) { for (Map.Entry entry : cluster2SettingsSource.getClientSSLSettings().getAsMap().entrySet()) { builder.put("xpack.security.audit.index.client." + entry.getKey(), entry.getValue()); } - } else { - builder.put("xpack.security.audit.index.client.xpack.ssl.client_authentication", "none"); + } + if (useSecurity == false && builder.get(NetworkModule.TRANSPORT_TYPE_KEY) == null) { + builder.put("xpack.security.audit.index.client." + NetworkModule.TRANSPORT_TYPE_KEY, + MockTcpTransportPlugin.MOCK_TCP_TRANSPORT_NAME); } remoteSettings = builder.build(); } @@ -283,6 +303,11 @@ public class IndexAuditTrailTests extends SecurityIntegTestCase { enqueuedMessage.set(message); super.enqueue(message, type); } + + @Override + List> remoteTransportClientPlugins() { + return Arrays.asList(XPackPlugin.class, MockTcpTransportPlugin.class); + } }; auditor.start(true); } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/audit/index/RemoteIndexAuditTrailStartingTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/audit/index/RemoteIndexAuditTrailStartingTests.java index aa98c451f56..680e03053c4 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/audit/index/RemoteIndexAuditTrailStartingTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/audit/index/RemoteIndexAuditTrailStartingTests.java @@ -34,7 +34,6 @@ import java.util.stream.StreamSupport; import static org.elasticsearch.test.InternalTestCluster.clusterName; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoTimeout; -import static org.hamcrest.Matchers.is; /** * This test checks to ensure that the IndexAuditTrail starts properly when indexing to a remote cluster. The cluster @@ -50,13 +49,13 @@ public class RemoteIndexAuditTrailStartingTests extends SecurityIntegTestCase { private InternalTestCluster remoteCluster; - private final boolean useSSL = randomBoolean(); + private final boolean useGeneratedSSL = randomBoolean(); private final boolean localAudit = randomBoolean(); private final String outputs = randomFrom("index", "logfile", "index,logfile"); @Override - public boolean sslTransportEnabled() { - return useSSL; + public boolean useGeneratedSSLConfig() { + return useGeneratedSSL; } @Override @@ -90,7 +89,7 @@ public class RemoteIndexAuditTrailStartingTests extends SecurityIntegTestCase { // Setup a second test cluster with a single node, security enabled, and SSL final int numNodes = 1; SecuritySettingsSource cluster2SettingsSource = - new SecuritySettingsSource(numNodes, useSSL, systemKey(), createTempDir(), Scope.TEST) { + new SecuritySettingsSource(numNodes, useGeneratedSSL, systemKey(), createTempDir(), Scope.TEST) { @Override public Settings nodeSettings(int nodeOrdinal) { Settings.Builder builder = Settings.builder() diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/RunAsIntegTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/RunAsIntegTests.java index 5c279304d6d..7a9b03df17c 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/RunAsIntegTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/RunAsIntegTests.java @@ -48,11 +48,6 @@ public class RunAsIntegTests extends SecurityIntegTestCase { .build(); } - @Override - public boolean sslTransportEnabled() { - return false; - } - @Override public String configRoles() { return ROLES + super.configRoles(); @@ -72,6 +67,11 @@ public class RunAsIntegTests extends SecurityIntegTestCase { + "transport_client:" + TRANSPORT_CLIENT_USER; } + @Override + public boolean useGeneratedSSLConfig() { + return true; + } + public void testUserImpersonation() throws Exception { try (TransportClient client = getTransportClient(Settings.builder() .put(Security.USER_SETTING.getKey(), TRANSPORT_CLIENT_USER + ":" + SecuritySettingsSource.DEFAULT_PASSWORD).build())) { @@ -222,7 +222,6 @@ public class RunAsIntegTests extends SecurityIntegTestCase { Settings settings = Settings.builder() .put(extraSettings) .put("cluster.name", clusterName) - .put("xpack.security.transport.ssl.enabled", false) .build(); return new TestXPackTransportClient(settings) diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java index 325f68cf91e..05110ffcc52 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java @@ -49,8 +49,9 @@ public class ESNativeMigrateToolTests extends NativeRealmIntegTestCase { } @Override - protected boolean sslTransportEnabled() { - return useSSL; + protected boolean useGeneratedSSLConfig() { + // don't use autogenerated when we expect a different cert + return useSSL == false; } private Environment nodeEnvironment() throws Exception { diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmIntegTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmIntegTests.java index 14b91156f12..e2b70ab7df5 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmIntegTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmIntegTests.java @@ -55,7 +55,9 @@ public class ReservedRealmIntegTests extends NativeRealmIntegTestCase { assertThat(response.getClusterName(), is(cluster().getClusterName())); } - ChangePasswordResponse response = securityClient().prepareChangePassword(username, newPassword).get(); + ChangePasswordResponse response = securityClient() + .prepareChangePassword(username, Arrays.copyOf(newPassword, newPassword.length)) + .get(); assertThat(response, notNullValue()); ElasticsearchSecurityException elasticsearchSecurityException = expectThrows(ElasticsearchSecurityException.class, () -> client() diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/AbstractActiveDirectoryIntegTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/AbstractActiveDirectoryIntegTests.java index 33608d1b27f..bf50261d7cf 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/AbstractActiveDirectoryIntegTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/AbstractActiveDirectoryIntegTests.java @@ -30,7 +30,7 @@ public class AbstractActiveDirectoryIntegTests extends ESTestCase { @Before public void initializeSslSocketFactory() throws Exception { useGlobalSSL = randomBoolean(); - Path keystore = getDataPath("../ldap/support/ldaptrust.jks"); + Path truststore = getDataPath("../ldap/support/ldaptrust.jks"); /* * Prior to each test we reinitialize the socket factory with a new SSLService so that we get a new SSLContext. * If we re-use a SSLContext, previously connected sessions can get re-established which breaks hostname @@ -38,19 +38,19 @@ public class AbstractActiveDirectoryIntegTests extends ESTestCase { */ Settings.Builder builder = Settings.builder().put("path.home", createTempDir()); if (useGlobalSSL) { - builder.put("xpack.ssl.keystore.path", keystore) - .put("xpack.ssl.keystore.password", "changeit"); + builder.put("xpack.ssl.truststore.path", truststore) + .put("xpack.ssl.truststore.password", "changeit"); // fake realm to load config with certificate verification mode - builder.put("xpack.security.authc.realms.bar.ssl.keystore.path", keystore); - builder.put("xpack.security.authc.realms.bar.ssl.keystore.password", "changeit"); + builder.put("xpack.security.authc.realms.bar.ssl.truststore.path", truststore); + builder.put("xpack.security.authc.realms.bar.ssl.truststore.password", "changeit"); builder.put("xpack.security.authc.realms.bar.ssl.verification_mode", VerificationMode.CERTIFICATE); } else { // fake realms so ssl will get loaded - builder.put("xpack.security.authc.realms.foo.ssl.truststore.path", keystore); + builder.put("xpack.security.authc.realms.foo.ssl.truststore.path", truststore); builder.put("xpack.security.authc.realms.foo.ssl.truststore.password", "changeit"); builder.put("xpack.security.authc.realms.foo.ssl.verification_mode", VerificationMode.FULL); - builder.put("xpack.security.authc.realms.bar.ssl.truststore.path", keystore); + builder.put("xpack.security.authc.realms.bar.ssl.truststore.path", truststore); builder.put("xpack.security.authc.realms.bar.ssl.truststore.password", "changeit"); builder.put("xpack.security.authc.realms.bar.ssl.verification_mode", VerificationMode.CERTIFICATE); } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/GroupsResolverTestCase.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/GroupsResolverTestCase.java index ce61d5bf8f2..ac83b77a4c0 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/GroupsResolverTestCase.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/GroupsResolverTestCase.java @@ -43,24 +43,24 @@ public abstract class GroupsResolverTestCase extends ESTestCase { @Before public void setUpLdapConnection() throws Exception { - Path keystore = getDataPath("../ldap/support/ldaptrust.jks"); + Path truststore = getDataPath("../ldap/support/ldaptrust.jks"); boolean useGlobalSSL = randomBoolean(); Settings.Builder builder = Settings.builder().put("path.home", createTempDir()); if (useGlobalSSL) { - builder.put("xpack.ssl.keystore.path", keystore) - .put("xpack.ssl.keystore.password", "changeit"); + builder.put("xpack.ssl.truststore.path", truststore) + .put("xpack.ssl.truststore.password", "changeit"); // fake realm to load config with certificate verification mode - builder.put("xpack.security.authc.realms.bar.ssl.keystore.path", keystore); - builder.put("xpack.security.authc.realms.bar.ssl.keystore.password", "changeit"); + builder.put("xpack.security.authc.realms.bar.ssl.truststore.path", truststore); + builder.put("xpack.security.authc.realms.bar.ssl.truststore.password", "changeit"); builder.put("xpack.security.authc.realms.bar.ssl.verification_mode", VerificationMode.CERTIFICATE); } else { // fake realms so ssl will get loaded - builder.put("xpack.security.authc.realms.foo.ssl.keystore.path", keystore); - builder.put("xpack.security.authc.realms.foo.ssl.keystore.password", "changeit"); + builder.put("xpack.security.authc.realms.foo.ssl.truststore.path", truststore); + builder.put("xpack.security.authc.realms.foo.ssl.truststore.password", "changeit"); builder.put("xpack.security.authc.realms.foo.ssl.verification_mode", VerificationMode.FULL); - builder.put("xpack.security.authc.realms.bar.ssl.keystore.path", keystore); - builder.put("xpack.security.authc.realms.bar.ssl.keystore.password", "changeit"); + builder.put("xpack.security.authc.realms.bar.ssl.truststore.path", truststore); + builder.put("xpack.security.authc.realms.bar.ssl.truststore.password", "changeit"); builder.put("xpack.security.authc.realms.bar.ssl.verification_mode", VerificationMode.CERTIFICATE); } Settings settings = builder.build(); @@ -78,8 +78,8 @@ public abstract class GroupsResolverTestCase extends ESTestCase { if (useGlobalSSL) { connectionSettings = Settings.EMPTY; } else { - connectionSettings = Settings.builder().put("keystore.path", keystore) - .put("keystore.password", "changeit").build(); + connectionSettings = Settings.builder().put("truststore.path", truststore) + .put("truststore.password", "changeit").build(); } ldapConnection = LdapUtils.privilegedConnect(() -> new LDAPConnection(sslService.sslSocketFactory(connectionSettings), options, ldapurl.getHost(), ldapurl.getPort(), bindDN(), bindPassword())); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java index c3e6900805a..4cf82d3de2a 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java @@ -37,7 +37,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { private SSLService sslService; @Before - public void setup() { + public void setup() throws Exception { globalSettings = Settings.builder().put("path.home", createTempDir()).build(); sslService = new SSLService(globalSettings, new Environment(globalSettings)); } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java index 6096762cf7e..624f1b9f2bd 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java @@ -57,8 +57,8 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { */ globalSettings = Settings.builder() .put("path.home", createTempDir()) - .put("xpack.ssl.keystore.path", keystore) - .put("xpack.ssl.keystore.password", "changeit") + .put("xpack.ssl.truststore.path", keystore) + .put("xpack.ssl.truststore.password", "changeit") .build(); sslService = new SSLService(globalSettings, env); } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapTests.java index 24a49d9c01a..814fb6ccfd7 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapTests.java @@ -44,7 +44,7 @@ public class OpenLdapTests extends ESTestCase { @Before public void initializeSslSocketFactory() throws Exception { - Path keystore = getDataPath("../ldap/support/ldaptrust.jks"); + Path truststore = getDataPath("../ldap/support/ldaptrust.jks"); /* * Prior to each test we reinitialize the socket factory with a new SSLService so that we get a new SSLContext. * If we re-use a SSLContext, previously connected sessions can get re-established which breaks hostname @@ -53,19 +53,19 @@ public class OpenLdapTests extends ESTestCase { useGlobalSSL = randomBoolean(); Settings.Builder builder = Settings.builder().put("path.home", createTempDir()); if (useGlobalSSL) { - builder.put("xpack.ssl.keystore.path", keystore) - .put("xpack.ssl.keystore.password", "changeit"); + builder.put("xpack.ssl.truststore.path", truststore) + .put("xpack.ssl.truststore.password", "changeit"); // fake realm to load config with certificate verification mode - builder.put("xpack.security.authc.realms.bar.ssl.keystore.path", keystore); - builder.put("xpack.security.authc.realms.bar.ssl.keystore.password", "changeit"); + builder.put("xpack.security.authc.realms.bar.ssl.truststore.path", truststore); + builder.put("xpack.security.authc.realms.bar.ssl.truststore.password", "changeit"); builder.put("xpack.security.authc.realms.bar.ssl.verification_mode", VerificationMode.CERTIFICATE); } else { // fake realms so ssl will get loaded - builder.put("xpack.security.authc.realms.foo.ssl.truststore.path", keystore); + builder.put("xpack.security.authc.realms.foo.ssl.truststore.path", truststore); builder.put("xpack.security.authc.realms.foo.ssl.truststore.password", "changeit"); builder.put("xpack.security.authc.realms.foo.ssl.verification_mode", VerificationMode.FULL); - builder.put("xpack.security.authc.realms.bar.ssl.truststore.path", keystore); + builder.put("xpack.security.authc.realms.bar.ssl.truststore.path", truststore); builder.put("xpack.security.authc.realms.bar.ssl.truststore.password", "changeit"); builder.put("xpack.security.authc.realms.bar.ssl.verification_mode", VerificationMode.CERTIFICATE); } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java index f74888925b8..7202ca0635d 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java @@ -25,7 +25,7 @@ import static org.hamcrest.Matchers.is; public class SessionFactoryTests extends ESTestCase { - public void testConnectionFactoryReturnsCorrectLDAPConnectionOptionsWithDefaultSettings() { + public void testConnectionFactoryReturnsCorrectLDAPConnectionOptionsWithDefaultSettings() throws Exception { final Environment environment = new Environment(Settings.builder().put("path.home", createTempDir()).build()); RealmConfig realmConfig = new RealmConfig("conn settings", Settings.EMPTY, environment.settings(), environment); LDAPConnectionOptions options = SessionFactory.connectionOptions(realmConfig, new SSLService(environment.settings(), environment), @@ -37,7 +37,7 @@ public class SessionFactoryTests extends ESTestCase { assertThat(options.getSSLSocketVerifier(), is(instanceOf(HostNameSSLSocketVerifier.class))); } - public void testConnectionFactoryReturnsCorrectLDAPConnectionOptions() { + public void testConnectionFactoryReturnsCorrectLDAPConnectionOptions() throws Exception { Settings settings = Settings.builder() .put(SessionFactory.TIMEOUT_TCP_CONNECTION_SETTING, "10ms") .put(SessionFactory.HOSTNAME_VERIFICATION_SETTING, "false") diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthenticationTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthenticationTests.java index 896e0a48295..859804e0819 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthenticationTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthenticationTests.java @@ -69,8 +69,8 @@ public class PkiAuthenticationTests extends SecurityIntegTestCase { } @Override - protected boolean sslTransportEnabled() { - return true; + protected boolean useGeneratedSSLConfig() { + return false; } public void testTransportClientCanAuthenticateViaPki() { diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiOptionalClientAuthTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiOptionalClientAuthTests.java index b975d4af696..aacbf056706 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiOptionalClientAuthTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiOptionalClientAuthTests.java @@ -10,15 +10,10 @@ import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; import org.elasticsearch.client.Response; import org.elasticsearch.client.ResponseException; import org.elasticsearch.client.RestClient; -import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.test.SecuritySettingsSource; -import org.elasticsearch.transport.Transport; -import org.elasticsearch.xpack.TestXPackTransportClient; -import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.ssl.SSLClientAuth; @@ -27,15 +22,11 @@ import org.junit.BeforeClass; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import java.io.InputStream; -import java.net.InetAddress; import java.nio.file.Files; import java.nio.file.Path; import java.security.KeyStore; import java.security.SecureRandom; -import static org.elasticsearch.test.SecuritySettingsSource.DEFAULT_PASSWORD; -import static org.elasticsearch.test.SecuritySettingsSource.DEFAULT_USER_NAME; -import static org.elasticsearch.test.SecuritySettingsSource.getSSLSettingsForStore; import static org.hamcrest.Matchers.is; public class PkiOptionalClientAuthTests extends SecurityIntegTestCase { @@ -71,8 +62,8 @@ public class PkiOptionalClientAuthTests extends SecurityIntegTestCase { } @Override - protected boolean sslTransportEnabled() { - return true; + protected boolean useGeneratedSSLConfig() { + return false; } public void testRestClientWithoutClientCertificate() throws Exception { @@ -93,26 +84,6 @@ public class PkiOptionalClientAuthTests extends SecurityIntegTestCase { } } - public void testTransportClientWithoutClientCertificate() { - Transport transport = internalCluster().getDataNodeInstance(Transport.class); - int port = randomFrom(transport.profileBoundAddresses().get("want_client_auth").boundAddresses()).address().getPort(); - - Settings sslSettingsForStore = getSSLSettingsForStore - ("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/truststore-testnode-only.jks", "truststore-testnode-only"); - Settings settings = Settings.builder() - .put(sslSettingsForStore) - .put(Security.USER_SETTING.getKey(), DEFAULT_USER_NAME + ":" + DEFAULT_PASSWORD) - .put("cluster.name", internalCluster().getClusterName()) - .put("xpack.ssl.client_authentication", SSLClientAuth.REQUIRED) - .build(); - - - try (TransportClient client = new TestXPackTransportClient(settings)) { - client.addTransportAddress(new TransportAddress(InetAddress.getLoopbackAddress(), port)); - assertGreenClusterState(client); - } - } - private SSLContext getSSLContext() throws Exception { SSLContext sc = SSLContext.getInstance("TLSv1.2"); Path truststore = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/truststore-testnode-only.jks"); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiRealmTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiRealmTests.java index d19f839e16e..94b97f2aad6 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiRealmTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiRealmTests.java @@ -44,13 +44,12 @@ public class PkiRealmTests extends ESTestCase { private SSLService sslService; @Before - public void setup() { + public void setup() throws Exception { Path testnodeStore = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"); globalSettings = Settings.builder() .put("path.home", createTempDir()) .put("xpack.ssl.keystore.path", testnodeStore) .put("xpack.ssl.keystore.password", "testnode") - .put("xpack.security.transport.ssl.enabled", true) .build(); sslService = new SSLService(globalSettings, new Environment(globalSettings)); } @@ -217,7 +216,7 @@ public class PkiRealmTests extends ESTestCase { assertThat(e.getMessage(), containsString("has SSL with client authentication enabled")); } - public void testHttpClientAuthOnly() { + public void testHttpClientAuthOnly() throws Exception { Settings settings = Settings.builder() .put(globalSettings) .put("xpack.ssl.client_authentication", "none") @@ -228,18 +227,6 @@ public class PkiRealmTests extends ESTestCase { new SSLService(settings, new Environment(settings))); } - public void testNoSSLThrowsException() throws Exception { - Settings settings = Settings.builder() - .put(globalSettings) - .put("xpack.security.transport.ssl.enabled", false) - .build(); - - IllegalStateException e = expectThrows(IllegalStateException.class, - () -> new PkiRealm(new RealmConfig("", Settings.EMPTY, settings), mock(DnRoleMapper.class), - new SSLService(settings, new Environment(settings)))); - assertThat(e.getMessage(), containsString("has SSL with client authentication enabled")); - } - static X509Certificate readCert(Path path) throws Exception { try (InputStream in = Files.newInputStream(path)) { CertificateFactory factory = CertificateFactory.getInstance("X.509"); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterIntegrationTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterIntegrationTests.java index dcea74a3282..42bbe41a868 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterIntegrationTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterIntegrationTests.java @@ -42,9 +42,10 @@ public class ServerTransportFilterIntegrationTests extends SecurityIntegTestCase randomClientPort = randomIntBetween(49000, 65500); // ephemeral port } + // don't use it here to simplify the settings we need @Override - protected boolean sslTransportEnabled() { - return true; + public boolean useGeneratedSSLConfig() { + return false; } @Override @@ -60,11 +61,9 @@ public class ServerTransportFilterIntegrationTests extends SecurityIntegTestCase throw new RuntimeException(e); } - if (sslTransportEnabled()) { - settingsBuilder.put("transport.profiles.client.xpack.security.truststore.path", store) // settings for client truststore - .put("transport.profiles.client.xpack.security.truststore.password", "testnode") - .put("xpack.ssl.client_authentication", SSLClientAuth.REQUIRED); - } + settingsBuilder.put("transport.profiles.client.xpack.security.truststore.path", store) // settings for client truststore + .put("transport.profiles.client.xpack.security.truststore.password", "testnode") + .put("xpack.ssl.client_authentication", SSLClientAuth.REQUIRED); return settingsBuilder .put(super.nodeSettings(nodeOrdinal)) @@ -94,7 +93,6 @@ public class ServerTransportFilterIntegrationTests extends SecurityIntegTestCase .put("network.host", "localhost") .put("cluster.name", internalCluster().getClusterName()) .put("discovery.zen.ping.unicast.hosts", unicastHost) - .put("xpack.security.transport.ssl.enabled", sslTransportEnabled()) .put("xpack.security.audit.enabled", false) .put("path.home", home) .put(NetworkModule.HTTP_ENABLED.getKey(), false) @@ -123,7 +121,6 @@ public class ServerTransportFilterIntegrationTests extends SecurityIntegTestCase .put(Security.USER_SETTING.getKey(), "test_user:changeme") .put("cluster.name", internalCluster().getClusterName()) .put("discovery.zen.ping.unicast.hosts", "localhost:" + randomClientPort) - .put("xpack.security.transport.ssl.enabled", sslTransportEnabled()) .put("xpack.security.audit.enabled", false) .put(NetworkModule.HTTP_ENABLED.getKey(), false) .put("discovery.initial_state_timeout", "0s") diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/netty4/DNSOnlyHostnameVerificationTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/netty4/DNSOnlyHostnameVerificationTests.java index 6686be19815..5109c3ae7cf 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/netty4/DNSOnlyHostnameVerificationTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/netty4/DNSOnlyHostnameVerificationTests.java @@ -88,8 +88,8 @@ public class DNSOnlyHostnameVerificationTests extends SecurityIntegTestCase { } @Override - public boolean sslTransportEnabled() { - return true; + public boolean useGeneratedSSLConfig() { + return false; } @Override @@ -97,7 +97,6 @@ public class DNSOnlyHostnameVerificationTests extends SecurityIntegTestCase { Settings defaultSettings = super.nodeSettings(nodeOrdinal); Settings.Builder builder = Settings.builder() .put(defaultSettings.filter((s) -> s.startsWith("xpack.ssl.") == false)) - .put(XPackSettings.TRANSPORT_SSL_ENABLED.getKey(), true) .put("transport.host", hostName); String confPath = Environment.PATH_CONF_SETTING.get(defaultSettings); Path path = PathUtils.get(confPath).resolve("keystore.jks"); @@ -126,8 +125,7 @@ public class DNSOnlyHostnameVerificationTests extends SecurityIntegTestCase { public Settings transportClientSettings() { Settings defaultSettings = super.transportClientSettings(); Settings.Builder builder = Settings.builder() - .put(defaultSettings.filter((s) -> s.startsWith("xpack.ssl.") == false)) - .put(XPackSettings.TRANSPORT_SSL_ENABLED.getKey(), true); + .put(defaultSettings.filter((s) -> s.startsWith("xpack.ssl.") == false)); Path path = createTempDir().resolve("keystore.jks"); try (OutputStream os = Files.newOutputStream(path)) { keystore.store(os, "changeme".toCharArray()); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/netty4/IPHostnameVerificationTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/netty4/IPHostnameVerificationTests.java index 11e332c7f01..19ca290600f 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/netty4/IPHostnameVerificationTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/netty4/IPHostnameVerificationTests.java @@ -22,19 +22,15 @@ public class IPHostnameVerificationTests extends SecurityIntegTestCase { Path keystore; @Override - protected boolean sslTransportEnabled() { - return true; + protected boolean useGeneratedSSLConfig() { + return false; } @Override protected Settings nodeSettings(int nodeOrdinal) { Settings settings = super.nodeSettings(nodeOrdinal); - Settings.Builder builder = Settings.builder(); - for (Entry entry : settings.getAsMap().entrySet()) { - if (entry.getKey().startsWith("xpack.ssl.") == false) { - builder.put(entry.getKey(), entry.getValue()); - } - } + Settings.Builder builder = Settings.builder() + .put(settings.filter((s) -> s.startsWith("xpack.ssl.") == false)); settings = builder.build(); // The default Unicast test behavior is to use 'localhost' with the port number. For this test we need to use IP diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4TransportTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4TransportTests.java index 94a520b1512..f2e28aeb4a5 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4TransportTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4TransportTests.java @@ -17,7 +17,6 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.netty4.Netty4MockUtil; -import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.xpack.ssl.SSLClientAuth; import org.elasticsearch.xpack.ssl.SSLService; import org.junit.Before; @@ -26,10 +25,8 @@ import javax.net.ssl.SSLEngine; import java.nio.file.Path; import java.util.Locale; -import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; import static org.mockito.Mockito.mock; public class SecurityNetty4TransportTests extends ESTestCase { @@ -49,14 +46,13 @@ public class SecurityNetty4TransportTests extends ESTestCase { sslService = new SSLService(settings, env); } - private SecurityNetty4Transport createTransport(boolean sslEnabled) { - return createTransport(sslEnabled, Settings.EMPTY); + private SecurityNetty4Transport createTransport() { + return createTransport(Settings.EMPTY); } - private SecurityNetty4Transport createTransport(boolean sslEnabled, Settings additionalSettings) { + private SecurityNetty4Transport createTransport(Settings additionalSettings) { final Settings settings = Settings.builder() - .put(XPackSettings.TRANSPORT_SSL_ENABLED.getKey(), sslEnabled) .put(additionalSettings) .build(); return new SecurityNetty4Transport( @@ -70,26 +66,8 @@ public class SecurityNetty4TransportTests extends ESTestCase { sslService); } - public void testThatSSLCanBeDisabledByProfile() throws Exception { - SecurityNetty4Transport transport = createTransport(true); - Netty4MockUtil.setOpenChannelsHandlerToMock(transport); - ChannelHandler handler = transport.getServerChannelInitializer("client", - Settings.builder().put("xpack.security.ssl.enabled", false).build()); - final EmbeddedChannel ch = new EmbeddedChannel(handler); - assertThat(ch.pipeline().get(SslHandler.class), nullValue()); - } - - public void testThatSSLCanBeEnabledByProfile() throws Exception { - SecurityNetty4Transport transport = createTransport(false); - Netty4MockUtil.setOpenChannelsHandlerToMock(transport); - ChannelHandler handler = transport.getServerChannelInitializer("client", - Settings.builder().put("xpack.security.ssl.enabled", true).build()); - final EmbeddedChannel ch = new EmbeddedChannel(handler); - assertThat(ch.pipeline().get(SslHandler.class), notNullValue()); - } - public void testThatProfileTakesDefaultSSLSetting() throws Exception { - SecurityNetty4Transport transport = createTransport(true); + SecurityNetty4Transport transport = createTransport(); Netty4MockUtil.setOpenChannelsHandlerToMock(transport); ChannelHandler handler = transport.getServerChannelInitializer("client", Settings.EMPTY); final EmbeddedChannel ch = new EmbeddedChannel(handler); @@ -97,7 +75,7 @@ public class SecurityNetty4TransportTests extends ESTestCase { } public void testDefaultClientAuth() throws Exception { - SecurityNetty4Transport transport = createTransport(true); + SecurityNetty4Transport transport = createTransport(); Netty4MockUtil.setOpenChannelsHandlerToMock(transport); ChannelHandler handler = transport.getServerChannelInitializer("client", Settings.EMPTY); final EmbeddedChannel ch = new EmbeddedChannel(handler); @@ -112,7 +90,7 @@ public class SecurityNetty4TransportTests extends ESTestCase { .put("xpack.ssl.client_authentication", value) .build(); sslService = new SSLService(settings, env); - SecurityNetty4Transport transport = createTransport(true, settings); + SecurityNetty4Transport transport = createTransport(settings); Netty4MockUtil.setOpenChannelsHandlerToMock(transport); ChannelHandler handler = transport.getServerChannelInitializer("client", Settings.EMPTY); final EmbeddedChannel ch = new EmbeddedChannel(handler); @@ -127,7 +105,7 @@ public class SecurityNetty4TransportTests extends ESTestCase { .put("xpack.ssl.client_authentication", value) .build(); sslService = new SSLService(settings, env); - SecurityNetty4Transport transport = createTransport(true, settings); + SecurityNetty4Transport transport = createTransport(settings); Netty4MockUtil.setOpenChannelsHandlerToMock(transport); ChannelHandler handler = transport.getServerChannelInitializer("client", Settings.EMPTY); final EmbeddedChannel ch = new EmbeddedChannel(handler); @@ -142,7 +120,7 @@ public class SecurityNetty4TransportTests extends ESTestCase { .put("xpack.ssl.client_authentication", value) .build(); sslService = new SSLService(settings, env); - SecurityNetty4Transport transport = createTransport(true, settings); + SecurityNetty4Transport transport = createTransport(settings); Netty4MockUtil.setOpenChannelsHandlerToMock(transport); ChannelHandler handler = transport.getServerChannelInitializer("client", Settings.EMPTY); final EmbeddedChannel ch = new EmbeddedChannel(handler); @@ -154,11 +132,10 @@ public class SecurityNetty4TransportTests extends ESTestCase { String value = randomFrom(SSLClientAuth.REQUIRED.name(), SSLClientAuth.REQUIRED.name().toLowerCase(Locale.ROOT)); Settings settings = Settings.builder() .put(env.settings()) - .put("xpack.security.transport.ssl.enabled", true) .put("transport.profiles.client.xpack.security.ssl.client_authentication", value) .build(); sslService = new SSLService(settings, env); - SecurityNetty4Transport transport = createTransport(true, settings); + SecurityNetty4Transport transport = createTransport(settings); Netty4MockUtil.setOpenChannelsHandlerToMock(transport); ChannelHandler handler = transport.getServerChannelInitializer("client", Settings.builder().put("xpack.security.ssl.client_authentication", value).build()); @@ -171,11 +148,10 @@ public class SecurityNetty4TransportTests extends ESTestCase { String value = randomFrom(SSLClientAuth.NONE.name(), SSLClientAuth.NONE.name().toLowerCase(Locale.ROOT)); Settings settings = Settings.builder() .put(env.settings()) - .put("xpack.security.transport.ssl.enabled", true) .put("transport.profiles.client.xpack.security.ssl.client_authentication", value) .build(); sslService = new SSLService(settings, env); - SecurityNetty4Transport transport = createTransport(true, settings); + SecurityNetty4Transport transport = createTransport(settings); Netty4MockUtil.setOpenChannelsHandlerToMock(transport); ChannelHandler handler = transport.getServerChannelInitializer("client", Settings.builder().put("xpack.security.ssl.client_authentication", value).build()); @@ -188,11 +164,10 @@ public class SecurityNetty4TransportTests extends ESTestCase { String value = randomFrom(SSLClientAuth.OPTIONAL.name(), SSLClientAuth.OPTIONAL.name().toLowerCase(Locale.ROOT)); Settings settings = Settings.builder() .put(env.settings()) - .put("xpack.security.transport.ssl.enabled", true) .put("transport.profiles.client.xpack.security.ssl.client_authentication", value) .build(); sslService = new SSLService(settings, env); - SecurityNetty4Transport transport = createTransport(true, settings); + SecurityNetty4Transport transport = createTransport(settings); Netty4MockUtil.setOpenChannelsHandlerToMock(transport); final ChannelHandler handler = transport.getServerChannelInitializer("client", Settings.builder().put("xpack.security.ssl.client_authentication", value).build()); @@ -201,56 +176,20 @@ public class SecurityNetty4TransportTests extends ESTestCase { assertThat(ch.pipeline().get(SslHandler.class).engine().getWantClientAuth(), is(true)); } - public void testThatExceptionIsThrownWhenConfiguredWithoutSslKey() throws Exception { - Settings settings = Settings.builder() - .put("xpack.ssl.truststore.path", - getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks")) - .put("xpack.ssl.truststore.password", "testnode") - .put(XPackSettings.TRANSPORT_SSL_ENABLED.getKey(), true) - .put("path.home", createTempDir()) - .build(); - env = new Environment(settings); - sslService = new SSLService(settings, env); - SecurityNetty4Transport transport = new SecurityNetty4Transport(settings, mock(ThreadPool.class), mock(NetworkService.class), - mock(BigArrays.class), mock(NamedWriteableRegistry.class), mock(CircuitBreakerService.class), null, sslService); - IllegalArgumentException e = expectThrows(IllegalArgumentException.class, - () -> transport.getServerChannelInitializer(randomAsciiOfLength(6), Settings.EMPTY)); - assertThat(e.getMessage(), containsString("key must be provided")); - } - - public void testNoExceptionWhenConfiguredWithoutSslKeySSLDisabled() throws Exception { - Settings settings = Settings.builder() - .put("xpack.ssl.truststore.path", - getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks")) - .put("xpack.ssl.truststore.password", "testnode") - .put(XPackSettings.TRANSPORT_SSL_ENABLED.getKey(), false) - .put("path.home", createTempDir()) - .build(); - env = new Environment(settings); - sslService = new SSLService(settings, env); - SecurityNetty4Transport transport = new SecurityNetty4Transport(settings, mock(ThreadPool.class), mock(NetworkService.class), - mock(BigArrays.class), mock(NamedWriteableRegistry.class), mock(CircuitBreakerService.class), null, sslService); - assertNotNull(transport.getServerChannelInitializer(randomAsciiOfLength(6), Settings.EMPTY)); - } - public void testTransportSSLOverridesGlobalSSL() throws Exception { - final boolean useGlobalKeystoreWithoutKey = randomBoolean(); Settings.Builder builder = Settings.builder() .put("xpack.security.transport.ssl.keystore.path", getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks")) .put("xpack.security.transport.ssl.keystore.password", "testnode") .put("xpack.security.transport.ssl.client_authentication", "none") - .put(XPackSettings.TRANSPORT_SSL_ENABLED.getKey(), true) + .put("xpack.ssl.truststore.path", + getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/truststore-testnode-only.jks")) + .put("xpack.ssl.truststore.password", "truststore-testnode-only") .put("path.home", createTempDir()); - if (useGlobalKeystoreWithoutKey) { - builder.put("xpack.ssl.keystore.path", - getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/truststore-testnode-only.jks")) - .put("xpack.ssl.keystore.password", "truststore-testnode-only"); - } Settings settings = builder.build(); env = new Environment(settings); sslService = new SSLService(settings, env); - SecurityNetty4Transport transport = createTransport(true, settings); + SecurityNetty4Transport transport = createTransport(settings); Netty4MockUtil.setOpenChannelsHandlerToMock(transport); final ChannelHandler handler = transport.getServerChannelInitializer("default", Settings.EMPTY); final EmbeddedChannel ch = new EmbeddedChannel(handler); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SslHostnameVerificationTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SslHostnameVerificationTests.java index cd63e710c88..98b629a0e18 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SslHostnameVerificationTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SslHostnameVerificationTests.java @@ -26,8 +26,8 @@ import static org.hamcrest.Matchers.containsString; public class SslHostnameVerificationTests extends SecurityIntegTestCase { @Override - protected boolean sslTransportEnabled() { - return true; + protected boolean useGeneratedSSLConfig() { + return false; } @Override diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslIntegrationTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslIntegrationTests.java index 5a611bb6380..33599c36f28 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslIntegrationTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslIntegrationTests.java @@ -52,8 +52,8 @@ public class SslIntegrationTests extends SecurityIntegTestCase { } @Override - protected boolean sslTransportEnabled() { - return true; + protected boolean useGeneratedSSLConfig() { + return false; } // no SSL exception as this is the exception is returned when connecting diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslMultiPortTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslMultiPortTests.java index f1f480c06df..8afc2264031 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslMultiPortTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslMultiPortTests.java @@ -30,13 +30,11 @@ import static org.hamcrest.Matchers.containsString; public class SslMultiPortTests extends SecurityIntegTestCase { private static int randomClientPort; - private static int randomNonSslPort; private static int randomNoClientAuthPort; @BeforeClass public static void getRandomPort() { randomClientPort = randomIntBetween(49000, 65500); // ephemeral port - randomNonSslPort = randomIntBetween(49000, 65500); randomNoClientAuthPort = randomIntBetween(49000, 65500); } @@ -46,13 +44,11 @@ public class SslMultiPortTests extends SecurityIntegTestCase { *
  • default: testnode keystore. Requires client auth
  • *
  • client: testnode-client-profile keystore that only trusts the testclient cert. Requires client auth
  • *
  • no_client_auth: testnode keystore. Does not require client auth
  • - *
  • no_ssl: plaintext transport profile
  • * */ @Override protected Settings nodeSettings(int nodeOrdinal) { String randomClientPortRange = randomClientPort + "-" + (randomClientPort+100); - String randomNonSslPortRange = randomNonSslPort + "-" + (randomNonSslPort+100); String randomNoClientAuthPortRange = randomNoClientAuthPort + "-" + (randomNoClientAuthPort+100); Path store; @@ -71,9 +67,6 @@ public class SslMultiPortTests extends SecurityIntegTestCase { .put("transport.profiles.client.bind_host", "localhost") .put("transport.profiles.client.xpack.security.ssl.truststore.path", store.toAbsolutePath()) .put("transport.profiles.client.xpack.security.ssl.truststore.password", "testnode-client-profile") - .put("transport.profiles.no_ssl.port", randomNonSslPortRange) - .put("transport.profiles.no_ssl.bind_host", "localhost") - .put("transport.profiles.no_ssl.xpack.security.ssl.enabled", "false") .put("transport.profiles.no_client_auth.port", randomNoClientAuthPortRange) .put("transport.profiles.no_client_auth.bind_host", "localhost") .put("transport.profiles.no_client_auth.xpack.security.ssl.client_authentication", SSLClientAuth.NONE) @@ -81,23 +74,13 @@ public class SslMultiPortTests extends SecurityIntegTestCase { } @Override - protected boolean sslTransportEnabled() { - return true; + protected boolean useGeneratedSSLConfig() { + return false; } private TransportClient createTransportClient(Settings additionalSettings) { - Settings clientSettings = transportClientSettings(); - if (additionalSettings.getByPrefix("xpack.ssl.").isEmpty() == false) { - Settings.Builder builder = Settings.builder(); - for (Entry entry : clientSettings.getAsMap().entrySet()) { - if (entry.getKey().startsWith("xpack.ssl.") == false) { - builder.put(entry.getKey(), entry.getValue()); - } - } - clientSettings = builder.build(); - } - - Settings settings = Settings.builder().put(clientSettings) + Settings settings = Settings.builder() + .put(transportClientSettings().filter(s -> s.startsWith("xpack.ssl") == false)) .put("node.name", "programmatic_transport_client") .put("cluster.name", internalCluster().getClusterName()) .put(additionalSettings) @@ -121,7 +104,11 @@ public class SslMultiPortTests extends SecurityIntegTestCase { * disabling the client auth requirement */ public void testThatStandardTransportClientCanConnectToNoClientAuthProfile() throws Exception { - try(TransportClient transportClient = createTransportClient(Settings.EMPTY)) { + try(TransportClient transportClient = new TestXPackTransportClient(Settings.builder() + .put(transportClientSettings()) + .put("node.name", "programmatic_transport_client") + .put("cluster.name", internalCluster().getClusterName()) + .build())) { transportClient.addTransportAddress(new TransportAddress(InetAddress.getLoopbackAddress(), getProfilePort("no_client_auth"))); assertGreenClusterState(transportClient); @@ -145,21 +132,6 @@ public class SslMultiPortTests extends SecurityIntegTestCase { } } - /** - * Uses a transport client with the same settings as the internal cluster transport client to test connection to the - * no_ssl profile. The internal transport client is not used here since we are connecting to a different - * profile. The no_ssl profile is plain text and the standard transport client uses SSL, so a connection will never work - */ - public void testThatStandardTransportClientCannotConnectToNoSslProfile() throws Exception { - try (TransportClient transportClient = createTransportClient(Settings.EMPTY)) { - transportClient.addTransportAddress(new TransportAddress(InetAddress.getLoopbackAddress(), getProfilePort("no_ssl"))); - assertGreenClusterState(transportClient); - fail("Expected NoNodeAvailableException"); - } catch (NoNodeAvailableException e) { - assertThat(e.getMessage(), containsString("None of the configured nodes are available: [{#transport#-")); - } - } - /** * Uses a transport client with a custom keystore; this keystore testclient-client-profile.jks trusts the testnode * certificate and had its own self signed certificate. This test connects to the client profile, which is only @@ -209,38 +181,6 @@ public class SslMultiPortTests extends SecurityIntegTestCase { } } - /** - * Uses a transport client with a custom keystore; this keystore testclient-client-profile.jks trusts the testnode - * certificate and had its own self signed certificate. This test connects to the no_ssl profile, which does not - * use SSL so the connection will never work - */ - public void testThatProfileTransportClientCannotConnectToNoSslProfile() throws Exception { - Settings settings = getSSLSettingsForStore( - "/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient-client-profile.jks", "testclient-client-profile"); - try (TransportClient transportClient = createTransportClient(settings)) { - transportClient.addTransportAddress(new TransportAddress(InetAddress.getLoopbackAddress(), getProfilePort("no_ssl"))); - transportClient.admin().cluster().prepareHealth().get(); - fail("Expected NoNodeAvailableException"); - } catch (NoNodeAvailableException e) { - assertThat(e.getMessage(), containsString("None of the configured nodes are available: [{#transport#-")); - } - } - - /** - * Uses a transport client with SSL disabled. This test connects to the no_ssl profile, which should always succeed - */ - public void testThatTransportClientCanConnectToNoSslProfile() throws Exception { - Settings settings = Settings.builder() - .put(Security.USER_SETTING.getKey(), DEFAULT_USER_NAME + ":" + DEFAULT_PASSWORD) - .put("xpack.security.transport.ssl.enabled", false) - .put("cluster.name", internalCluster().getClusterName()) - .build(); - try (TransportClient transportClient = new TestXPackTransportClient(settings)) { - transportClient.addTransportAddress(new TransportAddress(InetAddress.getLoopbackAddress(), getProfilePort("no_ssl"))); - assertGreenClusterState(transportClient); - } - } - /** * Uses a transport client with SSL disabled. This test connects to the default profile, which should always fail * as a non-ssl transport client cannot connect to a ssl profile @@ -305,7 +245,6 @@ public class SslMultiPortTests extends SecurityIntegTestCase { Settings settings = Settings.builder() .put(Security.USER_SETTING.getKey(), DEFAULT_USER_NAME + ":" + DEFAULT_PASSWORD) .put("cluster.name", internalCluster().getClusterName()) - .put("xpack.security.transport.ssl.enabled", true) .put("xpack.ssl.truststore.path", getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/truststore-testnode-only.jks")) .put("xpack.ssl.truststore.password", "truststore-testnode-only") @@ -365,29 +304,6 @@ public class SslMultiPortTests extends SecurityIntegTestCase { } } - /** - * Uses a transport client with a custom truststore; this truststore truststore-testnode-only only trusts the testnode - * certificate and contains no other certification. This test connects to the no_ssl profile, which does not use - * SSL so the connection should never succeed - */ - public void testThatTransportClientWithOnlyTruststoreCannotConnectToNoSslProfile() throws Exception { - Settings settings = Settings.builder() - .put(Security.USER_SETTING.getKey(), DEFAULT_USER_NAME + ":" + DEFAULT_PASSWORD) - .put("cluster.name", internalCluster().getClusterName()) - .put("xpack.security.transport.ssl.enabled", true) - .put("xpack.ssl.truststore.path", - getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/truststore-testnode-only.jks")) - .put("xpack.ssl.truststore.password", "truststore-testnode-only") - .build(); - try (TransportClient transportClient = new TestXPackTransportClient(settings)) { - transportClient.addTransportAddress(new TransportAddress(InetAddress.getLoopbackAddress(), getProfilePort("no_ssl"))); - assertGreenClusterState(transportClient); - fail("Expected NoNodeAvailableException"); - } catch (NoNodeAvailableException e) { - assertThat(e.getMessage(), containsString("None of the configured nodes are available: [{#transport#-")); - } - } - /** * Uses a transport client with the default JDK truststore; this truststore only trusts the known good public * certificate authorities. This test connects to the default profile, which uses a self-signed certificate that diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslNullCipherTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslNullCipherTests.java new file mode 100644 index 00000000000..4ea92016ed3 --- /dev/null +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslNullCipherTests.java @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.security.transport.ssl; + +import org.elasticsearch.action.DocWriteResponse.Result; +import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.test.SecurityIntegTestCase; + +/** + * An extremely simple test that shows SSL will work with a cipher that does not perform encryption + */ +public class SslNullCipherTests extends SecurityIntegTestCase { + + @Override + public boolean useGeneratedSSLConfig() { + return true; + } + + @Override + public Settings nodeSettings(int nodeOrdinal) { + Settings settings = super.nodeSettings(nodeOrdinal); + Settings.Builder builder = Settings.builder() + .put(settings.filter((s) -> s.startsWith("xpack.ssl") == false)); + builder.put("xpack.security.transport.ssl.cipher_suites", "TLS_RSA_WITH_NULL_SHA256"); + return builder.build(); + } + + @Override + public Settings transportClientSettings() { + Settings settings = super.transportClientSettings(); + Settings.Builder builder = Settings.builder() + .put(settings.filter((s) -> s.startsWith("xpack.ssl") == false)); + + builder.put("xpack.security.transport.ssl.cipher_suites", "TLS_RSA_WITH_NULL_SHA256"); + return builder.build(); + } + + public void testClusterIsFormed() { + ensureGreen(); + Client client = internalCluster().transportClient(); + IndexResponse response = client.prepareIndex("index", "type").setSource("foo", "bar").get(); + assertEquals(Result.CREATED, response.getResult()); + } +} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/GeneratedKeyConfigTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/GeneratedKeyConfigTests.java new file mode 100644 index 00000000000..d9803315bdc --- /dev/null +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/GeneratedKeyConfigTests.java @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.ssl; + +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.node.Node; +import org.elasticsearch.test.ESTestCase; + +import javax.net.ssl.X509ExtendedKeyManager; + +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; + +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + +public class GeneratedKeyConfigTests extends ESTestCase { + + public void testGenerating() throws Exception { + Settings settings = Settings.builder().put(Node.NODE_NAME_SETTING.getKey(), randomAsciiOfLengthBetween(1, 8)).build(); + GeneratedKeyConfig keyConfig = new GeneratedKeyConfig(settings); + assertThat(keyConfig.filesToMonitor(null), is(empty())); + X509ExtendedKeyManager keyManager = keyConfig.createKeyManager(null); + assertNotNull(keyManager); + assertNotNull(keyConfig.createTrustManager(null)); + + String[] aliases = keyManager.getServerAliases("RSA", null); + assertEquals(1, aliases.length); + PrivateKey privateKey = keyManager.getPrivateKey(aliases[0]); + assertNotNull(privateKey); + assertThat(privateKey, instanceOf(RSAPrivateKey.class)); + X509Certificate[] certificates = keyManager.getCertificateChain(aliases[0]); + assertEquals(2, certificates.length); + assertEquals(GeneratedKeyConfig.readCACert(), certificates[1]); + + X509Certificate generatedCertificate = certificates[0]; + assertEquals("CN=" + Node.NODE_NAME_SETTING.get(settings), generatedCertificate.getSubjectX500Principal().getName()); + assertEquals(certificates[1].getSubjectX500Principal(), generatedCertificate.getIssuerX500Principal()); + } +} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLBootstrapCheckTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLBootstrapCheckTests.java new file mode 100644 index 00000000000..470308e0c7f --- /dev/null +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLBootstrapCheckTests.java @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.ssl; + +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.env.Environment; +import org.elasticsearch.test.ESTestCase; + +public class SSLBootstrapCheckTests extends ESTestCase { + + public void testSSLBootstrapCheckWithNoKey() throws Exception { + SSLService sslService = new SSLService(Settings.EMPTY, null); + SSLBootstrapCheck bootstrapCheck = new SSLBootstrapCheck(sslService, Settings.EMPTY, null); + assertTrue(bootstrapCheck.check()); + } + + public void testSSLBootstrapCheckWithKey() throws Exception { + final String keyPrefix = randomBoolean() ? "security.transport." : ""; + Settings settings = Settings.builder() + .put("path.home", createTempDir()) + .put("xpack." + keyPrefix + "ssl.key", + getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.pem")) + .put("xpack." + keyPrefix + "ssl.key_passphrase", "testclient") + .put("xpack." + keyPrefix + "ssl.certificate", + getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt")) + .build(); + final Environment env = randomBoolean() ? new Environment(settings) : null; + SSLBootstrapCheck bootstrapCheck = new SSLBootstrapCheck(new SSLService(settings, env), settings, env); + assertFalse(bootstrapCheck.check()); + } + + public void testSSLBootstrapCheckWithDefaultCABeingTrusted() throws Exception { + final String keyPrefix = randomBoolean() ? "security.transport." : ""; + Settings settings = Settings.builder() + .put("path.home", createTempDir()) + .put("xpack." + keyPrefix + "ssl.key", + getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.pem")) + .put("xpack." + keyPrefix + "ssl.key_passphrase", "testclient") + .put("xpack." + keyPrefix + "ssl.certificate", + getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt")) + .putArray("xpack." + keyPrefix + "ssl.certificate_authorities", + getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt").toString(), + getDataPath("/org/elasticsearch/xpack/ssl/ca.pem").toString()) + .build(); + final Environment env = randomBoolean() ? new Environment(settings) : null; + SSLBootstrapCheck bootstrapCheck = new SSLBootstrapCheck(new SSLService(settings, env), settings, env); + assertTrue(bootstrapCheck.check()); + + settings = Settings.builder().put(settings.filter((s) -> s.contains(".certificate_authorities"))) + .put("xpack.security.http.ssl.certificate_authorities", + getDataPath("/org/elasticsearch/xpack/ssl/ca.pem").toString()) + .build(); + bootstrapCheck = new SSLBootstrapCheck(new SSLService(settings, env), settings, env); + assertTrue(bootstrapCheck.check()); + } + + public void testSSLBootstrapCheckWithDefaultKeyBeingUsed() throws Exception { + final String keyPrefix = randomBoolean() ? "security.transport." : ""; + Settings settings = Settings.builder() + .put("path.home", createTempDir()) + .put("xpack." + keyPrefix + "ssl.key", + getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.pem")) + .put("xpack." + keyPrefix + "ssl.key_passphrase", "testclient") + .put("xpack." + keyPrefix + "ssl.certificate", + getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt")) + .put("xpack.security.http.ssl.key", getDataPath("/org/elasticsearch/xpack/ssl/private.pem").toString()) + .put("xpack.security.http.ssl.certificate", getDataPath("/org/elasticsearch/xpack/ssl/ca.pem").toString()) + .build(); + final Environment env = randomBoolean() ? new Environment(settings) : null; + SSLBootstrapCheck bootstrapCheck = new SSLBootstrapCheck(new SSLService(settings, env), settings, env); + assertTrue(bootstrapCheck.check()); + + settings = Settings.builder().put(settings.filter((s) -> s.contains(".http.ssl."))) + .put("xpack.security.transport.profiles.foo.xpack.security.ssl.key", + getDataPath("/org/elasticsearch/xpack/ssl/private.pem").toString()) + .put("xpack.security.transport.profiles.foo.xpack.security.ssl.certificate", + getDataPath("/org/elasticsearch/xpack/ssl/ca.pem").toString()) + .build(); + bootstrapCheck = new SSLBootstrapCheck(new SSLService(settings, env), settings, env); + assertTrue(bootstrapCheck.check()); + } +} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLClientAuthTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLClientAuthTests.java index 285eff35b3f..f6582095273 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLClientAuthTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLClientAuthTests.java @@ -55,8 +55,8 @@ public class SSLClientAuthTests extends SecurityIntegTestCase { } @Override - protected boolean sslTransportEnabled() { - return true; + protected boolean useGeneratedSSLConfig() { + return false; } public void testThatHttpFailsWithoutSslClientAuth() throws IOException { @@ -90,7 +90,6 @@ public class SSLClientAuthTests extends SecurityIntegTestCase { } Settings settings = Settings.builder() - .put("xpack.security.transport.ssl.enabled", true) .put("xpack.ssl.client_authentication", SSLClientAuth.NONE) .put("xpack.ssl.keystore.path", store) .put("xpack.ssl.keystore.password", "testclient-client-profile") diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLConfigurationReloaderTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLConfigurationReloaderTests.java index bf72385993d..ee77aa0a298 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLConfigurationReloaderTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLConfigurationReloaderTests.java @@ -19,6 +19,7 @@ import org.junit.Before; import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedTrustManager; +import javax.security.auth.x500.X500Principal; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -34,8 +35,9 @@ import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.concurrent.CountDownLatch; -import java.util.function.BiFunction; +import java.util.function.BiConsumer; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; @@ -81,21 +83,19 @@ public class SSLConfigurationReloaderTests extends ESTestCase { .build(); final Environment env = randomBoolean() ? null : new Environment(settings); - final BiFunction keyManagerPreChecks = (keyManager, config) -> { + final BiConsumer keyManagerPreChecks = (keyManager, config) -> { // key manager checks String[] aliases = keyManager.getServerAliases("RSA", null); assertNotNull(aliases); assertThat(aliases.length, is(1)); assertThat(aliases[0], is("testnode")); - return null; }; final SetOnce trustedCount = new SetOnce<>(); - final BiFunction trustManagerPreChecks = (trustManager, config) -> { + final BiConsumer trustManagerPreChecks = (trustManager, config) -> { // trust manager checks Certificate[] certificates = trustManager.getAcceptedIssuers(); trustedCount.set(certificates.length); - return null; }; final Runnable modifier = () -> { @@ -103,6 +103,10 @@ public class SSLConfigurationReloaderTests extends ESTestCase { // modify it KeyStore keyStore = KeyStore.getInstance("jks"); keyStore.load(null, null); + final KeyPair keyPair = CertUtils.generateKeyPair(512); + X509Certificate cert = CertUtils.generateSignedCertificate(new X500Principal("CN=testReloadingKeyStore"), null, keyPair, + null, null, 365); + keyStore.setKeyEntry("key", keyPair.getPrivate(), "testnode".toCharArray(), new X509Certificate[] { cert }); Path updated = tempDir.resolve("updated.jks"); try (OutputStream out = Files.newOutputStream(updated)) { keyStore.store(out, "testnode".toCharArray()); @@ -113,11 +117,16 @@ public class SSLConfigurationReloaderTests extends ESTestCase { } }; - final BiFunction trustManagerPostChecks = (updatedTrustManager, config) -> { - assertThat(trustedCount.get() - updatedTrustManager.getAcceptedIssuers().length, is(5)); - return null; + final BiConsumer keyManagerPostChecks = (updatedKeyManager, config) -> { + String[] aliases = updatedKeyManager.getServerAliases("RSA", null); + assertNotNull(aliases); + assertThat(aliases.length, is(1)); + assertThat(aliases[0], is("key")); }; - validateSSLConfigurationIsReloaded(settings, env, keyManagerPreChecks, trustManagerPreChecks, modifier, (k, c) -> null, + final BiConsumer trustManagerPostChecks = (updatedTrustManager, config) -> { + assertThat(trustedCount.get() - updatedTrustManager.getAcceptedIssuers().length, is(4)); + }; + validateSSLConfigurationIsReloaded(settings, env, keyManagerPreChecks, trustManagerPreChecks, modifier, keyManagerPostChecks, trustManagerPostChecks); } @@ -144,14 +153,13 @@ public class SSLConfigurationReloaderTests extends ESTestCase { new Environment(Settings.builder().put("path.home", createTempDir()).build()); final SetOnce privateKey = new SetOnce<>(); - final BiFunction keyManagerPreChecks = (keyManager, config) -> { + final BiConsumer keyManagerPreChecks = (keyManager, config) -> { String[] aliases = keyManager.getServerAliases("RSA", null); assertNotNull(aliases); assertThat(aliases.length, is(1)); assertThat(aliases[0], is("key")); privateKey.set(keyManager.getPrivateKey("key")); assertNotNull(privateKey.get()); - return null; }; final KeyPair keyPair = CertUtils.generateKeyPair(randomFrom(1024, 2048)); @@ -180,14 +188,13 @@ public class SSLConfigurationReloaderTests extends ESTestCase { } }; - final BiFunction keyManagerPostChecks = (keyManager, config) -> { + final BiConsumer keyManagerPostChecks = (keyManager, config) -> { String[] aliases = keyManager.getServerAliases("RSA", null); assertNotNull(aliases); assertThat(aliases.length, is(1)); assertThat(aliases[0], is("key")); assertThat(keyManager.getPrivateKey(aliases[0]), not(equalTo(privateKey))); assertThat(keyManager.getPrivateKey(aliases[0]), is(equalTo(keyPair.getPrivate()))); - return null; }; validateKeyConfigurationIsReloaded(settings, env, keyManagerPreChecks, modifier, keyManagerPostChecks); } @@ -205,13 +212,15 @@ public class SSLConfigurationReloaderTests extends ESTestCase { .put("path.home", createTempDir()) .build(); Environment env = randomBoolean() ? null : new Environment(settings); + final X500Principal expectedPrincipal = new X500Principal("CN=xpack public development ca"); final SetOnce trustedCount = new SetOnce<>(); - final BiFunction trustManagerPreChecks = (trustManager, config) -> { + final BiConsumer trustManagerPreChecks = (trustManager, config) -> { // trust manager checks Certificate[] certificates = trustManager.getAcceptedIssuers(); trustedCount.set(certificates.length); - return null; + assertTrue(Arrays.stream(trustManager.getAcceptedIssuers()) + .anyMatch((cert) -> expectedPrincipal.equals(cert.getSubjectX500Principal()))); }; @@ -229,9 +238,10 @@ public class SSLConfigurationReloaderTests extends ESTestCase { } }; - final BiFunction trustManagerPostChecks = (updatedTrustManager, config) -> { + final BiConsumer trustManagerPostChecks = (updatedTrustManager, config) -> { assertThat(trustedCount.get() - updatedTrustManager.getAcceptedIssuers().length, is(5)); - return null; + assertTrue(Arrays.stream(updatedTrustManager.getAcceptedIssuers()) + .anyMatch((cert) -> expectedPrincipal.equals(cert.getSubjectX500Principal()))); }; validateTrustConfigurationIsReloaded(settings, env, trustManagerPreChecks, modifier, trustManagerPostChecks); @@ -250,13 +260,15 @@ public class SSLConfigurationReloaderTests extends ESTestCase { .put("path.home", createTempDir()) .build(); Environment env = randomBoolean() ? null : new Environment(settings); + final X500Principal expectedPrincipal = new X500Principal("CN=xpack public development ca"); - final BiFunction trustManagerPreChecks = (trustManager, config) -> { + final BiConsumer trustManagerPreChecks = (trustManager, config) -> { // trust manager checks Certificate[] certificates = trustManager.getAcceptedIssuers(); - assertThat(certificates.length, is(1)); + assertThat(certificates.length, is(2)); assertThat(((X509Certificate)certificates[0]).getSubjectX500Principal().getName(), containsString("Test Client")); - return null; + assertTrue(Arrays.stream(trustManager.getAcceptedIssuers()) + .anyMatch((cert) -> expectedPrincipal.equals(cert.getSubjectX500Principal()))); }; final Runnable modifier = () -> { @@ -270,11 +282,12 @@ public class SSLConfigurationReloaderTests extends ESTestCase { } }; - final BiFunction trustManagerPostChecks = (updatedTrustManager, config) -> { + final BiConsumer trustManagerPostChecks = (updatedTrustManager, config) -> { Certificate[] updatedCerts = updatedTrustManager.getAcceptedIssuers(); - assertThat(updatedCerts.length, is(1)); + assertThat(updatedCerts.length, is(2)); assertThat(((X509Certificate)updatedCerts[0]).getSubjectX500Principal().getName(), containsString("Test Node")); - return null; + assertTrue(Arrays.stream(updatedTrustManager.getAcceptedIssuers()) + .anyMatch((cert) -> expectedPrincipal.equals(cert.getSubjectX500Principal()))); }; validateTrustConfigurationIsReloaded(settings, env, trustManagerPreChecks, modifier, trustManagerPostChecks); @@ -425,9 +438,9 @@ public class SSLConfigurationReloaderTests extends ESTestCase { * Validates the trust configuration aspect of the SSLConfiguration is reloaded */ private void validateTrustConfigurationIsReloaded(Settings settings, Environment env, - BiFunction trustManagerPreChecks, + BiConsumer trustManagerPreChecks, Runnable modificationFunction, - BiFunction trustManagerPostChecks) + BiConsumer trustManagerPostChecks) throws Exception { validateSSLConfigurationIsReloaded(settings, env, false, true, null, trustManagerPreChecks, modificationFunction, null, trustManagerPostChecks); @@ -437,10 +450,10 @@ public class SSLConfigurationReloaderTests extends ESTestCase { * Validates the trust configuration aspect of the SSLConfiguration is reloaded */ private void validateKeyConfigurationIsReloaded(Settings settings, Environment env, - BiFunction keyManagerPreChecks, - Runnable modificationFunction, - BiFunction keyManagerPostChecks) - throws Exception { + BiConsumer keyManagerPreChecks, + Runnable modificationFunction, + BiConsumer keyManagerPostChecks) + throws Exception { validateSSLConfigurationIsReloaded(settings, env, true, false, keyManagerPreChecks, null, modificationFunction, keyManagerPostChecks, null); } @@ -449,22 +462,22 @@ public class SSLConfigurationReloaderTests extends ESTestCase { * Validates that both the key and trust configuration aspects of the SSLConfiguration are reloaded */ private void validateSSLConfigurationIsReloaded(Settings settings, Environment env, - BiFunction keyManagerPreChecks, - BiFunction trustManagerPreChecks, + BiConsumer keyManagerPreChecks, + BiConsumer trustManagerPreChecks, Runnable modificationFunction, - BiFunction keyManagerPostChecks, - BiFunction trustManagerPostChecks) + BiConsumer keyManagerPostChecks, + BiConsumer trustManagerPostChecks) throws Exception { validateSSLConfigurationIsReloaded(settings, env, true, true, keyManagerPreChecks, trustManagerPreChecks, modificationFunction, keyManagerPostChecks, trustManagerPostChecks); } private void validateSSLConfigurationIsReloaded(Settings settings, Environment env, boolean checkKeys, boolean checkTrust, - BiFunction keyManagerPreChecks, - BiFunction trustManagerPreChecks, + BiConsumer keyManagerPreChecks, + BiConsumer trustManagerPreChecks, Runnable modificationFunction, - BiFunction keyManagerPostChecks, - BiFunction trustManagerPostChecks) + BiConsumer keyManagerPostChecks, + BiConsumer trustManagerPostChecks) throws Exception { final CountDownLatch reloadLatch = new CountDownLatch(1); @@ -494,12 +507,12 @@ public class SSLConfigurationReloaderTests extends ESTestCase { // key manager checks if (checkKeys) { - keyManagerPreChecks.apply(keyManager, config); + keyManagerPreChecks.accept(keyManager, config); } // trust manager checks if (checkTrust) { - trustManagerPreChecks.apply(trustManager, config); + trustManagerPreChecks.accept(trustManager, config); } assertEquals("nothing should have called reload", 1, reloadLatch.getCount()); @@ -512,14 +525,14 @@ public class SSLConfigurationReloaderTests extends ESTestCase { if (checkKeys) { final X509ExtendedKeyManager updatedKeyManager = sslService.sslContextHolder(config).keyManager().getKeyManager(); assertThat(updatedKeyManager, not(sameInstance(keyManager))); - keyManagerPostChecks.apply(updatedKeyManager, config); + keyManagerPostChecks.accept(updatedKeyManager, config); } // check trust manager if (checkTrust) { final X509ExtendedTrustManager updatedTrustManager = sslService.sslContextHolder(config).trustManager().getTrustManager(); assertThat(updatedTrustManager, not(sameInstance(trustManager))); - trustManagerPostChecks.apply(updatedTrustManager, config); + trustManagerPostChecks.accept(updatedTrustManager, config); } } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLConfigurationTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLConfigurationTests.java index a07054e610c..7f6babcda43 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLConfigurationTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLConfigurationTests.java @@ -7,8 +7,8 @@ package org.elasticsearch.xpack.ssl; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; -import org.elasticsearch.xpack.ssl.DefaultJDKTrustConfig.CombiningTrustConfig; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.ssl.TrustConfig.CombiningTrustConfig; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLReloadIntegTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLReloadIntegTests.java index b14a0f4ab6e..5b55cae9d3a 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLReloadIntegTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLReloadIntegTests.java @@ -42,7 +42,6 @@ import java.security.KeyStore; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.Locale; -import java.util.Map.Entry; import java.util.concurrent.CountDownLatch; import static org.hamcrest.Matchers.containsString; @@ -68,12 +67,8 @@ public class SSLReloadIntegTests extends SecurityIntegTestCase { } } Settings settings = super.nodeSettings(nodeOrdinal); - Settings.Builder builder = Settings.builder(); - for (Entry entry : settings.getAsMap().entrySet()) { - if (entry.getKey().startsWith("xpack.ssl.") == false) { - builder.put(entry.getKey(), entry.getValue()); - } - } + Settings.Builder builder = Settings.builder() + .put(settings.filter((s) -> s.startsWith("xpack.ssl.") == false)); builder.put("resource.reload.interval.high", "1s") .put(SecuritySettingsSource.getSSLSettingsForStore( @@ -88,8 +83,8 @@ public class SSLReloadIntegTests extends SecurityIntegTestCase { } @Override - protected boolean sslTransportEnabled() { - return true; + protected boolean useGeneratedSSLConfig() { + return false; } public void testThatSSLConfigurationReloadsOnModification() throws Exception { diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLServiceTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLServiceTests.java index d5005e7d23a..12fc35ce651 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLServiceTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLServiceTests.java @@ -157,18 +157,9 @@ public class SSLServiceTests extends ESTestCase { assertThat(sslEngine, notNullValue()); } - public void testCreateWithoutAnySettingsNotValidForServer() throws Exception { + public void testCreateWithoutAnySettingsValidForServer() throws Exception { SSLService sslService = new SSLService(Settings.EMPTY, env); - assertFalse(sslService.isConfigurationValidForServerUsage(Settings.EMPTY, Settings.EMPTY)); - } - - public void testCreateWithOnlyTruststoreNotValidForServer() throws Exception { - Settings settings = Settings.builder() - .put("xpack.ssl.truststore.path", testnodeStore) - .put("xpack.ssl.truststore.password", "testnode") - .build(); - SSLService sslService = new SSLService(settings, env); - assertFalse(sslService.isConfigurationValidForServerUsage(Settings.EMPTY, Settings.EMPTY)); + assertTrue(sslService.isConfigurationValidForServerUsage(Settings.EMPTY, true)); } public void testCreateWithKeystoreIsValidForServer() throws Exception { @@ -177,7 +168,7 @@ public class SSLServiceTests extends ESTestCase { .put("xpack.ssl.keystore.password", "testnode") .build(); SSLService sslService = new SSLService(settings, env); - assertTrue(sslService.isConfigurationValidForServerUsage(Settings.EMPTY, Settings.EMPTY)); + assertTrue(sslService.isConfigurationValidForServerUsage(Settings.EMPTY, false)); } public void testValidForServerWithFallback() throws Exception { @@ -186,7 +177,8 @@ public class SSLServiceTests extends ESTestCase { .put("xpack.ssl.truststore.password", "testnode") .build(); SSLService sslService = new SSLService(settings, env); - assertFalse(sslService.isConfigurationValidForServerUsage(Settings.EMPTY, settings.getByPrefix("xpack.ssl."))); + // transport is valid in default config + assertTrue(sslService.isConfigurationValidForServerUsage(Settings.EMPTY, true)); settings = Settings.builder() .put("xpack.ssl.truststore.path", testnodeStore) @@ -195,13 +187,13 @@ public class SSLServiceTests extends ESTestCase { .put("xpack.security.transport.ssl.keystore.password", "testnode") .build(); sslService = new SSLService(settings, env); - assertFalse(sslService.isConfigurationValidForServerUsage(Settings.EMPTY, settings.getByPrefix("xpack.ssl."))); + assertFalse(sslService.isConfigurationValidForServerUsage(Settings.EMPTY, false)); assertTrue(sslService.isConfigurationValidForServerUsage( - settings.getByPrefix("xpack.security.transport.ssl."), settings.getByPrefix("xpack.ssl."))); - assertTrue(sslService.isConfigurationValidForServerUsage(Settings.EMPTY, settings.getByPrefix("xpack.security.transport.ssl."))); + settings.getByPrefix("xpack.security.transport.ssl."), false)); + assertTrue(sslService.isConfigurationValidForServerUsage(Settings.EMPTY, true)); } - public void testGetVerificationMode() { + public void testGetVerificationMode() throws Exception { SSLService sslService = new SSLService(Settings.EMPTY, env); assertThat(sslService.getVerificationMode(Settings.EMPTY, Settings.EMPTY), is(XPackSettings.VERIFICATION_MODE_DEFAULT)); @@ -220,7 +212,7 @@ public class SSLServiceTests extends ESTestCase { is(VerificationMode.CERTIFICATE)); } - public void testIsSSLClientAuthEnabled() { + public void testIsSSLClientAuthEnabled() throws Exception { SSLService sslService = new SSLService(Settings.EMPTY, env); assertTrue(sslService.isSSLClientAuthEnabled(Settings.EMPTY)); assertTrue(sslService.isSSLClientAuthEnabled(Settings.EMPTY, Settings.EMPTY)); @@ -238,7 +230,7 @@ public class SSLServiceTests extends ESTestCase { settings.getByPrefix("xpack.security.transport.ssl."))); } - public void testThatHttpClientAuthDefaultsToNone() { + public void testThatHttpClientAuthDefaultsToNone() throws Exception { final Settings globalSettings = Settings.builder() .put("xpack.security.http.ssl.enabled", true) .put("xpack.ssl.client_authentication", SSLClientAuth.OPTIONAL.name()) diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/TestsSSLService.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/TestsSSLService.java index 7942d10c35e..768a2ef8e81 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/TestsSSLService.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/TestsSSLService.java @@ -5,17 +5,25 @@ */ package org.elasticsearch.xpack.ssl; +import org.bouncycastle.operator.OperatorCreationException; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import javax.net.ssl.SSLContext; +import javax.security.auth.DestroyFailedException; +import java.io.IOException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; /** * Extending SSLService to make helper methods public to access in tests */ public class TestsSSLService extends SSLService { - public TestsSSLService(Settings settings, Environment environment) { + public TestsSSLService(Settings settings, Environment environment) throws CertificateException, UnrecoverableKeyException, + NoSuchAlgorithmException, IOException, DestroyFailedException, KeyStoreException, OperatorCreationException { super(settings, environment); } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/bench/WatcherExecutorServiceBenchmark.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/bench/WatcherExecutorServiceBenchmark.java index d5d1202c994..fa079d51a3a 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/bench/WatcherExecutorServiceBenchmark.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/bench/WatcherExecutorServiceBenchmark.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.watcher.test.bench; +import org.bouncycastle.operator.OperatorCreationException; import org.elasticsearch.client.Client; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.settings.Settings; @@ -24,7 +25,12 @@ import org.elasticsearch.xpack.watcher.trigger.ScheduleTriggerEngineMock; import org.elasticsearch.xpack.watcher.trigger.TriggerEngine; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleRegistry; +import javax.security.auth.DestroyFailedException; import java.io.IOException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; import java.time.Clock; import java.util.Arrays; @@ -201,7 +207,8 @@ public class WatcherExecutorServiceBenchmark { public static final class XPackBenchmarkPlugin extends XPackPlugin { - public XPackBenchmarkPlugin(Settings settings) throws IOException { + public XPackBenchmarkPlugin(Settings settings) throws IOException, CertificateException, UnrecoverableKeyException, + NoSuchAlgorithmException, KeyStoreException, DestroyFailedException, OperatorCreationException { super(settings); watcher = new BenchmarkWatcher(settings); } diff --git a/qa/rolling-upgrade/build.gradle b/qa/rolling-upgrade/build.gradle index e676b574988..56016498871 100644 --- a/qa/rolling-upgrade/build.gradle +++ b/qa/rolling-upgrade/build.gradle @@ -47,6 +47,8 @@ Closure waitWithAuth = { NodeInfo node, AntBuilder ant -> return tmpFile.exists() } +String outputDir = "generated-resources/${project.name}" + task oldClusterTest(type: RestIntegTestTask) { mustRunAfter(precommit) cluster { @@ -58,6 +60,10 @@ task oldClusterTest(type: RestIntegTestTask) { clusterName = 'rolling-upgrade' waitCondition = waitWithAuth setting 'logger.org.elasticsearch.xpack.security', 'TRACE' + setting 'xpack.security.transport.ssl.enabled', 'true' + setting 'xpack.ssl.keystore.path', 'testnode.jks' + setting 'xpack.ssl.keystore.password', 'testnode' + extraConfigFile 'testnode.jks', new File(outputDir + '/testnode.jks') } systemProperty 'tests.rest.suite', 'old_cluster' } @@ -71,6 +77,9 @@ task mixedClusterTest(type: RestIntegTestTask) { unicastTransportUri = { seedNode, node, ant -> oldClusterTest.nodes.get(0).transportUri() } dataDir = "${-> oldClusterTest.nodes[1].dataDir}" waitCondition = waitWithAuth + setting 'xpack.ssl.keystore.path', 'testnode.jks' + setting 'xpack.ssl.keystore.password', 'testnode' + extraConfigFile 'testnode.jks', new File(outputDir + '/testnode.jks') } systemProperty 'tests.rest.suite', 'mixed_cluster' finalizedBy 'oldClusterTest#node0.stop' @@ -85,6 +94,9 @@ task upgradedClusterTest(type: RestIntegTestTask) { unicastTransportUri = { seedNode, node, ant -> mixedClusterTest.nodes.get(0).transportUri() } dataDir = "${-> oldClusterTest.nodes[0].dataDir}" waitCondition = waitWithAuth + setting 'xpack.ssl.keystore.path', 'testnode.jks' + setting 'xpack.ssl.keystore.password', 'testnode' + extraConfigFile 'testnode.jks', new File(outputDir + '/testnode.jks') } systemProperty 'tests.rest.suite', 'upgraded_cluster' // only need to kill the mixed cluster tests node here because we explicitly told it to not stop nodes upon completion @@ -104,7 +116,6 @@ dependencies { } // copy x-pack plugin info so it is on the classpath and security manager has the right permissions -String outputDir = "generated-resources/${project.name}" task copyXPackRestSpec(type: Copy) { dependsOn(project.configurations.restSpec, 'processTestResources') from project(':x-pack:elasticsearch').sourceSets.test.resources @@ -112,8 +123,15 @@ task copyXPackRestSpec(type: Copy) { into project.sourceSets.test.output.resourcesDir } -task copyXPackPluginProps(type: Copy) { +task copyTestNodeKeystore(type: Copy) { dependsOn(copyXPackRestSpec) + from project(':x-pack:elasticsearch') + .file('src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks') + into outputDir +} + +task copyXPackPluginProps(type: Copy) { + dependsOn(copyTestNodeKeystore) from project(':x-pack:elasticsearch').file('src/main/plugin-metadata') from project(':x-pack:elasticsearch').tasks.pluginProperties into outputDir diff --git a/qa/smoke-test-plugins-ssl/build.gradle b/qa/smoke-test-plugins-ssl/build.gradle index 5ea4c29bd28..5802b87be6a 100644 --- a/qa/smoke-test-plugins-ssl/build.gradle +++ b/qa/smoke-test-plugins-ssl/build.gradle @@ -171,10 +171,9 @@ integTest { setting 'xpack.monitoring.exporters._http.auth.username', 'monitoring_agent' setting 'xpack.monitoring.exporters._http.auth.password', 'changeme' - setting 'xpack.security.transport.ssl.enabled', 'true' setting 'xpack.security.http.ssl.enabled', 'true' - setting 'xpack.ssl.keystore.path', nodeKeystore.name - setting 'xpack.ssl.keystore.password', 'keypass' + setting 'xpack.security.http.ssl.keystore.path', nodeKeystore.name + setting 'xpack.security.http.ssl.keystore.password', 'keypass' plugin ':x-pack:elasticsearch' diff --git a/qa/smoke-test-plugins-ssl/src/test/java/org/elasticsearch/smoketest/SmokeTestMonitoringWithSecurityIT.java b/qa/smoke-test-plugins-ssl/src/test/java/org/elasticsearch/smoketest/SmokeTestMonitoringWithSecurityIT.java index 9bb9b472caf..78f4ed097a7 100644 --- a/qa/smoke-test-plugins-ssl/src/test/java/org/elasticsearch/smoketest/SmokeTestMonitoringWithSecurityIT.java +++ b/qa/smoke-test-plugins-ssl/src/test/java/org/elasticsearch/smoketest/SmokeTestMonitoringWithSecurityIT.java @@ -5,27 +5,19 @@ */ package org.elasticsearch.smoketest; -import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse; -import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.network.NetworkAddress; import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.xpack.security.Security; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.xpack.XPackPlugin; import org.junit.After; -import org.junit.AfterClass; import org.junit.Before; -import org.junit.BeforeClass; import java.net.InetSocketAddress; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -47,7 +39,6 @@ import static org.hamcrest.Matchers.greaterThanOrEqualTo; public class SmokeTestMonitoringWithSecurityIT extends ESIntegTestCase { private static final String USER = "test_user"; private static final String PASS = "changeme"; - private static final String KEYSTORE_PASS = "keypass"; private static final String MONITORING_PATTERN = ".monitoring-*"; @Override @@ -59,9 +50,6 @@ public class SmokeTestMonitoringWithSecurityIT extends ESIntegTestCase { protected Settings externalClusterClientSettings() { return Settings.builder() .put(Security.USER_SETTING.getKey(), USER + ":" + PASS) - .put("xpack.security.transport.ssl.enabled", true) - .put("xpack.ssl.keystore.path", clientKeyStore) - .put("xpack.ssl.keystore.password", KEYSTORE_PASS) .put(NetworkModule.TRANSPORT_TYPE_KEY, Security.NAME4).build(); } @@ -122,23 +110,4 @@ public class SmokeTestMonitoringWithSecurityIT extends ESIntegTestCase { } return httpAddresses; } - - static Path clientKeyStore; - - @BeforeClass - public static void loadKeyStore() { - try { - clientKeyStore = PathUtils.get(SmokeTestMonitoringWithSecurityIT.class.getResource("/test-client.jks").toURI()); - } catch (URISyntaxException e) { - throw new ElasticsearchException("exception while reading the store", e); - } - if (!Files.exists(clientKeyStore)) { - throw new IllegalStateException("Keystore file [" + clientKeyStore + "] does not exist."); - } - } - - @AfterClass - public static void clearClientKeyStore() { - clientKeyStore = null; - } }