Support management port for health and metrics (#27629)

* Support management port for health and metrics

Closes #19334

Signed-off-by: Martin Bartoš <mabartos@redhat.com>

* Deprecate option

Signed-off-by: Martin Bartoš <mabartos@redhat.com>

* Remove relativePath first-class citizen, rename ManagementSpec

Signed-off-by: Martin Bartoš <mabartos@redhat.com>

* Fix KeycloakDistConfiguratorTest

Signed-off-by: Martin Bartoš <mabartos@redhat.com>

---------

Signed-off-by: Martin Bartoš <mabartos@redhat.com>
This commit is contained in:
Martin Bartoš 2024-04-03 16:18:44 +02:00 committed by GitHub
parent 8ef3423f4a
commit 7f048300fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 1634 additions and 165 deletions

View file

@ -60,6 +60,8 @@ public final class Constants {
public static final Integer KEYCLOAK_DISCOVERY_SERVICE_PORT = 7800;
public static final String KEYCLOAK_DISCOVERY_TCP_PORT_NAME = "tcp";
public static final String KEYCLOAK_DISCOVERY_SERVICE_SUFFIX = "-discovery";
public static final Integer KEYCLOAK_MANAGEMENT_PORT = 9000;
public static final String KEYCLOAK_MANAGEMENT_PORT_NAME = "management";
public static final String KEYCLOAK_INGRESS_SUFFIX = "-ingress";
@ -72,4 +74,5 @@ public final class Constants {
public static final String CACHE_CONFIG_FOLDER = CONFIG_FOLDER + "/" + CACHE_CONFIG_SUBFOLDER;
public static final String KEYCLOAK_HTTP_RELATIVE_PATH_KEY = "http-relative-path";
public static final String KEYCLOAK_HTTP_MANAGEMENT_RELATIVE_PATH_KEY = "http-management-relative-path";
}

View file

@ -22,7 +22,6 @@ import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
import io.fabric8.kubernetes.api.model.EnvVarSource;
import io.fabric8.kubernetes.api.model.EnvVarSourceBuilder;
import io.fabric8.kubernetes.api.model.PodResourceClaim;
import io.fabric8.kubernetes.api.model.PodSpec;
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
import io.fabric8.kubernetes.api.model.Secret;
@ -45,6 +44,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.CacheSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpManagementSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.Truststore;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TruststoreSource;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpec;
@ -284,12 +284,13 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
containerBuilder.addToArgs(0, getJGroupsParameter(keycloakCR));
// probes
var tlsConfigured = isTlsConfigured(keycloakCR);
var protocol = !tlsConfigured ? "HTTP" : "HTTPS";
var kcPort = KeycloakServiceDependentResource.getServicePort(tlsConfigured, keycloakCR);
// Relative path ends with '/'
var kcRelativePath = readConfigurationValue(Constants.KEYCLOAK_HTTP_RELATIVE_PATH_KEY, keycloakCR, context)
var protocol = isTlsConfigured(keycloakCR) ? "HTTPS" : "HTTP";
var port = Optional.ofNullable(keycloakCR.getSpec())
.map(KeycloakSpec::getHttpManagementSpec)
.map(HttpManagementSpec::getPort)
.orElse(Constants.KEYCLOAK_MANAGEMENT_PORT);
var relativePath = readConfigurationValue(Constants.KEYCLOAK_HTTP_MANAGEMENT_RELATIVE_PATH_KEY, keycloakCR, context)
.or(() -> readConfigurationValue(Constants.KEYCLOAK_HTTP_RELATIVE_PATH_KEY, keycloakCR, context))
.map(path -> !path.endsWith("/") ? path + "/" : path)
.orElse("/");
@ -299,8 +300,8 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
.withFailureThreshold(3)
.withNewHttpGet()
.withScheme(protocol)
.withNewPort(kcPort)
.withPath(kcRelativePath + "health/ready")
.withNewPort(port)
.withPath(relativePath + "health/ready")
.endHttpGet()
.endReadinessProbe();
}
@ -310,8 +311,8 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
.withFailureThreshold(3)
.withNewHttpGet()
.withScheme(protocol)
.withNewPort(kcPort)
.withPath(kcRelativePath + "health/live")
.withNewPort(port)
.withPath(relativePath + "health/live")
.endHttpGet()
.endLivenessProbe();
}
@ -321,14 +322,14 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
.withFailureThreshold(600)
.withNewHttpGet()
.withScheme(protocol)
.withNewPort(kcPort)
.withPath(kcRelativePath + "health/started")
.withNewPort(port)
.withPath(relativePath + "health/started")
.endHttpGet()
.endStartupProbe();
}
// add in ports - there's no merging being done here
StatefulSet baseDeployment = containerBuilder
final StatefulSet baseDeployment = containerBuilder
.addNewPort()
.withName(Constants.KEYCLOAK_HTTPS_PORT_NAME)
.withContainerPort(Constants.KEYCLOAK_HTTPS_PORT)
@ -339,6 +340,11 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
.withContainerPort(Constants.KEYCLOAK_HTTP_PORT)
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL)
.endPort()
.addNewPort()
.withName(Constants.KEYCLOAK_MANAGEMENT_PORT_NAME)
.withContainerPort(Constants.KEYCLOAK_MANAGEMENT_PORT)
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL)
.endPort()
.endContainer().endSpec().endTemplate().endSpec().build();
return baseDeployment;

View file

@ -32,6 +32,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.spec.DatabaseSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpManagementSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.ProxySpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TransactionsSpec;
@ -70,6 +71,7 @@ public class KeycloakDistConfigurator {
configureDatabase();
configureCache();
configureProxy();
configureManagement();
}
/**
@ -137,6 +139,11 @@ public class KeycloakDistConfigurator {
.mapOption("proxy-headers", ProxySpec::getHeaders);
}
void configureManagement() {
optionMapper(keycloakCR -> keycloakCR.getSpec().getHttpManagementSpec())
.mapOption("http-management-port", HttpManagementSpec::getPort);
}
/* ---------- END of configuration of first-class citizen fields ---------- */
/**

View file

@ -29,7 +29,9 @@ import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDep
import org.keycloak.operator.Constants;
import org.keycloak.operator.Utils;
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpManagementSpec;
import java.util.Optional;
@ -56,13 +58,31 @@ public class KeycloakServiceDependentResource extends CRUDKubernetesDependentRes
Optional<HttpSpec> httpSpec = Optional.ofNullable(keycloak.getSpec().getHttpSpec());
boolean httpEnabled = httpSpec.map(HttpSpec::getHttpEnabled).orElse(false);
if (!tlsConfigured || httpEnabled) {
builder.addNewPort().withPort(getServicePort(false, keycloak)).withName(Constants.KEYCLOAK_HTTP_PORT_NAME)
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL).endPort();
builder.addNewPort()
.withPort(getServicePort(false, keycloak))
.withName(Constants.KEYCLOAK_HTTP_PORT_NAME)
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL)
.endPort();
}
if (tlsConfigured) {
builder.addNewPort().withPort(getServicePort(true, keycloak)).withName(Constants.KEYCLOAK_HTTPS_PORT_NAME)
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL).endPort();
builder.addNewPort()
.withPort(getServicePort(true, keycloak))
.withName(Constants.KEYCLOAK_HTTPS_PORT_NAME)
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL)
.endPort();
}
var managementPort = Optional.ofNullable(keycloak.getSpec())
.map(KeycloakSpec::getHttpManagementSpec)
.map(HttpManagementSpec::getPort)
.orElse(Constants.KEYCLOAK_MANAGEMENT_PORT);
builder.addNewPort()
.withPort(managementPort)
.withName(Constants.KEYCLOAK_MANAGEMENT_PORT_NAME)
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL)
.endPort();
return builder.build();
}

View file

@ -24,6 +24,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.spec.CacheSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.DatabaseSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpManagementSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.ProxySpec;
@ -105,6 +106,10 @@ public class KeycloakSpec {
@JsonPropertyDescription("In this section you can configure Keycloak's reverse proxy setting")
private ProxySpec proxySpec;
@JsonProperty("httpManagement")
@JsonPropertyDescription("In this section you can configure Keycloak's management interface setting.")
private HttpManagementSpec httpManagementSpec;
public HttpSpec getHttpSpec() {
return httpSpec;
}
@ -185,6 +190,14 @@ public class KeycloakSpec {
this.imagePullSecrets = imagePullSecrets;
}
public HttpManagementSpec getHttpManagementSpec() {
return httpManagementSpec;
}
public void setHttpManagementSpec(HttpManagementSpec httpManagementSpec) {
this.httpManagementSpec = httpManagementSpec;
}
public List<ValueOrSecret> getAdditionalOptions() {
if (this.additionalOptions == null) {
this.additionalOptions = new ArrayList<>();

View file

@ -0,0 +1,38 @@
/*
* Copyright 2024 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.operator.crds.v2alpha1.deployment.spec;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import io.sundr.builder.annotations.Buildable;
import org.keycloak.operator.Constants;
@JsonInclude(JsonInclude.Include.NON_NULL)
@Buildable(editableEnabled = false, builderPackage = "io.fabric8.kubernetes.api.builder")
public class HttpManagementSpec {
@JsonPropertyDescription("Port of the management interface.")
private Integer port = Constants.KEYCLOAK_MANAGEMENT_PORT;
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
}

View file

@ -287,6 +287,7 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
deployKeycloak(k8sclient, kc, true);
assertKeycloakAccessibleViaService(kc, false, Constants.KEYCLOAK_HTTP_PORT);
assertManagementInterfaceAccessibleViaService(kc, false);
}
@Test
@ -296,6 +297,9 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
deployKeycloak(k8sclient, kc, true);
assertKeycloakAccessibleViaService(kc, false, Constants.KEYCLOAK_HTTP_PORT);
// if TLS is enabled, management interface should use https
assertManagementInterfaceAccessibleViaService(kc, true);
}
@Test
@ -723,7 +727,7 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
String serviceName = KeycloakServiceDependentResource.getServiceName(kc);
assertThat(k8sclient.resources(Service.class).withName(serviceName).require().getSpec().getPorts()
.stream().map(ServicePort::getName).anyMatch(protocol::equals));
.stream().map(ServicePort::getName).anyMatch(protocol::equals)).isTrue();
String url = protocol + "://" + serviceName + "." + namespace + ":" + port + "/admin/master/console/";
Log.info("Checking url: " + url);
@ -734,4 +738,23 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
assertEquals("200", curlOutput);
});
}
private void assertManagementInterfaceAccessibleViaService(Keycloak kc, boolean https) {
Awaitility.await()
.ignoreExceptions()
.untilAsserted(() -> {
String serviceName = KeycloakServiceDependentResource.getServiceName(kc);
assertThat(k8sclient.resources(Service.class).withName(serviceName).require().getSpec().getPorts()
.stream().map(ServicePort::getName).anyMatch(Constants.KEYCLOAK_MANAGEMENT_PORT_NAME::equals)).isTrue();
String protocol = https ? "https" : "http";
String url = protocol + "://" + serviceName + "." + namespace + ":" + Constants.KEYCLOAK_MANAGEMENT_PORT;
Log.info("Checking url: " + url);
var curlOutput = K8sUtils.inClusterCurl(k8sclient, namespace, url);
Log.info("Curl Output: " + curlOutput);
assertEquals("200", curlOutput);
});
}
}

View file

@ -61,9 +61,6 @@ public class KeycloakServicesTest extends BaseOperatorTest {
currentService.getSpec().setSessionAffinity("ClientIP");
var origSpecs = new ServiceSpecBuilder(currentService.getSpec()).build(); // deep copy
// a managed change
currentService.getSpec().getPorts().get(0).setName(null);
currentService.getMetadata().getLabels().putAll(labels);
currentService.getMetadata().setResourceVersion(null);

View file

@ -26,6 +26,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.DatabaseSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpManagementSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TransactionsSpec;
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
import org.keycloak.operator.testsuite.utils.K8sUtils;
@ -89,6 +90,10 @@ public class CRSerializationTest {
assertEquals("usernameSecretKey", databaseSpec.getUsernameSecret().getKey());
assertEquals("passwordSecret", databaseSpec.getPasswordSecret().getName());
assertEquals("passwordSecretKey", databaseSpec.getPasswordSecret().getKey());
HttpManagementSpec managementSpec = keycloak.getSpec().getHttpManagementSpec();
assertNotNull(managementSpec);
assertEquals(9003, managementSpec.getPort());
}
@Test

View file

@ -144,6 +144,15 @@ public class KeycloakDistConfiguratorTest {
testFirstClassCitizen(expectedValues);
}
@Test
public void management() {
final Map<String, String> expectedValues = new HashMap<>(Map.of(
"http-management-port", "9003"
));
testFirstClassCitizen(expectedValues);
}
/* UTILS */
private void testFirstClassCitizen(Map<String, String> expectedValues) {

View file

@ -55,6 +55,7 @@ import jakarta.inject.Inject;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@QuarkusTest
@ -341,7 +342,7 @@ public class PodTemplateTest {
@Test
public void testRelativePathHealthProbes() {
final Function<String, Container> setUpRelativePath = (path) -> getDeployment(null, new StatefulSet(),
spec -> spec.withAdditionalOptions(new ValueOrSecret("http-relative-path", path)))
spec -> spec.withAdditionalOptions(new ValueOrSecret("http-management-relative-path", path)))
.getSpec()
.getTemplate()
.getSpec()
@ -351,18 +352,22 @@ public class PodTemplateTest {
var first = setUpRelativePath.apply("/");
assertEquals("/health/ready", first.getReadinessProbe().getHttpGet().getPath());
assertEquals("/health/live", first.getLivenessProbe().getHttpGet().getPath());
assertEquals("/health/started", first.getStartupProbe().getHttpGet().getPath());
var second = setUpRelativePath.apply("some");
assertEquals("some/health/ready", second.getReadinessProbe().getHttpGet().getPath());
assertEquals("some/health/live", second.getLivenessProbe().getHttpGet().getPath());
assertEquals("some/health/started", second.getStartupProbe().getHttpGet().getPath());
var third = setUpRelativePath.apply("");
assertEquals("/health/ready", third.getReadinessProbe().getHttpGet().getPath());
assertEquals("/health/live", third.getLivenessProbe().getHttpGet().getPath());
assertEquals("/health/started", third.getStartupProbe().getHttpGet().getPath());
var fourth = setUpRelativePath.apply("/some/");
assertEquals("/some/health/ready", fourth.getReadinessProbe().getHttpGet().getPath());
assertEquals("/some/health/live", fourth.getLivenessProbe().getHttpGet().getPath());
assertEquals("/some/health/started", fourth.getStartupProbe().getHttpGet().getPath());
}
@Test
@ -372,10 +377,27 @@ public class PodTemplateTest {
// Act
var podTemplate = getDeployment(additionalPodTemplate).getSpec().getTemplate();
var container = podTemplate.getSpec().getContainers().get(0);
// Assert
assertThat(podTemplate.getSpec().getContainers().get(0).getArgs()).doesNotContain(KeycloakDeploymentDependentResource.OPTIMIZED_ARG);
assertThat(podTemplate.getSpec().getContainers().get(0).getEnv().stream().anyMatch(envVar -> envVar.getName().equals(KeycloakDeploymentDependentResource.KC_TRUSTSTORE_PATHS)));
assertNotNull(container);
assertThat(container.getArgs()).doesNotContain(KeycloakDeploymentDependentResource.OPTIMIZED_ARG);
assertThat(container.getEnv().stream()).anyMatch(envVar -> envVar.getName().equals(KeycloakDeploymentDependentResource.KC_TRUSTSTORE_PATHS));
var readiness = container.getReadinessProbe().getHttpGet();
assertNotNull(readiness);
assertThat(readiness.getPath()).isEqualTo("/health/ready");
assertThat(readiness.getPort().getIntVal()).isEqualTo(Constants.KEYCLOAK_MANAGEMENT_PORT);
var liveness = container.getLivenessProbe().getHttpGet();
assertNotNull(liveness);
assertThat(liveness.getPath()).isEqualTo("/health/live");
assertThat(liveness.getPort().getIntVal()).isEqualTo(Constants.KEYCLOAK_MANAGEMENT_PORT);
var startup = container.getStartupProbe().getHttpGet();
assertNotNull(startup);
assertThat(startup.getPath()).isEqualTo("/health/started");
assertThat(startup.getPort().getIntVal()).isEqualTo(Constants.KEYCLOAK_MANAGEMENT_PORT);
}
@Test

View file

@ -69,6 +69,8 @@ spec:
x:
secret:
name: my-secret
httpManagement:
port: 9003
unsupported:
podTemplate:
metadata:

View file

@ -0,0 +1,104 @@
/*
* Copyright 2024 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.config;
import java.io.File;
import java.util.List;
/**
* Options for the management interface that handles management endpoints (f.e. health and metrics endpoints)
*/
public class ManagementOptions {
public static final Option<Boolean> LEGACY_OBSERVABILITY_INTERFACE = new OptionBuilder<>("legacy-observability-interface", Boolean.class)
.category(OptionCategory.MANAGEMENT)
.deprecated()
.description("If metrics/health endpoints should be exposed on the main HTTP server (not recommended). If set to true, the management interface is disabled.")
.defaultValue(Boolean.FALSE)
.buildTime(true)
.build();
public static final Option<String> HTTP_MANAGEMENT_RELATIVE_PATH = new OptionBuilder<>("http-management-relative-path", String.class)
.category(OptionCategory.MANAGEMENT)
.description("Set the path relative to '/' for serving resources from management interface. The path must start with a '/'. If not given, the value is inherited from HTTP options.")
.defaultValue("/")
.buildTime(true)
.build();
public static final Option<Integer> HTTP_MANAGEMENT_PORT = new OptionBuilder<>("http-management-port", Integer.class)
.category(OptionCategory.MANAGEMENT)
.description("Port of the management interface.")
.defaultValue(9000)
.build();
public static final Option<String> HTTP_MANAGEMENT_HOST = new OptionBuilder<>("http-management-host", String.class)
.hidden()
.category(OptionCategory.MANAGEMENT)
.description("Host of the management interface. If not given, the value is inherited from HTTP options.")
.defaultValue("0.0.0.0")
.build();
//HTTPS
public static final Option<HttpOptions.ClientAuth> HTTPS_MANAGEMENT_CLIENT_AUTH = new OptionBuilder<>("https-management-client-auth", HttpOptions.ClientAuth.class)
.category(OptionCategory.MANAGEMENT)
.description("Configures the management interface to require/request client authentication. If not given, the value is inherited from HTTP options.")
.defaultValue(HttpOptions.ClientAuth.none)
.buildTime(true)
.build();
public static final Option<String> HTTPS_MANAGEMENT_CIPHER_SUITES = new OptionBuilder<>("https-management-cipher-suites", String.class)
.hidden()
.category(OptionCategory.MANAGEMENT)
.description("The cipher suites to use for the management server. If not given, the value is inherited from HTTP options.")
.hidden()
.build();
public static final Option<List<String>> HTTPS_MANAGEMENT_PROTOCOLS = OptionBuilder.listOptionBuilder("https-management-protocols", String.class)
.hidden()
.category(OptionCategory.MANAGEMENT)
.description("The list of protocols to explicitly enable for the management server. If not given, the value is inherited from HTTP options.")
.defaultValue(List.of("TLSv1.3,TLSv1.2"))
.hidden()
.build();
public static final Option<File> HTTPS_MANAGEMENT_CERTIFICATE_FILE = new OptionBuilder<>("https-management-certificate-file", File.class)
.category(OptionCategory.MANAGEMENT)
.description("The file path to a server certificate or certificate chain in PEM format for the management server. If not given, the value is inherited from HTTP options.")
.build();
public static final Option<File> HTTPS_MANAGEMENT_CERTIFICATE_KEY_FILE = new OptionBuilder<>("https-management-certificate-key-file", File.class)
.category(OptionCategory.MANAGEMENT)
.description("The file path to a private key in PEM format for the management server. If not given, the value is inherited from HTTP options.")
.build();
public static final Option<File> HTTPS_MANAGEMENT_KEY_STORE_FILE = new OptionBuilder<>("https-management-key-store-file", File.class)
.category(OptionCategory.MANAGEMENT)
.description("The key store which holds the certificate information instead of specifying separate files for the management server. If not given, the value is inherited from HTTP options.")
.build();
public static final Option<String> HTTPS_MANAGEMENT_KEY_STORE_PASSWORD = new OptionBuilder<>("https-management-key-store-password", String.class)
.category(OptionCategory.MANAGEMENT)
.description("The password of the key store file for the management server. If not given, the value is inherited from HTTP options.")
.defaultValue("password")
.build();
public static final Option<String> HTTPS_MANAGEMENT_KEY_STORE_TYPE = new OptionBuilder<>("https-management-key-store-type", String.class)
.hidden()
.category(OptionCategory.MANAGEMENT)
.description("The type of the key store file for the management server. If not given, the value is inherited from HTTP options.")
.build();
}

View file

@ -3,13 +3,14 @@ package org.keycloak.config;
public enum OptionCategory {
// ordered by name asc
CACHE("Cache", 10, ConfigSupportLevel.SUPPORTED),
CONFIG("Config", 15, ConfigSupportLevel.SUPPORTED),
DATABASE("Database", 20, ConfigSupportLevel.SUPPORTED),
TRANSACTION("Transaction",30, ConfigSupportLevel.SUPPORTED),
FEATURE("Feature", 40, ConfigSupportLevel.SUPPORTED),
HOSTNAME("Hostname", 50, ConfigSupportLevel.SUPPORTED),
HTTP("HTTP(S)", 60, ConfigSupportLevel.SUPPORTED),
HEALTH("Health", 70, ConfigSupportLevel.SUPPORTED),
CONFIG("Config", 75, ConfigSupportLevel.SUPPORTED),
MANAGEMENT("Management", 75, ConfigSupportLevel.SUPPORTED),
METRICS("Metrics", 80, ConfigSupportLevel.SUPPORTED),
PROXY("Proxy", 90, ConfigSupportLevel.SUPPORTED),
VAULT("Vault", 100, ConfigSupportLevel.SUPPORTED),

View file

@ -35,5 +35,6 @@ USER 1000
EXPOSE 8080
EXPOSE 8443
EXPOSE 9000
ENTRYPOINT [ "/opt/keycloak/bin/kc.sh" ]

View file

@ -0,0 +1,28 @@
/*
* Copyright 2024 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.deployment;
import org.keycloak.quarkus.runtime.configuration.mappers.ManagementPropertyMappers;
import java.util.function.BooleanSupplier;
public class IsManagementEnabled implements BooleanSupplier {
@Override
public boolean getAsBoolean() {
return ManagementPropertyMappers.isManagementEnabled();
}
}

View file

@ -45,6 +45,8 @@ import io.quarkus.hibernate.orm.deployment.spi.AdditionalJpaModelBuildItem;
import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.runtime.configuration.ProfileManager;
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.smallrye.config.ConfigValue;
import org.eclipse.microprofile.health.Readiness;
import org.hibernate.cfg.AvailableSettings;
@ -69,6 +71,7 @@ import org.keycloak.common.crypto.FipsMode;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.config.DatabaseOptions;
import org.keycloak.config.HealthOptions;
import org.keycloak.config.ManagementOptions;
import org.keycloak.config.MetricsOptions;
import org.keycloak.config.SecurityOptions;
import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
@ -131,7 +134,6 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
@ -232,6 +234,21 @@ class KeycloakProcessor {
recorder.configureProfile(profile.getName(), profile.getFeatures());
}
@Record(ExecutionTime.STATIC_INIT)
@BuildStep(onlyIf = IsManagementEnabled.class)
@Consume(ConfigBuildItem.class)
void configureManagementInterface(BuildProducer<RouteBuildItem> routes,
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
KeycloakRecorder recorder) {
final var path = Configuration.getOptionalKcValue(ManagementOptions.HTTP_MANAGEMENT_RELATIVE_PATH.getKey()).orElse("/");
routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
.management()
.route(path)
.handler(recorder.getManagementHandler())
.build());
}
@Record(ExecutionTime.STATIC_INIT)
@BuildStep
@Consume(ConfigBuildItem.class)

View file

@ -0,0 +1,59 @@
/*
* Copyright 2020 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 test.org.keycloak.quarkus.services.health;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import static io.restassured.RestAssured.given;
class KeycloakMetricsConfigurationTest {
@BeforeEach
void setUpPort() {
RestAssured.port = 9001;
}
@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addAsResource("keycloak.conf", "META-INF/keycloak.conf"))
.overrideConfigKey("quarkus.micrometer.export.prometheus.path", "/prom/metrics");
@Test
void testMetrics() {
given().basePath("/")
.when().get("prom/metrics")
.then()
.statusCode(200);
}
@Test
void testWrongMetricsEndpoints() {
given().basePath("/")
.when().get("metrics")
.then()
// Metrics are available under `/prom/metrics` (see quarkus.micrometer.export.prometheus.path)
// so /metrics should return 404.
.statusCode(404);
}
}

View file

@ -18,6 +18,7 @@ package test.org.keycloak.quarkus.services.health;
import io.agroal.api.AgroalDataSource;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import org.hamcrest.Matchers;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
@ -41,6 +42,8 @@ public class KeycloakNegativeHealthCheckTest {
@Test
public void testReadinessDown() {
agroalDataSource.close();
RestAssured.port = 9001;
given()
.when().get("/health/ready")
.then()

View file

@ -17,8 +17,10 @@
package test.org.keycloak.quarkus.services.health;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@ -26,40 +28,18 @@ import static io.restassured.RestAssured.given;
class KeycloakPathConfigurationTest {
@BeforeEach
void setUpPort() {
RestAssured.port = 9001;
}
@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addAsResource("keycloak.conf", "META-INF/keycloak.conf"))
.overrideConfigKey("kc.http-relative-path","/auth")
.overrideConfigKey("quarkus.http.non-application-root-path", "/q")
.overrideConfigKey("quarkus.micrometer.export.prometheus.path", "/prom/metrics");
@Test
void testHealth() {
given().basePath("/")
.when().get("q/health")
.then()
.statusCode(200);
}
@Test
void testWrongHealthEndpoints() {
given().basePath("/")
.when().get("health")
.then()
// Health is available under `/q/health` (see non-application-root-path),
// so /health should return 404.
.statusCode(404);
given().basePath("/")
.when().get("auth/health")
.then()
// Health is available under `/q/health` (see non-application-root-path),
// so /auth/health one should return 404.
.statusCode(404);
}
@Test
void testMetrics() {
given().basePath("/")
@ -68,6 +48,20 @@ class KeycloakPathConfigurationTest {
.statusCode(200);
}
@Test
void testHealth() {
given().basePath("/")
.when().get("health")
.then()
// Health is available under `/auth/health` (see http-relative-path),
.statusCode(404);
given().basePath("/")
.when().get("auth/health")
.then()
.statusCode(200);
}
@Test
void testWrongMetricsEndpoints() {
given().basePath("/")

View file

@ -17,19 +17,23 @@
package test.org.keycloak.quarkus.services.health;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import org.hamcrest.Matchers;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import java.sql.SQLException;
import static io.restassured.RestAssured.given;
public class KeycloakReadyHealthCheckTest {
@BeforeEach
void setUpPort() {
RestAssured.port = 9001;
}
@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
@ -45,7 +49,7 @@ public class KeycloakReadyHealthCheckTest {
}
@Test
public void testReadinessUp() throws SQLException {
public void testReadinessUp() {
given()
.when().get("/health/ready")
.then()

View file

@ -30,6 +30,8 @@ import io.quarkus.agroal.DataSource;
import io.quarkus.arc.Arc;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeInitListener;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import liquibase.Scope;
import org.hibernate.cfg.AvailableSettings;
@ -73,6 +75,11 @@ public class KeycloakRecorder {
Profile.init(profileName, features);
}
// default handler for the management interface
public Handler<RoutingContext> getManagementHandler() {
return routingContext -> routingContext.response().end("Keycloak Management Interface");
}
public void configureTruststore() {
String[] truststores = Configuration.getOptionalKcValue(TruststoreOptions.TRUSTSTORE_PATHS.getKey())
.map(s -> s.split(",")).orElse(new String[0]);

View file

@ -52,6 +52,10 @@ public final class Configuration {
return getOptionalBooleanValue(NS_KEYCLOAK_PREFIX + option.getKey()).orElse(false);
}
public static boolean isTrue(String propertyName) {
return getOptionalBooleanValue(propertyName).orElse(false);
}
public static boolean contains(Option<?> option, String value) {
return getOptionalValue(NS_KEYCLOAK_PREFIX + option.getKey())
.filter(f -> f.contains(value))

View file

@ -0,0 +1,146 @@
/*
* Copyright 2024 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.configuration.mappers;
import io.smallrye.config.ConfigSourceInterceptorContext;
import org.keycloak.config.HttpOptions;
import org.keycloak.config.ManagementOptions;
import org.keycloak.quarkus.runtime.Messages;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import java.util.Optional;
import static org.keycloak.quarkus.runtime.configuration.Configuration.isTrue;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
public class ManagementPropertyMappers {
private static final String MANAGEMENT_ENABLED_MSG = "Management interface is enabled";
private ManagementPropertyMappers() {
}
public static PropertyMapper<?>[] getManagementPropertyMappers() {
return new PropertyMapper[]{
fromOption(ManagementOptions.LEGACY_OBSERVABILITY_INTERFACE)
.to("quarkus.management.enabled") // ATM, the management interface state is only based on the legacy-observability-interface property
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
.transformer(ManagementPropertyMappers::managementEnabledTransformer)
.build(),
fromOption(ManagementOptions.HTTP_MANAGEMENT_RELATIVE_PATH)
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
.mapFrom(HttpOptions.HTTP_RELATIVE_PATH.getKey())
.to("quarkus.management.root-path")
.paramLabel("path")
.build(),
fromOption(ManagementOptions.HTTP_MANAGEMENT_PORT)
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
.to("quarkus.management.port")
.paramLabel("port")
.build(),
fromOption(ManagementOptions.HTTP_MANAGEMENT_HOST)
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
.mapFrom(HttpOptions.HTTP_HOST.getKey())
.to("quarkus.management.host")
.paramLabel("host")
.build(),
// HTTPS
fromOption(ManagementOptions.HTTPS_MANAGEMENT_CLIENT_AUTH)
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
.mapFrom(HttpOptions.HTTPS_CLIENT_AUTH.getKey())
.to("quarkus.management.ssl.client-auth")
.paramLabel("auth")
.build(),
fromOption(ManagementOptions.HTTPS_MANAGEMENT_CIPHER_SUITES)
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
.mapFrom(HttpOptions.HTTPS_CIPHER_SUITES.getKey())
.to("quarkus.management.ssl.cipher-suites")
.paramLabel("ciphers")
.build(),
fromOption(ManagementOptions.HTTPS_MANAGEMENT_PROTOCOLS)
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
.mapFrom(HttpOptions.HTTPS_PROTOCOLS.getKey())
.to("quarkus.management.ssl.protocols")
.paramLabel("protocols")
.build(),
fromOption(ManagementOptions.HTTPS_MANAGEMENT_CERTIFICATE_FILE)
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
.mapFrom(HttpOptions.HTTPS_CERTIFICATE_FILE.getKey())
.to("quarkus.management.ssl.certificate.files")
.validator((mapper, value) -> validateTlsProperties())
.paramLabel("file")
.build(),
fromOption(ManagementOptions.HTTPS_MANAGEMENT_CERTIFICATE_KEY_FILE)
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
.mapFrom(HttpOptions.HTTPS_CERTIFICATE_KEY_FILE.getKey())
.to("quarkus.management.ssl.certificate.key-files")
.validator((mapper, value) -> validateTlsProperties())
.paramLabel("file")
.build(),
fromOption(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_FILE)
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
.mapFrom(HttpOptions.HTTPS_KEY_STORE_FILE.getKey())
.to("quarkus.management.ssl.certificate.key-store-file")
.validator((mapper, value) -> validateTlsProperties())
.paramLabel("file")
.build(),
fromOption(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_PASSWORD)
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
.mapFrom(HttpOptions.HTTPS_KEY_STORE_PASSWORD.getKey())
.to("quarkus.management.ssl.certificate.key-store-password")
.validator((mapper, value) -> validateTlsProperties())
.paramLabel("password")
.isMasked(true)
.build(),
fromOption(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_TYPE)
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
.mapFrom(HttpOptions.HTTPS_KEY_STORE_TYPE.getKey())
.to("quarkus.management.ssl.certificate.key-store-file-type")
.transformer((value, config) -> value.or(() -> Configuration.getOptionalKcValue(HttpOptions.HTTPS_KEY_STORE_TYPE.getKey())))
.paramLabel("type")
.build(),
};
}
public static boolean isManagementEnabled() {
return isTrue("quarkus.management.enabled");
}
public static boolean isManagementTlsEnabled() {
var key = Configuration.getOptionalKcValue(ManagementOptions.HTTPS_MANAGEMENT_CERTIFICATE_KEY_FILE.getKey());
var cert = Configuration.getOptionalKcValue(ManagementOptions.HTTPS_MANAGEMENT_CERTIFICATE_FILE.getKey());
if (key.isPresent() && cert.isPresent()) return true;
var keystore = Configuration.getOptionalKcValue(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_FILE.getKey());
return keystore.isPresent();
}
private static void validateTlsProperties() {
var isHttpEnabled = Configuration.isTrue(HttpOptions.HTTP_ENABLED);
if (!isHttpEnabled && !isManagementTlsEnabled()) {
throw new PropertyException(Messages.httpsConfigurationNotSet());
}
}
private static Optional<String> managementEnabledTransformer(Optional<String> value, ConfigSourceInterceptorContext ctx) {
if (value.isPresent()) {
var b = Boolean.parseBoolean(value.get());
return Optional.of(Boolean.toString(!b)); // negate the output
}
return Optional.of(Boolean.TRUE.toString());
}
}

View file

@ -52,6 +52,7 @@ public final class PropertyMappers {
MAPPERS.addAll(HttpPropertyMappers.getHttpPropertyMappers());
MAPPERS.addAll(HealthPropertyMappers.getHealthPropertyMappers());
MAPPERS.addAll(ConfigKeystorePropertyMappers.getConfigKeystorePropertyMappers());
MAPPERS.addAll(ManagementPropertyMappers.getManagementPropertyMappers());
MAPPERS.addAll(MetricsPropertyMappers.getMetricsPropertyMappers());
MAPPERS.addAll(ProxyPropertyMappers.getProxyPropertyMappers());
MAPPERS.addAll(VaultPropertyMappers.getVaultPropertyMappers());

View file

@ -77,6 +77,10 @@ public class ConfigurationTest {
}
}
public static void putEnvVars(Map<String, String> map) {
map.forEach(ConfigurationTest::putEnvVar);
}
@SuppressWarnings("unchecked")
public static void removeEnvVar(String name) {
Map<String, String> env = System.getenv();
@ -573,7 +577,7 @@ public class ConfigurationTest {
assertEquals("secret", secret.getValue());
}
private Config.Scope initConfig(String... scope) {
protected Config.Scope initConfig(String... scope) {
Config.init(new MicroProfileConfigProvider(createConfig()));
return Config.scope(scope);
}

View file

@ -0,0 +1,277 @@
/*
* Copyright 2024 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.configuration.test;
import org.junit.Test;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.mappers.ManagementPropertyMappers;
import java.util.Map;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
public class ManagementConfigurationTest extends ConfigurationTest {
@Test
public void managementDefaults() {
initConfig();
assertConfig(Map.of(
"http-management-port", "9000",
"http-management-relative-path", "/",
"http-management-host", "0.0.0.0"
));
assertManagementEnabled(true);
assertManagementHttpsEnabled(false);
}
@Test
public void managementBasicChanges() {
putEnvVars(Map.of(
"KC_HTTP_MANAGEMENT_PORT", "9999",
"KC_HTTP_MANAGEMENT_RELATIVE_PATH", "/management2",
"KC_HTTP_MANAGEMENT_HOST", "somehost"
));
initConfig();
assertConfig(Map.of(
"http-management-port", "9999",
"http-management-relative-path", "/management2",
"http-relative-path", "/",
"http-management-host", "somehost"
));
assertManagementEnabled(true);
}
@Test
public void managementRelativePath() {
putEnvVar("KC_HTTP_RELATIVE_PATH", "/management3");
initConfig();
assertConfig(Map.of(
"http-management-relative-path", "/management3",
"http-relative-path", "/management3"
));
assertManagementEnabled(true);
}
@Test
public void managementHttpsValues() {
putEnvVars(Map.of(
"KC_HTTP_MANAGEMENT_HOST", "host1",
"KC_HTTPS_MANAGEMENT_CLIENT_AUTH", "requested",
"KC_HTTPS_MANAGEMENT_CIPHER_SUITES", "some-cipher-suite1",
"KC_HTTPS_MANAGEMENT_PROTOCOLS", "TLSv1.3",
"KC_HTTPS_MANAGEMENT_CERTIFICATE_FILE", "/some/path/s.crt.pem",
"KC_HTTPS_MANAGEMENT_CERTIFICATE_KEY_FILE", "/some/path/s.key.pem",
"KC_HTTPS_MANAGEMENT_KEY_STORE_FILE", "keystore123.p12",
"KC_HTTPS_MANAGEMENT_KEY_STORE_PASSWORD", "ultra-password123",
"KC_HTTPS_MANAGEMENT_KEY_STORE_TYPE", "BCFKS-0.1"
));
initConfig();
assertConfig(Map.of(
"http-management-host", "host1",
"https-management-client-auth", "requested",
"https-management-cipher-suites", "some-cipher-suite1",
"https-management-protocols", "TLSv1.3",
"https-management-certificate-file", "/some/path/s.crt.pem",
"https-management-certificate-key-file", "/some/path/s.key.pem",
"https-management-key-store-file", "keystore123.p12",
"https-management-key-store-password", "ultra-password123",
"https-management-key-store-type", "BCFKS-0.1"
));
assertManagementEnabled(true);
assertManagementHttpsEnabled(true);
}
@Test
public void managementMappedValues() {
putEnvVars(Map.of(
"KC_HTTP_HOST", "host123",
"KC_HTTPS_CLIENT_AUTH", "required",
"KC_HTTPS_CIPHER_SUITES", "some-cipher-suite",
"KC_HTTPS_PROTOCOLS", "TLSv1.2",
"KC_HTTPS_CERTIFICATE_FILE", "/some/path/srv.crt.pem",
"KC_HTTPS_CERTIFICATE_KEY_FILE", "/some/path/srv.key.pem",
"KC_HTTPS_KEY_STORE_FILE", "keystore.p12",
"KC_HTTPS_KEY_STORE_PASSWORD", "ultra-password",
"KC_HTTPS_KEY_STORE_TYPE", "BCFKS"
));
initConfig();
assertConfig(Map.of(
"http-management-host", "host123",
"https-management-client-auth", "required",
"https-management-cipher-suites", "some-cipher-suite",
"https-management-protocols", "TLSv1.2",
"https-management-certificate-file", "/some/path/srv.crt.pem",
"https-management-certificate-key-file", "/some/path/srv.key.pem",
"https-management-key-store-file", "keystore.p12",
"https-management-key-store-password", "ultra-password",
"https-management-key-store-type", "BCFKS"
));
assertManagementEnabled(true);
assertManagementHttpsEnabled(true);
}
@Test
public void managementDefaultHttps() {
putEnvVars(Map.of(
"KC_HTTPS_CERTIFICATE_FILE", "/some/path/srv.crt.pem",
"KC_HTTPS_CERTIFICATE_KEY_FILE", "/some/path/srv.key.pem"
));
initConfig();
assertConfig(Map.of(
"https-certificate-file", "/some/path/srv.crt.pem",
"https-certificate-key-file", "/some/path/srv.key.pem",
"https-management-certificate-file", "/some/path/srv.crt.pem",
"https-management-certificate-key-file", "/some/path/srv.key.pem"
));
assertManagementEnabled(true);
assertManagementHttpsEnabled(true);
}
@Test
public void managementDefaultHttpsManagementProps() {
putEnvVars(Map.of(
"KC_HTTPS_MANAGEMENT_CERTIFICATE_FILE", "/some/path/srv.crt.pem",
"KC_HTTPS_MANAGEMENT_CERTIFICATE_KEY_FILE", "/some/path/srv.key.pem"
));
initConfig();
assertConfig(Map.of(
"https-management-certificate-file", "/some/path/srv.crt.pem",
"https-management-certificate-key-file", "/some/path/srv.key.pem"
));
assertManagementEnabled(true);
assertManagementHttpsEnabled(true);
}
@Test
public void managementDefaultHttpsCertDisabled() {
putEnvVar("KC_HTTPS_CERTIFICATE_FILE", "/some/path/srv.crt.pem");
initConfig();
assertConfig("https-management-certificate-file", "/some/path/srv.crt.pem");
assertManagementEnabled(true);
assertManagementHttpsEnabled(false);
}
@Test
public void managementDefaultHttpsKeyDisabled() {
putEnvVar("KC_HTTPS_CERTIFICATE_KEY_FILE", "/some/path/srv.key.pem");
initConfig();
assertConfig("https-management-certificate-key-file", "/some/path/srv.key.pem");
assertManagementEnabled(true);
assertManagementHttpsEnabled(false);
}
@Test
public void managementEnabledDefaultHttpsKeystore(){
putEnvVar("KC_HTTPS_KEY_STORE_FILE", "keystore.p12");
initConfig();
assertConfig(Map.of(
"https-key-store-file", "keystore.p12",
"https-management-key-store-file", "keystore.p12"
));
assertManagementEnabled(true);
assertManagementHttpsEnabled(true);
}
@Test
public void fipsKeystoreType(){
putEnvVar("KC_FIPS_MODE", "strict");
initConfig();
assertConfig(Map.of(
"https-key-store-type", "BCFKS",
"https-management-key-store-type", "BCFKS"
));
assertManagementEnabled(true);
}
@Test
public void keystoreType(){
putEnvVars(Map.of(
"KC_HTTPS_KEY_STORE_TYPE", "pkcs12",
"KC_HTTPS_MANAGEMENT_KEY_STORE_TYPE", "BCFKS"
));
initConfig();
assertConfig(Map.of(
"https-key-store-type", "pkcs12",
"https-management-key-store-type", "BCFKS"
));
assertManagementEnabled(true);
}
@Test
public void legacyObservabilityInterface() {
putEnvVar("KC_LEGACY_OBSERVABILITY_INTERFACE", "true");
initConfig();
assertConfig("legacy-observability-interface", "true");
assertManagementEnabled(false);
}
@Test
public void legacyObservabilityInterfaceFalse() {
putEnvVar("KC_LEGACY_OBSERVABILITY_INTERFACE", "false");
initConfig();
assertConfig("legacy-observability-interface", "false");
assertManagementEnabled(true);
}
private void assertManagementEnabled(boolean expected) {
assertThat("Expected value for Management interface state is different", ManagementPropertyMappers.isManagementEnabled(), is(expected));
}
private void assertManagementHttpsEnabled(boolean expected) {
assertThat("Expected value for Management HTTPS is different", ManagementPropertyMappers.isManagementTlsEnabled(), is(expected));
}
private void assertConfig(String key, String expectedValue) {
var value = Configuration.getKcConfigValue(key).getValue();
assertThat(String.format("Value is null for key '%s'", key), value, notNullValue());
assertThat(String.format("Different value for key '%s'", key), value, is(expectedValue));
}
private void assertConfig(Map<String, String> expectedValues) {
expectedValues.forEach(this::assertConfig);
}
}

View file

@ -29,7 +29,9 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@DistributionTest(keepAlive =true)
@DistributionTest(keepAlive = true,
requestPort = 9000,
containerExposedPorts = {8080, 9000})
public class HealthDistTest {
@Test
@ -83,7 +85,7 @@ public class HealthDistTest {
@Test
void testUsingRelativePath(KeycloakDistribution distribution) {
for (String relativePath : List.of("/auth", "/auth/", "auth")) {
distribution.run("start-dev", "--health-enabled=true", "--http-relative-path=" + relativePath);
distribution.run("start-dev", "--health-enabled=true", "--http-management-relative-path=" + relativePath);
if (!relativePath.endsWith("/")) {
relativePath = relativePath + "/";
}
@ -95,7 +97,7 @@ public class HealthDistTest {
@Test
void testMultipleRequests(KeycloakDistribution distribution) throws Exception {
for (String relativePath : List.of("/", "/auth/", "auth")) {
distribution.run("start-dev", "--health-enabled=true", "--http-relative-path=" + relativePath);
distribution.run("start-dev", "--health-enabled=true", "--http-management-relative-path=" + relativePath);
CompletableFuture future = CompletableFuture.completedFuture(null);
for (int i = 0; i < 3; i++) {
@ -123,7 +125,9 @@ public class HealthDistTest {
@Test
@Launch({ "start-dev", "--features=multi-site" })
void testLoadBalancerCheck() {
void testLoadBalancerCheck(KeycloakDistribution distribution) {
distribution.setRequestPort(8080);
when().get("/lb-check").then()
.statusCode(200);
}

View file

@ -0,0 +1,167 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.it.cli.dist;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DistributionType;
import org.keycloak.it.utils.KeycloakDistribution;
import java.net.SocketException;
import static io.restassured.RestAssured.when;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
@DistributionTest(keepAlive = true,
defaultOptions = {"--health-enabled=true", "--metrics-enabled=true"},
requestPort = 9000,
containerExposedPorts = {9000, 8080, 9005})
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ManagementDistTest {
@Test
@Order(1)
@Launch({"start", "--hostname=hostname", "--http-enabled=false"})
void testManagementNoHttps(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertNoMessage("Management interface listening on");
cliResult.assertError("Key material not provided to setup HTTPS. Please configure your keys/certificates or start the server in development mode.");
}
@Test
@Order(2)
@Launch({"start-dev", "--legacy-observability-interface=true"})
void testManagementDisabled(LaunchResult result, KeycloakDistribution distribution) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertNoMessage("Management interface listening on");
assertThrows(SocketException.class, () -> when().get("/"), "Connection refused must be thrown");
assertThrows(SocketException.class, () -> when().get("/health"), "Connection refused must be thrown");
distribution.setRequestPort(8080);
when().get("/health").then()
.statusCode(200);
}
@Test
@Order(3)
@Launch({"start-dev"})
void testManagementEnabled(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertMessage("Management interface listening on http://0.0.0.0:9000");
when().get("/").then()
.statusCode(200)
.and()
.body(is("Keycloak Management Interface"));
when().get("/health").then()
.statusCode(200);
when().get("/health/live").then()
.statusCode(200);
when().get("/health/ready").then()
.statusCode(200);
when().get("/metrics").then()
.statusCode(200);
}
@Test
@Launch({"start-dev", "--http-management-port=9005"})
void testManagementDifferentPort(LaunchResult result, KeycloakDistribution distribution) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertMessage("Management interface listening on http://0.0.0.0:9005");
distribution.setRequestPort(9005);
when().get("/").then()
.statusCode(200)
.and()
.body(is("Keycloak Management Interface"));
when().get("/health").then()
.statusCode(200);
when().get("/health/live").then()
.statusCode(200);
when().get("/health/ready").then()
.statusCode(200);
when().get("/metrics").then()
.statusCode(200);
}
@Test
@Launch({"start-dev", "--http-relative-path=/management2"})
void testManagementInheritedRelativePath(LaunchResult result) {
assertRelativePath(result, "/management2");
}
@Test
@Launch({"start-dev", "--http-management-relative-path=/management"})
void testManagementDifferentRelativePath(LaunchResult result) {
assertRelativePath(result, "/management");
}
private void assertRelativePath(LaunchResult result, String relativePath) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertMessage("Management interface listening on http://0.0.0.0:9000");
when().get(relativePath).then()
.statusCode(200)
.and()
.body(is("Keycloak Management Interface"));
when().get(relativePath + "/health").then()
.statusCode(200);
when().get("/health").then()
.statusCode(404);
when().get(relativePath + "/health/live").then()
.statusCode(200);
when().get(relativePath + "/health/ready").then()
.statusCode(200);
when().get(relativePath + "/metrics").then()
.statusCode(200);
when().get("/metrics").then()
.statusCode(404);
}
@Test
@Launch({"start-dev", "--http-management-host=localhost"})
void testManagementDifferentHost(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertMessage("Management interface listening on http://localhost:9000");
// If running in container, we cannot access the localhost due to network host settings
if (DistributionType.isContainerDist()) return;
when().get("/").then()
.statusCode(200)
.and()
.body(is("Keycloak Management Interface"));
when().get("/health").then()
.statusCode(200);
when().get("/health/live").then()
.statusCode(200);
when().get("/health/ready").then()
.statusCode(200);
when().get("/metrics").then()
.statusCode(200);
}
}

View file

@ -0,0 +1,65 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.it.cli.dist;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
import io.restassured.RestAssured;
import io.restassured.config.RedirectConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.RawDistOnly;
import static io.restassured.RestAssured.when;
import static org.hamcrest.CoreMatchers.is;
@DistributionTest(keepAlive = true,
enableTls = true,
defaultOptions = {"--health-enabled=true", "--metrics-enabled=true"},
requestPort = 9000)
@RawDistOnly(reason = "We do not test TLS in containers")
public class ManagementHttpsDistTest {
@BeforeEach
public void setRestAssuredHttps() {
RestAssured.useRelaxedHTTPSValidation();
RestAssured.config = RestAssured.config.redirect(RedirectConfig.redirectConfig().followRedirects(false));
}
@Test
@Launch({"start-dev"})
public void simpleHttpsStartDev(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
var url = "https://localhost:9000";
cliResult.assertMessage("Management interface listening on https://0.0.0.0:9000");
when().get(url).then()
.statusCode(200)
.and()
.body(is("Keycloak Management Interface"));
when().get(url + "/health").then()
.statusCode(200);
when().get(url + "/health/live").then()
.statusCode(200);
when().get(url + "/health/ready").then()
.statusCode(200);
when().get(url + "/metrics").then()
.statusCode(200);
}
}

View file

@ -30,7 +30,9 @@ import org.keycloak.it.utils.KeycloakDistribution;
import io.quarkus.test.junit.main.Launch;
@DistributionTest(keepAlive =true)
@DistributionTest(keepAlive = true,
requestPort = 9000,
containerExposedPorts = {8080, 9000})
public class MetricsDistTest {
@Test
@ -67,7 +69,7 @@ public class MetricsDistTest {
@Test
void testUsingRelativePath(KeycloakDistribution distribution) {
for (String relativePath : List.of("/auth", "/auth/", "auth")) {
distribution.run("start-dev", "--metrics-enabled=true", "--http-relative-path=" + relativePath);
distribution.run("start-dev", "--metrics-enabled=true", "--http-management-relative-path=" + relativePath);
if (!relativePath.endsWith("/")) {
relativePath = relativePath + "/";
}
@ -79,8 +81,8 @@ public class MetricsDistTest {
@Test
void testMultipleRequests(KeycloakDistribution distribution) throws Exception {
for (String relativePath : List.of("/", "/auth/", "auth")) {
distribution.run("start-dev", "--metrics-enabled=true", "--http-relative-path=" + relativePath);
CompletableFuture future = CompletableFuture.completedFuture(null);
distribution.run("start-dev", "--metrics-enabled=true", "--http-management-relative-path=" + relativePath);
CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
for (int i = 0; i < 3; i++) {
future = CompletableFuture.allOf(CompletableFuture.runAsync(new Runnable() {

View file

@ -26,6 +26,7 @@ import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTI
import java.util.Optional;
import java.util.function.Consumer;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
@ -121,6 +122,7 @@ public class QuarkusPropertiesDistTest {
void testUnknownQuarkusBuildTimePropertyApplied(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertNoBuild();
RestAssured.port = 9000;
when().get("/metrics").then().statusCode(200)
.body(containsString("jvm_gc_"));
}

View file

@ -66,6 +66,23 @@ Health:
are available at the '/health', '/health/ready' and '/health/live'
endpoints. Default: false.
Management:
--http-management-relative-path <path>
Set the path relative to '/' for serving resources from management interface.
The path must start with a '/'. If not given, the value is inherited from
HTTP options. Default: /. Available only when Management interface is
enabled.
--https-management-client-auth <auth>
Configures the management interface to require/request client authentication.
If not given, the value is inherited from HTTP options. Possible values are:
none, request, required. Default: none. Available only when Management
interface is enabled.
--legacy-observability-interface <true|false>
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
server (not recommended). If set to true, the management interface is
disabled. Default: false.
Metrics:
--metrics-enabled <true|false>

View file

@ -14,6 +14,15 @@ Options:
built a server image using the 'build' command.
-v, --verbose Print out error details when running this command.
Config:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
Database:
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
@ -59,14 +68,42 @@ Feature:
--features-disabled <feature>
Disables a set of one or more features. Possible values are: <...>.
Config:
Management:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
--http-management-port <port>
Port of the management interface. Default: 9000. Available only when
Management interface is enabled.
--http-management-relative-path <path>
Set the path relative to '/' for serving resources from management interface.
The path must start with a '/'. If not given, the value is inherited from
HTTP options. Default: /. Available only when Management interface is
enabled.
--https-management-certificate-file <file>
The file path to a server certificate or certificate chain in PEM format for
the management server. If not given, the value is inherited from HTTP
options. Available only when Management interface is enabled.
--https-management-certificate-key-file <file>
The file path to a private key in PEM format for the management server. If not
given, the value is inherited from HTTP options. Available only when
Management interface is enabled.
--https-management-client-auth <auth>
Configures the management interface to require/request client authentication.
If not given, the value is inherited from HTTP options. Possible values are:
none, request, required. Default: none. Available only when Management
interface is enabled.
--https-management-key-store-file <file>
The key store which holds the certificate information instead of specifying
separate files for the management server. If not given, the value is
inherited from HTTP options. Available only when Management interface is
enabled.
--https-management-key-store-password <password>
The password of the key store file for the management server. If not given,
the value is inherited from HTTP options. Default: password. Available only
when Management interface is enabled.
--legacy-observability-interface <true|false>
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
server (not recommended). If set to true, the management interface is
disabled. Default: false.
Logging:

View file

@ -14,6 +14,15 @@ Options:
built a server image using the 'build' command.
-v, --verbose Print out error details when running this command.
Config:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
Database:
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
@ -59,14 +68,42 @@ Feature:
--features-disabled <feature>
Disables a set of one or more features. Possible values are: <...>.
Config:
Management:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
--http-management-port <port>
Port of the management interface. Default: 9000. Available only when
Management interface is enabled.
--http-management-relative-path <path>
Set the path relative to '/' for serving resources from management interface.
The path must start with a '/'. If not given, the value is inherited from
HTTP options. Default: /. Available only when Management interface is
enabled.
--https-management-certificate-file <file>
The file path to a server certificate or certificate chain in PEM format for
the management server. If not given, the value is inherited from HTTP
options. Available only when Management interface is enabled.
--https-management-certificate-key-file <file>
The file path to a private key in PEM format for the management server. If not
given, the value is inherited from HTTP options. Available only when
Management interface is enabled.
--https-management-client-auth <auth>
Configures the management interface to require/request client authentication.
If not given, the value is inherited from HTTP options. Possible values are:
none, request, required. Default: none. Available only when Management
interface is enabled.
--https-management-key-store-file <file>
The key store which holds the certificate information instead of specifying
separate files for the management server. If not given, the value is
inherited from HTTP options. Available only when Management interface is
enabled.
--https-management-key-store-password <password>
The password of the key store file for the management server. If not given,
the value is inherited from HTTP options. Default: password. Available only
when Management interface is enabled.
--legacy-observability-interface <true|false>
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
server (not recommended). If set to true, the management interface is
disabled. Default: false.
Logging:

View file

@ -14,6 +14,15 @@ Options:
built a server image using the 'build' command.
-v, --verbose Print out error details when running this command.
Config:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
Database:
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
@ -59,14 +68,42 @@ Feature:
--features-disabled <feature>
Disables a set of one or more features. Possible values are: <...>.
Config:
Management:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
--http-management-port <port>
Port of the management interface. Default: 9000. Available only when
Management interface is enabled.
--http-management-relative-path <path>
Set the path relative to '/' for serving resources from management interface.
The path must start with a '/'. If not given, the value is inherited from
HTTP options. Default: /. Available only when Management interface is
enabled.
--https-management-certificate-file <file>
The file path to a server certificate or certificate chain in PEM format for
the management server. If not given, the value is inherited from HTTP
options. Available only when Management interface is enabled.
--https-management-certificate-key-file <file>
The file path to a private key in PEM format for the management server. If not
given, the value is inherited from HTTP options. Available only when
Management interface is enabled.
--https-management-client-auth <auth>
Configures the management interface to require/request client authentication.
If not given, the value is inherited from HTTP options. Possible values are:
none, request, required. Default: none. Available only when Management
interface is enabled.
--https-management-key-store-file <file>
The key store which holds the certificate information instead of specifying
separate files for the management server. If not given, the value is
inherited from HTTP options. Available only when Management interface is
enabled.
--https-management-key-store-password <password>
The password of the key store file for the management server. If not given,
the value is inherited from HTTP options. Default: password. Available only
when Management interface is enabled.
--legacy-observability-interface <true|false>
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
server (not recommended). If set to true, the management interface is
disabled. Default: false.
Logging:

View file

@ -14,6 +14,15 @@ Options:
built a server image using the 'build' command.
-v, --verbose Print out error details when running this command.
Config:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
Database:
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
@ -59,14 +68,42 @@ Feature:
--features-disabled <feature>
Disables a set of one or more features. Possible values are: <...>.
Config:
Management:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
--http-management-port <port>
Port of the management interface. Default: 9000. Available only when
Management interface is enabled.
--http-management-relative-path <path>
Set the path relative to '/' for serving resources from management interface.
The path must start with a '/'. If not given, the value is inherited from
HTTP options. Default: /. Available only when Management interface is
enabled.
--https-management-certificate-file <file>
The file path to a server certificate or certificate chain in PEM format for
the management server. If not given, the value is inherited from HTTP
options. Available only when Management interface is enabled.
--https-management-certificate-key-file <file>
The file path to a private key in PEM format for the management server. If not
given, the value is inherited from HTTP options. Available only when
Management interface is enabled.
--https-management-client-auth <auth>
Configures the management interface to require/request client authentication.
If not given, the value is inherited from HTTP options. Possible values are:
none, request, required. Default: none. Available only when Management
interface is enabled.
--https-management-key-store-file <file>
The key store which holds the certificate information instead of specifying
separate files for the management server. If not given, the value is
inherited from HTTP options. Available only when Management interface is
enabled.
--https-management-key-store-password <password>
The password of the key store file for the management server. If not given,
the value is inherited from HTTP options. Default: password. Available only
when Management interface is enabled.
--legacy-observability-interface <true|false>
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
server (not recommended). If set to true, the management interface is
disabled. Default: false.
Logging:

View file

@ -66,6 +66,15 @@ Cache:
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
Possible values are: tcp, udp, kubernetes, ec2, azure, google.
Config:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
Database:
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
@ -198,14 +207,42 @@ Health:
are available at the '/health', '/health/ready' and '/health/live'
endpoints. Default: false.
Config:
Management:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
--http-management-port <port>
Port of the management interface. Default: 9000. Available only when
Management interface is enabled.
--http-management-relative-path <path>
Set the path relative to '/' for serving resources from management interface.
The path must start with a '/'. If not given, the value is inherited from
HTTP options. Default: /. Available only when Management interface is
enabled.
--https-management-certificate-file <file>
The file path to a server certificate or certificate chain in PEM format for
the management server. If not given, the value is inherited from HTTP
options. Available only when Management interface is enabled.
--https-management-certificate-key-file <file>
The file path to a private key in PEM format for the management server. If not
given, the value is inherited from HTTP options. Available only when
Management interface is enabled.
--https-management-client-auth <auth>
Configures the management interface to require/request client authentication.
If not given, the value is inherited from HTTP options. Possible values are:
none, request, required. Default: none. Available only when Management
interface is enabled.
--https-management-key-store-file <file>
The key store which holds the certificate information instead of specifying
separate files for the management server. If not given, the value is
inherited from HTTP options. Available only when Management interface is
enabled.
--https-management-key-store-password <password>
The password of the key store file for the management server. If not given,
the value is inherited from HTTP options. Default: password. Available only
when Management interface is enabled.
--legacy-observability-interface <true|false>
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
server (not recommended). If set to true, the management interface is
disabled. Default: false.
Metrics:

View file

@ -69,6 +69,15 @@ Cache:
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
Possible values are: tcp, udp, kubernetes, ec2, azure, google.
Config:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
Database:
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
@ -201,14 +210,42 @@ Health:
are available at the '/health', '/health/ready' and '/health/live'
endpoints. Default: false.
Config:
Management:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
--http-management-port <port>
Port of the management interface. Default: 9000. Available only when
Management interface is enabled.
--http-management-relative-path <path>
Set the path relative to '/' for serving resources from management interface.
The path must start with a '/'. If not given, the value is inherited from
HTTP options. Default: /. Available only when Management interface is
enabled.
--https-management-certificate-file <file>
The file path to a server certificate or certificate chain in PEM format for
the management server. If not given, the value is inherited from HTTP
options. Available only when Management interface is enabled.
--https-management-certificate-key-file <file>
The file path to a private key in PEM format for the management server. If not
given, the value is inherited from HTTP options. Available only when
Management interface is enabled.
--https-management-client-auth <auth>
Configures the management interface to require/request client authentication.
If not given, the value is inherited from HTTP options. Possible values are:
none, request, required. Default: none. Available only when Management
interface is enabled.
--https-management-key-store-file <file>
The key store which holds the certificate information instead of specifying
separate files for the management server. If not given, the value is
inherited from HTTP options. Available only when Management interface is
enabled.
--https-management-key-store-password <password>
The password of the key store file for the management server. If not given,
the value is inherited from HTTP options. Default: password. Available only
when Management interface is enabled.
--legacy-observability-interface <true|false>
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
server (not recommended). If set to true, the management interface is
disabled. Default: false.
Metrics:

View file

@ -67,6 +67,15 @@ Cache:
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
Possible values are: tcp, udp, kubernetes, ec2, azure, google.
Config:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
Database:
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
@ -199,14 +208,42 @@ Health:
are available at the '/health', '/health/ready' and '/health/live'
endpoints. Default: false.
Config:
Management:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
--http-management-port <port>
Port of the management interface. Default: 9000. Available only when
Management interface is enabled.
--http-management-relative-path <path>
Set the path relative to '/' for serving resources from management interface.
The path must start with a '/'. If not given, the value is inherited from
HTTP options. Default: /. Available only when Management interface is
enabled.
--https-management-certificate-file <file>
The file path to a server certificate or certificate chain in PEM format for
the management server. If not given, the value is inherited from HTTP
options. Available only when Management interface is enabled.
--https-management-certificate-key-file <file>
The file path to a private key in PEM format for the management server. If not
given, the value is inherited from HTTP options. Available only when
Management interface is enabled.
--https-management-client-auth <auth>
Configures the management interface to require/request client authentication.
If not given, the value is inherited from HTTP options. Possible values are:
none, request, required. Default: none. Available only when Management
interface is enabled.
--https-management-key-store-file <file>
The key store which holds the certificate information instead of specifying
separate files for the management server. If not given, the value is
inherited from HTTP options. Available only when Management interface is
enabled.
--https-management-key-store-password <password>
The password of the key store file for the management server. If not given,
the value is inherited from HTTP options. Default: password. Available only
when Management interface is enabled.
--legacy-observability-interface <true|false>
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
server (not recommended). If set to true, the management interface is
disabled. Default: false.
Metrics:

View file

@ -70,6 +70,15 @@ Cache:
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
Possible values are: tcp, udp, kubernetes, ec2, azure, google.
Config:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
Database:
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
@ -202,14 +211,42 @@ Health:
are available at the '/health', '/health/ready' and '/health/live'
endpoints. Default: false.
Config:
Management:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
--http-management-port <port>
Port of the management interface. Default: 9000. Available only when
Management interface is enabled.
--http-management-relative-path <path>
Set the path relative to '/' for serving resources from management interface.
The path must start with a '/'. If not given, the value is inherited from
HTTP options. Default: /. Available only when Management interface is
enabled.
--https-management-certificate-file <file>
The file path to a server certificate or certificate chain in PEM format for
the management server. If not given, the value is inherited from HTTP
options. Available only when Management interface is enabled.
--https-management-certificate-key-file <file>
The file path to a private key in PEM format for the management server. If not
given, the value is inherited from HTTP options. Available only when
Management interface is enabled.
--https-management-client-auth <auth>
Configures the management interface to require/request client authentication.
If not given, the value is inherited from HTTP options. Possible values are:
none, request, required. Default: none. Available only when Management
interface is enabled.
--https-management-key-store-file <file>
The key store which holds the certificate information instead of specifying
separate files for the management server. If not given, the value is
inherited from HTTP options. Available only when Management interface is
enabled.
--https-management-key-store-password <password>
The password of the key store file for the management server. If not given,
the value is inherited from HTTP options. Default: password. Available only
when Management interface is enabled.
--legacy-observability-interface <true|false>
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
server (not recommended). If set to true, the management interface is
disabled. Default: false.
Metrics:

View file

@ -55,6 +55,15 @@ Cache:
specified, 'cache-remote-host' and 'cache-remote-password' are required as
well and the related configuration in XML file should not be present.
Config:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
Database:
--db-password <password>
@ -159,14 +168,28 @@ HTTP(S):
'strict' and no value is set, it defaults to 'BCFKS'. Use the System
Truststore instead, see the docs for details.
Config:
Management:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
--http-management-port <port>
Port of the management interface. Default: 9000. Available only when
Management interface is enabled.
--https-management-certificate-file <file>
The file path to a server certificate or certificate chain in PEM format for
the management server. If not given, the value is inherited from HTTP
options. Available only when Management interface is enabled.
--https-management-certificate-key-file <file>
The file path to a private key in PEM format for the management server. If not
given, the value is inherited from HTTP options. Available only when
Management interface is enabled.
--https-management-key-store-file <file>
The key store which holds the certificate information instead of specifying
separate files for the management server. If not given, the value is
inherited from HTTP options. Available only when Management interface is
enabled.
--https-management-key-store-password <password>
The password of the key store file for the management server. If not given,
the value is inherited from HTTP options. Default: password. Available only
when Management interface is enabled.
Proxy:

View file

@ -58,6 +58,15 @@ Cache:
specified, 'cache-remote-host' and 'cache-remote-password' are required as
well and the related configuration in XML file should not be present.
Config:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
Database:
--db-password <password>
@ -162,14 +171,28 @@ HTTP(S):
'strict' and no value is set, it defaults to 'BCFKS'. Use the System
Truststore instead, see the docs for details.
Config:
Management:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
--http-management-port <port>
Port of the management interface. Default: 9000. Available only when
Management interface is enabled.
--https-management-certificate-file <file>
The file path to a server certificate or certificate chain in PEM format for
the management server. If not given, the value is inherited from HTTP
options. Available only when Management interface is enabled.
--https-management-certificate-key-file <file>
The file path to a private key in PEM format for the management server. If not
given, the value is inherited from HTTP options. Available only when
Management interface is enabled.
--https-management-key-store-file <file>
The key store which holds the certificate information instead of specifying
separate files for the management server. If not given, the value is
inherited from HTTP options. Available only when Management interface is
enabled.
--https-management-key-store-password <password>
The password of the key store file for the management server. If not given,
the value is inherited from HTTP options. Default: password. Available only
when Management interface is enabled.
Proxy:

View file

@ -60,6 +60,10 @@
<version>${junit.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>

View file

@ -64,5 +64,15 @@ public @interface DistributionTest {
* If any option must be set when starting the server.
*/
String[] defaultOptions() default {};
/**
* Exposed ports when container is used
*/
int[] containerExposedPorts() default {8080};
/**
* Default port for making HTTP requests with RestAssured
*/
int requestPort() default 8080;
}

View file

@ -33,7 +33,8 @@ public enum DistributionType {
return new DockerKeycloakDistribution(
config.debug(),
config.keepAlive(),
!DistributionTest.ReInstall.NEVER.equals(config.reInstall()));
config.requestPort(),
config.containerExposedPorts());
}
private static KeycloakDistribution createRawDistribution(DistributionTest config) {
@ -42,7 +43,8 @@ public enum DistributionType {
config.keepAlive(),
config.enableTls(),
!DistributionTest.ReInstall.NEVER.equals(config.reInstall()),
config.removeBuildOptionsAfterBuild());
config.removeBuildOptionsAfterBuild(),
config.requestPort());
}
private final Function<DistributionTest, KeycloakDistribution> factory;
@ -65,6 +67,14 @@ public enum DistributionType {
}
}
public static boolean isContainerDist() {
return DistributionType.getCurrent().map(f -> f.equals(DistributionType.DOCKER)).orElse(false);
}
public static boolean isRawDist() {
return DistributionType.getCurrent().map(f -> f.equals(DistributionType.RAW)).orElse(false);
}
public KeycloakDistribution newInstance(DistributionTest config) {
return factory.apply(config);
}

