Having machines behind an Amazon Elastic Load Balancer (ELB) is great but can have some unwanted side effects. We have an admin console for our application that we wanted to protect with nginx basic IP level access control. Sadly, the ELB reports the sending IP as itself and not the originating request IP. A little annoying I think you’d agree.
The answer seemed to be in the Real-IP module that pulls the IP address from the HTTP X_Forwarded_For header (in the ELB case – it can be set to pull from a different header). I compiled it in… But no, the X_Forwarded_For header can contain multiple IP addresses and in the ELB case it does however, the module gets the last IP address in this list rather than the first which is the originating IP.
With that I decided on a different solution using the map directive in nginx. map allows you to assign a value to a variable based on the contents of another variable using regular expressions. This is much like a case statement in a shell script. With no further ado, here is my solution.
To keep things tidy in configuration files I use include statements where it makes sense. So the first include comes in the server section :
include conf.d/admin_ip_map.conf;
The file it refers to is doing the first part of the work we need it to do :
map $http_x_forwarded_for $allowed {
default deny;
~*^23.24.25.26\s.* allow; # office external IP
~*^8.8.8.8\s.* allow; # internal.example.com
~*^9.0.2.1\s.* allow; # some user home IP
}
By default we are setting the $allowed variable to “deny”. The following lines show the regex applied to the $http_x_forwarded_for variable which, if true, set the $allowed variable to “allow”. You will notice the \s detecting the white space after the IP. You could get away without this but you would leave yourself open to a few more IP addresses than you night want to….
location /admin {
if ( $allowed = "deny" ) {
return 403 ;
}
try_files $uri @rails ;
}
This sends an access not allowed response for any time that the $allowed variable contains “deny” otherwise it does a normal “try_files”.
This is how I got around the limitations of being behind an Amazon ELB using the nginx map function. It seems to work well for our application.


