fix: adds additional info / warnings to hostname v2 (#33261)

* fix: adds additional info / warnings to hostname v2

closes: #24815

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

* refining the proxy-headers language from #33209

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

* adding hostname-strict-https

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

* moving removed property check to the quarkus side

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

* Update quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HostnameV2PropertyMappers.java

Co-authored-by: Martin Bartoš <mabartos@redhat.com>
Signed-off-by: Steven Hawkins <shawkins@redhat.com>

* Update docs/guides/server/hostname.adoc

Signed-off-by: Steven Hawkins <shawkins@redhat.com>

---------

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
Signed-off-by: Steven Hawkins <shawkins@redhat.com>
Co-authored-by: Martin Bartoš <mabartos@redhat.com>
This commit is contained in:
Steven Hawkins 2024-09-28 04:48:09 -04:00 committed by GitHub
parent cf2ecf87f6
commit f1a7a4804e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 33 additions and 10 deletions

View file

@ -48,7 +48,7 @@ The result of this configuration is that you can continue to access {project_nam
== Using a reverse proxy == 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 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. 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.

View file

@ -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]. * `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`. * `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: For example:

View file

@ -23,7 +23,9 @@ import org.keycloak.quarkus.runtime.KeycloakMain;
import org.keycloak.quarkus.runtime.Messages; import org.keycloak.quarkus.runtime.Messages;
import org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler; import org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler;
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource; 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.quarkus.runtime.configuration.mappers.HttpPropertyMappers;
import org.keycloak.url.HostnameV2ProviderFactory;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
@ -44,6 +46,7 @@ public abstract class AbstractStartCommand extends AbstractCommand implements Ru
doBeforeRun(); doBeforeRun();
CommandLine cmd = spec.commandLine(); CommandLine cmd = spec.commandLine();
HttpPropertyMappers.validateConfig(); HttpPropertyMappers.validateConfig();
HostnameV2PropertyMappers.validateConfig();
validateConfig(); validateConfig();
if (ConfigArgsConfigSource.getAllCliArgs().contains(OPTIMIZED_BUILD_OPTION_LONG) && !wasBuildEverRun()) { if (ConfigArgsConfigSource.getAllCliArgs().contains(OPTIMIZED_BUILD_OPTION_LONG) && !wasBuildEverRun()) {

View file

@ -1,13 +1,20 @@
package org.keycloak.quarkus.runtime.configuration.mappers; package org.keycloak.quarkus.runtime.configuration.mappers;
import org.keycloak.common.Profile;
import org.keycloak.config.HostnameV2Options;
import java.util.stream.Stream;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption; import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
final class HostnameV2PropertyMappers { import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import org.jboss.logging.Logger;
import org.keycloak.common.Profile;
import org.keycloak.config.HostnameV2Options;
import org.keycloak.quarkus.runtime.configuration.Configuration;
public final class HostnameV2PropertyMappers {
private static final Logger LOGGER = Logger.getLogger(PropertyMappers.class);
private static final List<String> REMOVED_OPTIONS = Arrays.asList("hostname-admin-url", "hostname-path", "hostname-port", "hostname-strict-backchannel", "hostname-url", "proxy", "hostname-strict-https");
private HostnameV2PropertyMappers(){} private HostnameV2PropertyMappers(){}
@ -29,4 +36,12 @@ final class HostnameV2PropertyMappers {
.toArray(s -> new PropertyMapper<?>[s]); .toArray(s -> new PropertyMapper<?>[s]);
} }
public static void validateConfig() {
List<String> 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 configuration", inUse);
}
}
} }

View file

@ -68,7 +68,7 @@ public class LoginStatusIframeEndpoint {
if (validWebOrigins.contains("*") || validWebOrigins.contains(origin)) { if (validWebOrigins.contains("*") || validWebOrigins.contains(origin)) {
return Response.noContent().build(); 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 { } else {
logger.debugf("client %s does not exist or not enabled, init will return a 403", clientId); logger.debugf("client %s does not exist or not enabled, init will return a 403", clientId);
} }

View file

@ -21,6 +21,7 @@ import java.net.URI;
import java.util.Arrays; import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -32,6 +33,9 @@ import org.keycloak.urls.HostnameProviderFactory;
* @author Vaclav Muzikar <vmuzikar@redhat.com> * @author Vaclav Muzikar <vmuzikar@redhat.com>
*/ */
public class HostnameV2ProviderFactory implements HostnameProviderFactory, EnvironmentDependentProviderFactory { public class HostnameV2ProviderFactory implements HostnameProviderFactory, EnvironmentDependentProviderFactory {
private static final Logger LOGGER = Logger.getLogger(HostnameV2ProviderFactory.class);
private static final String INVALID_HOSTNAME = "Provided hostname is neither a plain hostname nor a valid URL"; private static final String INVALID_HOSTNAME = "Provided hostname is neither a plain hostname nor a valid URL";
private String hostname; private String hostname;
private URI hostnameUrl; private URI hostnameUrl;
@ -41,7 +45,7 @@ public class HostnameV2ProviderFactory implements HostnameProviderFactory, Envir
@Override @Override
public void init(Config.Scope config) { public void init(Config.Scope config) {
// Strict mode is used just for enforcing that hostname is set // 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"); String hostnameRaw = config.get("hostname");
if (strictMode && hostnameRaw == null) { if (strictMode && hostnameRaw == null) {
@ -49,6 +53,7 @@ public class HostnameV2ProviderFactory implements HostnameProviderFactory, Envir
} else if (hostnameRaw != null && !strictMode) { } 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. // 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"); // throw new IllegalArgumentException("hostname is configured, hostname-strict must be set to true");
LOGGER.info("If hostanme is specified, hostname-strict is effectively ignored");
} }
// Set hostname, can be either a full URL, or just hostname // Set hostname, can be either a full URL, or just hostname