HADOOP-7119. add Kerberos HTTP SPNEGO authentication support to Hadoop JT/NN/DN/TT web-consoles. (Alejandro Abdelnur via atm)

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1159804 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Aaron Myers 2011-08-19 22:31:06 +00:00
parent f6d24536ba
commit a0f120ce68
58 changed files with 5934 additions and 8 deletions

View File

@ -0,0 +1,20 @@
Build instructions for Hadoop Alfredo
Same as for Hadoop.
For more details refer to the Alfredo documentation pages.
-----------------------------------------------------------------------------
Caveats:
* Alfredo has profile to enable Kerberos testcases (testKerberos)
To run Kerberos testcases a KDC, 2 kerberos principals and a keytab file
are required (refer to the Alfredo documentation pages for details).
* Alfredo does not have a distribution profile (dist)
* Alfredo does not have a native code profile (native)
-----------------------------------------------------------------------------

15
hadoop-alfredo/README.txt Normal file
View File

@ -0,0 +1,15 @@
Hadoop Alfredo, Java HTTP SPNEGO
Hadoop Alfredo is a Java library consisting of a client and a server
components to enable Kerberos SPNEGO authentication for HTTP.
The client component is the AuthenticatedURL class.
The server component is the AuthenticationFilter servlet filter class.
Authentication mechanisms support is pluggable in both the client and
the server components via interfaces.
In addition to Kerberos SPNEGO, Alfredo also supports Pseudo/Simple
authentication (trusting the value of the query string parameter
'user.name').

217
hadoop-alfredo/pom.xml Normal file
View File

@ -0,0 +1,217 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed 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. See accompanying LICENSE file.
-->
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-project</artifactId>
<version>0.23.0-SNAPSHOT</version>
<relativePath>../hadoop-project</relativePath>
</parent>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-alfredo</artifactId>
<version>0.23.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Apache Hadoop Alfredo</name>
<description>Apache Hadoop Alfredo - Java HTTP SPNEGO</description>
<url>http://hadoop.apache.org/alfredo</url>
<properties>
<maven.build.timestamp.format>yyyyMMdd</maven.build.timestamp.format>
<kerberos.realm>LOCALHOST</kerberos.realm>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<testResources>
<testResource>
<directory>${basedir}/src/test/resources</directory>
<filtering>true</filtering>
<includes>
<include>krb5.conf</include>
</includes>
</testResource>
<testResource>
<directory>${basedir}/src/test/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>krb5.conf</exclude>
</excludes>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<forkMode>always</forkMode>
<forkedProcessTimeoutInSeconds>600</forkedProcessTimeoutInSeconds>
<systemPropertyVariables>
<java.security.krb5.conf>${project.build.directory}/test-classes/krb5.conf</java.security.krb5.conf>
<kerberos.realm>${kerberos.realm}</kerberos.realm>
</systemPropertyVariables>
<excludes>
<exclude>**/${test.exclude}.java</exclude>
<exclude>${test.exclude.pattern}</exclude>
<exclude>**/TestKerberosAuth*.java</exclude>
<exclude>**/Test*$*.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<phase>prepare-package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
<configuration>
<attach>true</attach>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>testKerberos</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<forkMode>always</forkMode>
<forkedProcessTimeoutInSeconds>600</forkedProcessTimeoutInSeconds>
<systemPropertyVariables>
<java.security.krb5.conf>${project.build.directory}/test-classes/krb5.conf</java.security.krb5.conf>
<kerberos.realm>${kerberos.realm}</kerberos.realm>
</systemPropertyVariables>
<excludes>
<exclude>**/${test.exclude}.java</exclude>
<exclude>${test.exclude.pattern}</exclude>
<exclude>**/Test*$*.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>docs</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>site</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<executions>
<execution>
<configuration>
<dependencyLocationsEnabled>false</dependencyLocationsEnabled>
</configuration>
<phase>package</phase>
<goals>
<goal>dependencies</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>javadoc</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed 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. See accompanying LICENSE file.
-->
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-project</artifactId>
<version>0.23.0-SNAPSHOT</version>
<relativePath>../hadoop-project</relativePath>
</parent>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-alfredo-examples</artifactId>
<version>0.23.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>Hadoop Alfredo Examples</name>
<description>Hadoop Alfredo - Java HTTP SPNEGO Examples</description>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-alfredo</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>org.apache.hadoop.alfredo.examples.WhoClient</mainClass>
<arguments>
<argument>${url}</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,183 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.examples;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Servlet filter that logs HTTP request/response headers
*/
public class RequestLoggerFilter implements Filter {
private static Logger LOG = LoggerFactory.getLogger(RequestLoggerFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
if (!LOG.isDebugEnabled()) {
filterChain.doFilter(request, response);
}
else {
XHttpServletRequest xRequest = new XHttpServletRequest((HttpServletRequest) request);
XHttpServletResponse xResponse = new XHttpServletResponse((HttpServletResponse) response);
try {
LOG.debug(xRequest.getResquestInfo().toString());
filterChain.doFilter(xRequest, xResponse);
}
finally {
LOG.debug(xResponse.getResponseInfo().toString());
}
}
}
@Override
public void destroy() {
}
private static class XHttpServletRequest extends HttpServletRequestWrapper {
public XHttpServletRequest(HttpServletRequest request) {
super(request);
}
public StringBuffer getResquestInfo() {
StringBuffer sb = new StringBuffer(512);
sb.append("\n").append("> ").append(getMethod()).append(" ").append(getRequestURL());
if (getQueryString() != null) {
sb.append("?").append(getQueryString());
}
sb.append("\n");
Enumeration names = getHeaderNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
Enumeration values = getHeaders(name);
while (values.hasMoreElements()) {
String value = (String) values.nextElement();
sb.append("> ").append(name).append(": ").append(value).append("\n");
}
}
sb.append(">");
return sb;
}
}
private static class XHttpServletResponse extends HttpServletResponseWrapper {
private Map<String, List<String>> headers = new HashMap<String, List<String>>();
private int status;
private String message;
public XHttpServletResponse(HttpServletResponse response) {
super(response);
}
private List<String> getHeaderValues(String name, boolean reset) {
List<String> values = headers.get(name);
if (reset || values == null) {
values = new ArrayList<String>();
headers.put(name, values);
}
return values;
}
@Override
public void addCookie(Cookie cookie) {
super.addCookie(cookie);
List<String> cookies = getHeaderValues("Set-Cookie", false);
cookies.add(cookie.getName() + "=" + cookie.getValue());
}
@Override
public void sendError(int sc, String msg) throws IOException {
super.sendError(sc, msg);
status = sc;
message = msg;
}
@Override
public void sendError(int sc) throws IOException {
super.sendError(sc);
status = sc;
}
@Override
public void setStatus(int sc) {
super.setStatus(sc);
status = sc;
}
@Override
public void setStatus(int sc, String msg) {
super.setStatus(sc, msg);
status = sc;
message = msg;
}
@Override
public void setHeader(String name, String value) {
super.setHeader(name, value);
List<String> values = getHeaderValues(name, true);
values.add(value);
}
@Override
public void addHeader(String name, String value) {
super.addHeader(name, value);
List<String> values = getHeaderValues(name, false);
values.add(value);
}
public StringBuffer getResponseInfo() {
if (status == 0) {
status = 200;
message = "OK";
}
StringBuffer sb = new StringBuffer(512);
sb.append("\n").append("< ").append("status code: ").append(status);
if (message != null) {
sb.append(", message: ").append(message);
}
sb.append("\n");
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
for (String value : entry.getValue()) {
sb.append("< ").append(entry.getKey()).append(": ").append(value).append("\n");
}
}
sb.append("<");
return sb;
}
}
}

View File

@ -0,0 +1,57 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.examples;
import org.apache.hadoop.alfredo.client.AuthenticatedURL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* Example that uses <code>AuthenticatedURL</code>.
*/
public class WhoClient {
public static void main(String[] args) {
try {
if (args.length != 1) {
System.err.println("Usage: <URL>");
System.exit(-1);
}
AuthenticatedURL.Token token = new AuthenticatedURL.Token();
URL url = new URL(args[0]);
HttpURLConnection conn = new AuthenticatedURL().openConnection(url, token);
System.out.println();
System.out.println("Token value: " + token);
System.out.println("Status code: " + conn.getResponseCode() + " " + conn.getResponseMessage());
System.out.println();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = reader.readLine();
while (line != null) {
System.out.println(line);
line = reader.readLine();
}
reader.close();
}
System.out.println();
}
catch (Exception ex) {
System.err.println("ERROR: " + ex.getMessage());
System.exit(-1);
}
}
}

View File

@ -0,0 +1,43 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.examples;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.text.MessageFormat;
/**
* Example servlet that returns the user and principal of the request.
*/
public class WhoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/plain");
resp.setStatus(HttpServletResponse.SC_OK);
String user = req.getRemoteUser();
String principal = (req.getUserPrincipal() != null) ? req.getUserPrincipal().getName() : null;
Writer writer = resp.getWriter();
writer.write(MessageFormat.format("You are: user[{0}] principal[{1}]\n", user, principal));
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}

View File

@ -0,0 +1,19 @@
#
# Licensed 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. See accompanying LICENSE file.
#
log4j.appender.test=org.apache.log4j.ConsoleAppender
log4j.appender.test.Target=System.out
log4j.appender.test.layout=org.apache.log4j.PatternLayout
log4j.appender.test.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
log4j.logger.org.apache.hadoop.alfredo=DEBUG, test

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed 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. See accompanying LICENSE file.
-->
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
<servlet>
<servlet-name>whoServlet</servlet-name>
<servlet-class>org.apache.hadoop.alfredo.examples.WhoServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>whoServlet</servlet-name>
<url-pattern>/anonymous/who</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>whoServlet</servlet-name>
<url-pattern>/simple/who</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>whoServlet</servlet-name>
<url-pattern>/kerberos/who</url-pattern>
</servlet-mapping>
<filter>
<filter-name>requestLoggerFilter</filter-name>
<filter-class>org.apache.hadoop.alfredo.examples.RequestLoggerFilter</filter-class>
</filter>
<filter>
<filter-name>anonymousFilter</filter-name>
<filter-class>org.apache.hadoop.alfredo.server.AuthenticationFilter</filter-class>
<init-param>
<param-name>type</param-name>
<param-value>simple</param-value>
</init-param>
<init-param>
<param-name>simple.anonymous.allowed</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>token.validity</param-name>
<param-value>30</param-value>
</init-param>
</filter>
<filter>
<filter-name>simpleFilter</filter-name>
<filter-class>org.apache.hadoop.alfredo.server.AuthenticationFilter</filter-class>
<init-param>
<param-name>type</param-name>
<param-value>simple</param-value>
</init-param>
<init-param>
<param-name>simple.anonymous.allowed</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>token.validity</param-name>
<param-value>30</param-value>
</init-param>
</filter>
<filter>
<filter-name>kerberosFilter</filter-name>
<filter-class>org.apache.hadoop.alfredo.server.AuthenticationFilter</filter-class>
<init-param>
<param-name>type</param-name>
<param-value>kerberos</param-value>
</init-param>
<init-param>
<param-name>kerberos.principal</param-name>
<param-value>HTTP/localhost@LOCALHOST</param-value>
</init-param>
<init-param>
<param-name>kerberos.keytab</param-name>
<param-value>/tmp/alfredo.keytab</param-value>
</init-param>
<init-param>
<param-name>token.validity</param-name>
<param-value>30</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>requestLoggerFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>anonymousFilter</filter-name>
<url-pattern>/anonymous/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>simpleFilter</filter-name>
<url-pattern>/simple/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>kerberosFilter</filter-name>
<url-pattern>/kerberos/*</url-pattern>
</filter-mapping>
</web-app>

View File

@ -0,0 +1,18 @@
<!--
Licensed 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. See accompanying LICENSE file.
-->
<html>
<body>
<h1>Hello Hadoop Alfredo Pseudo/Simple Authentication with anonymous users!</h1>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!--
Licensed 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. See accompanying LICENSE file.
-->
<html>
<body>
<h1>Hello Hadoop Alfredo Examples</h1>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!--
Licensed 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. See accompanying LICENSE file.
-->
<html>
<body>
<h1>Hello Hadoop Alfredo Kerberos SPNEGO Authentication!</h1>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!--
Licensed 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. See accompanying LICENSE file.
-->
<html>
<body>
<h1>Hello Hadoop Alfredo Pseudo/Simple Authentication!</h1>
</body>
</html>

View File

@ -0,0 +1,274 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.client;
import org.apache.hadoop.alfredo.server.AuthenticationFilter;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;
/**
* The {@link AuthenticatedURL} class enables the use of the JDK {@link URL} class
* against HTTP endpoints protected with the {@link AuthenticationFilter}.
* <p/>
* The authentication mechanisms supported by default are Hadoop Simple authentication
* (also known as pseudo authentication) and Kerberos SPNEGO authentication.
* <p/>
* Additional authentication mechanisms can be supported via {@link Authenticator} implementations.
* <p/>
* The default {@link Authenticator} is the {@link KerberosAuthenticator} class which supports
* automatic fallback from Kerberos SPNEGO to Hadoop Simple authentication.
* <p/>
* <code>AuthenticatedURL</code> instances are not thread-safe.
* <p/>
* The usage pattern of the {@link AuthenticatedURL} is:
* <p/>
* <pre>
*
* // establishing an initial connection
*
* URL url = new URL("http://foo:8080/bar");
* AuthenticatedURL.Token token = new AuthenticatedURL.Token();
* AuthenticatedURL aUrl = new AuthenticatedURL();
* HttpURLConnection conn = new AuthenticatedURL(url, token).openConnection();
* ....
* // use the 'conn' instance
* ....
*
* // establishing a follow up connection using a token from the previous connection
*
* HttpURLConnection conn = new AuthenticatedURL(url, token).openConnection();
* ....
* // use the 'conn' instance
* ....
*
* </pre>
*/
public class AuthenticatedURL {
/**
* Name of the HTTP cookie used for the authentication token between the client and the server.
*/
public static final String AUTH_COOKIE = "alfredo.auth";
private static final String AUTH_COOKIE_EQ = AUTH_COOKIE + "=";
/**
* Client side authentication token.
*/
public static class Token {
private String token;
/**
* Creates a token.
*/
public Token() {
}
/**
* Creates a token using an existing string representation of the token.
*
* @param tokenStr string representation of the tokenStr.
*/
public Token(String tokenStr) {
if (tokenStr == null) {
throw new IllegalArgumentException("tokenStr cannot be null");
}
set(tokenStr);
}
/**
* Returns if a token from the server has been set.
*
* @return if a token from the server has been set.
*/
public boolean isSet() {
return token != null;
}
/**
* Sets a token.
*
* @param tokenStr string representation of the tokenStr.
*/
void set(String tokenStr) {
token = tokenStr;
}
/**
* Returns the string representation of the token.
*
* @return the string representation of the token.
*/
@Override
public String toString() {
return token;
}
/**
* Return the hashcode for the token.
*
* @return the hashcode for the token.
*/
@Override
public int hashCode() {
return (token != null) ? token.hashCode() : 0;
}
/**
* Return if two token instances are equal.
*
* @param o the other token instance.
*
* @return if this instance and the other instance are equal.
*/
@Override
public boolean equals(Object o) {
boolean eq = false;
if (o instanceof Token) {
Token other = (Token) o;
eq = (token == null && other.token == null) || (token != null && this.token.equals(other.token));
}
return eq;
}
}
private static Class<? extends Authenticator> DEFAULT_AUTHENTICATOR = KerberosAuthenticator.class;
/**
* Sets the default {@link Authenticator} class to use when an {@link AuthenticatedURL} instance
* is created without specifying an authenticator.
*
* @param authenticator the authenticator class to use as default.
*/
public static void setDefaultAuthenticator(Class<? extends Authenticator> authenticator) {
DEFAULT_AUTHENTICATOR = authenticator;
}
/**
* Returns the default {@link Authenticator} class to use when an {@link AuthenticatedURL} instance
* is created without specifying an authenticator.
*
* @return the authenticator class to use as default.
*/
public static Class<? extends Authenticator> getDefaultAuthenticator() {
return DEFAULT_AUTHENTICATOR;
}
private Authenticator authenticator;
/**
* Creates an {@link AuthenticatedURL}.
*/
public AuthenticatedURL() {
this(null);
}
/**
* Creates an <code>AuthenticatedURL</code>.
*
* @param authenticator the {@link Authenticator} instance to use, if <code>null</code> a {@link
* KerberosAuthenticator} is used.
*/
public AuthenticatedURL(Authenticator authenticator) {
try {
this.authenticator = (authenticator != null) ? authenticator : DEFAULT_AUTHENTICATOR.newInstance();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Returns an authenticated {@link HttpURLConnection}.
*
* @param url the URL to connect to. Only HTTP/S URLs are supported.
* @param token the authentication token being used for the user.
*
* @return an authenticated {@link HttpURLConnection}.
*
* @throws IOException if an IO error occurred.
* @throws AuthenticationException if an authentication exception occurred.
*/
public HttpURLConnection openConnection(URL url, Token token) throws IOException, AuthenticationException {
if (url == null) {
throw new IllegalArgumentException("url cannot be NULL");
}
if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) {
throw new IllegalArgumentException("url must be for a HTTP or HTTPS resource");
}
if (token == null) {
throw new IllegalArgumentException("token cannot be NULL");
}
authenticator.authenticate(url, token);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
injectToken(conn, token);
return conn;
}
/**
* Helper method that injects an authentication token to send with a connection.
*
* @param conn connection to inject the authentication token into.
* @param token authentication token to inject.
*/
public static void injectToken(HttpURLConnection conn, Token token) {
String t = token.token;
if (t != null) {
if (!t.startsWith("\"")) {
t = "\"" + t + "\"";
}
conn.addRequestProperty("Cookie", AUTH_COOKIE_EQ + t);
}
}
/**
* Helper method that extracts an authentication token received from a connection.
* <p/>
* This method is used by {@link Authenticator} implementations.
*
* @param conn connection to extract the authentication token from.
* @param token the authentication token.
*
* @throws IOException if an IO error occurred.
* @throws AuthenticationException if an authentication exception occurred.
*/
public static void extractToken(HttpURLConnection conn, Token token) throws IOException, AuthenticationException {
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
Map<String, List<String>> headers = conn.getHeaderFields();
List<String> cookies = headers.get("Set-Cookie");
if (cookies != null) {
for (String cookie : cookies) {
if (cookie.startsWith(AUTH_COOKIE_EQ)) {
String value = cookie.substring(AUTH_COOKIE_EQ.length());
int separator = value.indexOf(";");
if (separator > -1) {
value = value.substring(0, separator);
}
if (value.length() > 0) {
token.set(value);
}
}
}
}
} else {
throw new AuthenticationException("Authentication failed, status: " + conn.getResponseCode() +
", message: " + conn.getResponseMessage());
}
}
}

