# NOT applied by any Argo CD Application. Deliberately kept in # manifests/multus/reserved/ rather than manifests/multus/ directly: # argocd/apps/multus.yaml's source has no directory.recurse: true, so it # only ever scans files directly in manifests/multus/, never this # subdirectory — even selective `argocd app sync multus --resource ...` # has no way to target a resource Argo CD doesn't know exists. This is # stronger isolation than the canary's own manual-sync-only Application # gate, on purpose: plan.md Phase 8/12 requires this workload stay out of # 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, # not deployed): # 1. Move it into manifests/multus/ (a sync-wave-numbered filename, # e.g. 30-browser-vpn-proxy.yaml) once qBittorrent/JDownloader are # both soaked. # 2. Create the browser-vpn-proxy-credentials Secret first (see the # companion secret script this needs — not written yet, follow the # exact pattern of manifests/media/pia-secret.sh: add # 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: # - serjs/go-socks5-proxy (github.com/serjs/socks5-server) — a small, # single-purpose Go SOCKS5 server, not a VPN client itself. It # doesn't need to be, here: unlike the legacy manifests/network/ # gluetun-proxy.yaml it's replacing (which runs its own OpenVPN # client), PIA egress for this workload comes from the same # netns-level routing as qBittorrent/JDownloader — the init # container below sends this pod's default route out net1/PIA, the # SOCKS5 server itself just needs to be a plain, correctly-behaving # proxy. Pinned to v0.0.4's digest, confirmed live against Docker # Hub's registry API 2026-08-24: # sha256:6828ddb2a6a93dec85209b69cd3842bd80094f7dd52f35ee1dd0f9e9165e2188 # - REQUIRE_AUTH=true + PROXY_USER/PROXY_PASSWORD (from the Secret # above) AND ALLOWED_IPS restricted to the home LAN range — both, not # either/or, matching plan.md's "authentication or LAN-restricted # exposure" as a floor, not a ceiling. # - Service is NodePort, not LoadBalancer/Ingress — reachable from the # LAN at :30889, never exposed publicly (no port-forward # on Flint/the router for it, same as the existing gluetun-proxy). # # Browser configuration — this is the part that actually matters for # "DNS must be resolved through the proxy": configuring a SOCKS5 proxy # address alone is NOT enough. By default most browsers resolve # hostnames locally (via the LAN's normal DNS) and only send the already- # resolved IP through the proxy — that leaks every site you visit to # your normal LAN resolver and bypasses Technitium/PIA for DNS entirely, # defeating the point. "socks5h" (vs plain "socks5") in a proxy URL is # the conventional way tools signal "resolve hostnames on the proxy side, # not locally" (e.g. curl --socks5-hostname, or a proxy URL scheme of # socks5h://). In Firefox specifically: Settings -> Network Settings -> # Manual proxy configuration -> SOCKS Host set to this Service -> SOCKS # v5 selected -> the checkbox "Proxy DNS when using SOCKS v5" MUST be # checked, or Firefox resolves locally despite the proxy being SOCKS5. # Verify this actually holds (a DNS leak test site, or packet capture # showing no DNS queries leaving the browser's own host) before trusting # it — do not assume the checkbox alone is sufficient without checking. apiVersion: apps/v1 kind: Deployment metadata: name: browser-vpn-proxy namespace: downloads spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: browser-vpn-proxy template: metadata: labels: app: browser-vpn-proxy annotations: k8s.v1.cni.cncf.io/networks: | [{"name": "vlan50", "namespace": "downloads", "interface": "net1", "ips": ["10.10.50.12/24"]}] spec: nodeSelector: node-role: storage kubernetes.io/hostname: nik-debian dnsPolicy: None dnsConfig: nameservers: - "10.10.40.53" initContainers: - name: vlan50-egress-guard image: nicolaka/netshoot:v0.11 command: ["/bin/sh", "/scripts/guard.sh"] env: - name: VLAN50_GATEWAY value: "10.10.50.1" - name: TECHNITIUM_IP value: "10.10.40.53" - name: POD_CIDR value: "10.42.0.0/16" - name: SERVICE_CIDR value: "10.43.0.0/16" - name: NODE_IP value: "10.10.40.20" securityContext: capabilities: drop: ["ALL"] add: ["NET_ADMIN"] volumeMounts: - name: guard-script mountPath: /scripts containers: - name: socks5 image: "serjs/go-socks5-proxy@sha256:6828ddb2a6a93dec85209b69cd3842bd80094f7dd52f35ee1dd0f9e9165e2188" # v0.0.4 ports: - containerPort: 1080 securityContext: capabilities: drop: ["ALL"] env: - name: REQUIRE_AUTH value: "true" - 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: requests: cpu: 20m memory: 32Mi limits: cpu: 200m memory: 128Mi volumes: - name: guard-script configMap: name: vlan50-egress-guard-script defaultMode: 365 # octal 0555, r-xr-xr-x --- apiVersion: v1 kind: Service metadata: name: browser-vpn-proxy namespace: downloads spec: selector: app: browser-vpn-proxy type: NodePort ports: - name: socks5 port: 1080 targetPort: 1080 nodePort: 30889 # verify unused before applying — see header