From 6a94e2f2c1fce9f1199dbb8ac66f69ee7665e972 Mon Sep 17 00:00:00 2001 From: Victor Zemtsov Date: Wed, 13 Mar 2019 18:09:06 +0300 Subject: [PATCH] Initial commit --- .gitignore | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE | 21 +++++++++++ README.md | 55 +++++++++++++++++++++++++++ keepass.py | 73 ++++++++++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 keepass.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9906182 --- /dev/null +++ b/.gitignore @@ -0,0 +1,108 @@ +# Created by .ignore support plugin (hsz.mobi) +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +.idea \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..952fa6e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Victor Zemtsov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..deb32ca --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# Ansible KeePass Lookup Plugin + +Perhaps, from a security view point, this solution is the same as `ansible-vault`. +Just if you are storing secrets data in KeePass, then why not use it, +instead of duplicating to `ansible-vault`. + + +## Installation + + pip install pykeepass --user + mkdir -p ~/.ansible/plugins/lookup && cd "$_" + curl https://raw.githubusercontent.com/viczem/ansible-keepass/master/keepass.py -o ./keepass.py + +[More about ansible plugins installation](https://docs.ansible.com/ansible/latest/dev_guide/developing_locally.html) + + +## Variables + +- `keepass_dbx` - path to Keepass database file +- `keepass_psw` - password +- `keepass_key` - *optional* path to keyfile + + +## Usage + +For global variables define them once in `group_vars/all`. + +For security reasons, do not store KeePass database password in plain text. +Use `ansible-vault encrypt_string` to encrypt the password. +I'm not sure, but I think that for simplicity, +it is safe to use the same `ansible-vault` password as KeePass database password. +To decrypt the passwod use `--ask-vault-pass` + e.g. `ansible all -m ping --ask-vault-pass`. + + + # file: group_vars/all + + keepass_dbx: "~/.keepass/database.kdbx" + keepass_psw: !vault | + $ANSIBLE_VAULT;1.1;AES256 + ... + + +Now you can create another variables you need e.g. in any file in group_vars + + + ansible_user : "{{ lookup('keepass', 'path/to/entry', 'username') }}" + ansible_become_pass: "{{ lookup('keepass', 'path/to/entry', 'password') }}" + + +You can get another [properties of an KeePass entry](https://github.com/pschmitt/pykeepass/blob/master/pykeepass/entry.py) +(not only `username` or `password`) + + +`ansible-doc -t lookup keepass` - to get description of the plugin \ No newline at end of file diff --git a/keepass.py b/keepass.py new file mode 100644 index 0000000..dabe52d --- /dev/null +++ b/keepass.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +try: + from __main__ import display +except ImportError: + from ansible.utils.display import Display + display = Display() + +import os +from pykeepass import PyKeePass +from construct.core import ChecksumError +from ansible.errors import AnsibleError +from ansible.plugins.lookup import LookupBase + + +DOCUMENTATION = """ + lookup: keepass + author: Victor Zemtsov + version_added: '0.1' + short_description: fetch data from KeePass file + description: + - This lookup returns a value of a property of a KeePass entry which fetched by given path. + - Required variables are + - keepass_dbx - path to database file and + - keepass_psw - password. + - Optional variable is keepass_key - path to key file + options: + _terms: + description: + - first is a path to KeePass entry + - second is a property name of the entry, e.g. username or password + required: True + notes: + - https://github.com/viczem/ansible-keepass + + example: + - "{{ lookup('keepass', 'path/to/entry', 'password') }}" +""" + + +class LookupModule(LookupBase): + + def run(self, terms, variables=None, **kwargs): + if not terms or len(terms) != 2: + raise AnsibleError('Wrong request format') + entry_path = terms[0].strip('/') + entry_attr = terms[1] + + keepass_psw = variables.get('keepass_psw', '') + keepass_dbx = variables.get('keepass_dbx', '') + keepass_dbx = os.path.realpath(os.path.expanduser(keepass_dbx)) + if os.path.isfile(keepass_dbx): + display.v(u"Found Keepass database file: %s" % keepass_dbx) + + keepass_key = variables.get('keepass_key') + if keepass_key: + keepass_key = os.path.realpath(os.path.expanduser(keepass_key)) + if os.path.isfile(keepass_key): + display.v(u"Found Keepass database keyfile: %s" % keepass_dbx) + + try: + with PyKeePass(keepass_dbx, keepass_psw, keepass_key) as kp: + entry = kp.find_entries_by_path(entry_path, first=True) + if entry is None: + raise AnsibleError(u"Entry '%s' is not found" % entry_path) + return [getattr(entry, entry_attr)] + except ChecksumError: + raise AnsibleError("Wrong password/keyfile {}".format(keepass_dbx)) + except (AttributeError, FileNotFoundError) as e: + raise AnsibleError(e)