NIFI-5135:

- Adding support for applying transforms to user identities and group names.

This closes #2673.

Signed-off-by: Bryan Bende <bbende@apache.org>
This commit is contained in:
Matt Gilman 2018-05-03 15:26:31 -04:00 committed by Bryan Bende
parent ff04f8efee
commit 5cfa29e48f
No known key found for this signature in database
GPG Key ID: A0DDA9ED50711C39
12 changed files with 323 additions and 85 deletions

View File

@ -151,6 +151,10 @@ public abstract class NiFiProperties {
public static final String SECURITY_OCSP_RESPONDER_CERTIFICATE = "nifi.security.ocsp.responder.certificate"; public static final String SECURITY_OCSP_RESPONDER_CERTIFICATE = "nifi.security.ocsp.responder.certificate";
public static final String SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX = "nifi.security.identity.mapping.pattern."; public static final String SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX = "nifi.security.identity.mapping.pattern.";
public static final String SECURITY_IDENTITY_MAPPING_VALUE_PREFIX = "nifi.security.identity.mapping.value."; public static final String SECURITY_IDENTITY_MAPPING_VALUE_PREFIX = "nifi.security.identity.mapping.value.";
public static final String SECURITY_IDENTITY_MAPPING_TRANSFORM_PREFIX = "nifi.security.identity.mapping.transform.";
public static final String SECURITY_GROUP_MAPPING_PATTERN_PREFIX = "nifi.security.group.mapping.pattern.";
public static final String SECURITY_GROUP_MAPPING_VALUE_PREFIX = "nifi.security.group.mapping.value.";
public static final String SECURITY_GROUP_MAPPING_TRANSFORM_PREFIX = "nifi.security.group.mapping.transform.";
// oidc // oidc
public static final String SECURITY_USER_OIDC_DISCOVERY_URL = "nifi.security.user.oidc.discovery.url"; public static final String SECURITY_USER_OIDC_DISCOVERY_URL = "nifi.security.user.oidc.discovery.url";

View File

@ -18,19 +18,33 @@ package org.apache.nifi.authorization.util;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static org.apache.nifi.authorization.util.IdentityMapping.Transform.NONE;
/** /**
* Holder to pass around the key, pattern, and replacement from an identity mapping in NiFiProperties. * Holder to pass around the key, pattern, and replacement from an identity mapping in NiFiProperties.
*/ */
public class IdentityMapping { public class IdentityMapping {
public enum Transform {
NONE,
UPPER,
LOWER
}
private final String key; private final String key;
private final Pattern pattern; private final Pattern pattern;
private final String replacementValue; private final String replacementValue;
private final Transform transform;
public IdentityMapping(String key, Pattern pattern, String replacementValue) { public IdentityMapping(String key, Pattern pattern, String replacementValue) {
this(key, pattern, replacementValue, NONE);
}
public IdentityMapping(String key, Pattern pattern, String replacementValue, Transform transform) {
this.key = key; this.key = key;
this.pattern = pattern; this.pattern = pattern;
this.replacementValue = replacementValue; this.replacementValue = replacementValue;
this.transform = transform;
} }
public String getKey() { public String getKey() {
@ -45,4 +59,7 @@ public class IdentityMapping {
return replacementValue; return replacementValue;
} }
public Transform getTransform() {
return transform;
}
} }

View File

@ -17,6 +17,7 @@
package org.apache.nifi.authorization.util; package org.apache.nifi.authorization.util;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.util.IdentityMapping.Transform;
import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.util.NiFiProperties;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -25,9 +26,17 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static org.apache.nifi.util.NiFiProperties.SECURITY_GROUP_MAPPING_PATTERN_PREFIX;
import static org.apache.nifi.util.NiFiProperties.SECURITY_GROUP_MAPPING_TRANSFORM_PREFIX;
import static org.apache.nifi.util.NiFiProperties.SECURITY_GROUP_MAPPING_VALUE_PREFIX;
import static org.apache.nifi.util.NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX;
import static org.apache.nifi.util.NiFiProperties.SECURITY_IDENTITY_MAPPING_TRANSFORM_PREFIX;
import static org.apache.nifi.util.NiFiProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX;
public class IdentityMappingUtil { public class IdentityMappingUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(IdentityMappingUtil.class); private static final Logger LOGGER = LoggerFactory.getLogger(IdentityMappingUtil.class);
@ -40,33 +49,76 @@ public class IdentityMappingUtil {
* @return a list of identity mappings * @return a list of identity mappings
*/ */
public static List<IdentityMapping> getIdentityMappings(final NiFiProperties properties) { public static List<IdentityMapping> getIdentityMappings(final NiFiProperties properties) {
return getMappings(
properties,
SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX,
SECURITY_IDENTITY_MAPPING_VALUE_PREFIX,
SECURITY_IDENTITY_MAPPING_TRANSFORM_PREFIX,
() -> "Identity");
}
/**
* Buils the group mappings from NiFiProperties.
*
* @param properties the NiFiProperties instance
* @return a list of group mappings
*/
public static List<IdentityMapping> getGroupMappings(final NiFiProperties properties) {
return getMappings(
properties,
SECURITY_GROUP_MAPPING_PATTERN_PREFIX,
SECURITY_GROUP_MAPPING_VALUE_PREFIX,
SECURITY_GROUP_MAPPING_TRANSFORM_PREFIX,
() -> "Group");
}
private static List<IdentityMapping> getMappings(final NiFiProperties properties, final String patternPrefix,
final String valuePrefix, final String transformPrefix, final Supplier<String> getSubject) {
final List<IdentityMapping> mappings = new ArrayList<>(); final List<IdentityMapping> mappings = new ArrayList<>();
// go through each property // go through each property
for (String propertyName : properties.getPropertyKeys()) { for (String propertyName : properties.getPropertyKeys()) {
if (StringUtils.startsWith(propertyName, NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX)) { if (StringUtils.startsWith(propertyName, patternPrefix)) {
final String key = StringUtils.substringAfter(propertyName, NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX); final String key = StringUtils.substringAfter(propertyName, patternPrefix);
final String identityPattern = properties.getProperty(propertyName); final String identityPattern = properties.getProperty(propertyName);
if (StringUtils.isBlank(identityPattern)) { if (StringUtils.isBlank(identityPattern)) {
LOGGER.warn("Identity Mapping property {} was found, but was empty", new Object[]{propertyName}); LOGGER.warn("{} Mapping property {} was found, but was empty", new Object[] {getSubject.get(), propertyName});
continue; continue;
} }
final String identityValueProperty = NiFiProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX + key; final String identityValueProperty = valuePrefix + key;
final String identityValue = properties.getProperty(identityValueProperty); final String identityValue = properties.getProperty(identityValueProperty);
if (StringUtils.isBlank(identityValue)) { if (StringUtils.isBlank(identityValue)) {
LOGGER.warn("Identity Mapping property {} was found, but corresponding value {} was not found", LOGGER.warn("{} Mapping property {} was found, but corresponding value {} was not found",
new Object[]{propertyName, identityValueProperty}); new Object[] {getSubject.get(), propertyName, identityValueProperty});
continue; continue;
} }
final IdentityMapping identityMapping = new IdentityMapping(key, Pattern.compile(identityPattern), identityValue); final String identityTransformProperty = transformPrefix + key;
String rawIdentityTransform = properties.getProperty(identityTransformProperty);
if (StringUtils.isBlank(rawIdentityTransform)) {
LOGGER.debug("{} Mapping property {} was found, but no transform was present. Using NONE.", new Object[] {getSubject.get(), propertyName});
rawIdentityTransform = Transform.NONE.name();
}
final Transform identityTransform;
try {
identityTransform = Transform.valueOf(rawIdentityTransform);
} catch (final IllegalArgumentException iae) {
LOGGER.warn("{} Mapping property {} was found, but corresponding transform {} was not valid. Allowed values {}",
new Object[] {getSubject.get(), propertyName, rawIdentityTransform, StringUtils.join(Transform.values(), ", ")});
continue;
}
final IdentityMapping identityMapping = new IdentityMapping(key, Pattern.compile(identityPattern), identityValue, identityTransform);
mappings.add(identityMapping); mappings.add(identityMapping);
LOGGER.debug("Found Identity Mapping with key = {}, pattern = {}, value = {}", LOGGER.debug("Found {} Mapping with key = {}, pattern = {}, value = {}, transform = {}",
new Object[] {key, identityPattern, identityValue}); new Object[] {getSubject.get(), key, identityPattern, identityValue, rawIdentityTransform});
} }
} }
@ -95,7 +147,15 @@ public class IdentityMappingUtil {
if (m.matches()) { if (m.matches()) {
final String pattern = mapping.getPattern().pattern(); final String pattern = mapping.getPattern().pattern();
final String replacementValue = escapeLiteralBackReferences(mapping.getReplacementValue(), m.groupCount()); final String replacementValue = escapeLiteralBackReferences(mapping.getReplacementValue(), m.groupCount());
return identity.replaceAll(pattern, replacementValue); final String replacement = identity.replaceAll(pattern, replacementValue);
if (Transform.UPPER.equals(mapping.getTransform())) {
return replacement.toUpperCase();
} else if (Transform.LOWER.equals(mapping.getTransform())) {
return replacement.toLowerCase();
} else {
return replacement;
}
} }
} }

View File

@ -3406,13 +3406,27 @@ The following examples demonstrate normalizing DNs from certificates and princip
---- ----
nifi.security.identity.mapping.pattern.dn=^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$ nifi.security.identity.mapping.pattern.dn=^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$
nifi.security.identity.mapping.value.dn=$1@$2 nifi.security.identity.mapping.value.dn=$1@$2
nifi.security.identity.mapping.transform.dn=NONE
nifi.security.identity.mapping.pattern.kerb=^(.*?)/instance@(.*?)$ nifi.security.identity.mapping.pattern.kerb=^(.*?)/instance@(.*?)$
nifi.security.identity.mapping.value.kerb=$1@$2 nifi.security.identity.mapping.value.kerb=$1@$2
nifi.security.identity.mapping.transform.kerb=NONE
---- ----
The last segment of each property is an identifier used to associate the pattern with the replacement value. When a user makes a request to NiFi, their identity is checked to see if it matches each of those patterns in lexicographical order. For the first one that matches, the replacement specified in the `nifi.security.identity.mapping.value.xxxx` property is used. So a login with `CN=localhost, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US` matches the DN mapping pattern above and the DN mapping value `$1@$2` is applied. The user is normalized to `localhost@Apache NiFi`. The last segment of each property is an identifier used to associate the pattern with the replacement value. When a user makes a request to NiFi, their identity is checked to see if it matches each of those patterns in lexicographical order. For the first one that matches, the replacement specified in the `nifi.security.identity.mapping.value.xxxx` property is used. So a login with `CN=localhost, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US` matches the DN mapping pattern above and the DN mapping value `$1@$2` is applied. The user is normalized to `localhost@Apache NiFi`.
NOTE: These mappings are also applied to the "Initial Admin Identity" and "Cluster Node Identity" properties in the authorizers.xml file (See <<authorizers-setup>>). In addition to mapping a transform may be applied. The supported versions are NONE (no transform applied), LOWER (identity lowercased), and UPPER (identity uppercased). If not specified, the default value is NONE.
NOTE: These mappings are also applied to the "Initial Admin Identity", "Cluster Node Identity", and any legacy users in the authorizers.xml file as well as users imported from LDAP (See <<authorizers-setup>>).
Group names can also be mapped. The following example will accept the existing group name but will lowercase it. This may be helpful when used in conjunction with an external authorizer.
----
nifi.security.group.mapping.pattern.anygroup=^(.*)$
nifi.security.group.mapping.value.anygroup=$1
nifi.security.group.mapping.transform.anygroup=LOWER
----
NOTE: These mappings are applied to any legacy groups referenced in the authorizers.xml as well as groups imported from LDAP.
=== Cluster Common Properties === Cluster Common Properties

View File

@ -16,39 +16,6 @@
*/ */
package org.apache.nifi.authorization; package org.apache.nifi.authorization;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.annotation.AuthorizerContext; import org.apache.nifi.authorization.annotation.AuthorizerContext;
import org.apache.nifi.authorization.exception.AuthorizationAccessException; import org.apache.nifi.authorization.exception.AuthorizationAccessException;
@ -76,6 +43,40 @@ import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvider { public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvider {
private static final Logger logger = LoggerFactory.getLogger(FileAccessPolicyProvider.class); private static final Logger logger = LoggerFactory.getLogger(FileAccessPolicyProvider.class);
@ -130,6 +131,7 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide
private Set<String> nodeIdentities; private Set<String> nodeIdentities;
private List<PortDTO> ports = new ArrayList<>(); private List<PortDTO> ports = new ArrayList<>();
private List<IdentityMapping> identityMappings; private List<IdentityMapping> identityMappings;
private List<IdentityMapping> groupMappings;
private UserGroupProvider userGroupProvider; private UserGroupProvider userGroupProvider;
private UserGroupProviderLookup userGroupProviderLookup; private UserGroupProviderLookup userGroupProviderLookup;
@ -200,6 +202,7 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide
// extract the identity mappings from nifi.properties if any are provided // extract the identity mappings from nifi.properties if any are provided
identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties)); identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
groupMappings = Collections.unmodifiableList(IdentityMappingUtil.getGroupMappings(properties));
// get the value of the initial admin identity // get the value of the initial admin identity
final PropertyValue initialAdminIdentityProp = configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY); final PropertyValue initialAdminIdentityProp = configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY);
@ -728,10 +731,12 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide
if (portDTO.getGroupAccessControl() != null) { if (portDTO.getGroupAccessControl() != null) {
for (String groupAccessControl : portDTO.getGroupAccessControl()) { for (String groupAccessControl : portDTO.getGroupAccessControl()) {
final String legacyGroupName = IdentityMappingUtil.mapIdentity(groupAccessControl, groupMappings);
// find a group where the name is the groupAccessControl // find a group where the name is the groupAccessControl
Group foundGroup = null; Group foundGroup = null;
for (Group group : userGroupProvider.getGroups()) { for (Group group : userGroupProvider.getGroups()) {
if (group.getName().equals(groupAccessControl)) { if (group.getName().equals(legacyGroupName)) {
foundGroup = group; foundGroup = group;
break; break;
} }
@ -740,7 +745,7 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide
// couldn't find the group matching the access control so log a warning and skip // couldn't find the group matching the access control so log a warning and skip
if (foundGroup == null) { if (foundGroup == null) {
logger.warn("Found port with group access control for {} but no group exists with this name, skipping...", logger.warn("Found port with group access control for {} but no group exists with this name, skipping...",
new Object[] {groupAccessControl}); new Object[] {legacyGroupName});
continue; continue;
} }
@ -751,7 +756,7 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide
resource.getIdentifier(), resource.getIdentifier(),
WRITE_CODE); WRITE_CODE);
addGroupToPolicy(IdentifierUtil.getIdentifier(groupAccessControl), policy); addGroupToPolicy(IdentifierUtil.getIdentifier(legacyGroupName), policy);
} }
} }
} }

View File

@ -16,39 +16,6 @@
*/ */
package org.apache.nifi.authorization; package org.apache.nifi.authorization;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.annotation.AuthorizerContext; import org.apache.nifi.authorization.annotation.AuthorizerContext;
import org.apache.nifi.authorization.exception.AuthorizationAccessException; import org.apache.nifi.authorization.exception.AuthorizationAccessException;
@ -72,6 +39,40 @@ import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FileUserGroupProvider implements ConfigurableUserGroupProvider { public class FileUserGroupProvider implements ConfigurableUserGroupProvider {
private static final Logger logger = LoggerFactory.getLogger(FileUserGroupProvider.class); private static final Logger logger = LoggerFactory.getLogger(FileUserGroupProvider.class);
@ -118,6 +119,7 @@ public class FileUserGroupProvider implements ConfigurableUserGroupProvider {
private String legacyAuthorizedUsersFile; private String legacyAuthorizedUsersFile;
private Set<String> initialUserIdentities; private Set<String> initialUserIdentities;
private List<IdentityMapping> identityMappings; private List<IdentityMapping> identityMappings;
private List<IdentityMapping> groupMappings;
private final AtomicReference<UserGroupHolder> userGroupHolder = new AtomicReference<>(); private final AtomicReference<UserGroupHolder> userGroupHolder = new AtomicReference<>();
@ -172,8 +174,9 @@ public class FileUserGroupProvider implements ConfigurableUserGroupProvider {
} }
} }
// extract the identity mappings from nifi.properties if any are provided // extract the identity and group mappings from nifi.properties if any are provided
identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties)); identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
groupMappings = Collections.unmodifiableList(IdentityMappingUtil.getGroupMappings(properties));
// get the value of the legacy authorized users file // get the value of the legacy authorized users file
final PropertyValue legacyAuthorizedUsersProp = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE); final PropertyValue legacyAuthorizedUsersProp = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE);
@ -697,8 +700,9 @@ public class FileUserGroupProvider implements ConfigurableUserGroupProvider {
org.apache.nifi.authorization.file.tenants.generated.User user = getOrCreateUser(tenants, legacyUserDn); org.apache.nifi.authorization.file.tenants.generated.User user = getOrCreateUser(tenants, legacyUserDn);
// if there was a group name find or create the group and add the user to it // if there was a group name find or create the group and add the user to it
org.apache.nifi.authorization.file.tenants.generated.Group group = getOrCreateGroup(tenants, legacyUser.getGroup()); if (StringUtils.isNotBlank(legacyUser.getGroup())) {
if (group != null) { final String legacyGroupName = IdentityMappingUtil.mapIdentity(legacyUser.getGroup(), groupMappings);
org.apache.nifi.authorization.file.tenants.generated.Group group = getOrCreateGroup(tenants, legacyGroupName);
org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User(); org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User();
groupUser.setIdentifier(user.getIdentifier()); groupUser.setIdentifier(user.getIdentifier());
group.getUser().add(groupUser); group.getUser().add(groupUser);

View File

@ -435,6 +435,10 @@ public class FileAuthorizerTest {
props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$"); props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$");
props.setProperty("nifi.security.identity.mapping.value.dn1", "$1"); props.setProperty("nifi.security.identity.mapping.value.dn1", "$1");
props.setProperty("nifi.security.group.mapping.pattern.anygroup", "^(.*)$");
props.setProperty("nifi.security.group.mapping.value.anygroup", "$1");
props.setProperty("nifi.security.group.mapping.transform.anygroup", "UPPER");
properties = getNiFiProperties(props); properties = getNiFiProperties(props);
when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile()); when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile());
when(properties.getFlowConfigurationFile()).thenReturn(flowWithDns); when(properties.getFlowConfigurationFile()).thenReturn(flowWithDns);
@ -469,7 +473,7 @@ public class FileAuthorizerTest {
final Set<Group> groups = authorizer.getGroups(); final Set<Group> groups = authorizer.getGroups();
assertEquals(1, groups.size()); assertEquals(1, groups.size());
final Group group1 = groups.iterator().next(); final Group group1 = groups.iterator().next();
assertEquals("group1", group1.getName()); assertEquals("GROUP1", group1.getName());
final Resource inputPortResource = ResourceFactory.getDataTransferResource( final Resource inputPortResource = ResourceFactory.getDataTransferResource(
ResourceFactory.getComponentResource(ResourceType.InputPort, "2f7d1606-b090-4be7-a592-a5b70fb55531", "TCP Input")); ResourceFactory.getComponentResource(ResourceType.InputPort, "2f7d1606-b090-4be7-a592-a5b70fb55531", "TCP Input"));

View File

@ -227,6 +227,52 @@ public class FileUserGroupProviderTest {
assertEquals("group1", group1.getName()); assertEquals("group1", group1.getName());
} }
@Test
public void testOnConfiguredWhenLegacyUsersFileProvidedWithIdentityMappingsAndTransforms() throws Exception {
final Properties props = new Properties();
props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$");
props.setProperty("nifi.security.identity.mapping.value.dn1", "$1");
props.setProperty("nifi.security.identity.mapping.transform.dn1", "UPPER");
props.setProperty("nifi.security.group.mapping.pattern.anygroup", "^(.*)$");
props.setProperty("nifi.security.group.mapping.value.anygroup", "$1");
props.setProperty("nifi.security.group.mapping.transform.anygroup", "UPPER");
properties = getNiFiProperties(props);
when(properties.getRestoreDirectory()).thenReturn(restoreTenants.getParentFile());
userGroupProvider.setNiFiProperties(properties);
when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
.thenReturn(new StandardPropertyValue("src/test/resources/authorized-users-with-dns.xml", null));
writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
userGroupProvider.onConfigured(configurationContext);
final User user1 = userGroupProvider.getUserByIdentity("USER1");
assertNotNull(user1);
final User user2 = userGroupProvider.getUserByIdentity("USER2");
assertNotNull(user2);
final User user3 = userGroupProvider.getUserByIdentity("USER3");
assertNotNull(user3);
final User user4 = userGroupProvider.getUserByIdentity("USER4");
assertNotNull(user4);
final User user5 = userGroupProvider.getUserByIdentity("USER5");
assertNotNull(user5);
final User user6 = userGroupProvider.getUserByIdentity("USER6");
assertNotNull(user6);
// verify one group got created
final Set<Group> groups = userGroupProvider.getGroups();
assertEquals(1, groups.size());
final Group group1 = groups.iterator().next();
assertEquals("GROUP1", group1.getName());
}
@Test(expected = AuthorizerCreationException.class) @Test(expected = AuthorizerCreationException.class)
public void testOnConfiguredWhenBadLegacyUsersFileProvided() throws Exception { public void testOnConfiguredWhenBadLegacyUsersFileProvided() throws Exception {
when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))

View File

@ -186,8 +186,18 @@ nifi.security.user.knox.audiences=${nifi.security.user.knox.audiences}
# #
# nifi.security.identity.mapping.pattern.dn=^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$ # nifi.security.identity.mapping.pattern.dn=^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$
# nifi.security.identity.mapping.value.dn=$1@$2 # nifi.security.identity.mapping.value.dn=$1@$2
# nifi.security.identity.mapping.transform.dn=NONE
# nifi.security.identity.mapping.pattern.kerb=^(.*?)/instance@(.*?)$ # nifi.security.identity.mapping.pattern.kerb=^(.*?)/instance@(.*?)$
# nifi.security.identity.mapping.value.kerb=$1@$2 # nifi.security.identity.mapping.value.kerb=$1@$2
# nifi.security.identity.mapping.transform.kerb=UPPER
# Group Mapping Properties #
# These properties allow normalizing group names coming from external sources like LDAP. The following example
# lowercases any group name.
#
# nifi.security.group.mapping.pattern.anygroup=^(.*)$
# nifi.security.group.mapping.value.anygroup=$1
# nifi.security.group.mapping.transform.anygroup=LOWER
# cluster common properties (all nodes must have same values) # # cluster common properties (all nodes must have same values) #
nifi.cluster.protocol.heartbeat.interval=${nifi.cluster.protocol.heartbeat.interval} nifi.cluster.protocol.heartbeat.interval=${nifi.cluster.protocol.heartbeat.interval}

View File

@ -47,6 +47,8 @@ public class TestStandardRootGroupPort {
final AuthorizationRequest request = invocation.getArgumentAt(0, AuthorizationRequest.class); final AuthorizationRequest request = invocation.getArgumentAt(0, AuthorizationRequest.class);
if ("node1@nifi.test".equals(request.getIdentity())) { if ("node1@nifi.test".equals(request.getIdentity())) {
return AuthorizationResult.approved(); return AuthorizationResult.approved();
} else if ("NODE1@NIFI.TEST".equals(request.getIdentity())) {
return AuthorizationResult.approved();
} }
return AuthorizationResult.denied(); return AuthorizationResult.denied();
}).when(authorizer).authorize(any(AuthorizationRequest.class)); }).when(authorizer).authorize(any(AuthorizationRequest.class));
@ -97,4 +99,31 @@ public class TestStandardRootGroupPort {
Assert.assertTrue(authResult.isAuthorized()); Assert.assertTrue(authResult.isAuthorized());
} }
@Test
public void testCheckUserAuthorizationByMappedDnWithTransformation() {
final NiFiProperties nifiProperties = mock(NiFiProperties.class);
final String mapKey = ".dn";
Set<String> propertyKeys = new LinkedHashSet<>();
propertyKeys.add(NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX + mapKey);
propertyKeys.add(NiFiProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX + mapKey);
propertyKeys.add(NiFiProperties.SECURITY_IDENTITY_MAPPING_TRANSFORM_PREFIX + mapKey);
doReturn(propertyKeys).when(nifiProperties).getPropertyKeys();
final String mapPattern = "^CN=(.*?), OU=(.*?)$";
final String mapValue = "$1@$2";
final String mapTransform = "UPPER";
doReturn(mapPattern).when(nifiProperties).getProperty(eq(NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX + mapKey));
doReturn(mapValue).when(nifiProperties).getProperty(eq(NiFiProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX + mapKey));
doReturn(mapTransform).when(nifiProperties).getProperty(eq(NiFiProperties.SECURITY_IDENTITY_MAPPING_TRANSFORM_PREFIX + mapKey));
final RootGroupPort port = createRootGroupPort(nifiProperties);
PortAuthorizationResult authResult = port.checkUserAuthorization("CN=node2, OU=nifi.test");
Assert.assertFalse(authResult.isAuthorized());
authResult = port.checkUserAuthorization("CN=node1, OU=nifi.test");
Assert.assertTrue(authResult.isAuthorized());
}
} }

View File

@ -117,6 +117,7 @@ public class LdapUserGroupProvider implements UserGroupProvider {
private static final long MINIMUM_SYNC_INTERVAL_MILLISECONDS = 10_000; private static final long MINIMUM_SYNC_INTERVAL_MILLISECONDS = 10_000;
private List<IdentityMapping> identityMappings; private List<IdentityMapping> identityMappings;
private List<IdentityMapping> groupMappings;
private NiFiProperties properties; private NiFiProperties properties;
private ScheduledExecutorService ldapSync; private ScheduledExecutorService ldapSync;
@ -350,6 +351,7 @@ public class LdapUserGroupProvider implements UserGroupProvider {
// extract the identity mappings from nifi.properties if any are provided // extract the identity mappings from nifi.properties if any are provided
identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties)); identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
groupMappings = Collections.unmodifiableList(IdentityMappingUtil.getGroupMappings(properties));
// set the base environment is necessary // set the base environment is necessary
if (!baseEnvironment.isEmpty()) { if (!baseEnvironment.isEmpty()) {
@ -708,7 +710,7 @@ public class LdapUserGroupProvider implements UserGroupProvider {
} }
} }
return name; return IdentityMappingUtil.mapIdentity(name, groupMappings);
} }
private String getReferencedGroupValue(final DirContextOperations ctx) { private String getReferencedGroupValue(final DirContextOperations ctx) {

View File

@ -507,6 +507,49 @@ public class LdapUserGroupProviderTest extends AbstractLdapTestUnit {
assertNotNull(ldapUserGroupProvider.getUserByIdentity("User 1,ou=users")); assertNotNull(ldapUserGroupProvider.getUserByIdentity("User 1,ou=users"));
} }
@Test
public void testUserIdentityMappingWithTransforms() throws Exception {
final Properties props = new Properties();
props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^cn=(.*?),ou=(.*?),o=(.*?)$");
props.setProperty("nifi.security.identity.mapping.value.dn1", "$1");
props.setProperty("nifi.security.identity.mapping.transform.dn1", "UPPER");
final NiFiProperties properties = getNiFiProperties(props);
ldapUserGroupProvider.setNiFiProperties(properties);
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, null);
when(configurationContext.getProperty(PROP_USER_SEARCH_FILTER)).thenReturn(new StandardPropertyValue("(uid=user1)", null));
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(1, ldapUserGroupProvider.getUsers().size());
assertNotNull(ldapUserGroupProvider.getUserByIdentity("USER 1"));
}
@Test
public void testUserIdentityAndGroupMappingWithTransforms() throws Exception {
final Properties props = new Properties();
props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^cn=(.*?),ou=(.*?),o=(.*?)$");
props.setProperty("nifi.security.identity.mapping.value.dn1", "$1");
props.setProperty("nifi.security.identity.mapping.transform.dn1", "UPPER");
props.setProperty("nifi.security.group.mapping.pattern.dn1", "^cn=(.*?),ou=(.*?),o=(.*?)$");
props.setProperty("nifi.security.group.mapping.value.dn1", "$1");
props.setProperty("nifi.security.group.mapping.transform.dn1", "UPPER");
final NiFiProperties properties = getNiFiProperties(props);
ldapUserGroupProvider.setNiFiProperties(properties);
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, GROUP_SEARCH_BASE);
when(configurationContext.getProperty(PROP_USER_SEARCH_FILTER)).thenReturn(new StandardPropertyValue("(uid=user1)", null));
when(configurationContext.getProperty(PROP_GROUP_SEARCH_FILTER)).thenReturn(new StandardPropertyValue("(cn=admins)", null));
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(1, ldapUserGroupProvider.getUsers().size());
assertNotNull(ldapUserGroupProvider.getUserByIdentity("USER 1"));
assertEquals(1, ldapUserGroupProvider.getGroups().size());
assertEquals("ADMINS", ldapUserGroupProvider.getGroups().iterator().next().getName());
}
@Test(expected = AuthorizerCreationException.class) @Test(expected = AuthorizerCreationException.class)
public void testReferencedGroupAttributeWithoutGroupSearchBase() throws Exception { public void testReferencedGroupAttributeWithoutGroupSearchBase() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration("ou=users-2,o=nifi", null); final AuthorizerConfigurationContext configurationContext = getBaseConfiguration("ou=users-2,o=nifi", null);