Skip to content
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

Add score based password verification #2557

Conversation

willyborankin
Copy link
Collaborator

@willyborankin willyborankin commented Mar 15, 2023

Description

The aim of this PR is to add support for a score-based password verification using zxcvbn library.
For that 2 new settings were added to the plugin configuration:

  • plugins.security.restapi.password_min_length - minimum password length, default and minimum is 8
  • plugins.security.restapi.password_score_based_validation_strength - the strength of the valid password
    Possible values:
    • fair - very guessable password: protection from throttled online attacks
    • good - somewhat guessable password: protection from unthrottled online attacks
    • strong - safely unguessable password: moderate protection from offline slow-hash scenario
    • very_strong - very unguessable password: strong protection from offline slow-hash scenario
      By default the plugin always checks strength of the password and its minimal length together with the regular expression if its set.

The calculation time for passwords around 100 characters is ~100ms as result to avoid of performance degradation for big passwords I suggest to set max length of the password to 100.

All other settings I mentioned before are not needed.

Check List

  • New functionality includes testing
  • New functionality has been documented
  • Commits are signed per the DCO using --signoff

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

Copy link
Member

@peternied peternied left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice security improvement around the passwords.

@willyborankin Would you mind creating a separate issue to discuss making these options the default? I think this would be a good security posture improvement and would want contributors / community members to have a chance to weight in without delaying the functionality getting merged.

A quick note on performance - looking at the documentation [link] there is a typically small impact of ~5-20ms I think this is a worthwhile tradeoff as password scoring only happens when passwords are set/updated.

@willyborankin
Copy link
Collaborator Author

Nice security improvement around the passwords.

@willyborankin Would you mind creating a separate issue to discuss making these options the default? I think this would be a good security posture improvement and would want contributors / community members to have a chance to weight in without delaying the functionality getting merged.

A quick note on performance - looking at the documentation [link] there is a typically small impact of ~5-20ms I think this is a worthwhile tradeoff as password scoring only happens when passwords are set/updated.

Sure, I will prepare an issue to discuss and address your comments. I will try to do it today,.

Copy link
Member

@DarshitChanpura DarshitChanpura left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work @willyborankin ! This is of great value to security posture!

One generic comment is to add a brief documentation on the new files. This would help in debugging in future.

final boolean isDebugEnabled = log.isDebugEnabled();

final String username = Utils.coalesce(request.param("name"), hasParams() ? (String) param[0] : null);
if (!Strings.isNullOrEmpty(regex) || useScoreBasedValidation) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a brief comment here stating what this condition does?

Copy link
Collaborator Author

@willyborankin willyborankin Apr 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i changed code so there is no such condition anymore

