326 lines
9.8 KiB
YAML
326 lines
9.8 KiB
YAML
# FlowerCore Remote Desktop — NetworkPolicies (GitOps-managed)
|
|
#
|
|
# Moved into bluejay-infra 2026-05-07 as part of the regroup audit. These
|
|
# four policies were previously applied via FlowerCore.RemoteDesktop's
|
|
# scripts/deploy-web.sh `kubectl apply` calls, which meant a fresh cluster
|
|
# rebuild from bluejay-infra alone would miss them — Browser Lab session
|
|
# isolation, control-plane allow-list, and HTTP-01 cert renewal would all
|
|
# silently fail to come up.
|
|
#
|
|
# Source-of-truth contract:
|
|
# - bluejay-infra OWNS all NetworkPolicy + Certificate + IngressRoute
|
|
# resources for fc-desktop.
|
|
# - FlowerCore.RemoteDesktop's scripts/deploy-web.sh continues to own
|
|
# the Deployment + Service apply (because the image ref
|
|
# `localhost/fc-desktop:linux-xfce` only exists on each node's
|
|
# containerd after a manual import — it can't be pulled from a
|
|
# registry, so a Deployment manifest in bluejay-infra would race the
|
|
# image-import step and crash-loop).
|
|
---
|
|
# 1) desktop-isolation — Browser Lab session pods.
|
|
#
|
|
# Locks down pods labeled `app.kubernetes.io/name=remote-desktop` (every
|
|
# session pod regardless of template). Allows guacd ingress for the display
|
|
# lane and remotedesktop-web's pre-handoff probing. Egress is deliberately
|
|
# narrow: named CoreDNS, direct Intranet web, and noc1 step-ca only. There is
|
|
# no broad Traefik/VIP or internet egress from desktop sessions. If a future
|
|
# Browser Lab path needs a public-style host, prefer an explicit Service rule
|
|
# or include the post-DNAT backend port per the Traefik VIP lint.
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: NetworkPolicy
|
|
metadata:
|
|
name: desktop-isolation
|
|
namespace: fc-desktop
|
|
labels:
|
|
app.kubernetes.io/part-of: remotedesktop
|
|
app.kubernetes.io/component: isolation
|
|
spec:
|
|
podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: remote-desktop
|
|
policyTypes:
|
|
- Ingress
|
|
- Egress
|
|
ingress:
|
|
- from:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: guacamole
|
|
ports:
|
|
- port: 3000
|
|
protocol: TCP
|
|
- port: 3001
|
|
protocol: TCP
|
|
- port: 5901
|
|
protocol: TCP
|
|
- port: 3389
|
|
protocol: TCP
|
|
- from:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: fc-desktop
|
|
podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: remotedesktop-web
|
|
ports:
|
|
- port: 3000
|
|
protocol: TCP
|
|
- port: 5901
|
|
protocol: TCP
|
|
egress:
|
|
# CoreDNS only. The old to: [] DNS rule accidentally allowed any DNS
|
|
# listener in any namespace or routed network.
|
|
- to:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: kube-system
|
|
podSelector:
|
|
matchLabels:
|
|
k8s-app: kube-dns
|
|
ports:
|
|
- port: 53
|
|
protocol: UDP
|
|
- port: 53
|
|
protocol: TCP
|
|
# Browser Lab home / internal docs target. Use the real service port
|
|
# directly rather than public Traefik host aliases.
|
|
- to:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: intranet
|
|
podSelector:
|
|
matchLabels:
|
|
app: intranet-web
|
|
ports:
|
|
- port: 5300
|
|
protocol: TCP
|
|
# noc1 step-ca ACME endpoint. The lane brief called out 9000/TCP; the live
|
|
# ACME directory currently answers on 9443/TCP, so both stay pinned to the
|
|
# same host rather than reopening Traefik or internet egress.
|
|
- to:
|
|
- ipBlock:
|
|
cidr: 10.0.56.10/32
|
|
ports:
|
|
- port: 9000
|
|
protocol: TCP
|
|
- port: 9443
|
|
protocol: TCP
|
|
---
|
|
# 2) fc-desktop-default-deny — namespace-wide catch-all.
|
|
#
|
|
# Selects every pod EXCEPT remotedesktop-web (the public-surface control
|
|
# plane) and applies default-deny semantics for both Ingress and Egress.
|
|
# Closes the gap where session pods land WITHOUT the desktop-isolation
|
|
# policy's `app.kubernetes.io/name=remote-desktop` label, plus prevents
|
|
# arbitrary debug sidecars / kubectl debug images from getting cluster
|
|
# access.
|
|
#
|
|
# CRITICAL: also catches transient cm-acme-http-solver pods (that's the
|
|
# bug this whole regroup chased). The cm-acme-http-solver-allow policy
|
|
# below is the explicit carve-out.
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: NetworkPolicy
|
|
metadata:
|
|
name: fc-desktop-default-deny
|
|
namespace: fc-desktop
|
|
labels:
|
|
app.kubernetes.io/part-of: remotedesktop
|
|
app.kubernetes.io/component: isolation
|
|
spec:
|
|
podSelector:
|
|
matchExpressions:
|
|
- key: app.kubernetes.io/name
|
|
operator: NotIn
|
|
values:
|
|
- remotedesktop-web
|
|
policyTypes:
|
|
- Ingress
|
|
- Egress
|
|
---
|
|
# 3) remotedesktop-web-isolation — control plane explicit allow-list.
|
|
#
|
|
# remotedesktop-web is the only pod label the default-deny excludes, so
|
|
# without this policy the control plane would have wide-open Ingress AND
|
|
# Egress. This re-introduces a tight allow-list:
|
|
# - Ingress: Traefik only on TCP/8080
|
|
# - Egress: CoreDNS, K8s API, Guacamole admin, NFS, Intranet,
|
|
# Traefik (cluster + LB), and the fc-desktop namespace itself
|
|
# (for session pod readiness probing).
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: NetworkPolicy
|
|
metadata:
|
|
name: remotedesktop-web-isolation
|
|
namespace: fc-desktop
|
|
labels:
|
|
app.kubernetes.io/part-of: remotedesktop
|
|
app.kubernetes.io/component: isolation
|
|
spec:
|
|
podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: remotedesktop-web
|
|
policyTypes:
|
|
- Ingress
|
|
- Egress
|
|
ingress:
|
|
- from:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: traefik-system
|
|
podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: traefik
|
|
ports:
|
|
- port: 8080
|
|
protocol: TCP
|
|
egress:
|
|
# CoreDNS
|
|
- to:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: kube-system
|
|
podSelector:
|
|
matchLabels:
|
|
k8s-app: kube-dns
|
|
ports:
|
|
- port: 53
|
|
protocol: UDP
|
|
- port: 53
|
|
protocol: TCP
|
|
# K8s API server
|
|
- to: []
|
|
ports:
|
|
- port: 443
|
|
protocol: TCP
|
|
- port: 6443
|
|
protocol: TCP
|
|
# Guacamole admin
|
|
- to:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: guacamole
|
|
ports:
|
|
- port: 8080
|
|
protocol: TCP
|
|
# NFS to Synology
|
|
- to:
|
|
- ipBlock:
|
|
cidr: 10.0.58.3/32
|
|
ports:
|
|
- port: 2049
|
|
protocol: TCP
|
|
- port: 2049
|
|
protocol: UDP
|
|
- port: 111
|
|
protocol: TCP
|
|
- port: 111
|
|
protocol: UDP
|
|
# Intranet web
|
|
- to:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: intranet
|
|
podSelector:
|
|
matchLabels:
|
|
app: intranet-web
|
|
ports:
|
|
- port: 5300
|
|
protocol: TCP
|
|
# Cluster Traefik pods (in-cluster service resolution + Guacamole
|
|
# routing handoff where web app builds URLs against the public host
|
|
# but resolves internally).
|
|
- to:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: traefik-system
|
|
podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: traefik
|
|
ports:
|
|
- port: 80
|
|
protocol: TCP
|
|
- port: 443
|
|
protocol: TCP
|
|
- port: 8080
|
|
protocol: TCP
|
|
- port: 8443
|
|
protocol: TCP
|
|
# fc-desktop namespace — session pod probing during browser-access
|
|
# readiness checks.
|
|
- to:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: fc-desktop
|
|
ports:
|
|
- port: 3000
|
|
protocol: TCP
|
|
- port: 3001
|
|
protocol: TCP
|
|
- port: 5901
|
|
protocol: TCP
|
|
- port: 3389
|
|
protocol: TCP
|
|
---
|
|
# 4) cm-acme-http-solver-allow — cert-manager HTTP-01 carve-out.
|
|
#
|
|
# Without this, fc-desktop-default-deny catches the transient solver pods
|
|
# cert-manager creates for each renewal (they don't carry the
|
|
# remotedesktop-web label). Caused 8-day silent renewal failure on
|
|
# desktop.iamworkin.lan in 2026-04-28..2026-05-07 (see
|
|
# feedback_certmanager_renewal_stuck_when_solver_blocked_by_namespace_default_deny.md).
|
|
#
|
|
# Authorizes:
|
|
# - Ingress on TCP/8089 from cluster Traefik (which proxies the external
|
|
# HTTP-01 GET on port 80 through to the solver).
|
|
# - Egress for cluster DNS (defensive — newer cert-manager probes from
|
|
# inside the solver too).
|
|
#
|
|
# The `acme.cert-manager.io/http01-solver=true` label is set by
|
|
# cert-manager itself on every solver pod automatically.
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: NetworkPolicy
|
|
metadata:
|
|
name: cm-acme-http-solver-allow
|
|
namespace: fc-desktop
|
|
labels:
|
|
app.kubernetes.io/part-of: remotedesktop
|
|
app.kubernetes.io/component: cert-renewal
|
|
spec:
|
|
podSelector:
|
|
matchLabels:
|
|
acme.cert-manager.io/http01-solver: "true"
|
|
policyTypes:
|
|
- Ingress
|
|
- Egress
|
|
ingress:
|
|
- from:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: traefik-system
|
|
podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: traefik
|
|
ports:
|
|
- port: 8089
|
|
protocol: TCP
|
|
egress:
|
|
- to:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: kube-system
|
|
podSelector:
|
|
matchLabels:
|
|
k8s-app: kube-dns
|
|
ports:
|
|
- port: 53
|
|
protocol: UDP
|
|
- port: 53
|
|
protocol: TCP
|
|
- to:
|
|
- ipBlock:
|
|
cidr: 10.0.56.10/32
|
|
ports:
|
|
- port: 9000
|
|
protocol: TCP
|
|
- port: 9443
|
|
protocol: TCP
|