Repository navigation
Description
Describe the bug
With cloudflareProxy: true on the ingress app, every HTTPS request to an Ingress with whitelist-source-range (or a tenant whitelist) is rejected with 403 through a Cloudflare-proxied DNS record, because nginx evaluates the client as 127.0.0.1 instead of the visitor. This started with the fix in #4444 (backported in #4445, shipped in v1.6.4), which conflicts with the --enable-ssl-passthrough flag that the ingress-nginx package always sets.
How #4444 conflicts with --enable-ssl-passthrough
packages/system/ingress-nginx/values.yaml starts every controller with --enable-ssl-passthrough. With passthrough on, nginx does not listen on 443. The controller's own TCP proxy takes 443 and forwards every connection to nginx on 127.0.0.1:442. So for every HTTPS request, the peer nginx sees is 127.0.0.1, never the Cloudflare edge.
cloudflareProxy sets real_ip_header CF-Connecting-IP and a list of trusted sources. nginx only replaces the client address with CF-Connecting-IP when the peer is in that trusted list.
- Up to v1.6.3 the trusted list was passed under
set_real_ip_from, which is not an ingress-nginx ConfigMap key, so the controller dropped it andproxy-real-ip-cidrstayed at its default0.0.0.0/0. That trusted the passthrough proxy at127.0.0.1, nginx readCF-Connecting-IP, and the whitelist matched the visitor. It worked only because every source was trusted, which let any client reaching the origin directly spoofCF-Connecting-IP. - fix(ingress): trust CF-Connecting-IP only from Cloudflare ranges #4444 moved the list to
proxy-real-ip-cidrand narrowed it to the Cloudflare ranges. That closes the spoofing hole, but the peer is always127.0.0.1, which is not a Cloudflare range. nginx ignoresCF-Connecting-IP, the client stays127.0.0.1, and the whitelist rejects every HTTPS request.
| v1.6.3 and older | v1.6.4 and newer | |
|---|---|---|
Rendered proxy-real-ip-cidr |
default 0.0.0.0/0 (set_real_ip_from key dropped) |
Cloudflare ranges |
| Peer nginx sees over HTTPS (passthrough proxy) | 127.0.0.1 |
127.0.0.1 |
127.0.0.1 trusted |
yes | no |
| Address the whitelist checks | visitor (CF-Connecting-IP) |
127.0.0.1, so 403 |
CF-Connecting-IP spoofable by a direct client |
yes | no |
Environment
- Cozystack version: v1.6.5 (worked on v1.6.3, broken since v1.6.4)
- Provider: on-prem, host ingress (
tenant-root) exposed astype: LoadBalancerwithexternalTrafficPolicy: Local
To Reproduce
- Create an Ingress on the
tenant-rootclass withnginx.ingress.kubernetes.io/whitelist-source-range: ${your-source-ip}/32, replacing${your-source-ip}with your IP. - Set
cloudflareProxy: trueon thetenant-rootingress app, and point a Cloudflare DNS record with proxy mode on at the ingress. - Request the hostname over HTTPS from your source IP.
- A 403 is returned: the peer nginx sees is
127.0.0.1(the ssl-passthrough proxy), not a Cloudflare range, soCF-Connecting-IPis ignored. - Repeat the same steps on v1.6.3: the whitelist matches, because
proxy-real-ip-cidrstays at0.0.0.0/0when the ranges are sent under the wrong key (set_real_ip_from).
Expected behaviour
With cloudflareProxy: true, the whitelist is evaluated against the CF-Connecting-IP visitor address when the request came from a Cloudflare edge, and CF-Connecting-IP is ignored when it did not.
Actual behaviour
Every HTTPS request is rejected with 403 and logged with client 127.0.0.1. The generated nginx.conf has listen 442 proxy_protocol ... ssl and no listener on 443.
Logs
[error] 48#48: *1151 access forbidden by rule, client: 127.0.0.1, server: app.example.com, request: "GET / HTTP/2.0", host: "app.example.com"
Rendered realip config with cloudflareProxy: true:
real_ip_header X-Forwarded-For; # http block
set_real_ip_from 173.245.48.0/20; # ...Cloudflare ranges only, no 127.0.0.1
real_ip_header CF-Connecting-IP; # server-snippet
Screenshots
If applicable, add screenshots to help explain the problem.
Additional context
Adding 127.0.0.1/32 to proxy-real-ip-cidr makes the whitelist work again, but it reopens the hole #4444 closed: every HTTPS connection arrives from 127.0.0.1, so a client connecting to the origin directly can send a CF-Connecting-IP of its choice and pass the whitelist. It is not a safe fix.
Trusting the passthrough proxy safely needs two lookups in a row: the PROXY-protocol header the passthrough proxy prepends (which carries the Cloudflare edge address, since the Service uses externalTrafficPolicy: Local), then CF-Connecting-IP. nginx's realip module reads only one source per request, so no ConfigMap key chains them. One option, for discussion: trust 127.0.0.1/32 and ::1/128 for CF-Connecting-IP, and reject the request when CF-Connecting-IP is present but $proxy_protocol_addr is not in the Cloudflare ranges (a geo $proxy_protocol_addr map), so only real Cloudflare edges can set the visitor address.
Checklist
- I have checked the documentation
- I have searched for similar issues
- I have included all required information
- I have provided clear steps to reproduce
- I have included relevant logs
Activity