traefik-crowdsec-bouncer: Bouncer doesn't correctly process X-Forwarded-For headers
crowdsec-bouncer-traefik | 2022-01-12T00:11:31Z DBG No decision for IP "192.168.0.13". Accepting
crowdsec-bouncer-traefik | {"level":"info","status":200,"method":"GET","path":"/api/v1/forwardAuth","ip":"192.168.0.13","latency":2.450771,"user_agent":"Gatus/1.0","time":"2022-01-12T00:11:31Z","message":"Request"}
crowdsec-bouncer-traefik | 2022-01-12T00:11:31Z DBG No decision for IP "192.168.0.13". Accepting
crowdsec-bouncer-traefik | {"level":"info","status":200,"method":"GET","path":"/api/v1/forwardAuth","ip":"192.168.0.13","latency":2.440972,"user_agent":"Gatus/1.0","time":"2022-01-12T00:11:31Z","message":"Request"}
crowdsec-bouncer-traefik | 2022-01-12T00:11:32Z DBG No decision for IP "172.20.6.1". Accepting
crowdsec-bouncer-traefik | {"level":"info","status":200,"method":"GET","path":"/api/v1/forwardAuth","ip":"172.20.6.1","latency":2.334519,"user_agent":"Prometheus/2.32.1","time":"2022-01-12T00:11:32Z","message":"Request"}
crowdsec-bouncer-traefik | 2022-01-12T00:11:34Z DBG No decision for IP "172.70.34.108". Accepting
crowdsec-bouncer-traefik | {"level":"info","status":200,"method":"GET","path":"/api/v1/forwardAuth","ip":"44.200.29.26","latency":2.456313,"user_agent":"axios/0.21.4","time":"2022-01-12T00:11:34Z","message":"Request"}
As you can see the initial requests are internal addresses and the DBG lookup matches the subsequent request, however, the last request in the list is doing a Decision lookup for the last hop address (Cloudflare in this instance - 172.70.34.108) rather than the real address (44.200.29.26) and is allowing it even if it’s banned.
In the specific instance:
crowdsec-bouncer-traefik | 2022-01-12T00:11:54Z DBG No decision for IP "162.158.183.237". Accepting
crowdsec-bouncer-traefik | {"level":"info","status":200,"method":"GET","path":"/api/v1/forwardAuth","ip":"157.90.177.214","latency":2.464756,"user_agent":"Mozilla/5.0 (compatible; BLEXBot/1.0; +http://webmeup-crawler.com/)","time":"2022-01-12T00:11:54Z","message":"Request"}
+---------+----------+-------------------+-----------------------------------+--------+---------+--------------------------------+--------+--------------------+----------+
| ID | SOURCE | SCOPE:VALUE | REASON | ACTION | COUNTRY | AS | EVENTS | EXPIRATION | ALERT ID |
+---------+----------+-------------------+-----------------------------------+--------+---------+--------------------------------+--------+--------------------+----------+
| 1010386 | crowdsec | Ip:157.90.177.214 | crowdsecurity/http-bad-user-agent | ban | US | 0 | 2 | 3h38m42.992170366s | 2623 |
157.90.177.214 is banned but the bouncer allows it because the lookup is performed against 162.158.183.237 (Cloudflare) instead.
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 27 (6 by maintainers)
It’s mainly an issue with your setup of traefik entry point. Within Kubernetes, Traefik Agents act as Network Load Balancers, so you must configure the EntryPoint Option “Forwarded Headers”.
https://doc.traefik.io/traefik/routing/entrypoints/#forwarded-headers
In addition, if you have another reverse proxy in front of Traefik, you must:
https://doc.traefik.io/traefik/routing/entrypoints/#proxyprotocol
Last but not least, make sure your Traefik middleware “FowardAuth” uses the option “trustForwardHeaders” https://doc.traefik.io/traefik/middlewares/http/forwardauth/#trustforwardheader
Sample Setup
values.yaml
Traefik Config Map & Cloudflare Secret
Quite honestly you don’t need to set the trusted proxy setting unless you’re setting it up on a different machine or something. Traefik will handle which are trusted before it hits this one. Keeping it blank will set it to trust all forwarding traffic. You could set it to trust docker networks if you really want to. The setting is about trusting the proxy in front of the bouncer really not necessarily cloudflare but you’d need to do testing.
You do need to set cloudflare CDN as trusted in the traefik config though.
I’ve opened a pull request for this, happy to talk through it or other concerns. I’ve tested it working for me (specifically sitting behind Cloudflare CDN) but I don’t have much golang experience so there might be formatting or other issues with it.
@youngt2, don’t worry about the ProxyProtocol from Cloudflare it’s automated. I’m using it too with a free account for my lab without any additional settings to make it works.
Only if you have a LoadBalancer or a Reverse Proxy in between your Kubernetes infrastructure and your internet router.
@SpaceComet, it’s the “k3s.service” file describing the service. If your OS uses systemd it should be located here:
/etc/systemd/system/k3s.serviceMainly, if you don’t have a Traefik Pod Instance per node, using “externaltrafficpolicy: Local” on the service will give you some wired routing situations (black hole, traffic routed on single node,…)
Well detailed explanations: https://technotes.adelerhof.eu/containers/kubernetes/externaltrafficpolicy/ Official Kubernetes Warnings: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip
In that case, as you are using Helm, that’s pretty easy, play with the “replicas” option into your helm chart values file.
However, according to Traefik documentations, certificate management in HA way is not implemented anymore (that feature was present in v1.x), only in the enterprise edition. (cf. https://doc.traefik.io/traefik-enterprise/features/#compare)
So, if you want to do it, make sure your SSL certificates are managed externally of Traefik, example by using “cert-manager”. (eg. https://www.scaleway.com/en/docs/tutorials/traefik-v2-cert-manager/)
By using Calico, you will have a better control of your ingress network and less pains with reverse proxies source ips. Then it should be solved if all points discussed previously are in place.
Hi. I have been working on this problem the last 3 days. First of all, thank you guys for taking the time to help me!
@vherrlein, at first I tried adding the
forwardedHeaders.insecureCLI options into myHelmChartConfigfile (traefik-config.yaml). Like hereAlso, my Traefik middleware “FowardAuth” uses the option “trustForwardHeaders.”
For some reason this didn’t work. Or at least not just by adding those extra flags. Then I read what @fbonalair posted. At the beginning I didn’t know how to do it until I found this.
So I also added this to my file:
I confirm too, that’s working fine now, even behind 2 reverse proxies .
https://github.com/fbonalair/traefik-crowdsec-bouncer/pull/14 fixed my problems. Thanks and great job @pewter77!
That’s what I have concluded too. I just did not have time to fix it until now.
Thanks for your work! I will merge it, it’s an additional feature for the project. Don’t worry about golang, I’m a noob myself, I have used this project to work on the language actually ^^.
Exactly, by default the Web framework I’m using is trusting everything by default. For this service and its scope, it should be fine.
This trusted proxy issue should be fixed from version 0.3.4 of the container.
In the interim I’ve forked the repo here https://github.com/thespad/traefik-crowdsec-bouncer and merged @pewter77 's PR.
Go is very much not my strong suit but I’ve been trying to get a handle on why the behaviour isn’t as expected and I think I’ve got a sense of it now.
So the
X-Forwarded-Forheader is the defacto standard for source (and intermediate) IP address recording. Proxies and other intercepting services are permitted to append to theX-Forwarded-Forheader so that you have a “paper trail” of the hops the request has passed through, but the first value should be the originating IP.X-Real-IPdoesn’t support appending and while it may show the originating IP there’s no way to know it hasn’t been overwritten along the way (technically this is also true withX-Forwarded-Forbut it at least allows appending). Consequently, it’s much more reliable to use theX-Forwarded-Forheader whenever there’s a possibility of more than one proxy between the origin and the destination server.For example, in this request:
Here,
162.158.107.233is a Cloudflare address, showing up as both the wholeX-Real-IPheader and the last entry inX-Forwarded-For.34.220.8.237is the originating IP, which also shows up in theX-Forwarded-Forheader as the first address.Traefik’s own logging shows:
Your current code is using the X-Real-IP header to do its auth checks, which is only going to show the last hop before Traefik, which in my case is Cloudflare’s edge.
I think it should be as straightforward as using the
X-Forwarded-Forheader and extracting the first address from the list.