Allow setting a URL to configure frontend and admin URLs
Closes #13524 Co-authored-by: Stian Thorgersen <stianst@gmail.com>
This commit is contained in:
parent
62790b8ce0
commit
52aad0bbdc
19 changed files with 264 additions and 191 deletions
|
@ -5,193 +5,122 @@
|
|||
<@tmpl.guide
|
||||
title="Configuring the hostname"
|
||||
summary="Learn how to configure the frontend and backchannel endpoints exposed by Keycloak."
|
||||
includedOptions="hostname-* proxy">
|
||||
includedOptions="hostname hostname-* proxy">
|
||||
|
||||
When running Keycloak in environments such as Kubernetes, OpenShift, or on-premise, you want to protect the internal URLs from exposure to the public facing internet.
|
||||
Instead, You want to expose your public hostname.
|
||||
This guide describes how to configure Keycloak to use the right hostname for different scenarios.
|
||||
== Server Endpoints
|
||||
|
||||
== Hostname Syntax:
|
||||
Keycloak does not impose strict validation for the configured hostname value. Please make sure the hostname value you configure complies to the standardized hostname syntax as outlined in RFC 952, RFC 1123 and others.
|
||||
Keycloak exposes different endpoints to talk with applications as well as to allow accessing the administration console. These endpoints
|
||||
can be categorized into three main groups:
|
||||
|
||||
.Example:
|
||||
`LOCALHOST` is a non-compliant hostname and leads to problems when the browser tries to resolve your requests. Instead, `localhost` will work fine.
|
||||
* Frontend
|
||||
* Backend
|
||||
* Administration Console
|
||||
|
||||
== Keycloak API Endpoint categories
|
||||
As this section explains, Keycloak exposes three API endpoint categories: frontend, backend, and administrative.
|
||||
Each category uses a specific base URL.
|
||||
The base URL for each group has an important impact on how tokens are issued and validated, on how links are created for actions that require the user
|
||||
to be redirected to Keycloak (for example, when resetting password through email links), and, most importantly, how applications will
|
||||
discover these endpoints when fetching the OpenID Connect Discovery Document from `realms/{realm-name}/.well-known/openid-configuration`.
|
||||
|
||||
=== Frontend Endpoints
|
||||
Frontend endpoints are used to externally access Keycloak.
|
||||
When no hostname is set, the base URL used for the frontend is taken from the incoming request.
|
||||
This choice has some major disadvantages.
|
||||
For example, in a high availability scenario, you may have multiple Keycloak instances.
|
||||
The choice of URL should not depend on the instance where the request lands.
|
||||
The URL should be used for all instances, so they are seen as one system from the outside.
|
||||
=== Frontend
|
||||
|
||||
To set the hostname part of the frontend base URL, enter this command:
|
||||
The frontend endpoints are those accessible through a public domain and usually related to authentication/authorization flows that happen
|
||||
through the front-channel. For instance, when an SPA wants to authenticate their users it redirects them to the `authorization_endpoint` so that users
|
||||
can authenticate using their browsers through the front-channel.
|
||||
|
||||
<@kc.start parameters="--hostname=<value>"/>
|
||||
By default, when the hostname settings are not set, the base URL for these endpoints is based on the incoming request so that the HTTP scheme,
|
||||
host, port, and path, are the same from the request. The default behavior also has a direct impact on how the server is going to issue tokens given that the issuer is also based on
|
||||
the URL set to the frontend endpoints. If the hostname settings are not set, the token issuer will also be based on the incoming request and also lack consistency if the client is requesting tokens using different URLs.
|
||||
|
||||
You can also set a different port if your proxy is exposing the frontend URL using a port other than the default HTTP (80) and HTTPS(443) ports. For that,
|
||||
set the `hostname-port` option.
|
||||
When deploying to production you usually want a consistent URL for the frontend endpoints and the token issuer regardless of how the request is constructed.
|
||||
In order to achieve this consistency, you can set either the `hostname` or the `hostname-url` options.
|
||||
|
||||
<@kc.start parameters="--hostname=<value> --hostname-port=<port>"/>
|
||||
Most of the time, it should be enough to set the `hostname` option in order to change only the *host* of the frontend URLs:
|
||||
|
||||
=== Backend Endpoints
|
||||
Backend endpoints are used for direct communication between Keycloak and applications.
|
||||
Examples of backend endpoints are the Token endpoint and the User info endpoint.
|
||||
Backend endpoints are also taking the base URL from the request by default.
|
||||
To override this behavior, set the `hostname-strict-backchannel` configuration option by entering this command:
|
||||
<@kc.start parameters="--hostname=<host>"/>
|
||||
|
||||
When using the `hostname` option the server is going to resolve the HTTP scheme, port, and path, automatically so that:
|
||||
|
||||
* `https` scheme is used unless you set `hostname-strict-https=false`
|
||||
* Use the standard HTTP ports (e.g.: `80` and `443`) if a `proxy` is set or use the port you set to the `hostname-port` option
|
||||
|
||||
However, if you want to set not only the host but also a scheme, port, and path, you can set the `hostname-url` option:
|
||||
|
||||
<@kc.start parameters="--hostname-url=<scheme>://<host>:<port>/<path>"/>
|
||||
|
||||
This option gives you more flexibility as you can set the different parts of the URL from a single option. Note that
|
||||
the `hostname` and `hostname-url` are mutually exclusive.
|
||||
|
||||
=== Backend
|
||||
|
||||
The backend endpoints are those accessible through a public domain or through a private network. They are used for a direct communication
|
||||
between the server and clients without any intermediary but plain HTTP requests. For instance, after the user is authenticated an SPA
|
||||
wants to exchange the `code` sent by the server with a set of tokens by sending a token request to `token_endpoint`.
|
||||
|
||||
By default, the URLs for backend endpoints are also based on the incoming request. To override this behavior, set the `hostname-strict-backchannel` configuration option by entering this command:
|
||||
|
||||
<@kc.start parameters="--hostname=<value> --hostname-strict-backchannel=true"/>
|
||||
|
||||
When all applications connected to Keycloak communicate through the public URL, set `hostname-strict-backchannel` to true.
|
||||
Otherwise, leave this parameter as false to allow internal applications to communicate with Keycloak through an internal URL.
|
||||
By setting the `hostname-strict-backchannel` option, the URLs for the backend endpoints are going to be exactly the same as the frontend endpoints.
|
||||
|
||||
=== Administrative Endpoints
|
||||
When all applications connected to Keycloak communicate through the public URL, set `hostname-strict-backchannel` to `true`.
|
||||
Otherwise, leave this parameter as `false` to allow client-server communication through a private network.
|
||||
|
||||
=== Administration Console
|
||||
|
||||
The server exposes the administration console and static resources using a specific URL.
|
||||
|
||||
By default, the URLs for the administration console are also based on the incoming request. However, you can set a specific host or base URL if you want
|
||||
to restrict access to the administration console using a specific URL. Similarly to how you set the frontend URLs, you can use the `hostname-admin` and `hostname-admin-url` options to achieve that.
|
||||
|
||||
Most of the time, it should be enough to set the `hostname-admin` option in order to change only the *host* of the administration console URLs:
|
||||
|
||||
<@kc.start parameters="--hostname-admin=<host>"/>
|
||||
|
||||
However, if you want to set not only the host but also a scheme, port, and path, you can set the `hostname-admin-url` option:
|
||||
|
||||
<@kc.start parameters="--hostname-admin-url=<scheme>://<host>:<port>/<path>"/>
|
||||
|
||||
Note that the `hostname-admin` and `hostname-admin-url` are mutually exclusive.
|
||||
|
||||
To reduce attack surface, the administration endpoints for Keycloak and the Admin Console should not be publicly accessible.
|
||||
Therefore, you can secure them by using a reverse proxy.
|
||||
For more information about which paths to expose using a reverse proxy, see the <@links.server id="reverseproxy"/> Guide.
|
||||
|
||||
==== Exposing the administration console using a different hostname
|
||||
|
||||
The administration console can be exposed using a hostname other than what you set to the frontend URLs via the `hostname` option. For that,
|
||||
you can set the `hostname-admin` option as follows:
|
||||
|
||||
<@kc.start parameters="--hostname=myurl --hostname-admin=myadminurl"/>
|
||||
|
||||
When the `hostname-admin` option is set the URLs used by the administration console will have that hostname hardcoded in them. Otherwise,
|
||||
the URLs used by the administration console are going to be based on the hostname from the request.
|
||||
|
||||
If you don't set this option and the administration console is accessed using a hostname other than what is set to the frontend URLs, you
|
||||
might get an error from the server telling you that the redirect URI used by the console is invalid. In this case, you should update the
|
||||
`security-admin-console` client to add a valid redirect URI based on the hostname you want the administration console to be accessible.
|
||||
|
||||
== Overriding the hostname path
|
||||
When running Keycloak behind a reverse proxy, you may expose Keycloak using a different context path such as `myproxy.url/mykeycloak`.
|
||||
To perform this action, you can override the hostname path to use the path defined in your reverse proxyas shown in this example:
|
||||
|
||||
<@kc.start parameters="--hostname=myurl --hostname-path=mykeycloak"/>
|
||||
|
||||
The `hostname-path` configuration takes effect when a reverse proxy is enabled.
|
||||
For details, see the <@links.server id="reverseproxy"/> Guide.
|
||||
|
||||
== Accessing Keycloak in production mode using HTTP
|
||||
When a `hostname` is set and the server is running in production mode, all the URLs generated by the server are going to use the `HTTPS` scheme. If you are not setting up TLS you might run into issues because some URLs generated by the server won't work.
|
||||
|
||||
Keycloak follows the "secure by design" principle, so it is absolutely not recommended to access Keycloak without proper transport encryption, as this opens up multiple attack vectors.
|
||||
|
||||
Nevertheless there are environments, where Keycloak is deployed behind a proxy/load balancer that terminates TLS completely and the internal requests are done using the unencrypted HTTP protocol.
|
||||
|
||||
To be able to work with Keycloak using HTTP for these environments, there is the hidden configuration option `hostname-strict-https=<true/false>`. This option is set to `true` by default for the production mode, and `false` for the development mode.
|
||||
|
||||
When you need to access Keycloak using HTTP in production mode, for example when you use `proxy=edge` and you want to access the administration console internally using HTTP, you have to set `hostname-strict-https=false`, otherwise a blank page will show up.
|
||||
|
||||
Keep in mind the recommended approach is to always use HTTPS, and this still is true for external clients.
|
||||
|
||||
== Using the hostname in development mode
|
||||
You run Keycloak in development mode by using `start-dev`.
|
||||
In this mode, the hostname setting is optional.
|
||||
When it is omitted, the incoming request headers are used.
|
||||
|
||||
=== Example: Hostname in development mode
|
||||
.Keycloak configuration:
|
||||
<@kc.startdev parameters=""/>
|
||||
|
||||
.Invoked command:
|
||||
[source, bash]
|
||||
----
|
||||
curl GET "https://localhost:8080/realms/master/.well-known/openid-configuration" | jq .
|
||||
----
|
||||
|
||||
.Result:
|
||||
[source, bash]
|
||||
----
|
||||
# Frontend endpoints: request://request:request -> http://localhost:8080
|
||||
# Backend endpoints: request://request:request -> http://localhost:8080
|
||||
----
|
||||
|
||||
In this example of using a curl GET request, the result shows the current OpenID Discovery configuration.
|
||||
All base URLS are taken from the incoming request, so `http://localhost:8080` is used for all endpoints.
|
||||
|
||||
== Example Scenarios
|
||||
The following are more example scenarios and the corresponding commands for setting up a hostname.
|
||||
|
||||
=== Assumptions for all scenarios
|
||||
* Keycloak is set up using HTTPS certificates and Port 8443.
|
||||
* `intlUrl` refers to an internal IP/DNS for Keycloak.
|
||||
* `myUrl` refers to an exposed public URL
|
||||
* Keycloak runs in production mode using the `start` command.
|
||||
Note that the `start` command requires setting up TLS. The corresponding options are not shown for example purposes. For more details,
|
||||
see <@links.server id="enabletls"/> guide.
|
||||
|
||||
=== Example 1: Hostname configuration without reverse proxy
|
||||
.Keycloak configuration:
|
||||
<@kc.start parameters="--hostname=myurl"/>
|
||||
=== Exposing the server behind a TLS termination proxy
|
||||
|
||||
.Invoked command:
|
||||
[source, bash]
|
||||
----
|
||||
curl GET "https://intUrl:8443/realms/master/.well-known/openid-configuration" | jq .
|
||||
----
|
||||
In this example, the server is running behind a TLS termination proxy and publicly available from `https://mykeycloak`.
|
||||
|
||||
.Result:
|
||||
[source, bash]
|
||||
----
|
||||
# Frontend Endpoints: request://myurl:request -> https://myurl:8443
|
||||
# Backend Endpoints: request://request:request -> https://internal:8443
|
||||
----
|
||||
.Configuration:
|
||||
<@kc.start parameters="--hostname=mykeycloak --proxy-edge"/>
|
||||
|
||||
=== Example 2: Hostname configuration without reverse proxy - strict backchannel enabled
|
||||
=== Exposing the server without a proxy
|
||||
|
||||
In this example, the server is running without a proxy and exposed using a URL using HTTPS.
|
||||
|
||||
.Keycloak configuration:
|
||||
<@kc.start parameters="--hostname=myurl --hostname-strict-backchannel=true"/>
|
||||
<@kc.start parameters="--hostname-url=https://mykeycloak"/>
|
||||
|
||||
.Invoked command:
|
||||
[source, bash]
|
||||
----
|
||||
curl GET "https://intUrl:8443/realms/master/.well-known/openid-configuration" | jq .
|
||||
----
|
||||
It is highly recommended using a TLS termination proxy in front of the server for security and availability reasons. For more details,
|
||||
see the <@links.server id="reverseproxy"/> guide.
|
||||
|
||||
.Result:
|
||||
[source, bash]
|
||||
----
|
||||
# Frontend: request://myurl:request -> https://myurl:8443
|
||||
# Backend: request://myurl:request -> https://myurl:8443
|
||||
----
|
||||
=== Forcing backend endpoints to use the same URL the server is exposed
|
||||
|
||||
In this example, backend endpoints are exposed using the same URL used by the server so that clients always fetch the same URL
|
||||
regardless of the origin of the request.
|
||||
|
||||
=== Example 3: Hostname configuration with reverse proxy
|
||||
.Keycloak configuration:
|
||||
<@kc.start parameters="--hostname=myurl --proxy=passthrough"/>
|
||||
<@kc.start parameters="--hostname=mykeycloak --hostname-strict-backchannel=true"/>
|
||||
|
||||
.Invoked command:
|
||||
[source, bash]
|
||||
----
|
||||
curl GET "https://intUrl:8443/realms/master/.well-known/openid-configuration" | jq .
|
||||
----
|
||||
=== Exposing the server using a port other than the default ports
|
||||
|
||||
.Result:
|
||||
[source, bash]
|
||||
----
|
||||
# Frontend Endpoints: request://myurl -> https://myurl
|
||||
# Backend Endpoints: request://request:request -> https://internal:8443
|
||||
----
|
||||
In this example, the server is accessible using a port other than the default ports.
|
||||
|
||||
=== Hostname configuration with reverse proxy and different path
|
||||
.Keycloak configuration:
|
||||
<@kc.start parameters="--hostname=myurl --proxy=passthrough --hostname-path=mykeycloak"/>
|
||||
|
||||
.Invoked command:
|
||||
[source, bash]
|
||||
----
|
||||
curl GET "https://intUrl:8443/realms/master/.well-known/openid-configuration" | jq .
|
||||
----
|
||||
|
||||
.Result:
|
||||
[source, bash]
|
||||
----
|
||||
# Frontend Endpoints: request://myurl -> https://myurl/mykeycloak
|
||||
# Backend Endpoints: request://request:request -> https://internal:8443
|
||||
----
|
||||
<@kc.start parameters="--hostname-url=https://mykeycloak:8989"/>
|
||||
|
||||
</@tmpl.guide>
|
|
@ -7,11 +7,21 @@ public class HostnameOptions {
|
|||
.description("Hostname for the Keycloak server.")
|
||||
.build();
|
||||
|
||||
public static final Option HOSTNAME_URL = new OptionBuilder<>("hostname-url", String.class)
|
||||
.category(OptionCategory.HOSTNAME)
|
||||
.description("Set the base URL for frontend URLs, including scheme, host, port and path.")
|
||||
.build();
|
||||
|
||||
public static final Option HOSTNAME_ADMIN = new OptionBuilder<>("hostname-admin", String.class)
|
||||
.category(OptionCategory.HOSTNAME)
|
||||
.description("The hostname for accessing the administration console. Use this option if you are exposing the administration console using a hostname other than the value set to the 'hostname' option.")
|
||||
.build();
|
||||
|
||||
public static final Option HOSTNAME_ADMIN_URL = new OptionBuilder<>("hostname-admin-url", String.class)
|
||||
.category(OptionCategory.HOSTNAME)
|
||||
.description("Set the base URL for accessing the administration console, including scheme, host, port and path")
|
||||
.build();
|
||||
|
||||
public static final Option HOSTNAME_STRICT = new OptionBuilder<>("hostname-strict", Boolean.class)
|
||||
.category(OptionCategory.HOSTNAME)
|
||||
.description("Disables dynamically resolving the hostname from request headers. Should always be set to true in production, unless proxy verifies the Host header.")
|
||||
|
@ -20,7 +30,7 @@ public class HostnameOptions {
|
|||
|
||||
public static final Option HOSTNAME_STRICT_HTTPS = new OptionBuilder<>("hostname-strict-https", Boolean.class)
|
||||
.category(OptionCategory.HOSTNAME)
|
||||
.description("Forces URLs to use HTTPS. Only needed if proxy does not properly set the X-Forwarded-Proto header.")
|
||||
.description("Forces frontend URLs to use the 'https' scheme. If set to false, the HTTP scheme is inferred from requests.")
|
||||
.hidden()
|
||||
.defaultValue(Boolean.TRUE)
|
||||
.build();
|
||||
|
|
|
@ -14,10 +14,18 @@ final class HostnamePropertyMappers {
|
|||
.to("kc.spi-hostname-default-hostname")
|
||||
.paramLabel("hostname")
|
||||
.build(),
|
||||
fromOption(HostnameOptions.HOSTNAME_URL)
|
||||
.to("kc.spi-hostname-default-hostname-url")
|
||||
.paramLabel("url")
|
||||
.build(),
|
||||
fromOption(HostnameOptions.HOSTNAME_ADMIN)
|
||||
.to("kc.spi-hostname-default-admin")
|
||||
.paramLabel("hostname")
|
||||
.build(),
|
||||
fromOption(HostnameOptions.HOSTNAME_ADMIN_URL)
|
||||
.to("kc.spi-hostname-default-admin-url")
|
||||
.paramLabel("url")
|
||||
.build(),
|
||||
fromOption(HostnameOptions.HOSTNAME_STRICT)
|
||||
.to("kc.spi-hostname-default-strict")
|
||||
.build(),
|
||||
|
|
|
@ -22,7 +22,10 @@ import static org.keycloak.urls.UrlType.BACKEND;
|
|||
import static org.keycloak.urls.UrlType.FRONTEND;
|
||||
import static org.keycloak.utils.StringUtil.isNotBlank;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
@ -30,6 +33,7 @@ import org.jboss.logging.Logger;
|
|||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.common.util.Resteasy;
|
||||
import org.keycloak.config.HostnameOptions;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||
|
@ -44,7 +48,7 @@ public final class DefaultHostnameProvider implements HostnameProvider, Hostname
|
|||
private static final int DEFAULT_HTTPS_PORT_VALUE = 443;
|
||||
private static final int RESTEASY_DEFAULT_PORT_VALUE = -1;
|
||||
|
||||
private String frontChannelHostName;
|
||||
private String frontEndHostName;
|
||||
private String defaultPath;
|
||||
private String defaultHttpScheme;
|
||||
private int defaultTlsPort;
|
||||
|
@ -54,57 +58,58 @@ public final class DefaultHostnameProvider implements HostnameProvider, Hostname
|
|||
private boolean hostnameEnabled;
|
||||
private boolean strictHttps;
|
||||
private int hostnamePort;
|
||||
private URI frontEndBaseUri;
|
||||
private URI adminBaseUri;
|
||||
|
||||
@Override
|
||||
public String getScheme(UriInfo originalUriInfo, UrlType urlType) {
|
||||
if (ADMIN.equals(urlType)) {
|
||||
return fromBaseUriOrDefault(URI::getScheme, adminBaseUri, getScheme(originalUriInfo));
|
||||
}
|
||||
|
||||
String scheme = forNonStrictBackChannel(originalUriInfo, urlType, this::getScheme, this::getScheme);
|
||||
|
||||
if (scheme != null) {
|
||||
return scheme;
|
||||
}
|
||||
|
||||
if (ADMIN.equals(urlType)) {
|
||||
return getScheme(originalUriInfo);
|
||||
}
|
||||
|
||||
return fromFrontChannel(originalUriInfo, URI::getScheme, this::getScheme, defaultHttpScheme);
|
||||
return fromFrontEndUrl(originalUriInfo, URI::getScheme, this::getScheme, defaultHttpScheme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHostname(UriInfo originalUriInfo, UrlType urlType) {
|
||||
if (ADMIN.equals(urlType)) {
|
||||
return fromBaseUriOrDefault(URI::getHost, adminBaseUri, adminHostName == null ? getHostname(originalUriInfo) : adminHostName);
|
||||
}
|
||||
|
||||
String hostname = forNonStrictBackChannel(originalUriInfo, urlType, this::getHostname, this::getHostname);
|
||||
|
||||
if (hostname != null) {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
if (ADMIN.equals(urlType)) {
|
||||
return adminHostName == null ? getHostname(originalUriInfo) : adminHostName;
|
||||
}
|
||||
|
||||
return fromFrontChannel(originalUriInfo, URI::getHost, this::getHostname, frontChannelHostName);
|
||||
return fromFrontEndUrl(originalUriInfo, URI::getHost, this::getHostname, frontEndHostName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContextPath(UriInfo originalUriInfo, UrlType urlType) {
|
||||
if (ADMIN.equals(urlType)) {
|
||||
return fromBaseUriOrDefault(URI::getPath, adminBaseUri, getContextPath(originalUriInfo));
|
||||
}
|
||||
|
||||
String path = forNonStrictBackChannel(originalUriInfo, urlType, this::getContextPath, this::getContextPath);
|
||||
|
||||
if (path != null) {
|
||||
return path;
|
||||
}
|
||||
|
||||
if (ADMIN.equals(urlType)) {
|
||||
// for admin we always resolve to the request path
|
||||
return getContextPath(originalUriInfo);
|
||||
}
|
||||
|
||||
return fromFrontChannel(originalUriInfo, URI::getPath, this::getContextPath, defaultPath);
|
||||
return fromFrontEndUrl(originalUriInfo, URI::getPath, this::getContextPath, defaultPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort(UriInfo originalUriInfo, UrlType urlType) {
|
||||
if (ADMIN.equals(urlType)) {
|
||||
return getRequestPort();
|
||||
return fromBaseUriOrDefault(URI::getPort, adminBaseUri, getRequestPort());
|
||||
}
|
||||
|
||||
Integer port = forNonStrictBackChannel(originalUriInfo, urlType, this::getPort, this::getPort);
|
||||
|
@ -114,11 +119,10 @@ public final class DefaultHostnameProvider implements HostnameProvider, Hostname
|
|||
}
|
||||
|
||||
if (hostnameEnabled && !noProxy) {
|
||||
// if proxy is enabled and hostname is set, assume the server is exposed using default ports
|
||||
return hostnamePort;
|
||||
return fromBaseUriOrDefault(URI::getPort, frontEndBaseUri, hostnamePort);
|
||||
}
|
||||
|
||||
return fromFrontChannel(originalUriInfo, URI::getPort, this::getPort, hostnamePort == -1 ? getPort(originalUriInfo) : hostnamePort);
|
||||
return fromFrontEndUrl(originalUriInfo, URI::getPort, this::getPort, hostnamePort == -1 ? getPort(originalUriInfo) : hostnamePort);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -139,7 +143,7 @@ public final class DefaultHostnameProvider implements HostnameProvider, Hostname
|
|||
return null;
|
||||
}
|
||||
|
||||
private <T> T fromFrontChannel(UriInfo originalUriInfo, Function<URI, T> frontEndTypeResolver, Function<UriInfo, T> defaultResolver,
|
||||
private <T> T fromFrontEndUrl(UriInfo originalUriInfo, Function<URI, T> frontEndTypeResolver, Function<UriInfo, T> defaultResolver,
|
||||
T defaultValue) {
|
||||
URI frontEndUrl = getRealmFrontEndUrl();
|
||||
|
||||
|
@ -147,15 +151,18 @@ public final class DefaultHostnameProvider implements HostnameProvider, Hostname
|
|||
return frontEndTypeResolver.apply(frontEndUrl);
|
||||
}
|
||||
|
||||
return defaultValue == null ? defaultResolver.apply(originalUriInfo) : defaultValue;
|
||||
if (frontEndBaseUri != null) {
|
||||
return frontEndTypeResolver.apply(frontEndBaseUri);
|
||||
}
|
||||
|
||||
return defaultValue == null ? defaultResolver.apply(originalUriInfo) : defaultValue;
|
||||
}
|
||||
|
||||
private boolean isHostFromFrontEndUrl(UriInfo originalUriInfo) {
|
||||
String requestHost = getHostname(originalUriInfo);
|
||||
String frontendUrl = getHostname(originalUriInfo, FRONTEND);
|
||||
String frontendUrlHost = getHostname(originalUriInfo, FRONTEND);
|
||||
|
||||
if (requestHost.equals(frontendUrl)) {
|
||||
if (requestHost.equals(frontendUrlHost)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -205,21 +212,40 @@ public final class DefaultHostnameProvider implements HostnameProvider, Hostname
|
|||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
frontChannelHostName = config.get("hostname");
|
||||
frontEndHostName = config.get("hostname");
|
||||
|
||||
if (config.getBoolean("strict", false) && frontChannelHostName == null) {
|
||||
throw new RuntimeException("Strict hostname resolution configured but no hostname was set");
|
||||
try {
|
||||
String url = config.get("hostname-url");
|
||||
|
||||
if (url != null) {
|
||||
frontEndBaseUri = new URL(url).toURI();
|
||||
}
|
||||
} catch (MalformedURLException | URISyntaxException cause) {
|
||||
throw new RuntimeException("Invalid base URL for FrontEnd URLs: " + config.get("hostname-url"), cause);
|
||||
}
|
||||
|
||||
hostnameEnabled = frontChannelHostName != null;
|
||||
if (frontEndHostName != null && frontEndBaseUri != null) {
|
||||
throw new RuntimeException("You can not set both '" + HostnameOptions.HOSTNAME.getKey() + "' and '" + HostnameOptions.HOSTNAME_URL.getKey() + "' options");
|
||||
}
|
||||
|
||||
if (config.getBoolean("strict", false) && (frontEndHostName == null && frontEndBaseUri == null)) {
|
||||
throw new RuntimeException("Strict hostname resolution configured but no hostname setting provided");
|
||||
}
|
||||
|
||||
hostnameEnabled = (frontEndHostName != null || frontEndBaseUri != null);
|
||||
|
||||
if (frontEndBaseUri == null) {
|
||||
strictHttps = config.getBoolean("strict-https", false);
|
||||
} else {
|
||||
frontEndHostName = frontEndBaseUri.getHost();
|
||||
strictHttps = "https".equals(frontEndBaseUri.getScheme());
|
||||
}
|
||||
|
||||
if (strictHttps) {
|
||||
defaultHttpScheme = "https";
|
||||
}
|
||||
|
||||
defaultPath = config.get("path");
|
||||
defaultPath = config.get("path", frontEndBaseUri == null ? null : frontEndBaseUri.getPath());
|
||||
noProxy = Configuration.getConfigValue("kc.proxy").getValue().equals("false");
|
||||
defaultTlsPort = Integer.parseInt(Configuration.getConfigValue("kc.https-port").getValue());
|
||||
|
||||
|
@ -227,17 +253,43 @@ public final class DefaultHostnameProvider implements HostnameProvider, Hostname
|
|||
defaultTlsPort = RESTEASY_DEFAULT_PORT_VALUE;
|
||||
}
|
||||
|
||||
if (frontEndBaseUri == null) {
|
||||
hostnamePort = Integer.parseInt(Configuration.getConfigValue("kc.hostname-port").getValue());
|
||||
} else {
|
||||
hostnamePort = frontEndBaseUri.getPort();
|
||||
}
|
||||
|
||||
adminHostName = config.get("admin");
|
||||
|
||||
try {
|
||||
String url = config.get("admin-url");
|
||||
|
||||
if (url != null) {
|
||||
adminBaseUri = new URL(url).toURI();
|
||||
}
|
||||
} catch (MalformedURLException | URISyntaxException cause) {
|
||||
throw new RuntimeException("Invalid base URL for Admin URLs: " + config.get("admin-url"), cause);
|
||||
}
|
||||
|
||||
if (adminHostName != null && adminBaseUri != null) {
|
||||
throw new RuntimeException("You can not set both '" + HostnameOptions.HOSTNAME_ADMIN.getKey() + "' and '" + HostnameOptions.HOSTNAME_ADMIN_URL.getKey() + "' options");
|
||||
}
|
||||
|
||||
if (adminBaseUri != null) {
|
||||
adminHostName = adminBaseUri.getHost();
|
||||
}
|
||||
|
||||
strictBackChannel = config.getBoolean("strict-backchannel", false);
|
||||
|
||||
LOGGER.infov("Hostname settings: FrontEnd: {0}, Strict HTTPS: {1}, Path: {2}, Strict BackChannel: {3}, Admin: {4}, Port: {5}, Proxied: {6}",
|
||||
frontChannelHostName == null ? "<request>" : frontChannelHostName,
|
||||
LOGGER.infov("Hostname settings: Base URL: {0}, Hostname: {1}, Strict HTTPS: {2}, Path: {3}, Strict BackChannel: {4}, Admin URL: {5}, Admin: {6}, Port: {7}, Proxied: {8}",
|
||||
frontEndBaseUri == null ? "<unset>" : frontEndBaseUri,
|
||||
frontEndHostName == null ? frontEndBaseUri == null ? "<request>" : frontEndBaseUri : frontEndHostName,
|
||||
strictHttps,
|
||||
defaultPath == null ? "<request>" : defaultPath,
|
||||
defaultPath == null ? "<request>" : "".equals(defaultPath) ? "/" : defaultPath,
|
||||
strictBackChannel,
|
||||
adminHostName == null ? "<request>" : adminHostName,
|
||||
hostnamePort,
|
||||
adminBaseUri == null ? "<unset>" : adminBaseUri,
|
||||
adminHostName == null ? adminBaseUri == null ? "<request>" : adminBaseUri : adminHostName,
|
||||
String.valueOf(hostnamePort),
|
||||
!noProxy);
|
||||
}
|
||||
|
||||
|
@ -245,4 +297,12 @@ public final class DefaultHostnameProvider implements HostnameProvider, Hostname
|
|||
KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
|
||||
return session.getContext().getContextObject(HttpRequest.class).getUri().getBaseUri().getPort();
|
||||
}
|
||||
|
||||
private <T> T fromBaseUriOrDefault(Function<URI, T> resolver, URI baseUri, T defaultValue) {
|
||||
if (baseUri != null) {
|
||||
return resolver.apply(baseUri);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,6 +123,18 @@ public class HostnameDistTest {
|
|||
Assert.assertTrue(when().get("https://mykeycloak.127.0.0.1.nip.io:8443/realms/master/protocol/openid-connect/auth?client_id=security-admin-console&redirect_uri=https://mykeycloakadmin.127.0.0.1.nip.io:8443/admin/master/console&state=02234324-d91e-4bf2-8396-57498e96b12a&response_mode=fragment&response_type=code&scope=openid&nonce=f8f3812e-e349-4bbf-8d15-cbba4927f5e5&code_challenge=7qjD_v11WGkt1ig-ZFHxJdrEvuTlzjFRgRGQ_5ADcko&code_challenge_method=S256").asString().contains("Invalid parameter: redirect_uri"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Launch({ "start", "--proxy=edge", "--hostname-url=http://mykeycloak.127.0.0.1.nip.io:1234" })
|
||||
public void testFrontendUrl() {
|
||||
assertFrontEndUrl("https://mykeycloak.127.0.0.1.nip.io:8443", "http://mykeycloak.127.0.0.1.nip.io:1234/");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Launch({ "start", "--proxy=edge", "--hostname=mykeycloak.127.0.0.1.nip.io", "--hostname-admin-url=http://mykeycloakadmin.127.0.0.1.nip.io:1234" })
|
||||
public void testAdminUrl() {
|
||||
Assert.assertTrue(when().get("https://mykeycloak.127.0.0.1.nip.io:8443").asString().contains("http://mykeycloakadmin.127.0.0.1.nip.io:1234/admin/"));
|
||||
}
|
||||
|
||||
private OIDCConfigurationRepresentation getServerMetadata(String baseUrl) {
|
||||
return when().get(baseUrl + "/realms/master/.well-known/openid-configuration").as(OIDCConfigurationRepresentation.class);
|
||||
}
|
||||
|
|
|
@ -64,6 +64,13 @@ public class ProxyDistTest {
|
|||
assertXForwardedHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Launch({ "start-dev", "--hostname-url=http://mykeycloak.127.0.0.1.nip.io:1234", "--hostname-admin-url=http://mykeycloakadmin.127.0.0.1.nip.io:1234", "--proxy=edge" })
|
||||
public void testIgnoreForwardedHeadersWhenFrontendUrlSet() {
|
||||
given().header("X-Forwarded-Host", "test").when().get("http://mykeycloak.127.0.0.1.nip.io:8080").then().body(containsString("http://mykeycloakadmin.127.0.0.1.nip.io:1234/admin"));
|
||||
given().header("X-Forwarded-Proto", "https").when().get("http://localhost:8080").then().body(containsString("http://mykeycloakadmin.127.0.0.1.nip.io:1234/admin"));
|
||||
}
|
||||
|
||||
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"));
|
||||
|
|
|
@ -47,7 +47,7 @@ public class StartCommandDistTest extends StartCommandTest {
|
|||
@Test
|
||||
@Launch({ "start", "--http-enabled=true" })
|
||||
void failNoHostnameNotSet(LaunchResult result) {
|
||||
assertTrue(result.getErrorOutput().contains("ERROR: Strict hostname resolution configured but no hostname was set"),
|
||||
assertTrue(result.getErrorOutput().contains("ERROR: Strict hostname resolution configured but no hostname setting provided"),
|
||||
() -> "The Output:\n" + result.getOutput() + "doesn't contains the expected string.");
|
||||
}
|
||||
|
||||
|
|
|
@ -88,6 +88,9 @@ Hostname:
|
|||
The hostname for accessing the administration console. Use this option if you
|
||||
are exposing the administration console using a hostname other than the
|
||||
value set to the 'hostname' option.
|
||||
--hostname-admin-url <url>
|
||||
Set the base URL for accessing the administration console, including scheme,
|
||||
host, port and path
|
||||
--hostname-path <path>
|
||||
This should be set if proxy uses a different context-path for Keycloak.
|
||||
--hostname-port <port>
|
||||
|
@ -101,6 +104,7 @@ Hostname:
|
|||
By default backchannel URLs are dynamically resolved from request headers to
|
||||
allow internal and external applications. If all applications use the public
|
||||
URL this option should be enabled. Default: false.
|
||||
--hostname-url <url> Set the base URL for frontend URLs, including scheme, host, port and path.
|
||||
|
||||
HTTP/TLS:
|
||||
|
||||
|
|
|
@ -88,6 +88,9 @@ Hostname:
|
|||
The hostname for accessing the administration console. Use this option if you
|
||||
are exposing the administration console using a hostname other than the
|
||||
value set to the 'hostname' option.
|
||||
--hostname-admin-url <url>
|
||||
Set the base URL for accessing the administration console, including scheme,
|
||||
host, port and path
|
||||
--hostname-path <path>
|
||||
This should be set if proxy uses a different context-path for Keycloak.
|
||||
--hostname-port <port>
|
||||
|
@ -101,6 +104,7 @@ Hostname:
|
|||
By default backchannel URLs are dynamically resolved from request headers to
|
||||
allow internal and external applications. If all applications use the public
|
||||
URL this option should be enabled. Default: false.
|
||||
--hostname-url <url> Set the base URL for frontend URLs, including scheme, host, port and path.
|
||||
|
||||
HTTP/TLS:
|
||||
|
||||
|
|
|
@ -149,6 +149,9 @@ Hostname:
|
|||
The hostname for accessing the administration console. Use this option if you
|
||||
are exposing the administration console using a hostname other than the
|
||||
value set to the 'hostname' option.
|
||||
--hostname-admin-url <url>
|
||||
Set the base URL for accessing the administration console, including scheme,
|
||||
host, port and path
|
||||
--hostname-path <path>
|
||||
This should be set if proxy uses a different context-path for Keycloak.
|
||||
--hostname-port <port>
|
||||
|
@ -162,6 +165,7 @@ Hostname:
|
|||
By default backchannel URLs are dynamically resolved from request headers to
|
||||
allow internal and external applications. If all applications use the public
|
||||
URL this option should be enabled. Default: false.
|
||||
--hostname-url <url> Set the base URL for frontend URLs, including scheme, host, port and path.
|
||||
|
||||
HTTP/TLS:
|
||||
|
||||
|
|
|
@ -149,6 +149,9 @@ Hostname:
|
|||
The hostname for accessing the administration console. Use this option if you
|
||||
are exposing the administration console using a hostname other than the
|
||||
value set to the 'hostname' option.
|
||||
--hostname-admin-url <url>
|
||||
Set the base URL for accessing the administration console, including scheme,
|
||||
host, port and path
|
||||
--hostname-path <path>
|
||||
This should be set if proxy uses a different context-path for Keycloak.
|
||||
--hostname-port <port>
|
||||
|
@ -162,6 +165,7 @@ Hostname:
|
|||
By default backchannel URLs are dynamically resolved from request headers to
|
||||
allow internal and external applications. If all applications use the public
|
||||
URL this option should be enabled. Default: false.
|
||||
--hostname-url <url> Set the base URL for frontend URLs, including scheme, host, port and path.
|
||||
|
||||
HTTP/TLS:
|
||||
|
||||
|
|
|
@ -94,6 +94,9 @@ Hostname:
|
|||
The hostname for accessing the administration console. Use this option if you
|
||||
are exposing the administration console using a hostname other than the
|
||||
value set to the 'hostname' option.
|
||||
--hostname-admin-url <url>
|
||||
Set the base URL for accessing the administration console, including scheme,
|
||||
host, port and path
|
||||
--hostname-path <path>
|
||||
This should be set if proxy uses a different context-path for Keycloak.
|
||||
--hostname-port <port>
|
||||
|
@ -107,6 +110,7 @@ Hostname:
|
|||
By default backchannel URLs are dynamically resolved from request headers to
|
||||
allow internal and external applications. If all applications use the public
|
||||
URL this option should be enabled. Default: false.
|
||||
--hostname-url <url> Set the base URL for frontend URLs, including scheme, host, port and path.
|
||||
|
||||
HTTP/TLS:
|
||||
|
||||
|
|
|
@ -94,6 +94,9 @@ Hostname:
|
|||
The hostname for accessing the administration console. Use this option if you
|
||||
are exposing the administration console using a hostname other than the
|
||||
value set to the 'hostname' option.
|
||||
--hostname-admin-url <url>
|
||||
Set the base URL for accessing the administration console, including scheme,
|
||||
host, port and path
|
||||
--hostname-path <path>
|
||||
This should be set if proxy uses a different context-path for Keycloak.
|
||||
--hostname-port <port>
|
||||
|
@ -107,6 +110,7 @@ Hostname:
|
|||
By default backchannel URLs are dynamically resolved from request headers to
|
||||
allow internal and external applications. If all applications use the public
|
||||
URL this option should be enabled. Default: false.
|
||||
--hostname-url <url> Set the base URL for frontend URLs, including scheme, host, port and path.
|
||||
|
||||
HTTP/TLS:
|
||||
|
||||
|
|
|
@ -155,6 +155,9 @@ Hostname:
|
|||
The hostname for accessing the administration console. Use this option if you
|
||||
are exposing the administration console using a hostname other than the
|
||||
value set to the 'hostname' option.
|
||||
--hostname-admin-url <url>
|
||||
Set the base URL for accessing the administration console, including scheme,
|
||||
host, port and path
|
||||
--hostname-path <path>
|
||||
This should be set if proxy uses a different context-path for Keycloak.
|
||||
--hostname-port <port>
|
||||
|
@ -168,6 +171,7 @@ Hostname:
|
|||
By default backchannel URLs are dynamically resolved from request headers to
|
||||
allow internal and external applications. If all applications use the public
|
||||
URL this option should be enabled. Default: false.
|
||||
--hostname-url <url> Set the base URL for frontend URLs, including scheme, host, port and path.
|
||||
|
||||
HTTP/TLS:
|
||||
|
||||
|
|
|
@ -155,6 +155,9 @@ Hostname:
|
|||
The hostname for accessing the administration console. Use this option if you
|
||||
are exposing the administration console using a hostname other than the
|
||||
value set to the 'hostname' option.
|
||||
--hostname-admin-url <url>
|
||||
Set the base URL for accessing the administration console, including scheme,
|
||||
host, port and path
|
||||
--hostname-path <path>
|
||||
This should be set if proxy uses a different context-path for Keycloak.
|
||||
--hostname-port <port>
|
||||
|
@ -168,6 +171,7 @@ Hostname:
|
|||
By default backchannel URLs are dynamically resolved from request headers to
|
||||
allow internal and external applications. If all applications use the public
|
||||
URL this option should be enabled. Default: false.
|
||||
--hostname-url <url> Set the base URL for frontend URLs, including scheme, host, port and path.
|
||||
|
||||
HTTP/TLS:
|
||||
|
||||
|
|
|
@ -56,6 +56,9 @@ Hostname:
|
|||
The hostname for accessing the administration console. Use this option if you
|
||||
are exposing the administration console using a hostname other than the
|
||||
value set to the 'hostname' option.
|
||||
--hostname-admin-url <url>
|
||||
Set the base URL for accessing the administration console, including scheme,
|
||||
host, port and path
|
||||
--hostname-path <path>
|
||||
This should be set if proxy uses a different context-path for Keycloak.
|
||||
--hostname-port <port>
|
||||
|
@ -69,6 +72,7 @@ Hostname:
|
|||
By default backchannel URLs are dynamically resolved from request headers to
|
||||
allow internal and external applications. If all applications use the public
|
||||
URL this option should be enabled. Default: false.
|
||||
--hostname-url <url> Set the base URL for frontend URLs, including scheme, host, port and path.
|
||||
|
||||
HTTP/TLS:
|
||||
|
||||
|
|
|
@ -56,6 +56,8 @@ Hostname:
|
|||
The hostname for accessing the administration console. Use this option if you
|
||||
are exposing the administration console using a hostname other than the
|
||||
value set to the 'hostname' option.
|
||||
--hostname-admin-url <url>
|
||||
Set the base URL for accessing the administration console.
|
||||
--hostname-path <path>
|
||||
This should be set if proxy uses a different context-path for Keycloak.
|
||||
--hostname-port <port>
|
||||
|
@ -69,6 +71,7 @@ Hostname:
|
|||
By default backchannel URLs are dynamically resolved from request headers to
|
||||
allow internal and external applications. If all applications use the public
|
||||
URL this option should be enabled. Default: false.
|
||||
--hostname-url <url> Set the base URL for frontend URLs, including scheme, host, port and path.
|
||||
|
||||
HTTP/TLS:
|
||||
|
||||
|
|
|
@ -73,6 +73,9 @@ Hostname:
|
|||
The hostname for accessing the administration console. Use this option if you
|
||||
are exposing the administration console using a hostname other than the
|
||||
value set to the 'hostname' option.
|
||||
--hostname-admin-url <url>
|
||||
Set the base URL for accessing the administration console, including scheme,
|
||||
host, port and path
|
||||
--hostname-path <path>
|
||||
This should be set if proxy uses a different context-path for Keycloak.
|
||||
--hostname-port <port>
|
||||
|
@ -86,6 +89,7 @@ Hostname:
|
|||
By default backchannel URLs are dynamically resolved from request headers to
|
||||
allow internal and external applications. If all applications use the public
|
||||
URL this option should be enabled. Default: false.
|
||||
--hostname-url <url> Set the base URL for frontend URLs, including scheme, host, port and path.
|
||||
|
||||
HTTP/TLS:
|
||||
|
||||
|
|
|
@ -73,6 +73,9 @@ Hostname:
|
|||
The hostname for accessing the administration console. Use this option if you
|
||||
are exposing the administration console using a hostname other than the
|
||||
value set to the 'hostname' option.
|
||||
--hostname-admin-url <url>
|
||||
Set the base URL for accessing the administration console, including scheme,
|
||||
host, port and path
|
||||
--hostname-path <path>
|
||||
This should be set if proxy uses a different context-path for Keycloak.
|
||||
--hostname-port <port>
|
||||
|
@ -86,6 +89,7 @@ Hostname:
|
|||
By default backchannel URLs are dynamically resolved from request headers to
|
||||
allow internal and external applications. If all applications use the public
|
||||
URL this option should be enabled. Default: false.
|
||||
--hostname-url <url> Set the base URL for frontend URLs, including scheme, host, port and path.
|
||||
|
||||
HTTP/TLS:
|
||||
|
||||
|
|
Loading…
Reference in a new issue