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

passwordstore: Prevent using path as password #4192

Merged
merged 1 commit into from
Feb 17, 2022

Conversation

grembo
Copy link
Contributor

@grembo grembo commented Feb 12, 2022

SUMMARY

Given a password stored in path/to/secret, requesting the password
path/to will literally return path/to. This can lead to using
weak passwords by accident/mess up logic in code, based on the
state of the password store.

This is worked around by applying the same logic pass uses:
If a password was returned, check if there is a .gpg file it could
have come from. If not, treat it as missing.

Fixes #4185

ISSUE TYPE
  • Bugfix Pull Request
COMPONENT NAME

passwordstore
plugins/lookup/passwordstore

ADDITIONAL INFORMATION

See:

@ansibullbot ansibullbot added bug This issue/PR relates to a bug lookup lookup plugin new_contributor Help guide this first time contributor plugins plugin (any type) labels Feb 12, 2022
@felixfontein felixfontein added backport-4 check-before-release PR will be looked at again shortly before release and merged if possible. labels Feb 12, 2022
@felixfontein
Copy link
Collaborator

Thanks for your contribution! Could you please add a changelog fragment?

I would also suggest to split this up into two PRs, one for the path check, and the other for locking. Combining unrelated things into one PR is something that should really be avoided.

Also regarding locking: I think this should only be done on creation, not in every case. The correct solution for the gpg issues is auto-expand-secmem and not locking. This should probably be configurable (so you can force locking if you don't want to use auto-expand-secmem).

@grembo
Copy link
Contributor Author

grembo commented Feb 12, 2022

Hi @felixfontein, thanks for reviewing.

Thanks for your contribution! Could you please add a changelog fragment?

I'll look into it.

I would also suggest to split this up into two PRs, one for the path check, and the other for locking. Combining unrelated things into one PR is something that should really be avoided.

To me, they are related from a user perspective, as both issues result in passwordstore returning unexpected/inconsistent passwords. If you insist, I can split it into two PRs once the discussion on the PR finished.

Also regarding locking: I think this should only be done on creation, not in every case. The correct solution for the gpg issues is auto-expand-secmem and not locking. This should probably be configurable (so you can force locking if you don't want to use auto-expand-secmem).

Locking needs to happen in all cases (reads and writes), as

  • the logic inside passwordstore.py (regardless of gpg issues) does reads to determine if create/write operations are required, and therefore is racy. So it does a read to figure out if a secret exists and - if it doesn't - it starts creation. This can happen in parallel, which ends up in undefined/unpredictable behavior. Preventing this reliably (within one play as well when running multiple plays in parallel which are backed by the same password store) is complicated and error-prone, when done on the playbook level. So the logic in there requires locking. Also, the pass utility itself doesn't do any internal locking to prevent problems (so calls to pass aren't atomic).
  • it works around the gpg issues without requiring users to modify their gpg-agent.conf (which is something each user of the playbook has to do).

I don't think the overhead added by the lock is problematic, looking at the overall cost of passwordstore operations.

IMHO the benefit of having predictable behavior, not having to worry about synchronization in playbooks, and not requiring to adjust gpg-agent.conf (plus reloading the agent) and/or any other special configuration outweighs this little extra overhead. It also makes maintaining the code in passwordstore.py easier and more robust in case pass changes.

@grembo grembo force-pushed the passwordstore-fixes branch from 80f351f to af84ce7 Compare February 12, 2022 18:24
@felixfontein
Copy link
Collaborator

I would also suggest to split this up into two PRs, one for the path check, and the other for locking. Combining unrelated things into one PR is something that should really be avoided.

To me, they are related from a user perspective, as both issues result in passwordstore returning unexpected/inconsistent passwords. If you insist, I can split it into two PRs once the discussion on the PR finished.

It would be good to split them up. The one is a proper bugfix (and shoul be backported to stable-3 and stable-4), the other more a feature than a bugfix (and thus only should be backported to stable-4). Keeping them in the same PR prevents this from being backported to stable-3.

Also regarding locking: I think this should only be done on creation, not in every case. The correct solution for the gpg issues is auto-expand-secmem and not locking. This should probably be configurable (so you can force locking if you don't want to use auto-expand-secmem).

Locking needs to happen in all cases (reads and writes), as

* the logic inside _passwordstore.py_ (regardless of gpg issues) does reads to determine if create/write operations are required, and therefore is racy.

But that's only true if create=true or missing=create! Otherwise no write is done at all, so locking would be an unnecessary performance hit.

So it does a read to figure out if a secret exists and - if it doesn't - it starts creation. This can happen in parallel, which ends up in undefined/unpredictable behavior. Preventing this reliably (within one play as well when running multiple plays in parallel which are backed by the same password store) is complicated and error-prone, when done on the playbook level. So the logic in there requires locking. Also, the pass utility itself doesn't do any internal locking to prevent problems (so calls to pass aren't atomic).

When create=true or missing=create locking makes sense (and should be done by default). But only then, or when the user explicitly requests it.

* it works around the gpg issues without requiring users to modify their _gpg-agent.conf_ (which is something _each_ user of the playbook has to do).

While this is annoying, it is the correct fix. Putting ugly hacks everywhere just because gpg has stupid defaults is bad practice.

I don't think the overhead added by the lock is problematic, looking at the overall cost of passwordstore operations.

I disagree here. Especially because passwordstore operations are expensive, locking makes the whole situation worse because with locking, all operations must be done sequentially and cannot be done in parallel.

IMHO the benefit of having predictable behavior, not having to worry about synchronization in playbooks, and not requiring to adjust gpg-agent.conf (plus reloading the agent) and/or any other special configuration outweighs this little extra overhead. It also makes maintaining the code in passwordstore.py easier and more robust in case pass changes.

I disagree with this, and I don't think this decision should be forced on every user of this plugin.

@grembo
Copy link
Contributor Author

grembo commented Feb 13, 2022

It would be good to split them up. The one is a proper bugfix (and should be backported to stable-3 and stable-4), the other more a feature than a bugfix (and thus only should be backported to stable-4). Keeping them in the same PR prevents this from being backported to stable-3.

They are both bugfixes to me (unless you consider preventing undefined behavior a feature).

We don't have to agree on that part and I can split them up of course. I wouldn't describe it as a "minor_change" though, as it fixes broken behavior (or if it's intentional: broken design) that causes problems.

Also regarding locking: I think this should only be done on creation, not in every case. The correct solution for the gpg issues is auto-expand-secmem and not locking. This should probably be configurable (so you can force locking if you don't want to use auto-expand-secmem).

  • the logic inside passwordstore.py (regardless of gpg issues) does reads to determine if create/write operations are required, and therefore is racy.

But that's only true if create=true or missing=create! Otherwise no write is done at all, so locking would be an unnecessary performance hit.

Are you certain that there is no scenario where a write is partially done and a read could suffer from that?

When create=true or missing=create locking makes sense (and should be done by default). But only then, or when the user explicitly requests it.

* it works around the gpg issues without requiring users to modify their _gpg-agent.conf_ (which is something _each_ user of the playbook has to do).

While this is annoying, it is the correct fix. Putting ugly hacks everywhere just because gpg has stupid defaults is bad practice.

I think that what's even worse than gpg having "stupid defaults" is to know about them and let users run into the same problem over and over again.

Do you have an idea how the plugin could check if gpg-agent is configured correctly? We certainly don't want users to learn about that it should be configured differently by spending hours figuring out why a playbook that worked yesterday suddenly failed.

If we can detect it reliably, we can make the plugin fail with a helpful error message if gpg-agent is misconfigured. If not, we should make locking the default and add a configuration option to disable it for those who understand the implications.

IMHO the benefit of having predictable behavior, not having to worry about synchronization in playbooks, and not requiring to adjust gpg-agent.conf (plus reloading the agent) and/or any other special configuration outweighs this little extra overhead. It also makes maintaining the code in passwordstore.py easier and more robust in case pass changes.

I disagree with this, and I don't think this decision should be forced on every user of this plugin.

I prefer a playbook to work reliably (even if it's slightly slower), as that's consuming the machine's time, while breakage costs my time and causes fallout.

That said, if we can find a fix that satisfies both positions, all the better.

@grembo grembo changed the title passwordstore: Prevent using path as password, add locking passwordstore: Prevent using path as password Feb 13, 2022
@grembo grembo force-pushed the passwordstore-fixes branch 2 times, most recently from b78112b to 7a5c2de Compare February 13, 2022 17:29
@grembo
Copy link
Contributor Author

grembo commented Feb 13, 2022

@felixfontein I reduced this PR to the path fix, I'll create a follow-up PR with locking once ready.

@grembo grembo force-pushed the passwordstore-fixes branch from 7a5c2de to 2454b38 Compare February 13, 2022 18:50
@ansibullbot ansibullbot added needs_ci This PR requires CI testing to be performed. Please close and re-open this PR to trigger CI and removed needs_ci This PR requires CI testing to be performed. Please close and re-open this PR to trigger CI labels Feb 13, 2022
@grembo grembo force-pushed the passwordstore-fixes branch from 2454b38 to de5ea02 Compare February 13, 2022 19:51
@grembo
Copy link
Contributor Author

grembo commented Feb 13, 2022

@felixfontein Follow-up for configurable locking is #4194, currently stacked on top of this one.

@grembo
Copy link
Contributor Author

grembo commented Feb 13, 2022

@felixfontein This change made the logic more obvious (e.g., getting rid of the superfluous e.returncode != 0 while handling CalledProcessError).

@grembo grembo force-pushed the passwordstore-fixes branch from 29619d5 to c66f057 Compare February 14, 2022 21:45
@ansibullbot ansibullbot added the needs_ci This PR requires CI testing to be performed. Please close and re-open this PR to trigger CI label Feb 14, 2022
@ansibullbot ansibullbot added the needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR label Feb 14, 2022
@grembo grembo force-pushed the passwordstore-fixes branch 2 times, most recently from d8d5bbd to e19c473 Compare February 14, 2022 21:51
@ansibullbot ansibullbot removed needs_ci This PR requires CI testing to be performed. Please close and re-open this PR to trigger CI needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR labels Feb 14, 2022
Given a password stored in _path/to/secret_, requesting the password
_path/to_ will literally return `path/to`. This can lead to using
weak passwords by accident/mess up logic in code, based on the
state of the password store.

This is worked around by applying the same logic `pass` uses:
If a password was returned, check if there is a .gpg file it could
have come from. If not, treat it as missing.

Fixes ansible-collections#4185
@grembo grembo force-pushed the passwordstore-fixes branch from e19c473 to 0e50713 Compare February 17, 2022 13:20
@grembo
Copy link
Contributor Author

grembo commented Feb 17, 2022

@felixfontein This should be ready to land now.

Reg. the localization issue: Turns out this will be as easy setting self.env['LANGUAGE']='C', as this will only affect the gettext library translations and not other parts of the locale system that would have an impact on the character set used, see https://www.gnu.org/software/gettext/manual/html_node/The-LANGUAGE-variable.html

Copy link
Collaborator

@felixfontein felixfontein left a comment

Choose a reason for hiding this comment

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

Looks good to me. (I'm not a passwordstore user and haven't tested it.)

@felixfontein
Copy link
Collaborator

Since nobody else commented here, I assume nobody else has an opinion, so I'll just merge so that the depending PRs can continue.

@felixfontein felixfontein removed the check-before-release PR will be looked at again shortly before release and merged if possible. label Feb 17, 2022
@felixfontein felixfontein merged commit da49c09 into ansible-collections:main Feb 17, 2022
@patchback
Copy link

patchback bot commented Feb 17, 2022

Backport to stable-3: 💚 backport PR created

✅ Backport PR branch: patchback/backports/stable-3/da49c0968d6056e7591a324ffab210ce85f9dde4/pr-4192

Backported as #4217

🤖 @patchback
I'm built with octomachinery and
my source is open — https://github.com/sanitizers/patchback-github-app.

patchback bot pushed a commit that referenced this pull request Feb 17, 2022
Given a password stored in _path/to/secret_, requesting the password
_path/to_ will literally return `path/to`. This can lead to using
weak passwords by accident/mess up logic in code, based on the
state of the password store.

This is worked around by applying the same logic `pass` uses:
If a password was returned, check if there is a .gpg file it could
have come from. If not, treat it as missing.

Fixes #4185

(cherry picked from commit da49c09)
@patchback
Copy link

patchback bot commented Feb 17, 2022

Backport to stable-4: 💚 backport PR created

✅ Backport PR branch: patchback/backports/stable-4/da49c0968d6056e7591a324ffab210ce85f9dde4/pr-4192

Backported as #4218

🤖 @patchback
I'm built with octomachinery and
my source is open — https://github.com/sanitizers/patchback-github-app.

@felixfontein
Copy link
Collaborator

@grembo thanks a lot for your contribution!

patchback bot pushed a commit that referenced this pull request Feb 17, 2022
Given a password stored in _path/to/secret_, requesting the password
_path/to_ will literally return `path/to`. This can lead to using
weak passwords by accident/mess up logic in code, based on the
state of the password store.

This is worked around by applying the same logic `pass` uses:
If a password was returned, check if there is a .gpg file it could
have come from. If not, treat it as missing.

Fixes #4185

(cherry picked from commit da49c09)
@grembo
Copy link
Contributor Author

grembo commented Feb 17, 2022

@grembo thanks a lot for your contribution!

@felixfontein And you for taking the time to review.

felixfontein pushed a commit that referenced this pull request Feb 17, 2022
Given a password stored in _path/to/secret_, requesting the password
_path/to_ will literally return `path/to`. This can lead to using
weak passwords by accident/mess up logic in code, based on the
state of the password store.

This is worked around by applying the same logic `pass` uses:
If a password was returned, check if there is a .gpg file it could
have come from. If not, treat it as missing.

Fixes #4185

(cherry picked from commit da49c09)

Co-authored-by: grembo <[email protected]>
felixfontein pushed a commit that referenced this pull request Feb 17, 2022
Given a password stored in _path/to/secret_, requesting the password
_path/to_ will literally return `path/to`. This can lead to using
weak passwords by accident/mess up logic in code, based on the
state of the password store.

This is worked around by applying the same logic `pass` uses:
If a password was returned, check if there is a .gpg file it could
have come from. If not, treat it as missing.

Fixes #4185

(cherry picked from commit da49c09)

Co-authored-by: grembo <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue/PR relates to a bug lookup lookup plugin new_contributor Help guide this first time contributor plugins plugin (any type)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

passwordstore: Distinguish between subdirs and passwords, don't use path names as passwords
3 participants