Skip to content

fix: move routing from CBC nginx to application nginx#549

Merged
tomudding merged 9 commits into
GEWIS:mainfrom
SuperVK:fix/nginx-routing
Jul 9, 2026
Merged

fix: move routing from CBC nginx to application nginx#549
tomudding merged 9 commits into
GEWIS:mainfrom
SuperVK:fix/nginx-routing

Conversation

@SuperVK

@SuperVK SuperVK commented Jul 7, 2026

Copy link
Copy Markdown
Member

Description

The CBC is trying to move to Traefik proxying instead of nginx. Traefik does not support proxying the cookie domain (without plugins), but from the CBC we also rather have that the ABC projects manage this complex routing themselves. I helped out a bit, this should work. Do you want me see if I can test this locally? Or is possible to quickly test this in production? Reverting back should be able without any hassle.

Related issues/external references

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

@SuperVK SuperVK force-pushed the fix/nginx-routing branch from d7387b7 to 3ccdfc0 Compare July 8, 2026 17:18
@tomudding

Copy link
Copy Markdown
Member

Thanks for the PR @SuperVK! I am currently reviewing it, however, we need to know the new IP(s) for the Traefik proxy before we can deploy: we need it for

'database_remoteaddress' => static function (ContainerInterface $container) {

Currently, we use two WAF ranges (/27s) and the existing gewisweb IP. What will be the replacement(s) of the the latter?

tomudding added 7 commits July 9, 2026 12:48
The join.gewis.nl and member.gewis.nl server blocks include a snippet
that was never committed, which is a fatal error on NGINX startup. Add
it and use it for the database.gewis.nl block as well.
Behind the join/member hop PHP sees REMOTE_ADDR=127.0.0.1, which must
be a trusted proxy for the X-Forwarded-For resolution to run at all.
Otherwise every join/member visitor resolves to localhost and the July
campus-only subscribe check rejects everyone.
The `$host` maps only ever defined a default, so they were constants
with indirection, and moving the `add_header` directives into a snippet
split the values from their use. Inline the literal values and drop the
maps.
`proxy_set_header` is inherited all-or-nothing per level and every
proxying location defines its own set, so these http-level directives
never apply. Keeping them is misleading next to proxy-to-backend.conf,
where `X-Forwarded-Proto` deliberately does not use $scheme.
Keep only what the config cannot express itself: why the loopback hop
exists and why the forwarded headers must not be rebuilt from local
variables. This also drops the incorrect claim that no FastCGI
equivalent exists for `session.cookie_domain`.
Production resolves the client from X-Forwarded-For; the X-Real-IP
default was stale and misleading. Behind the new proxy chain X-Real-IP
does not reliably hold the client (an intermediate hop overwrites it),
so chain resolution is the mechanism of record. Also document which
hops belong in PROXY_IPS.
Setting it from $remote_addr records the previous proxy instead of the
client. Unset headers are passed through unchanged, so the values from
the edge already reach the backend; the X-Forwarded-Proto and
X-Forwarded-Port lines were no-op pass-throughs and are dropped too.
@tomudding

Copy link
Copy Markdown
Member

Once again thank you for the PR. It did need a few functional fixes though (running nginx -t at least once would have helped). I have committed them for you:

The join and member server blocks include snippets/securitytxt.conf, but that file was not committed. This would have prevented NGINX from starting. I added the snippet and let the database.gewis.nl block use it as well.

The loopback hop set X-Real-IP to $remote_addr, which at that point is the previous proxy instead of the client. The header is now passed through unchanged. The client IP itself is resolved from X-Forwarded-For in production, but behind the hop PHP sees REMOTE_ADDR 127.0.0.1, so 127.0.0.1 has to be added to PROXY_IPS. Without it every join/member visitor resolves to localhost and the July campus check on the subscribe form rejects everyone. .env.dist now documents this and defaults to X-Forwarded-For.

The remaining commits are cleanup: the security header values are inlined in the snippet since the maps only defined defaults, the unused http-level proxy_set_header directives in proxy.conf are removed, and the comments are trimmed.

Or is possible to quickly test this in production? Reverting back should be able without any hassle.

There is no need to test in production. I verified locally that nginx -t passes and, with the dev stack running, that the subscribe and renewal flows work through the loopback hop, static assets are served, other paths return 404, and the security headers and session cookie are present. Test and development are unaffected: anything that is not join.gewis.nl or member.gewis.nl ends up in the default_server block, so separate test vhosts are not needed.

Note that this config and the current CBC proxy cannot run together. The CBC proxy rewrites URLs before proxying and those rewritten forms are not in the whitelists, so this image behind the old proxy breaks renewal links and the checkout redirects, while the old image behind Traefik serves the full application with a broken cookie domain. The merge therefore has to land in the same window as the proxy migration, and a revert of the migration also means rolling back this image.

In the same window PROXY_IPS needs to be extended with 127.0.0.1, the ingress hop in front of Traefik (to be confirmed by the CBC - see #549 (comment)), and the IP Traefik uses to reach gewiscontainer, which becomes both the new REMOTE_ADDR and the last X-Forwarded-For entry on join/member requests. The CBC also needs to set up the allowlists the old proxy handled: GEWIS-only for database.gewis.nl and TU/e-only for database.test.gewis.nl with the Stripe webhook path publicly reachable. After the migration the subscribe, renewal and checkout flows should be checked, including whether the client IP resolves correctly.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR shifts host-based routing responsibilities from the CBC’s nginx layer into the application’s own nginx configuration, enabling join/member vhosts to re-proxy to the main application and rewrite cookie domains (to support Traefik at the edge).

Changes:

  • Add dedicated join.gewis.nl and member.gewis.nl server blocks that proxy to a loopback backend and apply proxy_cookie_domain.
  • Extract shared nginx concerns into snippets (security headers, security.txt proxying, backend proxying) and simplify global proxy settings.
  • Update default proxy header configuration in .env.dist to align with X-Forwarded-For usage and the extra loopback hop.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
docker/nginx/snippets/securitytxt.conf Adds centralized proxying of /.well-known/security.txt from gewis.nl.
docker/nginx/snippets/security-headers.conf Centralizes common security headers previously embedded in nginx.conf.
docker/nginx/snippets/proxy-to-backend.conf Defines the loopback proxy settings used by join/member vhosts to reach the main app.
docker/nginx/proxy.conf Removes globally forced forwarded headers to avoid incorrect values on re-proxied requests.
docker/nginx/nginx.conf Introduces join/member server blocks and refactors security/security.txt behavior into snippets.
.env.dist Adjusts proxy header defaults to X-Forwarded-For and documents multi-hop trusted proxy configuration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docker/nginx/snippets/securitytxt.conf Outdated
Comment thread docker/nginx/snippets/security-headers.conf
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@tomudding tomudding merged commit 3c86c1c into GEWIS:main Jul 9, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants