You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by jg...@apache.org on 2010/07/03 02:02:04 UTC

svn commit: r960137 - in /hadoop/common/trunk: CHANGES.txt src/java/org/apache/hadoop/http/HttpServer.java src/java/org/apache/hadoop/security/Krb5AndCertsSslSocketConnector.java

Author: jghoman
Date: Sat Jul  3 00:02:03 2010
New Revision: 960137

URL: http://svn.apache.org/viewvc?rev=960137&view=rev
Log:
HADOOP-6584. Provide Kerberized SSL encryption for webservices. 

Added:
    hadoop/common/trunk/src/java/org/apache/hadoop/security/Krb5AndCertsSslSocketConnector.java
Modified:
    hadoop/common/trunk/CHANGES.txt
    hadoop/common/trunk/src/java/org/apache/hadoop/http/HttpServer.java

Modified: hadoop/common/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/CHANGES.txt?rev=960137&r1=960136&r2=960137&view=diff
==============================================================================
--- hadoop/common/trunk/CHANGES.txt (original)
+++ hadoop/common/trunk/CHANGES.txt Sat Jul  3 00:02:03 2010
@@ -13,6 +13,9 @@ Trunk (unreleased changes)
     they can be used for authorization (Kan Zhang and Jitendra Pandey 
     via jghoman)
 
+    HADOOP-6584. Provide Kerberized SSL encryption for webservices.
+    (jghoman and Kan Zhang via jghoman)
+
   IMPROVEMENTS
 
     HADOOP-6644. util.Shell getGROUPS_FOR_USER_COMMAND method name 

Modified: hadoop/common/trunk/src/java/org/apache/hadoop/http/HttpServer.java
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/src/java/org/apache/hadoop/http/HttpServer.java?rev=960137&r1=960136&r2=960137&view=diff
==============================================================================
--- hadoop/common/trunk/src/java/org/apache/hadoop/http/HttpServer.java (original)
+++ hadoop/common/trunk/src/java/org/apache/hadoop/http/HttpServer.java Sat Jul  3 00:02:03 2010
@@ -46,6 +46,8 @@ import org.apache.commons.logging.LogFac
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.log.LogLevel;
 import org.apache.hadoop.metrics.MetricsServlet;
+import org.apache.hadoop.security.Krb5AndCertsSslSocketConnector;
+import org.apache.hadoop.security.Krb5AndCertsSslSocketConnector.MODE;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.security.authorize.AccessControlList;
 import org.apache.hadoop.util.ReflectionUtils;
@@ -162,7 +164,11 @@ public class HttpServer implements Filte
     webServer.addHandler(webAppContext);
 
     addDefaultApps(contexts, appDir, conf);
