Files
automations/deployments/squid/README.md
T
57_Wolve e7ba68790b Done — the Squid deployment is fully built, wired in, and statically verified on the feat/squid-proxy branch. ## What landed deployments/squid/ (13 files) plus wiring into [automations.sh](automations.sh:51), [README.md](README.md), and [.gitignore](.gitignore). All six approved design decisions are implemented: - **Self-built minimal Alpine image** — apk add squid (ssl-bump compiled in), openssl/tini. [entrypoint.sh](deployments/squid/entrypoint.sh) renders squid.conf and generates the cache policy from the lists at start. - **SSL-bump** peek→splice→bump with [splice-domains.txt](deployments/squid/splice-domains.txt) passthrough. - **Wildcard hostname caching** — leading-dot [cache-domains.txt](deployments/squid/cache-domains.txt) + optional regex; CACHE_ONLY_LISTED boost/allowlist toggle. - **HTML/dynamic storage gate**, with query strings exempt on boosted domains. - **CA lifecycle** — generated on first deploy via the built image, key 0600 on the host, staged to tmpfs for the container, never embedded, git-ignored. - **Deny-by-default http_access + BIND_ADDR pinning** as the real access gate (Docker bypasses INPUT). ## One bug caught & fixed The test of the entrypoint's policy generation showed it emitting ubuntu.com with an **unescaped dot** — the s/\./\\./g double-backslash is sed-implementation-dependent and silently failed. Switched to a [.] character class (ubuntu[.]com), portable across busybox/git-bash, re-embedded, re-audited. ## Verified vs. pending **Verified locally:** bash -n on all scripts, LF endings, launcher parses, embedded archive byte-matches the loose files, and the cache-policy generation produces correct Squid directives in both modes. **Pending a Docker host** (this Windows box has none, as the plan anticipated): the container functional smoke test — build, CA mint, a bumped HTTPS fetch, a TCP_HIT, and a splice check. A few version-sensitive specifics get confirmed there: tls-cert=/tls-key= option names, the security_file_certgen path, busybox install -o/-g, ssl::server_name subdomain matching, and the squid -z flag. (All isolated and easy to adjust if any differ.) ## Commit (for you to GPG-sign) Staged and ready. Per your convention I haven't committed — here's the message: `` feat(squid): add SSL-bump caching forward-proxy deployment 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. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> ` `bash git commit -S -F- <<'EOF' <paste message above> EOF ` Want me to **push feat/squid-proxy** once you've committed (you'd open the PR in Gitea), or hold? And if you can point me at a Docker host (or run docker compose build in /srv/squid` after a test deploy), I'll drive the functional smoke test and fix anything the live Squid version flags.
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.
2026-06-22 16:32:25 -05:00

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.