Merge "SSO via client SSL certificates"

This commit is contained in:
Shawn Pearce
2010-10-13 21:12:52 -07:00
committed by Android Code Review
10 changed files with 173 additions and 3 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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;

View File

@@ -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());

View File

@@ -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 {
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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>

View File

@@ -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
//

View File

@@ -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;