fix: Scope OBC health check to each app's own bucket
ci/woodpecker/manual/migrate-authentik-db Pipeline was successful
ci/woodpecker/manual/migrate-paperless Pipeline failed

Previously the OBC CEL health check lived on infrastructure-ceph-cluster,
which holds OBCs for every app. A slow bind in one namespace stalled
every other app that depends on infrastructure-ceph-cluster. Move each
OBC into its own app Kustomization (paperless-db, authentik-db) and
apply the health check there so each app only waits on its own bucket.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-19 00:39:52 +02:00
co-authored by Claude Opus 4.7
parent 2844dfdaae
commit 80945a38d7
9 changed files with 101 additions and 23 deletions
@@ -2,6 +2,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- woodpecker-api.sops.yaml
- migration-bucket.yaml
- postgres.yaml
- trigger-backup-job.yaml
- restore-job.yaml
@@ -0,0 +1,8 @@
apiVersion: objectbucket.io/v1alpha1
kind: ObjectBucketClaim
metadata:
name: migration-bucket
namespace: authentik
spec:
generateBucketName: migration
storageClassName: ceph-bucket
@@ -2,6 +2,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- woodpecker-api.sops.yaml
- migration-bucket.yaml
- postgres.yaml
- trigger-backup-job.yaml
- restore-db-job.yaml
@@ -0,0 +1,8 @@
apiVersion: objectbucket.io/v1alpha1
kind: ObjectBucketClaim
metadata:
name: migration-bucket
namespace: paperless
spec:
generateBucketName: migration-paperless
storageClassName: ceph-bucket
@@ -13,6 +13,12 @@ spec:
path: ./kubernetes/apps/authentik-db
prune: true
wait: true
healthCheckExprs:
- apiVersion: objectbucket.io/v1alpha1
kind: ObjectBucketClaim
current: 'status.phase == "Bound"'
inProgress: 'has(status.phase) == false || status.phase != "Bound"'
failed: 'status.phase == "Failed"'
decryption:
provider: sops
secretRef:
@@ -57,11 +57,5 @@ spec:
path: ./kubernetes/infrastructure/rook-ceph-cluster
prune: true
wait: true
healthCheckExprs:
- apiVersion: objectbucket.io/v1alpha1
kind: ObjectBucketClaim
current: 'status.phase == "Bound"'
inProgress: 'has(status.phase) == false || status.phase != "Bound"'
failed: 'status.phase == "Failed"'
dependsOn:
- name: infrastructure
@@ -13,6 +13,12 @@ spec:
path: ./kubernetes/apps/paperless-db
prune: true
wait: true
healthCheckExprs:
- apiVersion: objectbucket.io/v1alpha1
kind: ObjectBucketClaim
current: 'status.phase == "Bound"'
inProgress: 'has(status.phase) == false || status.phase != "Bound"'
failed: 'status.phase == "Failed"'
decryption:
provider: sops
secretRef:
@@ -60,20 +60,5 @@ spec:
store: migration
displayName: migration
---
apiVersion: objectbucket.io/v1alpha1
kind: ObjectBucketClaim
metadata:
name: migration-bucket
namespace: authentik
spec:
generateBucketName: migration
storageClassName: ceph-bucket
---
apiVersion: objectbucket.io/v1alpha1
kind: ObjectBucketClaim
metadata:
name: migration-bucket
namespace: paperless
spec:
generateBucketName: migration-paperless
storageClassName: ceph-bucket
# Per-app OBCs live in each app's own kustomization so their bind state
# doesn't cross-gate other apps through infrastructure-ceph-cluster.
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
set -euo pipefail
# Emergency 2FA removal for a single Authentik user.
# Deletes every authenticator device (TOTP, WebAuthn, static backup codes,
# SMS, Duo, mobile, endpoint) directly in Postgres — bypasses the API so it
# works even if you can't complete the MFA challenge to log in.
#
# Usage: ./scripts/authentik-disable-2fa.sh [username]
# Defaults to akadmin.
USER_NAME="${1:-akadmin}"
NS="authentik"
CLUSTER="ak-pg"
DEVICE_TABLES=(
authentik_stages_authenticator_totp_totpdevice
authentik_stages_authenticator_webauthn_webauthndevice
authentik_stages_authenticator_static_staticdevice
authentik_stages_authenticator_sms_smsdevice
authentik_stages_authenticator_duo_duodevice
authentik_stages_authenticator_mobile_mobiledevice
authentik_stages_authenticator_endpoint_gdtc_endpointdevice
)
# Find the current primary instance
PRIMARY=$(kubectl get cluster -n "$NS" "$CLUSTER" -o jsonpath='{.status.currentPrimary}' 2>/dev/null || true)
[[ -n "$PRIMARY" ]] || { echo "Could not find CNPG primary for $NS/$CLUSTER — is the cluster running?"; exit 1; }
echo "Primary: $PRIMARY"
# Sanity check user exists
USER_ID=$(kubectl exec -n "$NS" "$PRIMARY" -c postgres -- \
psql -At -U postgres authentik \
-c "SELECT id FROM authentik_core_user WHERE username = '$USER_NAME';" | tr -d '[:space:]')
[[ -n "$USER_ID" ]] || { echo "User '$USER_NAME' not found in authentik_core_user"; exit 1; }
echo "User id: $USER_ID"
# Build a DELETE for every device table, combined in a single transaction
SQL="BEGIN;"
for t in "${DEVICE_TABLES[@]}"; do
# Skip tables that don't exist in this Authentik version (older/newer schemas)
SQL+=" DO \$\$ BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = '$t') THEN
EXECUTE 'DELETE FROM $t WHERE user_id = $USER_ID';
END IF;
END \$\$;"
done
SQL+=" COMMIT;"
echo "Removing all MFA devices for $USER_NAME..."
kubectl exec -n "$NS" "$PRIMARY" -c postgres -- \
psql -v ON_ERROR_STOP=1 -U postgres authentik -c "$SQL"
echo
echo "Remaining device counts for $USER_NAME:"
for t in "${DEVICE_TABLES[@]}"; do
count=$(kubectl exec -n "$NS" "$PRIMARY" -c postgres -- \
psql -At -U postgres authentik \
-c "SELECT count(*) FROM information_schema.tables WHERE table_name = '$t';" | tr -d '[:space:]')
[[ "$count" == "1" ]] || continue
n=$(kubectl exec -n "$NS" "$PRIMARY" -c postgres -- \
psql -At -U postgres authentik \
-c "SELECT count(*) FROM $t WHERE user_id = $USER_ID;" | tr -d '[:space:]')
printf " %-60s %s\n" "$t" "$n"
done
echo
echo "Done. User '$USER_NAME' can now log in with just the password."
echo "Re-enroll MFA from the user's settings after logging in."