/** * 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.services.knox.client; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.ranger.plugin.client.BaseClient; import org.apache.ranger.plugin.client.HadoopException; import org.apache.ranger.plugin.util.PasswordUtils; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.map.ObjectMapper; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; public class KnoxClient { private static final String EXPECTED_MIME_TYPE = "application/json"; private static final Log LOG = LogFactory.getLog(KnoxClient.class); private String knoxUrl; private String userName; private String password; /* Sample curl calls to Knox to discover topologies curl -ivk -u <user-name>:<user-password> https://localhost:8443/gateway/admin/api/v1/topologies curl -ivk -u <user-name>:<user-password> https://localhost:8443/gateway/admin/api/v1/topologies/admin */ public KnoxClient(String knoxUrl, String userName, String password) { LOG.debug("Constructed KnoxClient with knoxUrl: " + knoxUrl + ", userName: " + userName); this.knoxUrl = knoxUrl; this.userName = userName; this.password = password; } public List<String> getTopologyList(String topologyNameMatching,List<String> knoxTopologyList) { // sample URI: https://hdp.example.com:8443/gateway/admin/api/v1/topologies LOG.debug("Getting Knox topology list for topologyNameMatching : " + topologyNameMatching); List<String> topologyList = new ArrayList<String>(); String errMsg = " You can still save the repository and start creating " + "policies, but you would not be able to use autocomplete for " + "resource names. Check ranger_admin.log for more info."; if (topologyNameMatching == null || topologyNameMatching.trim().isEmpty()) { topologyNameMatching = ""; } String decryptedPwd=null; try { decryptedPwd=PasswordUtils.decryptPassword(password); } catch(Exception ex) { LOG.info("Password decryption failed; trying knox connection with received password string"); decryptedPwd=null; } finally { if (decryptedPwd==null) { decryptedPwd=password; } } try { Client client = null; ClientResponse response = null; try { client = Client.create(); client.addFilter(new HTTPBasicAuthFilter(userName, decryptedPwd)); WebResource webResource = client.resource(knoxUrl); response = webResource.accept(EXPECTED_MIME_TYPE) .get(ClientResponse.class); LOG.debug("Knox topology list response: " + response); if (response != null) { if (response.getStatus() == 200) { String jsonString = response.getEntity(String.class); LOG.debug("Knox topology list response JSON string: "+ jsonString); ObjectMapper objectMapper = new ObjectMapper(); JsonNode rootNode = objectMapper.readTree(jsonString); JsonNode topologyNode = rootNode.findValue("topology"); if (topologyNode == null) { return topologyList; } Iterator<JsonNode> elements = topologyNode.getElements(); while (elements.hasNext()) { JsonNode element = elements.next(); JsonNode nameElement = element.get("name"); if (nameElement != null) { String topologyName = nameElement.getValueAsText(); LOG.debug("Found Knox topologyName: " + topologyName); if (knoxTopologyList != null && topologyName != null && knoxTopologyList.contains(topologyNameMatching)) { continue; } if (topologyName != null && ( "*".equals(topologyNameMatching) || topologyName.startsWith(topologyNameMatching))) { topologyList.add(topologyName); } } } } else { LOG.error("Got invalid REST response from: " + knoxUrl + ", responseStatus: " + response.getStatus()); } } else { String msgDesc = "Unable to get a valid response for " + "getTopologyList() call for KnoxUrl : [" + knoxUrl + "] - got null response."; LOG.error(msgDesc); HadoopException hdpException = new HadoopException(msgDesc); hdpException.generateResponseDataMap(false, msgDesc, msgDesc + errMsg, null, null); throw hdpException; } } finally { if (response != null) { response.close(); } if (client != null) { client.destroy(); } } } catch (HadoopException he) { throw he; } catch (Throwable t) { String msgDesc = "Exception on REST call to KnoxUrl : " + knoxUrl + "."; HadoopException hdpException = new HadoopException(msgDesc, t); LOG.error(msgDesc, t); hdpException.generateResponseDataMap(false, BaseClient.getMessage(t), msgDesc + errMsg, null, null); throw hdpException; } if (LOG.isDebugEnabled()) { LOG.debug("<== KnoxClient.getTopologyList() Topology Matching: " + topologyNameMatching + " Result : " + topologyList.toString()); } return topologyList; } public List<String> getServiceList(List<String> knoxTopologyList, String serviceNameMatching, List<String> knoxServiceList) { // sample URI: .../admin/api/v1/topologies/<topologyName> if (LOG.isDebugEnabled()) { LOG.debug("==> KnoxClient.getServiceList() Service Name: " + serviceNameMatching ); } List<String> serviceList = new ArrayList<String>(); String errMsg = " You can still save the repository and start creating " + "policies, but you would not be able to use autocomplete for " + "resource names. Check ranger_admin.log for more info."; if (serviceNameMatching == null || serviceNameMatching.trim().isEmpty()) { serviceNameMatching = ""; } String decryptedPwd=null; try { decryptedPwd=PasswordUtils.decryptPassword(password); } catch(Exception ex) { LOG.info("Password decryption failed; trying knox connection with received password string"); decryptedPwd=null; } finally { if (decryptedPwd==null) { decryptedPwd=password; } } try { Client client = null; ClientResponse response = null; try { client = Client.create(); client.addFilter(new HTTPBasicAuthFilter(userName, decryptedPwd)); for (String topologyName : knoxTopologyList) { WebResource webResource = client.resource(knoxUrl + "/" + topologyName); response = webResource.accept(EXPECTED_MIME_TYPE) .get(ClientResponse.class); LOG.debug("Knox service lookup response: " + response); if (response != null) { if (response.getStatus() == 200) { String jsonString = response.getEntity(String.class); LOG.debug("Knox service lookup response JSON string: " + jsonString); ObjectMapper objectMapper = new ObjectMapper(); JsonNode rootNode = objectMapper.readTree(jsonString); JsonNode topologyNode = rootNode.findValue("topology"); if (topologyNode != null) { JsonNode servicesNode = topologyNode.get("service"); if (servicesNode != null) { Iterator<JsonNode> services = servicesNode.getElements(); while (services.hasNext()) { JsonNode service = services.next(); JsonNode serviceElement = service.get("role"); if (serviceElement != null) { String serviceName = serviceElement.getValueAsText(); LOG.debug("Knox serviceName: " + serviceName); if (serviceName == null || (knoxServiceList != null && knoxServiceList.contains(serviceName))){ continue; } if (serviceName.startsWith(serviceNameMatching) || "*".equals(serviceNameMatching)) { serviceList.add(serviceName); } } } } } } else { LOG.error("Got invalid REST response from: " + knoxUrl + ", responsStatus: " + response.getStatus()); } } else { String msgDesc = "Unable to get a valid response for " + "getServiceList() call for KnoxUrl : [" + knoxUrl + "] - got null response."; LOG.error(msgDesc); HadoopException hdpException = new HadoopException(msgDesc); hdpException.generateResponseDataMap(false, msgDesc, msgDesc + errMsg, null, null); throw hdpException; } } } finally{ if (response != null) { response.close(); } if (client != null) { client.destroy(); } } } catch (HadoopException he) { throw he; } catch (Throwable t) { String msgDesc = "Exception on REST call to KnoxUrl : " + knoxUrl + "."; HadoopException hdpException = new HadoopException(msgDesc, t); LOG.error(msgDesc, t); hdpException.generateResponseDataMap(false, BaseClient.getMessage(t), msgDesc + errMsg, null, null); throw hdpException; } return serviceList; } public static void main(String[] args) { KnoxClient knoxClient = null; if (args.length != 3) { System.err.println("USAGE: java " + KnoxClient.class.getName() + " knoxUrl userName password [sslConfigFileName]"); System.exit(1); } knoxClient = new KnoxClient(args[0], args[1], args[2]); List<String> topologyList = knoxClient.getTopologyList("",null); if ((topologyList == null) || topologyList.isEmpty()) { System.out.println("No knox topologies found"); } else { List<String> serviceList = knoxClient.getServiceList(topologyList,"*",null); if ((serviceList == null) || serviceList.isEmpty()) { System.out.println("No services found for knox topology: "); } else { for (String service : serviceList) { System.out.println(" Found service for topology: " + service ); } } } } public static Map<String, Object> connectionTest(String serviceName, Map<String, String> configs) { String errMsg = " You can still save the repository and start creating " + "policies, but you would not be able to use autocomplete for " + "resource names. Check ranger_admin.log for more info."; boolean connectivityStatus = false; Map<String, Object> responseData = new HashMap<String, Object>(); KnoxClient knoxClient = getKnoxClient(serviceName, configs); List<String> strList = getKnoxResources(knoxClient, "", null,null,null); if (strList != null && (strList.size() != 0)) { connectivityStatus = true; } if (connectivityStatus) { String successMsg = "ConnectionTest Successful"; BaseClient.generateResponseDataMap(connectivityStatus, successMsg, successMsg, null, null, responseData); } else { String failureMsg = "Unable to retrieve any topologies/services using given parameters."; BaseClient.generateResponseDataMap(connectivityStatus, failureMsg, failureMsg + errMsg, null, null, responseData); } return responseData; } public static KnoxClient getKnoxClient(String serviceName, Map<String, String> configs) { KnoxClient knoxClient = null; if(LOG.isDebugEnabled()){ LOG.debug("Getting knoxClient for ServiceName: " + serviceName); LOG.debug("configMap: " + configs); } String errMsg = " You can still save the repository and start creating " + "policies, but you would not be able to use autocomplete for " + "resource names. Check ranger_admin.log for more info."; if ( configs != null && !configs.isEmpty()) { String knoxUrl = configs.get("knox.url"); String knoxAdminUser = configs.get("username"); String knoxAdminPassword = configs.get("password"); knoxClient = new KnoxClient(knoxUrl, knoxAdminUser, knoxAdminPassword); } else { String msgDesc = "Could not connect as Connection ConfigMap is empty."; LOG.error(msgDesc); HadoopException hdpException = new HadoopException(msgDesc); hdpException.generateResponseDataMap(false, msgDesc, msgDesc + errMsg, null, null); throw hdpException; } return knoxClient; } public static List<String> getKnoxResources(final KnoxClient knoxClient, String topologyName, String serviceName, List<String> knoxTopologyList, List<String> knoxServiceList) { if (LOG.isDebugEnabled() ) { LOG.debug("==> KnoxClient.getKnoxResource " + "topology: " + topologyName + "Service Name: " + serviceName); } List<String> resultList = new ArrayList<String>(); String errMsg = " You can still save the repository and start creating " + "policies, but you would not be able to use autocomplete for " + "resource names. Check ranger_admin.log for more info."; try { if (knoxClient == null) { // LOG.error("Unable to get knox resources: knoxClient is null"); // return new ArrayList<String>(); String msgDesc = "Unable to get knox resources: knoxClient is null."; LOG.error(msgDesc); HadoopException hdpException = new HadoopException(msgDesc); hdpException.generateResponseDataMap(false, msgDesc, msgDesc + errMsg, null, null); throw hdpException; } final Callable<List<String>> callableObj; if (serviceName != null) { final String finalServiceNameMatching = serviceName.trim(); final List<String> finalknoxServiceList = knoxServiceList; final List<String> finalTopologyList = knoxTopologyList; callableObj = new Callable<List<String>>() { @Override public List<String> call() { return knoxClient.getServiceList(finalTopologyList, finalServiceNameMatching,finalknoxServiceList); } }; } else { final String finalTopologyNameMatching = (topologyName == null) ? "" : topologyName.trim(); final List<String> finalknoxTopologyList = knoxTopologyList; callableObj = new Callable<List<String>>() { @Override public List<String> call() { return knoxClient .getTopologyList(finalTopologyNameMatching,finalknoxTopologyList); } }; } resultList = timedTask(callableObj, 5, TimeUnit.SECONDS); } catch (HadoopException he) { throw he; } catch (Exception e) { String msgDesc = "Unable to get knox resources."; LOG.error(msgDesc, e); HadoopException hdpException = new HadoopException(msgDesc); hdpException.generateResponseDataMap(false, BaseClient.getMessage(e), msgDesc + errMsg, null, null); throw hdpException; } if (LOG.isDebugEnabled()) { LOG.debug("<== KnoxClient.getKnoxResources() Result : "+ resultList ); } return resultList; } public static <T> T timedTask(Callable<T> callableObj, long timeout, TimeUnit timeUnit) throws Exception { return callableObj.call(); } }