KEYCLOAK-2505 Keystore configuration is not honored for LDAP over SSL connections

This commit is contained in:
mposolda 2016-02-19 18:02:39 +01:00
parent 706d4fc01c
commit daca6d7062
8 changed files with 75 additions and 19 deletions

View file

@ -58,9 +58,8 @@ public class LDAPConfig {
} }
} }
public String getSecurityProtocol() { public String getUseTruststoreSpi() {
// hardcoded for now return config.get(LDAPConstants.USE_TRUSTSTORE_SPI);
return config.get(LDAPConstants.SECURITY_PROTOCOL);
} }
public String getUsersDn() { public String getUsersDn() {

View file

@ -480,15 +480,6 @@ public class LDAPOperationManager {
env.put(Context.INITIAL_CONTEXT_FACTORY, this.config.getFactoryName()); env.put(Context.INITIAL_CONTEXT_FACTORY, this.config.getFactoryName());
env.put(Context.SECURITY_AUTHENTICATION, authType); env.put(Context.SECURITY_AUTHENTICATION, authType);
String protocol = this.config.getSecurityProtocol();
if (protocol != null) {
env.put(Context.SECURITY_PROTOCOL, protocol);
if ("ssl".equals(protocol)) {
env.put("java.naming.ldap.factory.socket", "org.keycloak.connections.truststore.SSLSocketFactory");
}
}
String bindDN = this.config.getBindDN(); String bindDN = this.config.getBindDN();
char[] bindCredential = null; char[] bindCredential = null;
@ -510,6 +501,9 @@ public class LDAPOperationManager {
logger.warn("LDAP URL is null. LDAPOperationManager won't work correctly"); logger.warn("LDAP URL is null. LDAPOperationManager won't work correctly");
} }
String useTruststoreSpi = this.config.getUseTruststoreSpi();
LDAPConstants.setTruststoreSpiIfNeeded(useTruststoreSpi, url, env);
String connectionPooling = this.config.getConnectionPooling(); String connectionPooling = this.config.getConnectionPooling();
if (connectionPooling != null) { if (connectionPooling != null) {
env.put("com.sun.jndi.ldap.connect.pool", connectionPooling); env.put("com.sun.jndi.ldap.connect.pool", connectionPooling);

View file

@ -17,6 +17,8 @@
package org.keycloak.models; package org.keycloak.models;
import java.util.Map;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
@ -38,7 +40,6 @@ public class LDAPConstants {
public static final String USER_OBJECT_CLASSES = "userObjectClasses"; public static final String USER_OBJECT_CLASSES = "userObjectClasses";
public static final String CONNECTION_URL = "connectionUrl"; public static final String CONNECTION_URL = "connectionUrl";
public static final String SECURITY_PROTOCOL = "securityProtocol";
public static final String BASE_DN = "baseDn"; // used for tests only public static final String BASE_DN = "baseDn"; // used for tests only
public static final String USERS_DN = "usersDn"; public static final String USERS_DN = "usersDn";
public static final String BIND_DN = "bindDn"; public static final String BIND_DN = "bindDn";
@ -48,6 +49,11 @@ public class LDAPConstants {
public static final String AUTH_TYPE_NONE = "none"; public static final String AUTH_TYPE_NONE = "none";
public static final String AUTH_TYPE_SIMPLE = "simple"; public static final String AUTH_TYPE_SIMPLE = "simple";
public static final String USE_TRUSTSTORE_SPI = "useTruststoreSpi";
public static final String USE_TRUSTSTORE_ALWAYS = "always";
public static final String USE_TRUSTSTORE_NEVER = "never";
public static final String USE_TRUSTSTORE_LDAPS_ONLY = "ldapsOnly";
public static final String SEARCH_SCOPE = "searchScope"; public static final String SEARCH_SCOPE = "searchScope";
public static final String CONNECTION_POOLING = "connectionPooling"; public static final String CONNECTION_POOLING = "connectionPooling";
public static final String PAGINATION = "pagination"; public static final String PAGINATION = "pagination";
@ -119,4 +125,21 @@ public class LDAPConstants {
return ENTRY_UUID; return ENTRY_UUID;
} }
public static void setTruststoreSpiIfNeeded(String useTruststoreSpi, String url, Map<String, Object> env) {
boolean shouldSetTruststore;
if (useTruststoreSpi != null && useTruststoreSpi.equals(LDAPConstants.USE_TRUSTSTORE_ALWAYS)) {
shouldSetTruststore = true;
} else if (useTruststoreSpi != null && useTruststoreSpi.equals(LDAPConstants.USE_TRUSTSTORE_NEVER)) {
shouldSetTruststore = false;
} else {
shouldSetTruststore = (url != null && url.startsWith("ldaps"));
}
if (shouldSetTruststore) {
env.put("java.naming.ldap.factory.socket", "org.keycloak.truststore.SSLSocketFactory");
}
}
} }

View file

@ -16,13 +16,14 @@
*/ */
package org.keycloak.services.managers; package org.keycloak.services.managers;
import org.keycloak.services.ServicesLogger;
import javax.naming.Context; import javax.naming.Context;
import javax.naming.NamingException; import javax.naming.NamingException;
import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.InitialLdapContext;
import java.util.Hashtable; import java.util.Hashtable;
import org.keycloak.models.LDAPConstants;
import org.keycloak.services.ServicesLogger;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
@ -33,7 +34,7 @@ public class LDAPConnectionTestManager {
public static final String TEST_CONNECTION = "testConnection"; public static final String TEST_CONNECTION = "testConnection";
public static final String TEST_AUTHENTICATION = "testAuthentication"; public static final String TEST_AUTHENTICATION = "testAuthentication";
public boolean testLDAP(String action, String connectionUrl, String bindDn, String bindCredential) { public boolean testLDAP(String action, String connectionUrl, String bindDn, String bindCredential, String useTruststoreSpi) {
if (!TEST_CONNECTION.equals(action) && !TEST_AUTHENTICATION.equals(action)) { if (!TEST_CONNECTION.equals(action) && !TEST_AUTHENTICATION.equals(action)) {
logger.unknownAction(action); logger.unknownAction(action);
return false; return false;
@ -43,10 +44,20 @@ public class LDAPConnectionTestManager {
try { try {
Hashtable<String, Object> env = new Hashtable<String, Object>(); Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
if (connectionUrl == null) {
logger.errorf("Unknown connection URL");
return false;
}
env.put(Context.PROVIDER_URL, connectionUrl); env.put(Context.PROVIDER_URL, connectionUrl);
if (TEST_AUTHENTICATION.equals(action)) { if (TEST_AUTHENTICATION.equals(action)) {
env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_AUTHENTICATION, "simple");
if (bindDn == null) {
logger.error("Unknown bind DN");
return false;
}
env.put(Context.SECURITY_PRINCIPAL, bindDn); env.put(Context.SECURITY_PRINCIPAL, bindDn);
char[] bindCredentialChar = null; char[] bindCredentialChar = null;
@ -56,6 +67,8 @@ public class LDAPConnectionTestManager {
env.put(Context.SECURITY_CREDENTIALS, bindCredentialChar); env.put(Context.SECURITY_CREDENTIALS, bindCredentialChar);
} }
LDAPConstants.setTruststoreSpiIfNeeded(useTruststoreSpi, connectionUrl, env);
ldapContext = new InitialLdapContext(env, null); ldapContext = new InitialLdapContext(env, null);
return true; return true;
} catch (Exception ne) { } catch (Exception ne) {

View file

@ -652,10 +652,11 @@ public class RealmAdminResource {
@GET @GET
@NoCache @NoCache
public Response testLDAPConnection(@QueryParam("action") String action, @QueryParam("connectionUrl") String connectionUrl, public Response testLDAPConnection(@QueryParam("action") String action, @QueryParam("connectionUrl") String connectionUrl,
@QueryParam("bindDn") String bindDn, @QueryParam("bindCredential") String bindCredential) { @QueryParam("bindDn") String bindDn, @QueryParam("bindCredential") String bindCredential,
@QueryParam("useTruststoreSpi") String useTruststoreSpi) {
auth.init(RealmAuth.Resource.REALM).requireManage(); auth.init(RealmAuth.Resource.REALM).requireManage();
boolean result = new LDAPConnectionTestManager().testLDAP(action, connectionUrl, bindDn, bindCredential); boolean result = new LDAPConnectionTestManager().testLDAP(action, connectionUrl, bindDn, bindCredential, useTruststoreSpi);
return result ? Response.noContent().build() : ErrorResponse.error("LDAP test error", Response.Status.BAD_REQUEST); return result ? Response.noContent().build() : ErrorResponse.error("LDAP test error", Response.Status.BAD_REQUEST);
} }

View file

@ -653,6 +653,8 @@ custom-user-ldap-filter=Custom User LDAP Filter
ldap.custom-user-ldap-filter.tooltip=Additional LDAP Filter for filtering searched users. Leave this empty if you don't need additional filter. Make sure that it starts with '(' and ends with ')' ldap.custom-user-ldap-filter.tooltip=Additional LDAP Filter for filtering searched users. Leave this empty if you don't need additional filter. Make sure that it starts with '(' and ends with ')'
search-scope=Search Scope search-scope=Search Scope
ldap.search-scope.tooltip=For one level, we search for users just in DNs specified by User DNs. For subtree, we search in whole of their subtree. See LDAP documentation for more details ldap.search-scope.tooltip=For one level, we search for users just in DNs specified by User DNs. For subtree, we search in whole of their subtree. See LDAP documentation for more details
use-truststore-spi=Use Truststore SPI
ldap.use-truststore-spi.tooltip=Specifies whether LDAP connection will use the truststore SPI with the truststore configured in keycloak-server.json. 'Always' means that it will always use it. 'Never' means that it won't use it. 'Only for ldaps' means that it will use if your connection URL use ldaps. Note even if keycloak-server.json is not configured, the default Java cacerts or certificate specified by 'javax.net.ssl.trustStore' property will be used.
connection-pooling=Connection Pooling connection-pooling=Connection Pooling
ldap.connection-pooling.tooltip=Does Keycloak should use connection pooling for accessing LDAP server ldap.connection-pooling.tooltip=Does Keycloak should use connection pooling for accessing LDAP server
ldap.pagination.tooltip=Does the LDAP server support pagination. ldap.pagination.tooltip=Does the LDAP server support pagination.

View file

@ -771,6 +771,12 @@ module.controller('LDAPCtrl', function($scope, $location, $route, Notifications,
{ "id": "2", "name": "Subtree" } { "id": "2", "name": "Subtree" }
]; ];
$scope.useTruststoreOptions = [
{ "id": "always", "name": "Always" },
{ "id": "ldapsOnly", "name": "Only for ldaps" },
{ "id": "never", "name": "Never" }
];
var DEFAULT_BATCH_SIZE = "1000"; var DEFAULT_BATCH_SIZE = "1000";
$scope.create = !instance.providerName; $scope.create = !instance.providerName;
@ -793,6 +799,7 @@ module.controller('LDAPCtrl', function($scope, $location, $route, Notifications,
instance.config.authType = 'simple'; instance.config.authType = 'simple';
instance.config.batchSizeForSync = DEFAULT_BATCH_SIZE; instance.config.batchSizeForSync = DEFAULT_BATCH_SIZE;
instance.config.searchScope = "1"; instance.config.searchScope = "1";
instance.config.useTruststoreSpi = "ldapsOnly";
$scope.fullSyncEnabled = false; $scope.fullSyncEnabled = false;
$scope.changedSyncEnabled = false; $scope.changedSyncEnabled = false;
@ -815,6 +822,9 @@ module.controller('LDAPCtrl', function($scope, $location, $route, Notifications,
if (!instance.config.searchScope) { if (!instance.config.searchScope) {
instance.config.searchScope = '1'; instance.config.searchScope = '1';
} }
if (!instance.config.useTruststoreSpi) {
instance.config.useTruststoreSpi = "ldapsOnly";
}
$scope.fullSyncEnabled = (instance.fullSyncPeriod && instance.fullSyncPeriod > 0); $scope.fullSyncEnabled = (instance.fullSyncPeriod && instance.fullSyncPeriod > 0);
$scope.changedSyncEnabled = (instance.changedSyncPeriod && instance.changedSyncPeriod > 0); $scope.changedSyncEnabled = (instance.changedSyncPeriod && instance.changedSyncPeriod > 0);
@ -939,7 +949,8 @@ module.controller('LDAPCtrl', function($scope, $location, $route, Notifications,
realm: $scope.realm.realm, realm: $scope.realm.realm,
connectionUrl: ldapConfig.connectionUrl, connectionUrl: ldapConfig.connectionUrl,
bindDn: ldapConfig.bindDn, bindDn: ldapConfig.bindDn,
bindCredential: ldapConfig.bindCredential bindCredential: ldapConfig.bindCredential,
useTruststoreSpi: ldapConfig.useTruststoreSpi
}; };
}; };

View file

@ -163,6 +163,19 @@
</div> </div>
<kc-tooltip>{{:: 'ldap.search-scope.tooltip' | translate}}</kc-tooltip> <kc-tooltip>{{:: 'ldap.search-scope.tooltip' | translate}}</kc-tooltip>
</div> </div>
<div class="form-group">
<label class="col-md-2 control-label" for="useTruststoreSpi">{{:: 'use-truststore-spi' | translate}}</label>
<div class="col-md-6">
<div>
<select class="form-control" id="useTruststoreSpi"
ng-model="instance.config.useTruststoreSpi"
ng-options="useTruststoreSpi.id as useTruststoreSpi.name for useTruststoreSpi in useTruststoreOptions"
required>
</select>
</div>
</div>
<kc-tooltip>{{:: 'ldap.use-truststore-spi.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix"> <div class="form-group clearfix">
<label class="col-md-2 control-label" for="connectionPooling">{{:: 'connection-pooling' | translate}}</label> <label class="col-md-2 control-label" for="connectionPooling">{{:: 'connection-pooling' | translate}}</label>
<div class="col-md-6"> <div class="col-md-6">