/* * 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.server.tomcat; import java.io.File; import java.io.IOException; import java.security.PrivilegedAction; import java.util.Date; import java.util.Iterator; import java.util.Properties; import java.util.logging.Logger; import java.util.List; import javax.servlet.ServletException; import org.apache.catalina.Context; import org.apache.catalina.LifecycleException; import org.apache.catalina.connector.Connector; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.valves.AccessLogValve; import org.apache.hadoop.security.SecureClientLogin; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.alias.CredentialProvider; import org.apache.hadoop.security.alias.CredentialProviderFactory; import org.apache.hadoop.security.alias.JavaKeyStoreProvider; import org.apache.ranger.plugin.util.XMLUtils; import javax.security.auth.Subject; public class EmbeddedServer { private static final Logger LOG = Logger.getLogger(EmbeddedServer.class .getName()); private static final String DEFAULT_NAME_RULE = "DEFAULT"; private static final String DEFAULT_CONFIG_FILENAME = "ranger-admin-default-site.xml"; private static final String CORE_SITE_CONFIG_FILENAME = "core-site.xml"; private static final String DEFAULT_WEBAPPS_ROOT_FOLDER = "webapps"; private static String configFile = "ranger-admin-site.xml"; private static final String AUTH_TYPE_KERBEROS = "kerberos"; private static final String AUTHENTICATION_TYPE = "hadoop.security.authentication"; private static final String ADMIN_USER_PRINCIPAL = "ranger.admin.kerberos.principal"; private static final String ADMIN_USER_KEYTAB = "ranger.admin.kerberos.keytab"; private static final String ADMIN_NAME_RULES = "hadoop.security.auth_to_local"; private static final String ADMIN_SERVER_NAME = "rangeradmin"; private Properties serverConfigProperties = new Properties(); public static void main(String[] args) { new EmbeddedServer(args).start(); } public EmbeddedServer(String[] args) { if (args.length > 0) { configFile = args[0]; } XMLUtils.loadConfig(CORE_SITE_CONFIG_FILENAME, serverConfigProperties); XMLUtils.loadConfig(DEFAULT_CONFIG_FILENAME, serverConfigProperties); XMLUtils.loadConfig(configFile, serverConfigProperties); } public static int DEFAULT_SHUTDOWN_PORT = 6185; public static String DEFAULT_SHUTDOWN_COMMAND = "SHUTDOWN"; public void start() { final Tomcat server = new Tomcat(); String logDir = null; logDir = getConfig("logdir"); if (logDir == null) { logDir = getConfig("kms.log.dir"); } String servername = getConfig("servername"); String hostName = getConfig("ranger.service.host"); int serverPort = getIntConfig("ranger.service.http.port", 6181); int sslPort = getIntConfig("ranger.service.https.port", -1); int shutdownPort = getIntConfig("ranger.service.shutdown.port",DEFAULT_SHUTDOWN_PORT); String shutdownCommand = getConfig("ranger.service.shutdown.command",DEFAULT_SHUTDOWN_COMMAND); server.setHostname(hostName); server.setPort(serverPort); server.getServer().setPort(shutdownPort); server.getServer().setShutdown(shutdownCommand); boolean isHttpsEnabled = Boolean.valueOf(getConfig("ranger.service.https.attrib.ssl.enabled", "false")); boolean ajpEnabled = Boolean.valueOf(getConfig("ajp.enabled", "false")); if (ajpEnabled) { Connector ajpConnector = new Connector( "org.apache.coyote.ajp.AjpNioProtocol"); ajpConnector.setPort(serverPort); ajpConnector.setProperty("protocol", "AJP/1.3"); server.getService().addConnector(ajpConnector); // Making this as a default connector server.setConnector(ajpConnector); LOG.info("Created AJP Connector"); } else if ((sslPort > 0) && isHttpsEnabled) { Connector ssl = new Connector(); ssl.setPort(sslPort); ssl.setSecure(true); ssl.setScheme("https"); ssl.setAttribute("SSLEnabled", "true"); ssl.setAttribute("sslProtocol", getConfig("ranger.service.https.attrib.ssl.protocol", "TLS")); String clientAuth=getConfig("ranger.service.https.attrib.clientAuth", "false"); if("false".equalsIgnoreCase(clientAuth)){ clientAuth=getConfig("ranger.service.https.attrib.client.auth", "want"); } ssl.setAttribute("clientAuth",clientAuth); String providerPath=getConfig("ranger.credential.provider.path"); String keyAlias= getConfig("ranger.service.https.attrib.keystore.credential.alias","keyStoreCredentialAlias"); String keystorePass=null; if(providerPath!=null && keyAlias!=null){ keystorePass=getDecryptedString(providerPath.trim(), keyAlias.trim()); if(keystorePass==null || keystorePass.trim().isEmpty() || "none".equalsIgnoreCase(keystorePass.trim())){ keystorePass=getConfig("ranger.service.https.attrib.keystore.pass"); } } ssl.setAttribute("keyAlias", getConfig("ranger.service.https.attrib.keystore.keyalias","rangeradmin")); ssl.setAttribute("keystorePass", keystorePass); ssl.setAttribute("keystoreFile", getKeystoreFile()); String defaultEnabledProtocols = "SSLv2Hello, TLSv1, TLSv1.1, TLSv1.2"; String enabledProtocols = getConfig("ranger.service.https.attrib.ssl.enabled.protocols", defaultEnabledProtocols); ssl.setAttribute("sslEnabledProtocols", enabledProtocols); server.getService().addConnector(ssl); // // Making this as a default connector // server.setConnector(ssl); } updateHttpConnectorAttribConfig(server); File logDirectory = new File(logDir); if (!logDirectory.exists()) { logDirectory.mkdirs(); } AccessLogValve valve = new AccessLogValve(); valve.setRotatable(true); valve.setAsyncSupported(true); valve.setBuffered(false); valve.setEnabled(true); valve.setFileDateFormat(getConfig("ranger.accesslog.dateformat", "yyyy-MM-dd.HH")); valve.setDirectory(logDirectory.getAbsolutePath()); valve.setSuffix(".log"); String logPattern = getConfig("ranger.accesslog.pattern", "%h %l %u %t \"%r\" %s %b"); valve.setPattern(logPattern); server.getHost().getPipeline().addValve(valve); try { String webapp_dir = getConfig("xa.webapp.dir"); if (webapp_dir == null || webapp_dir.trim().isEmpty()) { // If webapp location property is not set, then let's derive // from catalina_base String catalina_base = getConfig("catalina.base"); if (catalina_base == null || catalina_base.trim().isEmpty()) { LOG.severe("Tomcat Server failed to start: catalina.base and/or xa.webapp.dir is not set"); System.exit(1); } webapp_dir = catalina_base + File.separator + "webapp"; LOG.info("Deriving webapp folder from catalina.base property. folder=" + webapp_dir); } //String webContextName = getConfig("xa.webapp.contextName", "/"); String webContextName = getConfig("ranger.contextName", "/"); if (webContextName == null) { webContextName = "/"; } else if (!webContextName.startsWith("/")) { LOG.info("Context Name [" + webContextName + "] is being loaded as [ /" + webContextName + "]"); webContextName = "/" + webContextName; } File wad = new File(webapp_dir); if (wad.isDirectory()) { LOG.info("Webapp file =" + webapp_dir + ", webAppName = " + webContextName); } else if (wad.isFile()) { File webAppDir = new File(DEFAULT_WEBAPPS_ROOT_FOLDER); if (!webAppDir.exists()) { webAppDir.mkdirs(); } LOG.info("Webapp file =" + webapp_dir + ", webAppName = " + webContextName); } LOG.info("Adding webapp [" + webContextName + "] = path [" + webapp_dir + "] ....."); Context webappCtx = server.addWebapp(webContextName, new File( webapp_dir).getAbsolutePath()); webappCtx.init(); LOG.info("Finished init of webapp [" + webContextName + "] = path [" + webapp_dir + "]."); } catch (ServletException e1) { LOG.severe("Tomcat Server failed to add webapp:" + e1.toString()); e1.printStackTrace(); } catch (LifecycleException lce) { LOG.severe("Tomcat Server failed to start webapp:" + lce.toString()); lce.printStackTrace(); } if (servername.equalsIgnoreCase(ADMIN_SERVER_NAME)) { String keytab = getConfig(ADMIN_USER_KEYTAB); String principal = null; try { principal = SecureClientLogin.getPrincipal(getConfig(ADMIN_USER_PRINCIPAL), hostName); } catch (IOException ignored) { LOG.warning("Failed to get ranger.admin.kerberos.principal. Reason: " + ignored.toString()); } String nameRules = getConfig(ADMIN_NAME_RULES); if (nameRules == null || nameRules.length() == 0) { LOG.info("Name is empty. Setting Name Rule as 'DEFAULT'"); nameRules = DEFAULT_NAME_RULE; } if (getConfig(AUTHENTICATION_TYPE) != null && getConfig(AUTHENTICATION_TYPE).trim().equalsIgnoreCase(AUTH_TYPE_KERBEROS) && SecureClientLogin.isKerberosCredentialExists(principal,keytab)) { try{ LOG.info("Provided Kerberos Credential : Principal = " + principal + " and Keytab = " + keytab); Subject sub = SecureClientLogin.loginUserFromKeytab(principal, keytab, nameRules); Subject.doAs(sub, new PrivilegedAction<Void>() { @Override public Void run() { LOG.info("Starting Server using kerberos credential"); startServer(server); return null; } }); } catch (Exception e) { LOG.severe("Tomcat Server failed to start:" + e.toString()); e.printStackTrace(); } } else { startServer(server); } } else { startServer(server); } } private void startServer(final Tomcat server) { try { server.start(); server.getServer().await(); shutdownServer(); } catch (LifecycleException e) { LOG.severe("Tomcat Server failed to start:" + e.toString()); e.printStackTrace(); } catch (Exception e) { LOG.severe("Tomcat Server failed to start:" + e.toString()); e.printStackTrace(); } } private String getKeystoreFile() { String keystoreFile=getConfig("ranger.service.https.attrib.keystore.file"); if (keystoreFile == null || keystoreFile.trim().isEmpty()) { // new property not configured, lets use the old property keystoreFile = getConfig("ranger.https.attrib.keystore.file"); } return keystoreFile; } protected String getConfig(String key) { String value = serverConfigProperties.getProperty(key); if (value == null || value.trim().isEmpty()) { // Value not found in properties file, let's try to get from // System's property value = System.getProperty(key); } return value; } protected String getConfig(String key, String defaultValue) { String ret = getConfig(key); if (ret == null) { ret = defaultValue; } return ret; } protected int getIntConfig(String key, int defaultValue) { int ret = defaultValue; String retStr = getConfig(key); try { if (retStr != null) { ret = Integer.parseInt(retStr); } } catch (Exception err) { LOG.warning(retStr + " can't be parsed to int. Reason: " + err.toString()); } return ret; } public void shutdownServer() { int timeWaitForShutdownInSeconds = getIntConfig( "service.waitTimeForForceShutdownInSeconds", 0); if (timeWaitForShutdownInSeconds > 0) { long endTime = System.currentTimeMillis() + (timeWaitForShutdownInSeconds * 1000L); LOG.info("Will wait for all threads to shutdown gracefully. Final shutdown Time: " + new Date(endTime)); while (System.currentTimeMillis() < endTime) { int activeCount = Thread.activeCount(); if (activeCount == 0) { LOG.info("Number of active threads = " + activeCount + "."); break; } else { LOG.info("Number of active threads = " + activeCount + ". Waiting for all threads to shutdown ..."); try { Thread.sleep(5000L); } catch (InterruptedException e) { LOG.warning("shutdownServer process is interrupted with exception: " + e); break; } } } } LOG.info("Shuting down the Server."); System.exit(0); } protected long getLongConfig(String key, long defaultValue) { long ret = defaultValue; String retStr = getConfig(key); try{ if (retStr != null) { ret = Long.parseLong(retStr); } }catch(Exception err){ LOG.warning(retStr + " can't be parsed to long. Reason: " + err.toString()); } return ret; } public void updateHttpConnectorAttribConfig(Tomcat server) { server.getConnector().setAllowTrace(Boolean.valueOf(getConfig("ranger.service.http.connector.attrib.allowTrace","false"))); server.getConnector().setAsyncTimeout(getLongConfig("ranger.service.http.connector.attrib.asyncTimeout", 10000)); server.getConnector().setEnableLookups(Boolean.valueOf(getConfig("ranger.service.http.connector.attrib.enableLookups","false"))); server.getConnector().setMaxHeaderCount(getIntConfig("ranger.service.http.connector.attrib.maxHeaderCount", 100)); server.getConnector().setMaxParameterCount(getIntConfig("ranger.service.http.connector.attrib.maxParameterCount", 10000)); server.getConnector().setMaxPostSize(getIntConfig("ranger.service.http.connector.attrib.maxPostSize", 2097152)); server.getConnector().setMaxSavePostSize(getIntConfig("ranger.service.http.connector.attrib.maxSavePostSize", 4096)); server.getConnector().setParseBodyMethods(getConfig("ranger.service.http.connector.attrib.methods", "POST")); server.getConnector().setURIEncoding(getConfig("ranger.service.http.connector.attrib.URIEncoding", "UTF-8")); Iterator<Object> iterator = serverConfigProperties.keySet().iterator(); String key = null; String property = null; while (iterator.hasNext()){ key = iterator.next().toString(); if(key != null && key.startsWith("ranger.service.http.connector.property.")){ property = key.replace("ranger.service.http.connector.property.",""); server.getConnector().setProperty(property,getConfig(key)); LOG.info(property + ":" + server.getConnector().getProperty(property)); } } } public String getDecryptedString(String CrendentialProviderPath,String alias) { String credential=null; try{ if(CrendentialProviderPath==null || alias==null||CrendentialProviderPath.trim().isEmpty()||alias.trim().isEmpty()){ return null; } char[] pass = null; Configuration conf = new Configuration(); String crendentialProviderPrefixJceks=JavaKeyStoreProvider.SCHEME_NAME + "://file"; String crendentialProviderPrefixLocalJceks="localjceks://file"; crendentialProviderPrefixJceks=crendentialProviderPrefixJceks.toLowerCase(); CrendentialProviderPath=CrendentialProviderPath.trim(); alias=alias.trim(); if(CrendentialProviderPath.toLowerCase().startsWith(crendentialProviderPrefixJceks) || CrendentialProviderPath.toLowerCase().startsWith(crendentialProviderPrefixLocalJceks)){ conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH,CrendentialProviderPath); }else{ if(CrendentialProviderPath.startsWith("/")){ conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH,JavaKeyStoreProvider.SCHEME_NAME + "://file" + CrendentialProviderPath); }else{ conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH,JavaKeyStoreProvider.SCHEME_NAME + "://file/" + CrendentialProviderPath); } } List<CredentialProvider> providers = CredentialProviderFactory.getProviders(conf); List<String> aliasesList; CredentialProvider.CredentialEntry credEntry=null; for(CredentialProvider provider: providers) { //System.out.println("Credential Provider :" + provider); aliasesList=provider.getAliases(); if(aliasesList!=null && aliasesList.contains(alias.toLowerCase())){ credEntry=null; credEntry= provider.getCredentialEntry(alias); pass = credEntry.getCredential(); if(pass!=null && pass.length>0){ credential=String.valueOf(pass); break; } } } }catch(Exception ex){ LOG.severe("CredentialReader failed while decrypting provided string. Reason: " + ex.toString()); credential=null; } return credential; } }