Some checks failed
validate / lint (push) Failing after 0s
Replace the NodePort (30889) with type: LoadBalancer on the standard SOCKS5 port 1080, at the user's request for a more natural port than a 30000+ NodePort value. loadBalancerIP: 10.10.40.20 (nik-debian) matches values/gitea.yaml's gitea-ssh Service pattern exactly. Documented the actual behavior after checking, not assuming: k3s's ServiceLB (Klipper) binds the port on every node via a DaemonSet by default - loadBalancerIP only sets the reported external IP, it doesn't scope which nodes bind the port. That needs a separate enablelb/lbpool node-label mechanism this repo doesn't use anywhere yet (including on the existing gitea-ssh Service, which likely has the same characteristic, unnoticed until now). So this change doesn't narrow exposure to nik-debian only - it's still reachable at any node IP on port 1080, the same multi-node reachability the NodePort version already had, just a different port number. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
216 lines
10 KiB
YAML
216 lines
10 KiB
YAML
# sync-wave 1 — same wave as the NAD (10-nad-vlan50.yaml), the only
|
|
# thing this actually needs to exist first. NOT wave 3/after-the-canary
|
|
# as originally drafted: that was a documentation convention, not a real
|
|
# dependency, and it turned out to actively break syncing. Root-caused
|
|
# live (2026-08-24): this cluster's argocd-cm sets
|
|
# resource.customizations.ignoreResourceUpdates.all to ignore /status
|
|
# changes cluster-wide (a deliberate, existing setting, not something to
|
|
# change just for this file) — Argo's wave orchestrator waits for each
|
|
# wave's resources to report Healthy before starting the next wave, but
|
|
# a Pod that's deleted and recreated (exactly what the canary is, every
|
|
# test round) needs Argo to observe a fresh Pending -> Running /status
|
|
# transition *during that sync*, which the ignore-updates config
|
|
# prevents it from ever seeing — so the operation hangs forever on
|
|
# "waiting for healthy state of /Pod/vlan50-canary" even once the Pod is
|
|
# actually Running. Already-stable resources (the DaemonSet, RBAC, NAD)
|
|
# never hit this because their cached health from a prior sync is still
|
|
# valid when nothing about them changed. Moved here from
|
|
# manifests/multus/reserved/ (2026-08-24) now that qBittorrent and
|
|
# JDownloader have both independently passed their canary, validation,
|
|
# and soak gates — the reservation this file's earlier header described
|
|
# is satisfied. Still governed only by the `multus` Argo CD Application's
|
|
# own manual-sync gate (no automated sync there at all) — the same gate
|
|
# already used for the DaemonSet, NAD, and canary, not a new mechanism.
|
|
#
|
|
# Before applying:
|
|
# 1. Verify port 1080 below doesn't collide with anything live —
|
|
# confirmed free 2026-08-24 (`kubectl get svc -A`, checked against
|
|
# every live LoadBalancer Service's ports: gitea-ssh uses 2222,
|
|
# traefik uses 80/443).
|
|
#
|
|
# 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/media/
|
|
# 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
|
|
# - No auth (REQUIRE_AUTH=false), no ALLOWED_IPS — a deliberate choice,
|
|
# not an oversight. plan.md originally called for auth AND
|
|
# LAN-restriction together as a floor; discussed and explicitly
|
|
# relaxed (2026-08-24) after confirming the home network's own
|
|
# firewall already restricts this Service's port 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
|
|
# Service actually preserves real client source IPs end-to-end
|
|
# (default `externalTrafficPolicy: Cluster` typically does not —
|
|
# this Service doesn't set `externalTrafficPolicy: Local` either,
|
|
# unlike values/traefik.yaml's LoadBalancer, which does specifically
|
|
# for this reason) rather than assuming it does.
|
|
# - Service is LoadBalancer (k3s's built-in ServiceLB/Klipper), port
|
|
# 1080 directly — not NodePort, at the user's request for a more
|
|
# natural port than a 30000+ NodePort value. `loadBalancerIP:
|
|
# 10.10.40.20` (nik-debian) matches values/gitea.yaml's gitea-ssh
|
|
# Service pattern exactly (`loadBalancerIP: 10.10.40.53`, minisforum)
|
|
# — confirmed by reading how Klipper actually works before adding
|
|
# this, not assumed: `loadBalancerIP` only sets the *reported*
|
|
# external IP (`.status.loadBalancer.ingress[].ip`); the underlying
|
|
# svclb DaemonSet still binds port 1080 on every node by default,
|
|
# same as gitea-ssh's `loadBalancerIP` pin does today. Actually
|
|
# scoping it to nik-debian only needs k3s's separate
|
|
# `svccontroller.k3s.cattle.io/enablelb`/`lbpool` node-label
|
|
# mechanism, which nothing in this repo uses yet — not added here,
|
|
# so this is reachable at <any node IP>:1080, functionally identical
|
|
# multi-node reachability to the NodePort version it replaced, just
|
|
# a different (and more memorable) port number. Never exposed
|
|
# publicly either way (no port-forward on Flint/the router for it,
|
|
# same as the now-decommissioned gluetun-proxy was).
|
|
#
|
|
# 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
|
|
annotations:
|
|
argocd.argoproj.io/sync-wave: "1"
|
|
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"
|
|
- name: EXPECTED_VLAN50_IP
|
|
value: "10.10.50.12"
|
|
securityContext:
|
|
capabilities:
|
|
drop: ["ALL"]
|
|
# 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:
|
|
- 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:
|
|
# 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
|
|
value: "false"
|
|
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: LoadBalancer
|
|
# Pinned to nik-debian's own address, matching values/gitea.yaml's
|
|
# gitea-ssh Service (loadBalancerIP: 10.10.40.53, minisforum) — see the
|
|
# header comment for what this does and does not actually restrict.
|
|
loadBalancerIP: "10.10.40.20"
|
|
ports:
|
|
- name: socks5
|
|
port: 1080
|
|
targetPort: 1080
|