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:
parent
8ef3423f4a
commit
7f048300fe
53 changed files with 1634 additions and 165 deletions
|
@ -60,6 +60,8 @@ public final class Constants {
|
||||||
public static final Integer KEYCLOAK_DISCOVERY_SERVICE_PORT = 7800;
|
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_TCP_PORT_NAME = "tcp";
|
||||||
public static final String KEYCLOAK_DISCOVERY_SERVICE_SUFFIX = "-discovery";
|
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";
|
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 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_RELATIVE_PATH_KEY = "http-relative-path";
|
||||||
|
public static final String KEYCLOAK_HTTP_MANAGEMENT_RELATIVE_PATH_KEY = "http-management-relative-path";
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import io.fabric8.kubernetes.api.model.EnvVar;
|
||||||
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
|
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
|
||||||
import io.fabric8.kubernetes.api.model.EnvVarSource;
|
import io.fabric8.kubernetes.api.model.EnvVarSource;
|
||||||
import io.fabric8.kubernetes.api.model.EnvVarSourceBuilder;
|
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.PodSpec;
|
||||||
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
|
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
|
||||||
import io.fabric8.kubernetes.api.model.Secret;
|
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.KeycloakSpec;
|
||||||
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
|
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.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.Truststore;
|
||||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TruststoreSource;
|
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TruststoreSource;
|
||||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpec;
|
import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpec;
|
||||||
|
@ -284,12 +284,13 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
|
||||||
containerBuilder.addToArgs(0, getJGroupsParameter(keycloakCR));
|
containerBuilder.addToArgs(0, getJGroupsParameter(keycloakCR));
|
||||||
|
|
||||||
// probes
|
// probes
|
||||||
var tlsConfigured = isTlsConfigured(keycloakCR);
|
var protocol = isTlsConfigured(keycloakCR) ? "HTTPS" : "HTTP";
|
||||||
var protocol = !tlsConfigured ? "HTTP" : "HTTPS";
|
var port = Optional.ofNullable(keycloakCR.getSpec())
|
||||||
var kcPort = KeycloakServiceDependentResource.getServicePort(tlsConfigured, keycloakCR);
|
.map(KeycloakSpec::getHttpManagementSpec)
|
||||||
|
.map(HttpManagementSpec::getPort)
|
||||||
// Relative path ends with '/'
|
.orElse(Constants.KEYCLOAK_MANAGEMENT_PORT);
|
||||||
var kcRelativePath = readConfigurationValue(Constants.KEYCLOAK_HTTP_RELATIVE_PATH_KEY, keycloakCR, context)
|
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)
|
.map(path -> !path.endsWith("/") ? path + "/" : path)
|
||||||
.orElse("/");
|
.orElse("/");
|
||||||
|
|
||||||
|
@ -299,8 +300,8 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
|
||||||
.withFailureThreshold(3)
|
.withFailureThreshold(3)
|
||||||
.withNewHttpGet()
|
.withNewHttpGet()
|
||||||
.withScheme(protocol)
|
.withScheme(protocol)
|
||||||
.withNewPort(kcPort)
|
.withNewPort(port)
|
||||||
.withPath(kcRelativePath + "health/ready")
|
.withPath(relativePath + "health/ready")
|
||||||
.endHttpGet()
|
.endHttpGet()
|
||||||
.endReadinessProbe();
|
.endReadinessProbe();
|
||||||
}
|
}
|
||||||
|
@ -310,8 +311,8 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
|
||||||
.withFailureThreshold(3)
|
.withFailureThreshold(3)
|
||||||
.withNewHttpGet()
|
.withNewHttpGet()
|
||||||
.withScheme(protocol)
|
.withScheme(protocol)
|
||||||
.withNewPort(kcPort)
|
.withNewPort(port)
|
||||||
.withPath(kcRelativePath + "health/live")
|
.withPath(relativePath + "health/live")
|
||||||
.endHttpGet()
|
.endHttpGet()
|
||||||
.endLivenessProbe();
|
.endLivenessProbe();
|
||||||
}
|
}
|
||||||
|
@ -321,14 +322,14 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
|
||||||
.withFailureThreshold(600)
|
.withFailureThreshold(600)
|
||||||
.withNewHttpGet()
|
.withNewHttpGet()
|
||||||
.withScheme(protocol)
|
.withScheme(protocol)
|
||||||
.withNewPort(kcPort)
|
.withNewPort(port)
|
||||||
.withPath(kcRelativePath + "health/started")
|
.withPath(relativePath + "health/started")
|
||||||
.endHttpGet()
|
.endHttpGet()
|
||||||
.endStartupProbe();
|
.endStartupProbe();
|
||||||
}
|
}
|
||||||
|
|
||||||
// add in ports - there's no merging being done here
|
// add in ports - there's no merging being done here
|
||||||
StatefulSet baseDeployment = containerBuilder
|
final StatefulSet baseDeployment = containerBuilder
|
||||||
.addNewPort()
|
.addNewPort()
|
||||||
.withName(Constants.KEYCLOAK_HTTPS_PORT_NAME)
|
.withName(Constants.KEYCLOAK_HTTPS_PORT_NAME)
|
||||||
.withContainerPort(Constants.KEYCLOAK_HTTPS_PORT)
|
.withContainerPort(Constants.KEYCLOAK_HTTPS_PORT)
|
||||||
|
@ -339,6 +340,11 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
|
||||||
.withContainerPort(Constants.KEYCLOAK_HTTP_PORT)
|
.withContainerPort(Constants.KEYCLOAK_HTTP_PORT)
|
||||||
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL)
|
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL)
|
||||||
.endPort()
|
.endPort()
|
||||||
|
.addNewPort()
|
||||||
|
.withName(Constants.KEYCLOAK_MANAGEMENT_PORT_NAME)
|
||||||
|
.withContainerPort(Constants.KEYCLOAK_MANAGEMENT_PORT)
|
||||||
|
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL)
|
||||||
|
.endPort()
|
||||||
.endContainer().endSpec().endTemplate().endSpec().build();
|
.endContainer().endSpec().endTemplate().endSpec().build();
|
||||||
|
|
||||||
return baseDeployment;
|
return baseDeployment;
|
||||||
|
|
|
@ -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.FeatureSpec;
|
||||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpec;
|
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.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.ProxySpec;
|
||||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TransactionsSpec;
|
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TransactionsSpec;
|
||||||
|
|
||||||
|
@ -70,6 +71,7 @@ public class KeycloakDistConfigurator {
|
||||||
configureDatabase();
|
configureDatabase();
|
||||||
configureCache();
|
configureCache();
|
||||||
configureProxy();
|
configureProxy();
|
||||||
|
configureManagement();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -137,6 +139,11 @@ public class KeycloakDistConfigurator {
|
||||||
.mapOption("proxy-headers", ProxySpec::getHeaders);
|
.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 ---------- */
|
/* ---------- END of configuration of first-class citizen fields ---------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -29,7 +29,9 @@ import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDep
|
||||||
import org.keycloak.operator.Constants;
|
import org.keycloak.operator.Constants;
|
||||||
import org.keycloak.operator.Utils;
|
import org.keycloak.operator.Utils;
|
||||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
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.HttpSpec;
|
||||||
|
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpManagementSpec;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@ -56,13 +58,31 @@ public class KeycloakServiceDependentResource extends CRUDKubernetesDependentRes
|
||||||
Optional<HttpSpec> httpSpec = Optional.ofNullable(keycloak.getSpec().getHttpSpec());
|
Optional<HttpSpec> httpSpec = Optional.ofNullable(keycloak.getSpec().getHttpSpec());
|
||||||
boolean httpEnabled = httpSpec.map(HttpSpec::getHttpEnabled).orElse(false);
|
boolean httpEnabled = httpSpec.map(HttpSpec::getHttpEnabled).orElse(false);
|
||||||
if (!tlsConfigured || httpEnabled) {
|
if (!tlsConfigured || httpEnabled) {
|
||||||
builder.addNewPort().withPort(getServicePort(false, keycloak)).withName(Constants.KEYCLOAK_HTTP_PORT_NAME)
|
builder.addNewPort()
|
||||||
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL).endPort();
|
.withPort(getServicePort(false, keycloak))
|
||||||
|
.withName(Constants.KEYCLOAK_HTTP_PORT_NAME)
|
||||||
|
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL)
|
||||||
|
.endPort();
|
||||||
}
|
}
|
||||||
if (tlsConfigured) {
|
if (tlsConfigured) {
|
||||||
builder.addNewPort().withPort(getServicePort(true, keycloak)).withName(Constants.KEYCLOAK_HTTPS_PORT_NAME)
|
builder.addNewPort()
|
||||||
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL).endPort();
|
.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();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.DatabaseSpec;
|
||||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec;
|
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.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.HttpSpec;
|
||||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpec;
|
import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpec;
|
||||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.ProxySpec;
|
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")
|
@JsonPropertyDescription("In this section you can configure Keycloak's reverse proxy setting")
|
||||||
private ProxySpec proxySpec;
|
private ProxySpec proxySpec;
|
||||||
|
|
||||||
|
@JsonProperty("httpManagement")
|
||||||
|
@JsonPropertyDescription("In this section you can configure Keycloak's management interface setting.")
|
||||||
|
private HttpManagementSpec httpManagementSpec;
|
||||||
|
|
||||||
public HttpSpec getHttpSpec() {
|
public HttpSpec getHttpSpec() {
|
||||||
return httpSpec;
|
return httpSpec;
|
||||||
}
|
}
|
||||||
|
@ -185,6 +190,14 @@ public class KeycloakSpec {
|
||||||
this.imagePullSecrets = imagePullSecrets;
|
this.imagePullSecrets = imagePullSecrets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HttpManagementSpec getHttpManagementSpec() {
|
||||||
|
return httpManagementSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHttpManagementSpec(HttpManagementSpec httpManagementSpec) {
|
||||||
|
this.httpManagementSpec = httpManagementSpec;
|
||||||
|
}
|
||||||
|
|
||||||
public List<ValueOrSecret> getAdditionalOptions() {
|
public List<ValueOrSecret> getAdditionalOptions() {
|
||||||
if (this.additionalOptions == null) {
|
if (this.additionalOptions == null) {
|
||||||
this.additionalOptions = new ArrayList<>();
|
this.additionalOptions = new ArrayList<>();
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -287,6 +287,7 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
|
||||||
deployKeycloak(k8sclient, kc, true);
|
deployKeycloak(k8sclient, kc, true);
|
||||||
|
|
||||||
assertKeycloakAccessibleViaService(kc, false, Constants.KEYCLOAK_HTTP_PORT);
|
assertKeycloakAccessibleViaService(kc, false, Constants.KEYCLOAK_HTTP_PORT);
|
||||||
|
assertManagementInterfaceAccessibleViaService(kc, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -296,6 +297,9 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
|
||||||
deployKeycloak(k8sclient, kc, true);
|
deployKeycloak(k8sclient, kc, true);
|
||||||
|
|
||||||
assertKeycloakAccessibleViaService(kc, false, Constants.KEYCLOAK_HTTP_PORT);
|
assertKeycloakAccessibleViaService(kc, false, Constants.KEYCLOAK_HTTP_PORT);
|
||||||
|
|
||||||
|
// if TLS is enabled, management interface should use https
|
||||||
|
assertManagementInterfaceAccessibleViaService(kc, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -723,7 +727,7 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
|
||||||
|
|
||||||
String serviceName = KeycloakServiceDependentResource.getServiceName(kc);
|
String serviceName = KeycloakServiceDependentResource.getServiceName(kc);
|
||||||
assertThat(k8sclient.resources(Service.class).withName(serviceName).require().getSpec().getPorts()
|
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/";
|
String url = protocol + "://" + serviceName + "." + namespace + ":" + port + "/admin/master/console/";
|
||||||
Log.info("Checking url: " + url);
|
Log.info("Checking url: " + url);
|
||||||
|
@ -734,4 +738,23 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
|
||||||
assertEquals("200", curlOutput);
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,9 +61,6 @@ public class KeycloakServicesTest extends BaseOperatorTest {
|
||||||
currentService.getSpec().setSessionAffinity("ClientIP");
|
currentService.getSpec().setSessionAffinity("ClientIP");
|
||||||
var origSpecs = new ServiceSpecBuilder(currentService.getSpec()).build(); // deep copy
|
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().getLabels().putAll(labels);
|
||||||
|
|
||||||
currentService.getMetadata().setResourceVersion(null);
|
currentService.getMetadata().setResourceVersion(null);
|
||||||
|
|
|
@ -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.DatabaseSpec;
|
||||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec;
|
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.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.deployment.spec.TransactionsSpec;
|
||||||
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
|
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
|
||||||
import org.keycloak.operator.testsuite.utils.K8sUtils;
|
import org.keycloak.operator.testsuite.utils.K8sUtils;
|
||||||
|
@ -89,6 +90,10 @@ public class CRSerializationTest {
|
||||||
assertEquals("usernameSecretKey", databaseSpec.getUsernameSecret().getKey());
|
assertEquals("usernameSecretKey", databaseSpec.getUsernameSecret().getKey());
|
||||||
assertEquals("passwordSecret", databaseSpec.getPasswordSecret().getName());
|
assertEquals("passwordSecret", databaseSpec.getPasswordSecret().getName());
|
||||||
assertEquals("passwordSecretKey", databaseSpec.getPasswordSecret().getKey());
|
assertEquals("passwordSecretKey", databaseSpec.getPasswordSecret().getKey());
|
||||||
|
|
||||||
|
HttpManagementSpec managementSpec = keycloak.getSpec().getHttpManagementSpec();
|
||||||
|
assertNotNull(managementSpec);
|
||||||
|
assertEquals(9003, managementSpec.getPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -144,6 +144,15 @@ public class KeycloakDistConfiguratorTest {
|
||||||
testFirstClassCitizen(expectedValues);
|
testFirstClassCitizen(expectedValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void management() {
|
||||||
|
final Map<String, String> expectedValues = new HashMap<>(Map.of(
|
||||||
|
"http-management-port", "9003"
|
||||||
|
));
|
||||||
|
|
||||||
|
testFirstClassCitizen(expectedValues);
|
||||||
|
}
|
||||||
|
|
||||||
/* UTILS */
|
/* UTILS */
|
||||||
|
|
||||||
private void testFirstClassCitizen(Map<String, String> expectedValues) {
|
private void testFirstClassCitizen(Map<String, String> expectedValues) {
|
||||||
|
|
|
@ -55,6 +55,7 @@ import jakarta.inject.Inject;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
@QuarkusTest
|
@QuarkusTest
|
||||||
|
@ -341,7 +342,7 @@ public class PodTemplateTest {
|
||||||
@Test
|
@Test
|
||||||
public void testRelativePathHealthProbes() {
|
public void testRelativePathHealthProbes() {
|
||||||
final Function<String, Container> setUpRelativePath = (path) -> getDeployment(null, new StatefulSet(),
|
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()
|
.getSpec()
|
||||||
.getTemplate()
|
.getTemplate()
|
||||||
.getSpec()
|
.getSpec()
|
||||||
|
@ -351,18 +352,22 @@ public class PodTemplateTest {
|
||||||
var first = setUpRelativePath.apply("/");
|
var first = setUpRelativePath.apply("/");
|
||||||
assertEquals("/health/ready", first.getReadinessProbe().getHttpGet().getPath());
|
assertEquals("/health/ready", first.getReadinessProbe().getHttpGet().getPath());
|
||||||
assertEquals("/health/live", first.getLivenessProbe().getHttpGet().getPath());
|
assertEquals("/health/live", first.getLivenessProbe().getHttpGet().getPath());
|
||||||
|
assertEquals("/health/started", first.getStartupProbe().getHttpGet().getPath());
|
||||||
|
|
||||||
var second = setUpRelativePath.apply("some");
|
var second = setUpRelativePath.apply("some");
|
||||||
assertEquals("some/health/ready", second.getReadinessProbe().getHttpGet().getPath());
|
assertEquals("some/health/ready", second.getReadinessProbe().getHttpGet().getPath());
|
||||||
assertEquals("some/health/live", second.getLivenessProbe().getHttpGet().getPath());
|
assertEquals("some/health/live", second.getLivenessProbe().getHttpGet().getPath());
|
||||||
|
assertEquals("some/health/started", second.getStartupProbe().getHttpGet().getPath());
|
||||||
|
|
||||||
var third = setUpRelativePath.apply("");
|
var third = setUpRelativePath.apply("");
|
||||||
assertEquals("/health/ready", third.getReadinessProbe().getHttpGet().getPath());
|
assertEquals("/health/ready", third.getReadinessProbe().getHttpGet().getPath());
|
||||||
assertEquals("/health/live", third.getLivenessProbe().getHttpGet().getPath());
|
assertEquals("/health/live", third.getLivenessProbe().getHttpGet().getPath());
|
||||||
|
assertEquals("/health/started", third.getStartupProbe().getHttpGet().getPath());
|
||||||
|
|
||||||
var fourth = setUpRelativePath.apply("/some/");
|
var fourth = setUpRelativePath.apply("/some/");
|
||||||
assertEquals("/some/health/ready", fourth.getReadinessProbe().getHttpGet().getPath());
|
assertEquals("/some/health/ready", fourth.getReadinessProbe().getHttpGet().getPath());
|
||||||
assertEquals("/some/health/live", fourth.getLivenessProbe().getHttpGet().getPath());
|
assertEquals("/some/health/live", fourth.getLivenessProbe().getHttpGet().getPath());
|
||||||
|
assertEquals("/some/health/started", fourth.getStartupProbe().getHttpGet().getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -372,10 +377,27 @@ public class PodTemplateTest {
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var podTemplate = getDeployment(additionalPodTemplate).getSpec().getTemplate();
|
var podTemplate = getDeployment(additionalPodTemplate).getSpec().getTemplate();
|
||||||
|
var container = podTemplate.getSpec().getContainers().get(0);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
assertThat(podTemplate.getSpec().getContainers().get(0).getArgs()).doesNotContain(KeycloakDeploymentDependentResource.OPTIMIZED_ARG);
|
assertNotNull(container);
|
||||||
assertThat(podTemplate.getSpec().getContainers().get(0).getEnv().stream().anyMatch(envVar -> envVar.getName().equals(KeycloakDeploymentDependentResource.KC_TRUSTSTORE_PATHS)));
|
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
|
@Test
|
||||||
|
|
|
@ -69,6 +69,8 @@ spec:
|
||||||
x:
|
x:
|
||||||
secret:
|
secret:
|
||||||
name: my-secret
|
name: my-secret
|
||||||
|
httpManagement:
|
||||||
|
port: 9003
|
||||||
unsupported:
|
unsupported:
|
||||||
podTemplate:
|
podTemplate:
|
||||||
metadata:
|
metadata:
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -3,13 +3,14 @@ package org.keycloak.config;
|
||||||
public enum OptionCategory {
|
public enum OptionCategory {
|
||||||
// ordered by name asc
|
// ordered by name asc
|
||||||
CACHE("Cache", 10, ConfigSupportLevel.SUPPORTED),
|
CACHE("Cache", 10, ConfigSupportLevel.SUPPORTED),
|
||||||
|
CONFIG("Config", 15, ConfigSupportLevel.SUPPORTED),
|
||||||
DATABASE("Database", 20, ConfigSupportLevel.SUPPORTED),
|
DATABASE("Database", 20, ConfigSupportLevel.SUPPORTED),
|
||||||
TRANSACTION("Transaction",30, ConfigSupportLevel.SUPPORTED),
|
TRANSACTION("Transaction",30, ConfigSupportLevel.SUPPORTED),
|
||||||
FEATURE("Feature", 40, ConfigSupportLevel.SUPPORTED),
|
FEATURE("Feature", 40, ConfigSupportLevel.SUPPORTED),
|
||||||
HOSTNAME("Hostname", 50, ConfigSupportLevel.SUPPORTED),
|
HOSTNAME("Hostname", 50, ConfigSupportLevel.SUPPORTED),
|
||||||
HTTP("HTTP(S)", 60, ConfigSupportLevel.SUPPORTED),
|
HTTP("HTTP(S)", 60, ConfigSupportLevel.SUPPORTED),
|
||||||
HEALTH("Health", 70, ConfigSupportLevel.SUPPORTED),
|
HEALTH("Health", 70, ConfigSupportLevel.SUPPORTED),
|
||||||
CONFIG("Config", 75, ConfigSupportLevel.SUPPORTED),
|
MANAGEMENT("Management", 75, ConfigSupportLevel.SUPPORTED),
|
||||||
METRICS("Metrics", 80, ConfigSupportLevel.SUPPORTED),
|
METRICS("Metrics", 80, ConfigSupportLevel.SUPPORTED),
|
||||||
PROXY("Proxy", 90, ConfigSupportLevel.SUPPORTED),
|
PROXY("Proxy", 90, ConfigSupportLevel.SUPPORTED),
|
||||||
VAULT("Vault", 100, ConfigSupportLevel.SUPPORTED),
|
VAULT("Vault", 100, ConfigSupportLevel.SUPPORTED),
|
||||||
|
|
|
@ -35,5 +35,6 @@ USER 1000
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
EXPOSE 8443
|
EXPOSE 8443
|
||||||
|
EXPOSE 9000
|
||||||
|
|
||||||
ENTRYPOINT [ "/opt/keycloak/bin/kc.sh" ]
|
ENTRYPOINT [ "/opt/keycloak/bin/kc.sh" ]
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,6 +45,8 @@ import io.quarkus.hibernate.orm.deployment.spi.AdditionalJpaModelBuildItem;
|
||||||
import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem;
|
import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem;
|
||||||
import io.quarkus.runtime.configuration.ConfigurationException;
|
import io.quarkus.runtime.configuration.ConfigurationException;
|
||||||
import io.quarkus.runtime.configuration.ProfileManager;
|
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 io.smallrye.config.ConfigValue;
|
||||||
import org.eclipse.microprofile.health.Readiness;
|
import org.eclipse.microprofile.health.Readiness;
|
||||||
import org.hibernate.cfg.AvailableSettings;
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
@ -69,6 +71,7 @@ import org.keycloak.common.crypto.FipsMode;
|
||||||
import org.keycloak.common.util.StreamUtil;
|
import org.keycloak.common.util.StreamUtil;
|
||||||
import org.keycloak.config.DatabaseOptions;
|
import org.keycloak.config.DatabaseOptions;
|
||||||
import org.keycloak.config.HealthOptions;
|
import org.keycloak.config.HealthOptions;
|
||||||
|
import org.keycloak.config.ManagementOptions;
|
||||||
import org.keycloak.config.MetricsOptions;
|
import org.keycloak.config.MetricsOptions;
|
||||||
import org.keycloak.config.SecurityOptions;
|
import org.keycloak.config.SecurityOptions;
|
||||||
import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
|
import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
|
||||||
|
@ -131,7 +134,6 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -232,6 +234,21 @@ class KeycloakProcessor {
|
||||||
recorder.configureProfile(profile.getName(), profile.getFeatures());
|
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)
|
@Record(ExecutionTime.STATIC_INIT)
|
||||||
@BuildStep
|
@BuildStep
|
||||||
@Consume(ConfigBuildItem.class)
|
@Consume(ConfigBuildItem.class)
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ package test.org.keycloak.quarkus.services.health;
|
||||||
|
|
||||||
import io.agroal.api.AgroalDataSource;
|
import io.agroal.api.AgroalDataSource;
|
||||||
import io.quarkus.test.QuarkusUnitTest;
|
import io.quarkus.test.QuarkusUnitTest;
|
||||||
|
import io.restassured.RestAssured;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
||||||
import org.jboss.shrinkwrap.api.spec.JavaArchive;
|
import org.jboss.shrinkwrap.api.spec.JavaArchive;
|
||||||
|
@ -41,6 +42,8 @@ public class KeycloakNegativeHealthCheckTest {
|
||||||
@Test
|
@Test
|
||||||
public void testReadinessDown() {
|
public void testReadinessDown() {
|
||||||
agroalDataSource.close();
|
agroalDataSource.close();
|
||||||
|
|
||||||
|
RestAssured.port = 9001;
|
||||||
given()
|
given()
|
||||||
.when().get("/health/ready")
|
.when().get("/health/ready")
|
||||||
.then()
|
.then()
|
||||||
|
|
|
@ -17,8 +17,10 @@
|
||||||
package test.org.keycloak.quarkus.services.health;
|
package test.org.keycloak.quarkus.services.health;
|
||||||
|
|
||||||
import io.quarkus.test.QuarkusUnitTest;
|
import io.quarkus.test.QuarkusUnitTest;
|
||||||
|
import io.restassured.RestAssured;
|
||||||
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
||||||
import org.jboss.shrinkwrap.api.spec.JavaArchive;
|
import org.jboss.shrinkwrap.api.spec.JavaArchive;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
|
@ -26,40 +28,18 @@ import static io.restassured.RestAssured.given;
|
||||||
|
|
||||||
class KeycloakPathConfigurationTest {
|
class KeycloakPathConfigurationTest {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpPort() {
|
||||||
|
RestAssured.port = 9001;
|
||||||
|
}
|
||||||
|
|
||||||
@RegisterExtension
|
@RegisterExtension
|
||||||
static final QuarkusUnitTest test = new QuarkusUnitTest()
|
static final QuarkusUnitTest test = new QuarkusUnitTest()
|
||||||
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
|
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
|
||||||
.addAsResource("keycloak.conf", "META-INF/keycloak.conf"))
|
.addAsResource("keycloak.conf", "META-INF/keycloak.conf"))
|
||||||
.overrideConfigKey("kc.http-relative-path","/auth")
|
.overrideConfigKey("kc.http-relative-path","/auth")
|
||||||
.overrideConfigKey("quarkus.http.non-application-root-path", "/q")
|
|
||||||
.overrideConfigKey("quarkus.micrometer.export.prometheus.path", "/prom/metrics");
|
.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
|
@Test
|
||||||
void testMetrics() {
|
void testMetrics() {
|
||||||
given().basePath("/")
|
given().basePath("/")
|
||||||
|
@ -68,6 +48,20 @@ class KeycloakPathConfigurationTest {
|
||||||
.statusCode(200);
|
.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
|
@Test
|
||||||
void testWrongMetricsEndpoints() {
|
void testWrongMetricsEndpoints() {
|
||||||
given().basePath("/")
|
given().basePath("/")
|
||||||
|
|
|
@ -17,19 +17,23 @@
|
||||||
package test.org.keycloak.quarkus.services.health;
|
package test.org.keycloak.quarkus.services.health;
|
||||||
|
|
||||||
import io.quarkus.test.QuarkusUnitTest;
|
import io.quarkus.test.QuarkusUnitTest;
|
||||||
|
import io.restassured.RestAssured;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
||||||
import org.jboss.shrinkwrap.api.spec.JavaArchive;
|
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.Test;
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
import static io.restassured.RestAssured.given;
|
import static io.restassured.RestAssured.given;
|
||||||
|
|
||||||
public class KeycloakReadyHealthCheckTest {
|
public class KeycloakReadyHealthCheckTest {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpPort() {
|
||||||
|
RestAssured.port = 9001;
|
||||||
|
}
|
||||||
|
|
||||||
@RegisterExtension
|
@RegisterExtension
|
||||||
static final QuarkusUnitTest test = new QuarkusUnitTest()
|
static final QuarkusUnitTest test = new QuarkusUnitTest()
|
||||||
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
|
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
|
||||||
|
@ -45,7 +49,7 @@ public class KeycloakReadyHealthCheckTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadinessUp() throws SQLException {
|
public void testReadinessUp() {
|
||||||
given()
|
given()
|
||||||
.when().get("/health/ready")
|
.when().get("/health/ready")
|
||||||
.then()
|
.then()
|
||||||
|
|
|
@ -30,6 +30,8 @@ import io.quarkus.agroal.DataSource;
|
||||||
import io.quarkus.arc.Arc;
|
import io.quarkus.arc.Arc;
|
||||||
import io.quarkus.arc.InstanceHandle;
|
import io.quarkus.arc.InstanceHandle;
|
||||||
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeInitListener;
|
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeInitListener;
|
||||||
|
import io.vertx.core.Handler;
|
||||||
|
import io.vertx.ext.web.RoutingContext;
|
||||||
import liquibase.Scope;
|
import liquibase.Scope;
|
||||||
|
|
||||||
import org.hibernate.cfg.AvailableSettings;
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
@ -73,6 +75,11 @@ public class KeycloakRecorder {
|
||||||
Profile.init(profileName, features);
|
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() {
|
public void configureTruststore() {
|
||||||
String[] truststores = Configuration.getOptionalKcValue(TruststoreOptions.TRUSTSTORE_PATHS.getKey())
|
String[] truststores = Configuration.getOptionalKcValue(TruststoreOptions.TRUSTSTORE_PATHS.getKey())
|
||||||
.map(s -> s.split(",")).orElse(new String[0]);
|
.map(s -> s.split(",")).orElse(new String[0]);
|
||||||
|
|
|
@ -52,6 +52,10 @@ public final class Configuration {
|
||||||
return getOptionalBooleanValue(NS_KEYCLOAK_PREFIX + option.getKey()).orElse(false);
|
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) {
|
public static boolean contains(Option<?> option, String value) {
|
||||||
return getOptionalValue(NS_KEYCLOAK_PREFIX + option.getKey())
|
return getOptionalValue(NS_KEYCLOAK_PREFIX + option.getKey())
|
||||||
.filter(f -> f.contains(value))
|
.filter(f -> f.contains(value))
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,6 +52,7 @@ public final class PropertyMappers {
|
||||||
MAPPERS.addAll(HttpPropertyMappers.getHttpPropertyMappers());
|
MAPPERS.addAll(HttpPropertyMappers.getHttpPropertyMappers());
|
||||||
MAPPERS.addAll(HealthPropertyMappers.getHealthPropertyMappers());
|
MAPPERS.addAll(HealthPropertyMappers.getHealthPropertyMappers());
|
||||||
MAPPERS.addAll(ConfigKeystorePropertyMappers.getConfigKeystorePropertyMappers());
|
MAPPERS.addAll(ConfigKeystorePropertyMappers.getConfigKeystorePropertyMappers());
|
||||||
|
MAPPERS.addAll(ManagementPropertyMappers.getManagementPropertyMappers());
|
||||||
MAPPERS.addAll(MetricsPropertyMappers.getMetricsPropertyMappers());
|
MAPPERS.addAll(MetricsPropertyMappers.getMetricsPropertyMappers());
|
||||||
MAPPERS.addAll(ProxyPropertyMappers.getProxyPropertyMappers());
|
MAPPERS.addAll(ProxyPropertyMappers.getProxyPropertyMappers());
|
||||||
MAPPERS.addAll(VaultPropertyMappers.getVaultPropertyMappers());
|
MAPPERS.addAll(VaultPropertyMappers.getVaultPropertyMappers());
|
||||||
|
|
|
@ -77,6 +77,10 @@ public class ConfigurationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void putEnvVars(Map<String, String> map) {
|
||||||
|
map.forEach(ConfigurationTest::putEnvVar);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static void removeEnvVar(String name) {
|
public static void removeEnvVar(String name) {
|
||||||
Map<String, String> env = System.getenv();
|
Map<String, String> env = System.getenv();
|
||||||
|
@ -573,7 +577,7 @@ public class ConfigurationTest {
|
||||||
assertEquals("secret", secret.getValue());
|
assertEquals("secret", secret.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Config.Scope initConfig(String... scope) {
|
protected Config.Scope initConfig(String... scope) {
|
||||||
Config.init(new MicroProfileConfigProvider(createConfig()));
|
Config.init(new MicroProfileConfigProvider(createConfig()));
|
||||||
return Config.scope(scope);
|
return Config.scope(scope);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,7 +29,9 @@ import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@DistributionTest(keepAlive =true)
|
@DistributionTest(keepAlive = true,
|
||||||
|
requestPort = 9000,
|
||||||
|
containerExposedPorts = {8080, 9000})
|
||||||
public class HealthDistTest {
|
public class HealthDistTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -83,7 +85,7 @@ public class HealthDistTest {
|
||||||
@Test
|
@Test
|
||||||
void testUsingRelativePath(KeycloakDistribution distribution) {
|
void testUsingRelativePath(KeycloakDistribution distribution) {
|
||||||
for (String relativePath : List.of("/auth", "/auth/", "auth")) {
|
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("/")) {
|
if (!relativePath.endsWith("/")) {
|
||||||
relativePath = relativePath + "/";
|
relativePath = relativePath + "/";
|
||||||
}
|
}
|
||||||
|
@ -95,7 +97,7 @@ public class HealthDistTest {
|
||||||
@Test
|
@Test
|
||||||
void testMultipleRequests(KeycloakDistribution distribution) throws Exception {
|
void testMultipleRequests(KeycloakDistribution distribution) throws Exception {
|
||||||
for (String relativePath : List.of("/", "/auth/", "auth")) {
|
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);
|
CompletableFuture future = CompletableFuture.completedFuture(null);
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
|
@ -123,7 +125,9 @@ public class HealthDistTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Launch({ "start-dev", "--features=multi-site" })
|
@Launch({ "start-dev", "--features=multi-site" })
|
||||||
void testLoadBalancerCheck() {
|
void testLoadBalancerCheck(KeycloakDistribution distribution) {
|
||||||
|
distribution.setRequestPort(8080);
|
||||||
|
|
||||||
when().get("/lb-check").then()
|
when().get("/lb-check").then()
|
||||||
.statusCode(200);
|
.statusCode(200);
|
||||||
}
|
}
|
||||||
|
|
167
quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ManagementDistTest.java
vendored
Normal file
167
quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ManagementDistTest.java
vendored
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
65
quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ManagementHttpsDistTest.java
vendored
Normal file
65
quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ManagementHttpsDistTest.java
vendored
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,7 +30,9 @@ import org.keycloak.it.utils.KeycloakDistribution;
|
||||||
|
|
||||||
import io.quarkus.test.junit.main.Launch;
|
import io.quarkus.test.junit.main.Launch;
|
||||||
|
|
||||||
@DistributionTest(keepAlive =true)
|
@DistributionTest(keepAlive = true,
|
||||||
|
requestPort = 9000,
|
||||||
|
containerExposedPorts = {8080, 9000})
|
||||||
public class MetricsDistTest {
|
public class MetricsDistTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -67,7 +69,7 @@ public class MetricsDistTest {
|
||||||
@Test
|
@Test
|
||||||
void testUsingRelativePath(KeycloakDistribution distribution) {
|
void testUsingRelativePath(KeycloakDistribution distribution) {
|
||||||
for (String relativePath : List.of("/auth", "/auth/", "auth")) {
|
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("/")) {
|
if (!relativePath.endsWith("/")) {
|
||||||
relativePath = relativePath + "/";
|
relativePath = relativePath + "/";
|
||||||
}
|
}
|
||||||
|
@ -79,8 +81,8 @@ public class MetricsDistTest {
|
||||||
@Test
|
@Test
|
||||||
void testMultipleRequests(KeycloakDistribution distribution) throws Exception {
|
void testMultipleRequests(KeycloakDistribution distribution) throws Exception {
|
||||||
for (String relativePath : List.of("/", "/auth/", "auth")) {
|
for (String relativePath : List.of("/", "/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);
|
||||||
CompletableFuture future = CompletableFuture.completedFuture(null);
|
CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
future = CompletableFuture.allOf(CompletableFuture.runAsync(new Runnable() {
|
future = CompletableFuture.allOf(CompletableFuture.runAsync(new Runnable() {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTI
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import io.restassured.RestAssured;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||||
import org.junit.jupiter.api.Order;
|
import org.junit.jupiter.api.Order;
|
||||||
|
@ -121,6 +122,7 @@ public class QuarkusPropertiesDistTest {
|
||||||
void testUnknownQuarkusBuildTimePropertyApplied(LaunchResult result) {
|
void testUnknownQuarkusBuildTimePropertyApplied(LaunchResult result) {
|
||||||
CLIResult cliResult = (CLIResult) result;
|
CLIResult cliResult = (CLIResult) result;
|
||||||
cliResult.assertNoBuild();
|
cliResult.assertNoBuild();
|
||||||
|
RestAssured.port = 9000;
|
||||||
when().get("/metrics").then().statusCode(200)
|
when().get("/metrics").then().statusCode(200)
|
||||||
.body(containsString("jvm_gc_"));
|
.body(containsString("jvm_gc_"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,23 @@ Health:
|
||||||
are available at the '/health', '/health/ready' and '/health/live'
|
are available at the '/health', '/health/ready' and '/health/live'
|
||||||
endpoints. Default: false.
|
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:
|
||||||
|
|
||||||
--metrics-enabled <true|false>
|
--metrics-enabled <true|false>
|
||||||
|
|
|
@ -14,6 +14,15 @@ Options:
|
||||||
built a server image using the 'build' command.
|
built a server image using the 'build' command.
|
||||||
-v, --verbose Print out error details when running this 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:
|
Database:
|
||||||
|
|
||||||
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
||||||
|
@ -59,14 +68,42 @@ Feature:
|
||||||
--features-disabled <feature>
|
--features-disabled <feature>
|
||||||
Disables a set of one or more features. Possible values are: <...>.
|
Disables a set of one or more features. Possible values are: <...>.
|
||||||
|
|
||||||
Config:
|
Management:
|
||||||
|
|
||||||
--config-keystore <config-keystore>
|
--http-management-port <port>
|
||||||
Specifies a path to the KeyStore Configuration Source.
|
Port of the management interface. Default: 9000. Available only when
|
||||||
--config-keystore-password <config-keystore-password>
|
Management interface is enabled.
|
||||||
Specifies a password to the KeyStore Configuration Source.
|
--http-management-relative-path <path>
|
||||||
--config-keystore-type <config-keystore-type>
|
Set the path relative to '/' for serving resources from management interface.
|
||||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
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:
|
Logging:
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,15 @@ Options:
|
||||||
built a server image using the 'build' command.
|
built a server image using the 'build' command.
|
||||||
-v, --verbose Print out error details when running this 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:
|
Database:
|
||||||
|
|
||||||
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
||||||
|
@ -59,14 +68,42 @@ Feature:
|
||||||
--features-disabled <feature>
|
--features-disabled <feature>
|
||||||
Disables a set of one or more features. Possible values are: <...>.
|
Disables a set of one or more features. Possible values are: <...>.
|
||||||
|
|
||||||
Config:
|
Management:
|
||||||
|
|
||||||
--config-keystore <config-keystore>
|
--http-management-port <port>
|
||||||
Specifies a path to the KeyStore Configuration Source.
|
Port of the management interface. Default: 9000. Available only when
|
||||||
--config-keystore-password <config-keystore-password>
|
Management interface is enabled.
|
||||||
Specifies a password to the KeyStore Configuration Source.
|
--http-management-relative-path <path>
|
||||||
--config-keystore-type <config-keystore-type>
|
Set the path relative to '/' for serving resources from management interface.
|
||||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
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:
|
Logging:
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,15 @@ Options:
|
||||||
built a server image using the 'build' command.
|
built a server image using the 'build' command.
|
||||||
-v, --verbose Print out error details when running this 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:
|
Database:
|
||||||
|
|
||||||
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
||||||
|
@ -59,14 +68,42 @@ Feature:
|
||||||
--features-disabled <feature>
|
--features-disabled <feature>
|
||||||
Disables a set of one or more features. Possible values are: <...>.
|
Disables a set of one or more features. Possible values are: <...>.
|
||||||
|
|
||||||
Config:
|
Management:
|
||||||
|
|
||||||
--config-keystore <config-keystore>
|
--http-management-port <port>
|
||||||
Specifies a path to the KeyStore Configuration Source.
|
Port of the management interface. Default: 9000. Available only when
|
||||||
--config-keystore-password <config-keystore-password>
|
Management interface is enabled.
|
||||||
Specifies a password to the KeyStore Configuration Source.
|
--http-management-relative-path <path>
|
||||||
--config-keystore-type <config-keystore-type>
|
Set the path relative to '/' for serving resources from management interface.
|
||||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
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:
|
Logging:
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,15 @@ Options:
|
||||||
built a server image using the 'build' command.
|
built a server image using the 'build' command.
|
||||||
-v, --verbose Print out error details when running this 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:
|
Database:
|
||||||
|
|
||||||
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
||||||
|
@ -59,14 +68,42 @@ Feature:
|
||||||
--features-disabled <feature>
|
--features-disabled <feature>
|
||||||
Disables a set of one or more features. Possible values are: <...>.
|
Disables a set of one or more features. Possible values are: <...>.
|
||||||
|
|
||||||
Config:
|
Management:
|
||||||
|
|
||||||
--config-keystore <config-keystore>
|
--http-management-port <port>
|
||||||
Specifies a path to the KeyStore Configuration Source.
|
Port of the management interface. Default: 9000. Available only when
|
||||||
--config-keystore-password <config-keystore-password>
|
Management interface is enabled.
|
||||||
Specifies a password to the KeyStore Configuration Source.
|
--http-management-relative-path <path>
|
||||||
--config-keystore-type <config-keystore-type>
|
Set the path relative to '/' for serving resources from management interface.
|
||||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
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:
|
Logging:
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,15 @@ Cache:
|
||||||
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
||||||
Possible values are: tcp, udp, kubernetes, ec2, azure, google.
|
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:
|
Database:
|
||||||
|
|
||||||
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
--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'
|
are available at the '/health', '/health/ready' and '/health/live'
|
||||||
endpoints. Default: false.
|
endpoints. Default: false.
|
||||||
|
|
||||||
Config:
|
Management:
|
||||||
|
|
||||||
--config-keystore <config-keystore>
|
--http-management-port <port>
|
||||||
Specifies a path to the KeyStore Configuration Source.
|
Port of the management interface. Default: 9000. Available only when
|
||||||
--config-keystore-password <config-keystore-password>
|
Management interface is enabled.
|
||||||
Specifies a password to the KeyStore Configuration Source.
|
--http-management-relative-path <path>
|
||||||
--config-keystore-type <config-keystore-type>
|
Set the path relative to '/' for serving resources from management interface.
|
||||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
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:
|
Metrics:
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,15 @@ Cache:
|
||||||
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
||||||
Possible values are: tcp, udp, kubernetes, ec2, azure, google.
|
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:
|
Database:
|
||||||
|
|
||||||
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
--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'
|
are available at the '/health', '/health/ready' and '/health/live'
|
||||||
endpoints. Default: false.
|
endpoints. Default: false.
|
||||||
|
|
||||||
Config:
|
Management:
|
||||||
|
|
||||||
--config-keystore <config-keystore>
|
--http-management-port <port>
|
||||||
Specifies a path to the KeyStore Configuration Source.
|
Port of the management interface. Default: 9000. Available only when
|
||||||
--config-keystore-password <config-keystore-password>
|
Management interface is enabled.
|
||||||
Specifies a password to the KeyStore Configuration Source.
|
--http-management-relative-path <path>
|
||||||
--config-keystore-type <config-keystore-type>
|
Set the path relative to '/' for serving resources from management interface.
|
||||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
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:
|
Metrics:
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,15 @@ Cache:
|
||||||
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
||||||
Possible values are: tcp, udp, kubernetes, ec2, azure, google.
|
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:
|
Database:
|
||||||
|
|
||||||
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
--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'
|
are available at the '/health', '/health/ready' and '/health/live'
|
||||||
endpoints. Default: false.
|
endpoints. Default: false.
|
||||||
|
|
||||||
Config:
|
Management:
|
||||||
|
|
||||||
--config-keystore <config-keystore>
|
--http-management-port <port>
|
||||||
Specifies a path to the KeyStore Configuration Source.
|
Port of the management interface. Default: 9000. Available only when
|
||||||
--config-keystore-password <config-keystore-password>
|
Management interface is enabled.
|
||||||
Specifies a password to the KeyStore Configuration Source.
|
--http-management-relative-path <path>
|
||||||
--config-keystore-type <config-keystore-type>
|
Set the path relative to '/' for serving resources from management interface.
|
||||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
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:
|
Metrics:
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,15 @@ Cache:
|
||||||
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
||||||
Possible values are: tcp, udp, kubernetes, ec2, azure, google.
|
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:
|
Database:
|
||||||
|
|
||||||
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
--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'
|
are available at the '/health', '/health/ready' and '/health/live'
|
||||||
endpoints. Default: false.
|
endpoints. Default: false.
|
||||||
|
|
||||||
Config:
|
Management:
|
||||||
|
|
||||||
--config-keystore <config-keystore>
|
--http-management-port <port>
|
||||||
Specifies a path to the KeyStore Configuration Source.
|
Port of the management interface. Default: 9000. Available only when
|
||||||
--config-keystore-password <config-keystore-password>
|
Management interface is enabled.
|
||||||
Specifies a password to the KeyStore Configuration Source.
|
--http-management-relative-path <path>
|
||||||
--config-keystore-type <config-keystore-type>
|
Set the path relative to '/' for serving resources from management interface.
|
||||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
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:
|
Metrics:
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,15 @@ Cache:
|
||||||
specified, 'cache-remote-host' and 'cache-remote-password' are required as
|
specified, 'cache-remote-host' and 'cache-remote-password' are required as
|
||||||
well and the related configuration in XML file should not be present.
|
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:
|
Database:
|
||||||
|
|
||||||
--db-password <password>
|
--db-password <password>
|
||||||
|
@ -159,14 +168,28 @@ HTTP(S):
|
||||||
'strict' and no value is set, it defaults to 'BCFKS'. Use the System
|
'strict' and no value is set, it defaults to 'BCFKS'. Use the System
|
||||||
Truststore instead, see the docs for details.
|
Truststore instead, see the docs for details.
|
||||||
|
|
||||||
Config:
|
Management:
|
||||||
|
|
||||||
--config-keystore <config-keystore>
|
--http-management-port <port>
|
||||||
Specifies a path to the KeyStore Configuration Source.
|
Port of the management interface. Default: 9000. Available only when
|
||||||
--config-keystore-password <config-keystore-password>
|
Management interface is enabled.
|
||||||
Specifies a password to the KeyStore Configuration Source.
|
--https-management-certificate-file <file>
|
||||||
--config-keystore-type <config-keystore-type>
|
The file path to a server certificate or certificate chain in PEM format for
|
||||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
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:
|
Proxy:
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,15 @@ Cache:
|
||||||
specified, 'cache-remote-host' and 'cache-remote-password' are required as
|
specified, 'cache-remote-host' and 'cache-remote-password' are required as
|
||||||
well and the related configuration in XML file should not be present.
|
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:
|
Database:
|
||||||
|
|
||||||
--db-password <password>
|
--db-password <password>
|
||||||
|
@ -162,14 +171,28 @@ HTTP(S):
|
||||||
'strict' and no value is set, it defaults to 'BCFKS'. Use the System
|
'strict' and no value is set, it defaults to 'BCFKS'. Use the System
|
||||||
Truststore instead, see the docs for details.
|
Truststore instead, see the docs for details.
|
||||||
|
|
||||||
Config:
|
Management:
|
||||||
|
|
||||||
--config-keystore <config-keystore>
|
--http-management-port <port>
|
||||||
Specifies a path to the KeyStore Configuration Source.
|
Port of the management interface. Default: 9000. Available only when
|
||||||
--config-keystore-password <config-keystore-password>
|
Management interface is enabled.
|
||||||
Specifies a password to the KeyStore Configuration Source.
|
--https-management-certificate-file <file>
|
||||||
--config-keystore-type <config-keystore-type>
|
The file path to a server certificate or certificate chain in PEM format for
|
||||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
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:
|
Proxy:
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,10 @@
|
||||||
<version>${junit.version}</version>
|
<version>${junit.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.rest-assured</groupId>
|
||||||
|
<artifactId>rest-assured</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.quarkus</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>quarkus-junit5-internal</artifactId>
|
<artifactId>quarkus-junit5-internal</artifactId>
|
||||||
|
|
|
@ -64,5 +64,15 @@ public @interface DistributionTest {
|
||||||
* If any option must be set when starting the server.
|
* If any option must be set when starting the server.
|
||||||
*/
|
*/
|
||||||
String[] defaultOptions() default {};
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,8 @@ public enum DistributionType {
|
||||||
return new DockerKeycloakDistribution(
|
return new DockerKeycloakDistribution(
|
||||||
config.debug(),
|
config.debug(),
|
||||||
config.keepAlive(),
|
config.keepAlive(),
|
||||||
!DistributionTest.ReInstall.NEVER.equals(config.reInstall()));
|
config.requestPort(),
|
||||||
|
config.containerExposedPorts());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static KeycloakDistribution createRawDistribution(DistributionTest config) {
|
private static KeycloakDistribution createRawDistribution(DistributionTest config) {
|
||||||
|
@ -42,7 +43,8 @@ public enum DistributionType {
|
||||||
config.keepAlive(),
|
config.keepAlive(),
|
||||||
config.enableTls(),
|
config.enableTls(),
|
||||||
!DistributionTest.ReInstall.NEVER.equals(config.reInstall()),
|
!DistributionTest.ReInstall.NEVER.equals(config.reInstall()),
|
||||||
config.removeBuildOptionsAfterBuild());
|
config.removeBuildOptionsAfterBuild(),
|
||||||
|
config.requestPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Function<DistributionTest, KeycloakDistribution> factory;
|
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) {
|
public KeycloakDistribution newInstance(DistributionTest config) {
|
||||||
return factory.apply(config);
|
return factory.apply(config);
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,16 @@ public class KeycloakDistributionDecorator implements KeycloakDistribution {
|
||||||
delegate.assertStopped();
|
delegate.assertStopped();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequestPort() {
|
||||||
|
delegate.setRequestPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequestPort(int port) {
|
||||||
|
delegate.setRequestPort(port);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <D extends KeycloakDistribution> D unwrap(Class<D> type) {
|
public <D extends KeycloakDistribution> D unwrap(Class<D> type) {
|
||||||
if (!KeycloakDistribution.class.isAssignableFrom(type)) {
|
if (!KeycloakDistribution.class.isAssignableFrom(type)) {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.keycloak.it.utils;
|
package org.keycloak.it.utils;
|
||||||
|
|
||||||
import com.github.dockerjava.api.DockerClient;
|
import com.github.dockerjava.api.DockerClient;
|
||||||
|
import com.github.dockerjava.api.exception.NotFoundException;
|
||||||
|
import io.restassured.RestAssured;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.Version;
|
import org.keycloak.common.Version;
|
||||||
import org.keycloak.it.junit5.extension.CLIResult;
|
import org.keycloak.it.junit5.extension.CLIResult;
|
||||||
|
@ -15,36 +17,41 @@ import org.testcontainers.utility.DockerImageName;
|
||||||
import org.testcontainers.utility.LazyFuture;
|
import org.testcontainers.utility.LazyFuture;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
public final class DockerKeycloakDistribution implements KeycloakDistribution {
|
public final class DockerKeycloakDistribution implements KeycloakDistribution {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(DockerKeycloakDistribution.class);
|
private static final Logger LOGGER = Logger.getLogger(DockerKeycloakDistribution.class);
|
||||||
|
|
||||||
private boolean debug;
|
private final boolean debug;
|
||||||
private boolean manualStop;
|
private final boolean manualStop;
|
||||||
|
private final int requestPort;
|
||||||
|
private final Integer[] exposedPorts;
|
||||||
|
|
||||||
private int exitCode = -1;
|
private int exitCode = -1;
|
||||||
|
|
||||||
private String stdout = "";
|
private String stdout = "";
|
||||||
private String stderr = "";
|
private String stderr = "";
|
||||||
private ToStringConsumer backupConsumer = new ToStringConsumer();
|
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 GenericContainer<?> keycloakContainer = null;
|
||||||
private String containerId = null;
|
private String containerId = null;
|
||||||
|
|
||||||
private Executor parallelReaperExecutor = Executors.newSingleThreadExecutor();
|
private final Executor parallelReaperExecutor = Executors.newSingleThreadExecutor();
|
||||||
private Map<String, String> envVars = new HashMap<>();
|
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.debug = debug;
|
||||||
this.manualStop = manualStop;
|
this.manualStop = manualStop;
|
||||||
|
this.requestPort = requestPort;
|
||||||
|
this.exposedPorts = IntStream.of(exposedPorts).boxed().toArray(Integer[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -79,10 +86,10 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
|
||||||
|
|
||||||
return new GenericContainer<>(image)
|
return new GenericContainer<>(image)
|
||||||
.withEnv(envVars)
|
.withEnv(envVars)
|
||||||
.withExposedPorts(8080)
|
.withExposedPorts(exposedPorts)
|
||||||
.withStartupAttempts(1)
|
.withStartupAttempts(1)
|
||||||
.withStartupTimeout(Duration.ofSeconds(120))
|
.withStartupTimeout(Duration.ofSeconds(120))
|
||||||
.waitingFor(Wait.forListeningPort());
|
.waitingFor(Wait.forListeningPorts(8080));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -118,20 +125,20 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trySetRestAssuredPort();
|
setRequestPort();
|
||||||
|
|
||||||
return CLIResult.create(getOutputStream(), getErrorStream(), getExitCode());
|
return CLIResult.create(getOutputStream(), getErrorStream(), getExitCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void trySetRestAssuredPort() {
|
@Override
|
||||||
try {
|
public void setRequestPort() {
|
||||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
setRequestPort(requestPort);
|
||||||
Class<?> restAssured = classLoader.loadClass("io.restassured.RestAssured");
|
}
|
||||||
Field port = restAssured.getDeclaredField("port");
|
|
||||||
port.set(null, keycloakContainer.getMappedPort(8080));
|
@Override
|
||||||
} catch (Exception ignore) {
|
public void setRequestPort(int port) {
|
||||||
// keeping the workaround to set the container port to restassured
|
if (keycloakContainer != null) {
|
||||||
// TODO: better way to expose the port to tests
|
RestAssured.port = keycloakContainer.getMappedPort(port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,9 +193,12 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
|
if (containerId == null) return;
|
||||||
DockerClient dockerClient = DockerClientFactory.lazyClient();
|
DockerClient dockerClient = DockerClientFactory.lazyClient();
|
||||||
dockerClient.killContainerCmd(containerId).exec();
|
dockerClient.killContainerCmd(containerId).exec();
|
||||||
dockerClient.removeContainerCmd(containerId).withRemoveVolumes(true).withForce(true).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) {
|
} catch (Exception cause) {
|
||||||
throw new RuntimeException("Failed to stop and remove container", cause);
|
throw new RuntimeException("Failed to stop and remove container", cause);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,10 @@ public interface KeycloakDistribution {
|
||||||
|
|
||||||
void assertStopped();
|
void assertStopped();
|
||||||
|
|
||||||
|
void setRequestPort();
|
||||||
|
|
||||||
|
void setRequestPort(int port);
|
||||||
|
|
||||||
default String[] getCliArgs(List<String> arguments) {
|
default String[] getCliArgs(List<String> arguments) {
|
||||||
throw new RuntimeException("Not implemented");
|
throw new RuntimeException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,7 @@ import javax.net.ssl.X509TrustManager;
|
||||||
import io.quarkus.deployment.util.FileUtil;
|
import io.quarkus.deployment.util.FileUtil;
|
||||||
import io.quarkus.fs.util.ZipUtils;
|
import io.quarkus.fs.util.ZipUtils;
|
||||||
|
|
||||||
|
import io.restassured.RestAssured;
|
||||||
import org.awaitility.Awaitility;
|
import org.awaitility.Awaitility;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
||||||
|
@ -91,20 +92,22 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
|
||||||
private String relativePath;
|
private String relativePath;
|
||||||
private int httpPort;
|
private int httpPort;
|
||||||
private int httpsPort;
|
private int httpsPort;
|
||||||
private boolean debug;
|
private final boolean debug;
|
||||||
private boolean enableTls;
|
private final boolean enableTls;
|
||||||
private boolean reCreate;
|
private final boolean reCreate;
|
||||||
private boolean removeBuildOptionsAfterBuild;
|
private final boolean removeBuildOptionsAfterBuild;
|
||||||
|
private final int requestPort;
|
||||||
private ExecutorService outputExecutor;
|
private ExecutorService outputExecutor;
|
||||||
private boolean inited = false;
|
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.debug = debug;
|
||||||
this.manualStop = manualStop;
|
this.manualStop = manualStop;
|
||||||
this.enableTls = enableTls;
|
this.enableTls = enableTls;
|
||||||
this.reCreate = reCreate;
|
this.reCreate = reCreate;
|
||||||
this.removeBuildOptionsAfterBuild = removeBuildOptionsAfterBuild;
|
this.removeBuildOptionsAfterBuild = removeBuildOptionsAfterBuild;
|
||||||
|
this.requestPort = requestPort;
|
||||||
this.distPath = prepareDistribution();
|
this.distPath = prepareDistribution();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,6 +148,8 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setRequestPort();
|
||||||
|
|
||||||
return CLIResult.create(getOutputStream(), getErrorStream(), getExitCode());
|
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 {
|
private void waitForReadiness() throws MalformedURLException {
|
||||||
waitForReadiness("http", httpPort);
|
waitForReadiness("http", httpPort);
|
||||||
|
|
||||||
|
|
|
@ -164,6 +164,8 @@ public abstract class AbstractQuarkusDeployableContainer implements DeployableCo
|
||||||
if (suiteContext.get().isAuthServerMigrationEnabled()) {
|
if (suiteContext.get().isAuthServerMigrationEnabled()) {
|
||||||
commands.add("--hostname-strict=false");
|
commands.add("--hostname-strict=false");
|
||||||
commands.add("--hostname-strict-https=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) {
|
if (configuration.getRoute() != null) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ public class KeycloakQuarkusConfiguration implements ContainerConfiguration {
|
||||||
private int bindHttpPort = 8080;
|
private int bindHttpPort = 8080;
|
||||||
private int bindHttpsPortOffset = 0;
|
private int bindHttpsPortOffset = 0;
|
||||||
private int bindHttpsPort = Integer.getInteger("auth.server.https.port", 8543);
|
private int bindHttpsPort = Integer.getInteger("auth.server.https.port", 8543);
|
||||||
|
private int managementPort = 9000;
|
||||||
|
|
||||||
private String keystoreFile = System.getProperty("auth.server.keystore");
|
private String keystoreFile = System.getProperty("auth.server.keystore");
|
||||||
|
|
||||||
|
@ -59,7 +60,7 @@ public class KeycloakQuarkusConfiguration implements ContainerConfiguration {
|
||||||
int newHttpsPort = baseHttpsPort + bindHttpsPortOffset;
|
int newHttpsPort = baseHttpsPort + bindHttpsPortOffset;
|
||||||
setBindHttpsPort(newHttpsPort);
|
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) {
|
if (this.keycloakConfigPropertyOverrides != null) {
|
||||||
try {
|
try {
|
||||||
|
@ -103,6 +104,14 @@ public class KeycloakQuarkusConfiguration implements ContainerConfiguration {
|
||||||
this.bindHttpPort = bindHttpPort;
|
this.bindHttpPort = bindHttpPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getManagementPort() {
|
||||||
|
return managementPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setManagementPort(int managementPort) {
|
||||||
|
this.managementPort = managementPort;
|
||||||
|
}
|
||||||
|
|
||||||
public String getKeystoreFile() {
|
public String getKeystoreFile() {
|
||||||
return keystoreFile;
|
return keystoreFile;
|
||||||
}
|
}
|
||||||
|
|
|
@ -693,6 +693,7 @@
|
||||||
<property name="bindHttpsPort">${auth.server.https.port}</property>
|
<property name="bindHttpsPort">${auth.server.https.port}</property>
|
||||||
<property name="bindHttpPortOffset">2</property>
|
<property name="bindHttpPortOffset">2</property>
|
||||||
<property name="bindHttpsPortOffset">2</property>
|
<property name="bindHttpsPortOffset">2</property>
|
||||||
|
<property name="managementPort">9001</property>
|
||||||
<property name="route">node2</property>
|
<property name="route">node2</property>
|
||||||
<property name="remoteMode">${quarkus.remote}</property>
|
<property name="remoteMode">${quarkus.remote}</property>
|
||||||
<property name="profile">ha</property>
|
<property name="profile">ha</property>
|
||||||
|
|
Loading…
Reference in a new issue