From ee794a61abd85a77f1a4c3970907127cb7e36c50 Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 2 Mar 2026 09:49:39 +0100 Subject: [PATCH] feat(sso-mfa): T02 K8s foundations manifests (NK-WP-0001-T02) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit namespaces/namespaces.yaml: - sso, mfa, databases with net-kingdom/component labels for NetworkPolicy selectors network-policies/{netpol-sso,netpol-mfa,netpol-databases}.yaml: - Default-deny-all posture on all three namespaces - sso: ingress from Traefik; egress to databases:5432 and mfa:8080 - mfa: ingress from Traefik + Keycloak; egress to databases:5432 - databases: ingress from sso/mfa + CNPG operator; egress to kube-dns + K8s API - DNS (kube-system:53) allowed for all pods in all namespaces cert-manager/issuers.yaml: - selfsigned-issuer (ClusterIssuer) for internal/test use - letsencrypt-prod (ClusterIssuer, HTTP-01/Traefik) — fill ACME_EMAIL before apply cert-manager/test-certificate.yaml: - 24h self-signed cert to smoke-test cert-manager storage/verify-pvc.yaml: - Test PVC + Pod to confirm default StorageClass provisioning verify-t02.sh: - Full verification script: namespaces, NetworkPolicies, issuers, certs, StorageClass Co-Authored-By: Claude Sonnet 4.6 --- sso-mfa/k8s/README.md | 95 ++++++++++++ sso-mfa/k8s/cert-manager/issuers.yaml | 44 ++++++ .../k8s/cert-manager/test-certificate.yaml | 33 +++++ sso-mfa/k8s/namespaces/namespaces.yaml | 24 ++++ .../network-policies/netpol-databases.yaml | 136 ++++++++++++++++++ sso-mfa/k8s/network-policies/netpol-mfa.yaml | 112 +++++++++++++++ sso-mfa/k8s/network-policies/netpol-sso.yaml | 111 ++++++++++++++ sso-mfa/k8s/storage/verify-pvc.yaml | 62 ++++++++ sso-mfa/k8s/verify-t02.sh | 114 +++++++++++++++ 9 files changed, 731 insertions(+) create mode 100644 sso-mfa/k8s/README.md create mode 100644 sso-mfa/k8s/cert-manager/issuers.yaml create mode 100644 sso-mfa/k8s/cert-manager/test-certificate.yaml create mode 100644 sso-mfa/k8s/namespaces/namespaces.yaml create mode 100644 sso-mfa/k8s/network-policies/netpol-databases.yaml create mode 100644 sso-mfa/k8s/network-policies/netpol-mfa.yaml create mode 100644 sso-mfa/k8s/network-policies/netpol-sso.yaml create mode 100644 sso-mfa/k8s/storage/verify-pvc.yaml create mode 100755 sso-mfa/k8s/verify-t02.sh diff --git a/sso-mfa/k8s/README.md b/sso-mfa/k8s/README.md new file mode 100644 index 0000000..4194088 --- /dev/null +++ b/sso-mfa/k8s/README.md @@ -0,0 +1,95 @@ +# T02 — K8s Foundations + +Phase 1 of NK-WP-0001: namespaces, NetworkPolicies, cert-manager, StorageClass. + +## Prerequisites + +- K3s cluster running (ThreePhoenix HA or single-node dev) +- T01 Phase 0a complete (KeePassXC vault populated, ops bundle exported) +- `kubectl` configured with cluster access + +## Apply order + +```bash +# 1. Install cert-manager (if not already on cluster) +helm repo add jetstack https://charts.jetstack.io +helm repo update +helm install cert-manager jetstack/cert-manager \ + --namespace cert-manager --create-namespace \ + --set crds.enabled=true + +# Wait for cert-manager to be ready +kubectl rollout status deployment/cert-manager -n cert-manager + +# 2. Create namespaces +kubectl apply -f namespaces/namespaces.yaml + +# 3. Apply NetworkPolicies +kubectl apply -f network-policies/netpol-sso.yaml +kubectl apply -f network-policies/netpol-mfa.yaml +kubectl apply -f network-policies/netpol-databases.yaml + +# 4. Create ClusterIssuers +# Edit issuers.yaml first: replace ACME_EMAIL with your address +kubectl apply -f cert-manager/issuers.yaml + +# 5. Verify cert-manager with test certificate +kubectl apply -f cert-manager/test-certificate.yaml +kubectl wait --for=condition=Ready certificate/selfsigned-test \ + -n cert-manager-test --timeout=60s +kubectl delete namespace cert-manager-test + +# 6. Verify StorageClass +kubectl apply -f storage/verify-pvc.yaml +kubectl wait --for=condition=Ready pod/storage-test \ + -n storage-test --timeout=60s +kubectl logs -n storage-test storage-test +kubectl delete namespace storage-test + +# 7. Run the full verification script +chmod +x verify-t02.sh +./verify-t02.sh +``` + +## NetworkPolicy design + +All three namespaces follow a default-deny-all posture. Only the minimal +required paths are opened: + +| Source | Destination | Port | Purpose | +|--------|-------------|------|---------| +| Traefik (kube-system) | Keycloak (sso) | 8080 | OIDC/SAML ingress | +| Traefik (kube-system) | privacyIDEA (mfa) | 8080 | MFA portal ingress | +| Keycloak (sso) | privacyIDEA (mfa) | 8080 | Provider API calls | +| Keycloak (sso) | PostgreSQL (databases) | 5432 | DB | +| privacyIDEA (mfa) | PostgreSQL (databases) | 5432 | DB | +| CNPG operator (cnpg-system) | PostgreSQL (databases) | 5432/9187 | Operator + metrics | +| All pods | kube-dns (kube-system) | 53 | DNS resolution | +| CNPG pods | K8s API | 6443 | Status updates | + +## Verifying denied paths (manual) + +After applying NetworkPolicies, confirm that illegal paths are blocked: + +```bash +# Test: Keycloak → databases direct (should be ALLOWED) +kubectl run test-allowed -n sso --rm -it --image=busybox --restart=Never \ + -- nc -zv net-kingdom-pg-rw.databases.svc.cluster.local 5432 + +# Test: mfa → sso (should be DENIED — privacyIDEA must not reach Keycloak directly) +kubectl run test-denied -n mfa --rm -it --image=busybox --restart=Never \ + -- nc -zv -w3 keycloak.sso.svc.cluster.local 8080 + +# Test: databases → sso (should be DENIED — DB pods must not initiate connections) +kubectl run test-denied2 -n databases --rm -it --image=busybox --restart=Never \ + -- nc -zw3 keycloak.sso.svc.cluster.local 8080 +``` + +## Notes + +- `net-kingdom/component` labels on namespaces are used by NetworkPolicy + `namespaceSelector` rules. Do not remove them. +- `cnpg.io/cluster: net-kingdom-pg` in `netpol-databases.yaml` must match + the name of the CloudNativePG `Cluster` CR you create in T03. +- The `letsencrypt-prod` ClusterIssuer requires public DNS and port 80 open + to Let's Encrypt servers. Update `ACME_EMAIL` before applying. diff --git a/sso-mfa/k8s/cert-manager/issuers.yaml b/sso-mfa/k8s/cert-manager/issuers.yaml new file mode 100644 index 0000000..71470ae --- /dev/null +++ b/sso-mfa/k8s/cert-manager/issuers.yaml @@ -0,0 +1,44 @@ +# cert-manager issuers for net-kingdom SSO/MFA +# +# Two issuers are defined: +# 1. selfsigned-issuer — self-signed CA for internal/test use +# 2. letsencrypt-prod — ACME (Let's Encrypt) for public-facing ingresses +# +# Apply order: +# kubectl apply -f issuers.yaml +# kubectl apply -f test-certificate.yaml # verify selfsigned-issuer works +# +# Prerequisites: cert-manager must be installed and its CRDs registered. +# On K3s: cert-manager is NOT installed by default — install via Helm: +# helm repo add jetstack https://charts.jetstack.io +# helm install cert-manager jetstack/cert-manager \ +# --namespace cert-manager --create-namespace \ +# --set crds.enabled=true + +# ── Self-signed ClusterIssuer (test / internal CA) ─────────────────────────── +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned-issuer +spec: + selfSigned: {} +--- +# ── Let's Encrypt production ClusterIssuer ─────────────────────────────────── +# Requires: public DNS pointing to the cluster, port 80 reachable by ACME. +# Traefik handles the HTTP-01 challenge automatically. +# +# Replace ACME_EMAIL with your address before applying. +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-prod +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: ACME_EMAIL # <-- replace before applying + privateKeySecretRef: + name: letsencrypt-prod-account-key + solvers: + - http01: + ingress: + ingressClassName: traefik diff --git a/sso-mfa/k8s/cert-manager/test-certificate.yaml b/sso-mfa/k8s/cert-manager/test-certificate.yaml new file mode 100644 index 0000000..5115fb2 --- /dev/null +++ b/sso-mfa/k8s/cert-manager/test-certificate.yaml @@ -0,0 +1,33 @@ +# Test Certificate — verifies cert-manager + selfsigned-issuer are working. +# +# Apply: +# kubectl apply -f test-certificate.yaml +# +# Verify: +# kubectl get certificate -n cert-manager-test +# kubectl describe certificate selfsigned-test -n cert-manager-test +# # READY=True means cert-manager is operational. +# +# Clean up after verification: +# kubectl delete namespace cert-manager-test + +apiVersion: v1 +kind: Namespace +metadata: + name: cert-manager-test +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: selfsigned-test + namespace: cert-manager-test +spec: + secretName: selfsigned-test-tls + duration: 24h + renewBefore: 1h + issuerRef: + name: selfsigned-issuer + kind: ClusterIssuer + commonName: test.net-kingdom.internal + dnsNames: + - test.net-kingdom.internal diff --git a/sso-mfa/k8s/namespaces/namespaces.yaml b/sso-mfa/k8s/namespaces/namespaces.yaml new file mode 100644 index 0000000..d0c1db8 --- /dev/null +++ b/sso-mfa/k8s/namespaces/namespaces.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: sso + labels: + # net-kingdom component label — used in NetworkPolicy namespaceSelector + net-kingdom/component: sso + app.kubernetes.io/part-of: net-kingdom-sso-mfa +--- +apiVersion: v1 +kind: Namespace +metadata: + name: mfa + labels: + net-kingdom/component: mfa + app.kubernetes.io/part-of: net-kingdom-sso-mfa +--- +apiVersion: v1 +kind: Namespace +metadata: + name: databases + labels: + net-kingdom/component: databases + app.kubernetes.io/part-of: net-kingdom-sso-mfa diff --git a/sso-mfa/k8s/network-policies/netpol-databases.yaml b/sso-mfa/k8s/network-policies/netpol-databases.yaml new file mode 100644 index 0000000..f6bd257 --- /dev/null +++ b/sso-mfa/k8s/network-policies/netpol-databases.yaml @@ -0,0 +1,136 @@ +# NetworkPolicies for the databases namespace (PostgreSQL via CloudNativePG) +# +# Allowed paths: +# INGRESS: sso (Keycloak) → PostgreSQL :5432 +# INGRESS: mfa (privacyIDEA) → PostgreSQL :5432 +# EGRESS: all pods → kube-dns :53 (needed by CloudNativePG operator probes) +# +# Everything else is denied — in particular, no direct internet egress. +# CloudNativePG operator itself runs in its own namespace (cnpg-system) and +# needs access to the cluster API, not to the database port from here. + +# ── Default deny all ingress and egress ────────────────────────────────────── +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-all + namespace: databases +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress +--- +# ── Allow ingress from Keycloak ────────────────────────────────────────────── +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-from-keycloak + namespace: databases +spec: + podSelector: + matchLabels: + # CloudNativePG sets cnpg.io/cluster= on postgres pods. + # Adjust the cluster name to match your CloudNativePG Cluster CR name. + cnpg.io/cluster: net-kingdom-pg + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + net-kingdom/component: sso + podSelector: + matchLabels: + app.kubernetes.io/name: keycloak + ports: + - port: 5432 + protocol: TCP +--- +# ── Allow ingress from privacyIDEA ─────────────────────────────────────────── +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-from-privacyidea + namespace: databases +spec: + podSelector: + matchLabels: + cnpg.io/cluster: net-kingdom-pg + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + net-kingdom/component: mfa + podSelector: + matchLabels: + app.kubernetes.io/name: privacyidea + ports: + - port: 5432 + protocol: TCP +--- +# ── Allow ingress from CloudNativePG operator ──────────────────────────────── +# The CNPG operator (in cnpg-system) manages the cluster and performs health +# probes. Without this, operator reconciliation fails. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-from-cnpg-operator + namespace: databases +spec: + podSelector: + matchLabels: + cnpg.io/cluster: net-kingdom-pg + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: cnpg-system + ports: + - port: 5432 + protocol: TCP + - port: 9187 # CloudNativePG metrics exporter + protocol: TCP +--- +# ── Allow egress DNS (all pods) ────────────────────────────────────────────── +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-dns + namespace: databases +spec: + podSelector: {} + policyTypes: + - Egress + egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP +--- +# ── Allow egress to K8s API (CNPG operator needs it from the pods) ─────────── +# CloudNativePG instance pods post status updates to the API server. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-kube-api + namespace: databases +spec: + podSelector: + matchLabels: + cnpg.io/cluster: net-kingdom-pg + policyTypes: + - Egress + egress: + - ports: + - port: 6443 + protocol: TCP diff --git a/sso-mfa/k8s/network-policies/netpol-mfa.yaml b/sso-mfa/k8s/network-policies/netpol-mfa.yaml new file mode 100644 index 0000000..9c3df24 --- /dev/null +++ b/sso-mfa/k8s/network-policies/netpol-mfa.yaml @@ -0,0 +1,112 @@ +# NetworkPolicies for the mfa namespace (privacyIDEA) +# +# Allowed paths: +# INGRESS: Traefik (kube-system) → privacyIDEA :8080 (user-facing portal) +# INGRESS: Keycloak (sso) → privacyIDEA :8080 (Provider API calls) +# EGRESS: privacyIDEA → databases :5432 (PostgreSQL) +# EGRESS: all pods → kube-dns :53 (UDP+TCP) +# +# Everything else is denied. + +# ── Default deny all ingress and egress ────────────────────────────────────── +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-all + namespace: mfa +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress +--- +# ── Allow ingress from Traefik ─────────────────────────────────────────────── +# pi.yourdomain.com and pi-account.yourdomain.com both terminate at Traefik. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-from-traefik + namespace: mfa +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: privacyidea + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + podSelector: + matchLabels: + app.kubernetes.io/name: traefik + ports: + - port: 8080 + protocol: TCP +--- +# ── Allow ingress from Keycloak (Provider API calls) ───────────────────────── +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-from-keycloak + namespace: mfa +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: privacyidea + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + net-kingdom/component: sso + podSelector: + matchLabels: + app.kubernetes.io/name: keycloak + ports: + - port: 8080 + protocol: TCP +--- +# ── Allow egress to PostgreSQL ─────────────────────────────────────────────── +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-to-postgres + namespace: mfa +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: privacyidea + policyTypes: + - Egress + egress: + - to: + - namespaceSelector: + matchLabels: + net-kingdom/component: databases + ports: + - port: 5432 + protocol: TCP +--- +# ── Allow egress DNS (all pods) ────────────────────────────────────────────── +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-dns + namespace: mfa +spec: + podSelector: {} + policyTypes: + - Egress + egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP diff --git a/sso-mfa/k8s/network-policies/netpol-sso.yaml b/sso-mfa/k8s/network-policies/netpol-sso.yaml new file mode 100644 index 0000000..10deb63 --- /dev/null +++ b/sso-mfa/k8s/network-policies/netpol-sso.yaml @@ -0,0 +1,111 @@ +# NetworkPolicies for the sso namespace (Keycloak) +# +# Allowed paths: +# INGRESS: Traefik (kube-system) → Keycloak :8080 +# EGRESS: Keycloak → databases :5432 (PostgreSQL) +# EGRESS: Keycloak → mfa :8080 (privacyIDEA API) +# EGRESS: all pods → kube-dns :53 (UDP+TCP) +# +# Everything else is denied. + +# ── Default deny all ingress and egress ────────────────────────────────────── +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-all + namespace: sso +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress +--- +# ── Allow ingress from Traefik (K3s ingress controller) ───────────────────── +# Traefik terminates TLS; Keycloak listens on HTTP :8080 internally. +# Traefik pods are in kube-system with label app.kubernetes.io/name=traefik. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-from-traefik + namespace: sso +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: keycloak + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + podSelector: + matchLabels: + app.kubernetes.io/name: traefik + ports: + - port: 8080 + protocol: TCP +--- +# ── Allow egress to PostgreSQL ─────────────────────────────────────────────── +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-to-postgres + namespace: sso +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: keycloak + policyTypes: + - Egress + egress: + - to: + - namespaceSelector: + matchLabels: + net-kingdom/component: databases + ports: + - port: 5432 + protocol: TCP +--- +# ── Allow egress to privacyIDEA ────────────────────────────────────────────── +# Keycloak calls the privacyIDEA REST API via the privacyIDEA Keycloak Provider. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-to-privacyidea + namespace: sso +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: keycloak + policyTypes: + - Egress + egress: + - to: + - namespaceSelector: + matchLabels: + net-kingdom/component: mfa + ports: + - port: 8080 + protocol: TCP +--- +# ── Allow egress DNS (all pods) ────────────────────────────────────────────── +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-dns + namespace: sso +spec: + podSelector: {} + policyTypes: + - Egress + egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP diff --git a/sso-mfa/k8s/storage/verify-pvc.yaml b/sso-mfa/k8s/storage/verify-pvc.yaml new file mode 100644 index 0000000..9a38f69 --- /dev/null +++ b/sso-mfa/k8s/storage/verify-pvc.yaml @@ -0,0 +1,62 @@ +# StorageClass verification — confirms the default StorageClass can provision PVCs. +# +# K3s default StorageClass: local-path (rancher/local-path-provisioner) +# This is adequate for single-node dev/staging; for HA ThreePhoenix, a +# distributed StorageClass (Longhorn, Rook-Ceph) is preferred. +# +# Apply: +# kubectl apply -f verify-pvc.yaml +# +# Verify: +# kubectl get pvc -n storage-test +# # STATUS=Bound means provisioning works. +# kubectl get pod -n storage-test +# # pod/storage-test should be Completed (exit 0). +# +# Clean up: +# kubectl delete namespace storage-test + +apiVersion: v1 +kind: Namespace +metadata: + name: storage-test +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: storage-test-pvc + namespace: storage-test +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi + # Omit storageClassName to use the cluster default. + # To test a specific class: storageClassName: local-path +--- +apiVersion: v1 +kind: Pod +metadata: + name: storage-test + namespace: storage-test +spec: + restartPolicy: Never + containers: + - name: writer + image: busybox:1.36 + command: + - sh + - -c + - | + echo "StorageClass test: writing file" && \ + echo "ok" > /data/test.txt && \ + cat /data/test.txt && \ + echo "StorageClass verification PASSED" + volumeMounts: + - name: data + mountPath: /data + volumes: + - name: data + persistentVolumeClaim: + claimName: storage-test-pvc diff --git a/sso-mfa/k8s/verify-t02.sh b/sso-mfa/k8s/verify-t02.sh new file mode 100755 index 0000000..0205eb0 --- /dev/null +++ b/sso-mfa/k8s/verify-t02.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +# verify-t02.sh — verify T02 (K8s foundations) completion criteria +# +# Run after applying all T02 manifests. Exits 0 if all checks pass. +# +# Usage: ./verify-t02.sh [--kubeconfig PATH] + +set -euo pipefail + +KUBECTL="kubectl" +if [[ "${1:-}" == "--kubeconfig" ]]; then + KUBECTL="kubectl --kubeconfig $2" +fi + +PASS=0 +FAIL=0 + +check() { + local desc="$1" + shift + if "$@" &>/dev/null; then + echo " PASS $desc" + PASS=$((PASS + 1)) + else + echo " FAIL $desc" + FAIL=$((FAIL + 1)) + fi +} + +check_output() { + local desc="$1" + local expected="$2" + shift 2 + local actual + actual="$("$@" 2>/dev/null || true)" + if echo "$actual" | grep -q "$expected"; then + echo " PASS $desc" + PASS=$((PASS + 1)) + else + echo " FAIL $desc (got: $actual)" + FAIL=$((FAIL + 1)) + fi +} + +echo "=== T02 Verification — K8s Foundations ===" +echo "" + +# ── Namespaces ──────────────────────────────────────────────────────────────── +echo "[ Namespaces ]" +check "namespace sso exists" $KUBECTL get namespace sso +check "namespace mfa exists" $KUBECTL get namespace mfa +check "namespace databases exists" $KUBECTL get namespace databases +check "sso has net-kingdom/component label" \ + $KUBECTL get namespace sso -o jsonpath='{.metadata.labels.net-kingdom/component}' +echo "" + +# ── NetworkPolicies ─────────────────────────────────────────────────────────── +echo "[ NetworkPolicies ]" +for ns in sso mfa databases; do + check "default-deny-all in $ns" \ + $KUBECTL get networkpolicy default-deny-all -n "$ns" + 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-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 +check "allow-ingress-from-keycloak in databases" $KUBECTL get networkpolicy allow-ingress-from-keycloak -n databases +check "allow-ingress-from-privacyidea in databases" $KUBECTL get networkpolicy allow-ingress-from-privacyidea -n databases +echo "" + +# ── cert-manager ───────────────────────────────────────────────────────────── +echo "[ cert-manager ]" +check "cert-manager namespace exists" $KUBECTL get namespace cert-manager +check "selfsigned-issuer exists" $KUBECTL get clusterissuer selfsigned-issuer +check "letsencrypt-prod issuer exists" $KUBECTL get clusterissuer letsencrypt-prod +check_output "selfsigned-issuer is Ready" "True" \ + $KUBECTL get clusterissuer selfsigned-issuer -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' + +# Test certificate (applied separately — only check if present) +if $KUBECTL get namespace cert-manager-test &>/dev/null; then + check_output "test certificate is Ready" "True" \ + $KUBECTL get certificate selfsigned-test -n cert-manager-test \ + -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' +else + echo " SKIP test certificate (namespace cert-manager-test not found — apply test-certificate.yaml)" +fi +echo "" + +# ── StorageClass ────────────────────────────────────────────────────────────── +echo "[ StorageClass ]" +check "a default StorageClass exists" \ + $KUBECTL get storageclass -o jsonpath='{.items[?(@.metadata.annotations.storageclass\.kubernetes\.io/is-default-class=="true")].metadata.name}' + +if $KUBECTL get namespace storage-test &>/dev/null; then + check_output "storage-test pod Completed" "Completed\|Succeeded" \ + $KUBECTL get pod storage-test -n storage-test -o jsonpath='{.status.phase}' +else + echo " SKIP StorageClass PVC test (apply storage/verify-pvc.yaml first)" +fi +echo "" + +# ── Summary ─────────────────────────────────────────────────────────────────── +TOTAL=$((PASS + FAIL)) +echo "=== Results: $PASS/$TOTAL passed ===" +if [[ $FAIL -gt 0 ]]; then + echo " $FAIL check(s) failed." + exit 1 +else + echo " T02 COMPLETE — all checks passed." +fi