Files
2026-09-15 15:24:03 -04:00

93 lines
3.3 KiB
Plaintext

# =====================================================================
# owuitools egress proxy - THE authoritative SSRF boundary.
#
# owuitools-api sits on an `internal: true` network with no default
# route. Every byte it sends to the internet goes through here, so this
# file - not the application's URL validation - is what actually stops a
# guest from talking the assistant into fetching http://192.168.1.10:9443
# or http://172.19.0.2:8080/api/.
#
# Squid resolves destination hostnames ITSELF and applies the ACLs to the
# address it resolved. That is the point: it means a DNS rebinding attack,
# where a name passes the app's check and then resolves to a private
# address a moment later, still dies here.
# =====================================================================
http_port 3128
# Only our own core network may use the proxy at all.
acl core_net src 172.22.0.0/24
# --- Destination denial -------------------------------------------------
# Everything RFC1918/loopback/link-local/CGNAT/documentation/reserved.
# 100.64.0.0/10 (CGNAT) is in here deliberately: this host may sit behind
# a carrier-grade NAT, and that range can reach other subscribers.
acl to_private dst 0.0.0.0/8
acl to_private dst 10.0.0.0/8
acl to_private dst 100.64.0.0/10
acl to_private dst 127.0.0.0/8
acl to_private dst 169.254.0.0/16
acl to_private dst 172.16.0.0/12
acl to_private dst 192.0.0.0/24
acl to_private dst 192.0.2.0/24
acl to_private dst 192.168.0.0/16
acl to_private dst 198.18.0.0/15
acl to_private dst 198.51.100.0/24
acl to_private dst 203.0.113.0/24
acl to_private dst 224.0.0.0/4
acl to_private dst 240.0.0.0/4
acl to_private6 dst ::/128
acl to_private6 dst ::1/128
acl to_private6 dst fe80::/10
acl to_private6 dst fc00::/7
# --- Port restriction ---------------------------------------------------
acl Safe_ports port 80
acl Safe_ports port 443
acl SSL_ports port 443
acl CONNECT method CONNECT
# --- Policy (order matters; first match wins) ---------------------------
http_access deny to_private
http_access deny to_private6
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow core_net
http_access deny all
# --- Behaviour ----------------------------------------------------------
# Stateless: memory cache only, no cache_dir, so the container needs no
# writable spool and can run with a read-only rootfs.
cache deny all
cache_mem 16 MB
maximum_object_size_in_memory 512 KB
# Don't advertise the proxy or the client to upstream sites.
forwarded_for delete
via off
httpd_suppress_version_string on
request_header_access X-Forwarded-For deny all
# Fail fast rather than hanging a tool call.
connect_timeout 10 seconds
read_timeout 45 seconds
request_timeout 45 seconds
dns_v4_first on
pid_filename none
# Audit trail: which tool reached for what. stdout so `docker logs` has it.
logfile_rotate 0
# squid creates its log files 0640 proxy:proxy, which the operator cannot
# read - and a log nobody can open is not an audit trail. This makes them
# world-readable. They contain URLs the assistant fetched, nothing secret.
umask 022
# THE AUDIT TRAIL: one line per outbound request, allowed or denied.
# Written to both, so `docker compose logs egress` keeps working and
# ./logs has it alongside every other component.
access_log stdio:/dev/stdout squid
access_log stdio:/logs/egress.access.log squid
cache_log stdio:/logs/egress.cache.log