mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 15:02:05 -03:30
Only allow LDAP URL references when following referrals (#44993)
Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com> Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com> Signed-off-by: Stian Thorgersen <stian@redhat.com> Co-authored-by: Alexander Schwartz <alexander.schwartz@ibm.com> Co-authored-by: Stian Thorgersen <stianst@gmail.com>
This commit is contained in:
parent
94dc60822b
commit
6a437521a9
@ -16,6 +16,9 @@ include::topics/templates/release-header.adoc[]
|
|||||||
== {project_name_full} 26.5.0
|
== {project_name_full} 26.5.0
|
||||||
include::topics/26_5_0.adoc[leveloffset=2]
|
include::topics/26_5_0.adoc[leveloffset=2]
|
||||||
|
|
||||||
|
== {project_name_full} 26.4.6
|
||||||
|
include::topics/26_4_6.adoc[leveloffset=2]
|
||||||
|
|
||||||
== {project_name_full} 26.4.0
|
== {project_name_full} 26.4.0
|
||||||
include::topics/26_4_0.adoc[leveloffset=2]
|
include::topics/26_4_0.adoc[leveloffset=2]
|
||||||
|
|
||||||
|
|||||||
9
docs/documentation/release_notes/topics/26_4_6.adoc
Normal file
9
docs/documentation/release_notes/topics/26_4_6.adoc
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Release notes should contain only headline-worthy new features,
|
||||||
|
// assuming that people who migrate will read the upgrading guide anyway.
|
||||||
|
|
||||||
|
This release adds filtering of LDAP referrals by default.
|
||||||
|
This change enhances security and aligns with best practices for LDAP configurations.
|
||||||
|
|
||||||
|
If you can not upgrade to this release yet, we recommend disabling LDAP referrals in all LDAP providers in all of your realms.
|
||||||
|
|
||||||
|
For detailed upgrade instructions, https://www.keycloak.org/docs/latest/upgrading/index.html[review the upgrading guide].
|
||||||
@ -23,6 +23,8 @@ import java.util.Map;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import javax.naming.spi.NamingManager;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.common.constants.KerberosConstants;
|
import org.keycloak.common.constants.KerberosConstants;
|
||||||
@ -85,6 +87,8 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LD
|
|||||||
private static final Logger logger = Logger.getLogger(LDAPStorageProviderFactory.class);
|
private static final Logger logger = Logger.getLogger(LDAPStorageProviderFactory.class);
|
||||||
public static final String PROVIDER_NAME = LDAPConstants.LDAP_PROVIDER;
|
public static final String PROVIDER_NAME = LDAPConstants.LDAP_PROVIDER;
|
||||||
private static final String LDAP_CONNECTION_POOL_PROTOCOL = "com.sun.jndi.ldap.connect.pool.protocol";
|
private static final String LDAP_CONNECTION_POOL_PROTOCOL = "com.sun.jndi.ldap.connect.pool.protocol";
|
||||||
|
private static final String SECURE_REFERRAL = "secureReferral";
|
||||||
|
private static final boolean SECURE_REFERRAL_DEFAULT = true;
|
||||||
|
|
||||||
private LDAPIdentityStoreRegistry ldapStoreRegistry;
|
private LDAPIdentityStoreRegistry ldapStoreRegistry;
|
||||||
|
|
||||||
@ -302,13 +306,36 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LD
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(Config.Scope config) {
|
public void init(Config.Scope config) {
|
||||||
|
if (config.getBoolean(SECURE_REFERRAL, SECURE_REFERRAL_DEFAULT)) {
|
||||||
|
setObjectFactoryBuilder();
|
||||||
|
} else {
|
||||||
|
logger.warnf("Insecure LDAP referrals are enabled. The option 'secure-referral' is deprecated and it will be removed in future releases.");
|
||||||
|
}
|
||||||
|
|
||||||
// set connection pooling for plain and tls protocols by default
|
// set connection pooling for plain and tls protocols by default
|
||||||
if (System.getProperty(LDAP_CONNECTION_POOL_PROTOCOL) == null) {
|
if (System.getProperty(LDAP_CONNECTION_POOL_PROTOCOL) == null) {
|
||||||
System.setProperty(LDAP_CONNECTION_POOL_PROTOCOL, "plain ssl");
|
System.setProperty(LDAP_CONNECTION_POOL_PROTOCOL, "plain ssl");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ldapStoreRegistry = new LDAPIdentityStoreRegistry();
|
this.ldapStoreRegistry = new LDAPIdentityStoreRegistry();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigMetadata() {
|
||||||
|
|
||||||
|
ProviderConfigurationBuilder builder = ProviderConfigurationBuilder.create();
|
||||||
|
|
||||||
|
builder.property()
|
||||||
|
.name(SECURE_REFERRAL)
|
||||||
|
.type("boolean")
|
||||||
|
.helpText("Allow only secure LDAP referrals (deprecated)")
|
||||||
|
.defaultValue(SECURE_REFERRAL_DEFAULT)
|
||||||
|
.add();
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
this.ldapStoreRegistry = null;
|
this.ldapStoreRegistry = null;
|
||||||
@ -728,4 +755,15 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LD
|
|||||||
return new KerberosUsernamePasswordAuthenticator(kerberosConfig);
|
return new KerberosUsernamePasswordAuthenticator(kerberosConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setObjectFactoryBuilder() {
|
||||||
|
try {
|
||||||
|
NamingManager.setObjectFactoryBuilder(new ObjectFactoryBuilder());
|
||||||
|
} catch (NamingException | IllegalStateException e) {
|
||||||
|
if (e instanceof IllegalStateException && ObjectFactoryBuilder.isSet()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("Failed to set the server JNDI ObjectFactoryBuilder", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,125 @@
|
|||||||
|
package org.keycloak.storage.ldap;
|
||||||
|
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.naming.CommunicationException;
|
||||||
|
import javax.naming.Context;
|
||||||
|
import javax.naming.Name;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import javax.naming.RefAddr;
|
||||||
|
import javax.naming.Reference;
|
||||||
|
import javax.naming.ldap.LdapContext;
|
||||||
|
import javax.naming.spi.NamingManager;
|
||||||
|
import javax.naming.spi.ObjectFactory;
|
||||||
|
|
||||||
|
import org.keycloak.storage.ldap.idm.store.ldap.SessionBoundInitialLdapContext;
|
||||||
|
import org.keycloak.utils.KeycloakSessionUtil;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>A {@link javax.naming.spi.ObjectFactoryBuilder} implementation to filter out referral references if they do not
|
||||||
|
* point to an LDAP URL.
|
||||||
|
*
|
||||||
|
* <p>When the LDAP provider encounters a referral, it tries to create an {@link ObjectFactory} from this builder.
|
||||||
|
* If the referral reference contains an LDAP URL, a {@link DirContextObjectFactory} is created to handle the referral.
|
||||||
|
* Otherwise, a {@link CommunicationException} is thrown to indicate that the referral cannot be processed.
|
||||||
|
*/
|
||||||
|
final class ObjectFactoryBuilder implements javax.naming.spi.ObjectFactoryBuilder, ObjectFactory {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(ObjectFactoryBuilder.class);
|
||||||
|
private static final String IS_KC_OBJECT_FACTORY_BUILDER = "kc.jndi.object.factory.builder";
|
||||||
|
|
||||||
|
static boolean isSet() {
|
||||||
|
Hashtable<Object, Object> env = new Hashtable<>();
|
||||||
|
|
||||||
|
env.put(ObjectFactoryBuilder.IS_KC_OBJECT_FACTORY_BUILDER, Boolean.TRUE);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object instance = NamingManager.getObjectInstance(null, null, null, env);
|
||||||
|
|
||||||
|
if (instance != null && instance.getClass().getName().equals(ObjectFactoryBuilder.class.getName())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to determine if ObjectFactoryBuilder is set", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObjectFactory createObjectFactory(Object obj, Hashtable<?, ?> environment) throws NamingException {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Creating ObjectFactory for object: %s", obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj instanceof Reference ref) {
|
||||||
|
String factoryClassName = ref.getFactoryClassName();
|
||||||
|
|
||||||
|
if (factoryClassName != null) {
|
||||||
|
logger.warnf("Referral refence contains an object factory %s but it will be ignored", factoryClassName);
|
||||||
|
}
|
||||||
|
|
||||||
|
String ldapUrl = getLdapUrl(ref);
|
||||||
|
|
||||||
|
if (ldapUrl != null) {
|
||||||
|
return new DirContextObjectFactory(ldapUrl);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debugf("Unsupported reference object of type %s: ", obj);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new CommunicationException("Referral reference does not contain an LDAP URL: " + obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> env) {
|
||||||
|
if (env != null && env.containsKey(IS_KC_OBJECT_FACTORY_BUILDER)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getLdapUrl(Reference ref) {
|
||||||
|
for (int i = 0; i < ref.size(); i++) {
|
||||||
|
RefAddr addr = ref.get(i);
|
||||||
|
String addrType = addr.getType();
|
||||||
|
|
||||||
|
if ("URL".equalsIgnoreCase(addrType)) {
|
||||||
|
Object content = addr.getContent();
|
||||||
|
|
||||||
|
if (content == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String rawUrl = content.toString();
|
||||||
|
|
||||||
|
for (String url : List.of(rawUrl.split(" "))) {
|
||||||
|
if (!url.toLowerCase().startsWith("ldap")) {
|
||||||
|
logger.warnf("Unsupported scheme from reference URL %s. Ignoring reference.", url);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawUrl;
|
||||||
|
} else {
|
||||||
|
logger.warnf("Ignoring address of type '%s' from referral reference", addrType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record DirContextObjectFactory(String ldapUrl) implements ObjectFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> env) throws Exception {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Hashtable<Object, Object> newEnv = (Hashtable<Object, Object>) env.clone();
|
||||||
|
newEnv.put(LdapContext.PROVIDER_URL, ldapUrl);
|
||||||
|
return new SessionBoundInitialLdapContext(KeycloakSessionUtil.getKeycloakSession(), newEnv, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user