Files
computer-playbook/roles/web-svc-mirror/templates/proxy.conf.j2
kevinveenbirkenbachandClaude Opus 5 ada2e4d311 fix(mirror): keep the origin prefix when an upstream redirects
The mirror proxies /<origin>/<path> to https://<origin>/<path> and relayed the upstream Location header untouched. A root-relative Location then resolved against the mirror instead of the origin, the prefix that names the origin was lost, the follow-up request matched no location block and fell into the catch-all 404. Chromium discards a 404 served for a script request, so the page lost the asset with net::ERR_BLOCKED_BY_ORB.

MIG job 105068912392 shows both halves, 28 times each: GET /unpkg.com/3d-force-graph/dist/3d-force-graph.min.js answered 302, then GET /3d-force-graph@1.80.0/dist/3d-force-graph.min.js answered 404. unpkg redirects an unversioned path to the resolved version and expresses the target as an absolute path.

The forms move into a redirect_sources filter so the template states the intent once and the list is unit-tested. Measured against a two-container nginx fixture: an absolute https Location, an absolute http one and a root-relative one all come back as /<origin>/target. A Location naming a third host stays untouched, which is unchanged behaviour and still leaks past the mirror.

A network-path form was written first and then dropped: nginx normalises such a target in return, and routing one through add_header emits two Location headers of which the proxy relays only the first, so the case could not be produced and the rule stayed unproven.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 12:21:41 +02:00

43 lines
1.4 KiB
Django/Jinja

{% set mirror_origin_hosts = lookup('applications') | external_asset_origins(None, DOMAIN_PRIMARY) %}
server
{
server_name {{ lookup('domain',application_id) }};
{% include 'roles/sys-front-tls/templates/nginx_ssl_header.j2' %}
{% include 'roles/sys-svc-proxy/templates/headers/content_security_policy.conf.j2' %}
{% set aca_origin = "* always" %}
{% include 'roles/sys-svc-proxy/templates/headers/access_control_allow.conf.j2' %}
charset utf-8;
{% for origin_host in mirror_origin_hosts %}
location /{{ origin_host }}/
{
resolver {{ NETWORK_PUBLIC_DNS_RESOLVERS | join(' ') }} ipv6={{ NETWORK_IPV6_ENABLED | on_off }} valid=300s;
set $mirror_upstream_{{ loop.index }} {{ origin_host }};
rewrite ^/{{ origin_host | replace('.', '\\.') }}/(.*)$ /$1 break;
proxy_pass https://$mirror_upstream_{{ loop.index }};
proxy_ssl_server_name on;
proxy_ssl_name {{ origin_host }};
proxy_set_header Host {{ origin_host }};
{% for redirect_source in origin_host | redirect_sources %}
proxy_redirect {{ redirect_source }} /{{ origin_host }}/;
{% endfor %}
proxy_cache cache;
proxy_cache_valid 200 301 302 168h;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_ignore_headers Set-Cookie Cache-Control Expires;
proxy_hide_header Set-Cookie;
proxy_hide_header Access-Control-Allow-Origin;
expires 7d;
}
{% endfor %}
location /
{
return 404;
}
}