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.
This commit is contained in:
@@ -19,6 +19,10 @@ id_ed25519
|
||||
id_ed25519_*
|
||||
*.pem
|
||||
|
||||
# Squid TLS-interception CA -- generated on the host at deploy time, never
|
||||
# committed (the private key can MITM any client that trusts it).
|
||||
deployments/squid/ssl/
|
||||
|
||||
# ── Backups ─────────────────────────────────────────────────────────────────
|
||||
*.tar.gz.age
|
||||
*-backup-*.tar.gz*
|
||||
|
||||
@@ -107,11 +107,13 @@ deployments/<name>/ # one folder per stack
|
||||
| [`beszel`](deployments/beszel/) | Server monitoring hub. OIDC via pocket-id (post-deploy). | pocket-id (OIDC, optional) |
|
||||
| [`headscale`](deployments/headscale/) | Self-hosted Tailscale control server, OIDC login. | pocket-id (OIDC) |
|
||||
| [`webfinger`](deployments/webfinger/) | Serves `/.well-known/webfinger` for OIDC discovery; redirects the rest. | pocket-id (issuer) |
|
||||
| [`squid`](deployments/squid/) | SSL-bump caching forward proxy — static-content cache + TLS interception via a local CA. **The exception: a forward proxy, not a Caddy/LE site.** | — |
|
||||
| [`simplex`](deployments/simplex/) | SimpleX SMP + XFTP relay with Tor hidden services + encrypted backups. | globals/age-pubkey.txt |
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Alpine + Docker Compose + Caddy/Let's Encrypt** across every stack.
|
||||
- **Alpine + Docker Compose + Caddy/Let's Encrypt** across every stack (except
|
||||
`squid`, which is a forward proxy with a local TLS-interception CA — no Caddy/LE).
|
||||
- **`build.sh` → `deploy.sh`**: each stack's `build.sh` embeds its
|
||||
`docker-compose.yml` / `Caddyfile` / `.env.example` into a single
|
||||
self-contained `deploy.sh` (base64 tar.gz). That one file can be `scp`'d to a
|
||||
@@ -140,8 +142,8 @@ instance user-data, and the host configures itself on first boot.
|
||||
## Multi-OS notes
|
||||
|
||||
The host-provisioning scripts (`setup-host`, `harden-ssh`, `harden-jumphost`,
|
||||
`sshuser`) and the four Docker stacks (pocket-id, beszel, headscale, webfinger)
|
||||
run on Alpine, Debian, and Alma. Distro differences live in
|
||||
`sshuser`) and the five Docker stacks (pocket-id, beszel, headscale, webfinger,
|
||||
squid) run on Alpine, Debian, and Alma. Distro differences live in
|
||||
[`scripts/oslib.sh`](scripts/oslib.sh) — package manager (`apk`/`apt`/`dnf`),
|
||||
init system (OpenRC/systemd), sshd service name, the per-distro `sftp-server`
|
||||
path, hostname, boot hooks, and the sshguard log source/backend.
|
||||
|
||||
+6
-1
@@ -48,7 +48,7 @@ fi
|
||||
. "$ROOT/scripts/lib.sh"
|
||||
load_globals
|
||||
|
||||
DEPLOYMENTS=(pocket-id beszel headscale webfinger simplex)
|
||||
DEPLOYMENTS=(pocket-id beszel headscale webfinger squid simplex)
|
||||
SCRIPTS=(setup-host harden-ssh harden-jumphost sshuser auto-update)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
@@ -94,6 +94,11 @@ ask_deployment_vars() {
|
||||
ask ISSUER_URL "OIDC issuer URL (e.g. https://auth.example.com)"
|
||||
ask REDIRECT_URL "Redirect target for other traffic (e.g. https://example.org)"
|
||||
ask ACME_EMAIL "Let's Encrypt email" ;;
|
||||
squid)
|
||||
ask TRUSTED_CIDR "Trusted client CIDR(s) allowed to use the proxy (e.g. 100.64.0.0/10)"
|
||||
ask BIND_ADDR "Host IP to bind the proxy on (blank = 0.0.0.0)" optional
|
||||
ask CACHE_SIZE_MB "On-disk cache size in MB (blank = 5000)" optional
|
||||
ask CACHE_ONLY_LISTED "Cache ONLY listed domains? (1=yes, blank=boost mode)" optional ;;
|
||||
simplex)
|
||||
ask DOMAIN "Apex domain (creates smp.DOMAIN, xftp.DOMAIN)"
|
||||
ask ACME_EMAIL "Let's Encrypt email"
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Copy to .env and fill in. docker compose picks .env up automatically.
|
||||
#
|
||||
# Squid SSL-bump caching forward proxy. Unlike the other stacks there is NO
|
||||
# public hostname / Let's Encrypt cert -- this is a forward proxy. The TLS
|
||||
# interception CA is generated on first deploy (deploy.sh) and never overwritten.
|
||||
|
||||
# ─── Who may use the proxy ──────────────────────────────────────────────────
|
||||
# Space-separated CIDR(s) allowed to connect (Squid http_access). This is the
|
||||
# REAL access gate -- keep it tight. Examples: 100.64.0.0/10 (Tailscale CGNAT),
|
||||
# 10.0.0.0/8, 192.168.0.0/16.
|
||||
TRUSTED_CIDR=100.64.0.0/10
|
||||
|
||||
# Host interface/IP to publish the proxy on, and the host-side port. Pin
|
||||
# BIND_ADDR to a trusted interface (e.g. your Tailscale IP) -- a published
|
||||
# Docker port BYPASSES the host INPUT firewall, so 0.0.0.0 exposes the proxy to
|
||||
# every reachable network. Use 0.0.0.0 only if TRUSTED_CIDR + upstream
|
||||
# firewalling already cover you.
|
||||
BIND_ADDR=0.0.0.0
|
||||
PROXY_PORT=3128
|
||||
|
||||
# ─── Cache sizing ───────────────────────────────────────────────────────────
|
||||
CACHE_SIZE_MB=5000 # on-disk cache budget (MB)
|
||||
MAX_OBJECT_SIZE_MB=256 # largest single object to cache (raise for ISOs/images)
|
||||
CACHE_MEM_MB=256 # in-memory hot cache (MB)
|
||||
|
||||
# ─── Cache scope ────────────────────────────────────────────────────────────
|
||||
# 0 = boost mode (default): cache everything per normal HTTP rules, and
|
||||
# force-cache the domains in cache-domains.txt / .regex with long TTLs.
|
||||
# 1 = strict allowlist: store ONLY the listed domains; pass the rest through.
|
||||
CACHE_ONLY_LISTED=0
|
||||
|
||||
# ─── TLS interception CA (generated on first deploy) ────────────────────────
|
||||
CA_CN=Squid Proxy CA
|
||||
CA_O=automations
|
||||
CA_DAYS=3650
|
||||
DYNAMIC_CERT_MEM_MB=8 # in-RAM cache of generated per-host leaf certs (MB)
|
||||
|
||||
# ─── Misc ───────────────────────────────────────────────────────────────────
|
||||
VISIBLE_HOSTNAME=squid-proxy
|
||||
# Local build tag for the image (built from ./Dockerfile).
|
||||
SQUID_IMAGE_TAG=automations/squid:latest
|
||||
@@ -0,0 +1,22 @@
|
||||
# Minimal Squid image with SSL-bump.
|
||||
#
|
||||
# Alpine ships its `squid` package built `--with-openssl`, so ssl-bump,
|
||||
# https_port and security_file_certgen are all compiled in -- no
|
||||
# compile-from-source needed (Debian/Ubuntu, by contrast, build squid against
|
||||
# GnuTLS and need the separate `squid-openssl` package). openssl is included so
|
||||
# deploy.sh can mint the CA via this image without a host openssl dependency.
|
||||
FROM alpine:3.21
|
||||
|
||||
RUN apk add --no-cache squid ca-certificates openssl tini \
|
||||
&& update-ca-certificates
|
||||
|
||||
COPY squid.conf.tmpl /etc/squid/squid.conf.tmpl
|
||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
# Explicit forward-proxy port. Caching/inspection happen here; clients set
|
||||
# HTTP(S)_PROXY to this host:3128.
|
||||
EXPOSE 3128
|
||||
|
||||
# tini reaps zombies and forwards signals so `docker stop` shuts squid cleanly.
|
||||
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/entrypoint.sh"]
|
||||
@@ -0,0 +1,134 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# build.sh -- (re)embed the loose deployment files into deploy.sh as a base64
|
||||
# tar.gz payload after __ARCHIVE_BELOW__. Idempotent: strips any existing
|
||||
# payload first.
|
||||
#
|
||||
# Run this after editing ANY embedded file below, then re-stage deploy.sh --
|
||||
# the deployed stack uses the EMBEDDED copies, not the loose files.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
SCRIPT="$DIR/deploy.sh"
|
||||
MARKER="__ARCHIVE_BELOW__"
|
||||
|
||||
# The CA (ssl/), README, cloud-init, and the build/deploy scripts are NOT
|
||||
# embedded -- the CA is generated on the host at deploy time.
|
||||
FILES=(
|
||||
docker-compose.yml
|
||||
Dockerfile
|
||||
entrypoint.sh
|
||||
squid.conf.tmpl
|
||||
splice-domains.txt
|
||||
cache-domains.txt
|
||||
cache-domains.regex
|
||||
.env.example
|
||||
)
|
||||
|
||||
[[ -f "$SCRIPT" ]] || { echo "deploy.sh not found at $SCRIPT" >&2; exit 1; }
|
||||
for f in "${FILES[@]}"; do
|
||||
[[ -f "$DIR/$f" ]] || { echo "Missing $DIR/$f" >&2; exit 1; }
|
||||
done
|
||||
|
||||
PAYLOAD=$(tar -czf - -C "$DIR" "${FILES[@]}" | base64)
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap 'rm -f "$TMP"' EXIT
|
||||
|
||||
sed "/^${MARKER}\$/,\$d" "$SCRIPT" > "$TMP"
|
||||
{
|
||||
echo "$MARKER"
|
||||
echo "$PAYLOAD"
|
||||
} >> "$TMP"
|
||||
|
||||
mv "$TMP" "$SCRIPT"
|
||||
chmod +x "$SCRIPT"
|
||||
trap - EXIT
|
||||
|
||||
size=$(wc -c < "$SCRIPT")
|
||||
echo "Built $SCRIPT (${size} bytes)"
|
||||
@@ -0,0 +1,14 @@
|
||||
# cache-domains.regex
|
||||
#
|
||||
# OPTIONAL. dstdom_regex patterns for glob-style hostnames that the leading-dot
|
||||
# matching in cache-domains.txt can't express (e.g. a wildcard INSIDE a label).
|
||||
# One POSIX extended regex per line, matched case-insensitively against the
|
||||
# request hostname. Full-line "#" comments only.
|
||||
#
|
||||
# Leave this file with comments only to disable it -- the entrypoint skips it
|
||||
# when there are no active entries (an empty ACL would otherwise error).
|
||||
#
|
||||
# Examples (uncomment / adapt):
|
||||
# ^mirror[0-9]+\.example\.com$
|
||||
# ^cdn-.*\.fastly\.net$
|
||||
# ^.*\.pkg\.dev$
|
||||
@@ -0,0 +1,42 @@
|
||||
# cache-domains.txt
|
||||
#
|
||||
# Hostnames to cache aggressively (long TTL + force-cache, overriding
|
||||
# Cache-Control: private/no-store). One entry per line; a LEADING DOT matches
|
||||
# the domain and all subdomains (.ubuntu.com matches archive.ubuntu.com,
|
||||
# security.ubuntu.com, ...). For wildcards INSIDE a label, use
|
||||
# cache-domains.regex instead.
|
||||
#
|
||||
# These are BUMPED (decrypted) so HTTPS bodies can be cached -- do not also
|
||||
# list them in splice-domains.txt. HTML and dynamic content are still skipped
|
||||
# even here (see the storage gate); only static objects are stored.
|
||||
#
|
||||
# Defaults target high-bandwidth distro / package / container mirrors for a
|
||||
# fleet of VMs. Trim or extend for your environment.
|
||||
|
||||
# ── Debian / Ubuntu ──
|
||||
.ubuntu.com
|
||||
.debian.org
|
||||
.launchpad.net
|
||||
|
||||
# ── Alpine ──
|
||||
.alpinelinux.org
|
||||
|
||||
# ── RHEL family (Alma / Rocky / Fedora / EPEL) ──
|
||||
.almalinux.org
|
||||
.rockylinux.org
|
||||
.fedoraproject.org
|
||||
.centos.org
|
||||
|
||||
# ── Container registries (layer blobs) ──
|
||||
.docker.io
|
||||
.docker.com
|
||||
.ghcr.io
|
||||
.quay.io
|
||||
.gcr.io
|
||||
.k8s.io
|
||||
|
||||
# ── Language package registries ──
|
||||
.pypi.org
|
||||
.pythonhosted.org
|
||||
.npmjs.org
|
||||
.crates.io
|
||||
@@ -0,0 +1,41 @@
|
||||
#cloud-config
|
||||
#
|
||||
# Squid SSL-bump caching proxy — harden SSH, then deploy, on a fresh host.
|
||||
#
|
||||
# Fill in REPO_URL and the values in the runcmd block, then paste this as the
|
||||
# instance user-data. Unlike the web stacks this is a FORWARD proxy: no public
|
||||
# DNS record or Let's Encrypt cert is needed, but clients must be able to reach
|
||||
# TRUSTED_CIDR and must trust the CA this generates on first boot.
|
||||
#
|
||||
# Only deploy this on networks/devices you own and are authorized to inspect.
|
||||
|
||||
packages:
|
||||
- git
|
||||
|
||||
runcmd:
|
||||
- hostnamectl set-hostname squid || true
|
||||
- |
|
||||
set -e
|
||||
REPO_URL=https://git.anomalous.dev/57_Wolve/automations.git
|
||||
REPO_BRANCH=main
|
||||
HARDEN_SSH=1 # harden SSH on this fresh VM (set 0 to skip)
|
||||
SSH_PORT=22
|
||||
ALLOWED_IP= # optional: whitelist your client IP in sshguard
|
||||
git clone --depth 1 --branch "$REPO_BRANCH" "$REPO_URL" /opt/automations
|
||||
cd /opt/automations
|
||||
|
||||
# Harden SSH first (PQ KEX, key-only auth, sshguard + deny-by-default
|
||||
# firewall). The firewall it installs is what deploy.sh registers the proxy
|
||||
# port with.
|
||||
if [ "$HARDEN_SSH" = 1 ]; then
|
||||
SSH_PORT="$SSH_PORT" ALLOWED_IP="$ALLOWED_IP" SKIP_PROMPTS=1 FORCE=1 \
|
||||
bash scripts/harden-ssh.sh
|
||||
fi
|
||||
|
||||
# Deploy the proxy. Set TRUSTED_CIDR to the network allowed to use it, and
|
||||
# BIND_ADDR to a trusted interface (a published Docker port bypasses the
|
||||
# host firewall, so this is the real exposure control).
|
||||
TRUSTED_CIDR=100.64.0.0/10 \
|
||||
BIND_ADDR=0.0.0.0 \
|
||||
SKIP_PROMPTS=1 \
|
||||
bash deployments/squid/deploy.sh
|
||||
@@ -0,0 +1,453 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# deploy.sh -- deploy the Squid SSL-bump caching forward proxy.
|
||||
#
|
||||
# What this does:
|
||||
# 1. Installs docker + compose if missing.
|
||||
# 2. Lays down the stack files in $STACK_DIR and builds the local image.
|
||||
# 3. Generates the TLS interception CA on first run (never overwritten).
|
||||
# 4. Generates .env on first run; existing .env is never overwritten.
|
||||
# 5. Prompts for required values not preset (TRUSTED_CIDR).
|
||||
# 6. Registers the proxy port with the host firewall if present.
|
||||
# 7. Brings the stack up and waits for health.
|
||||
#
|
||||
# Role: this is a FORWARD proxy, not an inbound web service -- there is no
|
||||
# public hostname or Let's Encrypt cert. Clients set HTTP(S)_PROXY to this host
|
||||
# and trust the generated CA. Only run it on networks/devices you own.
|
||||
#
|
||||
# Idempotent: re-run to apply config changes / rebuild the image.
|
||||
#
|
||||
# Self-contained: the compose file, Dockerfile, entrypoint, config template and
|
||||
# domain lists are embedded as a base64 tar.gz at the bottom of this file.
|
||||
# Rebuild with build.sh after editing the loose source files.
|
||||
#
|
||||
# Usage:
|
||||
# bash deploy.sh # interactive prompts
|
||||
# TRUSTED_CIDR=10.0.0.0/8 BIND_ADDR=100.64.0.1 \
|
||||
# SKIP_PROMPTS=1 bash deploy.sh # non-interactive
|
||||
# STACK_DIR=/opt/squid bash deploy.sh
|
||||
# SKIP_DOCKER_INSTALL=1 bash deploy.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${STACK_DIR:=/srv/squid}"
|
||||
: "${SKIP_DOCKER_INSTALL:=0}"
|
||||
: "${FORCE:=0}"
|
||||
: "${SKIP_PROMPTS:=0}" # non-interactive: require values via env, no prompts
|
||||
[[ "$SKIP_PROMPTS" == "1" ]] && FORCE=1
|
||||
: "${TRUSTED_CIDR:=}"
|
||||
: "${BIND_ADDR:=}"
|
||||
: "${PROXY_PORT:=3128}"
|
||||
: "${CACHE_SIZE_MB:=}"
|
||||
: "${CACHE_ONLY_LISTED:=}"
|
||||
: "${CA_CN:=Squid Proxy CA}"
|
||||
: "${CA_O:=automations}"
|
||||
: "${CA_DAYS:=3650}"
|
||||
: "${SQUID_IMAGE_TAG:=automations/squid:latest}"
|
||||
|
||||
log() { printf '\033[1;32m[+]\033[0m %s\n' "$*"; }
|
||||
warn() { printf '\033[1;33m[!]\033[0m %s\n' "$*" >&2; }
|
||||
die() { printf '\033[1;31m[x]\033[0m %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
[[ $EUID -eq 0 ]] || die "Run as root."
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OS detection + Docker install (Alpine / Debian / Alma). This deploy.sh is
|
||||
# self-contained (scp'd standalone), so the OS logic is inlined here instead
|
||||
# of sourced from scripts/oslib.sh.
|
||||
# ---------------------------------------------------------------------------
|
||||
osfam() {
|
||||
local id="" like=""
|
||||
if [[ -r /etc/os-release ]]; then
|
||||
id="$(. /etc/os-release 2>/dev/null && echo "${ID:-}")"
|
||||
like="$(. /etc/os-release 2>/dev/null && echo "${ID_LIKE:-}")"
|
||||
fi
|
||||
case " $id $like " in
|
||||
*" alpine "*) echo alpine ;;
|
||||
*" debian "*|*" ubuntu "*) echo debian ;;
|
||||
*" rhel "*|*" fedora "*|*" centos "*) echo rhel ;;
|
||||
*) echo "${id:-unknown}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
install_docker() {
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
log "Docker already installed: $(docker --version)"
|
||||
else
|
||||
log "Installing Docker (OS: $(osfam))..."
|
||||
case "$(osfam)" in
|
||||
alpine) apk add -q docker docker-cli-compose openrc ;;
|
||||
debian|rhel) command -v curl >/dev/null 2>&1 || \
|
||||
{ command -v apt-get >/dev/null 2>&1 && apt-get install -y -qq curl; } || \
|
||||
{ command -v dnf >/dev/null 2>&1 && dnf install -y -q curl; }
|
||||
curl -fsSL https://get.docker.com | sh ;;
|
||||
*) die "Unsupported OS for auto Docker install. Set SKIP_DOCKER_INSTALL=1 and install Docker yourself." ;;
|
||||
esac
|
||||
fi
|
||||
if command -v rc-update >/dev/null 2>&1; then
|
||||
rc-update add docker default >/dev/null 2>&1 || true
|
||||
rc-service docker status >/dev/null 2>&1 || rc-service docker start
|
||||
elif command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl enable --now docker >/dev/null 2>&1 || systemctl start docker || true
|
||||
fi
|
||||
}
|
||||
|
||||
open_proxy_port() {
|
||||
# Register the proxy port. Prefer the host firewall (harden-firewall.sh)
|
||||
# when present; else ufw/firewalld if active.
|
||||
#
|
||||
# NOTE: a Docker-published port reaches the host through nat/FORWARD and
|
||||
# BYPASSES the INPUT firewall, so this is belt-and-braces -- the real access
|
||||
# gate is Squid's http_access (TRUSTED_CIDR) plus pinning BIND_ADDR to a
|
||||
# trusted interface. The ports.d format is port-only (no source CIDR), so
|
||||
# restrict the source via BIND_ADDR / upstream firewalling, not here.
|
||||
local port="${PROXY_PORT:-3128}"
|
||||
if [[ -d /etc/firewall/ports.d && -x /usr/local/sbin/firewall-apply ]]; then
|
||||
log "Registering ${port}/tcp with host firewall..."
|
||||
printf '%s/tcp\n' "$port" > /etc/firewall/ports.d/squid.rule
|
||||
/usr/local/sbin/firewall-apply
|
||||
elif command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then
|
||||
log "ufw active -- allowing ${port}/tcp..."
|
||||
ufw allow "${port}/tcp" >/dev/null
|
||||
elif command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then
|
||||
log "firewalld active -- allowing ${port}/tcp..."
|
||||
firewall-cmd -q --add-port="${port}/tcp" --permanent
|
||||
firewall-cmd -q --reload
|
||||
fi
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Extract embedded archive
|
||||
# ----------------------------------------------------------------------------
|
||||
SCRIPT_DIR=$(mktemp -d -t squid-deploy.XXXXXX)
|
||||
trap 'rm -rf "$SCRIPT_DIR"' EXIT
|
||||
|
||||
extract_archive() {
|
||||
grep -a -A 9999999 '^__ARCHIVE_BELOW__$' "$0" \
|
||||
| tail -n +2 \
|
||||
| base64 -d \
|
||||
| tar -xz -C "$SCRIPT_DIR"
|
||||
}
|
||||
|
||||
if grep -q -a '^__ARCHIVE_BELOW__$' "$0"; then
|
||||
log "Extracting embedded deployment files..."
|
||||
extract_archive
|
||||
else
|
||||
die "No embedded archive found. Run build.sh to embed deployment files."
|
||||
fi
|
||||
|
||||
EMBEDDED=(docker-compose.yml Dockerfile entrypoint.sh squid.conf.tmpl
|
||||
splice-domains.txt cache-domains.txt cache-domains.regex .env.example)
|
||||
for f in "${EMBEDDED[@]}"; do
|
||||
[[ -f "$SCRIPT_DIR/$f" ]] || die "Embedded archive missing $f"
|
||||
done
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Prompt for required vars
|
||||
# ----------------------------------------------------------------------------
|
||||
prompt() {
|
||||
local varname="$1" message="$2"
|
||||
local -n ref="$varname"
|
||||
if [[ -z "${ref:-}" ]]; then
|
||||
[[ "$SKIP_PROMPTS" == "1" ]] && die "$varname required (set it in the environment; running with SKIP_PROMPTS=1)."
|
||||
read -r -p "$message: " ref
|
||||
[[ -n "$ref" ]] || die "$varname required."
|
||||
fi
|
||||
}
|
||||
|
||||
prompt TRUSTED_CIDR "Trusted client CIDR(s) allowed to use the proxy (e.g. 100.64.0.0/10)"
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Docker + firewall
|
||||
# ----------------------------------------------------------------------------
|
||||
if [[ "$SKIP_DOCKER_INSTALL" != "1" ]]; then
|
||||
install_docker
|
||||
fi
|
||||
open_proxy_port
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Stack directory + files
|
||||
# ----------------------------------------------------------------------------
|
||||
log "Setting up $STACK_DIR..."
|
||||
install -d -m 0750 "$STACK_DIR"
|
||||
install -m 0644 "$SCRIPT_DIR/docker-compose.yml" "$STACK_DIR/docker-compose.yml"
|
||||
install -m 0644 "$SCRIPT_DIR/Dockerfile" "$STACK_DIR/Dockerfile"
|
||||
install -m 0755 "$SCRIPT_DIR/entrypoint.sh" "$STACK_DIR/entrypoint.sh"
|
||||
install -m 0644 "$SCRIPT_DIR/squid.conf.tmpl" "$STACK_DIR/squid.conf.tmpl"
|
||||
install -m 0644 "$SCRIPT_DIR/splice-domains.txt" "$STACK_DIR/splice-domains.txt"
|
||||
install -m 0644 "$SCRIPT_DIR/cache-domains.txt" "$STACK_DIR/cache-domains.txt"
|
||||
install -m 0644 "$SCRIPT_DIR/cache-domains.regex" "$STACK_DIR/cache-domains.regex"
|
||||
|
||||
ENV_FILE="$STACK_DIR/.env"
|
||||
set_env() { # <KEY> <value>: update KEY in .env, or append if absent
|
||||
local key="$1" val="$2" esc
|
||||
esc=${val//\\/\\\\}; esc=${esc//|/\\|}; esc=${esc//&/\\&}
|
||||
if grep -qE "^${key}=" "$ENV_FILE"; then
|
||||
sed -i -e "s|^${key}=.*|${key}=${esc}|" "$ENV_FILE"
|
||||
else
|
||||
printf '%s=%s\n' "$key" "$val" >> "$ENV_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
log "Seeding $ENV_FILE..."
|
||||
install -m 0600 "$SCRIPT_DIR/.env.example" "$ENV_FILE"
|
||||
set_env TRUSTED_CIDR "$TRUSTED_CIDR"
|
||||
if [[ -n "${BIND_ADDR:-}" ]]; then set_env BIND_ADDR "$BIND_ADDR"; fi
|
||||
if [[ -n "${PROXY_PORT:-}" ]]; then set_env PROXY_PORT "$PROXY_PORT"; fi
|
||||
if [[ -n "${CACHE_SIZE_MB:-}" ]]; then set_env CACHE_SIZE_MB "$CACHE_SIZE_MB"; fi
|
||||
if [[ -n "${CACHE_ONLY_LISTED:-}" ]]; then set_env CACHE_ONLY_LISTED "$CACHE_ONLY_LISTED"; fi
|
||||
set_env CA_CN "$CA_CN"
|
||||
set_env CA_O "$CA_O"
|
||||
set_env SQUID_IMAGE_TAG "$SQUID_IMAGE_TAG"
|
||||
else
|
||||
log ".env exists; leaving it alone."
|
||||
fi
|
||||
|
||||
# Validate required value landed.
|
||||
grep -E "^TRUSTED_CIDR=.+$" "$ENV_FILE" >/dev/null || die "TRUSTED_CIDR missing in $ENV_FILE."
|
||||
|
||||
# Use the image tag actually recorded in .env (keeps build + CA-gen in sync).
|
||||
IMAGE=$(grep -E '^SQUID_IMAGE_TAG=' "$ENV_FILE" | head -n1 | cut -d= -f2-)
|
||||
IMAGE=${IMAGE:-$SQUID_IMAGE_TAG}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Confirm (interception is a big deal)
|
||||
# ----------------------------------------------------------------------------
|
||||
if [[ "$FORCE" != "1" ]]; then
|
||||
cat <<EOF
|
||||
|
||||
About to build the Squid image and start an SSL-bump forward proxy from
|
||||
$STACK_DIR.
|
||||
|
||||
This generates a local CA and INTERCEPTS TLS for every client that trusts it
|
||||
(except spliced domains). Only do this on networks/devices you own and are
|
||||
authorized to inspect. Clients reach it at: http://<this-host>:${PROXY_PORT}
|
||||
|
||||
Continue? [y/N]
|
||||
EOF
|
||||
read -r ans
|
||||
[[ "${ans,,}" == "y" || "${ans,,}" == "yes" ]] || { warn "Aborted."; exit 0; }
|
||||
fi
|
||||
|
||||
cd "$STACK_DIR"
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Build image, then generate the CA (needs the built image's openssl)
|
||||
# ----------------------------------------------------------------------------
|
||||
log "Building image $IMAGE..."
|
||||
docker compose build
|
||||
|
||||
SSL_DIR="$STACK_DIR/ssl"
|
||||
install -d -m 0750 "$SSL_DIR"
|
||||
if [[ ! -f "$SSL_DIR/squid-ca-key.pem" || ! -f "$SSL_DIR/squid-ca-cert.pem" ]]; then
|
||||
log "Generating TLS interception CA (one-time; CN=${CA_CN})..."
|
||||
docker run --rm -v "$SSL_DIR:/out" --entrypoint openssl "$IMAGE" \
|
||||
req -x509 -newkey rsa:4096 -sha256 -days "$CA_DAYS" -nodes \
|
||||
-keyout /out/squid-ca-key.pem -out /out/squid-ca-cert.pem \
|
||||
-subj "/CN=${CA_CN}/O=${CA_O}" \
|
||||
-addext "basicConstraints=critical,CA:TRUE" \
|
||||
-addext "keyUsage=critical,keyCertSign,cRLSign"
|
||||
chmod 0600 "$SSL_DIR/squid-ca-key.pem"
|
||||
chmod 0644 "$SSL_DIR/squid-ca-cert.pem"
|
||||
log "CA created. Distribute $SSL_DIR/squid-ca-cert.pem to client trust stores."
|
||||
else
|
||||
log "CA already present in $SSL_DIR; leaving it."
|
||||
fi
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Bring up the stack
|
||||
# ----------------------------------------------------------------------------
|
||||
log "Starting stack..."
|
||||
docker compose up -d --remove-orphans
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Wait for health
|
||||
# ----------------------------------------------------------------------------
|
||||
log "Waiting for squid to become healthy (up to 120s)..."
|
||||
deadline=$(( $(date +%s) + 120 ))
|
||||
while (( $(date +%s) < deadline )); do
|
||||
status=$(docker compose ps --format '{{.Service}} {{.Health}}' 2>/dev/null || true)
|
||||
unhealthy=$(echo "$status" | awk '$2 != "healthy" && $2 != "" {print $1}')
|
||||
if [[ -z "$unhealthy" && -n "$status" ]]; then
|
||||
log "Squid healthy."
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
echo
|
||||
log "Stack status:"
|
||||
docker compose ps
|
||||
echo
|
||||
CERT="$SSL_DIR/squid-ca-cert.pem"
|
||||
cat <<EOF
|
||||
================================================================
|
||||
DEPLOYED -- Squid SSL-bump caching forward proxy
|
||||
|
||||
Proxy: http://<this-host>:${PROXY_PORT}
|
||||
Stack dir: ${STACK_DIR}
|
||||
CA cert: ${CERT}
|
||||
|
||||
1. Point clients at the proxy:
|
||||
export http_proxy=http://<this-host>:${PROXY_PORT}
|
||||
export https_proxy=http://<this-host>:${PROXY_PORT}
|
||||
(apk: /etc/apk/repositories via http_proxy; apt: Acquire::http(s)::Proxy;
|
||||
dnf: proxy= in /etc/dnf/dnf.conf)
|
||||
|
||||
2. Trust the CA on each client (so bumped HTTPS validates):
|
||||
Debian/Ubuntu: cp ${CERT##*/} /usr/local/share/ca-certificates/squid-ca.crt && update-ca-certificates
|
||||
Alpine: cp ${CERT##*/} /usr/local/share/ca-certificates/squid-ca.crt && update-ca-certificates
|
||||
Alma/RHEL: cp ${CERT##*/} /etc/pki/ca-trust/source/anchors/squid-ca.pem && update-ca-trust
|
||||
Browsers/Java keep their own trust stores -- import there too if needed.
|
||||
|
||||
3. Tune behavior, then re-render (lists are bind-mounted):
|
||||
${STACK_DIR}/cache-domains.txt domains to cache hard (wildcards)
|
||||
${STACK_DIR}/cache-domains.regex glob/regex cache patterns (optional)
|
||||
${STACK_DIR}/splice-domains.txt domains to NOT decrypt
|
||||
edit, then: cd ${STACK_DIR} && docker compose restart
|
||||
|
||||
Logs / cache stats:
|
||||
docker compose logs -f
|
||||
docker exec squid squid -k parse # validate config
|
||||
docker exec squid tail -f /var/log/squid/access.log # TCP_HIT / TCP_MISS
|
||||
|
||||
Manage:
|
||||
docker compose pull >/dev/null; docker compose build && docker compose up -d # update
|
||||
docker compose down # stop, keep cache + CA
|
||||
docker compose down -v # stop, WIPE cache + leaf-cert DB (CA on host survives)
|
||||
|
||||
SECURITY: keep ${SSL_DIR}/squid-ca-key.pem secret (it can MITM every client
|
||||
that trusts it). It is 0600 root and never leaves this host. Only intercept
|
||||
traffic on networks/devices you own.
|
||||
|
||||
Or just re-run this script -- it's idempotent.
|
||||
================================================================
|
||||
EOF
|
||||
|
||||
# IMPORTANT: do not put any code below this exit. Everything after the
|
||||
# __ARCHIVE_BELOW__ marker is the embedded tar.gz payload (base64), appended by
|
||||
# build.sh.
|
||||
exit 0
|
||||
__ARCHIVE_BELOW__
|
||||
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
|
||||
@@ -0,0 +1,51 @@
|
||||
# Squid SSL-bump caching forward proxy.
|
||||
#
|
||||
# Built locally from ./Dockerfile (the repo's one self-built image) -- there is
|
||||
# no upstream ssl-bump image we want to trust in a hardened setup. The TLS
|
||||
# interception CA lives on the host in ./ssl (generated by deploy.sh, mounted
|
||||
# read-only); the leaf-cert DB, cache, and logs are named volumes.
|
||||
#
|
||||
# A published Docker port BYPASSES the host INPUT firewall, so BIND_ADDR should
|
||||
# pin the listener to a trusted interface and TRUSTED_CIDR (Squid http_access)
|
||||
# is the real access gate.
|
||||
|
||||
name: squid
|
||||
|
||||
volumes:
|
||||
squid-cache:
|
||||
squid-ssl-db:
|
||||
squid-logs:
|
||||
|
||||
services:
|
||||
squid:
|
||||
build:
|
||||
context: .
|
||||
image: ${SQUID_IMAGE_TAG:-automations/squid:latest}
|
||||
container_name: squid
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${BIND_ADDR:-0.0.0.0}:${PROXY_PORT:-3128}:3128"
|
||||
environment:
|
||||
TRUSTED_CIDR: "${TRUSTED_CIDR}"
|
||||
CACHE_SIZE_MB: "${CACHE_SIZE_MB:-5000}"
|
||||
MAX_OBJECT_SIZE_MB: "${MAX_OBJECT_SIZE_MB:-256}"
|
||||
CACHE_MEM_MB: "${CACHE_MEM_MB:-256}"
|
||||
DYNAMIC_CERT_MEM_MB: "${DYNAMIC_CERT_MEM_MB:-8}"
|
||||
CACHE_ONLY_LISTED: "${CACHE_ONLY_LISTED:-0}"
|
||||
VISIBLE_HOSTNAME: "${VISIBLE_HOSTNAME:-squid-proxy}"
|
||||
volumes:
|
||||
- ./ssl:/etc/squid/ssl:ro
|
||||
- ./splice-domains.txt:/etc/squid/splice-domains.txt:ro
|
||||
- ./cache-domains.txt:/etc/squid/cache-domains.txt:ro
|
||||
- ./cache-domains.regex:/etc/squid/cache-domains.regex:ro
|
||||
- squid-cache:/var/cache/squid
|
||||
- squid-ssl-db:/var/lib/squid/ssl_db
|
||||
- squid-logs:/var/log/squid
|
||||
healthcheck:
|
||||
# `squid -k check` signals the running master process via its pid file --
|
||||
# reliable and always present (no dependency on squidclient/nc).
|
||||
test: ["CMD-SHELL", "squid -k check -f /etc/squid/squid.conf || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
@@ -0,0 +1,145 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# entrypoint.sh -- container start-up for the Squid SSL-bump caching proxy.
|
||||
#
|
||||
# 1. Render /etc/squid/squid.conf from squid.conf.tmpl (@VAR@ -> env).
|
||||
# 2. Generate the cache-policy include from cache-domains.txt / .regex and the
|
||||
# CACHE_ONLY_LISTED toggle (boost ACLs, storage gate, per-domain TTLs).
|
||||
# 3. Stage the CA into a squid-readable tmpfs copy (host key stays 0600 root).
|
||||
# 4. Init the dynamic-cert DB + cache_dir structure (idempotent).
|
||||
# 5. Validate the config, then exec squid in the foreground.
|
||||
#
|
||||
# Runs as root (needs chown/install); squid then drops to cache_effective_user.
|
||||
|
||||
set -eu
|
||||
|
||||
TMPL=/etc/squid/squid.conf.tmpl
|
||||
CONF=/etc/squid/squid.conf
|
||||
GEN_DIR=/etc/squid/conf.d
|
||||
GEN="$GEN_DIR/cache-policy.conf"
|
||||
SSL_SRC=/etc/squid/ssl
|
||||
SSL_RUN=/run/squid
|
||||
SSL_DB=/var/lib/squid/ssl_db
|
||||
CACHE_DOMAINS=/etc/squid/cache-domains.txt
|
||||
CACHE_REGEX=/etc/squid/cache-domains.regex
|
||||
|
||||
# Tunables -- docker-compose passes these from .env; defaults keep the image
|
||||
# runnable on its own for a smoke test.
|
||||
: "${TRUSTED_CIDR:=127.0.0.1/32}"
|
||||
: "${CACHE_SIZE_MB:=5000}"
|
||||
: "${MAX_OBJECT_SIZE_MB:=256}"
|
||||
: "${CACHE_MEM_MB:=256}"
|
||||
: "${DYNAMIC_CERT_MEM_MB:=8}"
|
||||
: "${CACHE_ONLY_LISTED:=0}"
|
||||
: "${VISIBLE_HOSTNAME:=squid-proxy}"
|
||||
|
||||
log() { printf '[entrypoint] %s\n' "$*" >&2; }
|
||||
|
||||
# A list file "has entries" if it holds >=1 non-blank, non-comment line.
|
||||
has_entries() { [ -f "$1" ] && grep -qE '^[[:space:]]*[^#[:space:]]' "$1"; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. Render the static config from the template (@VAR@ placeholders).
|
||||
# ---------------------------------------------------------------------------
|
||||
sed \
|
||||
-e "s|@TRUSTED_CIDR@|${TRUSTED_CIDR}|g" \
|
||||
-e "s|@CACHE_SIZE_MB@|${CACHE_SIZE_MB}|g" \
|
||||
-e "s|@MAX_OBJECT_SIZE_MB@|${MAX_OBJECT_SIZE_MB}|g" \
|
||||
-e "s|@CACHE_MEM_MB@|${CACHE_MEM_MB}|g" \
|
||||
-e "s|@DYNAMIC_CERT_MEM_MB@|${DYNAMIC_CERT_MEM_MB}|g" \
|
||||
-e "s|@VISIBLE_HOSTNAME@|${VISIBLE_HOSTNAME}|g" \
|
||||
"$TMPL" > "$CONF"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. Generate the cache-policy include from the domain lists + toggle.
|
||||
# Storage gate runs first (whether to store); per-domain refresh_patterns
|
||||
# only tune freshness of what survived the gate.
|
||||
# ---------------------------------------------------------------------------
|
||||
mkdir -p "$GEN_DIR"
|
||||
{
|
||||
echo "# AUTO-GENERATED by entrypoint.sh at container start -- do not edit."
|
||||
echo "# Source: cache-domains.txt / cache-domains.regex + CACHE_ONLY_LISTED."
|
||||
echo
|
||||
|
||||
# Boost ACLs are only emitted when their backing file is non-empty, else
|
||||
# squid would error on an empty ACL.
|
||||
exc=""
|
||||
if has_entries "$CACHE_DOMAINS"; then
|
||||
echo 'acl boost dstdomain "/etc/squid/cache-domains.txt"'
|
||||
exc="$exc !boost"
|
||||
fi
|
||||
if has_entries "$CACHE_REGEX"; then
|
||||
echo 'acl boost_re dstdom_regex "/etc/squid/cache-domains.regex"'
|
||||
exc="$exc !boost_re"
|
||||
fi
|
||||
|
||||
cat <<'ACLS'
|
||||
acl has_query urlpath_regex \?
|
||||
acl dyn_ext urlpath_regex (/cgi-bin/|\.(cgi|php|aspx?|jsp)$)
|
||||
acl html_ext urlpath_regex \.html?(\?|$)
|
||||
acl html_ct rep_mime_type -i ^text/html
|
||||
|
||||
# Storage gate: never store HTML (by ext or content-type) or dynamic content.
|
||||
cache deny html_ext
|
||||
cache deny dyn_ext
|
||||
ACLS
|
||||
# Query strings are dynamic EXCEPT on boosted domains (versioned static
|
||||
# assets like app.js?v=123 should still cache there).
|
||||
echo "cache deny has_query${exc}"
|
||||
echo 'store_miss deny html_ct'
|
||||
|
||||
if [ "$CACHE_ONLY_LISTED" = "1" ]; then
|
||||
echo
|
||||
echo "# CACHE_ONLY_LISTED=1: store ONLY the listed domains."
|
||||
if has_entries "$CACHE_DOMAINS"; then echo 'cache allow boost'; fi
|
||||
if has_entries "$CACHE_REGEX"; then echo 'cache allow boost_re'; fi
|
||||
echo 'cache deny all'
|
||||
fi
|
||||
|
||||
if has_entries "$CACHE_DOMAINS"; then
|
||||
echo
|
||||
echo "# Boost: long TTL + force-cache for each listed wildcard domain."
|
||||
grep -E '^[[:space:]]*[^#[:space:]]' "$CACHE_DOMAINS" | while read -r dom _rest; do
|
||||
# ".example.com" / "example.com" -> "example[.]com" ([.] matches a
|
||||
# literal dot portably -- avoids sed backslash-escaping quirks).
|
||||
d=$(printf '%s' "$dom" | sed 's/^\.//; s/\./[.]/g')
|
||||
printf 'refresh_pattern -i %s 1440 100%% 43200 override-expire ignore-private ignore-no-store override-lastmod\n' \
|
||||
"^https?://([^/]+[.])?${d}/"
|
||||
done
|
||||
fi
|
||||
} > "$GEN"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. Stage the CA into a squid-readable tmpfs copy. The host key is 0600 root
|
||||
# (mounted read-only); root copies it to /run owned by squid so the signer
|
||||
# can read it without loosening the host file.
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ ! -f "$SSL_SRC/squid-ca-cert.pem" ] || [ ! -f "$SSL_SRC/squid-ca-key.pem" ]; then
|
||||
log "FATAL: CA missing in $SSL_SRC (need squid-ca-cert.pem + squid-ca-key.pem)."
|
||||
exit 1
|
||||
fi
|
||||
install -d -m 0710 -o squid -g squid "$SSL_RUN"
|
||||
install -m 0444 -o squid -g squid "$SSL_SRC/squid-ca-cert.pem" "$SSL_RUN/ca-cert.pem"
|
||||
install -m 0400 -o squid -g squid "$SSL_SRC/squid-ca-key.pem" "$SSL_RUN/ca-key.pem"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. Init the dynamic-cert DB + cache_dir structure (idempotent).
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ ! -s "$SSL_DB/index.txt" ]; then
|
||||
log "Initializing TLS cert DB at $SSL_DB..."
|
||||
find "$SSL_DB" -mindepth 1 -delete 2>/dev/null || true
|
||||
/usr/lib/squid/security_file_certgen -c -s "$SSL_DB" -M "${DYNAMIC_CERT_MEM_MB}MB"
|
||||
fi
|
||||
chown -R squid:squid "$SSL_DB" /var/cache/squid /var/log/squid 2>/dev/null || true
|
||||
|
||||
if [ ! -d /var/cache/squid/00 ]; then
|
||||
log "Initializing cache_dir structure..."
|
||||
squid -f "$CONF" -z --foreground || squid -f "$CONF" -z || true
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. Validate the rendered + generated config, then hand off to squid.
|
||||
# ---------------------------------------------------------------------------
|
||||
squid -k parse -f "$CONF"
|
||||
log "Starting squid (visible_hostname=${VISIBLE_HOSTNAME})..."
|
||||
exec squid -N -f "$CONF" "$@"
|
||||
@@ -0,0 +1,30 @@
|
||||
# splice-domains.txt
|
||||
#
|
||||
# Domains Squid must NOT decrypt. With SSL-bump, "splice" = transparent
|
||||
# passthrough: the client's TLS goes straight to the origin, so these are
|
||||
# NOT cached and NOT inspected. Use for cert-pinned apps, banking, app-store /
|
||||
# OS update channels, and anything that breaks under interception.
|
||||
#
|
||||
# One entry per line; a LEADING DOT matches all subdomains (.apple.com matches
|
||||
# www.apple.com, gs.apple.com, ...). Full-line "#" comments only.
|
||||
#
|
||||
# IMPORTANT: do not also list a domain here AND in cache-domains.txt -- splice
|
||||
# wins, so it would never cache. Tune this list for your environment.
|
||||
|
||||
# ── OS / app-store update channels (commonly cert-pinned) ──
|
||||
.apple.com
|
||||
.icloud.com
|
||||
.mzstatic.com
|
||||
.windowsupdate.com
|
||||
.update.microsoft.com
|
||||
.android.clients.google.com
|
||||
.play.googleapis.com
|
||||
|
||||
# ── Banking / payments (examples -- add your own) ──
|
||||
.paypal.com
|
||||
.stripe.com
|
||||
|
||||
# ── Messaging / pinned services ──
|
||||
.whatsapp.net
|
||||
.signal.org
|
||||
.telegram.org
|
||||
@@ -0,0 +1,76 @@
|
||||
# squid.conf.tmpl
|
||||
#
|
||||
# Rendered to /etc/squid/squid.conf by entrypoint.sh at container start:
|
||||
# - @VAR@ placeholders are substituted from the environment (.env);
|
||||
# - the cache policy (boost ACLs, storage gate, per-domain refresh_patterns)
|
||||
# is generated from cache-domains.txt / .regex into the include below.
|
||||
#
|
||||
# Role: EXPLICIT forward proxy with SSL-bump. Clients set HTTP(S)_PROXY to this
|
||||
# host:3128. (Transparent/intercepting mode is future work -- see README.)
|
||||
|
||||
visible_hostname @VISIBLE_HOSTNAME@
|
||||
|
||||
# ── Listener: explicit proxy, SSL-bump enabled ──────────────────────────────
|
||||
# tls-cert/tls-key are the local CA used to mint per-host leaf certs on the fly.
|
||||
# The key is staged into /run by the entrypoint so the squid user can read it
|
||||
# while the on-host key file stays 0600 root.
|
||||
http_port 3128 ssl-bump \
|
||||
tls-cert=/run/squid/ca-cert.pem \
|
||||
tls-key=/run/squid/ca-key.pem \
|
||||
generate-host-certificates=on \
|
||||
dynamic_cert_mem_cache_size=@DYNAMIC_CERT_MEM_MB@MB
|
||||
|
||||
sslcrtd_program /usr/lib/squid/security_file_certgen -s /var/lib/squid/ssl_db -M @DYNAMIC_CERT_MEM_MB@MB
|
||||
sslcrtd_children 8 startup=1 idle=1
|
||||
|
||||
# Validate UPSTREAM certificates -- never silently MITM a broken origin cert.
|
||||
sslproxy_cert_error deny all
|
||||
|
||||
# ── SSL-bump policy: peek at SNI → splice allowlist → bump everything else ──
|
||||
acl step1 at_step SslBump1
|
||||
acl splice_dom ssl::server_name "/etc/squid/splice-domains.txt"
|
||||
ssl_bump peek step1
|
||||
ssl_bump splice splice_dom
|
||||
ssl_bump bump all
|
||||
|
||||
# ── Access control: deny-by-default, trusted clients only ───────────────────
|
||||
acl SSL_ports port 443
|
||||
acl Safe_ports port 80 443 21 70 210 1025-65535 280 488 591 777
|
||||
acl CONNECT method CONNECT
|
||||
http_access deny !Safe_ports
|
||||
http_access deny CONNECT !SSL_ports
|
||||
|
||||
# Cache manager: localhost only (used by the container healthcheck).
|
||||
http_access allow localhost manager
|
||||
http_access deny manager
|
||||
|
||||
acl trusted_clients src @TRUSTED_CIDR@
|
||||
http_access allow localhost
|
||||
http_access allow trusted_clients
|
||||
http_access deny all
|
||||
|
||||
# ── Cache sizing ────────────────────────────────────────────────────────────
|
||||
cache_mem @CACHE_MEM_MB@ MB
|
||||
maximum_object_size_in_memory 1 MB
|
||||
maximum_object_size @MAX_OBJECT_SIZE_MB@ MB
|
||||
cache_dir ufs /var/cache/squid @CACHE_SIZE_MB@ 16 256
|
||||
coredump_dir /var/cache/squid
|
||||
|
||||
# ── Cache policy (generated: boost ACLs, storage gate, per-domain TTLs) ─────
|
||||
include /etc/squid/conf.d/cache-policy.conf
|
||||
|
||||
# Base refresh patterns (fallbacks; per-domain boosts live in the include and
|
||||
# are matched first). Packages get long, confident TTLs; everything else is
|
||||
# conservative and revalidates.
|
||||
refresh_pattern -i \.(deb|rpm|apk|pkg|tar\.(gz|xz|zst|bz2)|whl|jar)$ 10080 100% 43200
|
||||
refresh_pattern . 0 20% 4320
|
||||
|
||||
# ── Logging ─────────────────────────────────────────────────────────────────
|
||||
access_log stdio:/var/log/squid/access.log
|
||||
cache_log /var/log/squid/cache.log
|
||||
logfile_rotate 7
|
||||
|
||||
# ── Privilege drop / lifecycle ──────────────────────────────────────────────
|
||||
cache_effective_user squid
|
||||
pid_filename /run/squid.pid
|
||||
shutdown_lifetime 5 seconds
|
||||
Reference in New Issue
Block a user