From 23c5199c0c1dcb1808228cfe702194a4a46227d6 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Tue, 5 Apr 2022 04:17:08 -0300 Subject: [PATCH] Allow overriding the host header using X-Forwarded-Host (#11021) Closes #10997 --- docs/guides/src/main/server/reverseproxy.adoc | 3 +- .../org/keycloak/guides/maven/Options.java | 4 + .../mappers/ProxyPropertyMappers.java | 10 +++ .../keycloak/it/cli/dist/ProxyDistTest.java | 84 +++++++++++++++++++ 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ProxyDistTest.java diff --git a/docs/guides/src/main/server/reverseproxy.adoc b/docs/guides/src/main/server/reverseproxy.adoc index ea0cf15c1c..a07def4397 100644 --- a/docs/guides/src/main/server/reverseproxy.adoc +++ b/docs/guides/src/main/server/reverseproxy.adoc @@ -34,8 +34,7 @@ Some Keycloak features rely on the assumption that the remote address of the HTT When you have a reverse proxy or loadbalancer in front of Keycloak, this might not be the case, so please make sure your reverse proxy is configured correctly by performing these actions: -* Set the X-Forwarded-For and X-Forwarded-Proto HTTP headers. -* Preserve the original 'Host' HTTP header. +* Set the X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host HTTP headers. To set these headers, consult the documentation for your reverse proxy. diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Options.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Options.java index 3bde472e81..1e63ddadcd 100644 --- a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Options.java +++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Options.java @@ -1,6 +1,7 @@ package org.keycloak.guides.maven; import org.keycloak.quarkus.runtime.configuration.mappers.ConfigCategory; +import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; import java.util.Collection; @@ -8,6 +9,8 @@ import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -18,6 +21,7 @@ public class Options { public Options() { options = PropertyMappers.getMappers().stream() .filter(m -> !m.isHidden()) + .filter(propertyMapper -> Objects.nonNull(propertyMapper.getDescription())) .map(m -> new Option(m.getFrom(), m.getCategory(), m.isBuildTime(), m.getDescription(), m.getDefaultValue(), m.getExpectedValues())) .sorted(Comparator.comparing(Option::getKey)) .collect(Collectors.toMap(Option::getKey, o -> o, (o1, o2) -> o1, LinkedHashMap::new)); // Need to ignore duplicate keys?? diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ProxyPropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ProxyPropertyMappers.java index 149fc29220..387029d4a6 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ProxyPropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ProxyPropertyMappers.java @@ -26,6 +26,12 @@ final class ProxyPropertyMappers { "Possible values are: " + String.join(",",possibleProxyValues)) .paramLabel("mode") .category(ConfigCategory.PROXY) + .build(), + builder().to("quarkus.http.proxy.enable-forwarded-host") + .mapFrom("proxy") + .defaultValue("false") + .transformer(ProxyPropertyMappers::resolveEnableForwardedHost) + .category(ConfigCategory.PROXY) .build() }; } @@ -46,6 +52,10 @@ final class ProxyPropertyMappers { }; } + private static String resolveEnableForwardedHost(String proxy, ConfigSourceInterceptorContext context) { + return String.valueOf(!"none".equals(proxy)); + } + private static PropertyMapper.Builder builder() { return PropertyMapper.builder(ConfigCategory.PROXY); } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ProxyDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ProxyDistTest.java new file mode 100644 index 0000000000..2815d1e2fb --- /dev/null +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ProxyDistTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2021 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.it.cli.dist; + +import static io.restassured.RestAssured.given; +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.containsString; + +import org.junit.Assert; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.keycloak.it.cli.dist.util.CopyTLSKeystore; +import org.keycloak.it.junit5.extension.BeforeStartDistribution; +import org.keycloak.it.junit5.extension.DistributionTest; +import org.keycloak.it.junit5.extension.RawDistOnly; +import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; + +import io.quarkus.test.junit.main.Launch; +import io.restassured.RestAssured; + +@DistributionTest(keepAlive = true, reInstall = DistributionTest.ReInstall.BEFORE_TEST) +@BeforeStartDistribution(CopyTLSKeystore.class) +@RawDistOnly(reason = "Containers are immutable") +public class ProxyDistTest { + + @BeforeAll + public static void onBeforeAll() { + RestAssured.useRelaxedHTTPSValidation(); + } + + @Test + @Launch({ "start-dev", "--hostname=mykeycloak.127.0.0.1.nip.io" }) + public void testSchemeAndPortFromRequestWhenNoProxySet() { + assertFrontEndUrl("http://mykeycloak.127.0.0.1.nip.io:8080", "http://mykeycloak.127.0.0.1.nip.io:8080/"); + assertFrontEndUrl("http://localhost:8080", "http://mykeycloak.127.0.0.1.nip.io:8080/"); + assertFrontEndUrl("https://localhost:8443", "https://mykeycloak.127.0.0.1.nip.io:8443/"); + given().header("X-Forwarded-Host", "test").when().get("http://localhost:8080").then().body(containsString("http://localhost:8080/admin")); + } + + @Test + @Launch({ "start-dev", "--hostname=mykeycloak.127.0.0.1.nip.io", "--proxy=edge" }) + public void testXForwardedHeadersWithEdge() { + assertXForwardedHeaders(); + } + + @Test + @Launch({ "start-dev", "--hostname=mykeycloak.127.0.0.1.nip.io", "--proxy=reencrypt" }) + public void testXForwardedHeadersWithReencrypt() { + assertXForwardedHeaders(); + } + + private void assertXForwardedHeaders() { + given().header("X-Forwarded-Host", "test").when().get("http://mykeycloak.127.0.0.1.nip.io:8080").then().body(containsString("http://test:8080/admin")); + given().header("X-Forwarded-Host", "test").when().get("http://localhost:8080").then().body(containsString("http://test:8080/admin")); + given().header("X-Forwarded-Host", "test").when().get("https://localhost:8443").then().body(containsString("https://test:8443/admin")); + given().header("X-Forwarded-Host", "mykeycloak.127.0.0.1.nip.io").when().get("https://localhost:8443/admin/master/console").then().body(containsString("