blob: d856f898b732c41ddfd4c98a49e9d63df7da0fd1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.ranger.admin.client;
import java.lang.reflect.Type;
import java.security.PrivilegedAction;
import java.util.Date;
import java.util.List;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.ranger.plugin.util.*;
import org.apache.ranger.audit.provider.MiscUtil;
import org.apache.ranger.authorization.hadoop.config.RangerConfiguration;
import org.glassfish.jersey.client.ClientProperties;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
public class RangerAdminJersey2RESTClient implements RangerAdminClient {
// none of the members are public -- this is only for testability. None of these is meant to be accessible
private static final Log LOG = LogFactory.getLog(RangerAdminJersey2RESTClient.class);
RangerRESTUtils _utils = new RangerRESTUtils();
boolean _isSSL = false;
volatile Client _client = null;
SSLContext _sslContext = null;
HostnameVerifier _hv;
String _baseUrl = null;
String _sslConfigFileName = null;
String _serviceName = null;
String _clusterName = null;
String _pluginId = null;
int _restClientConnTimeOutMs;
int _restClientReadTimeOutMs;
@Override
public void init(String serviceName, String appId, String configPropertyPrefix) {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerAdminJersey2RESTClient.init(" + configPropertyPrefix + ")");
}
_serviceName = serviceName;
_pluginId = _utils.getPluginId(serviceName, appId);
_baseUrl = _utils.getPolicyRestUrl(configPropertyPrefix);
_sslConfigFileName = _utils.getSsslConfigFileName(configPropertyPrefix);
_isSSL = _utils.isSsl(_baseUrl);
_restClientConnTimeOutMs = RangerConfiguration.getInstance().getInt(configPropertyPrefix + ".policy.rest.client.connection.timeoutMs", 120 * 1000);
_restClientReadTimeOutMs = RangerConfiguration.getInstance().getInt(configPropertyPrefix + ".policy.rest.client.read.timeoutMs", 30 * 1000);
_clusterName = RangerConfiguration.getInstance().get(configPropertyPrefix + ".ambari.cluster.name", "");
LOG.info("Init params: " + String.format("Base URL[%s], SSL Congig filename[%s], ServiceName=[%s]", _baseUrl, _sslConfigFileName, _serviceName));
_client = getClient();
_client.property(ClientProperties.CONNECT_TIMEOUT, _restClientConnTimeOutMs);
_client.property(ClientProperties.READ_TIMEOUT, _restClientReadTimeOutMs);
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerAdminJersey2RESTClient.init(" + configPropertyPrefix + "): " + _client.toString());
}
}
@Override
public ServicePolicies getServicePoliciesIfUpdated(final long lastKnownVersion, final long lastActivationTimeInMillis) throws Exception {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerAdminJersey2RESTClient.getServicePoliciesIfUpdated(" + lastKnownVersion + ", " + lastActivationTimeInMillis + ")");
}
UserGroupInformation user = MiscUtil.getUGILoginUser();
boolean isSecureMode = user != null && UserGroupInformation.isSecurityEnabled();
String url = null;
ServicePolicies servicePolicies = null;
Response response = null;
if (isSecureMode) {
if (LOG.isDebugEnabled()) {
LOG.debug("Checking Service policy if updated as user : " + user);
}
url = _utils.getSecureUrlForPolicyUpdate(_baseUrl, _serviceName);
final String secureUrl = url;
PrivilegedAction<Response> action = new PrivilegedAction<Response>() {
public Response run() {
return _client.target(secureUrl)
.queryParam(RangerRESTUtils.REST_PARAM_LAST_KNOWN_POLICY_VERSION, Long.toString(lastKnownVersion))
.queryParam(RangerRESTUtils.REST_PARAM_LAST_ACTIVATION_TIME, Long.toString(lastActivationTimeInMillis))
.queryParam(RangerRESTUtils.REST_PARAM_PLUGIN_ID, _pluginId)
.queryParam(RangerRESTUtils.REST_PARAM_CLUSTER_NAME, _clusterName)
.request(MediaType.APPLICATION_JSON_TYPE)
.get();
}
};
response = user.doAs(action);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Checking Service policy if updated with old api call");
}
url = _utils.getUrlForPolicyUpdate(_baseUrl, _serviceName);
response = _client.target(url)
.queryParam(RangerRESTUtils.REST_PARAM_LAST_KNOWN_POLICY_VERSION, Long.toString(lastKnownVersion))
.queryParam(RangerRESTUtils.REST_PARAM_LAST_ACTIVATION_TIME, Long.toString(lastActivationTimeInMillis))
.queryParam(RangerRESTUtils.REST_PARAM_PLUGIN_ID, _pluginId)
.queryParam(RangerRESTUtils.REST_PARAM_CLUSTER_NAME, _clusterName)
.request(MediaType.APPLICATION_JSON_TYPE)
.get();
}
int httpResponseCode = response == null ? -1 : response.getStatus();
String body = null;
switch (httpResponseCode) {
case 200:
body = response.readEntity(String.class);
if (LOG.isDebugEnabled()) {
LOG.debug("Response from 200 server: " + body);
}
Gson gson = getGson();
servicePolicies = gson.fromJson(body, ServicePolicies.class);
if (LOG.isDebugEnabled()) {
LOG.debug("Deserialized response to: " + servicePolicies);
}
break;
case 304:
LOG.debug("Got response: 304. Ok. Returning null");
break;
case -1:
LOG.warn("Unexpected: Null response from policy server while trying to get policies! Returning null!");
break;
case 404: {
if (response.hasEntity()) {
body = response.readEntity(String.class);
if (StringUtils.isNotBlank(body)) {
RangerServiceNotFoundException.throwExceptionIfServiceNotFound(_serviceName, body);
}
}
LOG.warn("Received 404 error code with body:[" + body + "], Ignoring");
break;
}
default:
body = response.readEntity(String.class);
LOG.warn(String.format("Unexpected: Received status[%d] with body[%s] form url[%s]", httpResponseCode, body, url));
break;
}
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerAdminJersey2RESTClient.getServicePoliciesIfUpdated(" + lastKnownVersion + ", " + lastActivationTimeInMillis + "): " + servicePolicies);
}
return servicePolicies;
}
@Override
public void grantAccess(GrantRevokeRequest request) throws Exception {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerAdminRESTClient.grantAccess(" + request + ")");
}
String url = _utils.getUrlForGrantAccess(_baseUrl, _serviceName);
Response response = _client.target(url)
.queryParam(RangerRESTUtils.REST_PARAM_PLUGIN_ID, _pluginId)
.request(MediaType.APPLICATION_JSON_TYPE)
.get();
int httpResponseCode = response == null ? -1 : response.getStatus();
switch(httpResponseCode) {
case -1:
LOG.warn("Unexpected: Null response from policy server while granting access! Returning null!");
throw new Exception("unknown error!");
case 200:
LOG.debug("grantAccess() suceeded: HTTP status=" + httpResponseCode);
break;
case 401:
throw new AccessControlException();
default:
String body = response.readEntity(String.class);
String message = String.format("Unexpected: Received status[%d] with body[%s] form url[%s]", httpResponseCode, body, url);
LOG.warn(message);
throw new Exception("HTTP status: " + httpResponseCode);
}
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerAdminRESTClient.grantAccess(" + request + ")");
}
}
@Override
public void revokeAccess(GrantRevokeRequest request) throws Exception {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerAdminRESTClient.grantAccess(" + request + ")");
}
String url = _utils.getUrlForRevokeAccess(_baseUrl, _serviceName);
Response response = _client.target(url)
.queryParam(RangerRESTUtils.REST_PARAM_PLUGIN_ID, _pluginId)
.request(MediaType.APPLICATION_JSON_TYPE)
.get();
int httpResponseCode = response == null ? -1 : response.getStatus();
switch(httpResponseCode) {
case -1:
LOG.warn("Unexpected: Null response from policy server while granting access! Returning null!");
throw new Exception("unknown error!");
case 200:
LOG.debug("grantAccess() suceeded: HTTP status=" + httpResponseCode);
break;
case 401:
throw new AccessControlException();
default:
String body = response.readEntity(String.class);
String message = String.format("Unexpected: Received status[%d] with body[%s] form url[%s]", httpResponseCode, body, url);
LOG.warn(message);
throw new Exception("HTTP status: " + httpResponseCode);
}
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerAdminRESTClient.grantAccess(" + request + ")");
}
}
@Override
public ServiceTags getServiceTagsIfUpdated(final long lastKnownVersion, final long lastActivationTimeInMillis) throws Exception {
if (LOG.isDebugEnabled()) {
LOG.debug("==> RangerAdminJersey2RESTClient.getServiceTagsIfUpdated(" + lastKnownVersion + ", " + lastActivationTimeInMillis + ")");
}
UserGroupInformation user = MiscUtil.getUGILoginUser();
boolean isSecureMode = user != null && UserGroupInformation.isSecurityEnabled();
String url = null;
ServiceTags serviceTags = null;
Response response = null;
if (isSecureMode) {
if (LOG.isDebugEnabled()) {
LOG.debug("Checking Service tags if updated as user : " + user);
}
url = _utils.getSecureUrlForTagUpdate(_baseUrl, _serviceName);
final String secureUrl = url;
PrivilegedAction<Response> action = new PrivilegedAction<Response>() {
public Response run() {
return _client.target(secureUrl)
.queryParam(RangerRESTUtils.LAST_KNOWN_TAG_VERSION_PARAM, Long.toString(lastKnownVersion))
.queryParam(RangerRESTUtils.REST_PARAM_LAST_ACTIVATION_TIME, Long.toString(lastActivationTimeInMillis))
.queryParam(RangerRESTUtils.REST_PARAM_PLUGIN_ID, _pluginId)
.request(MediaType.APPLICATION_JSON_TYPE)
.get();
}
};
response = user.doAs(action);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Checking Service tags if updated with old api call");
}
url = _utils.getUrlForTagUpdate(_baseUrl, _serviceName);
response = _client.target(url)
.queryParam(RangerRESTUtils.LAST_KNOWN_TAG_VERSION_PARAM, Long.toString(lastKnownVersion))
.queryParam(RangerRESTUtils.REST_PARAM_LAST_ACTIVATION_TIME, Long.toString(lastActivationTimeInMillis))
.queryParam(RangerRESTUtils.REST_PARAM_PLUGIN_ID, _pluginId)
.request(MediaType.APPLICATION_JSON_TYPE)
.get();
}
int httpResponseCode = response == null ? -1 : response.getStatus();
String body = null;
switch (httpResponseCode) {
case 200:
body = response.readEntity(String.class);
if (LOG.isDebugEnabled()) {
LOG.debug("Response from 200 server: " + body);
}
Gson gson = getGson();
serviceTags = gson.fromJson(body, ServiceTags.class);
if (LOG.isDebugEnabled()) {
LOG.debug("Deserialized response to: " + serviceTags);
}
break;
case 304:
LOG.debug("Got response: 304. Ok. Returning null");
break;
case -1:
LOG.warn("Unexpected: Null response from tag server while trying to get tags! Returning null!");
break;
case 404:
if (response.hasEntity()) {
body = response.readEntity(String.class);
if (StringUtils.isNotBlank(body)) {
RangerServiceNotFoundException.throwExceptionIfServiceNotFound(_serviceName, body);
}
}
LOG.warn("Received 404 error code with body:[" + body + "], Ignoring");
break;
default:
body = response.readEntity(String.class);
LOG.warn(String.format("Unexpected: Received status[%d] with body[%s] form url[%s]", httpResponseCode, body, url));
break;
}
if (LOG.isDebugEnabled()) {
LOG.debug("<== RangerAdminJersey2RESTClient.getServiceTagsIfUpdated(" + lastKnownVersion + ", " + lastActivationTimeInMillis + "): " + serviceTags);
}
return serviceTags;
}
@Override
public List<String> getTagTypes(String pattern) throws Exception {
throw new Exception("RangerAdminjersey2RESTClient.getTagTypes() -- *** NOT IMPLEMENTED *** ");
}
// We get date from the policy manager as unix long! This deserializer exists to deal with it. Remove this class once we start send date/time per RFC 3339
public static class GsonUnixDateDeserializer implements JsonDeserializer<Date> {
@Override
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return new Date(json.getAsJsonPrimitive().getAsLong());
}
}
// package level methods left so (and not private only for testability!) Not intended for use outside this class!!
Gson getGson() {
return new GsonBuilder()
.setPrettyPrinting()
// We get date from the policy manager as unix long! This deserializer exists to deal with it. Remove this class once we start send date/time per RFC 3339
.registerTypeAdapter(Date.class, new GsonUnixDateDeserializer())
.create();
}
Client getClient() {
Client result = _client;
if(result == null) {
synchronized(this) {
result = _client;
if(result == null) {
_client = result = buildClient();
}
}
}
return result;
}
Client buildClient() {
if (_isSSL) {
if (_sslContext == null) {
RangerSslHelper sslHelper = new RangerSslHelper(_sslConfigFileName);
_sslContext = sslHelper.createContext();
}
if (_hv == null) {
_hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
return session.getPeerHost().equals(urlHostName);
}
};
}
_client = ClientBuilder.newBuilder()
.sslContext(_sslContext)
.hostnameVerifier(_hv)
.build();
}
if(_client == null) {
_client = ClientBuilder.newClient();
}
return _client;
}
}