From 6d25d088d76b73ae184b57b8a44ae9e177b39b46 Mon Sep 17 00:00:00 2001 From: Bernd Worsch Date: Fri, 20 Mar 2026 02:57:41 +0000 Subject: [PATCH] =?UTF-8?q?feat(sso-mfa):=20T02/T03=20live=20apply=20?= =?UTF-8?q?=E2=80=94=20age-encrypted=20secrets,=20CNPG=20cluster=20(NK-WP-?= =?UTF-8?q?0001-T02/T03)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add encrypt-secrets.sh / decrypt-secrets.sh: age-based secrets workflow replaces KeePassXC dependency; encrypted .env.age files committed to repo - Add bootstrap/secrets.enc/: all component secrets encrypted to age pubkey - Fix .gitignore: allow secrets.enc/**/*.age while blocking plaintext - Fix verify-t02.sh: update netpol names for Authelia+LLDAP+KeyCape stack - Fix verify-t03.sh: remove keycloak_db/role checks; fix ((PASS++)) set-e bug - Update postgresql/cluster.yaml: drop keycloak_db, bootstrap privacyidea_db only - Update postgresql/create-secrets.sh: remove keycloak secret - Fix netpol-databases.yaml: add port 8000 for CNPG instance manager HTTP API - T02 COMPLETE: namespaces, network policies, cert-manager issuers applied - T03 COMPLETE: CNPG operator installed, net-kingdom-pg cluster healthy Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 1 + sso-mfa/bootstrap/decrypt-secrets.sh | 62 ++++++++++++++ sso-mfa/bootstrap/encrypt-secrets.sh | 79 ++++++++++++++++++ .../secrets.enc/authelia/secrets.env.age | Bin 0 -> 1507 bytes .../secrets.enc/breakglass/secrets.env.age | Bin 0 -> 491 bytes .../secrets.enc/keycape/secrets.env.age | Bin 0 -> 588 bytes .../secrets.enc/lldap/secrets.env.age | 7 ++ .../secrets.enc/postgres/secrets.env.age | Bin 0 -> 594 bytes .../secrets.enc/privacyidea/secrets.env.age | Bin 0 -> 1143 bytes .../network-policies/netpol-databases.yaml | 2 + sso-mfa/k8s/postgresql/cluster.yaml | 31 ++----- sso-mfa/k8s/postgresql/create-secrets.sh | 30 ++----- sso-mfa/k8s/verify-t02.sh | 5 +- sso-mfa/k8s/verify-t03.sh | 30 +++---- 14 files changed, 181 insertions(+), 66 deletions(-) create mode 100755 sso-mfa/bootstrap/decrypt-secrets.sh create mode 100755 sso-mfa/bootstrap/encrypt-secrets.sh create mode 100644 sso-mfa/bootstrap/secrets.enc/authelia/secrets.env.age create mode 100644 sso-mfa/bootstrap/secrets.enc/breakglass/secrets.env.age create mode 100644 sso-mfa/bootstrap/secrets.enc/keycape/secrets.env.age create mode 100644 sso-mfa/bootstrap/secrets.enc/lldap/secrets.env.age create mode 100644 sso-mfa/bootstrap/secrets.enc/postgres/secrets.env.age create mode 100644 sso-mfa/bootstrap/secrets.enc/privacyidea/secrets.env.age diff --git a/.gitignore b/.gitignore index 8ba0b2d..669ac0d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # ── Secrets (never commit) ───────────────────────────────────────────────────── sso-mfa/bootstrap/secrets/ *.age +!sso-mfa/bootstrap/secrets.enc/**/*.age *.kdbx # ---> Python diff --git a/sso-mfa/bootstrap/decrypt-secrets.sh b/sso-mfa/bootstrap/decrypt-secrets.sh new file mode 100755 index 0000000..84774a5 --- /dev/null +++ b/sso-mfa/bootstrap/decrypt-secrets.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# decrypt-secrets.sh — decrypt secrets.enc/ to secrets/ using age +# +# Usage: +# ./decrypt-secrets.sh [OUTPUT_DIR] [AGE_KEY_FILE] +# +# OUTPUT_DIR where to write plaintext secrets (default: ./secrets) +# AGE_KEY_FILE age private key file (default: ~/.config/net-kingdom/age.key) +# +# Decrypts all *.age files in secrets.enc/ to OUTPUT_DIR for use by +# create-secrets.sh scripts. Shred OUTPUT_DIR when done: +# find secrets/ -type f -exec shred -u {} \; && rm -rf secrets/ +# +# The age key must be present on the machine. Keep it outside the repo: +# ~/.config/net-kingdom/age.key + +set -euo pipefail + +OUTPUT_DIR="${1:-./secrets}" +AGE_KEY="${2:-$HOME/.config/net-kingdom/age.key}" + +ENC_DIR="$(dirname "$OUTPUT_DIR")/secrets.enc" + +if [[ ! -d "$ENC_DIR" ]]; then + echo "ERROR: encrypted secrets directory not found: $ENC_DIR" >&2 + echo "Expected secrets.enc/ next to the output directory." >&2 + exit 1 +fi + +if [[ ! -f "$AGE_KEY" ]]; then + echo "ERROR: age key not found: $AGE_KEY" >&2 + echo "Copy your age key to $AGE_KEY or pass the path as the second argument." >&2 + exit 1 +fi + +if [[ -e "$OUTPUT_DIR" ]]; then + echo "ERROR: $OUTPUT_DIR already exists. Remove it first or choose a different path." >&2 + exit 1 +fi + +echo "Decrypting $ENC_DIR → $OUTPUT_DIR/" +echo "" + +count=0 +for component_dir in "$ENC_DIR"/*/; do + component=$(basename "$component_dir") + mkdir -p "$OUTPUT_DIR/$component" + for f in "$component_dir"*.age; do + [[ -f "$f" ]] || continue + fname=$(basename "${f%.age}") + out="$OUTPUT_DIR/$component/$fname" + age -d -i "$AGE_KEY" -o "$out" "$f" + echo " decrypted: secrets.enc/$component/$(basename "$f") → $component/$fname" + count=$((count + 1)) + done +done + +echo "" +echo "$count file(s) decrypted to $OUTPUT_DIR/" +echo "" +echo "Use create-secrets.sh scripts, then shred:" +echo " find $OUTPUT_DIR -type f -exec shred -u {} \\; && rm -rf $OUTPUT_DIR" diff --git a/sso-mfa/bootstrap/encrypt-secrets.sh b/sso-mfa/bootstrap/encrypt-secrets.sh new file mode 100755 index 0000000..df950f2 --- /dev/null +++ b/sso-mfa/bootstrap/encrypt-secrets.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# encrypt-secrets.sh — encrypt secrets/ directory to secrets.enc/ using age +# +# Usage: +# ./encrypt-secrets.sh [SECRETS_DIR] [AGE_KEY_FILE] +# +# SECRETS_DIR plaintext secrets directory (default: ./secrets) +# AGE_KEY_FILE age private key file (default: ~/.config/net-kingdom/age.key) +# +# Reads the public key from the age key file and encrypts each *.env file +# (and pi.enc if present) to secrets.enc//.age. +# +# After a successful encrypt, shreds the plaintext secrets directory unless +# --no-shred is passed. +# +# Run after gen-secrets.sh to store secrets safely in the repo. +# Commit secrets.enc/ to git. + +set -euo pipefail + +SECRETS_DIR="${1:-./secrets}" +AGE_KEY="${2:-$HOME/.config/net-kingdom/age.key}" +NO_SHRED=false +for arg in "$@"; do [[ "$arg" == "--no-shred" ]] && NO_SHRED=true; done + +if [[ ! -d "$SECRETS_DIR" ]]; then + echo "ERROR: secrets directory not found: $SECRETS_DIR" >&2 + echo "Run gen-secrets.sh first." >&2 + exit 1 +fi + +if [[ ! -f "$AGE_KEY" ]]; then + echo "ERROR: age key not found: $AGE_KEY" >&2 + echo "Generate with: age-keygen -o $AGE_KEY" >&2 + exit 1 +fi + +# Extract public key from the private key file +PUBKEY=$(grep 'public key:' "$AGE_KEY" | awk '{print $NF}') +if [[ -z "$PUBKEY" ]]; then + echo "ERROR: could not read public key from $AGE_KEY" >&2 + exit 1 +fi + +ENC_DIR="$(dirname "$SECRETS_DIR")/secrets.enc" +mkdir -p "$ENC_DIR" + +echo "Encrypting secrets → $ENC_DIR/" +echo "Recipient: $PUBKEY" +echo "" + +count=0 +for component_dir in "$SECRETS_DIR"/*/; do + component=$(basename "$component_dir") + mkdir -p "$ENC_DIR/$component" + for f in "$component_dir"*; do + [[ -f "$f" ]] || continue + fname=$(basename "$f") + out="$ENC_DIR/$component/$fname.age" + age -r "$PUBKEY" -o "$out" "$f" + echo " encrypted: $component/$fname → secrets.enc/$component/$fname.age" + count=$((count + 1)) + done +done + +echo "" +echo "$count file(s) encrypted to $ENC_DIR/" +echo "" + +if [[ "$NO_SHRED" == false ]]; then + echo "Shredding plaintext secrets..." + find "$SECRETS_DIR" -type f -exec shred -u {} \; + rm -rf "$SECRETS_DIR" + echo "Done. Plaintext secrets shredded." +else + echo "(--no-shred: plaintext kept at $SECRETS_DIR)" +fi +echo "" +echo "Next: commit secrets.enc/ to git." diff --git a/sso-mfa/bootstrap/secrets.enc/authelia/secrets.env.age b/sso-mfa/bootstrap/secrets.enc/authelia/secrets.env.age new file mode 100644 index 0000000000000000000000000000000000000000..90cb7633d746d7982ab642ea094df8ef79c37119 GIT binary patch literal 1507 zcmV<91swWeXJsvAZewzJaCB*JZZ2~R>j z1m?2`@VllnJ#0QQx)QAm0=E;~aHvcmvu{*=`$S_5b^;SBuP#Kk%S=9O{S;x(DE<#` zof$_xsRK+B=`$W$?OdZ(vvokAPvG(*I)8+%J-w_@?e#DWL5RyAed(l^FF-59eT?M4 zWI)Mk54I>Dvaj!KFRt#m0O7gYH>oHVj+!Nx(2#4>VdL;blgZj{oTN$TLBJb6`_OdC z12u$q0#s&q6{7uc%@(cae@V)1EQaZsw;&y1FR-IHd46WY0se=ax%rp^pK~PUM#&m+ z&)FpIpAHvbPyHoye+Nj;0C9{Nd_W>8t}4pkqCc5eQuybOeS%y0(hW7-xO2saqZ(6C z&ThLU%CYFlsr;BX)yKflv9a-*?0M|2j*z(3C4EP2LJE|4>{5Jp2+giGl|0jL&7?KA zZNayC%t8{UH?E1oBpioPyF=ktZ}*#dcG&mon^nqf2-0VIxmlvxF{L(05C1QsB9s{2N_Moh{~yz0Eqpd2?Nj5nutp*l$255!%09N}h5Lk23-!j`jck|4{O^7OH+Z^e zp$XExeo>@C+2VB3!xSUgAON8MH<+6}tV;RE28OKd2f9)T=?7D&iaAdo>6P5>U{7_VQQ^H)Sh4IWQ0o?xxQSS#Le3Ar+HtMvS zIF5>$MaRi=KQw8 z2gpJ^lV`fm>`J}T>zQB0_9&}pzCV2gȗmJ-chge2tctg%@@9X?4cpPe?E7UN6nJWC2e<9m{O%yj$&KZMzH5# zhA@)?)B|JB4=$kF*$;|)sgj9uAjzunL}KVc`9cjYJ2OA=8jveHUu?r09yR>>?K6=4 zus-4zkT$W%Q1|EPY=WXqh|f*Q=}_a{eO4~g)hdhJ?~FVF<+~!K+|)XOw#6y?qgKd= zlerl>Nrx=N?b$e^v&_r}fOsgWXh8}(4{JzQ1klj5bDz!sN$sG+BMhVivuwR|BmyiG zzcAhCsIK-!8zi#uD!WyL z4Hi$h)sm2MZ;`=-G*PWxc|(A;1D#Dk*=Fc)e2U;La(MQ4MdH52XX|P5*K}%EN)>-k zsMq!lXok}WRn&>TXx_pXjiT-?k0EY#<3wv-m3!Y@mVXIyN}gRCC}&GZE4cDPhu2Zz zmp%EF-mVsVHFN>wm7PH;6< zK}tAHMrKK7LTOY&bxCb`c54dMev_4aSr!?;p?F2rSGF-+ick5J!M7hD;*`bAXrE2etAW z$<$Nmp8QL8l!Ee|yBO)hLd4z*=7KlSfK;<5!kK`4Vj;i#iE8-^HtQ1>Wvne6L*( zp=da0^(p7;hI;fnL)QRHPGxdhZEI(G zLToTZO-OKfL1a=xb7*R0SSvVAY*A7-b8JdiGgS&_aaT%2crP$QEi&sij%Y)>wXcCw+1V7-SZe7_xY%6n7#SVVal15Dv3y|tc$?*K?L4=SW6CD3Al#60eM_0X0Y74O{lpcs4e+WiiiBu776s7(^>Mk^ zN#JG&Nf1?~1X~b4TrplYX@rV_Y X25519 yR2D3J78/vw1ohcdXCLy5IOoIuG+FtRs7Eiswk3gKyo +c9axBYTsFS4Gqb3Zdv5Gtk+/yEtKNH21iFLU1U3mxNs +--- Kc/0n9icRSyEEcAHJJdx2Vcv5CgjLucU8FdZArV3C2U +9eYdT -GiΑ%0x y=0O֫豃־Q"-[gߋ3eV3wt1C$rj2\z=IW7>=K8JUTGl"bv{g3@-:Ƚ2;PrUHA-띰Zx.x}@EM+H +Ӛ$;<>ie1xtCU4҂Oz +O{qԏqE١?S]2 ^1}RXKl Iz ,E'(Ӝ1ZS&=ZWퟲL O˜~:l!am+Ÿ혲6TlǢE.11Plrz hmGtv(, \ No newline at end of file diff --git a/sso-mfa/bootstrap/secrets.enc/postgres/secrets.env.age b/sso-mfa/bootstrap/secrets.enc/postgres/secrets.env.age new file mode 100644 index 0000000000000000000000000000000000000000..dd0a3c4f6439f3e6add1c064fb2d5fde3318960d GIT binary patch literal 594 zcmV-Y0|rc)a@yX9KVmYDW;-wet1{Am?Qp+Mr@^6r$HQ_g_hsJ>B6c-yJ6{Qik&33`b)@9_4GAfSPKfQKpMMbcDpY%nx7v+)@NNU|2OX;A z>=0Ag3@ik&zs^z;jKD%_6~*Iszd?T7KdBux-gOopCg_`r^S_Ko=gTnkY>Khcn)Kz- gdw|~!Hp+IGcm;y(0Z$`#XJsvAZewzJaCB*JZZ2nMQgUf%dQ4VXNkc?rZ!bbsaX4d9 zW_dDeF;i}CV^exmQZ;Z*S4uQDbZ-hREiE8ZOJ{U7HBd)SQ%QDrGC65%Lux}gV{Jls zFl2E-b4fQhX+?D}Id?)$WpfIT9|f@|NEo7ZFPpE2NO^7Q4atXrGbzE%#gr}(I+d@@o!EWQ4(A^N&SSQIQW~dEnERN zMo_VIH^A8Ao<{LEy9>$q-XZvyiyIcFc`nQsP1R_p2TiAf?h|n$I}}m_luNtIc0>aO ze3hO^wplroY5jm^0dSFriZ1HlKz7n1Z8^(R<5@tP)QuK|(JJd$Ug~MA^dbQ+Tf0jd zc~Nir(``0%NE0Ax4{S`%B=1~GG3EJBFo79exxb>Bqv^dE%ib|rt6DFIbg9eP0VhpG zSV~5qfgnc$Eirq=uAq2ZX;!=2?AZ2$V(8HwZh&o?A=F2ak(f3%Rpk_R&h2rnsst3h zQPi`*Kxr3e-^=(C+Lu2V9?V6|bH92(=}ra5A|Jpr<<@Mg@P`QBYZ<`wF`0CMQVE#Z z!D6?a9^qn~lx~&G17PZ8%U(U@@L+lNmJ2?NK%a0$N+1p|*}1HVR-LWgAGMB4upnOP z41p9h^O!)IEUMS--mM6Tr_tVrau{JVmNNfI7&8fEDJT)Q$v zZggaX9D-6$2j2}Gp%jDR;qE&0H>^U0K|C%=78W1?(KYMuONm}Qs5|CaxmO_h>Ib>I zZ0?D#G zIHv~rAVK)*=YhO7ftczG0|mRzm0RZ^D)kKE&gk(NCtynWkLNx3mQ_sSeFT%?{_iMX z_D}+XlC9KH%Ffh|>;mVhJQaM^Oi~Ic>!WmYmAJNnl}gH6L6&JTq3$3v=aN@5p3pgD z9)@_kOZ`;4uSAZXdmTbq73IS^jpgdQJCkg6>x}OX!rv6VP?QD{0MWaRvO2420pwfY z( is the output directory produced by sso-mfa/bootstrap/gen-secrets.sh # (default: ../../bootstrap/secrets). # -# Creates two K8s Secrets in the databases namespace: -# net-kingdom-pg-keycloak-app — keycloak DB credentials +# Creates one K8s Secret in the databases namespace: # net-kingdom-pg-privacyidea-app — privacyIDEA DB credentials # +# Note: net-kingdom-pg-keycloak-app removed — Keycloak replaced by Authelia+LLDAP+KeyCape (T05). +# # These secrets must exist before applying cluster.yaml. # Re-run this script whenever you rotate passwords in KeePassXC / gen-secrets.sh. @@ -24,36 +25,23 @@ if [[ ! -d "$SECRETS_DIR" ]]; then exit 1 fi -PG_SECRETS="$SECRETS_DIR/postgres/secrets.env" PI_SECRETS="$SECRETS_DIR/privacyidea/secrets.env" -if [[ ! -f "$PG_SECRETS" ]]; then - echo "ERROR: $PG_SECRETS not found" >&2 - exit 1 -fi if [[ ! -f "$PI_SECRETS" ]]; then echo "ERROR: $PI_SECRETS not found" >&2 exit 1 fi -# Source the generated env files (they contain KEY=VALUE pairs, no export) +# Source the generated env file (KEY=VALUE pairs, no export) # Use a subshell to avoid polluting the current environment. -PG_KC_PASS=$(bash -c "source $PG_SECRETS 2>/dev/null; echo \$PG_KEYCLOAK_PASSWORD") PI_DB_PASS=$(bash -c "source $PI_SECRETS 2>/dev/null; echo \$PI_DB_PASSWORD") -if [[ -z "$PG_KC_PASS" || -z "$PI_DB_PASS" ]]; then - echo "ERROR: could not read passwords from secrets files." >&2 - echo "Check that gen-secrets.sh ran successfully and the files are intact." >&2 +if [[ -z "$PI_DB_PASS" ]]; then + echo "ERROR: could not read PI_DB_PASSWORD from $PI_SECRETS" >&2 + echo "Check that gen-secrets.sh ran successfully and the file is intact." >&2 exit 1 fi -echo "Creating K8s Secret: net-kingdom-pg-keycloak-app" -kubectl create secret generic net-kingdom-pg-keycloak-app \ - --namespace=databases \ - --from-literal=username=keycloak \ - --from-literal=password="$PG_KC_PASS" \ - --dry-run=client -o yaml | kubectl apply -f - - echo "Creating K8s Secret: net-kingdom-pg-privacyidea-app" kubectl create secret generic net-kingdom-pg-privacyidea-app \ --namespace=databases \ @@ -62,8 +50,8 @@ kubectl create secret generic net-kingdom-pg-privacyidea-app \ --dry-run=client -o yaml | kubectl apply -f - echo "" -echo "Done. Secrets created in namespace: databases" +echo "Done. Secret created in namespace: databases" echo "" echo "Verify:" echo " kubectl get secrets -n databases" -echo " kubectl describe secret net-kingdom-pg-keycloak-app -n databases" +echo " kubectl describe secret net-kingdom-pg-privacyidea-app -n databases" diff --git a/sso-mfa/k8s/verify-t02.sh b/sso-mfa/k8s/verify-t02.sh index 0205eb0..063b9e7 100755 --- a/sso-mfa/k8s/verify-t02.sh +++ b/sso-mfa/k8s/verify-t02.sh @@ -62,9 +62,8 @@ for ns in sso mfa databases; do check "allow-egress-dns in $ns" \ $KUBECTL get networkpolicy allow-egress-dns -n "$ns" done -check "allow-ingress-from-traefik in sso" $KUBECTL get networkpolicy allow-ingress-from-traefik -n sso -check "allow-egress-to-postgres in sso" $KUBECTL get networkpolicy allow-egress-to-postgres -n sso -check "allow-egress-to-privacyidea in sso" $KUBECTL get networkpolicy allow-egress-to-privacyidea -n sso +check "allow-traefik-to-keycape in sso" $KUBECTL get networkpolicy allow-traefik-to-keycape -n sso +check "allow-keycape-egress-to-privacyidea in sso" $KUBECTL get networkpolicy allow-keycape-egress-to-privacyidea -n sso check "allow-ingress-from-traefik in mfa" $KUBECTL get networkpolicy allow-ingress-from-traefik -n mfa check "allow-ingress-from-keycloak in mfa" $KUBECTL get networkpolicy allow-ingress-from-keycloak -n mfa check "allow-egress-to-postgres in mfa" $KUBECTL get networkpolicy allow-egress-to-postgres -n mfa diff --git a/sso-mfa/k8s/verify-t03.sh b/sso-mfa/k8s/verify-t03.sh index 6ffa824..ff2a91a 100755 --- a/sso-mfa/k8s/verify-t03.sh +++ b/sso-mfa/k8s/verify-t03.sh @@ -4,11 +4,13 @@ # Checks: # 1. CloudNativePG operator is installed and running # 2. Cluster net-kingdom-pg is Ready -# 3. Both application databases exist (keycloak_db, privacyidea_db) -# 4. Both application roles exist (keycloak, privacyidea) +# 3. privacyidea_db database exists +# 4. privacyidea role exists # 5. K8s Secrets are present in the databases namespace # 6. (Optional) Scheduled backup CR is present when backup is configured # +# Note: keycloak_db removed — Keycloak replaced by Authelia+LLDAP+KeyCape (T05). +# # Usage: # chmod +x verify-t03.sh # ./verify-t03.sh @@ -19,9 +21,9 @@ PASS=0 FAIL=0 WARN=0 -pass() { echo " [PASS] $1"; ((PASS++)); } -fail() { echo " [FAIL] $1"; ((FAIL++)); } -warn() { echo " [WARN] $1"; ((WARN++)); } +pass() { echo " [PASS] $1"; PASS=$((PASS + 1)); } +fail() { echo " [FAIL] $1"; FAIL=$((FAIL + 1)); } +warn() { echo " [WARN] $1"; WARN=$((WARN + 1)); } section() { echo ""; echo "── $1 ──────────────────────────────────────"; } @@ -76,15 +78,9 @@ section "3. Databases" if [[ -n "$PRIMARY_POD" ]]; then DB_LIST=$(kubectl exec -n databases "$PRIMARY_POD" -- \ - psql -U postgres -tAc "SELECT datname FROM pg_database WHERE datname IN ('keycloak_db','privacyidea_db') ORDER BY datname;" \ + psql -U postgres -tAc "SELECT datname FROM pg_database WHERE datname = 'privacyidea_db';" \ 2>/dev/null || echo "") - if echo "$DB_LIST" | grep -q "keycloak_db"; then - pass "keycloak_db exists" - else - fail "keycloak_db not found" - fi - if echo "$DB_LIST" | grep -q "privacyidea_db"; then pass "privacyidea_db exists" else @@ -99,15 +95,9 @@ section "4. Database roles" if [[ -n "$PRIMARY_POD" ]]; then ROLE_LIST=$(kubectl exec -n databases "$PRIMARY_POD" -- \ - psql -U postgres -tAc "SELECT rolname FROM pg_roles WHERE rolname IN ('keycloak','privacyidea') ORDER BY rolname;" \ + psql -U postgres -tAc "SELECT rolname FROM pg_roles WHERE rolname = 'privacyidea';" \ 2>/dev/null || echo "") - if echo "$ROLE_LIST" | grep -q "keycloak"; then - pass "role keycloak exists" - else - fail "role keycloak not found" - fi - if echo "$ROLE_LIST" | grep -q "privacyidea"; then pass "role privacyidea exists" else @@ -120,7 +110,7 @@ fi # ── 5. K8s Secrets ──────────────────────────────────────────────────────────── section "5. K8s Secrets (databases namespace)" -for secret in net-kingdom-pg-keycloak-app net-kingdom-pg-privacyidea-app; do +for secret in net-kingdom-pg-privacyidea-app; do if kubectl get secret "$secret" -n databases &>/dev/null; then pass "Secret $secret exists" else