Log real IP from header

I’m trying to get the real IP from cloudflare clients using the CF-Connecting-IP header. I’ve done this successfully on nginx using the real IP module.

The way I attempted to do it with haproxy is setting a variable, and logging based on it. For some reason why I do this half of my logs are empty.

This is my configuration:

# Check if CF header set
acl is_real_ip hdr_ip(CF-Connecting-IP) -m len 0

# Header set, set txn.real_ip to CF ip
http-request set-var(txn.real_ip) hdr_ip(CF-Connecting-IP) if ! is_real_ip

# Set txn.real_ip to src ip
http-request set-var(txn.real_ip) src if is_real_ip

log-format "%[var(txn.real_ip)] %cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r"

I would think based on this they would either get the source IP or the cloudflare IP, but sometimes they are empty, and %[var(txn.real_ip)] is replaced with '-'.

I’m also not sure if there’s an easier way to do it than this.

Share the entire configuration and the output of haproxy -vv.

I’d guess that some request come in directly, without going through Cloudflare.

That ACL is probably not doing what you indent it to do and the configuration is also a huge security risk. Everyone connecting to your server directly could just send an CF-Connecting-IP header and therefor you’d log a attacker controller IP, not the real IP of the attacker.

You need to match the real IP with a cloudflare whitelist, that’s how you know it comes from Cloudflare:

I’ll match against their IP in the final config, this was just trying to get logging working.

Shouldn’t this set the regular IP in the log if they dont go through cloudflare?

http-request set-var(txn.real_ip) src if is_real_ip

Not if the ACL is_real_ip always returns true. You need to fix that ACL.

Shouldn’t this only be true when the length of the header is 0, in other words it’s not set?

No.

First of all you are not accessing the header, but transforming the header into an IP address (hdr_ip). So an empty header most likely gets transformed into 0.0.0.0.

Second, len is supposed to be applied to a string. What happens if you apply it to an IP address (not a string of an IP address) is undefined.

And then of course your logic would actually be inverted:

  • you are saying the ACL is supposed to set is_real_ip to true, if the header is NOT SET
  • but you are using the ACL - as it’s name implies - like it would be true if the header IS SET

Use:

acl is_real_ip hdr_cnt(CF-Connecting-IP) eq 1