fix(poll): resolve the client address via nginx real_ip, never from X-Forwarded-For
A client-supplied X-Forwarded-For could mint a fresh device hash per request. nginx now trusts only the edge hop (192.168.178.164) when unwinding the chain and passes the result as X-Real-IP; the service uses nothing else. setup-poll.sh --reset wipes the vote table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MScAinbyMdeNc7H2BZdnnG
This commit is contained in:
parent
9fd9d1b7d1
commit
aaba14731c
3 changed files with 22 additions and 7 deletions
|
|
@ -31,9 +31,15 @@ server {
|
|||
|
||||
# Poll counter (ops/poll/pollsvc.py) — cookie-free, rate-limited.
|
||||
location /api/poll/ {
|
||||
# Public traffic arrives from the edge proxy via 192.168.178.164.
|
||||
# Only that hop is trusted when unwinding X-Forwarded-For, so a
|
||||
# client-supplied X-Forwarded-For cannot forge a fresh "device".
|
||||
set_real_ip_from 192.168.178.164;
|
||||
real_ip_header X-Forwarded-For;
|
||||
real_ip_recursive on;
|
||||
|
||||
limit_req zone=poll burst=10 nodelay;
|
||||
proxy_pass http://127.0.0.1:8090/;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header User-Agent $http_user_agent;
|
||||
proxy_read_timeout 5s;
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ beyond the standard library.
|
|||
GET /<poll> -> {"poll": "logo", "counts": {"m": 3, "r": 5}, "total": 8}
|
||||
POST /<poll> -> body {"choice": "m"}; answers like GET plus "yours"
|
||||
|
||||
A device is identified by a salted SHA-256 of client IP + User-Agent, so
|
||||
nobody needs a cookie and the raw address is never stored. Voting again
|
||||
A device is identified by a salted SHA-256 of client IP (as resolved by
|
||||
nginx from the trusted edge proxy) + User-Agent, so nobody needs a cookie
|
||||
and the raw address is never stored. Voting again
|
||||
from the same device changes the vote instead of adding one.
|
||||
"""
|
||||
|
||||
|
|
@ -85,9 +86,10 @@ class Handler(BaseHTTPRequestHandler):
|
|||
log.debug(fmt, *args)
|
||||
|
||||
def _client_ip(self) -> str:
|
||||
xff = self.headers.get("X-Forwarded-For")
|
||||
if xff:
|
||||
return xff.split(",")[0].strip()
|
||||
# nginx resolves the trusted proxy chain (real_ip module, see
|
||||
# ops/nginx/furtka.org.conf) and hands us the result as X-Real-IP.
|
||||
# We never parse X-Forwarded-For here: its leading entries are
|
||||
# whatever the client chose to send.
|
||||
return self.headers.get("X-Real-IP") or self.client_address[0]
|
||||
|
||||
def _device(self) -> str:
|
||||
|
|
|
|||
|
|
@ -2,10 +2,17 @@
|
|||
# Install / update the poll counter on forge-runner-01. Idempotent.
|
||||
#
|
||||
# Usage (on the VM, with sudo):
|
||||
# sudo ops/poll/setup-poll.sh
|
||||
# sudo ops/poll/setup-poll.sh # install / update
|
||||
# sudo ops/poll/setup-poll.sh --reset # ... and wipe all votes
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
if [ "${1:-}" = "--reset" ]; then
|
||||
systemctl stop furtka-poll 2>/dev/null || true
|
||||
rm -f /var/lib/furtka-poll/votes.db
|
||||
echo "votes wiped"
|
||||
fi
|
||||
|
||||
install -d -m 0755 /opt/furtka-poll
|
||||
install -m 0644 "$HERE/pollsvc.py" /opt/furtka-poll/pollsvc.py
|
||||
install -m 0644 "$HERE/furtka-poll.service" /etc/systemd/system/furtka-poll.service
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue