Ensure we always respect a user specified filter in the AD realm (elastic/x-pack-elasticsearch#1161)

When the active directory realm was refactored to add support for authenticating against multiple
domains, only the default authenticator respected the user_search.filter setting. This commit moves
this down to the base authenticator and also changes the UPN filter to not include sAMAccountName
in the filter.

Original commit: elastic/x-pack-elasticsearch@d2c19c9bee
This commit is contained in:
Jay Modi 2017-04-27 10:20:59 -04:00 committed by GitHub
parent 4f12f04c65
commit f7fb02f21f
5 changed files with 89 additions and 18 deletions

View File

@ -194,6 +194,21 @@ operation are supported: failover and load balancing
must be a valid LDAP user search filter, for example must be a valid LDAP user search filter, for example
`(&(objectClass=user)(sAMAccountName={0}))`. For more `(&(objectClass=user)(sAMAccountName={0}))`. For more
information, see https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx[Search Filter Syntax]. 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 | `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 has membership. Defaults to the root of the Active Directory
domain. domain.

View File

@ -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 filter looks up `user` objects with either `sAMAccountName` or
`userPrincipalName`. `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`:: `group_search.base_dn`::
The context to search for groups in which the user has membership. Defaults The context to search for groups in which the user has membership. Defaults
to the root of the Active Directory domain. to the root of the Active Directory domain.

View File

@ -53,12 +53,14 @@ class ActiveDirectorySessionFactory extends SessionFactory {
static final String AD_GROUP_SEARCH_SCOPE_SETTING = "group_search.scope"; 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_BASEDN_SETTING = "user_search.base_dn";
static final String AD_USER_SEARCH_FILTER_SETTING = "user_search.filter"; 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"; static final String AD_USER_SEARCH_SCOPE_SETTING = "user_search.scope";
private static final String NETBIOS_NAME_FILTER_TEMPLATE = "(netbiosname={0})"; private static final String NETBIOS_NAME_FILTER_TEMPLATE = "(netbiosname={0})";
private final DefaultADAuthenticator defaultADAuthenticator; final DefaultADAuthenticator defaultADAuthenticator;
private final DownLevelADAuthenticator downLevelADAuthenticator; final DownLevelADAuthenticator downLevelADAuthenticator;
private final UpnADAuthenticator upnADAuthenticator; final UpnADAuthenticator upnADAuthenticator;
ActiveDirectorySessionFactory(RealmConfig config, SSLService sslService) { ActiveDirectorySessionFactory(RealmConfig config, SSLService sslService) {
super(config, 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_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_BASEDN_SETTING, Setting.Property.NodeScope));
settings.add(Setting.simpleString(AD_USER_SEARCH_FILTER_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)); settings.add(Setting.simpleString(AD_USER_SEARCH_SCOPE_SETTING, Setting.Property.NodeScope));
return settings; return settings;
} }
@ -145,15 +149,18 @@ class ActiveDirectorySessionFactory extends SessionFactory {
final GroupsResolver groupsResolver; final GroupsResolver groupsResolver;
final String userSearchDN; final String userSearchDN;
final LdapSearchScope userSearchScope; final LdapSearchScope userSearchScope;
final String userSearchFilter;
ADAuthenticator(Settings settings, TimeValue timeout, boolean ignoreReferralErrors, 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.timeout = timeout;
this.ignoreReferralErrors = ignoreReferralErrors; this.ignoreReferralErrors = ignoreReferralErrors;
this.logger = logger; this.logger = logger;
this.groupsResolver = groupsResolver; this.groupsResolver = groupsResolver;
userSearchDN = settings.get(AD_USER_SEARCH_BASEDN_SETTING, domainDN); userSearchDN = settings.get(AD_USER_SEARCH_BASEDN_SETTING, domainDN);
userSearchScope = LdapSearchScope.resolve(settings.get(AD_USER_SEARCH_SCOPE_SETTING), LdapSearchScope.SUB_TREE); 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, final void authenticate(LDAPConnection connection, String username, SecureString password,
@ -189,6 +196,11 @@ class ActiveDirectorySessionFactory extends SessionFactory {
return username; return username;
} }
// pkg-private for testing
final String getUserSearchFilter() {
return userSearchFilter;
}
abstract void searchForDN(LDAPConnection connection, String username, SecureString password, int timeLimitSeconds, abstract void searchForDN(LDAPConnection connection, String username, SecureString password, int timeLimitSeconds,
ActionListener<SearchResultEntry> listener); ActionListener<SearchResultEntry> listener);
} }
@ -200,15 +212,13 @@ class ActiveDirectorySessionFactory extends SessionFactory {
*/ */
static class DefaultADAuthenticator extends ADAuthenticator { static class DefaultADAuthenticator extends ADAuthenticator {
final String userSearchFilter;
final String domainName; final String domainName;
DefaultADAuthenticator(Settings settings, TimeValue timeout, boolean ignoreReferralErrors, DefaultADAuthenticator(Settings settings, TimeValue timeout, boolean ignoreReferralErrors,
Logger logger, GroupsResolver groupsResolver, String domainDN) { 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); domainName = settings.get(AD_DOMAIN_NAME_SETTING);
userSearchFilter = settings.get(AD_USER_SEARCH_FILTER_SETTING, "(&(objectClass=user)(|(sAMAccountName={0})" +
"(userPrincipalName={0}@" + domainName + ")))");
} }
@Override @Override
@ -235,6 +245,7 @@ class ActiveDirectorySessionFactory extends SessionFactory {
* this class contains the logic necessary to authenticate this form of a username * this class contains the logic necessary to authenticate this form of a username
*/ */
static class DownLevelADAuthenticator extends ADAuthenticator { static class DownLevelADAuthenticator extends ADAuthenticator {
static final String DOWN_LEVEL_FILTER = "(&(objectClass=user)(sAMAccountName={0}))";
Cache<String, String> domainNameCache = CacheBuilder.<String, String>builder().setMaximumWeight(100).build(); Cache<String, String> domainNameCache = CacheBuilder.<String, String>builder().setMaximumWeight(100).build();
final String domainDN; final String domainDN;
@ -246,8 +257,8 @@ class ActiveDirectorySessionFactory extends SessionFactory {
boolean ignoreReferralErrors, Logger logger, boolean ignoreReferralErrors, Logger logger,
GroupsResolver groupsResolver, String domainDN, GroupsResolver groupsResolver, String domainDN,
SSLService sslService) { SSLService sslService) {
super(config.settings(), timeout, ignoreReferralErrors, logger, groupsResolver, super(config.settings(), timeout, ignoreReferralErrors, logger, groupsResolver, domainDN,
domainDN); AD_DOWN_LEVEL_USER_SEARCH_FILTER_SETTING, DOWN_LEVEL_FILTER);
this.domainDN = domainDN; this.domainDN = domainDN;
this.settings = config.settings(); this.settings = config.settings();
this.sslService = sslService; this.sslService = sslService;
@ -269,7 +280,7 @@ class ActiveDirectorySessionFactory extends SessionFactory {
} else { } else {
try { try {
searchForEntry(connection, domainDN, LdapSearchScope.SUB_TREE.scope(), searchForEntry(connection, domainDN, LdapSearchScope.SUB_TREE.scope(),
createFilter("(&(objectClass=user)(sAMAccountName={0}))", createFilter(userSearchFilter,
accountName), timeLimitSeconds, ignoreReferralErrors, accountName), timeLimitSeconds, ignoreReferralErrors,
listener, attributesToSearchFor(groupsResolver.attributes())); listener, attributesToSearchFor(groupsResolver.attributes()));
} catch (LDAPException e) { } catch (LDAPException e) {
@ -375,11 +386,12 @@ class ActiveDirectorySessionFactory extends SessionFactory {
static class UpnADAuthenticator extends ADAuthenticator { 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, UpnADAuthenticator(Settings settings, TimeValue timeout, boolean ignoreReferralErrors,
Logger logger, GroupsResolver groupsResolver, String domainDN) { 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, void searchForDN(LDAPConnection connection, String username, SecureString password, int timeLimitSeconds,

View File

@ -299,10 +299,9 @@ public final class LdapUtils {
); );
} }
public static Filter createFilter(String filterTemplate, String... arguments) public static Filter createFilter(String filterTemplate, String... arguments) throws LDAPException {
throws LDAPException {
return Filter.create(new MessageFormat(filterTemplate, Locale.ROOT) return Filter.create(new MessageFormat(filterTemplate, Locale.ROOT)
.format((Object[]) encodeFilterValues(arguments), new StringBuffer(), null) .format(encodeFilterValues(arguments), new StringBuffer(), null)
.toString()); .toString());
} }

View File

@ -19,6 +19,8 @@ import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.Environment; 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.user.User;
import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm; 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(ROLE_MAPPING_FILE_SETTING, getDataPath("role_mapping.yml"))
.put("load_balance.type", loadBalanceType) .put("load_balance.type", loadBalanceType)
.build()); .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); ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
DnRoleMapper roleMapper = new DnRoleMapper(LdapRealm.AD_TYPE, config, resourceWatcherService); DnRoleMapper roleMapper = new DnRoleMapper(LdapRealm.AD_TYPE, config, resourceWatcherService);
LdapRealm realm = new LdapRealm(LdapRealm.AD_TYPE, config, sessionFactory, roleMapper, threadPool); 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)); 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 { private Settings settings() throws Exception {
return settings(Settings.EMPTY); return settings(Settings.EMPTY);
} }