Skip to content

ingress: cloudflareProxy blocks all HTTPS behind ssl-passthrough since v1.6.4; whitelist sees every client as 127.0.0.1 #5327

Open

Description

@cyaoz94

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 and proxy-real-ip-cidr stayed at its default 0.0.0.0/0. That trusted the passthrough proxy at 127.0.0.1, nginx read CF-Connecting-IP, and the whitelist matched the visitor. It worked only because every source was trusted, which let any client reaching the origin directly spoof CF-Connecting-IP.
  • fix(ingress): trust CF-Connecting-IP only from Cloudflare ranges #4444 moved the list to proxy-real-ip-cidr and narrowed it to the Cloudflare ranges. That closes the spoofing hole, but the peer is always 127.0.0.1, which is not a Cloudflare range. nginx ignores CF-Connecting-IP, the client stays 127.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 as type: LoadBalancer with externalTrafficPolicy: Local

To Reproduce

  1. Create an Ingress on the tenant-root class with nginx.ingress.kubernetes.io/whitelist-source-range: ${your-source-ip}/32, replacing ${your-source-ip} with your IP.
  2. Set cloudflareProxy: true on the tenant-root ingress app, and point a Cloudflare DNS record with proxy mode on at the ingress.
  3. Request the hostname over HTTPS from your source IP.
  4. A 403 is returned: the peer nginx sees is 127.0.0.1 (the ssl-passthrough proxy), not a Cloudflare range, so CF-Connecting-IP is ignored.
  5. Repeat the same steps on v1.6.3: the whitelist matches, because proxy-real-ip-cidr stays at 0.0.0.0/0 when 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.

Related: #4444, #4445.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/bugCategorizes issue or PR as related to a bugtriage/needs-triageIndicates an issue needs triage by a maintainer

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions