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_roles" : ".esvm-shield-config/users_roles"
|
||||
},
|
||||
"ldap" : {
|
||||
"mode" : "active_directory",
|
||||
"active_directory" : {
|
||||
"domain_name" : "ad.test.elasticsearch.com",
|
||||
"url" : "ldaps://ad.test.elasticsearch.com:636",
|
||||
"unmapped_groups_as_roles" : "false",
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
},
|
||||
"authc": {
|
||||
"ldap" : {
|
||||
"mode" : "ldap",
|
||||
"url" : "ldaps://54.200.235.244:636",
|
||||
"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",
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.shield.authc;
|
|||
|
||||
import org.elasticsearch.common.collect.ImmutableList;
|
||||
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.ldap.LdapModule;
|
||||
import org.elasticsearch.shield.authc.system.SystemRealm;
|
||||
|
@ -26,7 +27,9 @@ public class AuthenticationModule extends AbstractShieldModule.Node.Spawn {
|
|||
return ImmutableList.of(
|
||||
new SystemRealm.Module(settings),
|
||||
new ESUsersModule(settings),
|
||||
new LdapModule(settings));
|
||||
new LdapModule(settings),
|
||||
new ActiveDirectoryModule(settings)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.elasticsearch.common.inject.Inject;
|
|||
import org.elasticsearch.common.inject.internal.Nullable;
|
||||
import org.elasticsearch.common.logging.ESLogger;
|
||||
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.ldap.LdapRealm;
|
||||
import org.elasticsearch.shield.authc.system.SystemRealm;
|
||||
|
@ -26,7 +27,10 @@ public class Realms {
|
|||
private final Realm[] realms;
|
||||
|
||||
@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<>();
|
||||
realms.add(system);
|
||||
|
@ -38,6 +42,10 @@ public class Realms {
|
|||
logger.info("Realm [" + ldap.type() + "] is used");
|
||||
realms.add(ldap);
|
||||
}
|
||||
if (activeDirectory != null) {
|
||||
logger.info("Realm [" + activeDirectory.type() + "] is used");
|
||||
realms.add(activeDirectory);
|
||||
}
|
||||
this.realms = realms.toArray(new Realm[realms.size()]);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
* 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;
|
||||
package org.elasticsearch.shield.authc.active_directory;
|
||||
|
||||
import org.elasticsearch.common.collect.ImmutableList;
|
||||
import org.elasticsearch.common.logging.ESLogger;
|
||||
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.NamingException;
|
||||
|
@ -15,7 +17,7 @@ import javax.naming.directory.*;
|
|||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* An Ldap Connection customized for active directory.
|
||||
*/
|
||||
public class ActiveDirectoryConnection implements LdapConnection {
|
||||
private static final ESLogger logger = ESLoggerFactory.getLogger(GenericLdapConnection.class.getName());
|
||||
|
@ -23,7 +25,6 @@ public class ActiveDirectoryConnection implements LdapConnection {
|
|||
protected final DirContext ldapContext;
|
||||
|
||||
private final String groupSearchDN;
|
||||
protected final String groupAttribute = "memberOf";
|
||||
|
||||
/**
|
||||
* This object is intended to be constructed by the LdapConnectionFactory
|
||||
|
@ -43,7 +44,7 @@ public class ActiveDirectoryConnection implements LdapConnection {
|
|||
try {
|
||||
ldapContext.close();
|
||||
} 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() {
|
||||
|
||||
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
|
||||
//Create the search controls
|
||||
|
@ -58,18 +60,12 @@ public class ActiveDirectoryConnection implements LdapConnection {
|
|||
|
||||
//Specify the search scope
|
||||
groupsSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
||||
groupsSearchCtls.setReturningAttributes(new String[] {}); //we only need the entry DN
|
||||
|
||||
//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);
|
||||
|
||||
ImmutableList.Builder<String> groups = ImmutableList.<String>builder();
|
||||
ImmutableList.Builder<String> groups = ImmutableList.builder();
|
||||
try {
|
||||
//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
|
||||
while (groupsAnswer.hasMoreElements()) {
|
||||
|
@ -77,13 +73,13 @@ public class ActiveDirectoryConnection implements LdapConnection {
|
|||
groups.add(sr.getNameInNamespace());
|
||||
}
|
||||
} 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();
|
||||
}
|
||||
|
||||
private String buildGroupQuery() {
|
||||
StringBuffer groupsSearchFilter = new StringBuffer("(|");
|
||||
StringBuilder groupsSearchFilter = new StringBuilder("(|");
|
||||
try {
|
||||
SearchControls userSearchCtls = new SearchControls();
|
||||
userSearchCtls.setSearchScope(SearchControls.OBJECT_SCOPE);
|
||||
|
@ -105,7 +101,9 @@ public class ActiveDirectoryConnection implements LdapConnection {
|
|||
Attribute attr = (Attribute)ae.next();
|
||||
for (NamingEnumeration e = attr.getAll();e.hasMore();) {
|
||||
byte[] sid = (byte[])e.next();
|
||||
groupsSearchFilter.append("(objectSid=" + binarySidToStringSid(sid) + ")");
|
||||
groupsSearchFilter.append("(objectSid=");
|
||||
groupsSearchFilter.append(binarySidToStringSid(sid));
|
||||
groupsSearchFilter.append(")");
|
||||
}
|
||||
groupsSearchFilter.append(")");
|
||||
}
|
||||
|
@ -113,7 +111,7 @@ public class ActiveDirectoryConnection implements LdapConnection {
|
|||
}
|
||||
|
||||
} 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();
|
||||
}
|
||||
|
@ -127,10 +125,9 @@ public class ActiveDirectoryConnection implements LdapConnection {
|
|||
* 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
|
||||
* @param SID byte encoded security ID
|
||||
* @return
|
||||
*/
|
||||
public String binarySidToStringSid( byte[] SID ) {
|
||||
String strSID = "";
|
||||
String strSID;
|
||||
|
||||
//convert the SID into string format
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* 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;
|
||||
package org.elasticsearch.shield.authc.active_directory;
|
||||
|
||||
import org.elasticsearch.common.Strings;
|
||||
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.shield.ShieldException;
|
||||
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.NamingEnumeration;
|
||||
|
@ -90,13 +92,13 @@ public class ActiveDirectoryConnectionFactory extends AbstractComponent implemen
|
|||
if (!results.hasMore()) {
|
||||
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) {
|
||||
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.ESLoggerFactory;
|
||||
import org.elasticsearch.shield.authc.support.ldap.LdapConnection;
|
||||
|
||||
import javax.naming.NamingEnumeration;
|
||||
import javax.naming.NamingException;
|
||||
|
@ -81,7 +82,7 @@ public class GenericLdapConnection implements LdapConnection {
|
|||
* @param userDn user fully distinguished name to fetch group membership for
|
||||
* @return fully distinguished names of the roles
|
||||
*/
|
||||
List<String> getGroupsFromSearch(String userDn){
|
||||
public List<String> getGroupsFromSearch(String userDn){
|
||||
List<String> groups = new LinkedList<>();
|
||||
SearchControls search = new SearchControls();
|
||||
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
|
||||
* @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<>();
|
||||
try {
|
||||
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.shield.ShieldException;
|
||||
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.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
|
||||
* 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 GROUP_SEARCH_SUBTREE_SETTING = "group_search.subtree_search";
|
||||
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 String[] userDnTemplates;
|
||||
|
@ -42,7 +43,7 @@ public class StandardLdapConnectionFactory extends AbstractComponent implements
|
|||
protected final boolean findGroupsByAttribute;
|
||||
|
||||
@Inject
|
||||
public StandardLdapConnectionFactory(Settings settings) {
|
||||
public GenericLdapConnectionFactory(Settings settings) {
|
||||
super(settings);
|
||||
userDnTemplates = componentSettings.getAsArray(USER_DN_TEMPLATES_SETTING);
|
||||
if (userDnTemplates == null) {
|
|
@ -5,169 +5,18 @@
|
|||
*/
|
||||
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.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.shield.authc.support.ldap.GroupToRoleMapper;
|
||||
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 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;
|
||||
|
||||
public class LdapGroupToRoleMapper extends GroupToRoleMapper {
|
||||
@Inject
|
||||
public LdapGroupToRoleMapper(Settings settings, Environment env, ResourceWatcherService watcherService) {
|
||||
this(settings, env, watcherService, null);
|
||||
}
|
||||
|
||||
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();
|
||||
super(settings, env, watcherService);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ package org.elasticsearch.shield.authc.ldap;
|
|||
|
||||
import org.elasticsearch.common.inject.util.Providers;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.shield.ShieldSettingsException;
|
||||
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;
|
||||
|
@ -19,9 +19,11 @@ import static org.elasticsearch.common.inject.name.Names.named;
|
|||
public class LdapModule extends AbstractShieldModule.Node {
|
||||
|
||||
private final boolean enabled;
|
||||
private final Settings ldapSettings;
|
||||
|
||||
public LdapModule(Settings settings) {
|
||||
super(settings);
|
||||
ldapSettings = settings.getComponentSettings(LdapModule.class);
|
||||
enabled = enabled(settings);
|
||||
}
|
||||
|
||||
|
@ -33,17 +35,6 @@ public class LdapModule extends AbstractShieldModule.Node {
|
|||
requestStaticInjection(LdapSslSocketFactory.class);
|
||||
|
||||
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 {
|
||||
bind(LdapRealm.class).toProvider(Providers.<LdapRealm>of(null));
|
||||
}
|
||||
|
@ -51,10 +42,10 @@ public class LdapModule extends AbstractShieldModule.Node {
|
|||
|
||||
public static boolean enabled(Settings settings) {
|
||||
Settings authcSettings = settings.getAsSettings("shield.authc");
|
||||
if (!authcSettings.names().contains("ldap")) {
|
||||
if (!authcSettings.names().contains(LdapRealm.TYPE)) {
|
||||
return false;
|
||||
}
|
||||
Settings ldapSettings = authcSettings.getAsSettings("ldap");
|
||||
Settings ldapSettings = authcSettings.getAsSettings(LdapRealm.TYPE);
|
||||
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.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;
|
||||
import org.elasticsearch.shield.authc.support.ldap.AbstractLdapRealm;
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
private final LdapConnectionFactory connectionFactory;
|
||||
private final LdapGroupToRoleMapper roleMapper;
|
||||
|
||||
@Inject
|
||||
public LdapRealm(Settings settings, LdapConnectionFactory ldap, LdapGroupToRoleMapper roleMapper, RestController restController) {
|
||||
super(settings);
|
||||
this.connectionFactory = ldap;
|
||||
this.roleMapper = roleMapper;
|
||||
roleMapper.addListener(new Listener());
|
||||
restController.registerRelevantHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER);
|
||||
public LdapRealm(Settings settings,
|
||||
GenericLdapConnectionFactory ldap,
|
||||
LdapGroupToRoleMapper roleMapper,
|
||||
RestController restController) {
|
||||
super(settings, ldap, roleMapper, restController);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String 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;
|
||||
* 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.util.List;
|
|
@ -3,7 +3,7 @@
|
|||
* 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;
|
||||
package org.elasticsearch.shield.authc.support.ldap;
|
||||
|
||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||
|
|
@ -3,8 +3,9 @@
|
|||
* 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;
|
||||
package org.elasticsearch.shield.authc.support.ldap;
|
||||
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.collect.ImmutableMap;
|
||||
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.
|
||||
*/
|
||||
@Inject
|
||||
public static void init(SSLService ssl) {
|
||||
public static void init(@Nullable SSLService ssl) {
|
||||
if (instance != null) {
|
||||
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.
|
||||
*/
|
||||
@Deprecated
|
||||
static void clear() {
|
||||
public static void clear() {
|
||||
logger.error("clear should only be called by tests");
|
||||
instance = null;
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
* 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;
|
||||
package org.elasticsearch.shield.authc.support.ldap;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
|
|
@ -11,7 +11,7 @@ import org.elasticsearch.common.inject.Inject;
|
|||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.shield.ShieldSettingsException;
|
||||
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 java.io.FileInputStream;
|
||||
|
|
|
@ -3,12 +3,16 @@
|
|||
* 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;
|
||||
package org.elasticsearch.shield.authc.active_directory;
|
||||
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.shield.authc.ldap.*;
|
||||
import org.elasticsearch.shield.authc.support.SecuredStringTests;
|
||||
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.junit.annotations.Network;
|
||||
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 PASSWORD = "NickFuryHeartsES";
|
||||
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
|
||||
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()
|
||||
.put("shield.ssl.keystore", filename)
|
||||
.put("shield.ssl.keystore_password", "changeit")
|
||||
|
@ -50,8 +54,6 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
|
|||
|
||||
String userName = "ironman";
|
||||
try (LdapConnection ldap = connectionFactory.bind(userName, SecuredStringTests.build(PASSWORD))) {
|
||||
String userDN = ldap.getAuthenticatedUserDn();
|
||||
|
||||
List<String> groups = ldap.getGroups();
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Geniuses"),
|
||||
|
@ -88,8 +90,6 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
|
|||
|
||||
String userName = "hulk";
|
||||
try (LdapConnection ldap = connectionFactory.bind(userName, SecuredStringTests.build(PASSWORD))) {
|
||||
String userDN = ldap.getAuthenticatedUserDn();
|
||||
|
||||
List<String> groups = ldap.getGroups();
|
||||
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
|
@ -97,7 +97,7 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
|
|||
containsString("SHIELD"),
|
||||
containsString("Geniuses"),
|
||||
containsString("Philanthropists"),
|
||||
containsString("Users"),
|
||||
//containsString("Users"), Users group is in a different user context
|
||||
containsString("Domain Users"),
|
||||
containsString("Supers")
|
||||
));
|
||||
|
@ -109,7 +109,7 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
|
|||
String groupSearchBase = "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;
|
||||
StandardLdapConnectionFactory connectionFactory = new StandardLdapConnectionFactory(
|
||||
GenericLdapConnectionFactory connectionFactory = new GenericLdapConnectionFactory(
|
||||
LdapTest.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, isSubTreeSearch));
|
||||
|
||||
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.SecuredStringTests;
|
||||
import org.elasticsearch.shield.authc.support.ldap.LdapTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -26,7 +27,7 @@ public class LdapConnectionTests extends LdapTest {
|
|||
"wrongname={0},ou=people,o=sevenSeas",
|
||||
"cn={0},ou=people,o=sevenSeas", //this last one should work
|
||||
};
|
||||
StandardLdapConnectionFactory connectionFactory = new StandardLdapConnectionFactory(
|
||||
GenericLdapConnectionFactory connectionFactory = new GenericLdapConnectionFactory(
|
||||
buildLdapSettings(ldapUrls, userTemplates, groupSearchBase, isSubTreeSearch));
|
||||
|
||||
String user = "Horatio Hornblower";
|
||||
|
@ -50,7 +51,7 @@ public class LdapConnectionTests extends LdapTest {
|
|||
"wrongname={0},ou=people,o=sevenSeas",
|
||||
"asdf={0},ou=people,o=sevenSeas", //none of these should work
|
||||
};
|
||||
StandardLdapConnectionFactory ldapFac = new StandardLdapConnectionFactory(
|
||||
GenericLdapConnectionFactory ldapFac = new GenericLdapConnectionFactory(
|
||||
buildLdapSettings(ldapUrl, userTemplates, groupSearchBase, isSubTreeSearch));
|
||||
|
||||
String user = "Horatio Hornblower";
|
||||
|
@ -65,7 +66,7 @@ public class LdapConnectionTests extends LdapTest {
|
|||
String userTemplate = "cn={0},ou=people,o=sevenSeas";
|
||||
|
||||
boolean isSubTreeSearch = true;
|
||||
StandardLdapConnectionFactory ldapFac = new StandardLdapConnectionFactory(
|
||||
GenericLdapConnectionFactory ldapFac = new GenericLdapConnectionFactory(
|
||||
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
|
||||
|
||||
String user = "Horatio Hornblower";
|
||||
|
@ -82,7 +83,7 @@ public class LdapConnectionTests extends LdapTest {
|
|||
String groupSearchBase = "ou=crews,ou=groups,o=sevenSeas";
|
||||
String userTemplate = "cn={0},ou=people,o=sevenSeas";
|
||||
boolean isSubTreeSearch = false;
|
||||
StandardLdapConnectionFactory ldapFac = new StandardLdapConnectionFactory(
|
||||
GenericLdapConnectionFactory ldapFac = new GenericLdapConnectionFactory(
|
||||
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
|
||||
|
||||
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.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.shield.authc.support.ldap.GroupToRoleMapper;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||
|
@ -52,12 +53,12 @@ public class LdapGroupToRoleMapperTest extends ElasticsearchTestCase {
|
|||
|
||||
@Test
|
||||
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()
|
||||
.put("shield.authc.ldap." + LdapGroupToRoleMapper.ROLE_MAPPING_FILE_SETTING, file.getCanonicalPath())
|
||||
.build();
|
||||
|
||||
LdapGroupToRoleMapper mapper = new LdapGroupToRoleMapper(settings,
|
||||
GroupToRoleMapper mapper = new LdapGroupToRoleMapper(settings,
|
||||
new Environment(settings),
|
||||
new ResourceWatcherService(settings, threadPool));
|
||||
|
||||
|
@ -70,10 +71,10 @@ public class LdapGroupToRoleMapperTest extends ElasticsearchTestCase {
|
|||
@Test
|
||||
public void testRelativeDN() {
|
||||
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();
|
||||
|
||||
LdapGroupToRoleMapper mapper = new LdapGroupToRoleMapper(settings,
|
||||
GroupToRoleMapper mapper = new LdapGroupToRoleMapper(settings,
|
||||
new Environment(settings),
|
||||
new ResourceWatcherService(settings, threadPool));
|
||||
|
||||
|
|
|
@ -5,18 +5,43 @@
|
|||
*/
|
||||
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.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.*;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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
|
||||
public void testEnabled() throws Exception {
|
||||
assertThat(LdapModule.enabled(ImmutableSettings.EMPTY), is(false));
|
||||
|
@ -33,4 +58,34 @@ public class LdapModuleTests extends ElasticsearchTestCase {
|
|||
.build();
|
||||
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.SecuredStringTests;
|
||||
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
||||
import org.elasticsearch.shield.authc.support.ldap.LdapTest;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||
import org.junit.After;
|
||||
|
@ -48,7 +49,7 @@ public class LdapRealmTest extends LdapTest {
|
|||
|
||||
@Test
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -58,7 +59,8 @@ public class LdapRealmTest extends LdapTest {
|
|||
boolean isSubTreeSearch = true;
|
||||
String userTemplate = VALID_USER_TEMPLATE;
|
||||
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);
|
||||
|
||||
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";
|
||||
boolean isSubTreeSearch = false;
|
||||
String userTemplate = VALID_USER_TEMPLATE;
|
||||
StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(
|
||||
GenericLdapConnectionFactory ldapFactory = new GenericLdapConnectionFactory(
|
||||
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
|
||||
|
||||
LdapRealm ldap = new LdapRealm(buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper(resourceWatcherService), restController);
|
||||
|
@ -86,7 +88,7 @@ public class LdapRealmTest extends LdapTest {
|
|||
String groupSearchBase = "o=sevenSeas";
|
||||
boolean isSubTreeSearch = true;
|
||||
String userTemplate = VALID_USER_TEMPLATE;
|
||||
StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(
|
||||
GenericLdapConnectionFactory ldapFactory = new GenericLdapConnectionFactory(
|
||||
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch) );
|
||||
|
||||
ldapFactory = spy(ldapFactory);
|
||||
|
@ -103,7 +105,7 @@ public class LdapRealmTest extends LdapTest {
|
|||
String groupSearchBase = "o=sevenSeas";
|
||||
boolean isSubTreeSearch = true;
|
||||
String userTemplate = VALID_USER_TEMPLATE;
|
||||
StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(
|
||||
GenericLdapConnectionFactory ldapFactory = new GenericLdapConnectionFactory(
|
||||
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch) );
|
||||
|
||||
LdapGroupToRoleMapper roleMapper = buildGroupAsRoleMapper(resourceWatcherService);
|
||||
|
@ -129,7 +131,7 @@ public class LdapRealmTest extends LdapTest {
|
|||
String groupSearchBase = "o=sevenSeas";
|
||||
boolean isSubTreeSearch = true;
|
||||
String userTemplate = VALID_USER_TEMPLATE;
|
||||
StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(
|
||||
GenericLdapConnectionFactory ldapFactory = new GenericLdapConnectionFactory(
|
||||
buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
|
||||
|
||||
ldapFactory = spy(ldapFactory);
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.elasticsearch.shield.authc.ldap;
|
|||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.shield.authc.support.SecuredStringTests;
|
||||
import org.elasticsearch.shield.ssl.SSLService;
|
||||
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.elasticsearch.test.junit.annotations.Network;
|
||||
import org.junit.AfterClass;
|
||||
|
@ -28,7 +29,7 @@ public class OpenLdapTests extends ElasticsearchTestCase {
|
|||
|
||||
@BeforeClass
|
||||
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()
|
||||
.put("shield.ssl.keystore", filename)
|
||||
.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 userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
|
||||
boolean isSubTreeSearch = true;
|
||||
StandardLdapConnectionFactory connectionFactory = new StandardLdapConnectionFactory(
|
||||
GenericLdapConnectionFactory connectionFactory = new GenericLdapConnectionFactory(
|
||||
LdapConnectionTests.buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, isSubTreeSearch));
|
||||
|
||||
String[] users = new String[]{"blackwidow", "cap", "hawkeye", "hulk", "ironman", "thor"};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* 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;
|
||||
package org.elasticsearch.shield.authc.support.ldap;
|
||||
|
||||
import com.carrotsearch.randomizedtesting.SysGlobals;
|
||||
import org.apache.directory.api.ldap.model.entry.Entry;
|
|
@ -3,12 +3,13 @@
|
|||
* 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;
|
||||
package org.elasticsearch.shield.authc.support.ldap;
|
||||
|
||||
import org.elasticsearch.common.collect.ImmutableMap;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.shield.ShieldSettingsException;
|
||||
import org.elasticsearch.shield.ssl.SSLService;
|
||||
import org.elasticsearch.shield.authc.ldap.LdapConnectionTests;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.AfterClass;
|
||||
|
@ -25,7 +26,7 @@ public class LdapSslSocketFactoryTests extends ElasticsearchTestCase {
|
|||
|
||||
@BeforeClass
|
||||
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()
|
||||
.put("shield.ssl.keystore", filename)
|
||||
.put("shield.ssl.keystore_password", "changeit")
|
||||
|
@ -45,7 +46,7 @@ public class LdapSslSocketFactoryTests extends ElasticsearchTestCase {
|
|||
LdapSslSocketFactory.configureJndiSSL(urls, builder);
|
||||
ImmutableMap<String, Serializable> settings = builder.build();
|
||||
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
|
||||
|
@ -55,7 +56,7 @@ public class LdapSslSocketFactoryTests extends ElasticsearchTestCase {
|
|||
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
|
||||
LdapSslSocketFactory.configureJndiSSL(urls, builder);
|
||||
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
|
|
@ -3,7 +3,7 @@
|
|||
* 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;
|
||||
package org.elasticsearch.shield.authc.support.ldap;
|
||||
|
||||
import com.carrotsearch.randomizedtesting.LifecycleScope;
|
||||
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.Settings;
|
||||
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.watcher.ResourceWatcherService;
|
||||
import org.junit.AfterClass;
|
||||
|
@ -41,16 +44,16 @@ public abstract class LdapTest extends ElasticsearchTestCase {
|
|||
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 );
|
||||
}
|
||||
|
||||
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()
|
||||
.putArray(SETTINGS_PREFIX + StandardLdapConnectionFactory.URLS_SETTING, ldapUrl)
|
||||
.putArray(SETTINGS_PREFIX + StandardLdapConnectionFactory.USER_DN_TEMPLATES_SETTING, userTemplate)
|
||||
.put(SETTINGS_PREFIX + StandardLdapConnectionFactory.GROUP_SEARCH_BASEDN_SETTING, groupSearchBase)
|
||||
.put(SETTINGS_PREFIX + StandardLdapConnectionFactory.GROUP_SEARCH_SUBTREE_SETTING, isSubTreeSearch).build();
|
||||
.putArray(SETTINGS_PREFIX + GenericLdapConnectionFactory.URLS_SETTING, ldapUrl)
|
||||
.putArray(SETTINGS_PREFIX + GenericLdapConnectionFactory.USER_DN_TEMPLATES_SETTING, userTemplate)
|
||||
.put(SETTINGS_PREFIX + GenericLdapConnectionFactory.GROUP_SEARCH_BASEDN_SETTING, groupSearchBase)
|
||||
.put(SETTINGS_PREFIX + GenericLdapConnectionFactory.GROUP_SEARCH_SUBTREE_SETTING, isSubTreeSearch).build();
|
||||
}
|
||||
|
||||
protected Settings buildNonCachingSettings() {
|
||||
|
@ -66,7 +69,7 @@ public abstract class LdapTest extends ElasticsearchTestCase {
|
|||
|
||||
protected LdapGroupToRoleMapper buildGroupAsRoleMapper(ResourceWatcherService resourceWatcherService) {
|
||||
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();
|
||||
|
||||
return new LdapGroupToRoleMapper(settings, new Environment(settings), resourceWatcherService);
|
Loading…
Reference in New Issue