# Generated by abox {{ abox_version }} for project "{{ project }}" — DO NOT EDIT.
#
# SNI-aware egress proxy. The agent's firewall sends every outbound 443
# connection here; this decides where it may go by reading the server name from
# the TLS ClientHello, without terminating TLS.
#
# This is what makes the allowlist *domain*-level. An ipset can only match
# addresses, so it cannot tell pypi.org from any other domain sharing a Fastly
# IP — a request to an allowed address carrying a different SNI gets through.
# Here the name itself is the key, so domain fronting fails.
#
# TLS is never decrypted: there is no CA, no certificate to install in the
# agent, and nothing inside the tunnel is visible to abox. Only the destination.

worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /tmp/nginx.pid;

events {
    worker_connections 1024;
}

stream {
    # Docker's embedded resolver: upstreams are named, not addressed, so the
    # proxy follows DNS changes the way a client would.
    resolver {{ resolver }} ipv6=off valid=30s;
    resolver_timeout 5s;

    log_format abox '$time_iso8601 client=$remote_addr sni="$ssl_preread_server_name" '
                    'upstream="$upstream_addr" status=$status bytes=$bytes_sent';
    access_log /var/log/nginx/sni.log abox;

    # The allowlist. Exact names only — abox rejects wildcards in egress
    # because a wildcard cannot be checked against what it actually matches.
    map $ssl_preread_server_name $abox_upstream {
        default "";
{%- for domain in egress %}
        "{{ domain }}" "{{ domain }}:443";
{%- endfor %}
    }

    server {
        listen {{ port }};
        ssl_preread on;

        # An empty upstream is a configuration error to nginx, which closes the
        # connection. Failing closed is the point: a name that is not in the map
        # above has no route, and the attempt is logged with its SNI.
        proxy_pass $abox_upstream;

        proxy_connect_timeout 10s;
        proxy_timeout {{ timeout }}s;
    }
}
