diff --git a/README.md b/README.md index cfec88b..b09fdf1 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,9 @@ The software in this kit includes the following: 5. A [set of DevOps automation skeletons / examples](./examples/) to get you up and running without having to construct everything yourself. 6. A [lookup plugin](./lookup_plugins) for + [`qubes-pass`](https://github.com/Rudd-O/qubes-pass) to get you to + look up passwords for your infrastructure stored in separate VMs. +6. A [module and action plugin](./library) for [`qubes-pass`](https://github.com/Rudd-O/qubes-pass) to get you to store passwords needed to manage your infrastructure in separate VMs. diff --git a/action_plugins/qubes_pass.py b/action_plugins/qubes_pass.py new file mode 100644 index 0000000..6433fe6 --- /dev/null +++ b/action_plugins/qubes_pass.py @@ -0,0 +1,56 @@ +from ansible.plugins.action import ActionBase +import errno +import os +import re +import subprocess + + +class ActionModule(ActionBase): + + def __init__(self, *args, **kw): + ActionBase.__init__(self, *args, **kw) + + def run(self, task_vars=None): + ''' handler for launcher operations ''' + if task_vars is None: + task_vars = dict() + + name = self._task.args["name"] + if self._task.args["state"] == "present": + return self.present(self._task.args["name"], self._task.args["content"]) + + def present(self, name, content): + result = {"changed": False} + + content = content.rstrip("\n") + + present = False + try: + oldcontent = subprocess.check_output( + ["qvm-pass", "get", "--", name], + stderr=file(os.devnull, "a")).rstrip("\n") + if oldcontent == content: + present = True + except subprocess.CalledProcessError as e: + if e.returncode != 8: + raise + # Else do nothing, the content is absent, we continue. + + if not present: + if not self._play_context.check_mode: + cmd = ["qvm-pass", "insert", "-f", "-m", "--", name] + p = subprocess.Popen(cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + out = p.communicate(content)[0].strip() + ret = p.wait() + if ret != 0: + raise subprocess.CalledProcessError(ret, cmd, out) + result["changed"] = True + if present: + result["msg"] = "Password entry %s updated." % (name,) + else: + result["msg"] = "Password entry %s created." % (name,) + + return result diff --git a/library/README.md b/library/README.md new file mode 100644 index 0000000..18492cc --- /dev/null +++ b/library/README.md @@ -0,0 +1,12 @@ +# Ansible Qubes Pass action module + +This action module can be used to store a password into your qubes-pass +(VM-backed) password manager +(see [`qubes-pass`](https://github.com/Rudd-O/qubes-pass). + +Note that this module will not run the `qubes-pass` command on the target +managed machine — it will run the `qubes-pass` command locally on the +control machine. + +Read the file `qubes_pass.py` in the `library/` directory of this +project for usage instructions. diff --git a/library/qubes_pass.py b/library/qubes_pass.py new file mode 100644 index 0000000..6c0fea0 --- /dev/null +++ b/library/qubes_pass.py @@ -0,0 +1,38 @@ +DOCUMENTATION = """ +--- +module: qubes_pass +author: Rudd-O +short_description: Save passwords in the keyring. +description: + - This module will call qvm-pass. On the control machine. + In this sense, it is very similar to the fetch module + — it acts on the state of the control machine. + Because of the way pass works, the final line endings + on a content that is a multiline string are stripped + before being stored. +options: + name: + required: true + description: name of the entry for qvm-pass + state: + required: false + choices: [ "present" ] + default: "present" + content: + required: false + description: set the name to these contents, when + state is present. +""" + +EXAMPLES = r""" +- qubes_pass: + name: key/a/b/c + content: password + +- qubes_pass: + name: g/h/i + content: | + multi + line + string +"""