From 742e59f35198b42fb0ae8575aad3a08464f79e5c Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Fri, 05 Jul 2013 13:01:34 +0000
Subject: [PATCH] Backport fix for OPENDJ-1033: The Rest2LDAP servlet does not support SSL
---
opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json | 65 ++++++++++++++++-----
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java | 72 ++++++++++++++++++++++-
2 files changed, 116 insertions(+), 21 deletions(-)
diff --git a/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json b/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
index 743d308..7c2e135 100644
--- a/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
+++ b/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
@@ -4,9 +4,40 @@
"ldapConnectionFactories" : {
// Unauthenticated connections used for performing bind requests.
"default" : {
- "connectionPoolSize" : 10,
+ // Indicates whether or not LDAP connections should be secured using
+ // SSL or StartTLS. Acceptable values are:
+ //
+ // "none" - use plain LDAP connections (default)
+ // "ssl" - secure connection using LDAPS
+ // "startTLS" - secure connection using LDAP+StartTLS
+ //
+ "connectionSecurity" : "none",
+
+ // Specifies the policy for trusting server certificates exchanged
+ // during SSL/StartTLS negotiation. This setting and the following
+ // trust policy settings will be ignored if there is no connection
+ // security. Acceptable values are:
+ //
+ // "trustAll" - blindly trust all server certificates (default)
+ // "jvm" - only certificates signed by the authorities
+ // associated with the host JVM will be accepted
+ // "file" - use a file-based trust store for validating
+ // certificates. This option requires the following
+ // "fileBasedTrustManager*" settings to be configured.
+ //
+ "trustManager" : "trustAll",
+
+ // File based trust manager configuration (see above).
+ "fileBasedTrustManagerType" : "JKS",
+ "fileBasedTrustManagerFile" : "/path/to/truststore",
+ "fileBasedTrustManagerPassword" : "password",
+
+ // Re-usable pool of 24 connections per server.
+ "connectionPoolSize" : 24,
+
+ // Check pooled connections are alive every 30 seconds.
"heartBeatIntervalSeconds" : 30,
-
+
// The preferred load-balancing pool.
"primaryLDAPServers" : [
{
@@ -19,13 +50,13 @@
// Empty.
]
},
-
+
// Authenticated connections which will be used for searches during
// authentication and proxied operations (if enabled). This factory
// will re-use the server "default" configuration.
"root" : {
"inheritFrom" : "default",
-
+
// Defines how authentication should be performed. Only "simple"
// authentication is supported at the moment.
"authentication" : {
@@ -36,7 +67,7 @@
}
}
},
-
+
// The Rest2LDAP authentication filter configuration. The filter will be
// disabled if the configuration is not present. Upon successful
// authentication the filter will create a security context containing the
@@ -47,21 +78,21 @@
"authenticationFilter" : {
// Indicates whether the filter should allow HTTP BASIC authentication.
"supportHTTPBasicAuthentication" : true,
-
+
// Indicates whether the filter should allow alternative authentication
// and, if so, which HTTP headers it should obtain the username and
// password from.
- "supportAltAuthentication" : true,
+ "supportAltAuthentication" : true,
"altAuthenticationUsernameHeader" : "X-OpenIDM-Username",
"altAuthenticationPasswordHeader" : "X-OpenIDM-Password",
-
+
// Indicates whether the authenticated LDAP connection should be cached
// for use within the Rest2LDAP Servlet for subsequent LDAP operations.
// If this is set to true then the Servlet will not need its own LDAP
// connection factory and will also not need to use proxied
// authorization.
"reuseAuthenticatedConnection" : true,
-
+
// Specifies how LDAP authentications should be performed. The method
// must be one of:
//
@@ -74,29 +105,29 @@
// substituting the username into the
// "searchFilterTemplate" using %s substitution.
"method" : "search-simple",
-
+
// The connection factory which will be exclusively used for
// authenticating users using LDAP bind operations.
"bindLDAPConnectionFactory" : "default",
-
+
// The SASL AuthzID template which will be used for "sasl-plain"
// authentication. The %s format parameters will be substituted with
// the client-provided username, using DN character escaping for DN
// AuthzIDs.
"saslAuthzIdTemplate" : "dn:uid=%s,ou=people,dc=example,dc=com",
-
+
// The connection factory which will be used for performing LDAP
// searches to locate users when "search-simple" authentication is
// enabled.
"searchLDAPConnectionFactory" : "root",
-
+
// The search parameters to use for "search-simple" authentication. The
// %s filter format parameters will be substituted with the
// client-provided username, using LDAP filter string character escaping.
"searchBaseDN" : "ou=people,dc=example,dc=com",
"searchScope" : "sub", // Or "one".
"searchFilterTemplate" : "(&(objectClass=inetOrgPerson)(uid=%s))"
-
+
// TODO: support for HTTP sessions?
},
@@ -110,7 +141,7 @@
// be passed on from the filter, which may not always be the case
// if the filter is configured to use HTTP sessions.
"ldapConnectionFactory" : "root",
-
+
// Specifies how LDAP authorization should be performed. The method
// must be one of:
//
@@ -125,12 +156,12 @@
// authorization will only be used if there is no
// pre-authenticated connection available.
"authorizationPolicy" : "proxy",
-
+
// The AuthzID template which will be used for proxied authorization.
// The template should contain fields which are expected to be found in
// the security context create during authentication, e.g. "dn" and "id".
"proxyAuthzIdTemplate" : "dn:{dn}",
-
+
// The REST APIs and their LDAP attribute mappings.
"mappings" : {
"/users" : {
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
index 7c97bcd..b9cbbe1 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -21,6 +21,8 @@
import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS;
import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
@@ -52,13 +54,16 @@
import org.forgerock.opendj.ldap.FailoverLoadBalancingAlgorithm;
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LDAPOptions;
import org.forgerock.opendj.ldap.LinkedAttribute;
import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
import org.forgerock.opendj.ldap.RDN;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.RoundRobinLoadBalancingAlgorithm;
+import org.forgerock.opendj.ldap.SSLContextBuilder;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.TimeoutResultException;
+import org.forgerock.opendj.ldap.TrustManagers;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
@@ -70,6 +75,23 @@
* collections.
*/
public final class Rest2LDAP {
+
+ /**
+ * Indicates whether or not LDAP client connections should use SSL or
+ * StartTLS.
+ */
+ private enum ConnectionSecurity {
+ NONE, SSL, STARTTLS
+ }
+
+ /**
+ * Specifies the mechanism which should be used for trusting certificates
+ * presented by the LDAP server.
+ */
+ private enum TrustManagerType {
+ TRUSTALL, JVM, FILE
+ }
+
/**
* A builder for incrementally constructing LDAP resource collections.
*/
@@ -965,6 +987,48 @@
bindRequest = null;
}
+ // Parse SSL/StartTLS parameters.
+ final ConnectionSecurity connectionSecurity =
+ configuration.get("connectionSecurity").defaultTo(ConnectionSecurity.NONE).asEnum(
+ ConnectionSecurity.class);
+ final LDAPOptions options = new LDAPOptions();
+ if (connectionSecurity != ConnectionSecurity.NONE) {
+ try {
+ // Configure SSL.
+ final SSLContextBuilder builder = new SSLContextBuilder();
+
+ // Parse trust store configuration.
+ final TrustManagerType trustManagerType =
+ configuration.get("trustManager").defaultTo(TrustManagerType.TRUSTALL)
+ .asEnum(TrustManagerType.class);
+ switch (trustManagerType) {
+ case TRUSTALL:
+ builder.setTrustManager(TrustManagers.trustAll());
+ break;
+ case JVM:
+ // Do nothing: JVM trust manager is the default.
+ break;
+ case FILE:
+ final String fileName =
+ configuration.get("fileBasedTrustManagerFile").required().asString();
+ final String password =
+ configuration.get("fileBasedTrustManagerPassword").asString();
+ final String type = configuration.get("fileBasedTrustManagerType").asString();
+ builder.setTrustManager(TrustManagers.checkUsingTrustStore(fileName,
+ password != null ? password.toCharArray() : null, type));
+ break;
+ }
+ options.setSSLContext(builder.getSSLContext());
+ options.setUseStartTLS(connectionSecurity == ConnectionSecurity.STARTTLS);
+ } catch (GeneralSecurityException e) {
+ // Rethrow as unchecked exception.
+ throw new IllegalArgumentException(e);
+ } catch (IOException e) {
+ // Rethrow as unchecked exception.
+ throw new IllegalArgumentException(e);
+ }
+ }
+
// Parse primary data center.
final JsonValue primaryLDAPServers = configuration.get("primaryLDAPServers");
if (!primaryLDAPServers.isList() || primaryLDAPServers.size() == 0) {
@@ -972,7 +1036,7 @@
}
final ConnectionFactory primary =
parseLDAPServers(primaryLDAPServers, bindRequest, connectionPoolSize,
- heartBeatIntervalSeconds);
+ heartBeatIntervalSeconds, options);
// Parse secondary data center(s).
final JsonValue secondaryLDAPServers = configuration.get("secondaryLDAPServers");
@@ -981,7 +1045,7 @@
if (secondaryLDAPServers.size() > 0) {
secondary =
parseLDAPServers(secondaryLDAPServers, bindRequest, connectionPoolSize,
- heartBeatIntervalSeconds);
+ heartBeatIntervalSeconds, options);
} else {
secondary = null;
}
@@ -1029,12 +1093,12 @@
private static ConnectionFactory parseLDAPServers(final JsonValue config,
final BindRequest bindRequest, final int connectionPoolSize,
- final int heartBeatIntervalSeconds) {
+ final int heartBeatIntervalSeconds, final LDAPOptions options) {
final List<ConnectionFactory> servers = new ArrayList<ConnectionFactory>(config.size());
for (final JsonValue server : config) {
final String host = server.get("hostname").required().asString();
final int port = server.get("port").required().asInteger();
- ConnectionFactory factory = new LDAPConnectionFactory(host, port);
+ ConnectionFactory factory = new LDAPConnectionFactory(host, port, options);
if (bindRequest != null) {
factory = Connections.newAuthenticatedConnectionFactory(factory, bindRequest);
}
--
Gitblit v1.10.0