diff --git a/docs/guides/server/hostname.adoc b/docs/guides/server/hostname.adoc index 9d823d12a6..ae2afa1a3a 100644 --- a/docs/guides/server/hostname.adoc +++ b/docs/guides/server/hostname.adoc @@ -122,4 +122,18 @@ In this example, the server is accessible using a port other than the default po .Keycloak configuration: <@kc.start parameters="--hostname-url=https://mykeycloak:8989"/> +== Troubleshooting + +To troubleshoot the hostname configuration, you can use a dedicated debug tool which can be enabled as: + +.Keycloak configuration: +<@kc.start parameters="--hostname=mykeycloak --hostname-debug=true"/> + +Then after Keycloak started properly, open your browser and go to: + +`http://mykeycloak:8080/realms//hostname-debug` + +.By default, this endpoint is disabled (`--hostname-debug=false`) + + diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/HostnameOptions.java b/quarkus/config-api/src/main/java/org/keycloak/config/HostnameOptions.java index 109407877b..50b3860b7c 100644 --- a/quarkus/config-api/src/main/java/org/keycloak/config/HostnameOptions.java +++ b/quarkus/config-api/src/main/java/org/keycloak/config/HostnameOptions.java @@ -50,4 +50,11 @@ public class HostnameOptions { .description("The port used by the proxy when exposing the hostname. Set this option if the proxy uses a port other than the default HTTP and HTTPS ports.") .defaultValue(-1) .build(); + + public static final Option HOSTNAME_DEBUG = new OptionBuilder<>("hostname-debug", Boolean.class) + .category(OptionCategory.HOSTNAME) + .description("Toggle the hostname debug page that is accessible at /realms/master/hostname-debug") + .defaultValue(Boolean.FALSE) + .build(); + } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HostnamePropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HostnamePropertyMappers.java index 5912eab617..83bd2ab320 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HostnamePropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HostnamePropertyMappers.java @@ -42,6 +42,9 @@ final class HostnamePropertyMappers { fromOption(HostnameOptions.HOSTNAME_PORT) .to("kc.spi-hostname-default-hostname-port") .paramLabel("port") + .build(), + fromOption(HostnameOptions.HOSTNAME_DEBUG) + .to("kc.spi-hostname-default-hostname-debug") .build() }; } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/jaxrs/QuarkusKeycloakApplication.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/jaxrs/QuarkusKeycloakApplication.java index e746a59eb9..e15346a3a2 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/jaxrs/QuarkusKeycloakApplication.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/jaxrs/QuarkusKeycloakApplication.java @@ -22,10 +22,14 @@ import java.util.stream.Collectors; import javax.ws.rs.ApplicationPath; +import org.keycloak.config.HostnameOptions; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.quarkus.runtime.configuration.Configuration; import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory; +import org.keycloak.quarkus.runtime.services.resources.DebugHostnameSettingsResource; import org.keycloak.services.resources.KeycloakApplication; import org.keycloak.quarkus.runtime.services.resources.QuarkusWelcomeResource; +import org.keycloak.services.resources.LoginActionsService; import org.keycloak.services.resources.WelcomeResource; @ApplicationPath("/") @@ -55,6 +59,10 @@ public class QuarkusKeycloakApplication extends KeycloakApplication { singletons.add(new QuarkusWelcomeResource()); + if (Configuration.getOptionalBooleanValue("--" + HostnameOptions.HOSTNAME_DEBUG.getKey()).orElse(Boolean.FALSE)) { + singletons.add(new DebugHostnameSettingsResource()); + } + return singletons; } } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/services/resources/ConstantsDebugHostname.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/services/resources/ConstantsDebugHostname.java new file mode 100644 index 0000000000..2ff612ecb9 --- /dev/null +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/services/resources/ConstantsDebugHostname.java @@ -0,0 +1,46 @@ +/* + * Copyright 2016 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.quarkus.runtime.services.resources; + +public class ConstantsDebugHostname { + public static final String[] RELEVANT_HEADERS = new String[] { + "Host", + "Forwarded", + "X-Forwarded-Host", + "X-Forwarded-Proto", + "X-Forwarded-Port", + "X-Forwarded-For" + }; + + public static final String[] RELEVANT_OPTIONS = { + "hostname", + "hostname-url", + "hostname-admin", + "hostname-admin-url", + "hostname-strict", + "hostname-strict-backchannel", + "hostname-strict-https", + "hostname-path", + "hostname-port", + "proxy", + "http-enabled", + "http-relative-path", + "http-port", + "https-port" + }; + +} diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/services/resources/DebugHostnameSettingsResource.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/services/resources/DebugHostnameSettingsResource.java new file mode 100644 index 0000000000..fef764c094 --- /dev/null +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/services/resources/DebugHostnameSettingsResource.java @@ -0,0 +1,146 @@ +/* + * Copyright 2016 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.quarkus.runtime.services.resources; + +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.quarkus.runtime.Environment; +import org.keycloak.quarkus.runtime.configuration.Configuration; +import org.keycloak.services.Urls; +import org.keycloak.services.resources.Cors; +import org.keycloak.theme.FreeMarkerException; +import org.keycloak.theme.Theme; +import org.keycloak.theme.freemarker.FreeMarkerProvider; +import org.keycloak.urls.UrlType; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.net.URI; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; + +@Path("/realms") +public class DebugHostnameSettingsResource { + public static final String DEFAULT_PATH_SUFFIX = "hostname-debug"; + public static final String PATH_FOR_TEST_CORS_IN_HEADERS = "test"; + + + @Context + private KeycloakSession keycloakSession; + + private final Map allConfigPropertiesMap; + + public DebugHostnameSettingsResource() { + + this.allConfigPropertiesMap = new LinkedHashMap<>(); + for (String key : ConstantsDebugHostname.RELEVANT_OPTIONS) { + addOption(key); + } + + } + + @GET + @Path("/{realmName}/" + DEFAULT_PATH_SUFFIX) + @Produces(MediaType.TEXT_HTML) + public String debug(final @PathParam("realmName") String realmName) throws IOException, FreeMarkerException { + FreeMarkerProvider freeMarkerProvider = keycloakSession.getProvider(FreeMarkerProvider.class); + RealmModel realmModel = keycloakSession.realms().getRealmByName(realmName); + + URI frontendUri = keycloakSession.getContext().getUri(UrlType.FRONTEND).getBaseUri(); + URI backendUri = keycloakSession.getContext().getUri(UrlType.BACKEND).getBaseUri(); + URI adminUri = keycloakSession.getContext().getUri(UrlType.ADMIN).getBaseUri(); + + String frontendTestUrl = getTest(realmModel, frontendUri); + String backendTestUrl = getTest(realmModel, backendUri); + String adminTestUrl = getTest(realmModel, adminUri); + + Map attributes = new HashMap<>(); + attributes.put("frontendUrl", frontendUri.toString()); + attributes.put("backendUrl", backendUri.toString()); + attributes.put("adminUrl", adminUri.toString()); + + attributes.put("realm", realmModel.getName()); + attributes.put("realmUrl", realmModel.getAttribute("frontendUrl")); + + attributes.put("frontendTestUrl", frontendTestUrl); + attributes.put("backendTestUrl", backendTestUrl); + attributes.put("adminTestUrl", adminTestUrl); + + attributes.put("serverMode", Environment.isDevMode() ? "dev [start-dev]" : "production [start]"); + + attributes.put("config", this.allConfigPropertiesMap); + attributes.put("headers", getHeaders()); + + return freeMarkerProvider.processTemplate( + attributes, + "debug-hostname-settings.ftl", + keycloakSession.theme().getTheme("base", Theme.Type.LOGIN) + ); + } + + @GET + @Path("/{realmName}/" + DEFAULT_PATH_SUFFIX + "/" + PATH_FOR_TEST_CORS_IN_HEADERS) + @Produces(MediaType.TEXT_PLAIN) + public Response test(final @PathParam("realmName") String realmName) { + Response.ResponseBuilder builder = Response.ok(PATH_FOR_TEST_CORS_IN_HEADERS + "-OK"); + String origin = keycloakSession.getContext().getRequestHeaders().getHeaderString(Cors.ORIGIN_HEADER); + builder.header(Cors.ACCESS_CONTROL_ALLOW_ORIGIN, origin); + builder.header(Cors.ACCESS_CONTROL_ALLOW_METHODS, "GET"); + return builder.build(); + } + + private void addOption(String key) { + String rawValue = Configuration.getRawValue("kc." + key); + if (rawValue != null && !rawValue.isEmpty()) { + this.allConfigPropertiesMap.put(key, rawValue); + } + } + + + private Map getHeaders() { + Map headers = new TreeMap<>(); + HttpHeaders requestHeaders = keycloakSession.getContext().getRequestHeaders(); + for (String h : ConstantsDebugHostname.RELEVANT_HEADERS) { + addProxyHeader(h, headers, requestHeaders); + } + return headers; + } + + private void addProxyHeader(String header, Map proxyHeaders, HttpHeaders requestHeaders) { + String value = requestHeaders.getHeaderString(header); + if (value != null && !value.isEmpty()) { + proxyHeaders.put(header, value); + } + } + + private String getTest(RealmModel realmModel, URI baseUri) { + return Urls.realmBase(baseUri) + .path("/{realmName}/{debugHostnameSettingsPath}/{pathForTestCORSInHeaders}") + .build(realmModel.getName(), DEFAULT_PATH_SUFFIX, PATH_FOR_TEST_CORS_IN_HEADERS) + .toString(); + } + +} diff --git a/quarkus/runtime/src/main/resources/theme-resources/templates/debug-hostname-settings.ftl b/quarkus/runtime/src/main/resources/theme-resources/templates/debug-hostname-settings.ftl new file mode 100755 index 0000000000..54a4aa5763 --- /dev/null +++ b/quarkus/runtime/src/main/resources/theme-resources/templates/debug-hostname-settings.ftl @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <#if realmUrl??> + + + + + + + + + + + + <#list config as key, value> + + + + + + + <#if headers?has_content> + + + + + + <#list headers as key, value> + + + + + + +
URLValue
Request
Frontend${frontendUrl} []
Backend${backendUrl} []
Admin${adminUrl} []
RuntimeValue
Server mode${serverMode}
Realm${realm}
Realm URL${realmUrl}
Configuration propertyValue
${key}${value}
HeaderValue
${key}${value}
+ + + + \ No newline at end of file diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/HostnameDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/HostnameDistTest.java index 97f826c063..20deb1bea5 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/HostnameDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/HostnameDistTest.java @@ -17,17 +17,17 @@ package org.keycloak.it.cli.dist; -import static io.restassured.RestAssured.when; - +import io.quarkus.test.junit.main.Launch; +import io.restassured.RestAssured; import org.junit.Assert; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.keycloak.it.junit5.extension.DistributionTest; import org.keycloak.it.junit5.extension.RawDistOnly; import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; +import org.keycloak.quarkus.runtime.services.resources.DebugHostnameSettingsResource; -import io.quarkus.test.junit.main.Launch; -import io.restassured.RestAssured; +import static io.restassured.RestAssured.when; @DistributionTest(keepAlive = true, enableTls = true, defaultOptions = { "--http-enabled=true" }) @RawDistOnly(reason = "Containers are immutable") @@ -115,6 +115,43 @@ public class HostnameDistTest { Assert.assertTrue(when().get("https://localhost:8443").asString().contains("https://localhost:8443/admin/")); } + @Test + @Launch({ "start", "--hostname=mykeycloak.org", "--hostname-debug=true" }) + public void testDebugHostnameSettingsEnabled() { + Assert.assertTrue(when().get("http://localhost:8080/realms/master/" + DebugHostnameSettingsResource.DEFAULT_PATH_SUFFIX).getStatusCode() == 200); + Assert.assertTrue(when().get("http://localhost:8080/realms/master/" + DebugHostnameSettingsResource.DEFAULT_PATH_SUFFIX).asString().contains("Configuration property")); + Assert.assertTrue(when().get("http://localhost:8080/realms/master/" + DebugHostnameSettingsResource.DEFAULT_PATH_SUFFIX).asString().contains("Server mode")); + Assert.assertTrue(when().get("http://localhost:8080/realms/master/" + DebugHostnameSettingsResource.DEFAULT_PATH_SUFFIX).asString().contains("production [start]")); + + Assert.assertTrue( + when().get("http://mykeycloak.org:8080/realms/master/" + + DebugHostnameSettingsResource.DEFAULT_PATH_SUFFIX + + "/" + DebugHostnameSettingsResource.PATH_FOR_TEST_CORS_IN_HEADERS) + .getStatusCode() == 200 + ); + Assert.assertTrue( + when().get("http://localhost:8080/realms/master/" + + DebugHostnameSettingsResource.DEFAULT_PATH_SUFFIX + + "/" + DebugHostnameSettingsResource.PATH_FOR_TEST_CORS_IN_HEADERS) + .asString() + .contains(DebugHostnameSettingsResource.PATH_FOR_TEST_CORS_IN_HEADERS + "-OK") + ); + } + + @Test + @Launch({ "start", "--hostname=mykeycloak.org", "--hostname-debug=false" }) + public void testDebugHostnameSettingsDisabledBySetting() { + Assert.assertTrue(when().get("http://localhost:8080/realms/master/" + DebugHostnameSettingsResource.DEFAULT_PATH_SUFFIX).getStatusCode() == 404); + Assert.assertTrue(when().get("http://localhost:8080/realms/master/" + DebugHostnameSettingsResource.DEFAULT_PATH_SUFFIX).asString().contains("404")); + } + + @Test + @Launch({ "start", "--hostname=mykeycloak.org"}) + public void testDebugHostnameSettingsDisabledByDefault() { + Assert.assertTrue(when().get("http://localhost:8080/realms/master/" + DebugHostnameSettingsResource.DEFAULT_PATH_SUFFIX).getStatusCode() == 404); + Assert.assertTrue(when().get("http://localhost:8080/realms/master/" + DebugHostnameSettingsResource.DEFAULT_PATH_SUFFIX).asString().contains("404")); + } + @Test @Launch({ "start", "--hostname=mykeycloak.org", "--hostname-admin=mykeycloakadmin.org" }) public void testHostnameAdminSet() { diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt index ed7b76ea8c..08ede9ce0d 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt @@ -95,6 +95,9 @@ Hostname: --hostname-admin-url Set the base URL for accessing the administration console, including scheme, host, port and path +--hostname-debug + Toggle the hostname debug page that is accessible at + /realms/master/hostname-debug Default: false. --hostname-path This should be set if proxy uses a different context-path for Keycloak. --hostname-port diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt index bb20604267..b0b7dc70f4 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt @@ -158,6 +158,9 @@ Hostname: --hostname-admin-url Set the base URL for accessing the administration console, including scheme, host, port and path +--hostname-debug + Toggle the hostname debug page that is accessible at + /realms/master/hostname-debug Default: false. --hostname-path This should be set if proxy uses a different context-path for Keycloak. --hostname-port diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt index d63aef227e..3d0f3aca5b 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt @@ -101,6 +101,9 @@ Hostname: --hostname-admin-url Set the base URL for accessing the administration console, including scheme, host, port and path +--hostname-debug + Toggle the hostname debug page that is accessible at + /realms/master/hostname-debug Default: false. --hostname-path This should be set if proxy uses a different context-path for Keycloak. --hostname-port diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt index 7db337651a..60b4ad5196 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt @@ -164,6 +164,9 @@ Hostname: --hostname-admin-url Set the base URL for accessing the administration console, including scheme, host, port and path +--hostname-debug + Toggle the hostname debug page that is accessible at + /realms/master/hostname-debug Default: false. --hostname-path This should be set if proxy uses a different context-path for Keycloak. --hostname-port diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.unix.approved.txt index 4cd485d847..78bf60b950 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.unix.approved.txt @@ -61,6 +61,9 @@ Hostname: --hostname-admin-url Set the base URL for accessing the administration console, including scheme, host, port and path +--hostname-debug + Toggle the hostname debug page that is accessible at + /realms/master/hostname-debug Default: false. --hostname-path This should be set if proxy uses a different context-path for Keycloak. --hostname-port diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.unix.approved.txt index cd13817d8e..1fedb47d0d 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.unix.approved.txt @@ -80,6 +80,9 @@ Hostname: --hostname-admin-url Set the base URL for accessing the administration console, including scheme, host, port and path +--hostname-debug + Toggle the hostname debug page that is accessible at + /realms/master/hostname-debug Default: false. --hostname-path This should be set if proxy uses a different context-path for Keycloak. --hostname-port