| /* |
| * 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.hadoop.ozone.om.multitenant; |
| |
| import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_KERBEROS_KEYTAB_FILE_KEY; |
| import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_KERBEROS_PRINCIPAL_KEY; |
| import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_HTTPS_ADDRESS_KEY; |
| import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_SERVICE; |
| import static org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod.KERBEROS; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.stream.Collectors; |
| |
| import com.google.common.base.Preconditions; |
| import org.apache.hadoop.hdds.conf.OzoneConfiguration; |
| import org.apache.hadoop.ozone.OmUtils; |
| import org.apache.hadoop.ozone.OzoneConsts; |
| import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer; |
| import org.apache.hadoop.security.SecurityUtil; |
| import org.apache.ranger.RangerServiceException; |
| import org.apache.ranger.plugin.model.RangerPolicy; |
| import org.apache.ranger.plugin.model.RangerRole; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import org.apache.ranger.RangerClient; |
| |
| /** |
| * Implementation of {@link MultiTenantAccessController} using the |
| * {@link RangerClient} to communicate with Ranger. |
| */ |
| public class RangerClientMultiTenantAccessController implements |
| MultiTenantAccessController { |
| |
| private static final Logger LOG = LoggerFactory |
| .getLogger(RangerClientMultiTenantAccessController.class); |
| |
| private final RangerClient client; |
| private final String rangerServiceName; |
| private final Map<IAccessAuthorizer.ACLType, String> aclToString; |
| private final Map<String, IAccessAuthorizer.ACLType> stringToAcl; |
| private final String omPrincipal; |
| |
| public RangerClientMultiTenantAccessController(OzoneConfiguration conf) |
| throws IOException { |
| aclToString = MultiTenantAccessController.getRangerAclStrings(); |
| stringToAcl = new HashMap<>(); |
| aclToString.forEach((type, string) -> stringToAcl.put(string, type)); |
| |
| // Should have passed the check in OMMultiTenantManager |
| String rangerHttpsAddress = conf.get(OZONE_RANGER_HTTPS_ADDRESS_KEY); |
| Preconditions.checkNotNull(rangerHttpsAddress); |
| rangerServiceName = conf.get(OZONE_RANGER_SERVICE); |
| Preconditions.checkNotNull(rangerServiceName); |
| |
| String configuredOmPrincipal = conf.get(OZONE_OM_KERBEROS_PRINCIPAL_KEY); |
| Preconditions.checkNotNull(configuredOmPrincipal); |
| // Replace _HOST pattern with host name in the Kerberos principal. Ranger |
| // client currently does not do this automatically. |
| omPrincipal = SecurityUtil.getServerPrincipal( |
| configuredOmPrincipal, OmUtils.getOmAddress(conf).getHostName()); |
| String keytabPath = conf.get(OZONE_OM_KERBEROS_KEYTAB_FILE_KEY); |
| Preconditions.checkNotNull(keytabPath); |
| |
| client = new RangerClient(rangerHttpsAddress, |
| KERBEROS.name().toLowerCase(), omPrincipal, keytabPath, |
| rangerServiceName, OzoneConsts.OZONE); |
| } |
| |
| @Override |
| public void createPolicy(Policy policy) throws RangerServiceException { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Sending create request for policy {} to Ranger.", |
| policy.getName()); |
| } |
| client.createPolicy(toRangerPolicy(policy)); |
| } |
| |
| @Override |
| public Policy getPolicy(String policyName) throws RangerServiceException { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Sending get request for policy {} to Ranger.", |
| policyName); |
| } |
| return fromRangerPolicy(client.getPolicy(rangerServiceName, policyName)); |
| } |
| |
| @Override |
| public List<Policy> getLabeledPolicies(String label) |
| throws RangerServiceException { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Sending get request for policies with label {} to Ranger.", |
| label); |
| } |
| Map<String, String> filterMap = new HashMap<>(); |
| filterMap.put("serviceName", rangerServiceName); |
| filterMap.put("policyLabelsPartial", label); |
| return client.findPolicies(filterMap).stream() |
| .map(this::fromRangerPolicy) |
| .collect(Collectors.toList()); |
| } |
| |
| @Override |
| public void updatePolicy(Policy policy) throws RangerServiceException { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Sending update request for policy {} to Ranger.", |
| policy.getName()); |
| } |
| client.updatePolicy(rangerServiceName, policy.getName(), |
| toRangerPolicy(policy)); |
| } |
| |
| @Override |
| public void deletePolicy(String policyName) throws RangerServiceException { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Sending delete request for policy {} to Ranger.", |
| policyName); |
| } |
| client.deletePolicy(rangerServiceName, policyName); |
| } |
| |
| @Override |
| public void createRole(Role role) throws RangerServiceException { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Sending create request for role {} to Ranger.", |
| role.getName()); |
| } |
| client.createRole(rangerServiceName, toRangerRole(role)); |
| } |
| |
| @Override |
| public Role getRole(String roleName) throws RangerServiceException { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Sending get request for role {} to Ranger.", |
| roleName); |
| } |
| return fromRangerRole(client.getRole(roleName, omPrincipal, |
| rangerServiceName)); |
| } |
| |
| @Override |
| public void updateRole(long roleID, Role role) throws RangerServiceException { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Sending update request for role ID {} to Ranger.", |
| roleID); |
| } |
| client.updateRole(roleID, toRangerRole(role)); |
| } |
| |
| @Override |
| public void deleteRole(String roleName) throws RangerServiceException { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Sending delete request for role {} to Ranger.", |
| roleName); |
| } |
| client.deleteRole(roleName, omPrincipal, rangerServiceName); |
| } |
| |
| @Override |
| public long getRangerServiceVersion() { |
| throw new UnsupportedOperationException("Ranger client implementation " + |
| "does not currently support this method. A workaround will be " + |
| "implemented in HDDS-6755."); |
| } |
| |
| private static List<RangerRole.RoleMember> toRangerRoleMembers( |
| Collection<String> members) { |
| return members.stream() |
| .map(princ -> new RangerRole.RoleMember(princ, false)) |
| .collect(Collectors.toList()); |
| } |
| |
| private static List<String> fromRangerRoleMembers( |
| Collection<RangerRole.RoleMember> members) { |
| return members.stream() |
| .map(rangerUser -> rangerUser.getName()) |
| .collect(Collectors.toList()); |
| } |
| |
| private static Role fromRangerRole(RangerRole rangerRole) { |
| return new Role.Builder() |
| .setID(rangerRole.getId()) |
| .setName(rangerRole.getName()) |
| .setDescription(rangerRole.getDescription()) |
| .addUsers(fromRangerRoleMembers(rangerRole.getUsers())) |
| .build(); |
| } |
| |
| private static RangerRole toRangerRole(Role role) { |
| RangerRole rangerRole = new RangerRole(); |
| rangerRole.setName(role.getName()); |
| rangerRole.setUsers(toRangerRoleMembers(role.getUsers())); |
| if (role.getDescription().isPresent()) { |
| rangerRole.setDescription(role.getDescription().get()); |
| } |
| return rangerRole; |
| } |
| |
| private Policy fromRangerPolicy(RangerPolicy rangerPolicy) { |
| Policy.Builder policyBuilder = new Policy.Builder(); |
| |
| // Get roles and their acls from the policy. |
| for (RangerPolicy.RangerPolicyItem policyItem: |
| rangerPolicy.getPolicyItems()) { |
| Collection<Acl> acls = new ArrayList<>(); |
| for (RangerPolicy.RangerPolicyItemAccess access: |
| policyItem.getAccesses()) { |
| if (access.getIsAllowed()) { |
| acls.add(Acl.allow(stringToAcl.get(access.getType()))); |
| } else { |
| acls.add(Acl.deny(stringToAcl.get(access.getType()))); |
| } |
| } |
| |
| for (String roleName: policyItem.getRoles()) { |
| policyBuilder.addRoleAcl(roleName, acls); |
| } |
| } |
| |
| // Add resources. |
| for (Map.Entry<String, RangerPolicy.RangerPolicyResource> resource: |
| rangerPolicy.getResources().entrySet()) { |
| String resourceType = resource.getKey(); |
| List<String> resourceNames = resource.getValue().getValues(); |
| switch (resourceType) { |
| case "volume": |
| policyBuilder.addVolumes(resourceNames); |
| break; |
| case "bucket": |
| policyBuilder.addBuckets(resourceNames); |
| break; |
| case "key": |
| policyBuilder.addKeys(resourceNames); |
| break; |
| default: |
| LOG.warn("Pulled Ranger policy with unknown resource type '{}' with" + |
| " names '{}'", resourceType, String.join(",", resourceNames)); |
| } |
| } |
| |
| policyBuilder.setName(rangerPolicy.getName()) |
| .setDescription(rangerPolicy.getDescription()) |
| .addLabels(rangerPolicy.getPolicyLabels()); |
| |
| return policyBuilder.build(); |
| } |
| |
| private RangerPolicy toRangerPolicy(Policy policy) { |
| RangerPolicy rangerPolicy = new RangerPolicy(); |
| rangerPolicy.setName(policy.getName()); |
| rangerPolicy.setService(rangerServiceName); |
| rangerPolicy.setPolicyLabels(new ArrayList<>(policy.getLabels())); |
| |
| // Add resources. |
| Map<String, RangerPolicy.RangerPolicyResource> resource = new HashMap<>(); |
| // Add volumes. |
| if (!policy.getVolumes().isEmpty()) { |
| RangerPolicy.RangerPolicyResource volumeResources = |
| new RangerPolicy.RangerPolicyResource(); |
| volumeResources.setValues(new ArrayList<>(policy.getVolumes())); |
| resource.put("volume", volumeResources); |
| } |
| // Add buckets. |
| if (!policy.getBuckets().isEmpty()) { |
| RangerPolicy.RangerPolicyResource bucketResources = |
| new RangerPolicy.RangerPolicyResource(); |
| bucketResources.setValues(new ArrayList<>(policy.getBuckets())); |
| resource.put("bucket", bucketResources); |
| } |
| // Add keys. |
| if (!policy.getKeys().isEmpty()) { |
| RangerPolicy.RangerPolicyResource keyResources = |
| new RangerPolicy.RangerPolicyResource(); |
| keyResources.setValues(new ArrayList<>(policy.getKeys())); |
| resource.put("key", keyResources); |
| } |
| rangerPolicy.setService(rangerServiceName); |
| rangerPolicy.setResources(resource); |
| if (policy.getDescription().isPresent()) { |
| rangerPolicy.setDescription(policy.getDescription().get()); |
| } |
| |
| // Add roles to the policy. |
| for (Map.Entry<String, Collection<Acl>> roleAcls: |
| policy.getRoleAcls().entrySet()) { |
| RangerPolicy.RangerPolicyItem item = new RangerPolicy.RangerPolicyItem(); |
| item.setRoles(Collections.singletonList(roleAcls.getKey())); |
| |
| for (Acl acl: roleAcls.getValue()) { |
| RangerPolicy.RangerPolicyItemAccess access = |
| new RangerPolicy.RangerPolicyItemAccess(); |
| access.setIsAllowed(acl.isAllowed()); |
| access.setType(aclToString.get(acl.getAclType())); |
| item.getAccesses().add(access); |
| } |
| |
| rangerPolicy.getPolicyItems().add(item); |
| } |
| |
| return rangerPolicy; |
| } |
| } |