Better management of domains in TrustedHostClientRegistrationPolicy (#139) (#28876)

Closes keycloak/keycloak-private#63

Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
Ricardo Martin 2024-04-18 16:06:50 +02:00 committed by GitHub
parent 8daace3f69
commit 4c2542b91f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 161 additions and 27 deletions

View file

@ -23,7 +23,7 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@ -97,6 +97,10 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo
String hostAddress = session.getContext().getConnection().getRemoteAddr();
verifyHost(hostAddress);
}
protected void verifyHost(String hostAddress) throws ClientRegistrationPolicyException {
logger.debugf("Verifying remote host : %s", hostAddress);
List<String> trustedHosts = getTrustedHosts();
@ -130,19 +134,8 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo
protected List<String> getTrustedDomains() {
List<String> trustedHostsConfig = componentModel.getConfig().getList(TrustedHostClientRegistrationPolicyFactory.TRUSTED_HOSTS);
List<String> domains = new LinkedList<>();
for (String hostname : trustedHostsConfig) {
if (hostname.startsWith("*.")) {
hostname = hostname.substring(2);
domains.add(hostname);
return componentModel.getConfig().getList(TrustedHostClientRegistrationPolicyFactory.TRUSTED_HOSTS);
}
}
return domains;
}
protected String verifyHostInTrustedHosts(String hostAddress, List<String> trustedHosts) {
for (String confHostName : trustedHosts) {
@ -162,16 +155,33 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo
return null;
}
private boolean checkTrustedDomain(String hostname, String trustedDomain) {
if (trustedDomain.startsWith("*.")) {
String domain = trustedDomain.substring(2);
return hostname.equals(domain) || hostname.endsWith("." + domain);
}
return hostname.equals(trustedDomain);
}
protected String verifyHostInTrustedDomains(String hostAddress, List<String> trustedDomains) {
if (!trustedDomains.isEmpty()) {
try {
String hostname = InetAddress.getByName(hostAddress).getHostName();
InetAddress address = InetAddress.getByName(hostAddress);
String hostname = address.getHostName();
logger.debugf("Trying verify request from address '%s' of host '%s' by domains", hostAddress, hostname);
if (hostname.equals(address.getHostAddress())) {
logger.debugf("The hostAddress '%s' was not resolved to a hostname", hostAddress);
return null;
}
if (Arrays.stream(InetAddress.getAllByName(hostname)).filter(a -> address.equals(a)).findAny().isEmpty()) {
logger.debugf("The hostAddress '%s' is not among the direct lookups returned resolving '%s'", hostAddress, hostname);
return null;
}
for (String confDomain : trustedDomains) {
if (hostname.endsWith(confDomain)) {
if (checkTrustedDomain(hostname, confDomain)) {
logger.debugf("Successfully verified host '%s' by trusted domain '%s'", hostname, confDomain);
return hostname;
}
@ -179,7 +189,6 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo
} catch (UnknownHostException uhe) {
logger.debugf(uhe, "Request of address '%s' came from unknown host. Skip verification by domains", hostAddress);
}
}
return null;
}
@ -260,7 +269,7 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo
}
for (String trustedDomain : trustedDomains) {
if (host.endsWith(trustedDomain)) {
if (checkTrustedDomain(host, trustedDomain)) {
return true;
}
}

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.keycloak.services.clientregistration.policies.impl;
package org.keycloak.services.clientregistration.policy.impl;
import java.net.InetAddress;

View file

@ -0,0 +1,125 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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 org.keycloak.services.clientregistration.policy.impl;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.keycloak.common.Profile;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.crypto.CryptoProvider;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyException;
import org.keycloak.services.resteasy.ResteasyKeycloakSession;
import org.keycloak.services.resteasy.ResteasyKeycloakSessionFactory;
/**
*
* @author rmartinc
*/
public class TrustedHostClientRegistrationPolicyTest {
private static KeycloakSession session;
@BeforeClass
public static void beforeClass() {
Profile.defaults();
CryptoIntegration.init(CryptoProvider.class.getClassLoader());
ResteasyKeycloakSessionFactory sessionFactory = new ResteasyKeycloakSessionFactory();
sessionFactory.init();
session = new ResteasyKeycloakSession(sessionFactory);
}
@Test
public void testLocalhostName() {
TrustedHostClientRegistrationPolicyFactory factory = new TrustedHostClientRegistrationPolicyFactory();
ComponentModel model = createComponentModel("localhost");
TrustedHostClientRegistrationPolicy policy = (TrustedHostClientRegistrationPolicy) factory.create(session, model);
policy.verifyHost("127.0.0.1");
Assert.assertThrows(ClientRegistrationPolicyException.class, () -> policy.verifyHost("10.0.0.1"));
policy.checkURLTrusted("https://localhost", policy.getTrustedHosts(), policy.getTrustedDomains());
Assert.assertThrows(ClientRegistrationPolicyException.class, () -> policy.checkURLTrusted("https://otherhost",
policy.getTrustedHosts(), policy.getTrustedDomains()));
}
@Test
public void testLocalhostDomain() {
TrustedHostClientRegistrationPolicyFactory factory = new TrustedHostClientRegistrationPolicyFactory();
ComponentModel model = createComponentModel("*.localhost");
TrustedHostClientRegistrationPolicy policy = (TrustedHostClientRegistrationPolicy) factory.create(session, model);
policy.verifyHost("127.0.0.1");
Assert.assertThrows(ClientRegistrationPolicyException.class, () -> policy.verifyHost("10.0.0.1"));
policy.checkURLTrusted("https://localhost", policy.getTrustedHosts(), policy.getTrustedDomains());
policy.checkURLTrusted("https://other.localhost", policy.getTrustedHosts(), policy.getTrustedDomains());
Assert.assertThrows(ClientRegistrationPolicyException.class, () -> policy.checkURLTrusted("https://otherlocalhost",
policy.getTrustedHosts(), policy.getTrustedDomains()));
}
@Test
public void testLocalhostIP() {
TrustedHostClientRegistrationPolicyFactory factory = new TrustedHostClientRegistrationPolicyFactory();
ComponentModel model = createComponentModel("127.0.0.1");
TrustedHostClientRegistrationPolicy policy = (TrustedHostClientRegistrationPolicy) factory.create(session, model);
policy.verifyHost("127.0.0.1");
Assert.assertThrows(ClientRegistrationPolicyException.class, () -> policy.verifyHost("10.0.0.1"));
policy.checkURLTrusted("https://127.0.0.1", policy.getTrustedHosts(), policy.getTrustedDomains());
Assert.assertThrows(ClientRegistrationPolicyException.class, () -> policy.checkURLTrusted("https://localhost",
policy.getTrustedHosts(), policy.getTrustedDomains()));
}
@Test
public void testGoogleCrawlBot() {
// https://developers.google.com/search/blog/2006/09/how-to-verify-googlebot
TrustedHostClientRegistrationPolicyFactory factory = new TrustedHostClientRegistrationPolicyFactory();
ComponentModel model = createComponentModel("*.googlebot.com");
TrustedHostClientRegistrationPolicy policy = (TrustedHostClientRegistrationPolicy) factory.create(session, model);
policy.verifyHost("66.249.66.1");
policy.checkURLTrusted("https://www.googlebot.com", policy.getTrustedHosts(), policy.getTrustedDomains());
policy.checkURLTrusted("https://googlebot.com", policy.getTrustedHosts(), policy.getTrustedDomains());
Assert.assertThrows(ClientRegistrationPolicyException.class, () -> policy.checkURLTrusted("https://www.othergooglebot.com",
policy.getTrustedHosts(), policy.getTrustedDomains()));
}
@Test
public void testGithubDomain() throws UnknownHostException {
TrustedHostClientRegistrationPolicyFactory factory = new TrustedHostClientRegistrationPolicyFactory();
ComponentModel model = createComponentModel("*.github.com");
TrustedHostClientRegistrationPolicy policy = (TrustedHostClientRegistrationPolicy) factory.create(session, model);
policy.verifyHost(InetAddress.getByName("www.github.com").getHostAddress());
policy.verifyHost(InetAddress.getByName("github.com").getHostAddress());
policy.checkURLTrusted("https://www.github.com", policy.getTrustedHosts(), policy.getTrustedDomains());
policy.checkURLTrusted("https://github.com", policy.getTrustedHosts(), policy.getTrustedDomains());
Assert.assertThrows(ClientRegistrationPolicyException.class, () -> policy.checkURLTrusted("https://othergithub.com",
policy.getTrustedHosts(), policy.getTrustedDomains()));
}
private ComponentModel createComponentModel(String... hosts) {
ComponentModel model = new ComponentModel();
model.put(TrustedHostClientRegistrationPolicyFactory.HOST_SENDING_REGISTRATION_REQUEST_MUST_MATCH, "true");
model.put(TrustedHostClientRegistrationPolicyFactory.CLIENT_URIS_MUST_MATCH, "true");
model.getConfig().addAll(TrustedHostClientRegistrationPolicyFactory.TRUSTED_HOSTS, hosts);
return model;
}
}