fix: adding checks around the hostname path (#43193)

closes: #43166

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins 2025-10-14 11:41:25 -04:00 committed by GitHub
parent bda0e2a67c
commit aa04ff8781
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 58 additions and 5 deletions

View File

@ -11,6 +11,7 @@ import java.util.stream.Stream;
import org.keycloak.common.Profile;
import org.keycloak.config.HostnameV2Options;
import org.keycloak.config.HttpOptions;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.cli.Picocli;
import org.keycloak.quarkus.runtime.cli.command.AbstractCommand;
@ -103,10 +104,30 @@ public final class HostnameV2PropertyMappers implements PropertyMapperGrouping {
// else might be allowable if HOST is overwritten
}
if (proxyHeaders == null && !url.getPath().isEmpty()
&& Boolean.valueOf(Configuration.getConfigValue(HostnameV2Options.HOSTNAME_BACKCHANNEL_DYNAMIC).getValue())
&& !normalizePath(url.getPath()).equals(
normalizePath(Configuration.getConfigValue(HttpOptions.HTTP_RELATIVE_PATH).getValue()))) {
warn.accept("Likely misconfiguration detected. When using a `hostname` that includes a path that does not match the `http-relative-path` you must use `proxy-headers` to properly detect backchannel requests.");
}
return true;
} catch (MalformedURLException e) {
return false;
}
}
private static String normalizePath(String path) {
if (path == null) {
return path;
}
if (!path.startsWith("/")) {
path = "/" + path;
}
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
return path;
}
}

View File

@ -103,6 +103,16 @@ public class ProxyHostnameV2DistTest {
cliResult.assertMessage(ADDRESS);
}
@Test
@Launch({ "start-dev", "--hostname=https://mykeycloak.org:8443/path", "--proxy-headers=forwarded", "--hostname-backchannel-dynamic=true" })
public void testForwardedProxyHeadersWithPathAndDynamicBackchannel(LaunchResult result) {
assertFrontEndUrl("https://mykeycloak.org:8443", "https://mykeycloak.org:8443/path/");
// a backend url generated via the frontend protocol/host/port should be a front-end url
assertBackEndUrl("https://mykeycloak.org:8443", "https://mykeycloak.org:8443/path/");
// any other protocol/host/port will be the backend
assertBackEndUrl("http://localhost:8080", "http://localhost:8080/");
}
@Test
@Launch({ "start-dev", "--hostname-strict=false", "--proxy-headers=xforwarded" })
public void testXForwardedProxyHeaders() {
@ -151,4 +161,10 @@ public class ProxyHostnameV2DistTest {
Assert.assertEquals(expectedBaseUrl + "realms/master/protocol/openid-connect/auth", getServerMetadata(requestBaseUrl)
.getAuthorizationEndpoint());
}
private void assertBackEndUrl(String requestBaseUrl, String expectedBaseUrl) {
Assert.assertEquals(expectedBaseUrl + "realms/master/protocol/openid-connect/token", getServerMetadata(requestBaseUrl)
.getTokenEndpoint());
}
}

View File

@ -67,7 +67,7 @@ public class HostnameV2Provider implements HostnameProvider {
builder.host("localhost");
break;
case BACKEND:
builder = backchannelDynamic ? originalUriInfo.getBaseUriBuilder() : getFrontUriBuilder(originalUriInfo);
builder = backchannelDynamic && !isFrontendRequest(originalUriInfo) ? originalUriInfo.getBaseUriBuilder() : getFrontUriBuilder(originalUriInfo);
break;
case FRONTEND:
builder = getFrontUriBuilder(originalUriInfo);
@ -76,13 +76,29 @@ public class HostnameV2Provider implements HostnameProvider {
throw new IllegalArgumentException("Unknown URL type");
}
URI uri = builder.build();
// sanitize ports
URI uriPeak = builder.build();
if ((uriPeak.getScheme().equals("http") && uriPeak.getPort() == 80) || (uriPeak.getScheme().equals("https") && uriPeak.getPort() == 443)) {
builder.port(-1);
int normalizedPort = normalizedPort(uri);
if (normalizedPort != uri.getPort()) {
builder.port(normalizedPort);
uri = builder.build();
}
return builder.build();
return uri;
}
private int normalizedPort(URI uri) {
if ((uri.getScheme().equals("http") && uri.getPort() == 80) || (uri.getScheme().equals("https") && uri.getPort() == 443)) {
return -1;
}
return uri.getPort();
}
private boolean isFrontendRequest(UriInfo originalUriInfo) {
URI frontend = getFrontUriBuilder(originalUriInfo).build();
return frontend.getScheme().equals(originalUriInfo.getBaseUri().getScheme()) &&
frontend.getHost().equals(originalUriInfo.getBaseUri().getHost()) &&
frontend.getPort() == normalizedPort(originalUriInfo.getBaseUri());
}
private UriBuilder getFrontUriBuilder(UriInfo originalUriInfo) {