Umbrella issue #915 (D1 sub-task). Aligns the chart's post-install OIDC config Job with the canonical wp-cli flow and the bp-keycloak tenant- realm contract C1's PR #918 ships. Chart 0.2.0 ----------- - templates/oidc-config-job.yaml rewritten to use the official wordpress:cli-2.12.0-php8.3 image (manifest-list digest pinned per Inviolable Principle #4). Replaces direct PHP/SQL UPSERTs against wp_options with: * wp core install (idempotent: wp core is-installed) * wp plugin install openid-connect-generic --activate (idempotent: wp plugin is-installed) * wp option update openid_connect_generic_settings <json> * wp option update default_role * wp theme install/activate * wp option update siteurl/home Going through wp-cli (i.e. WordPress core's own PHP API) is more resilient than schema-shape-dependent INSERT statements and survives WordPress minor upgrades. - values.yaml: new canonical oidc.* block — oidc.{enabled, issuerURL, clientId, clientSecretName, defaultRole, identityKey, roleMapping, cliImage}. Default oidc.clientSecretName = "wordpress-oidc-client-secret" matches the K8s Secret bp-keycloak's PR #918 emits alongside the realm import ConfigMap (so the realm JSON's `secret` field and the Secret bytes never drift). - Legacy keycloak.{realmURL, clientID, clientSecretName} kept as a back-compat alias. _helpers.tpl folds it into oidc.* when the modern keys are at their values.yaml defaults so chart 0.1.x clusters keep reconciling. Removed in chart 0.3.0. - oidc.defaultRole=subscriber — newly auto-created SSO users land with subscriber capability (operator overrides via overlay). - Redirect URIs: the openid-connect-generic plugin's default callback is /wp-admin/admin-ajax.php?action=openid-connect-authorize when alternate_redirect_uri=0 (we set 0). bp-keycloak (PR #918) registers the same URL plus /wp-login.php and a /* wildcard, so the client's allowed-redirect-URI list aligns with what the plugin actually issues. Orchestrator emit ----------------- - products/catalyst/bootstrap/api/internal/handler/sme_tenant_gitops.go smeTenantBPWordPress now emits the canonical oidc.* block AND the legacy keycloak.* alias (for chart 0.1.x clusters mid-upgrade). Tests ----- - chart/tests/oidc-config.sh — 7 helm-template assertions: 1. Canonical oidc.* render produces a Job with the required wp-cli command flow + wordpress:cli-2.12.0-php8.3 image. 2. Legacy keycloak.* fold path (chart 0.1.x compat). 3. oidc.enabled=false short-circuits the Job. 4. alternate_redirect_uri=0 (so plugin URL matches the realm- registered redirect URI from PR #918). 5. defaultRole rendered + propagated. 6. Render YAML is parseable and contains all required kinds. 7. wp-content PVC mounted in the Job (so pg4wp's db.php drop-in loads — failure here would silently fall back to mysqli). - internal/handler/sme_tenant_test.go: * TestRenderSMETenantOverlay_WordPressEmitsOIDC — pins the canonical oidc.* block + legacy keycloak.* alias the orchestrator emits for the alice@omantel test fixture. * TestRenderSMETenantOverlay_WordPressOIDC_BYOMode — BYO domain mode renders wordpress.<byo-domain> as the ingress host. Verification ------------ - helm lint clean - helm template smoke green for: oidc.* canonical, keycloak.* legacy fold, oidc.enabled=false short-circuit - chart/tests/oidc-config.sh: 7/7 PASS - chart/tests/observability-toggle.sh: 2/2 PASS (regression) - go test ./internal/handler/ -run "SMETenant|TestRenderSME": all green (TestAuthHandover_HappyPath failure is pre-existing on main, unrelated to this change) Closes (D1 sub-task) of #915. Co-authored-by: hatiyildiz <hatice@openova.io> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
286 lines
14 KiB
YAML
286 lines
14 KiB
YAML
{{- if and .Values.wordpress.enabled .Values.oidc.enabled }}
|
|
{{- /*
|
|
Helm post-install / post-upgrade Job that:
|
|
|
|
1. Waits for the WordPress Pod to be reachable (its initContainers
|
|
have seeded /var/www/html/wp-content with pg4wp + the
|
|
openid-connect-generic plugin from the wordpress.org plugin
|
|
registry — see deployment.yaml).
|
|
2. Runs `wp core install` (idempotent — `wp core is-installed` first).
|
|
This populates the wp_* tables via WordPress's own install code,
|
|
which goes through pg4wp and is therefore Postgres-safe.
|
|
3. Runs `wp plugin install openid-connect-generic --activate` —
|
|
idempotent: skips when the plugin is already present.
|
|
4. Runs `wp option update openid_connect_generic_settings <json>`
|
|
with the per-tenant Keycloak realm + client + secret from the
|
|
chart values. Idempotent — overwrites with the same values on
|
|
`helm upgrade`.
|
|
5. Runs `wp theme activate <defaultTheme>` if the theme is
|
|
installed; otherwise installs + activates it.
|
|
|
|
Why wp-cli (not direct PHP/SQL writes)
|
|
──────────────────────────────────────
|
|
- WordPress's table schema, option-row serialisation format, and the
|
|
`active_plugins` / `template` / `stylesheet` semantics evolve
|
|
between minor releases. wp-cli sits ON TOP of WordPress core's
|
|
public PHP API and is the only stable contract.
|
|
- pg4wp's drop-in handles MySQL→Postgres translation transparently
|
|
for any code that goes through `wpdb` — wp-cli uses `wpdb`, so it
|
|
Just Works against bp-cnpg.
|
|
- The official `wordpress:cli-*` image bundles the same WordPress
|
|
core layout as the runtime image, so the wp-content PVC mount is
|
|
byte-for-byte interchangeable.
|
|
|
|
Why post-install / weight 10
|
|
────────────────────────────
|
|
- weight 5: database-secret-sync-job populates `wordpress-database-
|
|
secret` from the CNPG-emitted `<cluster>-app` Secret.
|
|
- weight 10: this Job — DB Secret is now real, WordPress Pod is up,
|
|
PVC initContainers seeded wp-content + db.php drop-in.
|
|
- weight 15: admin-user-job pre-seeds the SME admin's wp_users row.
|
|
|
|
Canonical seam — see umbrella issue #915 (D1 sub-task) and the
|
|
matching tenant-realm Keycloak client registration in
|
|
platform/keycloak/chart/templates/configmap-tenant-realm.yaml
|
|
(PR #918) which emits `wordpress-oidc-client-secret` carrying the
|
|
same client_secret bytes the realm JSON registers.
|
|
*/}}
|
|
{{- $ns := .Release.Namespace }}
|
|
{{- $issuer := include "bp-wordpress-tenant.oidcIssuerURL" . }}
|
|
{{- $clientId := include "bp-wordpress-tenant.oidcClientId" . }}
|
|
{{- $secretName := include "bp-wordpress-tenant.oidcClientSecretName" . }}
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: {{ include "bp-wordpress-tenant.fullname" . }}-oidc-config
|
|
namespace: {{ $ns }}
|
|
labels:
|
|
{{- include "bp-wordpress-tenant.labels" . | nindent 4 }}
|
|
catalyst.openova.io/component: wordpress-oidc-config
|
|
annotations:
|
|
"helm.sh/hook": "post-install,post-upgrade"
|
|
"helm.sh/hook-weight": "10"
|
|
"helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded"
|
|
spec:
|
|
backoffLimit: 6
|
|
ttlSecondsAfterFinished: 600
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/name: wordpress-oidc-config
|
|
catalyst.openova.io/blueprint: bp-wordpress-tenant
|
|
spec:
|
|
restartPolicy: OnFailure
|
|
securityContext:
|
|
{{- toYaml .Values.wordpress.podSecurityContext | nindent 8 }}
|
|
containers:
|
|
- name: oidc-config
|
|
image: {{ include "bp-wordpress-tenant.wpCliImage" . | quote }}
|
|
imagePullPolicy: {{ .Values.oidc.cliImage.pullPolicy }}
|
|
securityContext:
|
|
{{- toYaml .Values.wordpress.containerSecurityContext | nindent 12 }}
|
|
env:
|
|
# WordPress DB connection — wp-cli reads these via the
|
|
# wp-config.php we materialise inline below. Identical
|
|
# variable names to the runtime container so the same
|
|
# values.yaml block drives both.
|
|
- name: WORDPRESS_DB_HOST
|
|
value: {{ include "bp-wordpress-tenant.cnpgRwHost" . | quote }}
|
|
- name: WORDPRESS_DB_NAME
|
|
value: {{ .Values.database.cluster.database | default "wordpress" | quote }}
|
|
- name: WORDPRESS_DB_USER
|
|
value: {{ .Values.database.cluster.owner | default "wordpress" | quote }}
|
|
- name: WORDPRESS_DB_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: {{ .Values.database.secretName }}
|
|
key: password
|
|
- name: WP_SITE_URL
|
|
value: "https://{{ include "bp-wordpress-tenant.ingressHost" . }}"
|
|
- name: WP_DEFAULT_THEME
|
|
value: {{ .Values.defaultTheme | quote }}
|
|
# OIDC inputs — folded from oidc.* / keycloak.* (legacy).
|
|
- name: KC_ISSUER_URL
|
|
value: {{ $issuer | quote }}
|
|
- name: KC_CLIENT_ID
|
|
value: {{ $clientId | quote }}
|
|
- name: KC_CLIENT_SECRET
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: {{ $secretName }}
|
|
key: client-secret
|
|
- name: OIDC_DEFAULT_ROLE
|
|
value: {{ .Values.oidc.defaultRole | quote }}
|
|
- name: OIDC_IDENTITY_KEY
|
|
value: {{ .Values.oidc.identityKey | quote }}
|
|
command:
|
|
- /bin/sh
|
|
- -c
|
|
- |
|
|
set -eu
|
|
# The wordpress:cli image's default WORKDIR is /var/www/html;
|
|
# wp-cli expects to find wp-config.php and the WordPress
|
|
# core files there. We materialise wp-config.php inline so
|
|
# `wp` boots WordPress and chain-loads pg4wp from the
|
|
# mounted /var/www/html/wp-content PVC (db.php drop-in).
|
|
cd /var/www/html
|
|
|
|
# 1. Materialise wp-config.php — same contract as the
|
|
# runtime container's WORDPRESS_CONFIG_EXTRA so the
|
|
# site URL + reverse-proxy headers are consistent.
|
|
if [ ! -f wp-config.php ]; then
|
|
cat > wp-config.php <<'PHPCFG'
|
|
<?php
|
|
define('DB_NAME', getenv('WORDPRESS_DB_NAME'));
|
|
define('DB_USER', getenv('WORDPRESS_DB_USER'));
|
|
define('DB_PASSWORD', getenv('WORDPRESS_DB_PASSWORD'));
|
|
define('DB_HOST', getenv('WORDPRESS_DB_HOST'));
|
|
define('DB_CHARSET', 'utf8');
|
|
define('DB_COLLATE', '');
|
|
$table_prefix = 'wp_';
|
|
define('WP_DEBUG', false);
|
|
define('WP_HOME', getenv('WP_SITE_URL'));
|
|
define('WP_SITEURL', getenv('WP_SITE_URL'));
|
|
if (!defined('ABSPATH')) {
|
|
define('ABSPATH', __DIR__ . '/');
|
|
}
|
|
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
|
|
$_SERVER['HTTPS'] = 'on';
|
|
}
|
|
require_once ABSPATH . 'wp-settings.php';
|
|
PHPCFG
|
|
echo "[oidc-config] wrote wp-config.php";
|
|
else
|
|
echo "[oidc-config] wp-config.php already present";
|
|
fi
|
|
|
|
# 2. Wait for the database to be reachable. wp-cli
|
|
# `wp db check` returns non-zero when the DB isn't up;
|
|
# we poll for up to 5 minutes. pg4wp's drop-in will
|
|
# automatically run from wp-content/db.php, having been
|
|
# seeded by the wp-plugin-install initContainer.
|
|
for i in $(seq 1 60); do
|
|
if wp db check --allow-root >/dev/null 2>&1; then
|
|
echo "[oidc-config] database reachable"
|
|
break
|
|
fi
|
|
if [ "$i" = "60" ]; then
|
|
echo "[oidc-config] FATAL: database not reachable after 5 min" >&2
|
|
exit 1
|
|
fi
|
|
sleep 5
|
|
done
|
|
|
|
# 3. Run `wp core install` if WordPress isn't installed
|
|
# yet. Idempotent — `wp core is-installed` exits 0
|
|
# when the install row in wp_options is present.
|
|
if ! wp core is-installed --allow-root >/dev/null 2>&1; then
|
|
echo "[oidc-config] wp_options/install row absent — running wp core install"
|
|
# Generate a strong throwaway admin password — the SME
|
|
# admin will only ever log in via Keycloak SSO; this
|
|
# password is never used. NEVER printed (Inviolable
|
|
# Principle #10).
|
|
ADMIN_PASS="$(head -c 48 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 32)"
|
|
wp core install \
|
|
--allow-root \
|
|
--url="${WP_SITE_URL}" \
|
|
--title="WordPress" \
|
|
--admin_user="wp-bootstrap" \
|
|
--admin_password="${ADMIN_PASS}" \
|
|
--admin_email="bootstrap@${WP_SITE_URL##https://}" \
|
|
--skip-email
|
|
unset ADMIN_PASS
|
|
echo "[oidc-config] wp core install complete"
|
|
else
|
|
echo "[oidc-config] WordPress already installed — skipping core install"
|
|
fi
|
|
|
|
# 4. Install + activate openid-connect-generic. wp-cli
|
|
# deduplicates: --activate is a no-op when already
|
|
# active; `wp plugin is-installed` returns 0 when the
|
|
# plugin directory exists on the PVC (seeded by the
|
|
# wp-plugin-install initContainer in deployment.yaml,
|
|
# so wp-cli skips the wordpress.org download).
|
|
if wp plugin is-installed openid-connect-generic --allow-root >/dev/null 2>&1; then
|
|
echo "[oidc-config] openid-connect-generic already installed"
|
|
wp plugin activate openid-connect-generic --allow-root >/dev/null
|
|
else
|
|
echo "[oidc-config] installing openid-connect-generic from wordpress.org"
|
|
wp plugin install openid-connect-generic --activate --allow-root
|
|
fi
|
|
echo "[oidc-config] openid-connect-generic activated"
|
|
|
|
# 5. Compose the OIDC settings option row. Fields match
|
|
# the openid-connect-generic plugin's option schema —
|
|
# see openid-connect-generic-settings.php upstream.
|
|
# `endpoint_*` URLs are derived from the issuer URL
|
|
# (Keycloak's standard /protocol/openid-connect/* paths).
|
|
ISSUER="${KC_ISSUER_URL%/}"
|
|
wp option update openid_connect_generic_settings --format=json --allow-root <<EOF
|
|
{
|
|
"login_type": "auto",
|
|
"client_id": "${KC_CLIENT_ID}",
|
|
"client_secret": "${KC_CLIENT_SECRET}",
|
|
"scope": "openid email profile",
|
|
"endpoint_login": "${ISSUER}/protocol/openid-connect/auth",
|
|
"endpoint_userinfo": "${ISSUER}/protocol/openid-connect/userinfo",
|
|
"endpoint_token": "${ISSUER}/protocol/openid-connect/token",
|
|
"endpoint_end_session": "${ISSUER}/protocol/openid-connect/logout",
|
|
"identity_key": "${OIDC_IDENTITY_KEY}",
|
|
"no_sslverify": 0,
|
|
"http_request_timeout": 5,
|
|
"enforce_privacy": 0,
|
|
"alternate_redirect_uri": 0,
|
|
"token_refresh_enable": 1,
|
|
"link_existing_users": 1,
|
|
"create_if_does_not_exist": 1,
|
|
"redirect_user_back": 1,
|
|
"redirect_on_logout": 1,
|
|
"acl_enabled": 0,
|
|
"enable_logging": 0,
|
|
"log_limit": 1000
|
|
}
|
|
EOF
|
|
echo "[oidc-config] openid_connect_generic_settings written"
|
|
|
|
# 6. Default WordPress role for newly-created SSO users —
|
|
# plugin reads this from the standard `default_role`
|
|
# option.
|
|
wp option update default_role "${OIDC_DEFAULT_ROLE}" --allow-root >/dev/null
|
|
echo "[oidc-config] default_role set to ${OIDC_DEFAULT_ROLE}"
|
|
|
|
# 7. Theme activation — install + activate idempotently.
|
|
if wp theme is-installed "${WP_DEFAULT_THEME}" --allow-root >/dev/null 2>&1; then
|
|
wp theme activate "${WP_DEFAULT_THEME}" --allow-root >/dev/null
|
|
else
|
|
wp theme install "${WP_DEFAULT_THEME}" --activate --allow-root
|
|
fi
|
|
echo "[oidc-config] theme ${WP_DEFAULT_THEME} active"
|
|
|
|
# 8. siteurl + home — overwrite the WP install defaults so
|
|
# links resolve through the ingress host.
|
|
wp option update siteurl "${WP_SITE_URL}" --allow-root >/dev/null
|
|
wp option update home "${WP_SITE_URL}" --allow-root >/dev/null
|
|
echo "[oidc-config] siteurl/home set to ${WP_SITE_URL}"
|
|
|
|
echo "[oidc-config] all OIDC bootstrap steps complete"
|
|
volumeMounts:
|
|
- name: wp-content
|
|
mountPath: /var/www/html/wp-content
|
|
resources:
|
|
requests:
|
|
cpu: 50m
|
|
memory: 128Mi
|
|
limits:
|
|
cpu: 500m
|
|
memory: 256Mi
|
|
volumes:
|
|
- name: wp-content
|
|
{{- if .Values.persistence.wpContent.enabled }}
|
|
persistentVolumeClaim:
|
|
claimName: {{ include "bp-wordpress-tenant.fullname" . }}-wp-content
|
|
{{- else }}
|
|
emptyDir: {}
|
|
{{- end }}
|
|
{{- end }}
|