David Neto
David Neto

Reputation: 808

Allow only a single specific IP address on Nginx under reverse proxy

I have an api running on localhost. To allow external access to the API under a specific domain path I've set a reverse proxy. This part works fine. Now I'm trying to filter access and allow only a single IP to connect to the API, in other words, deny all IP's connections except from a specific one.

With the configuration bellow all IPs are being blocked successfully, but it's also blocking the one IP I want to allow. I've researched and tried several fixes and I suspect I need to get the real_IP under the reverse proxy, but haven't manage to make it work for my specific situation. All help is appreciated. Here's the code for my nginx config file inside sites-available:

server {

    root /var/www/html;
    index index.html index.htm index.nginx-debian.html;
    server_name foo.com www.foo.com;

    location / {

        allow XX.XX.XX.XX;
        #allow 127.0.0.1;
        deny  all;

        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;

    }


    listen [::]:443 ssl; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/foo.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/foo.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

XX.XX.XX.XX is the ip I want to allow which is actually the server's actual IP. But I don't think it makes a difference. I've also tried adding the following inside "location /{ }" scope, but no luck:

set_real_ip_from XX.XX.XX.XX;
real_ip_header    X-Forwarded-For;
real_ip_recursive on;

Upvotes: 3

Views: 7379

Answers (1)

num8er
num8er

Reputation: 19372

I don't think it's good idea to put that control to web server level.

Giving access is more firewallish task.

But in some conditions when You want to give access to registered host dynamically without restarting or configuring somethings - it's better to make guard method on app level.


For now I can recommend one of these:

1) Put firewall in front of app or nginx. You may use ufw

2) Put access control to app level. If it's nodejs app write middleware:

middlewares/allowByIp.js :

'use strict';

cons db = require('../database'); // mongoose models abstraction

const AllowedHosts = db.model('AllowedHost');

module.exports = async (req, res, next) => {

  const isAllowed = await AllowedHosts.findOne({ip: req.ip});
  if (!isAllowed) {
    res.status(403).send('Forbidden');
  }

  next();
};

or :

'use strict';

cons allowedHosts = [... ip listing ...]; // take care of graceful restarting of Your app when You'll modify this array

module.exports = async (req, res, next) => {

  const isAllowed = allowedHosts.includes(req.ip);
  if (!isAllowed) {
    res.status(403).send('Forbidden');
  }

  next();
};

in app.js :

const express = require('express');
const app = express();

const ipFirewall = require('middlewares/allowByIp');
app.use(ipFirewall);

...

app.listen(3000);


I've checked Your nginx example it works as expected.

So I suspect that nginx gets different ip. Check /var/log/nginx/access.log for real ip address that Your nginx gets when source part does request to destination.

But, if You want to limit access internally, to tell server to use loopback interface in requests to foo.com then add such line in /etc/hosts file:

127.0.0.1   foo.com www.foo.com

it will tell Your server to not request DNS server to resolve that hostname which will give global ip and result with request from outside.

Upvotes: 1

Related Questions