mirror of
https://github.com/freeipa/ansible-freeipa.git
synced 2026-07-25 17:04:43 +00:00
Current implementation does not allow the change of an existingi Vault
type. To allow it, data is retrieved from the current vault, the vault
is modifiend, and then, data is stored again in the new vault.
Due to changing the process of modifying a vault, this change also
fixes the update of asymmetric vault keys. To change the key used,
the task must provide the old private key, used to retrieve data,
and the new public_key, used to store the data again. A new alias
was added to public_key (new_public_key) and public_key_file
(new_public_key_file) so that the playbook better express the
intention of the tak.
Vault tests have been updated to better test against the new update
process, and a new test file has bee added:
tests/vault/test_vault_change_type.
1027 lines
37 KiB
Python
1027 lines
37 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Authors:
|
|
# Rafael Guterres Jeffman <rjeffman@redhat.com>
|
|
#
|
|
# Copyright (C) 2019 Red Hat
|
|
# see file 'COPYING' for use and warranty information
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
ANSIBLE_METADATA = {
|
|
"metadata_version": "1.0",
|
|
"supported_by": "community",
|
|
"status": ["preview"],
|
|
}
|
|
|
|
DOCUMENTATION = """
|
|
---
|
|
module: ipavault
|
|
short description: Manage vaults and secret vaults.
|
|
description: Manage vaults and secret vaults. KRA service must be enabled.
|
|
options:
|
|
ipaadmin_principal:
|
|
description: The admin principal
|
|
default: admin
|
|
ipaadmin_password:
|
|
description: The admin password
|
|
required: false
|
|
name:
|
|
description: The vault name
|
|
required: true
|
|
aliases: ["cn"]
|
|
description:
|
|
description: The vault description
|
|
required: false
|
|
public_key:
|
|
description: Base64 encode public key.
|
|
required: false
|
|
type: string
|
|
aliases: ["ipavaultpublickey", "vault_public_key"]
|
|
public_key_file:
|
|
description: Path to file with public key.
|
|
required: false
|
|
type: string
|
|
aliases: ["vault_public_key_file"]
|
|
private_key:
|
|
description: Base64 encode private key.
|
|
required: false
|
|
type: string
|
|
aliases: ["ipavaultprivatekey", "vault_private_key"]
|
|
private_key_file:
|
|
description: Path to file with private key.
|
|
required: false
|
|
type: string
|
|
aliases: ["vault_private_key_file"]
|
|
password:
|
|
description: password to be used on symmetric vault.
|
|
required: false
|
|
type: string
|
|
aliases: ["ipavaultpassword", "vault_password", "old_password"]
|
|
password_file:
|
|
description: file with password to be used on symmetric vault.
|
|
required: false
|
|
type: string
|
|
aliases: ["vault_password_file", "old_password_file"]
|
|
new_password:
|
|
description: new password to be used on symmetric vault.
|
|
required: false
|
|
type: string
|
|
new_password_file:
|
|
description: file with new password to be used on symmetric vault.
|
|
required: false
|
|
type: string
|
|
salt:
|
|
description: Vault salt.
|
|
required: false
|
|
type: list
|
|
aliases: ["ipavaultsalt", "vault_salt"]
|
|
vault_type:
|
|
description: Vault types are based on security level.
|
|
required: true
|
|
default: symmetric
|
|
choices: ["standard", "symmetric", "asymmetric"]
|
|
aliases: ["ipavaulttype"]
|
|
service:
|
|
description: Any service can own one or more service vaults.
|
|
required: false
|
|
type: list
|
|
username:
|
|
description: Any user can own one or more user vaults.
|
|
required: false
|
|
type: string
|
|
aliases: ["user"]
|
|
shared:
|
|
description: Vault is shared.
|
|
required: false
|
|
type: boolean
|
|
users:
|
|
description: Users that are member of the vault.
|
|
required: false
|
|
type: list
|
|
groups:
|
|
description: Groups that are member of the vault.
|
|
required: false
|
|
type: list
|
|
owners:
|
|
description: Users that are owners of the vault.
|
|
required: false
|
|
type: list
|
|
ownergroups:
|
|
description: Groups that are owners of the vault.
|
|
required: false
|
|
type: list
|
|
ownerservices:
|
|
description: Services that are owners of the vault.
|
|
required: false
|
|
type: list
|
|
services:
|
|
description: Services that are member of the container.
|
|
required: false
|
|
type: list
|
|
data:
|
|
description: Data to be stored in the vault.
|
|
required: false
|
|
type: string
|
|
aliases: ["ipavaultdata", "vault_data"]
|
|
in:
|
|
description: Path to file with data to be stored in the vault.
|
|
required: false
|
|
type: string
|
|
aliases: ["datafile_in"]
|
|
out:
|
|
description: Path to file to store data retrieved from the vault.
|
|
required: false
|
|
type: string
|
|
aliases: ["datafile_out"]
|
|
action:
|
|
description: Work on vault or member level.
|
|
default: vault
|
|
choices: ["vault", "member"]
|
|
state:
|
|
description: State to ensure
|
|
default: present
|
|
choices: ["present", "absent", "retrieved"]
|
|
author:
|
|
- Rafael Jeffman
|
|
"""
|
|
|
|
EXAMPLES = """
|
|
# Ensure vault symvault is present
|
|
- ipavault:
|
|
ipaadmin_password: SomeADMINpassword
|
|
name: symvault
|
|
username: admin
|
|
vault_type: symmetric
|
|
password: SomeVAULTpassword
|
|
salt: MTIzNDU2Nzg5MAo=
|
|
|
|
# Ensure group ipausers is a vault member.
|
|
- ipavault:
|
|
ipaadmin_password: SomeADMINpassword
|
|
name: symvault
|
|
username: admin
|
|
groups: ipausers
|
|
action: member
|
|
|
|
# Ensure group ipausers is not a vault member.
|
|
- ipavault:
|
|
ipaadmin_password: SomeADMINpassword
|
|
name: symvault
|
|
username: admin
|
|
groups: ipausers
|
|
action: member
|
|
state: absent
|
|
|
|
# Ensure vault users are present.
|
|
- ipavault:
|
|
ipaadmin_password: SomeADMINpassword
|
|
name: symvault
|
|
username: admin
|
|
users:
|
|
- user01
|
|
- user02
|
|
action: member
|
|
|
|
# Ensure vault users are absent.
|
|
- ipavault:
|
|
ipaadmin_password: SomeADMINpassword
|
|
name: symvault
|
|
username: admin
|
|
users:
|
|
- user01
|
|
- user02
|
|
action: member
|
|
status: absent
|
|
|
|
# Ensure user owns vault.
|
|
- ipavault:
|
|
ipaadmin_password: SomeADMINpassword
|
|
name: symvault
|
|
username: admin
|
|
action: member
|
|
owners: user01
|
|
|
|
# Ensure user does not own vault.
|
|
- ipavault:
|
|
ipaadmin_password: SomeADMINpassword
|
|
name: symvault
|
|
username: admin
|
|
owners: user01
|
|
action: member
|
|
status: absent
|
|
|
|
# Ensure data is archived to a symmetric vault
|
|
- ipavault:
|
|
ipaadmin_password: SomeADMINpassword
|
|
name: symvault
|
|
username: admin
|
|
password: SomeVAULTpassword
|
|
data: >
|
|
Data archived.
|
|
More data archived.
|
|
action: member
|
|
|
|
# Retrieve data archived from a symmetric vault
|
|
- ipavault:
|
|
ipaadmin_password: SomeADMINpassword
|
|
name: symvault
|
|
username: admin
|
|
password: SomeVAULTpassword
|
|
state: retrieved
|
|
register: result
|
|
- debug:
|
|
msg: "{{ result.vault.data }}"
|
|
|
|
# Change password of a symmetric vault
|
|
- ipavault:
|
|
ipaadmin_password: SomeADMINpassword
|
|
name: symvault
|
|
username: admin
|
|
old_password: SomeVAULTpassword
|
|
new_password: SomeNEWpassword
|
|
|
|
# Ensure vault symvault is absent
|
|
- ipavault:
|
|
ipaadmin_password: SomeADMINpassword
|
|
name: symvault
|
|
user: admin
|
|
state: absent
|
|
|
|
# Ensure asymmetric vault is present.
|
|
- ipavault:
|
|
ipaadmin_password: SomeADMINpassword
|
|
name: asymvault
|
|
username: user01
|
|
description: An asymmetric vault
|
|
vault_type: asymmetric
|
|
public_key: |
|
|
LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTR
|
|
HTkFEQ0JpUUtCZ1FDdGFudjRkK3ptSTZ0T3ova1RXdGowY3AxRAowUENoYy8vR0pJMTUzTi
|
|
9CN3UrN0h3SXlRVlZoNUlXZG1UcCtkWXYzd09yeVpPbzYvbHN5eFJaZ2pZRDRwQ3VGCjlxM
|
|
295VTFEMnFOZERYeGtSaFFETXBiUEVSWWlHbE1jbzdhN0hIVDk1bGNQbmhObVFkb3VGdHlV
|
|
bFBUVS96V1kKZldYWTBOeU1UbUtoeFRseUV3SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVk
|
|
tLS0tLQo=
|
|
|
|
# Ensure data is archived in an asymmetric vault
|
|
- ipavault:
|
|
ipaadmin_password: SomeADMINpassword
|
|
name: asymvault
|
|
username: admin
|
|
data: >
|
|
Data archived.
|
|
More data archived.
|
|
action: member
|
|
|
|
# Retrive data archived in an asymmetric vault, using a private key file.
|
|
- ipavault:
|
|
ipaadmin_password: SomeADMINpassword
|
|
name: asymvault
|
|
username: admin
|
|
private_key_file: private.pem
|
|
state: retrieved
|
|
|
|
# Ensure asymmetric vault is absent.
|
|
- ipavault:
|
|
ipaadmin_password: SomeADMINpassword
|
|
name: asymvault
|
|
username: user01
|
|
vault_type: asymmetric
|
|
state: absent
|
|
"""
|
|
|
|
RETURN = """
|
|
vault:
|
|
description: Vault dict with archived data.
|
|
returned: If state is `retrieved`.
|
|
type: dict
|
|
options:
|
|
data:
|
|
description: The vault data.
|
|
returned: always
|
|
type: string
|
|
"""
|
|
|
|
import os
|
|
from base64 import b64decode
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible.module_utils._text import to_text
|
|
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
|
|
temp_kdestroy, valid_creds, api_connect, api_command, \
|
|
gen_add_del_lists, compare_args_ipa, module_params_get, exit_raw_json
|
|
from ipalib.errors import EmptyModlist, NotFound
|
|
|
|
|
|
def find_vault(module, name, username, service, shared):
|
|
_args = {
|
|
"all": True,
|
|
"cn": name,
|
|
}
|
|
|
|
if username is not None:
|
|
_args['username'] = username
|
|
elif service is not None:
|
|
_args['service'] = service
|
|
else:
|
|
_args['shared'] = shared
|
|
|
|
_result = api_command(module, "vault_find", name, _args)
|
|
|
|
if len(_result["result"]) > 1:
|
|
module.fail_json(
|
|
msg="There is more than one vault '%s'" % (name))
|
|
if len(_result["result"]) == 1:
|
|
return _result["result"][0]
|
|
|
|
return None
|
|
|
|
|
|
def gen_args(description, username, service, shared, vault_type, salt,
|
|
password, password_file, public_key, public_key_file, vault_data,
|
|
datafile_in, datafile_out):
|
|
_args = {}
|
|
vault_type = vault_type or to_text("symmetric")
|
|
|
|
_args['ipavaulttype'] = vault_type
|
|
if description is not None:
|
|
_args['description'] = description
|
|
if username is not None:
|
|
_args['username'] = username
|
|
if service is not None:
|
|
_args['service'] = service
|
|
if shared is not None:
|
|
_args['shared'] = shared
|
|
|
|
if vault_type == "symmetric":
|
|
if salt is not None:
|
|
_args['ipavaultsalt'] = salt
|
|
_args['ipavaultpublickey'] = None
|
|
|
|
elif vault_type == "asymmetric":
|
|
if public_key is not None:
|
|
_args['ipavaultpublickey'] = b64decode(public_key.encode('utf-8'))
|
|
if public_key_file is not None:
|
|
with open(public_key_file, 'r') as keyfile:
|
|
keydata = keyfile.read()
|
|
_args['ipavaultpublickey'] = keydata.strip().encode('utf-8')
|
|
_args['ipavaultsalt'] = None
|
|
|
|
elif vault_type == "standard":
|
|
_args['ipavaultsalt'] = None
|
|
_args['ipavaultpublickey'] = None
|
|
|
|
return _args
|
|
|
|
|
|
def gen_member_args(args, users, groups, services):
|
|
remove = ['ipavaulttype', 'description', 'ipavaultpublickey',
|
|
'ipavaultsalt']
|
|
_args = {k: v for k, v in args.items() if k not in remove}
|
|
|
|
if any([users, groups, services]):
|
|
if users is not None:
|
|
_args['user'] = users
|
|
if groups is not None:
|
|
_args['group'] = groups
|
|
if services is not None:
|
|
_args['services'] = services
|
|
|
|
return _args
|
|
|
|
return None
|
|
|
|
|
|
def data_storage_args(vault_type, args, data, password, password_file,
|
|
private_key, private_key_file, datafile_in,
|
|
datafile_out):
|
|
remove = ['ipavaulttype', 'description', 'ipavaultpublickey',
|
|
'ipavaultsalt']
|
|
_args = {k: v for k, v in args.items() if k not in remove}
|
|
|
|
if 'username' in args:
|
|
_args['username'] = args['username']
|
|
if 'service' in args:
|
|
_args['service'] = args['service']
|
|
if 'shared' in args:
|
|
_args['shared'] = args['shared']
|
|
|
|
if vault_type is None or vault_type == "symmetric":
|
|
if password is not None:
|
|
_args['password'] = password
|
|
if password_file is not None:
|
|
_args['password_file'] = password_file
|
|
|
|
if vault_type == "asymmetric":
|
|
if private_key is not None:
|
|
_args['private_key'] = private_key
|
|
if private_key_file is not None:
|
|
_args['private_key_file'] = private_key_file
|
|
|
|
if datafile_in is not None:
|
|
_args['in'] = datafile_in
|
|
else:
|
|
if data is None:
|
|
_args['data'] = b''
|
|
else:
|
|
_args['data'] = data.encode('utf-8')
|
|
|
|
if datafile_out is not None:
|
|
_args['out'] = datafile_out
|
|
|
|
return _args
|
|
|
|
|
|
def check_parameters(module, state, action, description, username, service,
|
|
shared, users, groups, services, owners, ownergroups,
|
|
ownerservices, vault_type, salt, password, password_file,
|
|
public_key, public_key_file, private_key,
|
|
private_key_file, vault_data, datafile_in, datafile_out,
|
|
new_password, new_password_file):
|
|
invalid = []
|
|
if state == "present":
|
|
invalid = ['datafile_out']
|
|
|
|
if all([password, password_file]) \
|
|
or all([new_password, new_password_file]):
|
|
module.fail_json(msg="Password specified multiple times.")
|
|
|
|
if any([new_password, new_password_file]) \
|
|
and not any([password, password_file]):
|
|
module.fail_json(
|
|
msg="Either `password` or `password_file` must be provided to "
|
|
"change symmetric vault password.")
|
|
|
|
if action == "member":
|
|
invalid.extend(['description', 'vault_type'])
|
|
|
|
elif state == "absent":
|
|
invalid = ['description', 'salt', 'vault_type', 'private_key',
|
|
'private_key_file', 'datafile_in', 'datafile_out',
|
|
'vault_data', 'new_password', 'new_password_file']
|
|
|
|
if action == "vault":
|
|
invalid.extend(['users', 'groups', 'services', 'owners',
|
|
'ownergroups', 'ownerservices', 'password',
|
|
'password_file', 'public_key', 'public_key_file'])
|
|
|
|
elif state == "retrieved":
|
|
invalid = ['description', 'salt', 'datafile_in', 'users', 'groups',
|
|
'owners', 'ownergroups', 'public_key', 'public_key_file',
|
|
'vault_data', 'new_password', 'new_password_file']
|
|
if action == 'member':
|
|
module.fail_json(
|
|
msg="State `retrieved` do not support action `member`.")
|
|
|
|
for arg in invalid:
|
|
if vars()[arg] is not None:
|
|
module.fail_json(
|
|
msg="Argument '%s' can not be used with state '%s', "
|
|
"action '%s'" % (arg, state, action))
|
|
|
|
|
|
def check_encryption_params(module, state, action, vault_type, salt,
|
|
password, password_file, public_key,
|
|
public_key_file, private_key, private_key_file,
|
|
vault_data, datafile_in, datafile_out,
|
|
new_password, new_password_file, res_find):
|
|
vault_type_invalid = []
|
|
|
|
existing_type = None
|
|
if res_find:
|
|
existing_type = res_find["ipavaulttype"][0]
|
|
|
|
if vault_type is None and res_find is not None:
|
|
vault_type = res_find['ipavaulttype']
|
|
if isinstance(vault_type, (tuple, list)):
|
|
vault_type = vault_type[0]
|
|
|
|
if vault_type == "standard":
|
|
vault_type_invalid = ['public_key', 'public_key_file', 'password',
|
|
'password_file', 'salt', 'new_password',
|
|
'new_password_file']
|
|
|
|
if vault_type is None or vault_type == "symmetric":
|
|
vault_type_invalid = ['public_key', 'public_key_file',
|
|
'private_key', 'private_key_file']
|
|
|
|
if password is None and password_file is None and action != 'member':
|
|
module.fail_json(
|
|
msg="Symmetric vault requires password or password_file "
|
|
"to store data or change `salt`.")
|
|
|
|
if any([new_password, new_password_file]) and res_find is None:
|
|
module.fail_json(
|
|
msg="Cannot modify password of inexistent vault.")
|
|
|
|
if (
|
|
salt is not None
|
|
and not(
|
|
any([password, password_file])
|
|
and any([new_password, new_password_file])
|
|
)
|
|
):
|
|
module.fail_json(
|
|
msg="Vault `salt` can only change when changing the password.")
|
|
|
|
if vault_type == "asymmetric":
|
|
vault_type_invalid = [
|
|
'password', 'password_file', 'new_password', 'new_password_file'
|
|
]
|
|
if not any([public_key, public_key_file]) and res_find is None:
|
|
module.fail_json(
|
|
msg="Assymmetric vault requires public_key "
|
|
"or public_key_file to store data.")
|
|
|
|
valid_fields = []
|
|
if existing_type == "symmetric":
|
|
valid_fields = [
|
|
'password', 'password_file', 'new_password', 'new_password_file',
|
|
'salt'
|
|
]
|
|
if existing_type == "asymmetric":
|
|
valid_fields = [
|
|
'public_key', 'public_key_file', 'private_key', 'private_key_file'
|
|
]
|
|
|
|
check_fields = [f for f in vault_type_invalid if f not in valid_fields]
|
|
|
|
for param in check_fields:
|
|
if vars()[param] is not None:
|
|
module.fail_json(
|
|
msg="Argument '%s' cannot be used with vault type '%s'" %
|
|
(param, vault_type or 'symmetric'))
|
|
|
|
|
|
def get_stored_data(module, res_find, args):
|
|
"""Retrieve data stored in the vault."""
|
|
# prepare arguments to retrieve data.
|
|
name = res_find["cn"][0]
|
|
copy_args = []
|
|
if res_find['ipavaulttype'][0] == "symmetric":
|
|
copy_args = ["password", "password_file"]
|
|
if res_find['ipavaulttype'][0] == "asymmetric":
|
|
copy_args = ["private_key", "private_key_file"]
|
|
|
|
pwdargs = {arg: args[arg] for arg in copy_args if arg in args}
|
|
|
|
# retrieve vault stored data
|
|
try:
|
|
result = api_command(module, 'vault_retrieve', name, pwdargs)
|
|
except NotFound:
|
|
return None
|
|
|
|
return result['result'].get('data')
|
|
|
|
|
|
def main():
|
|
ansible_module = AnsibleModule(
|
|
argument_spec=dict(
|
|
# generalgroups
|
|
ipaadmin_principal=dict(type="str", default="admin"),
|
|
ipaadmin_password=dict(type="str", required=False, no_log=True),
|
|
|
|
name=dict(type="list", aliases=["cn"], default=None,
|
|
required=True),
|
|
|
|
description=dict(required=False, type="str", default=None),
|
|
vault_type=dict(type="str", aliases=["ipavaulttype"],
|
|
default=None, required=False,
|
|
choices=["standard", "symmetric", "asymmetric"]),
|
|
vault_public_key=dict(type="str", required=False, default=None,
|
|
aliases=['ipavaultpublickey', 'public_key',
|
|
'new_public_key']),
|
|
vault_public_key_file=dict(type="str", required=False,
|
|
default=None,
|
|
aliases=['public_key_file',
|
|
'new_public_key_file']),
|
|
vault_private_key=dict(
|
|
type="str", required=False, default=None, no_log=True,
|
|
aliases=['ipavaultprivatekey', 'private_key']),
|
|
vault_private_key_file=dict(type="str", required=False,
|
|
default=None,
|
|
aliases=['private_key_file']),
|
|
vault_salt=dict(type="str", required=False, default=None,
|
|
aliases=['ipavaultsalt', 'salt']),
|
|
username=dict(type="str", required=False, default=None,
|
|
aliases=['user']),
|
|
service=dict(type="str", required=False, default=None),
|
|
shared=dict(type="bool", required=False, default=None),
|
|
|
|
users=dict(required=False, type='list', default=None),
|
|
groups=dict(required=False, type='list', default=None),
|
|
services=dict(required=False, type='list', default=None),
|
|
owners=dict(required=False, type='list', default=None,
|
|
aliases=['ownerusers']),
|
|
ownergroups=dict(required=False, type='list', default=None),
|
|
ownerservices=dict(required=False, type='list', default=None),
|
|
vault_data=dict(type="str", required=False, default=None,
|
|
no_log=True, aliases=['ipavaultdata', 'data']),
|
|
datafile_in=dict(type="str", required=False, default=None,
|
|
aliases=['in']),
|
|
datafile_out=dict(type="str", required=False, default=None,
|
|
aliases=['out']),
|
|
vault_password=dict(type="str", required=False, default=None,
|
|
no_log=True,
|
|
aliases=['ipavaultpassword', 'password',
|
|
"old_password"]),
|
|
vault_password_file=dict(type="str", required=False, default=None,
|
|
no_log=False,
|
|
aliases=[
|
|
'password_file', "old_password_file"
|
|
]),
|
|
new_password=dict(type="str", required=False, default=None,
|
|
no_log=True),
|
|
new_password_file=dict(type="str", required=False, default=None,
|
|
no_log=False),
|
|
# state
|
|
action=dict(type="str", default="vault",
|
|
choices=["vault", "data", "member"]),
|
|
state=dict(type="str", default="present",
|
|
choices=["present", "absent", "retrieved"]),
|
|
),
|
|
supports_check_mode=True,
|
|
mutually_exclusive=[['username', 'service', 'shared'],
|
|
['datafile_in', 'vault_data'],
|
|
['new_password', 'new_password_file'],
|
|
['vault_password', 'vault_password_file'],
|
|
['vault_public_key', 'vault_public_key_file']],
|
|
)
|
|
|
|
ansible_module._ansible_debug = True
|
|
|
|
# general
|
|
ipaadmin_principal = module_params_get(ansible_module,
|
|
"ipaadmin_principal")
|
|
ipaadmin_password = module_params_get(ansible_module, "ipaadmin_password")
|
|
names = module_params_get(ansible_module, "name")
|
|
|
|
# present
|
|
description = module_params_get(ansible_module, "description")
|
|
|
|
username = module_params_get(ansible_module, "username")
|
|
service = module_params_get(ansible_module, "service")
|
|
shared = module_params_get(ansible_module, "shared")
|
|
|
|
users = module_params_get(ansible_module, "users")
|
|
groups = module_params_get(ansible_module, "groups")
|
|
services = module_params_get(ansible_module, "services")
|
|
owners = module_params_get(ansible_module, "owners")
|
|
ownergroups = module_params_get(ansible_module, "ownergroups")
|
|
ownerservices = module_params_get(ansible_module, "ownerservices")
|
|
|
|
vault_type = module_params_get(ansible_module, "vault_type")
|
|
salt = module_params_get(ansible_module, "vault_salt")
|
|
password = module_params_get(ansible_module, "vault_password")
|
|
password_file = module_params_get(ansible_module, "vault_password_file")
|
|
new_password = module_params_get(ansible_module, "new_password")
|
|
new_password_file = module_params_get(ansible_module, "new_password_file")
|
|
public_key = module_params_get(ansible_module, "vault_public_key")
|
|
public_key_file = module_params_get(ansible_module,
|
|
"vault_public_key_file")
|
|
private_key = module_params_get(ansible_module, "vault_private_key")
|
|
private_key_file = module_params_get(ansible_module,
|
|
"vault_private_key_file")
|
|
|
|
vault_data = module_params_get(ansible_module, "vault_data")
|
|
|
|
datafile_in = module_params_get(ansible_module, "datafile_in")
|
|
datafile_out = module_params_get(ansible_module, "datafile_out")
|
|
|
|
action = module_params_get(ansible_module, "action")
|
|
state = module_params_get(ansible_module, "state")
|
|
|
|
# Check parameters
|
|
|
|
if state == "present":
|
|
if len(names) != 1:
|
|
ansible_module.fail_json(
|
|
msg="Only one vault can be added at a time.")
|
|
|
|
elif state == "absent":
|
|
if len(names) < 1:
|
|
ansible_module.fail_json(msg="No name given.")
|
|
|
|
elif state == "retrieved":
|
|
if len(names) != 1:
|
|
ansible_module.fail_json(
|
|
msg="Only one vault can be retrieved at a time.")
|
|
|
|
else:
|
|
ansible_module.fail_json(msg="Invalid state '%s'" % state)
|
|
|
|
check_parameters(ansible_module, state, action, description, username,
|
|
service, shared, users, groups, services, owners,
|
|
ownergroups, ownerservices, vault_type, salt, password,
|
|
password_file, public_key, public_key_file, private_key,
|
|
private_key_file, vault_data, datafile_in, datafile_out,
|
|
new_password, new_password_file)
|
|
# Init
|
|
|
|
changed = False
|
|
exit_args = {}
|
|
ccache_dir = None
|
|
ccache_name = None
|
|
try:
|
|
if not valid_creds(ansible_module, ipaadmin_principal):
|
|
ccache_dir, ccache_name = temp_kinit(ipaadmin_principal,
|
|
ipaadmin_password)
|
|
# Need to set krb5 ccache name, due to context='ansible-freeipa'
|
|
if ccache_name is not None:
|
|
os.environ["KRB5CCNAME"] = ccache_name
|
|
|
|
api_connect(context='ansible-freeipa')
|
|
|
|
commands = []
|
|
|
|
for name in names:
|
|
# Make sure vault exists
|
|
res_find = find_vault(
|
|
ansible_module, name, username, service, shared)
|
|
|
|
# Set default vault_type if needed.
|
|
res_type = res_find.get('ipavaulttype')[0] if res_find else None
|
|
if vault_type is None:
|
|
vault_type = res_type if res_find is not None else u"symmetric"
|
|
|
|
# Generate args
|
|
args = gen_args(description, username, service, shared, vault_type,
|
|
salt, password, password_file, public_key,
|
|
public_key_file, vault_data, datafile_in,
|
|
datafile_out)
|
|
pwdargs = None
|
|
|
|
# Create command
|
|
if state == "present":
|
|
# verify data encription args
|
|
check_encryption_params(
|
|
ansible_module, state, action, vault_type, salt, password,
|
|
password_file, public_key, public_key_file, private_key,
|
|
private_key_file, vault_data, datafile_in, datafile_out,
|
|
new_password, new_password_file, res_find)
|
|
|
|
change_passwd = any([
|
|
new_password, new_password_file,
|
|
(private_key or private_key_file) and
|
|
(public_key or public_key_file)
|
|
])
|
|
if action == "vault":
|
|
# Found the vault
|
|
if res_find is not None:
|
|
arg_type = args.get("ipavaulttype")
|
|
|
|
modified = not compare_args_ipa(ansible_module,
|
|
args, res_find)
|
|
|
|
if arg_type != res_type or change_passwd:
|
|
stargs = data_storage_args(
|
|
res_type, args, vault_data, password,
|
|
password_file, private_key,
|
|
private_key_file, datafile_in,
|
|
datafile_out)
|
|
stored = get_stored_data(
|
|
ansible_module, res_find, stargs
|
|
)
|
|
if stored:
|
|
vault_data = \
|
|
(stored or b"").decode("utf-8")
|
|
|
|
remove_attrs = {
|
|
"symmetric": ["private_key", "public_key"],
|
|
"asymmetric": ["password", "ipavaultsalt"],
|
|
"standard": [
|
|
"private_key", "public_key",
|
|
"password", "ipavaultsalt"
|
|
],
|
|
}
|
|
for attr in remove_attrs.get(arg_type, []):
|
|
if attr in args:
|
|
del args[attr]
|
|
|
|
if vault_type == 'symmetric':
|
|
if 'ipavaultsalt' not in args:
|
|
args['ipavaultsalt'] = os.urandom(32)
|
|
else:
|
|
args['ipavaultsalt'] = b''
|
|
|
|
if modified:
|
|
commands.append([name, "vault_mod_internal", args])
|
|
else:
|
|
if vault_type == 'symmetric' \
|
|
and 'ipavaultsalt' not in args:
|
|
args['ipavaultsalt'] = os.urandom(32)
|
|
|
|
commands.append([name, "vault_add_internal", args])
|
|
|
|
if vault_type != 'standard' and vault_data is None:
|
|
vault_data = ''
|
|
|
|
# Set res_find to empty dict for next steps
|
|
res_find = {}
|
|
|
|
# Generate adittion and removal lists
|
|
user_add, user_del = \
|
|
gen_add_del_lists(users,
|
|
res_find.get('member_user', []))
|
|
group_add, group_del = \
|
|
gen_add_del_lists(groups,
|
|
res_find.get('member_group', []))
|
|
service_add, service_del = \
|
|
gen_add_del_lists(services,
|
|
res_find.get('member_service', []))
|
|
|
|
owner_add, owner_del = \
|
|
gen_add_del_lists(owners,
|
|
res_find.get('owner_user', []))
|
|
|
|
ownergroups_add, ownergroups_del = \
|
|
gen_add_del_lists(ownergroups,
|
|
res_find.get('owner_group', []))
|
|
|
|
ownerservice_add, ownerservice_del = \
|
|
gen_add_del_lists(ownerservices,
|
|
res_find.get('owner_service', []))
|
|
|
|
# Add users and groups
|
|
user_add_args = gen_member_args(args, user_add,
|
|
group_add, service_add)
|
|
if user_add_args is not None:
|
|
commands.append(
|
|
[name, 'vault_add_member', user_add_args])
|
|
|
|
# Remove users and groups
|
|
user_del_args = gen_member_args(args, user_del,
|
|
group_del, service_del)
|
|
if user_del_args is not None:
|
|
commands.append(
|
|
[name, 'vault_remove_member', user_del_args])
|
|
|
|
# Add owner users and groups
|
|
owner_add_args = gen_member_args(
|
|
args, owner_add, ownergroups_add, ownerservice_add)
|
|
if owner_add_args is not None:
|
|
commands.append(
|
|
[name, 'vault_add_owner', owner_add_args])
|
|
|
|
# Remove owner users and groups
|
|
owner_del_args = gen_member_args(
|
|
args, owner_del, ownergroups_del, ownerservice_del)
|
|
if owner_del_args is not None:
|
|
commands.append(
|
|
[name, 'vault_remove_owner', owner_del_args])
|
|
|
|
elif action in "member":
|
|
# Add users and groups
|
|
if any([users, groups, services]):
|
|
user_args = gen_member_args(args, users, groups,
|
|
services)
|
|
commands.append([name, 'vault_add_member', user_args])
|
|
if any([owners, ownergroups, ownerservices]):
|
|
owner_args = gen_member_args(args, owners, ownergroups,
|
|
ownerservices)
|
|
commands.append([name, 'vault_add_owner', owner_args])
|
|
|
|
if any([vault_data, datafile_in]):
|
|
if change_passwd:
|
|
pwdargs = data_storage_args(
|
|
vault_type, args, vault_data, new_password,
|
|
new_password_file, private_key, private_key_file,
|
|
datafile_in, datafile_out)
|
|
else:
|
|
pwdargs = data_storage_args(
|
|
vault_type, args, vault_data, password,
|
|
password_file, private_key, private_key_file,
|
|
datafile_in, datafile_out)
|
|
|
|
pwdargs['override_password'] = True
|
|
pwdargs.pop("private_key", None)
|
|
pwdargs.pop("private_key_file", None)
|
|
commands.append([name, "vault_archive", pwdargs])
|
|
|
|
elif state == "retrieved":
|
|
if res_find is None:
|
|
ansible_module.fail_json(
|
|
msg="Vault `%s` not found to retrieve data." % name)
|
|
|
|
# verify data encription args
|
|
check_encryption_params(
|
|
ansible_module, state, action, vault_type, salt, password,
|
|
password_file, public_key, public_key_file, private_key,
|
|
private_key_file, vault_data, datafile_in, datafile_out,
|
|
new_password, new_password_file, res_find)
|
|
|
|
pwdargs = data_storage_args(
|
|
res_find["ipavaulttype"][0], args, vault_data, password,
|
|
password_file, private_key, private_key_file, datafile_in,
|
|
datafile_out)
|
|
if 'data' in pwdargs:
|
|
del pwdargs['data']
|
|
|
|
commands.append([name, "vault_retrieve", pwdargs])
|
|
|
|
elif state == "absent":
|
|
if 'ipavaulttype' in args:
|
|
del args['ipavaulttype']
|
|
|
|
if action == "vault":
|
|
if res_find is not None:
|
|
remove = ['ipavaultsalt', 'ipavaultpublickey']
|
|
args = {
|
|
k: v for k, v in args.items() if k not in remove
|
|
}
|
|
commands.append([name, "vault_del", args])
|
|
|
|
elif action == "member":
|
|
# remove users and groups
|
|
if any([users, groups, services]):
|
|
user_args = gen_member_args(
|
|
args, users, groups, services)
|
|
commands.append(
|
|
[name, 'vault_remove_member', user_args])
|
|
|
|
if any([owners, ownergroups, ownerservices]):
|
|
owner_args = gen_member_args(
|
|
args, owners, ownergroups, ownerservices)
|
|
commands.append(
|
|
[name, 'vault_remove_owner', owner_args])
|
|
else:
|
|
ansible_module.fail_json(
|
|
msg="Invalid action '%s' for state '%s'" %
|
|
(action, state))
|
|
else:
|
|
ansible_module.fail_json(msg="Unknown state '%s'" % state)
|
|
|
|
# Execute commands
|
|
|
|
errors = []
|
|
for name, command, args in commands:
|
|
try:
|
|
result = api_command(ansible_module, command, name, args)
|
|
|
|
if command == 'vault_archive':
|
|
changed = 'Archived data into' in result['summary']
|
|
elif command == 'vault_retrieve':
|
|
if 'result' not in result:
|
|
raise Exception("No result obtained.")
|
|
if "data" in result["result"]:
|
|
data_return = exit_args.setdefault("vault", {})
|
|
data_return["data"] = result["result"]["data"]
|
|
else:
|
|
if not datafile_out:
|
|
raise Exception("No data retrieved.")
|
|
changed = False
|
|
else:
|
|
if "completed" in result:
|
|
if result["completed"] > 0:
|
|
changed = True
|
|
else:
|
|
changed = True
|
|
except EmptyModlist:
|
|
result = {}
|
|
except Exception as exception:
|
|
ansible_module.fail_json(
|
|
msg="%s: %s: %s" % (command, name, str(exception)))
|
|
|
|
# Get all errors
|
|
# All "already a member" and "not a member" failures in the
|
|
# result are ignored. All others are reported.
|
|
if "failed" in result and len(result["failed"]) > 0:
|
|
for item in result["failed"]:
|
|
failed_item = result["failed"][item]
|
|
for member_type in failed_item:
|
|
for member, failure in failed_item[member_type]:
|
|
if "already a member" in failure \
|
|
or "not a member" in failure:
|
|
continue
|
|
errors.append("%s: %s %s: %s" % (
|
|
command, member_type, member, failure))
|
|
if len(errors) > 0:
|
|
ansible_module.fail_json(msg=", ".join(errors))
|
|
|
|
except Exception as exception:
|
|
ansible_module.fail_json(msg=str(exception))
|
|
|
|
finally:
|
|
temp_kdestroy(ccache_dir, ccache_name)
|
|
|
|
# Done
|
|
|
|
# exit_raw_json is a replacement for ansible_module.exit_json that
|
|
# does not mask the output.
|
|
exit_raw_json(ansible_module, changed=changed, **exit_args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|