Remote IP Address with Go
We need remote ips
Yesterday I was tasked with figuring out how to properly ascertain remote web client IP Addresses in our golang web services. This sounds like a stupidly easy exercise, and golang MUST have a fancy utility for this right? Nope. Turns out getting the remote IP Address is really hard.
The naïve approach
Within the golang net.Request
structure there is a RemoteAddr
attribute,
which unsurprisingly contains the remote address of the requester. Job done,
right? Well not really if you use any form of reverse proxy or load balancer
for your application, which we do. This will always appear to the go server
as if every request is coming from the load balancer, which would be terrible
if you wanted to use this as a throttling metric of any kind. So, we can
immediately throw this out as non-useful for our purposes, unless you have one
go server running not behind any form of reverse proxy.
That thrown out as we run our server behind nginx as a reverse proxy, we can
start looking at various headers that contain better information. I came across
two headers that are potentially useful, X-Real-Ip
and X-Forwarded-For
.
X-Forwarded-For
you can think of as a request hop log. Every proxy the request
traverses, that proxy is supposed to append the origination IP address it sees
to this header. For example, if you have a home router proxy (ip1) and the
service has a load balancer (ip2) your application would have an X-Forwarded-For
value of ip1, ip2
Very naïvely, we can just pull the first record off of this header and that would be the client’s ip address, right?
A better solution
What if the client uses a client side proxy for web requests? If that happens
the first entry in the X-Forwarded-For
header could potentially be in a “private”
sub-net, not globally accessible. Below is a better solution that filters out
private sub-nets, as well as multi-cast address space, and localhost address space:
As you can see the above is a lot of infrastructure, but now, we are able to get an ip address that is our first globally visible IP address the request originates from. Granted this isn’t perfect, because a malicious user could clearly create fake X-Forwarded-For header records to play chaos monkey, but I guess nothing in life is perfect.
UPDATE!
After hearing from a friend about this mechanism it is pretty clear the above
mechanism could be exploited by griefers, he had a suggestion. Instead of walking
from the left to right, walk backwards through the number of ip addresses by the
number of proxies you have in your environment to the internet. That way, you
will be adverse to any mucking with the X-Forwarded-For
header by the client.
Below is the updated version of the Above implementing this: