diff --git a/client/pom.xml b/client/pom.xml
index cffe29fe0..cbc19a6b6 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -58,6 +58,18 @@
persistence-api
provided
+
+
+ com.cloudera.alfredo
+ alfredo
+ compile
+
+
+ org.slf4j
+ slf4j-api
+
+
+
diff --git a/client/src/main/bin/oozie b/client/src/main/bin/oozie
index c6d95d1b3..872d61c23 100644
--- a/client/src/main/bin/oozie
+++ b/client/src/main/bin/oozie
@@ -43,9 +43,8 @@ else
JAVA_BIN=${JAVA_HOME}/bin/java
fi
-JAVA_PROPERTIES=""
while [[ ${1} =~ ^\-D ]]; do
JAVA_PROPERTIES="${JAVA_PROPERTIES} ${1}"
shift
done
-${JAVA_BIN} ${JAVA_PROPERTIES} -cp ${OOZIECPPATH} org.apache.oozie.cli.OozieCLI "${@}"
+${JAVA_BIN} ${JAVA_PROPERTIES} -cp ${OOZIECPPATH} org.apache.oozie.cli.AuthOozieCLI "${@}"
diff --git a/client/src/main/java/org/apache/oozie/cli/AuthOozieCLI.java b/client/src/main/java/org/apache/oozie/cli/AuthOozieCLI.java
new file mode 100644
index 000000000..e57ac7c99
--- /dev/null
+++ b/client/src/main/java/org/apache/oozie/cli/AuthOozieCLI.java
@@ -0,0 +1,125 @@
+/**
+ * Copyright (c) 2010 Yahoo! Inc. All rights reserved.
+ * 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.oozie.cli;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.oozie.BuildInfo;
+import org.apache.oozie.client.AuthOozieClient;
+import org.apache.oozie.client.CoordinatorAction;
+import org.apache.oozie.client.CoordinatorJob;
+import org.apache.oozie.client.OozieClient;
+import org.apache.oozie.client.OozieClient.SYSTEM_MODE;
+import org.apache.oozie.client.OozieClientException;
+import org.apache.oozie.client.WorkflowAction;
+import org.apache.oozie.client.WorkflowJob;
+import org.apache.oozie.client.XOozieClient;
+import org.apache.oozie.client.rest.JsonCoordinatorAction;
+import org.apache.oozie.client.rest.RestConstants;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+import org.xml.sax.SAXException;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TimeZone;
+
+/**
+ * Oozie command line utility.
+ */
+public class AuthOozieCLI extends OozieCLI {
+ /**
+ * Entry point for the Oozie CLI when invoked from the command line.
+ *
+ * Upon completion this method exits the JVM with '0' (success) or '-1'
+ * (failure).
+ *
+ * @param args options and arguments for the Oozie CLI.
+ */
+ public static void main(String[] args) {
+ if (!System.getProperties().contains(AuthOozieClient.USE_AUTH_TOKEN_CACHE_SYS_PROP)) {
+ System.setProperty(AuthOozieClient.USE_AUTH_TOKEN_CACHE_SYS_PROP, "true");
+ }
+ System.exit(new AuthOozieCLI().run(args));
+ }
+
+ /**
+ * Create a OozieClient. It injects any '-Dheader:' as header to the the {@link
+ * OozieClient}.
+ *
+ * @param commandLine the parsed command line options.
+ * @return a pre configured eXtended workflow client.
+ * @throws OozieCLIException thrown if the OozieClient could not be
+ * configured.
+ */
+ @Override
+ protected OozieClient createOozieClient(CommandLine commandLine) throws OozieCLIException {
+ return createXOozieClient(commandLine);
+ }
+
+ //TODO: This method should be made protected in OozieCLI so we don't have to reimplement it here
+ private void addHeader(OozieClient wc) {
+ for (Map.Entry entry : System.getProperties().entrySet()) {
+ String key = (String) entry.getKey();
+ if (key.startsWith(WS_HEADER_PREFIX)) {
+ String header = key.substring(WS_HEADER_PREFIX.length());
+ wc.setHeader(header, (String) entry.getValue());
+ }
+ }
+ }
+
+ /**
+ * Create a XOozieClient. It injects any '-Dheader:' as header to the the {@link
+ * OozieClient}.
+ *
+ * @param commandLine the parsed command line options.
+ * @return a pre configured eXtended workflow client.
+ * @throws OozieCLIException thrown if the XOozieClient could not be
+ * configured.
+ */
+ @Override
+ protected XOozieClient createXOozieClient(CommandLine commandLine) throws OozieCLIException {
+ XOozieClient wc = new AuthOozieClient(getOozieUrl(commandLine));
+ addHeader(wc);
+ return wc;
+ }
+
+}
diff --git a/client/src/main/java/org/apache/oozie/client/AuthOozieClient.java b/client/src/main/java/org/apache/oozie/client/AuthOozieClient.java
new file mode 100644
index 000000000..4d50dd08a
--- /dev/null
+++ b/client/src/main/java/org/apache/oozie/client/AuthOozieClient.java
@@ -0,0 +1,216 @@
+/**
+ * Copyright (c) 2010 Yahoo! Inc. All rights reserved.
+ * 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.oozie.client;
+
+import com.cloudera.alfredo.client.AuthenticatedURL;
+import com.cloudera.alfredo.client.AuthenticationException;
+import com.cloudera.alfredo.client.Authenticator;
+import com.cloudera.alfredo.client.KerberosAuthenticator;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * This subclass of {@link XOozieClient} supports Kerberos HTTP SPNEGO and simple authentication.
+ */
+public class AuthOozieClient extends XOozieClient {
+
+ /**
+ * Java system property to specify a custom Authenticator implementation.
+ */
+ public static final String AUTHENTICATOR_CLASS_SYS_PROP = "authenticator.class";
+
+ /**
+ * Java system property that, if set the authentication token will be cached in the user home directory in a hidden
+ * file .oozie-auth-token with user read/write permissions only.
+ */
+ public static final String USE_AUTH_TOKEN_CACHE_SYS_PROP = "oozie.auth.token.cache";
+
+ /**
+ * File constant that defines the location of the authentication token cache file.
+ *
+ * It resolves to ${user.home}/.oozie-auth-token.
+ */
+ public static final File AUTH_TOKEN_CACHE_FILE = new File(System.getProperty("user.home"), ".oozie-auth-token");
+
+ /**
+ * Create an instance of the AuthOozieClient.
+ *
+ * @param oozieUrl the Oozie URL
+ */
+ public AuthOozieClient(String oozieUrl) {
+ super(oozieUrl);
+ }
+
+ /**
+ * Create an authenticated connection to the Oozie server.
+ *
+ * It uses Alfredo client authentication which by default supports
+ * Kerberos HTTP SPNEGO, Pseudo/Simple and anonymous.
+ *
+ * if the Java system property {@link #USE_AUTH_TOKEN_CACHE_SYS_PROP} is set to true Alfredo
+ * authentication token will be cached/used in/from the '.oozie-auth-token' file in the user
+ * home directory.
+ *
+ * @param url the URL to open a HTTP connection to.
+ * @param method the HTTP method for the HTTP connection.
+ * @return an authenticated connection to the Oozie server.
+ * @throws IOException if an IO error occurred.
+ * @throws OozieClientException if an oozie client error occurred.
+ */
+ @Override
+ protected HttpURLConnection createConnection(URL url, String method) throws IOException, OozieClientException {
+ boolean useAuthFile = System.getProperty(USE_AUTH_TOKEN_CACHE_SYS_PROP, "false").equalsIgnoreCase("true");
+ AuthenticatedURL.Token readToken = new AuthenticatedURL.Token();
+ AuthenticatedURL.Token currentToken = new AuthenticatedURL.Token();
+
+ if (useAuthFile) {
+ readToken = readAuthToken();
+ if (readToken != null) {
+ currentToken = new AuthenticatedURL.Token(readToken.toString());
+ }
+ }
+
+ if (currentToken.isSet()) {
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestMethod("OPTIONS");
+ injectToken(conn, currentToken);
+ if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
+ AUTH_TOKEN_CACHE_FILE.delete();
+ currentToken = new AuthenticatedURL.Token();
+ }
+ }
+
+ if (!currentToken.isSet()) {
+ Authenticator authenticator = getAuthenticator();
+ try {
+ new AuthenticatedURL(authenticator).openConnection(url, currentToken);
+ }
+ catch (AuthenticationException ex) {
+ AUTH_TOKEN_CACHE_FILE.delete();
+ throw new OozieClientException(OozieClientException.AUTHENTICATION,
+ "Could not authenticate, " + ex.getMessage(), ex);
+ }
+ }
+ if (useAuthFile && !currentToken.equals(readToken)) {
+ writeAuthToken(currentToken);
+ }
+ HttpURLConnection conn = super.createConnection(url, method);
+
+ injectToken(conn, currentToken);
+ return conn;
+ }
+
+ //TODO: replace this with AutheticatedURL.injectToken when the method is made public in Alfredo 0.1.5
+ private void injectToken(HttpURLConnection conn, AuthenticatedURL.Token token) {
+ if (token.isSet()) {
+ String t = token.toString();
+ if (t != null) {
+ if (!t.startsWith("\"")) {
+ t = "\"" + t + "\"";
+ }
+ conn.addRequestProperty("Cookie", AuthenticatedURL.AUTH_COOKIE + "=" + t);
+ }
+ }
+ }
+
+ /**
+ * Read a authentication token cached in the user home directory.
+ *
+ *
+ * @return the authentication token cached in the user home directory, NULL if none.
+ */
+ protected AuthenticatedURL.Token readAuthToken() {
+ AuthenticatedURL.Token authToken = null;
+ if (AUTH_TOKEN_CACHE_FILE.exists()) {
+ try {
+ BufferedReader reader = new BufferedReader(new FileReader(AUTH_TOKEN_CACHE_FILE));
+ String line = reader.readLine();
+ reader.close();
+ if (line != null) {
+ authToken = new AuthenticatedURL.Token(line);
+ }
+ }
+ catch (IOException ex) {
+ //NOP
+ }
+ }
+ return authToken;
+ }
+
+ /**
+ * Write the current authenthication token to the user home directory.
+ *
+ * The file is written with user only read/write permissions.
+ *
+ * If the file cannot be updated or the user only ready/write permissions cannot be set the file is deleted.
+ *
+ * @param authToken the authentication token to cache.
+ */
+ protected void writeAuthToken(AuthenticatedURL.Token authToken) {
+ try {
+ Writer writer = new FileWriter(AUTH_TOKEN_CACHE_FILE);
+ writer.write(authToken.toString());
+ writer.close();
+ // sets read-write permissions to owner only
+ AUTH_TOKEN_CACHE_FILE.setReadable(false, false);
+ AUTH_TOKEN_CACHE_FILE.setReadable(true, true);
+ AUTH_TOKEN_CACHE_FILE.setWritable(true, true);
+ }
+ catch (Exception ex) {
+ // if case of any error we just delete the cache, if user-only
+ // write permissions are not properly set a security exception
+ // is thrown and the file will be deleted.
+ AUTH_TOKEN_CACHE_FILE.delete();
+ }
+ }
+
+ /**
+ * Return the Alfredo Authenticator to use.
+ *
+ * It looks for value of the {@link #AUTHENTICATOR_CLASS_SYS_PROP} Java system property, if not set it uses Alfredo
+ * KerberosAuthenticator which supports both Kerberos HTTP SPNEGO and Pseudo/simple authentication.
+ *
+ * @return the Authenticator to use, NULL if none.
+ *
+ * @throws OozieClientException thrown if the authenticator could not be instatiated.
+ */
+ protected Authenticator getAuthenticator() throws OozieClientException {
+ String className = System.getProperty(AUTHENTICATOR_CLASS_SYS_PROP, KerberosAuthenticator.class.getName());
+ if (className != null) {
+ try {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ Class klass = (cl != null) ? cl.loadClass(className) : getClass().getClassLoader().loadClass(className);
+ return (Authenticator) klass.newInstance();
+ }
+ catch (Exception ex) {
+ throw new OozieClientException(OozieClientException.AUTHENTICATION,
+ "Could not instantiate Authenticator [" + className + "], " +
+ ex.getMessage(), ex);
+ }
+ }
+ else {
+ throw new OozieClientException(OozieClientException.AUTHENTICATION,
+ "Authenticator class not found [" + className + "]");
+ }
+ }
+
+}
diff --git a/core/pom.xml b/core/pom.xml
index 661fce131..a4e1ef1b6 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -94,12 +94,6 @@
provided
-
- org.slf4j
- slf4j-log4j12
- test
-
-
com.googlecode.json-simple
json-simple
@@ -169,6 +163,17 @@
compile
+
+ com.cloudera.alfredo
+ alfredo
+ compile
+
+
+
+ org.slf4j
+ slf4j-log4j12
+ compile
+
diff --git a/core/src/main/conf/oozie-log4j.properties b/core/src/main/conf/oozie-log4j.properties
index 3882d8abb..d4db27652 100644
--- a/core/src/main/conf/oozie-log4j.properties
+++ b/core/src/main/conf/oozie-log4j.properties
@@ -59,3 +59,4 @@ log4j.logger.org.apache.oozie=DEBUG, oozie
log4j.logger.org.apache.hadoop=WARN, oozie
log4j.logger.org.mortbay=WARN, oozie
log4j.logger.org.hsqldb=WARN, oozie
+log4j.logger.com.cloudera.alfredo=DEBUG, oozie
diff --git a/core/src/main/conf/oozie-site.xml b/core/src/main/conf/oozie-site.xml
index c0544376a..c32f0769c 100644
--- a/core/src/main/conf/oozie-site.xml
+++ b/core/src/main/conf/oozie-site.xml
@@ -228,5 +228,71 @@
+
+ oozie.authentication.type
+ simple
+
+ Defines authentication used for Oozie HTTP endpoint.
+ Supported values are: simple | kerberos | #AUTHENTICATION_HANDLER_CLASSNAME#
+
+
+
+
+ oozie.authentication.token.validity
+ 36000
+
+ Indicates how long (in seconds) an authentication token is valid before it has
+ to be renewed.
+
+
+
+
+ oozie.authentication.signature.secret
+ oozie
+
+ The signature secret for signing the authentication tokens.
+ If not set a random secret is generated at startup time.
+ In order to authentiation to work correctly across multiple hosts
+ the secret must be the same across al the hosts.
+
+
+
+
+ oozie.authentication.cookie.domain
+
+
+ The domain to use for the HTTP cookie that stores the authentication token.
+ In order to authentiation to work correctly across multiple hosts
+ the domain must be correctly set.
+
+
+
+
+ oozie.authentication.simple.anonymous.allowed
+ true
+
+ Indicates if anonymous requests are allowed.
+ This setting is meaningful only when using 'simple' authentication.
+
+
+
+
+ oozie.authentication.kerberos.principal
+ HTTP/localhost@${local.realm}
+
+ Indicates the Kerberos principal to be used for HTTP endpoint.
+ The principal MUST start with 'HTTP/' as per Kerberos HTTP SPNEGO specification.
+
+
+
+
+ oozie.authentication.kerberos.keytab
+ ${oozie.service.HadoopAccessorService.keytab.file}
+
+ 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.
+
+
+
diff --git a/core/src/main/java/org/apache/oozie/servlet/AuthFilter.java b/core/src/main/java/org/apache/oozie/servlet/AuthFilter.java
new file mode 100644
index 000000000..f63ee5d68
--- /dev/null
+++ b/core/src/main/java/org/apache/oozie/servlet/AuthFilter.java
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2010 Yahoo! Inc. All rights reserved.
+ * 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.oozie.servlet;
+
+import com.cloudera.alfredo.server.AuthenticationFilter;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.oozie.service.Services;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Authentication filter that extends Alfredo AuthenticationFilter to override
+ * the configuration loading.
+ */
+public class AuthFilter extends AuthenticationFilter {
+ private static final String OOZIE_PREFIX = "oozie.authentication.";
+
+ private HttpServlet optionsServlet;
+
+ /**
+ * Initialize the filter.
+ *
+ * @param filterConfig filter configuration.
+ * @throws ServletException thrown if the filter could not be initialized.
+ */
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ super.init(filterConfig);
+ optionsServlet = new HttpServlet() {};
+ optionsServlet.init();
+ }
+
+ /**
+ * Destroy the filter.
+ */
+ @Override
+ public void destroy() {
+ optionsServlet.destroy();
+ super.destroy();
+ }
+
+ /**
+ * Returns the configuration from Oozie configuration to be used by the authentication filter.
+ *
+ * All properties from Oozie configuration which name starts with {@link #OOZIE_PREFIX} will
+ * be returned. The keys of the returned properties are trimmed from the {@link #OOZIE_PREFIX}
+ * prefix, for example the Oozie configuration property name 'oozie.authentication.type' will
+ * be just 'type'.
+ *
+ * @param configPrefix configuration prefix, this parameter is ignored by this implementation.
+ * @param filterConfig filter configuration, this parameter is ignored by this implementation.
+ * @return all Oozie configuration properties prefixed with {@link #OOZIE_PREFIX}, without the
+ * prefix.
+ */
+ @Override
+ protected Properties getConfiguration(String configPrefix, FilterConfig filterConfig) {
+ Properties props = new Properties();
+ Configuration conf = Services.get().getConf();
+
+ //setting the cookie path to root '/' so it is used for all resources.
+ props.setProperty(AuthenticationFilter.COOKIE_PATH, "/");
+
+ for (Map.Entry entry : conf) {
+ String name = entry.getKey();
+ if (name.startsWith(OOZIE_PREFIX)) {
+ String value = conf.get(name);
+ name = name.substring(OOZIE_PREFIX.length());
+ props.setProperty(name, value);
+ }
+ }
+
+ return props;
+ }
+
+ /**
+ * Enforces authentication using Alfredo AuthenticationFilter.
+ *
+ * This method is overriden to respond to HTTP OPTIONS requests for authenticated calls, regardless
+ * of the target servlet supporting OPTIONS or not and to inject the authenticated user name as
+ * request attribute for Oozie to retrieve the user id.
+ *
+ * @param request http request.
+ * @param response http response.
+ * @param filterChain filter chain.
+ * @throws IOException thrown if an IO error occurs.
+ * @throws ServletException thrown if a servlet error occurs.
+ */
+ @Override
+ public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain)
+ throws IOException, ServletException {
+
+ FilterChain filterChainWrapper = new FilterChain() {
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse)
+ throws IOException, ServletException {
+ HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
+ if (httpRequest.getMethod().equals("OPTIONS")) {
+ optionsServlet.service(request, response);
+ }
+ else {
+ httpRequest.setAttribute(JsonRestServlet.USER_NAME, httpRequest.getRemoteUser());
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+ }
+ };
+
+ super.doFilter(request, response, filterChainWrapper);
+ }
+
+}
diff --git a/core/src/main/resources/oozie-default.xml b/core/src/main/resources/oozie-default.xml
index 6d52d9d59..1baa9b148 100644
--- a/core/src/main/resources/oozie-default.xml
+++ b/core/src/main/resources/oozie-default.xml
@@ -1197,4 +1197,71 @@
+
+
+
+ oozie.authentication.type
+ simple
+
+ Defines authentication used for Oozie HTTP endpoint.
+ Supported values are: simple | kerberos | #AUTHENTICATION_HANDLER_CLASSNAME#
+
+
+
+
+ oozie.authentication.token.validity
+ 36000
+
+ Indicates how long (in seconds) an authentication token is valid before it has
+ to be renewed.
+
+
+
+
+ oozie.authentication.signature.secret
+ oozie
+
+ The signature secret for signing the authentication tokens.
+ If not set a random secret is generated at startup time.
+ In order to authentiation to work correctly across multiple hosts
+ the secret must be the same across al the hosts.
+
+
+
+
+ oozie.authentication.cookie.domain
+
+
+ The domain to use for the HTTP cookie that stores the authentication token.
+ In order to authentiation to work correctly across multiple hosts
+ the domain must be correctly set.
+
+
+
+
+ oozie.authentication.simple.anonymous.allowed
+ true
+
+ Indicates if anonymous requests are allowed when using 'simple' authentication.
+
+
+
+
+ oozie.authentication.kerberos.principal
+ HTTP/localhost@${local.realm}
+
+ Indicates the Kerberos principal to be used for HTTP endpoint.
+ The principal MUST start with 'HTTP/' as per Kerberos HTTP SPNEGO specification.
+
+
+
+
+ oozie.authentication.kerberos.keytab
+ ${oozie.service.HadoopAccessorService.keytab.file}
+
+ 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.
+
+
+
diff --git a/core/src/test/java/org/apache/oozie/servlet/TestAuthFilterAuthOozieClient.java b/core/src/test/java/org/apache/oozie/servlet/TestAuthFilterAuthOozieClient.java
new file mode 100644
index 000000000..071097c4a
--- /dev/null
+++ b/core/src/test/java/org/apache/oozie/servlet/TestAuthFilterAuthOozieClient.java
@@ -0,0 +1,241 @@
+/**
+ * Copyright (c) 2010 Yahoo! Inc. All rights reserved.
+ * 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.oozie.servlet;
+
+import com.cloudera.alfredo.client.AuthenticatedURL;
+import com.cloudera.alfredo.client.AuthenticationException;
+import com.cloudera.alfredo.client.PseudoAuthenticator;
+import org.apache.oozie.cli.AuthOozieCLI;
+import org.apache.oozie.cli.OozieCLI;
+import org.apache.oozie.client.AuthOozieClient;
+import org.apache.oozie.client.HeaderTestingVersionServlet;
+import org.apache.oozie.client.XOozieClient;
+import org.apache.oozie.service.ForTestAuthorizationService;
+import org.apache.oozie.service.ForTestWorkflowStoreService;
+import org.apache.oozie.service.Services;
+import org.apache.oozie.test.EmbeddedServletContainer;
+import org.apache.oozie.test.XTestCase;
+import org.apache.oozie.util.IOUtils;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+/**
+ *
+ */
+public class TestAuthFilterAuthOozieClient extends XTestCase {
+ private EmbeddedServletContainer container;
+
+ protected String getContextURL() {
+ return container.getContextURL();
+ }
+
+ protected URL createURL(String servletPath, String resource, Map parameters) throws Exception {
+ StringBuilder sb = new StringBuilder();
+ sb.append(container.getServletURL(servletPath));
+ if (resource != null && resource.length() > 0) {
+ sb.append("/").append(resource);
+ }
+ if (parameters.size() > 0) {
+ String separator = "?";
+ for (Map.Entry param : parameters.entrySet()) {
+ sb.append(separator).append(URLEncoder.encode(param.getKey(), "UTF-8")).append("=")
+ .append(URLEncoder.encode(param.getValue(), "UTF-8"));
+ separator = "&";
+ }
+ }
+ return new URL(sb.toString());
+ }
+
+ protected void runTest(Callable assertions) throws Exception {
+ Services services = new Services();
+ try {
+ services.init();
+ Services.get().setService(ForTestAuthorizationService.class);
+ Services.get().setService(ForTestWorkflowStoreService.class);
+ Services.get().setService(MockDagEngineService.class);
+ Services.get().setService(MockCoordinatorEngineService.class);
+ container = new EmbeddedServletContainer("oozie");
+ container.addServletEndpoint("/versions", HeaderTestingVersionServlet.class);
+ String version = "/v" + XOozieClient.WS_PROTOCOL_VERSION;
+ container.addServletEndpoint(version + "/admin/*", V1AdminServlet.class);
+ container.addFilter("/*", AuthFilter.class);
+ container.start();
+ assertions.call();
+ }
+ finally {
+ if (container != null) {
+ container.stop();
+ }
+ services.destroy();
+ container = null;
+ }
+ }
+
+ public static class Authenticator4Test extends PseudoAuthenticator {
+
+ private static boolean USED = false;
+
+ @Override
+ public void authenticate(URL url, AuthenticatedURL.Token token) throws IOException, AuthenticationException {
+ USED = true;
+ super.authenticate(url, token);
+ }
+ }
+
+ public void testNonAuthUrlWithAnonymous() throws Exception {
+ setSystemProperty("oozie.authentication.simple.anonymous.allowed", "true");
+ runTest(new Callable() {
+ public Void call() throws Exception {
+ URL url = createURL("/versions", null, new HashMap());
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode());
+ return null;
+ }
+ });
+ }
+
+ public void testNonAuthUrlWithoutAnonymous() throws Exception {
+ setSystemProperty("oozie.authentication.simple.anonymous.allowed", "false");
+ runTest(new Callable() {
+ public Void call() throws Exception {
+ URL url = createURL("/versions", null, new HashMap());
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, conn.getResponseCode());
+ return null;
+ }
+ });
+ }
+
+ public void testNonAuthClientWithAnonymous() throws Exception {
+ setSystemProperty("oozie.authentication.simple.anonymous.allowed", "true");
+ runTest(new Callable() {
+ public Void call() throws Exception {
+ String oozieUrl = getContextURL();
+ String[] args = new String[]{"admin", "-status", "-oozie", oozieUrl};
+ assertEquals(0, new OozieCLI().run(args));
+ return null;
+ }
+ });
+ }
+
+ public void testNonAuthClientWithoutAnonymous() throws Exception {
+ setSystemProperty("oozie.authentication.simple.anonymous.allowed", "false");
+ runTest(new Callable() {
+ public Void call() throws Exception {
+ String oozieUrl = getContextURL();
+ String[] args = new String[]{"admin", "-status", "-oozie", oozieUrl};
+ assertNotSame(0, new OozieCLI().run(args));
+ return null;
+ }
+ });
+ }
+
+ public void testAuthClientWithAnonymous() throws Exception {
+ setSystemProperty("oozie.authentication.simple.anonymous.allowed", "true");
+ runTest(new Callable() {
+ public Void call() throws Exception {
+ String oozieUrl = getContextURL();
+ String[] args = new String[]{"admin", "-status", "-oozie", oozieUrl};
+ assertEquals(0, new AuthOozieCLI().run(args));
+ return null;
+ }
+ });
+ }
+
+ public void testAuthClientWithoutAnonymous() throws Exception {
+ setSystemProperty("oozie.authentication.simple.anonymous.allowed", "false");
+ runTest(new Callable() {
+ public Void call() throws Exception {
+ String oozieUrl = getContextURL();
+ String[] args = new String[]{"admin", "-status", "-oozie", oozieUrl};
+ assertEquals(0, new AuthOozieCLI().run(args));
+ return null;
+ }
+ });
+ }
+
+ public void testAuthClientWithCustomAuthenticator() throws Exception {
+ setSystemProperty("authenticator.class", Authenticator4Test.class.getName());
+ setSystemProperty("oozie.authentication.simple.anonymous.allowed", "false");
+ Authenticator4Test.USED = false;
+ runTest(new Callable() {
+ public Void call() throws Exception {
+ String oozieUrl = getContextURL();
+ String[] args = new String[]{"admin", "-status", "-oozie", oozieUrl};
+ assertEquals(0, new AuthOozieCLI().run(args));
+ return null;
+ }
+ });
+ assertTrue(Authenticator4Test.USED);
+ }
+
+
+ public void testAuthClientAuthTokenCache() throws Exception {
+ //not using cache
+ setSystemProperty("oozie.authentication.simple.anonymous.allowed", "false");
+ AuthOozieClient.AUTH_TOKEN_CACHE_FILE.delete();
+ assertFalse(AuthOozieClient.AUTH_TOKEN_CACHE_FILE.exists());
+ runTest(new Callable() {
+ public Void call() throws Exception {
+ String oozieUrl = getContextURL();
+ String[] args = new String[]{"admin", "-status", "-oozie", oozieUrl};
+ assertEquals(0, new AuthOozieCLI().run(args));
+ return null;
+ }
+ });
+ assertFalse(AuthOozieClient.AUTH_TOKEN_CACHE_FILE.exists());
+
+ //using cache
+ setSystemProperty("oozie.auth.token.cache", "true");
+ setSystemProperty("oozie.authentication.simple.anonymous.allowed", "false");
+ setSystemProperty("oozie.authentication.signature.secret", "secret");
+ AuthOozieClient.AUTH_TOKEN_CACHE_FILE.delete();
+ assertFalse(AuthOozieClient.AUTH_TOKEN_CACHE_FILE.exists());
+ runTest(new Callable() {
+ public Void call() throws Exception {
+ String oozieUrl = getContextURL();
+ String[] args = new String[]{"admin", "-status", "-oozie", oozieUrl};
+ assertEquals(0, new AuthOozieCLI().run(args));
+ return null;
+ }
+ });
+ assertTrue(AuthOozieClient.AUTH_TOKEN_CACHE_FILE.exists());
+ String currentCache = IOUtils.getReaderAsString(new FileReader(AuthOozieClient.AUTH_TOKEN_CACHE_FILE), -1);
+
+ //re-using cache
+ setSystemProperty("oozie.auth.token.cache", "true");
+ setSystemProperty("oozie.authentication.simple.anonymous.allowed", "false");
+ setSystemProperty("oozie.authentication.signature.secret", "secret");
+ runTest(new Callable() {
+ public Void call() throws Exception {
+ String oozieUrl = getContextURL();
+ String[] args = new String[]{"admin", "-status", "-oozie", oozieUrl};
+ assertEquals(0, new AuthOozieCLI().run(args));
+ return null;
+ }
+ });
+ assertTrue(AuthOozieClient.AUTH_TOKEN_CACHE_FILE.exists());
+ String newCache = IOUtils.getReaderAsString(new FileReader(AuthOozieClient.AUTH_TOKEN_CACHE_FILE), -1);
+ assertEquals(currentCache, newCache);
+ }
+
+}
diff --git a/docs/src/site/twiki/AG_Install.twiki b/docs/src/site/twiki/AG_Install.twiki
index 0f53924e8..28ea3dd93 100644
--- a/docs/src/site/twiki/AG_Install.twiki
+++ b/docs/src/site/twiki/AG_Install.twiki
@@ -160,11 +160,66 @@ Oozie logs in 4 different files:
The embedded Tomcat and embedded Derby log files are also written to Oozie's =logs/= directory.
----+++ Oozie Authentication Configuration
+---+++ Oozie User Authentication Configuration
+
+*IMPORTANT:* This feature is only available in Cloudera's distribution of Oozie (CDH3b4 onwards).
+
+Oozie supports Kerberos HTTP SPNEGO authentication, pseudo/simple authentication and anonymous access
+for client connections.
+
+Anonymous access (*default*) does not require the user to authenticate and the user ID is obtained from
+the job properties on job submission operations, other operations are anonymous.
+
+Pseudo/simple authentication requires the user to specify the user name on the request, this must be done
+using the additional query string parameter =user.name=.
+
+Kerberos HTTP SPNEGO authentication requires the user to perform a Kerberos HTTP SPNEGO authentication sequence.
+
+If Pseudo/simple or Kerberos HTTP SPNEGO authentication mechanisms are used, Oozie will return the user an
+authentication token HTTP Cookie that can be used in later requests as identy proof.
+
+Oozie uses [[http://www.github.com/cloudera/alfredo][Alfredo, Java HTTP SPENGO]] library for authentication.
+This library can be extended to support other authentication mechanisms.
+
+Oozie user authentication is configured using the following configuration properties (default values shown):
+
+
+ oozie.authentication.type=simple
+ oozie.authentication.token.validity=36000
+ oozie.authentication.signature.secret=
+ oozie.authentication.cookie.domain=
+ oozie.authentication.simple.anonymous.allowed=true
+ oozie.authentication.kerberos.principal=HTTP/localhost@${local.realm}
+ oozie.authentication.kerberos.keytab=${oozie.service.HadoopAccessorService.keytab.file}
+
+
+The =type= defines authentication used for Oozie HTTP endpoint, the supported values are:
+simple | kerberos | #AUTHENTICATION_HANDLER_CLASSNAME#.
+
+The =token.validity= indicates how long (in seconds) an authentication token is valid before it has
+to be renewed.
+
+The =signature.secret= is the signature secret for signing the authentication tokens. If not set a random
+secret is generated at startup time.
+
+The =oozie.authentication.cookie.domain= 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.
+
+The =simple.anonymous.allowed= indicates if anonymous requests are allowed. This setting is meaningful
+only when using 'simple' authentication.
+
+The =kerberos.principal= indicates the Kerberos principal to be used for HTTP endpoint.
+The principal MUST start with 'HTTP/' as per Kerberos HTTP SPNEGO specification.
+
+The =kerberos.keytab= indicates the location of the keytab file with the credentials for the principal.
+It should be the same keytab file Oozie uses for its Kerberos credentials for Hadoop.
+
+---+++ Oozie Hadoop Authentication Configuration
Oozie can work with Hadoop 20 with Security distribution which supports Kerberos authentication.
-Oozie authentication is configured using the following configuration properties (default values shown):
+Oozie Hadoop authentication is configured using the following configuration properties (default values shown):
oozie.service.HadoopAccessorService.kerberos.enabled=false
diff --git a/docs/src/site/twiki/DG_CommandLineTool.twiki b/docs/src/site/twiki/DG_CommandLineTool.twiki
index 6e169a6b2..ede377101 100644
--- a/docs/src/site/twiki/DG_CommandLineTool.twiki
+++ b/docs/src/site/twiki/DG_CommandLineTool.twiki
@@ -21,9 +21,9 @@ usage:
custom headers for Oozie web services can be specified using '-Dheader:NAME=VALUE'
oozie help : display usage
-
+.
oozie version : show client version
-
+.
oozie job : job operations
-action coordinator rerun on action ids (requires -rerun)
-change change a coordinator job
@@ -52,7 +52,7 @@ usage:
-value new endtime/concurrency/pausetime value for changing a
coordinator job
-verbose verbose mode
-
+.
oozie jobs : jobs status
-filter user=;name=;group=;status=;...
-jobtype job type ('Supported in Oozie-2.0 or later versions ONLY -
@@ -62,7 +62,7 @@ usage:
-offset jobs offset (default '1')
-oozie Oozie URL
-verbose verbose mode
-
+.
oozie admin : admin operations
-oozie Oozie URL
-queuedump show Oozie server queue elements
@@ -70,14 +70,14 @@ usage:
-systemmode Supported in Oozie-2.0 or later versions ONLY. Change oozie
system mode [NORMAL|NOWEBSERVICE|SAFEMODE]
-version show Oozie server build version
-
+.
oozie validate : validate a workflow XML file
-
+.
oozie sla : sla operations (Supported in Oozie-2.0 or later)
-len number of results (default '100')
-offset start offset (default '0')
-oozie Oozie URL
-
+.
oozie pig -X : submit a pig job, everything after '-X' are pass-through parameters to pig
-config job configuration file '.properties'
-D set/override value for given property
@@ -87,6 +87,33 @@ usage:
---++ Common CLI Options
+---+++ Authentication
+
+*IMPORTANT:* This feature is only available in Cloudera's distribution of Oozie (CDH3b3 onwards).
+
+The =oozie= CLI automatically perform authentication i the Oozie server requests it. By default it supports both
+pseudo/simple authentication and Kerberos HTTP SPNEGO authentication.
+
+For pseudo/simple authentication the =oozie= CLI uses the user name of the current OS user.
+
+For Kerberos HTTP SPNEGO authentication the =oozie= CLI uses the default principal for the OS Kerberos cache
+(normally the principal that did =kinit=).
+
+Oozie uses [[http://www.github.com/cloudera/alfredo][Alfredo, Java HTTP SPENGO]] library for authentication.
+This library can be extended to support other authentication mechanisms.
+
+To disable authentication altogether the =-Doozie.client.class= option must be set with no value. This is required
+for the =oozie= CLI to work with older versions of Oozie server.
+
+Once authentication is performed successfully the received authentication token is cached in the user home directory
+in the =.oozie-auth-token= file with owner-only permissions. Subsequent requests reuse the cached token while valid.
+
+The use of the cache file can be disabled by invoking the =oozie= CLI with the =-Doozie.auth.token.cache=false=
+option.
+
+To use an custom authentication mechanism, an Alfredo =Authenticator= implementation must be specified with the
+ =-Dauthenticator.class= = =CLASS= option.
+
---+++ Oozie URL
All =oozie= CLI sub-commands expect the -oozie OOZIE_URL option indicating the URL of the Oozie system
@@ -257,7 +284,6 @@ After the command is executed the rerun coordiator action will be in =WAITING= s
Refer to the [[DG_CoordinatorRerun][Rerunning Coordinator Actions]] for details on rerun.
-
---+++ Checking the Status of a Workflow Job or Coordinator Job or Coordinator Action
Example:
diff --git a/pom.xml b/pom.xml
index 919fcc70f..4cb311df5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -99,6 +99,13 @@
true
+
+ cdh.releases
+ https://repository.cloudera.com/content/repositories/releases
+
+ false
+
+
@@ -220,7 +227,7 @@
commons-codec
commons-codec
- 1.3
+ 1.4
@@ -425,7 +432,13 @@
10.6.1.0
compile
-
+
+
+ com.cloudera.alfredo
+ alfredo
+ 0.1.4
+
+
diff --git a/release-log.txt b/release-log.txt
index fe2e273df..e7dc5780b 100644
--- a/release-log.txt
+++ b/release-log.txt
@@ -1,5 +1,6 @@
-- Oozie 3.0.0 release
+GH-0035 Add support for Kerberos HTTP SPNEGO authentication (using Alfredo)
GH-0542 Refactor CoordSuspendXCommand & CoordResumeXCommand to TransitionXCommand based
GH-0543 Refactor CoordRerunXCommand to TransitionXCommand based
GH-0530 Update workflow rerun twiki.
diff --git a/webapp/pom.xml b/webapp/pom.xml
index 1ae21fc26..427495ee3 100644
--- a/webapp/pom.xml
+++ b/webapp/pom.xml
@@ -67,14 +67,6 @@
javax.servlet
jsp-api
-
- org.slf4j
- slf4j-api
-
-
- org.slf4j
- slf4j-log4j12
-
commons-logging
commons-logging-api
diff --git a/webapp/src/main/webapp/WEB-INF/web.xml b/webapp/src/main/webapp/WEB-INF/web.xml
index 542960b4c..390427b92 100644
--- a/webapp/src/main/webapp/WEB-INF/web.xml
+++ b/webapp/src/main/webapp/WEB-INF/web.xml
@@ -143,4 +143,64 @@
index.html
+
+ authenticationfilter
+ org.apache.oozie.servlet.AuthFilter
+
+
+
+ authenticationfilter
+ /versions/*
+
+
+
+ authenticationfilter
+ /v0/admin/*
+
+
+
+ authenticationfilter
+ /v1/admin/*
+
+
+
+ authenticationfilter
+ /v0/jobs
+
+
+
+ authenticationfilter
+ /v1/jobs
+
+
+
+ authenticationfilter
+ /v0/job/*
+
+
+
+ authenticationfilter
+ /v1/job/*
+
+
+
+ authenticationfilter
+ /index.html
+
+
+
+ authenticationfilter
+ *.js
+
+
+
+ authenticationfilter
+ /ext-2.2/*
+
+
+
+ authenticationfilter
+ /docs/*
+
+