Allow overriding the host header using X-Forwarded-Host (#11021)
Closes #10997
This commit is contained in:
parent
2b5d68d645
commit
23c5199c0c
4 changed files with 99 additions and 2 deletions
|
@ -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.
|
||||
|
||||
|
|
|
@ -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??
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
84
quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ProxyDistTest.java
vendored
Normal file
84
quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ProxyDistTest.java
vendored
Normal 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());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue