Merge "SSO via client SSL certificates"
This commit is contained in:
		@@ -56,6 +56,19 @@ from the user's account object in LDAP.  The user's group membership
 | 
			
		||||
is also pulled from LDAP, making any LDAP groups that a user is a
 | 
			
		||||
member of available as groups in Gerrit.
 | 
			
		||||
+
 | 
			
		||||
* `CLIENT_SSL_CERT_LDAP`
 | 
			
		||||
+
 | 
			
		||||
This authentication type is actually kind of SSO. Gerrit will configure
 | 
			
		||||
Jetty's SSL channel to request client's SSL certificate. For this
 | 
			
		||||
authentication to work a Gerrit administrator has to import the root
 | 
			
		||||
certificate of the trust chain used to issue the client's certificate
 | 
			
		||||
into the <review-site>/etc/keystore.
 | 
			
		||||
After the authentication is done Gerrit will obtain basic user
 | 
			
		||||
registration (name and email) from LDAP, and some group memberships.
 | 
			
		||||
Therefore, the "_LDAP" suffix in the name of this authentication type.
 | 
			
		||||
This authentication type can only be used under hosted daemon mode, and
 | 
			
		||||
the httpd.listenUrl must use https:// as the protocol.
 | 
			
		||||
+
 | 
			
		||||
* `LDAP`
 | 
			
		||||
+
 | 
			
		||||
Gerrit prompts the user to enter a username and a password, which
 | 
			
		||||
@@ -1105,8 +1118,9 @@ By default, 5 minutes.
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
LDAP integration is only enabled if `auth.type` was set to
 | 
			
		||||
`HTTP_LDAP` or `LDAP`.  See above for a detailed description of
 | 
			
		||||
the auth.type settings and their implications.
 | 
			
		||||
`HTTP_LDAP`, `LDAP` or `CLIENT_SSL_CERT_LDAP`.  See above for a
 | 
			
		||||
detailed description of the auth.type settings and their
 | 
			
		||||
implications.
 | 
			
		||||
 | 
			
		||||
An example LDAP configuration follows, and then discussion of
 | 
			
		||||
the parameters introduced here.  Suitable defaults for most
 | 
			
		||||
 
 | 
			
		||||