View File

@ -0,0 +1,50 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.client;
/**
* Exception thrown when an authentication error occurrs.
*/
public class AuthenticationException extends Exception {
static final long serialVersionUID = 0;
/**
* Creates an {@link AuthenticationException}.
*
* @param cause original exception.
*/
public AuthenticationException(Throwable cause) {
super(cause);
}
/**
* Creates an {@link AuthenticationException}.
*
* @param msg exception message.
*/
public AuthenticationException(String msg) {
super(msg);
}
/**
* Creates an {@link AuthenticationException}.
*
* @param msg exception message.
* @param cause original exception.
*/
public AuthenticationException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@ -0,0 +1,39 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.client;
import java.io.IOException;
import java.net.URL;
/**
* Interface for client authentication mechanisms.
* <p/>
* Implementations are use-once instances, they don't need to be thread safe.
*/
public interface Authenticator {
/**
* Authenticates against a URL and returns a {@link AuthenticatedURL.Token} to be
* used by subsequent requests.
*
* @param url the URl to authenticate against.
* @param token the authentication token being used for the user.
*
* @throws IOException if an IO error occurred.
* @throws AuthenticationException if an authentication error occurred.
*/
public void authenticate(URL url, AuthenticatedURL.Token token) throws IOException, AuthenticationException;
}

View File

@ -0,0 +1,270 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.client;
import com.sun.security.auth.module.Krb5LoginModule;
import org.apache.commons.codec.binary.Base64;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import sun.security.jgss.GSSUtil;
import javax.security.auth.Subject;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.HashMap;
import java.util.Map;
/**
* The {@link KerberosAuthenticator} implements the Kerberos SPNEGO authentication sequence.
* <p/>
* It uses the default principal for the Kerberos cache (normally set via kinit).
* <p/>
* It falls back to the {@link PseudoAuthenticator} if the HTTP endpoint does not trigger an SPNEGO authentication
* sequence.
*/
public class KerberosAuthenticator implements Authenticator {
/**
* HTTP header used by the SPNEGO server endpoint during an authentication sequence.
*/
public static String WWW_AUTHENTICATE = "WWW-Authenticate";
/**
* HTTP header used by the SPNEGO client endpoint during an authentication sequence.
*/
public static String AUTHORIZATION = "Authorization";
/**
* HTTP header prefix used by the SPNEGO client/server endpoints during an authentication sequence.
*/
public static String NEGOTIATE = "Negotiate";
private static final String AUTH_HTTP_METHOD = "OPTIONS";
/*
* Defines the Kerberos configuration that will be used to obtain the Kerberos principal from the
* Kerberos cache.
*/
private static class KerberosConfiguration extends Configuration {
private static final String OS_LOGIN_MODULE_NAME;
private static final boolean windows = System.getProperty("os.name").startsWith("Windows");
static {
if (windows) {
OS_LOGIN_MODULE_NAME = "com.sun.security.auth.module.NTLoginModule";
} else {
OS_LOGIN_MODULE_NAME = "com.sun.security.auth.module.UnixLoginModule";
}
}
private static final AppConfigurationEntry OS_SPECIFIC_LOGIN =
new AppConfigurationEntry(OS_LOGIN_MODULE_NAME,
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
new HashMap<String, String>());
private static final Map<String, String> USER_KERBEROS_OPTIONS = new HashMap<String, String>();
static {
USER_KERBEROS_OPTIONS.put("doNotPrompt", "true");
USER_KERBEROS_OPTIONS.put("useTicketCache", "true");
USER_KERBEROS_OPTIONS.put("renewTGT", "true");
String ticketCache = System.getenv("KRB5CCNAME");
if (ticketCache != null) {
USER_KERBEROS_OPTIONS.put("ticketCache", ticketCache);
}
}
private static final AppConfigurationEntry USER_KERBEROS_LOGIN =
new AppConfigurationEntry(Krb5LoginModule.class.getName(),
AppConfigurationEntry.LoginModuleControlFlag.OPTIONAL,
USER_KERBEROS_OPTIONS);
private static final AppConfigurationEntry[] USER_KERBEROS_CONF =
new AppConfigurationEntry[]{OS_SPECIFIC_LOGIN, USER_KERBEROS_LOGIN};
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String appName) {
return USER_KERBEROS_CONF;
}
}
static {
javax.security.auth.login.Configuration.setConfiguration(new KerberosConfiguration());
}
private URL url;
private HttpURLConnection conn;
private Base64 base64;
/**
* Performs SPNEGO authentication against the specified URL.
* <p/>
* If a token is given it does a NOP and returns the given token.
* <p/>
* If no token is given, it will perform the SPNEGO authentication sequence using an
* HTTP <code>OPTIONS</code> request.
*
* @param url the URl to authenticate against.
* @param token the authentication token being used for the user.
*
* @throws IOException if an IO error occurred.
* @throws AuthenticationException if an authentication error occurred.
*/
@Override
public void authenticate(URL url, AuthenticatedURL.Token token)
throws IOException, AuthenticationException {
if (!token.isSet()) {
this.url = url;
base64 = new Base64(0);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(AUTH_HTTP_METHOD);
conn.connect();
if (isNegotiate()) {
doSpnegoSequence(token);
} else {
getFallBackAuthenticator().authenticate(url, token);
}
}
}
/**
* If the specified URL does not support SPNEGO authentication, a fallback {@link Authenticator} will be used.
* <p/>
* This implementation returns a {@link PseudoAuthenticator}.
*
* @return the fallback {@link Authenticator}.
*/
protected Authenticator getFallBackAuthenticator() {
return new PseudoAuthenticator();
}
/*
* Indicates if the response is starting a SPNEGO negotiation.
*/
private boolean isNegotiate() throws IOException {
boolean negotiate = false;
if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
String authHeader = conn.getHeaderField(WWW_AUTHENTICATE);
negotiate = authHeader != null && authHeader.trim().startsWith(NEGOTIATE);
}
return negotiate;
}
/**
* Implements the SPNEGO authentication sequence interaction using the current default principal
* in the Kerberos cache (normally set via kinit).
*
* @param token the authentication token being used for the user.
*
* @throws IOException if an IO error occurred.
* @throws AuthenticationException if an authentication error occurred.
*/
private void doSpnegoSequence(AuthenticatedURL.Token token) throws IOException, AuthenticationException {
try {
AccessControlContext context = AccessController.getContext();
Subject subject = Subject.getSubject(context);
if (subject == null) {
subject = new Subject();
LoginContext login = new LoginContext("", subject);
login.login();
}
Subject.doAs(subject, new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
GSSContext gssContext = null;
try {
GSSManager gssManager = GSSManager.getInstance();
String servicePrincipal = "HTTP/" + KerberosAuthenticator.this.url.getHost();
GSSName serviceName = gssManager.createName(servicePrincipal,
GSSUtil.NT_GSS_KRB5_PRINCIPAL);
gssContext = gssManager.createContext(serviceName, GSSUtil.GSS_KRB5_MECH_OID, null,
GSSContext.DEFAULT_LIFETIME);
gssContext.requestCredDeleg(true);
gssContext.requestMutualAuth(true);
byte[] inToken = new byte[0];
byte[] outToken;
boolean established = false;
// Loop while the context is still not established
while (!established) {
outToken = gssContext.initSecContext(inToken, 0, inToken.length);
if (outToken != null) {
sendToken(outToken);
}
if (!gssContext.isEstablished()) {
inToken = readToken();
} else {
established = true;
}
}
} finally {
if (gssContext != null) {
gssContext.dispose();
gssContext = null;
}
}
return null;
}
});
} catch (PrivilegedActionException ex) {
throw new AuthenticationException(ex.getException());
} catch (LoginException ex) {
throw new AuthenticationException(ex);
}
AuthenticatedURL.extractToken(conn, token);
}
/*
* Sends the Kerberos token to the server.
*/
private void sendToken(byte[] outToken) throws IOException, AuthenticationException {
String token = base64.encodeToString(outToken);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(AUTH_HTTP_METHOD);
conn.setRequestProperty(AUTHORIZATION, NEGOTIATE + " " + token);
conn.connect();
}
/*
* Retrieves the Kerberos token returned by the server.
*/
private byte[] readToken() throws IOException, AuthenticationException {
int status = conn.getResponseCode();
if (status == HttpURLConnection.HTTP_OK || status == HttpURLConnection.HTTP_UNAUTHORIZED) {
String authHeader = conn.getHeaderField(WWW_AUTHENTICATE);
if (authHeader == null || !authHeader.trim().startsWith(NEGOTIATE)) {
throw new AuthenticationException("Invalid SPNEGO sequence, '" + WWW_AUTHENTICATE +
"' header incorrect: " + authHeader);
}
String negotiation = authHeader.trim().substring((NEGOTIATE + " ").length()).trim();
return base64.decode(negotiation);
}
throw new AuthenticationException("Invalid SPNEGO sequence, status code: " + status);
}
}

View File

@ -0,0 +1,74 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.client;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* The {@link PseudoAuthenticator} implementation provides an authentication equivalent to Hadoop's
* Simple authentication, it trusts the value of the 'user.name' Java System property.
* <p/>
* The 'user.name' value is propagated using an additional query string parameter {@link #USER_NAME} ('user.name').
*/
public class PseudoAuthenticator implements Authenticator {
/**
* Name of the additional parameter that carries the 'user.name' value.
*/
public static final String USER_NAME = "user.name";
private static final String USER_NAME_EQ = USER_NAME + "=";
/**
* Performs simple authentication against the specified URL.
* <p/>
* If a token is given it does a NOP and returns the given token.
* <p/>
* If no token is given, it will perform an HTTP <code>OPTIONS</code> request injecting an additional
* parameter {@link #USER_NAME} in the query string with the value returned by the {@link #getUserName()}
* method.
* <p/>
* If the response is successful it will update the authentication token.
*
* @param url the URl to authenticate against.
* @param token the authencation token being used for the user.
*
* @throws IOException if an IO error occurred.
* @throws AuthenticationException if an authentication error occurred.
*/
@Override
public void authenticate(URL url, AuthenticatedURL.Token token) throws IOException, AuthenticationException {
String strUrl = url.toString();
String paramSeparator = (strUrl.contains("?")) ? "&" : "?";
strUrl += paramSeparator + USER_NAME_EQ + getUserName();
url = new URL(strUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("OPTIONS");
conn.connect();
AuthenticatedURL.extractToken(conn, token);
}
/**
* Returns the current user name.
* <p/>
* This implementation returns the value of the Java system property 'user.name'
*
* @return the current user name.
*/
protected String getUserName() {
return System.getProperty("user.name");
}
}

View File

@ -0,0 +1,402 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.server;
import org.apache.hadoop.alfredo.client.AuthenticatedURL;
import org.apache.hadoop.alfredo.client.AuthenticationException;
import org.apache.hadoop.alfredo.util.Signer;
import org.apache.hadoop.alfredo.util.SignerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Principal;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Random;
/**
* The {@link AuthenticationFilter} enables protecting web application resources with different (pluggable)
* authentication mechanisms.
* <p/>
* Out of the box it provides 2 authentication mechanisms: Pseudo and Kerberos SPNEGO.
* <p/>
* Additional authentication mechanisms are supported via the {@link AuthenticationHandler} interface.
* <p/>
* This filter delegates to the configured authentication handler for authentication and once it obtains an
* {@link AuthenticationToken} from it, sets a signed HTTP cookie with the token. For client requests
* that provide the signed HTTP cookie, it verifies the validity of the cookie, extracts the user information
* and lets the request proceed to the target resource.
* <p/>
* The supported configuration properties are:
* <ul>
* <li>config.prefix: indicates the prefix to be used by all other configuration properties, the default value
* is no prefix. See below for details on how/why this prefix is used.</li>
* <li>[#PREFIX#.]type: simple|kerberos|#CLASS#, 'simple' is short for the
* {@link PseudoAuthenticationHandler}, 'kerberos' is short for {@link KerberosAuthenticationHandler}, otherwise
* the full class name of the {@link AuthenticationHandler} must be specified.</li>
* <li>[#PREFIX#.]signature.secret: the secret used to sign the HTTP cookie value. The default value is a random
* value. Unless multiple webapp instances need to share the secret the random value is adequate.</li>
* <li>[#PREFIX#.]token.validity: time -in seconds- that the generated token is valid before a
* new authentication is triggered, default value is <code>3600</code> seconds.</li>
* <li>[#PREFIX#.]cookie.domain: domain to use for the HTTP cookie that stores the authentication token.</li>
* <li>[#PREFIX#.]cookie.path: path to use for the HTTP cookie that stores the authentication token.</li>
* </ul>
* <p/>
* The rest of the configuration properties are specific to the {@link AuthenticationHandler} implementation and the
* {@link AuthenticationFilter} will take all the properties that start with the prefix #PREFIX#, it will remove
* the prefix from it and it will pass them to the the authentication handler for initialization. Properties that do
* not start with the prefix will not be passed to the authentication handler initialization.
*/
public class AuthenticationFilter implements Filter {
private static Logger LOG = LoggerFactory.getLogger(AuthenticationFilter.class);
/**
* Constant for the property that specifies the configuration prefix.
*/
public static final String CONFIG_PREFIX = "config.prefix";
/**
* Constant for the property that specifies the authentication handler to use.
*/
public static final String AUTH_TYPE = "type";
/**
* Constant for the property that specifies the secret to use for signing the HTTP Cookies.
*/
public static final String SIGNATURE_SECRET = "signature.secret";
/**
* Constant for the configuration property that indicates the validity of the generated token.
*/
public static final String AUTH_TOKEN_VALIDITY = "token.validity";
/**
* Constant for the configuration property that indicates the domain to use in the HTTP cookie.
*/
public static final String COOKIE_DOMAIN = "cookie.domain";
/**
* Constant for the configuration property that indicates the path to use in the HTTP cookie.
*/
public static final String COOKIE_PATH = "cookie.path";
private Signer signer;
private AuthenticationHandler authHandler;
private boolean randomSecret;
private long validity;
private String cookieDomain;
private String cookiePath;
/**
* Initializes the authentication filter.
* <p/>
* It instantiates and initializes the specified {@link AuthenticationHandler}.
* <p/>
*
* @param filterConfig filter configuration.
*
* @throws ServletException thrown if the filter or the authentication handler could not be initialized properly.
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String configPrefix = filterConfig.getInitParameter(CONFIG_PREFIX);
configPrefix = (configPrefix != null) ? configPrefix + "." : "";
Properties config = getConfiguration(configPrefix, filterConfig);
String authHandlerName = config.getProperty(AUTH_TYPE, null);
String authHandlerClassName;
if (authHandlerName == null) {
throw new ServletException("Authentication type must be specified: simple|kerberos|<class>");
}
if (authHandlerName.equals("simple")) {
authHandlerClassName = PseudoAuthenticationHandler.class.getName();
} else if (authHandlerName.equals("kerberos")) {
authHandlerClassName = KerberosAuthenticationHandler.class.getName();
} else {
authHandlerClassName = authHandlerName;
}
try {
Class klass = Thread.currentThread().getContextClassLoader().loadClass(authHandlerClassName);
authHandler = (AuthenticationHandler) klass.newInstance();
authHandler.init(config);
} catch (ClassNotFoundException ex) {
throw new ServletException(ex);
} catch (InstantiationException ex) {
throw new ServletException(ex);
} catch (IllegalAccessException ex) {
throw new ServletException(ex);
}
String signatureSecret = config.getProperty(configPrefix + SIGNATURE_SECRET);
if (signatureSecret == null) {
signatureSecret = Long.toString(new Random(System.currentTimeMillis()).nextLong());
randomSecret = true;
LOG.warn("'signature.secret' configuration not set, using a random value as secret");
}
signer = new Signer(signatureSecret.getBytes());
validity = Long.parseLong(config.getProperty(AUTH_TOKEN_VALIDITY, "36000")) * 1000; //10 hours
cookieDomain = config.getProperty(COOKIE_DOMAIN, null);
cookiePath = config.getProperty(COOKIE_PATH, null);
}
/**
* Returns the authentication handler being used.
*
* @return the authentication handler being used.
*/
protected AuthenticationHandler getAuthenticationHandler() {
return authHandler;
}
/**
* Returns if a random secret is being used.
*
* @return if a random secret is being used.
*/
protected boolean isRandomSecret() {
return randomSecret;
}
/**
* Returns the validity time of the generated tokens.
*
* @return the validity time of the generated tokens, in seconds.
*/
protected long getValidity() {
return validity / 1000;
}
/**
* Returns the cookie domain to use for the HTTP cookie.
*
* @return the cookie domain to use for the HTTP cookie.
*/
protected String getCookieDomain() {
return cookieDomain;
}
/**
* Returns the cookie path to use for the HTTP cookie.
*
* @return the cookie path to use for the HTTP cookie.
*/
protected String getCookiePath() {
return cookiePath;
}
/**
* Destroys the filter.
* <p/>
* It invokes the {@link AuthenticationHandler#destroy()} method to release any resources it may hold.
*/
@Override
public void destroy() {
if (authHandler != null) {
authHandler.destroy();
authHandler = null;
}
}
/**
* Returns the filtered configuration (only properties starting with the specified prefix). The property keys
* are also trimmed from the prefix. The returned {@link Properties} object is used to initialized the
* {@link AuthenticationHandler}.
* <p/>
* This method can be overriden by subclasses to obtain the configuration from other configuration source than
* the web.xml file.
*
* @param configPrefix configuration prefix to use for extracting configuration properties.
* @param filterConfig filter configuration object
*
* @return the configuration to be used with the {@link AuthenticationHandler} instance.
*
* @throws ServletException thrown if the configuration could not be created.
*/
protected Properties getConfiguration(String configPrefix, FilterConfig filterConfig) throws ServletException {
Properties props = new Properties();
Enumeration names = filterConfig.getInitParameterNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
if (name.startsWith(configPrefix)) {
String value = filterConfig.getInitParameter(name);
props.put(name.substring(configPrefix.length()), value);
}
}
return props;
}
/**
* Returns the full URL of the request including the query string.
* <p/>
* Used as a convenience method for logging purposes.
*
* @param request the request object.
*
* @return the full URL of the request including the query string.
*/
protected String getRequestURL(HttpServletRequest request) {
StringBuffer sb = request.getRequestURL();
if (request.getQueryString() != null) {
sb.append("?").append(request.getQueryString());
}
return sb.toString();
}
/**
* Returns the {@link AuthenticationToken} for the request.
* <p/>
* It looks at the received HTTP cookies and extracts the value of the {@link AuthenticatedURL#AUTH_COOKIE}
* if present. It verifies the signature and if correct it creates the {@link AuthenticationToken} and returns
* it.
* <p/>
* If this method returns <code>null</code> the filter will invoke the configured {@link AuthenticationHandler}
* to perform user authentication.
*
* @param request request object.
*
* @return the Authentication token if the request is authenticated, <code>null</code> otherwise.
*
* @throws IOException thrown if an IO error occurred.
* @throws AuthenticationException thrown if the token is invalid or if it has expired.
*/
protected AuthenticationToken getToken(HttpServletRequest request) throws IOException, AuthenticationException {
AuthenticationToken token = null;
String tokenStr = null;
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(AuthenticatedURL.AUTH_COOKIE)) {
tokenStr = cookie.getValue();
try {
tokenStr = signer.verifyAndExtract(tokenStr);
} catch (SignerException ex) {
throw new AuthenticationException(ex);
}
break;
}
}
}
if (tokenStr != null) {
token = AuthenticationToken.parse(tokenStr);
if (!token.getType().equals(authHandler.getType())) {
throw new AuthenticationException("Invalid AuthenticationToken type");
}
if (token.isExpired()) {
throw new AuthenticationException("AuthenticationToken expired");
}
}
return token;
}
/**
* If the request has a valid authentication token it allows the request to continue to the target resource,
* otherwise it triggers an authentication sequence using the configured {@link AuthenticationHandler}.
*
* @param request the request object.
* @param response the response object.
* @param filterChain the filter chain object.
*
* @throws IOException thrown if an IO error occurred.
* @throws ServletException thrown if a processing error occurred.
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
try {
boolean newToken = false;
AuthenticationToken token = getToken(httpRequest);
if (token == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Request [{}] triggering authentication", getRequestURL(httpRequest));
}
token = authHandler.authenticate(httpRequest, httpResponse);
if (token != null && token != AuthenticationToken.ANONYMOUS) {
token.setExpires(System.currentTimeMillis() + getValidity() * 1000);
}
newToken = true;
}
if (token != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Request [{}] user [{}] authenticated", getRequestURL(httpRequest), token.getUserName());
}
final AuthenticationToken authToken = token;
httpRequest = new HttpServletRequestWrapper(httpRequest) {
@Override
public String getAuthType() {
return authToken.getType();
}
@Override
public String getRemoteUser() {
return authToken.getUserName();
}
@Override
public Principal getUserPrincipal() {
return (authToken != AuthenticationToken.ANONYMOUS) ? authToken : null;
}
};
if (newToken && token != AuthenticationToken.ANONYMOUS) {
String signedToken = signer.sign(token.toString());
Cookie cookie = createCookie(signedToken);
httpResponse.addCookie(cookie);
}
filterChain.doFilter(httpRequest, httpResponse);
}
} catch (AuthenticationException ex) {
if (!httpResponse.isCommitted()) {
Cookie cookie = createCookie("");
cookie.setMaxAge(0);
httpResponse.addCookie(cookie);
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, ex.getMessage());
}
LOG.warn("Authentication exception: " + ex.getMessage(), ex);
}
}
/**
* Creates the Alfredo authentiation HTTP cookie.
* <p/>
* It sets the domain and path specified in the configuration.
*
* @param token authentication token for the cookie.
*
* @return the HTTP cookie.
*/
protected Cookie createCookie(String token) {
Cookie cookie = new Cookie(AuthenticatedURL.AUTH_COOKIE, token);
if (getCookieDomain() != null) {
cookie.setDomain(getCookieDomain());
}
if (getCookiePath() != null) {
cookie.setPath(getCookiePath());
}
return cookie;
}
}

View File

@ -0,0 +1,89 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.server;
import org.apache.hadoop.alfredo.client.AuthenticationException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Properties;
/**
* Interface for server authentication mechanisms.
* <p/>
* The {@link AuthenticationFilter} manages the lifecycle of the authentication handler.
* <p/>
* Implementations must be thread-safe as one instance is initialized and used for all requests.
*/
public interface AuthenticationHandler {
/**
* Returns the authentication type of the authentication handler.
* <p/>
* This should be a name that uniquely identifies the authentication type.
* For example 'simple' or 'kerberos'.
*
* @return the authentication type of the authentication handler.
*/
public String getType();
/**
* Initializes the authentication handler instance.
* <p/>
* This method is invoked by the {@link AuthenticationFilter#init} method.
*
* @param config configuration properties to initialize the handler.
*
* @throws ServletException thrown if the handler could not be initialized.
*/
public void init(Properties config) throws ServletException;
/**
* Destroys the authentication handler instance.
* <p/>
* This method is invoked by the {@link AuthenticationFilter#destroy} method.
*/
public void destroy();
/**
* Performs an authentication step for the given HTTP client request.
* <p/>
* This method is invoked by the {@link AuthenticationFilter} only if the HTTP client request is
* not yet authenticated.
* <p/>
* Depending upon the authentication mechanism being implemented, a particular HTTP client may
* end up making a sequence of invocations before authentication is successfully established (this is
* the case of Kerberos SPNEGO).
* <p/>
* This method must return an {@link AuthenticationToken} only if the the HTTP client request has
* been successfully and fully authenticated.
* <p/>
* If the HTTP client request has not been completely authenticated, this method must take over
* the corresponding HTTP response and it must return <code>null</code>.
*
* @param request the HTTP client request.
* @param response the HTTP client response.
*
* @return an {@link AuthenticationToken} if the HTTP client request has been authenticated,
* <code>null</code> otherwise (in this case it must take care of the response).
*
* @throws IOException thrown if an IO error occurred.
* @throws AuthenticationException thrown if an Authentication error occurred.
*/
public AuthenticationToken authenticate(HttpServletRequest request, HttpServletResponse response)
throws IOException, AuthenticationException;
}

View File

@ -0,0 +1,226 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.server;
import org.apache.hadoop.alfredo.client.AuthenticationException;
import java.security.Principal;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
/**
* The {@link AuthenticationToken} contains information about an authenticated HTTP client and doubles
* as the {@link Principal} to be returned by authenticated {@link HttpServletRequest}s
* <p/>
* The token can be serialized/deserialized to and from a string as it is sent and received in HTTP client
* responses and requests as a HTTP cookie (this is done by the {@link AuthenticationFilter}).
*/
public class AuthenticationToken implements Principal {
/**
* Constant that identifies an anonymous request.
*/
public static final AuthenticationToken ANONYMOUS = new AuthenticationToken();
private static final String ATTR_SEPARATOR = "&";
private static final String USER_NAME = "u";
private static final String PRINCIPAL = "p";
private static final String EXPIRES = "e";
private static final String TYPE = "t";
private final static Set<String> ATTRIBUTES =
new HashSet<String>(Arrays.asList(USER_NAME, PRINCIPAL, EXPIRES, TYPE));
private String userName;
private String principal;
private String type;
private long expires;
private String token;
private AuthenticationToken() {
userName = null;
principal = null;
type = null;
expires = -1;
token = "ANONYMOUS";
generateToken();
}
private static final String ILLEGAL_ARG_MSG = " is NULL, empty or contains a '" + ATTR_SEPARATOR + "'";
/**
* Creates an authentication token.
*
* @param userName user name.
* @param principal principal (commonly matches the user name, with Kerberos is the full/long principal
* name while the userName is the short name).
* @param type the authentication mechanism name.
* (<code>System.currentTimeMillis() + validityPeriod</code>).
*/
public AuthenticationToken(String userName, String principal, String type) {
checkForIllegalArgument(userName, "userName");
checkForIllegalArgument(principal, "principal");
checkForIllegalArgument(type, "type");
this.userName = userName;
this.principal = principal;
this.type = type;
this.expires = -1;
}
/**
* Check if the provided value is invalid. Throw an error if it is invalid, NOP otherwise.
*
* @param value the value to check.
* @param name the parameter name to use in an error message if the value is invalid.
*/
private static void checkForIllegalArgument(String value, String name) {
if (value == null || value.length() == 0 || value.contains(ATTR_SEPARATOR)) {
throw new IllegalArgumentException(name + ILLEGAL_ARG_MSG);
}
}
/**
* Sets the expiration of the token.
*
* @param expires expiration time of the token in milliseconds since the epoch.
*/
public void setExpires(long expires) {
if (this != AuthenticationToken.ANONYMOUS) {
this.expires = expires;
generateToken();
}
}
/**
* Generates the token.
*/
private void generateToken() {
StringBuffer sb = new StringBuffer();
sb.append(USER_NAME).append("=").append(userName).append(ATTR_SEPARATOR);
sb.append(PRINCIPAL).append("=").append(principal).append(ATTR_SEPARATOR);
sb.append(TYPE).append("=").append(type).append(ATTR_SEPARATOR);
sb.append(EXPIRES).append("=").append(expires);
token = sb.toString();
}
/**
* Returns the user name.
*
* @return the user name.
*/
public String getUserName() {
return userName;
}
/**
* Returns the principal name (this method name comes from the JDK {@link Principal} interface).
*
* @return the principal name.
*/
@Override
public String getName() {
return principal;
}
/**
* Returns the authentication mechanism of the token.
*
* @return the authentication mechanism of the token.
*/
public String getType() {
return type;
}
/**
* Returns the expiration time of the token.
*
* @return the expiration time of the token, in milliseconds since Epoc.
*/
public long getExpires() {
return expires;
}
/**
* Returns if the token has expired.
*
* @return if the token has expired.
*/
public boolean isExpired() {
return expires != -1 && System.currentTimeMillis() > expires;
}
/**
* Returns the string representation of the token.
* <p/>
* This string representation is parseable by the {@link #parse} method.
*
* @return the string representation of the token.
*/
@Override
public String toString() {
return token;
}
/**
* Parses a string into an authentication token.
*
* @param tokenStr string representation of a token.
*
* @return the parsed authentication token.
*
* @throws AuthenticationException thrown if the string representation could not be parsed into
* an authentication token.
*/
public static AuthenticationToken parse(String tokenStr) throws AuthenticationException {
Map<String, String> map = split(tokenStr);
if (!map.keySet().equals(ATTRIBUTES)) {
throw new AuthenticationException("Invalid token string, missing attributes");
}
long expires = Long.parseLong(map.get(EXPIRES));
AuthenticationToken token = new AuthenticationToken(map.get(USER_NAME), map.get(PRINCIPAL), map.get(TYPE));
token.setExpires(expires);
return token;
}
/**
* Splits the string representation of a token into attributes pairs.
*
* @param tokenStr string representation of a token.
*
* @return a map with the attribute pairs of the token.
*
* @throws AuthenticationException thrown if the string representation of the token could not be broken into
* attribute pairs.
*/
private static Map<String, String> split(String tokenStr) throws AuthenticationException {
Map<String, String> map = new HashMap<String, String>();
StringTokenizer st = new StringTokenizer(tokenStr, ATTR_SEPARATOR);
while (st.hasMoreTokens()) {
String part = st.nextToken();
int separator = part.indexOf('=');
if (separator == -1) {
throw new AuthenticationException("Invalid authentication token");
}
String key = part.substring(0, separator);
String value = part.substring(separator + 1);
map.put(key, value);
}
return map;
}
}

View File

@ -0,0 +1,310 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.server;
import org.apache.hadoop.alfredo.client.AuthenticationException;
import org.apache.hadoop.alfredo.client.KerberosAuthenticator;
import com.sun.security.auth.module.Krb5LoginModule;
import org.apache.commons.codec.binary.Base64;
import org.apache.hadoop.alfredo.util.KerberosName;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosPrincipal;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* The {@link KerberosAuthenticationHandler} implements the Kerberos SPNEGO authentication mechanism for HTTP.
* <p/>
* The supported configuration properties are:
* <ul>
* <li>kerberos.principal: the Kerberos principal to used by the server. As stated by the Kerberos SPNEGO
* specification, it should be <code>HTTP/${HOSTNAME}@{REALM}</code>. The realm can be omitted from the
* principal as the JDK GSS libraries will use the realm name of the configured default realm.
* It does not have a default value.</li>
* <li>kerberos.keytab: the keytab file containing the credentials for the Kerberos principal.
* It does not have a default value.</li>
* </ul>
*/
public class KerberosAuthenticationHandler implements AuthenticationHandler {
private static Logger LOG = LoggerFactory.getLogger(KerberosAuthenticationHandler.class);
/**
* Kerberos context configuration for the JDK GSS library.
*/
private static class KerberosConfiguration extends Configuration {
private String keytab;
private String principal;
public KerberosConfiguration(String keytab, String principal) {
this.keytab = keytab;
this.principal = principal;
}
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
Map<String, String> options = new HashMap<String, String>();
options.put("keyTab", keytab);
options.put("principal", principal);
options.put("useKeyTab", "true");
options.put("storeKey", "true");
options.put("doNotPrompt", "true");
options.put("useTicketCache", "true");
options.put("renewTGT", "true");
options.put("refreshKrb5Config", "true");
options.put("isInitiator", "false");
String ticketCache = System.getenv("KRB5CCNAME");
if (ticketCache != null) {
options.put("ticketCache", ticketCache);
}
if (LOG.isDebugEnabled()) {
options.put("debug", "true");
}
return new AppConfigurationEntry[]{
new AppConfigurationEntry(Krb5LoginModule.class.getName(),
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
options),};
}
}
/**
* Constant that identifies the authentication mechanism.
*/
public static final String TYPE = "kerberos";
/**
* Constant for the configuration property that indicates the kerberos principal.
*/
public static final String PRINCIPAL = TYPE + ".principal";
/**
* Constant for the configuration property that indicates the keytab file path.
*/
public static final String KEYTAB = TYPE + ".keytab";
/**
* Constant for the configuration property that indicates the Kerberos name
* rules for the Kerberos principals.
*/
public static final String NAME_RULES = TYPE + ".name.rules";
private String principal;
private String keytab;
private GSSManager gssManager;
private LoginContext loginContext;
/**
* Initializes the authentication handler instance.
* <p/>
* It creates a Kerberos context using the principal and keytab specified in the configuration.
* <p/>
* This method is invoked by the {@link AuthenticationFilter#init} method.
*
* @param config configuration properties to initialize the handler.
*
* @throws ServletException thrown if the handler could not be initialized.
*/
@Override
public void init(Properties config) throws ServletException {
try {
principal = config.getProperty(PRINCIPAL, principal);
if (principal == null || principal.trim().length() == 0) {
throw new ServletException("Principal not defined in configuration");
}
keytab = config.getProperty(KEYTAB, keytab);
if (keytab == null || keytab.trim().length() == 0) {
throw new ServletException("Keytab not defined in configuration");
}
if (!new File(keytab).exists()) {
throw new ServletException("Keytab does not exist: " + keytab);
}
String nameRules = config.getProperty(NAME_RULES, "DEFAULT");
KerberosName.setRules(nameRules);
Set<Principal> principals = new HashSet<Principal>();
principals.add(new KerberosPrincipal(principal));
Subject subject = new Subject(false, principals, new HashSet<Object>(), new HashSet<Object>());
KerberosConfiguration kerberosConfiguration = new KerberosConfiguration(keytab, principal);
loginContext = new LoginContext("", subject, null, kerberosConfiguration);
loginContext.login();
Subject serverSubject = loginContext.getSubject();
try {
gssManager = Subject.doAs(serverSubject, new PrivilegedExceptionAction<GSSManager>() {
@Override
public GSSManager run() throws Exception {
return GSSManager.getInstance();
}
});
} catch (PrivilegedActionException ex) {
throw ex.getException();
}
LOG.info("Initialized, principal [{}] from keytab [{}]", principal, keytab);
} catch (Exception ex) {
throw new ServletException(ex);
}
}
/**
* Releases any resources initialized by the authentication handler.
* <p/>
* It destroys the Kerberos context.
*/
@Override
public void destroy() {
try {
if (loginContext != null) {
loginContext.logout();
loginContext = null;
}
} catch (LoginException ex) {
LOG.warn(ex.getMessage(), ex);
}
}
/**
* Returns the authentication type of the authentication handler, 'kerberos'.
* <p/>
*
* @return the authentication type of the authentication handler, 'kerberos'.
*/
@Override
public String getType() {
return TYPE;
}
/**
* Returns the Kerberos principal used by the authentication handler.
*
* @return the Kerberos principal used by the authentication handler.
*/
protected String getPrincipal() {
return principal;
}
/**
* Returns the keytab used by the authentication handler.
*
* @return the keytab used by the authentication handler.
*/
protected String getKeytab() {
return keytab;
}
/**
* It enforces the the Kerberos SPNEGO authentication sequence returning an {@link AuthenticationToken} only
* after the Kerberos SPNEGO sequence has completed successfully.
* <p/>
*
* @param request the HTTP client request.
* @param response the HTTP client response.
*
* @return an authentication token if the Kerberos SPNEGO sequence is complete and valid,
* <code>null</code> if it is in progress (in this case the handler handles the response to the client).
*
* @throws IOException thrown if an IO error occurred.
* @throws AuthenticationException thrown if Kerberos SPNEGO sequence failed.
*/
@Override
public AuthenticationToken authenticate(HttpServletRequest request, final HttpServletResponse response)
throws IOException, AuthenticationException {
AuthenticationToken token = null;
String authorization = request.getHeader(KerberosAuthenticator.AUTHORIZATION);
if (authorization == null || !authorization.startsWith(KerberosAuthenticator.NEGOTIATE)) {
response.setHeader(KerberosAuthenticator.WWW_AUTHENTICATE, KerberosAuthenticator.NEGOTIATE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
if (authorization == null) {
LOG.trace("SPNEGO starting");
} else {
LOG.warn("'" + KerberosAuthenticator.AUTHORIZATION + "' does not start with '" +
KerberosAuthenticator.NEGOTIATE + "' : {}", authorization);
}
} else {
authorization = authorization.substring(KerberosAuthenticator.NEGOTIATE.length()).trim();
final Base64 base64 = new Base64(0);
final byte[] clientToken = base64.decode(authorization);
Subject serverSubject = loginContext.getSubject();
try {
token = Subject.doAs(serverSubject, new PrivilegedExceptionAction<AuthenticationToken>() {
@Override
public AuthenticationToken run() throws Exception {
AuthenticationToken token = null;
GSSContext gssContext = null;
try {
gssContext = gssManager.createContext((GSSCredential) null);
byte[] serverToken = gssContext.acceptSecContext(clientToken, 0, clientToken.length);
if (serverToken != null && serverToken.length > 0) {
String authenticate = base64.encodeToString(serverToken);
response.setHeader(KerberosAuthenticator.WWW_AUTHENTICATE,
KerberosAuthenticator.NEGOTIATE + " " + authenticate);
}
if (!gssContext.isEstablished()) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
LOG.trace("SPNEGO in progress");
} else {
String clientPrincipal = gssContext.getSrcName().toString();
KerberosName kerberosName = new KerberosName(clientPrincipal);
String userName = kerberosName.getShortName();
token = new AuthenticationToken(userName, clientPrincipal, TYPE);
response.setStatus(HttpServletResponse.SC_OK);
LOG.trace("SPNEGO completed for principal [{}]", clientPrincipal);
}
} finally {
if (gssContext != null) {
gssContext.dispose();
}
}
return token;
}
});
} catch (PrivilegedActionException ex) {
if (ex.getException() instanceof IOException) {
throw (IOException) ex.getException();
}
else {
throw new AuthenticationException(ex.getException());
}
}
}
return token;
}
}

View File

@ -0,0 +1,134 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.server;
import org.apache.hadoop.alfredo.client.AuthenticationException;
import org.apache.hadoop.alfredo.client.PseudoAuthenticator;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Properties;
/**
* The <code>PseudoAuthenticationHandler</code> provides a pseudo authentication mechanism that accepts
* the user name specified as a query string parameter.
* <p/>
* This mimics the model of Hadoop Simple authentication which trust the 'user.name' property provided in
* the configuration object.
* <p/>
* This handler can be configured to support anonymous users.
* <p/>
* The only supported configuration property is:
* <ul>
* <li>simple.anonymous.allowed: <code>true|false</code>, default value is <code>false</code></li>
* </ul>
*/
public class PseudoAuthenticationHandler implements AuthenticationHandler {
/**
* Constant that identifies the authentication mechanism.
*/
public static final String TYPE = "simple";
/**
* Constant for the configuration property that indicates if anonymous users are allowed.
*/
public static final String ANONYMOUS_ALLOWED = TYPE + ".anonymous.allowed";
private boolean acceptAnonymous;
/**
* Initializes the authentication handler instance.
* <p/>
* This method is invoked by the {@link AuthenticationFilter#init} method.
*
* @param config configuration properties to initialize the handler.
*
* @throws ServletException thrown if the handler could not be initialized.
*/
@Override
public void init(Properties config) throws ServletException {
acceptAnonymous = Boolean.parseBoolean(config.getProperty(ANONYMOUS_ALLOWED, "false"));
}
/**
* Returns if the handler is configured to support anonymous users.
*
* @return if the handler is configured to support anonymous users.
*/
protected boolean getAcceptAnonymous() {
return acceptAnonymous;
}
/**
* Releases any resources initialized by the authentication handler.
* <p/>
* This implementation does a NOP.
*/
@Override
public void destroy() {
}
/**
* Returns the authentication type of the authentication handler, 'simple'.
* <p/>
*
* @return the authentication type of the authentication handler, 'simple'.
*/
@Override
public String getType() {
return TYPE;
}
/**
* Authenticates an HTTP client request.
* <p/>
* It extracts the {@link PseudoAuthenticator#USER_NAME} parameter from the query string and creates
* an {@link AuthenticationToken} with it.
* <p/>
* If the HTTP client request does not contain the {@link PseudoAuthenticator#USER_NAME} parameter and
* the handler is configured to allow anonymous users it returns the {@link AuthenticationToken#ANONYMOUS}
* token.
* <p/>
* If the HTTP client request does not contain the {@link PseudoAuthenticator#USER_NAME} parameter and
* the handler is configured to disallow anonymous users it throws an {@link AuthenticationException}.
*
* @param request the HTTP client request.
* @param response the HTTP client response.
*
* @return an authentication token if the HTTP client request is accepted and credentials are valid.
*
* @throws IOException thrown if an IO error occurred.
* @throws AuthenticationException thrown if HTTP client request was not accepted as an authentication request.
*/
@Override
public AuthenticationToken authenticate(HttpServletRequest request, HttpServletResponse response)
throws IOException, AuthenticationException {
AuthenticationToken token;
String userName = request.getParameter(PseudoAuthenticator.USER_NAME);
if (userName == null) {
if (getAcceptAnonymous()) {
token = AuthenticationToken.ANONYMOUS;
} else {
throw new AuthenticationException("Anonymous requests are disallowed");
}
} else {
token = new AuthenticationToken(userName, userName, TYPE);
}
return token;
}
}

View File

@ -0,0 +1,395 @@
package org.apache.hadoop.alfredo.util;
/**
* 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.
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import sun.security.krb5.Config;
import sun.security.krb5.KrbException;
/**
* This class implements parsing and handling of Kerberos principal names. In
* particular, it splits them apart and translates them down into local
* operating system names.
*/
@SuppressWarnings("all")
@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
@InterfaceStability.Evolving
public class KerberosName {
/** The first component of the name */
private final String serviceName;
/** The second component of the name. It may be null. */
private final String hostName;
/** The realm of the name. */
private final String realm;
/**
* A pattern that matches a Kerberos name with at most 2 components.
*/
private static final Pattern nameParser =
Pattern.compile("([^/@]*)(/([^/@]*))?@([^/@]*)");
/**
* A pattern that matches a string with out '$' and then a single
* parameter with $n.
*/
private static Pattern parameterPattern =
Pattern.compile("([^$]*)(\\$(\\d*))?");
/**
* A pattern for parsing a auth_to_local rule.
*/
private static final Pattern ruleParser =
Pattern.compile("\\s*((DEFAULT)|(RULE:\\[(\\d*):([^\\]]*)](\\(([^)]*)\\))?"+
"(s/([^/]*)/([^/]*)/(g)?)?))");
/**
* A pattern that recognizes simple/non-simple names.
*/
private static final Pattern nonSimplePattern = Pattern.compile("[/@]");
/**
* The list of translation rules.
*/
private static List<Rule> rules;
private static String defaultRealm;
private static Config kerbConf;
static {
try {
kerbConf = Config.getInstance();
defaultRealm = kerbConf.getDefaultRealm();
} catch (KrbException ke) {
defaultRealm="";
}
}
/**
* Create a name from the full Kerberos principal name.
* @param name
*/
public KerberosName(String name) {
Matcher match = nameParser.matcher(name);
if (!match.matches()) {
if (name.contains("@")) {
throw new IllegalArgumentException("Malformed Kerberos name: " + name);
} else {
serviceName = name;
hostName = null;
realm = null;
}
} else {
serviceName = match.group(1);
hostName = match.group(3);
realm = match.group(4);
}
}
/**
* Get the configured default realm.
* @return the default realm from the krb5.conf
*/
public String getDefaultRealm() {
return defaultRealm;
}
/**
* Put the name back together from the parts.
*/
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append(serviceName);
if (hostName != null) {
result.append('/');
result.append(hostName);
}
if (realm != null) {
result.append('@');
result.append(realm);
}
return result.toString();
}
/**
* Get the first component of the name.
* @return the first section of the Kerberos principal name
*/
public String getServiceName() {
return serviceName;
}
/**
* Get the second component of the name.
* @return the second section of the Kerberos principal name, and may be null
*/
public String getHostName() {
return hostName;
}
/**
* Get the realm of the name.
* @return the realm of the name, may be null
*/
public String getRealm() {
return realm;
}
/**
* An encoding of a rule for translating kerberos names.
*/
private static class Rule {
private final boolean isDefault;
private final int numOfComponents;
private final String format;
private final Pattern match;
private final Pattern fromPattern;
private final String toPattern;
private final boolean repeat;
Rule() {
isDefault = true;
numOfComponents = 0;
format = null;
match = null;
fromPattern = null;
toPattern = null;
repeat = false;
}
Rule(int numOfComponents, String format, String match, String fromPattern,
String toPattern, boolean repeat) {
isDefault = false;
this.numOfComponents = numOfComponents;
this.format = format;
this.match = match == null ? null : Pattern.compile(match);
this.fromPattern =
fromPattern == null ? null : Pattern.compile(fromPattern);
this.toPattern = toPattern;
this.repeat = repeat;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
if (isDefault) {
buf.append("DEFAULT");
} else {
buf.append("RULE:[");
buf.append(numOfComponents);
buf.append(':');
buf.append(format);
buf.append(']');
if (match != null) {
buf.append('(');
buf.append(match);
buf.append(')');
}
if (fromPattern != null) {
buf.append("s/");
buf.append(fromPattern);
buf.append('/');
buf.append(toPattern);
buf.append('/');
if (repeat) {
buf.append('g');
}
}
}
return buf.toString();
}
/**
* Replace the numbered parameters of the form $n where n is from 1 to
* the length of params. Normal text is copied directly and $n is replaced
* by the corresponding parameter.
* @param format the string to replace parameters again
* @param params the list of parameters
* @return the generated string with the parameter references replaced.
* @throws BadFormatString
*/
static String replaceParameters(String format,
String[] params) throws BadFormatString {
Matcher match = parameterPattern.matcher(format);
int start = 0;
StringBuilder result = new StringBuilder();
while (start < format.length() && match.find(start)) {
result.append(match.group(1));
String paramNum = match.group(3);
if (paramNum != null) {
try {
int num = Integer.parseInt(paramNum);
if (num < 0 || num > params.length) {
throw new BadFormatString("index " + num + " from " + format +
" is outside of the valid range 0 to " +
(params.length - 1));
}
result.append(params[num]);
} catch (NumberFormatException nfe) {
throw new BadFormatString("bad format in username mapping in " +
paramNum, nfe);
}
}
start = match.end();
}
return result.toString();
}
/**
* Replace the matches of the from pattern in the base string with the value
* of the to string.
* @param base the string to transform
* @param from the pattern to look for in the base string
* @param to the string to replace matches of the pattern with
* @param repeat whether the substitution should be repeated
* @return
*/
static String replaceSubstitution(String base, Pattern from, String to,
boolean repeat) {
Matcher match = from.matcher(base);
if (repeat) {
return match.replaceAll(to);
} else {
return match.replaceFirst(to);
}
}
/**
* Try to apply this rule to the given name represented as a parameter
* array.
* @param params first element is the realm, second and later elements are
* are the components of the name "a/b@FOO" -> {"FOO", "a", "b"}
* @return the short name if this rule applies or null
* @throws IOException throws if something is wrong with the rules
*/
String apply(String[] params) throws IOException {
String result = null;
if (isDefault) {
if (defaultRealm.equals(params[0])) {
result = params[1];
}
} else if (params.length - 1 == numOfComponents) {
String base = replaceParameters(format, params);
if (match == null || match.matcher(base).matches()) {
if (fromPattern == null) {
result = base;
} else {
result = replaceSubstitution(base, fromPattern, toPattern, repeat);
}
}
}
if (result != null && nonSimplePattern.matcher(result).find()) {
throw new NoMatchingRule("Non-simple name " + result +
" after auth_to_local rule " + this);
}
return result;
}
}
static List<Rule> parseRules(String rules) {
List<Rule> result = new ArrayList<Rule>();
String remaining = rules.trim();
while (remaining.length() > 0) {
Matcher matcher = ruleParser.matcher(remaining);
if (!matcher.lookingAt()) {
throw new IllegalArgumentException("Invalid rule: " + remaining);
}
if (matcher.group(2) != null) {
result.add(new Rule());
} else {
result.add(new Rule(Integer.parseInt(matcher.group(4)),
matcher.group(5),
matcher.group(7),
matcher.group(9),
matcher.group(10),
"g".equals(matcher.group(11))));
}
remaining = remaining.substring(matcher.end());
}
return result;
}
@SuppressWarnings("serial")
public static class BadFormatString extends IOException {
BadFormatString(String msg) {
super(msg);
}
BadFormatString(String msg, Throwable err) {
super(msg, err);
}
}
@SuppressWarnings("serial")
public static class NoMatchingRule extends IOException {
NoMatchingRule(String msg) {
super(msg);
}
}
/**
* Get the translation of the principal name into an operating system
* user name.
* @return the short name
* @throws IOException
*/
public String getShortName() throws IOException {
String[] params;
if (hostName == null) {
// if it is already simple, just return it
if (realm == null) {
return serviceName;
}
params = new String[]{realm, serviceName};
} else {
params = new String[]{realm, serviceName, hostName};
}
for(Rule r: rules) {
String result = r.apply(params);
if (result != null) {
return result;
}
}
throw new NoMatchingRule("No rules applied to " + toString());
}
/**
* Set the rules.
* @param ruleString the rules string.
*/
public static void setRules(String ruleString) {
rules = parseRules(ruleString);
}
static void printRules() throws IOException {
int i = 0;
for(Rule r: rules) {
System.out.println(++i + " " + r);
}
}
}

View File

@ -0,0 +1,100 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.util;
import org.apache.commons.codec.binary.Base64;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Signs strings and verifies signed strings using a SHA digest.
*/
public class Signer {
private static final String SIGNATURE = "&s=";
private byte[] secret;
/**
* Creates a Signer instance using the specified secret.
*
* @param secret secret to use for creating the digest.
*/
public Signer(byte[] secret) {
if (secret == null) {
throw new IllegalArgumentException("secret cannot be NULL");
}
this.secret = secret.clone();
}
/**
* Returns a signed string.
* <p/>
* The signature '&s=SIGNATURE' is appended at the end of the string.
*
* @param str string to sign.
*
* @return the signed string.
*/
public String sign(String str) {
if (str == null || str.length() == 0) {
throw new IllegalArgumentException("NULL or empty string to sign");
}
String signature = computeSignature(str);
return str + SIGNATURE + signature;
}
/**
* Verifies a signed string and extracts the original string.
*
* @param signedStr the signed string to verify and extract.
*
* @return the extracted original string.
*
* @throws SignerException thrown if the given string is not a signed string or if the signature is invalid.
*/
public String verifyAndExtract(String signedStr) throws SignerException {
int index = signedStr.lastIndexOf(SIGNATURE);
if (index == -1) {
throw new SignerException("Invalid signed text: " + signedStr);
}
String originalSignature = signedStr.substring(index + SIGNATURE.length());
String rawValue = signedStr.substring(0, index);
String currentSignature = computeSignature(rawValue);
if (!originalSignature.equals(currentSignature)) {
throw new SignerException("Invalid signature");
}
return rawValue;
}
/**
* Returns then signature of a string.
*
* @param str string to sign.
*
* @return the signature for the string.
*/
protected String computeSignature(String str) {
try {
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(str.getBytes());
md.update(secret);
byte[] digest = md.digest();
return new Base64(0).encodeToString(digest);
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException("It should not happen, " + ex.getMessage(), ex);
}
}
}

View File

@ -0,0 +1,31 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.util;
/**
* Exception thrown by {@link Signer} when a string signature is invalid.
*/
public class SignerException extends Exception {
static final long serialVersionUID = 0;
/**
* Creates an exception instance.
*
* @param msg message for the exception.
*/
public SignerException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,75 @@
~~ Licensed 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. See accompanying LICENSE file.
---
Hadoop Alfredo, Java HTTP SPNEGO ${project.version} - Building It
---
---
${maven.build.timestamp}
Hadoop Alfredo, Java HTTP SPNEGO ${project.version} - Building It
\[ {{{./index.html}Go Back}} \]
* Requirements
* Java 6+
* Maven 3+
* Kerberos KDC (for running Kerberos test cases)
* Building
Use Maven goals: clean, test, compile, package, install
Available profiles: docs, testKerberos
* Testing
By default Kerberos testcases are not run.
The requirements to run Kerberos testcases are a running KDC, a keytab
file with a client principal and a kerberos principal.
To run Kerberos tescases use the <<<testKerberos>>> Maven profile:
+---+
$ mvn test -PtestKerberos
+---+
The following Maven <<<-D>>> options can be used to change the default
values:
* <<<alfredo.test.kerberos.realm>>>: default value <<LOCALHOST>>
* <<<alfredo.test.kerberos.client.principal>>>: default value <<client>>
* <<<alfredo.test.kerberos.server.principal>>>: default value
<<HTTP/localhost>> (it must start 'HTTP/')
* <<<alfredo.test.kerberos.keytab.file>>>: default value
<<${HOME}/${USER}.keytab>>
** Generating Documentation
To create the documentation use the <<<docs>>> Maven profile:
+---+
$ mvn package -Pdocs
+---+
The generated documentation is available at
<<<hadoop-alfredo/target/site/>>>.
\[ {{{./index.html}Go Back}} \]

View File

@ -0,0 +1,181 @@
~~ Licensed 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. See accompanying LICENSE file.
---
Hadoop Alfredo, Java HTTP SPNEGO ${project.version} - Server Side
Configuration
---
---
${maven.build.timestamp}
Hadoop Alfredo, Java HTTP SPNEGO ${project.version} - Server Side
Configuration
\[ {{{./index.html}Go Back}} \]
* Server Side Configuration Setup
The {{{./apidocs/org/apache/hadoop/alfredo/server/AuthenticationFilter.html}
AuthenticationFilter filter}} is Alfredo's server side component.
This filter must be configured in front of all the web application resources
that required authenticated requests. For example:
The Alfredo and dependent JAR files must be in the web application classpath
(commonly the <<<WEB-INF/lib>>> directory).
Alfredo uses SLF4J-API for logging. Alfredo Maven POM dependencies define the
SLF4J API dependency but it does not define the dependency on a concrete
logging implementation, this must be addded explicitly to the web
application. For example, if the web applicationan uses Log4j, the
SLF4J-LOG4J12 and LOG4J jar files must be part part of the web application
classpath as well as the Log4j configuration file.
** Common Configuration parameters
* <<<config.prefix>>>: If specified, all other configuration parameter names
must start with the prefix. The default value is no prefix.
* <<<[PREFIX.]type>>>: the authentication type keyword (<<<simple>>> or
<<<kerberos>>>) or a
{{{./apidocs/org/apache/hadoop/alfredo/server/AuthenticationHandler.html}
Authentication handler implementation}}.
* <<<[PREFIX.]signature.secret>>>: The secret to SHA-sign the generated
authentication tokens. If a secret is not provided a random secret is
generated at start up time. If using multiple web application instances
behind a load-balancer a secret must be set for the application to work
properly.
* <<<[PREFIX.]token.validity>>>: The validity -in seconds- of the generated
authentication token. The default value is <<<3600>>> seconds.
* <<<[PREFIX.]cookie.domain>>>: domain to use for the HTTP cookie that stores
the authentication token.
* <<<[PREFIX.]cookie.path>>>: path to use for the HTTP cookie that stores the
authentication token.
** Kerberos Configuration
<<IMPORTANT>>: A KDC must be configured and running.
To use Kerberos SPNEGO as the authentication mechanism, the authentication
filter must be configured with the following init parameters:
* <<<[PREFIX.]type>>>: the keyword <<<kerberos>>>.
* <<<[PREFIX.]kerberos.principal>>>: The web-application Kerberos principal
name. The Kerberos principal name must start with <<<HTTP/...>>>. For
example: <<<HTTP/localhost@LOCALHOST>>>. There is no default value.
* <<<[PREFIX.]kerberos.keytab>>>: The path to the keytab file containing
the credentials for the kerberos principal. For example:
<<</Users/tucu/alfredo.keytab>>>. There is no default value.
<<Example>>:
+---+
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
...
<filter>
<filter-name>kerberosFilter</filter-name>
<filter-class>org.apache.hadoop.alfredo.server.AuthenticationFilter</filter-class>
<init-param>
<param-name>type</param-name>
<param-value>kerberos</param-value>
</init-param>
<init-param>
<param-name>token.validity</param-name>
<param-value>30</param-value>
</init-param>
<init-param>
<param-name>cookie.domain</param-name>
<param-value>.foo.com</param-value>
</init-param>
<init-param>
<param-name>cookie.path</param-name>
<param-value>/</param-value>
</init-param>
<init-param>
<param-name>kerberos.principal</param-name>
<param-value>HTTP/localhost@LOCALHOST</param-value>
</init-param>
<init-param>
<param-name>kerberos.keytab</param-name>
<param-value>/tmp/alfredo.keytab</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>kerberosFilter</filter-name>
<url-pattern>/kerberos/*</url-pattern>
</filter-mapping>
...
</web-app>
+---+
** Pseudo/Simple Configuration
To use Pseudo/Simple as the authentication mechanism (trusting the value of
the query string parameter 'user.name'), the authentication filter must be
configured with the following init parameters:
* <<<[PREFIX.]type>>>: the keyword <<<simple>>>.
* <<<[PREFIX.]simple.anonymous.allowed>>>: is a boolean parameter that
indicates if anonymous requests are allowed or not. The default value is
<<<false>>>.
<<Example>>:
+---+
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
...
<filter>
<filter-name>simpleFilter</filter-name>
<filter-class>org.apache.hadoop.alfredo.server.AuthenticationFilter</filter-class>
<init-param>
<param-name>type</param-name>
<param-value>simple</param-value>
</init-param>
<init-param>
<param-name>token.validity</param-name>
<param-value>30</param-value>
</init-param>
<init-param>
<param-name>cookie.domain</param-name>
<param-value>.foo.com</param-value>
</init-param>
<init-param>
<param-name>cookie.path</param-name>
<param-value>/</param-value>
</init-param>
<init-param>
<param-name>simple.anonymous.allowed</param-name>
<param-value>false</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>simpleFilter</filter-name>
<url-pattern>/simple/*</url-pattern>
</filter-mapping>
...
</web-app>
+---+
\[ {{{./index.html}Go Back}} \]

View File

@ -0,0 +1,137 @@
~~ Licensed 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. See accompanying LICENSE file.
---
Hadoop Alfredo, Java HTTP SPNEGO ${project.version} - Examples
---
---
${maven.build.timestamp}
Hadoop Alfredo, Java HTTP SPNEGO ${project.version} - Examples
\[ {{{./index.html}Go Back}} \]
* Accessing a Alfredo protected URL Using a browser
<<IMPORTANT:>> The browser must support HTTP Kerberos SPNEGO. For example,
Firefox or Internet Explorer.
For Firefox access the low level configuration page by loading the
<<<about:config>>> page. Then go to the
<<<network.negotiate-auth.trusted-uris>>> preference and add the hostname or
the domain of the web server that is HTTP Kerberos SPNEGO protected (if using
multiple domains and hostname use comma to separate them).
* Accessing a Alfredo protected URL Using <<<curl>>>
<<IMPORTANT:>> The <<<curl>>> version must support GSS, run <<<curl -V>>>.
+---+
$ curl -V
curl 7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3
Protocols: tftp ftp telnet dict ldap http file https ftps
Features: GSS-Negotiate IPv6 Largefile NTLM SSL libz
+---+
Login to the KDC using <<kinit>> and then use <<<curl>>> to fetch protected
URL:
+---+
$ kinit
Please enter the password for tucu@LOCALHOST:
$ curl --negotiate -u foo -b ~/cookiejar.txt -c ~/cookiejar.txt http://localhost:8080/alfredo-examples/kerberos/who
Enter host password for user 'tucu':
Hello Alfredo!
+---+
* The <<<--negotiate>>> option enables SPNEGO in <<<curl>>>.
* The <<<-u foo>>> option is required but the user ignored (the principal
that has been kinit-ed is used).
* The <<<-b>>> and <<<-c>>> are use to store and send HTTP Cookies.
* Using the Java Client
Use the <<<AuthenticatedURL>>> class to obtain an authenticated HTTP
connection:
+---+
...
URL url = new URL("http://localhost:8080/alfredo/kerberos/who");
AuthenticatedURL.Token token = new AuthenticatedURL.Token();
...
HttpURLConnection conn = new AuthenticatedURL(url, token).openConnection();
...
conn = new AuthenticatedURL(url, token).openConnection();
...
+---+
* Building and Running the Examples
Download Alfredo's source code, the examples are in the
<<<src/main/examples>>> directory.
** Server Example:
Edit the <<<src/main/examples/src/main/webapp/WEB-INF/web.xml>>> and set the
right configuration init parameters for the <<<AuthenticationFilter>>>
definition configured for Kerberos (the right Kerberos principal and keytab
file must be specified). Refer to the {{{./Configuration.html}Configuration
document}} for details.
Create the web application WAR file by running the <<<mvn package>>> command.
Deploy the WAR file in a servlet container. For example, if using Tomcat,
copy the WAR file to Tomcat's <<<webapps/>>> directory.
Start the servlet container.
** Accessing the server using <<<curl>>>
Try accessing protected resources using <<<curl>>>. The protected resources
are:
+---+
$ kinit
Please enter the password for tucu@LOCALHOST:
$ curl http://localhost:8080/alfredo-examples/anonymous/who
$ curl http://localhost:8080/alfredo-examples/simple/who?user.name=foo
$ curl --negotiate -u foo -b ~/cookiejar.txt -c ~/cookiejar.txt http://localhost:8080/alfredo-examples/kerberos/who
+---+
** Accessing the server using the Java client example
+---+
$ kinit
Please enter the password for tucu@LOCALHOST:
$ cd examples
$ mvn exec:java -Durl=http://localhost:8080/alfredo-examples/kerberos/who
....
Token value: "u=tucu,p=tucu@LOCALHOST,t=kerberos,e=1295305313146,s=sVZ1mpSnC5TKhZQE3QLN5p2DWBo="
Status code: 200 OK
You are: user[tucu] principal[tucu@LOCALHOST]
....
+---+
\[ {{{./index.html}Go Back}} \]

View File

@ -0,0 +1,53 @@
~~ Licensed 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. See accompanying LICENSE file.
---
Hadoop Alfredo, Java HTTP SPNEGO ${project.version}
---
---
${maven.build.timestamp}
Hadoop Alfredo, Java HTTP SPNEGO ${project.version}
Hadoop Alfredo is a Java library consisting of a client and a server
components to enable Kerberos SPNEGO authentication for HTTP.
Alfredo also supports additional authentication mechanisms on the client
and the server side via 2 simple interfaces.
* License
Alfredo is distributed under {{{http://www.apache.org/licenses/}Apache
License 2.0}}.
* How Does Alfredo Works?
Alfredo enforces authentication on protected resources, once authentiation
has been established it sets a signed HTTP Cookie that contains an
authentication token with the user name, user principal, authentication type
and expiration time.
Subsequent HTTP client requests presenting the signed HTTP Cookie have access
to the protected resources until the HTTP Cookie expires.
* User Documentation
* {{{./Examples.html}Examples}}
* {{{./Configuration.html}Configuration}}
* {{{./BuildingIt.html}Building It}}
* {{{./apidocs/index.html}JavaDocs}}
* {{{./dependencies.html}Dependencies}}

View File

@ -0,0 +1,34 @@
<!--
Licensed 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. See accompanying LICENSE file.
-->
<project name="Hadoop Alfredo">
<version position="right"/>
<bannerLeft>
<name>&nbsp;</name>
</bannerLeft>
<skin>
<groupId>org.apache.maven.skins</groupId>
<artifactId>maven-stylus-skin</artifactId>
<version>1.1</version>
</skin>
<body>
<links>
<item name="Apache Hadoop" href="http://hadoop.apache.org/"/>
</links>
</body>
</project>

View File

@ -0,0 +1,129 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo;
import com.sun.security.auth.module.Krb5LoginModule;
import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosPrincipal;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import java.io.File;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
/**
* Test helper class for Java Kerberos setup.
*/
public class KerberosTestUtils {
private static final String PREFIX = "alfredo.test.";
public static final String REALM = PREFIX + "kerberos.realm";
public static final String CLIENT_PRINCIPAL = PREFIX + "kerberos.client.principal";
public static final String SERVER_PRINCIPAL = PREFIX + "kerberos.server.principal";
public static final String KEYTAB_FILE = PREFIX + "kerberos.keytab.file";
public static String getRealm() {
return System.getProperty(REALM, "LOCALHOST");
}
public static String getClientPrincipal() {
return System.getProperty(CLIENT_PRINCIPAL, "client") + "@" + getRealm();
}
public static String getServerPrincipal() {
return System.getProperty(SERVER_PRINCIPAL, "HTTP/localhost") + "@" + getRealm();
}
public static String getKeytabFile() {
String keytabFile =
new File(System.getProperty("user.home"), System.getProperty("user.name") + ".keytab").toString();
return System.getProperty(KEYTAB_FILE, keytabFile);
}
private static class KerberosConfiguration extends Configuration {
private String principal;
public KerberosConfiguration(String principal) {
this.principal = principal;
}
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
Map<String, String> options = new HashMap<String, String>();
options.put("keyTab", KerberosTestUtils.getKeytabFile());
options.put("principal", principal);
options.put("useKeyTab", "true");
options.put("storeKey", "true");
options.put("doNotPrompt", "true");
options.put("useTicketCache", "true");
options.put("renewTGT", "true");
options.put("refreshKrb5Config", "true");
options.put("isInitiator", "true");
String ticketCache = System.getenv("KRB5CCNAME");
if (ticketCache != null) {
options.put("ticketCache", ticketCache);
}
options.put("debug", "true");
return new AppConfigurationEntry[]{
new AppConfigurationEntry(Krb5LoginModule.class.getName(),
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
options),};
}
}
public static <T> T doAs(String principal, final Callable<T> callable) throws Exception {
LoginContext loginContext = null;
try {
Set<Principal> principals = new HashSet<Principal>();
principals.add(new KerberosPrincipal(KerberosTestUtils.getClientPrincipal()));
Subject subject = new Subject(false, principals, new HashSet<Object>(), new HashSet<Object>());
loginContext = new LoginContext("", subject, null, new KerberosConfiguration(principal));
loginContext.login();
subject = loginContext.getSubject();
return Subject.doAs(subject, new PrivilegedExceptionAction<T>() {
@Override
public T run() throws Exception {
return callable.call();
}
});
} catch (PrivilegedActionException ex) {
throw ex.getException();
} finally {
if (loginContext != null) {
loginContext.logout();
}
}
}
public static <T> T doAsClient(Callable<T> callable) throws Exception {
return doAs(getClientPrincipal(), callable);
}
public static <T> T doAsServer(Callable<T> callable) throws Exception {
return doAs(getServerPrincipal(), callable);
}
}

View File

@ -0,0 +1,152 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.client;
import org.apache.hadoop.alfredo.server.AuthenticationFilter;
import junit.framework.TestCase;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.servlet.Context;
import org.mortbay.jetty.servlet.FilterHolder;
import org.mortbay.jetty.servlet.ServletHolder;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.ServerSocket;
import java.net.URL;
import java.util.Properties;
public abstract class AuthenticatorTestCase extends TestCase {
private Server server;
private String host = null;
private int port = -1;
Context context;
private static Properties authenticatorConfig;
protected static void setAuthenticationHandlerConfig(Properties config) {
authenticatorConfig = config;
}
public static class TestFilter extends AuthenticationFilter {
@Override
protected Properties getConfiguration(String configPrefix, FilterConfig filterConfig) throws ServletException {
return authenticatorConfig;
}
}
public static class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setStatus(HttpServletResponse.SC_OK);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
InputStream is = req.getInputStream();
OutputStream os = resp.getOutputStream();
int c = is.read();
while (c > -1) {
os.write(c);
c = is.read();
}
is.close();
os.close();
resp.setStatus(HttpServletResponse.SC_OK);
}
}
protected void start() throws Exception {
server = new Server(0);
context = new Context();
context.setContextPath("/foo");
server.setHandler(context);
context.addFilter(new FilterHolder(TestFilter.class), "/*", 0);
context.addServlet(new ServletHolder(TestServlet.class), "/bar");
host = "localhost";
ServerSocket ss = new ServerSocket(0);
port = ss.getLocalPort();
ss.close();
server.getConnectors()[0].setHost(host);
server.getConnectors()[0].setPort(port);
server.start();
System.out.println("Running embedded servlet container at: http://" + host + ":" + port);
}
protected void stop() throws Exception {
try {
server.stop();
} catch (Exception e) {
}
try {
server.destroy();
} catch (Exception e) {
}
}
protected String getBaseURL() {
return "http://" + host + ":" + port + "/foo/bar";
}
private String POST = "test";
protected void _testAuthentication(Authenticator authenticator, boolean doPost) throws Exception {
start();
try {
URL url = new URL(getBaseURL());
AuthenticatedURL.Token token = new AuthenticatedURL.Token();
AuthenticatedURL aUrl = new AuthenticatedURL(authenticator);
HttpURLConnection conn = aUrl.openConnection(url, token);
String tokenStr = token.toString();
if (doPost) {
conn.setRequestMethod("POST");
conn.setDoOutput(true);
}
conn.connect();
if (doPost) {
Writer writer = new OutputStreamWriter(conn.getOutputStream());
writer.write(POST);
writer.close();
}
assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode());
if (doPost) {
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String echo = reader.readLine();
assertEquals(POST, echo);
assertNull(reader.readLine());
}
aUrl = new AuthenticatedURL();
conn = aUrl.openConnection(url, token);
conn.connect();
assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode());
assertEquals(tokenStr, token.toString());
} finally {
stop();
}
}
}

View File

