ansible-collections / community.general

Ansible Community General Collection
https://galaxy.ansible.com/ui/repo/published/community/general/
GNU General Public License v3.0
821 stars 1.51k forks source link

Passwordstore locktime not respected #6196

Closed eulenleber closed 1 year ago

eulenleber commented 1 year ago

Summary

"msg": "An unhandled exception occurred while running the lookup plugin 'passwordstore'. Error was a <class 'ansible.errors.AnsibleError'>, original message: exit code 2 while running ['pass', 'show', 'path/to/file']. Error output: gpg: decryption failed: No secret key\n
pass show path/to/file

does wait though

gpg-agent.conf

pinentry-timeout 0
pinentry-program /usr/bin/pinentry-tty

The config is respected, as pinentry-tty is used in favor of ncures

ansible.cfg

[passwordstore_lookup]
lock=readwrite
locktimeout=30m

Here at least 30 minutes should pass before timing out...

Issue Type

Bug Report

Component Name

https://github.com/ansible-collections/community.general/blob/e8a7c27cab302565f4f0bc8e5522e63497f0be6e/plugins/lookup/passwordstore.py#L363

Ansible Version

$ ansible --version
ansible [core 2.14.3]
  config file = .../ansible.cfg
  configured module search path = ['.../.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.10/site-packages/ansible
  ansible collection location = .../.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.10.9 (main, Dec 19 2022, 17:35:49) [GCC 12.2.0] (/usr/bin/python)
  jinja version = 3.1.2
  libyaml = True

Community.general Version

$ ansible-galaxy collection list community.general
Collection        Version
----------------- -------
community.general 6.4.0  

Configuration

$ ansible-config dump --only-changed
CONFIG_FILE() = .../ansible.cfg

weirdly no password_store section is shown

OS / Environment

archlinux

Steps to Reproduce

Expected Results

I expected that the timeout time is respected but it fails after approx 1 min

Actual Results

Code of Conduct

ansibullbot commented 1 year ago

Files identified in the description:

If these files are incorrect, please update the component name section of the description or use the !component bot command.

click here for bot help

ansibullbot commented 1 year ago

cc @Akasurde @None @RevBits @azenk @dagwieers @delineaKrehl @eric-belhomme @felixfontein @galanoff @jparrill @jpmens @konstruktoid @lungj @samdoran @scottsb @tylerezimmerman click here for bot help

felixfontein commented 1 year ago

!component =plugins/lookup/passwordstore.py

felixfontein commented 1 year ago

CC @grembo

ansibullbot commented 1 year ago

Files identified in the description:

If these files are incorrect, please update the component name section of the description or use the !component bot command.

click here for bot help

grembo commented 1 year ago

locktimeout is how long the script waits to obtain a lock when synchronizing operations. It does not control how long gpg-agent is waiting to get input. The comment in documentation about it being related to gpg-agent's timeouts is about locktimeout ideally being longer than pinentry-timeout. It's only relevant if more than one secret is required at the same time and actual synchronization needs to happen.

  1. Obtain lock <--- this is where the timeout is relevant
  2. Get secret (including gpg call) <--- here, 'locktimeout' makes no difference
  3. Release lock

So the actual issue you're having is gpg not respecting the timeout you configured (for whatever reason).

In case you're actually having concurrent open requests, this could be caused by gpg-agent not having enough secure memory (which makes some requests fail automatically).

In this case, adding

auto-expand-secmem 32

to gpg-agent.conf helps.

I usually use pinentry-gtk - in case you can provide a minimum working example (simple play) to test with, I can try to reproduce locally.

eulenleber commented 1 year ago

I see, I suggest an improvement in documentation in that case.

Still, the passwordstore_lookup behaves differently than plain shell - which is odd, as the pinentry-program is respected. pinentry-timeout is neither respected if set to 3 in which case the pin entry should timeout after 3 seconds...

The timeout problem happens with pinentry-gtk as well

eulenleber commented 1 year ago

@grembo

I usually use pinentry-gtk - in case you can provide a minimum working example (simple play) to test with, I can try to reproduce locally.

cd /tmp
mkdir passwordstore gpg
chmod 700 gpg secrets
export GPG_TTY=$(tty)   
export PASSWORD_STORE_DIR=$(realpath passwordstore)
export GNUPGHOME=$(realpath gpg)
cat>gpg/gpg-agent.conf <<EOF
pinentry-timeout 0
pinentry-program /usr/bin/pinentry-tty
EOF
gpg --full-gen-key  --batch <<EOF
Key-Type: 1
Key-Length: 4096
Subkey-Type: 1
Subkey-Length: 4096
Name-Real: root
Name-Email: root@localhost
Expire-Date: 0
EOF
pass init root@localhost
# create password
ansible localhost -m debug -a "msg={{ lookup('passwordstore', 'passwordstore/test',  missing='create')  }}" 
# ask for password
time ansible localhost -m debug -a "msg={{ lookup('passwordstore', 'passwordstore/test',  missing='create')  }}" 
localhost | FAILED! => {
    "msg": "An unhandled exception occurred while running the lookup plugin 'passwordstore'. Error was a <class 'ansible.errors.AnsibleError'>, original message: exit code 2 while running ['pass', 'show', 'passwordstore/test']. Error output: gpg: decryption failed: No secret key\n. exit code 2 while running ['pass', 'show', 'passwordstore/test']. Error output: gpg: decryption failed: No secret key\n "
}
ansible localhost -m debug -a   2.19s user 0.29s system 4% cpu 1:00.59 total

Interesting though that pinentry-timeout 5 will result in user 0.09s

grembo commented 1 year ago

Thanks for providing an example, I reduced the test to:

time gpg2 -d --use-agent /tmp/passwordstore/passwordstore/test.gpg

Testing with pass directly (like you did in the bug description above) yields the same results though:

$ time pass show passwordstore/test
Please enter the passphrase to unlock the OpenPGP secret key:
"root <root@localhost>"
4096-bit RSA key, ID 6B97EC90F794BC6A,
created 2023-03-15 (main key ID 60A530606C118FFF).

Passphrase: 
gpg: public key decryption failed: Timeout
gpg: decryption failed: Timeout
       60.27 real         0.01 user         0.02 sys

So it's unclear why that test would have worked for you (as in: "no timeout was applied").

Part of the solution is found on the gpg-agent man page / in gnupg's documentation:

--pinentry-timeout n This option asks the Pinentry to timeout after n seconds with no user input. The default value of 0 does not ask the pinentry to timeout, however a Pinentry may use its own default timeout value in this case. A Pinentry may or may not honor this request.

So pinentry-timeout 0 doesn't mean "unlimited", but "use whatever that pinentry implementation's default setting is".

I checked gpg-agent sources to confirm that the SETTIMEOUT command is only sent to pinentry in case pinentry_timeout != 0: https://github.com/gpg/gnupg/blob/283ccbc824d8062b86e27818a84ab5a29facd7a5/agent/call-pinentry.c#L593-L603

Knowing this, I checked where the 60 seconds come from: https://github.com/gpg/pinentry/blob/6b697bd3e9f859cea338936894079241f2e15ffc/pinentry/pinentry.c#L182

So it seems like, basically all pinentry programs use a default timeout of 60 seconds.

Therefore the solution to your problem is specifying a long pinentry timeout in gpg-agent.conf (don't forget to restart gpg-agent afterwards), e.g.:

pinentry-timeout 3600
pinentry-program /usr/bin/pinentry-tty

(personally, I use 120 seconds).

Hope that helps.

eulenleber commented 1 year ago

you are right, I can not reproduce the infinite wait. I honestly dont know why I got that impression. Thanks very much for your analysis. And sorry for wasting your time.