From 64360bb7927d3a7f77228d923f6e21913f5bf1e4 Mon Sep 17 00:00:00 2001 From: Min Zhang Date: Thu, 21 May 2026 18:03:09 -0400 Subject: [PATCH] feat: add rh-keycloak wrapper chart with PostSync cleanup Add a wrapper chart (charts/rh-keycloak) that consumes the rhbk chart as a dependency and adds a PostSync cleanup job for one-shot ExternalSecret provisioning. The PostSync job: 1. Waits for labeled ExternalSecrets to sync 2. Deletes them with --cascade=orphan (Secrets survive) 3. Cleans up ephemeral Secrets labeled for deletion (keycloak-users) Switch values-hub.yaml from the remote rhbk chart to the local rh-keycloak wrapper chart path. Requires rhbk-chart >= 0.0.9 with externalSecrets.oneShot support. Commented-out overrides updated with rhbk. prefix to match the wrapper chart structure. Signed-off-by: Min Zhang --- charts/rh-keycloak/Chart.yaml | 17 ++++ .../templates/cleanup-externalsecrets.yaml | 94 +++++++++++++++++++ charts/rh-keycloak/values.yaml | 15 +++ values-hub.yaml | 10 +- 4 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 charts/rh-keycloak/Chart.yaml create mode 100644 charts/rh-keycloak/templates/cleanup-externalsecrets.yaml create mode 100644 charts/rh-keycloak/values.yaml diff --git a/charts/rh-keycloak/Chart.yaml b/charts/rh-keycloak/Chart.yaml new file mode 100644 index 00000000..e274f18f --- /dev/null +++ b/charts/rh-keycloak/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: rh-keycloak +description: ZTVP Keycloak deployment — wraps the rhbk chart and adds PostSync cleanup for one-shot ExternalSecrets +type: application +version: 0.1.0 +dependencies: + - name: rhbk + version: ">=0.0.9" + repository: "oci://quay.io/validatedpatterns" +maintainers: + - name: Zero Trust Validated Patterns Team + email: ztvp-arch-group@redhat.com +keywords: + - keycloak + - rhbk + - zero-trust + - pattern diff --git a/charts/rh-keycloak/templates/cleanup-externalsecrets.yaml b/charts/rh-keycloak/templates/cleanup-externalsecrets.yaml new file mode 100644 index 00000000..fd8dac90 --- /dev/null +++ b/charts/rh-keycloak/templates/cleanup-externalsecrets.yaml @@ -0,0 +1,94 @@ +{{- if .Values.cleanup.enabled }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: cleanup-ephemeral-secrets + namespace: {{ .Release.Namespace }} + annotations: + argocd.argoproj.io/hook: PostSync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: cleanup-ephemeral-secrets + namespace: {{ .Release.Namespace }} + annotations: + argocd.argoproj.io/hook: PostSync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation +rules: +- apiGroups: ["external-secrets.io"] + resources: ["externalsecrets"] + verbs: ["get", "list", "delete"] +- apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "delete"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: cleanup-ephemeral-secrets + namespace: {{ .Release.Namespace }} + annotations: + argocd.argoproj.io/hook: PostSync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: cleanup-ephemeral-secrets +subjects: +- kind: ServiceAccount + name: cleanup-ephemeral-secrets + namespace: {{ .Release.Namespace }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: cleanup-ephemeral-secrets + namespace: {{ .Release.Namespace }} + annotations: + argocd.argoproj.io/hook: PostSync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation +spec: + backoffLimit: 2 + activeDeadlineSeconds: {{ .Values.cleanup.activeDeadlineSeconds }} + template: + spec: + serviceAccountName: cleanup-ephemeral-secrets + restartPolicy: Never + containers: + - name: cleanup + image: {{ .Values.cleanup.image }} + command: + - /bin/bash + - -ce + - | + LABEL="{{ .Values.cleanup.label }}" + NS="{{ .Release.Namespace }}" + + ES_COUNT=$(oc get externalsecret -l "${LABEL}=one-shot" -n "${NS}" --no-headers 2>/dev/null | wc -l) + if [ "${ES_COUNT}" -eq 0 ]; then + echo "No one-shot ExternalSecrets found. Nothing to do." + else + echo "Found ${ES_COUNT} one-shot ExternalSecret(s)." + echo "Waiting for ExternalSecrets to sync..." + oc wait externalsecret -l "${LABEL}=one-shot" -n "${NS}" \ + --for=condition=Ready --timeout=90s 2>/dev/null || \ + echo "WARNING: Timed out waiting for Ready, proceeding." + + echo "Deleting ExternalSecrets (orphaning dependent Secrets)..." + oc delete externalsecret -l "${LABEL}=one-shot" -n "${NS}" \ + --cascade=orphan --ignore-not-found + fi + + SEC_COUNT=$(oc get secret -l "${LABEL}=delete" -n "${NS}" --no-headers 2>/dev/null | wc -l) + if [ "${SEC_COUNT}" -eq 0 ]; then + echo "No ephemeral Secrets to clean up." + else + echo "Deleting ${SEC_COUNT} ephemeral Secret(s)..." + oc delete secret -l "${LABEL}=delete" -n "${NS}" --ignore-not-found + fi + + echo "Cleanup complete." +{{- end }} diff --git a/charts/rh-keycloak/values.yaml b/charts/rh-keycloak/values.yaml new file mode 100644 index 00000000..e1b564d0 --- /dev/null +++ b/charts/rh-keycloak/values.yaml @@ -0,0 +1,15 @@ +# PostSync cleanup for ephemeral Secrets. +# When enabled, a PostSync Job deletes the keycloak-users ExternalSecret +# with --cascade=orphan (so the Secret survives), then removes Secrets +# labeled for deletion (keycloak-users). +cleanup: + enabled: true + image: registry.redhat.io/openshift4/ose-cli-rhel9:latest + label: "ztvp.io/cleanup" + activeDeadlineSeconds: 120 + +# Values passed through to the rhbk subchart. +rhbk: + externalSecrets: + oneShot: true + secretCleanupLabel: "ztvp.io/cleanup" diff --git a/values-hub.yaml b/values-hub.yaml index a5dbd0e6..fcaad6d7 100644 --- a/values-hub.yaml +++ b/values-hub.yaml @@ -494,16 +494,16 @@ clusterGroup: name: rh-keycloak namespace: keycloak-system project: hub - chart: rhbk - chartVersion: 0.0.* + path: charts/rh-keycloak annotations: argocd.argoproj.io/sync-wave: "35" - # SPIFFE Identity Provider is enabled by default in the chart. + # SPIFFE Identity Provider is enabled by default in the rhbk subchart. # Override issuer/jwksUrl only if auto-generated values from cluster domain are not suitable. + # Note: overrides must use the rhbk. prefix to reach the subchart. # overrides: - # - name: keycloak.spiffeIdentityProvider.config.config.issuer + # - name: rhbk.keycloak.spiffeIdentityProvider.config.config.issuer # value: "spiffe://apps.example.com" - # - name: keycloak.spiffeIdentityProvider.config.config.jwksUrl + # - name: rhbk.keycloak.spiffeIdentityProvider.config.config.jwksUrl # value: "https://spire-spiffe-oidc-discovery-provider.apps.example.com/keys" rh-cert-manager: name: rh-cert-manager