@ -0,0 +1,113 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.client;
import junit.framework.TestCase;
import org.mockito.Mockito;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TestAuthenticatedURL extends TestCase {
public void testToken() throws Exception {
AuthenticatedURL.Token token = new AuthenticatedURL.Token();
assertFalse(token.isSet());
token = new AuthenticatedURL.Token("foo");
assertTrue(token.isSet());
assertEquals("foo", token.toString());
AuthenticatedURL.Token token1 = new AuthenticatedURL.Token();
AuthenticatedURL.Token token2 = new AuthenticatedURL.Token();
assertEquals(token1.hashCode(), token2.hashCode());
assertTrue(token1.equals(token2));
token1 = new AuthenticatedURL.Token();
token2 = new AuthenticatedURL.Token("foo");
assertNotSame(token1.hashCode(), token2.hashCode());
assertFalse(token1.equals(token2));
token1 = new AuthenticatedURL.Token("foo");
token2 = new AuthenticatedURL.Token();
assertNotSame(token1.hashCode(), token2.hashCode());
assertFalse(token1.equals(token2));
token1 = new AuthenticatedURL.Token("foo");
token2 = new AuthenticatedURL.Token("foo");
assertEquals(token1.hashCode(), token2.hashCode());
assertTrue(token1.equals(token2));
token1 = new AuthenticatedURL.Token("bar");
token2 = new AuthenticatedURL.Token("foo");
assertNotSame(token1.hashCode(), token2.hashCode());
assertFalse(token1.equals(token2));
token1 = new AuthenticatedURL.Token("foo");
token2 = new AuthenticatedURL.Token("bar");
assertNotSame(token1.hashCode(), token2.hashCode());
assertFalse(token1.equals(token2));
}
public void testInjectToken() throws Exception {
HttpURLConnection conn = Mockito.mock(HttpURLConnection.class);
AuthenticatedURL.Token token = new AuthenticatedURL.Token();
token.set("foo");
AuthenticatedURL.injectToken(conn, token);
Mockito.verify(conn).addRequestProperty(Mockito.eq("Cookie"), Mockito.anyString());
}
public void testExtractTokenOK() throws Exception {
HttpURLConnection conn = Mockito.mock(HttpURLConnection.class);
Mockito.when(conn.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
String tokenStr = "foo";
Map<String, List<String>> headers = new HashMap<String, List<String>>();
List<String> cookies = new ArrayList<String>();
cookies.add(AuthenticatedURL.AUTH_COOKIE + "=" + tokenStr);
headers.put("Set-Cookie", cookies);
Mockito.when(conn.getHeaderFields()).thenReturn(headers);
AuthenticatedURL.Token token = new AuthenticatedURL.Token();
AuthenticatedURL.extractToken(conn, token);
assertEquals(tokenStr, token.toString());
}
public void testExtractTokenFail() throws Exception {
HttpURLConnection conn = Mockito.mock(HttpURLConnection.class);
Mockito.when(conn.getResponseCode()).thenReturn(HttpURLConnection.HTTP_UNAUTHORIZED);
String tokenStr = "foo";
Map<String, List<String>> headers = new HashMap<String, List<String>>();
List<String> cookies = new ArrayList<String>();
cookies.add(AuthenticatedURL.AUTH_COOKIE + "=" + tokenStr);
headers.put("Set-Cookie", cookies);
Mockito.when(conn.getHeaderFields()).thenReturn(headers);
try {
AuthenticatedURL.extractToken(conn, new AuthenticatedURL.Token());
fail();
} catch (AuthenticationException ex) {
// Expected
} catch (Exception ex) {
fail();
}
}
}

View File

@ -0,0 +1,83 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.client;
import org.apache.hadoop.alfredo.KerberosTestUtils;
import org.apache.hadoop.alfredo.server.AuthenticationFilter;
import org.apache.hadoop.alfredo.server.PseudoAuthenticationHandler;
import org.apache.hadoop.alfredo.server.KerberosAuthenticationHandler;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Properties;
import java.util.concurrent.Callable;
public class TestKerberosAuthenticator extends AuthenticatorTestCase {
private Properties getAuthenticationHandlerConfiguration() {
Properties props = new Properties();
props.setProperty(AuthenticationFilter.AUTH_TYPE, "kerberos");
props.setProperty(KerberosAuthenticationHandler.PRINCIPAL, KerberosTestUtils.getServerPrincipal());
props.setProperty(KerberosAuthenticationHandler.KEYTAB, KerberosTestUtils.getKeytabFile());
props.setProperty(KerberosAuthenticationHandler.NAME_RULES,
"RULE:[1:$1@$0](.*@" + KerberosTestUtils.getRealm()+")s/@.*//\n");
return props;
}
public void testFallbacktoPseudoAuthenticator() throws Exception {
Properties props = new Properties();
props.setProperty(AuthenticationFilter.AUTH_TYPE, "simple");
props.setProperty(PseudoAuthenticationHandler.ANONYMOUS_ALLOWED, "false");
setAuthenticationHandlerConfig(props);
_testAuthentication(new KerberosAuthenticator(), false);
}
public void testNotAuthenticated() throws Exception {
setAuthenticationHandlerConfig(getAuthenticationHandlerConfiguration());
start();
try {
URL url = new URL(getBaseURL());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect();
assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, conn.getResponseCode());
assertTrue(conn.getHeaderField(KerberosAuthenticator.WWW_AUTHENTICATE) != null);
} finally {
stop();
}
}
public void testAuthentication() throws Exception {
setAuthenticationHandlerConfig(getAuthenticationHandlerConfiguration());
KerberosTestUtils.doAsClient(new Callable<Void>() {
@Override
public Void call() throws Exception {
_testAuthentication(new KerberosAuthenticator(), false);
return null;
}
});
}
public void testAuthenticationPost() throws Exception {
setAuthenticationHandlerConfig(getAuthenticationHandlerConfiguration());
KerberosTestUtils.doAsClient(new Callable<Void>() {
@Override
public Void call() throws Exception {
_testAuthentication(new KerberosAuthenticator(), true);
return null;
}
});
}
}

View File

@ -0,0 +1,83 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.client;
import org.apache.hadoop.alfredo.server.AuthenticationFilter;
import org.apache.hadoop.alfredo.server.PseudoAuthenticationHandler;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Properties;
public class TestPseudoAuthenticator extends AuthenticatorTestCase {
private Properties getAuthenticationHandlerConfiguration(boolean anonymousAllowed) {
Properties props = new Properties();
props.setProperty(AuthenticationFilter.AUTH_TYPE, "simple");
props.setProperty(PseudoAuthenticationHandler.ANONYMOUS_ALLOWED, Boolean.toString(anonymousAllowed));
return props;
}
public void testGetUserName() throws Exception {
PseudoAuthenticator authenticator = new PseudoAuthenticator();
assertEquals(System.getProperty("user.name"), authenticator.getUserName());
}
public void testAnonymousAllowed() throws Exception {
setAuthenticationHandlerConfig(getAuthenticationHandlerConfiguration(true));
start();
try {
URL url = new URL(getBaseURL());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect();
assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode());
} finally {
stop();
}
}
public void testAnonymousDisallowed() throws Exception {
setAuthenticationHandlerConfig(getAuthenticationHandlerConfiguration(false));
start();
try {
URL url = new URL(getBaseURL());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect();
assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, conn.getResponseCode());
} finally {
stop();
}
}
public void testAuthenticationAnonymousAllowed() throws Exception {
setAuthenticationHandlerConfig(getAuthenticationHandlerConfiguration(true));
_testAuthentication(new PseudoAuthenticator(), false);
}
public void testAuthenticationAnonymousDisallowed() throws Exception {
setAuthenticationHandlerConfig(getAuthenticationHandlerConfiguration(false));
_testAuthentication(new PseudoAuthenticator(), false);
}
public void testAuthenticationAnonymousAllowedWithPost() throws Exception {
setAuthenticationHandlerConfig(getAuthenticationHandlerConfiguration(true));
_testAuthentication(new PseudoAuthenticator(), true);
}
public void testAuthenticationAnonymousDisallowedWithPost() throws Exception {
setAuthenticationHandlerConfig(getAuthenticationHandlerConfiguration(false));
_testAuthentication(new PseudoAuthenticator(), true);
}
}

View File

@ -0,0 +1,611 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.server;
import org.apache.hadoop.alfredo.client.AuthenticatedURL;
import org.apache.hadoop.alfredo.client.AuthenticationException;
import org.apache.hadoop.alfredo.util.Signer;
import junit.framework.TestCase;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Properties;
import java.util.Vector;
public class TestAuthenticationFilter extends TestCase {
public void testGetConfiguration() throws Exception {
AuthenticationFilter filter = new AuthenticationFilter();
FilterConfig config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameter(AuthenticationFilter.CONFIG_PREFIX)).thenReturn("");
Mockito.when(config.getInitParameter("a")).thenReturn("A");
Mockito.when(config.getInitParameterNames()).thenReturn(new Vector(Arrays.asList("a")).elements());
Properties props = filter.getConfiguration("", config);
assertEquals("A", props.getProperty("a"));
config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameter(AuthenticationFilter.CONFIG_PREFIX)).thenReturn("foo");
Mockito.when(config.getInitParameter("foo.a")).thenReturn("A");
Mockito.when(config.getInitParameterNames()).thenReturn(new Vector(Arrays.asList("foo.a")).elements());
props = filter.getConfiguration("foo.", config);
assertEquals("A", props.getProperty("a"));
}
public void testInitEmpty() throws Exception {
AuthenticationFilter filter = new AuthenticationFilter();
try {
FilterConfig config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameterNames()).thenReturn(new Vector().elements());
filter.init(config);
fail();
} catch (ServletException ex) {
// Expected
} catch (Exception ex) {
fail();
} finally {
filter.destroy();
}
}
public static class DummyAuthenticationHandler implements AuthenticationHandler {
public static boolean init;
public static boolean destroy;
public static final String TYPE = "dummy";
public static void reset() {
init = false;
destroy = false;
}
@Override
public void init(Properties config) throws ServletException {
init = true;
}
@Override
public void destroy() {
destroy = true;
}
@Override
public String getType() {
return TYPE;
}
@Override
public AuthenticationToken authenticate(HttpServletRequest request, HttpServletResponse response)
throws IOException, AuthenticationException {
AuthenticationToken token = null;
String param = request.getParameter("authenticated");
if (param != null && param.equals("true")) {
token = new AuthenticationToken("u", "p", "t");
token.setExpires(System.currentTimeMillis() + 1000);
} else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
return token;
}
}
public void testInit() throws Exception {
// minimal configuration & simple auth handler (Pseudo)
AuthenticationFilter filter = new AuthenticationFilter();
try {
FilterConfig config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn("simple");
Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TOKEN_VALIDITY)).thenReturn("1000");
Mockito.when(config.getInitParameterNames()).thenReturn(
new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE,
AuthenticationFilter.AUTH_TOKEN_VALIDITY)).elements());
filter.init(config);
assertEquals(PseudoAuthenticationHandler.class, filter.getAuthenticationHandler().getClass());
assertTrue(filter.isRandomSecret());
assertNull(filter.getCookieDomain());
assertNull(filter.getCookiePath());
assertEquals(1000, filter.getValidity());
} finally {
filter.destroy();
}
// custom secret
filter = new AuthenticationFilter();
try {
FilterConfig config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn("simple");
Mockito.when(config.getInitParameter(AuthenticationFilter.SIGNATURE_SECRET)).thenReturn("secret");
Mockito.when(config.getInitParameterNames()).thenReturn(
new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE,
AuthenticationFilter.SIGNATURE_SECRET)).elements());
filter.init(config);
assertFalse(filter.isRandomSecret());
} finally {
filter.destroy();
}
// custom cookie domain and cookie path
filter = new AuthenticationFilter();
try {
FilterConfig config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn("simple");
Mockito.when(config.getInitParameter(AuthenticationFilter.COOKIE_DOMAIN)).thenReturn(".foo.com");
Mockito.when(config.getInitParameter(AuthenticationFilter.COOKIE_PATH)).thenReturn("/bar");
Mockito.when(config.getInitParameterNames()).thenReturn(
new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE,
AuthenticationFilter.COOKIE_DOMAIN,
AuthenticationFilter.COOKIE_PATH)).elements());
filter.init(config);
assertEquals(".foo.com", filter.getCookieDomain());
assertEquals("/bar", filter.getCookiePath());
} finally {
filter.destroy();
}
// authentication handler lifecycle, and custom impl
DummyAuthenticationHandler.reset();
filter = new AuthenticationFilter();
try {
FilterConfig config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn(
DummyAuthenticationHandler.class.getName());
Mockito.when(config.getInitParameterNames()).thenReturn(
new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE)).elements());
filter.init(config);
assertTrue(DummyAuthenticationHandler.init);
} finally {
filter.destroy();
assertTrue(DummyAuthenticationHandler.destroy);
}
// kerberos auth handler
filter = new AuthenticationFilter();
try {
FilterConfig config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn("kerberos");
Mockito.when(config.getInitParameterNames()).thenReturn(
new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE)).elements());
filter.init(config);
} catch (ServletException ex) {
// Expected
} finally {
assertEquals(KerberosAuthenticationHandler.class, filter.getAuthenticationHandler().getClass());
filter.destroy();
}
}
public void testGetRequestURL() throws Exception {
AuthenticationFilter filter = new AuthenticationFilter();
try {
FilterConfig config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn(
DummyAuthenticationHandler.class.getName());
Mockito.when(config.getInitParameterNames()).thenReturn(
new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE)).elements());
filter.init(config);
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getRequestURL()).thenReturn(new StringBuffer("http://foo:8080/bar"));
Mockito.when(request.getQueryString()).thenReturn("a=A&b=B");
assertEquals("http://foo:8080/bar?a=A&b=B", filter.getRequestURL(request));
} finally {
filter.destroy();
}
}
public void testGetToken() throws Exception {
AuthenticationFilter filter = new AuthenticationFilter();
try {
FilterConfig config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn(
DummyAuthenticationHandler.class.getName());
Mockito.when(config.getInitParameter(AuthenticationFilter.SIGNATURE_SECRET)).thenReturn("secret");
Mockito.when(config.getInitParameterNames()).thenReturn(
new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE,
AuthenticationFilter.SIGNATURE_SECRET)).elements());
filter.init(config);
AuthenticationToken token = new AuthenticationToken("u", "p", DummyAuthenticationHandler.TYPE);
token.setExpires(System.currentTimeMillis() + 1000);
Signer signer = new Signer("secret".getBytes());
String tokenSigned = signer.sign(token.toString());
Cookie cookie = new Cookie(AuthenticatedURL.AUTH_COOKIE, tokenSigned);
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie});
AuthenticationToken newToken = filter.getToken(request);
assertEquals(token.toString(), newToken.toString());
} finally {
filter.destroy();
}
}
public void testGetTokenExpired() throws Exception {
AuthenticationFilter filter = new AuthenticationFilter();
try {
FilterConfig config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn(
DummyAuthenticationHandler.class.getName());
Mockito.when(config.getInitParameter(AuthenticationFilter.SIGNATURE_SECRET)).thenReturn("secret");
Mockito.when(config.getInitParameterNames()).thenReturn(
new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE,
AuthenticationFilter.SIGNATURE_SECRET)).elements());
filter.init(config);
AuthenticationToken token = new AuthenticationToken("u", "p", "invalidtype");
token.setExpires(System.currentTimeMillis() - 1000);
Signer signer = new Signer("secret".getBytes());
String tokenSigned = signer.sign(token.toString());
Cookie cookie = new Cookie(AuthenticatedURL.AUTH_COOKIE, tokenSigned);
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie});
try {
filter.getToken(request);
fail();
} catch (AuthenticationException ex) {
// Expected
} catch (Exception ex) {
fail();
}
} finally {
filter.destroy();
}
}
public void testGetTokenInvalidType() throws Exception {
AuthenticationFilter filter = new AuthenticationFilter();
try {
FilterConfig config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn(
DummyAuthenticationHandler.class.getName());
Mockito.when(config.getInitParameter(AuthenticationFilter.SIGNATURE_SECRET)).thenReturn("secret");
Mockito.when(config.getInitParameterNames()).thenReturn(
new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE,
AuthenticationFilter.SIGNATURE_SECRET)).elements());
filter.init(config);
AuthenticationToken token = new AuthenticationToken("u", "p", "invalidtype");
token.setExpires(System.currentTimeMillis() + 1000);
Signer signer = new Signer("secret".getBytes());
String tokenSigned = signer.sign(token.toString());
Cookie cookie = new Cookie(AuthenticatedURL.AUTH_COOKIE, tokenSigned);
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie});
try {
filter.getToken(request);
fail();
} catch (AuthenticationException ex) {
// Expected
} catch (Exception ex) {
fail();
}
} finally {
filter.destroy();
}
}
public void testDoFilterNotAuthenticated() throws Exception {
AuthenticationFilter filter = new AuthenticationFilter();
try {
FilterConfig config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn(
DummyAuthenticationHandler.class.getName());
Mockito.when(config.getInitParameterNames()).thenReturn(
new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE)).elements());
filter.init(config);
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getRequestURL()).thenReturn(new StringBuffer("http://foo:8080/bar"));
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
FilterChain chain = Mockito.mock(FilterChain.class);
Mockito.doAnswer(
new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
fail();
return null;
}
}
).when(chain).doFilter(Mockito.<ServletRequest>anyObject(), Mockito.<ServletResponse>anyObject());
filter.doFilter(request, response, chain);
Mockito.verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
} finally {
filter.destroy();
}
}
private void _testDoFilterAuthentication(boolean withDomainPath) throws Exception {
AuthenticationFilter filter = new AuthenticationFilter();
try {
FilterConfig config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn(
DummyAuthenticationHandler.class.getName());
Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TOKEN_VALIDITY)).thenReturn("1000");
Mockito.when(config.getInitParameter(AuthenticationFilter.SIGNATURE_SECRET)).thenReturn("secret");
Mockito.when(config.getInitParameterNames()).thenReturn(
new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE,
AuthenticationFilter.AUTH_TOKEN_VALIDITY,
AuthenticationFilter.SIGNATURE_SECRET)).elements());
if (withDomainPath) {
Mockito.when(config.getInitParameter(AuthenticationFilter.COOKIE_DOMAIN)).thenReturn(".foo.com");
Mockito.when(config.getInitParameter(AuthenticationFilter.COOKIE_PATH)).thenReturn("/bar");
Mockito.when(config.getInitParameterNames()).thenReturn(
new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE,
AuthenticationFilter.AUTH_TOKEN_VALIDITY,
AuthenticationFilter.SIGNATURE_SECRET,
AuthenticationFilter.COOKIE_DOMAIN,
AuthenticationFilter.COOKIE_PATH)).elements());
}
filter.init(config);
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getParameter("authenticated")).thenReturn("true");
Mockito.when(request.getRequestURL()).thenReturn(new StringBuffer("http://foo:8080/bar"));
Mockito.when(request.getQueryString()).thenReturn("authenticated=true");
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
FilterChain chain = Mockito.mock(FilterChain.class);
final boolean[] calledDoFilter = new boolean[1];
Mockito.doAnswer(
new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
calledDoFilter[0] = true;
return null;
}
}
).when(chain).doFilter(Mockito.<ServletRequest>anyObject(), Mockito.<ServletResponse>anyObject());
final Cookie[] setCookie = new Cookie[1];
Mockito.doAnswer(
new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
setCookie[0] = (Cookie) args[0];
return null;
}
}
).when(response).addCookie(Mockito.<Cookie>anyObject());
filter.doFilter(request, response, chain);
assertNotNull(setCookie[0]);
assertEquals(AuthenticatedURL.AUTH_COOKIE, setCookie[0].getName());
assertTrue(setCookie[0].getValue().contains("u="));
assertTrue(setCookie[0].getValue().contains("p="));
assertTrue(setCookie[0].getValue().contains("t="));
assertTrue(setCookie[0].getValue().contains("e="));
assertTrue(setCookie[0].getValue().contains("s="));
assertTrue(calledDoFilter[0]);
Signer signer = new Signer("secret".getBytes());
String value = signer.verifyAndExtract(setCookie[0].getValue());
AuthenticationToken token = AuthenticationToken.parse(value);
assertEquals(System.currentTimeMillis() + 1000 * 1000, token.getExpires(), 100);
if (withDomainPath) {
assertEquals(".foo.com", setCookie[0].getDomain());
assertEquals("/bar", setCookie[0].getPath());
} else {
assertNull(setCookie[0].getDomain());
assertNull(setCookie[0].getPath());
}
} finally {
filter.destroy();
}
}
public void testDoFilterAuthentication() throws Exception {
_testDoFilterAuthentication(false);
}
public void testDoFilterAuthenticationWithDomainPath() throws Exception {
_testDoFilterAuthentication(true);
}
public void testDoFilterAuthenticated() throws Exception {
AuthenticationFilter filter = new AuthenticationFilter();
try {
FilterConfig config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn(
DummyAuthenticationHandler.class.getName());
Mockito.when(config.getInitParameterNames()).thenReturn(
new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE)).elements());
filter.init(config);
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getRequestURL()).thenReturn(new StringBuffer("http://foo:8080/bar"));
AuthenticationToken token = new AuthenticationToken("u", "p", "t");
token.setExpires(System.currentTimeMillis() + 1000);
Signer signer = new Signer("alfredo".getBytes());
String tokenSigned = signer.sign(token.toString());
Cookie cookie = new Cookie(AuthenticatedURL.AUTH_COOKIE, tokenSigned);
Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie});
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
FilterChain chain = Mockito.mock(FilterChain.class);
Mockito.doAnswer(
new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
HttpServletRequest request = (HttpServletRequest) args[0];
assertEquals("u", request.getRemoteUser());
assertEquals("p", request.getUserPrincipal().getName());
return null;
}
}
).when(chain).doFilter(Mockito.<ServletRequest>anyObject(), Mockito.<ServletResponse>anyObject());
filter.doFilter(request, response, chain);
} finally {
filter.destroy();
}
}
public void testDoFilterAuthenticatedExpired() throws Exception {
AuthenticationFilter filter = new AuthenticationFilter();
try {
FilterConfig config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn(
DummyAuthenticationHandler.class.getName());
Mockito.when(config.getInitParameterNames()).thenReturn(
new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE)).elements());
filter.init(config);
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getRequestURL()).thenReturn(new StringBuffer("http://foo:8080/bar"));
AuthenticationToken token = new AuthenticationToken("u", "p", DummyAuthenticationHandler.TYPE);
token.setExpires(System.currentTimeMillis() - 1000);
Signer signer = new Signer("alfredo".getBytes());
String tokenSigned = signer.sign(token.toString());
Cookie cookie = new Cookie(AuthenticatedURL.AUTH_COOKIE, tokenSigned);
Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie});
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
FilterChain chain = Mockito.mock(FilterChain.class);
Mockito.doAnswer(
new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
fail();
return null;
}
}
).when(chain).doFilter(Mockito.<ServletRequest>anyObject(), Mockito.<ServletResponse>anyObject());
final Cookie[] setCookie = new Cookie[1];
Mockito.doAnswer(
new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
setCookie[0] = (Cookie) args[0];
return null;
}
}
).when(response).addCookie(Mockito.<Cookie>anyObject());
filter.doFilter(request, response, chain);
Mockito.verify(response).sendError(Mockito.eq(HttpServletResponse.SC_UNAUTHORIZED), Mockito.anyString());
assertNotNull(setCookie[0]);
assertEquals(AuthenticatedURL.AUTH_COOKIE, setCookie[0].getName());
assertEquals("", setCookie[0].getValue());
} finally {
filter.destroy();
}
}
public void testDoFilterAuthenticatedInvalidType() throws Exception {
AuthenticationFilter filter = new AuthenticationFilter();
try {
FilterConfig config = Mockito.mock(FilterConfig.class);
Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn(
DummyAuthenticationHandler.class.getName());
Mockito.when(config.getInitParameterNames()).thenReturn(
new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE)).elements());
filter.init(config);
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getRequestURL()).thenReturn(new StringBuffer("http://foo:8080/bar"));
AuthenticationToken token = new AuthenticationToken("u", "p", "invalidtype");
token.setExpires(System.currentTimeMillis() + 1000);
Signer signer = new Signer("alfredo".getBytes());
String tokenSigned = signer.sign(token.toString());
Cookie cookie = new Cookie(AuthenticatedURL.AUTH_COOKIE, tokenSigned);
Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie});
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
FilterChain chain = Mockito.mock(FilterChain.class);
Mockito.doAnswer(
new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
fail();
return null;
}
}
).when(chain).doFilter(Mockito.<ServletRequest>anyObject(), Mockito.<ServletResponse>anyObject());
final Cookie[] setCookie = new Cookie[1];
Mockito.doAnswer(
new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
setCookie[0] = (Cookie) args[0];
return null;
}
}
).when(response).addCookie(Mockito.<Cookie>anyObject());
filter.doFilter(request, response, chain);
Mockito.verify(response).sendError(Mockito.eq(HttpServletResponse.SC_UNAUTHORIZED), Mockito.anyString());
assertNotNull(setCookie[0]);
assertEquals(AuthenticatedURL.AUTH_COOKIE, setCookie[0].getName());
assertEquals("", setCookie[0].getValue());
} finally {
filter.destroy();
}
}
}