View file

@ -126,6 +126,16 @@ public class KeycloakDistributionDecorator implements KeycloakDistribution {
delegate.assertStopped();
}
@Override
public void setRequestPort() {
delegate.setRequestPort();
}
@Override
public void setRequestPort(int port) {
delegate.setRequestPort(port);
}
@Override
public <D extends KeycloakDistribution> D unwrap(Class<D> type) {
if (!KeycloakDistribution.class.isAssignableFrom(type)) {

View file

@ -1,6 +1,8 @@
package org.keycloak.it.utils;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.exception.NotFoundException;
import io.restassured.RestAssured;
import org.jboss.logging.Logger;
import org.keycloak.common.Version;
import org.keycloak.it.junit5.extension.CLIResult;
@ -15,36 +17,41 @@ import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.LazyFuture;
import java.io.File;
import java.lang.reflect.Field;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;
public final class DockerKeycloakDistribution implements KeycloakDistribution {
private static final Logger LOGGER = Logger.getLogger(DockerKeycloakDistribution.class);
private boolean debug;
private boolean manualStop;
private final boolean debug;
private final boolean manualStop;
private final int requestPort;
private final Integer[] exposedPorts;
private int exitCode = -1;
private String stdout = "";
private String stderr = "";
private ToStringConsumer backupConsumer = new ToStringConsumer();
private File dockerScriptFile = new File("../../container/ubi-null.sh");
private final File dockerScriptFile = new File("../../container/ubi-null.sh");
private GenericContainer<?> keycloakContainer = null;
private String containerId = null;
private Executor parallelReaperExecutor = Executors.newSingleThreadExecutor();
private Map<String, String> envVars = new HashMap<>();
private final Executor parallelReaperExecutor = Executors.newSingleThreadExecutor();
private final Map<String, String> envVars = new HashMap<>();
public DockerKeycloakDistribution(boolean debug, boolean manualStop, boolean reCreate) {
public DockerKeycloakDistribution(boolean debug, boolean manualStop, int requestPort, int[] exposedPorts) {
this.debug = debug;
this.manualStop = manualStop;
this.requestPort = requestPort;
this.exposedPorts = IntStream.of(exposedPorts).boxed().toArray(Integer[]::new);
}
@Override
@ -79,10 +86,10 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
return new GenericContainer<>(image)
.withEnv(envVars)
.withExposedPorts(8080)
.withExposedPorts(exposedPorts)
.withStartupAttempts(1)
.withStartupTimeout(Duration.ofSeconds(120))
.waitingFor(Wait.forListeningPort());
.waitingFor(Wait.forListeningPorts(8080));
}
@Override
@ -118,20 +125,20 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
}
}
trySetRestAssuredPort();
setRequestPort();
return CLIResult.create(getOutputStream(), getErrorStream(), getExitCode());
}
private void trySetRestAssuredPort() {
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> restAssured = classLoader.loadClass("io.restassured.RestAssured");
Field port = restAssured.getDeclaredField("port");
port.set(null, keycloakContainer.getMappedPort(8080));
} catch (Exception ignore) {
// keeping the workaround to set the container port to restassured
// TODO: better way to expose the port to tests
@Override
public void setRequestPort() {
setRequestPort(requestPort);
}
@Override
public void setRequestPort(int port) {
if (keycloakContainer != null) {
RestAssured.port = keycloakContainer.getMappedPort(port);
}
}
@ -186,9 +193,12 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
@Override
public void run() {
try {
if (containerId == null) return;
DockerClient dockerClient = DockerClientFactory.lazyClient();
dockerClient.killContainerCmd(containerId).exec();
dockerClient.removeContainerCmd(containerId).withRemoveVolumes(true).withForce(true).exec();
} catch (NotFoundException notFound) {
LOGGER.debug("Container is already cleaned up, no additional cleanup required");
} catch (Exception cause) {
throw new RuntimeException("Failed to stop and remove container", cause);
}

View file

@ -29,6 +29,10 @@ public interface KeycloakDistribution {
void assertStopped();
void setRequestPort();
void setRequestPort(int port);
default String[] getCliArgs(List<String> arguments) {
throw new RuntimeException("Not implemented");
}

View file

@ -60,6 +60,7 @@ import javax.net.ssl.X509TrustManager;
import io.quarkus.deployment.util.FileUtil;
import io.quarkus.fs.util.ZipUtils;
import io.restassured.RestAssured;
import org.awaitility.Awaitility;
import org.jboss.logging.Logger;
import org.jboss.shrinkwrap.api.ShrinkWrap;
@ -91,20 +92,22 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
private String relativePath;
private int httpPort;
private int httpsPort;
private boolean debug;
private boolean enableTls;
private boolean reCreate;
private boolean removeBuildOptionsAfterBuild;
private final boolean debug;
private final boolean enableTls;
private final boolean reCreate;
private final boolean removeBuildOptionsAfterBuild;
private final int requestPort;
private ExecutorService outputExecutor;
private boolean inited = false;
private Map<String, String> envVars = new HashMap<>();
private final Map<String, String> envVars = new HashMap<>();
public RawKeycloakDistribution(boolean debug, boolean manualStop, boolean enableTls, boolean reCreate, boolean removeBuildOptionsAfterBuild) {
public RawKeycloakDistribution(boolean debug, boolean manualStop, boolean enableTls, boolean reCreate, boolean removeBuildOptionsAfterBuild, int requestPort) {
this.debug = debug;
this.manualStop = manualStop;
this.enableTls = enableTls;
this.reCreate = reCreate;
this.removeBuildOptionsAfterBuild = removeBuildOptionsAfterBuild;
this.requestPort = requestPort;
this.distPath = prepareDistribution();
}
@ -145,6 +148,8 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
}
}
setRequestPort();
return CLIResult.create(getOutputStream(), getErrorStream(), getExitCode());
}
@ -276,6 +281,16 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
}
}
@Override
public void setRequestPort() {
setRequestPort(requestPort);
}
@Override
public void setRequestPort(int port) {
RestAssured.port = port;
}
private void waitForReadiness() throws MalformedURLException {
waitForReadiness("http", httpPort);

View file

@ -164,6 +164,8 @@ public abstract class AbstractQuarkusDeployableContainer implements DeployableCo
if (suiteContext.get().isAuthServerMigrationEnabled()) {
commands.add("--hostname-strict=false");
commands.add("--hostname-strict-https=false");
} else { // Do not set management port for older versions of Keycloak for migration tests - available since Keycloak ~22
commands.add("--http-management-port=" + configuration.getManagementPort());
}
if (configuration.getRoute() != null) {

View file

@ -24,6 +24,7 @@ public class KeycloakQuarkusConfiguration implements ContainerConfiguration {
private int bindHttpPort = 8080;
private int bindHttpsPortOffset = 0;
private int bindHttpsPort = Integer.getInteger("auth.server.https.port", 8543);
private int managementPort = 9000;
private String keystoreFile = System.getProperty("auth.server.keystore");
@ -59,7 +60,7 @@ public class KeycloakQuarkusConfiguration implements ContainerConfiguration {
int newHttpsPort = baseHttpsPort + bindHttpsPortOffset;
setBindHttpsPort(newHttpsPort);
log.info("Keycloak will listen for http on port: " + newPort + " and for https on port: " + newHttpsPort);
log.infof("Keycloak will listen for http on port: %d, for https on port: %d, and for management on port: %d\n", newPort, newHttpsPort, managementPort);
if (this.keycloakConfigPropertyOverrides != null) {
try {
@ -103,6 +104,14 @@ public class KeycloakQuarkusConfiguration implements ContainerConfiguration {
this.bindHttpPort = bindHttpPort;
}
public int getManagementPort() {
return managementPort;
}
public void setManagementPort(int managementPort) {
this.managementPort = managementPort;
}
public String getKeystoreFile() {
return keystoreFile;
}

View file

@ -693,6 +693,7 @@
<property name="bindHttpsPort">${auth.server.https.port}</property>
<property name="bindHttpPortOffset">2</property>
<property name="bindHttpsPortOffset">2</property>
<property name="managementPort">9001</property>
<property name="route">node2</property>
<property name="remoteMode">${quarkus.remote}</property>
<property name="profile">ha</property>