blob: 96994da199f2d2a2bdc92971474dab5c95e00615 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.registry.ranger;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.registry.security.authorization.AccessPolicy;
import org.apache.nifi.registry.security.authorization.Group;
import org.apache.nifi.registry.security.authorization.RequestAction;
import org.apache.nifi.registry.security.authorization.User;
import org.apache.nifi.registry.security.authorization.UserGroupProvider;
import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException;
import org.apache.ranger.plugin.service.RangerBasePlugin;
import org.apache.ranger.plugin.util.ServicePolicies;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Extends the base plugin to convert service policies into NiFi Registry policy domain model.
*/
public class RangerBasePluginWithPolicies extends RangerBasePlugin {
private static final Logger logger = LoggerFactory.getLogger(RangerBasePluginWithPolicies.class);
private final static String WILDCARD_ASTERISK = "*";
private UserGroupProvider userGroupProvider;
private AtomicReference<PolicyLookup> policies = new AtomicReference<>(new PolicyLookup());
public RangerBasePluginWithPolicies(final String serviceType, final String appId) {
this(serviceType, appId, null);
}
public RangerBasePluginWithPolicies(final String serviceType, final String appId, final UserGroupProvider userGroupProvider) {
super(serviceType, appId);
this.userGroupProvider = userGroupProvider; // will be null if used outside of the managed RangerAuthorizer
}
@Override
public void setPolicies(final ServicePolicies policies) {
super.setPolicies(policies);
if (policies == null || policies.getPolicies() == null) {
this.policies.set(new PolicyLookup());
} else {
this.policies.set(createPolicyLookup(policies));
}
}
/**
* Determines if a policy exists for the given resource.
*
* @param resourceIdentifier the id of the resource
*
* @return true if a policy exists for the given resource, false otherwise
*/
public boolean doesPolicyExist(final String resourceIdentifier, final RequestAction requestAction) {
if (resourceIdentifier == null) {
return false;
}
final PolicyLookup policyLookup = policies.get();
return policyLookup.getAccessPolicy(resourceIdentifier, requestAction) != null;
}
public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
return policies.get().getAccessPolicies();
}
public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
return policies.get().getAccessPolicy(identifier);
}
public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
return policies.get().getAccessPolicy(resourceIdentifier, action);
}
private PolicyLookup createPolicyLookup(final ServicePolicies servicePolicies) {
final Map<String, AccessPolicy> policiesByIdentifier = new HashMap<>();
final Map<String, Map<RequestAction, AccessPolicy>> policiesByResource = new HashMap<>();
logger.debug("Converting Ranger ServicePolicies model into NiFi Registry policy model for viewing purposes in NiFi Registry UI.");
servicePolicies.getPolicies().stream().forEach(policy -> {
// only consider policies that are enabled
if (Boolean.TRUE.equals(policy.getIsEnabled())) {
// get all the resources for this policy - excludes/recursive support disabled
final Set<String> resources = policy.getResources().values().stream()
.filter(resource -> {
final boolean isMissingResource;
final boolean isWildcard;
if (resource.getValues() == null) {
isMissingResource = true;
isWildcard = false;
} else {
isMissingResource = false;
isWildcard = resource.getValues().stream().anyMatch(value -> value.contains(WILDCARD_ASTERISK));
}
final boolean isExclude = Boolean.TRUE.equals(resource.getIsExcludes());
final boolean isRecursive = Boolean.TRUE.equals(resource.getIsRecursive());
if (isMissingResource) {
logger.warn("Encountered resources missing values. Skipping policy for viewing purposes. Will still be used for access decisions.");
}
if (isWildcard) {
logger.warn(String.format("Resources [%s] include a wildcard value. Skipping policy for viewing purposes. "
+ "Will still be used for access decisions.", StringUtils.join(resource.getValues(), ", ")));
}
if (isExclude) {
logger.warn(String.format("Resources [%s] marked as an exclude policy. Skipping policy for viewing purposes. "
+ "Will still be used for access decisions.", StringUtils.join(resource.getValues(), ", ")));
}
if (isRecursive) {
logger.warn(String.format("Resources [%s] marked as a recursive policy. Skipping policy for viewing purposes. "
+ "Will still be used for access decisions.", StringUtils.join(resource.getValues(), ", ")));
}
return !isMissingResource && !isWildcard && !isExclude && !isRecursive;
})
.flatMap(resource -> resource.getValues().stream())
.collect(Collectors.toSet());
policy.getPolicyItems().forEach(policyItem -> {
// get all the users for this policy item, excluding unknown users
final Set<String> userIds = policyItem.getUsers().stream()
.map(userIdentity -> getUser(userIdentity))
.filter(Objects::nonNull)
.map(user -> user.getIdentifier())
.collect(Collectors.toSet());
// get all groups for this policy item, excluding unknown groups
final Set<String> groupIds = policyItem.getGroups().stream()
.map(groupName -> getGroup(groupName))
.filter(Objects::nonNull)
.map(group -> group.getIdentifier())
.collect(Collectors.toSet());
// check if this policy item is a delegate admin
final boolean isDelegateAdmin = Boolean.TRUE.equals(policyItem.getDelegateAdmin());
policyItem.getAccesses().forEach(access -> {
try {
// interpret the request action
final RequestAction action = RequestAction.valueOf(access.getType());
// function for creating an access policy
final Function<String, AccessPolicy> createPolicy = resource -> new AccessPolicy.Builder()
.identifierGenerateFromSeed(resource + access.getType())
.resource(resource)
.action(action)
.addUsers(userIds)
.addGroups(groupIds)
.build();
resources.forEach(resource -> {
// create the access policy for the specified resource
final AccessPolicy accessPolicy = createPolicy.apply(resource);
policiesByIdentifier.put(accessPolicy.getIdentifier(), accessPolicy);
policiesByResource.computeIfAbsent(resource, r -> new HashMap<>()).put(action, accessPolicy);
// if this is a delegate admin, also create the admin policy for the specified resource
if (isDelegateAdmin) {
// build the admin resource identifier
final String adminResource;
if (resource.startsWith("/")) {
adminResource = "/policies" + resource;
} else {
adminResource = "/policies/" + resource;
}
final AccessPolicy adminAccessPolicy = createPolicy.apply(adminResource);
policiesByIdentifier.put(adminAccessPolicy.getIdentifier(), adminAccessPolicy);
policiesByResource.computeIfAbsent(adminResource, ar -> new HashMap<>()).put(action, adminAccessPolicy);
}
});
} catch (final IllegalArgumentException e) {
logger.warn(String.format("Unrecognized request action '%s'. Skipping policy for viewing purposes. Will still be used for access decisions.", access.getType()));
}
});
});
}
});
return new PolicyLookup(policiesByIdentifier, policiesByResource);
}
private User getUser(final String identity) {
if (userGroupProvider == null) {
// generate the user deterministically when running outside of the ManagedRangerAuthorizer
return new User.Builder().identifierGenerateFromSeed(identity).identity(identity).build();
} else {
// find the user in question
final User user = userGroupProvider.getUserByIdentity(identity);
if (user == null) {
logger.warn(String.format("Cannot find user '%s' in the configured User Group Provider. Skipping user for viewing purposes. Will still be used for access decisions.", identity));
}
return user;
}
}
private Group getGroup(final String name) {
if (userGroupProvider == null) {
// generate the group deterministically when running outside of the ManagedRangerAuthorizer
return new Group.Builder().identifierGenerateFromSeed(name).name(name).build();
} else {
// find the group in question
final Group group = userGroupProvider.getGroups().stream().filter(g -> g.getName().equals(name)).findFirst().orElse(null);
if (group == null) {
logger.warn(String.format("Cannot find group '%s' in the configured User Group Provider. Skipping group for viewing purposes. Will still be used for access decisions.", name));
}
return group;
}
}
private static class PolicyLookup {
private final Map<String, AccessPolicy> policiesByIdentifier;
private final Map<String, Map<RequestAction, AccessPolicy>> policiesByResource;
private final Set<AccessPolicy> allPolicies;
private PolicyLookup() {
this(null, null);
}
private PolicyLookup(final Map<String, AccessPolicy> policiesByIdentifier, final Map<String, Map<RequestAction, AccessPolicy>> policiesByResource) {
if (policiesByIdentifier == null) {
allPolicies = Collections.EMPTY_SET;
} else {
allPolicies = Collections.unmodifiableSet(new HashSet<>(policiesByIdentifier.values()));
}
this.policiesByIdentifier = policiesByIdentifier;
this.policiesByResource = policiesByResource;
}
private Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
return allPolicies;
}
private AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
if (policiesByIdentifier == null) {
return null;
}
return policiesByIdentifier.get(identifier);
}
private AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
if (policiesByResource == null) {
return null;
}
final Map<RequestAction, AccessPolicy> policiesForResource = policiesByResource.get(resourceIdentifier);
if (policiesForResource != null) {
return policiesForResource.get(action);
}
return null;
}
}
}