Closed Bug 738000 Opened 13 years ago Closed 13 years ago

enable CSRF protection in existing balrog code

Categories

(Release Engineering :: General, defect, P3)

defect

Tracking

(Not tracked)

RESOLVED FIXED

People

(Reporter: bhearsum, Assigned: bhearsum)

References

Details

(Whiteboard: [balrog])

Attachments

(4 files, 2 obsolete files)

The security team says that we need to have CSRF protection for Balrog, even though it's behind a VPN. There's already some code landed that doesn't have it, and bug 672918 will add a bunch more. We should add csrf protection for existing code as soon as we can, and make sure all new code uses it, too.
http://packages.python.org/Flask-WTF/ seems to say that we only need to put it on our forms, and then make sure it's passed with AJAX requests.
Priority: -- → P3
Whiteboard: [balrog]
We will need to update the csrf token on the admin-ui pages when the form is submitted. Currently we can only do one 'submit' per reload of the page. If we make a mistake on a form, or want to do multiple operations, we need to reload. We shouldn't need to reload for each form submission.
Catlee suggested that we could return a new csrf token in the HTTP headers of PUT/POST/DELETE responses. Eg, X-CSRF-TOKEN or something. That seems a bit nicer than mixing it in with the real data.
I looked into this further today and found that the latest versions of WTForms and Flask-WTF don't require csrf token regeneration with every request - they use an hmac digest that's based on the secret key to generate the token, and set a relatively short expiry time. This seems like a much easier way to do things...I'm going to try upgrading us.
Assignee: nobody → bhearsum
Attached patch client csrf support (obsolete) — Splinter Review
I think this patch is mostly straightforward. I did a little bit of refactoring, to make API a proper base class, and taught it how to get CSRF tokens and data versions from headers. I also had to use a session because the WTForms CSRF protection puts the hmac key in it.
Attachment #628901 - Flags: feedback?(rail)
I'm not sure I'm completely happy with this, as it muddies are usage of csrf_token and data_version a bit, so I'm curious what you think. For templates/UI, we're still putting hidden csrf_token and data_version fields onto the pages. For API endpoints (eg, /releases/:release/builds/:platform/:locale), we return them in X-CSRF-Token and X-Data-Version headers. I was going to put the data version in the response body instead, but most of those methods don't have a nice place to put it, because they return parts or all of a blob that aren't wrapped with a variable name. Eg, returning {'complete': {...}, 'data_version': 1} seems a bit awkward. However, I'm not sure it makes sense to put data version in a header either... What do you think?
Attachment #628903 - Flags: feedback?(catlee)
Comment on attachment 628901 [details] [diff] [review] client csrf support Review of attachment 628901 [details] [diff] [review]: ----------------------------------------------------------------- ::: lib/python/balrog/client/api.py @@ +13,5 @@ > > +def is_csrf_token_expired(token): > + from datetime import datetime > + expiry = token.split('##')[0] > + if expiry <= datetime.now().strftime('%Y%m%d%H%M%S'): * This function isn't used anywhere. Do you need it? * split and strftime may raise exceptions, be careful :)
Attachment #628901 - Flags: feedback?(rail) → feedback+
(In reply to Rail Aliiev [:rail] from comment #8) > Comment on attachment 628901 [details] [diff] [review] > client csrf support > > Review of attachment 628901 [details] [diff] [review]: > ----------------------------------------------------------------- > > ::: lib/python/balrog/client/api.py > @@ +13,5 @@ > > > > +def is_csrf_token_expired(token): > > + from datetime import datetime > > + expiry = token.split('##')[0] > > + if expiry <= datetime.now().strftime('%Y%m%d%H%M%S'): > > * This function isn't used anywhere. Do you need it? > * split and strftime may raise exceptions, be careful :) Whoops, I forgot to re-add expiration checking after I refactored!
Attachment #628901 - Attachment is obsolete: true
Attachment #629181 - Flags: review?(rail)
Attachment #629181 - Flags: review?(rail) → review+
Comment on attachment 628903 [details] [diff] [review] upgrade flask, wtforms, flask-wtf; fix up server side csrf implementation Review of attachment 628903 [details] [diff] [review]: ----------------------------------------------------------------- looks good to me! ::: auslib/web/views/__init__.py @@ +3,3 @@ > from auslib.web.views.permissions import * > from auslib.web.views.releases import * > from auslib.web.views.rules import * import *'s are bad...how hard would it be to import views explicitly? ::: requirements/prod.txt @@ +1,4 @@ > +flask==0.8 > +Werkzeug==0.8.3 > +wtforms==1.0.1 > +flask-wtf==0.6 are these required even though they're available in the vendor lib?
Attachment #628903 - Flags: feedback?(catlee) → feedback+
(In reply to Chris AtLee [:catlee] from comment #11) > Comment on attachment 628903 [details] [diff] [review] > upgrade flask, wtforms, flask-wtf; fix up server side csrf implementation > > Review of attachment 628903 [details] [diff] [review]: > ----------------------------------------------------------------- > > looks good to me! > > ::: auslib/web/views/__init__.py > @@ +3,3 @@ > > from auslib.web.views.permissions import * > > from auslib.web.views.releases import * > > from auslib.web.views.rules import * > > import *'s are bad...how hard would it be to import views explicitly? Easy enough to fix. > ::: requirements/prod.txt > @@ +1,4 @@ > > +flask==0.8 > > +Werkzeug==0.8.3 > > +wtforms==1.0.1 > > +flask-wtf==0.6 > > are these required even though they're available in the vendor lib? I'm using the model described here: https://github.com/mozilla/zamboni-lib. I believe the idea is that prod.txt contains the list of libraries in your vendor lib. You use this file when you build it, eg: pip install -I --install-option="--home=`pwd`/vendor" --src='vendor/src' -r requirements/prod.txt
Attachment #628903 - Attachment is obsolete: true
Attachment #630192 - Flags: review?(catlee)
Attachment #629181 - Flags: checked-in+
Attachment #630192 - Flags: review?(catlee) → review+
Comment on attachment 630192 [details] [diff] [review] move routing to avoid import * Jenkins run is here: https://jenkins.mozilla.org/job/Balrog/90/ I restarted the apache daemons on the web heads, to work around bug 745890.
Attachment #630192 - Flags: checked-in+
I missed an edge case in my original patch. It didn't work in the case that the release existed but the platform didn't. It would receive a 404 from the initial HEAD request, and then send a HEAD to /csrf_token. However, because it never received a data version it eventually got an HTTP 400 when trying to PUT because it was missing. This patch adds those headers to /releases/:name, so we can retrieve them in that case.
Attachment #630285 - Flags: review?(catlee)
This patch makes it possible to retrieve the csrf token and data version from a different URL than we're submitting to. We need this to support the scenario described in the previous comment.
Attachment #630286 - Flags: review?(rail)
Attachment #630286 - Flags: review?(rail) → review+
Comment on attachment 630285 [details] [diff] [review] add csrf/data_version to /releases/:name endpoint Review of attachment 630285 [details] [diff] [review]: ----------------------------------------------------------------- ::: admin.py @@ +17,5 @@ > ) > > parser.add_option("-d", "--db", dest="db", help="database to use, relative to inputdir") > parser.add_option("-p", "--port", dest="port", type="int", help="port for server") > + parser.add_option("--host", dest="host", default='127.0.0.1', help="host for server") this is the address the server will be listening on, right? e.g. you could have a public ip or 0.0.0.0 to listen on all interfaces? if so, can you adjust the help string to indicate that more clearly?
Attachment #630285 - Flags: review?(catlee) → review+
Comment on attachment 630286 [details] [diff] [review] use a different url to retrieve data_version/csrf_token Once the Balrog patch gets reviewed/landed we should have updates on Linux/Mac every day again.
Attachment #630286 - Flags: checked-in+
Comment on attachment 630285 [details] [diff] [review] add csrf/data_version to /releases/:name endpoint I fixed up the help string and landed this: https://jenkins.mozilla.org/job/Balrog/91/ After the webheads pick it up I'll restart them to pick up the changes, and then resubmit any failed mozilla-central submissions from this morning.
Attachment #630285 - Flags: checked-in+
Status: NEW → RESOLVED
Closed: 13 years ago
Resolution: --- → FIXED
Product: mozilla.org → Release Engineering
Component: General Automation → General
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: