93 lines
3.3 KiB
Plaintext
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
|