Files
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

146 lines
6.1 KiB
Bash

#!/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" "$@"