-
Notifications
You must be signed in to change notification settings - Fork 687
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added passlib for more flexible password hashing #3506
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #3506 +/- ##
===========================================
+ Coverage 85.69% 85.83% +0.14%
===========================================
Files 40 41 +1
Lines 2629 2655 +26
Branches 284 287 +3
===========================================
+ Hits 2253 2279 +26
- Misses 310 312 +2
+ Partials 66 64 -2
Continue to review full report at Codecov.
|
Ok, not gonna lie I am actually blown away that I managed to get a PR in that passed CI on the first attempt. This isn't a useful thing to say, but it's late, and I expected to have build error emails. Anyway. |
@@ -103,7 +103,7 @@ created by default when running ``make dev``. In addition, sources and | |||
submissions are present. The test users have the following credentials: | |||
|
|||
* **Username:** ``journalist`` or ``dellsberg`` | |||
* **Password:** ``WEjwn8ZyczDhQSK24YKM8C9a`` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This password is used for the demo and published at http://demo.securedrop.club/ and http://i18n.securedrop.club/. I'm not against changing it because it is consistent with the password constraints and will pass validation.
The only downside I can see is that it is slightly more complicated to copy/paste.
It would be nice to also have a merge request against the demo to change the index page
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we really want to keep it, I can do that, but it would mean adding (a small amount) of complexity to get code. It's already very terse, and we'd basically end up factoring out a single function call to get this behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am in favor of changing to a passphrase instead of the legacy password.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(haven't tested this, just a few comments from a first pass through the diff)
@@ -6,5 +6,5 @@ Homepage: https://securedrop.org | |||
Package: securedrop-app-code | |||
Version: 0.8.0~rc1 | |||
Architecture: amd64 | |||
Depends: python-pip,apparmor-utils,gnupg2,haveged,python,secure-delete,sqlite3,apache2-mpm-worker,libapache2-mod-wsgi,libapache2-mod-xsendfile,redis-server,supervisor,securedrop-keyring,securedrop-config | |||
Depends: python-pip,apparmor-utils,gnupg2,haveged,python,secure-delete,sqlite3,apache2-mpm-worker,libapache2-mod-wsgi,libapache2-mod-xsendfile,redis-server,supervisor,securedrop-keyring,securedrop-config,libpython2.7-dev |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
libpython2.7-dev is in security lists 👍
securedrop/models.py
Outdated
@@ -31,6 +32,9 @@ | |||
# precisely control which code paths are exercised. | |||
if os.environ.get('SECUREDROP_ENV') == 'test': | |||
LOGIN_HARDENING = False | |||
ARGON2_PARAMS = dict(memory_cost=2**3, rounds=1, parallelism=1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hey is this strictly necessary (i.e. did this provide a significant speedup)? I'm asking because having test-only code paths in prod code is in my opinion an anti-pattern and this if/else in particular is one that we can hopefully snip out some point soon
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
heartsucker@7bee4bf4317f:/home/heartsucker/code/freedomofpress/securedrop/securedrop$ python -m timeit 'from passlib.hash import argon2; argon2.using(memory_cost=2**3, rounds=1, parallelism=1).hash("test")'
10000 loops, best of 3: 189 usec per loop
heartsucker@7bee4bf4317f:/home/heartsucker/code/freedomofpress/securedrop/securedrop$ python -m timeit 'from passlib.hash import argon2; argon2.using(memory_cost=2**16, rounds=4, parallelism=1).hash("test")'
10 loops, best of 3: 284 msec per loop
So I'm gonna say no, not necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was removed.
securedrop/models.py
Outdated
self.passphrase_hash = \ | ||
argon2.using(**ARGON2_PARAMS).hash(passphrase) | ||
self.pw_hash = None | ||
self.pw_salt = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setting pw_salt
to None is such an important implementation detail that it should have a comment to indicate to everyone that in passlib.hash.argon2
salts are auto-generated and stored in the passphrase_hash
column
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in both place.
@@ -64,6 +64,12 @@ def new_journalist(): | |||
nullable=False), | |||
pw, | |||
random_bool()) | |||
if random_bool(): | |||
# to add legacy passwords back in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice 😎
Hey @heartsucker, can you confirm you were able to run through your test plan successfully? I'm having trouble upgrading in staging VMs:
|
@redshiftzero You are right. I was using a dirty vagrant box when I did that test. I pushed a change that should fix it. |
Aww crap. I overwrote my fixes for your comments with a force push. This is gonna need a fix before you re-review. |
@heartsucker Recoverable via |
@conorsch Possibly, but given that I don't access to one of the machines the code is on, fixing again is probably easier. :) |
Can someone kick the CI job since I think this is a transient error with PyPI. It's been acting up with me today. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tested an upgrade scenario in staging VMs with this branch, and I can confirm that upon a new login, passwords are hashes and stored with argon2 in the passphrase_hash
column, and that previous scrypt password/salts (pw_hash
and pw_salt
) are nulled in the database upon login of an existing journalist. The parameter selection makes sense to me. I have a couple of comments that I don't think should block merge but that we should consider in the near future:
-
A backup of a pre-migrated database causes an error 500 with an
OperationalError: (sqlite3.OperationalError) no such column: journalists.passphrase_hash
. Reinstalling (and therefore running the alembic migration again) fixes this problem. Perhaps reinstalling the securedrop-app-code package as part of the backup script might be an easy way to fix this and ensure forward compatibility for backups. -
The argon2 hashes are stored in a newly created column, and the previous hash columns are nulled. It seems like a new column / migration is required if we decide to update parameters or algorithm. Would it make sense to create a column containing an identifier to the type of hash (and params)?
-
Finally, something we should at least be aware of is that passlib appears to be the work of a single developer, and there have been no commits in the past year.
I'll let someone else take one final look at this before merging, otherwise these changes (and the very careful tests) look good to me 👍 !
This being the back job that ansible does?
If we update the params, we can always do: if self.passphrase_hash.startswith('$argon2i$known_old_args$'):
new_hash = argon2.using(**ARGS).hash(submitted_pw)
else:
new_hash = None
valid = argon2.verify(submitted_pw)
if valid and new_hash:
self.passphrase_hash = new_hash
return valid This works because the params are stored as part of the hash string:
Should we reach out and discuss maintainability? Also for a lib like this, I imagine there's not a whole lot to do once the basic features are in place. |
Booping this PR for final sign off. |
Test plan fails for me at the vagrant provision /staging/ step - looks like libpython2-dev is not getting installed:
|
Needs a rebase for conflicts with alembic, also for passing CI given merge of #3654. Following up on my previous comment about the liberal installation of dependencies, will test the proposed resolution locally and push up a patch if that resolves. |
@conorsch @zenmonkeykstop I think we actually don't need the last commit I added that included the dependencies. We only need it for exactly the test case above when updating a staging box from a non- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks great. prior to merge we just need to:
- get CI passing (see my two comments inline re: the tests CI job)
- once that is good, @zenmonkeykstop can run through the upgrade testing scenario on this PR (this PR is a perfect candidate for the upgrade scenario ✨) and confirm upgrade is smooth
since my other PR created the need for the changes described inline, lmk if you want me to add a commit addressing the two comments described inline, happy to do so
|
||
# revision identifiers, used by Alembic. | ||
revision = '2d0ce3ee5bdc' | ||
down_revision = 'faac8092c123' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test CI job is failing here as down_revision
needs to be updated to fccf57ceef02
after merge of #3619
'flagged': bool_or_none(), | ||
'last_updated': random_datetime(nullable=True), | ||
'pending': bool_or_none(), | ||
'interaction_count': random.randint(0, 1000), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and we'll need to add the UUID column here otherwise the unique constraint on UUID will cause an integrity error if UUID is null everywhere in the sources table
nothing breaks here, so we know this is safe to remove
this removes additional code paths that we shouldn't have to maintain
@redshiftzero @conorsch Ok thiiiiiiissss time I think I got it :D |
These changes look good, thanks @heartsucker! When you get a chance @zenmonkeykstop can you give this an upgrade test? Once you confirm a smooth upgrade, I'll approve this for merge. |
Tested as follows:
Looks good to me based on the above testing. |
Beautiful, thanks for the crystal clear report @zenmonkeykstop! |
Egg on face time: I took a look at the app db after finishing testing and realized the passphrase_hash column hadn't been created. Looks like there was a flaw in my upgrade testing - the debs built on the develop branch before switching to the passlib branch were being used for the upgrade! Revised procedure is as follows:
Luckily the results are the same, no functional change, but this time the db change is verified as well! |
Fantastic, thanks for following up, @zenmonkeykstop! Good to have the initial results confirmed now. N.B. I opened #3669 to track docs creating for the upgrade testing scenario. Had we had strong docs for that flow, we could have minimized the confusion around what specifically was being tested and how. |
Status
Ready for review
Description of Changes
Fixes #2918
passlib
library for handing password hashing and verificationscrypt
withargon2
Testing
Check the manual tests.
Do a manual test
Deployment
This includes legacy support for old password hashing schemes, so old journalists accounts will still be able to log in. This adds a new apt dependency, but it's in the security lists so we won't error out.
Checklist
If you made changes to the server application code:
make ci-lint
) and tests (make -C securedrop test
) pass in the development containerIf you made changes to documentation:
make docs-lint
) passed locally