log.debug("Username must not match password");
}
this.errorType = ErrorType.INVALID_PASSWORD;
return false;
}
}
if (!Strings.isNullOrEmpty(regex)) {
return regexBasedPasswordValidation(regex, password);
} else if (useScoreBasedValidation) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why either or here? Why not have both?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it

}
final Strength strength = new Zxcvbn().measure(password, ImmutableList.of(username));
if (strength.getScore() < scoreStrength.score()) {
this.errorType = ErrorType.INVALID_PASSWORD;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about changing the errorType to say WEAK_PASSWORD here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added WEAK_PASSWORD an SIMILAR_PASSWORD error types

final boolean similar = strength.getSequence()
.stream()
.filter(m -> m.matchedWord != null)
.anyMatch(m -> m.matchedWord.equals("similar"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice


addUserWithPassword("ok4", "$1aAAAAAAAAC", HttpStatus.SC_OK);

//its not allowed to use the username as password (case insensitive)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the behavior when username and password is only 1 or 2 letters off? Will the password be allowed as valid?

Copy link
Collaborator Author

@willyborankin willyborankin Apr 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in this case the score == 0 which means a weak password

Copy link
Collaborator

@RyanL1997 RyanL1997 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work @willyborankin! This is definitely a huge enhancement for current password mechanism. I also left some thoughts attach to the previous comments with some details about the human understandable wording and some of the validation conditions.

@willyborankin
Copy link
Collaborator Author

@peternied, @DarshitChanpura and @RyanL1997 thank you for the feedback I will address all you comments asap. As @peternied suggested I created a feature request #2569 and tried to explain how the library works and how to improve existing password verification functionality in it. So lets move our discussion there.

@codecov-commenter
Copy link

codecov-commenter commented Apr 5, 2023

Codecov Report

Merging #2557 (8a7475d) into main (7c4e06d) will increase coverage by 0.03%.
The diff coverage is 84.69%.

❗ Current head 8a7475d differs from pull request most recent head df5109a. Consider uploading reports for the commit df5109a to get more accurate results

@@             Coverage Diff              @@
##               main    #2557      +/-   ##
============================================
+ Coverage     61.31%   61.34%   +0.03%     
+ Complexity     3408     3347      -61     
============================================
  Files           272      261      -11     
  Lines         18846    18591     -255     
  Branches       3295     3277      -18     
============================================
- Hits          11555    11405     -150     
+ Misses         5690     5592      -98     
+ Partials       1601     1594       -7     
Impacted Files Coverage Δ
...g/opensearch/security/support/ConfigConstants.java 94.44% <ø> (ø)
...ity/dlic/rest/validation/CredentialsValidator.java 70.00% <63.63%> (+4.21%) ⬆️
...curity/dlic/rest/validation/PasswordValidator.java 84.05% <84.05%> (ø)
.../opensearch/security/OpenSearchSecurityPlugin.java 80.12% <100.00%> (-0.04%) ⬇️
...est/validation/AbstractConfigurationValidator.java 83.64% <100.00%> (+1.09%) ⬆️

... and 36 files with indirect coverage changes

@willyborankin willyborankin force-pushed the score-based-password-validation branch 2 times, most recently from c2b2065 to a156c99 Compare April 5, 2023 15:48
DarshitChanpura
DarshitChanpura previously approved these changes Apr 5, 2023

private static final int MAX_LENGTH = 100;
private final static Predicate<Match> USERNAME_SIMILARITY_CHECK = m ->
m.pattern == com.nulabinc.zxcvbn.Pattern.Dictionary && m.dictionaryName.equals("user_inputs");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my understanding why the pattern check against hard code string "user_inputs"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user_inputs the default dictionary which library creates automatically to check similarity. I will add a comment otherwise it is confusing. So all you need is just check the dictionary name since matchedWord is a password.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay..thank yoU!

import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH;
import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX;

public class PasswordValidatorTest {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nicely done! 👏


ErrorType validate(final String username, final String password) {
if (minPasswordLength > 0 && password.length() < minPasswordLength) {
logger.debug(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These seems like well written error messages seem valuable, could we return this messages instead to be visible to the user. If we did that, we could remove the password_validation_error_message (and related) value that is in the config. What do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well I would prefer to stay it as is. The main reason, that some companies prefer to send the same error message when a user creates/updates the password (kinda less possibilities for attackers to know what is the password creation rules :-) which I personally do not believe in since if somebody wanna hack the system it will do it).

peternied
peternied previously approved these changes Apr 6, 2023
Copy link
Member

@peternied peternied left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still a couple of outstanding comments, but I'm happy with the change as is. Thanks for the great new feature @willyborankin !

@RyanL1997
Copy link
Collaborator

Hi @willyborankin, thanks for all the follow-ups! My comments have been resolved, and I'm happy to merge this change if we resolve the branch conflicts.

Signed-off-by: Andrey Pleskach <ples@aiven.io>
@willyborankin willyborankin force-pushed the score-based-password-validation branch from 7411d38 to df5109a Compare May 15, 2023 15:51
@willyborankin
Copy link
Collaborator Author

@peternied, @scrawfor99, @RyanL1997 and @DarshitChanpura all tests now green

Copy link
Contributor

@stephen-crawford stephen-crawford left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @willyborankin. This looks great.

Copy link
Member

@cwperks cwperks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for another great contribution @willyborankin !

@stephen-crawford stephen-crawford merged commit 15860b6 into opensearch-project:main May 15, 2023
@cwperks cwperks added the backport 2.x backport to 2.x branch label May 16, 2023
@opensearch-trigger-bot
Copy link
Contributor

The backport to 2.x failed:

The process '/usr/bin/git' failed with exit code 1

To backport manually, run these commands in your terminal:

# Fetch latest updates from GitHub
git fetch
# Create a new working tree
git worktree add .worktrees/backport-2.x 2.x
# Navigate to the new working tree
cd .worktrees/backport-2.x
# Create a new branch
git switch --create backport/backport-2557-to-2.x
# Cherry-pick the merged commit of this pull request and resolve the conflicts
git cherry-pick -x --mainline 1 15860b65f33861f875b62ed57b6d8b7ab3c8a65d
# Push it to GitHub
git push --set-upstream origin backport/backport-2557-to-2.x
# Go back to the original working tree
cd ../..
# Delete the working tree
git worktree remove .worktrees/backport-2.x

Then, create a pull request where the base branch is 2.x and the compare/head branch is backport/backport-2557-to-2.x.

sebastianmichalski pushed a commit to sebastianmichalski/security that referenced this pull request May 19, 2023
Signed-off-by: Andrey Pleskach <ples@aiven.io>
@willyborankin willyborankin deleted the score-based-password-validation branch May 22, 2023 15:18
@willyborankin willyborankin restored the score-based-password-validation branch May 22, 2023 15:23
MaciejMierzwa pushed a commit to MaciejMierzwa/security that referenced this pull request Jun 13, 2023
Signed-off-by: Andrey Pleskach <ples@aiven.io>
Signed-off-by: Maciej Mierzwa <dev.maciej.mierzwa@gmail.com>
MaciejMierzwa pushed a commit to MaciejMierzwa/security that referenced this pull request Jun 13, 2023
Signed-off-by: Andrey Pleskach <ples@aiven.io>
Signed-off-by: Maciej Mierzwa <dev.maciej.mierzwa@gmail.com>
@willyborankin willyborankin deleted the score-based-password-validation branch June 15, 2023 19:50
samuelcostae pushed a commit to samuelcostae/security that referenced this pull request Jun 19, 2023
Signed-off-by: Andrey Pleskach <ples@aiven.io>
samuelcostae pushed a commit to samuelcostae/security that referenced this pull request Jun 19, 2023
Signed-off-by: Andrey Pleskach <ples@aiven.io>
Signed-off-by: Sam <samuel.costa@eliatra.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport 2.x backport to 2.x branch
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants