org.apache.ranger.audit.utils.InMemoryJAASConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ranger.audit.utils.InMemoryJAASConfiguration.java

Source

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