View File

@ -0,0 +1,124 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.server;
import org.apache.hadoop.alfredo.client.AuthenticationException;
import junit.framework.TestCase;
public class TestAuthenticationToken extends TestCase {
public void testAnonymous() {
assertNotNull(AuthenticationToken.ANONYMOUS);
assertEquals(null, AuthenticationToken.ANONYMOUS.getUserName());
assertEquals(null, AuthenticationToken.ANONYMOUS.getName());
assertEquals(null, AuthenticationToken.ANONYMOUS.getType());
assertEquals(-1, AuthenticationToken.ANONYMOUS.getExpires());
assertFalse(AuthenticationToken.ANONYMOUS.isExpired());
}
public void testConstructor() throws Exception {
try {
new AuthenticationToken(null, "p", "t");
fail();
} catch (IllegalArgumentException ex) {
// Expected
} catch (Throwable ex) {
fail();
}
try {
new AuthenticationToken("", "p", "t");
fail();
} catch (IllegalArgumentException ex) {
// Expected
} catch (Throwable ex) {
fail();
}
try {
new AuthenticationToken("u", null, "t");
fail();
} catch (IllegalArgumentException ex) {
// Expected
} catch (Throwable ex) {
fail();
}
try {
new AuthenticationToken("u", "", "t");
fail();
} catch (IllegalArgumentException ex) {
// Expected
} catch (Throwable ex) {
fail();
}
try {
new AuthenticationToken("u", "p", null);
fail();
} catch (IllegalArgumentException ex) {
// Expected
} catch (Throwable ex) {
fail();
}
try {
new AuthenticationToken("u", "p", "");
fail();
} catch (IllegalArgumentException ex) {
// Expected
} catch (Throwable ex) {
fail();
}
new AuthenticationToken("u", "p", "t");
}
public void testGetters() throws Exception {
long expires = System.currentTimeMillis() + 50;
AuthenticationToken token = new AuthenticationToken("u", "p", "t");
token.setExpires(expires);
assertEquals("u", token.getUserName());
assertEquals("p", token.getName());
assertEquals("t", token.getType());
assertEquals(expires, token.getExpires());
assertFalse(token.isExpired());
Thread.sleep(51);
assertTrue(token.isExpired());
}
public void testToStringAndParse() throws Exception {
long expires = System.currentTimeMillis() + 50;
AuthenticationToken token = new AuthenticationToken("u", "p", "t");
token.setExpires(expires);
String str = token.toString();
token = AuthenticationToken.parse(str);
assertEquals("p", token.getName());
assertEquals("t", token.getType());
assertEquals(expires, token.getExpires());
assertFalse(token.isExpired());
Thread.sleep(51);
assertTrue(token.isExpired());
}
public void testParseInvalid() throws Exception {
long expires = System.currentTimeMillis() + 50;
AuthenticationToken token = new AuthenticationToken("u", "p", "t");
token.setExpires(expires);
String str = token.toString();
str = str.substring(0, str.indexOf("e="));
try {
AuthenticationToken.parse(str);
fail();
} catch (AuthenticationException ex) {
// Expected
} catch (Exception ex) {
fail();
}
}
}

View File

