Move manifests/multus/reserved/browser-vpn-proxy.yaml into the active multus tree (sync-wave 3) now that qBittorrent and JDownloader have both independently passed their canary, validation, and soak gates - the precondition this file's own header already called for. Add EXPECTED_VLAN50_IP/NET_RAW to its guard init container, matching the fix qBittorrent/JDownloader/canary all needed for the arping-based checks to run. Deliberately no auth (REQUIRE_AUTH=false) and no ALLOWED_IPS, after discussion: access restriction relies on the home network's own firewall (Trusted/Lab -> Lab NodePort ALLOW, other VLANs DENY except Technitium DNS), not an app-level control. Along the way, found and avoided shipping a real bug in the original draft - go-socks5-proxy's ALLOWED_IPS parses values with net.ParseIP (not CIDR-aware) and matches with exact net.IP.Equal(), so the drafted "10.10.40.0/24" value would have produced a nil whitelist entry that matches no real client, rejecting every connection. This library has no way to express a subnet allowlist at all - removed rather than left silently broken. Also drops the now-unneeded browser-vpn-proxy-credentials Secret and its companion script/README references. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
163 lines
6.7 KiB
YAML
163 lines
6.7 KiB
YAML
# 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 <any node IP>: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
|