Serve a static file with restricted access

1,692 views
Skip to first unread message

mrtn

unread,
Oct 16, 2012, 11:18:11 AM10/16/12
to python-...@googlegroups.com

I have a static a.html file I would much rather serve using nginx. However there is a problem. I want to restrict the access to this .html in a way that users can only visit it after visiting some other page named check. In other words, no direct access to a.html.

Normally we can use Tornado's secure cookie for this, i.e. self.set_secure_cookie('checked', 'True', httponly=True) on page check, and then check against this cookie before serving a. However, as I use nginx to serve a, there seems to be no way for nginx to verify a secure cookie produced by Tornado (nginx can check normal plain cookie, but that wouldn't be secure).

Is there any solution to this while still utilizing nginx? If not, what would be the proper way in Tornado?

 

Serge S. Koval

unread,
Oct 16, 2012, 12:12:07 PM10/16/12
to python-...@googlegroups.com
Instead of serving file through the Tornado, you can tell nginx to send file instead. All you have to do is to expose special header with path to the file and nginx will serve this file instead of proxying your response back to client. So, just do any kind of logic you need (credentials verification, etc) and then either serve something from Tornado or let nginx send the file.

Here's related article: http://wiki.nginx.org/XSendfile
Here's Django sample, which can be easily adopted to Tornado: https://gist.github.com/1776202

Serge.

mrtn

unread,
Oct 16, 2012, 3:33:35 PM10/16/12
to python-...@googlegroups.com
Instead of serving file through the Tornado, you can tell nginx to send file instead. All you have to do is to expose special header with path to the file and nginx will serve this file instead of proxying your response back to client. So, just do any kind of logic you need (credentials verification, etc) and then either serve something from Tornado or let nginx send the file.

Here's related article: http://wiki.nginx.org/XSendfile
Here's Django sample, which can be easily adopted to Tornado: https://gist.github.com/1776202

Serge.

Thank you Serge. For my understanding, and others' reference in the future, I wonder whether the following fits what you meant:

Put a.html at /some/path/protected/a.html on the filesystem.

In nginx:

location /protected/ {
    internal;
    root          /some/path;
}

In tornado:

class CheckHandler(web.RequestHandler):
    def get(self):
        # do some check logic here
        ...
        if (check_succeeded):
            redirect('/protected/a')
        else:
            ...

class ProtectedHandler(web.RequestHandler):
    def get(self, file):
        self.set_header('X-Accel-Redirect', ''.join(('/protected/', file, '.html')))
        self.finish()

...

handlers = [(r'/check', CheckHandler),
            (r'/protected/([a-zA-Z0-9_-]+$)', ProtectedHandler)]

Given the above, I would expect the following to happen, please correct me if I get anything wrong:

1. mysite.com/protected/a cannot be directly accessed, because in nginx settings, anything under /protected/ will be internal only.
2. the only way to visit a.html is go via page mysite.com/check, which will perform the checking logic. If success, a redirect will be made, and this redirect will be handled by ProtectedHandler.
3. the ProtectedHandler's get method will set the X-Accel-Redirect header, so that the static file a.html will be served by nginx.

mrtn

unread,
Oct 16, 2012, 3:40:24 PM10/16/12
to python-...@googlegroups.com

I forgot to add that, both nginx and tornado servers sit behind a HAProxy, and the load balancer is set up such that all requests to mysite.com/protected/blah go to nginx. This way, users cannot directly access anything under /protected/ as mentioned above, but the redirect within the Tornado app will be handled by the ProtectedHandler, which would allow nginx serve the file.

Serge S. Koval

unread,
Oct 16, 2012, 3:58:54 PM10/16/12
to python-...@googlegroups.com
No, this won't work.

Here's a flow:
1. You need to setup nginx to proxy requests to your tornado application. You can do it based on URL matching and, say, mysite.com/download/ will go to Tornado;

location /download/ {
    proxy_pass         http://localhost:8080/;
    proxy_redirect     off;
    ... maybe some other options as well ...
  }

2. You need to add protected directory. For example:

location /protected/ {
    internal;
    root          /some/path;
}

3. Then, in your CheckHandler you need to set protected cookie based on some condition
4. In ProtectedHandler, check if cookie was set. If it was, set X-Accel-Redirect header. Otherwise return 403 or something.

So, this how it will work:
1. User makes request to CheckHandler, cookie will be set. It can be done either through enginx or directly, as long as /download/ is on the same domain;
2. User decides to download file, clicks on the /download/ link;
3. Request will be made to the Nginx;
4. Nginx will make request to ProtectedHandler;
5. ProtectedHandler will make decision what should happen. Lets assume cookie was set;
6. Nginx will receive X-Accel-Redirect header from the Tornado and will send static file back instead of proxying original response;
7. User is happy.

Serge.

mrtn

unread,
Oct 16, 2012, 5:01:58 PM10/16/12
to python-...@googlegroups.com

So just to confirm, the reason that my approach won't work is because the redirect within CheckHandler will be routed (by HAProxy) to nginx again, instead of to ProtectedHandler? Or is there some other reason? 

I used redirect because i wanted to avoided using cookies, which may or may not work depending on user browser settings.

Thanks!

Serge S. Koval

unread,
Oct 16, 2012, 5:13:05 PM10/16/12
to python-...@googlegroups.com
It won't work, because you're redirecting to nginx protected resource, which is not accessible from outside.

Instead, you need to forward users to nginx proxy, which will forward request to your ProtectedHandler. Otherwise nginx won't know it should serve file from protected resource.

If you don't want to have 2 pages (one Check, one Protected), you can serve your CheckHandler through nginx and return this header immediately.

If you don't want to use cookies - pass some kind of token in query string (encrypt it with shared key and add some noise, so it is different every time, but you can decrypt and validate it in ProtectedHandler), etc.

Serge.

mrtn

unread,
Oct 16, 2012, 5:22:56 PM10/16/12
to python-...@googlegroups.com

Understood. I see you points. Thanks very much for the clarifications!
Reply all
Reply to author
Forward
0 new messages