@ -0,0 +1,178 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.server;
import org.apache.hadoop.alfredo.KerberosTestUtils;
import org.apache.hadoop.alfredo.client.AuthenticationException;
import org.apache.hadoop.alfredo.client.KerberosAuthenticator;
import junit.framework.TestCase;
import org.apache.commons.codec.binary.Base64;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.mockito.Mockito;
import sun.security.jgss.GSSUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Properties;
import java.util.concurrent.Callable;
public class TestKerberosAuthenticationHandler extends TestCase {
private KerberosAuthenticationHandler handler;
@Override
protected void setUp() throws Exception {
super.setUp();
handler = new KerberosAuthenticationHandler();
Properties props = new Properties();
props.setProperty(KerberosAuthenticationHandler.PRINCIPAL, KerberosTestUtils.getServerPrincipal());
props.setProperty(KerberosAuthenticationHandler.KEYTAB, KerberosTestUtils.getKeytabFile());
props.setProperty(KerberosAuthenticationHandler.NAME_RULES,
"RULE:[1:$1@$0](.*@" + KerberosTestUtils.getRealm()+")s/@.*//\n");
try {
handler.init(props);
} catch (Exception ex) {
handler = null;
throw ex;
}
}
@Override
protected void tearDown() throws Exception {
if (handler != null) {
handler.destroy();
handler = null;
}
super.tearDown();
}
public void testInit() throws Exception {
assertEquals(KerberosTestUtils.getServerPrincipal(), handler.getPrincipal());
assertEquals(KerberosTestUtils.getKeytabFile(), handler.getKeytab());
}
public void testType() throws Exception {
KerberosAuthenticationHandler handler = new KerberosAuthenticationHandler();
assertEquals(KerberosAuthenticationHandler.TYPE, handler.getType());
}
public void testRequestWithoutAuthorization() throws Exception {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
assertNull(handler.authenticate(request, response));
Mockito.verify(response).setHeader(KerberosAuthenticator.WWW_AUTHENTICATE, KerberosAuthenticator.NEGOTIATE);
Mockito.verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
public void testRequestWithInvalidAuthorization() throws Exception {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
Mockito.when(request.getHeader(KerberosAuthenticator.AUTHORIZATION)).thenReturn("invalid");
assertNull(handler.authenticate(request, response));
Mockito.verify(response).setHeader(KerberosAuthenticator.WWW_AUTHENTICATE, KerberosAuthenticator.NEGOTIATE);
Mockito.verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
public void testRequestWithIncompleteAuthorization() throws Exception {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
Mockito.when(request.getHeader(KerberosAuthenticator.AUTHORIZATION))
.thenReturn(KerberosAuthenticator.NEGOTIATE);
try {
handler.authenticate(request, response);
fail();
} catch (AuthenticationException ex) {
// Expected
} catch (Exception ex) {
fail();
}
}
public void testRequestWithAuthorization() throws Exception {
String token = KerberosTestUtils.doAsClient(new Callable<String>() {
@Override
public String call() throws Exception {
GSSManager gssManager = GSSManager.getInstance();
GSSContext gssContext = null;
try {
String servicePrincipal = KerberosTestUtils.getServerPrincipal();
GSSName serviceName = gssManager.createName(servicePrincipal, GSSUtil.NT_GSS_KRB5_PRINCIPAL);
gssContext = gssManager.createContext(serviceName, GSSUtil.GSS_KRB5_MECH_OID, null,
GSSContext.DEFAULT_LIFETIME);
gssContext.requestCredDeleg(true);
gssContext.requestMutualAuth(true);
byte[] inToken = new byte[0];
byte[] outToken = gssContext.initSecContext(inToken, 0, inToken.length);
Base64 base64 = new Base64(0);
return base64.encodeToString(outToken);
} finally {
if (gssContext != null) {
gssContext.dispose();
}
}
}
});
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
Mockito.when(request.getHeader(KerberosAuthenticator.AUTHORIZATION))
.thenReturn(KerberosAuthenticator.NEGOTIATE + " " + token);
AuthenticationToken authToken = handler.authenticate(request, response);
if (authToken != null) {
Mockito.verify(response).setHeader(Mockito.eq(KerberosAuthenticator.WWW_AUTHENTICATE),
Mockito.matches(KerberosAuthenticator.NEGOTIATE + " .*"));
Mockito.verify(response).setStatus(HttpServletResponse.SC_OK);
assertEquals(KerberosTestUtils.getClientPrincipal(), authToken.getName());
assertTrue(KerberosTestUtils.getClientPrincipal().startsWith(authToken.getUserName()));
assertEquals(KerberosAuthenticationHandler.TYPE, authToken.getType());
} else {
Mockito.verify(response).setHeader(Mockito.eq(KerberosAuthenticator.WWW_AUTHENTICATE),
Mockito.matches(KerberosAuthenticator.NEGOTIATE + " .*"));
Mockito.verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
public void testRequestWithInvalidKerberosAuthorization() throws Exception {
String token = new Base64(0).encodeToString(new byte[]{0, 1, 2});
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
Mockito.when(request.getHeader(KerberosAuthenticator.AUTHORIZATION)).thenReturn(
KerberosAuthenticator.NEGOTIATE + token);
try {
handler.authenticate(request, response);
fail();
} catch (AuthenticationException ex) {
// Expected
} catch (Exception ex) {
fail();
}
}
}

View File

@ -0,0 +1,113 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.server;
import org.apache.hadoop.alfredo.client.AuthenticationException;
import junit.framework.TestCase;
import org.apache.hadoop.alfredo.client.PseudoAuthenticator;
import org.mockito.Mockito;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Properties;
public class TestPseudoAuthenticationHandler extends TestCase {
public void testInit() throws Exception {
PseudoAuthenticationHandler handler = new PseudoAuthenticationHandler();
try {
Properties props = new Properties();
props.setProperty(PseudoAuthenticationHandler.ANONYMOUS_ALLOWED, "false");
handler.init(props);
assertEquals(false, handler.getAcceptAnonymous());
} finally {
handler.destroy();
}
}
public void testType() throws Exception {
PseudoAuthenticationHandler handler = new PseudoAuthenticationHandler();
assertEquals(PseudoAuthenticationHandler.TYPE, handler.getType());
}
public void testAnonymousOn() throws Exception {
PseudoAuthenticationHandler handler = new PseudoAuthenticationHandler();
try {
Properties props = new Properties();
props.setProperty(PseudoAuthenticationHandler.ANONYMOUS_ALLOWED, "true");
handler.init(props);
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
AuthenticationToken token = handler.authenticate(request, response);
assertEquals(AuthenticationToken.ANONYMOUS, token);
} finally {
handler.destroy();
}
}
public void testAnonymousOff() throws Exception {
PseudoAuthenticationHandler handler = new PseudoAuthenticationHandler();
try {
Properties props = new Properties();
props.setProperty(PseudoAuthenticationHandler.ANONYMOUS_ALLOWED, "false");
handler.init(props);
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
handler.authenticate(request, response);
fail();
} catch (AuthenticationException ex) {
// Expected
} catch (Exception ex) {
fail();
} finally {
handler.destroy();
}
}
private void _testUserName(boolean anonymous) throws Exception {
PseudoAuthenticationHandler handler = new PseudoAuthenticationHandler();
try {
Properties props = new Properties();
props.setProperty(PseudoAuthenticationHandler.ANONYMOUS_ALLOWED, Boolean.toString(anonymous));
handler.init(props);
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
Mockito.when(request.getParameter(PseudoAuthenticator.USER_NAME)).thenReturn("user");
AuthenticationToken token = handler.authenticate(request, response);
assertNotNull(token);
assertEquals("user", token.getUserName());
assertEquals("user", token.getName());
assertEquals(PseudoAuthenticationHandler.TYPE, token.getType());
} finally {
handler.destroy();
}
}
public void testUserNameAnonymousOff() throws Exception {
_testUserName(false);
}
public void testUserNameAnonymousOn() throws Exception {
_testUserName(true);
}
}

View File

@ -0,0 +1,88 @@
package org.apache.hadoop.alfredo.util;
/**
* 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.
*/
import java.io.IOException;
import org.apache.hadoop.alfredo.KerberosTestUtils;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class TestKerberosName {
@Before
public void setUp() throws Exception {
String rules =
"RULE:[1:$1@$0](.*@YAHOO\\.COM)s/@.*//\n" +
"RULE:[2:$1](johndoe)s/^.*$/guest/\n" +
"RULE:[2:$1;$2](^.*;admin$)s/;admin$//\n" +
"RULE:[2:$2](root)\n" +
"DEFAULT";
KerberosName.setRules(rules);
KerberosName.printRules();
}
private void checkTranslation(String from, String to) throws Exception {
System.out.println("Translate " + from);
KerberosName nm = new KerberosName(from);
String simple = nm.getShortName();
System.out.println("to " + simple);
assertEquals("short name incorrect", to, simple);
}
@Test
public void testRules() throws Exception {
checkTranslation("omalley@" + KerberosTestUtils.getRealm(), "omalley");
checkTranslation("hdfs/10.0.0.1@" + KerberosTestUtils.getRealm(), "hdfs");
checkTranslation("oom@YAHOO.COM", "oom");
checkTranslation("johndoe/zoo@FOO.COM", "guest");
checkTranslation("joe/admin@FOO.COM", "joe");
checkTranslation("joe/root@FOO.COM", "root");
}
private void checkBadName(String name) {
System.out.println("Checking " + name + " to ensure it is bad.");
try {
new KerberosName(name);
fail("didn't get exception for " + name);
} catch (IllegalArgumentException iae) {
// PASS
}
}
private void checkBadTranslation(String from) {
System.out.println("Checking bad translation for " + from);
KerberosName nm = new KerberosName(from);
try {
nm.getShortName();
fail("didn't get exception for " + from);
} catch (IOException ie) {
// PASS
}
}
@Test
public void testAntiPatterns() throws Exception {
checkBadName("owen/owen/owen@FOO.COM");
checkBadName("owen@foo/bar.com");
checkBadTranslation("foo@ACME.COM");
checkBadTranslation("root/joe@FOO.COM");
}
}

View File

@ -0,0 +1,93 @@
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.alfredo.util;
import junit.framework.TestCase;
public class TestSigner extends TestCase {
public void testNoSecret() throws Exception {
try {
new Signer(null);
fail();
}
catch (IllegalArgumentException ex) {
}
}
public void testNullAndEmptyString() throws Exception {
Signer signer = new Signer("secret".getBytes());
try {
signer.sign(null);
fail();
} catch (IllegalArgumentException ex) {
// Expected
} catch (Throwable ex) {
fail();
}
try {
signer.sign("");
fail();
} catch (IllegalArgumentException ex) {
// Expected
} catch (Throwable ex) {
fail();
}
}
public void testSignature() throws Exception {
Signer signer = new Signer("secret".getBytes());
String s1 = signer.sign("ok");
String s2 = signer.sign("ok");
String s3 = signer.sign("wrong");
assertEquals(s1, s2);
assertNotSame(s1, s3);
}
public void testVerify() throws Exception {
Signer signer = new Signer("secret".getBytes());
String t = "test";
String s = signer.sign(t);
String e = signer.verifyAndExtract(s);
assertEquals(t, e);
}
public void testInvalidSignedText() throws Exception {
Signer signer = new Signer("secret".getBytes());
try {
signer.verifyAndExtract("test");
fail();
} catch (SignerException ex) {
// Expected
} catch (Throwable ex) {
fail();
}
}
public void testTampering() throws Exception {
Signer signer = new Signer("secret".getBytes());
String t = "test";
String s = signer.sign(t);
s += "x";
try {
signer.verifyAndExtract(s);
fail();
} catch (SignerException ex) {
// Expected
} catch (Throwable ex) {
fail();
}
}
}

View File

@ -0,0 +1,28 @@
#
# 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.
#
[libdefaults]
default_realm = ${kerberos.realm}
udp_preference_limit = 1
extra_addresses = 127.0.0.1
[realms]
${kerberos.realm} = {
admin_server = localhost:88
kdc = localhost:88
}
[domain_realm]
localhost = ${kerberos.realm}

View File

@ -63,6 +63,9 @@ Trunk (unreleased changes)
HADOOP-6385. dfs should support -rmdir (was HDFS-639). (Daryn Sharp
via mattf)
HADOOP-7119. add Kerberos HTTP SPNEGO authentication support to Hadoop
JT/NN/DN/TT web-consoles. (Alejandro Abdelnur via atm)
IMPROVEMENTS
HADOOP-7042. Updates to test-patch.sh to include failed test names and

View File

@ -237,6 +237,11 @@
<artifactId>protobuf-java</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-alfredo</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,124 @@
<?xml version="1.0"?>
<!--
Copyright 2002-2004 The Apache Software Foundation
Licensed 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.
-->
<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN"
"http://forrest.apache.org/dtd/document-v20.dtd">
<document>
<header>
<title>
Authentication for Hadoop HTTP web-consoles
</title>
</header>
<body>
<section>
<title> Introduction </title>
<p>
This document describes how to configure Hadoop HTTP web-consoles to require user
authentication.
</p>
<p>
By default Hadoop HTTP web-consoles (JobTracker, NameNode, TaskTrackers and DataNodes) allow
access without any form of authentication.
</p>
<p>
Similarly to Hadoop RPC, Hadoop HTTP web-consoles can be configured to require Kerberos
authentication using HTTP SPNEGO protocol (supported by browsers like Firefox and Internet
Explorer).
</p>
<p>
In addition, Hadoop HTTP web-consoles support the equivalent of Hadoop's Pseudo/Simple
authentication. If this option is enabled, user must specify their user name in the first
browser interaction using the <code>user.name</code> query string parameter. For example:
<code>http://localhost:50030/jobtracker.jsp?user.name=babu</code>.
</p>
<p>
If a custom authentication mechanism is required for the HTTP web-consoles, it is possible
to implement a plugin to support the alternate authentication mechanism (refer to
Hadoop Alfredo for details on writing an <code>AuthenticatorHandler</code>).
</p>
<p>
The next section describes how to configure Hadoop HTTP web-consoles to require user
authentication.
</p>
</section>
<section>
<title> Configuration </title>
<p>
The following properties should be in the <code>core-site.xml</code> of all the nodes
in the cluster.
</p>
<p><code>hadoop.http.filter.initializers</code>: add to this property the
<code>org.apache.hadoop.security.AuthenticationFilterInitializer</code> initializer class.
</p>
<p><code>hadoop.http.authentication.type</code>: Defines authentication used for the HTTP
web-consoles. The supported values are: <code>simple | kerberos |
#AUTHENTICATION_HANDLER_CLASSNAME#</code>. The dfeault value is <code>simple</code>.
</p>
<p><code>hadoop.http.authentication.token.validity</code>: Indicates how long (in seconds)
an authentication token is valid before it has to be renewed. The default value is
<code>36000</code>.
</p>
<p><code>hadoop.http.authentication.signature.secret</code>: The signature secret for
signing the authentication tokens. If not set a random secret is generated at
startup time. The same secret should be used for all nodes in the cluster, JobTracker,
NameNode, DataNode and TastTracker. The default value is a <code>hadoop</code> value.
</p>
<p><code>hadoop.http.authentication.cookie.domain</code>: The domain to use for the HTTP
cookie that stores the authentication token. In order to authentiation to work
correctly across all nodes in the cluster the domain must be correctly set.
There is no default value, the HTTP cookie will not have a domain working only
with the hostname issuing the HTTP cookie.
</p>
<p>
IMPORTANT: when using IP addresses, browsers ignore cookies with domain settings.
For this setting to work properly all nodes in the cluster must be configured
to generate URLs with hostname.domain names on it.
</p>
<p><code>hadoop.http.authentication.simple.anonymous.allowed</code>: Indicates if anonymous
requests are allowed when using 'simple' authentication. The default value is
<code>true</code>
</p>
<p><code>hadoop.http.authentication.kerberos.principal</code>: Indicates the Kerberos
principal to be used for HTTP endpoint when using 'kerberos' authentication.
The principal short name must be <code>HTTP</code> per Kerberos HTTP SPENGO specification.
The default value is <code>HTTP/localhost@$LOCALHOST</code>.
</p>
<p><code>hadoop.http.authentication.kerberos.keytab</code>: Location of the keytab file
with the credentials for the Kerberos principal used for the HTTP endpoint.
The default value is <code>${user.home}/hadoop.keytab</code>.i
</p>
</section>
</body>
</document>

View File

@ -45,6 +45,7 @@ See http://forrest.apache.org/docs/linking.html for more info.
<SLA label="Service Level Authorization" href="service_level_auth.html"/>
<native_lib label="Native Libraries" href="native_libraries.html" />
<superusers label="Superusers Acting On Behalf Of Other Users" href="Superusers.html"/>
<http_authentication label="Authentication for Hadoop HTTP web-consoles" href="HttpAuthentication.html"/>
</docs>
<docs label="Miscellaneous">

View File

@ -0,0 +1,75 @@
/**
* 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.security;
import org.apache.hadoop.alfredo.server.AuthenticationFilter;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.http.FilterContainer;
import org.apache.hadoop.http.FilterInitializer;
import java.util.HashMap;
import java.util.Map;
/**
* Initializes Alfredo AuthenticationFilter which provides support for
* Kerberos HTTP SPENGO authentication.
* <p/>
* It enables anonymous access, simple/speudo and Kerberos HTTP SPNEGO
* authentication for Hadoop JobTracker, NameNode, DataNodes and
* TaskTrackers.
* <p/>
* Refer to the <code>core-default.xml</code> file, after the comment
* 'HTTP Authentication' for details on the configuration options.
* All related configuration properties have 'hadoop.http.authentication.'
* as prefix.
*/
public class AuthenticationFilterInitializer extends FilterInitializer {
private static final String PREFIX = "hadoop.http.authentication.";
/**
* Initializes Alfredo AuthenticationFilter.
* <p/>
* Propagates to Alfredo AuthenticationFilter configuration all Hadoop
* configuration properties prefixed with "hadoop.http.authentication."
*
* @param container The filter container
* @param conf Configuration for run-time parameters
*/
@Override
public void initFilter(FilterContainer container, Configuration conf) {
Map<String, String> filterConfig = new HashMap<String, String>();
//setting the cookie path to root '/' so it is used for all resources.
filterConfig.put(AuthenticationFilter.COOKIE_PATH, "/");
for (Map.Entry<String, String> entry : conf) {
String name = entry.getKey();
if (name.startsWith(PREFIX)) {
String value = conf.get(name);
name = name.substring(PREFIX.length());
filterConfig.put(name, value);
}
}
container.addFilter("authentication",
AuthenticationFilter.class.getName(),
filterConfig);
}
}

View File

@ -0,0 +1,74 @@
/**
* 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.security;
import java.io.IOException;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.alfredo.util.KerberosName;
import sun.security.krb5.Config;
import sun.security.krb5.KrbException;
/**
* This class implements parsing and handling of Kerberos principal names. In
* particular, it splits them apart and translates them down into local
* operating system names.
*/
@SuppressWarnings("all")
@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
@InterfaceStability.Evolving
public class HadoopKerberosName extends KerberosName {
static {
try {
Config.getInstance().getDefaultRealm();
} catch (KrbException ke) {
if(UserGroupInformation.isSecurityEnabled())
throw new IllegalArgumentException("Can't get Kerberos configuration",ke);
}
}
/**
* Create a name from the full Kerberos principal name.
* @param name
*/
public HadoopKerberosName(String name) {
super(name);
}
/**
* Set the static configuration to get the rules.
* @param conf the new configuration
* @throws IOException
*/
public static void setConfiguration(Configuration conf) throws IOException {
String ruleString = conf.get("hadoop.security.auth_to_local", "DEFAULT");
setRules(ruleString);
}
public static void main(String[] args) throws Exception {
setConfiguration(new Configuration());
for(String arg: args) {
HadoopKerberosName name = new HadoopKerberosName(arg);
System.out.println("Name: " + name + " to " + name.getShortName());
}
}
}

View File

@ -290,7 +290,7 @@ public class SecurityUtil {
* @return host name if the the string conforms to the above format, else null
*/
public static String getHostFromPrincipal(String principalName) {
return new KerberosName(principalName).getHostName();
return new HadoopKerberosName(principalName).getHostName();
}
private static ServiceLoader<SecurityInfo> securityInfoProviders =

View File

@ -45,7 +45,7 @@ class User implements Principal {
public User(String name, AuthenticationMethod authMethod, LoginContext login) {
try {
shortName = new KerberosName(name).getShortName();
shortName = new HadoopKerberosName(name).getShortName();
} catch (IOException ioe) {
throw new IllegalArgumentException("Illegal principal name " + name, ioe);
}

View File

@ -194,7 +194,7 @@ public class UserGroupInformation {
initUGI(conf);
// give the configuration on how to translate Kerberos names
try {
KerberosName.setConfiguration(conf);
HadoopKerberosName.setConfiguration(conf);
} catch (IOException ioe) {
throw new RuntimeException("Problem with Kerberos auth_to_local name " +
"configuration", ioe);

View File

@ -27,7 +27,7 @@ import java.io.IOException;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableUtils;
import org.apache.hadoop.security.KerberosName;
import org.apache.hadoop.security.HadoopKerberosName;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.token.TokenIdentifier;
@ -58,7 +58,7 @@ extends TokenIdentifier {
if (renewer == null) {
this.renewer = new Text();
} else {
KerberosName renewerKrbName = new KerberosName(renewer.toString());
HadoopKerberosName renewerKrbName = new HadoopKerberosName(renewer.toString());
try {
this.renewer = new Text(renewerKrbName.getShortName());
} catch (IOException e) {

View File

@ -35,11 +35,10 @@ import javax.crypto.SecretKey;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.KerberosName;
import org.apache.hadoop.security.HadoopKerberosName;
import org.apache.hadoop.security.token.Token;
import org.apache.hadoop.security.token.SecretManager;
import org.apache.hadoop.util.Daemon;
import org.apache.hadoop.util.StringUtils;
@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
@InterfaceStability.Evolving
@ -284,7 +283,7 @@ extends AbstractDelegationTokenIdentifier>
}
String owner = id.getUser().getUserName();
Text renewer = id.getRenewer();
KerberosName cancelerKrbName = new KerberosName(canceller);
HadoopKerberosName cancelerKrbName = new HadoopKerberosName(canceller);
String cancelerShortName = cancelerKrbName.getShortName();
if (!canceller.equals(owner)
&& (renewer == null || "".equals(renewer.toString()) || !cancelerShortName

View File

@ -782,4 +782,73 @@
</description>
</property>
<!-- HTTP web-consoles Authentication -->
<property>
<name>hadoop.http.authentication.type</name>
<value>simple</value>
<description>
Defines authentication used for Oozie HTTP endpoint.
Supported values are: simple | kerberos | #AUTHENTICATION_HANDLER_CLASSNAME#
</description>
</property>
<property>
<name>hadoop.http.authentication.token.validity</name>
<value>36000</value>
<description>
Indicates how long (in seconds) an authentication token is valid before it has
to be renewed.
</description>
</property>
<property>
<name>hadoop.http.authentication.signature.secret</name>
<value>hadoop</value>
<description>
The signature secret for signing the authentication tokens.
If not set a random secret is generated at startup time.
The same secret should be used for JT/NN/DN/TT configurations.
</description>
</property>
<property>
<name>hadoop.http.authentication.cookie.domain</name>
<value></value>
<description>
The domain to use for the HTTP cookie that stores the authentication token.
In order to authentiation to work correctly across all Hadoop nodes web-consoles
the domain must be correctly set.
IMPORTANT: when using IP addresses, browsers ignore cookies with domain settings.
For this setting to work properly all nodes in the cluster must be configured
to generate URLs with hostname.domain names on it.
</description>
</property>
<property>
<name>hadoop.http.authentication.simple.anonymous.allowed</name>
<value>true</value>
<description>
Indicates if anonymous requests are allowed when using 'simple' authentication.
</description>
</property>
<property>
<name>hadoop.http.authentication.kerberos.principal</name>
<value>HTTP/localhost@LOCALHOST</value>
<description>
Indicates the Kerberos principal to be used for HTTP endpoint.
The principal MUST start with 'HTTP/' as per Kerberos HTTP SPNEGO specification.
</description>
</property>
<property>
<name>hadoop.http.authentication.kerberos.keytab</name>
<value>${user.home}/hadoop.keytab</value>
<description>
Location of the keytab file with the credentials for the principal.
Referring to the same keytab file Oozie uses for its Kerberos credentials for Hadoop.
</description>
</property>
</configuration>

View File

@ -0,0 +1,72 @@
/**
* 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.security;
import junit.framework.TestCase;
import org.apache.hadoop.alfredo.server.AuthenticationFilter;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.http.FilterContainer;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.Map;
public class TestAuthenticationFilter extends TestCase {
@SuppressWarnings("unchecked")
public void testConfiguration() {
Configuration conf = new Configuration();
conf.set("hadoop.http.authentication.foo", "bar");
FilterContainer container = Mockito.mock(FilterContainer.class);
Mockito.doAnswer(
new Answer() {
public Object answer(InvocationOnMock invocationOnMock)
throws Throwable {
Object[] args = invocationOnMock.getArguments();
assertEquals("authentication", args[0]);
assertEquals(AuthenticationFilter.class.getName(), args[1]);
Map<String, String> conf = (Map<String, String>) args[2];
assertEquals("/", conf.get("cookie.path"));
assertEquals("simple", conf.get("type"));
assertEquals("36000", conf.get("token.validity"));
assertEquals("hadoop", conf.get("signature.secret"));
assertNull(conf.get("cookie.domain"));
assertEquals("true", conf.get("simple.anonymous.allowed"));
assertEquals("HTTP/localhost@LOCALHOST",
conf.get("kerberos.principal"));
assertEquals(System.getProperty("user.home") +
"/hadoop.keytab", conf.get("kerberos.keytab"));
assertEquals("bar", conf.get("foo"));
return null;
}
}
).when(container).addFilter(Mockito.<String>anyObject(),
Mockito.<String>anyObject(),
Mockito.<Map<String, String>>anyObject());
new AuthenticationFilterInitializer().initFilter(container, conf);
}
}

View File

@ -104,6 +104,11 @@
<version>${project.version}</version>
<type>test-jar</type>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-alfredo</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
@ -468,6 +473,16 @@
<artifactId>jspc-maven-plugin</artifactId>
<version>2.0-alpha-3</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>2.1.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>2.4</version>
</plugin>
</plugins>
</pluginManagement>

View File

@ -38,6 +38,7 @@
<module>hadoop-project-distro</module>
<module>hadoop-assemblies</module>
<module>hadoop-annotations</module>
<module>hadoop-alfredo</module>
<module>hadoop-common</module>
<module>hadoop-hdfs</module>
</modules>