diff --git a/backend/doc/api.swagger.json b/backend/doc/api.swagger.json index 3fa19fc4..c692cb1c 100644 --- a/backend/doc/api.swagger.json +++ b/backend/doc/api.swagger.json @@ -90,6 +90,8 @@ "nginx_err": "Command failed: /usr/sbin/nginx -t -g \"error_log off;\"\nnginx: [emerg] unknown directive \"sdfsdfsdf\" in /data/nginx/proxy_host/1.conf:37\nnginx: configuration file /etc/nginx/nginx.conf test failed\n" }, "allow_websocket_upgrade": 0, + "proxy_support_enabled": 0, + "load_balancer_ip": "", "http2_support": 0, "forward_scheme": "http", "enabled": 1, @@ -210,6 +212,8 @@ "dns_challenge": false }, "allow_websocket_upgrade": 0, + "proxy_support_enabled": 0, + "load_balancer_ip": "", "http2_support": 0, "forward_scheme": "http", "enabled": 1, diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js index 77933e73..84630938 100644 --- a/backend/internal/nginx.js +++ b/backend/internal/nginx.js @@ -155,6 +155,7 @@ const internalNginx = { let locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id}, {ssl_forced: host.ssl_forced}, {caching_enabled: host.caching_enabled}, {block_exploits: host.block_exploits}, {allow_websocket_upgrade: host.allow_websocket_upgrade}, {http2_support: host.http2_support}, + {enable_proxy_protocol: host.enable_proxy_protocol}, {load_balancer_ip: host.load_balancer_ip}, {hsts_enabled: host.hsts_enabled}, {hsts_subdomains: host.hsts_subdomains}, {access_list: host.access_list}, {certificate: host.certificate}, host.locations[i]); diff --git a/backend/migrations/20240310085523_proxy_protocol.js b/backend/migrations/20240310085523_proxy_protocol.js new file mode 100644 index 00000000..3a65268f --- /dev/null +++ b/backend/migrations/20240310085523_proxy_protocol.js @@ -0,0 +1,41 @@ +const migrate_name = 'proxy_protocol'; +const logger = require('../logger').migrate; + +/** + * Migrate + * + * @see http://knexjs.org/#Schema + * + * @param {Object} knex + * @param {Promise} Promise + * @returns {Promise} + */ +exports.up = function (knex/*, Promise*/) { + logger.info('[' + migrate_name + '] Migrating Up...'); + + return knex.schema.table('proxy_host', function (proxy_host) { + proxy_host.integer('enable_proxy_protocol').notNull().unsigned().defaultTo(0); + proxy_host.string('load_balancer_ip').notNull().defaultTo(''); + }) + .then(() => { + logger.info('[' + migrate_name + '] proxy_host Table altered'); + }); + +}; + +/** + * Undo Migrate + * + * @param {Object} knex + * @param {Promise} Promise + * @returns {Promise} + */ +exports.down = function (knex, Promise) { + return knex.schema.table('proxy_host', function (proxy_host) { + proxy_host.dropColumn('enable_proxy_protocol') + proxy_host.dropColumn('load_balancer_ip') + }) + .then(function () { + logger.info('[' + migrate_name + '] proxy_host Table altered'); + }); +}; \ No newline at end of file diff --git a/backend/schema/endpoints/proxy-hosts.json b/backend/schema/endpoints/proxy-hosts.json index 9a3fff2f..27a8ec2a 100644 --- a/backend/schema/endpoints/proxy-hosts.json +++ b/backend/schema/endpoints/proxy-hosts.json @@ -58,6 +58,16 @@ "example": true, "type": "boolean" }, + "enable_proxy_protocol": { + "description": "Enable PROXY Protocol support", + "example": true, + "type": "boolean" + }, + "load_balancer_ip": { + "type": "string", + "minLength": 0, + "maxLength": 255 + }, "access_list_id": { "$ref": "../definitions.json#/definitions/access_list_id" }, @@ -155,6 +165,12 @@ "allow_websocket_upgrade": { "$ref": "#/definitions/allow_websocket_upgrade" }, + "enable_proxy_protocol": { + "$ref": "#/definitions/enable_proxy_protocol" + }, + "load_balancer_ip": { + "$ref": "#/definitions/load_balancer_ip" + }, "access_list_id": { "$ref": "#/definitions/access_list_id" }, @@ -245,6 +261,12 @@ "allow_websocket_upgrade": { "$ref": "#/definitions/allow_websocket_upgrade" }, + "enable_proxy_protocol": { + "$ref": "#/definitions/enable_proxy_protocol" + }, + "load_balancer_ip": { + "$ref": "#/definitions/load_balancer_ip" + }, "access_list_id": { "$ref": "#/definitions/access_list_id" }, @@ -318,6 +340,12 @@ "allow_websocket_upgrade": { "$ref": "#/definitions/allow_websocket_upgrade" }, + "enable_proxy_protocol": { + "$ref": "#/definitions/enable_proxy_protocol" + }, + "load_balancer_ip": { + "$ref": "#/definitions/load_balancer_ip" + }, "access_list_id": { "$ref": "#/definitions/access_list_id" }, diff --git a/backend/templates/_listen.conf b/backend/templates/_listen.conf index ad1c96ba..6a4bd92a 100644 --- a/backend/templates/_listen.conf +++ b/backend/templates/_listen.conf @@ -1,15 +1,24 @@ - listen 80; -{% if ipv6 -%} - listen [::]:80; +{% if enable_proxy_protocol == 1 or enable_proxy_protocol == true -%} +{% assign port_number_http = "88" -%} +{% assign port_number_https = "444" -%} +{% assign listen_extra_args = "proxy_protocol" -%} {% else -%} - #listen [::]:80; -{% endif %} +{% assign port_number_http = "80" -%} +{% assign port_number_https = "443" -%} +{% assign listen_extra_args = "" -%} +{% endif -%} + + listen {{ port_number_http }} {{ listen_extra_args }}; +{% if ipv6 -%} + listen [::]:{{ port_number_http }} {{ listen_extra_args }}; +{% endif -%} + {% if certificate -%} - listen 443 ssl{% if http2_support == 1 or http2_support == true %} http2{% endif %}; + {% capture listen_extra_args_https %}ssl{% if http2_support %} http2{% endif %} {{ listen_extra_args }}{% endcapture -%} + listen {{ port_number_https }} {{ listen_extra_args_https }}; {% if ipv6 -%} - listen [::]:443 ssl{% if http2_support == 1 or http2_support == true %} http2{% endif %}; -{% else -%} - #listen [::]:443; -{% endif %} -{% endif %} + listen [::]:{{ port_number_https }} {{ listen_extra_args_https }}; +{% endif -%} +{% endif -%} + server_name {{ domain_names | join: " " }}; diff --git a/backend/templates/_proxy_protocol.conf b/backend/templates/_proxy_protocol.conf new file mode 100644 index 00000000..cba0424f --- /dev/null +++ b/backend/templates/_proxy_protocol.conf @@ -0,0 +1,6 @@ +{% if enable_proxy_protocol == 1 or enable_proxy_protocol == true %} +{% if load_balancer_ip != '' %} + set_real_ip_from {{ load_balancer_ip }}; + real_ip_header proxy_protocol; +{% endif %} +{% endif %} diff --git a/backend/templates/proxy_host.conf b/backend/templates/proxy_host.conf index d23ca46f..e753b6dd 100644 --- a/backend/templates/proxy_host.conf +++ b/backend/templates/proxy_host.conf @@ -15,6 +15,7 @@ server { {% include "_exploits.conf" %} {% include "_hsts.conf" %} {% include "_forced_ssl.conf" %} +{% include "_proxy_protocol.conf" %} {% if allow_websocket_upgrade == 1 or allow_websocket_upgrade == true %} proxy_set_header Upgrade $http_upgrade; diff --git a/docker/Dockerfile b/docker/Dockerfile index 799ee2a6..9ce4d5cc 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -33,7 +33,7 @@ RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \ COPY docker/scripts/install-s6 /tmp/install-s6 RUN /tmp/install-s6 "${TARGETPLATFORM}" && rm -f /tmp/install-s6 -EXPOSE 80 81 443 +EXPOSE 80 81 88 443 444 COPY backend /app COPY frontend/dist /app/frontend diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index 3c21849c..1936a788 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -29,5 +29,5 @@ RUN chmod 644 /etc/logrotate.d/nginx-proxy-manager COPY scripts/install-s6 /tmp/install-s6 RUN /tmp/install-s6 "${TARGETPLATFORM}" && rm -f /tmp/install-s6 -EXPOSE 80 81 443 +EXPOSE 80 81 88 443 444 ENTRYPOINT [ "/init" ] diff --git a/docker/docker-compose.ci.yml b/docker/docker-compose.ci.yml index 66770e53..0ae07b13 100644 --- a/docker/docker-compose.ci.yml +++ b/docker/docker-compose.ci.yml @@ -19,7 +19,9 @@ services: expose: - 81 - 80 + - 88 - 443 + - 444 depends_on: - db healthcheck: @@ -43,7 +45,9 @@ services: expose: - 81 - 80 + - 88 - 443 + - 444 healthcheck: test: ["CMD", "/usr/bin/check-health"] interval: 10s diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 6d8cf87c..3a056945 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -11,7 +11,9 @@ services: ports: - 3080:80 - 3081:81 + - 3088:88 - 3443:443 + - 3444:444 networks: - nginx_proxy_manager environment: diff --git a/docs/advanced-config/README.md b/docs/advanced-config/README.md index 472b8f12..9d5dbe18 100644 --- a/docs/advanced-config/README.md +++ b/docs/advanced-config/README.md @@ -208,3 +208,15 @@ You can customise the logrotate configuration through a mount (if your custom co ``` For reference, the default configuration can be found [here](https://github.com/NginxProxyManager/nginx-proxy-manager/blob/develop/docker/rootfs/etc/logrotate.d/nginx-proxy-manager). + +## Enabling Proxy Protocol for Proxy Hosts + +When running NPM behind a load balancer, you might want to use the [PROXY procotol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) to receive client information such as the source IP address (useful for banning IPs). + +When configuring the PROXY protocol for proxy hosts, NPM uses the ports 88 for http and 444 for https traffic to allow you to decide on a per host basis whether to use the PROSY protocol. + +To enable the Proxy Protocol for your hosts you need to perform the following steps: + +1. Expose the ports `88` (and `444` is applicable) by adjusting your `docker-compose.yml` +2. Edit your proxy hosts to enable the PROXY protocol +3. Edit your upstream load balancer to redirect traffic to the port `88`/`444` and enable the PROXY protocol diff --git a/docs/setup/README.md b/docs/setup/README.md index d881d88d..bf307ddf 100644 --- a/docs/setup/README.md +++ b/docs/setup/README.md @@ -61,6 +61,8 @@ services: - '80:80' # Public HTTP Port - '443:443' # Public HTTPS Port - '81:81' # Admin Web Port + # - '88:88' # Public HTTP Port with proxy_protocol enabled + # - '444:444' # Public HTTPS Port with proxy_protocol enabled # Add any other Stream port you want to expose # - '21:21' # FTP environment: diff --git a/frontend/js/app/nginx/proxy/form.ejs b/frontend/js/app/nginx/proxy/form.ejs index 8e7a2a2d..b45ef749 100644 --- a/frontend/js/app/nginx/proxy/form.ejs +++ b/frontend/js/app/nginx/proxy/form.ejs @@ -72,7 +72,7 @@ -