305 lines
8.9 KiB
Python
305 lines
8.9 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright: (c) 2020, Yo <johan@nosd.in>
|
|
# Use ansible-vault python library from Tomohiro Nakamura <quickness.net@gmail.com> (Prefer v2)
|
|
# Vastly inspired by the lineinfile module
|
|
#
|
|
# v.0.5 19/05/2020
|
|
#
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
__metaclass__ = type
|
|
|
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
'status': ['preview'],
|
|
'supported_by': 'community'}
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: putinvault
|
|
short_description: Manage Ansible vault file
|
|
description:
|
|
- Manages passwords in Ansible Vault files
|
|
version_added: "0.1"
|
|
options:
|
|
path:
|
|
description:
|
|
- Ansible vault File path
|
|
type: path
|
|
required: true
|
|
aliases: [ name, vault ]
|
|
password:
|
|
description:
|
|
- The password to access the vault.
|
|
- Required for C(state=present).
|
|
type: str
|
|
required: true
|
|
aliases: [ vault_pass ]
|
|
key:
|
|
description:
|
|
- The password name to insert/replace into the vault file.
|
|
- Required for C(state=present).
|
|
type: str
|
|
required: true
|
|
value:
|
|
description:
|
|
- The password value to insert/replace into the vault file.
|
|
- Required for C(state=present).
|
|
type: str
|
|
state:
|
|
description:
|
|
- Whether the password should be there or not.
|
|
type: str
|
|
choices: [ absent, present ]
|
|
default: present
|
|
replace:
|
|
description:
|
|
- Used with C(state=present).
|
|
- If specified and the password already exist in vault file, it will be overwritten with value.
|
|
- By default it will fail if password exist in vault.
|
|
type: bool
|
|
default: no
|
|
aliases: [ overwrite ]
|
|
strict:
|
|
description:
|
|
- Used with C(state=absent).
|
|
- If specified to no, remove password whatever value is.
|
|
- By default it will remove password only when key and value match specified.
|
|
type: bool
|
|
default: yes
|
|
create:
|
|
description:
|
|
- Used with C(state=present).
|
|
- If specified, the vault file will be created if it does not already exist.
|
|
- By default it will fail if the vault file is missing.
|
|
type: bool
|
|
default: no
|
|
backup:
|
|
description:
|
|
- Create a backup file including the timestamp information so you can
|
|
get the original file back if you somehow clobbered it incorrectly.
|
|
type: bool
|
|
default: no
|
|
author:
|
|
- Johan Vaucourt (johan.vaucourt@gmail.com)
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
- name: Add a new password to a vault if the file does not exist
|
|
putinvault:
|
|
path: /tmp/vault.yml
|
|
key: 'vaulted_ldap_admin_password'
|
|
value: 'admin123'
|
|
create: yes
|
|
|
|
- name: Add a password to an existing vault. If the file does not exist or key already exists, it will trig an error
|
|
putinvault:
|
|
path: vault.yml
|
|
password: 'vault123'
|
|
key: 'vaulted_ldap_reader_password'
|
|
value: 'read123'
|
|
|
|
- name: Overwrite LDAP admin password
|
|
putinvault:
|
|
path: vault.yml
|
|
key: 'vaulted_ldap_admin_password'
|
|
value: 'newadmin123'
|
|
replace: yes
|
|
'''
|
|
|
|
import os
|
|
import re
|
|
import tempfile
|
|
from ansible_vault import Vault
|
|
|
|
# import module snippets
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible.module_utils._text import to_bytes, to_native
|
|
|
|
def check_file_attrs(module, changed, message, diff):
|
|
|
|
file_args = module.load_file_common_arguments(module.params)
|
|
if module.set_fs_attributes_if_different(file_args, False, diff=diff):
|
|
|
|
if changed:
|
|
message += " and "
|
|
changed = True
|
|
message += "ownership, perms or SE linux context changed"
|
|
|
|
return message, changed
|
|
|
|
|
|
def present(module, dest, password, key, value, replace, create, backup):
|
|
|
|
diff = {'before': '',
|
|
'after': '',
|
|
'before_header': '%s (content)' % dest,
|
|
'after_header': '%s (content)' % dest}
|
|
|
|
b_dest = to_bytes(dest, errors='surrogate_or_strict')
|
|
if not os.path.exists(b_dest):
|
|
if not create:
|
|
module.fail_json(rc=257, msg='Destination %s does not exist !' % dest)
|
|
b_destpath = os.path.dirname(b_dest)
|
|
if b_destpath and not os.path.exists(b_destpath) and not module.check_mode:
|
|
try:
|
|
os.makedirs(b_destpath)
|
|
except Exception as e:
|
|
module.fail_json(msg='Error creating %s Error code: %s Error description: %s' % (b_destpath, e[0], e[1]))
|
|
|
|
vault = Vault(password)
|
|
v_data = {}
|
|
else:
|
|
# TODO : Manage erroneous password
|
|
vault = Vault(password)
|
|
with open(b_dest, 'rb') as f:
|
|
v_data = vault.load(f.read())
|
|
|
|
if module._diff:
|
|
diff['before'] = to_native(v_data)
|
|
|
|
msg =''
|
|
changed = False
|
|
|
|
if key in v_data:
|
|
if replace == False:
|
|
module.warn(
|
|
"The password already exist in vault. If you want to replace it disregarding it current value, set replace to True.")
|
|
pass
|
|
else:
|
|
v_data[key] = value
|
|
msg = 'line replaced'
|
|
changed = True
|
|
else:
|
|
v_data[key] = value
|
|
msg = 'line added'
|
|
changed = True
|
|
|
|
if module._diff:
|
|
diff['after'] = to_native(v_data)
|
|
|
|
backupdest = ""
|
|
if changed and not module.check_mode:
|
|
if backup and os.path.exists(b_dest):
|
|
backupdest = module.backup_local(dest)
|
|
vault.dump(v_data, open(b_dest, 'w'))
|
|
|
|
if module.check_mode and not os.path.exists(b_dest):
|
|
module.exit_json(changed=changed, msg=msg, backup=backupdest, diff=diff)
|
|
|
|
attr_diff = {}
|
|
msg, changed = check_file_attrs(module, changed, msg, attr_diff)
|
|
|
|
attr_diff['before_header'] = '%s (file attributes)' % dest
|
|
attr_diff['after_header'] = '%s (file attributes)' % dest
|
|
|
|
difflist = [diff, attr_diff]
|
|
module.exit_json(changed=changed, msg=msg, backup=backupdest, diff=difflist)
|
|
|
|
|
|
def absent(module, dest, password, key, value, strict, backup):
|
|
|
|
b_dest = to_bytes(dest, errors='surrogate_or_strict')
|
|
if not os.path.exists(b_dest):
|
|
module.exit_json(changed=False, msg="file not present")
|
|
|
|
msg = ''
|
|
changed = False
|
|
|
|
diff = {'before': '',
|
|
'after': '',
|
|
'before_header': '%s (content)' % dest,
|
|
'after_header': '%s (content)' % dest}
|
|
|
|
# TODO : Manage erroneous password
|
|
vault = Vault(password)
|
|
with open(b_dest, 'rb') as f:
|
|
v_data = vault.load(f.read())
|
|
|
|
if module._diff:
|
|
diff['before'] = to_native(v_data)
|
|
|
|
if key in v_data:
|
|
if strict == True:
|
|
if v_data[key] == value:
|
|
del v_data[key]
|
|
changed = True
|
|
else:
|
|
module.fail_json(msg='value should match what is in vault with strict=true')
|
|
else:
|
|
del v_data[key]
|
|
changed = True
|
|
|
|
if module._diff:
|
|
diff['after'] = to_native(v_data)
|
|
|
|
backupdest = ""
|
|
if changed and not module.check_mode:
|
|
if backup:
|
|
backupdest = module.backup_local(dest)
|
|
vault.dump(v_data, open(b_dest, 'w'))
|
|
|
|
if changed:
|
|
msg = "Password removed"
|
|
|
|
attr_diff = {}
|
|
msg, changed = check_file_attrs(module, changed, msg, attr_diff)
|
|
|
|
attr_diff['before_header'] = '%s (file attributes)' % dest
|
|
attr_diff['after_header'] = '%s (file attributes)' % dest
|
|
|
|
difflist = [diff, attr_diff]
|
|
|
|
module.exit_json(changed=changed, msg=msg, backup=backupdest, diff=difflist)
|
|
|
|
|
|
def main():
|
|
module = AnsibleModule(
|
|
argument_spec=dict(
|
|
path=dict(type='path', required=True, aliases=['dest', 'destfile', 'name']),
|
|
password=dict(type='str', required=True, aliases=['vault_pass']),
|
|
state=dict(type='str', default='present', choices=['absent', 'present']),
|
|
key=dict(type='str', required=True),
|
|
value=dict(type='str'),
|
|
replace=dict(type='bool', aliases=['overwrite'], default=False),
|
|
strict=dict(type='bool', default=True),
|
|
create=dict(type='bool', default=False),
|
|
backup=dict(type='bool', default=False),
|
|
),
|
|
add_file_common_args=True,
|
|
supports_check_mode=True,
|
|
)
|
|
|
|
params = module.params
|
|
path = params['path']
|
|
password = params['password']
|
|
state = params['state']
|
|
key = params['key']
|
|
value = params['value']
|
|
replace = params['replace']
|
|
strict = params['strict']
|
|
create = params['create']
|
|
backup = params['backup']
|
|
|
|
b_path = to_bytes(path, errors='surrogate_or_strict')
|
|
if os.path.isdir(b_path):
|
|
module.fail_json(rc=256, msg='Path %s is a directory !' % path)
|
|
|
|
if params['state'] == 'present':
|
|
if key is None or value is None:
|
|
module.fail_json(msg='key and value are required with state=present')
|
|
|
|
present(module, path, password, key, value, replace, create, backup)
|
|
else:
|
|
if value is None and strict is True:
|
|
module.fail_json(msg='value is required with strict=True')
|
|
|
|
absent(module, path, password, key, value, strict, backup)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|
|
|