Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/guides/operator/advanced-configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -308,4 +308,10 @@ stringData:
When running on a Kubernetes or OpenShift environment well-known locations of trusted certificates are included automatically.
This includes `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt` and the `/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt` when present.

=== Admin Bootstrapping

When you create a new instance the Keycloak CR spec.bootstrapAdmin stanza may be used to configure the bootstrap user and/or service account. If you do not specify anything for the spec.bootstrapAdmin, the operator will create a Secret named "metadata.name"-initial-admin with a username temp-admin and a generated password. If you specify a Secret name for bootstrap admin user, then the Secret will need to contain `username` and `password` key value pairs. If you specify a Secret name for bootstrap admin service account, then the Secret will need to contain `client-id` and `client-secret` key value pairs.

If a master realm has already been created for you cluster, then the spec.boostrapAdmin is effectively ignored. If you need to create a recovery admin account, then you'll need to run the CLI command against a Pod directly.

</@tmpl.guide>
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,32 @@
import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected;
import io.javaoperatorsdk.operator.processing.dependent.Creator;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;

import org.keycloak.operator.Constants;
import org.keycloak.operator.Utils;
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.BootstrapAdminSpec;

import java.util.Optional;
import java.util.UUID;

@KubernetesDependent(labelSelector = Constants.DEFAULT_LABELS_AS_STRING, resourceDiscriminator = KeycloakAdminSecretDependentResource.NameResourceDiscriminator.class)
public class KeycloakAdminSecretDependentResource extends KubernetesDependentResource<Secret, Keycloak> implements Creator<Secret, Keycloak>, GarbageCollected<Keycloak> {

public static class EnabledCondition implements Condition<Secret, Keycloak> {
@Override
public boolean isMet(DependentResource<Secret, Keycloak> dependentResource, Keycloak primary,
Context<Keycloak> context) {
return Optional.ofNullable(primary.getSpec().getBootstrapAdminSpec()).map(BootstrapAdminSpec::getUser)
.map(BootstrapAdminSpec.User::getSecret).filter(s -> !s.equals(KeycloakAdminSecretDependentResource.getName(primary))).isEmpty();
}
}

public static class NameResourceDiscriminator implements ResourceDiscriminator<Secret, Keycloak> {
@Override
Expand All @@ -39,8 +51,9 @@ protected Secret desired(Keycloak primary, Context<Keycloak> context) {
.addToLabels(Utils.allInstanceLabels(primary))
.withNamespace(primary.getMetadata().getNamespace())
.endMetadata()
.withType("Opaque")
.withType("kubernetes.io/basic-auth")
.addToData("username", Utils.asBase64("admin"))
.addToData("username", Utils.asBase64("temp-admin"))
.addToData("password", Utils.asBase64(UUID.randomUUID().toString().replace("-", "")))
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@

@ControllerConfiguration(
dependents = {
@Dependent(type = KeycloakDeploymentDependentResource.class),
@Dependent(type = KeycloakAdminSecretDependentResource.class),
@Dependent(type = KeycloakAdminSecretDependentResource.class, reconcilePrecondition = KeycloakAdminSecretDependentResource.EnabledCondition.class),
@Dependent(type = KeycloakIngressDependentResource.class, reconcilePrecondition = KeycloakIngressDependentResource.EnabledCondition.class),
@Dependent(type = KeycloakServiceDependentResource.class, useEventSourceWithName = "serviceSource"),
@Dependent(type = KeycloakDiscoveryServiceDependentResource.class, useEventSourceWithName = "serviceSource")
Expand All @@ -78,6 +77,8 @@ public class KeycloakController implements Reconciler<Keycloak>, EventSourceInit

@Inject
KeycloakDistConfigurator distConfigurator;

volatile KeycloakDeploymentDependentResource deploymentDependentResource;

@Override
public Map<String, EventSource> prepareEventSources(EventSourceContext<Keycloak> context) {
Expand All @@ -94,6 +95,10 @@ public Map<String, EventSource> prepareEventSources(EventSourceContext<Keycloak>

Map<String, EventSource> sources = new HashMap<>();
sources.put("serviceSource", servicesEvent);

this.deploymentDependentResource = new KeycloakDeploymentDependentResource(config, watchedResources, distConfigurator);
sources.putAll(EventSourceInitializer.nameEventSourcesFromDependentResource(context, this.deploymentDependentResource));

return sources;
}

Expand All @@ -104,6 +109,8 @@ public UpdateControl<Keycloak> reconcile(Keycloak kc, Context<Keycloak> context)

Log.debugf("--- Reconciling Keycloak: %s in namespace: %s", kcName, namespace);

// TODO - these modifications to the resource may belong in a webhook because dependents run first
// only the statefulset is deferred until after
boolean modifiedSpec = false;
if (kc.getSpec().getInstances() == null) {
// explicitly set defaults - and let another reconciliation happen
Expand All @@ -125,6 +132,9 @@ public UpdateControl<Keycloak> reconcile(Keycloak kc, Context<Keycloak> context)
if (modifiedSpec) {
return UpdateControl.updateResource(kc);
}

// after the spec has possibly been updated, reconcile the StatefulSet
this.deploymentDependentResource.reconcile(kc, context);

var statusAggregator = new KeycloakStatusAggregator(kc.getStatus(), kc.getMetadata().getGeneration());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfigBuilder;
import io.quarkus.logging.Log;

import org.keycloak.operator.Config;
Expand Down Expand Up @@ -68,13 +68,10 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import jakarta.inject.Inject;

import static org.keycloak.operator.Utils.addResources;
import static org.keycloak.operator.controllers.KeycloakDistConfigurator.getKeycloakOptionEnvVarName;
import static org.keycloak.operator.crds.v2alpha1.CRDUtils.isTlsConfigured;

@KubernetesDependent(labelSelector = Constants.DEFAULT_LABELS_AS_STRING)
public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependentResource<StatefulSet, Keycloak> {

private static final List<String> COPY_ENV = Arrays.asList("HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY");
Expand All @@ -92,20 +89,23 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent

public static final String OPTIMIZED_ARG = "--optimized";

@Inject
Config operatorConfig;

@Inject
WatchedResources watchedResources;

@Inject
KeycloakDistConfigurator distConfigurator;

private boolean useServiceCaCrt;

public KeycloakDeploymentDependentResource() {
public KeycloakDeploymentDependentResource(Config operatorConfig, WatchedResources watchedResources, KeycloakDistConfigurator distConfigurator) {
super(StatefulSet.class);
this.operatorConfig = operatorConfig;
this.watchedResources = watchedResources;
this.distConfigurator = distConfigurator;
useServiceCaCrt = Files.exists(Path.of(SERVICE_CA_CRT));
this.configureWith(new KubernetesDependentResourceConfigBuilder<StatefulSet>()
.withLabelSelector(Constants.DEFAULT_LABELS_AS_STRING)
.build());
}

public void setUseServiceCaCrt(boolean useServiceCaCrt) {
Expand Down Expand Up @@ -403,8 +403,7 @@ private static String getJGroupsParameter(Keycloak keycloakCR) {
private void addEnvVars(StatefulSet baseDeployment, Keycloak keycloakCR, TreeSet<String> allSecrets) {
var firstClasssEnvVars = distConfigurator.configureDistOptions(keycloakCR);

String adminSecretName = KeycloakAdminSecretDependentResource.getName(keycloakCR);
var additionalEnvVars = getDefaultAndAdditionalEnvVars(keycloakCR, adminSecretName);
var additionalEnvVars = getDefaultAndAdditionalEnvVars(keycloakCR);

var env = Optional.ofNullable(baseDeployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv()).orElse(List.of());

Expand All @@ -426,15 +425,14 @@ private void addEnvVars(StatefulSet baseDeployment, Keycloak keycloakCR, TreeSet

// watch the secrets used by secret key - we don't currently expect configmaps, optional refs, or watch the initial-admin
TreeSet<String> serverConfigSecretsNames = envVars.stream().map(EnvVar::getValueFrom).filter(Objects::nonNull)
.map(EnvVarSource::getSecretKeyRef).filter(Objects::nonNull).map(SecretKeySelector::getName)
.filter(n -> !n.equals(adminSecretName)).collect(Collectors.toCollection(TreeSet::new));
.map(EnvVarSource::getSecretKeyRef).filter(Objects::nonNull).map(SecretKeySelector::getName).collect(Collectors.toCollection(TreeSet::new));

Log.debugf("Found config secrets names: %s", serverConfigSecretsNames);

allSecrets.addAll(serverConfigSecretsNames);
}

private List<EnvVar> getDefaultAndAdditionalEnvVars(Keycloak keycloakCR, String adminSecretName) {
private List<EnvVar> getDefaultAndAdditionalEnvVars(Keycloak keycloakCR) {
// default config values
List<ValueOrSecret> serverConfigsList = new ArrayList<>(Constants.DEFAULT_DIST_CONFIG_LIST);

Expand Down Expand Up @@ -467,29 +465,6 @@ private List<EnvVar> getDefaultAndAdditionalEnvVars(Keycloak keycloakCR, String
}
}

envVars.add(
new EnvVarBuilder()
.withName("KEYCLOAK_ADMIN")
.withNewValueFrom()
.withNewSecretKeyRef()
.withName(adminSecretName)
.withKey("username")
.withOptional(false)
.endSecretKeyRef()
.endValueFrom()
.build());
envVars.add(
new EnvVarBuilder()
.withName("KEYCLOAK_ADMIN_PASSWORD")
.withNewValueFrom()
.withNewSecretKeyRef()
.withName(adminSecretName)
.withKey("password")
.withOptional(false)
.endSecretKeyRef()
.endValueFrom()
.build());

return envVars;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusAggregator;
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.BootstrapAdminSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.DatabaseSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpec;
Expand Down Expand Up @@ -73,6 +74,7 @@ public KeycloakDistConfigurator() {
configureCache();
configureProxy();
configureManagement();
configureBootstrapAdmin();
}

/**
Expand All @@ -86,6 +88,26 @@ public void validateOptions(Keycloak keycloakCR, KeycloakStatusAggregator status

/* ---------- Configuration of first-class citizen fields ---------- */

void configureBootstrapAdmin() {
optionMapper(Function.identity())
.mapOption("bootstrap-admin-username",
keycloakCR -> Optional.ofNullable(keycloakCR.getSpec().getBootstrapAdminSpec())
.map(BootstrapAdminSpec::getUser).map(BootstrapAdminSpec.User::getSecret)
.or(() -> Optional.of(KeycloakAdminSecretDependentResource.getName(keycloakCR)))
.map(s -> new SecretKeySelector("username", s, null)).orElse(null))
.mapOption("bootstrap-admin-password",
keycloakCR -> Optional.ofNullable(keycloakCR.getSpec().getBootstrapAdminSpec())
.map(BootstrapAdminSpec::getUser).map(BootstrapAdminSpec.User::getSecret)
.or(() -> Optional.of(KeycloakAdminSecretDependentResource.getName(keycloakCR)))
.map(s -> new SecretKeySelector("password", s, null)).orElse(null));

optionMapper(keycloakCR -> keycloakCR.getSpec().getBootstrapAdminSpec())
.mapOption("bootstrap-admin-client-id",
spec -> Optional.ofNullable(spec.getService()).map(BootstrapAdminSpec.Service::getSecret).map(s -> new SecretKeySelector("client-id", s, null)).orElse(null))
.mapOption("bootstrap-admin-client-secret",
spec -> Optional.ofNullable(spec.getService()).map(BootstrapAdminSpec.Service::getSecret).map(s -> new SecretKeySelector("client-secret", s, null)).orElse(null));
}

void configureHostname() {
optionMapper(keycloakCR -> keycloakCR.getSpec().getHostnameSpec())
.mapOption("hostname", HostnameSpec::getHostname)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;

import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfigBuilder;
import jakarta.inject.Inject;
import org.keycloak.operator.Config;
import org.keycloak.operator.Constants;
import org.keycloak.operator.Utils;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.fabric8.kubernetes.api.model.ResourceRequirements;
import io.fabric8.kubernetes.model.annotation.SpecReplicas;

import org.keycloak.operator.crds.v2alpha1.deployment.spec.BootstrapAdminSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.CacheSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.DatabaseSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec;
Expand Down Expand Up @@ -115,6 +116,10 @@ public class KeycloakSpec {
@JsonPropertyDescription("In this section you can configure Keycloak's scheduling")
private SchedulingSpec schedulingSpec;

@JsonProperty("bootstrapAdmin")
@JsonPropertyDescription("In this section you can configure Keycloak's bootstrap admin - will be used only for inital cluster creation.")
private BootstrapAdminSpec bootstrapAdminSpec;

public HttpSpec getHttpSpec() {
return httpSpec;
}
Expand Down Expand Up @@ -264,4 +269,12 @@ public SchedulingSpec getSchedulingSpec() {
public void setSchedulingSpec(SchedulingSpec schedulingSpec) {
this.schedulingSpec = schedulingSpec;
}

public BootstrapAdminSpec getBootstrapAdminSpec() {
return bootstrapAdminSpec;
}

public void setBootstrapAdminSpec(BootstrapAdminSpec bootstrapAdminSpec) {
this.bootstrapAdminSpec = bootstrapAdminSpec;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.keycloak.operator.crds.v2alpha1.deployment.spec;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;

import io.sundr.builder.annotations.Buildable;

@JsonInclude(JsonInclude.Include.NON_NULL)
@Buildable(editableEnabled = false, builderPackage = "io.fabric8.kubernetes.api.builder")
public class BootstrapAdminSpec {

public static class User {
@JsonPropertyDescription("Name of the Secret that contains the username and password keys")
private String secret;

public String getSecret() {
return secret;
}

public void setSecret(String secret) {
this.secret = secret;
}
}

public static class Service {
@JsonPropertyDescription("Name of the Secret that contains the client-id and client-secret keys")
private String secret;

public String getSecret() {
return secret;
}

public void setSecret(String secret) {
this.secret = secret;
}

}

//private Integer expiration;
@JsonPropertyDescription("Configures the bootstrap admin user")
private User user;
@JsonPropertyDescription("Configures the bootstrap admin service account")
private Service service;

/*public Integer getExpiration() {
return expiration;
}

public void setExpiration(Integer expiration) {
this.expiration = expiration;
}*/

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

public Service getService() {
return service;
}

public void setService(Service service) {
this.service = service;
}

}
Loading