diff --git a/changelogs/fragments/1681-add_passwordstore_yaml_support.yaml b/changelogs/fragments/1681-add_passwordstore_yaml_support.yaml new file mode 100644 index 0000000000..ebfac65a14 --- /dev/null +++ b/changelogs/fragments/1681-add_passwordstore_yaml_support.yaml @@ -0,0 +1,2 @@ +breaking_changes: + - "passwordstore lookup plugin - now parsing a password store entry as YAML if possible, skipping the first line (which by convention only contains the password and nothing else). If it cannot be parsed as YAML, the old ``key: value`` parser will be used to process the entry. Can break backwards compatibility if YAML formatted code was parsed in a non-YAML interpreted way, e.g. ``foo: [bar, baz]`` will become a list with two elements in the new version, but a string ``'[bar, baz]'`` in the old (https://github.com/ansible-collections/community.general/issues/1673)." diff --git a/plugins/lookup/passwordstore.py b/plugins/lookup/passwordstore.py index f533caaeb6..79c69ed962 100644 --- a/plugins/lookup/passwordstore.py +++ b/plugins/lookup/passwordstore.py @@ -105,6 +105,8 @@ _raw: import os import subprocess import time +import yaml + from distutils import util from ansible.errors import AnsibleError, AnsibleAssertionError @@ -209,10 +211,15 @@ class LookupModule(LookupBase): ).splitlines() self.password = self.passoutput[0] self.passdict = {} - for line in self.passoutput[1:]: - if ':' in line: - name, value = line.split(':', 1) - self.passdict[name.strip()] = value.strip() + try: + values = yaml.safe_load('\n'.join(self.passoutput[1:])) + for key, item in values.items(): + self.passdict[key] = item + except (yaml.YAMLError, AttributeError): + for line in self.passoutput[1:]: + if ':' in line: + name, value = line.split(':', 1) + self.passdict[name.strip()] = value.strip() except (subprocess.CalledProcessError) as e: if e.returncode != 0 and 'not in the password store' in e.output: # if pass returns 1 and return string contains 'is not in the password store.' diff --git a/tests/integration/targets/lookup_passwordstore/tasks/tests.yml b/tests/integration/targets/lookup_passwordstore/tasks/tests.yml index d702edafef..aba5457c0a 100644 --- a/tests/integration/targets/lookup_passwordstore/tasks/tests.yml +++ b/tests/integration/targets/lookup_passwordstore/tasks/tests.yml @@ -60,3 +60,63 @@ assert: that: - readpass == newpass + +# As inserting multiline passwords on the commandline would require something +# like expect, simply create it by using default gpg on a file with the correct +# structure. +- name: Create the YAML password content + copy: + dest: "~/.password-store/test-yaml-pass" + content: | + testpassword + key: | + multi + line + +- name: Read .gpg-id from .password-store + set_fact: + gpgid: "{{ lookup('file', '~/.password-store/.gpg-id') }}" + +- name: Encrypt the file using the gpg key + command: "{{ gpg2_bin }} --batch --encrypt -r {{ gpgid }} ~/.password-store/test-yaml-pass" + +- name: Fetch a password with YAML subkey + set_fact: + readyamlpass: "{{ lookup('community.general.passwordstore', 'test-yaml-pass subkey=key') }}" + +- name: Read a yaml subkey + assert: + that: + - readyamlpass == 'multi\nline' + +- name: Create a non-YAML multiline file + copy: + dest: "~/.password-store/test-multiline-pass" + content: | + testpassword + random additional line + +- name: Read .gpg-id from .password-store + set_fact: + gpgid: "{{ lookup('file', '~/.password-store/.gpg-id') }}" + +- name: Encrypt the file using the gpg key + command: "{{ gpg2_bin }} --batch --encrypt -r {{ gpgid }} ~/.password-store/test-multiline-pass" + +- name: Fetch password from multiline file + set_fact: + readyamlpass: "{{ lookup('community.general.passwordstore', 'test-multiline-pass') }}" + +- name: Multiline pass only returns first line + assert: + that: + - readyamlpass == 'testpassword' + +- name: Fetch all from multiline file + set_fact: + readyamlpass: "{{ lookup('community.general.passwordstore', 'test-multiline-pass returnall=yes') }}" + +- name: Multiline pass returnall returns everything in the file + assert: + that: + - readyamlpass == 'testpassword\nrandom additional line'