From cd8085898972befcd941874aa8ffed70b419055f Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Sat, 30 Mar 2019 01:06:36 +1100 Subject: [PATCH 01/20] WIP: Add support for LDAP bind using SASL GSS-API --- .../ldap/PoolingSessionFactorySettings.java | 24 +- .../plugin-metadata/plugin-security.policy | 19 +- .../ldap/ActiveDirectorySessionFactory.java | 69 +++-- .../ldap/LdapUserSearchSessionFactory.java | 9 +- .../authc/ldap/PoolingSessionFactory.java | 39 +-- .../authc/ldap/bind/BindRequestBuilder.java | 145 +++++++++ .../plugin-metadata/plugin-security.policy | 6 +- .../LdapUserSearchSessionFactoryTests.java | 38 ++- .../ldap/bind/BindRequestBuilderTests.java | 184 ++++++++++++ .../authc/ldap/support/LdapTestCase.java | 16 +- x-pack/qa/openldap-tests/build.gradle | 5 + .../ldap/OpenLdapKerberosGSSAPIBindTests.java | 283 ++++++++++++++++++ .../src/test/resources/plugin-security.policy | 19 ++ x-pack/test/idp-fixture/README.txt | 1 + x-pack/test/idp-fixture/build.gradle | 22 +- x-pack/test/idp-fixture/docker-compose.yml | 39 ++- x-pack/test/idp-fixture/kdc-kadmin/Dockerfile | 40 +++ .../idp-fixture/kdc-kadmin/init-script.sh | 197 ++++++++++++ .../idp-fixture/openldap/ldif/config.ldif | 12 + 19 files changed, 1076 insertions(+), 91 deletions(-) create mode 100644 x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/bind/BindRequestBuilder.java create mode 100644 x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/bind/BindRequestBuilderTests.java create mode 100644 x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java create mode 100644 x-pack/qa/openldap-tests/src/test/resources/plugin-security.policy create mode 100644 x-pack/test/idp-fixture/kdc-kadmin/Dockerfile create mode 100755 x-pack/test/idp-fixture/kdc-kadmin/init-script.sh diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/PoolingSessionFactorySettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/PoolingSessionFactorySettings.java index b7b0d529d33e3..0c6fe69afd763 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/PoolingSessionFactorySettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/PoolingSessionFactorySettings.java @@ -7,6 +7,7 @@ import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.core.security.authc.RealmSettings; @@ -35,6 +36,26 @@ public final class PoolingSessionFactorySettings { key -> secureString(key, null) ); + public static final Function> BIND_MODE = RealmSettings.affixSetting("bind.mode", + key -> new Setting<>(key, "simple", Function.identity(), v -> { + switch (v) { + case "simple": + case "sasl_gssapi": + break; + default: + throw new IllegalArgumentException("only [simple] and [sasl_gssapi] bind mode are allowed, [" + v + "] is invalid"); + } + }, Setting.Property.NodeScope)); + + public static final Function> SASL_GSSAPI_PRINCIPAL = RealmSettings + .affixSetting("sasl_gssapi.bind.principal", key -> Setting.simpleString(key, Setting.Property.NodeScope, Property.Filtered)); + public static final Function> SASL_GSSAPI_USE_KEYTAB = RealmSettings + .affixSetting("sasl_gssapi.bind.use_keytab", key -> Setting.boolSetting(key, false, Setting.Property.NodeScope)); + public static final Function> SASL_GSSAPI_KEYTAB_PATH = RealmSettings + .affixSetting("sasl_gssapi.bind.keytab.path", key -> Setting.simpleString(key, Setting.Property.NodeScope, Property.Filtered)); + public static final Function> SASL_GSSAPI_DEBUG = RealmSettings + .affixSetting("sasl_gssapi.bind.debug", key -> Setting.boolSetting(key, false, Setting.Property.NodeScope)); + public static final int DEFAULT_CONNECTION_POOL_INITIAL_SIZE = 0; public static final Function> POOL_INITIAL_SIZE = RealmSettings.affixSetting( "user_search.pool.initial_size", @@ -63,7 +84,8 @@ private PoolingSessionFactorySettings() { public static Set> getSettings(String realmType) { return Stream.of( POOL_INITIAL_SIZE, POOL_SIZE, HEALTH_CHECK_ENABLED, HEALTH_CHECK_INTERVAL, HEALTH_CHECK_DN, BIND_DN, - LEGACY_BIND_PASSWORD, SECURE_BIND_PASSWORD + LEGACY_BIND_PASSWORD, SECURE_BIND_PASSWORD, BIND_MODE, SASL_GSSAPI_PRINCIPAL, SASL_GSSAPI_USE_KEYTAB, SASL_GSSAPI_KEYTAB_PATH, + SASL_GSSAPI_DEBUG ).map(f -> f.apply(realmType)).collect(Collectors.toSet()); } } diff --git a/x-pack/plugin/core/src/main/plugin-metadata/plugin-security.policy b/x-pack/plugin/core/src/main/plugin-metadata/plugin-security.policy index 70abb67a76240..b1c3a68eaa04c 100644 --- a/x-pack/plugin/core/src/main/plugin-metadata/plugin-security.policy +++ b/x-pack/plugin/core/src/main/plugin-metadata/plugin-security.policy @@ -9,7 +9,24 @@ grant { permission java.util.PropertyPermission "*", "read,write"; // needed for multiple server implementations used in tests - permission java.net.SocketPermission "*", "accept,connect"; + permission java.net.SocketPermission "*", "accept,connect,resolve"; + + // needed for GSSAPI bind to LDAP + permission javax.security.auth.AuthPermission "modifyPrincipals"; + permission javax.security.auth.AuthPermission "modifyPrivateCredentials"; + permission javax.security.auth.PrivateCredentialPermission "javax.security.auth.kerberos.KerberosKey * \"*\"", "read"; + permission javax.security.auth.PrivateCredentialPermission "javax.security.auth.kerberos.KeyTab * \"*\"", "read"; + permission javax.security.auth.PrivateCredentialPermission "javax.security.auth.kerberos.KerberosTicket * \"*\"", "read"; + permission javax.security.auth.AuthPermission "doAs"; + permission javax.security.auth.kerberos.ServicePermission "*","initiate,accept"; + + permission java.util.PropertyPermission "javax.security.auth.useSubjectCredsOnly","write"; + permission java.util.PropertyPermission "java.security.krb5.conf","write"; + permission java.util.PropertyPermission "sun.security.krb5.debug","write"; + permission java.util.PropertyPermission "java.security.debug","write"; + + permission javax.security.auth.AuthPermission "createLoginContext.GSSAPIBindRequest"; + permission javax.security.auth.AuthPermission "setLoginConfiguration"; }; grant codeBase "${codebase.netty-common}" { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java index da258a99d0cb4..b01f36259119f 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.security.authc.ldap; +import com.unboundid.ldap.sdk.BindRequest; import com.unboundid.ldap.sdk.Filter; import com.unboundid.ldap.sdk.LDAPConnection; import com.unboundid.ldap.sdk.LDAPConnectionPool; @@ -14,10 +15,12 @@ import com.unboundid.ldap.sdk.ServerSet; import com.unboundid.ldap.sdk.SimpleBindRequest; import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl; + import org.apache.logging.log4j.Logger; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRunnable; +import org.elasticsearch.common.CharArrays; import org.elasticsearch.common.cache.Cache; import org.elasticsearch.common.cache.CacheBuilder; import org.elasticsearch.common.logging.DeprecationLogger; @@ -32,8 +35,8 @@ import org.elasticsearch.xpack.core.security.authc.ldap.ActiveDirectorySessionFactorySettings; import org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings; import org.elasticsearch.xpack.core.security.authc.ldap.support.LdapSearchScope; -import org.elasticsearch.common.CharArrays; import org.elasticsearch.xpack.core.ssl.SSLService; +import org.elasticsearch.xpack.security.authc.ldap.bind.BindRequestBuilder; import org.elasticsearch.xpack.security.authc.ldap.support.LdapMetaDataResolver; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsResolver; @@ -66,7 +69,7 @@ class ActiveDirectorySessionFactory extends PoolingSessionFactory { ActiveDirectorySessionFactory(RealmConfig config, SSLService sslService, ThreadPool threadPool) throws LDAPException { super(config, sslService, new ActiveDirectoryGroupsResolver(config), ActiveDirectorySessionFactorySettings.POOL_ENABLED, - config.hasSetting(PoolingSessionFactorySettings.BIND_DN) ? getBindDN(config) : null, + new BindRequestBuilder(config, c -> c.hasSetting(PoolingSessionFactorySettings.BIND_DN) ? getBindDN(config) : null).build(), () -> { if (config.hasSetting(PoolingSessionFactorySettings.BIND_DN)) { final String healthCheckDn = config.getSetting(PoolingSessionFactorySettings.BIND_DN); @@ -149,7 +152,7 @@ void getUnauthenticatedSessionWithoutPool(String user, ActionListener c.getSetting(PoolingSessionFactorySettings.BIND_DN, () -> null)).build(), () -> "cn=Horatio Hornblower,ou=people,o=sevenSeas"); try { assertThat(connectionPool.getCurrentAvailableConnections(), @@ -445,12 +449,14 @@ public void testConnectionPoolSettings() throws Exception { .put(getFullSettingKey(REALM_IDENTIFIER, PoolingSessionFactorySettings.POOL_INITIAL_SIZE), 10) .put(getFullSettingKey(REALM_IDENTIFIER, PoolingSessionFactorySettings.POOL_SIZE), 12) .put(getFullSettingKey(REALM_IDENTIFIER, PoolingSessionFactorySettings.HEALTH_CHECK_ENABLED), false); - configureBindPassword(realmSettings); + final String secureKey = getFullSettingKey(REALM_IDENTIFIER, PoolingSessionFactorySettings.SECURE_BIND_PASSWORD); + realmSettings.setSecureSettings(newSecureSettings(secureKey, "pass")); + RealmConfig config = getRealmConfig(realmSettings.build()); LDAPConnectionPool connectionPool = LdapUserSearchSessionFactory.createConnectionPool(config, new SingleServerSet("localhost", randomFrom(ldapServers).getListenPort()), TimeValue.timeValueSeconds(5), NoOpLogger.INSTANCE, - new SimpleBindRequest("cn=Horatio Hornblower,ou=people,o=sevenSeas", "pass"), + new BindRequestBuilder(config, c -> c.getSetting(PoolingSessionFactorySettings.BIND_DN, () -> null)).build(), () -> "cn=Horatio Hornblower,ou=people,o=sevenSeas"); try { assertThat(connectionPool.getCurrentAvailableConnections(), is(10)); @@ -523,8 +529,8 @@ public void testEmptyBindDNReturnsAnonymousBindRequest() throws LDAPException { RealmConfig config = new RealmConfig(REALM_IDENTIFIER, realmSettings.build(), TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); try (LdapUserSearchSessionFactory searchSessionFactory = getLdapUserSearchSessionFactory(config, sslService, threadPool)) { - assertThat(searchSessionFactory.bindCredentials, notNullValue()); - assertThat(searchSessionFactory.bindCredentials.getBindDN(), isEmptyString()); + assertThat(searchSessionFactory.bindRequestCredentials, notNullValue()); + assertThat(((SimpleBindRequest) searchSessionFactory.bindRequestCredentials).getBindDN(), isEmptyString()); } assertDeprecationWarnings(config.identifier(), false, useLegacyBindPassword); } @@ -541,8 +547,8 @@ public void testThatBindRequestReturnsSimpleBindRequest() throws LDAPException { RealmConfig config = new RealmConfig(REALM_IDENTIFIER, realmSettings.build(), TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); try (LdapUserSearchSessionFactory searchSessionFactory = getLdapUserSearchSessionFactory(config, sslService, threadPool)) { - assertThat(searchSessionFactory.bindCredentials, notNullValue()); - assertThat(searchSessionFactory.bindCredentials.getBindDN(), is("cn=ironman")); + assertThat(searchSessionFactory.bindRequestCredentials, notNullValue()); + assertThat(((SimpleBindRequest) searchSessionFactory.bindRequestCredentials).getBindDN(), is("cn=ironman")); } assertDeprecationWarnings(config.identifier(), false, useLegacyBindPassword); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/bind/BindRequestBuilderTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/bind/BindRequestBuilderTests.java new file mode 100644 index 0000000000000..d4159f81e6f7a --- /dev/null +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/bind/BindRequestBuilderTests.java @@ -0,0 +1,184 @@ +/* + * 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.authc.ldap.bind; + +import com.unboundid.ldap.sdk.BindRequest; +import com.unboundid.ldap.sdk.GSSAPIBindRequest; +import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.SimpleBindRequest; + +import org.elasticsearch.common.settings.MockSecureSettings; +import org.elasticsearch.common.settings.SecureSettings; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.env.Environment; +import org.elasticsearch.env.TestEnvironment; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.security.authc.RealmConfig; +import org.elasticsearch.xpack.core.security.authc.RealmSettings; +import org.elasticsearch.xpack.core.security.authc.RealmConfig.RealmIdentifier; + +import static org.elasticsearch.xpack.core.security.authc.RealmSettings.getFullSettingKey; + +import org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings; +import org.elasticsearch.xpack.security.authc.kerberos.KerberosRealmTestCase; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Function; + +import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.BIND_DN; +import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.LEGACY_BIND_PASSWORD; +import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.SASL_GSSAPI_KEYTAB_PATH; +import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB; +import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.SECURE_BIND_PASSWORD; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + +public class BindRequestBuilderTests extends ESTestCase { + private RealmIdentifier realmId = new RealmConfig.RealmIdentifier("ldap", "ldap-1"); + + public void testForInvalidBindModeExceptionIsThrown() throws LDAPException { + final String invalidBindMode = randomAlphaOfLength(7); + final Settings realmSettings = Settings.builder() + .put("path.home", createTempDir()) + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB), "true") + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL), "bind-user@DEV.LOCAL") + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.BIND_MODE), invalidBindMode).build(); + final Environment env = TestEnvironment.newEnvironment(realmSettings); + final RealmConfig realmConfig = new RealmConfig(realmId, realmSettings, env, new ThreadContext(realmSettings)); + final BindRequestBuilder builder = new BindRequestBuilder(realmConfig, c -> c.getSetting(BIND_DN, () -> null)); + + final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> builder.build()); + assertThat(iae.getMessage(), + equalTo("only [simple] and [sasl_gssapi] bind mode are allowed, ["+invalidBindMode+"] is invalid")); + } + + public void testGSSAPIBindRequestWithNoKeytabPathFails() throws LDAPException { + final Settings realmSettings = Settings.builder() + .put("path.home", createTempDir()) + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB), "true") + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL), "bind-user@DEV.LOCAL") + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.BIND_MODE), "sasl_gssapi").build(); + final Environment env = TestEnvironment.newEnvironment(realmSettings); + final RealmConfig realmConfig = new RealmConfig(realmId, realmSettings, env, new ThreadContext(realmSettings)); + final BindRequestBuilder builder = new BindRequestBuilder(realmConfig, c -> c.getSetting(BIND_DN, () -> null)); + + final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> builder.build()); + assertThat(iae.getMessage(), + equalTo("setting [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_USE_KEYTAB) + + "] is enabled but keytab path [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_KEYTAB_PATH) + + "] has not been configured")); + } + + public void testGSSAPIBindRequestWithBothKeyTabAndPasswordFails() throws LDAPException, IOException { + final Path dir = createTempDir(); + Path configDir = dir.resolve("config"); + if (Files.exists(configDir) == false) { + configDir = Files.createDirectory(configDir); + } + final Settings.Builder settingsBuilder = Settings.builder().put("path.home", dir) + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.BIND_MODE), "sasl_gssapi") + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL), "bind-user@DEV.LOCAL"); + KerberosRealmTestCase.writeKeyTab(dir.resolve("config").resolve("bind_principal.keytab"), null); + settingsBuilder.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB), Boolean.toString(true)) + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_KEYTAB_PATH), "bind_principal.keytab") + .setSecureSettings(secureSettings(PoolingSessionFactorySettings.SECURE_BIND_PASSWORD, realmId, "passwd")); + + final Settings realmSettings = settingsBuilder.build(); + final Environment env = TestEnvironment.newEnvironment(realmSettings); + final RealmConfig realmConfig = new RealmConfig(realmId, realmSettings, env, new ThreadContext(realmSettings)); + final BindRequestBuilder builder = new BindRequestBuilder(realmConfig, c -> c.getSetting(BIND_DN, () -> null)); + + final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> builder.build()); + assertThat(iae.getMessage(), + equalTo("You cannot specify both [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_USE_KEYTAB) + "] and ([" + + RealmSettings.getFullSettingKey(realmConfig, LEGACY_BIND_PASSWORD) + "] or [" + + RealmSettings.getFullSettingKey(realmConfig, SECURE_BIND_PASSWORD) + "])")); + } + + public void testGSSAPIBindRequestWithNoPasswordFails() throws LDAPException, IOException { + final Path dir = createTempDir(); + Path configDir = dir.resolve("config"); + if (Files.exists(configDir) == false) { + configDir = Files.createDirectory(configDir); + } + final Settings.Builder settingsBuilder = Settings.builder().put("path.home", dir) + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.BIND_MODE), "sasl_gssapi") + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL), "bind-user@DEV.LOCAL"); + KerberosRealmTestCase.writeKeyTab(dir.resolve("config").resolve("bind_principal.keytab"), null); + settingsBuilder.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB), Boolean.toString(false)); + + final Settings realmSettings = settingsBuilder.build(); + final Environment env = TestEnvironment.newEnvironment(realmSettings); + final RealmConfig realmConfig = new RealmConfig(realmId, realmSettings, env, new ThreadContext(realmSettings)); + final BindRequestBuilder builder = new BindRequestBuilder(realmConfig, c -> c.getSetting(BIND_DN, () -> null)); + + final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> builder.build()); + assertThat(iae.getMessage(), equalTo("Either keytab [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_KEYTAB_PATH) + + "] or principal password " + RealmSettings.getFullSettingKey(realmConfig, SECURE_BIND_PASSWORD) + " must be configured")); + } + + public void testGSSAPIBindRequest() throws LDAPException, IOException { + final Path dir = createTempDir(); + Path configDir = dir.resolve("config"); + if (Files.exists(configDir) == false) { + configDir = Files.createDirectory(configDir); + } + final Settings.Builder settingsBuilder = Settings.builder().put("path.home", dir) + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.BIND_MODE), "sasl_gssapi") + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL), "bind-user@DEV.LOCAL"); + final boolean useKeyTab = randomBoolean(); + if (useKeyTab) { + KerberosRealmTestCase.writeKeyTab(dir.resolve("config").resolve("bind_principal.keytab"), null); + settingsBuilder + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB), Boolean.toString(useKeyTab)) + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_KEYTAB_PATH), "bind_principal.keytab"); + } else { + settingsBuilder.setSecureSettings(secureSettings(PoolingSessionFactorySettings.SECURE_BIND_PASSWORD, realmId, "passwd")); + } + + final Settings realmSettings = settingsBuilder.build(); + final Environment env = TestEnvironment.newEnvironment(realmSettings); + final RealmConfig realmConfig = new RealmConfig(realmId, realmSettings, env, new ThreadContext(realmSettings)); + final BindRequestBuilder builder = new BindRequestBuilder(realmConfig, c -> c.getSetting(BIND_DN, () -> null)); + final BindRequest bindRequest = builder.build(); + assertThat(bindRequest, instanceOf(GSSAPIBindRequest.class)); + if (useKeyTab) { + assertThat(((GSSAPIBindRequest) bindRequest).getKeyTabPath(), + is(dir.resolve("config").resolve("bind_principal.keytab").toString())); + } else { + assertThat(((GSSAPIBindRequest) bindRequest).getPasswordString(), is("passwd")); + } + } + + public void testSimpleBindRequest() throws LDAPException { + final Settings realmSettings = Settings.builder() + .put("path.home", createTempDir()) + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.BIND_DN), "bind-user@DEV.LOCAL") + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.BIND_MODE), "simple") + .setSecureSettings(secureSettings(PoolingSessionFactorySettings.SECURE_BIND_PASSWORD, realmId, "passwd")) + .build(); + final Environment env = TestEnvironment.newEnvironment(realmSettings); + final RealmConfig realmConfig = new RealmConfig(realmId, realmSettings, env, + new ThreadContext(realmSettings)); + final BindRequestBuilder builder = new BindRequestBuilder(realmConfig, c -> c.getSetting(BIND_DN, () -> null)); + final BindRequest bindRequest = builder.build(); + assertThat(bindRequest, instanceOf(SimpleBindRequest.class)); + } + + private SecureSettings secureSettings(Function> settingFactory, + RealmConfig.RealmIdentifier identifier, String value) { + final MockSecureSettings secureSettings = new MockSecureSettings(); + secureSettings.setString(getFullSettingKey(identifier, settingFactory), value); + return secureSettings; + } +} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java index 957167e60d281..1cca3031e6f94 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java @@ -9,6 +9,7 @@ import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; import com.unboundid.ldap.sdk.Attribute; +import com.unboundid.ldap.sdk.BindRequest; import com.unboundid.ldap.sdk.LDAPConnection; import com.unboundid.ldap.sdk.LDAPConnectionPool; import com.unboundid.ldap.sdk.LDAPException; @@ -218,20 +219,27 @@ protected LdapSession unauthenticatedSession(SessionFactory factory, String user return future.actionGet(); } - protected static void assertConnectionValid(LDAPInterface conn, SimpleBindRequest bindRequest) { + protected static void assertConnectionValid(LDAPInterface conn, BindRequest bindRequest) { AccessController.doPrivileged(new PrivilegedAction() { @Override public Void run() { try { if (conn instanceof LDAPConnection) { assertTrue(((LDAPConnection) conn).isConnected()); - assertEquals(bindRequest.getBindDN(), - ((SimpleBindRequest) ((LDAPConnection) conn).getLastBindRequest()).getBindDN()); + assertEquals(bindRequest.getBindType(), ((LDAPConnection) conn).getLastBindRequest().getBindType()); + if (bindRequest instanceof SimpleBindRequest) { + assertEquals(((SimpleBindRequest) bindRequest).getBindDN(), + ((SimpleBindRequest) ((LDAPConnection) conn).getLastBindRequest()).getBindDN()); + } ((LDAPConnection) conn).reconnect(); } else if (conn instanceof LDAPConnectionPool) { try (LDAPConnection c = ((LDAPConnectionPool) conn).getConnection()) { assertTrue(c.isConnected()); - assertEquals(bindRequest.getBindDN(), ((SimpleBindRequest) c.getLastBindRequest()).getBindDN()); + assertEquals(bindRequest.getBindType(), c.getLastBindRequest().getBindType()); + if (bindRequest instanceof SimpleBindRequest) { + assertEquals(((SimpleBindRequest) bindRequest).getBindDN(), + ((SimpleBindRequest) c.getLastBindRequest()).getBindDN()); + } c.reconnect(); } } diff --git a/x-pack/qa/openldap-tests/build.gradle b/x-pack/qa/openldap-tests/build.gradle index 5305699b9a0c7..f7d42d8e8e382 100644 --- a/x-pack/qa/openldap-tests/build.gradle +++ b/x-pack/qa/openldap-tests/build.gradle @@ -12,9 +12,14 @@ testFixtures.useFixture ":x-pack:test:idp-fixture" Project idpFixtureProject = xpackProject("test:idp-fixture") String outputDir = "${project.buildDir}/generated-resources/${project.name}" + task copyIdpTrust(type: Copy) { from idpFixtureProject.file('openldap/certs/ca.jks'); from idpFixtureProject.file('openldap/certs/ca_server.pem'); + from idpFixtureProject.file('build/shared/keytabs/es-bind.keytab'); + from idpFixtureProject.file('build/shared/krb-conf/krb5.conf'); into outputDir + dependsOn project(':x-pack:test:idp-fixture').postProcessFixture } + project.sourceSets.test.output.dir(outputDir, builtBy: copyIdpTrust) diff --git a/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java b/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java new file mode 100644 index 0000000000000..7129ceab29205 --- /dev/null +++ b/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java @@ -0,0 +1,283 @@ +/* + * 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.authc.ldap; + +import com.unboundid.ldap.sdk.LDAPException; + +import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.SuppressForbidden; +import org.elasticsearch.common.settings.MockSecureSettings; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.env.Environment; +import org.elasticsearch.env.TestEnvironment; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.OpenLdapTests; +import org.elasticsearch.threadpool.TestThreadPool; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.watcher.ResourceWatcherService; +import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; +import org.elasticsearch.xpack.core.security.authc.RealmConfig; +import org.elasticsearch.xpack.core.security.authc.RealmConfig.RealmIdentifier; +import org.elasticsearch.xpack.core.security.authc.ldap.LdapUserSearchSessionFactorySettings; +import org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings; +import org.elasticsearch.xpack.core.security.authc.ldap.SearchGroupsResolverSettings; +import org.elasticsearch.xpack.core.security.authc.ldap.support.LdapSearchScope; +import org.elasticsearch.xpack.core.security.authc.support.DnRoleMapperSettings; +import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; +import org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings; +import org.elasticsearch.xpack.core.ssl.SSLService; +import org.elasticsearch.xpack.core.ssl.VerificationMode; +import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession; +import org.elasticsearch.xpack.security.authc.ldap.support.LdapTestCase; +import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory; +import org.elasticsearch.xpack.security.authc.support.DnRoleMapper; +import org.junit.After; +import org.junit.Before; + +import java.nio.file.Path; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.text.MessageFormat; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +import javax.security.auth.login.Configuration; + +import static org.elasticsearch.xpack.core.security.authc.RealmSettings.getFullSettingKey; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This tests user bind to LDAP happens over SASL - GSSAPI + */ +public class OpenLdapKerberosGSSAPIBindTests extends ESTestCase { + + private static final String GSSAPI_BIND_PASSWORD = "esadmin"; + private static final String GSSAPI_BIND_USER_PRINCIPAL = "kerb-bind-user@DEV.LOCAL"; + private static final String GSSAPI_BIND_KEYTAB_PRINCIPAL = "kerb-ktab-bind-user@DEV.LOCAL"; + private static final String GSSAPI_BIND_KEYTAB_PATH = "/es-bind.keytab"; + private static final String LDAPCACERT_PATH = "/ca_server.pem"; + private static final String LDAPTRUST_PATH = "/ca.jks"; + + public static final String VALID_USER_TEMPLATE = "cn={0},ou=people,o=sevenSeas"; + public static final String VALID_USERNAME = "Thomas Masterman Hardy"; + public static final String PASSWORD = "pass"; + + private Settings globalSettings; + private ThreadPool threadPool; + + + + private ResourceWatcherService resourceWatcherService; + private Settings defaultGlobalSettings; + private SSLService sslService; + private XPackLicenseState licenseState; + + @Before + public void init() throws PrivilegedActionException { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + @SuppressForbidden(reason = "set or clear system property krb5 debug in ldap-gssapi-bind tests") + public Void run() throws Exception { + System.setProperty("java.security.krb5.conf", getDataPath("/krb5.conf").toString()); + // JAAS config needs to be cleared as between tests we will be changing the configuration. + Configuration.setConfiguration(null); + + System.getProperty("sun.security.krb5.debug", "true"); + //System.getProperty("com.unboundid.ldap.sdk.debug.enabled", "true"); + //System.getProperty("com.unboundid.ldap.sdk.debug.level", "FINEST"); + //System.getProperty("com.unboundid.ldap.sdk.debug.type", "LDAP"); + return null; + } + }); + + Path caPath = getDataPath(LDAPCACERT_PATH); + /* + * 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 + * verification tests since a re-established connection does not perform hostname verification. + */ + globalSettings = Settings.builder() + .put("path.home", createTempDir()) + .put("xpack.security.authc.realms.ldap.oldap-test.ssl.certificate_authorities", caPath) + .build(); + threadPool = new TestThreadPool("OpenLdapKerberosGSSAPIBindTests"); + resourceWatcherService = new ResourceWatcherService(Settings.EMPTY, threadPool); + defaultGlobalSettings = Settings.builder().put("path.home", createTempDir()).build(); + sslService = new SSLService(defaultGlobalSettings, TestEnvironment.newEnvironment(defaultGlobalSettings)); + licenseState = mock(XPackLicenseState.class); + when(licenseState.isAuthorizationRealmAllowed()).thenReturn(true); + } + + @After + public void shutdown() { + resourceWatcherService.stop(); + terminate(threadPool); + } + + public void testAuthenticateUserWhereBindingHappensUsingGSSAPI() { + Path truststore = getDataPath(LDAPTRUST_PATH); + /* + * 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 + * verification tests since a re-established connection does not perform hostname verification. + */ + MockSecureSettings mockSecureSettings = new MockSecureSettings(); + Settings.Builder builder = Settings.builder().put("path.home", createTempDir()); + // fake realms so ssl will get loaded + builder.put("xpack.security.authc.realms.ldap.foo.ssl.truststore.path", truststore); + mockSecureSettings.setString("xpack.security.authc.realms.ldap.foo.ssl.truststore.secure_password", "changeit"); + builder.put("xpack.security.authc.realms.ldap.foo.ssl.verification_mode", VerificationMode.FULL); + builder.put("xpack.security.authc.realms.ldap.oldap-test.ssl.truststore.path", truststore); + mockSecureSettings.setString("xpack.security.authc.realms.ldap.oldap-test.ssl.truststore.secure_password", "changeit"); + builder.put("xpack.security.authc.realms.ldap.oldap-test.ssl.verification_mode", VerificationMode.CERTIFICATE); + + builder.put("xpack.security.authc.realms.ldap.vmode_full.ssl.truststore.path", truststore); + mockSecureSettings.setString("xpack.security.authc.realms.ldap.vmode_full.ssl.truststore.secure_password", "changeit"); + builder.put("xpack.security.authc.realms.ldap.vmode_full.ssl.verification_mode", VerificationMode.FULL); + globalSettings = builder.setSecureSettings(mockSecureSettings).build(); + Environment environment = TestEnvironment.newEnvironment(globalSettings); + sslService = new SSLService(globalSettings, environment); + + final RealmConfig.RealmIdentifier realmId = new RealmConfig.RealmIdentifier("ldap", "oldap-test"); + + String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; + final Settings.Builder realmSettings = commonRealmSettings(realmId, userTemplate); + realmSettings.put(getFullSettingKey(realmId, DnRoleMapperSettings.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING), true); + bindCredentialsSettings(true, realmId, realmSettings); + final Settings settings = realmSettings.put(globalSettings).build(); + + RealmConfig config = new RealmConfig(realmId, settings, TestEnvironment.newEnvironment(settings), new ThreadContext(settings)); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, sslService, threadPool); + + LdapRealm ldap = new LdapRealm(config, ldapFactory, new DnRoleMapper(config, resourceWatcherService), + threadPool); + ldap.initialize(Collections.singleton(ldap), licenseState); + + PlainActionFuture future = new PlainActionFuture<>(); + ldap.authenticate(new UsernamePasswordToken("hulk", new SecureString(OpenLdapTests.PASSWORD)), future); + final AuthenticationResult result = future.actionGet(); + assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS)); + } + + public void testUserSearchWithGSSAPIBindUsingKeytab() throws Exception { + final RealmConfig.RealmIdentifier realmId = new RealmConfig.RealmIdentifier("ldap", "oldap-test"); + + final Settings.Builder realmSettings = commonRealmSettings(realmId, null); + bindCredentialsSettings(true, realmId, realmSettings); + final Settings settings = realmSettings.put(globalSettings).build(); + + verifyBindIsSuccessfulAndUserCanBeSearched(realmId, settings); + } + + public void testUserSearchWithGSSAPIBindUsingUsernamePassword() throws Exception { + final RealmConfig.RealmIdentifier realmId = new RealmConfig.RealmIdentifier("ldap", "oldap-test"); + + final Settings.Builder realmSettings = commonRealmSettings(realmId, null); + bindCredentialsSettings(false, realmId, realmSettings); + final Settings settings = realmSettings.put(globalSettings).build(); + + verifyBindIsSuccessfulAndUserCanBeSearched(realmId, settings); + } + + private Settings.Builder commonRealmSettings(final RealmConfig.RealmIdentifier realmId, String userDnTemplate) { + String[] userDnTemplates = (Strings.hasText(userDnTemplate)) ? new String[] { userDnTemplate } : Strings.EMPTY_ARRAY; + String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; + String userSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; + final Settings.Builder realmSettings = Settings.builder() + .put(LdapTestCase.buildLdapSettings(realmId, new String[]{OpenLdapTests.OPEN_LDAP_DNS_URL}, userDnTemplates, + groupSearchBase, LdapSearchScope.ONE_LEVEL, null, false)) + .put(getFullSettingKey(realmId.getName(), LdapUserSearchSessionFactorySettings.SEARCH_BASE_DN), userSearchBase) + .put(getFullSettingKey(realmId.getName(), SearchGroupsResolverSettings.USER_ATTRIBUTE), "uid") + .put(getFullSettingKey(realmId.getName(), LdapUserSearchSessionFactorySettings.POOL_ENABLED), randomBoolean()) + .put(getFullSettingKey(realmId, SSLConfigurationSettings.VERIFICATION_MODE_SETTING_REALM), "full"); + + realmSettings.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.BIND_MODE), + "sasl_gssapi"); + realmSettings.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_DEBUG), true); + return realmSettings; + } + + private void verifyBindIsSuccessfulAndUserCanBeSearched(final RealmConfig.RealmIdentifier realmId, final Settings settings) + throws LDAPException { + RealmConfig config = new RealmConfig(realmId, settings, + TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); + + SSLService sslService = new SSLService(settings, TestEnvironment.newEnvironment(settings)); + + // Verify bind happens and user can be searched + String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor"}; + try (LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, sslService, threadPool)) { + logger.info("# BRC = " + sessionFactory.bindRequestCredentials); + + for (String user : users) { + //auth + try (LdapSession ldap = session(sessionFactory, user, new SecureString(OpenLdapTests.PASSWORD))) { + assertThat(ldap.userDn(), is(equalTo(new MessageFormat("uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com", + Locale.ROOT).format(new Object[]{user}, new StringBuffer(), null).toString()))); + assertThat(groups(ldap), hasItem(containsString("Avengers"))); + } + + //lookup + try (LdapSession ldap = unauthenticatedSession(sessionFactory, user)) { + assertThat(ldap.userDn(), is(equalTo(new MessageFormat("uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com", + Locale.ROOT).format(new Object[]{user}, new StringBuffer(), null).toString()))); + assertThat(groups(ldap), hasItem(containsString("Avengers"))); + } + } + } + } + + private void bindCredentialsSettings(boolean useKeyTab, RealmIdentifier realmId, Settings.Builder realmSettings) { + logger.info("# useKeyTab = "+useKeyTab); + logger.info("# useKeyTab = "+getDataPath(GSSAPI_BIND_KEYTAB_PATH)); + if (useKeyTab) { + realmSettings.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB), useKeyTab); + realmSettings.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL), + GSSAPI_BIND_KEYTAB_PRINCIPAL); + realmSettings.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_KEYTAB_PATH), + getDataPath(GSSAPI_BIND_KEYTAB_PATH)); + } else { + realmSettings.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB), useKeyTab); + realmSettings.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL), + GSSAPI_BIND_USER_PRINCIPAL); + final MockSecureSettings secureSettings = new MockSecureSettings(); + secureSettings.setString(getFullSettingKey(realmId, PoolingSessionFactorySettings.SECURE_BIND_PASSWORD), GSSAPI_BIND_PASSWORD); + realmSettings.setSecureSettings(secureSettings); + } + } + + private LdapSession session(SessionFactory factory, String username, SecureString password) { + PlainActionFuture future = new PlainActionFuture<>(); + factory.session(username, password, future); + return future.actionGet(); + } + + private List groups(LdapSession ldapSession) { + Objects.requireNonNull(ldapSession); + PlainActionFuture> future = new PlainActionFuture<>(); + ldapSession.groups(future); + return future.actionGet(); + } + + private LdapSession unauthenticatedSession(SessionFactory factory, String username) { + PlainActionFuture future = new PlainActionFuture<>(); + factory.unauthenticatedSession(username, future); + return future.actionGet(); + } +} diff --git a/x-pack/qa/openldap-tests/src/test/resources/plugin-security.policy b/x-pack/qa/openldap-tests/src/test/resources/plugin-security.policy new file mode 100644 index 0000000000000..ceb84de48592f --- /dev/null +++ b/x-pack/qa/openldap-tests/src/test/resources/plugin-security.policy @@ -0,0 +1,19 @@ +grant { + + // needed for GSSAPI bind to LDAP + permission javax.security.auth.AuthPermission "modifyPrincipals"; + permission javax.security.auth.AuthPermission "modifyPrivateCredentials"; + permission javax.security.auth.PrivateCredentialPermission "javax.security.auth.kerberos.KerberosKey * \"*\"", "read"; + permission javax.security.auth.PrivateCredentialPermission "javax.security.auth.kerberos.KeyTab * \"*\"", "read"; + permission javax.security.auth.PrivateCredentialPermission "javax.security.auth.kerberos.KerberosTicket * \"*\"", "read"; + permission javax.security.auth.AuthPermission "doAs"; + permission javax.security.auth.kerberos.ServicePermission "*","initiate,accept"; + + permission java.util.PropertyPermission "javax.security.auth.useSubjectCredsOnly","write"; + permission java.util.PropertyPermission "java.security.krb5.conf","write"; + permission java.util.PropertyPermission "sun.security.krb5.debug","write"; + permission java.util.PropertyPermission "java.security.debug","write"; + + permission javax.security.auth.AuthPermission "createLoginContext.GSSAPIBindRequest"; + permission javax.security.auth.AuthPermission "setLoginConfiguration"; +}; diff --git a/x-pack/test/idp-fixture/README.txt b/x-pack/test/idp-fixture/README.txt index 8e42bb142e4ee..91f215fd269f6 100644 --- a/x-pack/test/idp-fixture/README.txt +++ b/x-pack/test/idp-fixture/README.txt @@ -1 +1,2 @@ Provisions OpenLDAP + shibboleth IDP 3.4.2 using docker compose +Also enables SASL on OpenLDAP with Kerberos setup. diff --git a/x-pack/test/idp-fixture/build.gradle b/x-pack/test/idp-fixture/build.gradle index c55123e08d0f1..921fecece55fc 100644 --- a/x-pack/test/idp-fixture/build.gradle +++ b/x-pack/test/idp-fixture/build.gradle @@ -1,4 +1,24 @@ apply plugin: 'elasticsearch.build' apply plugin: 'elasticsearch.test.fixtures' -test.enabled = false \ No newline at end of file +test.enabled = false + +preProcessFixture.doLast { + // We need to create these up-front because if docker creates them they will be owned by root and we won't be + // able to clean them up + file("${buildDir}/shared/keytabs/${it}").mkdirs() + file("${buildDir}/shared/krb-conf/${it}").mkdirs() +} + +postProcessFixture { + inputs.dir("${buildDir}/shared") + File confTemplate = file("${buildDir}/shared/krb-conf/krb5.conf.template") + File confFile = file("${buildDir}/shared/krb-conf/krb5.conf") + outputs.file(confFile) + doLast { + assert confTemplate.exists() + String confContents = confTemplate.text + .replace("\${MAPPED_PORT}", "${ext."test.fixtures.kdc-kadmin.tcp.88"}") + confFile.text = confContents + } +} \ No newline at end of file diff --git a/x-pack/test/idp-fixture/docker-compose.yml b/x-pack/test/idp-fixture/docker-compose.yml index c549fbbfa5dd7..26c782c0e8a0c 100644 --- a/x-pack/test/idp-fixture/docker-compose.yml +++ b/x-pack/test/idp-fixture/docker-compose.yml @@ -1,8 +1,28 @@ version: '3.1' services: + kdc-kadmin: + build: ./kdc-kadmin + image: kdc:latest + environment: + REALM: "DEV.LOCAL" + SUPPORTED_ENCRYPTION_TYPES: "aes256-cts-hmac-sha1-96:normal" + KADMIN_PRINCIPAL: "kadmin/admin" + KADMIN_PASSWORD: "esadmin" + volumes: + # This is needed otherwise there won't be enough entropy to generate a new kerberos realm + - /dev/urandom:/dev/random + - ./:/fixture + ports: + - "749" + - "88" + networks: + - test-net + openldap: command: --copy-service --loglevel debug - image: "osixia/openldap:1.2.3" + image: "osixia/openldap:1.2.4" + hostname: openldap + domainname: idp-fixture_dev.local ports: - "389" - "636" @@ -17,10 +37,17 @@ services: LDAP_TLS_VERIFY_CLIENT: "never" LDAP_TLS_CIPHER_SUITE: "NORMAL" LDAP_LOG_LEVEL: 256 + HOSTNAME: openldap.idp-fixture_dev.local volumes: - ./openldap/ldif/users.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/20-bootstrap-users.ldif - ./openldap/ldif/config.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/10-bootstrap-config.ldif - ./openldap/certs:/container/service/slapd/assets/certs + - ./build/shared/keytabs/ldap.keytab:/etc/krb5.keytab + - ./build/shared/krb-conf/ldap-krb5.conf:/etc/krb5.conf + depends_on: + - kdc-kadmin + networks: + - test-net shibboleth-idp: image: "unicon/shibboleth-idp:3.4.2" @@ -38,10 +65,18 @@ services: - ./idp/shibboleth-idp/conf:/opt/shibboleth-idp/conf - ./idp/shibboleth-idp/credentials:/opt/shibboleth-idp/credentials - ./idp/shib-jetty-base/start.d/ssl.ini:/opt/shib-jetty-base/start.d/ssl.ini + networks: + - test-net oidc-provider: image: "c2id/c2id-server:7.8" ports: - "8080" volumes: - - ./oidc/override.properties:/etc/c2id/override.properties \ No newline at end of file + - ./oidc/override.properties:/etc/c2id/override.properties + networks: + - test-net + +networks: + test-net: + diff --git a/x-pack/test/idp-fixture/kdc-kadmin/Dockerfile b/x-pack/test/idp-fixture/kdc-kadmin/Dockerfile new file mode 100644 index 0000000000000..c1d6128844ba1 --- /dev/null +++ b/x-pack/test/idp-fixture/kdc-kadmin/Dockerfile @@ -0,0 +1,40 @@ +#!/bin/bash + +# Licensed to Elasticsearch under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +FROM debian:buster + +EXPOSE 749 88 + +ENV DEBIAN_FRONTEND noninteractive +# The -qq implies --yes +RUN apt-get -qq update +RUN apt-get -qq install locales krb5-kdc krb5-admin-server krb5-user +RUN apt-get -qq clean + +RUN locale-gen "en_US.UTF-8" +RUN echo "LC_ALL=\"en_US.UTF-8\"" >> /etc/default/locale + +ENV REALM ${REALM:-EXAMPLE.COM} +ENV SUPPORTED_ENCRYPTION_TYPES ${SUPPORTED_ENCRYPTION_TYPES:-aes256-cts-hmac-sha1-96:normal} +ENV KADMIN_PRINCIPAL ${KADMIN_PRINCIPAL:-kadmin/admin} +ENV KADMIN_PASSWORD ${KADMIN_PASSWORD:-MITiys4K5} + +COPY init-script.sh /tmp/ +RUN chmod +x /tmp/init-script.sh +CMD /tmp/init-script.sh diff --git a/x-pack/test/idp-fixture/kdc-kadmin/init-script.sh b/x-pack/test/idp-fixture/kdc-kadmin/init-script.sh new file mode 100755 index 0000000000000..624a8df2d4f99 --- /dev/null +++ b/x-pack/test/idp-fixture/kdc-kadmin/init-script.sh @@ -0,0 +1,197 @@ +#!/bin/bash + +# Licensed to Elasticsearch under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +KEYTABS_DIR=/fixture/build/shared/keytabs/ +KRB_CONF_DIR=/fixture/build/shared/krb-conf/ + +echo "===================================================================================" +echo "Kerberos KDC and Kadmin configuration" +KADMIN_PRINCIPAL_FULL=$KADMIN_PRINCIPAL@$REALM + +echo "REALM: $REALM" +echo "KADMIN_PRINCIPAL_FULL: $KADMIN_PRINCIPAL_FULL" +echo "KADMIN_PASSWORD: $KADMIN_PASSWORD" +echo "" + +KDC_KADMIN_SERVER=$(hostname -f) +tee /etc/krb5.conf < Date: Fri, 12 Apr 2019 12:24:42 +1000 Subject: [PATCH 02/20] remove image tag during build, not sure if this will fix the build, locally it is running fine --- x-pack/test/idp-fixture/docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/test/idp-fixture/docker-compose.yml b/x-pack/test/idp-fixture/docker-compose.yml index 26c782c0e8a0c..df30bdd33ca1f 100644 --- a/x-pack/test/idp-fixture/docker-compose.yml +++ b/x-pack/test/idp-fixture/docker-compose.yml @@ -2,7 +2,6 @@ version: '3.1' services: kdc-kadmin: build: ./kdc-kadmin - image: kdc:latest environment: REALM: "DEV.LOCAL" SUPPORTED_ENCRYPTION_TYPES: "aes256-cts-hmac-sha1-96:normal" From 230bc87db713c19f280ff3201667c63096e3bdf8 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 12 Apr 2019 13:32:14 +1000 Subject: [PATCH 03/20] add healthcheck for kdc-kadmin --- .../authc/ldap/OpenLdapKerberosGSSAPIBindTests.java | 3 --- x-pack/test/idp-fixture/docker-compose.yml | 7 +++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java b/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java index 7129ceab29205..07a1c7c15460b 100644 --- a/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java +++ b/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java @@ -223,7 +223,6 @@ private void verifyBindIsSuccessfulAndUserCanBeSearched(final RealmConfig.RealmI // Verify bind happens and user can be searched String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor"}; try (LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, sslService, threadPool)) { - logger.info("# BRC = " + sessionFactory.bindRequestCredentials); for (String user : users) { //auth @@ -244,8 +243,6 @@ private void verifyBindIsSuccessfulAndUserCanBeSearched(final RealmConfig.RealmI } private void bindCredentialsSettings(boolean useKeyTab, RealmIdentifier realmId, Settings.Builder realmSettings) { - logger.info("# useKeyTab = "+useKeyTab); - logger.info("# useKeyTab = "+getDataPath(GSSAPI_BIND_KEYTAB_PATH)); if (useKeyTab) { realmSettings.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB), useKeyTab); realmSettings.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL), diff --git a/x-pack/test/idp-fixture/docker-compose.yml b/x-pack/test/idp-fixture/docker-compose.yml index df30bdd33ca1f..e149c252d6524 100644 --- a/x-pack/test/idp-fixture/docker-compose.yml +++ b/x-pack/test/idp-fixture/docker-compose.yml @@ -16,6 +16,11 @@ services: - "88" networks: - test-net + healthcheck: + test: ["CMD", "test", "-f", "/fixture/build/shared/keytabs/ldap.keytab"] + interval: 30s + timeout: 10s + retries: 5 openldap: command: --copy-service --loglevel debug @@ -45,6 +50,8 @@ services: - ./build/shared/krb-conf/ldap-krb5.conf:/etc/krb5.conf depends_on: - kdc-kadmin + links: + - kdc-kadmin networks: - test-net From 33dbb099108d6429d982d90afa34672d68de4644 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 12 Apr 2019 14:48:17 +1000 Subject: [PATCH 04/20] create temp file so it can be mounted --- x-pack/test/idp-fixture/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/idp-fixture/build.gradle b/x-pack/test/idp-fixture/build.gradle index 921fecece55fc..4f0f541b8ab42 100644 --- a/x-pack/test/idp-fixture/build.gradle +++ b/x-pack/test/idp-fixture/build.gradle @@ -8,6 +8,7 @@ preProcessFixture.doLast { // able to clean them up file("${buildDir}/shared/keytabs/${it}").mkdirs() file("${buildDir}/shared/krb-conf/${it}").mkdirs() + new File("${buildDir}/shared/krb-conf/krb5.conf").text = "" } postProcessFixture { From d3be59fe8a13451f6a885d2893523f156342194e Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 12 Apr 2019 15:27:53 +1000 Subject: [PATCH 05/20] correct the file name --- x-pack/test/idp-fixture/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/idp-fixture/build.gradle b/x-pack/test/idp-fixture/build.gradle index 4f0f541b8ab42..91d5bdc410310 100644 --- a/x-pack/test/idp-fixture/build.gradle +++ b/x-pack/test/idp-fixture/build.gradle @@ -8,7 +8,7 @@ preProcessFixture.doLast { // able to clean them up file("${buildDir}/shared/keytabs/${it}").mkdirs() file("${buildDir}/shared/krb-conf/${it}").mkdirs() - new File("${buildDir}/shared/krb-conf/krb5.conf").text = "" + new File("${buildDir}/shared/krb-conf/ldap-krb5.conf").text = "" } postProcessFixture { From 20cbecdc47853e14cd7893841ca973e60c1a727c Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 12 Apr 2019 15:37:43 +1000 Subject: [PATCH 06/20] another file --- x-pack/test/idp-fixture/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/idp-fixture/build.gradle b/x-pack/test/idp-fixture/build.gradle index 91d5bdc410310..162d82e4b2f5d 100644 --- a/x-pack/test/idp-fixture/build.gradle +++ b/x-pack/test/idp-fixture/build.gradle @@ -9,6 +9,7 @@ preProcessFixture.doLast { file("${buildDir}/shared/keytabs/${it}").mkdirs() file("${buildDir}/shared/krb-conf/${it}").mkdirs() new File("${buildDir}/shared/krb-conf/ldap-krb5.conf").text = "" + new File("${buildDir}/shared/keytabs/ldap.keytab").text = "" } postProcessFixture { From e29cf2688d413cddfd0af9ac73a0439ceb3cbc6e Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 12 Apr 2019 16:16:16 +1000 Subject: [PATCH 07/20] things not working on CI, debug --- x-pack/test/idp-fixture/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/test/idp-fixture/build.gradle b/x-pack/test/idp-fixture/build.gradle index 162d82e4b2f5d..e47c5a7b0f33b 100644 --- a/x-pack/test/idp-fixture/build.gradle +++ b/x-pack/test/idp-fixture/build.gradle @@ -18,9 +18,13 @@ postProcessFixture { File confFile = file("${buildDir}/shared/krb-conf/krb5.conf") outputs.file(confFile) doLast { + println "krb5.conf.template exists ? " + confTemplate.exists() + File ldapKeytab = file("${buildDir}/shared/keytabs/ldap.keytab") + println "ldap.keytab exists ? " + ldapKeytab.exists() assert confTemplate.exists() String confContents = confTemplate.text .replace("\${MAPPED_PORT}", "${ext."test.fixtures.kdc-kadmin.tcp.88"}") confFile.text = confContents + println "krb5.conf exists after writing ? " + confFile.exists() } } \ No newline at end of file From 7332792e98e5a9e82cfa3e58ca7b009f1ca07404 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 12 Apr 2019 17:13:13 +1000 Subject: [PATCH 08/20] remove unrelated changes --- x-pack/test/idp-fixture/docker-compose.yml | 14 -------------- x-pack/test/idp-fixture/kdc-kadmin/init-script.sh | 4 ---- 2 files changed, 18 deletions(-) diff --git a/x-pack/test/idp-fixture/docker-compose.yml b/x-pack/test/idp-fixture/docker-compose.yml index e149c252d6524..3a41f62c77524 100644 --- a/x-pack/test/idp-fixture/docker-compose.yml +++ b/x-pack/test/idp-fixture/docker-compose.yml @@ -14,8 +14,6 @@ services: ports: - "749" - "88" - networks: - - test-net healthcheck: test: ["CMD", "test", "-f", "/fixture/build/shared/keytabs/ldap.keytab"] interval: 30s @@ -25,8 +23,6 @@ services: openldap: command: --copy-service --loglevel debug image: "osixia/openldap:1.2.4" - hostname: openldap - domainname: idp-fixture_dev.local ports: - "389" - "636" @@ -41,7 +37,6 @@ services: LDAP_TLS_VERIFY_CLIENT: "never" LDAP_TLS_CIPHER_SUITE: "NORMAL" LDAP_LOG_LEVEL: 256 - HOSTNAME: openldap.idp-fixture_dev.local volumes: - ./openldap/ldif/users.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/20-bootstrap-users.ldif - ./openldap/ldif/config.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/10-bootstrap-config.ldif @@ -52,8 +47,6 @@ services: - kdc-kadmin links: - kdc-kadmin - networks: - - test-net shibboleth-idp: image: "unicon/shibboleth-idp:3.4.2" @@ -71,8 +64,6 @@ services: - ./idp/shibboleth-idp/conf:/opt/shibboleth-idp/conf - ./idp/shibboleth-idp/credentials:/opt/shibboleth-idp/credentials - ./idp/shib-jetty-base/start.d/ssl.ini:/opt/shib-jetty-base/start.d/ssl.ini - networks: - - test-net oidc-provider: image: "c2id/c2id-server:7.8" @@ -80,9 +71,4 @@ services: - "8080" volumes: - ./oidc/override.properties:/etc/c2id/override.properties - networks: - - test-net - -networks: - test-net: diff --git a/x-pack/test/idp-fixture/kdc-kadmin/init-script.sh b/x-pack/test/idp-fixture/kdc-kadmin/init-script.sh index 624a8df2d4f99..ca60dec7e6131 100755 --- a/x-pack/test/idp-fixture/kdc-kadmin/init-script.sh +++ b/x-pack/test/idp-fixture/kdc-kadmin/init-script.sh @@ -148,8 +148,6 @@ tee $KRB_CONF_DIR/ldap-krb5.conf < Date: Fri, 12 Apr 2019 19:17:42 +1000 Subject: [PATCH 09/20] explicit volumes docker, see if it works --- x-pack/test/idp-fixture/build.gradle | 2 +- x-pack/test/idp-fixture/docker-compose.yml | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/x-pack/test/idp-fixture/build.gradle b/x-pack/test/idp-fixture/build.gradle index e47c5a7b0f33b..a0add00d45491 100644 --- a/x-pack/test/idp-fixture/build.gradle +++ b/x-pack/test/idp-fixture/build.gradle @@ -16,10 +16,10 @@ postProcessFixture { inputs.dir("${buildDir}/shared") File confTemplate = file("${buildDir}/shared/krb-conf/krb5.conf.template") File confFile = file("${buildDir}/shared/krb-conf/krb5.conf") + File ldapKeytab = file("${buildDir}/shared/keytabs/ldap.keytab") outputs.file(confFile) doLast { println "krb5.conf.template exists ? " + confTemplate.exists() - File ldapKeytab = file("${buildDir}/shared/keytabs/ldap.keytab") println "ldap.keytab exists ? " + ldapKeytab.exists() assert confTemplate.exists() String confContents = confTemplate.text diff --git a/x-pack/test/idp-fixture/docker-compose.yml b/x-pack/test/idp-fixture/docker-compose.yml index 3a41f62c77524..9b160e381476e 100644 --- a/x-pack/test/idp-fixture/docker-compose.yml +++ b/x-pack/test/idp-fixture/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.1' +version: '3.2' services: kdc-kadmin: build: ./kdc-kadmin @@ -10,13 +10,17 @@ services: volumes: # This is needed otherwise there won't be enough entropy to generate a new kerberos realm - /dev/urandom:/dev/random - - ./:/fixture + - type: bind + source: ./ + target: /fixture + bind: + propagation: shared ports: - "749" - "88" healthcheck: test: ["CMD", "test", "-f", "/fixture/build/shared/keytabs/ldap.keytab"] - interval: 30s + interval: 10s timeout: 10s retries: 5 @@ -41,8 +45,16 @@ services: - ./openldap/ldif/users.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/20-bootstrap-users.ldif - ./openldap/ldif/config.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/10-bootstrap-config.ldif - ./openldap/certs:/container/service/slapd/assets/certs - - ./build/shared/keytabs/ldap.keytab:/etc/krb5.keytab - - ./build/shared/krb-conf/ldap-krb5.conf:/etc/krb5.conf + - type: bind + source: ./build/shared/krb-conf/ldap-krb5.conf + target: /etc/krb5.conf + bind: + propagation: shared + - type: bind + source: ./build/shared/keytabs/ldap.keytab + target: /etc/krb5.keytab + bind: + propagation: shared depends_on: - kdc-kadmin links: From 955469e4645932f277a4c178924effdc8ae0d9c0 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 12 Apr 2019 19:42:26 +1000 Subject: [PATCH 10/20] we are running as root inside container, give permissions --- x-pack/test/idp-fixture/kdc-kadmin/init-script.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-pack/test/idp-fixture/kdc-kadmin/init-script.sh b/x-pack/test/idp-fixture/kdc-kadmin/init-script.sh index ca60dec7e6131..e483e5a745c22 100755 --- a/x-pack/test/idp-fixture/kdc-kadmin/init-script.sh +++ b/x-pack/test/idp-fixture/kdc-kadmin/init-script.sh @@ -20,6 +20,7 @@ KEYTABS_DIR=/fixture/build/shared/keytabs/ KRB_CONF_DIR=/fixture/build/shared/krb-conf/ + echo "===================================================================================" echo "Kerberos KDC and Kadmin configuration" KADMIN_PRINCIPAL_FULL=$KADMIN_PRINCIPAL@$REALM @@ -190,4 +191,8 @@ kadmind -nofork touch /tmp/kerb.started +# We are running as root in the container +chmod -R 777 $KEYTABS_DIR +chmod -R 777 $KRB_CONF_DIR + sleep infinity From b2b4963361e3d450d28c2d7fa530c3178503dabe Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 12 Apr 2019 21:16:34 +1000 Subject: [PATCH 11/20] remove unused code --- .../authc/ldap/OpenLdapKerberosGSSAPIBindTests.java | 9 +-------- x-pack/test/idp-fixture/build.gradle | 4 ---- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java b/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java index 07a1c7c15460b..bf051085c5cff 100644 --- a/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java +++ b/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java @@ -74,15 +74,8 @@ public class OpenLdapKerberosGSSAPIBindTests extends ESTestCase { private static final String LDAPCACERT_PATH = "/ca_server.pem"; private static final String LDAPTRUST_PATH = "/ca.jks"; - public static final String VALID_USER_TEMPLATE = "cn={0},ou=people,o=sevenSeas"; - public static final String VALID_USERNAME = "Thomas Masterman Hardy"; - public static final String PASSWORD = "pass"; - private Settings globalSettings; private ThreadPool threadPool; - - - private ResourceWatcherService resourceWatcherService; private Settings defaultGlobalSettings; private SSLService sslService; @@ -258,7 +251,7 @@ private void bindCredentialsSettings(boolean useKeyTab, RealmIdentifier realmId, realmSettings.setSecureSettings(secureSettings); } } - + private LdapSession session(SessionFactory factory, String username, SecureString password) { PlainActionFuture future = new PlainActionFuture<>(); factory.session(username, password, future); diff --git a/x-pack/test/idp-fixture/build.gradle b/x-pack/test/idp-fixture/build.gradle index a0add00d45491..162d82e4b2f5d 100644 --- a/x-pack/test/idp-fixture/build.gradle +++ b/x-pack/test/idp-fixture/build.gradle @@ -16,15 +16,11 @@ postProcessFixture { inputs.dir("${buildDir}/shared") File confTemplate = file("${buildDir}/shared/krb-conf/krb5.conf.template") File confFile = file("${buildDir}/shared/krb-conf/krb5.conf") - File ldapKeytab = file("${buildDir}/shared/keytabs/ldap.keytab") outputs.file(confFile) doLast { - println "krb5.conf.template exists ? " + confTemplate.exists() - println "ldap.keytab exists ? " + ldapKeytab.exists() assert confTemplate.exists() String confContents = confTemplate.text .replace("\${MAPPED_PORT}", "${ext."test.fixtures.kdc-kadmin.tcp.88"}") confFile.text = confContents - println "krb5.conf exists after writing ? " + confFile.exists() } } \ No newline at end of file From eaa59654057583a4be347c3cb677a53022d43fce Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Sat, 13 Apr 2019 04:35:28 +1000 Subject: [PATCH 12/20] remove empty keytab --- x-pack/test/idp-fixture/build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/test/idp-fixture/build.gradle b/x-pack/test/idp-fixture/build.gradle index 162d82e4b2f5d..921fecece55fc 100644 --- a/x-pack/test/idp-fixture/build.gradle +++ b/x-pack/test/idp-fixture/build.gradle @@ -8,8 +8,6 @@ preProcessFixture.doLast { // able to clean them up file("${buildDir}/shared/keytabs/${it}").mkdirs() file("${buildDir}/shared/krb-conf/${it}").mkdirs() - new File("${buildDir}/shared/krb-conf/ldap-krb5.conf").text = "" - new File("${buildDir}/shared/keytabs/ldap.keytab").text = "" } postProcessFixture { From e6eda93a43ee4c3430cfccb616d8c43884394fd8 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Sat, 13 Apr 2019 12:26:46 +1000 Subject: [PATCH 13/20] create file, check if non-file empty krb5.keytab exists as healthcheck --- x-pack/test/idp-fixture/build.gradle | 2 ++ x-pack/test/idp-fixture/docker-compose.yml | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/x-pack/test/idp-fixture/build.gradle b/x-pack/test/idp-fixture/build.gradle index 921fecece55fc..b5b6d1414642a 100644 --- a/x-pack/test/idp-fixture/build.gradle +++ b/x-pack/test/idp-fixture/build.gradle @@ -8,6 +8,8 @@ preProcessFixture.doLast { // able to clean them up file("${buildDir}/shared/keytabs/${it}").mkdirs() file("${buildDir}/shared/krb-conf/${it}").mkdirs() + file("${buildDir}/shared/krb-conf/ldap-krb5.conf").createNewFile() + file("${buildDir}/shared/keytabs/ldap.keytab").createNewFile() } postProcessFixture { diff --git a/x-pack/test/idp-fixture/docker-compose.yml b/x-pack/test/idp-fixture/docker-compose.yml index 9b160e381476e..bb5f7f39f6320 100644 --- a/x-pack/test/idp-fixture/docker-compose.yml +++ b/x-pack/test/idp-fixture/docker-compose.yml @@ -20,9 +20,9 @@ services: - "88" healthcheck: test: ["CMD", "test", "-f", "/fixture/build/shared/keytabs/ldap.keytab"] - interval: 10s - timeout: 10s - retries: 5 + interval: 5s + timeout: 5s + retries: 10 openldap: command: --copy-service --loglevel debug @@ -59,6 +59,12 @@ services: - kdc-kadmin links: - kdc-kadmin + restart: always + healthcheck: + test: ["CMD", "test", "-s", "/etc/krb5.keytab"] + interval: 5s + timeout: 5s + retries: 10 shibboleth-idp: image: "unicon/shibboleth-idp:3.4.2" From d9e5293081c8f30cf305c0a263057e3acca47bc7 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Mon, 15 Apr 2019 10:59:14 +1000 Subject: [PATCH 14/20] correct the method name --- .../security/authc/ldap/ActiveDirectorySessionFactory.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java index b01f36259119f..3a490230762d0 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java @@ -199,7 +199,7 @@ static String getBindDN(RealmConfig config) { return bindDN; } - static boolean isSaslGssapiMode(RealmConfig config) { + static boolean isSimpleBind(RealmConfig config) { String mode = config.getSetting(PoolingSessionFactorySettings.BIND_MODE); return "simple".equals(mode); } @@ -274,7 +274,7 @@ protected void doRun() throws Exception { } }; String bindDN = getBindDN(realm); - if (isSaslGssapiMode(realm) && (bindDN == null || bindDN.isEmpty())) { + if (isSimpleBind(realm) && (bindDN == null || bindDN.isEmpty())) { searchRunnable.run(); } else { LdapUtils.maybeForkThenBind(connection, bindRequestBuilder.build(), threadPool, searchRunnable); @@ -441,7 +441,7 @@ void netBiosDomainNameToDn(LDAPInterface ldapInterface, String netBiosDomainName final byte[] passwordBytes = CharArrays.toUtf8Bytes(password.getChars()); final BindRequest bind; String bindDN = getBindDN(config); - if (isSaslGssapiMode(config) && (bindDN == null || bindDN.isEmpty())) { + if (isSimpleBind(config) && (bindDN == null || bindDN.isEmpty())) { bind = new SimpleBindRequest(username, passwordBytes); } else { bind = bindRequestBuilder.build(); From 23bfb7340420e9df24419fe9315fe036ca3b4466 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 18 Apr 2019 12:21:43 +1000 Subject: [PATCH 15/20] Address review comments --- .../ldap/PoolingSessionFactorySettings.java | 12 ++-- .../authc/ldap/bind/BindRequestBuilder.java | 69 +++++++++++-------- .../ldap/bind/BindRequestBuilderTests.java | 54 ++++++++------- 3 files changed, 73 insertions(+), 62 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/PoolingSessionFactorySettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/PoolingSessionFactorySettings.java index 0c6fe69afd763..555cf7363214a 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/PoolingSessionFactorySettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/PoolingSessionFactorySettings.java @@ -45,16 +45,14 @@ public final class PoolingSessionFactorySettings { default: throw new IllegalArgumentException("only [simple] and [sasl_gssapi] bind mode are allowed, [" + v + "] is invalid"); } - }, Setting.Property.NodeScope)); + }, Property.NodeScope)); public static final Function> SASL_GSSAPI_PRINCIPAL = RealmSettings - .affixSetting("sasl_gssapi.bind.principal", key -> Setting.simpleString(key, Setting.Property.NodeScope, Property.Filtered)); - public static final Function> SASL_GSSAPI_USE_KEYTAB = RealmSettings - .affixSetting("sasl_gssapi.bind.use_keytab", key -> Setting.boolSetting(key, false, Setting.Property.NodeScope)); + .affixSetting("sasl_gssapi.bind.principal", key -> Setting.simpleString(key, Property.NodeScope)); public static final Function> SASL_GSSAPI_KEYTAB_PATH = RealmSettings - .affixSetting("sasl_gssapi.bind.keytab.path", key -> Setting.simpleString(key, Setting.Property.NodeScope, Property.Filtered)); + .affixSetting("sasl_gssapi.bind.keytab.path", key -> Setting.simpleString(key, Property.NodeScope, Property.Filtered)); public static final Function> SASL_GSSAPI_DEBUG = RealmSettings - .affixSetting("sasl_gssapi.bind.debug", key -> Setting.boolSetting(key, false, Setting.Property.NodeScope)); + .affixSetting("sasl_gssapi.bind.debug", key -> Setting.boolSetting(key, false, Property.NodeScope, Property.Dynamic)); public static final int DEFAULT_CONNECTION_POOL_INITIAL_SIZE = 0; public static final Function> POOL_INITIAL_SIZE = RealmSettings.affixSetting( @@ -84,7 +82,7 @@ private PoolingSessionFactorySettings() { public static Set> getSettings(String realmType) { return Stream.of( POOL_INITIAL_SIZE, POOL_SIZE, HEALTH_CHECK_ENABLED, HEALTH_CHECK_INTERVAL, HEALTH_CHECK_DN, BIND_DN, - LEGACY_BIND_PASSWORD, SECURE_BIND_PASSWORD, BIND_MODE, SASL_GSSAPI_PRINCIPAL, SASL_GSSAPI_USE_KEYTAB, SASL_GSSAPI_KEYTAB_PATH, + LEGACY_BIND_PASSWORD, SECURE_BIND_PASSWORD, BIND_MODE, SASL_GSSAPI_PRINCIPAL, SASL_GSSAPI_KEYTAB_PATH, SASL_GSSAPI_DEBUG ).map(f -> f.apply(realmType)).collect(Collectors.toSet()); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/bind/BindRequestBuilder.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/bind/BindRequestBuilder.java index 6d501785f88fb..6a5797e36651c 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/bind/BindRequestBuilder.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/bind/BindRequestBuilder.java @@ -22,12 +22,12 @@ import java.nio.file.Path; import java.util.function.Function; +import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.BIND_DN; import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.BIND_MODE; import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.LEGACY_BIND_PASSWORD; import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.SASL_GSSAPI_DEBUG; import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.SASL_GSSAPI_KEYTAB_PATH; import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL; -import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB; import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.SECURE_BIND_PASSWORD; /** @@ -61,12 +61,11 @@ public BindRequest build() throws LDAPException { private BindRequest buildGSSAPIBindRequest(final byte[] bindPassword) throws LDAPException { final BindRequest bindRequest; - final boolean isUseKeyTab = realmConfig.getSetting(SASL_GSSAPI_USE_KEYTAB); - Path keytabPath = validateGSSAPISettings(bindPassword, isUseKeyTab); + Path keytabPath = validateGSSAPISettings(bindPassword); final String principal = realmConfig.getSetting(SASL_GSSAPI_PRINCIPAL); final GSSAPIBindRequestProperties gssapiBindRequestProperties = new GSSAPIBindRequestProperties(principal, bindPassword); - if (isUseKeyTab) { + if (keytabPath != null) { gssapiBindRequestProperties.setUseKeyTab(true); gssapiBindRequestProperties.setKeyTabPath(keytabPath.toString()); } @@ -79,40 +78,50 @@ private BindRequest buildGSSAPIBindRequest(final byte[] bindPassword) throws LDA return bindRequest; } - private Path validateGSSAPISettings(final byte[] bindPassword, final boolean isUseKeyTab) { - Path keytabPath = null; - if (isUseKeyTab) { - if (Strings.hasText(realmConfig.getSetting(SASL_GSSAPI_KEYTAB_PATH)) == false) { - throw new IllegalArgumentException("setting [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_USE_KEYTAB) - + "] is enabled but keytab path [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_KEYTAB_PATH) - + "] has not been configured"); - } else { - keytabPath = realmConfig.env().configFile().resolve(realmConfig.getSetting(SASL_GSSAPI_KEYTAB_PATH)); - - if (Files.exists(keytabPath) == false) { - throw new IllegalArgumentException("configured key tab file [" + keytabPath + "] does not exist"); - } - if (Files.isDirectory(keytabPath)) { - throw new IllegalArgumentException("configured key tab file [" + keytabPath + "] is a directory"); - } - if (Files.isReadable(keytabPath) == false) { - throw new IllegalArgumentException("configured key tab file [" + keytabPath + "] must have read permission"); - } - } + private Path validateGSSAPISettings(final byte[] bindPassword) { + final String principal = realmConfig.getSetting(SASL_GSSAPI_PRINCIPAL); + if (Strings.hasText(principal) == false) { + throw new IllegalArgumentException( + "Principal setting [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_PRINCIPAL) + "] must be configured"); } - if (keytabPath != null && bindPassword != null) { + final String bindDn = extractBindDn.apply(realmConfig); + if (Strings.hasText(bindDn)) { throw new IllegalArgumentException( - "You cannot specify both [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_USE_KEYTAB) + "] and ([" - + RealmSettings.getFullSettingKey(realmConfig, LEGACY_BIND_PASSWORD) + "] or [" - + RealmSettings.getFullSettingKey(realmConfig, SECURE_BIND_PASSWORD) + "])"); + "You cannot specify [" + + RealmSettings.getFullSettingKey(realmConfig, BIND_DN) + "] in 'sasl_gssapi' mode"); } - if (isUseKeyTab == false && bindPassword == null) { + + final String keyTabFile = realmConfig.getSetting(SASL_GSSAPI_KEYTAB_PATH); + if (Strings.hasText(keyTabFile) == false && bindPassword == null) { throw new IllegalArgumentException( "Either keytab [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_KEYTAB_PATH) + "] or principal password " + RealmSettings.getFullSettingKey(realmConfig, SECURE_BIND_PASSWORD) + " must be configured"); } - return keytabPath; + + if (Strings.hasText(keyTabFile)) { + if (bindPassword != null) { + throw new IllegalArgumentException( + "You cannot specify both [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_KEYTAB_PATH) + "] and ([" + + RealmSettings.getFullSettingKey(realmConfig, LEGACY_BIND_PASSWORD) + "] or [" + + RealmSettings.getFullSettingKey(realmConfig, SECURE_BIND_PASSWORD) + "])"); + } else { + final Path keytabPath = realmConfig.env().configFile().resolve(keyTabFile); + if (keytabPath != null) { + if (Files.exists(keytabPath) == false) { + throw new IllegalArgumentException("configured key tab file [" + keytabPath + "] does not exist"); + } + if (Files.isDirectory(keytabPath)) { + throw new IllegalArgumentException("configured key tab file [" + keytabPath + "] is a directory"); + } + if (Files.isReadable(keytabPath) == false) { + throw new IllegalArgumentException("configured key tab file [" + keytabPath + "] must have read permission"); + } + } + return keytabPath; + } + } + return null; } private BindRequest buildSimpleBindRequest(final String bindDn, final byte[] bindPassword) { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/bind/BindRequestBuilderTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/bind/BindRequestBuilderTests.java index d4159f81e6f7a..0f07102148cf5 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/bind/BindRequestBuilderTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/bind/BindRequestBuilderTests.java @@ -37,7 +37,7 @@ import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.BIND_DN; import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.LEGACY_BIND_PASSWORD; import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.SASL_GSSAPI_KEYTAB_PATH; -import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB; +import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL; import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.SECURE_BIND_PASSWORD; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; @@ -50,7 +50,6 @@ public void testForInvalidBindModeExceptionIsThrown() throws LDAPException { final String invalidBindMode = randomAlphaOfLength(7); final Settings realmSettings = Settings.builder() .put("path.home", createTempDir()) - .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB), "true") .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL), "bind-user@DEV.LOCAL") .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.BIND_MODE), invalidBindMode).build(); final Environment env = TestEnvironment.newEnvironment(realmSettings); @@ -62,10 +61,9 @@ public void testForInvalidBindModeExceptionIsThrown() throws LDAPException { equalTo("only [simple] and [sasl_gssapi] bind mode are allowed, ["+invalidBindMode+"] is invalid")); } - public void testGSSAPIBindRequestWithNoKeytabPathFails() throws LDAPException { + public void testGSSAPIBindRequestWithNoKeytabPathAndPasswordFails() throws LDAPException { final Settings realmSettings = Settings.builder() .put("path.home", createTempDir()) - .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB), "true") .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL), "bind-user@DEV.LOCAL") .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.BIND_MODE), "sasl_gssapi").build(); final Environment env = TestEnvironment.newEnvironment(realmSettings); @@ -73,10 +71,8 @@ public void testGSSAPIBindRequestWithNoKeytabPathFails() throws LDAPException { final BindRequestBuilder builder = new BindRequestBuilder(realmConfig, c -> c.getSetting(BIND_DN, () -> null)); final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> builder.build()); - assertThat(iae.getMessage(), - equalTo("setting [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_USE_KEYTAB) - + "] is enabled but keytab path [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_KEYTAB_PATH) - + "] has not been configured")); + assertThat(iae.getMessage(), equalTo("Either keytab [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_KEYTAB_PATH) + + "] or principal password " + RealmSettings.getFullSettingKey(realmConfig, SECURE_BIND_PASSWORD) + " must be configured")); } public void testGSSAPIBindRequestWithBothKeyTabAndPasswordFails() throws LDAPException, IOException { @@ -89,7 +85,7 @@ public void testGSSAPIBindRequestWithBothKeyTabAndPasswordFails() throws LDAPExc .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.BIND_MODE), "sasl_gssapi") .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL), "bind-user@DEV.LOCAL"); KerberosRealmTestCase.writeKeyTab(dir.resolve("config").resolve("bind_principal.keytab"), null); - settingsBuilder.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB), Boolean.toString(true)) + settingsBuilder .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_KEYTAB_PATH), "bind_principal.keytab") .setSecureSettings(secureSettings(PoolingSessionFactorySettings.SECURE_BIND_PASSWORD, realmId, "passwd")); @@ -100,31 +96,40 @@ public void testGSSAPIBindRequestWithBothKeyTabAndPasswordFails() throws LDAPExc final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> builder.build()); assertThat(iae.getMessage(), - equalTo("You cannot specify both [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_USE_KEYTAB) + "] and ([" + equalTo("You cannot specify both [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_KEYTAB_PATH) + "] and ([" + RealmSettings.getFullSettingKey(realmConfig, LEGACY_BIND_PASSWORD) + "] or [" + RealmSettings.getFullSettingKey(realmConfig, SECURE_BIND_PASSWORD) + "])")); } - public void testGSSAPIBindRequestWithNoPasswordFails() throws LDAPException, IOException { - final Path dir = createTempDir(); - Path configDir = dir.resolve("config"); - if (Files.exists(configDir) == false) { - configDir = Files.createDirectory(configDir); - } - final Settings.Builder settingsBuilder = Settings.builder().put("path.home", dir) + public void testGSSAPIBindRequestWithNoPrincipalFails() throws LDAPException { + final Settings realmSettings = Settings.builder() + .put("path.home", createTempDir()) .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.BIND_MODE), "sasl_gssapi") - .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL), "bind-user@DEV.LOCAL"); - KerberosRealmTestCase.writeKeyTab(dir.resolve("config").resolve("bind_principal.keytab"), null); - settingsBuilder.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB), Boolean.toString(false)); - - final Settings realmSettings = settingsBuilder.build(); + .setSecureSettings(secureSettings(PoolingSessionFactorySettings.SECURE_BIND_PASSWORD, realmId, "passwd")) + .build(); final Environment env = TestEnvironment.newEnvironment(realmSettings); final RealmConfig realmConfig = new RealmConfig(realmId, realmSettings, env, new ThreadContext(realmSettings)); final BindRequestBuilder builder = new BindRequestBuilder(realmConfig, c -> c.getSetting(BIND_DN, () -> null)); final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> builder.build()); - assertThat(iae.getMessage(), equalTo("Either keytab [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_KEYTAB_PATH) - + "] or principal password " + RealmSettings.getFullSettingKey(realmConfig, SECURE_BIND_PASSWORD) + " must be configured")); + assertThat(iae.getMessage(), equalTo( + "Principal setting [" + RealmSettings.getFullSettingKey(realmConfig, SASL_GSSAPI_PRINCIPAL) + "] must be configured")); + } + + public void testGSSAPIBindRequestWithBindDnFails() throws LDAPException { + final Settings realmSettings = Settings.builder() + .put("path.home", createTempDir()) + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL), "bind-user@DEV.LOCAL") + .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.BIND_MODE), "sasl_gssapi") + .setSecureSettings(secureSettings(PoolingSessionFactorySettings.SECURE_BIND_PASSWORD, realmId, "passwd")) + .build(); + final Environment env = TestEnvironment.newEnvironment(realmSettings); + final RealmConfig realmConfig = new RealmConfig(realmId, realmSettings, env, new ThreadContext(realmSettings)); + final BindRequestBuilder builder = new BindRequestBuilder(realmConfig, c -> c.getSetting(BIND_DN, () -> randomAlphaOfLength(8))); + + final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> builder.build()); + assertThat(iae.getMessage(), + equalTo("You cannot specify [" + RealmSettings.getFullSettingKey(realmConfig, BIND_DN) + "] in 'sasl_gssapi' mode")); } public void testGSSAPIBindRequest() throws LDAPException, IOException { @@ -140,7 +145,6 @@ public void testGSSAPIBindRequest() throws LDAPException, IOException { if (useKeyTab) { KerberosRealmTestCase.writeKeyTab(dir.resolve("config").resolve("bind_principal.keytab"), null); settingsBuilder - .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB), Boolean.toString(useKeyTab)) .put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_KEYTAB_PATH), "bind_principal.keytab"); } else { settingsBuilder.setSecureSettings(secureSettings(PoolingSessionFactorySettings.SECURE_BIND_PASSWORD, realmId, "passwd")); From 0ee24b1627edbbce51816cfbf905032216f02b46 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 18 Apr 2019 13:02:47 +1000 Subject: [PATCH 16/20] removed use keytab setting --- .../security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java b/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java index bf051085c5cff..630c4f0ec9c91 100644 --- a/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java +++ b/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapKerberosGSSAPIBindTests.java @@ -237,13 +237,11 @@ private void verifyBindIsSuccessfulAndUserCanBeSearched(final RealmConfig.RealmI private void bindCredentialsSettings(boolean useKeyTab, RealmIdentifier realmId, Settings.Builder realmSettings) { if (useKeyTab) { - realmSettings.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB), useKeyTab); realmSettings.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL), GSSAPI_BIND_KEYTAB_PRINCIPAL); realmSettings.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_KEYTAB_PATH), getDataPath(GSSAPI_BIND_KEYTAB_PATH)); } else { - realmSettings.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_USE_KEYTAB), useKeyTab); realmSettings.put(getFullSettingKey(realmId, PoolingSessionFactorySettings.SASL_GSSAPI_PRINCIPAL), GSSAPI_BIND_USER_PRINCIPAL); final MockSecureSettings secureSettings = new MockSecureSettings(); From 1119857feb1f016ea10429abeefb9e791f0f1dd5 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 18 Apr 2019 14:51:05 +1000 Subject: [PATCH 17/20] Increase interval time for kdc kadmin health check --- x-pack/test/idp-fixture/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/idp-fixture/docker-compose.yml b/x-pack/test/idp-fixture/docker-compose.yml index bb5f7f39f6320..b77a492e7673e 100644 --- a/x-pack/test/idp-fixture/docker-compose.yml +++ b/x-pack/test/idp-fixture/docker-compose.yml @@ -20,7 +20,7 @@ services: - "88" healthcheck: test: ["CMD", "test", "-f", "/fixture/build/shared/keytabs/ldap.keytab"] - interval: 5s + interval: 10s timeout: 5s retries: 10 From 7a5dc4c5bac049147365951a6f8ae0dbdd8d8bc8 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 18 Apr 2019 16:54:18 +1000 Subject: [PATCH 18/20] add autoheal for openldap container --- x-pack/test/idp-fixture/docker-compose.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/x-pack/test/idp-fixture/docker-compose.yml b/x-pack/test/idp-fixture/docker-compose.yml index b77a492e7673e..859ec307929e1 100644 --- a/x-pack/test/idp-fixture/docker-compose.yml +++ b/x-pack/test/idp-fixture/docker-compose.yml @@ -1,5 +1,13 @@ version: '3.2' services: + autoheal: + restart: always + image: willfarrell/autoheal + environment: + - AUTOHEAL_CONTAINER_LABEL=openldap + volumes: + - /var/run/docker.sock:/var/run/docker.sock + kdc-kadmin: build: ./kdc-kadmin environment: From b6dba5da2c0c2d83b0b873c4f95fb701454f1603 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 18 Apr 2019 22:36:27 +1000 Subject: [PATCH 19/20] add docs, also make kerberos principal name filtered --- .../settings/security-settings.asciidoc | 18 +++++++++++++++++- .../ldap/PoolingSessionFactorySettings.java | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/reference/settings/security-settings.asciidoc b/docs/reference/settings/security-settings.asciidoc index b767b7869dbd9..c671b35822895 100644 --- a/docs/reference/settings/security-settings.asciidoc +++ b/docs/reference/settings/security-settings.asciidoc @@ -293,6 +293,12 @@ When using `dns_failover` or `dns_round_robin` as the load balancing type, this setting controls the amount of time to cache DNS lookups. Defaults to `1h`. +`bind.mode`:: +LDAP bind operation mode supports `simple` and `sasl_gssapi`. +`simple` bind which uses configured username (in a dn form) and credentials. +`sasl_gssapi` which uses Kerberos user principal and credentials (either password or keytab). +Defaults to `simple` LDAP bind. + `bind_dn`:: The DN of the user that is used to bind to the LDAP and perform searches. Only applicable in user search mode. @@ -300,17 +306,27 @@ If not specified, an anonymous bind is attempted. Defaults to Empty. Due to its potential security impact, `bind_dn` is not exposed via the <>. +`sasl_gssapi.bind.principal`:: +The user principal name that is used to bind to the LDAP. Only applicable when +the `bind.mode` is `sasl_gssapi`. Due to its potential security impact, +`sasl_gssapi.bind.principal` is not exposed via the <>. + `bind_password`:: deprecated[6.3] Use `secure_bind_password` instead. The password for the user that is used to bind to the LDAP directory. Defaults to Empty. Due to its potential security impact, `bind_password` is not exposed via the <>. - `secure_bind_password` (<>):: The password for the user that is used to bind to the LDAP directory. Defaults to Empty. +`sasl_gssapi.bind.keytab.path`:: +Specifies the path to the Kerberos keytab file that contains the credentials for +the user principal name used for LDAP bind. This must be a location within the +{es} configuration directory and the file must have read permissions. Only applicable +when the `bind.mode` is `sasl_gssapi`. + `user_dn_templates`:: The DN template that replaces the user name with the string `{0}`. This setting is multivalued; you can specify multiple user contexts. diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/PoolingSessionFactorySettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/PoolingSessionFactorySettings.java index 555cf7363214a..d67d46b8421f5 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/PoolingSessionFactorySettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/PoolingSessionFactorySettings.java @@ -48,7 +48,7 @@ public final class PoolingSessionFactorySettings { }, Property.NodeScope)); public static final Function> SASL_GSSAPI_PRINCIPAL = RealmSettings - .affixSetting("sasl_gssapi.bind.principal", key -> Setting.simpleString(key, Property.NodeScope)); + .affixSetting("sasl_gssapi.bind.principal", key -> Setting.simpleString(key, Property.NodeScope, Property.Filtered)); public static final Function> SASL_GSSAPI_KEYTAB_PATH = RealmSettings .affixSetting("sasl_gssapi.bind.keytab.path", key -> Setting.simpleString(key, Property.NodeScope, Property.Filtered)); public static final Function> SASL_GSSAPI_DEBUG = RealmSettings From 6fbf757be64ba219d48409972c8b9b44098b812e Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 30 Apr 2019 15:53:49 +1000 Subject: [PATCH 20/20] use filtered for setting instead of dynamic --- .../core/security/authc/ldap/PoolingSessionFactorySettings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/PoolingSessionFactorySettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/PoolingSessionFactorySettings.java index d67d46b8421f5..2581d739eeba5 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/PoolingSessionFactorySettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/PoolingSessionFactorySettings.java @@ -52,7 +52,7 @@ public final class PoolingSessionFactorySettings { public static final Function> SASL_GSSAPI_KEYTAB_PATH = RealmSettings .affixSetting("sasl_gssapi.bind.keytab.path", key -> Setting.simpleString(key, Property.NodeScope, Property.Filtered)); public static final Function> SASL_GSSAPI_DEBUG = RealmSettings - .affixSetting("sasl_gssapi.bind.debug", key -> Setting.boolSetting(key, false, Property.NodeScope, Property.Dynamic)); + .affixSetting("sasl_gssapi.bind.debug", key -> Setting.boolSetting(key, false, Property.NodeScope, Property.Filtered)); public static final int DEFAULT_CONNECTION_POOL_INITIAL_SIZE = 0; public static final Function> POOL_INITIAL_SIZE = RealmSettings.affixSetting(