@@ -481,6 +481,7 @@ public class Gerrit implements EntryPoint {
 | 
			
		||||
      switch (cfg.getAuthType()) {
 | 
			
		||||
        case HTTP:
 | 
			
		||||
        case HTTP_LDAP:
 | 
			
		||||
        case CLIENT_SSL_CERT_LDAP:
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
        case OPENID:
 | 
			
		||||
 
 | 
			
		||||
@@ -225,6 +225,7 @@ public class AccountGroupScreen extends AccountScreen {
 | 
			
		||||
      case HTTP_LDAP:
 | 
			
		||||
      case LDAP:
 | 
			
		||||
      case LDAP_BIND:
 | 
			
		||||
      case CLIENT_SSL_CERT_LDAP:
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        return;
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ import static com.google.inject.Scopes.SINGLETON;
 | 
			
		||||
import com.google.gerrit.common.data.GerritConfig;
 | 
			
		||||
import com.google.gerrit.httpd.auth.become.BecomeAnyAccountLoginServlet;
 | 
			
		||||
import com.google.gerrit.httpd.auth.container.HttpAuthModule;
 | 
			
		||||
import com.google.gerrit.httpd.auth.container.HttpsClientSslCertModule;
 | 
			
		||||
import com.google.gerrit.httpd.auth.ldap.LdapAuthModule;
 | 
			
		||||
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
 | 
			
		||||
import com.google.gerrit.httpd.gitweb.GitWebModule;
 | 
			
		||||
@@ -101,6 +102,10 @@ public class WebModule extends FactoryModule {
 | 
			
		||||
        install(new HttpAuthModule());
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case CLIENT_SSL_CERT_LDAP:
 | 
			
		||||
        install(new HttpsClientSslCertModule());
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case LDAP:
 | 
			
		||||
      case LDAP_BIND:
 | 
			
		||||
        install(new LdapAuthModule());
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,94 @@
 | 
			
		||||
// Copyright (C) 2010 The Android Open Source Project
 | 
			
		||||
//
 | 
			
		||||
// 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.
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.httpd.auth.container;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.httpd.WebSession;
 | 
			
		||||
import com.google.gerrit.server.account.AccountException;
 | 
			
		||||
import com.google.gerrit.server.account.AccountManager;
 | 
			
		||||
import com.google.gerrit.server.account.AuthRequest;
 | 
			
		||||
import com.google.gerrit.server.account.AuthResult;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
import com.google.inject.Singleton;
 | 
			
		||||
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.security.cert.X509Certificate;
 | 
			
		||||
import java.util.regex.Matcher;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.Filter;
 | 
			
		||||
import javax.servlet.FilterChain;
 | 
			
		||||
import javax.servlet.FilterConfig;
 | 
			
		||||
import javax.servlet.ServletException;
 | 
			
		||||
import javax.servlet.ServletRequest;
 | 
			
		||||
import javax.servlet.ServletResponse;
 | 
			
		||||
 | 
			
		||||
@Singleton
 | 
			
		||||
class HttpsClientSslCertAuthFilter implements Filter {
 | 
			
		||||
 | 
			
		||||
  private static final Pattern REGEX_USERID = Pattern.compile("CN=([^,]*),.*");
 | 
			
		||||
  private static final Logger log =
 | 
			
		||||
    LoggerFactory.getLogger(HttpsClientSslCertAuthFilter.class);
 | 
			
		||||
 | 
			
		||||
  private final Provider<WebSession> webSession;
 | 
			
		||||
  private final AccountManager accountManager;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  HttpsClientSslCertAuthFilter(final Provider<WebSession> webSession,
 | 
			
		||||
      final AccountManager accountManager) {
 | 
			
		||||
    this.webSession = webSession;
 | 
			
		||||
    this.accountManager = accountManager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void destroy() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void doFilter(ServletRequest req, ServletResponse rsp,
 | 
			
		||||
      FilterChain chain) throws IOException, ServletException {
 | 
			
		||||
    X509Certificate[] certs = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
 | 
			
		||||
    if (certs == null || certs.length == 0) {
 | 
			
		||||
      throw new ServletException(
 | 
			
		||||
          "Couldn't get the attribute javax.servlet.request.X509Certificate from the request");
 | 
			
		||||
    }
 | 
			
		||||
    String name = certs[0].getSubjectDN().getName();
 | 
			
		||||
    Matcher m = REGEX_USERID.matcher(name);
 | 
			
		||||
    String userName;
 | 
			
		||||
    if (m.matches()) {
 | 
			
		||||
      userName = m.group(1);
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new ServletException("Couldn't extract username from your certificate");
 | 
			
		||||
    }
 | 
			
		||||
    final AuthRequest areq = AuthRequest.forUser(userName);
 | 
			
		||||
    final AuthResult arsp;
 | 
			
		||||
    try {
 | 
			
		||||
      arsp = accountManager.authenticate(areq);
 | 
			
		||||
    } catch (AccountException e) {
 | 
			
		||||
      String err = "Unable to authenticate user \"" + userName + "\"";
 | 
			
		||||
      log.error(err, e);
 | 
			
		||||
      throw new ServletException(err, e);
 | 
			
		||||
    }
 | 
			
		||||
    webSession.get().login(arsp, true);
 | 
			
		||||
    chain.doFilter(req, rsp);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void init(FilterConfig arg0) throws ServletException {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
// Copyright (C) 2010 The Android Open Source Project
 | 
			
		||||
//
 | 
			
		||||
// 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.
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.httpd.auth.container;
 | 
			
		||||
 | 
			
		||||
import com.google.inject.servlet.ServletModule;
 | 
			
		||||
 | 
			
		||||
/** Servlets and support related to CLIENT_SSL_CERT_LDAP authentication. */
 | 
			
		||||
public class HttpsClientSslCertModule extends ServletModule {
 | 
			
		||||
  @Override
 | 
			
		||||
  protected void configureServlets() {
 | 
			
		||||
    filter("/").through(HttpsClientSslCertAuthFilter.class);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -19,7 +19,9 @@ import static java.util.concurrent.TimeUnit.SECONDS;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.launcher.GerritLauncher;
 | 
			
		||||
import com.google.gerrit.lifecycle.LifecycleListener;
 | 
			
		||||
import com.google.gerrit.reviewdb.AuthType;
 | 
			
		||||
import com.google.gerrit.server.CurrentUser;
 | 
			
		||||
import com.google.gerrit.server.config.ConfigUtil;
 | 
			
		||||
import com.google.gerrit.server.config.GerritServerConfig;
 | 
			
		||||
import com.google.gerrit.server.config.SitePaths;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
@@ -139,6 +141,7 @@ public class JettyServer {
 | 
			
		||||
    final URI[] listenUrls = listenURLs(cfg);
 | 
			
		||||
    final boolean reuseAddress = cfg.getBoolean("httpd", "reuseaddress", true);
 | 
			
		||||
    final int acceptors = cfg.getInt("httpd", "acceptorThreads", 2);
 | 
			
		||||
    final AuthType authType = ConfigUtil.getEnum(cfg, "auth", null, "type", AuthType.OPENID);
 | 
			
		||||
 | 
			
		||||
    reverseProxy = true;
 | 
			
		||||
    final Connector[] connectors = new Connector[listenUrls.length];
 | 
			
		||||
@@ -147,11 +150,17 @@ public class JettyServer {
 | 
			
		||||
      final int defaultPort;
 | 
			
		||||
      final SelectChannelConnector c;
 | 
			
		||||
 | 
			
		||||
      if (AuthType.CLIENT_SSL_CERT_LDAP.equals(authType) && ! "https".equals(u.getScheme())) {
 | 
			
		||||
        throw new IllegalArgumentException("Protocol '" + u.getScheme()
 | 
			
		||||
            + "' " + " not supported in httpd.listenurl '" + u
 | 
			
		||||
            + "' when auth.type = '" + AuthType.CLIENT_SSL_CERT_LDAP.name()
 | 
			
		||||
            + "'; only 'https' is supported");
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ("http".equals(u.getScheme())) {
 | 
			
		||||
        reverseProxy = false;
 | 
			
		||||
        defaultPort = 80;
 | 
			
		||||
        c = new SelectChannelConnector();
 | 
			
		||||
 | 
			
		||||
      } else if ("https".equals(u.getScheme())) {
 | 
			
		||||
        final SslSelectChannelConnector ssl = new SslSelectChannelConnector();
 | 
			
		||||
        final File keystore = getFile(cfg, "sslkeystore", "etc/keystore");
 | 
			
		||||
@@ -164,6 +173,10 @@ public class JettyServer {
 | 
			
		||||
        ssl.setKeyPassword(password);
 | 
			
		||||
        ssl.setTrustPassword(password);
 | 
			
		||||
 | 
			
		||||
        if (AuthType.CLIENT_SSL_CERT_LDAP.equals(authType)) {
 | 
			
		||||
          ssl.setNeedClientAuth(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reverseProxy = false;
 | 
			
		||||
        defaultPort = 443;
 | 
			
		||||
        c = ssl;
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,21 @@ public enum AuthType {
 | 
			
		||||
   */
 | 
			
		||||
  HTTP_LDAP,
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Login via client SSL certificate.
 | 
			
		||||
   * <p>
 | 
			
		||||
   * This authentication type is actually kind of SSO. Gerrit will configure
 | 
			
		||||
   * Jetty's SSL channel to request client's SSL certificate. For this
 | 
			
		||||
   * authentication to work a Gerrit administrator has to import the root
 | 
			
		||||
   * certificate of the trust chain used to issue the client's certificate
 | 
			
		||||
   * into the <review-site>/etc/keystore.
 | 
			
		||||
   * <p>
 | 
			
		||||
   * After the authentication is done Gerrit will obtain basic user
 | 
			
		||||
   * registration (name and email) from LDAP, and some group memberships.
 | 
			
		||||
   * Therefore, the "_LDAP" suffix in the name of this authentication type.
 | 
			
		||||
   */
 | 
			
		||||
  CLIENT_SSL_CERT_LDAP,
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Login collects username and password through a web form, and binds to LDAP.
 | 
			
		||||
   * <p>
 | 
			
		||||
 
 | 
			
		||||
@@ -146,6 +146,7 @@ public class AuthConfig {
 | 
			
		||||
      case HTTP_LDAP:
 | 
			
		||||
      case LDAP:
 | 
			
		||||
      case LDAP_BIND:
 | 
			
		||||
      case CLIENT_SSL_CERT_LDAP:
 | 
			
		||||
        // Its safe to assume yes for an HTTP authentication type, as the
 | 
			
		||||
        // only way in is through some external system that the admin trusts
 | 
			
		||||
        //
 | 
			
		||||
 
 | 
			
		||||
@@ -127,6 +127,7 @@ public class GerritGlobalModule extends FactoryModule {
 | 
			
		||||
      case HTTP_LDAP:
 | 
			
		||||
      case LDAP:
 | 
			
		||||
      case LDAP_BIND:
 | 
			
		||||
      case CLIENT_SSL_CERT_LDAP:
 | 
			
		||||
        install(new LdapModule());
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user