You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ozone.apache.org by er...@apache.org on 2021/11/29 17:24:00 UTC

[ozone] branch HDDS-4944 updated: HDDS-5942. Move Ranger REST API interactions under same interface as Ranger client. (#2805)

This is an automated email from the ASF dual-hosted git repository.

erose 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 a19d252  HDDS-5942. Move Ranger REST API interactions under same interface as Ranger client. (#2805)
a19d252 is described below

commit a19d252f988862e1eadb29ba471441a572409035
Author: Ethan Rose <33...@users.noreply.github.com>
AuthorDate: Mon Nov 29 09:23:45 2021 -0800

    HDDS-5942. Move Ranger REST API interactions under same interface as Ranger client. (#2805)
---
 .../org/apache/hadoop/ozone/om/OMConfigKeys.java   |  14 +-
 .../multitenant/MultiTenantAccessController.java   | 213 +++++++
 .../RangerRestMultiTenantAccessController.java     | 631 +++++++++++++++++++++
 .../hadoop/ozone/TestOzoneConfigurationFields.java |   4 +
 4 files changed, 855 insertions(+), 7 deletions(-)

diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java
index 84ac238..97fbb34d 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java
@@ -276,26 +276,26 @@ public final class OMConfigKeys {
       "ozone.path.deleting.limit.per.task";
   public static final int OZONE_PATH_DELETING_LIMIT_PER_TASK_DEFAULT = 10000;
 
+  /**
+   * Temporary configuration properties for Ranger REST use in multitenancy.
+   */
   public static final String OZONE_RANGER_OM_IGNORE_SERVER_CERT =
       "ozone.om.ranger.ignore.cert";
   public static final boolean OZONE_RANGER_OM_IGNORE_SERVER_CERT_DEFAULT =
       true;
-
   public static final String OZONE_RANGER_OM_CONNECTION_TIMEOUT =
       "ozone.om.ranger.connection.timeout";
   public static final String OZONE_RANGER_OM_CONNECTION_TIMEOUT_DEFAULT = "5s";
-
   public static final String OZONE_RANGER_OM_CONNECTION_REQUEST_TIMEOUT =
       "ozone.om.ranger.connection.request.timeout";
   public static final String
       OZONE_RANGER_OM_CONNECTION_REQUEST_TIMEOUT_DEFAULT = "5s";
-
-  public static final String OZONE_RANGER_HTTPS_ADDRESS_KEY =
-      "ozone.om.ranger.https-address";
-
   public static final String OZONE_OM_RANGER_HTTPS_ADMIN_API_USER =
       "ozone.om.ranger.https.admin.api.user";
-
   public static final String OZONE_OM_RANGER_HTTPS_ADMIN_API_PASSWD =
       "ozone.om.ranger.https.admin.api.passwd";
+  public static final String OZONE_RANGER_HTTPS_ADDRESS_KEY =
+      "ozone.om.ranger.https-address";
+  public static final String OZONE_RANGER_SERVICE =
+      "ozone.om.ranger.service";
 }
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
new file mode 100644
index 0000000..3b8975b
--- /dev/null
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/MultiTenantAccessController.java
@@ -0,0 +1,213 @@
+/*
+ * 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
new file mode 100644
index 0000000..4a79d11
--- /dev/null
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/RangerRestMultiTenantAccessController.java
@@ -0,0 +1,631 @@
+/*
+ * 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.*;
+
+/**
+ * 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/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java
index 899e152..2ee7710 100644
--- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java
+++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java
@@ -67,6 +67,10 @@ public class TestOzoneConfigurationFields extends TestConfigurationFieldsBase {
         .add(ScmConfig.ConfigStrings.HDDS_SCM_INIT_DEFAULT_LAYOUT_VERSION);
     // This property is tested in TestHttpServer2 instead
     xmlPropsToSkipCompare.add(HttpServer2.HTTP_IDLE_TIMEOUT_MS_KEY);
+
+    // TODO: Remove this once ranger configs are finalized in HDDS-5836
+    configurationPrefixToSkipCompare.add("ozone.om.ranger");
+
     addPropertiesNotInXml();
   }
 

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@ozone.apache.org
For additional commands, e-mail: commits-help@ozone.apache.org