Allow overriding the host header using X-Forwarded-Host (#11021)

Closes #10997
This commit is contained in:
Pedro Igor 2022-04-05 04:17:08 -03:00 committed by GitHub
parent 2b5d68d645
commit 23c5199c0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 99 additions and 2 deletions

View file

@ -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.

View file

@ -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??

View file

@ -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);
}

View file

@ -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("<script src=\"https://mykeycloak.127.0.0.1.nip.io:8443/js/keycloak.js"));
given().header("X-Forwarded-Proto", "https").when().get("http://localhost:8080").then().body(containsString("https://localhost/admin"));
given().header("X-Forwarded-Proto", "https").header("X-Forwarded-Port", "8443").when().get("http://localhost:8080").then().body(containsString("https://localhost:8443/admin"));
}
private OIDCConfigurationRepresentation getServerMetadata(String baseUrl) {
return when().get(baseUrl + "/realms/master/.well-known/openid-configuration").as(OIDCConfigurationRepresentation.class);
}
private void assertFrontEndUrl(String requestBaseUrl, String expectedBaseUrl) {
Assert.assertEquals(expectedBaseUrl + "realms/master/protocol/openid-connect/auth", getServerMetadata(requestBaseUrl)
.getAuthorizationEndpoint());
}
}