LDAP: splitting AD and LDAP realm
This splits the realm into two so that configuration for both are separate. Original commit: elastic/x-pack-elasticsearch@782997d54b
This commit is contained in:
parent
e8119ec933
commit
32f0f621d5
|
@ -24,8 +24,7 @@
|
||||||
"users" : ".esvm-shield-config/users",
|
"users" : ".esvm-shield-config/users",
|
||||||
"users_roles" : ".esvm-shield-config/users_roles"
|
"users_roles" : ".esvm-shield-config/users_roles"
|
||||||
},
|
},
|
||||||
"ldap" : {
|
"active_directory" : {
|
||||||
"mode" : "active_directory",
|
|
||||||
"domain_name" : "ad.test.elasticsearch.com",
|
"domain_name" : "ad.test.elasticsearch.com",
|
||||||
"url" : "ldaps://ad.test.elasticsearch.com:636",
|
"url" : "ldaps://ad.test.elasticsearch.com:636",
|
||||||
"unmapped_groups_as_roles" : "false",
|
"unmapped_groups_as_roles" : "false",
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
},
|
},
|
||||||
"authc": {
|
"authc": {
|
||||||
"ldap" : {
|
"ldap" : {
|
||||||
"mode" : "ldap",
|
|
||||||
"url" : "ldaps://54.200.235.244:636",
|
"url" : "ldaps://54.200.235.244:636",
|
||||||
"user_dn_templates": ["uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"],
|
"user_dn_templates": ["uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"],
|
||||||
"group_search.group_search_dn" : "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com",
|
"group_search.group_search_dn" : "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com",
|
||||||
|
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.shield.authc;
|
||||||
|
|
||||||
import org.elasticsearch.common.collect.ImmutableList;
|
import org.elasticsearch.common.collect.ImmutableList;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.shield.authc.active_directory.ActiveDirectoryModule;
|
||||||
import org.elasticsearch.shield.authc.esusers.ESUsersModule;
|
import org.elasticsearch.shield.authc.esusers.ESUsersModule;
|
||||||
import org.elasticsearch.shield.authc.ldap.LdapModule;
|
import org.elasticsearch.shield.authc.ldap.LdapModule;
|
||||||
import org.elasticsearch.shield.authc.system.SystemRealm;
|
import org.elasticsearch.shield.authc.system.SystemRealm;
|
||||||
|
@ -26,7 +27,9 @@ public class AuthenticationModule extends AbstractShieldModule.Node.Spawn {
|
||||||
return ImmutableList.of(
|
return ImmutableList.of(
|
||||||
new SystemRealm.Module(settings),
|
new SystemRealm.Module(settings),
|
||||||
new ESUsersModule(settings),
|
new ESUsersModule(settings),
|
||||||
new LdapModule(settings));
|
new LdapModule(settings),
|
||||||
|
new ActiveDirectoryModule(settings)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -9,6 +9,7 @@ import org.elasticsearch.common.inject.Inject;
|
||||||
import org.elasticsearch.common.inject.internal.Nullable;
|
import org.elasticsearch.common.inject.internal.Nullable;
|
||||||
import org.elasticsearch.common.logging.ESLogger;
|
import org.elasticsearch.common.logging.ESLogger;
|
||||||
import org.elasticsearch.common.logging.ESLoggerFactory;
|
import org.elasticsearch.common.logging.ESLoggerFactory;
|
||||||
|
import org.elasticsearch.shield.authc.active_directory.ActiveDirectoryRealm;
|
||||||
import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
|
import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
|
||||||
import org.elasticsearch.shield.authc.ldap.LdapRealm;
|
import org.elasticsearch.shield.authc.ldap.LdapRealm;
|
||||||
import org.elasticsearch.shield.authc.system.SystemRealm;
|
import org.elasticsearch.shield.authc.system.SystemRealm;
|
||||||
|
@ -26,7 +27,10 @@ public class Realms {
|
||||||
private final Realm[] realms;
|
private final Realm[] realms;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public Realms(SystemRealm system, @Nullable ESUsersRealm esusers, @Nullable LdapRealm ldap) {
|
public Realms(SystemRealm system,
|
||||||
|
@Nullable ESUsersRealm esusers,
|
||||||
|
@Nullable LdapRealm ldap,
|
||||||
|
@Nullable ActiveDirectoryRealm activeDirectory) {
|
||||||
|
|
||||||
List<Realm> realms = new ArrayList<>();
|
List<Realm> realms = new ArrayList<>();
|
||||||
realms.add(system);
|
realms.add(system);
|
||||||
|
@ -38,6 +42,10 @@ public class Realms {
|
||||||
logger.info("Realm [" + ldap.type() + "] is used");
|
logger.info("Realm [" + ldap.type() + "] is used");
|
||||||
realms.add(ldap);
|
realms.add(ldap);
|
||||||
}
|
}
|
||||||
|
if (activeDirectory != null) {
|
||||||
|
logger.info("Realm [" + activeDirectory.type() + "] is used");
|
||||||
|
realms.add(activeDirectory);
|
||||||
|
}
|
||||||
this.realms = realms.toArray(new Realm[realms.size()]);
|
this.realms = realms.toArray(new Realm[realms.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,13 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.authc.ldap;
|
package org.elasticsearch.shield.authc.active_directory;
|
||||||
|
|
||||||
import org.elasticsearch.common.collect.ImmutableList;
|
import org.elasticsearch.common.collect.ImmutableList;
|
||||||
import org.elasticsearch.common.logging.ESLogger;
|
import org.elasticsearch.common.logging.ESLogger;
|
||||||
import org.elasticsearch.common.logging.ESLoggerFactory;
|
import org.elasticsearch.common.logging.ESLoggerFactory;
|
||||||
|
import org.elasticsearch.shield.authc.ldap.GenericLdapConnection;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.LdapConnection;
|
||||||
|
|
||||||
import javax.naming.NamingEnumeration;
|
import javax.naming.NamingEnumeration;
|
||||||
import javax.naming.NamingException;
|
import javax.naming.NamingException;
|
||||||
|
@ -15,7 +17,7 @@ import javax.naming.directory.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* An Ldap Connection customized for active directory.
|
||||||
*/
|
*/
|
||||||
public class ActiveDirectoryConnection implements LdapConnection {
|
public class ActiveDirectoryConnection implements LdapConnection {
|
||||||
private static final ESLogger logger = ESLoggerFactory.getLogger(GenericLdapConnection.class.getName());
|
private static final ESLogger logger = ESLoggerFactory.getLogger(GenericLdapConnection.class.getName());
|
||||||
|
@ -23,7 +25,6 @@ public class ActiveDirectoryConnection implements LdapConnection {
|
||||||
protected final DirContext ldapContext;
|
protected final DirContext ldapContext;
|
||||||
|
|
||||||
private final String groupSearchDN;
|
private final String groupSearchDN;
|
||||||
protected final String groupAttribute = "memberOf";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This object is intended to be constructed by the LdapConnectionFactory
|
* This object is intended to be constructed by the LdapConnectionFactory
|
||||||
|
@ -43,7 +44,7 @@ public class ActiveDirectoryConnection implements LdapConnection {
|
||||||
try {
|
try {
|
||||||
ldapContext.close();
|
ldapContext.close();
|
||||||
} catch (NamingException e) {
|
} catch (NamingException e) {
|
||||||
throw new LdapException("Could not close the LDAP connection", e);
|
throw new ActiveDirectoryException("Could not close the LDAP connection", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +52,7 @@ public class ActiveDirectoryConnection implements LdapConnection {
|
||||||
public List<String> getGroups() {
|
public List<String> getGroups() {
|
||||||
|
|
||||||
String groupsSearchFilter = buildGroupQuery();
|
String groupsSearchFilter = buildGroupQuery();
|
||||||
|
logger.debug("group SID to DN search filter: [{}]", groupsSearchFilter);
|
||||||
|
|
||||||
// Search for groups the user belongs to in order to get their names
|
// Search for groups the user belongs to in order to get their names
|
||||||
//Create the search controls
|
//Create the search controls
|
||||||
|
@ -58,18 +60,12 @@ public class ActiveDirectoryConnection implements LdapConnection {
|
||||||
|
|
||||||
//Specify the search scope
|
//Specify the search scope
|
||||||
groupsSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
groupsSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
||||||
|
groupsSearchCtls.setReturningAttributes(new String[] {}); //we only need the entry DN
|
||||||
|
|
||||||
//Specify the Base for the search
|
ImmutableList.Builder<String> groups = ImmutableList.builder();
|
||||||
String groupsSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
|
|
||||||
|
|
||||||
//Specify the attributes to return
|
|
||||||
String groupsReturnedAtts[]={};
|
|
||||||
groupsSearchCtls.setReturningAttributes(groupsReturnedAtts);
|
|
||||||
|
|
||||||
ImmutableList.Builder<String> groups = ImmutableList.<String>builder();
|
|
||||||
try {
|
try {
|
||||||
//Search for objects using the filter
|
//Search for objects using the filter
|
||||||
NamingEnumeration groupsAnswer = ldapContext.search(groupsSearchBase, groupsSearchFilter.toString(), groupsSearchCtls);
|
NamingEnumeration groupsAnswer = ldapContext.search(groupSearchDN, groupsSearchFilter, groupsSearchCtls);
|
||||||
|
|
||||||
//Loop through the search results
|
//Loop through the search results
|
||||||
while (groupsAnswer.hasMoreElements()) {
|
while (groupsAnswer.hasMoreElements()) {
|
||||||
|
@ -77,13 +73,13 @@ public class ActiveDirectoryConnection implements LdapConnection {
|
||||||
groups.add(sr.getNameInNamespace());
|
groups.add(sr.getNameInNamespace());
|
||||||
}
|
}
|
||||||
} catch (NamingException ne) {
|
} catch (NamingException ne) {
|
||||||
throw new LdapException("Exception occurred fetching AD groups", bindDn, ne);
|
throw new ActiveDirectoryException("Exception occurred fetching AD groups", bindDn, ne);
|
||||||
}
|
}
|
||||||
return groups.build();
|
return groups.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildGroupQuery() {
|
private String buildGroupQuery() {
|
||||||
StringBuffer groupsSearchFilter = new StringBuffer("(|");
|
StringBuilder groupsSearchFilter = new StringBuilder("(|");
|
||||||
try {
|
try {
|
||||||
SearchControls userSearchCtls = new SearchControls();
|
SearchControls userSearchCtls = new SearchControls();
|
||||||
userSearchCtls.setSearchScope(SearchControls.OBJECT_SCOPE);
|
userSearchCtls.setSearchScope(SearchControls.OBJECT_SCOPE);
|
||||||
|
@ -105,7 +101,9 @@ public class ActiveDirectoryConnection implements LdapConnection {
|
||||||
Attribute attr = (Attribute)ae.next();
|
Attribute attr = (Attribute)ae.next();
|
||||||
for (NamingEnumeration e = attr.getAll();e.hasMore();) {
|
for (NamingEnumeration e = attr.getAll();e.hasMore();) {
|
||||||
byte[] sid = (byte[])e.next();
|
byte[] sid = (byte[])e.next();
|
||||||
groupsSearchFilter.append("(objectSid=" + binarySidToStringSid(sid) + ")");
|
groupsSearchFilter.append("(objectSid=");
|
||||||
|
groupsSearchFilter.append(binarySidToStringSid(sid));
|
||||||
|
groupsSearchFilter.append(")");
|
||||||
}
|
}
|
||||||
groupsSearchFilter.append(")");
|
groupsSearchFilter.append(")");
|
||||||
}
|
}
|
||||||
|
@ -113,7 +111,7 @@ public class ActiveDirectoryConnection implements LdapConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (NamingException ne) {
|
} catch (NamingException ne) {
|
||||||
throw new LdapException("Exception occurred fetching AD groups", bindDn, ne);
|
throw new ActiveDirectoryException("Exception occurred fetching AD groups", bindDn, ne);
|
||||||
}
|
}
|
||||||
return groupsSearchFilter.toString();
|
return groupsSearchFilter.toString();
|
||||||
}
|
}
|
||||||
|
@ -127,10 +125,9 @@ public class ActiveDirectoryConnection implements LdapConnection {
|
||||||
* No idea whats going on here. Its copied from here:
|
* No idea whats going on here. Its copied from here:
|
||||||
* http://blogs.msdn.com/b/alextch/archive/2007/06/18/sample-java-application-that-retrieves-group-membership-of-an-active-directory-user-account.aspx
|
* http://blogs.msdn.com/b/alextch/archive/2007/06/18/sample-java-application-that-retrieves-group-membership-of-an-active-directory-user-account.aspx
|
||||||
* @param SID byte encoded security ID
|
* @param SID byte encoded security ID
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
public String binarySidToStringSid( byte[] SID ) {
|
public String binarySidToStringSid( byte[] SID ) {
|
||||||
String strSID = "";
|
String strSID;
|
||||||
|
|
||||||
//convert the SID into string format
|
//convert the SID into string format
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.authc.ldap;
|
package org.elasticsearch.shield.authc.active_directory;
|
||||||
|
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.collect.ImmutableMap;
|
import org.elasticsearch.common.collect.ImmutableMap;
|
||||||
|
@ -12,6 +12,8 @@ import org.elasticsearch.common.inject.Inject;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.shield.ShieldException;
|
import org.elasticsearch.shield.ShieldException;
|
||||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.LdapConnectionFactory;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory;
|
||||||
|
|
||||||
import javax.naming.Context;
|
import javax.naming.Context;
|
||||||
import javax.naming.NamingEnumeration;
|
import javax.naming.NamingEnumeration;
|
||||||
|
@ -90,13 +92,13 @@ public class ActiveDirectoryConnectionFactory extends AbstractComponent implemen
|
||||||
if (!results.hasMore()) {
|
if (!results.hasMore()) {
|
||||||
return new ActiveDirectoryConnection(ctx, name, userSearchDN);
|
return new ActiveDirectoryConnection(ctx, name, userSearchDN);
|
||||||
}
|
}
|
||||||
throw new LdapException("Search for user [" + userName + "] by principle name yielded multiple results");
|
throw new ActiveDirectoryException("Search for user [" + userName + "] by principle name yielded multiple results");
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new LdapException("Search for user [" + userName + "], search root [" + userSearchDN + "] yielded no results");
|
throw new ActiveDirectoryException("Search for user [" + userName + "], search root [" + userSearchDN + "] yielded no results");
|
||||||
|
|
||||||
} catch (NamingException e) {
|
} catch (NamingException e) {
|
||||||
throw new LdapException("Unable to authenticate user [" + userName + "] to active directory domain ["+ domainName +"]", e);
|
throw new ActiveDirectoryException("Unable to authenticate user [" + userName + "] to active directory domain ["+ domainName +"]", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* 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.shield.authc.active_directory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LdapExceptions typically wrap jndi Naming exceptions, and have an additional
|
||||||
|
* parameter of DN attached to each message.
|
||||||
|
*/
|
||||||
|
public class ActiveDirectoryException extends SecurityException {
|
||||||
|
|
||||||
|
public ActiveDirectoryException(String msg){
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActiveDirectoryException(String msg, Throwable cause){
|
||||||
|
super(msg, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActiveDirectoryException(String msg, String dn) {
|
||||||
|
this(msg, dn, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActiveDirectoryException(String msg, String dn, Throwable cause) {
|
||||||
|
super( msg + "; DN=[" + dn + "]", cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO map active directory error codes to better messages like in this:
|
||||||
|
// https://github.com/spring-projects/spring-security/blob/master/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java#L65
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* 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.shield.authc.active_directory;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.env.Environment;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.GroupToRoleMapper;
|
||||||
|
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LDAP Group to role mapper specific to the "shield.authc.ldap" package
|
||||||
|
*/
|
||||||
|
public class ActiveDirectoryGroupToRoleMapper extends GroupToRoleMapper{
|
||||||
|
@Inject
|
||||||
|
public ActiveDirectoryGroupToRoleMapper(Settings settings, Environment env, ResourceWatcherService watcherService) {
|
||||||
|
super(settings, env, watcherService);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* 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.shield.authc.active_directory;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.inject.util.Providers;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.shield.authc.Realm;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory;
|
||||||
|
import org.elasticsearch.shield.support.AbstractShieldModule;
|
||||||
|
|
||||||
|
import static org.elasticsearch.common.inject.name.Names.named;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures ActiveDirectory Realm object injections
|
||||||
|
*/
|
||||||
|
public class ActiveDirectoryModule extends AbstractShieldModule.Node {
|
||||||
|
|
||||||
|
private final boolean enabled;
|
||||||
|
|
||||||
|
public ActiveDirectoryModule(Settings settings) {
|
||||||
|
super(settings);
|
||||||
|
enabled = enabled(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureNode() {
|
||||||
|
if (enabled) {
|
||||||
|
/* This socket factory needs to be configured before any LDAP connections are created. LDAP configuration
|
||||||
|
for JNDI invokes a static getSocketFactory method from LdapSslSocketFactory. */
|
||||||
|
requestStaticInjection(LdapSslSocketFactory.class);
|
||||||
|
|
||||||
|
bind(Realm.class).annotatedWith(named(ActiveDirectoryRealm.type)).to(ActiveDirectoryRealm.class).asEagerSingleton();
|
||||||
|
} else {
|
||||||
|
bind(ActiveDirectoryRealm.class).toProvider(Providers.<ActiveDirectoryRealm>of(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean enabled(Settings settings) {
|
||||||
|
Settings authcSettings = settings.getAsSettings("shield.authc");
|
||||||
|
if (!authcSettings.names().contains(ActiveDirectoryRealm.type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Settings ldapSettings = authcSettings.getAsSettings(ActiveDirectoryRealm.type);
|
||||||
|
return ldapSettings.getAsBoolean("enabled", true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* 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.shield.authc.active_directory;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.rest.RestController;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.AbstractLdapRealm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ActiveDirectoryRealm extends AbstractLdapRealm {
|
||||||
|
|
||||||
|
public static final String type = "active_directory";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ActiveDirectoryRealm(Settings settings,
|
||||||
|
ActiveDirectoryConnectionFactory connectionFactory,
|
||||||
|
ActiveDirectoryGroupToRoleMapper roleMapper,
|
||||||
|
RestController restController) {
|
||||||
|
super(settings, connectionFactory, roleMapper, restController);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String type() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.shield.authc.ldap;
|
||||||
|
|
||||||
import org.elasticsearch.common.logging.ESLogger;
|
import org.elasticsearch.common.logging.ESLogger;
|
||||||
import org.elasticsearch.common.logging.ESLoggerFactory;
|
import org.elasticsearch.common.logging.ESLoggerFactory;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.LdapConnection;
|
||||||
|
|
||||||
import javax.naming.NamingEnumeration;
|
import javax.naming.NamingEnumeration;
|
||||||
import javax.naming.NamingException;
|
import javax.naming.NamingException;
|
||||||
|
@ -81,7 +82,7 @@ public class GenericLdapConnection implements LdapConnection {
|
||||||
* @param userDn user fully distinguished name to fetch group membership for
|
* @param userDn user fully distinguished name to fetch group membership for
|
||||||
* @return fully distinguished names of the roles
|
* @return fully distinguished names of the roles
|
||||||
*/
|
*/
|
||||||
List<String> getGroupsFromSearch(String userDn){
|
public List<String> getGroupsFromSearch(String userDn){
|
||||||
List<String> groups = new LinkedList<>();
|
List<String> groups = new LinkedList<>();
|
||||||
SearchControls search = new SearchControls();
|
SearchControls search = new SearchControls();
|
||||||
search.setReturningAttributes( new String[0] );
|
search.setReturningAttributes( new String[0] );
|
||||||
|
@ -110,7 +111,7 @@ public class GenericLdapConnection implements LdapConnection {
|
||||||
* @param userDn User fully distinguished name to fetch group membership from
|
* @param userDn User fully distinguished name to fetch group membership from
|
||||||
* @return list of groups the user is a member of.
|
* @return list of groups the user is a member of.
|
||||||
*/
|
*/
|
||||||
List<String> getGroupsFromUserAttrs(String userDn) {
|
public List<String> getGroupsFromUserAttrs(String userDn) {
|
||||||
List<String> groupDns = new LinkedList<>();
|
List<String> groupDns = new LinkedList<>();
|
||||||
try {
|
try {
|
||||||
Attributes results = ldapContext.getAttributes(userDn, new String[]{groupAttribute});
|
Attributes results = ldapContext.getAttributes(userDn, new String[]{groupAttribute});
|
||||||
|
|
|
@ -12,6 +12,8 @@ import org.elasticsearch.common.inject.Inject;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.shield.ShieldException;
|
import org.elasticsearch.shield.ShieldException;
|
||||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.LdapConnectionFactory;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory;
|
||||||
|
|
||||||
import javax.naming.Context;
|
import javax.naming.Context;
|
||||||
import javax.naming.NamingException;
|
import javax.naming.NamingException;
|
||||||
|
@ -28,12 +30,11 @@ import java.util.Hashtable;
|
||||||
* Note that even though there is a separate factory for Active Directory, this factory would work against AD. A template
|
* Note that even though there is a separate factory for Active Directory, this factory would work against AD. A template
|
||||||
* for each user context would need to be supplied.
|
* for each user context would need to be supplied.
|
||||||
*/
|
*/
|
||||||
public class StandardLdapConnectionFactory extends AbstractComponent implements LdapConnectionFactory {
|
public class GenericLdapConnectionFactory extends AbstractComponent implements LdapConnectionFactory {
|
||||||
|
|
||||||
public static final String USER_DN_TEMPLATES_SETTING = "user_dn_templates";
|
public static final String USER_DN_TEMPLATES_SETTING = "user_dn_templates";
|
||||||
public static final String GROUP_SEARCH_SUBTREE_SETTING = "group_search.subtree_search";
|
public static final String GROUP_SEARCH_SUBTREE_SETTING = "group_search.subtree_search";
|
||||||
public static final String GROUP_SEARCH_BASEDN_SETTING = "group_search.group_search_dn";
|
public static final String GROUP_SEARCH_BASEDN_SETTING = "group_search.group_search_dn";
|
||||||
static final String MODE_NAME = "ldap";
|
|
||||||
|
|
||||||
private final ImmutableMap<String, Serializable> sharedLdapEnv;
|
private final ImmutableMap<String, Serializable> sharedLdapEnv;
|
||||||
private final String[] userDnTemplates;
|
private final String[] userDnTemplates;
|
||||||
|
@ -42,7 +43,7 @@ public class StandardLdapConnectionFactory extends AbstractComponent implements
|
||||||
protected final boolean findGroupsByAttribute;
|
protected final boolean findGroupsByAttribute;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public StandardLdapConnectionFactory(Settings settings) {
|
public GenericLdapConnectionFactory(Settings settings) {
|
||||||
super(settings);
|
super(settings);
|
||||||
userDnTemplates = componentSettings.getAsArray(USER_DN_TEMPLATES_SETTING);
|
userDnTemplates = componentSettings.getAsArray(USER_DN_TEMPLATES_SETTING);
|
||||||
if (userDnTemplates == null) {
|
if (userDnTemplates == null) {
|
|
@ -5,169 +5,18 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.authc.ldap;
|
package org.elasticsearch.shield.authc.ldap;
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
|
||||||
import org.elasticsearch.common.collect.ImmutableMap;
|
|
||||||
import org.elasticsearch.common.component.AbstractComponent;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
import org.elasticsearch.common.logging.ESLogger;
|
|
||||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.shield.authc.support.RefreshListener;
|
import org.elasticsearch.shield.authc.support.ldap.GroupToRoleMapper;
|
||||||
import org.elasticsearch.shield.plugin.ShieldPlugin;
|
|
||||||
import org.elasticsearch.watcher.FileChangesListener;
|
|
||||||
import org.elasticsearch.watcher.FileWatcher;
|
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||||
|
|
||||||
import javax.naming.InvalidNameException;
|
|
||||||
import javax.naming.ldap.LdapName;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class loads and monitors the file defining the mappings of LDAP Group DNs to internal ES Roles.
|
* LDAP Group to role mapper specific to the "shield.authc.ldap" package
|
||||||
*/
|
*/
|
||||||
public class LdapGroupToRoleMapper extends AbstractComponent {
|
public class LdapGroupToRoleMapper extends GroupToRoleMapper {
|
||||||
|
|
||||||
public static final String DEFAULT_FILE_NAME = "role_mapping.yml";
|
|
||||||
public static final String ROLE_MAPPING_FILE_SETTING = "files.role_mapping";
|
|
||||||
public static final String USE_UNMAPPED_GROUPS_AS_ROLES_SETTING = "unmapped_groups_as_roles";
|
|
||||||
|
|
||||||
private final Path file;
|
|
||||||
private final boolean useUnmappedGroupsAsRoles;
|
|
||||||
private volatile ImmutableMap<LdapName, Set<String>> groupRoles;
|
|
||||||
|
|
||||||
private CopyOnWriteArrayList<RefreshListener> listeners;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public LdapGroupToRoleMapper(Settings settings, Environment env, ResourceWatcherService watcherService) {
|
public LdapGroupToRoleMapper(Settings settings, Environment env, ResourceWatcherService watcherService) {
|
||||||
this(settings, env, watcherService, null);
|
super(settings, env, watcherService);
|
||||||
}
|
|
||||||
|
|
||||||
LdapGroupToRoleMapper(Settings settings, Environment env, ResourceWatcherService watcherService, RefreshListener listener) {
|
|
||||||
super(settings);
|
|
||||||
useUnmappedGroupsAsRoles = componentSettings.getAsBoolean(USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, false);
|
|
||||||
file = resolveFile(componentSettings, env);
|
|
||||||
groupRoles = parseFile(file, logger);
|
|
||||||
FileWatcher watcher = new FileWatcher(file.getParent().toFile());
|
|
||||||
watcher.addListener(new FileListener());
|
|
||||||
watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);
|
|
||||||
listeners = new CopyOnWriteArrayList<>();
|
|
||||||
if (listener != null) {
|
|
||||||
listeners.add(listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void addListener(RefreshListener listener) {
|
|
||||||
listeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Path resolveFile(Settings settings, Environment env) {
|
|
||||||
String location = settings.get(ROLE_MAPPING_FILE_SETTING);
|
|
||||||
if (location == null) {
|
|
||||||
return ShieldPlugin.resolveConfigFile(env, DEFAULT_FILE_NAME);
|
|
||||||
}
|
|
||||||
return Paths.get(location);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ImmutableMap<LdapName, Set<String>> parseFile(Path path, ESLogger logger) {
|
|
||||||
if (!Files.exists(path)) {
|
|
||||||
return ImmutableMap.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
try (FileInputStream in = new FileInputStream( path.toFile() )){
|
|
||||||
Settings settings = ImmutableSettings.builder()
|
|
||||||
.loadFromStream(path.toString(), in)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Map<LdapName, Set<String>> groupToRoles = new HashMap<>();
|
|
||||||
Set<String> roles = settings.names();
|
|
||||||
for(String role: roles){
|
|
||||||
for(String ldapDN: settings.getAsArray(role)){
|
|
||||||
try {
|
|
||||||
LdapName group = new LdapName(ldapDN);
|
|
||||||
Set<String> groupRoles = groupToRoles.get(group);
|
|
||||||
if (groupRoles == null){
|
|
||||||
groupRoles = new HashSet<>();
|
|
||||||
groupToRoles.put(group, groupRoles);
|
|
||||||
}
|
|
||||||
groupRoles.add(role);
|
|
||||||
} catch (InvalidNameException e) {
|
|
||||||
logger.error("Invalid group DN [{}] found in ldap group to role mappings [{}]. Skipping... ", e, ldapDN, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return ImmutableMap.copyOf(groupToRoles);
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ElasticsearchException("unable to load ldap role mapper file [" + path.toAbsolutePath() + "]", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This will map the groupDN's to ES Roles
|
|
||||||
*/
|
|
||||||
public Set<String> mapRoles(List<String> groupDns) {
|
|
||||||
Set<String> roles = new HashSet<>();
|
|
||||||
for(String groupDn: groupDns){
|
|
||||||
LdapName groupLdapName = LdapUtils.ldapName(groupDn);
|
|
||||||
if (this.groupRoles.containsKey(groupLdapName)) {
|
|
||||||
roles.addAll(this.groupRoles.get(groupLdapName));
|
|
||||||
} else if (useUnmappedGroupsAsRoles) {
|
|
||||||
roles.add(getRelativeName(groupLdapName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("The roles [{}], are mapped from these LDAP groups [{}]", roles, groupDns);
|
|
||||||
}
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getRelativeName(LdapName groupLdapName) {
|
|
||||||
return (String) groupLdapName.getRdn(groupLdapName.size() - 1).getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void notifyRefresh() {
|
|
||||||
for (RefreshListener listener : listeners) {
|
|
||||||
listener.onRefresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FileListener extends FileChangesListener {
|
|
||||||
@Override
|
|
||||||
public void onFileCreated(File file) {
|
|
||||||
onFileChanged(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFileDeleted(File file) {
|
|
||||||
onFileChanged(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFileChanged(File file) {
|
|
||||||
if (file.equals(LdapGroupToRoleMapper.this.file.toFile())) {
|
|
||||||
groupRoles = parseFile(file.toPath(), logger);
|
|
||||||
notifyRefresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static interface Listener {
|
|
||||||
|
|
||||||
final Listener NOOP = new Listener() {
|
|
||||||
@Override
|
|
||||||
public void onRefresh() {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void onRefresh();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ package org.elasticsearch.shield.authc.ldap;
|
||||||
|
|
||||||
import org.elasticsearch.common.inject.util.Providers;
|
import org.elasticsearch.common.inject.util.Providers;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.shield.ShieldSettingsException;
|
|
||||||
import org.elasticsearch.shield.authc.Realm;
|
import org.elasticsearch.shield.authc.Realm;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory;
|
||||||
import org.elasticsearch.shield.support.AbstractShieldModule;
|
import org.elasticsearch.shield.support.AbstractShieldModule;
|
||||||
|
|
||||||
import static org.elasticsearch.common.inject.name.Names.named;
|
import static org.elasticsearch.common.inject.name.Names.named;
|
||||||
|
@ -19,9 +19,11 @@ import static org.elasticsearch.common.inject.name.Names.named;
|
||||||
public class LdapModule extends AbstractShieldModule.Node {
|
public class LdapModule extends AbstractShieldModule.Node {
|
||||||
|
|
||||||
private final boolean enabled;
|
private final boolean enabled;
|
||||||
|
private final Settings ldapSettings;
|
||||||
|
|
||||||
public LdapModule(Settings settings) {
|
public LdapModule(Settings settings) {
|
||||||
super(settings);
|
super(settings);
|
||||||
|
ldapSettings = settings.getComponentSettings(LdapModule.class);
|
||||||
enabled = enabled(settings);
|
enabled = enabled(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,17 +35,6 @@ public class LdapModule extends AbstractShieldModule.Node {
|
||||||
requestStaticInjection(LdapSslSocketFactory.class);
|
requestStaticInjection(LdapSslSocketFactory.class);
|
||||||
|
|
||||||
bind(Realm.class).annotatedWith(named(LdapRealm.TYPE)).to(LdapRealm.class).asEagerSingleton();
|
bind(Realm.class).annotatedWith(named(LdapRealm.TYPE)).to(LdapRealm.class).asEagerSingleton();
|
||||||
bind(LdapGroupToRoleMapper.class).asEagerSingleton();
|
|
||||||
String mode = settings.getComponentSettings(LdapModule.class).get("mode", StandardLdapConnectionFactory.MODE_NAME);
|
|
||||||
|
|
||||||
if (StandardLdapConnectionFactory.MODE_NAME.equals(mode)) {
|
|
||||||
bind(LdapConnectionFactory.class).to(StandardLdapConnectionFactory.class);
|
|
||||||
} else if (ActiveDirectoryConnectionFactory.MODE_NAME.equals(mode)) {
|
|
||||||
bind(LdapConnectionFactory.class).to(ActiveDirectoryConnectionFactory.class);
|
|
||||||
} else {
|
|
||||||
throw new ShieldSettingsException("LDAP is enabled but mode [" + mode + "] does not match [" +
|
|
||||||
StandardLdapConnectionFactory.MODE_NAME + "] or [" + ActiveDirectoryConnectionFactory.MODE_NAME +"]");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
bind(LdapRealm.class).toProvider(Providers.<LdapRealm>of(null));
|
bind(LdapRealm.class).toProvider(Providers.<LdapRealm>of(null));
|
||||||
}
|
}
|
||||||
|
@ -51,10 +42,10 @@ public class LdapModule extends AbstractShieldModule.Node {
|
||||||
|
|
||||||
public static boolean enabled(Settings settings) {
|
public static boolean enabled(Settings settings) {
|
||||||
Settings authcSettings = settings.getAsSettings("shield.authc");
|
Settings authcSettings = settings.getAsSettings("shield.authc");
|
||||||
if (!authcSettings.names().contains("ldap")) {
|
if (!authcSettings.names().contains(LdapRealm.TYPE)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Settings ldapSettings = authcSettings.getAsSettings("ldap");
|
Settings ldapSettings = authcSettings.getAsSettings(LdapRealm.TYPE);
|
||||||
return ldapSettings.getAsBoolean("enabled", true);
|
return ldapSettings.getAsBoolean("enabled", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,73 +8,25 @@ package org.elasticsearch.shield.authc.ldap;
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.rest.RestController;
|
import org.elasticsearch.rest.RestController;
|
||||||
import org.elasticsearch.shield.ShieldException;
|
import org.elasticsearch.shield.authc.support.ldap.AbstractLdapRealm;
|
||||||
import org.elasticsearch.shield.User;
|
|
||||||
import org.elasticsearch.shield.authc.AuthenticationToken;
|
|
||||||
import org.elasticsearch.shield.authc.Realm;
|
|
||||||
import org.elasticsearch.shield.authc.support.CachingUsernamePasswordRealm;
|
|
||||||
import org.elasticsearch.shield.authc.support.RefreshListener;
|
|
||||||
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
|
||||||
import org.elasticsearch.transport.TransportMessage;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticates username/password tokens against ldap, locates groups and maps them to roles.
|
* Authenticates username/password tokens against ldap, locates groups and maps them to roles.
|
||||||
*/
|
*/
|
||||||
public class LdapRealm extends CachingUsernamePasswordRealm implements Realm<UsernamePasswordToken> {
|
public class LdapRealm extends AbstractLdapRealm {
|
||||||
|
|
||||||
public static final String TYPE = "ldap";
|
public static final String TYPE = "ldap";
|
||||||
|
|
||||||
private final LdapConnectionFactory connectionFactory;
|
|
||||||
private final LdapGroupToRoleMapper roleMapper;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public LdapRealm(Settings settings, LdapConnectionFactory ldap, LdapGroupToRoleMapper roleMapper, RestController restController) {
|
public LdapRealm(Settings settings,
|
||||||
super(settings);
|
GenericLdapConnectionFactory ldap,
|
||||||
this.connectionFactory = ldap;
|
LdapGroupToRoleMapper roleMapper,
|
||||||
this.roleMapper = roleMapper;
|
RestController restController) {
|
||||||
roleMapper.addListener(new Listener());
|
super(settings, ldap, roleMapper, restController);
|
||||||
restController.registerRelevantHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String type() {
|
public String type() {
|
||||||
return TYPE;
|
return TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public UsernamePasswordToken token(TransportMessage<?> message) {
|
|
||||||
return UsernamePasswordToken.extractToken(message, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean supports(AuthenticationToken token) {
|
|
||||||
return token instanceof UsernamePasswordToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a username and password, connect to ldap, retrieve groups, map to roles and build the user.
|
|
||||||
* @return User with elasticsearch roles
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
|
||||||
try (LdapConnection session = connectionFactory.bind(token.principal(), token.credentials())) {
|
|
||||||
List<String> groupDNs = session.getGroups();
|
|
||||||
Set<String> roles = roleMapper.mapRoles(groupDNs);
|
|
||||||
return new User.Simple(token.principal(), roles.toArray(new String[roles.size()]));
|
|
||||||
} catch (ShieldException e){
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Authentication Failed for user [{}]", e, token.principal());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Listener implements RefreshListener {
|
|
||||||
@Override
|
|
||||||
public void onRefresh() {
|
|
||||||
expireAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* 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.shield.authc.support.ldap;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.rest.RestController;
|
||||||
|
import org.elasticsearch.shield.ShieldException;
|
||||||
|
import org.elasticsearch.shield.User;
|
||||||
|
import org.elasticsearch.shield.authc.AuthenticationToken;
|
||||||
|
import org.elasticsearch.shield.authc.Realm;
|
||||||
|
import org.elasticsearch.shield.authc.support.CachingUsernamePasswordRealm;
|
||||||
|
import org.elasticsearch.shield.authc.support.RefreshListener;
|
||||||
|
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
||||||
|
import org.elasticsearch.transport.TransportMessage;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supporting class for JNDI-based Realms
|
||||||
|
*/
|
||||||
|
public abstract class AbstractLdapRealm extends CachingUsernamePasswordRealm implements Realm<UsernamePasswordToken> {
|
||||||
|
|
||||||
|
protected final LdapConnectionFactory connectionFactory;
|
||||||
|
protected final GroupToRoleMapper roleMapper;
|
||||||
|
|
||||||
|
public AbstractLdapRealm(Settings settings, LdapConnectionFactory ldap, GroupToRoleMapper roleMapper, RestController restController) {
|
||||||
|
super(settings);
|
||||||
|
this.connectionFactory = ldap;
|
||||||
|
this.roleMapper = roleMapper;
|
||||||
|
roleMapper.addListener(new Listener());
|
||||||
|
restController.registerRelevantHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UsernamePasswordToken token(TransportMessage<?> message) {
|
||||||
|
return UsernamePasswordToken.extractToken(message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supports(AuthenticationToken token) {
|
||||||
|
return token instanceof UsernamePasswordToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a username and password, connect to ldap, retrieve groups, map to roles and build the user.
|
||||||
|
* @return User with elasticsearch roles
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||||
|
try (LdapConnection session = connectionFactory.bind(token.principal(), token.credentials())) {
|
||||||
|
List<String> groupDNs = session.getGroups();
|
||||||
|
Set<String> roles = roleMapper.mapRoles(groupDNs);
|
||||||
|
return new User.Simple(token.principal(), roles.toArray(new String[roles.size()]));
|
||||||
|
} catch (ShieldException e){
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Authentication Failed for user [{}]", e, token.principal());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Listener implements RefreshListener {
|
||||||
|
@Override
|
||||||
|
public void onRefresh() {
|
||||||
|
expireAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
/*
|
||||||
|
* 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.shield.authc.support.ldap;
|
||||||
|
|
||||||
|
import org.elasticsearch.ElasticsearchException;
|
||||||
|
import org.elasticsearch.common.collect.ImmutableMap;
|
||||||
|
import org.elasticsearch.common.component.AbstractComponent;
|
||||||
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
import org.elasticsearch.common.logging.ESLogger;
|
||||||
|
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.env.Environment;
|
||||||
|
import org.elasticsearch.shield.authc.support.RefreshListener;
|
||||||
|
import org.elasticsearch.shield.plugin.ShieldPlugin;
|
||||||
|
import org.elasticsearch.watcher.FileChangesListener;
|
||||||
|
import org.elasticsearch.watcher.FileWatcher;
|
||||||
|
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||||
|
|
||||||
|
import javax.naming.InvalidNameException;
|
||||||
|
import javax.naming.ldap.LdapName;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class loads and monitors the file defining the mappings of LDAP Group DNs to internal ES Roles.
|
||||||
|
*/
|
||||||
|
public abstract class GroupToRoleMapper extends AbstractComponent {
|
||||||
|
|
||||||
|
public static final String DEFAULT_FILE_NAME = "role_mapping.yml";
|
||||||
|
public static final String ROLE_MAPPING_FILE_SETTING = "files.role_mapping";
|
||||||
|
public static final String USE_UNMAPPED_GROUPS_AS_ROLES_SETTING = "unmapped_groups_as_roles";
|
||||||
|
|
||||||
|
private final Path file;
|
||||||
|
private final boolean useUnmappedGroupsAsRoles;
|
||||||
|
private volatile ImmutableMap<LdapName, Set<String>> groupRoles;
|
||||||
|
|
||||||
|
private CopyOnWriteArrayList<RefreshListener> listeners;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public GroupToRoleMapper(Settings settings, Environment env, ResourceWatcherService watcherService) {
|
||||||
|
this(settings, env, watcherService, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupToRoleMapper(Settings settings, Environment env, ResourceWatcherService watcherService, RefreshListener listener) {
|
||||||
|
super(settings);
|
||||||
|
useUnmappedGroupsAsRoles = componentSettings.getAsBoolean(USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, false);
|
||||||
|
file = resolveFile(componentSettings, env);
|
||||||
|
groupRoles = parseFile(file, logger);
|
||||||
|
FileWatcher watcher = new FileWatcher(file.getParent().toFile());
|
||||||
|
watcher.addListener(new FileListener());
|
||||||
|
watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);
|
||||||
|
listeners = new CopyOnWriteArrayList<>();
|
||||||
|
if (listener != null) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void addListener(RefreshListener listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Path resolveFile(Settings settings, Environment env) {
|
||||||
|
String location = settings.get(ROLE_MAPPING_FILE_SETTING);
|
||||||
|
if (location == null) {
|
||||||
|
return ShieldPlugin.resolveConfigFile(env, DEFAULT_FILE_NAME);
|
||||||
|
}
|
||||||
|
return Paths.get(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImmutableMap<LdapName, Set<String>> parseFile(Path path, ESLogger logger) {
|
||||||
|
if (!Files.exists(path)) {
|
||||||
|
return ImmutableMap.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
try (FileInputStream in = new FileInputStream( path.toFile() )){
|
||||||
|
Settings settings = ImmutableSettings.builder()
|
||||||
|
.loadFromStream(path.toString(), in)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Map<LdapName, Set<String>> groupToRoles = new HashMap<>();
|
||||||
|
Set<String> roles = settings.names();
|
||||||
|
for(String role: roles){
|
||||||
|
for(String ldapDN: settings.getAsArray(role)){
|
||||||
|
try {
|
||||||
|
LdapName group = new LdapName(ldapDN);
|
||||||
|
Set<String> groupRoles = groupToRoles.get(group);
|
||||||
|
if (groupRoles == null){
|
||||||
|
groupRoles = new HashSet<>();
|
||||||
|
groupToRoles.put(group, groupRoles);
|
||||||
|
}
|
||||||
|
groupRoles.add(role);
|
||||||
|
} catch (InvalidNameException e) {
|
||||||
|
logger.error("Invalid group DN [{}] found in ldap group to role mappings [{}]. Skipping... ", e, ldapDN, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return ImmutableMap.copyOf(groupToRoles);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ElasticsearchException("unable to load ldap role mapper file [" + path.toAbsolutePath() + "]", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will map the groupDN's to ES Roles
|
||||||
|
*/
|
||||||
|
public Set<String> mapRoles(List<String> groupDns) {
|
||||||
|
Set<String> roles = new HashSet<>();
|
||||||
|
for(String groupDn: groupDns){
|
||||||
|
LdapName groupLdapName = LdapUtils.ldapName(groupDn);
|
||||||
|
if (this.groupRoles.containsKey(groupLdapName)) {
|
||||||
|
roles.addAll(this.groupRoles.get(groupLdapName));
|
||||||
|
} else if (useUnmappedGroupsAsRoles) {
|
||||||
|
roles.add(getRelativeName(groupLdapName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("The roles [{}], are mapped from these LDAP groups [{}]", roles, groupDns);
|
||||||
|
}
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getRelativeName(LdapName groupLdapName) {
|
||||||
|
return (String) groupLdapName.getRdn(groupLdapName.size() - 1).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyRefresh() {
|
||||||
|
for (RefreshListener listener : listeners) {
|
||||||
|
listener.onRefresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FileListener extends FileChangesListener {
|
||||||
|
@Override
|
||||||
|
public void onFileCreated(File file) {
|
||||||
|
onFileChanged(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFileDeleted(File file) {
|
||||||
|
onFileChanged(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFileChanged(File file) {
|
||||||
|
if (file.equals(GroupToRoleMapper.this.file.toFile())) {
|
||||||
|
groupRoles = parseFile(file.toPath(), logger);
|
||||||
|
notifyRefresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static interface Listener {
|
||||||
|
|
||||||
|
final Listener NOOP = new Listener() {
|
||||||
|
@Override
|
||||||
|
public void onRefresh() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void onRefresh();
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.authc.ldap;
|
package org.elasticsearch.shield.authc.support.ldap;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.util.List;
|
import java.util.List;
|
|
@ -3,7 +3,7 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.authc.ldap;
|
package org.elasticsearch.shield.authc.support.ldap;
|
||||||
|
|
||||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.authc.ldap;
|
package org.elasticsearch.shield.authc.support.ldap;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.collect.ImmutableMap;
|
import org.elasticsearch.common.collect.ImmutableMap;
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
@ -46,7 +47,7 @@ public class LdapSslSocketFactory extends SocketFactory {
|
||||||
* This should only be invoked once to establish a static instance that will be used for each constructor.
|
* This should only be invoked once to establish a static instance that will be used for each constructor.
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
public static void init(SSLService ssl) {
|
public static void init(@Nullable SSLService ssl) {
|
||||||
if (instance != null) {
|
if (instance != null) {
|
||||||
logger.error("LdapSslSocketFactory already configured, this change could lead to threading issues");
|
logger.error("LdapSslSocketFactory already configured, this change could lead to threading issues");
|
||||||
}
|
}
|
||||||
|
@ -62,7 +63,7 @@ public class LdapSslSocketFactory extends SocketFactory {
|
||||||
* testing this is useful.
|
* testing this is useful.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
static void clear() {
|
public static void clear() {
|
||||||
logger.error("clear should only be called by tests");
|
logger.error("clear should only be called by tests");
|
||||||
instance = null;
|
instance = null;
|
||||||
}
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.authc.ldap;
|
package org.elasticsearch.shield.authc.support.ldap;
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
import org.elasticsearch.ElasticsearchException;
|
||||||
|
|
|
@ -11,7 +11,7 @@ import org.elasticsearch.common.inject.Inject;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.shield.ShieldSettingsException;
|
import org.elasticsearch.shield.ShieldSettingsException;
|
||||||
import org.elasticsearch.shield.authc.ldap.LdapModule;
|
import org.elasticsearch.shield.authc.ldap.LdapModule;
|
||||||
import org.elasticsearch.shield.authc.ldap.LdapSslSocketFactory;
|
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory;
|
||||||
|
|
||||||
import javax.net.ssl.*;
|
import javax.net.ssl.*;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
|
|
@ -3,12 +3,16 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.authc.ldap;
|
package org.elasticsearch.shield.authc.active_directory;
|
||||||
|
|
||||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.shield.authc.ldap.*;
|
||||||
import org.elasticsearch.shield.authc.support.SecuredStringTests;
|
import org.elasticsearch.shield.authc.support.SecuredStringTests;
|
||||||
import org.elasticsearch.shield.ssl.SSLService;
|
import org.elasticsearch.shield.ssl.SSLService;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.LdapConnection;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.LdapTest;
|
||||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||||
import org.elasticsearch.test.junit.annotations.Network;
|
import org.elasticsearch.test.junit.annotations.Network;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
|
@ -27,11 +31,11 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
|
||||||
public static final String AD_LDAP_URL = "ldaps://54.213.145.20:636";
|
public static final String AD_LDAP_URL = "ldaps://54.213.145.20:636";
|
||||||
public static final String PASSWORD = "NickFuryHeartsES";
|
public static final String PASSWORD = "NickFuryHeartsES";
|
||||||
public static final String AD_DOMAIN = "ad.test.elasticsearch.com";
|
public static final String AD_DOMAIN = "ad.test.elasticsearch.com";
|
||||||
public static String SETTINGS_PREFIX = LdapRealm.class.getPackage().getName().substring("com.elasticsearch.".length()) + '.';
|
public static String SETTINGS_PREFIX = ActiveDirectoryRealm.class.getPackage().getName().substring("com.elasticsearch.".length()) + '.';
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void setTrustStore() throws URISyntaxException {
|
public static void setTrustStore() throws URISyntaxException {
|
||||||
File filename = new File(LdapConnectionTests.class.getResource("ldaptrust.jks").toURI()).getAbsoluteFile();
|
File filename = new File(LdapConnectionTests.class.getResource("../support/ldap/ldaptrust.jks").toURI()).getAbsoluteFile();
|
||||||
LdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder()
|
LdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder()
|
||||||
.put("shield.ssl.keystore", filename)
|
.put("shield.ssl.keystore", filename)
|
||||||
.put("shield.ssl.keystore_password", "changeit")
|
.put("shield.ssl.keystore_password", "changeit")
|
||||||
|
@ -50,8 +54,6 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
String userName = "ironman";
|
String userName = "ironman";
|
||||||
try (LdapConnection ldap = connectionFactory.bind(userName, SecuredStringTests.build(PASSWORD))) {
|
try (LdapConnection ldap = connectionFactory.bind(userName, SecuredStringTests.build(PASSWORD))) {
|
||||||
String userDN = ldap.getAuthenticatedUserDn();
|
|
||||||
|
|
||||||
List<String> groups = ldap.getGroups();
|
List<String> groups = ldap.getGroups();
|
||||||
assertThat(groups, containsInAnyOrder(
|
assertThat(groups, containsInAnyOrder(
|
||||||
containsString("Geniuses"),
|
containsString("Geniuses"),
|
||||||
|
@ -88,8 +90,6 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
String userName = "hulk";
|
String userName = "hulk";
|
||||||
try (LdapConnection ldap = connectionFactory.bind(userName, SecuredStringTests.build(PASSWORD))) {
|
try (LdapConnection ldap = connectionFactory.bind(userName, SecuredStringTests.build(PASSWORD))) {
|
||||||
String userDN = ldap.getAuthenticatedUserDn();
|
|
||||||
|
|
||||||
List<String> groups = ldap.getGroups();
|
List<String> groups = ldap.getGroups();
|
||||||
|
|
||||||
assertThat(groups, containsInAnyOrder(
|
assertThat(groups, containsInAnyOrder(
|
||||||
|
@ -97,10 +97,10 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
|
||||||
containsString("SHIELD"),
|
containsString("SHIELD"),
|
||||||
containsString("Geniuses"),
|
containsString("Geniuses"),
|
||||||
containsString("Philanthropists"),
|
containsString("Philanthropists"),
|
||||||
containsString("Users"),
|
//containsString("Users"), Users group is in a different user context
|
||||||
containsString("Domain Users"),
|
containsString("Domain Users"),
|
||||||
containsString("Supers")
|
containsString("Supers")
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
|
||||||
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
|
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||||
String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||||
boolean isSubTreeSearch = true;
|
boolean isSubTreeSearch = true;
|
||||||
StandardLdapConnectionFactory connectionFactory = new StandardLdapConnectionFactory(
|
GenericLdapConnectionFactory connectionFactory = new GenericLdapConnectionFactory(
|
||||||
LdapTest.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, isSubTreeSearch));
|
LdapTest.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, isSubTreeSearch));
|
||||||
|
|
||||||
String user = "Bruce Banner";
|
String user = "Bruce Banner";
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* 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.shield.authc.active_directory;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.inject.AbstractModule;
|
||||||
|
import org.elasticsearch.common.inject.Guice;
|
||||||
|
import org.elasticsearch.common.inject.Injector;
|
||||||
|
import org.elasticsearch.common.inject.util.Providers;
|
||||||
|
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.env.Environment;
|
||||||
|
import org.elasticsearch.rest.RestController;
|
||||||
|
import org.elasticsearch.shield.ssl.SSLService;
|
||||||
|
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||||
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
|
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ActiveDirectoryModuleTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
|
private ThreadPool threadPool;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() throws Exception {
|
||||||
|
threadPool = new ThreadPool("test");
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void shutdown() {
|
||||||
|
threadPool.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEnabled() throws Exception {
|
||||||
|
assertThat(ActiveDirectoryModule.enabled(ImmutableSettings.EMPTY), is(false));
|
||||||
|
Settings settings = ImmutableSettings.builder()
|
||||||
|
.put("shield.authc", false)
|
||||||
|
.build();
|
||||||
|
assertThat(ActiveDirectoryModule.enabled(settings), is(false));
|
||||||
|
settings = ImmutableSettings.builder()
|
||||||
|
.put("shield.authc.active_directory.enabled", false)
|
||||||
|
.build();
|
||||||
|
assertThat(ActiveDirectoryModule.enabled(settings), is(false));
|
||||||
|
settings = ImmutableSettings.builder()
|
||||||
|
.put("shield.authc.active_directory.enabled", true)
|
||||||
|
.build();
|
||||||
|
assertThat(ActiveDirectoryModule.enabled(settings), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws Exception {
|
||||||
|
Settings settings = ImmutableSettings.builder()
|
||||||
|
.put("client.type", "node")
|
||||||
|
.put("shield.authc.active_directory.url", "ldap://example.com:389")
|
||||||
|
.put("shield.authc.active_directory.domain_name", "example.com")
|
||||||
|
.build();
|
||||||
|
Injector injector = Guice.createInjector(new TestModule(settings), new ActiveDirectoryModule(settings));
|
||||||
|
ActiveDirectoryRealm realm = injector.getInstance(ActiveDirectoryRealm.class);
|
||||||
|
assertThat(realm, notNullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestModule extends AbstractModule {
|
||||||
|
private final Settings settings;
|
||||||
|
|
||||||
|
public TestModule(Settings settings) {
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
Environment env = new Environment(settings);
|
||||||
|
bind(Settings.class).toInstance(settings);
|
||||||
|
bind(Environment.class).toInstance(env);
|
||||||
|
bind(ThreadPool.class).toInstance(threadPool);
|
||||||
|
bind(ResourceWatcherService.class).asEagerSingleton();
|
||||||
|
bind(RestController.class).toInstance(mock(RestController.class));
|
||||||
|
bind(SSLService.class).toProvider(Providers.<SSLService>of(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,169 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.shield.authc.ldap;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
|
||||||
|
|
||||||
import javax.naming.Context;
|
|
||||||
import javax.naming.NamingEnumeration;
|
|
||||||
import javax.naming.NamingException;
|
|
||||||
import javax.naming.directory.Attribute;
|
|
||||||
import javax.naming.directory.Attributes;
|
|
||||||
import javax.naming.directory.SearchControls;
|
|
||||||
import javax.naming.directory.SearchResult;
|
|
||||||
import javax.naming.ldap.InitialLdapContext;
|
|
||||||
import javax.naming.ldap.LdapContext;
|
|
||||||
import java.io.File;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.Hashtable;
|
|
||||||
|
|
||||||
public class ADGroups {
|
|
||||||
public static void main (String[] args) throws URISyntaxException {
|
|
||||||
LdapSslSocketFactory.init(ImmutableSettings.builder()
|
|
||||||
.put("shield.authc.ldap.truststore", new File(LdapConnectionTests.class.getResource("ldaptrust.jks").toURI()))
|
|
||||||
.build());
|
|
||||||
|
|
||||||
Hashtable env = new Hashtable();
|
|
||||||
String adminName = "CN=Tony Stark,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
|
||||||
String adminPassword = "NickFuryHeartsES";
|
|
||||||
String ldapURL = "ldaps://ad.test.elasticsearch.com:636";
|
|
||||||
//set security credentials, note using simple cleartext authentication
|
|
||||||
env.put(Context.SECURITY_AUTHENTICATION, "simple");
|
|
||||||
env.put(Context.SECURITY_PRINCIPAL, adminName);
|
|
||||||
env.put(Context.SECURITY_CREDENTIALS, adminPassword);
|
|
||||||
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
|
|
||||||
env.put("java.naming.ldap.factory.socket", LdapSslSocketFactory.class.getName());
|
|
||||||
|
|
||||||
//connect to my domain controller
|
|
||||||
env.put(Context.PROVIDER_URL, ldapURL);
|
|
||||||
//specify attributes to be returned in binary format
|
|
||||||
env.put("java.naming.ldap.attributes.binary", "tokenGroups");
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
//Create the initial directory context
|
|
||||||
LdapContext ctx = new InitialLdapContext(env,null);
|
|
||||||
|
|
||||||
//Create the search controls
|
|
||||||
SearchControls userSearchCtls = new SearchControls();
|
|
||||||
|
|
||||||
//Specify the search scope
|
|
||||||
userSearchCtls.setSearchScope(SearchControls.OBJECT_SCOPE);
|
|
||||||
|
|
||||||
//specify the LDAP search filter to find the user in question
|
|
||||||
String userSearchFilter = "(objectClass=user)";
|
|
||||||
|
|
||||||
//paceholder for an LDAP filter that will store SIDs of the groups the user belongs to
|
|
||||||
StringBuffer groupsSearchFilter = new StringBuffer();
|
|
||||||
groupsSearchFilter.append("(|");
|
|
||||||
|
|
||||||
//Specify the Base for the search
|
|
||||||
String userSearchBase = "CN=Tony Stark,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
|
||||||
|
|
||||||
//Specify the attributes to return
|
|
||||||
String userReturnedAtts[] = { "tokenGroups", "CN" };
|
|
||||||
userSearchCtls.setReturningAttributes(userReturnedAtts);
|
|
||||||
|
|
||||||
//Search for objects using the filter
|
|
||||||
NamingEnumeration userAnswer = ctx.search(userSearchBase, userSearchFilter, userSearchCtls);
|
|
||||||
|
|
||||||
//Loop through the search results
|
|
||||||
while (userAnswer.hasMoreElements()) {
|
|
||||||
|
|
||||||
SearchResult sr = (SearchResult)userAnswer.next();
|
|
||||||
Attributes attrs = sr.getAttributes();
|
|
||||||
|
|
||||||
if (attrs != null) {
|
|
||||||
try {
|
|
||||||
for (NamingEnumeration ae = attrs.getAll();ae.hasMore();) {
|
|
||||||
Attribute attr = (Attribute)ae.next();
|
|
||||||
for (NamingEnumeration e = attr.getAll();e.hasMore();) {
|
|
||||||
|
|
||||||
byte[] sid = (byte[])e.next();
|
|
||||||
groupsSearchFilter.append("(objectSid=" + binarySidToStringSid(sid) + ")");
|
|
||||||
|
|
||||||
}
|
|
||||||
groupsSearchFilter.append(")");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (NamingException e) {
|
|
||||||
System.err.println("Problem listing membership: " + e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Search for groups the user belongs to in order to get their names
|
|
||||||
//Create the search controls
|
|
||||||
SearchControls groupsSearchCtls = new SearchControls();
|
|
||||||
|
|
||||||
//Specify the search scope
|
|
||||||
groupsSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
|
||||||
|
|
||||||
//Specify the Base for the search
|
|
||||||
String groupsSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
|
|
||||||
|
|
||||||
//Specify the attributes to return
|
|
||||||
String groupsReturnedAtts[]={};
|
|
||||||
groupsSearchCtls.setReturningAttributes(groupsReturnedAtts);
|
|
||||||
|
|
||||||
//Search for objects using the filter
|
|
||||||
NamingEnumeration groupsAnswer = ctx.search(groupsSearchBase, groupsSearchFilter.toString(), groupsSearchCtls);
|
|
||||||
|
|
||||||
//Loop through the search results
|
|
||||||
while (groupsAnswer.hasMoreElements()) {
|
|
||||||
SearchResult sr = (SearchResult)groupsAnswer.next();
|
|
||||||
System.out.println(sr.getNameInNamespace());
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.close();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
catch (NamingException e) {
|
|
||||||
System.err.println("Problem searching directory: " + e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static final String binarySidToStringSid( byte[] SID ) {
|
|
||||||
|
|
||||||
String strSID = "";
|
|
||||||
|
|
||||||
//convert the SID into string format
|
|
||||||
|
|
||||||
long version;
|
|
||||||
long authority;
|
|
||||||
long count;
|
|
||||||
long rid;
|
|
||||||
|
|
||||||
strSID = "S";
|
|
||||||
version = SID[0];
|
|
||||||
strSID = strSID + "-" + Long.toString(version);
|
|
||||||
authority = SID[4];
|
|
||||||
|
|
||||||
for (int i = 0;i<4;i++) {
|
|
||||||
authority <<= 8;
|
|
||||||
authority += SID[4+i] & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
strSID = strSID + "-" + Long.toString(authority);
|
|
||||||
count = SID[2];
|
|
||||||
count <<= 8;
|
|
||||||
count += SID[1] & 0xFF;
|
|
||||||
for (int j=0;j<count;j++) {
|
|
||||||
rid = SID[11 + (j*4)] & 0xFF;
|
|
||||||
for (int k=1;k<4;k++) {
|
|
||||||
rid <<= 8;
|
|
||||||
rid += SID[11-k + (j*4)] & 0xFF;
|
|
||||||
}
|
|
||||||
strSID = strSID + "-" + Long.toString(rid);
|
|
||||||
}
|
|
||||||
return strSID;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.shield.authc.ldap;
|
||||||
|
|
||||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||||
import org.elasticsearch.shield.authc.support.SecuredStringTests;
|
import org.elasticsearch.shield.authc.support.SecuredStringTests;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.LdapTest;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -26,7 +27,7 @@ public class LdapConnectionTests extends LdapTest {
|
||||||
"wrongname={0},ou=people,o=sevenSeas",
|
"wrongname={0},ou=people,o=sevenSeas",
|
||||||
"cn={0},ou=people,o=sevenSeas", //this last one should work
|
"cn={0},ou=people,o=sevenSeas", //this last one should work
|
||||||
};
|
};
|
||||||
StandardLdapConnectionFactory connectionFactory = new StandardLdapConnectionFactory(
|
GenericLdapConnectionFactory connectionFactory = new GenericLdapConnectionFactory(
|
||||||
buildLdapSettings(ldapUrls, userTemplates, groupSearchBase, isSubTreeSearch));
|
buildLdapSettings(ldapUrls, userTemplates, groupSearchBase, isSubTreeSearch));
|
||||||
|
|
||||||
String user = "Horatio Hornblower";
|
String user = "Horatio Hornblower";
|
||||||
|
@ -50,7 +51,7 @@ public class LdapConnectionTests extends LdapTest {
|
||||||
"wrongname={0},ou=people,o=sevenSeas",
|
"wrongname={0},ou=people,o=sevenSeas",
|
||||||
"asdf={0},ou=people,o=sevenSeas", //none of these should work
|
"asdf={0},ou=people,o=sevenSeas", //none of these should work
|
||||||
};
|
};
|
||||||
StandardLdapConnectionFactory ldapFac = new StandardLdapConnectionFactory(
|
GenericLdapConnectionFactory ldapFac = new GenericLdapConnectionFactory(
|
||||||
buildLdapSettings(ldapUrl, userTemplates, groupSearchBase, isSubTreeSearch));
|
buildLdapSettings(ldapUrl, userTemplates, groupSearchBase, isSubTreeSearch));
|
||||||
|
|
||||||
String user = "Horatio Hornblower";
|
String user = "Horatio Hornblower";
|
||||||
|
@ -65,7 +66,7 @@ public class LdapConnectionTests extends LdapTest {
|
||||||
String userTemplate = "cn={0},ou=people,o=sevenSeas";
|
String userTemplate = "cn={0},ou=people,o=sevenSeas";
|
||||||
|
|
||||||
boolean isSubTreeSearch = true;
|
boolean isSubTreeSearch = true;
|
||||||
StandardLdapConnectionFactory ldapFac = new StandardLdapConnectionFactory(
|
GenericLdapConnectionFactory ldapFac = new GenericLdapConnectionFactory(
|
||||||
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
|
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
|
||||||
|
|
||||||
String user = "Horatio Hornblower";
|
String user = "Horatio Hornblower";
|
||||||
|
@ -82,7 +83,7 @@ public class LdapConnectionTests extends LdapTest {
|
||||||
String groupSearchBase = "ou=crews,ou=groups,o=sevenSeas";
|
String groupSearchBase = "ou=crews,ou=groups,o=sevenSeas";
|
||||||
String userTemplate = "cn={0},ou=people,o=sevenSeas";
|
String userTemplate = "cn={0},ou=people,o=sevenSeas";
|
||||||
boolean isSubTreeSearch = false;
|
boolean isSubTreeSearch = false;
|
||||||
StandardLdapConnectionFactory ldapFac = new StandardLdapConnectionFactory(
|
GenericLdapConnectionFactory ldapFac = new GenericLdapConnectionFactory(
|
||||||
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
|
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
|
||||||
|
|
||||||
String user = "Horatio Hornblower";
|
String user = "Horatio Hornblower";
|
||||||
|
|
|
@ -8,6 +8,7 @@ package org.elasticsearch.shield.authc.ldap;
|
||||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.GroupToRoleMapper;
|
||||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||||
|
@ -52,12 +53,12 @@ public class LdapGroupToRoleMapperTest extends ElasticsearchTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testYaml() throws IOException {
|
public void testYaml() throws IOException {
|
||||||
File file = this.getResource("role_mapping.yml");
|
File file = this.getResource("../support/ldap/role_mapping.yml");
|
||||||
Settings settings = ImmutableSettings.settingsBuilder()
|
Settings settings = ImmutableSettings.settingsBuilder()
|
||||||
.put("shield.authc.ldap." + LdapGroupToRoleMapper.ROLE_MAPPING_FILE_SETTING, file.getCanonicalPath())
|
.put("shield.authc.ldap." + LdapGroupToRoleMapper.ROLE_MAPPING_FILE_SETTING, file.getCanonicalPath())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
LdapGroupToRoleMapper mapper = new LdapGroupToRoleMapper(settings,
|
GroupToRoleMapper mapper = new LdapGroupToRoleMapper(settings,
|
||||||
new Environment(settings),
|
new Environment(settings),
|
||||||
new ResourceWatcherService(settings, threadPool));
|
new ResourceWatcherService(settings, threadPool));
|
||||||
|
|
||||||
|
@ -70,10 +71,10 @@ public class LdapGroupToRoleMapperTest extends ElasticsearchTestCase {
|
||||||
@Test
|
@Test
|
||||||
public void testRelativeDN() {
|
public void testRelativeDN() {
|
||||||
Settings settings = ImmutableSettings.builder()
|
Settings settings = ImmutableSettings.builder()
|
||||||
.put("shield.authc.ldap." + LdapGroupToRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, true)
|
.put("shield.authc.ldap." + GroupToRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, true)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
LdapGroupToRoleMapper mapper = new LdapGroupToRoleMapper(settings,
|
GroupToRoleMapper mapper = new LdapGroupToRoleMapper(settings,
|
||||||
new Environment(settings),
|
new Environment(settings),
|
||||||
new ResourceWatcherService(settings, threadPool));
|
new ResourceWatcherService(settings, threadPool));
|
||||||
|
|
||||||
|
|
|
@ -5,18 +5,43 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.authc.ldap;
|
package org.elasticsearch.shield.authc.ldap;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.inject.AbstractModule;
|
||||||
|
import org.elasticsearch.common.inject.Guice;
|
||||||
|
import org.elasticsearch.common.inject.Injector;
|
||||||
|
import org.elasticsearch.common.inject.util.Providers;
|
||||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.env.Environment;
|
||||||
|
import org.elasticsearch.rest.RestController;
|
||||||
|
import org.elasticsearch.shield.ssl.SSLService;
|
||||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||||
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
|
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class LdapModuleTests extends ElasticsearchTestCase {
|
public class LdapModuleTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
|
private ThreadPool threadPool;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() throws Exception {
|
||||||
|
threadPool = new ThreadPool("test");
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void shutdown() {
|
||||||
|
threadPool.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEnabled() throws Exception {
|
public void testEnabled() throws Exception {
|
||||||
assertThat(LdapModule.enabled(ImmutableSettings.EMPTY), is(false));
|
assertThat(LdapModule.enabled(ImmutableSettings.EMPTY), is(false));
|
||||||
|
@ -33,4 +58,34 @@ public class LdapModuleTests extends ElasticsearchTestCase {
|
||||||
.build();
|
.build();
|
||||||
assertThat(LdapModule.enabled(settings), is(true));
|
assertThat(LdapModule.enabled(settings), is(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws Exception {
|
||||||
|
Settings settings = ImmutableSettings.builder()
|
||||||
|
.put("client.type", "node")
|
||||||
|
.put("shield.authc.ldap.url", "ldap://example.com:389")
|
||||||
|
.build();
|
||||||
|
Injector injector = Guice.createInjector(new TestModule(settings), new LdapModule(settings));
|
||||||
|
LdapRealm realm = injector.getInstance(LdapRealm.class);
|
||||||
|
assertThat(realm, notNullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestModule extends AbstractModule {
|
||||||
|
private final Settings settings;
|
||||||
|
|
||||||
|
public TestModule(Settings settings) {
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
Environment env = new Environment(settings);
|
||||||
|
bind(Settings.class).toInstance(settings);
|
||||||
|
bind(Environment.class).toInstance(env);
|
||||||
|
bind(ThreadPool.class).toInstance(threadPool);
|
||||||
|
bind(ResourceWatcherService.class).asEagerSingleton();
|
||||||
|
bind(RestController.class).toInstance(mock(RestController.class));
|
||||||
|
bind(SSLService.class).toProvider(Providers.<SSLService>of(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import org.elasticsearch.shield.User;
|
||||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||||
import org.elasticsearch.shield.authc.support.SecuredStringTests;
|
import org.elasticsearch.shield.authc.support.SecuredStringTests;
|
||||||
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.LdapTest;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
@ -48,7 +49,7 @@ public class LdapRealmTest extends LdapTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRestHeaderRegistration() {
|
public void testRestHeaderRegistration() {
|
||||||
new LdapRealm(ImmutableSettings.EMPTY, mock(LdapConnectionFactory.class), mock(LdapGroupToRoleMapper.class), restController);
|
new LdapRealm(ImmutableSettings.EMPTY, mock(GenericLdapConnectionFactory.class), mock(LdapGroupToRoleMapper.class), restController);
|
||||||
verify(restController).registerRelevantHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER);
|
verify(restController).registerRelevantHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +59,8 @@ public class LdapRealmTest extends LdapTest {
|
||||||
boolean isSubTreeSearch = true;
|
boolean isSubTreeSearch = true;
|
||||||
String userTemplate = VALID_USER_TEMPLATE;
|
String userTemplate = VALID_USER_TEMPLATE;
|
||||||
Settings settings = buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch);
|
Settings settings = buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch);
|
||||||
StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(settings);
|
GenericLdapConnectionFactory ldapFactory = new GenericLdapConnectionFactory(settings);
|
||||||
|
|
||||||
LdapRealm ldap = new LdapRealm(buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper(resourceWatcherService), restController);
|
LdapRealm ldap = new LdapRealm(buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper(resourceWatcherService), restController);
|
||||||
|
|
||||||
User user = ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
|
User user = ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
|
||||||
|
@ -71,7 +73,7 @@ public class LdapRealmTest extends LdapTest {
|
||||||
String groupSearchBase = "ou=crews,ou=groups,o=sevenSeas";
|
String groupSearchBase = "ou=crews,ou=groups,o=sevenSeas";
|
||||||
boolean isSubTreeSearch = false;
|
boolean isSubTreeSearch = false;
|
||||||
String userTemplate = VALID_USER_TEMPLATE;
|
String userTemplate = VALID_USER_TEMPLATE;
|
||||||
StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(
|
GenericLdapConnectionFactory ldapFactory = new GenericLdapConnectionFactory(
|
||||||
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
|
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
|
||||||
|
|
||||||
LdapRealm ldap = new LdapRealm(buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper(resourceWatcherService), restController);
|
LdapRealm ldap = new LdapRealm(buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper(resourceWatcherService), restController);
|
||||||
|
@ -86,7 +88,7 @@ public class LdapRealmTest extends LdapTest {
|
||||||
String groupSearchBase = "o=sevenSeas";
|
String groupSearchBase = "o=sevenSeas";
|
||||||
boolean isSubTreeSearch = true;
|
boolean isSubTreeSearch = true;
|
||||||
String userTemplate = VALID_USER_TEMPLATE;
|
String userTemplate = VALID_USER_TEMPLATE;
|
||||||
StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(
|
GenericLdapConnectionFactory ldapFactory = new GenericLdapConnectionFactory(
|
||||||
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch) );
|
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch) );
|
||||||
|
|
||||||
ldapFactory = spy(ldapFactory);
|
ldapFactory = spy(ldapFactory);
|
||||||
|
@ -103,7 +105,7 @@ public class LdapRealmTest extends LdapTest {
|
||||||
String groupSearchBase = "o=sevenSeas";
|
String groupSearchBase = "o=sevenSeas";
|
||||||
boolean isSubTreeSearch = true;
|
boolean isSubTreeSearch = true;
|
||||||
String userTemplate = VALID_USER_TEMPLATE;
|
String userTemplate = VALID_USER_TEMPLATE;
|
||||||
StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(
|
GenericLdapConnectionFactory ldapFactory = new GenericLdapConnectionFactory(
|
||||||
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch) );
|
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch) );
|
||||||
|
|
||||||
LdapGroupToRoleMapper roleMapper = buildGroupAsRoleMapper(resourceWatcherService);
|
LdapGroupToRoleMapper roleMapper = buildGroupAsRoleMapper(resourceWatcherService);
|
||||||
|
@ -129,7 +131,7 @@ public class LdapRealmTest extends LdapTest {
|
||||||
String groupSearchBase = "o=sevenSeas";
|
String groupSearchBase = "o=sevenSeas";
|
||||||
boolean isSubTreeSearch = true;
|
boolean isSubTreeSearch = true;
|
||||||
String userTemplate = VALID_USER_TEMPLATE;
|
String userTemplate = VALID_USER_TEMPLATE;
|
||||||
StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(
|
GenericLdapConnectionFactory ldapFactory = new GenericLdapConnectionFactory(
|
||||||
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
|
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
|
||||||
|
|
||||||
ldapFactory = spy(ldapFactory);
|
ldapFactory = spy(ldapFactory);
|
||||||
|
|
|
@ -8,6 +8,7 @@ package org.elasticsearch.shield.authc.ldap;
|
||||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||||
import org.elasticsearch.shield.authc.support.SecuredStringTests;
|
import org.elasticsearch.shield.authc.support.SecuredStringTests;
|
||||||
import org.elasticsearch.shield.ssl.SSLService;
|
import org.elasticsearch.shield.ssl.SSLService;
|
||||||
|
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory;
|
||||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||||
import org.elasticsearch.test.junit.annotations.Network;
|
import org.elasticsearch.test.junit.annotations.Network;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
|
@ -28,7 +29,7 @@ public class OpenLdapTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void setTrustStore() throws URISyntaxException {
|
public static void setTrustStore() throws URISyntaxException {
|
||||||
File filename = new File(LdapConnectionTests.class.getResource("ldaptrust.jks").toURI()).getAbsoluteFile();
|
File filename = new File(LdapConnectionTests.class.getResource("../support/ldap/ldaptrust.jks").toURI()).getAbsoluteFile();
|
||||||
LdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder()
|
LdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder()
|
||||||
.put("shield.ssl.keystore", filename)
|
.put("shield.ssl.keystore", filename)
|
||||||
.put("shield.ssl.keystore_password", "changeit")
|
.put("shield.ssl.keystore_password", "changeit")
|
||||||
|
@ -47,7 +48,7 @@ public class OpenLdapTests extends ElasticsearchTestCase {
|
||||||
String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
|
String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
|
||||||
String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
|
String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
|
||||||
boolean isSubTreeSearch = true;
|
boolean isSubTreeSearch = true;
|
||||||
StandardLdapConnectionFactory connectionFactory = new StandardLdapConnectionFactory(
|
GenericLdapConnectionFactory connectionFactory = new GenericLdapConnectionFactory(
|
||||||
LdapConnectionTests.buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, isSubTreeSearch));
|
LdapConnectionTests.buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, isSubTreeSearch));
|
||||||
|
|
||||||
String[] users = new String[]{"blackwidow", "cap", "hawkeye", "hulk", "ironman", "thor"};
|
String[] users = new String[]{"blackwidow", "cap", "hawkeye", "hulk", "ironman", "thor"};
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.authc.ldap;
|
package org.elasticsearch.shield.authc.support.ldap;
|
||||||
|
|
||||||
import com.carrotsearch.randomizedtesting.SysGlobals;
|
import com.carrotsearch.randomizedtesting.SysGlobals;
|
||||||
import org.apache.directory.api.ldap.model.entry.Entry;
|
import org.apache.directory.api.ldap.model.entry.Entry;
|
|
@ -3,12 +3,13 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.authc.ldap;
|
package org.elasticsearch.shield.authc.support.ldap;
|
||||||
|
|
||||||
import org.elasticsearch.common.collect.ImmutableMap;
|
import org.elasticsearch.common.collect.ImmutableMap;
|
||||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||||
import org.elasticsearch.shield.ShieldSettingsException;
|
import org.elasticsearch.shield.ShieldSettingsException;
|
||||||
import org.elasticsearch.shield.ssl.SSLService;
|
import org.elasticsearch.shield.ssl.SSLService;
|
||||||
|
import org.elasticsearch.shield.authc.ldap.LdapConnectionTests;
|
||||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
|
@ -25,7 +26,7 @@ public class LdapSslSocketFactoryTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void setTrustStore() throws URISyntaxException {
|
public static void setTrustStore() throws URISyntaxException {
|
||||||
File filename = new File(LdapConnectionTests.class.getResource("ldaptrust.jks").toURI()).getAbsoluteFile();
|
File filename = new File(LdapConnectionTests.class.getResource("../support/ldap/ldaptrust.jks").toURI()).getAbsoluteFile();
|
||||||
LdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder()
|
LdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder()
|
||||||
.put("shield.ssl.keystore", filename)
|
.put("shield.ssl.keystore", filename)
|
||||||
.put("shield.ssl.keystore_password", "changeit")
|
.put("shield.ssl.keystore_password", "changeit")
|
||||||
|
@ -45,7 +46,7 @@ public class LdapSslSocketFactoryTests extends ElasticsearchTestCase {
|
||||||
LdapSslSocketFactory.configureJndiSSL(urls, builder);
|
LdapSslSocketFactory.configureJndiSSL(urls, builder);
|
||||||
ImmutableMap<String, Serializable> settings = builder.build();
|
ImmutableMap<String, Serializable> settings = builder.build();
|
||||||
assertThat(settings.get(LdapSslSocketFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET),
|
assertThat(settings.get(LdapSslSocketFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET),
|
||||||
Matchers.<Serializable>equalTo("org.elasticsearch.shield.authc.ldap.LdapSslSocketFactory"));
|
Matchers.<Serializable>equalTo("org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -55,7 +56,7 @@ public class LdapSslSocketFactoryTests extends ElasticsearchTestCase {
|
||||||
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
|
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
|
||||||
LdapSslSocketFactory.configureJndiSSL(urls, builder);
|
LdapSslSocketFactory.configureJndiSSL(urls, builder);
|
||||||
ImmutableMap<String, Serializable> settings = builder.build();
|
ImmutableMap<String, Serializable> settings = builder.build();
|
||||||
assertThat(settings.get(LdapSslSocketFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET), Matchers.<Serializable>equalTo("org.elasticsearch.shield.authc.ldap.LdapSslSocketFactory"));
|
assertThat(settings.get(LdapSslSocketFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET), Matchers.<Serializable>equalTo("org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
|
@ -3,7 +3,7 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.authc.ldap;
|
package org.elasticsearch.shield.authc.support.ldap;
|
||||||
|
|
||||||
import com.carrotsearch.randomizedtesting.LifecycleScope;
|
import com.carrotsearch.randomizedtesting.LifecycleScope;
|
||||||
import com.carrotsearch.randomizedtesting.ThreadFilter;
|
import com.carrotsearch.randomizedtesting.ThreadFilter;
|
||||||
|
@ -11,6 +11,9 @@ import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;
|
||||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
|
import org.elasticsearch.shield.authc.ldap.GenericLdapConnectionFactory;
|
||||||
|
import org.elasticsearch.shield.authc.ldap.LdapGroupToRoleMapper;
|
||||||
|
import org.elasticsearch.shield.authc.ldap.LdapRealm;
|
||||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
|
@ -41,16 +44,16 @@ public abstract class LdapTest extends ElasticsearchTestCase {
|
||||||
return ldap.getUrl();
|
return ldap.getUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Settings buildLdapSettings(String ldapUrl, String userTemplate, String groupSearchBase, boolean isSubTreeSearch) {
|
public static Settings buildLdapSettings(String ldapUrl, String userTemplate, String groupSearchBase, boolean isSubTreeSearch) {
|
||||||
return buildLdapSettings( new String[]{ldapUrl}, new String[]{userTemplate}, groupSearchBase, isSubTreeSearch );
|
return buildLdapSettings( new String[]{ldapUrl}, new String[]{userTemplate}, groupSearchBase, isSubTreeSearch );
|
||||||
}
|
}
|
||||||
|
|
||||||
static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, String groupSearchBase, boolean isSubTreeSearch) {
|
public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, String groupSearchBase, boolean isSubTreeSearch) {
|
||||||
return ImmutableSettings.builder()
|
return ImmutableSettings.builder()
|
||||||
.putArray(SETTINGS_PREFIX + StandardLdapConnectionFactory.URLS_SETTING, ldapUrl)
|
.putArray(SETTINGS_PREFIX + GenericLdapConnectionFactory.URLS_SETTING, ldapUrl)
|
||||||
.putArray(SETTINGS_PREFIX + StandardLdapConnectionFactory.USER_DN_TEMPLATES_SETTING, userTemplate)
|
.putArray(SETTINGS_PREFIX + GenericLdapConnectionFactory.USER_DN_TEMPLATES_SETTING, userTemplate)
|
||||||
.put(SETTINGS_PREFIX + StandardLdapConnectionFactory.GROUP_SEARCH_BASEDN_SETTING, groupSearchBase)
|
.put(SETTINGS_PREFIX + GenericLdapConnectionFactory.GROUP_SEARCH_BASEDN_SETTING, groupSearchBase)
|
||||||
.put(SETTINGS_PREFIX + StandardLdapConnectionFactory.GROUP_SEARCH_SUBTREE_SETTING, isSubTreeSearch).build();
|
.put(SETTINGS_PREFIX + GenericLdapConnectionFactory.GROUP_SEARCH_SUBTREE_SETTING, isSubTreeSearch).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Settings buildNonCachingSettings() {
|
protected Settings buildNonCachingSettings() {
|
||||||
|
@ -66,7 +69,7 @@ public abstract class LdapTest extends ElasticsearchTestCase {
|
||||||
|
|
||||||
protected LdapGroupToRoleMapper buildGroupAsRoleMapper(ResourceWatcherService resourceWatcherService) {
|
protected LdapGroupToRoleMapper buildGroupAsRoleMapper(ResourceWatcherService resourceWatcherService) {
|
||||||
Settings settings = ImmutableSettings.builder()
|
Settings settings = ImmutableSettings.builder()
|
||||||
.put("shield.authc.ldap." + LdapGroupToRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, true)
|
.put("shield.authc.ldap." + GroupToRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, true)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return new LdapGroupToRoleMapper(settings, new Environment(settings), resourceWatcherService);
|
return new LdapGroupToRoleMapper(settings, new Environment(settings), resourceWatcherService);
|
Loading…
Reference in New Issue