diff --git a/README.md b/README.md index 9ac6a6c85..e9c81b15b 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,11 @@ Immediately after logging in with this default user you will be asked to modify All are welcome to create pull requests for this project, against the `develop` branch. Official releases are created from the `master` branch. +Run the following commands to test locally: + +- `cd backend && npm i && node_modules/eslint/bin/eslint.js .` +- `cd test && npm i && npm run cypress` + CI is used in this project. All PR's must pass before being considered. After passing, docker builds for PR's are available on dockerhub for manual verifications. diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js index 5f802c004..b18d4b83b 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 000000000..a520d9247 --- /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/migrations/20240310100432_proxy_protocol_streams.js b/backend/migrations/20240310100432_proxy_protocol_streams.js new file mode 100644 index 000000000..a2a9a51fd --- /dev/null +++ b/backend/migrations/20240310100432_proxy_protocol_streams.js @@ -0,0 +1,41 @@ +const migrate_name = 'proxy_protocol_streams'; +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('stream', function (stream) { + stream.integer('enable_proxy_protocol').notNull().unsigned().defaultTo(0); + stream.string('load_balancer_ip').notNull().defaultTo(''); + }) + .then(() => { + logger.info('[' + migrate_name + '] stream Table altered'); + }); + +}; + +/** + * Undo Migrate + * + * @param {Object} knex + * @param {Promise} Promise + * @returns {Promise} + */ +exports.down = function (knex/*, Promise*/) { + return knex.schema.table('stream', function (stream) { + stream.dropColumn('enable_proxy_protocol'); + stream.dropColumn('load_balancer_ip'); + }) + .then(function () { + logger.info('[' + migrate_name + '] stream Table altered'); + }); +}; \ No newline at end of file diff --git a/backend/templates/_listen.conf b/backend/templates/_listen.conf index ad1c96ba0..6a4bd92a9 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 000000000..cba0424fb --- /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 d23ca46fa..e753b6dde 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/backend/templates/stream.conf b/backend/templates/stream.conf index 76159a646..2694e6f5a 100644 --- a/backend/templates/stream.conf +++ b/backend/templates/stream.conf @@ -1,31 +1,38 @@ # ------------------------------------------------------------ # {{ incoming_port }} TCP: {{ tcp_forwarding }} UDP: {{ udp_forwarding }} # ------------------------------------------------------------ +{% if enable_proxy_protocol == 1 or enable_proxy_protocol == true -%} +{% capture listen_extra_args %}proxy_protocol{% endcapture -%} +{% endif -%} {% if enabled %} {% if tcp_forwarding == 1 or tcp_forwarding == true -%} server { - listen {{ incoming_port }}; + listen {{ incoming_port }} {{ listen_extra_args }}; {% if ipv6 -%} - listen [::]:{{ incoming_port }}; + listen [::]:{{ incoming_port }} {{ listen_extra_args }}; {% else -%} - #listen [::]:{{ incoming_port }}; + #listen [::]:{{ incoming_port }} {{ listen_extra_args }}; {% endif %} proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; +{% include '_proxy_protocol.conf' %} + # Custom include /data/nginx/custom/server_stream[.]conf; include /data/nginx/custom/server_stream_tcp[.]conf; } {% endif %} {% if udp_forwarding == 1 or udp_forwarding == true %} +{% # Proxy Protocol is not supported for UDP %} +{% assign listen_extra_args = "" %} server { - listen {{ incoming_port }} udp; + listen {{ incoming_port }} udp {{ listen_extra_args }}; {% if ipv6 -%} - listen [::]:{{ incoming_port }} udp; + listen [::]:{{ incoming_port }} udp {{ listen_extra_args }}; {% else -%} - #listen [::]:{{ incoming_port }} udp; + #listen [::]:{{ incoming_port }} udp {{ listen_extra_args }}; {% endif %} proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; diff --git a/docker/Dockerfile b/docker/Dockerfile index 0603e2ded..0f582c37e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -35,7 +35,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 bb4ac6d44..c2ca82e47 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -35,5 +35,5 @@ RUN rm -f /etc/nginx/conf.d/production.conf \ COPY --from=pebbleca /test/certs/pebble.minica.pem /etc/ssl/certs/pebble.minica.pem COPY --from=testca /home/step/certs/root_ca.crt /etc/ssl/certs/NginxProxyManager.crt -EXPOSE 80 81 443 +EXPOSE 80 81 88 443 444 ENTRYPOINT [ "/init" ] diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 2bfa2b798..d7a8b5638 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -10,7 +10,9 @@ services: ports: - 3080:80 - 3081:81 + - 3088:88 - 3443:443 + - 3444:444 networks: nginx_proxy_manager: aliases: diff --git a/docs/src/advanced-config/index.md b/docs/src/advanced-config/index.md index efeaefec3..26c34493f 100644 --- a/docs/src/advanced-config/index.md +++ b/docs/src/advanced-config/index.md @@ -214,6 +214,29 @@ 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 + +## Enabling PROXY protocol for Streams + +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). + +Keep in mind that the PROXY procotol cannot be enabled for udp endpoints. + +To enable the PROXY protocol for streams: + +1. Expose the desired port by adjusting you `docker-compose.yml` +2. Edit the Stream to enable the PROXY protocol +3. Edit your upstream load balancer to enable the PROXY protocol ## Enabling the geoip2 module To enable the geoip2 module, you can create the custom configuration file `/data/nginx/custom/root_top.conf` and include the following snippet: diff --git a/docs/src/setup/index.md b/docs/src/setup/index.md index 9b1505bed..6ab2ebffc 100644 --- a/docs/src/setup/index.md +++ b/docs/src/setup/index.md @@ -65,6 +65,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 8e7a2a2df..b45ef7493 100644 --- a/frontend/js/app/nginx/proxy/form.ejs +++ b/frontend/js/app/nginx/proxy/form.ejs @@ -72,7 +72,7 @@ -