| /* |
| * 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; |
| } |
| } |