From 2499cc241f04c0a2b4ed17669217959478cfcb4a Mon Sep 17 00:00:00 2001 From: Story Crater Bot <19826264+Riotpiaole@users.noreply.github.com> Date: Thu, 23 Jul 2026 09:02:19 -0700 Subject: [PATCH] fix(ddb): add PostSync Job for per-database schema permissions ROOT CAUSE: CNPG Database CR creates databases but doesn't grant schema permissions to the owner role. Bootstrap DB owner 'app' retains CREATE privilege on public schema, blocking authentik/temporal from creating tables. SECURITY FIX: Removed insecure 'GRANT TO PUBLIC' from postInitApplicationSQL. SOLUTION: PostSync Job connects as 'app' (DB owner) and grants schema permissions to named roles (authentik, temporal) in their respective databases. Runs after Database CRs reconcile, survives CNPG database recreation. Pattern: Per-database grants via PostSync, not cluster-wide PUBLIC grants. --- k8s/data/cluster/ddb-cluster.yaml | 5 - .../schemas/grant-schema-permissions-job.yaml | 113 ++++++++++++++++++ k8s/data/schemas/kustomization.yaml | 1 + 3 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 k8s/data/schemas/grant-schema-permissions-job.yaml diff --git a/k8s/data/cluster/ddb-cluster.yaml b/k8s/data/cluster/ddb-cluster.yaml index 335b269..28da2ff 100644 --- a/k8s/data/cluster/ddb-cluster.yaml +++ b/k8s/data/cluster/ddb-cluster.yaml @@ -26,11 +26,6 @@ spec: - CREATE EXTENSION IF NOT EXISTS vector; - CREATE EXTENSION IF NOT EXISTS pgcrypto; - CREATE EXTENSION IF NOT EXISTS pg_trgm; - # Universal fix: grant schema permissions to all roles - # Allows any role to create tables in public schema of any database - - GRANT ALL ON SCHEMA public TO PUBLIC; - - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO PUBLIC; - - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO PUBLIC; # Per-app login roles, passwords sourced from secrets (CNPG reconciles the # role password to match the secret). Their databases are separate Database diff --git a/k8s/data/schemas/grant-schema-permissions-job.yaml b/k8s/data/schemas/grant-schema-permissions-job.yaml new file mode 100644 index 0000000..33d345a --- /dev/null +++ b/k8s/data/schemas/grant-schema-permissions-job.yaml @@ -0,0 +1,113 @@ +# PostSync Job to grant schema permissions after Database CRs reconcile +# +# ROOT CAUSE: CNPG Database CR creates databases but doesn't grant schema +# permissions to the specified owner role. The bootstrap database owner +# (app) retains CREATE privilege on public schema, blocking other roles. +# +# SOLUTION: After Database CRs reconcile, connect as 'app' (DB owner) and +# grant ALL on schema public to each Database's owner role. +# +# This runs every sync (BeforeHookCreation policy), ensuring permissions +# survive CNPG database recreation or cluster rebuilds. +apiVersion: v1 +kind: ServiceAccount +metadata: + name: grant-schema-permissions + namespace: ddb +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: grant-schema-permissions + namespace: ddb +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get"] + - apiGroups: ["postgresql.cnpg.io"] + resources: ["databases"] + verbs: ["list", "get"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: grant-schema-permissions + namespace: ddb +subjects: + - kind: ServiceAccount + name: grant-schema-permissions + namespace: ddb +roleRef: + kind: Role + name: grant-schema-permissions + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: grant-schema-permissions + namespace: ddb + annotations: + argocd.argoproj.io/hook: PostSync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation +spec: + backoffLimit: 5 + template: + spec: + serviceAccountName: grant-schema-permissions + restartPolicy: Never + containers: + - name: grant-permissions + image: postgres:16-alpine + command: + - /bin/sh + - -c + - | + set -e + + echo "Granting schema permissions to database owners..." + + # Get app user password (owns all databases) + export PGPASSWORD=$(cat /app-secret/password) + PGHOST=ddb-cluster-rw.ddb.svc.cluster.local + PGUSER=app + + # Grant for authentik database + echo "Granting to authentik role in authentik database..." + psql -h "$PGHOST" -U "$PGUSER" -d authentik << 'SQL' + GRANT ALL ON SCHEMA public TO authentik; + GRANT ALL ON ALL TABLES IN SCHEMA public TO authentik; + GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO authentik; + ALTER DEFAULT PRIVILEGES FOR ROLE app IN SCHEMA public GRANT ALL ON TABLES TO authentik; + ALTER DEFAULT PRIVILEGES FOR ROLE app IN SCHEMA public GRANT ALL ON SEQUENCES TO authentik; + SQL + + # Grant for temporal database + echo "Granting to temporal role in temporal database..." + psql -h "$PGHOST" -U "$PGUSER" -d temporal << 'SQL' + GRANT ALL ON SCHEMA public TO temporal; + GRANT ALL ON ALL TABLES IN SCHEMA public TO temporal; + GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO temporal; + ALTER DEFAULT PRIVILEGES FOR ROLE app IN SCHEMA public GRANT ALL ON TABLES TO temporal; + ALTER DEFAULT PRIVILEGES FOR ROLE app IN SCHEMA public GRANT ALL ON SEQUENCES TO temporal; + SQL + + # Grant for temporal_visibility database + echo "Granting to temporal role in temporal_visibility database..." + psql -h "$PGHOST" -U "$PGUSER" -d temporal_visibility << 'SQL' + GRANT ALL ON SCHEMA public TO temporal; + GRANT ALL ON ALL TABLES IN SCHEMA public TO temporal; + GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO temporal; + ALTER DEFAULT PRIVILEGES FOR ROLE app IN SCHEMA public GRANT ALL ON TABLES TO temporal; + ALTER DEFAULT PRIVILEGES FOR ROLE app IN SCHEMA public GRANT ALL ON SEQUENCES TO temporal; + SQL + + echo "✅ Schema permissions granted successfully" + volumeMounts: + - name: app-secret + mountPath: /app-secret + readOnly: true + volumes: + - name: app-secret + secret: + secretName: ddb-cluster-app diff --git a/k8s/data/schemas/kustomization.yaml b/k8s/data/schemas/kustomization.yaml index 415fabf..d114433 100644 --- a/k8s/data/schemas/kustomization.yaml +++ b/k8s/data/schemas/kustomization.yaml @@ -8,6 +8,7 @@ namespace: ddb # GitOps-managed database schemas (ArgoCD wave 6). # These depend on ddb-cluster existing (bootstrap wave 0). resources: + - grant-schema-permissions-job.yaml - authentik-database.yaml - temporal-database.yaml - temporal-visibility-database.yaml