Compare commits
39 Commits
Author | SHA1 | Date |
---|---|---|
Victor Zemtsov | b633a57fc3 | |
Michael Ellnebrand | acb07e8335 | |
Victor Zemtsov | c1ecea1fb5 | |
Victor Zemtsov | 6be989a0d3 | |
Victor Zemtsov | ab6d3a8228 | |
Victor Zemtsov | 61a8f39081 | |
Markus Heberling | 1988cb62a6 | |
Victor Zemtsov | dd97f8a830 | |
Victor Zemtsov | 8259d85305 | |
Victor Zemtsov | 02fc763fd5 | |
Victor Zemtsov | 47ebe2e6dd | |
Jan-Piet Mens | 808391953c | |
Victor Zemtsov | 237f3a0da5 | |
Victor Zemtsov | 5ec13cdee3 | |
Victor Zemtsov | 625be39764 | |
Victor Zemtsov | 37309942d9 | |
Victor Zemtsov | 6db523cedb | |
Victor Zemtsov | 9bcd8ce6a7 | |
Sven Anders | 6ca6efde8a | |
Sven Anders | ee68122d74 | |
Victor Zemtsov | 107fb09a1a | |
Victor Zemtsov | ac84c3ccbc | |
Victor Zemtsov | 5c247f921b | |
Victor Zemtsov | ee4aba14df | |
Victor Zemtsov | affcf5e950 | |
Victor Zemtsov | f55e4d84d6 | |
Victor Zemtsov | e9392a54e3 | |
Victor Zemtsov | c622672bab | |
Victor Zemtsov | ae19f7c2c5 | |
Victor Zemtsov | 655b343e03 | |
Victor Zemtsov | a61cc8dd80 | |
Victor Zemtsov | 05576cca7d | |
Victor Zemtsov | 0a523d7fdf | |
Victor Zemtsov | bdc8febd7e | |
Victor Zemtsov | dd935327e7 | |
Victor Zemtsov | de1d72afe0 | |
Jimisola Laursen | 10f4a191fe | |
Jimisola Laursen | ef15128536 | |
Jimisola Laursen | 26422700ab |
|
@ -105,4 +105,6 @@ venv.bak/
|
|||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
.idea
|
||||
.idea
|
||||
/docs/examples/attachment*
|
||||
/*.tar.gz
|
55
README.md
55
README.md
|
@ -1,15 +1,14 @@
|
|||
# Ansible KeePass Lookup Plugin
|
||||
|
||||
This collection provides a plugin that allows to read data from KeePass file (modifying is not supported)
|
||||
This collection provides plugins that allows to read data from KeePass file (modifying is not supported)
|
||||
|
||||
## How it works
|
||||
|
||||
The plugin opens a UNIX socket with decrypted KeePass file.
|
||||
For performance reasons, decryption occurs only once at socket startup,
|
||||
The lookup plugin opens a UNIX socket with decrypted KeePass file.
|
||||
For performance reasons, decryption occurs only once at socket startup,
|
||||
and the KeePass file remains decrypted as long as the socket is open.
|
||||
The UNIX socket file is stored in a temporary folder according to OS.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Requirements: `python 3`, `pykeepass==4.0.3`
|
||||
|
@ -21,17 +20,38 @@ Requirements: `python 3`, `pykeepass==4.0.3`
|
|||
## Variables
|
||||
|
||||
- `keepass_dbx` - path to KeePass file
|
||||
- `keepass_psw` - password
|
||||
- `keepass_key` - *Optional*. Path to keyfile
|
||||
- `keepass_ttl` - *Optional*. Socket TTL (will be closed automatically when not used).
|
||||
- `keepass_psw` - *Optional*. Password (required if `keepass_key` is not set)
|
||||
- `keepass_key` - *Optional*. Path to keyfile (required if `keepass_psw` is not set)
|
||||
- `keepass_ttl` - *Optional*. Socket TTL (will be closed automatically when not used).
|
||||
Default 60 seconds.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
If you want to use ansible-keepass with continuous integration, it could be helpful not to use ansible variables but Shell environment variables.
|
||||
|
||||
- `ANSIBLE_KEEPASS_PSW` Password
|
||||
- `ANSIBLE_KEEPASS_KEY` Path to keyfile
|
||||
- `ANSIBLE_KEEPASS_TTL` Socket TTL
|
||||
- `ANSIBLE_KEEPASS_SOCKET` Path to Keepass Socket
|
||||
|
||||
The environment variables will only be used, if no ansible variable is set.
|
||||
|
||||
You can than start the socket in another background process like this
|
||||
```sh
|
||||
export ANSIBLE_KEEPASS_PSW=mySecret
|
||||
export ANSIBLE_KEEPASS_SOCKET=/home/build/.my-ansible-sock.${CI_JOB_ID}
|
||||
export ANSIBLE_TTL=600 # 10 Minutes
|
||||
/home/build/ansible-pyenv/bin/python3 /home/build/.ansible/roles/ansible_collections/viczem/keepass/plugins/lookup/keepass.py /path-to/my-keepass.kdbx &
|
||||
ansible-playbook -v playbook1.yml
|
||||
ansible-playbook -v playbook2.yml
|
||||
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
`ansible-doc -t lookup keepass` to get description of the plugin
|
||||
|
||||
> **WARNING**: For security reasons, do not store KeePass passwords in plain text.
|
||||
> **WARNING**: For security reasons, do not store KeePass passwords in plain text.
|
||||
Use `ansible-vault encrypt_string` to encrypt it and use it like below
|
||||
|
||||
# file: group_vars/all
|
||||
|
@ -41,11 +61,26 @@ Use `ansible-vault encrypt_string` to encrypt it and use it like below
|
|||
$ANSIBLE_VAULT;1.1;AES256
|
||||
...encrypted password...
|
||||
|
||||
### Example
|
||||
### Examples
|
||||
|
||||
More examples see in [/docs/examples](/docs/examples).
|
||||
|
||||
#### Lookup
|
||||
|
||||
ansible_user : "{{ lookup('viczem.keepass.keepass', 'path/to/entry', 'username') }}"
|
||||
ansible_become_pass : "{{ lookup('viczem.keepass.keepass', 'path/to/entry', 'password') }}"
|
||||
custom_field : "{{ lookup('viczem.keepass.keepass', 'path/to/entry', 'custom_properties', 'a_custom_property_name') }}"
|
||||
attachment : "{{ lookup('viczem.keepass.keepass', 'path/to/entry', 'attachments', 'a_file_name') }}"
|
||||
|
||||
More examples see in [/doc/examples](/doc/examples).
|
||||
#### Module
|
||||
- name: "Export file: attachment.txt"
|
||||
viczem.keepass.attachment:
|
||||
database: "{{ keepass_dbx }}"
|
||||
password: "{{ keepass_psw }}"
|
||||
entrypath: example/attachments
|
||||
attachment: "attachment.txt"
|
||||
dest: "{{ keepass_attachment_1_name }}"
|
||||
|
||||
## Contributing
|
||||
|
||||
See [/docs/contributing](docs/contributing).
|
|
@ -1,5 +0,0 @@
|
|||
# Example
|
||||
|
||||
`ansible-playbook example-playbook.yml --ask-vault-pass -vvv`
|
||||
|
||||
Password: `spamham`
|
Binary file not shown.
|
@ -0,0 +1,29 @@
|
|||
# Contributing
|
||||
|
||||
1. Create ansible.cfg in cloned directory:
|
||||
|
||||
```
|
||||
[defaults]
|
||||
COLLECTIONS_PATH = ./collections
|
||||
```
|
||||
|
||||
2. Create requirements.yml in cloned directory:
|
||||
|
||||
```
|
||||
---
|
||||
collections:
|
||||
- name: namespace.collection_name
|
||||
source: /where/is/your/clone
|
||||
type: dir
|
||||
```
|
||||
|
||||
|
||||
3. To install the collection _locally_ in your cloned directory, just install it through ansible-galaxy
|
||||
```shell
|
||||
rm -rf ./collections && ansible-galaxy install -r requirements.yml
|
||||
```
|
||||
|
||||
Note: Any change on your clone imply to reinstall the collection.
|
||||
|
||||
|
||||
Tip: You can place a ansible.cfg with `COLLECTIONS_PATH = ../../collections` in the examples dictory if you want to run the example on local collection in your cloned directory.
|
|
@ -0,0 +1,10 @@
|
|||
# Example
|
||||
|
||||
`ansible-playbook example-playbook.yml --ask-vault-pass -vvv`
|
||||
|
||||
Password: `spamham`
|
||||
|
||||
|
||||
## Docker
|
||||
|
||||
`DOCKER_BUILDKIT=1 docker-compose up --build`
|
|
@ -11,7 +11,8 @@
|
|||
slash_url: "{{ lookup('viczem.keepass.keepass', 'slash\\/group/slash\\/title', 'url') }}"
|
||||
pork_custom_property: "{{ lookup('viczem.keepass.keepass', 'example/pork', 'custom_properties', 'pork_custom_property')}}"
|
||||
attachment: "{{ lookup('viczem.keepass.keepass', 'example/pork', 'attachments', 'test.txt')}}"
|
||||
|
||||
keepass_attachment_1_name: "attachment_1.txt"
|
||||
keepass_attachment_2_name: "attachment_2.zip"
|
||||
|
||||
tasks:
|
||||
- debug:
|
||||
|
@ -33,4 +34,22 @@
|
|||
- debug:
|
||||
msg: "fetch entry: '/slash\\/group/slash\\/title'; username: '{{ slash_login }}'; url: '{{ slash_url }}'"
|
||||
|
||||
- debug: "{{ lookup('viczem.keepass.keepass', 'close') }}"
|
||||
- debug:
|
||||
msg: "close {{ lookup('viczem.keepass.keepass', 'close') }}"
|
||||
|
||||
- name: "Export file: {{ keepass_attachment_1_name }}"
|
||||
viczem.keepass.attachment:
|
||||
database: "{{ keepass_dbx }}"
|
||||
password: "{{ keepass_psw }}"
|
||||
entrypath: example/attachments
|
||||
attachment: "{{ keepass_attachment_1_name }}"
|
||||
dest: "{{ keepass_attachment_1_name }}"
|
||||
|
||||
- name: "Export file: {{ keepass_attachment_2_name }}"
|
||||
viczem.keepass.attachment:
|
||||
database: "{{ keepass_dbx }}"
|
||||
password: "{{ keepass_psw }}"
|
||||
entrypath: example/attachments
|
||||
attachment: "{{ keepass_attachment_2_name }}"
|
||||
dest: "{{ keepass_attachment_2_name }}"
|
||||
mode: 0600
|
Binary file not shown.
|
@ -8,7 +8,7 @@ namespace: viczem
|
|||
name: keepass
|
||||
|
||||
# The version of the collection. Must be compatible with semantic versioning
|
||||
version: 0.6.0
|
||||
version: 0.7.5
|
||||
|
||||
# The path to the Markdown (.md) readme file. This path is relative to the root of the collection
|
||||
readme: README.md
|
||||
|
@ -21,11 +21,7 @@ authors:
|
|||
|
||||
### OPTIONAL but strongly recommended
|
||||
# A short summary description of the collection
|
||||
description: The collection provides a lookup plugin that allow to read data from KeePass file.
|
||||
|
||||
# Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only
|
||||
# accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file'
|
||||
license: []
|
||||
description: The collection provides plugins that allow to read data from KeePass file.
|
||||
|
||||
# The path to the license file for the collection. This path is relative to the root of the collection. This key is
|
||||
# mutually exclusive with 'license'
|
||||
|
@ -36,6 +32,7 @@ license_file: 'LICENSE'
|
|||
tags:
|
||||
- keepass
|
||||
- lookup
|
||||
- module
|
||||
- plugin
|
||||
|
||||
# Collections that this collection requires to be installed for it to be usable. The key of the dict is the
|
||||
|
|
|
@ -3,6 +3,7 @@ __metaclass__ = type
|
|||
import argparse
|
||||
import getpass
|
||||
import hashlib
|
||||
import fcntl
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
|
@ -21,7 +22,7 @@ from pykeepass.exceptions import CredentialsError
|
|||
DOCUMENTATION = """
|
||||
lookup: keepass
|
||||
author: Victor Zemtsov <viczem.dev@gmail.com>
|
||||
version_added: '0.6.0'
|
||||
version_added: '0.7.5'
|
||||
short_description: Fetching data from KeePass file
|
||||
description:
|
||||
- This lookup returns a value of a property of a KeePass entry
|
||||
|
@ -71,26 +72,37 @@ class LookupModule(LookupBase):
|
|||
|
||||
# Check key file (optional)
|
||||
var_key = self._var(variables_.get("keepass_key", ""))
|
||||
if not var_key and "ANSIBLE_KEEPASS_KEY_FILE" in os.environ:
|
||||
var_key = os.environ.get('ANSIBLE_KEEPASS_KEY_FILE')
|
||||
|
||||
if var_key:
|
||||
var_key = os.path.realpath(os.path.expanduser(os.path.expandvars(var_key)))
|
||||
if not os.path.isfile(var_key):
|
||||
raise AnsibleError("KeePass: '%s' is not found" % var_key)
|
||||
|
||||
# Check password (required)
|
||||
# Check password (optional)
|
||||
var_psw = self._var(variables_.get("keepass_psw", ""))
|
||||
if not var_psw:
|
||||
raise AnsibleError("KeePass: 'keepass_psw' is not set")
|
||||
|
||||
if not var_psw and "ANSIBLE_KEEPASS_PSW" in os.environ:
|
||||
var_psw = os.environ.get('ANSIBLE_KEEPASS_PSW')
|
||||
|
||||
if not var_key and not var_psw:
|
||||
raise AnsibleError("KeePass: 'keepass_psw' and/or 'keepass_key' is not set")
|
||||
|
||||
# TTL of keepass socket (optional, default: 60 seconds)
|
||||
var_ttl = self._var(str(variables_.get("keepass_ttl", "60")))
|
||||
default_ttl = "60"
|
||||
if "ANSIBLE_KEEPASS_TTL" in os.environ:
|
||||
default_ttl = os.environ.get("ANSIBLE_KEEPASS_TTL")
|
||||
var_ttl = self._var(str(variables_.get("keepass_ttl", default_ttl)))
|
||||
|
||||
socket_path = _keepass_socket_path(var_dbx)
|
||||
lock_file_ = socket_path + ".lock"
|
||||
|
||||
if not os.path.isfile(lock_file_):
|
||||
try:
|
||||
os.open(lock_file_, os.O_RDWR)
|
||||
except FileNotFoundError:
|
||||
cmd = [
|
||||
"/usr/bin/env",
|
||||
"python3",
|
||||
sys.executable,
|
||||
os.path.abspath(__file__),
|
||||
var_dbx,
|
||||
socket_path,
|
||||
|
@ -121,7 +133,6 @@ class LookupModule(LookupBase):
|
|||
if resp[1] == "0":
|
||||
success = True
|
||||
else:
|
||||
sock.send(_rq("close"))
|
||||
raise AnsibleError("KeePass: wrong dbx password")
|
||||
sock.close()
|
||||
break
|
||||
|
@ -136,6 +147,7 @@ class LookupModule(LookupBase):
|
|||
|
||||
if len(terms) == 1 and terms[0] in ("quit", "exit", "close"):
|
||||
self._send(socket_path, terms[0], [])
|
||||
return []
|
||||
else:
|
||||
# Fetching data from the keepass socket
|
||||
return self._send(socket_path, "fetch", terms)
|
||||
|
@ -153,7 +165,14 @@ class LookupModule(LookupBase):
|
|||
display.vvv("KeePass: %s %s" % (cmd, terms))
|
||||
sock.send(_rq(cmd, *terms))
|
||||
|
||||
resp = sock.recv(1024).decode().splitlines()
|
||||
data = b''
|
||||
while True:
|
||||
_ = sock.recv(1024)
|
||||
data += _
|
||||
if len(_) < 1024:
|
||||
break
|
||||
|
||||
resp = data.decode().splitlines()
|
||||
resp_len = len(resp)
|
||||
if resp_len == 0:
|
||||
raise AnsibleError("KeePass: '%s' result is empty" % cmd)
|
||||
|
@ -189,7 +208,6 @@ def _keepass_socket(kdbx, kdbx_key, sock_path, ttl=60, kdbx_password=None):
|
|||
"""
|
||||
tmp_files = []
|
||||
try:
|
||||
os.umask(0o177)
|
||||
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
|
||||
s.bind(sock_path)
|
||||
s.listen(1)
|
||||
|
@ -229,16 +247,20 @@ def _keepass_socket(kdbx, kdbx_key, sock_path, ttl=60, kdbx_password=None):
|
|||
|
||||
# CMD: password
|
||||
if kp is None:
|
||||
if arg_len == 0:
|
||||
conn.send(_resp("password", 1))
|
||||
break
|
||||
if cmd == "password" and arg[0]:
|
||||
if cmd == "password" and arg_len > 0:
|
||||
kp = PyKeePass(kdbx, arg[0], kdbx_key)
|
||||
conn.send(_resp("password", 0))
|
||||
break
|
||||
elif cmd == "password" and kdbx_key:
|
||||
kp = PyKeePass(kdbx, None, kdbx_key)
|
||||
conn.send(_resp("password", 0))
|
||||
break
|
||||
else:
|
||||
conn.send(_resp("password", 1))
|
||||
break
|
||||
elif cmd == "password":
|
||||
conn.send(_resp("password", 0))
|
||||
break
|
||||
|
||||
# CMD: fetch
|
||||
# Read data from decrypted KeePass file
|
||||
|
@ -348,7 +370,7 @@ def _keepass_socket(kdbx, kdbx_key, sock_path, ttl=60, kdbx_password=None):
|
|||
)
|
||||
)
|
||||
break
|
||||
conn.send(_resp("fetch", 0, getattr(entry, prop)))
|
||||
conn.send(_resp("fetch", 0, entry.deref(prop)))
|
||||
except CredentialsError:
|
||||
print("%s failed to decrypt" % kdbx)
|
||||
sys.exit(1)
|
||||
|
@ -395,6 +417,9 @@ def _resp(cmd, status_code, payload=""):
|
|||
|
||||
def _keepass_socket_path(dbx_path):
|
||||
# UNIX socket path for a dbx (supported multiple dbx)
|
||||
if "ANSIBLE_KEEPASS_SOCKET" in os.environ:
|
||||
return os.environ.get('ANSIBLE_KEEPASS_SOCKET')
|
||||
# else:
|
||||
tempdir = tempfile.gettempdir()
|
||||
if not os.access(tempdir, os.W_OK):
|
||||
raise AnsibleError("KeePass: no write permissions to '%s'" % tempdir)
|
||||
|
@ -403,6 +428,20 @@ def _keepass_socket_path(dbx_path):
|
|||
return "%s/ansible-keepass-%s.sock" % (tempdir, suffix[:8])
|
||||
|
||||
|
||||
def lock(kdbx_sock_path):
|
||||
fd = os.open(kdbx_sock_path + ".lock", os.O_RDWR | os.O_CREAT | os.O_TRUNC)
|
||||
|
||||
try:
|
||||
# The LOCK_EX means that only one process can hold the lock
|
||||
# The LOCK_NB means that the fcntl.flock() is not blocking
|
||||
# https://docs.python.org/3/library/fcntl.html#fcntl.flock
|
||||
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except (IOError, OSError):
|
||||
return None
|
||||
|
||||
return fd
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
arg_parser = argparse.ArgumentParser()
|
||||
arg_parser.add_argument("kdbx", type=str)
|
||||
|
@ -412,24 +451,29 @@ if __name__ == "__main__":
|
|||
arg_parser.add_argument("--ask-pass", action="store_true")
|
||||
args = arg_parser.parse_args()
|
||||
|
||||
kdbx = os.path.realpath(os.path.expanduser(os.path.expandvars(args.kdbx)))
|
||||
arg_kdbx = os.path.realpath(os.path.expanduser(os.path.expandvars(args.kdbx)))
|
||||
if args.key:
|
||||
key = os.path.realpath(os.path.expanduser(os.path.expandvars(args.key)))
|
||||
arg_key = os.path.realpath(os.path.expanduser(os.path.expandvars(args.key)))
|
||||
else:
|
||||
key = None
|
||||
arg_key = None
|
||||
|
||||
if args.kdbx_sock:
|
||||
kdbx_sock = args.kdbx_sock
|
||||
arg_kdbx_sock = args.kdbx_sock
|
||||
else:
|
||||
kdbx_sock = _keepass_socket_path(kdbx)
|
||||
arg_kdbx_sock = _keepass_socket_path(arg_kdbx)
|
||||
|
||||
password = None
|
||||
if args.ask_pass:
|
||||
password = getpass.getpass("Password: ")
|
||||
if isinstance(password, bytes):
|
||||
password = password.decode(sys.stdin.encoding)
|
||||
elif "ANSIBLE_KEEPASS_PSW" in os.environ:
|
||||
password = os.environ.get('ANSIBLE_KEEPASS_PSW')
|
||||
|
||||
lock_file = kdbx_sock + ".lock"
|
||||
if not os.path.isfile(lock_file):
|
||||
open(lock_file, "a").close()
|
||||
_keepass_socket(kdbx, key, kdbx_sock, args.ttl, password)
|
||||
arg_ttl = args.ttl
|
||||
if arg_ttl is None and "ANSIBLE_KEEPASS_TTL" in os.environ:
|
||||
arg_ttl = os.environ.get('ANSIBLE_KEEPASS_TTL')
|
||||
|
||||
os.umask(0o177)
|
||||
if lock(arg_kdbx_sock):
|
||||
_keepass_socket(arg_kdbx, arg_key, arg_kdbx_sock, arg_ttl, password)
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2022, Jimisola Laursen <jimisola@jimisola.com>
|
||||
# Copyright: (c) 2022, LFV <www.lfv.se>
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
import traceback
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible.module_utils._text import to_bytes, to_native
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
LIB_IMP_ERR = None
|
||||
try:
|
||||
from pykeepass import PyKeePass
|
||||
|
||||
HAS_LIB = True
|
||||
except Exception:
|
||||
HAS_LIB = False
|
||||
LIB_IMP_ERR = traceback.format_exc()
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
---
|
||||
module: attachment
|
||||
author:
|
||||
- Jimisola Laursen (@lfvjimisola)
|
||||
- Jimisola Laursen (@jimisola)
|
||||
|
||||
short_description: Exports KeePass attachments
|
||||
description:
|
||||
- This module will export an attachment in a KeePass entry to a file.
|
||||
|
||||
version_added: "0.1.0"
|
||||
|
||||
extends_documentation_fragment:
|
||||
- files
|
||||
- action_common_attributes
|
||||
|
||||
requirements:
|
||||
- pykeepass
|
||||
|
||||
options:
|
||||
database:
|
||||
description: Path to KeePass database file
|
||||
required: true
|
||||
type: str
|
||||
password:
|
||||
description: Password for KeePass database file
|
||||
required: true
|
||||
type: str
|
||||
entrypath:
|
||||
description: Path to KeePass entry containing the attachment that should be exported
|
||||
required: true
|
||||
type: str
|
||||
attachment:
|
||||
description: Name of attachment that should be exported
|
||||
required: true
|
||||
type: str
|
||||
dest:
|
||||
description: Absolute path where the file should be exported to
|
||||
required: true
|
||||
type: str
|
||||
|
||||
attributes:
|
||||
check_mode:
|
||||
support: none
|
||||
diff_mode:
|
||||
support: none
|
||||
platform:
|
||||
platforms: posix
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
# Export a file
|
||||
- name: Export a file from KeePass
|
||||
keepass:
|
||||
database: database.kdbx
|
||||
password: somepassword
|
||||
path: "group/subgroup/entry"
|
||||
attachment: somefile.txt
|
||||
dest: somefile_exported.txt
|
||||
"""
|
||||
|
||||
RETURN = r""" # """
|
||||
|
||||
|
||||
def check_file_attrs(module, result, diff):
|
||||
|
||||
changed, msg = result["changed"], result["msg"]
|
||||
|
||||
file_args = module.load_file_common_arguments(module.params)
|
||||
if module.set_fs_attributes_if_different(file_args, False, diff=diff):
|
||||
|
||||
if changed:
|
||||
msg += " and "
|
||||
changed = True
|
||||
msg += "ownership, perms or SE linux context changed"
|
||||
|
||||
result["changed"] = changed
|
||||
result["msg"] = msg
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def export_attachment(module, result):
|
||||
try:
|
||||
# load database
|
||||
kp = PyKeePass(
|
||||
module.params["database"],
|
||||
password=module.params["password"],
|
||||
keyfile=module.params["keyfile"])
|
||||
|
||||
entrypath = module.params["entrypath"]
|
||||
dest = module.params["dest"]
|
||||
attachment = module.params["attachment"]
|
||||
|
||||
# find entry
|
||||
kp_entry = kp.find_entries(path=entrypath.split("/"), first=True)
|
||||
|
||||
if kp_entry is None:
|
||||
module.fail_json(msg="Entry '{0}' not found".format(entrypath))
|
||||
|
||||
kp_attachment = None
|
||||
for item in kp_entry.attachments:
|
||||
if item.filename == attachment:
|
||||
kp_attachment = item
|
||||
|
||||
if kp_attachment is None:
|
||||
module.fail_json(
|
||||
msg="Entry '{0}' does not contain attachment '{1}'".format(
|
||||
entrypath, attachment
|
||||
)
|
||||
)
|
||||
|
||||
b_data = kp_attachment.binary
|
||||
|
||||
tmpfd, tmpfile = tempfile.mkstemp()
|
||||
f = os.fdopen(tmpfd, "wb")
|
||||
f.write(b_data)
|
||||
f.close()
|
||||
|
||||
module.atomic_move(
|
||||
tmpfile,
|
||||
to_native(
|
||||
os.path.realpath(to_bytes(dest, errors="surrogate_or_strict")),
|
||||
errors="surrogate_or_strict",
|
||||
),
|
||||
unsafe_writes=module.params["unsafe_writes"],
|
||||
)
|
||||
|
||||
result["changed"] = True
|
||||
result["msg"] = "attachment '{0}' exported to file '{1}'".format(
|
||||
module.params["attachment"], dest
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
result["msg"] = "Module viczem.keepass.attachment failed: {0}".format(e)
|
||||
module.fail_json(**result)
|
||||
|
||||
attr_diff = None
|
||||
|
||||
result = check_file_attrs(module, result, attr_diff)
|
||||
|
||||
module.exit_json(**result, diff=attr_diff)
|
||||
|
||||
|
||||
def main():
|
||||
module_args = dict(
|
||||
database=dict(type="str", required=True),
|
||||
password=dict(type="str", no_log=True, required=True),
|
||||
keyfile=dict(type="str", no_log=True, required=False),
|
||||
entrypath=dict(type="str", required=True),
|
||||
attachment=dict(type="str", required=True),
|
||||
dest=dict(type="path", required=True),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=module_args,
|
||||
add_file_common_args=True,
|
||||
)
|
||||
|
||||
if not HAS_LIB:
|
||||
module.fail_json(msg=missing_required_lib("pykeepass"), exception=LIB_IMP_ERR)
|
||||
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
|
||||
dest = module.params["dest"]
|
||||
b_dest = to_bytes(dest, errors="surrogate_or_strict")
|
||||
|
||||
if os.path.isdir(b_dest):
|
||||
module.fail_json(rc=256, msg="Destination {0} is a directory!".format(dest))
|
||||
|
||||
export_attachment(module, result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Binary file not shown.
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<KeyFile>
|
||||
<Meta>
|
||||
<Version>2.0</Version>
|
||||
</Meta>
|
||||
<Key>
|
||||
<Data Hash="95ED5C71">
|
||||
8810353D 83453EDC 2266A931 A0A073F9
|
||||
54B90B68 1E341EF4 6B47729B F42DBE0A
|
||||
</Data>
|
||||
</Key>
|
||||
</KeyFile>
|
|
@ -0,0 +1,2 @@
|
|||
[test]
|
||||
127.0.0.1 keepass_dbx=./ansible.kdbx keepass_key=./ansible.keyx keepass_ttl=3
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
- name: test-keepass-keyfile-only
|
||||
hosts: test
|
||||
connection: local
|
||||
vars:
|
||||
test_username: "{{ lookup('viczem.keepass.keepass', 'test', 'username') }}"
|
||||
test_password: "{{ lookup('viczem.keepass.keepass', 'test', 'password') }}"
|
||||
|
||||
tasks:
|
||||
- debug:
|
||||
msg: "fetch entry: '/test'; username: '{{ test_username }}'; password: '{{ test_password }}'"
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
ansible-playbook -i hosts.ini -vvvv playbook.yml
|
Binary file not shown.
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<KeyFile>
|
||||
<Meta>
|
||||
<Version>2.0</Version>
|
||||
</Meta>
|
||||
<Key>
|
||||
<Data Hash="E13D7CBE">
|
||||
D7A7EA4F D6DCBFD7 B2DFE21C E89FFBB0
|
||||
B203AAA5 4A32C405 D6C1B3CA B69C40BF
|
||||
</Data>
|
||||
</Key>
|
||||
</KeyFile>
|
|
@ -0,0 +1,2 @@
|
|||
[test]
|
||||
127.0.0.1 keepass_dbx=./ansible.kdbx keepass_psw=spamham keepass_key=./ansible.keyx keepass_ttl=3
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
- name: test-keepass-keyfile-only
|
||||
hosts: test
|
||||
connection: local
|
||||
vars:
|
||||
test_username: "{{ lookup('viczem.keepass.keepass', 'test', 'username') }}"
|
||||
test_password: "{{ lookup('viczem.keepass.keepass', 'test', 'password') }}"
|
||||
|
||||
tasks:
|
||||
- debug:
|
||||
msg: "fetch entry: '/test'; username: '{{ test_username }}'; password: '{{ test_password }}'"
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
ansible-playbook -i hosts.ini -vvvv playbook.yml
|
Binary file not shown.
|
@ -0,0 +1,2 @@
|
|||
[test]
|
||||
127.0.0.1 keepass_dbx=./ansible.kdbx keepass_psw=spamham keepass_ttl=3
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
- name: test-keepass-keyfile-only
|
||||
hosts: test
|
||||
connection: local
|
||||
vars:
|
||||
test_username: "{{ lookup('viczem.keepass.keepass', 'test', 'username') }}"
|
||||
test_password: "{{ lookup('viczem.keepass.keepass', 'test', 'password') }}"
|
||||
|
||||
tasks:
|
||||
- debug:
|
||||
msg: "fetch entry: '/test'; username: '{{ test_username }}'; password: '{{ test_password }}'"
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
ansible-playbook -i hosts.ini -vvvv playbook.yml
|
|
@ -0,0 +1,3 @@
|
|||
[defaults]
|
||||
host_key_checking = False
|
||||
inventory = ./inventory.ini
|
Binary file not shown.
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
|
||||
ssh-keygen -R 172.24.2.1
|
||||
ssh-keygen -R 172.24.2.2
|
||||
ssh-keygen -R 172.24.2.3
|
||||
ssh-keygen -R 172.24.2.4
|
||||
ssh-keygen -R 172.24.2.5
|
||||
|
||||
cd ./docker || exit
|
||||
docker-compose down
|
||||
docker rmi ansible-keepass-test-1 ansible-keepass-test-2 ansible-keepass-test-3 ansible-keepass-test-4 ansible-keepass-test-5
|
|
@ -0,0 +1,7 @@
|
|||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACCW034btEPrMzPe4xNKw02O70DhQr+EOnwz0vqNNqUgtAAAAJjHR3u1x0d7
|
||||
tQAAAAtzc2gtZWQyNTUxOQAAACCW034btEPrMzPe4xNKw02O70DhQr+EOnwz0vqNNqUgtA
|
||||
AAAEBnKHslpVj1lBKjreOmPTIhd5mPgl3jaCHlEleLVmSfd5bTfhu0Q+szM97jE0rDTY7v
|
||||
QOFCv4Q6fDPS+o02pSC0AAAAE2V4YW1wbGVAZXhhbXBsZS5jb20BAg==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
|
@ -0,0 +1 @@
|
|||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJbTfhu0Q+szM97jE0rDTY7vQOFCv4Q6fDPS+o02pSC0 example@example.com
|
|
@ -0,0 +1,25 @@
|
|||
FROM alpine:3.16
|
||||
|
||||
ARG USERNAME
|
||||
ARG PASSWORD
|
||||
|
||||
|
||||
RUN apk add --update --no-cache sudo openssh python3 \
|
||||
&& cd /etc/ssh && ssh-keygen -A \
|
||||
&& echo "PubkeyAuthentication yes" >> /etc/ssh/sshd_config \
|
||||
&& echo "PermitRootLogin no" >> /etc/ssh/sshd_config \
|
||||
&& echo "PasswordAuthentication no" >> /etc/ssh/sshd_config \
|
||||
&& echo '%wheel ALL=(ALL) ALL' > /etc/sudoers.d/wheel
|
||||
|
||||
RUN adduser -D $USERNAME -G wheel \
|
||||
&& echo $USERNAME:$PASSWORD | chpasswd \
|
||||
&& mkdir -p /home/$USERNAME/.ssh \
|
||||
&& chmod go-w /home/$USERNAME \
|
||||
&& chmod 700 /home/$USERNAME/.ssh \
|
||||
&& chown $USERNAME -R /home/$USERNAME/.ssh
|
||||
|
||||
COPY --chmod=600 --chown=$USERNAME .ssh/id_ed25519.pub /home/$USERNAME/.ssh/authorized_keys
|
||||
|
||||
EXPOSE 22
|
||||
|
||||
CMD ["/usr/sbin/sshd", "-D"]
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
## UP test servers
|
||||
|
||||
```sh
|
||||
DOCKER_BUILDKIT=1 docker-compose build
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## DOWN test servers
|
||||
```sh
|
||||
docker-compose down
|
||||
docker rmi ansible-keepass-test-1 ansible-keepass-test-2 ansible-keepass-test-3 ansible-keepass-test-4 ansible-keepass-test-5
|
||||
```
|
|
@ -0,0 +1,65 @@
|
|||
version: "3"
|
||||
services:
|
||||
ansible-keepass-test-1:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
USERNAME: user1
|
||||
PASSWORD: password1
|
||||
image: "ansible-keepass-test-1:latest"
|
||||
networks:
|
||||
ansible-net:
|
||||
ipv4_address: 172.24.2.1
|
||||
|
||||
ansible-keepass-test-2:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
USERNAME: user2
|
||||
PASSWORD: password2
|
||||
image: "ansible-keepass-test-2:latest"
|
||||
networks:
|
||||
ansible-net:
|
||||
ipv4_address: 172.24.2.2
|
||||
|
||||
|
||||
ansible-keepass-test-3:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
USERNAME: user3
|
||||
PASSWORD: password3
|
||||
image: "ansible-keepass-test-3:latest"
|
||||
networks:
|
||||
ansible-net:
|
||||
ipv4_address: 172.24.2.3
|
||||
|
||||
ansible-keepass-test-4:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
USERNAME: user4
|
||||
PASSWORD: password4
|
||||
image: "ansible-keepass-test-4:latest"
|
||||
networks:
|
||||
ansible-net:
|
||||
ipv4_address: 172.24.2.4
|
||||
|
||||
ansible-keepass-test-5:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
USERNAME: user5
|
||||
PASSWORD: password5
|
||||
image: "ansible-keepass-test-5:latest"
|
||||
networks:
|
||||
ansible-net:
|
||||
ipv4_address: 172.24.2.5
|
||||
|
||||
networks:
|
||||
ansible-net:
|
||||
driver: bridge
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: "172.24.2.0/16"
|
|
@ -0,0 +1,2 @@
|
|||
ansible_user : "{{ lookup('viczem.keepass.keepass', 'srv-1', 'username') }}"
|
||||
ansible_become_pass : "{{ lookup('viczem.keepass.keepass', 'srv-1', 'password') }}"
|
|
@ -0,0 +1,3 @@
|
|||
ansible_host : 172.24.2.2
|
||||
ansible_user : "{{ lookup('viczem.keepass.keepass', 'srv-2', 'username') }}"
|
||||
ansible_become_pass : "{{ lookup('viczem.keepass.keepass', 'srv-2', 'password') }}"
|
|
@ -0,0 +1,3 @@
|
|||
ansible_host : 172.24.2.3
|
||||
ansible_user : "{{ lookup('viczem.keepass.keepass', 'srv-3', 'username') }}"
|
||||
ansible_become_pass : "{{ lookup('viczem.keepass.keepass', 'srv-3', 'password') }}"
|
|
@ -0,0 +1,3 @@
|
|||
ansible_host : 172.24.2.4
|
||||
ansible_user : "{{ lookup('viczem.keepass.keepass', 'srv-4', 'username') }}"
|
||||
ansible_become_pass : "{{ lookup('viczem.keepass.keepass', 'srv-4', 'password') }}"
|
|
@ -0,0 +1,3 @@
|
|||
ansible_host : 172.24.2.5
|
||||
ansible_user : "{{ lookup('viczem.keepass.keepass', 'srv-5', 'username') }}"
|
||||
ansible_become_pass : "{{ lookup('viczem.keepass.keepass', 'srv-5', 'password') }}"
|
|
@ -0,0 +1,5 @@
|
|||
keepass_dbx: ./ansible.kdbx
|
||||
keepass_psw: spamham
|
||||
keepass_ttl: 3
|
||||
|
||||
ansible_ssh_private_key_file: ./docker/.ssh/id_ed25519
|
|
@ -0,0 +1,14 @@
|
|||
[SRV1]
|
||||
srv-1 ansible_host=172.24.2.1
|
||||
|
||||
[SRV2]
|
||||
srv-2 ansible_host=172.24.2.2
|
||||
|
||||
[SRV3]
|
||||
srv-3 ansible_host=172.24.2.3
|
||||
|
||||
[SRV4]
|
||||
srv-4 ansible_host=172.24.2.4
|
||||
|
||||
[SRV5]
|
||||
srv-5 ansible_host=172.24.2.5
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
- name: Parallel
|
||||
hosts: all
|
||||
|
||||
tasks:
|
||||
- ansible.builtin.ping:
|
||||
|
||||
- name: pause to emulate long time operation (greater than keepass_ttl)
|
||||
pause:
|
||||
seconds: 5
|
||||
|
||||
- ansible.builtin.ping:
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
# ansible all -m ping
|
||||
ansible-playbook playbook.yml -f5
|
Loading…
Reference in New Issue