ARTEMIS-1373 - support memberOf type query for role mapping and respect roleName attribute AMQ-3064
This commit is contained in:
parent
53a9c9b47b
commit
99b2e4c0fb
|
@ -29,6 +29,8 @@ import javax.naming.directory.DirContext;
|
|||
import javax.naming.directory.InitialDirContext;
|
||||
import javax.naming.directory.SearchControls;
|
||||
import javax.naming.directory.SearchResult;
|
||||
import javax.naming.ldap.LdapName;
|
||||
import javax.naming.ldap.Rdn;
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
|
@ -91,6 +93,8 @@ public class LDAPLoginModule implements LoginModule {
|
|||
private boolean userAuthenticated = false;
|
||||
private boolean authenticateUser = true;
|
||||
private Subject brokerGssapiIdentity = null;
|
||||
private boolean isRoleAttributeSet = false;
|
||||
private String roleAttributeName = null;
|
||||
|
||||
@Override
|
||||
public void initialize(Subject subject,
|
||||
|
@ -104,6 +108,8 @@ public class LDAPLoginModule implements LoginModule {
|
|||
if (isLoginPropertySet(AUTHENTICATE_USER)) {
|
||||
authenticateUser = Boolean.valueOf(getLDAPPropertyValue(AUTHENTICATE_USER));
|
||||
}
|
||||
isRoleAttributeSet = isLoginPropertySet(ROLE_NAME);
|
||||
roleAttributeName = getLDAPPropertyValue(ROLE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -338,7 +344,25 @@ public class LDAPLoginModule implements LoginModule {
|
|||
throw new FailedLoginException("User found, but LDAP entry malformed: " + username);
|
||||
}
|
||||
if (isLoginPropertySet(USER_ROLE_NAME)) {
|
||||
addAttributeValues(getLDAPPropertyValue(USER_ROLE_NAME), attrs, roles);
|
||||
Attribute roleNames = attrs.get(getLDAPPropertyValue(USER_ROLE_NAME));
|
||||
if (roleNames != null) {
|
||||
NamingEnumeration<?> e = roleNames.getAll();
|
||||
while (e.hasMore()) {
|
||||
String roleDnString = (String) e.next();
|
||||
if (isRoleAttributeSet) {
|
||||
// parse out the attribute from the group Dn
|
||||
LdapName ldapRoleName = new LdapName(roleDnString);
|
||||
for (int i = 0; i < ldapRoleName.size(); i++) {
|
||||
Rdn candidate = ldapRoleName.getRdn(i);
|
||||
if (roleAttributeName.equals(candidate.getType())) {
|
||||
roles.add((String) candidate.getValue());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
roles.add(roleDnString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (CommunicationException e) {
|
||||
closeContext();
|
||||
|
@ -359,6 +383,11 @@ public class LDAPLoginModule implements LoginModule {
|
|||
String dn,
|
||||
String username,
|
||||
List<String> currentRoles) throws NamingException {
|
||||
|
||||
if (!isLoginPropertySet(ROLE_SEARCH_MATCHING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
MessageFormat roleSearchMatchingFormat;
|
||||
boolean roleSearchSubtreeBool;
|
||||
boolean expandRolesBool;
|
||||
|
@ -366,9 +395,6 @@ public class LDAPLoginModule implements LoginModule {
|
|||
roleSearchSubtreeBool = Boolean.valueOf(getLDAPPropertyValue(ROLE_SEARCH_SUBTREE)).booleanValue();
|
||||
expandRolesBool = Boolean.valueOf(getLDAPPropertyValue(EXPAND_ROLES)).booleanValue();
|
||||
|
||||
if (!isLoginPropertySet(ROLE_NAME)) {
|
||||
return;
|
||||
}
|
||||
final String filter = roleSearchMatchingFormat.format(new String[]{doRFC2254Encoding(dn), doRFC2254Encoding(username)});
|
||||
|
||||
SearchControls constraints = new SearchControls();
|
||||
|
@ -397,15 +423,11 @@ public class LDAPLoginModule implements LoginModule {
|
|||
|
||||
while (results.hasMore()) {
|
||||
SearchResult result = results.next();
|
||||
Attributes attrs = result.getAttributes();
|
||||
if (expandRolesBool) {
|
||||
haveSeenNames.add(result.getNameInNamespace());
|
||||
pendingNameExpansion.add(result.getNameInNamespace());
|
||||
}
|
||||
if (attrs == null) {
|
||||
continue;
|
||||
}
|
||||
addAttributeValues(getLDAPPropertyValue(ROLE_NAME), attrs, currentRoles);
|
||||
addRoleAttribute(result, currentRoles);
|
||||
}
|
||||
if (expandRolesBool) {
|
||||
MessageFormat expandRolesMatchingFormat = new MessageFormat(getLDAPPropertyValue(EXPAND_ROLES_MATCHING));
|
||||
|
@ -424,8 +446,7 @@ public class LDAPLoginModule implements LoginModule {
|
|||
SearchResult result = results.next();
|
||||
name = result.getNameInNamespace();
|
||||
if (!haveSeenNames.contains(name)) {
|
||||
Attributes attrs = result.getAttributes();
|
||||
addAttributeValues(getLDAPPropertyValue(ROLE_NAME), attrs, currentRoles);
|
||||
addRoleAttribute(result, currentRoles);
|
||||
haveSeenNames.add(name);
|
||||
pendingNameExpansion.add(name);
|
||||
}
|
||||
|
@ -497,23 +518,17 @@ public class LDAPLoginModule implements LoginModule {
|
|||
return isValid;
|
||||
}
|
||||
|
||||
private void addAttributeValues(String attrId,
|
||||
Attributes attrs,
|
||||
List<String> values) throws NamingException {
|
||||
private void addRoleAttribute(SearchResult searchResult, List<String> roles) throws NamingException {
|
||||
if (isRoleAttributeSet) {
|
||||
Attribute roleAttribute = searchResult.getAttributes().get(roleAttributeName);
|
||||
if (roleAttribute != null) {
|
||||
roles.add((String) roleAttribute.get());
|
||||
}
|
||||
} else {
|
||||
roles.add(searchResult.getNameInNamespace());
|
||||
}
|
||||
}
|
||||
|
||||
if (attrId == null || attrs == null) {
|
||||
return;
|
||||
}
|
||||
Attribute attr = attrs.get(attrId);
|
||||
if (attr == null) {
|
||||
return;
|
||||
}
|
||||
NamingEnumeration<?> e = attr.getAll();
|
||||
while (e.hasMore()) {
|
||||
String value = (String) e.next();
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
protected void openContext() throws Exception {
|
||||
if (context == null) {
|
||||
|
|
|
@ -510,7 +510,7 @@ managed using the X.500 system. It is implemented by `org.apache.activemq.artemi
|
|||
the `ou=Group,ou=ActiveMQ,ou=system` node.
|
||||
|
||||
- `roleName` - specifies the attribute type of the role entry that contains the name of the role/group (e.g. C, O,
|
||||
OU, etc.). If you omit this option, the role search feature is effectively disabled.
|
||||
OU, etc.). If you omit this option the full DN of the role is used.
|
||||
|
||||
- `roleSearchMatching` - specifies an LDAP search filter, which is applied to the subtree selected by `roleBase`.
|
||||
This works in a similar manner to the `userSearchMatching` option, except that it supports two substitution strings,
|
||||
|
@ -526,7 +526,8 @@ managed using the X.500 system. It is implemented by `org.apache.activemq.artemi
|
|||
search filter is applied to the subtree selected by the role base, `ou=Group,ou=ActiveMQ,ou=system`, it matches all
|
||||
role entries that have a `member` attribute equal to `uid=jdoe` (the value of a `member` attribute is a DN).
|
||||
|
||||
This option must always be set, even if role searching is disabled, because it has no default value.
|
||||
This option must always be set to enable role searching because it has no default value. Leaving it unset disables
|
||||
role searching and the role information must come from `userRoleName`.
|
||||
|
||||
If you use OpenLDAP, the syntax of the search filter is `(member:=uid=jdoe)`.
|
||||
|
||||
|
@ -702,11 +703,26 @@ An example configuration scope for `login.config` that will pick up a Kerberos k
|
|||
On the server, the Kerberos authenticated Peer Principal can be added to the Subject's principal set as an Apache ActiveMQ Artemis UserPrincipal
|
||||
using the Apache ActiveMQ Artemis `Krb5LoginModule` login module. The [PropertiesLoginModule](#propertiesloginmodule) or
|
||||
[LDAPLoginModule](#ldaploginmodule) can then be used to map
|
||||
the authenticated Kerberos Peer Principal to an Apache ActiveMQ Artemis [Role](#role-based-security-for-addresses).
|
||||
the authenticated Kerberos Peer Principal to an Apache ActiveMQ Artemis [Role](#role-based-security-for-addresses). Note
|
||||
that the Kerberos Peer Principal does not exist as an Apache ActiveMQ Artemis user, only as a role member.
|
||||
|
||||
Note: the Kerberos Peer Principal does not exist as an Apache ActiveMQ Artemis user.
|
||||
|
||||
org.apache.activemq.artemis.spi.core.security.jaas.Krb5LoginModule optional;
|
||||
org.apache.activemq.artemis.spi.core.security.jaas.Krb5LoginModule required
|
||||
;
|
||||
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule optional
|
||||
debug=true
|
||||
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
|
||||
connectionURL="ldap://localhost:1024"
|
||||
authentication=GSSAPI
|
||||
saslLoginConfigScope=broker-sasl-gssapi
|
||||
connectionProtocol=s
|
||||
userBase="ou=users,dc=example,dc=com"
|
||||
userSearchMatching="(krb5PrincipalName={0})"
|
||||
userSearchSubtree=true
|
||||
authenticateUser=false
|
||||
roleBase="ou=system"
|
||||
roleSearchMatching="(member={0})"
|
||||
roleSearchSubtree=false
|
||||
;
|
||||
|
||||
#### TLS Kerberos Cipher Suites
|
||||
|
||||
|
|
|
@ -48,7 +48,6 @@ import java.util.Hashtable;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.activemq.artemis.api.core.TransportConfiguration;
|
||||
import org.apache.activemq.artemis.core.config.Configuration;
|
||||
|
@ -105,7 +104,8 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import static org.apache.activemq.artemis.tests.util.ActiveMQTestBase.NETTY_ACCEPTOR_FACTORY;
|
||||
|
||||
@RunWith(FrameworkRunner.class) @CreateDS(name = "Example",
|
||||
@RunWith(FrameworkRunner.class)
|
||||
@CreateDS(name = "Example",
|
||||
partitions = {@CreatePartition(name = "example", suffix = "dc=example,dc=com",
|
||||
contextEntry = @ContextEntry(entryLdif = "dn: dc=example,dc=com\n" + "dc: example\n" + "objectClass: top\n" + "objectClass: domain\n\n"),
|
||||
indexes = {@CreateIndex(attribute = "objectClass"), @CreateIndex(attribute = "dc"), @CreateIndex(attribute = "ou")})},
|
||||
|
@ -160,7 +160,19 @@ public class SaslKrb5LDAPSecurityTest extends AbstractLdapTestUnit {
|
|||
|
||||
testDir = temporaryFolder.getRoot().getAbsolutePath();
|
||||
|
||||
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager("Krb5PlusLdap");
|
||||
// hard coded match, default_keytab_name in minikdc-krb5.conf template
|
||||
File userKeyTab = new File("target/test.krb5.keytab");
|
||||
createPrincipal(userKeyTab, "client", "amqp/localhost", "ldap/localhost");
|
||||
|
||||
if (debug) {
|
||||
dumpLdapContents();
|
||||
}
|
||||
|
||||
rewriteKerb5Conf();
|
||||
}
|
||||
|
||||
private void createArtemisServer(String securityConfigScope) {
|
||||
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager(securityConfigScope);
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
params.put(TransportConstants.PORT_PROP_NAME, String.valueOf(5672));
|
||||
params.put(TransportConstants.PROTOCOLS_PROP_NAME, "AMQP");
|
||||
|
@ -171,17 +183,6 @@ public class SaslKrb5LDAPSecurityTest extends AbstractLdapTestUnit {
|
|||
|
||||
Configuration configuration = new ConfigurationImpl().setSecurityEnabled(true).addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params, "netty-amqp", amqpParams)).setJournalDirectory(ActiveMQTestBase.getJournalDir(testDir, 0, false)).setBindingsDirectory(ActiveMQTestBase.getBindingsDir(testDir, 0, false)).setPagingDirectory(ActiveMQTestBase.getPageDir(testDir, 0, false)).setLargeMessagesDirectory(ActiveMQTestBase.getLargeMessagesDir(testDir, 0, false));
|
||||
server = ActiveMQServers.newActiveMQServer(configuration, ManagementFactory.getPlatformMBeanServer(), securityManager, false);
|
||||
|
||||
|
||||
// hard coded match, default_keytab_name in minikdc-krb5.conf template
|
||||
File userKeyTab = new File("target/test.krb5.keytab");
|
||||
createPrincipal(userKeyTab, "client", "amqp/localhost", "ldap/localhost");
|
||||
|
||||
if (debug) {
|
||||
dumpLdapContents();
|
||||
}
|
||||
|
||||
rewriteKerb5Conf();
|
||||
}
|
||||
|
||||
private void rewriteKerb5Conf() throws Exception {
|
||||
|
@ -257,7 +258,14 @@ public class SaslKrb5LDAPSecurityTest extends AbstractLdapTestUnit {
|
|||
|
||||
public synchronized void createPrincipal(String principal, String password) throws Exception {
|
||||
String baseDn = getKdcServer().getSearchBaseDn();
|
||||
String content = "dn: uid=" + principal + "," + baseDn + "\n" + "objectClass: top\n" + "objectClass: person\n" + "objectClass: inetOrgPerson\n" + "objectClass: krb5principal\n" + "objectClass: krb5kdcentry\n" + "cn: " + principal + "\n" + "sn: " + principal + "\n" + "uid: " + principal + "\n" + "userPassword: " + password + "\n" + "krb5PrincipalName: " + principal + "@" + getRealm() + "\n" + "krb5KeyVersionNumber: 0";
|
||||
String content = "dn: uid=" + principal + "," + baseDn + "\n" + "objectClass: top\n" + "objectClass: person\n" + "objectClass: inetOrgPerson\n" + "objectClass: krb5principal\n"
|
||||
+ "objectClass: krb5kdcentry\n" + "cn: " + principal + "\n" + "sn: " + principal + "\n"
|
||||
+ "uid: " + principal + "\n" + "userPassword: " + password + "\n"
|
||||
// using businessCategory as a proxy for memberoOf attribute pending: https://issues.apache.org/jira/browse/DIRSERVER-1844
|
||||
+ "businessCategory: " + "cn=admins,ou=system" + "\n"
|
||||
+ "businessCategory: " + "cn=bees,ou=system" + "\n"
|
||||
+ "krb5PrincipalName: " + principal + "@" + getRealm() + "\n"
|
||||
+ "krb5KeyVersionNumber: 0";
|
||||
|
||||
for (LdifEntry ldifEntry : new LdifReader(new StringReader(content))) {
|
||||
service.getAdminSession().add(new DefaultEntry(service.getSchemaManager(), ldifEntry.getEntry()));
|
||||
|
@ -265,7 +273,7 @@ public class SaslKrb5LDAPSecurityTest extends AbstractLdapTestUnit {
|
|||
}
|
||||
|
||||
public void createPrincipal(File keytabFile, String... principals) throws Exception {
|
||||
String generatedPassword = UUID.randomUUID().toString();
|
||||
String generatedPassword = "notSecret!";
|
||||
Keytab keytab = new Keytab();
|
||||
List<KeytabEntry> entries = new ArrayList<>();
|
||||
for (String principal : principals) {
|
||||
|
@ -288,8 +296,10 @@ public class SaslKrb5LDAPSecurityTest extends AbstractLdapTestUnit {
|
|||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (server != null) {
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunning() throws Exception {
|
||||
|
@ -358,9 +368,31 @@ public class SaslKrb5LDAPSecurityTest extends AbstractLdapTestUnit {
|
|||
|
||||
@Test
|
||||
public void testJAASSecurityManagerAuthorizationPositive() throws Exception {
|
||||
dotestJAASSecurityManagerAuthorizationPositive("Krb5PlusLdap", "admins");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJAASSecurityManagerAuthorizationPositiveMemberOf() throws Exception {
|
||||
// using businessCategory as a proxy for memberoOf attribute pending: https://issues.apache.org/jira/browse/DIRSERVER-1844
|
||||
dotestJAASSecurityManagerAuthorizationPositive("Krb5PlusLdapMemberOf", "bees");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJAASSecurityManagerAuthorizationPositiveNoRoleName() throws Exception {
|
||||
dotestJAASSecurityManagerAuthorizationPositive("Krb5PlusLdapNoRoleName", "cn=admins,ou=system");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJAASSecurityManagerAuthorizationPositiveMemberOfNoRoleName() throws Exception {
|
||||
dotestJAASSecurityManagerAuthorizationPositive("Krb5PlusLdapMemberOfNoRoleName", "cn=bees,ou=system");
|
||||
}
|
||||
|
||||
public void dotestJAASSecurityManagerAuthorizationPositive(String jaasConfigScope, String artemisRoleName) throws Exception {
|
||||
|
||||
createArtemisServer(jaasConfigScope);
|
||||
|
||||
Set<Role> roles = new HashSet<>();
|
||||
roles.add(new Role("admins", true, true, true, true, true, true, true, true, true, true));
|
||||
roles.add(new Role(artemisRoleName, true, true, true, true, true, true, true, true, true, true));
|
||||
server.getConfiguration().putSecurityRoles(QUEUE_NAME, roles);
|
||||
server.start();
|
||||
|
||||
|
|
|
@ -151,7 +151,7 @@ Krb5Plus {
|
|||
|
||||
Krb5PlusLdap {
|
||||
|
||||
org.apache.activemq.artemis.spi.core.security.jaas.Krb5LoginModule optional
|
||||
org.apache.activemq.artemis.spi.core.security.jaas.Krb5LoginModule required
|
||||
debug=true;
|
||||
|
||||
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule optional
|
||||
|
@ -172,6 +172,69 @@ Krb5PlusLdap {
|
|||
;
|
||||
};
|
||||
|
||||
Krb5PlusLdapNoRoleName {
|
||||
|
||||
org.apache.activemq.artemis.spi.core.security.jaas.Krb5LoginModule required
|
||||
debug=true;
|
||||
|
||||
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule optional
|
||||
debug=true
|
||||
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
|
||||
connectionURL="ldap://localhost:1024"
|
||||
authentication=GSSAPI
|
||||
saslLoginConfigScope=broker-sasl-gssapi
|
||||
connectionProtocol=s
|
||||
userBase="ou=users,dc=example,dc=com"
|
||||
userSearchMatching="(krb5PrincipalName={0})"
|
||||
userSearchSubtree=true
|
||||
authenticateUser=false
|
||||
roleBase="ou=system"
|
||||
roleSearchMatching="(member={0})"
|
||||
roleSearchSubtree=false
|
||||
;
|
||||
};
|
||||
|
||||
Krb5PlusLdapMemberOf {
|
||||
|
||||
org.apache.activemq.artemis.spi.core.security.jaas.Krb5LoginModule required
|
||||
debug=true;
|
||||
|
||||
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule optional
|
||||
debug=true
|
||||
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
|
||||
connectionURL="ldap://localhost:1024"
|
||||
authentication=GSSAPI
|
||||
saslLoginConfigScope=broker-sasl-gssapi
|
||||
connectionProtocol=s
|
||||
userBase="ou=users,dc=example,dc=com"
|
||||
userSearchMatching="(krb5PrincipalName={0})"
|
||||
userSearchSubtree=true
|
||||
authenticateUser=false
|
||||
userRoleName=businessCategory
|
||||
roleName=cn
|
||||
;
|
||||
};
|
||||
|
||||
Krb5PlusLdapMemberOfNoRoleName {
|
||||
|
||||
org.apache.activemq.artemis.spi.core.security.jaas.Krb5LoginModule required
|
||||
debug=true;
|
||||
|
||||
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule optional
|
||||
debug=true
|
||||
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
|
||||
connectionURL="ldap://localhost:1024"
|
||||
authentication=GSSAPI
|
||||
saslLoginConfigScope=broker-sasl-gssapi
|
||||
connectionProtocol=s
|
||||
userBase="ou=users,dc=example,dc=com"
|
||||
userSearchMatching="(krb5PrincipalName={0})"
|
||||
userSearchSubtree=true
|
||||
authenticateUser=false
|
||||
userRoleName=businessCategory
|
||||
;
|
||||
};
|
||||
|
||||
core-tls-krb5-server {
|
||||
com.sun.security.auth.module.Krb5LoginModule required
|
||||
isInitiator=false
|
||||
|
|
Loading…
Reference in New Issue