| /** |
| * 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.audit.utils; |
| |
| import org.apache.commons.collections.MapUtils; |
| import org.apache.commons.lang.ArrayUtils; |
| import org.apache.hadoop.security.SecurityUtil; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.SortedSet; |
| import java.util.StringTokenizer; |
| import java.util.TreeSet; |
| |
| import javax.security.auth.login.AppConfigurationEntry; |
| import javax.security.auth.login.Configuration; |
| |
| /** |
| * InMemoryJAASConfiguration |
| * |
| * An utility class - which has a static method init to load all JAAS configuration from Application properties file (eg: kafka.properties) and |
| * set it as part of the default lookup configuration for all JAAS configuration lookup. |
| * |
| * Example settings in application.properties: |
| * |
| * xasecure.audit.jaas.KafkaClient.loginModuleName = com.sun.security.auth.module.Krb5LoginModule |
| * xasecure.audit.jaas.KafkaClient.loginModuleControlFlag = required |
| * xasecure.audit.jaas.KafkaClient.option.useKeyTab = true |
| * xasecure.audit.jaas.KafkaClient.option.storeKey = true |
| * xasecure.audit.jaas.KafkaClient.option.serviceName = kafka |
| * xasecure.audit.jaas.KafkaClient.option.keyTab = /etc/security/keytabs/kafka_client.keytab |
| * xasecure.audit.jaas.KafkaClient.option.principal = kafka-client-1@EXAMPLE.COM |
| |
| * xasecure.audit.jaas.MyClient.0.loginModuleName = com.sun.security.auth.module.Krb5LoginModule |
| * xasecure.audit.jaas.MyClient.0.loginModuleControlFlag = required |
| * xasecure.audit.jaas.MyClient.0.option.useKeyTab = true |
| * xasecure.audit.jaas.MyClient.0.option.storeKey = true |
| * xasecure.audit.jaas.MyClient.0.option.serviceName = kafka |
| * xasecure.audit.jaas.MyClient.0.option.keyTab = /etc/security/keytabs/kafka_client.keytab |
| * xasecure.audit.jaas.MyClient.0.option.principal = kafka-client-1@EXAMPLE.COM |
| * |
| * xasecure.audit.jaas.MyClient.1.loginModuleName = com.sun.security.auth.module.Krb5LoginModule |
| * xasecure.audit.jaas.MyClient.1.loginModuleControlFlag = optional |
| * xasecure.audit.jaas.MyClient.1.option.useKeyTab = true |
| * xasecure.audit.jaas.MyClient.1.option.storeKey = true |
| * xasecure.audit.jaas.MyClient.1.option.serviceName = kafka |
| * xasecure.audit.jaas.MyClient.1.option.keyTab = /etc/security/keytabs/kafka_client.keytab |
| * xasecure.audit.jaas.MyClient.1.option.principal = kafka-client-1@EXAMPLE.COM |
| |
| * This will set the JAAS configuration - equivalent to the jaas.conf file entries: |
| * KafkaClient { |
| * com.sun.security.auth.module.Krb5LoginModule required |
| * useKeyTab=true |
| * storeKey=true |
| * serviceName=kafka |
| * keyTab="/etc/security/keytabs/kafka_client.keytab" |
| * principal="kafka-client-1@EXAMPLE.COM"; |
| * }; |
| * MyClient { |
| * com.sun.security.auth.module.Krb5LoginModule required |
| * useKeyTab=true |
| * storeKey=true |
| * serviceName=kafka keyTab="/etc/security/keytabs/kafka_client.keytab" |
| * principal="kafka-client-1@EXAMPLE.COM"; |
| * }; |
| * MyClient { |
| * com.sun.security.auth.module.Krb5LoginModule optional |
| * useKeyTab=true |
| * storeKey=true |
| * serviceName=kafka |
| * keyTab="/etc/security/keytabs/kafka_client.keytab" |
| * principal="kafka-client-1@EXAMPLE.COM"; |
| * }; |
| * |
| * Here is the syntax for atlas.properties to add JAAS configuration: |
| * |
| * The property name has to begin with 'xasecure.audit.jaas.' + clientId (in case of Kafka client, |
| * it expects the clientId to be KafkaClient). |
| * The following property must be there to specify the JAAS loginModule name |
| * 'xasecure.audit.jaas.' +' + clientId + '.loginModuleName' |
| * The following optional property should be set to specify the loginModuleControlFlag |
| * 'xasecure.audit.jaas.' +' + clientId + '.loginModuleControlFlag' |
| * Default value : required , Possible values: required, optional, sufficient, requisite |
| * Then you can add additional optional parameters as options for the configuration using the following |
| * syntax: |
| * 'xasecure.audit.jaas.' +' + clientId + '.option.' + <optionName> = <optionValue> |
| * |
| * The current setup will lookup JAAS configration from the atlas-application.properties first, if not available, |
| * it will delegate to the original configuration |
| * |
| */ |
| |
| public final class InMemoryJAASConfiguration extends Configuration { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(InMemoryJAASConfiguration.class); |
| |
| private static final String JAAS_CONFIG_PREFIX_PARAM = "xasecure.audit.jaas."; |
| private static final String JAAS_CONFIG_LOGIN_MODULE_NAME_PARAM = "loginModuleName"; |
| private static final String JAAS_CONFIG_LOGIN_MODULE_CONTROL_FLAG_PARAM = "loginModuleControlFlag"; |
| private static final String JAAS_CONFIG_LOGIN_OPTIONS_PREFIX = "option"; |
| private static final String JAAS_PRINCIPAL_PROP = "principal"; |
| |
| private Configuration parent = null; |
| private Map<String, List<AppConfigurationEntry>> applicationConfigEntryMap = new HashMap<>(); |
| |
| public static void init(String propFile) throws Exception { |
| LOG.debug("==> InMemoryJAASConfiguration.init( {} ) ", propFile); |
| |
| InputStream in = null; |
| |
| try { |
| Properties properties = new Properties(); |
| in = ClassLoader.getSystemResourceAsStream(propFile); |
| if (in == null) { |
| if (!propFile.startsWith("/")) { |
| in = ClassLoader.getSystemResourceAsStream("/" + propFile); |
| } |
| if (in == null) { |
| in = new FileInputStream(new File(propFile)); |
| } |
| } |
| properties.load(in); |
| init(properties); |
| } catch (IOException e) { |
| throw new Exception("Failed to load JAAS application properties", e); |
| } finally { |
| if ( in != null) { |
| try { |
| in.close(); |
| } catch ( Exception e) { |
| //Ignore |
| } |
| } |
| } |
| LOG.debug("<== InMemoryJAASConfiguration.init( {} ) ", propFile); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public static void init(org.apache.commons.configuration.Configuration configuration) throws Exception { |
| LOG.debug("==> InMemoryJAASConfiguration.init()"); |
| |
| if (configuration != null && !configuration.isEmpty()) { |
| Properties properties = new Properties(); |
| Iterator<String> iterator = configuration.getKeys(); |
| while (iterator.hasNext()) { |
| String key = iterator.next(); |
| properties.put(key, configuration.getProperty(key)); |
| } |
| init(properties); |
| } else { |
| throw new Exception("Failed to load JAAS application properties: configuration NULL or empty!"); |
| } |
| |
| LOG.debug("<== InMemoryJAASConfiguration.init()"); |
| } |
| |
| public static void init(Properties properties) throws Exception { |
| LOG.debug("==> InMemoryJAASConfiguration.init()"); |
| |
| if (properties != null && MapUtils.isNotEmpty(properties)) { |
| InMemoryJAASConfiguration conf = new InMemoryJAASConfiguration(properties); |
| Configuration.setConfiguration(conf); |
| } else { |
| throw new Exception("Failed to load JAAS application properties: properties NULL or empty!"); |
| } |
| |
| LOG.debug("<== InMemoryJAASConfiguration.init()"); |
| } |
| |
| @Override |
| public AppConfigurationEntry[] getAppConfigurationEntry(String name) { |
| LOG.trace("==> InMemoryJAASConfiguration.getAppConfigurationEntry( {} )", name); |
| |
| AppConfigurationEntry[] ret = null; |
| if (parent != null) { |
| ret = parent.getAppConfigurationEntry(name); |
| } |
| if (ret == null || ret.length == 0) { |
| List<AppConfigurationEntry> retList = applicationConfigEntryMap.get(name); |
| if (retList != null && retList.size() > 0) { |
| int sz = retList.size(); |
| ret = new AppConfigurationEntry[sz]; |
| ret = retList.toArray(ret); |
| } |
| } |
| LOG.trace("<== InMemoryJAASConfiguration.getAppConfigurationEntry( {} ) : {}", name, ArrayUtils.toString(ret)); |
| return ret; |
| } |
| |
| private InMemoryJAASConfiguration(Properties prop) { |
| parent = Configuration.getConfiguration(); |
| initialize(prop); |
| } |
| |
| private void initialize(Properties properties) { |
| LOG.debug("==> InMemoryJAASConfiguration.initialize()"); |
| |
| int prefixLen = JAAS_CONFIG_PREFIX_PARAM.length(); |
| |
| Map<String, SortedSet<Integer>> jaasClients = new HashMap<>(); |
| for(String key : properties.stringPropertyNames()) { |
| if (key.startsWith(JAAS_CONFIG_PREFIX_PARAM)) { |
| String jaasKey = key.substring(prefixLen); |
| StringTokenizer tokenizer = new StringTokenizer(jaasKey, "."); |
| int tokenCount =tokenizer.countTokens(); |
| if (tokenCount > 0) { |
| String clientId = tokenizer.nextToken(); |
| SortedSet<Integer> indexList = jaasClients.get(clientId); |
| if (indexList == null) { |
| indexList = new TreeSet<Integer>(); |
| jaasClients.put(clientId, indexList); |
| } |
| String indexStr = tokenizer.nextToken(); |
| |
| int indexId = isNumeric(indexStr) ? Integer.parseInt(indexStr) : -1; |
| |
| Integer clientIdIndex = Integer.valueOf(indexId); |
| |
| if (!indexList.contains(clientIdIndex)) { |
| indexList.add(clientIdIndex); |
| } |
| |
| } |
| } |
| } |
| for(String jaasClient : jaasClients.keySet()) { |
| |
| for(Integer index : jaasClients.get(jaasClient)) { |
| |
| String keyPrefix = JAAS_CONFIG_PREFIX_PARAM + jaasClient + "."; |
| |
| if (index > -1) { |
| keyPrefix = keyPrefix + String.valueOf(index) + "."; |
| } |
| |
| String keyParam = keyPrefix + JAAS_CONFIG_LOGIN_MODULE_NAME_PARAM; |
| String loginModuleName = properties.getProperty(keyParam); |
| |
| if (loginModuleName == null) { |
| LOG.error("Unable to add JAAS configuration for " |
| + "client [" + jaasClient + "] as it is missing param [" + keyParam + "]." |
| + " Skipping JAAS config for [" + jaasClient + "]"); |
| continue; |
| } else { |
| loginModuleName = loginModuleName.trim(); |
| } |
| |
| keyParam = keyPrefix + JAAS_CONFIG_LOGIN_MODULE_CONTROL_FLAG_PARAM; |
| String controlFlag = properties.getProperty(keyParam); |
| |
| AppConfigurationEntry.LoginModuleControlFlag loginControlFlag = null; |
| if (controlFlag != null) { |
| controlFlag = controlFlag.trim().toLowerCase(); |
| if (controlFlag.equals("optional")) { |
| loginControlFlag = AppConfigurationEntry.LoginModuleControlFlag.OPTIONAL; |
| } else if (controlFlag.equals("requisite")) { |
| loginControlFlag = AppConfigurationEntry.LoginModuleControlFlag.REQUISITE; |
| } else if (controlFlag.equals("sufficient")) { |
| loginControlFlag = AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT; |
| } else if (controlFlag.equals("required")) { |
| loginControlFlag = AppConfigurationEntry.LoginModuleControlFlag.REQUIRED; |
| } else { |
| String validValues = "optional|requisite|sufficient|required"; |
| LOG.warn("Unknown JAAS configuration value for (" + keyParam |
| + ") = [" + controlFlag + "], valid value are [" + validValues |
| + "] using the default value, REQUIRED"); |
| loginControlFlag = AppConfigurationEntry.LoginModuleControlFlag.REQUIRED; |
| } |
| } else { |
| LOG.warn("Unable to find JAAS configuration (" |
| + keyParam + "); using the default value, REQUIRED"); |
| loginControlFlag = AppConfigurationEntry.LoginModuleControlFlag.REQUIRED; |
| } |
| |
| |
| Map<String, String> options = new HashMap<>(); |
| String optionPrefix = keyPrefix + JAAS_CONFIG_LOGIN_OPTIONS_PREFIX + "."; |
| int optionPrefixLen = optionPrefix.length(); |
| for(String key : properties.stringPropertyNames()) { |
| if (key.startsWith(optionPrefix)) { |
| String optionKey = key.substring(optionPrefixLen); |
| String optionVal = properties.getProperty(key); |
| if (optionVal != null) { |
| optionVal = optionVal.trim(); |
| |
| try { |
| if (optionKey.equalsIgnoreCase(JAAS_PRINCIPAL_PROP)) { |
| optionVal = SecurityUtil.getServerPrincipal(optionVal, (String) null); |
| } |
| } catch (IOException e) { |
| LOG.warn("Failed to build serverPrincipal. Using provided value:[" |
| + optionVal + "]"); |
| } |
| } |
| options.put(optionKey, optionVal); |
| } |
| } |
| |
| AppConfigurationEntry entry = new AppConfigurationEntry(loginModuleName, loginControlFlag, options); |
| |
| if (LOG.isDebugEnabled()) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("Adding client: [").append(jaasClient).append("{").append(index).append("}]\n"); |
| sb.append("\tloginModule: [").append(loginModuleName).append("]\n"); |
| sb.append("\tcontrolFlag: [").append(loginControlFlag).append("]\n"); |
| for (String key : options.keySet()) { |
| String val = options.get(key); |
| sb.append("\tOptions: [").append(key).append("] => [").append(val).append("]\n"); |
| } |
| LOG.debug(sb.toString()); |
| } |
| |
| List<AppConfigurationEntry> retList = applicationConfigEntryMap.get(jaasClient); |
| if (retList == null) { |
| retList = new ArrayList<AppConfigurationEntry>(); |
| applicationConfigEntryMap.put(jaasClient, retList); |
| } |
| retList.add(entry); |
| |
| |
| } |
| } |
| LOG.debug("<== InMemoryJAASConfiguration.initialize()"); |
| } |
| |
| private static boolean isNumeric(String str) { |
| return str.matches("-?\\d+(\\.\\d+)?"); //match a number with optional '-' and decimal. |
| } |
| } |