1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

passwordstore: Prevent using path as password (#4192) (#4217)

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/community.general#4185

(cherry picked from commit da49c0968d)

Co-authored-by: grembo <freebsd@grem.de>
This commit is contained in:
patchback[bot] 2022-02-17 21:33:09 +01:00 committed by GitHub
parent 78de6f2494
commit 56c5a8b9b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 27 additions and 21 deletions

View file

@ -0,0 +1,2 @@
bugfixes:
- passwordstore lookup plugin - prevent returning path names as passwords by accident (https://github.com/ansible-collections/community.general/issues/4185, https://github.com/ansible-collections/community.general/pull/4192).

View file

@ -154,6 +154,7 @@ display = Display()
# backhacked check_output with input for python 2.7 # backhacked check_output with input for python 2.7
# http://stackoverflow.com/questions/10103551/passing-data-to-subprocess-check-output # http://stackoverflow.com/questions/10103551/passing-data-to-subprocess-check-output
# note: contains special logic for calling 'pass', so not a drop-in replacement for check_output
def check_output2(*popenargs, **kwargs): def check_output2(*popenargs, **kwargs):
if 'stdout' in kwargs: if 'stdout' in kwargs:
raise ValueError('stdout argument not allowed, it will be overridden.') raise ValueError('stdout argument not allowed, it will be overridden.')
@ -175,9 +176,10 @@ def check_output2(*popenargs, **kwargs):
process.wait() process.wait()
raise raise
retcode = process.poll() retcode = process.poll()
if retcode != 0 or \ if retcode == 0 and (b'encryption failed: Unusable public key' in b_out or
b'encryption failed: Unusable public key' in b_out or \ b'encryption failed: Unusable public key' in b_err):
b'encryption failed: Unusable public key' in b_err: retcode = 78 # os.EX_CONFIG
if retcode != 0:
cmd = kwargs.get("args") cmd = kwargs.get("args")
if cmd is None: if cmd is None:
cmd = popenargs[0] cmd = popenargs[0]
@ -228,12 +230,11 @@ class LookupModule(LookupBase):
# Collect pass environment variables from the plugin's parameters. # Collect pass environment variables from the plugin's parameters.
self.env = os.environ.copy() self.env = os.environ.copy()
# Set PASSWORD_STORE_DIR if directory is set # Set PASSWORD_STORE_DIR
if self.paramvals['directory']: if os.path.isdir(self.paramvals['directory']):
if os.path.isdir(self.paramvals['directory']): self.env['PASSWORD_STORE_DIR'] = self.paramvals['directory']
self.env['PASSWORD_STORE_DIR'] = self.paramvals['directory'] else:
else: raise AnsibleError('Passwordstore directory \'{0}\' does not exist'.format(self.paramvals['directory']))
raise AnsibleError('Passwordstore directory \'{0}\' does not exist'.format(self.paramvals['directory']))
# Set PASSWORD_STORE_UMASK if umask is set # Set PASSWORD_STORE_UMASK if umask is set
if 'umask' in self.paramvals: if 'umask' in self.paramvals:
@ -261,19 +262,20 @@ class LookupModule(LookupBase):
if ':' in line: if ':' in line:
name, value = line.split(':', 1) name, value = line.split(':', 1)
self.passdict[name.strip()] = value.strip() self.passdict[name.strip()] = value.strip()
if os.path.isfile(os.path.join(self.paramvals['directory'], self.passname + ".gpg")):
# Only accept password as found, if there a .gpg file for it (might be a tree node otherwise)
return True
except (subprocess.CalledProcessError) as e: except (subprocess.CalledProcessError) as e:
if e.returncode != 0 and 'not in the password store' in e.output: # 'not in password store' is the expected error if a password wasn't found
# if pass returns 1 and return string contains 'is not in the password store.' if 'not in the password store' not in e.output:
# We need to determine if this is valid or Error.
if self.paramvals['missing'] == 'error':
raise AnsibleError('passwordstore: passname {0} not found and missing=error is set'.format(self.passname))
else:
if self.paramvals['missing'] == 'warn':
display.warning('passwordstore: passname {0} not found'.format(self.passname))
return False
else:
raise AnsibleError(e) raise AnsibleError(e)
return True
if self.paramvals['missing'] == 'error':
raise AnsibleError('passwordstore: passname {0} not found and missing=error is set'.format(self.passname))
elif self.paramvals['missing'] == 'warn':
display.warning('passwordstore: passname {0} not found'.format(self.passname))
return False
def get_newpass(self): def get_newpass(self):
if self.paramvals['nosymbols']: if self.paramvals['nosymbols']:
@ -329,7 +331,9 @@ class LookupModule(LookupBase):
result = [] result = []
self.paramvals = { self.paramvals = {
'subkey': 'password', 'subkey': 'password',
'directory': variables.get('passwordstore'), 'directory': variables.get('passwordstore', os.environ.get(
'PASSWORD_STORE_DIR',
os.path.expanduser('~/.password-store'))),
'create': False, 'create': False,
'returnall': False, 'returnall': False,
'overwrite': False, 'overwrite': False,