diff --git a/DEVELOPER.md b/DEVELOPER.md index 55cf01b0..32cf3d2b 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -1,5 +1,23 @@ # Developer instructions +## Production deployments + +### Environment variables + +It's important to set `DUO_ENV=prod`. + +### Proxies + +Note also that `X-Forwarded-For` headers are treated as the user's real IP by +Duolicious, which assumes that there's a proxy between it and users. + +If there's no proxy, `X-Forwarded-For` headers can be spoofed by users. This +will allow malicious users to partially bypass rate limits and bans. + +Whether `X-Forwarded-For` is used or not should probably be configurable in +Duolicious, but it's currently not. Although hardcoding the solution isn't too +hard: Simply remove the use of `werkzeug.middleware.proxy_fix.ProxyFix`. + ## Running the tests Install these: diff --git a/service/application/decorators.py b/service/application/decorators.py index b872d927..6db304f1 100644 --- a/service/application/decorators.py +++ b/service/application/decorators.py @@ -7,6 +7,7 @@ import duotypes import os from pathlib import Path +from werkzeug.middleware.proxy_fix import ProxyFix import ipaddress import traceback from antispam import normalize_email @@ -65,6 +66,7 @@ def _get_remote_address() -> str: CORS_ORIGINS = os.environ.get('DUO_CORS_ORIGINS', '*') app = Flask(__name__) +app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1) app.config['MAX_CONTENT_LENGTH'] = constants.MAX_CONTENT_LENGTH; default_limits = "60 per minute; 12 per second" diff --git a/vm/nginx/duolicious.conf b/vm/nginx/duolicious.conf index cebae62e..62b22345 100644 --- a/vm/nginx/duolicious.conf +++ b/vm/nginx/duolicious.conf @@ -20,40 +20,20 @@ upstream chat_backend { server localhost:5445; server localhost:5446; - # server localhost:5447; - # server localhost:5448; - # server localhost:5449; - # server localhost:5450; - - # server localhost:5451; - # server localhost:5452; - # server localhost:5453; - # server localhost:5454; - - # server localhost:5455; - # server localhost:5456; - # server localhost:5457; - # server localhost:5458; - - # server localhost:5459; - # server localhost:5460; - # server localhost:5461; - # server localhost:5462; - - # server localhost:5463; - # server localhost:5464; - # server localhost:5465; - # server localhost:5466; - - # server localhost:5467; - # server localhost:5468; - # server localhost:5469; - # server localhost:5470; - - # server localhost:5471; - # server localhost:5472; - # server localhost:5473; - # server localhost:5474; + server localhost:5447; + server localhost:5448; + server localhost:5449; + server localhost:5450; + + server localhost:5451; + server localhost:5452; + server localhost:5453; + server localhost:5454; + + server localhost:5455; + server localhost:5456; + server localhost:5457; + server localhost:5458; } server { @@ -70,28 +50,29 @@ server { client_max_body_size 20M; client_body_buffer_size 128k; - # Cloudflare IP ranges - allow 173.245.48.0/20; - allow 103.21.244.0/22; - allow 103.22.200.0/22; - allow 103.31.4.0/22; - allow 141.101.64.0/18; - allow 108.162.192.0/18; - allow 190.93.240.0/20; - allow 188.114.96.0/20; - allow 197.234.240.0/22; - allow 198.41.128.0/17; - allow 162.158.0.0/15; - allow 104.16.0.0/13; - allow 104.24.0.0/14; - allow 172.64.0.0/13; - allow 131.0.72.0/22; - - deny all; + # Mark Cloudflare proxies as trusted + set_real_ip_from 173.245.48.0/20; + set_real_ip_from 103.21.244.0/22; + set_real_ip_from 103.22.200.0/22; + set_real_ip_from 103.31.4.0/22; + set_real_ip_from 141.101.64.0/18; + set_real_ip_from 108.162.192.0/18; + set_real_ip_from 190.93.240.0/20; + set_real_ip_from 188.114.96.0/20; + set_real_ip_from 197.234.240.0/22; + set_real_ip_from 198.41.128.0/17; + set_real_ip_from 162.158.0.0/15; + set_real_ip_from 104.16.0.0/13; + set_real_ip_from 104.24.0.0/14; + set_real_ip_from 172.64.0.0/13; + set_real_ip_from 131.0.72.0/22; # Use the header Cloudflare sets to pass the original client IP real_ip_header CF-Connecting-IP; + # If multiple proxies are involved, recursively resolve the real IP + real_ip_recursive on; + location / { proxy_pass http://api_backend; @@ -121,41 +102,35 @@ server { proxy_cache_bypass $http_upgrade; } - # listen 443 ssl; # managed by Certbot - # listen [::]:443 ssl; # managed by Certbot - # ssl_certificate /etc/letsencrypt/live/api.duolicious.app/fullchain.pem; # managed by Certbot - # ssl_certificate_key /etc/letsencrypt/live/api.duolicious.app/privkey.pem; # managed by Certbot - # include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot - # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot - } # Configuration for chat.duolicious.app server { server_name chat.duolicious.app; - # Cloudflare IP ranges - allow 173.245.48.0/20; - allow 103.21.244.0/22; - allow 103.22.200.0/22; - allow 103.31.4.0/22; - allow 141.101.64.0/18; - allow 108.162.192.0/18; - allow 190.93.240.0/20; - allow 188.114.96.0/20; - allow 197.234.240.0/22; - allow 198.41.128.0/17; - allow 162.158.0.0/15; - allow 104.16.0.0/13; - allow 104.24.0.0/14; - allow 172.64.0.0/13; - allow 131.0.72.0/22; - - deny all; + # Mark Cloudflare proxies as trusted + set_real_ip_from 173.245.48.0/20; + set_real_ip_from 103.21.244.0/22; + set_real_ip_from 103.22.200.0/22; + set_real_ip_from 103.31.4.0/22; + set_real_ip_from 141.101.64.0/18; + set_real_ip_from 108.162.192.0/18; + set_real_ip_from 190.93.240.0/20; + set_real_ip_from 188.114.96.0/20; + set_real_ip_from 197.234.240.0/22; + set_real_ip_from 198.41.128.0/17; + set_real_ip_from 162.158.0.0/15; + set_real_ip_from 104.16.0.0/13; + set_real_ip_from 104.24.0.0/14; + set_real_ip_from 172.64.0.0/13; + set_real_ip_from 131.0.72.0/22; # Use the header Cloudflare sets to pass the original client IP real_ip_header CF-Connecting-IP; + # If multiple proxies are involved, recursively resolve the real IP + real_ip_recursive on; + location / { proxy_pass http://chat_backend; @@ -166,39 +141,4 @@ server { } - # listen 443 ssl; # managed by Certbot - # listen [::]:443 ssl; # managed by Certbot - # ssl_certificate /etc/letsencrypt/live/api.duolicious.app/fullchain.pem; # managed by Certbot - # ssl_certificate_key /etc/letsencrypt/live/api.duolicious.app/privkey.pem; # managed by Certbot - # include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot - # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot - } - -# server { -# if ($host = api.duolicious.app) { -# return 301 https://$host$request_uri; -# } # managed by Certbot -# -# -# listen 80; -# listen [::]:80; -# server_name api.duolicious.app; -# return 404; # managed by Certbot -# -# -# } -# server { -# if ($host = chat.duolicious.app) { -# return 301 https://$host$request_uri; -# } # managed by Certbot -# -# -# listen 80; -# listen [::]:80; -# server_name chat.duolicious.app; -# return 404; # managed by Certbot -# -# -# -# }