NginxProxyManager / nginx-proxy-manager

Docker container for managing Nginx proxy hosts with a simple, powerful interface
https://nginxproxymanager.com
MIT License
22k stars 2.53k forks source link

Access Lists: support for dynamic IP-Addresses #1708

Open BasePointer opened 2 years ago

BasePointer commented 2 years ago

Is your feature request related to a problem? Please describe.

I use the access list feature to restrict access to a subdomain to devices from my home network. Since my ISP does not assign IP addresses statically, I have to log into the webinterface every so often and replace my old IP address with my new one.

Describe the solution you'd like

I have a dynamic dns record that is kept up-to-date with my home IP address. I would like to use this dynamic dns entry for the access list. I imagine that the proxy manager periodically resolves the domain and then replaces the ip address in this access list accordingly.

Describe alternatives you've considered

I dont really see an alternative to my proposal. Will update this issue if a better solution comes to my mind.

devantler commented 2 years ago

I would really like this as well. I think the approach presented by Mike from WPBullet would work really well if containerized.

https://guides.wp-bullet.com/auto-whitelist-multiple-dynamic-dns-addresses-for-nginx-security/

The variables the script relies on could be written to a file or environment variables, and later be retrieved by the script that could run as a cron job.

The Access List could be extended so either an IP address is given or a domain is given. Entering a domain should extend the list of domains the script would whitelist.

WladyX commented 2 years ago

I'm in the same situation, did you find any workarounds for this?

pxlfrk commented 1 year ago

+1

threehappypenguins commented 1 year ago

+1

threehappypenguins commented 1 year ago

Is there any way for me to finagle this to work with npm running in docker?

threehappypenguins commented 1 year ago

I did some finagling, and I have it working, but it requires modifying the docker container and adding a script. The downside, of course, is that you have to re-modify the container when you update. But I figure that doing this is better than the unpredictability of having your IP address change and then having to manually change it right when you needed access to your site. I did the following:

# find container ID
sudo docker ps

#enter container shell
sudo docker exec -it <mycontainer> bash

# install dependencies
apt update
apt install nano
apt install dnsutils

# create your script as per Mike from WPBullet and paste into the .sh file
mkdir /scripts
nano /scripts/nginx-dynamic-multiple.sh
#save and exit

# make script executable
chmod +x /scripts/nginx-dynamic-multiple.sh

# test script
bash /scripts/nginx-dynamic-multiple.sh
cat /etc/nginx/conf.d/dynamicips

The cat command showed my IP so the script worked for that purpose. In Nginx Proxy Manager, I navigated to Hosts > Edit > Advanced and in Custom Nginx Configuration I pasted:

location = / {
    include /etc/nginx/conf.d/dynamicips;
    deny all;
}

This adds to a conf file in (the docker container) /data/nginx/proxy_host/4.conf and for me, specifically /data/nginx/proxy_host/4.conf.

The problem is, when I go to my site (from my added IP address), I get 403 Forbidden. Checked the error log (again, within the docker container): tail -100 /data/logs/proxy-host-4_error.log (log path was defined in /data/nginx/proxy_host/4.conf:

access forbidden by rule, client: 192.168.1.1, server: my.server.com, request: "GET / HTTP/2.0", host: "my.server.com"

I also had the same issue with the address 10.6.0.2 if I went outside my LAN (data on my phone) and connected to my home via OpenVPN. This is because I am trying to connect to it from within the same network my machine (with NPM and my site) is on.

So I modified my Custom Nginx Configuration in NPM to:

location = / {
    include /etc/nginx/conf.d/dynamicips;
    allow 192.168.1.1;
    allow 10.6.0.2;
    deny all;
}

I don't have any way to test this currently, but I think all of this will work properly if I connect to one of the LANs for one of the public IPs in the list. All I have left to do is create a cron job to keep the IP addresses updated.

Edit: I wanted to add that I changed the script a little (copied from someone who commented on the page with the script) so that if a host is unreachable, it doesn't crash the nginx. This is the section that I changed:

#create an array of the dynamic IPs
if [ ! -f /etc/nginx/conf.d/dynamicips ]; then
        for DNS in "${DDNS[@]}"
        do
                IP="$(dig x +short ${DDNS[$i]})"
                if [ "$IP" != "" ]; then
                        echo "allow $IP;" >> /etc/nginx/conf.d/dynamicips
                fi
        done
fi
pxlfrk commented 1 year ago

Nice work! Thank you for that! What do others think, is there any chance to see this in the final build as optional feature? You may start a Pull Request, so that others can review your changes directly.

threehappypenguins commented 1 year ago

You may start a Pull Request, so that others can review your changes directly.

I hope this isn't a dumb question, but wouldn't I need a more complete solution to start a pull request? Like, we would need a GUI solution added so that users can perhaps add their DDNS address in Access Lists > Add Access List > Access and there would be the addition of typing in a DDNS and not just IP address or CIDR. I'm new to the whole PR thing. lol

mczeus commented 1 year ago

@threehappypenguins great workaround, it works for what I have tested so far. But a crontab is still needed!?!

crontab -e /10 * /bin/bash /scripts/nginx-dynamic-multiple.sh /etc/init.d/cron start

But after restarting the container, it's gone again. How can I make this permanent?

Should be included in the release. great big thx

threehappypenguins commented 1 year ago

I don't have enough coding experience so I'm hoping someone can maybe poke around a little more and find a way to implement this. Here are my observations so far:

When you create an Access List, /internal/access-list.js creates an empty file called 1 in the folder /data/access. Any other access lists created will receive the name 2, then 3, etc (even if you delete the old list first). It writes the IP address (that you enter in the Access List for allow) to the mysql database (as evidenced by the binary file, /data/mysql/aria_log.00000001).

Then, if you apply your access list to a host, it writes the IP address(es) to a conf file in /data/nginx/proxy_host (in my case, /data/nginx/proxy_host/4.conf), the same file Custom Nginx Configuration writes to, but a different location = / {} section. It's right under the Custom Nginx Configuration location section. It will go from (my IP redacted):

  location / {

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;
    proxy_http_version 1.1;

    # Proxy!
    include conf.d/include/proxy.conf;
  }

to:

  location / {

    # Access Rules
    allow <mypublicipaddress>;
    deny all;

    # Access checks must...

    satisfy all;

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;
    proxy_http_version 1.1;

    # Proxy!
    include conf.d/include/proxy.conf;
  }

I'm thinking that maybe somehow add the ability to access a DDNS in the access list, and it will trigger the script (with placeholders/variables in place of the actual IP addresses), and those IP addresses could then be saved to the mysql database, ready to be added to the /data/nginx/proxy_host .conf file like the other IP addresses. The script could be set to run every so often, but then there would need to be a way to periodically check to see if the IP addresses change in order for them to be updated in the database, then applied to the .conf file.

OR, maybe we could have everything function as normal where the script writes to a file, and if the access list is applied to the host, it writes that line include /etc/nginx/conf.d/dynamicips; to the .conf file.

Just thinking out loud here.

threehappypenguins commented 1 year ago

I just wanted to give an update not that add a CIDR like 192.168.1.1/24. When I would try to connect from one of the public IP addresses in my dynamicips list, it was forbidden, despite being in the list. nginx -t showed the error:

nginx: [warn] low address bits of 192.168.1.1/24 are meaningless in /data/nginx/proxy_host/4.conf:58

So I removed the /24 from the end, and I could connect just fine.

mczeus commented 1 year ago

I noticed when the IP changes. You also have to save again in Custom Nginx Configuration so that it applies the change of the new IP

threehappypenguins commented 1 year ago

Something else I noticed, if you do an update of your container, then you get a connection refused error in the browser when trying to open the npm management page. Looking at the npm logs from portainer, I noticed:

❯ Enabling IPV6 in hosts: /data/nginx
  ❯ /data/nginx/proxy_host/18.conf
  ❯ /data/nginx/proxy_host/4.conf
  ❯ /data/nginx/proxy_host/16.conf
  ❯ /data/nginx/default_host/site.conf
nginx: [emerg] open() "/etc/nginx/conf.d/dynamicips" failed (2: No such file or directory) in /data/nginx/proxy_host/4.conf:58

So what happens is the dynamicips file disappears upon update, and because the Custom Nginx Configuration is still there (calling on to include a file that doesn't exist), it causes the npm site not to load. So I had to go into the docker container shell, and I commented out the location / section that shows up when you add to Custom Nginx Configuration. After I saved, I was able to log into the npm site. Then I deleted the Custom Nginx Configuration, and had to start over with all the steps I outlined above (creation of the script, etc).

firefart commented 1 year ago

I developed a little auth helper that handles this usecase with http_auth_request_module: https://github.com/firefart/nginxreverseauth

It's a go binary you need to run next to nginx that will handle reverse dns lookups. Maybe you can also make use of this in NginxProxyManager

threehappypenguins commented 1 year ago

I developed a little auth helper that handles this usecase with http_auth_request_module: https://github.com/firefart/nginxreverseauth

It's a go binary you need to run next to nginx that will handle reverse dns lookups. Maybe you can also make use of this in NginxProxyManager

Can you explain how we implement this? I don't really understand what to do with it... like, do we install it within the docker container or something?

firefart commented 1 year ago

Can you explain how we implement this? I don't really understand what to do with it... like, do we install it within the docker container or something?

you need to run it in a seperate (not exposed to the internet) container but it all boils down if you can configure the http_auth_request_module directive in nginx proxy manager. I only use this helper in a plain nginx setup but I thought it might be also helpful here.

rbullers commented 1 year ago

I've created a fork of NPM that builds the hack from @threehappypenguins here into a docker image that lets you pass your DDNS hosts as an environment variable. The script has been added to a cron schedule also, which checks every 60s for a change to the IP and outputs if there was an update or not to var/log/cron

More info on the repo here

threehappypenguins commented 1 year ago

Awesome! I'm gonna take a look! Thanks!

mczeus commented 1 year ago

@rbullers Thank you very much for that. I have tested this. It can be a small error in the description?

"127.0.0.1;"

should be

"allow 127.0.0.1;"

right? I'll keep testing thanks

rbullers commented 1 year ago

@mczeus Yes you're right - I've amended the error, thanks for pointing it out.

efnats commented 1 year ago

I've created a fork of NPM that builds the hack from @threehappypenguins here into a docker image that lets you pass your DDNS hosts as an environment variable. The script has been added to a cron schedule also, which checks every 60s for a change to the IP and outputs if there was an update or not to var/log/cron

More info on the repo here

rbullers this is great!! Would you mind doing a pull request to https://github.com/NginxProxyManager/nginx-proxy-manager ?

vari commented 9 months ago

I created a PR which lets you use the NPM web UI to directly specify dynamic host names in the access list address field (using ddns:subdomain.ddnsdomain.com to specify use the IP of subdomain.ddnsdomain.com).

It automatically handles recreating host config & nginx reloading whenever the IP changes (by default it polls every 1 hour, but this can be configured via the DDNS_UPDATE_INTERVAL env variable - set it to the desired interval in seconds).

Since its integrated to nginx proxy manager backend server, it is much easier to use than specifying a list of domains via env var and having to go in to Custom Nginx Configuration of each proxy host and entering the custom include code. Additionally, if you ever change the domain in the access list, it will automatically resolve the new domain's IP and update each host that uses the access list. (Plus no need to edit the compose file to add/remove/modify domains :) ).

Unfortunately the CI build job is down, so there is no easy to use image with the changes to try it out. If you want to try it out, you can follow the manual steps in my PR comment. The easiest way to test/use the changes is to use jc21/nginx-proxy-manager:github-pr-3364 as the image for your nginx proxy manager container.

E.g. in your docker compose file, replace: image: 'jc21/nginx-proxy-manager:latest' with image: 'jc21/nginx-proxy-manager:github-pr-3364'

threehappypenguins commented 7 months ago

I created a PR which lets you use the NPM web UI to directly specify dynamic host names in the access list address field (using ddns:subdomain.ddnsdomain.com to specify use the IP of subdomain.ddnsdomain.com).

It automatically handles recreating host config & nginx reloading whenever the IP changes (by default it polls every 1 hour, but this can be configured via the DDNS_UPDATE_INTERVAL env variable - set it to the desired interval in seconds).

Since its integrated to nginx proxy manager backend server, it is much easier to use than specifying a list of domains via env var and having to go in to Custom Nginx Configuration of each proxy host and entering the custom include code. Additionally, if you ever change the domain in the access list, it will automatically resolve the new domain's IP and update each host that uses the access list. (Plus no need to edit the compose file to add/remove/modify domains :) ).

Unfortunately the CI build job is down, so there is no easy to use image with the changes to try it out. If you want to try it out, you can follow the manual steps in my PR comment.

Thank you! I'm really late in trying this out, but I just did, and it works great!

vari commented 7 months ago

Thank you! I'm really late in trying this out, but I just did, and it works great!

Glad you found it useful!

virtualdj commented 7 months ago

@vari Hoping it will be merged on main as well!

jimmyjules153 commented 6 months ago

Hey all <3

I had a hard crack at doing this with a cron+script and got something fairly simple in place. I'm running docker with Nginx Proxy Manager and wanted to update my access control lists to my dynamic DNS IP address, since I don't have a static IP. This is so I can lock down my proxy hosts to only be accessible from my WAN address with the inbuilt Nginx proxy manager access lists. I didn't want to use a fork because i wanted to make sure i was always running the latest version of NPM for security reasons.

My nginx host configs are located at '/apps/nginxproxy/data/nginx/proxy_host/'. My docker volumes for NPM are mounted on the host at this folder location for easy access. Your location for this will more than likely be different, but if you can find your nginxproxymanager folder, the configs for your hosts should be in the 'data/nginx/proxy_host/' subfolders.

The below script does the following:

WAN IP Retrieval: It fetches the current WAN IP using curl and stores it in the WAN_IP variable.

Previous IP Check: It checks if a file named wan_ip.txt exists in the /home/user/scripts/nginx/ directory. If the file exists, it reads the stored IP into the OLD_WAN_IP variable. If the file doesn't exist, it creates the file and stores the current WAN IP in it.

WAN IP Change Detection: It compares the newly obtained WAN_IP with the OLD_WAN_IP. If they are the same, it logs a message and exits without further action.

WAN IP Update: If the IP has changed, it logs a message and updates the wan_ip.txt file with the new IP. It then uses sed to modify all configuration files in the /apps/nginxproxy/data/nginx/proxy_host/ directory.

Configuration File Modification: The sed command searches for lines starting with "allow" (^\s*allow). If found, it replaces the entire line with allow $WAN_IP/32; (with the extra leading spaces for keeping formatting nice in the file).

Reload nginx proxy manager configuration Reloads NPM config, without restarting NPM so there's no downtime

`#!/bin/bash date +"%Y-%m-%d_%H:%M:%S" >> /home/user/scripts/nginx/log.txt

Get the current WAN IP address

WAN_IP=$(curl -s https://icanhazip.com)

check if the WAN IP file exists, create it with the WAN IP if not.

if test -f /home/user/scripts/nginx/wan_ip.txt; then OLD_WAN_IP=cat /home/user/scripts/nginx/wan_ip.txt else echo $WAN_IP > /home/user/scripts/nginx/wan_ip.txt fi

if [ "$WAN_IP" == "$OLD_WAN_IP" ]; then echo "WAN IP has not changed since last run." >> /home/user/scripts/nginx/log.txt else echo "WAN IP has changed! Updating nginx configs..." >> /home/user/scripts/nginx/log.txt echo $WAN_IP > /home/user/scripts/nginx/wan_ip.txt sed -i "/^\sallow/c\ allow $WAN_IP/32;" /apps/nginxproxy/data/nginx/proxy_host/.conf fi

docker exec nginxproxy-app-1 /bin/bash -c "/usr/sbin/nginx -s reload"`

This solved my issues and now I won't get locked out of my Nginx proxy manager hosted sites from my ACL's every time my WAN IP changes..!! Also this won't get wiped out of NPM happens to release an update. I hope this helps someone else 💜

I'll keep an eye out for replies so if you have questions shoot them through and i'll answer if I can help 😊

TwoPlayer commented 5 months ago

Hey all <3

I had a hard crack at doing this with a cron+script and got something fairly simple in place. I'm running docker with Nginx Proxy Manager and wanted to update my access control lists to my dynamic DNS IP address, since I don't have a static IP. This is so I can lock down my proxy hosts to only be accessible from my WAN address with the inbuilt Nginx proxy manager access lists. I didn't want to use a fork because i wanted to make sure i was always running the latest version of NPM for security reasons.

My nginx host configs are located at '/apps/nginxproxy/data/nginx/proxy_host/'. My docker volumes for NPM are mounted on the host at this folder location for easy access. Your location for this will more than likely be different, but if you can find your nginxproxymanager folder, the configs for your hosts should be in the 'data/nginx/proxy_host/' subfolders.

The below script does the following:

WAN IP Retrieval: It fetches the current WAN IP using curl and stores it in the WAN_IP variable.

Previous IP Check: It checks if a file named wan_ip.txt exists in the /home/user/scripts/nginx/ directory. If the file exists, it reads the stored IP into the OLD_WAN_IP variable. If the file doesn't exist, it creates the file and stores the current WAN IP in it.

WAN IP Change Detection: It compares the newly obtained WAN_IP with the OLD_WAN_IP. If they are the same, it logs a message and exits without further action.

WAN IP Update: If the IP has changed, it logs a message and updates the wan_ip.txt file with the new IP. It then uses sed to modify all configuration files in the /apps/nginxproxy/data/nginx/proxy_host/ directory.

Configuration File Modification: The sed command searches for lines starting with "allow" (^\s*allow). If found, it replaces the entire line with allow $WAN_IP/32; (with the extra leading spaces for keeping formatting nice in the file).

Reload nginx proxy manager configuration Reloads NPM config, without restarting NPM so there's no downtime

`#!/bin/bash date +"%Y-%m-%d_%H:%M:%S" >> /home/user/scripts/nginx/log.txt #Get the current WAN IP address WAN_IP=$(curl -s https://icanhazip.com)

check if the WAN IP file exists, create it with the WAN IP if not. if test -f /home/user/scripts/nginx/wan_ip.txt; then OLD_WAN_IP=cat /home/user/scripts/nginx/wan_ip.txt else echo $WAN_IP > /home/user/scripts/nginx/wan_ip.txt fi

if [ "$WAN_IP" == "$OLD_WAN_IP" ]; then echo "WAN IP has not changed since last run." >> /home/user/scripts/nginx/log.txt else echo "WAN IP has changed! Updating nginx configs..." >> /home/user/scripts/nginx/log.txt echo $WAN_IP > /home/user/scripts/nginx/wan_ip.txt sed -i "/^\s_allow/c\ allow $WAN_IP/32;" /apps/nginxproxy/data/nginx/proxyhost/.conf fi

docker exec nginxproxy-app-1 /bin/bash -c "/usr/sbin/nginx -s reload"`

This solved my issues and now I won't get locked out of my Nginx proxy manager hosted sites from my ACL's every time my WAN IP changes..!! Also this won't get wiped out of NPM happens to release an update. I hope this helps someone else 💜

I'll keep an eye out for replies so if you have questions shoot them through and i'll answer if I can help 😊

Hey, I try to implement your script, but it won't change the line in the config. Could it be, that the format of the sed command was messed up, because of markdown? Could you maybe provide the script in a code bracket so with three backticks (```) instead of one? 😊