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:
@@ -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" "$@"
|
||||
Reference in New Issue
Block a user