-
+    
+    defineFilter(webAppContext, "krb5Filter", 
+        Krb5AndCertsSslSocketConnector.Krb5SslFilter.class.getName(), 
+        null, null);
+    
     addGlobalFilter("safety", QuotingInputFilter.class.getName(), null);
     final FilterInitializer[] initializers = getFilterInitializers(conf); 
     if (initializers != null) {
@@ -290,7 +296,7 @@ public class HttpServer implements Filte
    */
   public void addServlet(String name, String pathSpec,
       Class<? extends HttpServlet> clazz) {
-    addInternalServlet(name, pathSpec, clazz);
+    addInternalServlet(name, pathSpec, clazz, false);
     addFilterPathMapping(pathSpec, webAppContext);
   }
 
@@ -306,11 +312,38 @@ public class HttpServer implements Filte
    */
   public void addInternalServlet(String name, String pathSpec,
       Class<? extends HttpServlet> clazz) {
+    addInternalServlet(name, pathSpec, clazz, false);
+  }
+
+  /**
+   * Add an internal servlet in the server, specifying whether or not to
+   * protect with Kerberos authentication. 
+   * Note: This method is to be used for adding servlets that facilitate
+   * internal communication and not for user facing functionality. For
+   * servlets added using this method, filters (except internal Kerberized
+   * filters) are not enabled. 
+   * 
+   * @param name The name of the servlet (can be passed as null)
+   * @param pathSpec The path spec for the servlet
+   * @param clazz The servlet class
+   */
+  public void addInternalServlet(String name, String pathSpec, 
+      Class<? extends HttpServlet> clazz, boolean requireAuth) {
     ServletHolder holder = new ServletHolder(clazz);
     if (name != null) {
       holder.setName(name);
     }
     webAppContext.addServlet(holder, pathSpec);
+    
+    if(requireAuth && UserGroupInformation.isSecurityEnabled()) {
+       LOG.info("Adding Kerberos filter to " + name);
+       ServletHandler handler = webAppContext.getServletHandler();
+       FilterMapping fmap = new FilterMapping();
+       fmap.setPathSpec(pathSpec);
+       fmap.setFilterName("krb5Filter");
+       fmap.setDispatches(Handler.ALL);
+       handler.addFilterMapping(fmap);
+    }
   }
 
   /** {@inheritDoc} */
@@ -451,10 +484,22 @@ public class HttpServer implements Filte
    */
   public void addSslListener(InetSocketAddress addr, Configuration sslConf,
       boolean needClientAuth) throws IOException {
+    addSslListener(addr, sslConf, needClientAuth, false);
+  }
+
+  /**
+   * Configure an ssl listener on the server.
+   * @param addr address to listen on
+   * @param sslConf conf to retrieve ssl options
+   * @param needCertsAuth whether x509 certificate authentication is required
+   * @param needKrbAuth whether to allow kerberos auth
+   */
+  public void addSslListener(InetSocketAddress addr, Configuration sslConf,
+      boolean needCertsAuth, boolean needKrbAuth) throws IOException {
     if (webServer.isStarted()) {
       throw new IOException("Failed to add ssl listener");
     }
-    if (needClientAuth) {
+    if (needCertsAuth) {
       // setting up SSL truststore for authenticating clients
       System.setProperty("javax.net.ssl.trustStore", sslConf.get(
           "ssl.server.truststore.location", ""));
@@ -463,14 +508,22 @@ public class HttpServer implements Filte
       System.setProperty("javax.net.ssl.trustStoreType", sslConf.get(
           "ssl.server.truststore.type", "jks"));
     }
-    SslSocketConnector sslListener = new SslSocketConnector();
+    Krb5AndCertsSslSocketConnector.MODE mode;
+    if(needCertsAuth && needKrbAuth)
+      mode = MODE.BOTH;
+    else if (!needCertsAuth && needKrbAuth)
+      mode = MODE.KRB;
+    else // Default to certificates
+      mode = MODE.CERTS;
+
+    SslSocketConnector sslListener = new Krb5AndCertsSslSocketConnector(mode);
     sslListener.setHost(addr.getHostName());
     sslListener.setPort(addr.getPort());
     sslListener.setKeystore(sslConf.get("ssl.server.keystore.location"));
     sslListener.setPassword(sslConf.get("ssl.server.keystore.password", ""));
     sslListener.setKeyPassword(sslConf.get("ssl.server.keystore.keypassword", ""));
     sslListener.setKeystoreType(sslConf.get("ssl.server.keystore.type", "jks"));
-    sslListener.setNeedClientAuth(needClientAuth);
+    sslListener.setNeedClientAuth(needCertsAuth);
     webServer.addConnector(sslListener);
   }
 

Added: hadoop/common/trunk/src/java/org/apache/hadoop/security/Krb5AndCertsSslSocketConnector.java
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/src/java/org/apache/hadoop/security/Krb5AndCertsSslSocketConnector.java?rev=960137&view=auto
==============================================================================
--- hadoop/common/trunk/src/java/org/apache/hadoop/security/Krb5AndCertsSslSocketConnector.java (added)
+++ hadoop/common/trunk/src/java/org/apache/hadoop/security/Krb5AndCertsSslSocketConnector.java Sat Jul  3 00:02:03 2010
@@ -0,0 +1,228 @@
+/**
+ * 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 java.net.InetAddress;
+import java.net.ServerSocket;
+import java.security.Principal;
+import java.util.Random;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSocket;
+import javax.security.auth.kerberos.KerberosPrincipal;
+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.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.mortbay.io.EndPoint;
+import org.mortbay.jetty.HttpSchemes;
+import org.mortbay.jetty.Request;
+import org.mortbay.jetty.security.ServletSSL;
+import org.mortbay.jetty.security.SslSocketConnector;
+
+/**
+ * Extend Jetty's {@link SslSocketConnector} to optionally also provide 
+ * Kerberos5ized SSL sockets.  The only change in behavior from superclass
+ * is that we no longer honor requests to turn off NeedAuthentication when
+ * running with Kerberos support.
+ */
+public class Krb5AndCertsSslSocketConnector extends SslSocketConnector {
+  public static final String[] KRB5_CIPHER_SUITES = 
+    new String [] {"TLS_KRB5_WITH_3DES_EDE_CBC_SHA"};
+  static {
+    System.setProperty("https.cipherSuites", KRB5_CIPHER_SUITES[0]);
+  }
+  
+  private static final Log LOG = LogFactory
+      .getLog(Krb5AndCertsSslSocketConnector.class);
+
+  private static final String REMOTE_PRINCIPAL = "remote_principal";
+
+  public enum MODE {KRB, CERTS, BOTH} // Support Kerberos, certificates or both?
+
+  private final boolean useKrb;
+  private final boolean useCerts;
+
+  public Krb5AndCertsSslSocketConnector() {
+    super();
+    useKrb = true;
+    useCerts = false;
+    
+    setPasswords();
+  }
+  
+  public Krb5AndCertsSslSocketConnector(MODE mode) {
+    super();
+    useKrb = mode == MODE.KRB || mode == MODE.BOTH;
+    useCerts = mode == MODE.CERTS || mode == MODE.BOTH;
+    setPasswords();
+    logIfDebug("useKerb = " + useKrb + ", useCerts = " + useCerts);
+  }
+
+  // If not using Certs, set passwords to random gibberish or else
+  // Jetty will actually prompt the user for some.
+  private void setPasswords() {
+   if(!useCerts) {
+     Random r = new Random();
+     System.setProperty("jetty.ssl.password", String.valueOf(r.nextLong()));
+     System.setProperty("jetty.ssl.keypassword", String.valueOf(r.nextLong()));
+   }
+  }
+  
+  @Override
+  protected SSLServerSocketFactory createFactory() throws Exception {
+    if(useCerts)
+      return super.createFactory();
+    
+    SSLContext context = super.getProvider()==null
+       ? SSLContext.getInstance(super.getProtocol())
+        :SSLContext.getInstance(super.getProtocol(), super.getProvider());
+    context.init(null, null, null);
+    
+    return context.getServerSocketFactory();
+  }
+  
+  /* (non-Javadoc)
+   * @see org.mortbay.jetty.security.SslSocketConnector#newServerSocket(java.lang.String, int, int)
+   */
+  @Override
+  protected ServerSocket newServerSocket(String host, int port, int backlog)
+      throws IOException {
+    logIfDebug("Creating new KrbServerSocket for: " + host);
+    SSLServerSocket ss = null;
+    
+    if(useCerts) // Get the server socket from the SSL super impl
+      ss = (SSLServerSocket)super.newServerSocket(host, port, backlog);
+    else { // Create a default server socket
+      try {
+        ss = (SSLServerSocket)(host == null 
+         ? createFactory().createServerSocket(port, backlog) :
+           createFactory().createServerSocket(port, backlog, InetAddress.getByName(host)));
+      } catch (Exception e)
+      {
+        LOG.warn("Could not create KRB5 Listener", e);
+        throw new IOException("Could not create KRB5 Listener: " + e.toString());
+      }
+    }
+    
+    // Add Kerberos ciphers to this socket server if needed.
+    if(useKrb) {
+      ss.setNeedClientAuth(true);
+      String [] combined;
+      if(useCerts) { // combine the cipher suites
+        String[] certs = ss.getEnabledCipherSuites();
+        combined = new String[certs.length + KRB5_CIPHER_SUITES.length];
+        System.arraycopy(certs, 0, combined, 0, certs.length);
+        System.arraycopy(KRB5_CIPHER_SUITES, 0, combined, certs.length, KRB5_CIPHER_SUITES.length);
+      } else { // Just enable Kerberos auth
+        combined = KRB5_CIPHER_SUITES;
+      }
+      
+      ss.setEnabledCipherSuites(combined);
+    }
+    
+    return ss;
+  };
+
+  @Override
+  public void customize(EndPoint endpoint, Request request) throws IOException {
+    if(useKrb) { // Add Kerberos-specific info
+      SSLSocket sslSocket = (SSLSocket)endpoint.getTransport();
+      Principal remotePrincipal = sslSocket.getSession().getPeerPrincipal();
+      logIfDebug("Remote principal = " + remotePrincipal);
+      request.setScheme(HttpSchemes.HTTPS);
+      request.setAttribute(REMOTE_PRINCIPAL, remotePrincipal);
+      
+      if(!useCerts) { // Add extra info that would have been added by super
+        String cipherSuite = sslSocket.getSession().getCipherSuite();
+        Integer keySize = Integer.valueOf(ServletSSL.deduceKeyLength(cipherSuite));;
+        
+        request.setAttribute("javax.servlet.request.cipher_suite", cipherSuite);
+        request.setAttribute("javax.servlet.request.key_size", keySize);
+      } 
+    }
+    
+    if(useCerts) super.customize(endpoint, request);
+  }
+  
+  private void logIfDebug(String s) {
+    if(LOG.isDebugEnabled())
+      LOG.debug(s);
+  }
+  
+  /**
+   * Filter that takes the Kerberos principal identified in the 
+   * {@link Krb5AndCertsSslSocketConnector} and provides it the to the servlet
+   * at runtime, setting the principal and short name.
+   */
+  public static class Krb5SslFilter implements Filter {
+    @Override
+    public void doFilter(ServletRequest req, ServletResponse resp,
+        FilterChain chain) throws IOException, ServletException {
+      final Principal princ = 
+        (Principal)req.getAttribute(Krb5AndCertsSslSocketConnector.REMOTE_PRINCIPAL);
+      
+      if(princ == null || !(princ instanceof KerberosPrincipal)) {
+        // Should never actually get here, since should be rejected at socket
+        // level.
+        LOG.warn("User not authenticated via kerberos from " + req.getRemoteAddr());
+        ((HttpServletResponse)resp).sendError(HttpServletResponse.SC_FORBIDDEN, 
+            "User not authenticated via Kerberos");
+        return;
+      }
+      
+      // Provide principal information for servlet at runtime
+      ServletRequest wrapper = 
+            new HttpServletRequestWrapper((HttpServletRequest) req) {
+        @Override
+        public Principal getUserPrincipal() {
+          return princ;
+        }
+        
+        /* 
+         * Return the full name of this remote user.
+         * @see javax.servlet.http.HttpServletRequestWrapper#getRemoteUser()
+         */
+        @Override 
+        public String getRemoteUser() {
+          return princ.getName();
+        }
+      };
+      
+      chain.doFilter(wrapper, resp);
+    }
+
+    @Override
+    public void init(FilterConfig arg0) throws ServletException {
+      /* Nothing to do here */
+    }
+
+    @Override
+    public void destroy() { /* Nothing to do here */ }
+  }
+}