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:
parent
4f12f04c65
commit
f7fb02f21f
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue