Skip to content

Add password complexity enforcement via zxcvbn - #23996

Open
Fryguy wants to merge 1 commit into
ManageIQ:masterfrom
Fryguy:password-hardening
Open

Fryguy wants to merge 1 commit into
ManageIQ:masterfrom
Fryguy:password-hardening

Conversation

@Fryguy

@Fryguy Fryguy commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Use the zxcvbn-ruby gem to enforce password strength when a user sets or changes their password (database auth mode only). Complexity checks are skipped when the password virtual attribute is not set, so existing passwords on upgrade are unaffected.

Configuration lives under Settings.authentication.password_rules:

  • complexity_min_score: 0-4 zxcvbn score threshold (default 3 in production and test; default 0 in development)
  • complexity_extra_words: operator-defined terms to treat as weak

ManageIQ-specific terms (manageiq, miq, vmdb) are hardcoded as a built-in dictionary and are not configurable.

The seeded admin user (smartvm password) bypasses the check via a skip_complexity_check flag set during User.seed, allowing first-boot to succeed while keeping enforcement active for all subsequent changes.

Passwords over 72 bytes are already rejected by has_secure_password before the complexity check runs, so no separate length guard is needed.

Includes a test for the operator-generated password format (RawURLEncoding of 12 random bytes), which scores 4 on zxcvbn.

@jrafanie Please review.

Depends on

Comment thread config/settings.yml
:locked_account_timeout: 2.minutes
:amazon_key:
:amazon_secret:
:password_rules:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

i structured it this way because I plan to add expiry_days in a follow up PR, which isn't complexity based, but is a password rule.

Would like your thoughts on naming here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Linux calls this password quality (https://linux.die.net/man/5/pwquality.conf), password rules is good too though

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

My only thought here is do we expect to use these rules on things other than passwords? Maybe more generic secret_rules if we anticipate wanting to handle secrets, keys, passwords, etc.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good question - I don't think so. This is very specifically for database auth passwords. All other passwords and secrets we have come from outside sources so we can't control them.

@Fryguy

Fryguy commented Oct 1, 2026

Copy link
Copy Markdown
Member Author

@miq-bot cross-repo-test /all

@Fryguy

Fryguy commented Oct 1, 2026

Copy link
Copy Markdown
Member Author

@agrare @bdunne Would also like your thoughts here.

Comment thread config/settings.yml Outdated
:amazon_key:
:amazon_secret:
:password_rules:
:complexity_enforce: true

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'm considering adding a config/settings/development.yml entry here setting this to false. Thoughts?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should this just be password_rules.enforce? I'm assuming if we added other non-complexity related rules this would disable those as well

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

So, as mentioned in the other comment, I'm going to add expiry_days here in a future PR, which really is independent of complexity (i.e. you can disable complexity checks and keep expiry checks or vice-versa. So this enforce is specific to complexity checks.

Actually now that we're mentioning it though, i don't need a separate enforce flag. For expiry, my plan is to just set to nil or false (or 0) to disable it, and I can do the same thing here with complexity_min_score. Setting the min_score to 0 would just disable complexity checks.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ok i dropped this particular setting in favor of the caller can just set complexity_min_score to 0. Then there's also no ambiguity about whether the settings affects all rules or just a subset of rules.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Right setting min_score to 0 would disable the complexity check but not the words like vmdb or miq Ah actually no it looks like that would skip this as well.

I guess do you intend to have a way to disable all password rules, just password complexity rules.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Specifically for complexity though, should we set min_score to 0 in dev?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes I think so

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ok updated to set to 0 in dev mode.

@Fryguy Fryguy added this to Roadmap Oct 1, 2026
@Fryguy Fryguy added this to the Uhlmann milestone Oct 1, 2026
@Fryguy Fryguy moved this to In progress in Roadmap Oct 1, 2026
@Fryguy
Fryguy force-pushed the password-hardening branch 5 times, most recently from ccc1b88 to a67f551 Compare October 2, 2026 14:17
Use the zxcvbn-ruby gem to enforce password strength when a user sets
or changes their password (database auth mode only). Complexity checks
are skipped when the password virtual attribute is not set, so existing
passwords on upgrade are unaffected.

Configuration lives under Settings.authentication.password_rules:
- complexity_min_score: 0-4 zxcvbn score threshold (default 3 in
  production and test; default 0 in development)
- complexity_extra_words: operator-defined terms to treat as weak

ManageIQ-specific terms (manageiq, miq, vmdb) are hardcoded as a
built-in dictionary and are not configurable.

The seeded admin user (smartvm password) bypasses the check via a
skip_complexity_check flag set during User.seed, allowing first-boot
to succeed while keeping enforcement active for all subsequent changes.

Passwords over 72 bytes are already rejected by has_secure_password
before the complexity check runs, so no separate length guard is needed.

Includes a test for the operator-generated password format
(RawURLEncoding of 12 random bytes), which scores 4 on zxcvbn.
@Fryguy
Fryguy force-pushed the password-hardening branch from a67f551 to 1f2f999 Compare October 2, 2026 18:46
@miq-bot

miq-bot commented Oct 2, 2026

Copy link
Copy Markdown
Member

Checked commit Fryguy@1f2f999 with ruby 3.3.12, rubocop 1.91.0, haml-lint 0.78.0, and yamllint 1.37.1
6 files checked, 0 offenses detected
Everything looks fine. ⭐

@agrare

agrare commented Oct 2, 2026

Copy link
Copy Markdown
Member

@Fryguy almost certainly a future task, but would we want these rules to be different depending on role / group / tenant? Maybe administrators have a higher complexity requirement, or one tenant has a stricter requirement than another. The current Settings are global, but maybe we merge MiqGroup#settings?

Comment thread app/models/user.rb
:allow_nil => true, :message => "must be a valid email address"},
:length => {:maximum => 255}
validates :current_group, :inclusion => {:in => proc { |u| u.miq_groups }, :allow_nil => true, :if => :current_group_id_changed?}
validate :password_complexity, :if => :password

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we check the validity of the password in the UI if the password isn't strong enough? It might be easier to check it earlier than at save time.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We check the validity on change password (so that's create or update). This line was interesting, but the way has_secure_password works, :password is not a real attribute stored in the database. It only has a lifetime when someone sets the value in the object, which will only happen if someone intends to change it. Any other time, such as when you query the User object, the :password is nil. So this line really says, "if someone sets the password attribute, then run the validation on save". This allows other non-password changes, such as an email change to not trigger the complexity heck, thereby allowing existing passwords to be considered "ok". I intentionally did not want to prevent upgrades or settings changes from suddenly breaking all existing passwords (even, potentially, the super-admin password).

I will have a follow up PR allowing super-admins to force password changes on demand, and my intention is to create a tool that an admin can run to audit all passwords against the rules and force a password change. This allows the admin to opt-in to that for existing passwords / users.

@Fryguy Fryguy Oct 2, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

If you're asking whether we could update the UI to do strength checks right in the UI - I think so, but that will be a follow up "nice to have". I am concerned we would need the same strength checks on both sides, so I'm not sure how that would work.

EDIT: Actually yeah, I'm pretty sure we can do that. zxcvbn is actually dropbox's algorithm and it's been ported to all the languages. That link is the original javascript version we could use on the UI side, and have a strength checker ahead of time. See also https://upmostly.com/tutorials/build-a-password-strength-meter-react

@Fryguy

Fryguy commented Oct 2, 2026

Copy link
Copy Markdown
Member Author

@Fryguy almost certainly a future task, but would we want these rules to be different depending on role / group / tenant? Maybe administrators have a higher complexity requirement, or one tenant has a stricter requirement than another. The current Settings are global, but maybe we merge MiqGroup#settings?

That's a cool idea, and I don't think the current PR prevents that. Definitely for a future PR.

@Fryguy

Fryguy commented Oct 2, 2026 •

Copy link
Copy Markdown
Member Author

I added a PR to UI and API (listed as dependent PRs) to remove weak passwords in tests and use the default factory password as much as possible, only using a strong password where the test actually exercises a code path that needs it (e.g. where we tests password changes over the API).

jrafanie pushed a commit to ManageIQ/manageiq-ui-classic that referenced this pull request Oct 7, 2026
In ManageIQ/manageiq#23996 password complexity rules are being added,
which will fail validation when simple passwords like 'x' or 'smartvm'
are passed into user factories.

Using the default password ('dummy') avoids failing the password
complexity check while still satisfying the test requirements.

Part of ManageIQ/manageiq#23996
jrafanie pushed a commit to ManageIQ/manageiq-api that referenced this pull request Oct 8, 2026
Factory user calls drop explicit weak passwords so the factory default
digest path is used; api_basic_authorize calls use the literal "dummy"
string. API payload tests use "Tr0ub4dor&3" since those go through the
controller which enforces complexity validation.

Part of ManageIQ/manageiq#23996
@jrafanie

jrafanie commented Oct 8, 2026

Copy link
Copy Markdown
Member

@Fryguy I think all dependent PRs have been merged, it looks like cross repo was kicked off before them, maybe kick that and we can decide to merge this when it's green.

@Fryguy

Fryguy commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

@miq-bot cross-repo-test /all

@jrafanie

jrafanie commented Oct 8, 2026

Copy link
Copy Markdown
Member

ManageIQ/manageiq-api#1351 should fix the api test failures

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

4 participants