--- # Namespace (trust zone; default-deny + policies apply) apiVersion: v1 kind: Namespace metadata: name: nextcloud labels: homelab.keithjackson.dev/app-namespace: 'true' gateway-access: shared annotations: homelab.keithjackson.dev/backup-policy: daily-offcluster --- # Default-deny baseline (R-NET-01): deny all, allow only DNS apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny namespace: nextcloud labels: homelab.keithjackson.dev/baseline: default-deny spec: podSelector: {} policyTypes: - Ingress - Egress ingress: [] egress: - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system ports: - protocol: UDP port: 53 - protocol: TCP port: 53 --- # PVC nextcloud-data (R-STATE) apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nextcloud-data namespace: nextcloud labels: app.kubernetes.io/name: nextcloud spec: accessModes: - ReadWriteOnce storageClassName: longhorn resources: requests: storage: 20Gi --- # Deployment (R-PLAT-02) apiVersion: apps/v1 kind: Deployment metadata: name: nextcloud namespace: nextcloud labels: app.kubernetes.io/name: nextcloud spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app.kubernetes.io/name: nextcloud template: metadata: labels: app.kubernetes.io/name: nextcloud homelab.keithjackson.dev/egress-class: internet homelab.keithjackson.dev/auth-mode: oidc annotations: homelab.keithjackson.dev/auth-mode: oidc homelab.keithjackson.dev/auth-groups: platform-admins spec: containers: - name: nextcloud image: nextcloud:31-apache ports: - containerPort: 80 env: - name: POSTGRES_HOST value: nextcloud-db-rw.nextcloud-db.svc.cluster.local - name: POSTGRES_DB value: nextcloud - name: POSTGRES_USER value: nextcloud - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: nextcloud-bootstrap key: db-password - name: NEXTCLOUD_ADMIN_USER valueFrom: secretKeyRef: name: nextcloud-bootstrap key: admin-username - name: NEXTCLOUD_ADMIN_PASSWORD valueFrom: secretKeyRef: name: nextcloud-bootstrap key: admin-password - name: NEXTCLOUD_TRUSTED_DOMAINS value: nextcloud.home.keithjackson.dev - name: OVERWRITEHOST value: nextcloud.home.keithjackson.dev - name: OVERWRITEPROTOCOL value: https - name: OVERWRITECLIURL value: https://nextcloud.home.keithjackson.dev - name: NEXTCLOUD_OIDC_CLIENT_SECRET valueFrom: secretKeyRef: name: nextcloud-bootstrap key: oidc-client-secret volumeMounts: - name: data mountPath: /var/www/html lifecycle: postStart: exec: command: - /bin/sh - -c - "# Idempotent Keycloak OIDC wiring (R-IDP-02/06). Runs alongside the nextcloud entrypoint\n# (which installs/upgrades on first boot). Installs+enables the user_oidc app and creates OR\n# updates the \"keycloak\" login provider (occ user_oidc:provider is create-or-update by\n# identifier). Auto-discovery pulls the realm's endpoints; the `groups` claim drives Nextcloud\n# group provisioning. occ runs as www-data (owns config.php); the client secret is passed via\n# --clientsecret-env so it never lands on a command line. Safe to re-run every start; a missing\n# secret or not-yet-installed instance is skipped so the pod never crashloops (the local\n# break-glass admin still works).\nset -u\nOCC() { su -s /bin/sh www-data -c \"php /var/www/html/occ $*\"; }\ni=0\nuntil OCC status 2>/dev/null | grep -q \"installed: true\"; do\n i=$((i + 1))\n [ \"$i\" -ge 300 ] && { echo \"[nextcloud-oidc] occ not ready after wait; skipping\" >&2; exit 0; }\n sleep 2\ndone\nif [ -z \"${NEXTCLOUD_OIDC_CLIENT_SECRET:-}\" ]; then\n echo \"[nextcloud-oidc] client secret unset; skipping OIDC (break-glass admin only)\"\n exit 0\nfi\nif ! OCC app:list 2>/dev/null | grep -q \"user_oidc\"; then\n OCC app:install user_oidc || echo \"[nextcloud-oidc] app:install user_oidc failed\" >&2\nfi\nOCC app:enable user_oidc >/dev/null 2>&1 || true\nif OCC user_oidc:provider keycloak \\\n --clientid=nextcloud \\\n --clientsecret-env=NEXTCLOUD_OIDC_CLIENT_SECRET \\\n --discoveryuri=https://keycloak.home.keithjackson.dev/realms/homelab/.well-known/openid-configuration \\\n --scope=\"openid email profile\" \\\n --unique-uid=0 \\\n --mapping-uid=preferred_username \\\n --mapping-email=email \\\n --mapping-display-name=name \\\n --group-provisioning=1 \\\n --mapping-groups=groups; then\n echo \"[nextcloud-oidc] provider 'keycloak' configured\"\nelse\n echo \"[nextcloud-oidc] provider config failed\" >&2\nfi\n" volumes: - name: data persistentVolumeClaim: claimName: nextcloud-data --- # Service apiVersion: v1 kind: Service metadata: name: nextcloud namespace: nextcloud labels: app.kubernetes.io/name: nextcloud spec: selector: app.kubernetes.io/name: nextcloud ports: - name: port-80 port: 80 targetPort: 80 --- # CiliumNetworkPolicy egress allow (R-NET-02): nextcloud-allow-nextcloud-db-5432 apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy metadata: name: nextcloud-allow-nextcloud-db-5432 namespace: nextcloud labels: app.kubernetes.io/name: nextcloud spec: endpointSelector: matchLabels: app.kubernetes.io/name: nextcloud egress: - toEndpoints: - matchLabels: app.kubernetes.io/name: nextcloud-db k8s:io.kubernetes.pod.namespace: nextcloud-db toPorts: - ports: - port: '5432' protocol: TCP --- # CiliumNetworkPolicy gateway-ingress allow (R-NET-01/02): nextcloud-allow-gateway-ingress apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy metadata: name: nextcloud-allow-gateway-ingress namespace: nextcloud labels: app.kubernetes.io/name: nextcloud spec: endpointSelector: matchLabels: app.kubernetes.io/name: nextcloud ingress: - fromEntities: - ingress toPorts: - ports: - port: '80' protocol: TCP --- # CiliumNetworkPolicy world egress allow (egress class governs the path; R-NET-05): nextcloud-allow-internet-egress apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy metadata: name: nextcloud-allow-internet-egress namespace: nextcloud labels: app.kubernetes.io/name: nextcloud spec: endpointSelector: matchLabels: app.kubernetes.io/name: nextcloud egress: - toEntities: - world --- # HTTPRoute nextcloud-80 (R-TLS/R-DNS) apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: nextcloud-80 namespace: nextcloud labels: app.kubernetes.io/name: nextcloud annotations: homelab.keithjackson.dev/auth-mode: oidc homelab.keithjackson.dev/auth-groups: platform-admins spec: parentRefs: - name: shared namespace: gateway sectionName: https hostnames: - nextcloud.home.keithjackson.dev rules: - matches: - path: type: PathPrefix value: / backendRefs: - name: nextcloud port: 80 --- # ServiceMonitor (R-OBS-01) apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: nextcloud namespace: nextcloud labels: release: kube-prometheus-stack app.kubernetes.io/name: nextcloud spec: selector: matchLabels: app.kubernetes.io/name: nextcloud namespaceSelector: matchNames: - nextcloud endpoints: - port: metrics interval: 30s --- # PrometheusRule SLI alerts (R-OBS-01) apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: name: nextcloud-slis namespace: nextcloud labels: release: kube-prometheus-stack app.kubernetes.io/name: nextcloud spec: groups: - name: nextcloud.slis rules: - alert: nextcloud-down expr: up{job="nextcloud"} == 0 for: 5m labels: severity: critical service: nextcloud annotations: summary: nextcloud is down description: 'nextcloud has been unreachable for 5m (SLI: up).'