passwordstore: Support subkey creation and update (#8952)

pull/9012/head
Manuel Luzarreta 2024-10-07 22:12:43 +02:00 committed by GitHub
parent 1d86d49688
commit 24f2b980b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 73 additions and 14 deletions

View File

@ -0,0 +1,2 @@
minor_changes:
- passwordstore lookup plugin - add subkey creation/update support (https://github.com/ansible-collections/community.general/pull/8952).

View File

@ -14,7 +14,7 @@ DOCUMENTATION = '''
short_description: manage passwords with passwordstore.org's pass utility
description:
- Enables Ansible to retrieve, create or update passwords from the passwordstore.org pass utility.
It also retrieves YAML style keys stored as multilines in the passwordfile.
It can also retrieve, create or update YAML style keys stored as multilines in the passwordfile.
- To avoid problems when accessing multiple secrets at once, add C(auto-expand-secmem) to
C(~/.gnupg/gpg-agent.conf). Where this is not possible, consider using O(lock=readwrite) instead.
options:
@ -33,11 +33,11 @@ DOCUMENTATION = '''
env:
- name: PASSWORD_STORE_DIR
create:
description: Create the password if it does not already exist. Takes precedence over O(missing).
description: Create the password or the subkey if it does not already exist. Takes precedence over O(missing).
type: bool
default: false
overwrite:
description: Overwrite the password if it does already exist.
description: Overwrite the password or the subkey if it does already exist.
type: bool
default: false
umask:
@ -53,7 +53,9 @@ DOCUMENTATION = '''
type: bool
default: false
subkey:
description: Return a specific subkey of the password. When set to V(password), always returns the first line.
description:
- By default return a specific subkey of the password. When set to V(password), always returns the first line.
- With O(overwrite=true), it will create the subkey and return it.
type: str
default: password
userpass:
@ -64,7 +66,7 @@ DOCUMENTATION = '''
type: integer
default: 16
backup:
description: Used with O(overwrite=true). Backup the previous password in a subkey.
description: Used with O(overwrite=true). Backup the previous password or subkey in a subkey.
type: bool
default: false
nosymbols:
@ -189,6 +191,17 @@ tasks.yml: |
vars:
mypassword: "{{ lookup('community.general.passwordstore', 'example/test', missing='create')}}"
- name: >-
Create a random 16 character password in a subkey. If the password file already exists, just add the subkey in it.
If the subkey exists, returns it
ansible.builtin.debug:
msg: "{{ lookup('community.general.passwordstore', 'example/test', create=true, subkey='foo') }}"
- name: >-
Create a random 16 character password in a subkey. Overwrite if it already exists and backup the old one.
ansible.builtin.debug:
msg: "{{ lookup('community.general.passwordstore', 'example/test', create=true, subkey='user', overwrite=true, backup=true) }}"
- name: Prints 'abc' if example/test does not exist, just give the password otherwise
ansible.builtin.debug:
var: mypassword
@ -411,15 +424,48 @@ class LookupModule(LookupBase):
def update_password(self):
# generate new password, insert old lines from current result and return new password
# if the target is a subkey, only modify the subkey
newpass = self.get_newpass()
datetime = time.strftime("%d/%m/%Y %H:%M:%S")
msg = newpass
if self.paramvals['preserve'] or self.paramvals['timestamp']:
msg += '\n'
if self.paramvals['preserve'] and self.passoutput[1:]:
msg += '\n'.join(self.passoutput[1:]) + '\n'
if self.paramvals['timestamp'] and self.paramvals['backup']:
msg += "lookup_pass: old password was {0} (Updated on {1})\n".format(self.password, datetime)
subkey = self.paramvals["subkey"]
if subkey != "password":
msg_lines = []
subkey_exists = False
subkey_line = "{0}: {1}".format(subkey, newpass)
oldpass = None
for line in self.passoutput:
if line.startswith("{0}: ".format(subkey)):
oldpass = self.passdict[subkey]
line = subkey_line
subkey_exists = True
msg_lines.append(line)
if not subkey_exists:
msg_lines.insert(2, subkey_line)
if self.paramvals["timestamp"] and self.paramvals["backup"] and oldpass and oldpass != newpass:
msg_lines.append(
"lookup_pass: old subkey '{0}' password was {1} (Updated on {2})\n".format(
subkey, oldpass, datetime
)
)
msg = os.linesep.join(msg_lines)
else:
msg = newpass
if self.paramvals['preserve'] or self.paramvals['timestamp']:
msg += '\n'
if self.paramvals['preserve'] and self.passoutput[1:]:
msg += '\n'.join(self.passoutput[1:]) + '\n'
if self.paramvals['timestamp'] and self.paramvals['backup']:
msg += "lookup_pass: old password was {0} (Updated on {1})\n".format(self.password, datetime)
try:
check_output2([self.pass_cmd, 'insert', '-f', '-m', self.passname], input=msg, env=self.env)
except (subprocess.CalledProcessError) as e:
@ -431,13 +477,21 @@ class LookupModule(LookupBase):
# use pwgen to generate the password and insert values with pass -m
newpass = self.get_newpass()
datetime = time.strftime("%d/%m/%Y %H:%M:%S")
msg = newpass
subkey = self.paramvals["subkey"]
if subkey != "password":
msg = "\n\n{0}: {1}".format(subkey, newpass)
else:
msg = newpass
if self.paramvals['timestamp']:
msg += '\n' + "lookup_pass: First generated by ansible on {0}\n".format(datetime)
try:
check_output2([self.pass_cmd, 'insert', '-f', '-m', self.passname], input=msg, env=self.env)
except (subprocess.CalledProcessError) as e:
raise AnsibleError('exit code {0} while running {1}. Error output: {2}'.format(e.returncode, e.cmd, e.output))
return newpass
def get_passresult(self):
@ -525,7 +579,10 @@ class LookupModule(LookupBase):
self.parse_params(term) # parse the input into paramvals
with self.opt_lock('readwrite'):
if self.check_pass(): # password exists
if self.paramvals['overwrite'] and self.paramvals['subkey'] == 'password':
if self.paramvals['overwrite']:
with self.opt_lock('write'):
result.append(self.update_password())
elif self.paramvals["subkey"] != "password" and not self.passdict.get(self.paramvals['subkey']): # password exists but not the subkey
with self.opt_lock('write'):
result.append(self.update_password())
else: