From eabc89728c5998fee26979661ece44a94b519095 Mon Sep 17 00:00:00 2001 From: Sasa Zivkov Date: Mon, 4 Oct 2010 15:47:08 +0200 Subject: [PATCH] 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 /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 --- Documentation/config-gerrit.txt | 18 +++- .../java/com/google/gerrit/client/Gerrit.java | 1 + .../client/admin/AccountGroupScreen.java | 1 + .../com/google/gerrit/httpd/WebModule.java | 5 + .../HttpsClientSslCertAuthFilter.java | 94 +++++++++++++++++++ .../container/HttpsClientSslCertModule.java | 25 +++++ .../gerrit/pgm/http/jetty/JettyServer.java | 15 ++- .../com/google/gerrit/reviewdb/AuthType.java | 15 +++ .../gerrit/server/config/AuthConfig.java | 1 + .../server/config/GerritGlobalModule.java | 1 + 10 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java create mode 100644 gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertModule.java diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt index f224a9c40b..c0a0b59129 100644 --- a/Documentation/config-gerrit.txt +++ b/Documentation/config-gerrit.txt @@ -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 /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 diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java index be962cec9d..d5ccdf9e10 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java @@ -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: diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java index 3fa7e2bd87..6a68d78a28 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java @@ -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; diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java index 93b6d09a0d..cc2e144742 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java @@ -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()); diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java new file mode 100644 index 0000000000..381daa8c87 --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java @@ -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; + private final AccountManager accountManager; + + @Inject + HttpsClientSslCertAuthFilter(final Provider 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 { + } +} diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertModule.java new file mode 100644 index 0000000000..f0976f3ed5 --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertModule.java @@ -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); + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java index e9d0f2e995..7ab8d30254 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java @@ -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; diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AuthType.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AuthType.java index 46b435c6fe..5d69e21da8 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AuthType.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AuthType.java @@ -39,6 +39,21 @@ public enum AuthType { */ HTTP_LDAP, + /** + * Login via client 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 /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. + */ + CLIENT_SSL_CERT_LDAP, + /** * Login collects username and password through a web form, and binds to LDAP. *

diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java index e66746da9d..6396431876 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java @@ -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 // diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java index c42d6539a0..cf7ff22010 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java @@ -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;