generated from coulomb/repo-seed
feat(sso-mfa): T02 K8s foundations manifests (NK-WP-0001-T02)
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 <noreply@anthropic.com>
This commit is contained in:
95
sso-mfa/k8s/README.md
Normal file
95
sso-mfa/k8s/README.md
Normal file
@@ -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.
|
||||||
44
sso-mfa/k8s/cert-manager/issuers.yaml
Normal file
44
sso-mfa/k8s/cert-manager/issuers.yaml
Normal file
@@ -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
|
||||||
33
sso-mfa/k8s/cert-manager/test-certificate.yaml
Normal file
33
sso-mfa/k8s/cert-manager/test-certificate.yaml
Normal file
@@ -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
|
||||||
24
sso-mfa/k8s/namespaces/namespaces.yaml
Normal file
24
sso-mfa/k8s/namespaces/namespaces.yaml
Normal file
@@ -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
|
||||||
136
sso-mfa/k8s/network-policies/netpol-databases.yaml
Normal file
136
sso-mfa/k8s/network-policies/netpol-databases.yaml
Normal file
@@ -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=<cluster-name> 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
|
||||||
112
sso-mfa/k8s/network-policies/netpol-mfa.yaml
Normal file
112
sso-mfa/k8s/network-policies/netpol-mfa.yaml
Normal file
@@ -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
|
||||||
111
sso-mfa/k8s/network-policies/netpol-sso.yaml
Normal file
111
sso-mfa/k8s/network-policies/netpol-sso.yaml
Normal file
@@ -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
|
||||||
62
sso-mfa/k8s/storage/verify-pvc.yaml
Normal file
62
sso-mfa/k8s/storage/verify-pvc.yaml
Normal file
@@ -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
|
||||||
114
sso-mfa/k8s/verify-t02.sh
Executable file
114
sso-mfa/k8s/verify-t02.sh
Executable file
@@ -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
|
||||||
Reference in New Issue
Block a user