New deployments/squid/: an explicit forward proxy with SSL-bump TLS interception (local CA, generated on first deploy) and hostname-targeted static-content caching. Unlike the other stacks it is a forward proxy, not a Caddy/Let's-Encrypt inbound site. - Self-built minimal Alpine image (apk squid ships ssl-bump); entrypoint renders squid.conf and generates the cache policy from the domain lists. - Wildcard hostname caching (cache-domains.txt leading-dot + optional cache-domains.regex); boost vs strict-allowlist toggle (CACHE_ONLY_LISTED). - Storage gate never caches HTML or dynamic content; query strings exempt on boosted domains so versioned static assets still cache. - splice-domains.txt passthrough for pinned/banking domains. - Deny-by-default http_access (TRUSTED_CIDR) + BIND_ADDR pinning; CA key 0600 on host, never embedded, git-ignored. - Wired into automations.sh, README, .gitignore; cloud-init for fresh VMs.
135 lines
6.1 KiB
Markdown
135 lines
6.1 KiB
Markdown
# 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://<host>:3128
|
|
export https_proxy=http://<host>:3128
|
|
```
|
|
|
|
Per-tool: apt → `Acquire::http(s)::Proxy "http://<host>:3128";`; dnf →
|
|
`proxy=http://<host>: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.
|