# squid — SSL-bump caching forward proxy A [Squid](https://www.squid-cache.org/) **forward proxy** that caches static content to cut bandwidth across a fleet and **intercepts TLS** (SSL-bump) with a locally-generated CA so HTTPS can be cached and inspected. This is the one deployment that **breaks the repo's Caddy/Let's-Encrypt convention on purpose**: it is not an inbound web service, has no public hostname, and uses no ACME cert. Clients point `HTTP(S)_PROXY` at it and trust its CA. > ⚠️ **TLS interception — read first.** SSL-bump decrypts your clients' HTTPS. > The CA private key it generates can impersonate **any** site to **any** client > that trusts it. Only run this on networks and devices **you own and are > authorized to inspect**. Keep `ssl/squid-ca-key.pem` secret. Cert-pinned and > HSTS sites (banking, app stores) will break unless you **splice** them > (passthrough, see below). ## What it does - **Explicit forward proxy** on port `3128`. Clients set `http_proxy` / `https_proxy` (transparent/intercepting mode is future work — see end). - **SSL-bump**: peek at the TLS SNI → **splice** (passthrough, no decryption) the domains in `splice-domains.txt` → **bump** (decrypt) everything else, minting per-host leaf certs on the fly from the local CA. - **Hostname-targeted caching** with wildcards, and a storage gate that **never caches HTML or dynamic content**. ## Deploy **Via the launcher** (from a clone or the one-liner): pick `deploy: squid`, then answer the prompts (trusted CIDR, etc.). **Standalone** (self-contained `deploy.sh`, scp'd to a host): ```bash TRUSTED_CIDR=100.64.0.0/10 BIND_ADDR=100.64.0.1 SKIP_PROMPTS=1 bash deploy.sh ``` **Fresh VM**: paste [`cloud-init.yml`](cloud-init.yml) as user-data (it hardens SSH first, then deploys). The deploy is idempotent. On first run it builds the local image, generates the CA into `ssl/` (never overwritten), seeds `.env`, registers the port with the host firewall if present, and brings the stack up. ## Point clients at it ```bash export http_proxy=http://:3128 export https_proxy=http://:3128 ``` Per-tool: apt → `Acquire::http(s)::Proxy "http://:3128";`; dnf → `proxy=http://:3128` in `/etc/dnf/dnf.conf`; apk → `http_proxy` env or `--proxy`. **Trust the CA** (so bumped HTTPS validates) — distribute `ssl/squid-ca-cert.pem`: | Client | Install | |---|---| | Debian/Ubuntu | `cp squid-ca-cert.pem /usr/local/share/ca-certificates/squid-ca.crt && update-ca-certificates` | | Alpine | `cp squid-ca-cert.pem /usr/local/share/ca-certificates/squid-ca.crt && update-ca-certificates` | | Alma/RHEL | `cp squid-ca-cert.pem /etc/pki/ca-trust/source/anchors/squid-ca.pem && update-ca-trust` | Browsers, Java, and some language runtimes keep their own trust stores — import there too if needed. ## Caching model Two knobs and three lists, all in the stack dir; they are bind-mounted, so edit then `docker compose restart`. - **`cache-domains.txt`** — hostnames to cache hard (long TTL, force-cache past `Cache-Control: private/no-store`). A **leading dot** is a subdomain wildcard: `.ubuntu.com` matches `ubuntu.com` and every subdomain. - **`cache-domains.regex`** — optional `dstdom_regex` patterns for wildcards *inside* a label (e.g. `^mirror[0-9]+\.example\.com$`). Comments-only = disabled. - **`CACHE_ONLY_LISTED`** (`.env`): - `0` (default, *boost*): cache everything per normal HTTP rules, and force-cache the listed domains aggressively. - `1` (*strict allowlist*): store **only** the listed domains; pass the rest through uncached. **Never cached** (storage gate, applies even to boosted domains): HTML (by `.html` extension and `text/html` content-type) and dynamic content (script endpoints + query strings). Query strings are **exempt on boosted domains**, so versioned static assets like `app.js?v=123` still cache there. ### splice ⇄ cache are mutually exclusive A spliced domain is passed through encrypted — there is nothing to cache or inspect. **Do not list the same domain in both** `splice-domains.txt` and `cache-domains.txt`; splice wins. ## Security posture - **Access control is Squid's `http_access`** (`TRUSTED_CIDR`), deny-by-default. This matters because a **published Docker port bypasses the host `INPUT` firewall** — so also pin **`BIND_ADDR`** to a trusted interface (e.g. your Tailscale IP). The `/etc/firewall/ports.d/squid.rule` entry is belt-and-braces. - **CA key** lives at `ssl/squid-ca-key.pem`, mode `0600` root, mounted read-only; the container stages a squid-readable copy into `/run` (tmpfs) at start. The key never enters the embedded archive and is git-ignored. - **Upstream certs are validated** (`sslproxy_cert_error deny all`) — the proxy won't silently launder a broken origin certificate to clients. ## Caching caveats (be realistic) Even with bump, much of the web is `Cache-Control: private/no-store`, dynamic, or personalized. The real wins are **distro packages** (apk/apt/dnf), **container layers**, **OS updates**, and large static assets across many hosts — which is what the default `cache-domains.txt` targets. It is not a blanket "cache the whole internet." ## Files | File | Purpose | |---|---| | `Dockerfile` | Minimal Alpine image (`apk add squid` — ssl-bump compiled in). | | `entrypoint.sh` | Renders `squid.conf`, generates the cache policy from the lists, stages the CA, inits the cert DB/cache, starts squid. | | `squid.conf.tmpl` | Static config (ports, bump policy, access control, cache sizing). | | `cache-domains.txt` / `.regex` | Wildcard hostnames to cache hard. | | `splice-domains.txt` | Domains to pass through without decrypting. | | `docker-compose.yml` / `.env.example` | Stack definition + tunables. | | `deploy.sh` / `build.sh` | Self-contained installer + archive embedder. | | `cloud-init.yml` | Fresh-VM bootstrap (harden SSH, then deploy). | ## Future work - **Transparent / intercepting mode** — add `intercept`/`tproxy` ports and an iptables `REDIRECT` recipe for when the box is the gateway (clients need no proxy config). Out of scope for v1. - Upstream `cache_peer` chaining; access-log shipping / dashboard.