From 5a0c89fb6aec59511dca47f80b5c3037b7c0f157 Mon Sep 17 00:00:00 2001 From: Evgeny Mandrikov Date: Tue, 11 Oct 2016 10:50:34 +0200 Subject: [PATCH] LDAP-53 Add support for StartTLS --- README.md | 23 +++++++++++++ docker-compose.yml | 31 +++++++++++++++++ docker/.gitignore | 1 + docker/Dockerfile.ldap | 7 ++++ docker/Dockerfile.sonarqube | 15 +++++++++ docker/gen-certs.sh | 22 +++++++++++++ docker/sonar.properties | 20 +++++++++++ docker/tester.ldif | 9 +++++ .../plugins/ldap/LdapContextFactory.java | 33 ++++++++++++++++++- 9 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 docker-compose.yml create mode 100644 docker/.gitignore create mode 100644 docker/Dockerfile.ldap create mode 100644 docker/Dockerfile.sonarqube create mode 100755 docker/gen-certs.sh create mode 100644 docker/sonar.properties create mode 100644 docker/tester.ldif diff --git a/README.md b/README.md index 1cfcb6b..463d2da 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,26 @@ SonarQube LDAP Plugin [![SonarQube.Com Quality Gate status](https://sonarqube.com/api/badges/gate?key=org.sonarsource.ldap%3Asonar-ldap-plugin)](https://sonarqube.com/overview?id=org.sonarsource.ldap%3Asonar-ldap-plugin) For more, see [the docs](http://docs.sonarqube.org/display/PLUG/LDAP+Plugin) + + +## Example + +You can check this plugin in action using Docker as described below. + +Build plugin: + + mvn clean package + +Generate certificates: + + ./docker/gen-certs.sh + +Build containers (SonarQube and OpenLDAP servers): + + docker-compose build + +Start containers: + + docker-compose up + +To access SonarQube use LDAP user `tester` with password `test`. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f7ca14f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,31 @@ +version: "2" + +services: + sonarqube-server: + build: + context: . + dockerfile: docker/Dockerfile.sonarqube + networks: + - sonarqube-network + ports: + - "9000:9000" + environment: + - SONARQUBE_WEB_JVM_OPTS=-Djavax.net.ssl.keyStore=/root/keystore -Djavax.net.ssl.keyStorePassword=changeit + + ldap-server: + build: + context: . + dockerfile: docker/Dockerfile.ldap + networks: + - sonarqube-network + environment: + - HOSTNAME=ldap-server + - LDAP_TLS_CRT_FILENAME=my-cert.crt + - LDAP_TLS_KEY_FILENAME=my-cert.key + - LDAP_TLS_CA_CRT_FILENAME=my-ca.crt + - LDAP_TLS_ENFORCE=true + - LDAP_TLS_VERIFY_CLIENT=demand + +networks: + sonarqube-network: + driver: bridge diff --git a/docker/.gitignore b/docker/.gitignore new file mode 100644 index 0000000..861b20c --- /dev/null +++ b/docker/.gitignore @@ -0,0 +1 @@ +/certs diff --git a/docker/Dockerfile.ldap b/docker/Dockerfile.ldap new file mode 100644 index 0000000..d508630 --- /dev/null +++ b/docker/Dockerfile.ldap @@ -0,0 +1,7 @@ +FROM osixia/openldap + +COPY docker/tester.ldif /container/service/slapd/assets/config/bootstrap/ldif/tester.ldif + +COPY docker/certs/server.crt /container/service/slapd/assets/certs/my-cert.crt +COPY docker/certs/server.key /container/service/slapd/assets/certs/my-cert.key +COPY docker/certs/ca.crt /container/service/slapd/assets/certs/my-ca.crt diff --git a/docker/Dockerfile.sonarqube b/docker/Dockerfile.sonarqube new file mode 100644 index 0000000..fd2943e --- /dev/null +++ b/docker/Dockerfile.sonarqube @@ -0,0 +1,15 @@ +FROM sonarqube:lts-alpine + +COPY docker/sonar.properties /opt/sonarqube/conf/sonar.properties +COPY sonar-ldap-plugin/target/sonar-ldap-plugin-*-SNAPSHOT.jar /opt/sonarqube/extensions/plugins/ + +COPY docker/certs/ca.crt /root/ca.crt +COPY docker/certs/client.p12 /root/client.p12 + +RUN keytool -import -trustcacerts -alias my-ca -file /root/ca.crt -keystore /etc/ssl/certs/java/cacerts -storepass changeit -noprompt + +RUN keytool -importkeystore \ + -deststorepass changeit -destkeypass changeit -destkeystore /root/keystore \ + -srckeystore /root/client.p12 -srcstoretype PKCS12 -srcstorepass pass + +RUN keytool -list -v -keystore /root/keystore -storepass changeit diff --git a/docker/gen-certs.sh b/docker/gen-certs.sh new file mode 100755 index 0000000..bd1fb6d --- /dev/null +++ b/docker/gen-certs.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -e + +cd docker +mkdir certs +cd certs + +openssl genrsa -out ca.key 4096 +openssl req -x509 -new -nodes -key ca.key -days 9131 -out ca.crt -subj "/C=CH/ST=Geneva/L=Geneva/O=Example/CN=example.org" + +openssl genrsa -out server.key 4096 +openssl req -new -key server.key -out server.csr -subj "/C=CH/ST=Geneva/L=Geneva/O=Example/CN=ldap-server" +openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 9131 + +openssl genrsa -out client.key 4096 +openssl req -new -key client.key -out client.csr -subj "/C=CH/ST=Geneva/L=Geneva/O=Example/CN=sonarqube-server" +openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 9131 + +cat client.crt ca.crt > cert-chain.txt +openssl pkcs12 -export -inkey client.key -in cert-chain.txt -out client.p12 -password pass:pass +rm cert-chain.txt diff --git a/docker/sonar.properties b/docker/sonar.properties new file mode 100644 index 0000000..6e78b01 --- /dev/null +++ b/docker/sonar.properties @@ -0,0 +1,20 @@ +# LDAP configuration + +# General Configuration +sonar.security.realm=LDAP + +ldap.url=ldap://ldap-server:389 +ldap.StartTLS=true + +ldap.bindDn=cn=admin,dc=example,dc=org +ldap.bindPassword=admin + +# User Configuration +ldap.user.baseDn=dc=example,dc=org +ldap.user.request=(&(objectClass=inetOrgPerson)(uid={login})) +ldap.user.realNameAttribute=cn +ldap.user.emailAttribute=mail + +# Group Configuration +#ldap.group.baseDn=ou=groups,dc=example,dc=org +#ldap.group.request=(&(objectClass=posixGroup)(memberUid={uid})) diff --git a/docker/tester.ldif b/docker/tester.ldif new file mode 100644 index 0000000..55722a6 --- /dev/null +++ b/docker/tester.ldif @@ -0,0 +1,9 @@ +dn: uid=tester,dc=example,dc=org +changetype: add +uid: tester +cn: Tester +sn: Tester +objectClass: top +objectClass: inetOrgPerson +userPassword: test +mail: tester@example.org diff --git a/sonar-ldap-plugin/src/main/java/org/sonar/plugins/ldap/LdapContextFactory.java b/sonar-ldap-plugin/src/main/java/org/sonar/plugins/ldap/LdapContextFactory.java index 63dc568..e00c05c 100644 --- a/sonar-ldap-plugin/src/main/java/org/sonar/plugins/ldap/LdapContextFactory.java +++ b/sonar-ldap-plugin/src/main/java/org/sonar/plugins/ldap/LdapContextFactory.java @@ -20,6 +20,7 @@ package org.sonar.plugins.ldap; import javax.annotation.Nullable; +import java.io.IOException; import javax.naming.Context; import javax.naming.NamingException; import javax.naming.directory.InitialDirContext; @@ -31,6 +32,8 @@ import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.Properties; +import javax.naming.ldap.StartTlsRequest; +import javax.naming.ldap.StartTlsResponse; import org.apache.commons.lang.StringUtils; import org.sonar.api.config.Settings; import org.sonar.api.utils.log.Logger; @@ -62,6 +65,7 @@ public class LdapContextFactory { private static final String SASL_REALM_PROPERTY = "java.naming.security.sasl.realm"; private final String providerUrl; + private final boolean startTLS; private final String authentication; private final String factory; private final String username; @@ -73,6 +77,7 @@ public LdapContextFactory(Settings settings, String settingsPrefix, String ldapU this.factory = StringUtils.defaultString(settings.getString(settingsPrefix + ".contextFactoryClass"), DEFAULT_FACTORY); this.realm = settings.getString(settingsPrefix + ".realm"); this.providerUrl = ldapUrl; + this.startTLS = settings.getBoolean(settingsPrefix + ".StartTLS"); this.username = settings.getString(settingsPrefix + ".bindDn"); this.password = settings.getString(settingsPrefix + ".bindPassword"); } @@ -97,7 +102,33 @@ public InitialDirContext createUserContext(String principal, String credentials) } private InitialDirContext createInitialDirContext(String principal, String credentials, boolean pooling) throws NamingException { - return new InitialLdapContext(getEnvironment(principal, credentials, pooling), null); + final InitialLdapContext ctx; + if (startTLS) { + // Note that pooling is not enabled for such connections, because "Stop TLS" is not performed. + Properties env = new Properties(); + env.put(Context.INITIAL_CONTEXT_FACTORY, factory); + env.put(Context.PROVIDER_URL, providerUrl); + env.put(Context.REFERRAL, DEFAULT_REFERRAL); + // At this point env should not contain properties SECURITY_AUTHENTICATION, SECURITY_PRINCIPAL and SECURITY_CREDENTIALS to avoid "bind" operation prior to StartTLS: + ctx = new InitialLdapContext(env, null); + // http://docs.oracle.com/javase/jndi/tutorial/ldap/ext/starttls.html + StartTlsResponse tls = (StartTlsResponse) ctx.extendedOperation(new StartTlsRequest()); + try { + tls.negotiate(); + } catch (IOException e) { + NamingException ex = new NamingException("StartTLS failed"); + ex.initCause(e); + throw ex; + } + // Explicitly initiate "bind" operation: + ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, authentication); + ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, principal); + ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials); + ctx.reconnect(null); + } else { + ctx = new InitialLdapContext(getEnvironment(principal, credentials, pooling), null); + } + return ctx; } private InitialDirContext createInitialDirContextUsingGssapi(String principal, String credentials) throws NamingException {