From 29ec95ba4495dd4238f480420858f95dfdecc275 Mon Sep 17 00:00:00 2001 From: dee077 Date: Thu, 15 Oct 2026 18:47:58 +0530 Subject: [PATCH] [fix] Stopped trusting X-Forwarded-For for the client IP address - Passed the connection address to the application by default - Added NGINX_TRUSTED_PROXIES to read X-Forwarded-For from trusted proxies - Documented that $real_ip and $http_x_forwarded_for can be spoofed --- docs/user/settings.rst | 42 ++++++++++++++++++----- images/common/init_command.sh | 20 +++++++++++ images/openwisp_nginx/Dockerfile | 3 +- images/openwisp_nginx/nginx.template.conf | 2 ++ 4 files changed, 57 insertions(+), 10 deletions(-) diff --git a/docs/user/settings.rst b/docs/user/settings.rst index 92277f34..82893005 100644 --- a/docs/user/settings.rst +++ b/docs/user/settings.rst @@ -1028,15 +1028,39 @@ Nginx ``NINGX_REAL_REMOTE_ADDR`` ~~~~~~~~~~~~~~~~~~~~~~~~~~ -- **Explanation:** The nginx header to get the value of the real IP - address of Access points. Example if a reverse proxy is used in your - cluster (Example if you are using an Ingress), then the real IP of the - AP is most likely the ``$http_x_forwarded_for``. If - ``$http_x_forwarded_for`` returns a list, you can use ``$real_ip`` for - getting first element of the list. -- **Valid Values:** ``$remote_addr``, ``$http_x_forwarded_for``, - ``$realip_remote_addr``, ``$real_ip``. -- **Default:** ``$real_ip``. +- **Explanation:** The nginx variable passed to the application as the + client IP address (``REMOTE_ADDR``). ``$remote_addr`` is the address of + the connection to nginx and cannot be set by the client. If a reverse + proxy or an ingress is used in your cluster, keep ``$remote_addr`` and + set ``NGINX_TRUSTED_PROXIES`` instead. +- **Valid Values:** ``$remote_addr``. +- **Default:** ``$remote_addr``. + +.. warning:: + + ``$http_x_forwarded_for`` and ``$real_ip`` are read from the + ``X-Forwarded-For`` header, which can be set by any client. Do not use + them, as clients would be able to spoof their IP address. + +``NGINX_TRUSTED_PROXIES`` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +- **Explanation:** IP addresses or subnets of the reverse proxies placed + in front of nginx (e.g. a load balancer or an ingress). The client IP + address is read from the ``X-Forwarded-For`` header only when the + request comes from one of these addresses. The client IP address is also + used by the ``allow`` rules of ``NGINX_ADMIN_ALLOW_NETWORK`` and + ``NGINX_HTTPS_ALLOWED_IPS``. +- **Valid Values:** A comma separated string of valid IP addresses or IP + networks. +- **Default:** ``''`` (empty, ``X-Forwarded-For`` is ignored). +- **Example:** ``192.0.2.10``. + +.. warning:: + + List only the addresses of your reverse proxies. Any host in these + networks, including the Docker network of the containers, can set its + own IP address through the ``X-Forwarded-For`` header. OpenVPN ------- diff --git a/images/common/init_command.sh b/images/common/init_command.sh index 3a78579e..76aafc42 100644 --- a/images/common/init_command.sh +++ b/images/common/init_command.sh @@ -58,6 +58,26 @@ elif [ "$MODULE_NAME" = 'nginx' ]; then # correctly into the nginx configuration template. NGINX_EVENTS_BLOCK=$(printf "%b" "${NGINX_EVENTS_BLOCK:-}") export NGINX_EVENTS_BLOCK + case "${NINGX_REAL_REMOTE_ADDR:-}" in + "\$real_ip" | "\$http_x_forwarded_for") + echo "WARNING: NINGX_REAL_REMOTE_ADDR=${NINGX_REAL_REMOTE_ADDR} lets clients spoof their IP address, use NGINX_TRUSTED_PROXIES instead." >&2 + ;; + esac + NGINX_REAL_IP_BLOCK="" + set -f + for proxy in $(echo "${NGINX_TRUSTED_PROXIES:-}" | tr ',' ' '); do + if ! echo "$proxy" | grep -Eq '^[0-9A-Fa-f:.]+(/[0-9]{1,3})?$'; then + echo "ERROR: invalid NGINX_TRUSTED_PROXIES entry: ${proxy}" >&2 + exit 1 + fi + NGINX_REAL_IP_BLOCK="${NGINX_REAL_IP_BLOCK}set_real_ip_from ${proxy};\n " + done + set +f + if [ -n "$NGINX_REAL_IP_BLOCK" ]; then + NGINX_REAL_IP_BLOCK="${NGINX_REAL_IP_BLOCK}real_ip_header X-Forwarded-For;\n real_ip_recursive on;" + fi + NGINX_REAL_IP_BLOCK=$(printf "%b" "$NGINX_REAL_IP_BLOCK") + export NGINX_REAL_IP_BLOCK # Use a sentinel value when the variable is unset. Since envsubst cannot # conditionally omit directives, we later remove any line containing this # sentinel from the generated nginx.conf. diff --git a/images/openwisp_nginx/Dockerfile b/images/openwisp_nginx/Dockerfile index d79f7a60..06261074 100644 --- a/images/openwisp_nginx/Dockerfile +++ b/images/openwisp_nginx/Dockerfile @@ -136,7 +136,8 @@ ENV MODULE_NAME=nginx \ NGINX_WORKER_CONNECTIONS=1024 \ NGINX_WORKER_RLIMIT_NOFILE=__UNSET__ \ NGINX_EVENTS_BLOCK='' \ - NINGX_REAL_REMOTE_ADDR='$real_ip' \ + NINGX_REAL_REMOTE_ADDR='$remote_addr' \ + NGINX_TRUSTED_PROXIES='' \ NGINX_SSL_PORT=443 \ # USWGI pass_port DASHBOARD_APP_PORT=8000 \ diff --git a/images/openwisp_nginx/nginx.template.conf b/images/openwisp_nginx/nginx.template.conf index aad7f3f8..46d5be05 100644 --- a/images/openwisp_nginx/nginx.template.conf +++ b/images/openwisp_nginx/nginx.template.conf @@ -41,6 +41,8 @@ http { default ${DOLLAR}remote_addr; } + ${NGINX_REAL_IP_BLOCK} + server_names_hash_bucket_size $NGINX_SERVER_NAME_HASH_BUCKET; server_tokens off;