Done http://code.google.com/p/web2py/issues/detail?id=1023
On Friday, September 21, 2012 2:05:41 PM UTC-4, Massimo Di Pierro wrote: > > Yarin, please open an issue on google code as suggested enhancement so ti > does not get lost. Also feel free to move the discussion on web2py > developers. > > > On Friday, 21 September 2012 12:22:57 UTC-5, Yarin wrote: >> >> Here's a complete example of our own implementation (simplified, >> untested) using the proposed auth settings: >> >> *In our model:* >> >> def force_https(trust_proxy = False, secure_session = False): >> """ Enforces HTTPS in appropriate environments >> >> Args: >> trust_proxy: Can we trust proxy header 'http_x_forwarded_proto' >> to determine SSL. >> (Set this only if ALL your traffic comes via trusted proxy.) >> secure_session: Secure the session as well. >> (Do this only when enforcing SSL throughout the session) >> """ >> >> # If cronjob or scheduler, exit: >> cronjob = request.global_settings.cronjob >> cmd_options = request.global_settings.cmd_options >> if cronjob or (cmd_options and cmd_options.scheduler): >> return >> >> # If local host, exit: >> if request.env.remote_addr == "127.0.0.1": >> return >> >> # If already HTTPS, exit: >> if request.env.wsgi_url_scheme in ['https', 'HTTPS']: >> if secure_session: >> current.session.secure() >> return >> >> # If HTTPS request forwarded over HTTP via a SSL-terminating proxy, >> exit: >> if trust_proxy and request.env.http_x_forwarded_proto in ['https', >> 'HTTPS']: >> if secure_session: >> current.session.secure() >> return >> >> # Redirect to HTTPS: >> redirect(URL(scheme='https', args=request.args, vars=request.vars)) >> >> # If a login function, force SSL: >> if request.controller == 'default' and request.function == 'user' andauth >> .settings.force_ssl_login: >> force_https(trust_proxy = auth.settings.is_proxied, secure_session = >> auth.settings.force_ssl_session) >> # If user is logged in and we're enforcing a full SSL session: >> elif auth.is_logged_in() and auth.settings.force_ssl_session: >> force_https(trust_proxy = auth.settings.is_proxied, secure_session = >> True) >> >> def on_login(form): >> """ Post login redirection""" >> >> # If we're enforcing SSL on login only, redirect from HTTPS to HTTP >> immediately after login: >> if auth.settings.force_ssl_login is True and >> auth.settings.force_ssl_session >> is False: >> if request.env.wsgi_url_scheme in ['https', 'HTTPS'] or request. >> env.http_x_forwarded_proto in ['https', 'HTTPS']: >> >> # Extract the post-login url value from auth >> # (hack - look at end of login() function in tools.py. This >> belongs in Auth itself.): >> login_next_path = auth.next or auth.settings.login_next >> # Build an absolute, HTTP url from it: >> login_next_url = URL(scheme='http',c='default',f='index') >> +login_next_path >> [1:] >> # Redirect to the HTTP URL: >> redirect(login_next_url) >> >> auth.settings.login_onaccept = on_login >> >> >> >> >> On Friday, September 21, 2012 12:35:37 PM UTC-4, Yarin wrote: >>> >>> You can't detect this- it must be a setting. Please see my previous >>> answer: >>> https://groups.google.com/forum/#!msg/web2py/me1e5d6Dudk/VQRQhdiryccJ >>> >>> "you cannot detect whether proxied traffic is real because headers are >>> unreliable. Instead you must securely set up a server behind a proxy and >>> set the .is_proxied flag explicitly." >>> >>> "you can't mix direct and proxied traffic. To be able to handle >>> proxy-terminated SSL, we need to know that *all* the traffic is via a >>> trusted proxy." >>> >>> >>> On Friday, September 21, 2012 12:05:56 PM UTC-4, Massimo Di Pierro wrote: >>>> >>>> Yes but how do you detect if is_proxied reliably? >>>> >>>> On Friday, 21 September 2012 10:28:26 UTC-5, Yarin wrote: >>>>> >>>>> FYI this is the enforcer function we wrote for our implementation- >>>>> basically a rewrite of request.requires_https(): >>>>> >>>>> def force_https(trust_proxy = False): >>>>> """ Enforces HTTPS in appropriate environments >>>>> >>>>> Args: >>>>> trust_proxy: Can we trust proxy header 'http_x_forwarded_proto' >>>>> to determine SSL. >>>>> (Set this only if ALL your traffic comes via trusted proxy.) >>>>> """ >>>>> >>>>> # If cronjob or scheduler, exit: >>>>> cronjob = request.global_settings.cronjob >>>>> cmd_options = request.global_settings.cmd_options >>>>> if cronjob or (cmd_options and cmd_options.scheduler): >>>>> return >>>>> >>>>> # If local host, exit: >>>>> if request.env.remote_addr == "127.0.0.1": >>>>> return >>>>> >>>>> # If already HTTPS, exit: >>>>> if request.env.wsgi_url_scheme in ['https', 'HTTPS']: >>>>> return >>>>> >>>>> # If HTTPS request forwarded over HTTP via SSL-terminating proxy, >>>>> exit: >>>>> if trust_proxy and request.env.http_x_forwarded_proto in ['https', >>>>> 'HTTPS']: >>>>> return >>>>> >>>>> # Redirect to HTTPS: >>>>> redirect(URL(scheme='https', args=request.args, vars=request.vars)) >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> On Friday, September 21, 2012 9:53:36 AM UTC-4, Yarin wrote: >>>>>> >>>>>> The completely naive approach would be to do: >>>>>> >>>>>> if request.env.http_x_forwarded_for and \ >>>>>> request.env.http_x_forwarded_proto in ['https', 'HTTPS']: >>>>>> # Is HTTPS... >>>>>> >>>>>> But you cannot detect whether proxied traffic is real because headers >>>>>> are unreliable. Instead it is up to the user to securely set up a server >>>>>> behind a proxy and set the .is_proxied flag themselves. >>>>>> >>>>>> *Example:* >>>>>> We put our app server behind an SSL-terminating load balancer on the >>>>>> cloud. The domain app.example.com points to the loadbalancer, so we >>>>>> configure app server's Apache to allow traffic from that domain only, >>>>>> and >>>>>> block any outside direct traffic. Then we set * >>>>>> auth.settings.is_proxied* to tell web2py "this proxy traffic is >>>>>> legit" >>>>>> >>>>>> HTTPS/443 requests will hit the loadbalancer, and be transformed to >>>>>> HTTP/80 traffic with *http_x_forwarded_for* and * >>>>>> http_x_forwarded_proto* headers set. Now we can confidently check: >>>>>> >>>>>> if auth.settings.is_proxied and \ >>>>>> request.env.http_x_forwarded_proto in ['https', 'HTTPS']: >>>>>> # Is HTTPS... >>>>>> >>>>>> In other words *http_x_forwarded_for* header is useless and you >>>>>> can't mix direct and proxied traffic. To be able to handle >>>>>> proxy-terminated >>>>>> SSL, we need to know that *all* the traffic is via a trusted proxy. >>>>>> >>>>>> >>>>>> On Friday, September 21, 2012 8:40:35 AM UTC-4, Massimo Di Pierro >>>>>> wrote: >>>>>>> >>>>>>> Can you suggest a way to detect that? >>>>>>> >>>>>>> On Thursday, 20 September 2012 13:56:55 UTC-5, Yarin wrote: >>>>>>>> >>>>>>>> @Massimo - that'd be great. >>>>>>>> >>>>>>>> One more kink to throw in is recognizing proxied SSL calls. This >>>>>>>> requires knowing whether you can trust the traffic headers (e.g. >>>>>>>> having >>>>>>>> apache locked down to all traffic except your load balancer), so maybe >>>>>>>> we >>>>>>>> need a trust_proxied_ssl or is_proxied setting somewhere? >>>>>>>> >>>>>>>> if request.env.http_x_forwarded_for and >>>>>>>> request.env.http_x_forwarded_proto >>>>>>>> in ['https', 'HTTPS'] and auth.settings.is_proxied: >>>>>>>> >>>>>>>> >>>>>>>> >>>>>>>> On Thursday, September 20, 2012 12:52:22 PM UTC-4, Massimo Di >>>>>>>> Pierro wrote: >>>>>>>>> >>>>>>>>> I think we should do something like this. >>>>>>>>> >>>>>>>>> I think we should have auth.settings.force_ssl_login >>>>>>>>> and auth.settings.force_ssl_login. >>>>>>>>> We could add secure=True option to existing requires validators. >>>>>>>>> >>>>>>>>> This should not be enforced from localhost. >>>>>>>>> >>>>>>>>> >>>>>>>>> On Thursday, 20 September 2012 09:07:14 UTC-5, Yarin wrote: >>>>>>>>>> >>>>>>>>>> A proposal for improving SSL support in web2py >>>>>>>>>> >>>>>>>>>> For authenticated web applications, there are two "grades" of SSL >>>>>>>>>> implementions: Forcing SSL on login, vs forcing SSL on the entire >>>>>>>>>> authenticated session. >>>>>>>>>> >>>>>>>>>> In the first case, HTTPS is forced on login/registration, but >>>>>>>>>> reverts back to HTTP upon authentication. This protects against >>>>>>>>>> passwords >>>>>>>>>> from being sent unencrypted, but won't prevent session hijacking as >>>>>>>>>> the >>>>>>>>>> session cookie can still be compromised on subsequent HTTP requests. >>>>>>>>>> (See >>>>>>>>>> Firesheep <http://codebutler.com/firesheep> for details). >>>>>>>>>> Nonetheless, many sites choose this approach for performance >>>>>>>>>> reasons, as >>>>>>>>>> SSL-delivered content is not cached by browsers as efficiently >>>>>>>>>> (discussed >>>>>>>>>> on 37signals >>>>>>>>>> blog<http://37signals.com/svn/posts/1431-mixed-content-warning-how-i-loathe-thee> >>>>>>>>>> ). >>>>>>>>>> >>>>>>>>>> In the second case, the entire authenticated session is secured >>>>>>>>>> by forcing all traffic to go over HTTPS while a user is logged in >>>>>>>>>> *and* by securing the session cookie so that it will only be >>>>>>>>>> sent by the browser over HTTPS. >>>>>>>>>> >>>>>>>>>> (Also discussed in web2py users group - Auth over >>>>>>>>>> SSL<https://groups.google.com/d/msg/web2py/7qoHMs-4Va8/jRFOqYHri4gJ> >>>>>>>>>> ) >>>>>>>>>> >>>>>>>>>> web2py should make it easier to deal with these scenarios. I just >>>>>>>>>> implemented a case-1 type solution and it took quite a bit of work. >>>>>>>>>> >>>>>>>>>> Moreover, web2py currently provides two SSL-control functions, >>>>>>>>>> which, taken on their own, can lead to problems for the uninitiated: >>>>>>>>>> >>>>>>>>>> - session.secure() will ensure that the session cookie is >>>>>>>>>> only transmitted over HTTPS, but doesn't force HTTPS, so that for >>>>>>>>>> any >>>>>>>>>> subsequent session calls made over HTTP will simply not have >>>>>>>>>> access to the >>>>>>>>>> auth session, but this is not obvious (Correct me if I'm wrong) >>>>>>>>>> - request.requires_https() (undocumented?) is a misnomer, >>>>>>>>>> because if forces HTTPS but then assumes a case-2 scenario >>>>>>>>>> and secures the session cookie >>>>>>>>>> >>>>>>>>>> >>>>>>>>>> *Proposals:* >>>>>>>>>> >>>>>>>>>> - SSL auth settings >>>>>>>>>> - auth.settings.force_ssl_login - Forces HTTPS for >>>>>>>>>> login/registration >>>>>>>>>> - auth.settings.force_ssl_session - Forces HTTPS >>>>>>>>>> throughout an authenticated session, and secure the session >>>>>>>>>> cookie (If >>>>>>>>>> True, force_ssl_login not necessary) >>>>>>>>>> - Other more granular controls >>>>>>>>>> - @requires_https() - decorator for controller functions >>>>>>>>>> that forces HTTPS for that function only >>>>>>>>>> - 'secure=True' option on forms ensures submission over >>>>>>>>>> HTTPS >>>>>>>>>> >>>>>>>>>> >>>>>>>>>> >>>>>>>>>> --