Skip to content

Commit

Permalink
Better management of domains in TrustedHostClientRegistrationPolicy (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
rmartinc authored Apr 18, 2024
1 parent 8daace3 commit 4c2542b
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
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;
Expand Down Expand Up @@ -97,6 +97,10 @@ protected void verifyHost() throws ClientRegistrationPolicyException {

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();
Expand Down Expand Up @@ -130,20 +134,9 @@ protected List<String> getTrustedHosts() {


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 domains;
return componentModel.getConfig().getList(TrustedHostClientRegistrationPolicyFactory.TRUSTED_HOSTS);
}


protected String verifyHostInTrustedHosts(String hostAddress, List<String> trustedHosts) {
for (String confHostName : trustedHosts) {
try {
Expand All @@ -162,23 +155,39 @@ protected String verifyHostInTrustedHosts(String hostAddress, List<String> trust
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();
try {
InetAddress address = InetAddress.getByName(hostAddress);
String hostname = address.getHostName();

logger.debugf("Trying verify request from address '%s' of host '%s' by domains", hostAddress, hostname);

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)) {
logger.debugf("Successfully verified host '%s' by trusted domain '%s'", hostname, confDomain);
return hostname;
}
for (String confDomain : trustedDomains) {
if (checkTrustedDomain(hostname, confDomain)) {
logger.debugf("Successfully verified host '%s' by trusted domain '%s'", hostname, confDomain);
return hostname;
}
} catch (UnknownHostException uhe) {
logger.debugf(uhe, "Request of address '%s' came from unknown host. Skip verification by domains", hostAddress);
}
} catch (UnknownHostException uhe) {
logger.debugf(uhe, "Request of address '%s' came from unknown host. Skip verification by domains", hostAddress);
}

return null;
Expand Down Expand Up @@ -260,7 +269,7 @@ private boolean checkHostTrusted(String host, List<String> trustedHosts, List<St
}

for (String trustedDomain : trustedDomains) {
if (host.endsWith(trustedDomain)) {
if (checkTrustedDomain(host, trustedDomain)) {
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit 4c2542b

Please sign in to comment.