/* * 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.kafka.common.security; import org.apache.kafka.common.config.SaslConfigs; import org.apache.kafka.common.config.types.Password; import org.apache.kafka.common.network.ListenerName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.security.auth.login.AppConfigurationEntry; import javax.security.auth.login.Configuration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; public class JaasContext { private static final Logger LOG = LoggerFactory.getLogger(JaasUtils.class); private static final String GLOBAL_CONTEXT_NAME_SERVER = "KafkaServer"; private static final String GLOBAL_CONTEXT_NAME_CLIENT = "KafkaClient"; /** * Returns an instance of this class. * * For contextType SERVER, the context will contain the default Configuration and the context name will be one of: * * 1. Lowercased listener name followed by a period and the string `KafkaServer` * 2. The string `KafkaServer` * * If both are valid entries in the JAAS configuration, the first option is chosen. * * For contextType CLIENT, if JAAS configuration property @link SaslConfigs#SASL_JAAS_CONFIG} is specified, * the configuration object is created by parsing the property value. Otherwise, the default Configuration * is returned. The context name is always `KafkaClient`. * * @throws IllegalArgumentException if JAAS configuration property is specified for contextType SERVER, if * listenerName is not defined for contextType SERVER of if listenerName is defined for contextType CLIENT. */ public static JaasContext load(JaasContext.Type contextType, ListenerName listenerName, Map<String, ?> configs) { String listenerContextName; String globalContextName; switch (contextType) { case CLIENT: if (listenerName != null) throw new IllegalArgumentException("listenerName should be null for CLIENT"); globalContextName = GLOBAL_CONTEXT_NAME_CLIENT; listenerContextName = null; break; case SERVER: if (listenerName == null) throw new IllegalArgumentException("listenerName should not be null for SERVER"); globalContextName = GLOBAL_CONTEXT_NAME_SERVER; listenerContextName = listenerName.value().toLowerCase(Locale.ROOT) + "." + GLOBAL_CONTEXT_NAME_SERVER; break; default: throw new IllegalArgumentException("Unexpected context type " + contextType); } return load(contextType, listenerContextName, globalContextName, configs); } static JaasContext load(JaasContext.Type contextType, String listenerContextName, String globalContextName, Map<String, ?> configs) { Password jaasConfigArgs = (Password) configs.get(SaslConfigs.SASL_JAAS_CONFIG); if (jaasConfigArgs != null) { if (contextType == JaasContext.Type.SERVER) throw new IllegalArgumentException("JAAS config property not supported for server"); else { JaasConfig jaasConfig = new JaasConfig(globalContextName, jaasConfigArgs.value()); AppConfigurationEntry[] clientModules = jaasConfig.getAppConfigurationEntry(globalContextName); int numModules = clientModules == null ? 0 : clientModules.length; if (numModules != 1) throw new IllegalArgumentException("JAAS config property contains " + numModules + " login modules, should be 1 module"); return new JaasContext(globalContextName, contextType, jaasConfig); } } else return defaultContext(contextType, listenerContextName, globalContextName); } private static JaasContext defaultContext(JaasContext.Type contextType, String listenerContextName, String globalContextName) { String jaasConfigFile = System.getProperty(JaasUtils.JAVA_LOGIN_CONFIG_PARAM); if (jaasConfigFile == null) { if (contextType == Type.CLIENT) { LOG.debug("System property '" + JaasUtils.JAVA_LOGIN_CONFIG_PARAM + "' and Kafka SASL property '" + SaslConfigs.SASL_JAAS_CONFIG + "' are not set, using default JAAS configuration."); } else { LOG.debug("System property '" + JaasUtils.JAVA_LOGIN_CONFIG_PARAM + "' is not set, using default JAAS " + "configuration."); } } Configuration jaasConfig = Configuration.getConfiguration(); AppConfigurationEntry[] configEntries = null; String contextName = globalContextName; if (listenerContextName != null) { configEntries = jaasConfig.getAppConfigurationEntry(listenerContextName); if (configEntries != null) contextName = listenerContextName; } if (configEntries == null) configEntries = jaasConfig.getAppConfigurationEntry(globalContextName); if (configEntries == null) { String listenerNameText = listenerContextName == null ? "" : " or '" + listenerContextName + "'"; String errorMessage = "Could not find a '" + globalContextName + "'" + listenerNameText + " entry in the JAAS " + "configuration. System property '" + JaasUtils.JAVA_LOGIN_CONFIG_PARAM + "' is " + (jaasConfigFile == null ? "not set" : jaasConfigFile); throw new IllegalArgumentException(errorMessage); } return new JaasContext(contextName, contextType, jaasConfig); } /** * The type of the SASL login context, it should be SERVER for the broker and CLIENT for the clients (consumer, producer, * etc.). This is used to validate behaviour (e.g. some functionality is only available in the broker or clients). */ public enum Type { CLIENT, SERVER; } private final String name; private final Type type; private final Configuration configuration; private final List<AppConfigurationEntry> configurationEntries; public JaasContext(String name, Type type, Configuration configuration) { this.name = name; this.type = type; this.configuration = configuration; AppConfigurationEntry[] entries = configuration.getAppConfigurationEntry(name); if (entries == null) throw new IllegalArgumentException("Could not find a '" + name + "' entry in this JAAS configuration."); this.configurationEntries = Collections.unmodifiableList(new ArrayList<>(Arrays.asList(entries))); } public String name() { return name; } public Type type() { return type; } public Configuration configuration() { return configuration; } public List<AppConfigurationEntry> configurationEntries() { return configurationEntries; } /** * Returns the configuration option for <code>key</code> from this context. * If login module name is specified, return option value only from that module. */ public String configEntryOption(String key, String loginModuleName) { for (AppConfigurationEntry entry : configurationEntries) { if (loginModuleName != null && !loginModuleName.equals(entry.getLoginModuleName())) continue; Object val = entry.getOptions().get(key); if (val != null) return (String) val; } return null; } }