Commit 403c04b3 by Jon Maron

BUG-32834 servlet context listener supporting simple and kerberos logins

parent 12beefbb
......@@ -156,6 +156,25 @@
<groupId>org.mortbay.jetty</groupId>
<artifactId>jsp-2.1</artifactId>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-minikdc</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
</dependencies>
<build>
......@@ -339,6 +358,14 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<inherited>true</inherited>
<extensions>true</extensions>
</plugin>
</plugins>
</build>
</project>
/*
* 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.hadoop.metadata.web.listeners;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.util.Shell;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.io.IOException;
import java.net.InetAddress;
/**
* A listener capable of performing a simple or kerberos login.
*/
public class LoginListener implements ServletContextListener {
private static final Logger LOG = LoggerFactory
.getLogger(LoginListener.class);
public static final String AUTHENTICATION_METHOD = "authentication.method";
public static final String AUTHENTICATION_PRINCIPAL = "authentication.principal";
public static final String AUTHENTICATION_KEYTAB = "authentication.keytab";
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
/**
* Perform a SIMPLE login based on established OS identity or a kerberos based login using the configured
* principal and keytab (via application.properties).
* @param servletContextEvent
*/
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
// first, let's see if we're running in a hadoop cluster and have the env configured
boolean isHadoopCluster = isHadoopCluster();
Configuration hadoopConfig = isHadoopCluster ? getHadoopConfiguration() : new Configuration(false);
PropertiesConfiguration configuration = null;
try {
configuration = getPropertiesConfiguration();
} catch (ConfigurationException e) {
LOG.warn("Error reading application configuration", e);
}
if (!isHadoopCluster) {
// need to read the configured authentication choice and create the UGI configuration
String authMethod;
authMethod = configuration != null ? configuration.getString(AUTHENTICATION_METHOD) : null;
// getString may return null, and would like to log the nature of the default setting
if (authMethod == null) {
LOG.info("No authentication method configured. Defaulting to simple authentication");
authMethod = "simple";
}
SecurityUtil.setAuthenticationMethod(
UserGroupInformation.AuthenticationMethod.valueOf(authMethod.toUpperCase()),
hadoopConfig);
}
UserGroupInformation.setConfiguration(hadoopConfig);
UserGroupInformation ugi = null;
UserGroupInformation.AuthenticationMethod authenticationMethod =
SecurityUtil.getAuthenticationMethod(hadoopConfig);
try {
if (authenticationMethod == UserGroupInformation.AuthenticationMethod.SIMPLE) {
UserGroupInformation.loginUserFromSubject(null);
} else if (authenticationMethod == UserGroupInformation.AuthenticationMethod.KERBEROS) {
UserGroupInformation.loginUserFromKeytab(
getServerPrincipal(configuration.getString(AUTHENTICATION_PRINCIPAL)),
configuration.getString(AUTHENTICATION_KEYTAB));
}
LOG.info("Logged in user {}", UserGroupInformation.getLoginUser());
} catch (IOException e) {
throw new IllegalStateException(String.format("Unable to perform %s login.", authenticationMethod), e);
}
}
/**
* Return a server (service) principal. The token "_HOST" in the principal will be replaced with the local host
* name (e.g. dgi/_HOST will be changed to dgi/localHostName)
* @param principal the input principal containing an option "_HOST" token
* @return the service principal.
* @throws IOException
*/
private String getServerPrincipal(String principal) throws IOException {
return SecurityUtil.getServerPrincipal(principal, InetAddress.getLocalHost().getHostName());
}
/**
* Returns a Hadoop configuration instance.
* @return the configuration.
*/
protected Configuration getHadoopConfiguration() {
return new Configuration();
}
/**
* Returns the metadata application configuration.
* @return the metadata configuration.
* @throws ConfigurationException
*/
protected PropertiesConfiguration getPropertiesConfiguration() throws ConfigurationException {
return new PropertiesConfiguration("application.properties");
}
/**
* Uses a hadoop shell to discern whether a hadoop cluster is available/configured.
* @return true if a hadoop cluster is detected.
*/
protected boolean isHadoopCluster() {
boolean isHadoopCluster = false;
try {
isHadoopCluster = Shell.getHadoopHome() != null;
} catch (IOException e) {
// ignore - false is default setting
}
return isHadoopCluster;
}
}
......@@ -48,6 +48,9 @@
</filter-mapping>
<listener>
<listener-class>org.apache.hadoop.metadata.web.listeners.LoginListener</listener-class>
</listener>
<listener>
<listener-class>org.apache.hadoop.metadata.web.listeners.GuiceServletConfig</listener-class>
</listener>
</web-app>
/*
* 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.hadoop.metadata.web.listeners;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
import org.apache.hadoop.minikdc.MiniKdc;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.util.Shell;
import org.apache.zookeeper.Environment;
import org.testng.annotations.Test;
import java.io.File;
import java.nio.file.Files;
import java.util.Locale;
import java.util.Properties;
/**
*
*/
public class LoginListenerIT {
private static final String JAAS_ENTRY =
"%s { \n"
+ " %s required\n"
// kerberos module
+ " keyTab=\"%s\"\n"
+ " debug=true\n"
+ " principal=\"%s\"\n"
+ " useKeyTab=true\n"
+ " useTicketCache=false\n"
+ " doNotPrompt=true\n"
+ " storeKey=true;\n"
+ "}; \n";
protected static final String kerberosRule =
"RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\nDEFAULT";
private MiniKdc kdc;
@Test
public void testDefaultSimpleLogin() throws Exception {
LoginListener listener = new LoginListener() {
@Override
protected PropertiesConfiguration getPropertiesConfiguration() throws ConfigurationException {
return new PropertiesConfiguration();
}
};
listener.contextInitialized(null);
assert UserGroupInformation.getCurrentUser() != null;
assert !UserGroupInformation.isLoginKeytabBased();
assert !UserGroupInformation.isSecurityEnabled();
}
@Test
public void testKerberosLogin() throws Exception {
final File keytab = setupKDCAndPrincipals();
LoginListener listener = new LoginListener() {
@Override
protected PropertiesConfiguration getPropertiesConfiguration() throws ConfigurationException {
PropertiesConfiguration config = new PropertiesConfiguration();
config.setProperty("authentication.method", "kerberos");
config.setProperty("authentication.principal", "dgi@EXAMPLE.COM");
config.setProperty("authentication.keytab", keytab.getAbsolutePath());
return config;
}
@Override
protected Configuration getHadoopConfiguration() {
Configuration config = new Configuration(false);
config.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
config.setBoolean(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHORIZATION, true);
config.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTH_TO_LOCAL, kerberosRule);
return config;
}
@Override
protected boolean isHadoopCluster() {
return true;
}
};
listener.contextInitialized(null);
assert UserGroupInformation.getLoginUser().getShortUserName().endsWith("dgi");
assert UserGroupInformation.getCurrentUser() != null;
assert UserGroupInformation.isLoginKeytabBased();
assert UserGroupInformation.isSecurityEnabled();
kdc.stop();
}
private File setupKDCAndPrincipals() throws Exception {
// set up the KDC
File target = Files.createTempDirectory("sectest").toFile();
File kdcWorkDir = new File(target, "kdc");
Properties kdcConf = MiniKdc.createConf();
kdcConf.setProperty(MiniKdc.DEBUG, "true");
kdc = new MiniKdc(kdcConf, kdcWorkDir);
kdc.start();
assert kdc.getRealm() != null;
File keytabFile = createKeytab(kdc, kdcWorkDir, "dgi", "dgi.keytab");
String dgiServerPrincipal = Shell.WINDOWS ? "dgi/127.0.0.1" : "dgi/localhost";
StringBuilder jaas = new StringBuilder(1024);
jaas.append(createJAASEntry("Client", "dgi", keytabFile));
jaas.append(createJAASEntry("Server", dgiServerPrincipal, keytabFile));
File jaasFile = new File(kdcWorkDir, "jaas.txt");
FileUtils.write(jaasFile, jaas.toString());
bindJVMtoJAASFile(jaasFile);
return keytabFile;
}
private File createKeytab(MiniKdc kdc, File kdcWorkDir, String principal, String filename) throws Exception {
File keytab = new File(kdcWorkDir, filename);
kdc.createPrincipal(keytab,
principal,
principal + "/localhost",
principal + "/127.0.0.1");
return keytab;
}
public String createJAASEntry(
String context,
String principal,
File keytab) {
String keytabpath = keytab.getAbsolutePath();
// fix up for windows; no-op on unix
keytabpath = keytabpath.replace('\\', '/');
return String.format(
Locale.ENGLISH,
JAAS_ENTRY,
context,
getKerberosAuthModuleForJVM(),
keytabpath,
principal);
}
private String getKerberosAuthModuleForJVM() {
if (System.getProperty("java.vendor").contains("IBM")) {
return "com.ibm.security.auth.module.Krb5LoginModule";
} else {
return "com.sun.security.auth.module.Krb5LoginModule";
}
}
private void bindJVMtoJAASFile(File jaasFile) {
String path = jaasFile.getAbsolutePath();
System.setProperty(Environment.JAAS_CONF_KEY, path);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment