From 60e03b48d8b95fa3f20bd31ee7b76bce35e31e91 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Tue, 24 Sep 2024 21:15:31 -0400 Subject: [PATCH 1/6] fix: adds additional info / warnings to hostname v2 closes: #24815 Signed-off-by: Steve Hawkins --- .../endpoints/LoginStatusIframeEndpoint.java | 2 +- .../keycloak/url/HostnameV2ProviderFactory.java | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LoginStatusIframeEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LoginStatusIframeEndpoint.java index 705c95815941..8fcd6d106ca6 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LoginStatusIframeEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LoginStatusIframeEndpoint.java @@ -68,7 +68,7 @@ public Response preCheck(@QueryParam("client_id") String clientId, @QueryParam(" if (validWebOrigins.contains("*") || validWebOrigins.contains(origin)) { return Response.noContent().build(); } - logger.debugf("client %s does not allow origin=%s for requestOrigin=%s (as determined by hostname settings), init will return a 403", clientId, origin, requestOrigin); + logger.debugf("client %s does not allow origin=%s for requestOrigin=%s (as determined by the proxy-header setting), init will return a 403", clientId, origin, requestOrigin); } else { logger.debugf("client %s does not exist or not enabled, init will return a 403", clientId); } diff --git a/services/src/main/java/org/keycloak/url/HostnameV2ProviderFactory.java b/services/src/main/java/org/keycloak/url/HostnameV2ProviderFactory.java index 30cd290dc987..6915389157fd 100644 --- a/services/src/main/java/org/keycloak/url/HostnameV2ProviderFactory.java +++ b/services/src/main/java/org/keycloak/url/HostnameV2ProviderFactory.java @@ -19,8 +19,10 @@ import java.net.URI; import java.util.Arrays; +import java.util.List; import java.util.Optional; +import org.jboss.logging.Logger; import org.keycloak.Config; import org.keycloak.common.Profile; import org.keycloak.models.KeycloakSession; @@ -32,6 +34,10 @@ * @author Vaclav Muzikar */ public class HostnameV2ProviderFactory implements HostnameProviderFactory, EnvironmentDependentProviderFactory { + + private static final Logger LOGGER = Logger.getLogger(HostnameV2ProviderFactory.class); + private static final List REMOVED_OPTIONS = Arrays.asList("hostname-admin-url", "hostname-path", "hostname-port", "hostname-strict-backchannel", "hostname-url", "proxy"); + private static final String INVALID_HOSTNAME = "Provided hostname is neither a plain hostname nor a valid URL"; private String hostname; private URI hostnameUrl; @@ -41,7 +47,7 @@ public class HostnameV2ProviderFactory implements HostnameProviderFactory, Envir @Override public void init(Config.Scope config) { // Strict mode is used just for enforcing that hostname is set - Boolean strictMode = config.getBoolean("hostname-strict", false); + boolean strictMode = config.getBoolean("hostname-strict", false); String hostnameRaw = config.get("hostname"); if (strictMode && hostnameRaw == null) { @@ -49,8 +55,15 @@ public void init(Config.Scope config) { } else if (hostnameRaw != null && !strictMode) { // We might not need this validation as it doesn't matter in this case if strict is true or false. It's just for consistency – hostname XOR !strict. // throw new IllegalArgumentException("hostname is configured, hostname-strict must be set to true"); + LOGGER.info("If hostanme is specified, hostname-strict is effectively ignored"); } - + + List inUse = REMOVED_OPTIONS.stream().filter(s -> config.get(s) != null).toList(); + + if (!inUse.isEmpty()) { + LOGGER.warnf("Hostname v1 options %s are still in use, please review your configuruation", inUse); + } + // Set hostname, can be either a full URL, or just hostname if (hostnameRaw != null) { if (!(hostnameRaw.startsWith("http://") || hostnameRaw.startsWith("https://"))) { From 882cb411a8edb45f7a6d51416bded39894911afd Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Wed, 25 Sep 2024 09:16:47 -0400 Subject: [PATCH 2/6] refining the proxy-headers language from #33209 Signed-off-by: Steve Hawkins --- docs/guides/server/hostname.adoc | 2 +- docs/guides/server/reverseproxy.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/server/hostname.adoc b/docs/guides/server/hostname.adoc index 807f59d12e45..359af52e4af1 100644 --- a/docs/guides/server/hostname.adoc +++ b/docs/guides/server/hostname.adoc @@ -48,7 +48,7 @@ The result of this configuration is that you can continue to access {project_nam == Using a reverse proxy -When a proxy is in use, the `proxy-headers` should be set. Depending on the hostname settings, some or all of the URL, may be dynamically determined. +When a proxy is in use for edge, reencrypt, or http passthrough, the `proxy-headers` should be set. Depending on the hostname settings, some or all of the URL, may be dynamically determined. WARNING: If either `forwarded` or `xforwarded` is selected, make sure your reverse proxy properly sets and overwrites the `Forwarded` or `X-Forwarded-*` headers respectively. To set these headers, consult the documentation for your reverse proxy. Misconfiguration will leave {project_name} exposed to security vulnerabilities. diff --git a/docs/guides/server/reverseproxy.adoc b/docs/guides/server/reverseproxy.adoc index c51713905fd4..2c5ed7b043b1 100644 --- a/docs/guides/server/reverseproxy.adoc +++ b/docs/guides/server/reverseproxy.adoc @@ -18,7 +18,7 @@ Distributed environments frequently require the use of a reverse proxy. {project * `forwarded` enables parsing of the `Forwarded` header as per https://www.rfc-editor.org/rfc/rfc7239.html[RFC7239]. * `xforwarded` enables parsing of non-standard `X-Forwarded-*` headers, such as `X-Forwarded-For`, `X-Forwarded-Proto`, `X-Forwarded-Host`, and `X-Forwarded-Port`. -NOTE: If you are using a reverse proxy and do not set the `proxy-headers` option, then by default you will see 403 Forbidden responses to requests via the proxy that perform origin checking. +NOTE: If you are using a reverse proxy for anything other than https passthrough and do not set the `proxy-headers` option, then by default you will see 403 Forbidden responses to requests via the proxy that perform origin checking. For example: From cf486d8a9687727070ba2e9fd9e9300ea88e0874 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Wed, 25 Sep 2024 10:40:28 -0400 Subject: [PATCH 3/6] adding hostname-strict-https Signed-off-by: Steve Hawkins --- .../main/java/org/keycloak/url/HostnameV2ProviderFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/src/main/java/org/keycloak/url/HostnameV2ProviderFactory.java b/services/src/main/java/org/keycloak/url/HostnameV2ProviderFactory.java index 6915389157fd..807ac42f7e35 100644 --- a/services/src/main/java/org/keycloak/url/HostnameV2ProviderFactory.java +++ b/services/src/main/java/org/keycloak/url/HostnameV2ProviderFactory.java @@ -36,7 +36,7 @@ public class HostnameV2ProviderFactory implements HostnameProviderFactory, EnvironmentDependentProviderFactory { private static final Logger LOGGER = Logger.getLogger(HostnameV2ProviderFactory.class); - private static final List REMOVED_OPTIONS = Arrays.asList("hostname-admin-url", "hostname-path", "hostname-port", "hostname-strict-backchannel", "hostname-url", "proxy"); + private static final List REMOVED_OPTIONS = Arrays.asList("hostname-admin-url", "hostname-path", "hostname-port", "hostname-strict-backchannel", "hostname-url", "proxy", "hostname-strict-https"); private static final String INVALID_HOSTNAME = "Provided hostname is neither a plain hostname nor a valid URL"; private String hostname; From e0ddbf3fb59c14f1e67347ec1f9c7788c4add96e Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Fri, 27 Sep 2024 10:43:24 -0400 Subject: [PATCH 4/6] moving removed property check to the quarkus side Signed-off-by: Steve Hawkins --- .../cli/command/AbstractStartCommand.java | 3 +++ .../mappers/HostnameV2PropertyMappers.java | 23 +++++++++++++++---- .../url/HostnameV2ProviderFactory.java | 10 +------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractStartCommand.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractStartCommand.java index 91bf436eeea8..8de08bcb28b1 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractStartCommand.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractStartCommand.java @@ -23,7 +23,9 @@ import org.keycloak.quarkus.runtime.Messages; import org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler; import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource; +import org.keycloak.quarkus.runtime.configuration.mappers.HostnameV2PropertyMappers; import org.keycloak.quarkus.runtime.configuration.mappers.HttpPropertyMappers; +import org.keycloak.url.HostnameV2ProviderFactory; import java.util.EnumSet; import java.util.List; @@ -42,6 +44,7 @@ public void run() { doBeforeRun(); CommandLine cmd = spec.commandLine(); HttpPropertyMappers.validateConfig(); + HostnameV2PropertyMappers.validateConfig(); validateConfig(); if (ConfigArgsConfigSource.getAllCliArgs().contains(OPTIMIZED_BUILD_OPTION_LONG) && !wasBuildEverRun()) { diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HostnameV2PropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HostnameV2PropertyMappers.java index 5fd30fe062fb..6c670dba8980 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HostnameV2PropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HostnameV2PropertyMappers.java @@ -1,13 +1,20 @@ package org.keycloak.quarkus.runtime.configuration.mappers; -import org.keycloak.common.Profile; -import org.keycloak.config.HostnameV2Options; +import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption; +import java.util.Arrays; +import java.util.List; import java.util.stream.Stream; -import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption; +import org.jboss.logging.Logger; +import org.keycloak.common.Profile; +import org.keycloak.config.HostnameV2Options; +import org.keycloak.quarkus.runtime.configuration.Configuration; -final class HostnameV2PropertyMappers { +public final class HostnameV2PropertyMappers { + + private static final Logger LOGGER = Logger.getLogger(PropertyMappers.class); + private static final List REMOVED_OPTIONS = Arrays.asList("hostname-admin-url", "hostname-path", "hostname-port", "hostname-strict-backchannel", "hostname-url", "proxy", "hostname-strict-https"); private HostnameV2PropertyMappers(){} @@ -28,5 +35,13 @@ public static PropertyMapper[] getHostnamePropertyMappers() { .map(b -> b.isEnabled(() -> Profile.isFeatureEnabled(Profile.Feature.HOSTNAME_V2), "hostname:v2 feature is enabled").build()) .toArray(s -> new PropertyMapper[s]); } + + public static void validateConfig() { + List inUse = REMOVED_OPTIONS.stream().filter(s -> Configuration.getOptionalKcValue(s).isPresent()).toList(); + + if (!inUse.isEmpty()) { + LOGGER.errorf("Hostname v1 options %s are still in use, please review your configuruation", inUse); + } + } } diff --git a/services/src/main/java/org/keycloak/url/HostnameV2ProviderFactory.java b/services/src/main/java/org/keycloak/url/HostnameV2ProviderFactory.java index 807ac42f7e35..f1c68b43cce8 100644 --- a/services/src/main/java/org/keycloak/url/HostnameV2ProviderFactory.java +++ b/services/src/main/java/org/keycloak/url/HostnameV2ProviderFactory.java @@ -19,7 +19,6 @@ import java.net.URI; import java.util.Arrays; -import java.util.List; import java.util.Optional; import org.jboss.logging.Logger; @@ -36,7 +35,6 @@ public class HostnameV2ProviderFactory implements HostnameProviderFactory, EnvironmentDependentProviderFactory { private static final Logger LOGGER = Logger.getLogger(HostnameV2ProviderFactory.class); - private static final List REMOVED_OPTIONS = Arrays.asList("hostname-admin-url", "hostname-path", "hostname-port", "hostname-strict-backchannel", "hostname-url", "proxy", "hostname-strict-https"); private static final String INVALID_HOSTNAME = "Provided hostname is neither a plain hostname nor a valid URL"; private String hostname; @@ -57,13 +55,7 @@ public void init(Config.Scope config) { // throw new IllegalArgumentException("hostname is configured, hostname-strict must be set to true"); LOGGER.info("If hostanme is specified, hostname-strict is effectively ignored"); } - - List inUse = REMOVED_OPTIONS.stream().filter(s -> config.get(s) != null).toList(); - - if (!inUse.isEmpty()) { - LOGGER.warnf("Hostname v1 options %s are still in use, please review your configuruation", inUse); - } - + // Set hostname, can be either a full URL, or just hostname if (hostnameRaw != null) { if (!(hostnameRaw.startsWith("http://") || hostnameRaw.startsWith("https://"))) { From 53b563512285dd3866b16d06b4edf856de44431d Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Fri, 27 Sep 2024 12:19:46 -0400 Subject: [PATCH 5/6] Update quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HostnameV2PropertyMappers.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Martin Bartoš Signed-off-by: Steven Hawkins --- .../configuration/mappers/HostnameV2PropertyMappers.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HostnameV2PropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HostnameV2PropertyMappers.java index 6c670dba8980..004a8847c765 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HostnameV2PropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HostnameV2PropertyMappers.java @@ -40,7 +40,7 @@ public static void validateConfig() { List inUse = REMOVED_OPTIONS.stream().filter(s -> Configuration.getOptionalKcValue(s).isPresent()).toList(); if (!inUse.isEmpty()) { - LOGGER.errorf("Hostname v1 options %s are still in use, please review your configuruation", inUse); + LOGGER.errorf("Hostname v1 options %s are still in use, please review your configuration", inUse); } } From 8c29b35a4dfb2fa0bead8b99cf6bd2960df1ccfb Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Fri, 27 Sep 2024 12:27:03 -0400 Subject: [PATCH 6/6] Update docs/guides/server/hostname.adoc Signed-off-by: Steven Hawkins --- docs/guides/server/hostname.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/server/hostname.adoc b/docs/guides/server/hostname.adoc index 359af52e4af1..30ef8e1123b3 100644 --- a/docs/guides/server/hostname.adoc +++ b/docs/guides/server/hostname.adoc @@ -48,7 +48,7 @@ The result of this configuration is that you can continue to access {project_nam == Using a reverse proxy -When a proxy is in use for edge, reencrypt, or http passthrough, the `proxy-headers` should be set. Depending on the hostname settings, some or all of the URL, may be dynamically determined. +When a proxy is forwarding http or reencrypted TLS requests, the `proxy-headers` option should be set. Depending on the hostname settings, some or all of the URL, may be dynamically determined. WARNING: If either `forwarded` or `xforwarded` is selected, make sure your reverse proxy properly sets and overwrites the `Forwarded` or `X-Forwarded-*` headers respectively. To set these headers, consult the documentation for your reverse proxy. Misconfiguration will leave {project_name} exposed to security vulnerabilities.