diff --git a/docs/en/security/authentication/active-directory-realm.asciidoc b/docs/en/security/authentication/active-directory-realm.asciidoc index e222ad4f5da..948ad47cdac 100644 --- a/docs/en/security/authentication/active-directory-realm.asciidoc +++ b/docs/en/security/authentication/active-directory-realm.asciidoc @@ -194,6 +194,21 @@ operation are supported: failover and load balancing must be a valid LDAP user search filter, for example `(&(objectClass=user)(sAMAccountName={0}))`. For more information, see https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx[Search Filter Syntax]. +| `user_search.upn_filter` | no | Specifies a filter to use to lookup a user given a user principal name. + The default filter looks up `user` objects with either + a matching `userPrincipalName` or a `sAMAccountName` matching the account + portion of the user principal name. If specified, this + must be a valid LDAP user search filter, for example + `(&(objectClass=user)(sAMAccountName={0}))`. `{0}` is the value + preceding the `@` sign in the user principal name and `{1}` is + the full user principal name provided by the user. For more + information, see https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx[Search Filter Syntax]. +| `user_search.down_level_filter` | no | Specifies a filter to use to lookup a user given a down level logon name (DOMAIN\user). + The default filter looks up `user` objects with a matching + `sAMAccountName` in the domain provided. If specified, this + must be a valid LDAP user search filter, for example + `(&(objectClass=user)(sAMAccountName={0}))`. For more + information, see https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx[Search Filter Syntax]. | `group_search.base_dn` | no | Specifies the context to search for groups in which the user has membership. Defaults to the root of the Active Directory domain. diff --git a/docs/en/settings/security-settings.asciidoc b/docs/en/settings/security-settings.asciidoc index d21951f2c1f..5671a3882f7 100644 --- a/docs/en/settings/security-settings.asciidoc +++ b/docs/en/settings/security-settings.asciidoc @@ -355,6 +355,23 @@ Specifies a filter to use to lookup a user given a username. The default filter looks up `user` objects with either `sAMAccountName` or `userPrincipalName`. +`user_search.upn_filter`:: +Specifies a filter to use to lookup a user given a user principal name. +The default filter looks up `user` objects with either +a matching `userPrincipalName` or a `sAMAccountName` matching the account +portion of the user principal name. If specified, this +must be a valid LDAP user search filter, for example +`(&(objectClass=user)(sAMAccountName={0}))`. `{0}` is the value preceding the +`@` sign in the user principal name and `{1}` is the full user principal name +provided by the user. + +`user_search.down_level_filter`:: +Specifies a filter to use to lookup a user given a down level logon name +(DOMAIN\user). The default filter looks up `user` objects with a matching +`sAMAccountName` in the domain provided. If specified, this +must be a valid LDAP user search filter, for example +`(&(objectClass=user)(sAMAccountName={0}))`. + `group_search.base_dn`:: The context to search for groups in which the user has membership. Defaults to the root of the Active Directory domain. diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java index 132c0124aee..299aa1c21eb 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java @@ -53,12 +53,14 @@ class ActiveDirectorySessionFactory extends SessionFactory { static final String AD_GROUP_SEARCH_SCOPE_SETTING = "group_search.scope"; static final String AD_USER_SEARCH_BASEDN_SETTING = "user_search.base_dn"; static final String AD_USER_SEARCH_FILTER_SETTING = "user_search.filter"; + static final String AD_UPN_USER_SEARCH_FILTER_SETTING = "user_search.upn_filter"; + static final String AD_DOWN_LEVEL_USER_SEARCH_FILTER_SETTING = "user_search.down_level_filter"; static final String AD_USER_SEARCH_SCOPE_SETTING = "user_search.scope"; private static final String NETBIOS_NAME_FILTER_TEMPLATE = "(netbiosname={0})"; - private final DefaultADAuthenticator defaultADAuthenticator; - private final DownLevelADAuthenticator downLevelADAuthenticator; - private final UpnADAuthenticator upnADAuthenticator; + final DefaultADAuthenticator defaultADAuthenticator; + final DownLevelADAuthenticator downLevelADAuthenticator; + final UpnADAuthenticator upnADAuthenticator; ActiveDirectorySessionFactory(RealmConfig config, SSLService sslService) { super(config, sslService); @@ -124,6 +126,8 @@ class ActiveDirectorySessionFactory extends SessionFactory { settings.add(Setting.simpleString(AD_GROUP_SEARCH_SCOPE_SETTING, Setting.Property.NodeScope)); settings.add(Setting.simpleString(AD_USER_SEARCH_BASEDN_SETTING, Setting.Property.NodeScope)); settings.add(Setting.simpleString(AD_USER_SEARCH_FILTER_SETTING, Setting.Property.NodeScope)); + settings.add(Setting.simpleString(AD_UPN_USER_SEARCH_FILTER_SETTING, Setting.Property.NodeScope)); + settings.add(Setting.simpleString(AD_DOWN_LEVEL_USER_SEARCH_FILTER_SETTING, Setting.Property.NodeScope)); settings.add(Setting.simpleString(AD_USER_SEARCH_SCOPE_SETTING, Setting.Property.NodeScope)); return settings; } @@ -145,15 +149,18 @@ class ActiveDirectorySessionFactory extends SessionFactory { final GroupsResolver groupsResolver; final String userSearchDN; final LdapSearchScope userSearchScope; + final String userSearchFilter; ADAuthenticator(Settings settings, TimeValue timeout, boolean ignoreReferralErrors, - Logger logger, GroupsResolver groupsResolver, String domainDN) { + Logger logger, GroupsResolver groupsResolver, String domainDN, String userSearchFilterSetting, + String defaultUserSearchFilter) { this.timeout = timeout; this.ignoreReferralErrors = ignoreReferralErrors; this.logger = logger; this.groupsResolver = groupsResolver; userSearchDN = settings.get(AD_USER_SEARCH_BASEDN_SETTING, domainDN); userSearchScope = LdapSearchScope.resolve(settings.get(AD_USER_SEARCH_SCOPE_SETTING), LdapSearchScope.SUB_TREE); + userSearchFilter = settings.get(userSearchFilterSetting, defaultUserSearchFilter); } final void authenticate(LDAPConnection connection, String username, SecureString password, @@ -189,6 +196,11 @@ class ActiveDirectorySessionFactory extends SessionFactory { return username; } + // pkg-private for testing + final String getUserSearchFilter() { + return userSearchFilter; + } + abstract void searchForDN(LDAPConnection connection, String username, SecureString password, int timeLimitSeconds, ActionListener listener); } @@ -200,15 +212,13 @@ class ActiveDirectorySessionFactory extends SessionFactory { */ static class DefaultADAuthenticator extends ADAuthenticator { - final String userSearchFilter; - final String domainName; + DefaultADAuthenticator(Settings settings, TimeValue timeout, boolean ignoreReferralErrors, Logger logger, GroupsResolver groupsResolver, String domainDN) { - super(settings, timeout, ignoreReferralErrors, logger, groupsResolver, domainDN); + super(settings, timeout, ignoreReferralErrors, logger, groupsResolver, domainDN, AD_USER_SEARCH_FILTER_SETTING, + "(&(objectClass=user)(|(sAMAccountName={0})(userPrincipalName={0}@" + settings.get(AD_DOMAIN_NAME_SETTING) + ")))"); domainName = settings.get(AD_DOMAIN_NAME_SETTING); - userSearchFilter = settings.get(AD_USER_SEARCH_FILTER_SETTING, "(&(objectClass=user)(|(sAMAccountName={0})" + - "(userPrincipalName={0}@" + domainName + ")))"); } @Override @@ -235,6 +245,7 @@ class ActiveDirectorySessionFactory extends SessionFactory { * this class contains the logic necessary to authenticate this form of a username */ static class DownLevelADAuthenticator extends ADAuthenticator { + static final String DOWN_LEVEL_FILTER = "(&(objectClass=user)(sAMAccountName={0}))"; Cache domainNameCache = CacheBuilder.builder().setMaximumWeight(100).build(); final String domainDN; @@ -246,8 +257,8 @@ class ActiveDirectorySessionFactory extends SessionFactory { boolean ignoreReferralErrors, Logger logger, GroupsResolver groupsResolver, String domainDN, SSLService sslService) { - super(config.settings(), timeout, ignoreReferralErrors, logger, groupsResolver, - domainDN); + super(config.settings(), timeout, ignoreReferralErrors, logger, groupsResolver, domainDN, + AD_DOWN_LEVEL_USER_SEARCH_FILTER_SETTING, DOWN_LEVEL_FILTER); this.domainDN = domainDN; this.settings = config.settings(); this.sslService = sslService; @@ -269,7 +280,7 @@ class ActiveDirectorySessionFactory extends SessionFactory { } else { try { searchForEntry(connection, domainDN, LdapSearchScope.SUB_TREE.scope(), - createFilter("(&(objectClass=user)(sAMAccountName={0}))", + createFilter(userSearchFilter, accountName), timeLimitSeconds, ignoreReferralErrors, listener, attributesToSearchFor(groupsResolver.attributes())); } catch (LDAPException e) { @@ -375,11 +386,12 @@ class ActiveDirectorySessionFactory extends SessionFactory { static class UpnADAuthenticator extends ADAuthenticator { - private static final String UPN_USER_FILTER = "(&(objectClass=user)(|(sAMAccountName={0})(userPrincipalName={1})))"; + static final String UPN_USER_FILTER = "(&(objectClass=user)(|(sAMAccountName={0})(userPrincipalName={1})))"; UpnADAuthenticator(Settings settings, TimeValue timeout, boolean ignoreReferralErrors, Logger logger, GroupsResolver groupsResolver, String domainDN) { - super(settings, timeout, ignoreReferralErrors, logger, groupsResolver, domainDN); + super(settings, timeout, ignoreReferralErrors, logger, groupsResolver, domainDN, + AD_UPN_USER_SEARCH_FILTER_SETTING, UPN_USER_FILTER); } void searchForDN(LDAPConnection connection, String username, SecureString password, int timeLimitSeconds, diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapUtils.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapUtils.java index 3417d157fe2..56664b0acb2 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapUtils.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapUtils.java @@ -299,10 +299,9 @@ public final class LdapUtils { ); } - public static Filter createFilter(String filterTemplate, String... arguments) - throws LDAPException { + public static Filter createFilter(String filterTemplate, String... arguments) throws LDAPException { return Filter.create(new MessageFormat(filterTemplate, Locale.ROOT) - .format((Object[]) encodeFilterValues(arguments), new StringBuffer(), null) + .format(encodeFilterValues(arguments), new StringBuffer(), null) .toString()); } diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java index f6e3c67e966..ca2cbadda57 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java @@ -19,6 +19,8 @@ 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.xpack.security.authc.ldap.ActiveDirectorySessionFactory.DownLevelADAuthenticator; +import org.elasticsearch.xpack.security.authc.ldap.ActiveDirectorySessionFactory.UpnADAuthenticator; import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm; @@ -269,7 +271,8 @@ public class ActiveDirectoryRealmTests extends ESTestCase { .put(ROLE_MAPPING_FILE_SETTING, getDataPath("role_mapping.yml")) .put("load_balance.type", loadBalanceType) .build()); - RealmConfig config = new RealmConfig("testRealmUsageStats", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings)); + RealmConfig config = new RealmConfig("testRealmUsageStats", settings, globalSettings, new Environment(globalSettings), + new ThreadContext(globalSettings)); ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService); DnRoleMapper roleMapper = new DnRoleMapper(LdapRealm.AD_TYPE, config, resourceWatcherService); LdapRealm realm = new LdapRealm(LdapRealm.AD_TYPE, config, sessionFactory, roleMapper, threadPool); @@ -283,6 +286,31 @@ public class ActiveDirectoryRealmTests extends ESTestCase { assertThat(stats, hasEntry("load_balance_type", loadBalanceType)); } + public void testDefaultSearchFilters() throws Exception { + Settings settings = settings(); + RealmConfig config = new RealmConfig("testDefaultSearchFilters", settings, globalSettings, new Environment(globalSettings), + new ThreadContext(globalSettings)); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService); + assertEquals("(&(objectClass=user)(|(sAMAccountName={0})(userPrincipalName={0}@ad.test.elasticsearch.com)))", + sessionFactory.defaultADAuthenticator.getUserSearchFilter()); + assertEquals(UpnADAuthenticator.UPN_USER_FILTER, sessionFactory.upnADAuthenticator.getUserSearchFilter()); + assertEquals(DownLevelADAuthenticator.DOWN_LEVEL_FILTER, sessionFactory.downLevelADAuthenticator.getUserSearchFilter()); + } + + public void testCustomSearchFilters() throws Exception { + Settings settings = settings(Settings.builder() + .put(ActiveDirectorySessionFactory.AD_USER_SEARCH_FILTER_SETTING, "(objectClass=default)") + .put(ActiveDirectorySessionFactory.AD_UPN_USER_SEARCH_FILTER_SETTING, "(objectClass=upn)") + .put(ActiveDirectorySessionFactory.AD_DOWN_LEVEL_USER_SEARCH_FILTER_SETTING, "(objectClass=down level)") + .build()); + RealmConfig config = new RealmConfig("testDefaultSearchFilters", settings, globalSettings, new Environment(globalSettings), + new ThreadContext(globalSettings)); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService); + assertEquals("(objectClass=default)", sessionFactory.defaultADAuthenticator.getUserSearchFilter()); + assertEquals("(objectClass=upn)", sessionFactory.upnADAuthenticator.getUserSearchFilter()); + assertEquals("(objectClass=down level)", sessionFactory.downLevelADAuthenticator.getUserSearchFilter()); + } + private Settings settings() throws Exception { return settings(Settings.EMPTY); }