SSO via client SSL certificates

Support for authentication using client side SSL certificate.  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.

For the Gerrit's server side SSL certificate one can use a certificate signed
by a CA or a self-signed certificate.

After the authentication is done Gerrit will obtain basic user
registration (name and email) from LDAP, and some group memberships.

Change-Id: Ic076178f844f05b73be5d7c8fe9c8bb29b458f26
This commit is contained in:
Sasa Zivkov
2010-10-04 15:47:08 +02:00
parent e5669acc57
commit eabc89728c
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;