Merge: Homepage apex landing dashboard (non-gating tier; forward-auth deferred)
This commit is contained in:
11 files changed
+511
No files matched your search
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
# Flux Kustomization: Homepage landing dashboard — reconciles ./platform/homepage.
|
||||||
|
#
|
||||||
|
# POST-PLATFORM TIER (dependsOn platform-config). Homepage is a LEAF landing UI at the apex
|
||||||
|
# home.keithjackson.dev. It depends on the trusted wildcard TLS cert + the shared Gateway (incl. the
|
||||||
|
# `https-apex` listener) + split-horizon DNS — all brought up by `platform` (controllers) and
|
||||||
|
# `platform-config` (the wildcard Certificate, cilium L2 VIP, Gateway). It therefore MUST NOT live in
|
||||||
|
# the gating `platform` tier: a UI whose pod can be unhealthy would keep `platform` not-Ready, and
|
||||||
|
# while `platform` is not-Ready NOTHING downstream reconciles — the exact incident behind the
|
||||||
|
# headlamp/oauth2-proxy moves (DECISIONS-LOG: "Headlamp gating stall"). Same lesson, same fix: a leaf
|
||||||
|
# that can be legitimately unhealthy reconciles in its OWN post-platform Kustomization.
|
||||||
|
#
|
||||||
|
# NON-GATING (wait:false): nothing in the graph dependsOn homepage (apps waves gate on
|
||||||
|
# platform-config, not on this), and a dashboard pod can be long-not-Ready during a cold start.
|
||||||
|
# wait:false guarantees its health can NEVER gate anything, even if a future resource is (mistakenly)
|
||||||
|
# pointed dependsOn it.
|
||||||
|
#
|
||||||
|
# NO SOPS decryption stanza: this component carries no encrypted Secret (plain manifests + a config
|
||||||
|
# ConfigMap with no API keys), so — unlike headlamp/oauth2-proxy — clusters/dev does NOT patch it.
|
||||||
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
name: homepage
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
interval: 10m
|
||||||
|
retryInterval: 2m
|
||||||
|
timeout: 5m
|
||||||
|
prune: true
|
||||||
|
wait: false
|
||||||
|
dependsOn:
|
||||||
|
- name: platform-config # wildcard TLS + Gateway (incl. https-apex listener) + split-horizon VIP first
|
||||||
|
sourceRef:
|
||||||
|
kind: GitRepository
|
||||||
|
name: flux-system
|
||||||
|
path: ./platform/homepage
|
||||||
|
# Homepage references no ${VARS} today; the substitution is retained for parity with the other
|
||||||
|
# tiers / future overlay knobs.
|
||||||
|
postBuild:
|
||||||
|
substituteFrom:
|
||||||
|
- kind: ConfigMap
|
||||||
|
name: cluster-vars
|
||||||
@@ -26,4 +26,8 @@ resources:
|
|||||||
# sit in the gating `platform` tier, where its unhealth stalled platform-config + oauth2-proxy +
|
# sit in the gating `platform` tier, where its unhealth stalled platform-config + oauth2-proxy +
|
||||||
# every apps wave/vm on "dependency not ready" (DECISIONS-LOG: "Headlamp gating stall").
|
# every apps wave/vm on "dependency not ready" (DECISIONS-LOG: "Headlamp gating stall").
|
||||||
- headlamp.yaml # Flux Kustomization -> ./platform/headlamp (dependsOn platform-config)
|
- headlamp.yaml # Flux Kustomization -> ./platform/headlamp (dependsOn platform-config)
|
||||||
|
# Homepage apex landing dashboard — POST-platform tier (dependsOn platform-config, wait:false).
|
||||||
|
# A leaf UI at home.keithjackson.dev; same non-gating rationale as headlamp/oauth2-proxy (a UI must
|
||||||
|
# never gate the platform chain). Carries no SOPS secret, so clusters/dev adds no decryption patch.
|
||||||
|
- homepage.yaml # Flux Kustomization -> ./platform/homepage (dependsOn platform-config)
|
||||||
- cluster-vars.yaml # overlay knob surface (base defaults)
|
- cluster-vars.yaml # overlay knob surface (base defaults)
|
||||||
@@ -37,6 +37,37 @@ spec:
|
|||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
gateway-access: "shared"
|
gateway-access: "shared"
|
||||||
|
# APEX listeners. The wildcard listeners above match ONLY subdomains: `*.home.keithjackson.dev`
|
||||||
|
# does NOT intersect the bare apex `home.keithjackson.dev`, so an HTTPRoute for the apex host
|
||||||
|
# (the Homepage landing dashboard, clusters/base/homepage.yaml) would bind to NO listener and
|
||||||
|
# never serve. These two listeners give the apex its own hostname. TLS is the SAME wildcard cert:
|
||||||
|
# `home.keithjackson.dev` is already a SAN on wildcard-certificate.yaml, so `wildcard-home-tls`
|
||||||
|
# terminates the apex too — no extra Certificate needed. Same gateway-access=shared opt-in.
|
||||||
|
- name: https-apex
|
||||||
|
protocol: HTTPS
|
||||||
|
port: 443
|
||||||
|
hostname: "home.keithjackson.dev"
|
||||||
|
tls:
|
||||||
|
mode: Terminate
|
||||||
|
certificateRefs:
|
||||||
|
- kind: Secret
|
||||||
|
name: wildcard-home-tls # apex is a SAN on the wildcard cert (wildcard-certificate.yaml)
|
||||||
|
allowedRoutes:
|
||||||
|
namespaces:
|
||||||
|
from: Selector
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
gateway-access: "shared"
|
||||||
|
- name: http-apex-redirect
|
||||||
|
protocol: HTTP
|
||||||
|
port: 80
|
||||||
|
hostname: "home.keithjackson.dev"
|
||||||
|
allowedRoutes:
|
||||||
|
namespaces:
|
||||||
|
from: Selector
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
gateway-access: "shared"
|
||||||
# TODO(WS-NET, Phase 2): pin the L2-announced VIP / IP pool; add HTTP->HTTPS redirect HTTPRoute;
|
# TODO(WS-NET, Phase 2): pin the L2-announced VIP / IP pool; add HTTP->HTTPS redirect HTTPRoute;
|
||||||
# confirm cross-namespace ReferenceGrant pattern with WS-APPS. Ingress opens NORTH-SOUTH only —
|
# confirm cross-namespace ReferenceGrant pattern with WS-APPS. Ingress opens NORTH-SOUTH only —
|
||||||
# it does not relax east-west default-deny (docs/09).
|
# it does not relax east-west default-deny (docs/09).
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# platform/homepage
|
||||||
|
|
||||||
|
**Homepage** (gethomepage) — the apex landing dashboard at **https://home.keithjackson.dev**,
|
||||||
|
aggregating links to the cluster's services.
|
||||||
|
|
||||||
|
- **Serves:** a single pane of glass for the homelab (docs/09 apex UI).
|
||||||
|
- **Tier:** non-gating post-platform Flux Kustomization
|
||||||
|
([`clusters/base/homepage.yaml`](../../clusters/base/homepage.yaml), `dependsOn: platform-config`,
|
||||||
|
`wait: false`) — mirrors `headlamp` / `oauth2-proxy`. A leaf UI must never gate the `platform`
|
||||||
|
tier (DECISIONS-LOG "Headlamp gating stall").
|
||||||
|
- **Namespace:** `homepage` — a PLATFORM namespace (no `app-namespace=true`), so the Kyverno
|
||||||
|
app-scoped policies (default-deny gen, auth-mode, egress-class) do not apply; we ship an explicit
|
||||||
|
default-deny + Cilium ingress policy as defense-in-depth.
|
||||||
|
|
||||||
|
## What this component owns
|
||||||
|
|
||||||
|
Plain manifests (not a HelmRelease — the app is one stateless Deployment + a config ConfigMap):
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `namespace.yaml` | `homepage` ns, `gateway-access=shared` |
|
||||||
|
| `configmap.yaml` | Homepage config (settings/services/bookmarks/widgets + empty docker/kubernetes + custom.css/js) |
|
||||||
|
| `deployment.yaml` | `ghcr.io/gethomepage/homepage` (non-root, subPath config mounts, `HOMEPAGE_ALLOWED_HOSTS`) |
|
||||||
|
| `service.yaml` | ClusterIP 80 -> 3000 |
|
||||||
|
| `networkpolicy.yaml` | default-deny (DNS-only egress) + Cilium Gateway-only ingress on 3000 |
|
||||||
|
| `httproute.yaml` | apex ingress on the Gateway `https-apex` listener |
|
||||||
|
|
||||||
|
## Apex nuances (why this isn't just another `*.home` route)
|
||||||
|
|
||||||
|
1. **TLS:** the apex `home.keithjackson.dev` is already a **SAN** on the wildcard Certificate
|
||||||
|
(`platform/gateway/wildcard-certificate.yaml`) — no new cert.
|
||||||
|
2. **Gateway listener:** the wildcard listeners' hostname `*.home.keithjackson.dev` does **not**
|
||||||
|
match the bare apex. This component's route binds a dedicated **`https-apex`** listener added to
|
||||||
|
the shared Gateway (`platform/gateway/gateway.yaml`); `http-apex-redirect` covers :80.
|
||||||
|
3. **`HOMEPAGE_ALLOWED_HOSTS`:** Homepage >= 1.0 returns 403 for any Host not in this list — set to
|
||||||
|
`home.keithjackson.dev` + the pod IP (for kubelet probes).
|
||||||
|
|
||||||
|
## Forward-auth — FOLLOW-UP (not yet enforced)
|
||||||
|
|
||||||
|
The HTTPRoute is annotated `auth-mode: forward-auth` (intended posture: oauth2-proxy -> Keycloak).
|
||||||
|
**The Gateway ext-authz attachment that actually enforces forward-auth does not exist yet** — it is a
|
||||||
|
Phase-6 WS-NET TODO (`platform/oauth2-proxy` is the authenticator; there is no `CiliumEnvoyConfig` /
|
||||||
|
ext-authz wiring anywhere in the repo). Until that mechanism lands, Homepage — like **every** other
|
||||||
|
`forward-auth` app (jackett, qbittorrent, pihole, radarr, sonarr) — is reachable unauthenticated.
|
||||||
|
When the Gateway ext-authz pattern is built, attach it to this route (and the others) with
|
||||||
|
`allowed-groups: platform-admins`. **PM decision flagged** in the deploy report.
|
||||||
|
|
||||||
|
## Deliberately deferred (avoid scope creep)
|
||||||
|
|
||||||
|
- **Service-status widgets** (Grafana/Plex/Sonarr/… API tiles) need per-service API-key **secrets** —
|
||||||
|
not wired; tiles are simple links. Add as SOPS secrets + `widgets.yaml` later.
|
||||||
|
- **Kubernetes widget** would need a read-only ServiceAccount + RBAC — skipped (`kubernetes.yaml`
|
||||||
|
empty, `automountServiceAccountToken: false`).
|
||||||
|
- **Brand icons** (selfh.st/dashboard-icons) need world:443 egress — we use bundled `mdi-*` icons so
|
||||||
|
egress stays DNS-only.
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
---
|
||||||
|
# Homepage configuration (gethomepage). Each key is mounted read-only into /app/config via a subPath
|
||||||
|
# (deployment.yaml) — the exact file set the app expects (services/settings/bookmarks/widgets +
|
||||||
|
# docker/kubernetes + custom.css/js). Plain link tiles only: NO service-widget API keys are wired
|
||||||
|
# here (that needs per-service secrets — flagged as a follow-up), so nothing sensitive lives in this
|
||||||
|
# ConfigMap. Icons use the app-BUNDLED Material Design set (`mdi-*`) so no icon CDN is fetched and the
|
||||||
|
# pod's egress stays DNS-only (networkpolicy.yaml). Brand icons (selfh.st/dashboard-icons) would need
|
||||||
|
# world:443 egress — deliberately not used.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: homepage
|
||||||
|
namespace: homepage
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: homepage
|
||||||
|
data:
|
||||||
|
settings.yaml: |
|
||||||
|
---
|
||||||
|
title: Home Network
|
||||||
|
headerStyle: clean
|
||||||
|
theme: dark
|
||||||
|
color: slate
|
||||||
|
hideVersion: true
|
||||||
|
layout:
|
||||||
|
Platform:
|
||||||
|
style: row
|
||||||
|
columns: 3
|
||||||
|
Media:
|
||||||
|
style: row
|
||||||
|
columns: 3
|
||||||
|
Network & Infra:
|
||||||
|
style: row
|
||||||
|
columns: 3
|
||||||
|
|
||||||
|
services.yaml: |
|
||||||
|
---
|
||||||
|
- Platform:
|
||||||
|
- Headlamp:
|
||||||
|
href: https://headlamp.home.keithjackson.dev
|
||||||
|
description: Kubernetes dashboard (per-user OIDC RBAC)
|
||||||
|
icon: mdi-kubernetes
|
||||||
|
- Grafana:
|
||||||
|
href: https://grafana.home.keithjackson.dev
|
||||||
|
description: Metrics, logs & dashboards (fronts Prometheus/Loki/Alertmanager)
|
||||||
|
icon: mdi-chart-areaspline
|
||||||
|
- Semaphore:
|
||||||
|
href: https://semaphore.home.keithjackson.dev
|
||||||
|
description: Ansible automation & CI
|
||||||
|
icon: mdi-cog-play
|
||||||
|
- Gitea:
|
||||||
|
href: https://gitea.home.keithjackson.dev
|
||||||
|
description: Git hosting & Actions (GitOps source of truth)
|
||||||
|
icon: mdi-git
|
||||||
|
- Keycloak:
|
||||||
|
href: https://keycloak.home.keithjackson.dev
|
||||||
|
description: Identity provider (SSO / OIDC)
|
||||||
|
icon: mdi-account-key
|
||||||
|
|
||||||
|
- Media:
|
||||||
|
- Plex:
|
||||||
|
href: https://plex.home.keithjackson.dev
|
||||||
|
description: Media streaming
|
||||||
|
icon: mdi-plex
|
||||||
|
- Overseerr:
|
||||||
|
href: https://seerr.home.keithjackson.dev
|
||||||
|
description: Media requests
|
||||||
|
icon: mdi-play-box-multiple
|
||||||
|
- Sonarr:
|
||||||
|
href: https://sonarr.home.keithjackson.dev
|
||||||
|
description: TV library manager
|
||||||
|
icon: mdi-television-classic
|
||||||
|
- Radarr:
|
||||||
|
href: https://radarr.home.keithjackson.dev
|
||||||
|
description: Movie library manager
|
||||||
|
icon: mdi-movie-open
|
||||||
|
- Jackett:
|
||||||
|
href: https://jackett.home.keithjackson.dev
|
||||||
|
description: Indexer proxy
|
||||||
|
icon: mdi-magnify
|
||||||
|
- qBittorrent:
|
||||||
|
href: https://qbittorrent.home.keithjackson.dev
|
||||||
|
description: Download client (VPN egress)
|
||||||
|
icon: mdi-download-network
|
||||||
|
|
||||||
|
- Network & Infra:
|
||||||
|
- Pi-hole:
|
||||||
|
href: https://pihole.home.keithjackson.dev
|
||||||
|
description: Network-wide DNS & ad-blocking
|
||||||
|
icon: mdi-shield-check
|
||||||
|
- UniFi:
|
||||||
|
href: https://unifi.home.keithjackson.dev:11443
|
||||||
|
description: Network controller (UOS)
|
||||||
|
icon: mdi-router-network
|
||||||
|
- Nextcloud:
|
||||||
|
href: https://nextcloud.home.keithjackson.dev
|
||||||
|
description: Files, calendar & contacts
|
||||||
|
icon: mdi-cloud
|
||||||
|
|
||||||
|
bookmarks.yaml: |
|
||||||
|
---
|
||||||
|
- Operations:
|
||||||
|
- Gitea (external):
|
||||||
|
- abbr: GT
|
||||||
|
href: https://gitea.keithjackson.dev
|
||||||
|
- Auth portal:
|
||||||
|
- abbr: SSO
|
||||||
|
href: https://auth.home.keithjackson.dev
|
||||||
|
- Reference:
|
||||||
|
- Homepage docs:
|
||||||
|
- abbr: HP
|
||||||
|
href: https://gethomepage.dev
|
||||||
|
|
||||||
|
widgets.yaml: |
|
||||||
|
---
|
||||||
|
- datetime:
|
||||||
|
text_size: xl
|
||||||
|
format:
|
||||||
|
dateStyle: long
|
||||||
|
timeStyle: short
|
||||||
|
hour12: false
|
||||||
|
- search:
|
||||||
|
provider: duckduckgo
|
||||||
|
target: _blank
|
||||||
|
|
||||||
|
# No Docker integration (Homepage does not reach the Docker socket in-cluster).
|
||||||
|
docker.yaml: |
|
||||||
|
---
|
||||||
|
|
||||||
|
# No Kubernetes integration (no SA / cluster RBAC granted — avoids scope creep; the kubernetes
|
||||||
|
# widget would need a read-only ServiceAccount. Left empty deliberately).
|
||||||
|
kubernetes.yaml: |
|
||||||
|
---
|
||||||
|
|
||||||
|
custom.css: ""
|
||||||
|
custom.js: ""
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
---
|
||||||
|
# Homepage (gethomepage) — the apex landing dashboard at https://home.keithjackson.dev. Plain
|
||||||
|
# manifests (not a HelmRelease): the whole app is a single stateless Deployment + a config ConfigMap,
|
||||||
|
# and hand-authoring the ConfigMap gives exact control over the service links without guessing a
|
||||||
|
# chart's values schema (same plain-manifest style as apps/vms/unifi). Rendered by its OWN non-gating
|
||||||
|
# post-platform Flux Kustomization (clusters/base/homepage.yaml, dependsOn platform-config,
|
||||||
|
# wait:false) — a leaf UI must never sit in the gating `platform` tier (DECISIONS-LOG: "Headlamp
|
||||||
|
# gating stall").
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: homepage
|
||||||
|
namespace: homepage
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: homepage
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: homepage
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: homepage
|
||||||
|
annotations:
|
||||||
|
# Re-roll the pod when the config changes (ConfigMap subPath mounts do NOT hot-reload).
|
||||||
|
checksum/config: config-v1
|
||||||
|
spec:
|
||||||
|
automountServiceAccountToken: false
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1000
|
||||||
|
runAsGroup: 1000
|
||||||
|
fsGroup: 1000
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: homepage
|
||||||
|
# Renovate maintains this pin (ghcr.io/gethomepage/homepage). Config lives in /app/config.
|
||||||
|
image: ghcr.io/gethomepage/homepage:v2.1.2
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 3000
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
# Referenced by HOMEPAGE_ALLOWED_HOSTS below so the in-pod liveness/readiness probes
|
||||||
|
# (which hit the pod IP, not the public host) are accepted by Homepage's host guard.
|
||||||
|
- name: MY_POD_IP
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: status.podIP
|
||||||
|
# REQUIRED by Homepage >= 1.0 — requests whose Host header is not in this list get 403.
|
||||||
|
# The public apex host (served via the Gateway) + the pod IP (for kubelet probes).
|
||||||
|
- name: HOMEPAGE_ALLOWED_HOSTS
|
||||||
|
value: "home.keithjackson.dev,$(MY_POD_IP):3000"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 25m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
memory: 256Mi
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
# NOTE: readOnlyRootFilesystem intentionally left unset — Homepage (Next.js) writes a
|
||||||
|
# runtime build/cache under /app/.next at boot. Same deferred-hardening note as headlamp.
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/healthcheck
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 15
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/healthcheck
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 15
|
||||||
|
volumeMounts:
|
||||||
|
# Homepage expects each config file present; mount them read-only by subPath so the rest
|
||||||
|
# of /app/config stays writable (it writes ./logs there at runtime).
|
||||||
|
- { name: homepage-config, mountPath: /app/config/settings.yaml, subPath: settings.yaml, readOnly: true }
|
||||||
|
- { name: homepage-config, mountPath: /app/config/services.yaml, subPath: services.yaml, readOnly: true }
|
||||||
|
- { name: homepage-config, mountPath: /app/config/bookmarks.yaml, subPath: bookmarks.yaml, readOnly: true }
|
||||||
|
- { name: homepage-config, mountPath: /app/config/widgets.yaml, subPath: widgets.yaml, readOnly: true }
|
||||||
|
- { name: homepage-config, mountPath: /app/config/docker.yaml, subPath: docker.yaml, readOnly: true }
|
||||||
|
- { name: homepage-config, mountPath: /app/config/kubernetes.yaml, subPath: kubernetes.yaml, readOnly: true }
|
||||||
|
- { name: homepage-config, mountPath: /app/config/custom.css, subPath: custom.css, readOnly: true }
|
||||||
|
- { name: homepage-config, mountPath: /app/config/custom.js, subPath: custom.js, readOnly: true }
|
||||||
|
# Writable scratch for Homepage's runtime logs (it writes /app/config/logs).
|
||||||
|
- { name: logs, mountPath: /app/config/logs }
|
||||||
|
volumes:
|
||||||
|
- name: homepage-config
|
||||||
|
configMap:
|
||||||
|
name: homepage
|
||||||
|
- name: logs
|
||||||
|
emptyDir: {}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
# North-south ingress for Homepage at the APEX home.keithjackson.dev. Attaches to the shared Gateway
|
||||||
|
# via the dedicated `https-apex` listener (sectionName) — the wildcard `https` listener's hostname
|
||||||
|
# `*.home.keithjackson.dev` does NOT match the bare apex, so this route MUST bind the apex listener
|
||||||
|
# added in platform/gateway/gateway.yaml. TLS is terminated at the Gateway by the wildcard cert (the
|
||||||
|
# apex is a SAN on it). Same-namespace backend, so no ReferenceGrant; the parentRef is permitted
|
||||||
|
# because the homepage namespace carries gateway-access=shared.
|
||||||
|
#
|
||||||
|
# auth-mode=forward-auth records the INTENDED posture: Homepage is a bare UI with no native OIDC, so
|
||||||
|
# it should sit behind oauth2-proxy forward-auth at the Gateway (R-IDP-02). NOTE: the Gateway
|
||||||
|
# ext-authz ATTACHMENT that actually enforces forward-auth is a Phase-6 WS-NET TODO that is NOT yet
|
||||||
|
# built anywhere in the repo (platform/oauth2-proxy is the authenticator only; no CiliumEnvoyConfig /
|
||||||
|
# ext-authz exists). Until that mechanism lands, this route — like every other forward-auth app
|
||||||
|
# (jackett, qbittorrent, pihole, radarr, …) — is reachable without auth. See README.md "Forward-auth".
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: homepage
|
||||||
|
namespace: homepage
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: homepage
|
||||||
|
annotations:
|
||||||
|
homelab.keithjackson.dev/auth-mode: forward-auth
|
||||||
|
homelab.keithjackson.dev/auth-groups: platform-admins
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: shared
|
||||||
|
namespace: gateway
|
||||||
|
sectionName: https-apex # the apex listener (wildcard `https` won't match the bare apex)
|
||||||
|
hostnames:
|
||||||
|
- home.keithjackson.dev
|
||||||
|
rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
|
- name: homepage
|
||||||
|
port: 80
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
# Homepage — the apex landing dashboard (home.keithjackson.dev). Rendered by its OWN non-gating
|
||||||
|
# post-platform Flux Kustomization (clusters/base/homepage.yaml, dependsOn platform-config,
|
||||||
|
# wait:false) — NOT by the gating `platform` tier, where a leaf UI's unhealth stalled the whole chain
|
||||||
|
# (DECISIONS-LOG "Headlamp gating stall"). Plain manifests (no HelmRelease): a single Deployment +
|
||||||
|
# a hand-authored config ConfigMap. The HTTPRoute travels WITH the component (the gateway.networking
|
||||||
|
# CRDs are registered by platform, Ready before this tier's platform-config dependency completes).
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: homepage
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- configmap.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
# default-deny baseline + Cilium gateway-ingress allow (DNS-only egress from the baseline).
|
||||||
|
- networkpolicy.yaml
|
||||||
|
- httproute.yaml # apex ingress (binds the Gateway `https-apex` listener)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: homepage
|
||||||
|
labels:
|
||||||
|
# Expose Homepage at the APEX home.keithjackson.dev via the shared Gateway (docs/09). The apex is
|
||||||
|
# a SAN on the *.home.keithjackson.dev wildcard cert, and the Gateway carries a dedicated
|
||||||
|
# `https-apex` listener for it (platform/gateway/gateway.yaml). This label opts the namespace in
|
||||||
|
# to attach HTTPRoutes to the shared Gateway (httproute.yaml).
|
||||||
|
gateway-access: "shared"
|
||||||
|
# NOTE: intentionally NOT homelab.keithjackson.dev/app-namespace=true. Homepage is a PLATFORM
|
||||||
|
# landing UI (like Grafana in `monitoring` / Headlamp in `headlamp`), so Kyverno does not generate
|
||||||
|
# the app default-deny baseline here and the app-scoped auth-mode / egress-class policies do not
|
||||||
|
# apply. We still ship an explicit default-deny NetworkPolicy + a CiliumNetworkPolicy
|
||||||
|
# (networkpolicy.yaml) as defense-in-depth: Gateway-only ingress, DNS-only egress.
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
# Default-deny baseline (R-NET-01): deny all ingress + egress, allow only DNS. `homepage` is a
|
||||||
|
# PLATFORM namespace so Kyverno does not auto-generate this — we ship it explicitly (defense in
|
||||||
|
# depth). Same shape as the Kyverno-generated app baseline. Cilium enforces the UNION of this
|
||||||
|
# NetworkPolicy and the CiliumNetworkPolicy below (allow if ANY policy allows).
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: default-deny
|
||||||
|
namespace: homepage
|
||||||
|
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
|
||||||
|
---
|
||||||
|
# Gateway ingress allow (R-NET-01/02): only the shared Gateway (the Cilium `ingress` entity) may
|
||||||
|
# reach Homepage, on its container port 3000. Homepage is a pure link dashboard — it makes NO
|
||||||
|
# outbound calls (bundled `mdi-*` icons, no service-status widgets), so DNS-only egress from the
|
||||||
|
# baseline above is sufficient and no egress CNP is added (minimal egress).
|
||||||
|
apiVersion: cilium.io/v2
|
||||||
|
kind: CiliumNetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: homepage-allow-gateway-ingress
|
||||||
|
namespace: homepage
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: homepage
|
||||||
|
spec:
|
||||||
|
endpointSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: homepage
|
||||||
|
ingress:
|
||||||
|
- fromEntities:
|
||||||
|
- ingress
|
||||||
|
toPorts:
|
||||||
|
- ports:
|
||||||
|
- port: "3000"
|
||||||
|
protocol: TCP
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
# ClusterIP fronting Homepage. The shared Gateway routes the apex host to this Service (httproute.yaml);
|
||||||
|
# Service port 80 -> container port 3000.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: homepage
|
||||||
|
namespace: homepage
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: homepage
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: homepage
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
Reference in new issue
Block a user