diff --git a/charts/rh-keycloak/Chart.yaml b/charts/rh-keycloak/Chart.yaml new file mode 100644 index 00000000..d9daf98a --- /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.10" + 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..4be4efa7 --- /dev/null +++ b/charts/rh-keycloak/templates/cleanup-externalsecrets.yaml @@ -0,0 +1,79 @@ +{{- 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: [""] + 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: + metadata: + labels: + app: cleanup-ephemeral-secrets + 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 }}" + + 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/templates/cleanup-network-policy.yaml b/charts/rh-keycloak/templates/cleanup-network-policy.yaml new file mode 100644 index 00000000..b54471a2 --- /dev/null +++ b/charts/rh-keycloak/templates/cleanup-network-policy.yaml @@ -0,0 +1,31 @@ +{{- if and .Values.cleanup.enabled (eq (.Values.rhbk.defaultDenyNetworkPolicy.enabled | toString) "true") }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: cleanup-ephemeral-secrets-network-policy + namespace: {{ .Release.Namespace }} + annotations: + argocd.argoproj.io/hook: PostSync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation +spec: + podSelector: + matchLabels: + app: cleanup-ephemeral-secrets + policyTypes: + - Egress + egress: + # DNS resolution via CoreDNS + - ports: + - protocol: UDP + port: 5353 + - protocol: TCP + port: 5353 + to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-dns + # Kubernetes API server — oc get/delete secret + - ports: + - protocol: TCP + port: 6443 +{{- end }} diff --git a/charts/rh-keycloak/values.yaml b/charts/rh-keycloak/values.yaml new file mode 100644 index 00000000..87e47d15 --- /dev/null +++ b/charts/rh-keycloak/values.yaml @@ -0,0 +1,15 @@ +# PostSync cleanup for ephemeral Secrets. +# When enabled, a PostSync Job deletes Secrets labeled for cleanup +# (e.g. keycloak-users) after the realm import completes. +# The ExternalSecret itself is removed by ArgoCD's HookSucceeded policy. +cleanup: + enabled: true + image: registry.redhat.io/openshift4/ose-cli-rhel9:latest + label: "validatedpatterns.io/cleanup" + activeDeadlineSeconds: 120 + +# Values passed through to the rhbk subchart. +rhbk: + externalSecrets: + oneShot: true + secretCleanupLabel: "validatedpatterns.io/cleanup" diff --git a/overrides/values-keycloak-network-policy.yaml b/overrides/values-keycloak-network-policy.yaml index 7b2e9f94..f6822269 100644 --- a/overrides/values-keycloak-network-policy.yaml +++ b/overrides/values-keycloak-network-policy.yaml @@ -1,153 +1,154 @@ -defaultDenyNetworkPolicy: - enabled: true - -networkPolicy: - keycloak: +rhbk: + defaultDenyNetworkPolicy: enabled: true - egress: - # DNS resolution via CoreDNS — OCP uses port 5353 - - ports: - - protocol: UDP - port: 5353 - - protocol: TCP - port: 5353 - to: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: openshift-dns - # PostgreSQL backend database - - ports: - - protocol: TCP - port: 5432 - to: - - podSelector: - matchLabels: - app: postgresql-db - # JGroups cluster discovery and failure detection (multi-instance clustering) - - ports: - - protocol: TCP - port: 7800 - - protocol: TCP - port: 57800 - to: - - podSelector: - matchLabels: - app: keycloak - app.kubernetes.io/instance: keycloak - app.kubernetes.io/managed-by: keycloak-operator - # Kubernetes API server — JDBC_PING discovery reads endpoints - # Endpoints are node IPs after DNAT, port-only rule required - - ports: - - protocol: TCP - port: 6443 - # SPIRE OIDC discovery provider — Keycloak fetches JWKS for federated - # client auth (spiffe feature). Traffic goes via the OCP router external - # IP, port-only rule required - - ports: - - protocol: TCP - port: 443 - realmImport: - enabled: true - podSelector: - app: keycloak-realm-import - egress: - # DNS resolution via CoreDNS - - ports: - - protocol: UDP - port: 5353 - - protocol: TCP - port: 5353 - to: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: openshift-dns - # PostgreSQL — realm import writes realm data to the database - - ports: - - protocol: TCP - port: 5432 - to: - - podSelector: - matchLabels: - app: postgresql-db - # Kubernetes API server — reads secrets referenced in KeycloakRealmImport CR - - ports: - - protocol: TCP - port: 6443 - # Keycloak HTTPS API — admin API calls during realm import - - ports: - - protocol: TCP - port: 8443 - to: - - podSelector: - matchLabels: - app: keycloak - app.kubernetes.io/instance: keycloak - app.kubernetes.io/managed-by: keycloak-operator + networkPolicy: + keycloak: + enabled: true + egress: + # DNS resolution via CoreDNS — OCP uses port 5353 + - ports: + - protocol: UDP + port: 5353 + - protocol: TCP + port: 5353 + to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-dns + # PostgreSQL backend database + - ports: + - protocol: TCP + port: 5432 + to: + - podSelector: + matchLabels: + app: postgresql-db + # JGroups cluster discovery and failure detection (multi-instance clustering) + - ports: + - protocol: TCP + port: 7800 + - protocol: TCP + port: 57800 + to: + - podSelector: + matchLabels: + app: keycloak + app.kubernetes.io/instance: keycloak + app.kubernetes.io/managed-by: keycloak-operator + # Kubernetes API server — JDBC_PING discovery reads endpoints + # Endpoints are node IPs after DNAT, port-only rule required + - ports: + - protocol: TCP + port: 6443 + # SPIRE OIDC discovery provider — Keycloak fetches JWKS for federated + # client auth (spiffe feature). Traffic goes via the OCP router external + # IP, port-only rule required + - ports: + - protocol: TCP + port: 443 - postgresql: - enabled: true - ingress: - # Accept connections from Keycloak pods and realm import jobs - - ports: - - protocol: TCP - port: 5432 - from: - - podSelector: - matchLabels: - app: keycloak - app.kubernetes.io/instance: keycloak - app.kubernetes.io/managed-by: keycloak-operator - - podSelector: - matchLabels: - app: keycloak-realm-import - egress: - # DNS resolution via CoreDNS - - ports: - - protocol: UDP - port: 5353 - - protocol: TCP - port: 5353 - to: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: openshift-dns + realmImport: + enabled: true + podSelector: + app: keycloak-realm-import + egress: + # DNS resolution via CoreDNS + - ports: + - protocol: UDP + port: 5353 + - protocol: TCP + port: 5353 + to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-dns + # PostgreSQL — realm import writes realm data to the database + - ports: + - protocol: TCP + port: 5432 + to: + - podSelector: + matchLabels: + app: postgresql-db + # Kubernetes API server — reads secrets referenced in KeycloakRealmImport CR + - ports: + - protocol: TCP + port: 6443 + # Keycloak HTTPS API — admin API calls during realm import + - ports: + - protocol: TCP + port: 8443 + to: + - podSelector: + matchLabels: + app: keycloak + app.kubernetes.io/instance: keycloak + app.kubernetes.io/managed-by: keycloak-operator - operator: - enabled: true - # No ingress rules — operator only initiates outbound connections - egress: - # DNS resolution via CoreDNS - - ports: - - protocol: UDP - port: 5353 - - protocol: TCP - port: 5353 - to: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: openshift-dns - # Kubernetes API server — operator watches CRs and manages resources - - ports: - - protocol: TCP - port: 6443 - # Keycloak management endpoint — health checks and reconciliation - - ports: - - protocol: TCP - port: 9000 - to: - - podSelector: - matchLabels: - app: keycloak - app.kubernetes.io/instance: keycloak - app.kubernetes.io/managed-by: keycloak-operator - # Keycloak HTTPS API — admin API calls during realm/client reconciliation - - ports: - - protocol: TCP - port: 8443 - to: - - podSelector: - matchLabels: - app: keycloak - app.kubernetes.io/instance: keycloak - app.kubernetes.io/managed-by: keycloak-operator + postgresql: + enabled: true + ingress: + # Accept connections from Keycloak pods and realm import jobs + - ports: + - protocol: TCP + port: 5432 + from: + - podSelector: + matchLabels: + app: keycloak + app.kubernetes.io/instance: keycloak + app.kubernetes.io/managed-by: keycloak-operator + - podSelector: + matchLabels: + app: keycloak-realm-import + egress: + # DNS resolution via CoreDNS + - ports: + - protocol: UDP + port: 5353 + - protocol: TCP + port: 5353 + to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-dns + + operator: + enabled: true + # No ingress rules — operator only initiates outbound connections + egress: + # DNS resolution via CoreDNS + - ports: + - protocol: UDP + port: 5353 + - protocol: TCP + port: 5353 + to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-dns + # Kubernetes API server — operator watches CRs and manages resources + - ports: + - protocol: TCP + port: 6443 + # Keycloak management endpoint — health checks and reconciliation + - ports: + - protocol: TCP + port: 9000 + to: + - podSelector: + matchLabels: + app: keycloak + app.kubernetes.io/instance: keycloak + app.kubernetes.io/managed-by: keycloak-operator + # Keycloak HTTPS API — admin API calls during realm/client reconciliation + - ports: + - protocol: TCP + port: 8443 + to: + - podSelector: + matchLabels: + app: keycloak + app.kubernetes.io/instance: keycloak + app.kubernetes.io/managed-by: keycloak-operator diff --git a/values-hub.yaml b/values-hub.yaml index cae32f1e..0a061d50 100644 --- a/values-hub.yaml +++ b/values-hub.yaml @@ -494,18 +494,18 @@ clusterGroup: name: rh-keycloak namespace: keycloak-system project: hub - chart: rhbk - chartVersion: 0.0.* + path: charts/rh-keycloak extraValueFiles: - /overrides/values-keycloak-network-policy.yaml 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