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
`(&(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.

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
`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.

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_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<SearchResultEntry> 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<String, String> domainNameCache = CacheBuilder.<String, String>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,

View File

@ -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());
}

View File

@ -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);
}