sourcediver.org


2015/01/18
Nginx is currently a common choice when setting up reverse proxies for ruby web services. I currently serve all projects using unicorn, which creates a unix socket through which nginx talks to the application. The nginx configuration is pretty straightforward, you define a server with some rules and a location for your application. Whenever a location matches a path of your application, nginx will forward this request to the application through the unix socket.
In one particular project, I am using the padrino framework which uses sinatra’s rack protection to prevent common attacks like XSS or Cross-Site-Request-Forgery attemps.
The nginx config was something like this
upstream app_server {
server unix:/var/sockets/padrino_app.sock fail_timeout=0;
}
server {
listen 80;
server_name example.org
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl spdy;
ssl on;
ssl_session_cache shared:SSL:20m;
ssl_session_timeout 10m;
ssl_certificate /etc/ssl/private/example.org.crt;
ssl_certificate_key /etc/ssl/private/example.org.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
ssl_dhparam /etc/ssl/private/dhparam.pem;
charset utf-8;
server_name example.org;
keepalive_timeout 5;
root /home/foo/example.org/current/public;
access_log /home/foo/example.org/shared/log/nginx_access.log;
error_log /home/foo/example.org/shared/log/nginx_error.log;
rewrite_log on;
location / {
try_files $uri $uri/ /index.html =404;
}
location ~* ^/app/ {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
proxy_buffering on;
proxy_redirect off;
if (!-f $request_filename) {
proxy_pass
http://app_server;
break;
}
}
}
This config always worked for setups without SSL so I was certain that it will just work with the SSL rewrite.
However I was getting 403 - Forbidden
for all requests !GET
. These
errors also showed up in the log files:
attack reported by Rack::Protection::AuthenticityToken
I was puzzled since all requests had their proper authenticity token and nothing was different from my development configuration where everything worked, same ruby version, same gemset.
After some debugging I found the solution for the problem which is pretty simple and obvious.
rack-protection
also checks the protocol which has been used for the
request - if nginx does not send this information correctly, there will be
a mismatch of the protocol (assumed http) and the referer (https) which
will result in a 403
.
Just add this line to your nginx config:
proxy_set_header X-Forwarded-Proto $scheme;
This will tell your application that the original request came in using
https
even though the request from nginx to unicorn is http
.
Update 2015/12/02: The rack module that is responsible for the error seems to be Rack::Protection::HttpOrigin. Thanks to @907th for the hint!