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:
@@ -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
|
is also pulled from LDAP, making any LDAP groups that a user is a
|
||||||
member of available as groups in Gerrit.
|
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`
|
* `LDAP`
|
||||||
+
|
+
|
||||||
Gerrit prompts the user to enter a username and a password, which
|
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
|
LDAP integration is only enabled if `auth.type` was set to
|
||||||
`HTTP_LDAP` or `LDAP`. See above for a detailed description of
|
`HTTP_LDAP`, `LDAP` or `CLIENT_SSL_CERT_LDAP`. See above for a
|
||||||
the auth.type settings and their implications.
|
detailed description of the auth.type settings and their
|
||||||
|
implications.
|
||||||
|
|
||||||
An example LDAP configuration follows, and then discussion of
|
An example LDAP configuration follows, and then discussion of
|
||||||
the parameters introduced here. Suitable defaults for most
|
the parameters introduced here. Suitable defaults for most
|
||||||
|
@@ -481,6 +481,7 @@ public class Gerrit implements EntryPoint {
|
|||||||
switch (cfg.getAuthType()) {
|
switch (cfg.getAuthType()) {
|
||||||
case HTTP:
|
case HTTP:
|
||||||
case HTTP_LDAP:
|
case HTTP_LDAP:
|
||||||
|
case CLIENT_SSL_CERT_LDAP:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPENID:
|
case OPENID:
|
||||||
|
@@ -225,6 +225,7 @@ public class AccountGroupScreen extends AccountScreen {
|
|||||||
case HTTP_LDAP:
|
case HTTP_LDAP:
|
||||||
case LDAP:
|
case LDAP:
|
||||||
case LDAP_BIND:
|
case LDAP_BIND:
|
||||||
|
case CLIENT_SSL_CERT_LDAP:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
|
@@ -19,6 +19,7 @@ import static com.google.inject.Scopes.SINGLETON;
|
|||||||
import com.google.gerrit.common.data.GerritConfig;
|
import com.google.gerrit.common.data.GerritConfig;
|
||||||
import com.google.gerrit.httpd.auth.become.BecomeAnyAccountLoginServlet;
|
import com.google.gerrit.httpd.auth.become.BecomeAnyAccountLoginServlet;
|
||||||
import com.google.gerrit.httpd.auth.container.HttpAuthModule;
|
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.ldap.LdapAuthModule;
|
||||||
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
|
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
|
||||||
import com.google.gerrit.httpd.gitweb.GitWebModule;
|
import com.google.gerrit.httpd.gitweb.GitWebModule;
|
||||||
@@ -101,6 +102,10 @@ public class WebModule extends FactoryModule {
|
|||||||
install(new HttpAuthModule());
|
install(new HttpAuthModule());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CLIENT_SSL_CERT_LDAP:
|
||||||
|
install(new HttpsClientSslCertModule());
|
||||||
|
break;
|
||||||
|
|
||||||
case LDAP:
|
case LDAP:
|
||||||
case LDAP_BIND:
|
case LDAP_BIND:
|
||||||
install(new LdapAuthModule());
|
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.launcher.GerritLauncher;
|
||||||
import com.google.gerrit.lifecycle.LifecycleListener;
|
import com.google.gerrit.lifecycle.LifecycleListener;
|
||||||
|
import com.google.gerrit.reviewdb.AuthType;
|
||||||
import com.google.gerrit.server.CurrentUser;
|
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.GerritServerConfig;
|
||||||
import com.google.gerrit.server.config.SitePaths;
|
import com.google.gerrit.server.config.SitePaths;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
@@ -139,6 +141,7 @@ public class JettyServer {
|
|||||||
final URI[] listenUrls = listenURLs(cfg);
|
final URI[] listenUrls = listenURLs(cfg);
|
||||||
final boolean reuseAddress = cfg.getBoolean("httpd", "reuseaddress", true);
|
final boolean reuseAddress = cfg.getBoolean("httpd", "reuseaddress", true);
|
||||||
final int acceptors = cfg.getInt("httpd", "acceptorThreads", 2);
|
final int acceptors = cfg.getInt("httpd", "acceptorThreads", 2);
|
||||||
|
final AuthType authType = ConfigUtil.getEnum(cfg, "auth", null, "type", AuthType.OPENID);
|
||||||
|
|
||||||
reverseProxy = true;
|
reverseProxy = true;
|
||||||
final Connector[] connectors = new Connector[listenUrls.length];
|
final Connector[] connectors = new Connector[listenUrls.length];
|
||||||
@@ -147,11 +150,17 @@ public class JettyServer {
|
|||||||
final int defaultPort;
|
final int defaultPort;
|
||||||
final SelectChannelConnector c;
|
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())) {
|
if ("http".equals(u.getScheme())) {
|
||||||
reverseProxy = false;
|
reverseProxy = false;
|
||||||
defaultPort = 80;
|
defaultPort = 80;
|
||||||
c = new SelectChannelConnector();
|
c = new SelectChannelConnector();
|
||||||
|
|
||||||
} else if ("https".equals(u.getScheme())) {
|
} else if ("https".equals(u.getScheme())) {
|
||||||
final SslSelectChannelConnector ssl = new SslSelectChannelConnector();
|
final SslSelectChannelConnector ssl = new SslSelectChannelConnector();
|
||||||
final File keystore = getFile(cfg, "sslkeystore", "etc/keystore");
|
final File keystore = getFile(cfg, "sslkeystore", "etc/keystore");
|
||||||
@@ -164,6 +173,10 @@ public class JettyServer {
|
|||||||
ssl.setKeyPassword(password);
|
ssl.setKeyPassword(password);
|
||||||
ssl.setTrustPassword(password);
|
ssl.setTrustPassword(password);
|
||||||
|
|
||||||
|
if (AuthType.CLIENT_SSL_CERT_LDAP.equals(authType)) {
|
||||||
|
ssl.setNeedClientAuth(true);
|
||||||
|
}
|
||||||
|
|
||||||
reverseProxy = false;
|
reverseProxy = false;
|
||||||
defaultPort = 443;
|
defaultPort = 443;
|
||||||
c = ssl;
|
c = ssl;
|
||||||
|
@@ -39,6 +39,21 @@ public enum AuthType {
|
|||||||
*/
|
*/
|
||||||
HTTP_LDAP,
|
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.
|
* Login collects username and password through a web form, and binds to LDAP.
|
||||||
* <p>
|
* <p>
|
||||||
|
@@ -146,6 +146,7 @@ public class AuthConfig {
|
|||||||
case HTTP_LDAP:
|
case HTTP_LDAP:
|
||||||
case LDAP:
|
case LDAP:
|
||||||
case LDAP_BIND:
|
case LDAP_BIND:
|
||||||
|
case CLIENT_SSL_CERT_LDAP:
|
||||||
// Its safe to assume yes for an HTTP authentication type, as the
|
// Its safe to assume yes for an HTTP authentication type, as the
|
||||||
// only way in is through some external system that the admin trusts
|
// 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 HTTP_LDAP:
|
||||||
case LDAP:
|
case LDAP:
|
||||||
case LDAP_BIND:
|
case LDAP_BIND:
|
||||||
|
case CLIENT_SSL_CERT_LDAP:
|
||||||
install(new LdapModule());
|
install(new LdapModule());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user