Compare commits
2 Commits
9b799c5ea2
...
b333b5e6d9
| Author | SHA1 | Date | |
|---|---|---|---|
| b333b5e6d9 | |||
| 89524d45e3 |
@ -16,7 +16,7 @@ subdirectories are consumed by Argo CD Applications in `argocd/apps`.
|
|||||||
| `homeassistant/` | Home Assistant external service and ingress |
|
| `homeassistant/` | Home Assistant external service and ingress |
|
||||||
| `media/` | Jellyfin, qBittorrent, JDownloader ([sending links](media/jdownloader.md)) |
|
| `media/` | Jellyfin, qBittorrent, JDownloader ([sending links](media/jdownloader.md)) |
|
||||||
| `monitoring/` | Monitoring PVs, Grafana datasource, Grafana/Auth OIDC secrets |
|
| `monitoring/` | Monitoring PVs, Grafana datasource, Grafana/Auth OIDC secrets |
|
||||||
| `multus/` | Multus CNI, VLAN 50 NetworkAttachmentDefinition, canary ([details](multus/README.md)) — manual-sync Application, see `argocd/apps/multus.yaml` |
|
| `multus/` | Multus CNI, VLAN 50 NetworkAttachmentDefinition, canary, browser-vpn-proxy SOCKS5 proxy ([details](multus/README.md)) — manual-sync Application, see `argocd/apps/multus.yaml` |
|
||||||
| `network/` | Pi-hole secrets, DDNS, Traefik dashboard, external host ingresses |
|
| `network/` | Pi-hole secrets, DDNS, Traefik dashboard, external host ingresses |
|
||||||
| `portfolio/` | Portfolio deployment, ingress, registry pull secret |
|
| `portfolio/` | Portfolio deployment, ingress, registry pull secret |
|
||||||
|
|
||||||
|
|||||||
@ -1,34 +1,25 @@
|
|||||||
# NOT applied by any Argo CD Application. Deliberately kept in
|
# sync-wave 3 — after RBAC/DaemonSet (0), the NAD (1), and the canary
|
||||||
# manifests/multus/reserved/ rather than manifests/multus/ directly:
|
# (2). Moved here from manifests/multus/reserved/ (2026-08-24) now that
|
||||||
# argocd/apps/multus.yaml's source has no directory.recurse: true, so it
|
# qBittorrent and JDownloader have both independently passed their
|
||||||
# only ever scans files directly in manifests/multus/, never this
|
# canary, validation, and soak gates — the reservation this file's
|
||||||
# subdirectory — even selective `argocd app sync multus --resource ...`
|
# earlier header described is satisfied. Still governed only by the
|
||||||
# has no way to target a resource Argo CD doesn't know exists. This is
|
# `multus` Argo CD Application's own manual-sync gate (no automated
|
||||||
# stronger isolation than the canary's own manual-sync-only Application
|
# sync there at all) — the same gate already used for the DaemonSet, NAD,
|
||||||
# gate, on purpose: plan.md Phase 8/12 requires this workload stay out of
|
# and canary, not a new mechanism.
|
||||||
# reconciliation entirely until qBittorrent AND JDownloader have
|
|
||||||
# independently passed their canary, validation, and soak gates — not
|
|
||||||
# just "not yet approved to sync", but "not a candidate for sync at all".
|
|
||||||
#
|
#
|
||||||
# When ready to actually build this (not yet — this file is prepared,
|
# Before applying:
|
||||||
# not deployed):
|
# 1. Verify the NodePort below (30889) doesn't collide with anything
|
||||||
# 1. Move it into manifests/multus/ (a sync-wave-numbered filename,
|
# live — confirmed free 2026-08-24 (`kubectl get svc -A`, checked
|
||||||
# e.g. 30-browser-vpn-proxy.yaml) once qBittorrent/JDownloader are
|
# against every NodePort in use). manifests/media/gluetun-proxy.yaml
|
||||||
# both soaked.
|
# (the existing browser proxy this replaces) uses 30888, not this
|
||||||
# 2. Create the browser-vpn-proxy-credentials Secret first (see the
|
# one — no collision, but that file stays live and unmodified until
|
||||||
# companion secret script this needs — not written yet, follow the
|
# this replacement is confirmed working end to end, then gets
|
||||||
# exact pattern of manifests/media/pia-secret.sh: add
|
# decommissioned as its own separate, explicitly-approved step.
|
||||||
# BROWSER_PROXY_USER/BROWSER_PROXY_PASSWORD to .env, a script that
|
|
||||||
# kubeseals a Secret with those as PROXY_USER/PROXY_PASSWORD).
|
|
||||||
# 3. Verify the NodePort below (30889) doesn't collide with anything
|
|
||||||
# live — manifests/network/gluetun-proxy.yaml already uses 30888
|
|
||||||
# for the existing browser SOCKS5 proxy this is meant to eventually
|
|
||||||
# replace; confirm 30889 is actually free before applying.
|
|
||||||
#
|
#
|
||||||
# Design:
|
# Design:
|
||||||
# - serjs/go-socks5-proxy (github.com/serjs/socks5-server) — a small,
|
# - serjs/go-socks5-proxy (github.com/serjs/socks5-server) — a small,
|
||||||
# single-purpose Go SOCKS5 server, not a VPN client itself. It
|
# single-purpose Go SOCKS5 server, not a VPN client itself. It
|
||||||
# doesn't need to be, here: unlike the legacy manifests/network/
|
# doesn't need to be, here: unlike the legacy manifests/media/
|
||||||
# gluetun-proxy.yaml it's replacing (which runs its own OpenVPN
|
# gluetun-proxy.yaml it's replacing (which runs its own OpenVPN
|
||||||
# client), PIA egress for this workload comes from the same
|
# client), PIA egress for this workload comes from the same
|
||||||
# netns-level routing as qBittorrent/JDownloader — the init
|
# netns-level routing as qBittorrent/JDownloader — the init
|
||||||
@ -37,10 +28,37 @@
|
|||||||
# proxy. Pinned to v0.0.4's digest, confirmed live against Docker
|
# proxy. Pinned to v0.0.4's digest, confirmed live against Docker
|
||||||
# Hub's registry API 2026-08-24:
|
# Hub's registry API 2026-08-24:
|
||||||
# sha256:6828ddb2a6a93dec85209b69cd3842bd80094f7dd52f35ee1dd0f9e9165e2188
|
# sha256:6828ddb2a6a93dec85209b69cd3842bd80094f7dd52f35ee1dd0f9e9165e2188
|
||||||
# - REQUIRE_AUTH=true + PROXY_USER/PROXY_PASSWORD (from the Secret
|
# - No auth (REQUIRE_AUTH=false), no ALLOWED_IPS — a deliberate choice,
|
||||||
# above) AND ALLOWED_IPS restricted to the home LAN range — both, not
|
# not an oversight. plan.md originally called for auth AND
|
||||||
# either/or, matching plan.md's "authentication or LAN-restricted
|
# LAN-restriction together as a floor; discussed and explicitly
|
||||||
# exposure" as a floor, not a ceiling.
|
# relaxed (2026-08-24) after confirming the home network's own
|
||||||
|
# firewall already restricts this Service's NodePort to Trusted
|
||||||
|
# (VLAN 10) and Lab (VLAN 40) only — Trusted/Lab → Lab is ALLOW,
|
||||||
|
# IoT/Guest/Management → Lab is DENY except the Technitium DNS
|
||||||
|
# exception (see ~/repo/home-network/Home Network Implementation
|
||||||
|
# Plan.md section 10). Residual risk accepted knowingly: Lab has its
|
||||||
|
# own Wi-Fi SSID with open DHCP, so "reachable" means "anyone with
|
||||||
|
# that Wi-Fi password or a device already on Trusted/Lab," not just
|
||||||
|
# deliberately provisioned devices — there is no proxy-level control
|
||||||
|
# backing this up.
|
||||||
|
# - ALLOWED_IPS is not a fallback for the above — it's removed because
|
||||||
|
# it cannot do what it looks like it does. Read this image's actual
|
||||||
|
# source (server.go, and the go-socks5 fork it replaces via go.mod's
|
||||||
|
# replace directive) to confirm, not assumed: ALLOWED_IPS values are
|
||||||
|
# parsed with net.ParseIP (not net.ParseCIDR) and matched with exact
|
||||||
|
# net.IP.Equal(), never CIDR containment. The original draft's value
|
||||||
|
# here, "10.10.40.0/24", would have failed to parse as a plain IP,
|
||||||
|
# producing a whitelist containing one nil entry — since Equal()
|
||||||
|
# against nil never matches any real client address, every single
|
||||||
|
# connection would have been rejected, including legitimate ones.
|
||||||
|
# This library has no way to express a subnet allowlist at all, only
|
||||||
|
# a list of exact individual IPs, which doesn't fit a dynamic-DHCP
|
||||||
|
# LAN. Access restriction is the OpenWrt firewall alone; there is no
|
||||||
|
# compensating netns-level (nft) inbound filter here either — one
|
||||||
|
# could be added later, but would first need confirming whether this
|
||||||
|
# NodePort Service actually preserves real client source IPs
|
||||||
|
# end-to-end (default `externalTrafficPolicy: Cluster` typically
|
||||||
|
# does not) rather than assuming it does.
|
||||||
# - Service is NodePort, not LoadBalancer/Ingress — reachable from the
|
# - Service is NodePort, not LoadBalancer/Ingress — reachable from the
|
||||||
# LAN at <any node IP>:30889, never exposed publicly (no port-forward
|
# LAN at <any node IP>:30889, never exposed publicly (no port-forward
|
||||||
# on Flint/the router for it, same as the existing gluetun-proxy).
|
# on Flint/the router for it, same as the existing gluetun-proxy).
|
||||||
@ -66,6 +84,8 @@ kind: Deployment
|
|||||||
metadata:
|
metadata:
|
||||||
name: browser-vpn-proxy
|
name: browser-vpn-proxy
|
||||||
namespace: downloads
|
namespace: downloads
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "3"
|
||||||
spec:
|
spec:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
strategy:
|
strategy:
|
||||||
@ -103,10 +123,16 @@ spec:
|
|||||||
value: "10.43.0.0/16"
|
value: "10.43.0.0/16"
|
||||||
- name: NODE_IP
|
- name: NODE_IP
|
||||||
value: "10.10.40.20"
|
value: "10.10.40.20"
|
||||||
|
- name: EXPECTED_VLAN50_IP
|
||||||
|
value: "10.10.50.12"
|
||||||
securityContext:
|
securityContext:
|
||||||
capabilities:
|
capabilities:
|
||||||
drop: ["ALL"]
|
drop: ["ALL"]
|
||||||
add: ["NET_ADMIN"]
|
# NET_RAW alongside NET_ADMIN: the guard script's
|
||||||
|
# arping-based duplicate-address and gateway-reachability
|
||||||
|
# checks need it — see
|
||||||
|
# vlan50-egress-guard-script.yaml's header comment.
|
||||||
|
add: ["NET_ADMIN", "NET_RAW"]
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: guard-script
|
- name: guard-script
|
||||||
mountPath: /scripts
|
mountPath: /scripts
|
||||||
@ -119,20 +145,15 @@ spec:
|
|||||||
capabilities:
|
capabilities:
|
||||||
drop: ["ALL"]
|
drop: ["ALL"]
|
||||||
env:
|
env:
|
||||||
|
# REQUIRE_AUTH defaults to true in this image (confirmed
|
||||||
|
# against its real source, params struct in server.go) — it
|
||||||
|
# must be explicitly set to "false" to actually go
|
||||||
|
# passwordless, omitting it entirely would NOT have that
|
||||||
|
# effect. Deliberately no auth, no ALLOWED_IPS — see the
|
||||||
|
# header comment for the reasoning and what actually
|
||||||
|
# restricts access instead.
|
||||||
- name: REQUIRE_AUTH
|
- name: REQUIRE_AUTH
|
||||||
value: "true"
|
value: "false"
|
||||||
- name: PROXY_USER
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: browser-vpn-proxy-credentials
|
|
||||||
key: PROXY_USER
|
|
||||||
- name: PROXY_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: browser-vpn-proxy-credentials
|
|
||||||
key: PROXY_PASSWORD
|
|
||||||
- name: ALLOWED_IPS
|
|
||||||
value: "10.10.40.0/24" # LAN only — narrow this further if a specific client range is confirmed
|
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 20m
|
cpu: 20m
|
||||||
@ -13,9 +13,9 @@ comment. Nothing here applies to the cluster just because it's merged to
|
|||||||
| `01-rbac.yaml` | 0 | Multus ServiceAccount/ClusterRole/ClusterRoleBinding |
|
| `01-rbac.yaml` | 0 | Multus ServiceAccount/ClusterRole/ClusterRoleBinding |
|
||||||
| `02-daemonset.yaml` | 0 | Multus thick-plugin DaemonSet — **nodeSelector restricted to `nik-debian` only**, k3s-specific CNI paths |
|
| `02-daemonset.yaml` | 0 | Multus thick-plugin DaemonSet — **nodeSelector restricted to `nik-debian` only**, k3s-specific CNI paths |
|
||||||
| `10-nad-vlan50.yaml` | 1 | VLAN 50 `NetworkAttachmentDefinition`, macvlan bridge on `enp1s0.50`, static IPAM |
|
| `10-nad-vlan50.yaml` | 1 | VLAN 50 `NetworkAttachmentDefinition`, macvlan bridge on `enp1s0.50`, static IPAM |
|
||||||
| `vlan50-egress-guard-script.yaml` | (no wave annotation — apply alongside 10) | Shared init-container script consumed by the canary and, later, qBittorrent/JDownloader |
|
| `vlan50-egress-guard-script.yaml` | (no wave annotation — apply alongside 10) | Shared init-container script consumed by the canary, qBittorrent, JDownloader, and `browser-vpn-proxy` |
|
||||||
| `20-canary.yaml` | 2 | Temporary canary pod — **do not sync without separate explicit approval**, see its own header |
|
| `20-canary.yaml` | 2 | Temporary canary pod — **do not sync without separate explicit approval**, see its own header |
|
||||||
| `reserved/browser-vpn-proxy.yaml` | n/a | The `.12` third workload, prepared not deployed — see its own header for why it lives in a subdirectory `argocd/apps/multus.yaml` never scans at all |
|
| `30-browser-vpn-proxy.yaml` | 3 | `.12` — SOCKS5 proxy for browser traffic via PIA, replaces `manifests/media/gluetun-proxy.yaml`. No auth, no app-level IP allowlist (see its own header for why and what actually restricts access instead) — **do not sync without separate explicit approval** |
|
||||||
|
|
||||||
## Before applying anything here
|
## Before applying anything here
|
||||||
|
|
||||||
@ -31,6 +31,9 @@ comment. Nothing here applies to the cluster just because it's merged to
|
|||||||
01 → 02, confirm the Multus pod is actually Running on `nik-debian`
|
01 → 02, confirm the Multus pod is actually Running on `nik-debian`
|
||||||
and `/var/lib/rancher/k3s/agent/etc/cni/net.d` now has a generated
|
and `/var/lib/rancher/k3s/agent/etc/cni/net.d` now has a generated
|
||||||
`00-multus.conf`, *then* 10, *then* — only with separate approval — 20.
|
`00-multus.conf`, *then* 10, *then* — only with separate approval — 20.
|
||||||
|
`30-browser-vpn-proxy.yaml` follows the same "separate approval" gate
|
||||||
|
as 20 — no Secret precondition, it needs no credentials at all (see
|
||||||
|
its own header for why).
|
||||||
|
|
||||||
## Canary lifecycle — re-running it after a Multus/CNI change
|
## Canary lifecycle — re-running it after a Multus/CNI change
|
||||||
|
|
||||||
@ -113,8 +116,8 @@ edge cases).
|
|||||||
## Why `ipam.type: static` and no shared allocator (Whereabouts etc.)
|
## Why `ipam.type: static` and no shared allocator (Whereabouts etc.)
|
||||||
|
|
||||||
Only ever a handful of fixed IPs on this network — qBittorrent (`.10`),
|
Only ever a handful of fixed IPs on this network — qBittorrent (`.10`),
|
||||||
JDownloader (`.11`), one reserved-not-deployed (`.12`), and a single
|
JDownloader (`.11`), `browser-vpn-proxy` (`.12`), and a single canary
|
||||||
canary (`.100`). A dynamic IPAM controller (CRD, webhook, its own failure
|
(`.100`). A dynamic IPAM controller (CRD, webhook, its own failure
|
||||||
modes) is unjustified complexity for that; each pod's own
|
modes) is unjustified complexity for that; each pod's own
|
||||||
`k8s.v1.cni.cncf.io/networks` annotation just states its IP directly.
|
`k8s.v1.cni.cncf.io/networks` annotation just states its IP directly.
|
||||||
|
|
||||||
@ -164,6 +167,7 @@ kubectl wait --for=condition=Ready pod/vlan50-rollback-probe --timeout=60s
|
|||||||
kubectl delete pod vlan50-rollback-probe
|
kubectl delete pod vlan50-rollback-probe
|
||||||
|
|
||||||
# 3. Only now, the Kubernetes-side resources, in this order:
|
# 3. Only now, the Kubernetes-side resources, in this order:
|
||||||
|
kubectl delete -f manifests/multus/30-browser-vpn-proxy.yaml # if it was ever applied
|
||||||
kubectl delete -f manifests/multus/20-canary.yaml # if it was ever applied
|
kubectl delete -f manifests/multus/20-canary.yaml # if it was ever applied
|
||||||
kubectl delete -f manifests/multus/10-nad-vlan50.yaml # only after no pod still references it
|
kubectl delete -f manifests/multus/10-nad-vlan50.yaml # only after no pod still references it
|
||||||
kubectl delete -f manifests/multus/vlan50-egress-guard-script.yaml
|
kubectl delete -f manifests/multus/vlan50-egress-guard-script.yaml
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user