From c4b978b6c8b8d67ef6135397350d00e37b0707d7 Mon Sep 17 00:00:00 2001 From: Jonathan Vila Date: Tue, 8 Feb 2022 15:13:58 +0100 Subject: [PATCH] Operator Clustering support Co-authored-by: Jonathan Vila Co-authored-by: Andrea Peruffo --- .github/workflows/operator-ci.yml | 2 +- operator/README.md | 16 + operator/pom.xml | 5 + .../java/org/keycloak/operator/Constants.java | 10 +- .../operator/OperatorManagedResource.java | 6 + .../org/keycloak/operator/StatusUpdater.java | 6 + .../operator/v2alpha1/KeycloakController.java | 22 +- .../operator/v2alpha1/KeycloakDeployment.java | 33 +- .../v2alpha1/KeycloakDiscoveryService.java | 95 + .../v2alpha1/KeycloakRealmImportJob.java | 7 +- .../v2alpha1/KeycloakRealmImportSecret.java | 7 +- .../operator/v2alpha1/KeycloakService.java | 94 + operator/src/main/kubernetes/kubernetes.yml | 1 + .../operator/ClusterOperatorTest.java | 6 +- .../keycloak/operator/ClusteringE2EIT.java | 200 ++ .../operator/KeycloakServicesE2EIT.java | 95 + .../keycloak/operator/RealmImportE2EIT.java | 121 +- .../org/keycloak/operator/utils/CRAssert.java | 5 + .../org/keycloak/operator/utils/K8sUtils.java | 49 + .../src/test/resources/token-test-realm.yaml | 1725 +++++++++++++++++ 20 files changed, 2373 insertions(+), 132 deletions(-) create mode 100644 operator/src/main/java/org/keycloak/operator/StatusUpdater.java create mode 100644 operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakDiscoveryService.java create mode 100644 operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakService.java create mode 100644 operator/src/test/java/org/keycloak/operator/ClusteringE2EIT.java create mode 100644 operator/src/test/java/org/keycloak/operator/KeycloakServicesE2EIT.java create mode 100644 operator/src/test/resources/token-test-realm.yaml diff --git a/.github/workflows/operator-ci.yml b/.github/workflows/operator-ci.yml index 3568cae18d..b429054aa7 100644 --- a/.github/workflows/operator-ci.yml +++ b/.github/workflows/operator-ci.yml @@ -61,7 +61,7 @@ jobs: - name: Test operator running in cluster working-directory: operator run: | - eval $(minikube -p minikube docker-env) + eval $(minikube -p minikube docker-env) mvn clean verify \ -Dquarkus.container-image.build=true -Dquarkus.container-image.tag=test \ -Dquarkus.kubernetes.deployment-target=kubernetes \ diff --git a/operator/README.md b/operator/README.md index 8e732f9067..1764136bae 100644 --- a/operator/README.md +++ b/operator/README.md @@ -64,3 +64,19 @@ Remove the created resources with: ```bash kubectl delete -k ``` + +### Testing + +Testing allows 2 methods specified in the property `test.operator.deployment` : `local` & `remote`. + +`local` : resources will be deployed to the local cluster and the operator will run out of the cluster + +`remote` : same as local test but an image for the operator will be generated and deployed run inside the cluster + +```bash +mvn clean verify \ + -Dquarkus.container-image.build=true \ + -Dquarkus.container-image.tag=test \ + -Dquarkus.kubernetes.deployment-target=kubernetes \ + -Dtest.operator.deployment=remote +``` \ No newline at end of file diff --git a/operator/pom.xml b/operator/pom.xml index e51ec24819..2827889048 100644 --- a/operator/pom.xml +++ b/operator/pom.xml @@ -137,6 +137,11 @@ ${awaitility.version} test + + io.rest-assured + rest-assured + test + diff --git a/operator/src/main/java/org/keycloak/operator/Constants.java b/operator/src/main/java/org/keycloak/operator/Constants.java index 51f4fb29aa..f81cc4b8e2 100644 --- a/operator/src/main/java/org/keycloak/operator/Constants.java +++ b/operator/src/main/java/org/keycloak/operator/Constants.java @@ -33,7 +33,9 @@ public final class Constants { ); public static final Map DEFAULT_DIST_CONFIG = Map.of( - "KC_HEALTH_ENABLED", "true" + "KC_HEALTH_ENABLED","true", + "KC_CACHE", "ispn", + "KC_CACHE_STACK", "kubernetes" ); // Init container @@ -42,4 +44,10 @@ public final class Constants { public static final String INIT_CONTAINER_NAME = "keycloak-extensions"; public static final String INIT_CONTAINER_EXTENSIONS_FOLDER = "/opt/extensions"; public static final String INIT_CONTAINER_EXTENSIONS_ENV_VAR = "KEYCLOAK_EXTENSIONS"; + + public static final Integer KEYCLOAK_SERVICE_PORT = 8080; + public static final String KEYCLOAK_SERVICE_PROTOCOL = "TCP"; + public static final String KEYCLOAK_SERVICE_SUFFIX = "-service"; + public static final Integer KEYCLOAK_DISCOVERY_SERVICE_PORT = 7800; + public static final String KEYCLOAK_DISCOVERY_SERVICE_SUFFIX = "-discovery"; } diff --git a/operator/src/main/java/org/keycloak/operator/OperatorManagedResource.java b/operator/src/main/java/org/keycloak/operator/OperatorManagedResource.java index e2ec8586c2..ca9ea5e596 100644 --- a/operator/src/main/java/org/keycloak/operator/OperatorManagedResource.java +++ b/operator/src/main/java/org/keycloak/operator/OperatorManagedResource.java @@ -85,4 +85,10 @@ public abstract class OperatorManagedResource { resource.getMetadata().setOwnerReferences(Collections.singletonList(owner)); } + + protected String getNamespace() { + return cr.getMetadata().getNamespace(); + } + + protected abstract String getName(); } diff --git a/operator/src/main/java/org/keycloak/operator/StatusUpdater.java b/operator/src/main/java/org/keycloak/operator/StatusUpdater.java new file mode 100644 index 0000000000..61b9856773 --- /dev/null +++ b/operator/src/main/java/org/keycloak/operator/StatusUpdater.java @@ -0,0 +1,6 @@ +package org.keycloak.operator; + +public interface StatusUpdater { + + void updateStatus(T status); +} diff --git a/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakController.java b/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakController.java index 6e01f6e300..e27a47a468 100644 --- a/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakController.java +++ b/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakController.java @@ -16,8 +16,7 @@ */ package org.keycloak.operator.v2alpha1; -import javax.inject.Inject; - +import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.informers.SharedIndexInformer; @@ -39,7 +38,7 @@ import org.keycloak.operator.v2alpha1.crds.Keycloak; import org.keycloak.operator.v2alpha1.crds.KeycloakStatus; import org.keycloak.operator.v2alpha1.crds.KeycloakStatusBuilder; -import java.util.Collections; +import javax.inject.Inject; import java.util.List; import java.util.Optional; @@ -62,9 +61,15 @@ public class KeycloakController implements Reconciler, EventSourceInit .withLabels(Constants.DEFAULT_LABELS) .runnableInformer(0); - EventSource deploymentEvent = new InformerEventSource<>(deploymentInformer, Mappers.fromOwnerReference()); + SharedIndexInformer servicesInformer = + client.services().inNamespace(context.getConfigurationService().getClientConfiguration().getNamespace()) + .withLabels(Constants.DEFAULT_LABELS) + .runnableInformer(0); - return List.of(deploymentEvent); + EventSource deploymentEvent = new InformerEventSource<>(deploymentInformer, Mappers.fromOwnerReference()); + EventSource servicesEvent = new InformerEventSource<>(servicesInformer, Mappers.fromOwnerReference()); + + return List.of(deploymentEvent, servicesEvent); } @Override @@ -82,6 +87,13 @@ public class KeycloakController implements Reconciler, EventSourceInit kcDeployment.updateStatus(statusBuilder); kcDeployment.createOrUpdateReconciled(); + var kcService = new KeycloakService(client, kc); + kcService.updateStatus(statusBuilder); + kcService.createOrUpdateReconciled(); + var kcDiscoveryService = new KeycloakDiscoveryService(client, kc); + kcDiscoveryService.updateStatus(statusBuilder); + kcDiscoveryService.createOrUpdateReconciled(); + var status = statusBuilder.build(); Log.info("--- Reconciliation finished successfully"); diff --git a/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakDeployment.java b/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakDeployment.java index f9286d010a..b8b72d68fb 100644 --- a/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakDeployment.java +++ b/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakDeployment.java @@ -18,6 +18,7 @@ package org.keycloak.operator.v2alpha1; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; +import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.EnvVarBuilder; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.VolumeBuilder; @@ -32,6 +33,7 @@ import io.quarkus.logging.Log; import org.keycloak.operator.Config; import org.keycloak.operator.Constants; import org.keycloak.operator.OperatorManagedResource; +import org.keycloak.operator.StatusUpdater; import org.keycloak.operator.v2alpha1.crds.Keycloak; import org.keycloak.operator.v2alpha1.crds.KeycloakStatusBuilder; @@ -44,7 +46,7 @@ import java.util.Optional; import java.util.function.Consumer; import java.util.stream.Collectors; -public class KeycloakDeployment extends OperatorManagedResource { +public class KeycloakDeployment extends OperatorManagedResource implements StatusUpdater { // public static final Pattern CONFIG_SECRET_PATTERN = Pattern.compile("^\\$\\{secret:([^:]+):(.+)}$"); @@ -365,17 +367,9 @@ public class KeycloakDeployment extends OperatorManagedResource { Container container = baseDeployment.getSpec().getTemplate().getSpec().getContainers().get(0); container.setImage(Optional.ofNullable(keycloakCR.getSpec().getImage()).orElse(config.keycloak().image())); - - var serverConfig = new HashMap<>(Constants.DEFAULT_DIST_CONFIG); - if (keycloakCR.getSpec().getServerConfiguration() != null) { - serverConfig.putAll(keycloakCR.getSpec().getServerConfiguration()); - } - container.setImagePullPolicy(config.keycloak().imagePullPolicy()); - container.setEnv(serverConfig.entrySet().stream() - .map(e -> new EnvVarBuilder().withName(e.getKey()).withValue(e.getValue()).build()) - .collect(Collectors.toList())); + container.setEnv(getEnvVars()); addInitContainer(baseDeployment, keycloakCR.getSpec().getExtensions()); mergePodTemplate(baseDeployment.getSpec().getTemplate()); @@ -406,6 +400,20 @@ public class KeycloakDeployment extends OperatorManagedResource { return baseDeployment; } + private List getEnvVars() { + var serverConfig = new HashMap<>(Constants.DEFAULT_DIST_CONFIG); + serverConfig.put("jgroups.dns.query", getName() + Constants.KEYCLOAK_DISCOVERY_SERVICE_SUFFIX +"." + getNamespace()); + if (keycloakCR.getSpec().getServerConfiguration() != null) { + serverConfig.putAll(keycloakCR.getSpec().getServerConfiguration()); + } + return serverConfig.entrySet().stream() + .map(e -> new EnvVarBuilder() + .withName(e.getKey()) + .withValue(e.getValue()) + .build()) + .collect(Collectors.toList()); + } + public void updateStatus(KeycloakStatusBuilder status) { validatePodTemplate(status); if (existingDeployment == null) { @@ -432,14 +440,11 @@ public class KeycloakDeployment extends OperatorManagedResource { // return configSecretsNames; // } + @Override public String getName() { return keycloakCR.getMetadata().getName(); } - public String getNamespace() { - return keycloakCR.getMetadata().getNamespace(); - } - public void rollingRestart() { client.apps().deployments() .inNamespace(getNamespace()) diff --git a/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakDiscoveryService.java b/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakDiscoveryService.java new file mode 100644 index 0000000000..b5858b7f7a --- /dev/null +++ b/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakDiscoveryService.java @@ -0,0 +1,95 @@ +/* + * Copyright 2021 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.v2alpha1; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.IntOrString; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.ServiceBuilder; +import io.fabric8.kubernetes.api.model.ServiceSpec; +import io.fabric8.kubernetes.api.model.ServiceSpecBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.keycloak.operator.Constants; +import org.keycloak.operator.OperatorManagedResource; +import org.keycloak.operator.StatusUpdater; +import org.keycloak.operator.v2alpha1.crds.Keycloak; +import org.keycloak.operator.v2alpha1.crds.KeycloakStatusBuilder; + +import java.util.Optional; + +public class KeycloakDiscoveryService extends OperatorManagedResource implements StatusUpdater { + + private Service existingService; + + public KeycloakDiscoveryService(KubernetesClient client, Keycloak keycloakCR) { + super(client, keycloakCR); + this.existingService = fetchExistingService(); + } + + private ServiceSpec getServiceSpec() { + return new ServiceSpecBuilder() + .addNewPort() + .withPort(Constants.KEYCLOAK_DISCOVERY_SERVICE_PORT) + .endPort() + .withSelector(Constants.DEFAULT_LABELS) + .withClusterIP("None") + .build(); + } + + @Override + protected Optional getReconciledResource() { + var service = fetchExistingService(); + if (service == null) { + service = newService(); + } else { + service.setSpec(getServiceSpec()); + } + + return Optional.of(service); + } + + private Service newService() { + Service service = new ServiceBuilder() + .withNewMetadata() + .withName(getName()) + .withNamespace(getNamespace()) + .endMetadata() + .withSpec(getServiceSpec()) + .build(); + return service; + } + + private Service fetchExistingService() { + return client + .services() + .inNamespace(getNamespace()) + .withName(getName()) + .get(); + } + + public void updateStatus(KeycloakStatusBuilder status) { + if (existingService == null) { + status.addNotReadyMessage("No existing Discovery Service found, waiting for creating a new one"); + return; + } + } + + @Override + public String getName() { + return cr.getMetadata().getName() + Constants.KEYCLOAK_DISCOVERY_SERVICE_SUFFIX; + } +} diff --git a/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakRealmImportJob.java b/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakRealmImportJob.java index 70d24d61bf..726c344e0b 100644 --- a/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakRealmImportJob.java +++ b/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakRealmImportJob.java @@ -192,14 +192,11 @@ public class KeycloakRealmImportJob extends OperatorManagedResource { } } - private String getName() { + @Override + protected String getName() { return realmCR.getMetadata().getName(); } - private String getNamespace() { - return realmCR.getMetadata().getNamespace(); - } - private String getKeycloakName() { return realmCR.getSpec().getKeycloakCRName(); } private String getRealmName() { return realmCR.getSpec().getRealm().getRealm(); } diff --git a/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakRealmImportSecret.java b/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakRealmImportSecret.java index 61477344a1..b968f39ea8 100644 --- a/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakRealmImportSecret.java +++ b/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakRealmImportSecret.java @@ -48,14 +48,11 @@ public class KeycloakRealmImportSecret extends OperatorManagedResource { .build(); } - private String getName() { + @Override + protected String getName() { return realmCR.getMetadata().getName(); } - private String getNamespace() { - return realmCR.getMetadata().getNamespace(); - } - private String getRealmName() { return realmCR.getSpec().getRealm().getRealm(); } public String getSecretName() { diff --git a/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakService.java b/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakService.java new file mode 100644 index 0000000000..81ff66183c --- /dev/null +++ b/operator/src/main/java/org/keycloak/operator/v2alpha1/KeycloakService.java @@ -0,0 +1,94 @@ +/* + * Copyright 2021 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.v2alpha1; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.IntOrString; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.ServiceBuilder; +import io.fabric8.kubernetes.api.model.ServiceSpec; +import io.fabric8.kubernetes.api.model.ServiceSpecBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.keycloak.operator.Constants; +import org.keycloak.operator.OperatorManagedResource; +import org.keycloak.operator.StatusUpdater; +import org.keycloak.operator.v2alpha1.crds.Keycloak; +import org.keycloak.operator.v2alpha1.crds.KeycloakStatusBuilder; + +import java.util.Optional; + +public class KeycloakService extends OperatorManagedResource implements StatusUpdater { + + private Service existingService; + + public KeycloakService(KubernetesClient client, Keycloak keycloakCR) { + super(client, keycloakCR); + this.existingService = fetchExistingService(); + } + + private ServiceSpec getServiceSpec() { + return new ServiceSpecBuilder() + .addNewPort() + .withPort(Constants.KEYCLOAK_SERVICE_PORT) + .withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL) + .endPort() + .withSelector(Constants.DEFAULT_LABELS) + .build(); + } + + @Override + protected Optional getReconciledResource() { + var service = fetchExistingService(); + if (service == null) { + service = newService(); + } else { + service.setSpec(getServiceSpec()); + } + + return Optional.of(service); + } + + private Service newService() { + Service service = new ServiceBuilder() + .withNewMetadata() + .withName(getName()) + .withNamespace(getNamespace()) + .endMetadata() + .withSpec(getServiceSpec()) + .build(); + return service; + } + + private Service fetchExistingService() { + return client + .services() + .inNamespace(getNamespace()) + .withName(getName()) + .get(); + } + + public void updateStatus(KeycloakStatusBuilder status) { + if (existingService == null) { + status.addNotReadyMessage("No existing Keycloak Service found, waiting for creating a new one"); + return; + } + } + + public String getName() { + return cr.getMetadata().getName() + Constants.KEYCLOAK_SERVICE_SUFFIX; + } +} diff --git a/operator/src/main/kubernetes/kubernetes.yml b/operator/src/main/kubernetes/kubernetes.yml index 76a83dbf64..43dd3e72da 100644 --- a/operator/src/main/kubernetes/kubernetes.yml +++ b/operator/src/main/kubernetes/kubernetes.yml @@ -19,6 +19,7 @@ rules: - "" resources: - secrets + - services verbs: - get - list diff --git a/operator/src/test/java/org/keycloak/operator/ClusterOperatorTest.java b/operator/src/test/java/org/keycloak/operator/ClusterOperatorTest.java index 0ba0a4a9a8..bcc38eae45 100644 --- a/operator/src/test/java/org/keycloak/operator/ClusterOperatorTest.java +++ b/operator/src/test/java/org/keycloak/operator/ClusterOperatorTest.java @@ -108,7 +108,7 @@ public abstract class ClusterOperatorTest { k8sclient.load(new FileInputStream(TARGET_KUBERNETES_GENERATED_YML_FOLDER +deploymentTarget+".yml")) .inNamespace(namespace).delete(); } - private static void createCRDs() throws FileNotFoundException { + private static void createCRDs() { Log.info("Creating CRDs"); try { var deploymentCRD = k8sclient.load(new FileInputStream(TARGET_KUBERNETES_GENERATED_YML_FOLDER + "keycloaks.keycloak.org-v1.yml")); @@ -152,7 +152,7 @@ public abstract class ClusterOperatorTest { protected static void deployDB() { // DB Log.info("Creating new PostgreSQL deployment"); - k8sclient.load(KeycloakDeploymentE2EIT.class.getResourceAsStream("/example-postgres.yaml")).inNamespace(namespace).createOrReplace(); + k8sclient.load(ClusterOperatorTest.class.getResourceAsStream("/example-postgres.yaml")).inNamespace(namespace).createOrReplace(); // Check DB has deployed and ready Log.info("Checking Postgres is running"); @@ -181,7 +181,7 @@ public abstract class ClusterOperatorTest { private static void setDefaultAwaitilityTimings() { Awaitility.setDefaultPollInterval(Duration.ofSeconds(1)); - Awaitility.setDefaultTimeout(Duration.ofSeconds(180)); + Awaitility.setDefaultTimeout(Duration.ofSeconds(240)); } @AfterEach diff --git a/operator/src/test/java/org/keycloak/operator/ClusteringE2EIT.java b/operator/src/test/java/org/keycloak/operator/ClusteringE2EIT.java new file mode 100644 index 0000000000..65220f55d1 --- /dev/null +++ b/operator/src/test/java/org/keycloak/operator/ClusteringE2EIT.java @@ -0,0 +1,200 @@ +package org.keycloak.operator; + +import com.fasterxml.jackson.databind.JsonNode; +import io.fabric8.kubernetes.client.utils.Serialization; +import io.quarkus.logging.Log; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.keycloak.operator.utils.CRAssert; +import org.keycloak.operator.v2alpha1.KeycloakService; +import org.keycloak.operator.v2alpha1.crds.Keycloak; +import org.keycloak.operator.utils.K8sUtils; +import org.keycloak.operator.v2alpha1.crds.KeycloakRealmImport; +import org.keycloak.operator.v2alpha1.crds.KeycloakRealmImportStatusCondition; +import org.keycloak.operator.v2alpha1.crds.KeycloakStatusCondition; + +import java.io.IOException; +import java.time.Duration; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; + + +@QuarkusTest +public class ClusteringE2EIT extends ClusterOperatorTest { + + @Test + public void testKeycloakScaleAsExpected() { + // given + var kc = K8sUtils.getDefaultKeycloakDeployment(); + var crSelector = k8sclient + .resources(Keycloak.class) + .inNamespace(kc.getMetadata().getNamespace()) + .withName(kc.getMetadata().getName()); + K8sUtils.deployKeycloak(k8sclient, kc, true); + + var kcPodsSelector = k8sclient.pods().inNamespace(namespace).withLabel("app", "keycloak"); + + Keycloak keycloak = crSelector.get(); + + // when scale it to 10 + keycloak.getSpec().setInstances(10); + k8sclient.resources(Keycloak.class).inNamespace(namespace).createOrReplace(keycloak); + + Awaitility.await() + .atMost(1, MINUTES) + .pollDelay(1, SECONDS) + .ignoreExceptions() + .untilAsserted(() -> CRAssert.assertKeycloakStatusCondition(crSelector.get(), KeycloakStatusCondition.READY, false)); + + Awaitility.await() + .atMost(Duration.ofSeconds(5)) + .untilAsserted(() -> assertThat(kcPodsSelector.list().getItems().size()).isEqualTo(10)); + + // when scale it down to 2 + keycloak.getSpec().setInstances(2); + k8sclient.resources(Keycloak.class).inNamespace(namespace).createOrReplace(keycloak); + Awaitility.await() + .atMost(Duration.ofSeconds(180)) + .untilAsserted(() -> assertThat(kcPodsSelector.list().getItems().size()).isEqualTo(2)); + + Awaitility.await() + .atMost(2, MINUTES) + .pollDelay(5, SECONDS) + .ignoreExceptions() + .untilAsserted(() -> CRAssert.assertKeycloakStatusCondition(crSelector.get(), KeycloakStatusCondition.READY, true)); + + // get the service + var service = new KeycloakService(k8sclient, kc); + String url = "http://" + service.getName() + "." + namespace + ":" + Constants.KEYCLOAK_SERVICE_PORT; + + Awaitility.await().atMost(5, MINUTES).untilAsserted(() -> { + Log.info("Starting curl Pod to test if the realm is available"); + Log.info("Url: '" + url + "'"); + String curlOutput = K8sUtils.inClusterCurl(k8sclient, namespace, url); + Log.info("Output from curl: '" + curlOutput + "'"); + assertThat(curlOutput).isEqualTo("200"); + }); + } + + // local debug commands: + // export TOKEN=$(curl --data "grant_type=password&client_id=token-test-client&username=test&password=test" http://localhost:8080/realms/token-test/protocol/openid-connect/token | jq -r '.access_token') + // + // curl http://localhost:8080/realms/token-test/protocol/openid-connect/userinfo -H "Authorization: bearer $TOKEN" + // + // example good answer: + // {"sub":"b660eec6-a93b-46fd-abb2-e9fbdff67a63","email_verified":false,"preferred_username":"test"} + // example error answer: + // {"error":"invalid_request","error_description":"Token not provided"} + @Test + public void testKeycloakCacheIsConnected() throws Exception { + // given + Log.info("Setup"); + var kc = K8sUtils.getDefaultKeycloakDeployment(); + var crSelector = k8sclient + .resources(Keycloak.class) + .inNamespace(kc.getMetadata().getNamespace()) + .withName(kc.getMetadata().getName()); + var targetInstances = 3; + kc.getSpec().setInstances(targetInstances); + k8sclient.resources(Keycloak.class).inNamespace(namespace).createOrReplace(kc); + var realm = k8sclient.resources(KeycloakRealmImport.class).inNamespace(namespace).load(getClass().getResourceAsStream("/token-test-realm.yaml")); + var realmImportSelector = k8sclient.resources(KeycloakRealmImport.class).inNamespace(namespace).withName("example-token-test-kc"); + realm.createOrReplace(); + + Log.info("Waiting for a stable Keycloak Cluster"); + Awaitility.await() + .atMost(10, MINUTES) + .pollDelay(5, SECONDS) + .ignoreExceptions() + .untilAsserted(() -> { + Log.info("Checking realm import has finished."); + CRAssert.assertKeycloakRealmImportStatusCondition(realmImportSelector.get(), KeycloakRealmImportStatusCondition.DONE, true); + Log.info("Checking Keycloak is stable."); + CRAssert.assertKeycloakStatusCondition(crSelector.get(), KeycloakStatusCondition.READY, true); + }); + + Log.info("Testing the Keycloak Cluster"); + Awaitility.await().atMost(5, MINUTES).ignoreExceptions().untilAsserted(() -> { + // Get the list of Keycloak pods + var pods = k8sclient + .pods() + .inNamespace(namespace) + .withLabels(Constants.DEFAULT_LABELS) + .list() + .getItems(); + + String token = null; + // Obtaining the token from the first pod + // Connecting using port-forward and a fixed port to respect the instance issuer used hostname + for (var pod: pods) { + Log.info("Testing Pod: " + pod.getMetadata().getName()); + try (var portForward = k8sclient + .pods() + .inNamespace(namespace) + .withName(pod.getMetadata().getName()) + .portForward(8080, 8080)) { + + token = (token != null) ? token : RestAssured.given() + .param("grant_type" , "password") + .param("client_id", "token-test-client") + .param("username", "test") + .param("password", "test") + .post("http://localhost:" + portForward.getLocalPort() + "/realms/token-test/protocol/openid-connect/token") + .body() + .jsonPath() + .getString("access_token"); + + Log.info("Using token:" + token); + + var username = RestAssured.given() + .header("Authorization", "Bearer " + token) + .get("http://localhost:" + portForward.getLocalPort() + "/realms/token-test/protocol/openid-connect/userinfo") + .body() + .jsonPath() + .getString("preferred_username"); + + Log.info("Username found: " + username); + + assertThat(username).isEqualTo("test"); + } + } + }); + + // This is to test passing through the "Service", not 100% deterministic, but a smoke test that things are working as expected + // Executed here to avoid paying the setup time again + var service = new KeycloakService(k8sclient, kc); + Awaitility.await().atMost(5, MINUTES).ignoreExceptions().untilAsserted(() -> { + String token2 = null; + // Obtaining the token from the first pod + // Connecting using port-forward and a fixed port to respect the instance issuer used hostname + for (int i = 0; i < (targetInstances * 2); i++) { + + if (token2 == null) { + var tokenUrl = "http://" + service.getName() + "." + namespace + ":" + Constants.KEYCLOAK_SERVICE_PORT + "/realms/token-test/protocol/openid-connect/token"; + Log.info("Checking url: " + tokenUrl); + + var tokenOutput = K8sUtils.inClusterCurl(k8sclient, namespace, "-s", "--data", "grant_type=password&client_id=token-test-client&username=test&password=test", tokenUrl); + Log.info("Curl Output with token: " + tokenOutput); + JsonNode tokenAnswer = Serialization.jsonMapper().readTree(tokenOutput); + assertThat(tokenAnswer.hasNonNull("access_token")).isTrue(); + token2 = tokenAnswer.get("access_token").asText(); + } + + String url = "http://" + service.getName() + "." + namespace + ":" + Constants.KEYCLOAK_SERVICE_PORT + "/realms/token-test/protocol/openid-connect/userinfo"; + Log.info("Checking url: " + url); + + var curlOutput = K8sUtils.inClusterCurl(k8sclient, namespace, "-s", "-H", "Authorization: Bearer " + token2, url); + Log.info("Curl Output on access attempt: " + curlOutput); + + + JsonNode answer = Serialization.jsonMapper().readTree(curlOutput); + assertThat(answer.hasNonNull("preferred_username")).isTrue(); + assertThat(answer.get("preferred_username").asText()).isEqualTo("test"); + } + }); + } +} diff --git a/operator/src/test/java/org/keycloak/operator/KeycloakServicesE2EIT.java b/operator/src/test/java/org/keycloak/operator/KeycloakServicesE2EIT.java new file mode 100644 index 0000000000..918256a496 --- /dev/null +++ b/operator/src/test/java/org/keycloak/operator/KeycloakServicesE2EIT.java @@ -0,0 +1,95 @@ +package org.keycloak.operator; + +import io.fabric8.kubernetes.api.model.ServiceSpecBuilder; +import io.quarkus.logging.Log; +import io.quarkus.test.junit.QuarkusTest; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.keycloak.operator.v2alpha1.KeycloakDiscoveryService; +import org.keycloak.operator.v2alpha1.KeycloakService; +import org.keycloak.operator.utils.K8sUtils; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@QuarkusTest +public class KeycloakServicesE2EIT extends ClusterOperatorTest { + @Test + public void testMainServiceDurability() { + var kc = K8sUtils.getDefaultKeycloakDeployment(); + K8sUtils.deployKeycloak(k8sclient, kc, true); + var service = new KeycloakService(k8sclient, kc); + var serviceSelector = k8sclient.services().inNamespace(namespace).withName(service.getName()); + + Log.info("Trying to delete the service"); + assertThat(serviceSelector.delete()).isTrue(); + Awaitility.await() + .untilAsserted(() -> assertThat(serviceSelector.get()).isNotNull()); + + K8sUtils.waitForKeycloakToBeReady(k8sclient, kc); // wait for reconciler to calm down to avoid race condititon + + Log.info("Trying to modify the service"); + + var currentService = serviceSelector.get(); + var labels = Map.of("address", "EvergreenTerrace742"); + // ignoring current IP/s + currentService.getSpec().setClusterIP(null); + currentService.getSpec().setClusterIPs(null); + var origSpecs = new ServiceSpecBuilder(currentService.getSpec()).build(); // deep copy + + currentService.getMetadata().getLabels().putAll(labels); + currentService.getSpec().setSessionAffinity("ClientIP"); + + serviceSelector.createOrReplace(currentService); + + Awaitility.await() + .untilAsserted(() -> { + var s = serviceSelector.get(); + assertThat(s.getMetadata().getLabels().entrySet().containsAll(labels.entrySet())).isTrue(); // additional labels should not be overwritten + // ignoring assigned IP/s + s.getSpec().setClusterIP(null); + s.getSpec().setClusterIPs(null); + assertThat(s.getSpec()).isEqualTo(origSpecs); // specs should be reconciled back to original values + }); + } + + @Test + public void testDiscoveryServiceDurability() { + var kc = K8sUtils.getDefaultKeycloakDeployment(); + K8sUtils.deployKeycloak(k8sclient, kc, true); + var discoveryService = new KeycloakDiscoveryService(k8sclient, kc); + var discoveryServiceSelector = k8sclient.services().inNamespace(namespace).withName(discoveryService.getName()); + + Log.info("Trying to delete the discovery service"); + assertThat(discoveryServiceSelector.delete()).isTrue(); + Awaitility.await() + .untilAsserted(() -> assertThat(discoveryServiceSelector.get()).isNotNull()); + + K8sUtils.waitForKeycloakToBeReady(k8sclient, kc); // wait for reconciler to calm down to avoid race condititon + + Log.info("Trying to modify the service"); + + var currentDiscoveryService = discoveryServiceSelector.get(); + var labels = Map.of("address", "EvergreenTerrace742"); + // ignoring current IP/s + currentDiscoveryService.getSpec().setClusterIP(null); + currentDiscoveryService.getSpec().setClusterIPs(null); + var origDiscoverySpecs = new ServiceSpecBuilder(currentDiscoveryService.getSpec()).build(); // deep copy + + currentDiscoveryService.getMetadata().getLabels().putAll(labels); + currentDiscoveryService.getSpec().setSessionAffinity("ClientIP"); + + discoveryServiceSelector.createOrReplace(currentDiscoveryService); + + Awaitility.await() + .untilAsserted(() -> { + var ds = discoveryServiceSelector.get(); + assertThat(ds.getMetadata().getLabels().entrySet().containsAll(labels.entrySet())).isTrue(); // additional labels should not be overwritten + // ignoring assigned IP/s + ds.getSpec().setClusterIP(null); + ds.getSpec().setClusterIPs(null); + assertThat(ds.getSpec()).isEqualTo(origDiscoverySpecs); // specs should be reconciled back to original values + }); + } +} diff --git a/operator/src/test/java/org/keycloak/operator/RealmImportE2EIT.java b/operator/src/test/java/org/keycloak/operator/RealmImportE2EIT.java index 2f8466151d..0e03a2bae7 100644 --- a/operator/src/test/java/org/keycloak/operator/RealmImportE2EIT.java +++ b/operator/src/test/java/org/keycloak/operator/RealmImportE2EIT.java @@ -1,22 +1,19 @@ package org.keycloak.operator; -import io.fabric8.kubernetes.api.model.Pod; -import io.fabric8.kubernetes.api.model.ServiceBuilder; -import io.fabric8.kubernetes.client.KubernetesClientException; -import io.fabric8.kubernetes.client.extended.run.RunConfigBuilder; import io.quarkus.logging.Log; import io.quarkus.test.junit.QuarkusTest; import org.awaitility.Awaitility; import org.junit.jupiter.api.Test; +import org.keycloak.operator.utils.CRAssert; +import org.keycloak.operator.v2alpha1.KeycloakService; import org.keycloak.operator.v2alpha1.crds.KeycloakRealmImport; -import org.keycloak.operator.v2alpha1.crds.KeycloakRealmImportStatusCondition; - -import java.util.List; -import java.util.Map; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; +import static org.keycloak.operator.Constants.KEYCLOAK_SERVICE_PORT; +import static org.keycloak.operator.utils.K8sUtils.getDefaultKeycloakDeployment; +import static org.keycloak.operator.utils.K8sUtils.inClusterCurl; import static org.keycloak.operator.v2alpha1.crds.KeycloakRealmImportStatusCondition.DONE; import static org.keycloak.operator.v2alpha1.crds.KeycloakRealmImportStatusCondition.STARTED; import static org.keycloak.operator.v2alpha1.crds.KeycloakRealmImportStatusCondition.HAS_ERRORS; @@ -24,39 +21,11 @@ import static org.keycloak.operator.v2alpha1.crds.KeycloakRealmImportStatusCondi @QuarkusTest public class RealmImportE2EIT extends ClusterOperatorTest { - final static String KEYCLOAK_SERVICE_NAME = "example-keycloak"; - final static int KEYCLOAK_PORT = 8080; - - private KeycloakRealmImportStatusCondition getCondition(List conditions, String type) { - return conditions - .stream() - .filter(c -> c.getType().equals(type)) - .findFirst() - .get(); - } - @Test public void testWorkingRealmImport() { - Log.info(((operatorDeployment == OperatorDeployment.remote) ? "Remote " : "Local ") + "Run Test :" + namespace); // Arrange - k8sclient.load(getClass().getResourceAsStream("/example-postgres.yaml")).inNamespace(namespace).createOrReplace(); k8sclient.load(getClass().getResourceAsStream("/example-keycloak.yml")).inNamespace(namespace).createOrReplace(); - k8sclient.services().inNamespace(namespace).create( - new ServiceBuilder() - .withNewMetadata() - .withName(KEYCLOAK_SERVICE_NAME) - .withNamespace(namespace) - .endMetadata() - .withNewSpec() - .withSelector(Map.of("app", "keycloak")) - .addNewPort() - .withPort(KEYCLOAK_PORT) - .endPort() - .endSpec() - .build() - ); - // Act k8sclient.load(getClass().getResourceAsStream("/example-realm.yaml")).inNamespace(namespace).createOrReplace(); @@ -70,14 +39,9 @@ public class RealmImportE2EIT extends ClusterOperatorTest { .pollDelay(5, SECONDS) .ignoreExceptions() .untilAsserted(() -> { - var conditions = crSelector - .get() - .getStatus() - .getConditions(); - - assertThat(getCondition(conditions, DONE).getStatus()).isFalse(); - assertThat(getCondition(conditions, STARTED).getStatus()).isTrue(); - assertThat(getCondition(conditions, HAS_ERRORS).getStatus()).isFalse(); + CRAssert.assertKeycloakRealmImportStatusCondition(crSelector.get(), DONE, false); + CRAssert.assertKeycloakRealmImportStatusCondition(crSelector.get(), STARTED, true); + CRAssert.assertKeycloakRealmImportStatusCondition(crSelector.get(), HAS_ERRORS, false); }); Awaitility.await() @@ -85,62 +49,26 @@ public class RealmImportE2EIT extends ClusterOperatorTest { .pollDelay(5, SECONDS) .ignoreExceptions() .untilAsserted(() -> { - var conditions = crSelector - .get() - .getStatus() - .getConditions(); - - assertThat(getCondition(conditions, DONE).getStatus()).isTrue(); - assertThat(getCondition(conditions, STARTED).getStatus()).isFalse(); - assertThat(getCondition(conditions, HAS_ERRORS).getStatus()).isFalse(); + CRAssert.assertKeycloakRealmImportStatusCondition(crSelector.get(), DONE, true); + CRAssert.assertKeycloakRealmImportStatusCondition(crSelector.get(), STARTED, false); + CRAssert.assertKeycloakRealmImportStatusCondition(crSelector.get(), HAS_ERRORS, false); }); - + var service = new KeycloakService(k8sclient, getDefaultKeycloakDeployment()); String url = - "http://" + KEYCLOAK_SERVICE_NAME + "." + namespace + ":" + KEYCLOAK_PORT + "/realms/count0"; + "http://" + service.getName() + "." + namespace + ":" + KEYCLOAK_SERVICE_PORT + "/realms/count0"; Awaitility.await().atMost(5, MINUTES).untilAsserted(() -> { - try { - Log.info("Starting curl Pod to test if the realm is available"); - - Pod curlPod = k8sclient.run().inNamespace(namespace) - .withRunConfig(new RunConfigBuilder() - .withArgs("-s", "-o", "/dev/null", "-w", "%{http_code}", url) - .withName("curl") - .withImage("curlimages/curl:7.78.0") - .withRestartPolicy("Never") - .build()) - .done(); - Log.info("Waiting for curl Pod to finish running"); - Awaitility.await().atMost(2, MINUTES) - .until(() -> { - String phase = - k8sclient.pods().inNamespace(namespace).withName("curl").get() - .getStatus().getPhase(); - return phase.equals("Succeeded") || phase.equals("Failed"); - }); - - String curlOutput = - k8sclient.pods().inNamespace(namespace) - .withName(curlPod.getMetadata().getName()).getLog(); - Log.info("Output from curl: '" + curlOutput + "'"); - assertThat(curlOutput).isEqualTo("200"); - } catch (KubernetesClientException ex) { - throw new AssertionError(ex); - } finally { - Log.info("Deleting curl Pod"); - k8sclient.pods().inNamespace(namespace).withName("curl").delete(); - Awaitility.await().atMost(1, MINUTES) - .until(() -> k8sclient.pods().inNamespace(namespace).withName("curl") - .get() == null); - } + Log.info("Starting curl Pod to test if the realm is available"); + Log.info("Url: '" + url + "'"); + String curlOutput = inClusterCurl(k8sclient, namespace, url); + Log.info("Output from curl: '" + curlOutput + "'"); + assertThat(curlOutput).isEqualTo("200"); }); } @Test public void testNotWorkingRealmImport() { - Log.info(((operatorDeployment == OperatorDeployment.remote) ? "Remote " : "Local ") + "Run Test :" + namespace); // Arrange - k8sclient.load(getClass().getResourceAsStream("/example-postgres.yaml")).inNamespace(namespace).createOrReplace(); k8sclient.load(getClass().getResourceAsStream("/example-keycloak.yml")).inNamespace(namespace).createOrReplace(); // Act @@ -152,17 +80,14 @@ public class RealmImportE2EIT extends ClusterOperatorTest { .pollDelay(5, SECONDS) .ignoreExceptions() .untilAsserted(() -> { - var conditions = k8sclient + var crSelector = k8sclient .resources(KeycloakRealmImport.class) .inNamespace(namespace) - .withName("example-count0-kc") - .get() - .getStatus() - .getConditions(); + .withName("example-count0-kc"); - assertThat(getCondition(conditions, HAS_ERRORS).getStatus()).isTrue(); - assertThat(getCondition(conditions, DONE).getStatus()).isFalse(); - assertThat(getCondition(conditions, STARTED).getStatus()).isFalse(); + CRAssert.assertKeycloakRealmImportStatusCondition(crSelector.get(), DONE, false); + CRAssert.assertKeycloakRealmImportStatusCondition(crSelector.get(), STARTED, false); + CRAssert.assertKeycloakRealmImportStatusCondition(crSelector.get(), HAS_ERRORS, true); }); } diff --git a/operator/src/test/java/org/keycloak/operator/utils/CRAssert.java b/operator/src/test/java/org/keycloak/operator/utils/CRAssert.java index 0d82663878..35c9100750 100644 --- a/operator/src/test/java/org/keycloak/operator/utils/CRAssert.java +++ b/operator/src/test/java/org/keycloak/operator/utils/CRAssert.java @@ -38,4 +38,9 @@ public final class CRAssert { (containedMessage == null || c.getMessage().contains(containedMessage))) ).isTrue(); } + + public static void assertKeycloakRealmImportStatusCondition(KeycloakRealmImport kri, String condition, boolean status) { + assertThat(kri.getStatus().getConditions().stream() + .anyMatch(c -> c.getType().equals(condition) && c.getStatus() == status)).isTrue(); + } } diff --git a/operator/src/test/java/org/keycloak/operator/utils/K8sUtils.java b/operator/src/test/java/org/keycloak/operator/utils/K8sUtils.java index 158c314138..4d41b6e7a2 100644 --- a/operator/src/test/java/org/keycloak/operator/utils/K8sUtils.java +++ b/operator/src/test/java/org/keycloak/operator/utils/K8sUtils.java @@ -17,8 +17,13 @@ package org.keycloak.operator.utils; +import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientException; +import io.fabric8.kubernetes.client.extended.run.RunConfigBuilder; +import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil; import io.fabric8.kubernetes.client.utils.Serialization; +import io.quarkus.kubernetes.client.runtime.KubernetesClientUtils; import io.quarkus.logging.Log; import org.awaitility.Awaitility; import org.keycloak.operator.v2alpha1.crds.Keycloak; @@ -27,6 +32,10 @@ import org.keycloak.operator.v2alpha1.crds.KeycloakStatusCondition; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.UUID; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; /** * @author Vaclav Muzikar @@ -67,4 +76,44 @@ public final class K8sUtils { CRAssert.assertKeycloakStatusCondition(currentKc, KeycloakStatusCondition.HAS_ERRORS, false); }); } + + public static String inClusterCurl(KubernetesClient k8sclient, String namespace, String url) { + return inClusterCurl(k8sclient, namespace, "-s", "-o", "/dev/null", "-w", "%{http_code}", url); + } + + public static String inClusterCurl(KubernetesClient k8sclient, String namespace, String... args) { + var podName = KubernetesResourceUtil.sanitizeName("curl-" + UUID.randomUUID()); + try { + Pod curlPod = k8sclient.run().inNamespace(namespace) + .withRunConfig(new RunConfigBuilder() + .withArgs(args) + .withName(podName) + .withImage("curlimages/curl:7.78.0") + .withRestartPolicy("Never") + .build()) + .done(); + Log.info("Waiting for curl Pod to finish running"); + Awaitility.await().atMost(2, MINUTES) + .until(() -> { + String phase = + k8sclient.pods().inNamespace(namespace).withName(podName).get() + .getStatus().getPhase(); + return phase.equals("Succeeded") || phase.equals("Failed"); + }); + + String curlOutput = + k8sclient.pods().inNamespace(namespace) + .withName(curlPod.getMetadata().getName()).getLog(); + + return curlOutput; + } catch (KubernetesClientException ex) { + throw new AssertionError(ex); + } finally { + Log.info("Deleting curl Pod"); + k8sclient.pods().inNamespace(namespace).withName(podName).delete(); + Awaitility.await().atMost(1, MINUTES) + .until(() -> k8sclient.pods().inNamespace(namespace).withName(podName) + .get() == null); + } + } } diff --git a/operator/src/test/resources/token-test-realm.yaml b/operator/src/test/resources/token-test-realm.yaml new file mode 100644 index 0000000000..be10c7d3ab --- /dev/null +++ b/operator/src/test/resources/token-test-realm.yaml @@ -0,0 +1,1725 @@ +apiVersion: keycloak.org/v2alpha1 +kind: KeycloakRealmImport +metadata: + name: example-token-test-kc +spec: + keycloakCRName: example-kc + realm: + id: token-test + realm: token-test + notBefore: 0 + defaultSignatureAlgorithm: RS256 + revokeRefreshToken: false + refreshTokenMaxReuse: 0 + accessTokenLifespan: 300 + accessTokenLifespanForImplicitFlow: 900 + ssoSessionIdleTimeout: 1800 + ssoSessionMaxLifespan: 36000 + ssoSessionIdleTimeoutRememberMe: 0 + ssoSessionMaxLifespanRememberMe: 0 + offlineSessionIdleTimeout: 2592000 + offlineSessionMaxLifespanEnabled: false + offlineSessionMaxLifespan: 5184000 + clientSessionIdleTimeout: 0 + clientSessionMaxLifespan: 0 + clientOfflineSessionIdleTimeout: 0 + clientOfflineSessionMaxLifespan: 0 + accessCodeLifespan: 60 + accessCodeLifespanUserAction: 300 + accessCodeLifespanLogin: 1800 + actionTokenGeneratedByAdminLifespan: 43200 + actionTokenGeneratedByUserLifespan: 300 + oauth2DeviceCodeLifespan: 600 + oauth2DevicePollingInterval: 5 + enabled: true + sslRequired: external + registrationAllowed: false + registrationEmailAsUsername: false + rememberMe: false + verifyEmail: false + loginWithEmailAllowed: true + duplicateEmailsAllowed: false + resetPasswordAllowed: false + editUsernameAllowed: false + bruteForceProtected: false + permanentLockout: false + maxFailureWaitSeconds: 900 + minimumQuickLoginWaitSeconds: 60 + waitIncrementSeconds: 60 + quickLoginCheckMilliSeconds: 1000 + maxDeltaTimeSeconds: 43200 + failureFactor: 30 + roles: + realm: + - id: f89e3220-2593-4072-bfc2-f06c49f99b0c + name: uma_authorization + description: "${role_uma_authorization}" + composite: false + clientRole: false + containerId: token-test + attributes: {} + - id: ce3f3328-a7a7-4098-99bc-e72456680177 + name: offline_access + description: "${role_offline-access}" + composite: false + clientRole: false + containerId: token-test + attributes: {} + - id: 41271c50-8fc7-45ee-a963-a1d3ce881833 + name: default-roles-token-test + description: "${role_default-roles}" + composite: true + composites: + realm: + - offline_access + - uma_authorization + client: + account: + - manage-account + - view-profile + clientRole: false + containerId: token-test + attributes: {} + client: + realm-management: + - id: 7de8f53c-8b48-4561-bc53-c23bc02f57b6 + name: manage-users + description: "${role_manage-users}" + composite: false + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: 2120ab3d-5700-4918-ab62-6dca0c7b5f41 + name: query-clients + description: "${role_query-clients}" + composite: false + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: 831793a7-e725-411a-aa2d-42f775f2a6bf + name: manage-events + description: "${role_manage-events}" + composite: false + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: e7e5c55f-4b0e-4eae-96cc-1acd038cfeeb + name: view-realm + description: "${role_view-realm}" + composite: false + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: 875a8ee1-96b8-485c-86a2-01105b15daa1 + name: view-identity-providers + description: "${role_view-identity-providers}" + composite: false + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: d5ac72f8-94e9-4e1c-98bf-f688f0558710 + name: view-clients + description: "${role_view-clients}" + composite: true + composites: + client: + realm-management: + - query-clients + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: a1a61887-0e5c-464f-890a-64f059dc7ca1 + name: create-client + description: "${role_create-client}" + composite: false + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: 8b50da86-e52d-45bd-a175-b546d5e76fb3 + name: view-events + description: "${role_view-events}" + composite: false + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: dede217d-c210-4278-aa58-fb622a88d562 + name: realm-admin + description: "${role_realm-admin}" + composite: true + composites: + client: + realm-management: + - manage-users + - query-clients + - manage-events + - view-realm + - view-identity-providers + - view-clients + - view-events + - create-client + - manage-identity-providers + - manage-realm + - manage-authorization + - impersonation + - query-realms + - view-users + - view-authorization + - query-groups + - query-users + - manage-clients + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: 6a789bf5-7adf-4666-8118-37cf3e2b1c44 + name: manage-identity-providers + description: "${role_manage-identity-providers}" + composite: false + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: f549403c-cccd-47a1-bb52-57c80d4ace89 + name: manage-realm + description: "${role_manage-realm}" + composite: false + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: 31ddb9c1-1a53-44ec-b67a-a4cc50a760c2 + name: manage-authorization + description: "${role_manage-authorization}" + composite: false + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: fa872842-7037-415a-a69d-c34a05ef0a79 + name: impersonation + description: "${role_impersonation}" + composite: false + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: de291aed-9b84-4622-94cb-f967bb8b8a31 + name: query-realms + description: "${role_query-realms}" + composite: false + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: 28008941-29ac-4693-94f4-0e7a4f6b8e63 + name: view-users + description: "${role_view-users}" + composite: true + composites: + client: + realm-management: + - query-groups + - query-users + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: 801f5414-67eb-4c92-91b7-34344255b8d5 + name: query-groups + description: "${role_query-groups}" + composite: false + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: 6cc9fb5b-3019-4731-876a-dc5b8d288b8c + name: view-authorization + description: "${role_view-authorization}" + composite: false + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: e3fa28de-0587-4736-9142-0bc4cfb468a2 + name: query-users + description: "${role_query-users}" + composite: false + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + - id: 24ba3e2b-ff03-42fd-952e-b60acf4d5aa0 + name: manage-clients + description: "${role_manage-clients}" + composite: false + clientRole: true + containerId: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + attributes: {} + token-test-client: [] + security-admin-console: [] + admin-cli: [] + account-console: [] + broker: + - id: c4b2960e-6bf5-4f89-8a35-766d60c16700 + name: read-token + description: "${role_read-token}" + composite: false + clientRole: true + containerId: b93b8aa2-9fbc-48aa-8aa9-5f0c6383330a + attributes: {} + account: + - id: 66b817f9-130e-477b-addc-64406e9149f1 + name: manage-account + description: "${role_manage-account}" + composite: true + composites: + client: + account: + - manage-account-links + clientRole: true + containerId: 884a5020-265a-47c8-babe-25786fda4650 + attributes: {} + - id: 4068eead-cc5d-49e6-bd0c-93895b019ab3 + name: manage-account-links + description: "${role_manage-account-links}" + composite: false + clientRole: true + containerId: 884a5020-265a-47c8-babe-25786fda4650 + attributes: {} + - id: 3d1e7b71-8e37-455a-9d47-3207143b167e + name: view-consent + description: "${role_view-consent}" + composite: false + clientRole: true + containerId: 884a5020-265a-47c8-babe-25786fda4650 + attributes: {} + - id: 617f7c3c-d7e3-4f76-b0f8-27abb06cc6bd + name: view-profile + description: "${role_view-profile}" + composite: false + clientRole: true + containerId: 884a5020-265a-47c8-babe-25786fda4650 + attributes: {} + - id: f7e170f3-5966-4cc1-933d-50a28a2c4603 + name: manage-consent + description: "${role_manage-consent}" + composite: true + composites: + client: + account: + - view-consent + clientRole: true + containerId: 884a5020-265a-47c8-babe-25786fda4650 + attributes: {} + - id: 39ece46a-7d4c-42fe-b4ef-c0b48256f407 + name: view-applications + description: "${role_view-applications}" + composite: false + clientRole: true + containerId: 884a5020-265a-47c8-babe-25786fda4650 + attributes: {} + - id: 696abcea-f88f-4319-83d1-dcdba957cc2e + name: delete-account + description: "${role_delete-account}" + composite: false + clientRole: true + containerId: 884a5020-265a-47c8-babe-25786fda4650 + attributes: {} + groups: [] + defaultRole: + id: 41271c50-8fc7-45ee-a963-a1d3ce881833 + name: default-roles-token-test + description: "${role_default-roles}" + composite: true + clientRole: false + containerId: token-test + requiredCredentials: + - password + otpPolicyType: totp + otpPolicyAlgorithm: HmacSHA1 + otpPolicyInitialCounter: 0 + otpPolicyDigits: 6 + otpPolicyLookAheadWindow: 1 + otpPolicyPeriod: 30 + otpSupportedApplications: + - FreeOTP + - Google Authenticator + webAuthnPolicyRpEntityName: keycloak + webAuthnPolicySignatureAlgorithms: + - ES256 + webAuthnPolicyRpId: '' + webAuthnPolicyAttestationConveyancePreference: not specified + webAuthnPolicyAuthenticatorAttachment: not specified + webAuthnPolicyRequireResidentKey: not specified + webAuthnPolicyUserVerificationRequirement: not specified + webAuthnPolicyCreateTimeout: 0 + webAuthnPolicyAvoidSameAuthenticatorRegister: false + webAuthnPolicyAcceptableAaguids: [] + webAuthnPolicyPasswordlessRpEntityName: keycloak + webAuthnPolicyPasswordlessSignatureAlgorithms: + - ES256 + webAuthnPolicyPasswordlessRpId: '' + webAuthnPolicyPasswordlessAttestationConveyancePreference: not specified + webAuthnPolicyPasswordlessAuthenticatorAttachment: not specified + webAuthnPolicyPasswordlessRequireResidentKey: not specified + webAuthnPolicyPasswordlessUserVerificationRequirement: not specified + webAuthnPolicyPasswordlessCreateTimeout: 0 + webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister: false + webAuthnPolicyPasswordlessAcceptableAaguids: [] + users: + - id: b660eec6-a93b-46fd-abb2-e9fbdff67a63 + createdTimestamp: 1645713689127 + username: test + enabled: true + totp: false + emailVerified: false + credentials: + - id: 5c2bcf07-204a-4c19-aa40-c652198b289a + type: password + createdDate: 1645713704041 + secretData: '{"value":"GbcXn5JEdNpblA2NnXwX60mm614FHjdbxhK1x7v6WwGc0E8ZrNvho7Se8upLt9+/NTHu2NmuWlWM1QwdOWfyHQ==","salt":"YaIEcNqTNMS4fZ2iUKd/wg==","additionalParameters":{}}' + credentialData: '{"hashIterations":27500,"algorithm":"pbkdf2-sha256","additionalParameters":{}}' + disableableCredentialTypes: [] + requiredActions: [] + realmRoles: + - default-roles-token-test + notBefore: 0 + groups: [] + scopeMappings: + - clientScope: offline_access + roles: + - offline_access + clientScopeMappings: + account: + - client: account-console + roles: + - manage-account + clients: + - id: 884a5020-265a-47c8-babe-25786fda4650 + clientId: account + name: "${client_account}" + rootUrl: "${authBaseUrl}" + baseUrl: "/realms/token-test/account/" + surrogateAuthRequired: false + enabled: true + alwaysDisplayInConsole: false + clientAuthenticatorType: client-secret + redirectUris: + - "/realms/token-test/account/*" + webOrigins: [] + notBefore: 0 + bearerOnly: false + consentRequired: false + standardFlowEnabled: true + implicitFlowEnabled: false + directAccessGrantsEnabled: false + serviceAccountsEnabled: false + publicClient: true + frontchannelLogout: false + protocol: openid-connect + attributes: {} + authenticationFlowBindingOverrides: {} + fullScopeAllowed: false + nodeReRegistrationTimeout: 0 + defaultClientScopes: + - web-origins + - roles + - profile + - email + optionalClientScopes: + - address + - phone + - offline_access + - microprofile-jwt + - id: 8248ac6a-9940-4fec-a6ad-4b11b4b303c2 + clientId: account-console + name: "${client_account-console}" + rootUrl: "${authBaseUrl}" + baseUrl: "/realms/token-test/account/" + surrogateAuthRequired: false + enabled: true + alwaysDisplayInConsole: false + clientAuthenticatorType: client-secret + redirectUris: + - "/realms/token-test/account/*" + webOrigins: [] + notBefore: 0 + bearerOnly: false + consentRequired: false + standardFlowEnabled: true + implicitFlowEnabled: false + directAccessGrantsEnabled: false + serviceAccountsEnabled: false + publicClient: true + frontchannelLogout: false + protocol: openid-connect + attributes: + pkce.code.challenge.method: S256 + authenticationFlowBindingOverrides: {} + fullScopeAllowed: false + nodeReRegistrationTimeout: 0 + protocolMappers: + - id: 60bbc11f-acea-4e61-8de7-d6e1a1d9bb0f + name: audience resolve + protocol: openid-connect + protocolMapper: oidc-audience-resolve-mapper + consentRequired: false + config: {} + defaultClientScopes: + - web-origins + - roles + - profile + - email + optionalClientScopes: + - address + - phone + - offline_access + - microprofile-jwt + - id: 2333c4da-18a6-4f3d-b37f-b0b57c83c511 + clientId: admin-cli + name: "${client_admin-cli}" + surrogateAuthRequired: false + enabled: true + alwaysDisplayInConsole: false + clientAuthenticatorType: client-secret + redirectUris: [] + webOrigins: [] + notBefore: 0 + bearerOnly: false + consentRequired: false + standardFlowEnabled: false + implicitFlowEnabled: false + directAccessGrantsEnabled: true + serviceAccountsEnabled: false + publicClient: true + frontchannelLogout: false + protocol: openid-connect + attributes: {} + authenticationFlowBindingOverrides: {} + fullScopeAllowed: false + nodeReRegistrationTimeout: 0 + defaultClientScopes: + - web-origins + - roles + - profile + - email + optionalClientScopes: + - address + - phone + - offline_access + - microprofile-jwt + - id: b93b8aa2-9fbc-48aa-8aa9-5f0c6383330a + clientId: broker + name: "${client_broker}" + surrogateAuthRequired: false + enabled: true + alwaysDisplayInConsole: false + clientAuthenticatorType: client-secret + redirectUris: [] + webOrigins: [] + notBefore: 0 + bearerOnly: true + consentRequired: false + standardFlowEnabled: true + implicitFlowEnabled: false + directAccessGrantsEnabled: false + serviceAccountsEnabled: false + publicClient: false + frontchannelLogout: false + protocol: openid-connect + attributes: {} + authenticationFlowBindingOverrides: {} + fullScopeAllowed: false + nodeReRegistrationTimeout: 0 + defaultClientScopes: + - web-origins + - roles + - profile + - email + optionalClientScopes: + - address + - phone + - offline_access + - microprofile-jwt + - id: 59cc4ef9-9e71-4304-89a3-c9aef6d90f24 + clientId: realm-management + name: "${client_realm-management}" + surrogateAuthRequired: false + enabled: true + alwaysDisplayInConsole: false + clientAuthenticatorType: client-secret + redirectUris: [] + webOrigins: [] + notBefore: 0 + bearerOnly: true + consentRequired: false + standardFlowEnabled: true + implicitFlowEnabled: false + directAccessGrantsEnabled: false + serviceAccountsEnabled: false + publicClient: false + frontchannelLogout: false + protocol: openid-connect + attributes: {} + authenticationFlowBindingOverrides: {} + fullScopeAllowed: false + nodeReRegistrationTimeout: 0 + defaultClientScopes: + - web-origins + - roles + - profile + - email + optionalClientScopes: + - address + - phone + - offline_access + - microprofile-jwt + - id: 79af8215-9c3c-462c-a005-bcf8ad5e3ea5 + clientId: security-admin-console + name: "${client_security-admin-console}" + rootUrl: "${authAdminUrl}" + baseUrl: "/admin/token-test/console/" + surrogateAuthRequired: false + enabled: true + alwaysDisplayInConsole: false + clientAuthenticatorType: client-secret + redirectUris: + - "/admin/token-test/console/*" + webOrigins: + - "+" + notBefore: 0 + bearerOnly: false + consentRequired: false + standardFlowEnabled: true + implicitFlowEnabled: false + directAccessGrantsEnabled: false + serviceAccountsEnabled: false + publicClient: true + frontchannelLogout: false + protocol: openid-connect + attributes: + pkce.code.challenge.method: S256 + authenticationFlowBindingOverrides: {} + fullScopeAllowed: false + nodeReRegistrationTimeout: 0 + protocolMappers: + - id: 0ff87aba-d404-4ac6-8244-16562aa42340 + name: locale + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: locale + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: locale + jsonType.label: String + defaultClientScopes: + - web-origins + - roles + - profile + - email + optionalClientScopes: + - address + - phone + - offline_access + - microprofile-jwt + - id: 723e0da4-e2cc-4b2c-9f40-f42101d3e7a5 + clientId: token-test-client + baseUrl: http://localhost:8080/realms/token-test/account/ + surrogateAuthRequired: false + enabled: true + alwaysDisplayInConsole: false + clientAuthenticatorType: client-secret + redirectUris: + - token-test + webOrigins: + - localhost + - 127.0.0.1:8080 + - localhost:8443 + - 127.0.0.1:8443 + - localhost:8080 + - 127.0.0.1 + notBefore: 0 + bearerOnly: false + consentRequired: false + standardFlowEnabled: true + implicitFlowEnabled: false + directAccessGrantsEnabled: true + serviceAccountsEnabled: false + publicClient: true + frontchannelLogout: false + protocol: openid-connect + attributes: + access.token.lifespan: '6000' + saml.force.post.binding: 'false' + saml.multivalued.roles: 'false' + oauth2.device.authorization.grant.enabled: 'false' + backchannel.logout.revoke.offline.tokens: 'false' + saml.server.signature.keyinfo.ext: 'false' + use.refresh.tokens: 'true' + oidc.ciba.grant.enabled: 'false' + backchannel.logout.session.required: 'true' + client_credentials.use_refresh_token: 'false' + require.pushed.authorization.requests: 'false' + saml.client.signature: 'false' + id.token.as.detached.signature: 'false' + saml.assertion.signature: 'false' + saml.encrypt: 'false' + saml.server.signature: 'false' + exclude.session.state.from.auth.response: 'false' + saml.artifact.binding: 'false' + saml_force_name_id_format: 'false' + acr.loa.map: "{}" + tls.client.certificate.bound.access.tokens: 'false' + saml.authnstatement: 'false' + display.on.consent.screen: 'false' + token.response.type.bearer.lower-case: 'false' + saml.onetimeuse.condition: 'false' + authenticationFlowBindingOverrides: {} + fullScopeAllowed: true + nodeReRegistrationTimeout: -1 + defaultClientScopes: + - web-origins + - roles + - profile + - email + optionalClientScopes: + - address + - phone + - offline_access + - microprofile-jwt + clientScopes: + - id: 83c642d1-0768-487f-9ea9-76f47b6bf308 + name: email + description: 'OpenID Connect built-in scope: email' + protocol: openid-connect + attributes: + include.in.token.scope: 'true' + display.on.consent.screen: 'true' + consent.screen.text: "${emailScopeConsentText}" + protocolMappers: + - id: 3c769676-15e6-40b9-8038-2564a42d2b71 + name: email + protocol: openid-connect + protocolMapper: oidc-usermodel-property-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: email + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: email + jsonType.label: String + - id: 0d8dd2f6-40b3-4b41-a6f7-b57458932670 + name: email verified + protocol: openid-connect + protocolMapper: oidc-usermodel-property-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: emailVerified + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: email_verified + jsonType.label: boolean + - id: 851084f7-5d63-43ee-8599-00e7101e61c3 + name: microprofile-jwt + description: Microprofile - JWT built-in scope + protocol: openid-connect + attributes: + include.in.token.scope: 'true' + display.on.consent.screen: 'false' + protocolMappers: + - id: 682a2488-36bb-42d3-a6e6-35b9d5e3d4c5 + name: groups + protocol: openid-connect + protocolMapper: oidc-usermodel-realm-role-mapper + consentRequired: false + config: + multivalued: 'true' + user.attribute: foo + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: groups + jsonType.label: String + - id: 398e9b68-8327-425a-89d7-e639cadfe784 + name: upn + protocol: openid-connect + protocolMapper: oidc-usermodel-property-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: username + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: upn + jsonType.label: String + - id: c6eb0bac-39a0-4a10-839e-98a2d9426a52 + name: roles + description: OpenID Connect scope for add user roles to the access token + protocol: openid-connect + attributes: + include.in.token.scope: 'false' + display.on.consent.screen: 'true' + consent.screen.text: "${rolesScopeConsentText}" + protocolMappers: + - id: f8c4efd0-aeaa-4540-a47c-20e04bef4954 + name: audience resolve + protocol: openid-connect + protocolMapper: oidc-audience-resolve-mapper + consentRequired: false + config: {} + - id: e22bb72a-5fae-4a92-b5e9-1dd57488910f + name: client roles + protocol: openid-connect + protocolMapper: oidc-usermodel-client-role-mapper + consentRequired: false + config: + user.attribute: foo + access.token.claim: 'true' + claim.name: resource_access.${client_id}.roles + jsonType.label: String + multivalued: 'true' + - id: db34ab22-a6d3-4b7e-8f39-158439375ccb + name: realm roles + protocol: openid-connect + protocolMapper: oidc-usermodel-realm-role-mapper + consentRequired: false + config: + user.attribute: foo + access.token.claim: 'true' + claim.name: realm_access.roles + jsonType.label: String + multivalued: 'true' + - id: 7a52c125-48f0-44fd-8f1a-1809f8b2de36 + name: role_list + description: SAML role list + protocol: saml + attributes: + consent.screen.text: "${samlRoleListScopeConsentText}" + display.on.consent.screen: 'true' + protocolMappers: + - id: 9e2e632e-9574-43b1-a51c-9aade0906f3f + name: role list + protocol: saml + protocolMapper: saml-role-list-mapper + consentRequired: false + config: + single: 'false' + attribute.nameformat: Basic + attribute.name: Role + - id: 3a61fa5e-64ff-45be-aede-2c781ee03541 + name: phone + description: 'OpenID Connect built-in scope: phone' + protocol: openid-connect + attributes: + include.in.token.scope: 'true' + display.on.consent.screen: 'true' + consent.screen.text: "${phoneScopeConsentText}" + protocolMappers: + - id: 14579adc-1b3b-42e5-9602-4d8f9fa88e80 + name: phone number verified + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: phoneNumberVerified + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: phone_number_verified + jsonType.label: boolean + - id: 0d582284-ae4e-4fd6-9e50-555f2dc7d078 + name: phone number + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: phoneNumber + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: phone_number + jsonType.label: String + - id: e48bc0ba-24e7-4d91-b0d1-7cc81e9afe5f + name: address + description: 'OpenID Connect built-in scope: address' + protocol: openid-connect + attributes: + include.in.token.scope: 'true' + display.on.consent.screen: 'true' + consent.screen.text: "${addressScopeConsentText}" + protocolMappers: + - id: bd21105a-0cd4-4c63-ada2-8edc37475d38 + name: address + protocol: openid-connect + protocolMapper: oidc-address-mapper + consentRequired: false + config: + user.attribute.formatted: formatted + user.attribute.country: country + user.attribute.postal_code: postal_code + userinfo.token.claim: 'true' + user.attribute.street: street + id.token.claim: 'true' + user.attribute.region: region + access.token.claim: 'true' + user.attribute.locality: locality + - id: e14c7a2b-c298-40e9-b8e2-01a41b1556b4 + name: offline_access + description: 'OpenID Connect built-in scope: offline_access' + protocol: openid-connect + attributes: + consent.screen.text: "${offlineAccessScopeConsentText}" + display.on.consent.screen: 'true' + - id: aa7fea10-12a7-4a2e-9513-8f449d18bdbd + name: web-origins + description: OpenID Connect scope for add allowed web origins to the access token + protocol: openid-connect + attributes: + include.in.token.scope: 'false' + display.on.consent.screen: 'false' + consent.screen.text: '' + protocolMappers: + - id: 134b42aa-8eb7-4f17-b468-0a4db3414b07 + name: allowed web origins + protocol: openid-connect + protocolMapper: oidc-allowed-origins-mapper + consentRequired: false + config: {} + - id: c6c98a14-edcf-4bf7-8b82-4230f8cf7eca + name: profile + description: 'OpenID Connect built-in scope: profile' + protocol: openid-connect + attributes: + include.in.token.scope: 'true' + display.on.consent.screen: 'true' + consent.screen.text: "${profileScopeConsentText}" + protocolMappers: + - id: c07e881a-2715-436b-8e23-738e9eb02304 + name: family name + protocol: openid-connect + protocolMapper: oidc-usermodel-property-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: lastName + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: family_name + jsonType.label: String + - id: 479cafcb-7a00-4c37-a94a-31b7e9622db7 + name: gender + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: gender + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: gender + jsonType.label: String + - id: 581d067c-0151-4cfc-9c7b-64ed762e03ae + name: full name + protocol: openid-connect + protocolMapper: oidc-full-name-mapper + consentRequired: false + config: + id.token.claim: 'true' + access.token.claim: 'true' + userinfo.token.claim: 'true' + - id: 87b0ce4b-86b3-4143-926f-301f3afee083 + name: middle name + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: middleName + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: middle_name + jsonType.label: String + - id: 2f4f8664-ed76-448e-9814-2bb84b8c8d03 + name: username + protocol: openid-connect + protocolMapper: oidc-usermodel-property-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: username + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: preferred_username + jsonType.label: String + - id: d1568b1c-5034-429c-b7f0-ef876b4dcef0 + name: zoneinfo + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: zoneinfo + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: zoneinfo + jsonType.label: String + - id: 070b8b25-a1f7-4a61-9786-d5a56bc62a70 + name: nickname + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: nickname + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: nickname + jsonType.label: String + - id: 651d7a9e-d368-464b-8890-1d6d8a383ec4 + name: profile + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: profile + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: profile + jsonType.label: String + - id: 650a0ddd-833d-4a31-9c5a-8aa64f6a7d22 + name: given name + protocol: openid-connect + protocolMapper: oidc-usermodel-property-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: firstName + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: given_name + jsonType.label: String + - id: 90b55b69-ac74-448c-ba77-c92e974f90db + name: locale + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: locale + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: locale + jsonType.label: String + - id: 52fa62e2-24f7-445f-8a1b-0b2c201cad3e + name: updated at + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: updatedAt + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: updated_at + jsonType.label: String + - id: 510d43fc-bda3-456a-b57b-b1802932975f + name: website + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: website + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: website + jsonType.label: String + - id: a9bd191a-7c39-4d5b-a730-8712e61bd047 + name: picture + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: picture + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: picture + jsonType.label: String + - id: 267cc28e-498c-414d-9f2c-25a9046e3b21 + name: birthdate + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + consentRequired: false + config: + userinfo.token.claim: 'true' + user.attribute: birthdate + id.token.claim: 'true' + access.token.claim: 'true' + claim.name: birthdate + jsonType.label: String + defaultDefaultClientScopes: + - role_list + - profile + - email + - roles + - web-origins + defaultOptionalClientScopes: + - offline_access + - address + - phone + - microprofile-jwt + browserSecurityHeaders: + contentSecurityPolicyReportOnly: '' + xContentTypeOptions: nosniff + xRobotsTag: none + xFrameOptions: SAMEORIGIN + contentSecurityPolicy: frame-src 'self'; frame-ancestors 'self'; object-src 'none'; + xXSSProtection: 1; mode=block + strictTransportSecurity: max-age=31536000; includeSubDomains + smtpServer: {} + eventsEnabled: false + eventsListeners: + - jboss-logging + enabledEventTypes: [] + adminEventsEnabled: false + adminEventsDetailsEnabled: false + identityProviders: [] + identityProviderMappers: [] + components: + org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy: + - id: 1fa57595-ddd4-4887-ab09-c511a040236f + name: Consent Required + providerId: consent-required + subType: anonymous + subComponents: {} + config: {} + - id: 7063fa94-4f9e-48cd-9659-bb46ccc09764 + name: Full Scope Disabled + providerId: scope + subType: anonymous + subComponents: {} + config: {} + - id: 02a54f88-b589-47a7-9f05-d3bbdc91f1cc + name: Allowed Protocol Mapper Types + providerId: allowed-protocol-mappers + subType: anonymous + subComponents: {} + config: + allowed-protocol-mapper-types: + - oidc-full-name-mapper + - saml-user-attribute-mapper + - oidc-usermodel-attribute-mapper + - saml-user-property-mapper + - oidc-sha256-pairwise-sub-mapper + - saml-role-list-mapper + - oidc-address-mapper + - oidc-usermodel-property-mapper + - id: 773c5f86-5d98-4de9-b671-7c16b6d9edec + name: Allowed Protocol Mapper Types + providerId: allowed-protocol-mappers + subType: authenticated + subComponents: {} + config: + allowed-protocol-mapper-types: + - oidc-full-name-mapper + - saml-role-list-mapper + - oidc-usermodel-attribute-mapper + - oidc-address-mapper + - oidc-sha256-pairwise-sub-mapper + - saml-user-attribute-mapper + - saml-user-property-mapper + - oidc-usermodel-property-mapper + - id: 295b5e57-10bf-49ea-91af-9f8e3efcbbd2 + name: Allowed Client Scopes + providerId: allowed-client-templates + subType: anonymous + subComponents: {} + config: + allow-default-scopes: + - 'true' + - id: d40fbdbf-2dfa-4e1a-b16a-a50fc188f8f3 + name: Allowed Client Scopes + providerId: allowed-client-templates + subType: authenticated + subComponents: {} + config: + allow-default-scopes: + - 'true' + - id: 848fadee-77c2-4ec6-9cb1-0a880f8a8ab9 + name: Trusted Hosts + providerId: trusted-hosts + subType: anonymous + subComponents: {} + config: + host-sending-registration-request-must-match: + - 'true' + client-uris-must-match: + - 'true' + - id: d9ea7724-fda7-4ff8-80ee-5d404e568e12 + name: Max Clients Limit + providerId: max-clients + subType: anonymous + subComponents: {} + config: + max-clients: + - '200' + org.keycloak.keys.KeyProvider: + - id: 2d50d57e-5ba0-400b-901b-fa2885e0b1ea + name: rsa-generated + providerId: rsa-generated + subComponents: {} + config: + privateKey: + - MIIEpAIBAAKCAQEAy+YQfjoAZZ2uTS0X09R4JMj3CLlrElSjuE+NHi+OU3HOWNl9v+fT2kIswlD7ijn3MeR9qoPZLwIsE6b1SlSw12I1Gc57JIOs++VCZKTG5eMoBsEOntHbVU71LjwbKAqNVE//UeyuTKRv6y9YNX8BoFH/KFNE9unemv5M1DpHiH/bbco+4hXR+BcyhEbP2U8+JHcBdxbD8k8fcRgMGIXFwylHwlzAQwnmOG/tQ1P0/u1frSFq6hNj4phZ0V1JdpjfICk88tKrggMtNG7bEmh7k3ZrXsoyqJ6XKrVaIrvI9zuTz4aIsgEdBR2d7Up6pjANR1oJmXyiNGoMxVo/OdedWwIDAQABAoIBAQCLnXYPqJGbAvRV3hmhr6uwrHcS3zukqpYMX1RmpfOTyaqchhgn7orOuV9CkwcaKATOggFWX7+4A4nAzyLIieMpKBLqH8uMPimVte7XUUjsIrXGoizrrRC9gjo6NWf26/rID5rpMuJKkpIb/SguQVAQwfSwXQws8gi+IoDjFSDkIkbGC/c5M1hPxocIc9hcKN5yTnSiSMY6PtX5YMRoNV2L7hwCnZnAgd9sZSeSi+i39BNT6XYXuBroBWTEc6R3dfG/cRI6jTJZMl43RxgxSCCcdEZvxZXuONpUQVrSDkqwvuDkSYx8d0CkbQha4sHQj8juR9E4ziF57og/WXfaUd1BAoGBAPPk5hsL4Fr+tEcv65L/7tGy3Q2VaFqRNvDM6lHmKZy/WjFGXa3rMdcXyUny5uvN7E+Iqwt7Up0ab4pmvciGBX05wJO6OyXmk3pZuWRSLPT9WmkUCH8HLciGCirlJZPQY3LqR+qcl/MQ4wlLQOMzJUFwwvoOICGEOz1MY6nadSFhAoGBANYE9Bsi1lo7p02SJSQIk4pQEFT7vPqGVaku+2VLFHVzcO/teLNBiO9PimHwibk3r4Gtmv3P6jqvK8Q4D0V0tNnVjWg3vsJ4AAgkY7H5rt7RdNf9uy7QZzy6FgimwjQ4vhJDC4hsNEHuz6noCK+uaG5TarWQ1IXj/M08p4U5RGw7AoGAYMyZk2R8UEFFFffr/LT9eVcPKyQAfemir6H04jqCi4ba6jGuXqe5aVA0gNgaVL6vKsXodS8mE9p5KKosatjedtwkFb3VWe6Q2/+eeDWxSC8B4jCkSp5zymGAyZOW/Xq47dQUZQZvvHYYVgj7IPGcuMNjb1GJ6SONS3/1EmX1FSECgYA0mJ8NFDCtmD9zdtkd0+W+dhKtb/hvcRgYLe2mZR8wBiDZNfkVxKNMfLW7gAu4sxC0w991ROWBao9M96H5JcdUSYEo/Zop3KfVWGwPzxbEt6EJe9fGl3znlavYkHLltpQvlL5+1mi5U2FBlj6cPjZ39pQg7ujrxq3YGnHo8bv5BQKBgQDgTPZG6RXWMSXabWKsWEXM28/6+yh/0wEiuiJpkkiCU9lX2OFLiQ4cMMBbDonZv0fdoXOOxUvP6i43avRinmT6SMrkBRAzsB7WnhfBPHo0H9sYyaUCEmufc7l+/kh44ovX+XAeaTkZoC+zOobGOOuyGw9lZL7ev7JH1K32Y4Ugiw== + keyUse: + - SIG + certificate: + - MIICozCCAYsCBgF/LCTfSDANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDAp0b2tlbi10ZXN0MB4XDTIyMDIyNDE0Mjk0OFoXDTMyMDIyNDE0MzEyOFowFTETMBEGA1UEAwwKdG9rZW4tdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMvmEH46AGWdrk0tF9PUeCTI9wi5axJUo7hPjR4vjlNxzljZfb/n09pCLMJQ+4o59zHkfaqD2S8CLBOm9UpUsNdiNRnOeySDrPvlQmSkxuXjKAbBDp7R21VO9S48GygKjVRP/1Hsrkykb+svWDV/AaBR/yhTRPbp3pr+TNQ6R4h/223KPuIV0fgXMoRGz9lPPiR3AXcWw/JPH3EYDBiFxcMpR8JcwEMJ5jhv7UNT9P7tX60hauoTY+KYWdFdSXaY3yApPPLSq4IDLTRu2xJoe5N2a17KMqielyq1WiK7yPc7k8+GiLIBHQUdne1KeqYwDUdaCZl8ojRqDMVaPznXnVsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEANCWowVIZYAplp6qgsp6lkHVwWEDUiN/mjnVaIRZu//rzHOUyGLawZcXgkorKGqu7hrUoWGylicfJopjsuuo6csmtlMKqJoLEJKppwEkQ5O6+NFRHBOPy7ZnfkKjFO8GO9C+npZAZtG9gmEuXyVvO9zahvtql0UqOrMUjrUrB60HoVsHaCBT9mBO3C8a/gp6QmCu1RBcuZv4WamoI7xp32e0GM+tof9zNxyPll8UgrzyRDbDGD/xrX7QOmJKDSUdFTzfkcbMob74e1D6pXIdEHXeQ1XGGDPMcd6pTFO0cmsGoZUjNUGb6ynvIi5cEwwJcPqUGeaYBmk6JpHtdeHiM1w== + priority: + - '100' + - id: 4ba83849-4d31-4754-ba69-68ea6f236a60 + name: aes-generated + providerId: aes-generated + subComponents: {} + config: + kid: + - c1ad49ba-3f84-435a-a5be-822f3e81b0e1 + secret: + - SZleSSrmV0L92MsR218QnQ + priority: + - '100' + - id: 15036151-75c1-4119-9d9b-05c050c0985f + name: rsa-enc-generated + providerId: rsa-enc-generated + subComponents: {} + config: + privateKey: + - MIIEowIBAAKCAQEAj0s9q7fl1k/k22sX6HgQqNMgiitptM9uMcL5qAtU+ilPl1gvy/wYUN0s4ZB6+IIoxKjOdabrX+O8BLgqMSh3D+U/SzuIPJqp6ORS/93atU2fzW3wHZybf2a5k5+raC9oQ1TokQ2WzRyGO/8kgHqnYSz9vYoRQ+V2pxjL9Zlldt/D8Hx5kZ/K3psRbMoADlbyi9zsnPME2lvdTTSR2mgBaDxAOc8lm8JO2RRURqUuETLW6ya8MJ+rSO3idqija1vBjKDF+sshZYJzv7/qJLC9J3YpeaS+KL+8TvB7xGoP97uR3jCRqqNo4Fd1CbZalHoT6MeL12Y3Aruxk8RFSRbIgQIDAQABAoIBABlHoueql+fJTIzRRfSDSh0esjzuD8YQWlZ5GWZmKWXA6APBUR1hqkCJ5KMexDMXc23Ogi4LdrcCDGegvgDSLL8nKJVzOUPH3XXy4hm14CHgQfMSFCyFNoGxc8fxgWHuCyzly+nbReGFyMDI8H2iJelk8JcBxq39y4MLQuBfYaEo8BfG8FsRz9Um0nvsgoSMCYPonmkLiNk+eIVLWtUkIAlFdnU9NnKP4xlT7Xz9bEPq5blqX470PHxhnyCe+WRoVK7yUF6B1ZNDu8hjiq9k5DvCfUOJa0LGUudfMMFQ2BptwT5XVVuPgeRNO8wX07QfsILZWixsZHK+qEbjL5WRX+ECgYEA6VVsIWIFNawqXjMHOIAWFlDknsVCc1IgI6TDO6+CryLusdkRgvY9XMaWLnA8z0qtYd7qa9K02pWZyWpnhIsQZLm/wwmXqGy44+mvS7+4NJ682PHdEqVRTrqytqs5s0nj79pozTNY64Z6sNmtKFEj9SqpFQOPB9qtFY3FHH6AA+UCgYEAnTayG3lJewx5p0Cw98z9dc4TOJgZvjJLihpuDo0My7VC7yo8Vf9oCUs9p1hQV05mbDqCWRsCm4wXZZBCfjsrUmOS3f3zN1Hxv2S3JK8cJSYLnS8Fj1haa1POtJHTHWouWKK+V4KQYV5PXloGRzXBRVhywaBfBm9qsgrKimsuoG0CgYAyA/2JqlkziBQM3SNPGSWi4vQelGoKDjCVc1vmD1kT8Yj41m7Kg73jhS3sBmMCjB9eO0pEkoXx1N+CLSzDXIvHC4nvZL79e1CmihDpS89QeDZCypV4ybcECUEFpu5XYB9b6pVMZxVIZyslfYOAgOQUSXC08G5YYKd5V0pJMVR/gQKBgFZqkkx3xuRUXyqIbL5Jd6khtX8OXFgn3U30aTqmXbo70KcWWEQNOGqjaShrav4SokorfyrrpetKfjHdsi8g5xdKlJhh1yc5a+EAw4rullH1L70e87dvoYQNdTncTmeEziT6kBYaNrLO3GHIqlrKOYqcq7ezJ4iqBcQIGn0rnV2hAoGBAL3nhwND0ur7YhR1XAP7ZtPX4QbbdrwEdqCEXkOTTvXFz5Dl7bnZ7K4uJuLx6nKIrP/+w4ZeLLKvlS8QfxhzTRbK44TqwlRlv/QXH0po/URvZioq8mS0ttgb39cAN9NVnBRfrIub3ONDbk5Oxg0JOy6KWHQa/U8oKxC0zj2JlqWL + keyUse: + - ENC + certificate: + - MIICozCCAYsCBgF/LCTfyjANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDAp0b2tlbi10ZXN0MB4XDTIyMDIyNDE0Mjk0OFoXDTMyMDIyNDE0MzEyOFowFTETMBEGA1UEAwwKdG9rZW4tdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAI9LPau35dZP5NtrF+h4EKjTIIorabTPbjHC+agLVPopT5dYL8v8GFDdLOGQeviCKMSoznWm61/jvAS4KjEodw/lP0s7iDyaqejkUv/d2rVNn81t8B2cm39muZOfq2gvaENU6JENls0chjv/JIB6p2Es/b2KEUPldqcYy/WZZXbfw/B8eZGfyt6bEWzKAA5W8ovc7JzzBNpb3U00kdpoAWg8QDnPJZvCTtkUVEalLhEy1usmvDCfq0jt4naoo2tbwYygxfrLIWWCc7+/6iSwvSd2KXmkvii/vE7we8RqD/e7kd4wkaqjaOBXdQm2WpR6E+jHi9dmNwK7sZPERUkWyIECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEATMNU9g2wXqrkA9DqQJce35eaB/MemlxaGkM4ngD7UXegdCsfTgVZE839ipb2/BIq8fxaPpeGDVve7/dmfqUZQ/fVYF5ZZh8/16I1GCv4UEpveLfQhYF6GkZtY+6EMN8NZ8NDxiBy/NHL92svUlfv7Ry3Ffv52QWJstWHYzNTDTggmyHlBYU044ONqKU/aHOjzzPG0+GAHQdmY/0WFQj5Cd3KQdC2REWJryjo6UbrembLpEmWe5DSesc0NYByVssn/e6Htzb5ivFlzqhUC/h7FgFGPapWFRk1k94T93F5ZvhecB6nO38gPNyQNqTxzNt9astkTxOizuww8/vgnd8MOw== + priority: + - '100' + algorithm: + - RSA-OAEP + - id: 30ecba87-6daf-447d-bc8d-21f61cd36f82 + name: hmac-generated + providerId: hmac-generated + subComponents: {} + config: + kid: + - e1b9e589-63d5-4919-9672-5c02b27537b9 + secret: + - Shquog8STeo_a26mKTFXQoMzJeyQprehSO6p9J3HBUAIE86Tk47HXf9TAATfaQZ8N9xTdESlRu9njpV7evbTJg + priority: + - '100' + algorithm: + - HS256 + internationalizationEnabled: false + supportedLocales: [] + authenticationFlows: + - id: 83251d05-9245-46b3-9ece-ab5cb0ad3435 + alias: Account verification options + description: Method with which to verity the existing account + providerId: basic-flow + topLevel: false + builtIn: true + authenticationExecutions: + - authenticator: idp-email-verification + authenticatorFlow: false + requirement: ALTERNATIVE + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - authenticatorFlow: true + requirement: ALTERNATIVE + priority: 20 + autheticatorFlow: true + flowAlias: Verify Existing Account by Re-authentication + userSetupAllowed: false + - id: 3254f2e7-1256-4f29-b53a-49e1b304b9a1 + alias: Authentication Options + description: Authentication options. + providerId: basic-flow + topLevel: false + builtIn: true + authenticationExecutions: + - authenticator: basic-auth + authenticatorFlow: false + requirement: REQUIRED + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: basic-auth-otp + authenticatorFlow: false + requirement: DISABLED + priority: 20 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: auth-spnego + authenticatorFlow: false + requirement: DISABLED + priority: 30 + autheticatorFlow: false + userSetupAllowed: false + - id: 4b2db265-8c09-4e0e-9d8d-1049ed15270f + alias: Browser - Conditional OTP + description: Flow to determine if the OTP is required for the authentication + providerId: basic-flow + topLevel: false + builtIn: true + authenticationExecutions: + - authenticator: conditional-user-configured + authenticatorFlow: false + requirement: REQUIRED + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: auth-otp-form + authenticatorFlow: false + requirement: REQUIRED + priority: 20 + autheticatorFlow: false + userSetupAllowed: false + - id: 6f90621a-570b-4de6-af8c-df0ad24b7d97 + alias: Direct Grant - Conditional OTP + description: Flow to determine if the OTP is required for the authentication + providerId: basic-flow + topLevel: false + builtIn: true + authenticationExecutions: + - authenticator: conditional-user-configured + authenticatorFlow: false + requirement: REQUIRED + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: direct-grant-validate-otp + authenticatorFlow: false + requirement: REQUIRED + priority: 20 + autheticatorFlow: false + userSetupAllowed: false + - id: 67799bee-a2ce-467e-beb1-afae45336ab2 + alias: First broker login - Conditional OTP + description: Flow to determine if the OTP is required for the authentication + providerId: basic-flow + topLevel: false + builtIn: true + authenticationExecutions: + - authenticator: conditional-user-configured + authenticatorFlow: false + requirement: REQUIRED + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: auth-otp-form + authenticatorFlow: false + requirement: REQUIRED + priority: 20 + autheticatorFlow: false + userSetupAllowed: false + - id: 50ea02e8-ebb2-4315-91a1-d0d1de53a981 + alias: Handle Existing Account + description: Handle what to do if there is existing account with same email/username + like authenticated identity provider + providerId: basic-flow + topLevel: false + builtIn: true + authenticationExecutions: + - authenticator: idp-confirm-link + authenticatorFlow: false + requirement: REQUIRED + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - authenticatorFlow: true + requirement: REQUIRED + priority: 20 + autheticatorFlow: true + flowAlias: Account verification options + userSetupAllowed: false + - id: badc98d1-2c45-4760-8f31-35a014b6a262 + alias: Reset - Conditional OTP + description: Flow to determine if the OTP should be reset or not. Set to REQUIRED + to force. + providerId: basic-flow + topLevel: false + builtIn: true + authenticationExecutions: + - authenticator: conditional-user-configured + authenticatorFlow: false + requirement: REQUIRED + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: reset-otp + authenticatorFlow: false + requirement: REQUIRED + priority: 20 + autheticatorFlow: false + userSetupAllowed: false + - id: 38e9254a-b453-479c-a7c1-ac19f7915f11 + alias: User creation or linking + description: Flow for the existing/non-existing user alternatives + providerId: basic-flow + topLevel: false + builtIn: true + authenticationExecutions: + - authenticatorConfig: create unique user config + authenticator: idp-create-user-if-unique + authenticatorFlow: false + requirement: ALTERNATIVE + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - authenticatorFlow: true + requirement: ALTERNATIVE + priority: 20 + autheticatorFlow: true + flowAlias: Handle Existing Account + userSetupAllowed: false + - id: ed4e514c-0102-4c0b-adf5-699757680488 + alias: Verify Existing Account by Re-authentication + description: Reauthentication of existing account + providerId: basic-flow + topLevel: false + builtIn: true + authenticationExecutions: + - authenticator: idp-username-password-form + authenticatorFlow: false + requirement: REQUIRED + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - authenticatorFlow: true + requirement: CONDITIONAL + priority: 20 + autheticatorFlow: true + flowAlias: First broker login - Conditional OTP + userSetupAllowed: false + - id: 2770f39c-b2b9-4e3a-990e-fefdd30dedfa + alias: browser + description: browser based authentication + providerId: basic-flow + topLevel: true + builtIn: true + authenticationExecutions: + - authenticator: auth-cookie + authenticatorFlow: false + requirement: ALTERNATIVE + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: auth-spnego + authenticatorFlow: false + requirement: DISABLED + priority: 20 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: identity-provider-redirector + authenticatorFlow: false + requirement: ALTERNATIVE + priority: 25 + autheticatorFlow: false + userSetupAllowed: false + - authenticatorFlow: true + requirement: ALTERNATIVE + priority: 30 + autheticatorFlow: true + flowAlias: forms + userSetupAllowed: false + - id: f23b4ef6-8b24-4416-8c54-503e4a679afc + alias: clients + description: Base authentication for clients + providerId: client-flow + topLevel: true + builtIn: true + authenticationExecutions: + - authenticator: client-secret + authenticatorFlow: false + requirement: ALTERNATIVE + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: client-jwt + authenticatorFlow: false + requirement: ALTERNATIVE + priority: 20 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: client-secret-jwt + authenticatorFlow: false + requirement: ALTERNATIVE + priority: 30 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: client-x509 + authenticatorFlow: false + requirement: ALTERNATIVE + priority: 40 + autheticatorFlow: false + userSetupAllowed: false + - id: 8b835a57-4145-49ba-a922-92100aa2ddec + alias: direct grant + description: OpenID Connect Resource Owner Grant + providerId: basic-flow + topLevel: true + builtIn: true + authenticationExecutions: + - authenticator: direct-grant-validate-username + authenticatorFlow: false + requirement: REQUIRED + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: direct-grant-validate-password + authenticatorFlow: false + requirement: REQUIRED + priority: 20 + autheticatorFlow: false + userSetupAllowed: false + - authenticatorFlow: true + requirement: CONDITIONAL + priority: 30 + autheticatorFlow: true + flowAlias: Direct Grant - Conditional OTP + userSetupAllowed: false + - id: 8474649e-8e1d-4218-97df-c1edbac87636 + alias: docker auth + description: Used by Docker clients to authenticate against the IDP + providerId: basic-flow + topLevel: true + builtIn: true + authenticationExecutions: + - authenticator: docker-http-basic-authenticator + authenticatorFlow: false + requirement: REQUIRED + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - id: ede3e69e-cbb5-46fb-8789-e3532e05e9d4 + alias: first broker login + description: Actions taken after first broker login with identity provider account, + which is not yet linked to any Keycloak account + providerId: basic-flow + topLevel: true + builtIn: true + authenticationExecutions: + - authenticatorConfig: review profile config + authenticator: idp-review-profile + authenticatorFlow: false + requirement: REQUIRED + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - authenticatorFlow: true + requirement: REQUIRED + priority: 20 + autheticatorFlow: true + flowAlias: User creation or linking + userSetupAllowed: false + - id: 4c207a4f-e46c-4443-a38d-e6cc05708e5f + alias: forms + description: Username, password, otp and other auth forms. + providerId: basic-flow + topLevel: false + builtIn: true + authenticationExecutions: + - authenticator: auth-username-password-form + authenticatorFlow: false + requirement: REQUIRED + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - authenticatorFlow: true + requirement: CONDITIONAL + priority: 20 + autheticatorFlow: true + flowAlias: Browser - Conditional OTP + userSetupAllowed: false + - id: d73c0597-fdd5-44de-a5e9-982033d970d2 + alias: http challenge + description: An authentication flow based on challenge-response HTTP Authentication + Schemes + providerId: basic-flow + topLevel: true + builtIn: true + authenticationExecutions: + - authenticator: no-cookie-redirect + authenticatorFlow: false + requirement: REQUIRED + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - authenticatorFlow: true + requirement: REQUIRED + priority: 20 + autheticatorFlow: true + flowAlias: Authentication Options + userSetupAllowed: false + - id: 15b7b51a-e7d6-4bb2-8204-3bcc1cc8ea67 + alias: registration + description: registration flow + providerId: basic-flow + topLevel: true + builtIn: true + authenticationExecutions: + - authenticator: registration-page-form + authenticatorFlow: true + requirement: REQUIRED + priority: 10 + autheticatorFlow: true + flowAlias: registration form + userSetupAllowed: false + - id: 2d517957-80f2-4c66-827a-c6c7ae4413e9 + alias: registration form + description: registration form + providerId: form-flow + topLevel: false + builtIn: true + authenticationExecutions: + - authenticator: registration-user-creation + authenticatorFlow: false + requirement: REQUIRED + priority: 20 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: registration-profile-action + authenticatorFlow: false + requirement: REQUIRED + priority: 40 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: registration-password-action + authenticatorFlow: false + requirement: REQUIRED + priority: 50 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: registration-recaptcha-action + authenticatorFlow: false + requirement: DISABLED + priority: 60 + autheticatorFlow: false + userSetupAllowed: false + - id: 88424650-0cad-49a8-9df1-9362a1928375 + alias: reset credentials + description: Reset credentials for a user if they forgot their password or something + providerId: basic-flow + topLevel: true + builtIn: true + authenticationExecutions: + - authenticator: reset-credentials-choose-user + authenticatorFlow: false + requirement: REQUIRED + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: reset-credential-email + authenticatorFlow: false + requirement: REQUIRED + priority: 20 + autheticatorFlow: false + userSetupAllowed: false + - authenticator: reset-password + authenticatorFlow: false + requirement: REQUIRED + priority: 30 + autheticatorFlow: false + userSetupAllowed: false + - authenticatorFlow: true + requirement: CONDITIONAL + priority: 40 + autheticatorFlow: true + flowAlias: Reset - Conditional OTP + userSetupAllowed: false + - id: 7e32b05b-7c3d-46d1-a721-b146eb90bbe9 + alias: saml ecp + description: SAML ECP Profile Authentication Flow + providerId: basic-flow + topLevel: true + builtIn: true + authenticationExecutions: + - authenticator: http-basic-authenticator + authenticatorFlow: false + requirement: REQUIRED + priority: 10 + autheticatorFlow: false + userSetupAllowed: false + authenticatorConfig: + - id: 7ee30b27-c4c4-4696-8479-4998ecc2cfe3 + alias: create unique user config + config: + require.password.update.after.registration: 'false' + - id: b300eb8b-11f4-4163-9843-bf2d2610731d + alias: review profile config + config: + update.profile.on.first.login: missing + requiredActions: + - alias: CONFIGURE_TOTP + name: Configure OTP + providerId: CONFIGURE_TOTP + enabled: true + defaultAction: false + priority: 10 + config: {} + - alias: terms_and_conditions + name: Terms and Conditions + providerId: terms_and_conditions + enabled: false + defaultAction: false + priority: 20 + config: {} + - alias: UPDATE_PASSWORD + name: Update Password + providerId: UPDATE_PASSWORD + enabled: true + defaultAction: false + priority: 30 + config: {} + - alias: UPDATE_PROFILE + name: Update Profile + providerId: UPDATE_PROFILE + enabled: true + defaultAction: false + priority: 40 + config: {} + - alias: VERIFY_EMAIL + name: Verify Email + providerId: VERIFY_EMAIL + enabled: true + defaultAction: false + priority: 50 + config: {} + - alias: delete_account + name: Delete Account + providerId: delete_account + enabled: false + defaultAction: false + priority: 60 + config: {} + - alias: update_user_locale + name: Update User Locale + providerId: update_user_locale + enabled: true + defaultAction: false + priority: 1000 + config: {} + browserFlow: browser + registrationFlow: registration + directGrantFlow: direct grant + resetCredentialsFlow: reset credentials + clientAuthenticationFlow: clients + dockerAuthenticationFlow: docker auth + attributes: + cibaBackchannelTokenDeliveryMode: poll + cibaExpiresIn: '120' + cibaAuthRequestedUserHint: login_hint + oauth2DeviceCodeLifespan: '600' + oauth2DevicePollingInterval: '5' + parRequestUriLifespan: '60' + cibaInterval: '5' + keycloakVersion: 18.0.0-SNAPSHOT + userManagedAccessAllowed: false + clientProfiles: + profiles: [] + clientPolicies: + policies: []