You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ozone.apache.org by si...@apache.org on 2022/05/20 18:25:16 UTC
[ozone] branch HDDS-4944 updated: HDDS-5836. [Multi-Tenant] Use Ranger java client. (#3408)
This is an automated email from the ASF dual-hosted git repository.
siyao pushed a commit to branch HDDS-4944
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/HDDS-4944 by this push:
new 917b079a61 HDDS-5836. [Multi-Tenant] Use Ranger java client. (#3408)
917b079a61 is described below
commit 917b079a61696c54ddf0bd31b887417fc38ad9cc
Author: Aswin Shakil Balasubramanian <as...@gmail.com>
AuthorDate: Fri May 20 11:25:11 2022 -0700
HDDS-5836. [Multi-Tenant] Use Ranger java client. (#3408)
---
.../multitenant/MultiTenantAccessController.java | 213 -------
.../RangerRestMultiTenantAccessController.java | 636 ---------------------
hadoop-ozone/dist/src/main/license/jar-report.txt | 24 +
hadoop-ozone/ozone-manager/pom.xml | 39 ++
.../multitenant/MultiTenantAccessController.java | 403 +++++++++++++
.../RangerClientMultiTenantAccessController.java | 324 +++++++++++
.../InMemoryMultiTenantAccessController.java | 154 +++++
.../TestMultiTenantAccessController.java | 398 +++++++++++++
8 files changed, 1342 insertions(+), 849 deletions(-)
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/MultiTenantAccessController.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/MultiTenantAccessController.java
deleted file mode 100644
index 3b8975be45..0000000000
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/MultiTenantAccessController.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * 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 org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
-import org.apache.http.auth.BasicUserPrincipal;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Optional;
-
-/**
- * Defines the operations needed for multi-tenant access control.
- * Each implemented method should be atomic. A failure partway through any
- * one method call should not leave any state behind.
- */
-public interface MultiTenantAccessController {
- /**
- * This operation will fail if a policy with the same name already exists,
- * or a policy for the same set of resources already exists.
- *
- * Roles defined in this policy that do not already exist will be created.
- *
- * @return The unique ID to refer to this policy.
- */
- long createPolicy(Policy policy) throws Exception;
-
- void deletePolicy(long policyID) throws Exception;
-
- Policy getPolicy(long policyID) throws Exception;
-
- void updatePolicy(long policyID, Policy policy) throws Exception;
-
- Map<Long, Policy> getPolicies() throws Exception;
-
- /**
- * This operation will fail if a role with the same name already exists.
- *
- * @return The unique ID to refer to this role.
- */
- long createRole(Role role) throws Exception;
-
- void deleteRole(long roleID) throws Exception;
-
- Role getRole(long roleID) throws Exception;
-
- void updateRole(long roleID, Role role) throws Exception;
-
- Map<Long, Role> getRoles() throws Exception;
-
- /**
- * Define a role to be created.
- */
- class Role {
- private final String name;
- private final Collection<BasicUserPrincipal> users;
- private String description;
-
- public Role(String roleName) {
- this.name = roleName;
- this.users = new HashSet<>();
- }
-
- public String getName() {
- return name;
- }
-
- public void addUsers(BasicUserPrincipal... newUsers) {
- users.addAll(Arrays.asList(newUsers));
- }
-
- public boolean removeUsers(BasicUserPrincipal... newUsers) {
- return users.removeAll(Arrays.asList(newUsers));
- }
-
- public Collection<BasicUserPrincipal> getUsers() {
- return Collections.unmodifiableCollection(users);
- }
-
- public Optional<String> getDescription() {
- return Optional.ofNullable(description);
- }
-
- public void setDescription(String description) {
- this.description = description;
- }
- }
-
- /**
- * Define an acl.
- */
- class Acl {
- private final boolean isAllowed;
- private final IAccessAuthorizer.ACLType acl;
-
- private Acl(IAccessAuthorizer.ACLType acl, boolean isAllowed) {
- this.isAllowed = isAllowed;
- this.acl = acl;
- }
-
- public static Acl allow(IAccessAuthorizer.ACLType acl) {
- return new Acl(acl, true);
- }
-
- public static Acl deny(IAccessAuthorizer.ACLType acl) {
- return new Acl(acl, false);
- }
-
- public IAccessAuthorizer.ACLType getAclType() {
- return acl;
- }
-
- public boolean isAllowed() {
- return isAllowed;
- }
- }
-
- /**
- * Define a policy to be created.
- */
- class Policy {
- private final String name;
- private final Collection<String> volumes;
- private final Collection<String> buckets;
- private final Collection<String> keys;
- private String description;
- private final Map<String, Collection<Acl>> roleAcls;
- private boolean isEnabled;
-
- public Policy(String policyName, String... volumeName) {
- this.name = policyName;
- this.volumes = new ArrayList<>();
- this.volumes.addAll(Arrays.asList(volumeName));
- this.buckets = new ArrayList<>();
- this.keys = new ArrayList<>();
- this.roleAcls = new HashMap<>();
- this.isEnabled = true;
- }
-
- public void setEnabled(boolean enabled) {
- isEnabled = enabled;
- }
-
- public boolean isEnabled() {
- return isEnabled;
- }
-
- public Collection<String> getVolumes() {
- return Collections.unmodifiableCollection(volumes);
- }
-
- public Collection<String> getBuckets() {
- return Collections.unmodifiableCollection(buckets);
- }
-
- public Collection<String> getKeys() {
- return Collections.unmodifiableCollection(keys);
- }
-
- public void addVolumes(String... volumeNames) {
- volumes.addAll(Arrays.asList(volumeNames));
- }
-
- public void addBuckets(String... bucketNames) {
- buckets.addAll(Arrays.asList(bucketNames));
- }
-
- public void addKeys(String... keyNames) {
- keys.addAll(Arrays.asList(keyNames));
- }
-
- public String getName() {
- return name;
- }
-
- public Optional<String> getDescription() {
- return Optional.ofNullable(description);
- }
-
- public void addRoleAcls(String roleName, Acl... acl) {
- roleAcls.putIfAbsent(roleName, new ArrayList<>());
- roleAcls.get(roleName).addAll(Arrays.asList(acl));
- }
-
- public Map<String, Collection<Acl>> getRoleAcls() {
- return Collections.unmodifiableMap(roleAcls);
- }
-
- public void setDescription(String description) {
- this.description = description;
- }
- }
-}
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/RangerRestMultiTenantAccessController.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/RangerRestMultiTenantAccessController.java
deleted file mode 100644
index 7184ea301d..0000000000
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/RangerRestMultiTenantAccessController.java
+++ /dev/null
@@ -1,636 +0,0 @@
-/*
- * 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 com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonParser;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.hdds.conf.OzoneConfiguration;
-import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
-import org.apache.http.auth.BasicUserPrincipal;
-import org.apache.kerby.util.Base64;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.lang.reflect.Type;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.util.Collection;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_RANGER_HTTPS_ADMIN_API_PASSWD;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_RANGER_HTTPS_ADMIN_API_USER;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_HTTPS_ADDRESS_KEY;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_OM_CONNECTION_REQUEST_TIMEOUT;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_OM_CONNECTION_REQUEST_TIMEOUT_DEFAULT;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_OM_CONNECTION_TIMEOUT;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_OM_CONNECTION_TIMEOUT_DEFAULT;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_OM_IGNORE_SERVER_CERT;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_OM_IGNORE_SERVER_CERT_DEFAULT;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_SERVICE;
-
-/**
- * Access controller for multi-tenancy implemented using Ranger's REST API.
- * This class is for testing and is not intended for production use.
- */
-public class RangerRestMultiTenantAccessController
- implements MultiTenantAccessController {
-
- public static final String OZONE_OM_RANGER_ADMIN_POLICY_HTTP_ENDPOINT =
- "/service/public/v2/api/policy/";
- public static final String OZONE_OM_RANGER_ADMIN_ROLE_HTTP_ENDPOINT =
- "/service/public/v2/api/roles/";
-
- private static final Logger LOG = LoggerFactory
- .getLogger(RangerRestMultiTenantAccessController.class);
-
- private final OzoneConfiguration conf;
- private boolean ignoreServerCert = false;
- private int connectionTimeout;
- private int connectionRequestTimeout;
- private String authHeaderValue;
- private final String rangerHttpsAddress;
- private final Gson jsonConverter;
- private final String rangerService;
- private final Map<IAccessAuthorizer.ACLType, String> aclToString;
- private final Map<String, IAccessAuthorizer.ACLType> stringToAcl;
-
- public RangerRestMultiTenantAccessController(Configuration configuration)
- throws IOException {
- conf = new OzoneConfiguration(configuration);
- rangerHttpsAddress = conf.get(OZONE_RANGER_HTTPS_ADDRESS_KEY);
- rangerService = conf.get(OZONE_RANGER_SERVICE);
-
- GsonBuilder gsonBuilder = new GsonBuilder();
- gsonBuilder.registerTypeAdapter(Policy.class, policySerializer);
- gsonBuilder.registerTypeAdapter(Policy.class, policyDeserializer);
- gsonBuilder.registerTypeAdapter(Role.class, roleSerializer);
- gsonBuilder.registerTypeAdapter(Role.class, roleDeserializer);
- gsonBuilder.registerTypeAdapter(BasicUserPrincipal.class, userSerializer);
- jsonConverter = gsonBuilder.create();
-
- aclToString = new EnumMap<>(IAccessAuthorizer.ACLType.class);
- stringToAcl = new HashMap<>();
- fillRangerAclStrings();
- initializeRangerConnection();
- }
-
- private void fillRangerAclStrings() {
- aclToString.put(IAccessAuthorizer.ACLType.ALL, "all");
- aclToString.put(IAccessAuthorizer.ACLType.LIST, "list");
- aclToString.put(IAccessAuthorizer.ACLType.READ, "read");
- aclToString.put(IAccessAuthorizer.ACLType.WRITE, "write");
- aclToString.put(IAccessAuthorizer.ACLType.CREATE, "create");
- aclToString.put(IAccessAuthorizer.ACLType.DELETE, "delete");
- aclToString.put(IAccessAuthorizer.ACLType.READ_ACL, "read_acl");
- aclToString.put(IAccessAuthorizer.ACLType.WRITE_ACL, "write_acl");
- aclToString.put(IAccessAuthorizer.ACLType.NONE, "");
-
- stringToAcl.put("all", IAccessAuthorizer.ACLType.ALL);
- stringToAcl.put("list", IAccessAuthorizer.ACLType.LIST);
- stringToAcl.put("read", IAccessAuthorizer.ACLType.READ);
- stringToAcl.put("write", IAccessAuthorizer.ACLType.WRITE);
- stringToAcl.put("create", IAccessAuthorizer.ACLType.CREATE);
- stringToAcl.put("delete", IAccessAuthorizer.ACLType.DELETE);
- stringToAcl.put("read_acl", IAccessAuthorizer.ACLType.READ_ACL);
- stringToAcl.put("write_acl", IAccessAuthorizer.ACLType.WRITE_ACL);
- stringToAcl.put("", IAccessAuthorizer.ACLType.NONE);
- }
-
- private void initializeRangerConnection() {
- setupRangerConnectionConfig();
- if (ignoreServerCert) {
- setupRangerIgnoreServerCertificate();
- }
- setupRangerConnectionAuthHeader();
- }
-
- private void setupRangerConnectionConfig() {
- connectionTimeout = (int) conf.getTimeDuration(
- OZONE_RANGER_OM_CONNECTION_TIMEOUT,
- conf.get(
- OZONE_RANGER_OM_CONNECTION_TIMEOUT,
- OZONE_RANGER_OM_CONNECTION_TIMEOUT_DEFAULT),
- TimeUnit.MILLISECONDS);
- connectionRequestTimeout = (int)conf.getTimeDuration(
- OZONE_RANGER_OM_CONNECTION_REQUEST_TIMEOUT,
- conf.get(
- OZONE_RANGER_OM_CONNECTION_REQUEST_TIMEOUT,
- OZONE_RANGER_OM_CONNECTION_REQUEST_TIMEOUT_DEFAULT),
- TimeUnit.MILLISECONDS
- );
- ignoreServerCert = conf.getBoolean(
- OZONE_RANGER_OM_IGNORE_SERVER_CERT,
- OZONE_RANGER_OM_IGNORE_SERVER_CERT_DEFAULT);
- }
-
- private void setupRangerIgnoreServerCertificate() {
- // Create a trust manager that does not validate certificate chains
- TrustManager[] trustAllCerts = new TrustManager[]{
- new X509TrustManager() {
- public java.security.cert.X509Certificate[] getAcceptedIssuers() {
- return null;
- }
- public void checkClientTrusted(
- java.security.cert.X509Certificate[] certs, String authType) {
- }
- public void checkServerTrusted(
- java.security.cert.X509Certificate[] certs, String authType) {
- }
- }
- };
-
- try {
- SSLContext sc = SSLContext.getInstance("SSL");
- sc.init(null, trustAllCerts, new java.security.SecureRandom());
- HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
- } catch (Exception e) {
- LOG.info("Setting DefaultSSLSocketFactory failed.");
- }
- }
-
- private void setupRangerConnectionAuthHeader() {
- String userName = conf.get(OZONE_OM_RANGER_HTTPS_ADMIN_API_USER);
- String passwd = conf.get(OZONE_OM_RANGER_HTTPS_ADMIN_API_PASSWD);
- String auth = userName + ":" + passwd;
- byte[] encodedAuth =
- Base64.encodeBase64(auth.getBytes(StandardCharsets.UTF_8));
- authHeaderValue = "Basic " +
- new String(encodedAuth, StandardCharsets.UTF_8);
- }
-
-
- @Override
- public long createPolicy(Policy policy) throws IOException {
- String rangerAdminUrl =
- rangerHttpsAddress + OZONE_OM_RANGER_ADMIN_POLICY_HTTP_ENDPOINT;
- HttpsURLConnection conn = makeHttpsPostCall(rangerAdminUrl,
- jsonConverter.toJsonTree(policy).getAsJsonObject());
- if (!successfulResponseCode(conn.getResponseCode())) {
- throw new IOException(String.format("Failed to create policy %s. " +
- "Http response code: %d", policy.getName(), conn.getResponseCode()));
- }
- String policyInfo = getResponseData(conn);
- long policyID;
- JsonObject jObject = new JsonParser().parse(policyInfo).getAsJsonObject();
- policyID = jObject.get("id").getAsLong();
- return policyID;
- }
-
- @Override
- public void deletePolicy(long policyID) throws IOException {
- String rangerAdminUrl =
- rangerHttpsAddress + OZONE_OM_RANGER_ADMIN_POLICY_HTTP_ENDPOINT
- + policyID;
- HttpsURLConnection conn = makeHttpsDeleteCall(rangerAdminUrl);
- if (!successfulResponseCode(conn.getResponseCode())) {
- throw new IOException(String.format("Failed to delete policy %d. " +
- "Http response code: %d", policyID, conn.getResponseCode()));
- }
- }
-
- @Override
- public Map<Long, Policy> getPolicies() throws Exception {
- // This API gets all policies for all services. The
- // /public/v2/api/policies/{serviceDefName}/for-resource endpoint is
- // supposed to get policies for only a specified service, but it does not
- // seem to work. This implementation should be ok for testing purposes as
- // this class is intended.
- String rangerAdminUrl =
- rangerHttpsAddress + OZONE_OM_RANGER_ADMIN_POLICY_HTTP_ENDPOINT;
- HttpsURLConnection conn = makeHttpsGetCall(rangerAdminUrl);
- if (!successfulResponseCode(conn.getResponseCode())) {
- throw new IOException(String.format("Failed to get all policies. " +
- "Http response code: %d", conn.getResponseCode()));
- }
- String allPoliciesString = getResponseData(conn);
- // Filter out policies not for Ozone service.
- JsonArray jsonPoliciesArray = new JsonParser().parse(allPoliciesString)
- .getAsJsonArray();
- Map<Long, Policy> policies = new HashMap<>();
- for (JsonElement jsonPolicy: jsonPoliciesArray) {
- JsonObject jsonPolicyObject = jsonPolicy.getAsJsonObject();
- String service = jsonPolicyObject.get("service").getAsString();
- if (service.equals(rangerService)) {
- long id = jsonPolicyObject.get("id").getAsLong();
- policies.put(id, jsonConverter.fromJson(jsonPolicyObject,
- Policy.class));
- }
- }
-
- return policies;
- }
-
- @Override
- public Policy getPolicy(long policyID) throws IOException {
- String rangerAdminUrl = rangerHttpsAddress +
- OZONE_OM_RANGER_ADMIN_POLICY_HTTP_ENDPOINT + policyID;
-
- HttpsURLConnection conn = makeHttpsGetCall(rangerAdminUrl);
- if (!successfulResponseCode(conn.getResponseCode())) {
- throw new IOException(String.format("Failed to get policy %d. " +
- "Http response code: %d", policyID, conn.getResponseCode()));
- }
- String policyInfo = getResponseData(conn);
- return jsonConverter.fromJson(policyInfo, Policy.class);
- }
-
- @Override
- public void updatePolicy(long policyID, Policy policy) throws IOException {
- String rangerAdminUrl = rangerHttpsAddress +
- OZONE_OM_RANGER_ADMIN_POLICY_HTTP_ENDPOINT + policyID;
-
- HttpsURLConnection conn = makeHttpsPutCall(rangerAdminUrl,
- jsonConverter.toJsonTree(policy));
- if (!successfulResponseCode(conn.getResponseCode())) {
- throw new IOException(String.format("Failed to update policy %d. " +
- "Http response code: %d", policyID, conn.getResponseCode()));
- }
- }
-
- @Override
- public long createRole(Role role) throws IOException {
- String rangerAdminUrl =
- rangerHttpsAddress + OZONE_OM_RANGER_ADMIN_ROLE_HTTP_ENDPOINT;
-
- HttpsURLConnection conn = makeHttpsPostCall(rangerAdminUrl,
- jsonConverter.toJsonTree(role).getAsJsonObject());
- if (!successfulResponseCode(conn.getResponseCode())) {
- throw new IOException(String.format("Failed to create role %s. " +
- "Http response code: %d", role.getName(), conn.getResponseCode()));
- }
- String responseString = getResponseData(conn);
- JsonObject jObject = new JsonParser().parse(responseString)
- .getAsJsonObject();
- return jObject.get("id").getAsLong();
- }
-
- @Override
- public void deleteRole(long roleID) throws IOException {
- String rangerAdminUrl =
- rangerHttpsAddress + OZONE_OM_RANGER_ADMIN_POLICY_HTTP_ENDPOINT
- + roleID;
- HttpsURLConnection conn = makeHttpsDeleteCall(rangerAdminUrl);
- if (!successfulResponseCode(conn.getResponseCode())) {
- throw new IOException(String.format("Failed to delete role %d. " +
- "Http response code: %d", roleID, conn.getResponseCode()));
- }
- }
-
- @Override
- public Map<Long, Role> getRoles() throws Exception {
- String rangerAdminUrl =
- rangerHttpsAddress + OZONE_OM_RANGER_ADMIN_ROLE_HTTP_ENDPOINT;
- HttpsURLConnection conn = makeHttpsGetCall(rangerAdminUrl);
- if (!successfulResponseCode(conn.getResponseCode())) {
- throw new IOException(String.format("Failed to get all roles. " +
- "Http response code: %d", conn.getResponseCode()));
- }
-
- String allRolesString = getResponseData(conn);
- JsonArray rolesArrayJson =
- new JsonParser().parse(allRolesString).getAsJsonArray();
- Map<Long, Role> roles = new HashMap<>();
- for (JsonElement roleJson: rolesArrayJson) {
- long id = roleJson.getAsJsonObject().get("id").getAsLong();
- roles.put(id, jsonConverter.fromJson(roleJson, Role.class));
- }
-
- return roles;
- }
-
- @Override
- public Role getRole(long roleID) throws IOException {
- String rangerAdminUrl =
- rangerHttpsAddress + OZONE_OM_RANGER_ADMIN_ROLE_HTTP_ENDPOINT + roleID;
-
- HttpsURLConnection conn = makeHttpsGetCall(rangerAdminUrl);
- if (!successfulResponseCode(conn.getResponseCode())) {
- throw new IOException(String.format("Failed to get role %d. " +
- "Http response code: %d", roleID, conn.getResponseCode()));
- }
- String roleInfo = getResponseData(conn);
- return jsonConverter.fromJson(roleInfo, Role.class);
- }
-
- @Override
- public void updateRole(long roleID, Role role) throws IOException {
- String rangerAdminUrl =
- rangerHttpsAddress + OZONE_OM_RANGER_ADMIN_ROLE_HTTP_ENDPOINT + roleID;
-
- HttpsURLConnection conn = makeHttpsPutCall(rangerAdminUrl,
- jsonConverter.toJsonTree(role));
- if (!successfulResponseCode(conn.getResponseCode())) {
- throw new IOException(String.format("Failed to update role %d. " +
- "Http response code: %d", roleID, conn.getResponseCode()));
- }
- }
-
- private HttpsURLConnection makeHttpsPutCall(String url, JsonElement content)
- throws IOException {
- HttpsURLConnection connection = makeBaseHttpsURLConnection(url);
- connection.setRequestMethod("PUT");
- return addJsonContentToConnection(connection, content);
- }
-
- private HttpsURLConnection makeHttpsPostCall(String url, JsonElement content)
- throws IOException {
- HttpsURLConnection connection = makeBaseHttpsURLConnection(url);
- connection.setRequestMethod("POST");
- return addJsonContentToConnection(connection, content);
- }
-
- private HttpsURLConnection addJsonContentToConnection(
- HttpsURLConnection connection, JsonElement content) throws IOException {
- connection.setDoOutput(true);
- connection.setRequestProperty("Content-Type", "application/json;");
- try (OutputStream os = connection.getOutputStream()) {
- byte[] input = content.toString().getBytes(StandardCharsets.UTF_8);
- os.write(input, 0, input.length);
- os.flush();
- }
-
- return connection;
- }
-
- private HttpsURLConnection makeHttpsGetCall(String urlString)
- throws IOException {
- HttpsURLConnection connection = makeBaseHttpsURLConnection(urlString);
- connection.setRequestMethod("GET");
- return connection;
- }
-
- private HttpsURLConnection makeHttpsDeleteCall(String urlString)
- throws IOException {
- HttpsURLConnection connection = makeBaseHttpsURLConnection(urlString);
- connection.setRequestMethod("DELETE");
- return connection;
- }
-
- private HttpsURLConnection makeBaseHttpsURLConnection(String urlString)
- throws IOException {
- URL url = new URL(urlString);
- HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();
- urlConnection.setConnectTimeout(connectionTimeout);
- urlConnection.setReadTimeout(connectionRequestTimeout);
- urlConnection.setRequestProperty("Accept", "application/json");
- urlConnection.setRequestProperty("Authorization", authHeaderValue);
-
- return urlConnection;
- }
-
- private String getResponseData(HttpsURLConnection urlConnection)
- throws IOException {
- StringBuilder response = new StringBuilder();
- try (BufferedReader br = new BufferedReader(
- new InputStreamReader(
- urlConnection.getInputStream(), StandardCharsets.UTF_8))) {
- String responseLine;
- while ((responseLine = br.readLine()) != null) {
- response.append(responseLine.trim());
- }
- }
- return response.toString();
- }
-
- private boolean successfulResponseCode(long responseCode) {
- return responseCode >= 200 && responseCode < 300;
- }
-
- /// SERIALIZATION ///
-
- private final JsonDeserializer<Policy> policyDeserializer =
- new JsonDeserializer<Policy>() {
- @Override public Policy deserialize(JsonElement jsonElement, Type type,
- JsonDeserializationContext jsonDeserializationContext)
- throws JsonParseException {
- JsonObject policyJson = jsonElement.getAsJsonObject();
- String name = policyJson.get("name").getAsString();
- Policy policy = new Policy(name);
- if (policyJson.has("description")) {
- policy.setDescription(policyJson.get("description").getAsString());
- }
- policy.setEnabled(policyJson.get("isEnabled").getAsBoolean());
-
- // Read volume, bucket, keys from json.
- JsonObject resourcesJson =
- policyJson.get("resources").getAsJsonObject();
- // All Ozone Ranger policies specify at least a volume.
- JsonObject jsonVolumeResource =
- resourcesJson.get("volume").getAsJsonObject();
- JsonArray volumes = jsonVolumeResource.get("values").getAsJsonArray();
- volumes.forEach(vol -> policy.addVolumes(vol.getAsString()));
-
- if (resourcesJson.has("bucket")) {
- JsonObject jsonBucketResource =
- resourcesJson.get("bucket").getAsJsonObject();
- JsonArray buckets =
- jsonBucketResource.get("values").getAsJsonArray();
- buckets.forEach(bucket -> policy.addBuckets(bucket.getAsString()));
- }
-
- if (resourcesJson.has("key")) {
- JsonObject jsonKeysResource =
- resourcesJson.get("key").getAsJsonObject();
- JsonArray keys = jsonKeysResource.get("values").getAsJsonArray();
- keys.forEach(key -> policy.addKeys(key.getAsString()));
- }
-
- // Read Roles and their ACLs.
- JsonArray policyItemsJson = policyJson.getAsJsonArray("policyItems");
- for (JsonElement policyItemElement : policyItemsJson) {
- JsonObject policyItemJson = policyItemElement.getAsJsonObject();
- JsonArray jsonRoles = policyItemJson.getAsJsonArray("roles");
- JsonArray jsonAclArray = policyItemJson.getAsJsonArray("accesses");
-
- for (JsonElement jsonAclElem : jsonAclArray) {
- JsonObject jsonAcl = jsonAclElem.getAsJsonObject();
- String aclType = jsonAcl.get("type").getAsString();
- Acl acl;
- if (jsonAcl.get("isAllowed").getAsBoolean()) {
- acl = Acl.allow(stringToAcl.get(aclType));
- } else {
- acl = Acl.deny(stringToAcl.get(aclType));
- }
-
- for (JsonElement roleNameJson : jsonRoles) {
- policy.addRoleAcls(roleNameJson.getAsString(), acl);
- }
- }
- }
-
- return policy;
- }
- };
-
- private final JsonDeserializer<Role> roleDeserializer =
- new JsonDeserializer<Role>() {
- @Override public Role deserialize(JsonElement jsonElement, Type type,
- JsonDeserializationContext jsonDeserializationContext)
- throws JsonParseException {
- JsonObject roleJson = jsonElement.getAsJsonObject();
- String name = roleJson.get("name").getAsString();
- Role role = new Role(name);
- if (roleJson.has("description")) {
- role.setDescription(roleJson.get("description").getAsString());
- }
- for (JsonElement jsonUser : roleJson.get("users").getAsJsonArray()) {
- String userName =
- jsonUser.getAsJsonObject().get("name").getAsString();
- role.addUsers(new BasicUserPrincipal(userName));
- }
-
- return role;
- }
- };
-
- private final JsonSerializer<Policy> policySerializer =
- new JsonSerializer<Policy>() {
- @Override public JsonElement serialize(Policy javaPolicy,
- Type typeOfSrc, JsonSerializationContext context) {
- JsonObject jsonPolicy = new JsonObject();
- jsonPolicy.addProperty("name", javaPolicy.getName());
- jsonPolicy.addProperty("service", rangerService);
- jsonPolicy.addProperty("isEnabled", javaPolicy.isEnabled());
- if (javaPolicy.getDescription().isPresent()) {
- jsonPolicy.addProperty("description",
- javaPolicy.getDescription().get());
- }
-
- // All resources under this policy are added to this object.
- JsonObject jsonResources = new JsonObject();
-
- // Add volumes. Ranger requires at least one volume to be specified.
- JsonArray jsonVolumeNameArray = new JsonArray();
- for (String volumeName : javaPolicy.getVolumes()) {
- jsonVolumeNameArray.add(new JsonPrimitive(volumeName));
- }
- JsonObject jsonVolumeResource = new JsonObject();
- jsonVolumeResource.add("values", jsonVolumeNameArray);
- jsonVolumeResource.addProperty("isRecursive", false);
- jsonVolumeResource.addProperty("isExcludes", false);
- jsonResources.add("volume", jsonVolumeResource);
-
- // Add buckets.
- JsonArray jsonBucketNameArray = new JsonArray();
- for (String bucketName : javaPolicy.getBuckets()) {
- jsonBucketNameArray.add(new JsonPrimitive(bucketName));
- }
-
- if (jsonBucketNameArray.size() > 0) {
- JsonObject jsonBucketResource = new JsonObject();
- jsonBucketResource.add("values", jsonBucketNameArray);
- jsonBucketResource.addProperty("isRecursive", false);
- jsonBucketResource.addProperty("isExcludes", false);
- jsonResources.add("bucket", jsonBucketResource);
- }
-
- // Add keys.
- JsonArray jsonKeyNameArray = new JsonArray();
- for (String keyName : javaPolicy.getKeys()) {
- jsonKeyNameArray.add(new JsonPrimitive(keyName));
- }
- if (jsonKeyNameArray.size() > 0) {
- JsonObject jsonKeyResource = new JsonObject();
- jsonKeyResource.add("values", jsonKeyNameArray);
- jsonKeyResource.addProperty("isRecursive", false);
- jsonKeyResource.addProperty("isExcludes", false);
- jsonResources.add("key", jsonKeyResource);
- }
-
- jsonPolicy.add("resources", jsonResources);
-
- // Add roles and their acls to the policy.
- JsonArray jsonPolicyItemArray = new JsonArray();
-
- // Make a new policy item for each role in the map.
- Map<String, Collection<Acl>> roleAcls = javaPolicy.getRoleAcls();
- for (Map.Entry<String, Collection<Acl>> entry : roleAcls.entrySet()) {
- // Add role to the policy item.
- String roleName = entry.getKey();
- JsonObject jsonPolicyItem = new JsonObject();
- JsonArray jsonRoles = new JsonArray();
- jsonRoles.add(new JsonPrimitive(roleName));
- jsonPolicyItem.add("roles", jsonRoles);
-
- // Add acls to the policy item.
- JsonArray jsonAclArray = new JsonArray();
- for (Acl acl : entry.getValue()) {
- JsonObject jsonAcl = new JsonObject();
- jsonAcl.addProperty("type", aclToString.get(acl.getAclType()));
- jsonAcl.addProperty("isAllowed", acl.isAllowed());
- jsonAclArray.add(jsonAcl);
- jsonPolicyItem.add("accesses", jsonAclArray);
- }
- jsonPolicyItemArray.add(jsonPolicyItem);
- }
- jsonPolicy.add("policyItems", jsonPolicyItemArray);
-
- return jsonPolicy;
- }
- };
-
- private final JsonSerializer<Role> roleSerializer =
- new JsonSerializer<Role>() {
- @Override public JsonElement serialize(Role javaRole, Type typeOfSrc,
- JsonSerializationContext context) {
- JsonObject jsonRole = new JsonObject();
- jsonRole.addProperty("name", javaRole.getName());
-
- JsonArray jsonUserArray = new JsonArray();
- for (BasicUserPrincipal javaUser : javaRole.getUsers()) {
- jsonUserArray.add(jsonConverter.toJsonTree(javaUser));
- }
-
- jsonRole.add("users", jsonUserArray);
- return jsonRole;
- }
- };
-
- private final JsonSerializer<BasicUserPrincipal> userSerializer =
- new JsonSerializer<BasicUserPrincipal>() {
- @Override public JsonElement serialize(BasicUserPrincipal user,
- Type typeOfSrc, JsonSerializationContext context) {
- JsonObject jsonMember = new JsonObject();
- jsonMember.addProperty("name", user.getName());
- jsonMember.addProperty("isAdmin", false);
- return jsonMember;
- }
- };
-}
diff --git a/hadoop-ozone/dist/src/main/license/jar-report.txt b/hadoop-ozone/dist/src/main/license/jar-report.txt
index da8350d4b7..7f68799785 100644
--- a/hadoop-ozone/dist/src/main/license/jar-report.txt
+++ b/hadoop-ozone/dist/src/main/license/jar-report.txt
@@ -8,6 +8,7 @@ share/ozone/lib/aopalliance-repackaged.jar
share/ozone/lib/asm.jar
share/ozone/lib/aspectjrt.jar
share/ozone/lib/aspectjweaver.jar
+share/ozone/lib/aws-java-sdk-bundle.jar
share/ozone/lib/aws-java-sdk-core.jar
share/ozone/lib/aws-java-sdk-kms.jar
share/ozone/lib/aws-java-sdk-s3.jar
@@ -26,6 +27,7 @@ share/ozone/lib/commons-daemon.jar
share/ozone/lib/commons-digester.jar
share/ozone/lib/commons-io.jar
share/ozone/lib/commons-lang3.jar
+share/ozone/lib/commons-lang.jar
share/ozone/lib/commons-logging.jar
share/ozone/lib/commons-math3.jar
share/ozone/lib/commons-net.jar
@@ -38,6 +40,7 @@ share/ozone/lib/dnsjava.jar
share/ozone/lib/error_prone_annotations.jar
share/ozone/lib/failureaccess.jar
share/ozone/lib/FastInfoset.jar
+share/ozone/lib/gethostname4j.jar
share/ozone/lib/grpc-api.jar
share/ozone/lib/grpc-context.jar
share/ozone/lib/grpc-core.jar
@@ -72,21 +75,29 @@ share/ozone/lib/hdds-interface-server.jar
share/ozone/lib/hdds-server-framework.jar
share/ozone/lib/hdds-server-scm.jar
share/ozone/lib/hdds-tools.jar
+share/ozone/lib/hive-storage-api.jar
share/ozone/lib/hk2-api.jar
share/ozone/lib/hk2-locator.jar
share/ozone/lib/hk2-utils.jar
+share/ozone/lib/hppc.jar
share/ozone/lib/htrace-core4-incubating.jar
+share/ozone/lib/httpasyncclient.jar
share/ozone/lib/httpclient.jar
share/ozone/lib/httpcore.jar
+share/ozone/lib/httpcore-nio.jar
+share/ozone/lib/httpmime.jar
share/ozone/lib/ion-java.jar
share/ozone/lib/istack-commons-runtime.jar
share/ozone/lib/j2objc-annotations.jar
share/ozone/lib/jackson-annotations.jar
+share/ozone/lib/jackson-core-asl.jar
share/ozone/lib/jackson-core.jar
share/ozone/lib/jackson-databind.jar
share/ozone/lib/jackson-dataformat-cbor.jar
share/ozone/lib/jackson-dataformat-xml.jar
share/ozone/lib/jackson-datatype-jsr310.jar
+share/ozone/lib/jackson-jaxrs.jar
+share/ozone/lib/jackson-mapper-asl.jar
share/ozone/lib/jackson-module-jaxb-annotations.jar
share/ozone/lib/jaeger-client.jar
share/ozone/lib/jaeger-core.jar
@@ -110,6 +121,7 @@ share/ozone/lib/jaxb-runtime.jar
share/ozone/lib/jcip-annotations.jar
share/ozone/lib/jersey-cdi1x.jar
share/ozone/lib/jersey-client.jar
+share/ozone/lib/jersey-client.jar
share/ozone/lib/jersey-common.jar
share/ozone/lib/jersey-container-servlet-core.jar
share/ozone/lib/jersey-container-servlet.jar
@@ -121,6 +133,7 @@ share/ozone/lib/jersey-media-json-jackson.jar
share/ozone/lib/jersey-server.jar
share/ozone/lib/jersey-server.jar
share/ozone/lib/jersey-servlet.jar
+share/ozone/lib/jetty-client.jar
share/ozone/lib/jetty-http.jar
share/ozone/lib/jetty-io.jar
share/ozone/lib/jetty-security.jar
@@ -131,6 +144,8 @@ share/ozone/lib/jetty-util.jar
share/ozone/lib/jetty-webapp.jar
share/ozone/lib/jetty-xml.jar
share/ozone/lib/jmespath-java.jar
+share/ozone/lib/jna.jar
+share/ozone/lib/jna-platform.jar
share/ozone/lib/joda-time.jar
share/ozone/lib/jooq-codegen.jar
share/ozone/lib/jooq.jar
@@ -140,6 +155,7 @@ share/ozone/lib/json-smart.jar
share/ozone/lib/jsp-api.jar
share/ozone/lib/jsr305.jar
share/ozone/lib/jsr311-api.jar
+share/ozone/lib/kafka-clients.jar
share/ozone/lib/kerb-admin.jar
share/ozone/lib/kerb-client.jar
share/ozone/lib/kerb-common.jar
@@ -182,6 +198,7 @@ share/ozone/lib/opentracing-api.jar
share/ozone/lib/opentracing-noop.jar
share/ozone/lib/opentracing-tracerresolver.jar
share/ozone/lib/opentracing-util.jar
+share/ozone/lib/orc-core.jar
share/ozone/lib/osgi-resource-locator.jar
share/ozone/lib/ozone-annotation-processing.jar
share/ozone/lib/ozone-client.jar
@@ -206,6 +223,11 @@ share/ozone/lib/protobuf-java.jar
share/ozone/lib/protobuf-java.jar
share/ozone/lib/protobuf-java-util.jar
share/ozone/lib/proto-google-common-protos.jar
+share/ozone/lib/ranger-intg.jar
+share/ozone/lib/ranger-plugin-classloader.jar
+share/ozone/lib/ranger-plugins-audit.jar
+share/ozone/lib/ranger-plugins-common.jar
+share/ozone/lib/ranger-plugins-cred.jar
share/ozone/lib/ratis-client.jar
share/ozone/lib/ratis-common.jar
share/ozone/lib/ratis-grpc.jar
@@ -226,6 +248,7 @@ share/ozone/lib/slf4j-api.jar
share/ozone/lib/slf4j-log4j12.jar
share/ozone/lib/snakeyaml.jar
share/ozone/lib/snappy-java.jar
+share/ozone/lib/solr-solrj.jar
share/ozone/lib/spring-beans.RELEASE.jar
share/ozone/lib/spring-core.RELEASE.jar
share/ozone/lib/spring-jcl.RELEASE.jar
@@ -238,3 +261,4 @@ share/ozone/lib/token-provider.jar
share/ozone/lib/txw2.jar
share/ozone/lib/weld-servlet.Final.jar
share/ozone/lib/woodstox-core.jar
+share/ozone/lib/zstd-jni.jar
diff --git a/hadoop-ozone/ozone-manager/pom.xml b/hadoop-ozone/ozone-manager/pom.xml
index 5e42414ed7..10369fc680 100644
--- a/hadoop-ozone/ozone-manager/pom.xml
+++ b/hadoop-ozone/ozone-manager/pom.xml
@@ -144,6 +144,45 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd">
<version>0.9.11</version>
</dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-client</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.ranger</groupId>
+ <artifactId>ranger-intg</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ <scope>compile</scope>
+ <!-- Workaround to prevent slf4j binding conflicts until ranger-intg is
+ fixed to not introduce this to the classpath -->
+ <exclusions>
+ <exclusion>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-bundle</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.lucene</groupId>
+ <artifactId>*</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.elasticsearch</groupId>
+ <artifactId>*</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.elasticsearch.client</groupId>
+ <artifactId>*</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.elasticsearch.plugin</groupId>
+ <artifactId>*</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
</dependencies>
<build>
<plugins>
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/multitenant/MultiTenantAccessController.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/multitenant/MultiTenantAccessController.java
new file mode 100644
index 0000000000..3d50108f78
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/multitenant/MultiTenantAccessController.java
@@ -0,0 +1,403 @@
+/*
+ * 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 org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
+import org.apache.http.auth.BasicUserPrincipal;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Defines the operations needed for multi-tenant access control.
+ */
+public interface MultiTenantAccessController {
+ /**
+ * This operation will fail if a policy with the same name already exists,
+ * or a policy for the same set of resources already exists.
+ *
+ * Roles defined in this policy that do not already exist will be created.
+ */
+ void createPolicy(Policy policy) throws Exception;
+
+ Policy getPolicy(String policyName) throws Exception;
+
+ List<Policy> getLabeledPolicies(String label) throws Exception;
+
+ void updatePolicy(Policy policy) throws Exception;
+
+ void deletePolicy(String policyName) throws Exception;
+
+ /**
+ * This operation will fail if a role with the same name already exists.
+ */
+ void createRole(Role role) throws Exception;
+
+ Role getRole(String roleName) throws Exception;
+
+ /**
+ * Replaces the role given by {@code roleID} with the contents of {@code
+ * role}. If {@code roleID} does not correspond to a role, an exception is
+ * thrown.
+ *
+ * The roleID of a given role can be retrieved from the {@code getRole}
+ * method.
+ */
+ void updateRole(long roleID, Role role) throws Exception;
+
+ void deleteRole(String roleName) throws Exception;
+
+ long getRangerServiceVersion() throws Exception;
+
+ static Map<IAccessAuthorizer.ACLType, String> getRangerAclStrings() {
+ Map<IAccessAuthorizer.ACLType, String> rangerAclStrings =
+ new EnumMap<>(IAccessAuthorizer.ACLType.class);
+ rangerAclStrings.put(IAccessAuthorizer.ACLType.ALL, "all");
+ rangerAclStrings.put(IAccessAuthorizer.ACLType.LIST, "list");
+ rangerAclStrings.put(IAccessAuthorizer.ACLType.READ, "read");
+ rangerAclStrings.put(IAccessAuthorizer.ACLType.WRITE, "write");
+ rangerAclStrings.put(IAccessAuthorizer.ACLType.CREATE, "create");
+ rangerAclStrings.put(IAccessAuthorizer.ACLType.DELETE, "delete");
+ rangerAclStrings.put(IAccessAuthorizer.ACLType.READ_ACL, "read_acl");
+ rangerAclStrings.put(IAccessAuthorizer.ACLType.WRITE_ACL, "write_acl");
+
+ return rangerAclStrings;
+ }
+
+ /**
+ * Define an acl.
+ */
+ class Acl {
+ private final boolean isAllowed;
+ private final IAccessAuthorizer.ACLType acl;
+
+ private Acl(IAccessAuthorizer.ACLType acl, boolean isAllowed) {
+ this.isAllowed = isAllowed;
+ this.acl = acl;
+ }
+
+ public static Acl allow(IAccessAuthorizer.ACLType acl) {
+ return new Acl(acl, true);
+ }
+
+ public static Acl deny(IAccessAuthorizer.ACLType acl) {
+ return new Acl(acl, false);
+ }
+
+ public IAccessAuthorizer.ACLType getAclType() {
+ return acl;
+ }
+
+ public boolean isAllowed() {
+ return isAllowed;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(acl);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+ Acl otherAcl = (Acl) other;
+ return isAllowed() == otherAcl.isAllowed() && acl == otherAcl.acl;
+ }
+ }
+
+ /**
+ * Define a role to be created.
+ */
+ class Role {
+ private final String name;
+ private final Set<BasicUserPrincipal> users;
+ private final String description;
+ private final Long roleID;
+
+ private Role(Builder builder) {
+ name = builder.name;
+ users = builder.users;
+ description = builder.description;
+ roleID = builder.roleID;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Set<BasicUserPrincipal> getUsers() {
+ return users;
+ }
+
+ public Optional<String> getDescription() {
+ return Optional.ofNullable(description);
+ }
+
+ public Optional<Long> getRoleID() {
+ return Optional.ofNullable(roleID);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+ Role role = (Role) other;
+ // If one role does not have the ID set, still consider them equal.
+ // Role ID may not be set if the policy is being sent to Ranger for
+ // creation, but will be set if the same policy is retrieved from Ranger.
+ boolean roleIDsMatch = true;
+ if (getRoleID().isPresent() && role.getRoleID().isPresent()) {
+ roleIDsMatch = getRoleID().equals(role.getRoleID());
+ }
+ return Objects.equals(getName(), role.getName()) &&
+ Objects.equals(getUsers(), role.getUsers()) &&
+ Objects.equals(getDescription(), role.getDescription()) &&
+ roleIDsMatch;
+ }
+
+ /**
+ * Builder class for a role.
+ */
+ public static final class Builder {
+ private String name;
+ private final Set<BasicUserPrincipal> users;
+ private String description;
+ private Long roleID;
+
+ public Builder() {
+ this.users = new HashSet<>();
+ }
+
+ public Builder(Role other) {
+ this.name = other.getName();
+ this.users = new HashSet<>(other.getUsers());
+ other.getDescription().ifPresent(desc -> this.description = desc);
+ other.getRoleID().ifPresent(id -> this.roleID = id);
+ }
+
+ public Builder setName(String roleName) {
+ this.name = roleName;
+ return this;
+ }
+
+ public Builder addUser(BasicUserPrincipal user) {
+ this.users.add(user);
+ return this;
+ }
+
+ public Builder addUsers(Collection<BasicUserPrincipal> roleUsers) {
+ this.users.addAll(roleUsers);
+ return this;
+ }
+
+ public Builder setDescription(String roleDescription) {
+ this.description = roleDescription;
+ return this;
+ }
+
+ public Builder setID(long roleId) {
+ this.roleID = roleId;
+ return this;
+ }
+
+ public Role build() {
+ return new Role(this);
+ }
+ }
+ }
+
+ /**
+ * Define a policy to be created.
+ */
+ class Policy {
+ private final String name;
+ private final Set<String> volumes;
+ private final Set<String> buckets;
+ private final Set<String> keys;
+ private final String description;
+ private final Map<String, Collection<Acl>> roleAcls;
+ private final Set<String> labels;
+
+ private Policy(Builder builder) {
+ name = builder.name;
+ volumes = builder.volumes;
+ buckets = builder.buckets;
+ keys = builder.keys;
+ description = builder.description;
+ roleAcls = builder.roleAcls;
+ labels = builder.labels;
+ }
+
+ public Set<String> getVolumes() {
+ return volumes;
+ }
+
+ public Set<String> getBuckets() {
+ return buckets;
+ }
+
+ public Set<String> getKeys() {
+ return keys;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Optional<String> getDescription() {
+ return Optional.ofNullable(description);
+ }
+
+ public Set<String> getLabels() {
+ return (labels);
+ }
+
+ public Map<String, Collection<Acl>> getRoleAcls() {
+ return roleAcls;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+ Policy policy = (Policy) other;
+ return Objects.equals(getName(), policy.getName()) &&
+ Objects.equals(getVolumes(), policy.getVolumes()) &&
+ Objects.equals(getBuckets(), policy.getBuckets()) &&
+ Objects.equals(getKeys(), policy.getKeys()) &&
+ Objects.equals(getDescription(), policy.getDescription()) &&
+ Objects.equals(getRoleAcls(), policy.getRoleAcls()) &&
+ Objects.equals(getLabels(), policy.getLabels());
+ }
+
+ /**
+ * Builder class for a policy.
+ */
+ public static final class Builder {
+ private String name;
+ private final Set<String> volumes;
+ private final Set<String> buckets;
+ private final Set<String> keys;
+ private String description;
+ private final Map<String, Collection<Acl>> roleAcls;
+ private final Set<String> labels;
+
+ public Builder() {
+ this.volumes = new HashSet<>();
+ this.buckets = new HashSet<>();
+ this.keys = new HashSet<>();
+ this.roleAcls = new HashMap<>();
+ this.labels = new HashSet<>();
+ }
+
+ public Builder setName(String policyName) {
+ this.name = policyName;
+ return this;
+ }
+
+ public Builder addVolume(String volume) {
+ this.volumes.add(volume);
+ return this;
+ }
+
+ public Builder addBucket(String bucket) {
+ this.buckets.add(bucket);
+ return this;
+ }
+
+ public Builder addKey(String key) {
+ this.keys.add(key);
+ return this;
+ }
+
+ public Builder addVolumes(Collection<String> volumeList) {
+ this.volumes.addAll(volumeList);
+ return this;
+ }
+
+ public Builder addBuckets(Collection<String> bucketList) {
+ this.buckets.addAll(bucketList);
+ return this;
+ }
+
+ public Builder addKeys(Collection<String> keyList) {
+ this.keys.addAll(keyList);
+ return this;
+ }
+
+ public Builder setDescription(String policyDescription) {
+ this.description = policyDescription;
+ return this;
+ }
+
+ public Builder addRoleAcl(String roleName, Collection<Acl> acls) {
+ this.roleAcls.put(roleName, new ArrayList<>(acls));
+ return this;
+ }
+
+ public Builder addLabel(String label) {
+ this.labels.add(label);
+ return this;
+ }
+
+ public Builder addLabels(Collection<String> labelsList) {
+ this.labels.addAll(labelsList);
+ return this;
+ }
+
+ public Policy build() {
+ if (name == null || name.isEmpty()) {
+ throw new IllegalStateException("A policy must have a non-empty " +
+ "name.");
+ }
+ return new Policy(this);
+ }
+ }
+ }
+}
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/multitenant/RangerClientMultiTenantAccessController.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/multitenant/RangerClientMultiTenantAccessController.java
new file mode 100644
index 0000000000..b04843789a
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/multitenant/RangerClientMultiTenantAccessController.java
@@ -0,0 +1,324 @@
+/*
+ * 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.http.auth.BasicUserPrincipal;
+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<BasicUserPrincipal> members) {
+ return members.stream()
+ .map(princ -> new RangerRole.RoleMember(princ.getName(), false))
+ .collect(Collectors.toList());
+ }
+
+ private static List<BasicUserPrincipal> fromRangerRoleMembers(
+ Collection<RangerRole.RoleMember> members) {
+ return members.stream()
+ .map(rangerUser -> new BasicUserPrincipal(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;
+ }
+}
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/multitenant/InMemoryMultiTenantAccessController.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/multitenant/InMemoryMultiTenantAccessController.java
new file mode 100644
index 0000000000..cfb2a05e2f
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/multitenant/InMemoryMultiTenantAccessController.java
@@ -0,0 +1,154 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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 java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * In Memory version of MultiTenantAccessController.
+ */
+public class InMemoryMultiTenantAccessController
+ implements MultiTenantAccessController {
+
+ private final Map<String, Policy> policies;
+ private final Map<String, Role> roles;
+ private long nextRoleID;
+ private long serviceVersion;
+
+ public InMemoryMultiTenantAccessController() {
+ nextRoleID = 0;
+ policies = new HashMap<>();
+ roles = new HashMap<>();
+ serviceVersion = 0;
+ }
+
+ @Override
+ public void createPolicy(Policy policy) throws Exception {
+ if (policies.containsKey(policy.getName())) {
+ throw new Exception("Policy already exists.");
+ }
+ // Multiple policies for the same resource should not be allowed.
+ for (Policy existingPolicy: policies.values()) {
+ if (existingPolicy.getVolumes().equals(policy.getVolumes()) &&
+ existingPolicy.getBuckets().equals(policy.getBuckets()) &&
+ existingPolicy.getKeys().equals(policy.getKeys())) {
+ throw new Exception("Policy for the same resource already defined.");
+ }
+ }
+ policies.put(policy.getName(), policy);
+ // Ranger will create roles if specified with policy creation.
+ for (String roleName: policy.getRoleAcls().keySet()) {
+ if (!roles.containsKey(roleName)) {
+ createRole(new Role.Builder().setName(roleName).build());
+ }
+ }
+
+ serviceVersion++;
+ }
+
+ @Override
+ public Policy getPolicy(String policyName) throws Exception {
+ if (!policies.containsKey(policyName)) {
+ throw new Exception("Policy does not exist.");
+ }
+ return policies.get(policyName);
+ }
+
+ @Override
+ public List<Policy> getLabeledPolicies(String label) throws Exception {
+ List<Policy> result = new ArrayList<>();
+ for (Policy policy: policies.values()) {
+ if (policy.getLabels().contains(label)) {
+ result.add(policy);
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public void updatePolicy(Policy policy) throws Exception {
+ if (!policies.containsKey(policy.getName())) {
+ throw new Exception("Policy does not exist.");
+ }
+ policies.put(policy.getName(), policy);
+ serviceVersion++;
+ }
+
+ @Override
+ public void deletePolicy(String policyName) throws Exception {
+ if (!policies.containsKey(policyName)) {
+ throw new Exception("Policy does not exist.");
+ }
+ policies.remove(policyName);
+ serviceVersion++;
+ }
+
+ @Override
+ public void createRole(Role role) throws Exception {
+ if (roles.containsKey(role.getName())) {
+ throw new Exception("Role already exists.");
+ }
+ Role newRole = new Role.Builder(role)
+ .setID(nextRoleID)
+ .build();
+ nextRoleID++;
+ roles.put(newRole.getName(), newRole);
+ serviceVersion++;
+ }
+
+ @Override
+ public Role getRole(String roleName) throws Exception {
+ if (!roles.containsKey(roleName)) {
+ throw new Exception("Role does not exist.");
+ }
+ return roles.get(roleName);
+ }
+
+ @Override
+ public void updateRole(long roleID, Role role) throws Exception {
+ Optional<Role> originalRole = roles.values().stream()
+ .filter(r -> r.getRoleID().isPresent() && r.getRoleID().get() == roleID)
+ .findFirst();
+ if (!originalRole.isPresent()) {
+ throw new Exception("Role does not exist.");
+ }
+ // New role may have same ID but different name.
+ roles.remove(originalRole.get().getName());
+ roles.put(role.getName(), role);
+ serviceVersion++;
+ }
+
+ @Override
+ public void deleteRole(String roleName) throws Exception {
+ if (!roles.containsKey(roleName)) {
+ throw new Exception("Role does not exist.");
+ }
+ roles.remove(roleName);
+ serviceVersion++;
+ }
+
+ @Override
+ public long getRangerServiceVersion() {
+ return serviceVersion;
+ }
+}
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/multitenant/TestMultiTenantAccessController.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/multitenant/TestMultiTenantAccessController.java
new file mode 100644
index 0000000000..8e0e871fca
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/multitenant/TestMultiTenantAccessController.java
@@ -0,0 +1,398 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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 org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.om.multitenant.MultiTenantAccessController.Acl;
+import org.apache.hadoop.ozone.om.multitenant.MultiTenantAccessController.Policy;
+import org.apache.hadoop.ozone.om.multitenant.MultiTenantAccessController.Role;
+import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType;
+import org.apache.http.auth.BasicUserPrincipal;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+/**
+ * To test MultiTenantAccessController with Ranger Client.
+ */
+public class TestMultiTenantAccessController {
+ private MultiTenantAccessController controller;
+ private List<BasicUserPrincipal> users;
+
+ @Before
+ public void setupUsers() {
+ // If testing against a real cluster, users must already be added to Ranger.
+ users = new ArrayList<>();
+ users.add(new BasicUserPrincipal("om"));
+ users.add(new BasicUserPrincipal("hdfs"));
+ }
+
+ /**
+ * Use this setup to test against a simulated Ranger instance.
+ */
+ @Before
+ public void setupUnitTest() {
+ controller = new InMemoryMultiTenantAccessController();
+ }
+
+ /**
+ * Use this setup to test against a live Ranger instance.
+ */
+ // @Before
+ public void setupClusterTest() throws Exception {
+ // These config keys must be set when the test is run:
+ // OZONE_RANGER_HTTPS_ADDRESS_KEY
+ // OZONE_RANGER_SERVICE
+ // OZONE_OM_KERBEROS_PRINCIPAL_KEY
+ // OZONE_OM_KERBEROS_KEYTAB_FILE_KEY
+ OzoneConfiguration conf = new OzoneConfiguration();
+ controller = new RangerClientMultiTenantAccessController(conf);
+ }
+
+ @Test
+ public void testCreateGetDeletePolicies() throws Exception {
+ // load a policy with everything possible except roles.
+ final String policyName = "test-policy";
+
+ MultiTenantAccessController.Policy originalPolicy =
+ new MultiTenantAccessController.Policy.Builder()
+ .setName(policyName)
+ .addVolume("vol1")
+ .addVolume("vol2")
+ .addBucket("vol1/bucket1")
+ .addBucket("vol2/bucket2")
+ .addKey("vol1/bucket1/key1")
+ .addKey("vol2/bucket2/key2")
+ .setDescription("description")
+ .addLabel("label1")
+ .addLabel("label2")
+ .build();
+
+ // create in ranger.
+ controller.createPolicy(originalPolicy);
+ // get to check it's there with all attributes.
+ MultiTenantAccessController.Policy retrievedPolicy =
+ controller.getPolicy(policyName);
+ Assert.assertEquals(originalPolicy, retrievedPolicy);
+
+ // delete policy.
+ controller.deletePolicy(policyName);
+ // get to check it is deleted.
+ try {
+ controller.getPolicy(policyName);
+ Assert.fail("Expected exception for missing policy.");
+ } catch (Exception ex) {
+ // Expected since policy is not there.
+ }
+ }
+
+ @Test
+ public void testCreateDuplicatePolicy() throws Exception {
+ final String policyName = "test-policy";
+ final String volumeName = "vol1";
+ MultiTenantAccessController.Policy originalPolicy =
+ new MultiTenantAccessController.Policy.Builder()
+ .setName(policyName)
+ .addVolume(volumeName)
+ .build();
+ // create in ranger.
+ controller.createPolicy(originalPolicy);
+ Assert.assertEquals(originalPolicy, controller.getPolicy(policyName));
+
+ // Create a policy with the same name but different resource.
+ // Check for error.
+ MultiTenantAccessController.Policy sameNamePolicy =
+ new MultiTenantAccessController.Policy.Builder()
+ .setName(policyName)
+ .addVolume(volumeName + "2")
+ .build();
+ try {
+ controller.createPolicy(sameNamePolicy);
+ Assert.fail("Expected exception for duplicate policy.");
+ } catch (Exception ex) {
+ // Expected since a policy with the same name should not be allowed.
+ }
+
+ // Create a policy with different name but same resource.
+ // Check for error.
+ MultiTenantAccessController.Policy sameResourcePolicy =
+ new MultiTenantAccessController.Policy.Builder()
+ .setName(policyName + "2")
+ .addVolume(volumeName)
+ .build();
+ try {
+ controller.createPolicy(sameResourcePolicy);
+ Assert.fail("Expected exception for duplicate policy.");
+ } catch (Exception ex) {
+ // Expected since a policy with the same resource should not be allowed.
+ }
+
+ // delete policy.
+ controller.deletePolicy(policyName);
+ }
+
+ @Test
+ public void testGetLabeledPolicies() throws Exception {
+ final String label = "label";
+ Policy labeledPolicy1 = new Policy.Builder()
+ .setName("policy1")
+ .addVolume(UUID.randomUUID().toString())
+ .addLabel(label)
+ .build();
+ Policy labeledPolicy2 = new Policy.Builder()
+ .setName("policy2")
+ .addVolume(UUID.randomUUID().toString())
+ .addLabel(label)
+ .build();
+
+ List<Policy> labeledPolicies = new ArrayList<>();
+ labeledPolicies.add(labeledPolicy1);
+ labeledPolicies.add(labeledPolicy2);
+ Policy unlabeledPolicy = new Policy.Builder()
+ .setName("policy3")
+ .addVolume(UUID.randomUUID().toString())
+ .build();
+
+ for (Policy policy: labeledPolicies) {
+ controller.createPolicy(policy);
+ }
+ controller.createPolicy(unlabeledPolicy);
+
+ // Get should only return policies with the specified label.
+ List<Policy> retrievedLabeledPolicies =
+ controller.getLabeledPolicies(label);
+ Assert.assertEquals(labeledPolicies.size(),
+ retrievedLabeledPolicies.size());
+ Assert.assertTrue(retrievedLabeledPolicies.containsAll(labeledPolicies));
+
+ // Get of a specific policy should also succeed.
+ Policy retrievedPolicy = controller.getPolicy(unlabeledPolicy.getName());
+ Assert.assertEquals(unlabeledPolicy, retrievedPolicy);
+
+ // Get of policies with nonexistent label should give an empty list.
+ Assert.assertTrue(controller.getLabeledPolicies(label + "1").isEmpty());
+
+ // Cleanup
+ for (Policy policy: labeledPolicies) {
+ controller.deletePolicy(policy.getName());
+ }
+ controller.deletePolicy(unlabeledPolicy.getName());
+ }
+
+ @Test
+ public void testUpdatePolicy() throws Exception {
+ String policyName = "policy";
+ // Since the roles will not exist when the policy is created, Ranger
+ // should create them.
+ Policy originalPolicy = new Policy.Builder()
+ .setName(policyName)
+ .addVolume("vol1")
+ .addLabel("label1")
+ .addRoleAcl("role1",
+ Collections.singletonList(Acl.allow(ACLType.READ_ACL)))
+ .build();
+ controller.createPolicy(originalPolicy);
+ Assert.assertEquals(originalPolicy,
+ controller.getPolicy(policyName));
+
+ Policy updatedPolicy = new Policy.Builder()
+ .setName(policyName)
+ .addVolume("vol2")
+ .addLabel("label2")
+ .addRoleAcl("role1",
+ Collections.singletonList(Acl.allow(ACLType.WRITE_ACL)))
+ .addRoleAcl("role2",
+ Collections.singletonList(Acl.allow(ACLType.READ_ACL)))
+ .build();
+ controller.updatePolicy(updatedPolicy);
+ Assert.assertEquals(updatedPolicy,
+ controller.getPolicy(policyName));
+
+ // Cleanup
+ controller.deletePolicy(policyName);
+ }
+
+ @Test
+ public void testCreatePolicyWithRoles() throws Exception {
+ // Create a policy with role acls.
+ final String roleName = "role1";
+ Policy policy = new Policy.Builder()
+ .setName("policy1")
+ .addVolume("volume")
+ .addRoleAcl(roleName,
+ Collections.singletonList(Acl.allow(ACLType.ALL)))
+ .build();
+ // This should create the role as well.
+ controller.createPolicy(policy);
+
+ // Test the acls set on the role for the policy.
+ Policy retrievedPolicy = controller.getPolicy(policy.getName());
+ Map<String, Collection<Acl>> retrievedRoleAcls =
+ retrievedPolicy.getRoleAcls();
+ Assert.assertEquals(1, retrievedRoleAcls.size());
+ List<Acl> roleAcls = new ArrayList<>(retrievedRoleAcls.get(roleName));
+ Assert.assertEquals(1, roleAcls.size());
+ Assert.assertEquals(ACLType.ALL, roleAcls.get(0).getAclType());
+ Assert.assertTrue(roleAcls.get(0).isAllowed());
+
+ // get one of the roles to check it is there but empty.
+ Role retrievedRole = controller.getRole(roleName);
+ Assert.assertFalse(retrievedRole.getDescription().isPresent());
+ Assert.assertTrue(retrievedRole.getUsers().isEmpty());
+ Assert.assertTrue(retrievedRole.getRoleID().isPresent());
+
+ // Add a user to the role.
+ retrievedRole.getUsers().add(users.get(0));
+ controller.updateRole(retrievedRole.getRoleID().get(), retrievedRole);
+
+ // Create a new policy containing the role. This should not overwrite the
+ // role.
+ Policy policy2 = new Policy.Builder()
+ .setName("policy2")
+ .addVolume("volume2")
+ .addRoleAcl(roleName,
+ Collections.singletonList(Acl.allow(ACLType.READ)))
+ .build();
+ controller.createPolicy(policy2);
+ Assert.assertEquals(controller.getRole(roleName), retrievedRole);
+
+ controller.deletePolicy("policy1");
+ controller.deletePolicy("policy2");
+ controller.deleteRole(roleName);
+ }
+
+ @Test
+ public void testCreateGetDeleteRoles() throws Exception {
+ // load a role with everything possible.
+ final String roleName = "test-role";
+
+ Role originalRole =
+ new Role.Builder()
+ .setName(roleName)
+ .addUsers(users)
+ .build();
+
+ // create in ranger.
+ controller.createRole(originalRole);
+ // get to check it's there with all attributes.
+ Role retrievedRole = controller.getRole(roleName);
+ // Role ID should have been added by Ranger.
+ Assert.assertTrue(retrievedRole.getRoleID().isPresent());
+ Assert.assertEquals(originalRole, retrievedRole);
+
+ // delete role.
+ controller.deleteRole(roleName);
+ // get to check it is deleted.
+ try {
+ controller.getRole(roleName);
+ Assert.fail("Expected exception for missing role.");
+ } catch (Exception ex) {
+ // Expected since policy is not there.
+ }
+ }
+
+ @Test
+ public void testCreateDuplicateRole() throws Exception {
+ final String roleName = "test-role";
+ Role originalRole = new Role.Builder()
+ .setName(roleName)
+ .build();
+ // create in Ranger.
+ controller.createRole(originalRole);
+ Assert.assertEquals(originalRole, controller.getRole(roleName));
+
+ // Create a role with the same name and check for error.
+ Role sameNameRole = new Role.Builder()
+ .setName(roleName)
+ .build();
+ try {
+ controller.createRole(sameNameRole);
+ Assert.fail("Expected exception for duplicate role.");
+ } catch (Exception ex) {
+ // Expected since a policy with the same name should not be allowed.
+ }
+
+ // delete role.
+ controller.deleteRole(roleName);
+ }
+
+ @Test
+ public void testUpdateRole() throws Exception {
+ final String roleName = "test-role";
+ Role originalRole = new Role.Builder()
+ .setName(roleName)
+ .addUsers(users)
+ .build();
+ // create in Ranger.
+ controller.createRole(originalRole);
+
+ Role retrievedRole = controller.getRole(roleName);
+ Assert.assertEquals(originalRole, retrievedRole);
+ Assert.assertTrue(retrievedRole.getRoleID().isPresent());
+ long roleID = retrievedRole.getRoleID().get();
+
+ // Remove a user from the role and update it.
+ retrievedRole.getUsers().remove(users.get(0));
+ Assert.assertEquals(originalRole.getUsers().size() - 1,
+ retrievedRole.getUsers().size());
+ controller.updateRole(roleID, retrievedRole);
+ Role retrievedUpdatedRole = controller.getRole(roleName);
+ Assert.assertEquals(retrievedRole, retrievedUpdatedRole);
+ Assert.assertEquals(originalRole.getUsers().size() - 1,
+ retrievedUpdatedRole.getUsers().size());
+
+ // Cleanup.
+ controller.deleteRole(roleName);
+ }
+
+ /**
+ * Test that Acl types are correctly converted to strings that Ranger can
+ * understand. An exception will be thrown if Ranger does not recognize the
+ * Acl.
+ */
+ @Test
+ public void testRangerAclStrings() throws Exception {
+ // Create a policy that uses all possible acl types.
+ List<Acl> acls = Arrays.stream(ACLType.values())
+ .map(Acl::allow)
+ .collect(Collectors.toList());
+ // Ranger does not support the NONE acl type.
+ Acl noneRemove = Acl.allow(ACLType.NONE);
+ acls.remove(noneRemove);
+ Policy policy = new Policy.Builder()
+ .setName("policy")
+ .addVolume("volume")
+ .addRoleAcl("role", acls)
+ .build();
+ // Converting from ACLType to Ranger strings should not produce an error.
+ controller.createPolicy(policy);
+ // Converting from Ranger strings to ACLType should not produce an error.
+ controller.getPolicy(policy.getName());
+ // cleanup.
+ controller.deletePolicy(policy.getName());
+ }
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@ozone.apache.org
For additional commands, e-mail: commits-help@ozone.apache.org