/**
* 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.ambari.view.hive20.resources.system.ranger;
import com.google.common.collect.Lists;
import org.apache.ambari.view.AmbariHttpException;
import org.apache.ambari.view.ViewContext;
import org.apache.ambari.view.hive20.utils.AuthorizationChecker;
import org.apache.ambari.view.utils.ambari.AmbariApi;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
*/
public class RangerService {
public static final String RANGER_HIVE_AUTHORIZER_FACTORY_CLASSNAME = "org.apache.ranger.authorization.hive.authorizer.RangerHiveAuthorizerFactory";
private static final String RANGER_CONFIG_URL = "/api/v1/clusters/%s/configurations/service_config_versions?service_name=RANGER&is_current=true";
public static final String HIVESERVER2_SITE = "hiveserver2-site";
public static final String AUTHORIZATION_MANAGER_KEY = "hive.security.authorization.manager";
protected final Logger LOG = LoggerFactory.getLogger(getClass());
private final AuthorizationChecker authChecker;
private final ViewContext context;
@Inject
public RangerService(AuthorizationChecker authChecker, ViewContext context) {
this.authChecker = authChecker;
this.context = context;
}
public List<Policy> getPolicies(String database, String table) {
if (context.getCluster() == null) {
return getPoliciesFromNonAmbariCluster(database, table);
} else {
if (!authChecker.isOperator()) {
LOG.error("User is not authorized to access the table authorization information");
throw new RangerException("User " + context.getUsername() + " does not have privilege to access the table authorization information", "NOT_OPERATOR_OR_ADMIN", 400);
}
return getPoliciesFromAmbariCluster(database, table);
}
}
private List<Policy> getPoliciesFromAmbariCluster(String database, String table) {
if (!isHiveRangerPluginEnabled()) {
LOG.error("Ranger authorization is not enabled for Hive");
throw new RangerException("Ranger authorization is not enabled for Hive", "CONFIGURATION_ERROR", 500);
}
String rangerUrl = null;
try {
rangerUrl = getRangerUrlFromAmbari();
} catch (AmbariHttpException e) {
LOG.error("Failed to fetch Ranger URL from ambari. Exception: {}", e);
throw new RangerException("Failed to fetch Ranger URL from Ambari", "AMBARI_FETCH_FAILED", 500, e);
}
if (StringUtils.isEmpty(rangerUrl)) {
LOG.info("Ranger url is not configured for the instance");
throw new RangerException("Ranger url is not configured in Ambari.", "CONFIGURATION_ERROR", 500);
}
return getPoliciesFromRanger(rangerUrl, database, table);
}
private List<Policy> getPoliciesFromNonAmbariCluster(String database, String table) {
String rangerUrl = getRangerUrlFromConfig();
if (StringUtils.isEmpty(rangerUrl)) {
LOG.info("Ranger url is not configured for the instance");
throw new RangerException("Ranger url is not configured in Ambari Instance.", "CONFIGURATION_ERROR", 500);
}
return getPoliciesFromRanger(rangerUrl, database, table);
}
private List<Policy> getPoliciesFromRanger(String rangerUrl, String database, String table) {
RangerCred cred = getRangerCredFromConfig();
if (!cred.isValid()) {
LOG.info("Ranger username and password are not configured");
throw new RangerException("Bad ranger username/password", "CONFIGURATION_ERROR", 500);
}
String rangerResponse = fetchResponseFromRanger(rangerUrl, cred.username, cred.password, database, table);
if (StringUtils.isEmpty(rangerResponse)) {
return Lists.newArrayList();
}
return parseResponse(rangerResponse);
}
private List<Policy> parseResponse(String rangerResponse) {
Object parsedResult = JSONValue.parse(rangerResponse);
if (parsedResult instanceof JSONObject) {
JSONObject obj = (JSONObject) parsedResult;
LOG.error("Bad response from Ranger: {}", rangerResponse);
int status = ((Long) obj.get("statusCode")).intValue();
status = status == Response.Status.UNAUTHORIZED.getStatusCode() ? Response.Status.FORBIDDEN.getStatusCode() : status;
throw new RangerException((String) obj.get("msgDesc"), "RANGER_ERROR", status);
}
JSONArray jsonArray = (JSONArray) parsedResult;
if (jsonArray.size() == 0) {
return new ArrayList<>();
}
List<Policy> policies = new ArrayList<>();
for (Object policy : jsonArray) {
JSONObject policyJson = (JSONObject) policy;
if ((Boolean) policyJson.get("isEnabled")) {
policies.add(parsePolicy(policyJson));
}
}
return policies;
}
private Policy parsePolicy(JSONObject policyJson) {
String name = (String) policyJson.get("name");
JSONArray policyItems = (JSONArray) policyJson.get("policyItems");
Policy policy = new Policy(name);
for (Object item : policyItems) {
PolicyCondition condition = new PolicyCondition();
JSONObject policyItem = (JSONObject) item;
JSONArray usersJson = (JSONArray) policyItem.get("users");
JSONArray groupsJson = (JSONArray) policyItem.get("groups");
JSONArray accesses = (JSONArray) policyItem.get("accesses");
for (Object accessJson : accesses) {
JSONObject access = (JSONObject) accessJson;
Boolean isAllowed = (Boolean) access.get("isAllowed");
if (isAllowed) {
condition.addAccess((String) access.get("type"));
}
}
for (Object user : usersJson) {
condition.addUser((String) user);
}
for (Object group : groupsJson) {
condition.addGroup((String) group);
}
policy.addCondition(condition);
}
return policy;
}
private String fetchResponseFromRanger(String rangerUrl, String username, String password, String database, String table) {
String serviceName = context.getProperties().get("hive.ranger.servicename");
if (StringUtils.isEmpty(serviceName)) {
LOG.error("Bad service name configured");
throw new RangerException("Ranger service name is not configured in Ambari Instance.", "CONFIGURATION_ERROR", 500);
}
Map<String, String> headers = getRangerHeaders(username, password);
StringBuilder urlBuilder = getRangerUrl(rangerUrl, database, table, serviceName);
try {
InputStream stream = context.getURLStreamProvider().readFrom(urlBuilder.toString(), "GET", (String) null, headers);
if (stream == null) {
LOG.error("Ranger returned an empty stream.");
throw new RangerException("Ranger returned an empty stream.", "RANGER_ERROR", 500);
}
return IOUtils.toString(stream);
} catch (IOException e) {
LOG.error("Bad response from Ranger. Exception: {}", e);
throw new RangerException("Bad response from Ranger", "RANGER_ERROR", 500, e);
}
}
private StringBuilder getRangerUrl(String rangerUrl, String database, String table, String serviceName) {
StringBuilder queryParams = new StringBuilder();
if (!StringUtils.isEmpty(database)) {
queryParams.append("resource:database=");
queryParams.append(database);
if (!StringUtils.isEmpty(table)) {
queryParams.append("&");
}
}
if (!StringUtils.isEmpty(table)) {
queryParams.append("resource:table=");
queryParams.append(table);
}
String queryParamString = queryParams.toString();
StringBuilder urlBuilder = new StringBuilder();
urlBuilder.append(rangerUrl);
urlBuilder.append("/service/public/v2/api/service/");
urlBuilder.append(serviceName);
urlBuilder.append("/policy");
if (!StringUtils.isEmpty(queryParamString)) {
urlBuilder.append("?");
urlBuilder.append(queryParamString);
}
return urlBuilder;
}
private Map<String, String> getRangerHeaders(String username, String password) {
String authString = username + ":" + password;
byte[] authBytes = Base64.encodeBase64(authString.getBytes());
String auth = new String(authBytes);
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Basic " + auth);
return headers;
}
private RangerCred getRangerCredFromConfig() {
return new RangerCred(context.getProperties().get("hive.ranger.username"),
context.getProperties().get("hive.ranger.password"));
}
public String getRangerUrlFromAmbari() throws AmbariHttpException {
AmbariApi ambariApi = new AmbariApi(context);
String url = String.format(RANGER_CONFIG_URL, context.getCluster().getName());
String config = ambariApi.readFromAmbari(url, "GET", null, null);
JSONObject configJson = (JSONObject) JSONValue.parse(config);
JSONArray itemsArray = (JSONArray) configJson.get("items");
if (itemsArray.size() == 0) {
LOG.error("Ranger service is not enabled in Ambari");
throw new RangerException("Ranger service is not enabled in Ambari", "SERVICE_ERROR", 500);
}
JSONObject item = (JSONObject) itemsArray.get(0);
JSONArray configurations = (JSONArray) item.get("configurations");
for (Object configuration : configurations) {
JSONObject configurationJson = (JSONObject) configuration;
String type = (String) configurationJson.get("type");
if (type.equalsIgnoreCase("admin-properties")) {
JSONObject properties = (JSONObject) configurationJson.get("properties");
return (String) properties.get("policymgr_external_url");
}
}
return null;
}
public String getRangerUrlFromConfig() {
return context.getProperties().get("hive.ranger.url");
}
/**
* Check if the ranger plugin is enable for hive
*/
private boolean isHiveRangerPluginEnabled() {
String authManagerConf = context.getCluster().getConfigurationValue(HIVESERVER2_SITE, AUTHORIZATION_MANAGER_KEY);
return !StringUtils.isEmpty(authManagerConf) && authManagerConf.equals(RANGER_HIVE_AUTHORIZER_FACTORY_CLASSNAME);
}
/**
* POJO class to store the policy information from Ranger
*/
public static class Policy {
private String name;
private List<PolicyCondition> conditions = new ArrayList<>();
public Policy(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<PolicyCondition> getConditions() {
return conditions;
}
public void addCondition(PolicyCondition condition) {
this.conditions.add(condition);
}
}
public static class PolicyCondition {
private List<String> users = new ArrayList<>();
private List<String> groups = new ArrayList<>();
private List<String> accesses = new ArrayList<>();
public List<String> getUsers() {
return users;
}
public void setUsers(List<String> users) {
this.users = users;
}
public List<String> getGroups() {
return groups;
}
public void setGroups(List<String> groups) {
this.groups = groups;
}
public List<String> getAccesses() {
return accesses;
}
public void setAccesses(List<String> accesses) {
this.accesses = accesses;
}
public void addUser(String user) {
users.add(user);
}
public void addGroup(String group) {
groups.add(group);
}
public void addAccess(String access) {
accesses.add(access);
}
}
/**
* POJO class to store the username and password for ranger access
*/
private class RangerCred {
public String username;
public String password;
public RangerCred(String username, String password) {
this.username = username;
this.password = password;
}
public boolean isValid() {
return !(StringUtils.isEmpty(username) || StringUtils.isEmpty(password));
}
}
}