|
@@ -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 */ }
|
|
|
+ }
|
|
|
+}
|