mirror of
https://github.com/freeipa/ansible-freeipa.git
synced 2026-05-07 22:03:18 +00:00
Merge branch 'master' into master
This commit is contained in:
@@ -28,6 +28,7 @@ import shutil
|
||||
import gssapi
|
||||
from datetime import datetime
|
||||
from ipalib import api
|
||||
from ipalib import errors as ipalib_errors
|
||||
from ipalib.config import Env
|
||||
from ipalib.constants import DEFAULT_CONFIG, LDAP_GENERALIZED_TIME_FORMAT
|
||||
try:
|
||||
@@ -42,6 +43,7 @@ try:
|
||||
from ipalib.x509 import Encoding
|
||||
except ImportError:
|
||||
from cryptography.hazmat.primitives.serialization import Encoding
|
||||
import socket
|
||||
import base64
|
||||
import six
|
||||
|
||||
@@ -151,6 +153,13 @@ def api_command(module, command, name, args):
|
||||
return api.Command[command](name, **args)
|
||||
|
||||
|
||||
def api_command_no_name(module, command, args):
|
||||
"""
|
||||
Call ipa.Command without a name.
|
||||
"""
|
||||
return api.Command[command](**args)
|
||||
|
||||
|
||||
def api_check_param(command, name):
|
||||
"""
|
||||
Return if param exists in command param list
|
||||
@@ -215,10 +224,20 @@ def compare_args_ipa(module, args, ipa):
|
||||
arg = [to_text(_arg) for _arg in arg]
|
||||
if isinstance(ipa_arg[0], unicode) and isinstance(arg[0], int):
|
||||
arg = [to_text(_arg) for _arg in arg]
|
||||
# module.warn("%s <=> %s" % (arg, ipa_arg))
|
||||
if set(arg) != set(ipa_arg):
|
||||
# module.warn("DIFFERENT")
|
||||
return False
|
||||
# module.warn("%s <=> %s" % (repr(arg), repr(ipa_arg)))
|
||||
try:
|
||||
arg_set = set(arg)
|
||||
ipa_arg_set = set(ipa_arg)
|
||||
except TypeError:
|
||||
if arg != ipa_arg:
|
||||
# module.warn("%s != %s" % (repr(arg), repr(ipa_arg)))
|
||||
return False
|
||||
else:
|
||||
if arg_set != ipa_arg_set:
|
||||
# module.warn("%s != %s" % (repr(arg), repr(ipa_arg)))
|
||||
return False
|
||||
|
||||
# module.warn("%s == %s" % (repr(arg), repr(ipa_arg)))
|
||||
|
||||
return True
|
||||
|
||||
@@ -261,10 +280,32 @@ def encode_certificate(cert):
|
||||
Encode a certificate using base64 with also taking FreeIPA and Python
|
||||
versions into account
|
||||
"""
|
||||
if isinstance(cert, str) or isinstance(cert, unicode):
|
||||
if isinstance(cert, (str, unicode, bytes)):
|
||||
encoded = base64.b64encode(cert)
|
||||
else:
|
||||
encoded = base64.b64encode(cert.public_bytes(Encoding.DER))
|
||||
if not six.PY2:
|
||||
encoded = encoded.decode('ascii')
|
||||
return encoded
|
||||
|
||||
|
||||
def is_ipv4_addr(ipaddr):
|
||||
"""
|
||||
Test if figen IP address is a valid IPv4 address
|
||||
"""
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET, ipaddr)
|
||||
except socket.error:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_ipv6_addr(ipaddr):
|
||||
"""
|
||||
Test if figen IP address is a valid IPv6 address
|
||||
"""
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET6, ipaddr)
|
||||
except socket.error:
|
||||
return False
|
||||
return True
|
||||
|
||||
257
plugins/modules/ipadnsconfig.py
Normal file
257
plugins/modules/ipadnsconfig.py
Normal file
@@ -0,0 +1,257 @@
|
||||
#!/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: ipadnsconfig
|
||||
short description: Manage FreeIPA dnsconfig
|
||||
description: Manage FreeIPA dnsconfig
|
||||
options:
|
||||
ipaadmin_principal:
|
||||
description: The admin principal
|
||||
default: admin
|
||||
ipaadmin_password:
|
||||
description: The admin password
|
||||
required: false
|
||||
|
||||
forwarders:
|
||||
description: The list of global DNS forwarders.
|
||||
required: false
|
||||
options:
|
||||
ip_address:
|
||||
description: The forwarder nameserver IP address list (IPv4 and IPv6).
|
||||
required: true
|
||||
port:
|
||||
description: The port to forward requests to.
|
||||
required: false
|
||||
forward_policy:
|
||||
description:
|
||||
Global forwarding policy. Set to "none" to disable any configured
|
||||
global forwarders.
|
||||
required: false
|
||||
choices: ['only', 'first', 'none']
|
||||
allow_sync_ptr:
|
||||
description:
|
||||
Allow synchronization of forward (A, AAAA) and reverse (PTR) records.
|
||||
required: false
|
||||
type: bool
|
||||
state:
|
||||
description: State to ensure
|
||||
default: present
|
||||
choices: ["present", "absent"]
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
# Ensure global DNS forward configuration, allowing PTR record synchronization.
|
||||
- ipadnsconfig:
|
||||
forwarders:
|
||||
- ip_address: 8.8.4.4
|
||||
- ip_address: 2001:4860:4860::8888
|
||||
port: 53
|
||||
forward_policy: only
|
||||
allow_sync_ptr: yes
|
||||
|
||||
# Ensure forwarder is absent.
|
||||
- ipadnsconfig:
|
||||
forwarders:
|
||||
- ip_address: 2001:4860:4860::8888
|
||||
port: 53
|
||||
state: absent
|
||||
|
||||
# Disable PTR record synchronization.
|
||||
- ipadnsconfig:
|
||||
allow_sync_ptr: no
|
||||
|
||||
# Disable global forwarders.
|
||||
- ipadnsconfig:
|
||||
forward_policy: none
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
"""
|
||||
|
||||
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, \
|
||||
api_command_no_name, compare_args_ipa, module_params_get, \
|
||||
gen_add_del_lists, is_ipv4_addr, is_ipv6_addr, ipalib_errors
|
||||
|
||||
|
||||
def find_dnsconfig(module):
|
||||
_args = {
|
||||
"all": True,
|
||||
}
|
||||
|
||||
_result = api_command_no_name(module, "dnsconfig_show", _args)
|
||||
|
||||
if "result" in _result:
|
||||
if _result["result"].get('idnsforwarders', None) is None:
|
||||
_result["result"]['idnsforwarders'] = ['']
|
||||
return _result["result"]
|
||||
else:
|
||||
module.fail("Could not retrieve current DNS configuration.")
|
||||
return None
|
||||
|
||||
|
||||
def gen_args(module, state, dnsconfig, forwarders, forward_policy,
|
||||
allow_sync_ptr):
|
||||
_args = {}
|
||||
|
||||
if forwarders:
|
||||
_forwarders = []
|
||||
for forwarder in forwarders:
|
||||
ip_address = forwarder.get('ip_address')
|
||||
port = forwarder.get('port')
|
||||
if not (is_ipv4_addr(ip_address) or is_ipv6_addr(ip_address)):
|
||||
module.fail(
|
||||
msg="Invalid IP for DNS forwarder: %s" % ip_address)
|
||||
if port is None:
|
||||
_forwarders.append(ip_address)
|
||||
else:
|
||||
_forwarders.append('%s port %d' % (ip_address, port))
|
||||
|
||||
global_forwarders = dnsconfig.get('idnsforwarders', [])
|
||||
if state == 'absent':
|
||||
_args['idnsforwarders'] = [
|
||||
fwd for fwd in global_forwarders if fwd not in _forwarders]
|
||||
# When all forwarders should be excluded, use an empty string ('').
|
||||
if not _args['idnsforwarders']:
|
||||
_args['idnsforwarders'] = ['']
|
||||
|
||||
elif state == 'present':
|
||||
_args['idnsforwarders'] = [
|
||||
fwd for fwd in _forwarders if fwd not in global_forwarders]
|
||||
# If no forwarders should be added, remove argument.
|
||||
if not _args['idnsforwarders']:
|
||||
del _args['idnsforwarders']
|
||||
|
||||
else:
|
||||
# shouldn't happen, but let's be paranoid.
|
||||
module.fail(msg="Invalid state: %s" % state)
|
||||
|
||||
if forward_policy is not None:
|
||||
_args['idnsforwardpolicy'] = forward_policy
|
||||
|
||||
if allow_sync_ptr is not None:
|
||||
_args['idnsallowsyncptr'] = 'TRUE' if allow_sync_ptr else 'FALSE'
|
||||
|
||||
return _args
|
||||
|
||||
|
||||
def main():
|
||||
forwarder_spec = dict(
|
||||
ip_address=dict(type=str, required=True),
|
||||
port=dict(type=int, required=False, default=None)
|
||||
)
|
||||
|
||||
ansible_module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
# general
|
||||
ipaadmin_principal=dict(type='str', default='admin'),
|
||||
ipaadmin_password=dict(type='str', no_log=True),
|
||||
|
||||
# dnsconfig
|
||||
forwarders=dict(type='list', default=None, required=False,
|
||||
options=dict(**forwarder_spec)),
|
||||
forward_policy=dict(type='str', required=False, default=None,
|
||||
choices=['only', 'first', 'none']),
|
||||
allow_sync_ptr=dict(type='bool', required=False, default=None),
|
||||
|
||||
# general
|
||||
state=dict(type="str", default="present",
|
||||
choices=["present", "absent"]),
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
|
||||
# general
|
||||
ipaadmin_principal = module_params_get(ansible_module,
|
||||
"ipaadmin_principal")
|
||||
ipaadmin_password = module_params_get(ansible_module,
|
||||
"ipaadmin_password")
|
||||
|
||||
forwarders = module_params_get(ansible_module, 'forwarders') or []
|
||||
forward_policy = module_params_get(ansible_module, 'forward_policy')
|
||||
allow_sync_ptr = module_params_get(ansible_module, 'allow_sync_ptr')
|
||||
|
||||
state = module_params_get(ansible_module, 'state')
|
||||
|
||||
# Check parameters.
|
||||
invalid = []
|
||||
if state == 'absent':
|
||||
invalid = ['forward_policy', 'allow_sync_ptr']
|
||||
|
||||
for x in invalid:
|
||||
if vars()[x] is not None:
|
||||
ansible_module.fail_json(
|
||||
msg="Argument '%s' can not be used with state '%s'" %
|
||||
(x, state))
|
||||
|
||||
# Init
|
||||
|
||||
changed = False
|
||||
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)
|
||||
api_connect()
|
||||
|
||||
res_find = find_dnsconfig(ansible_module)
|
||||
args = gen_args(ansible_module, state, res_find, forwarders,
|
||||
forward_policy, allow_sync_ptr)
|
||||
|
||||
# Execute command only if configuration changes.
|
||||
if not compare_args_ipa(ansible_module, args, res_find):
|
||||
try:
|
||||
api_command_no_name(ansible_module, 'dnsconfig_mod', args)
|
||||
# If command did not fail, something changed.
|
||||
changed = True
|
||||
|
||||
except Exception as e:
|
||||
msg = str(e)
|
||||
ansible_module.fail_json(msg="dnsconfig_mod: %s" % msg)
|
||||
|
||||
except Exception as e:
|
||||
ansible_module.fail_json(msg=str(e))
|
||||
|
||||
finally:
|
||||
temp_kdestroy(ccache_dir, ccache_name)
|
||||
|
||||
# Done
|
||||
|
||||
ansible_module.exit_json(changed=changed)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -90,23 +90,23 @@ author:
|
||||
EXAMPLES = """
|
||||
# Create group ops with gid 1234
|
||||
- ipagroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: ops
|
||||
gidnumber: 1234
|
||||
|
||||
# Create group sysops
|
||||
- ipagroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: sysops
|
||||
|
||||
# Create group appops
|
||||
- ipagroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: appops
|
||||
|
||||
# Add user member pinky to group sysops
|
||||
- ipagroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: sysops
|
||||
action: member
|
||||
user:
|
||||
@@ -114,7 +114,7 @@ EXAMPLES = """
|
||||
|
||||
# Add user member brain to group sysops
|
||||
- ipagroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: sysops
|
||||
action: member
|
||||
user:
|
||||
@@ -122,7 +122,7 @@ EXAMPLES = """
|
||||
|
||||
# Add group members sysops and appops to group sysops
|
||||
- ipagroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: ops
|
||||
group:
|
||||
- sysops
|
||||
@@ -130,7 +130,7 @@ EXAMPLES = """
|
||||
|
||||
# Remove goups sysops, appops and ops
|
||||
- ipagroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: sysops,appops,ops
|
||||
state: absent
|
||||
"""
|
||||
|
||||
@@ -103,52 +103,52 @@ author:
|
||||
EXAMPLES = """
|
||||
# Ensure HBAC Rule allhosts is present
|
||||
- ipahbacrule:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: allhosts
|
||||
usercategory: all
|
||||
|
||||
# Ensure host server is present in HBAC Rule allhosts
|
||||
- ipahbacrule:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: allhosts
|
||||
host: server
|
||||
action: member
|
||||
|
||||
# Ensure HBAC Rule sshd-pinky is present
|
||||
- ipahbacrule:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: sshd-pinky
|
||||
hostcategory: all
|
||||
|
||||
# Ensure user pinky is present in HBAC Rule sshd-pinky
|
||||
- ipahbacrule:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: sshd-pinky
|
||||
user: pinky
|
||||
action: member
|
||||
|
||||
# Ensure HBAC service sshd is present in HBAC Rule sshd-pinky
|
||||
- ipahbacrule:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: sshd-pinky
|
||||
hbacsvc: sshd
|
||||
action: member
|
||||
|
||||
# Ensure HBAC Rule sshd-pinky is disabled
|
||||
- ipahbacrule:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: sshd-pinky
|
||||
state: disabled
|
||||
|
||||
# Ensure HBAC Rule sshd-pinky is enabled
|
||||
- ipahbacrule:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: sshd-pinky
|
||||
state: enabled
|
||||
|
||||
# Ensure HBAC Rule sshd-pinky is absent
|
||||
- ipahbacrule:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: sshd-pinky
|
||||
state: absent
|
||||
"""
|
||||
@@ -344,41 +344,41 @@ def main():
|
||||
# Generate addition and removal lists
|
||||
host_add = list(
|
||||
set(host or []) -
|
||||
set(res_find.get("member_host", [])))
|
||||
set(res_find.get("memberhost_host", [])))
|
||||
host_del = list(
|
||||
set(res_find.get("member_host", [])) -
|
||||
set(res_find.get("memberhost_host", [])) -
|
||||
set(host or []))
|
||||
hostgroup_add = list(
|
||||
set(hostgroup or []) -
|
||||
set(res_find.get("member_hostgroup", [])))
|
||||
set(res_find.get("memberhost_hostgroup", [])))
|
||||
hostgroup_del = list(
|
||||
set(res_find.get("member_hostgroup", [])) -
|
||||
set(res_find.get("memberhost_hostgroup", [])) -
|
||||
set(hostgroup or []))
|
||||
|
||||
hbacsvc_add = list(
|
||||
set(hbacsvc or []) -
|
||||
set(res_find.get("member_hbacsvc", [])))
|
||||
set(res_find.get("memberservice_hbacsvc", [])))
|
||||
hbacsvc_del = list(
|
||||
set(res_find.get("member_hbacsvc", [])) -
|
||||
set(res_find.get("memberservice_hbacsvc", [])) -
|
||||
set(hbacsvc or []))
|
||||
hbacsvcgroup_add = list(
|
||||
set(hbacsvcgroup or []) -
|
||||
set(res_find.get("member_hbacsvcgroup", [])))
|
||||
set(res_find.get("memberservice_hbacsvcgroup", [])))
|
||||
hbacsvcgroup_del = list(
|
||||
set(res_find.get("member_hbacsvcgroup", [])) -
|
||||
set(res_find.get("memberservice_hbacsvcgroup", [])) -
|
||||
set(hbacsvcgroup or []))
|
||||
|
||||
user_add = list(
|
||||
set(user or []) -
|
||||
set(res_find.get("member_user", [])))
|
||||
set(res_find.get("memberuser_user", [])))
|
||||
user_del = list(
|
||||
set(res_find.get("member_user", [])) -
|
||||
set(res_find.get("memberuser_user", [])) -
|
||||
set(user or []))
|
||||
group_add = list(
|
||||
set(group or []) -
|
||||
set(res_find.get("member_group", [])))
|
||||
set(res_find.get("memberuser_group", [])))
|
||||
group_del = list(
|
||||
set(res_find.get("member_group", [])) -
|
||||
set(res_find.get("memberuser_group", [])) -
|
||||
set(group or []))
|
||||
|
||||
# Add hosts and hostgroups
|
||||
|
||||
@@ -56,13 +56,13 @@ author:
|
||||
EXAMPLES = """
|
||||
# Ensure HBAC Service for http is present
|
||||
- ipahbacsvc:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: http
|
||||
description: Web service
|
||||
|
||||
# Ensure HBAC Service for tftp is absent
|
||||
- ipahbacsvc:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: tftp
|
||||
state: absent
|
||||
"""
|
||||
|
||||
@@ -69,14 +69,14 @@ author:
|
||||
EXAMPLES = """
|
||||
# Ensure hbacsvcgroup login is present
|
||||
- ipahbacsvcgroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: login
|
||||
hbacsvc:
|
||||
- sshd
|
||||
|
||||
# Ensure hbacsvc sshd is present in existing login hbacsvcgroup
|
||||
- ipahbacsvcgroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: databases
|
||||
hbacsvc:
|
||||
- sshd
|
||||
@@ -84,7 +84,7 @@ EXAMPLES = """
|
||||
|
||||
# Ensure hbacsvc sshd is abdsent in existing login hbacsvcgroup
|
||||
- ipahbacsvcgroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: databases
|
||||
hbacsvc:
|
||||
- sshd
|
||||
@@ -93,7 +93,7 @@ EXAMPLES = """
|
||||
|
||||
# Ensure hbacsvcgroup login is absent
|
||||
- ipahbacsvcgroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: login
|
||||
state: absent
|
||||
"""
|
||||
|
||||
@@ -147,9 +147,10 @@ options:
|
||||
Defines a whitelist for Authentication Indicators. Use 'otp' to allow
|
||||
OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA
|
||||
authentications. Other values may be used for custom configurations.
|
||||
Use empty string to reset auth_ind to the initial value.
|
||||
type: list
|
||||
aliases: ["krbprincipalauthind"]
|
||||
choices: ["radius", "otp", "pkinit", "hardened"]
|
||||
choices: ["radius", "otp", "pkinit", "hardened", ""]
|
||||
required: false
|
||||
requires_pre_auth:
|
||||
description: Pre-authentication is required for the service
|
||||
@@ -175,11 +176,16 @@ options:
|
||||
default: true
|
||||
required: false
|
||||
ip_address:
|
||||
description: The host IP address
|
||||
description:
|
||||
The host IP address list (IPv4 and IPv6). No IP address conflict
|
||||
check will be done.
|
||||
aliases: ["ipaddress"]
|
||||
required: false
|
||||
update_dns:
|
||||
description: Update DNS entries
|
||||
description:
|
||||
Controls the update of the DNS SSHFP records for existing hosts and
|
||||
the removal of all DNS entries if a host gets removed with state
|
||||
absent.
|
||||
required: false
|
||||
description:
|
||||
description: The host description
|
||||
@@ -277,9 +283,10 @@ options:
|
||||
Defines a whitelist for Authentication Indicators. Use 'otp' to allow
|
||||
OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA
|
||||
authentications. Other values may be used for custom configurations.
|
||||
Use empty string to reset auth_ind to the initial value.
|
||||
type: list
|
||||
aliases: ["krbprincipalauthind"]
|
||||
choices: ["radius", "otp", "pkinit", "hardened"]
|
||||
choices: ["radius", "otp", "pkinit", "hardened", ""]
|
||||
required: false
|
||||
requires_pre_auth:
|
||||
description: Pre-authentication is required for the service
|
||||
@@ -304,11 +311,16 @@ options:
|
||||
default: true
|
||||
required: false
|
||||
ip_address:
|
||||
description: The host IP address
|
||||
description:
|
||||
The host IP address list (IPv4 and IPv6). No IP address conflict
|
||||
check will be done.
|
||||
aliases: ["ipaddress"]
|
||||
required: false
|
||||
update_dns:
|
||||
description: Update DNS entries
|
||||
description:
|
||||
Controls the update of the DNS SSHFP records for existing hosts and
|
||||
the removal of all DNS entries if a host gets removed with state
|
||||
absent.
|
||||
required: false
|
||||
update_password:
|
||||
description:
|
||||
@@ -331,7 +343,7 @@ author:
|
||||
EXAMPLES = """
|
||||
# Ensure host is present
|
||||
- ipahost:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: host01.example.com
|
||||
description: Example host
|
||||
ip_address: 192.168.0.123
|
||||
@@ -346,14 +358,14 @@ EXAMPLES = """
|
||||
|
||||
# Ensure host is present without DNS
|
||||
- ipahost:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: host02.example.com
|
||||
description: Example host
|
||||
force: yes
|
||||
|
||||
# Initiate generation of a random password for the host
|
||||
- ipahost:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: host01.example.com
|
||||
description: Example host
|
||||
ip_address: 192.168.0.123
|
||||
@@ -361,7 +373,7 @@ EXAMPLES = """
|
||||
|
||||
# Ensure host is disabled
|
||||
- ipahost:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: host01.example.com
|
||||
update_dns: yes
|
||||
state: disabled
|
||||
@@ -396,7 +408,8 @@ 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, compare_args_ipa, \
|
||||
module_params_get, gen_add_del_lists, encode_certificate, api_get_realm
|
||||
module_params_get, gen_add_del_lists, encode_certificate, api_get_realm, \
|
||||
is_ipv4_addr, is_ipv6_addr, ipalib_errors
|
||||
import six
|
||||
|
||||
|
||||
@@ -426,6 +439,32 @@ def find_host(module, name):
|
||||
return None
|
||||
|
||||
|
||||
def find_dnsrecord(module, name):
|
||||
domain_name = name[name.find(".")+1:]
|
||||
host_name = name[:name.find(".")]
|
||||
|
||||
_args = {
|
||||
"all": True,
|
||||
"idnsname": to_text(host_name),
|
||||
}
|
||||
|
||||
_result = api_command(module, "dnsrecord_find", to_text(domain_name),
|
||||
_args)
|
||||
|
||||
if len(_result["result"]) > 1:
|
||||
module.fail_json(
|
||||
msg="There is more than one host '%s'" % (name))
|
||||
elif len(_result["result"]) == 1:
|
||||
_res = _result["result"][0]
|
||||
certs = _res.get("usercertificate")
|
||||
if certs is not None:
|
||||
_res["usercertificate"] = [encode_certificate(cert) for
|
||||
cert in certs]
|
||||
return _res
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def show_host(module, name):
|
||||
_result = api_command(module, "host_show", to_text(name), {})
|
||||
return _result["result"]
|
||||
@@ -468,16 +507,34 @@ def gen_args(description, locality, location, platform, os, password, random,
|
||||
_args["ipakrboktoauthasdelegate"] = ok_to_auth_as_delegate
|
||||
if force is not None:
|
||||
_args["force"] = force
|
||||
if reverse is not None:
|
||||
_args["no_reverse"] = not reverse
|
||||
if ip_address is not None:
|
||||
_args["ip_address"] = ip_address
|
||||
# IP addresses are handed extra, therefore it is needed to set
|
||||
# the force option here to make sure that host-add is able to
|
||||
# add a host without IP address.
|
||||
_args["force"] = True
|
||||
if update_dns is not None:
|
||||
_args["updatedns"] = update_dns
|
||||
|
||||
return _args
|
||||
|
||||
|
||||
def gen_dnsrecord_args(module, ip_address, reverse):
|
||||
_args = {}
|
||||
if reverse is not None:
|
||||
_args["a_extra_create_reverse"] = reverse
|
||||
_args["aaaa_extra_create_reverse"] = reverse
|
||||
if ip_address is not None:
|
||||
for ip in ip_address:
|
||||
if is_ipv4_addr(ip):
|
||||
_args.setdefault("arecord", []).append(ip)
|
||||
elif is_ipv6_addr(ip):
|
||||
_args.setdefault("aaaarecord", []).append(ip)
|
||||
else:
|
||||
module.fail_json(msg="'%s' is not a valid IP address." % ip)
|
||||
|
||||
return _args
|
||||
|
||||
|
||||
def check_parameters(
|
||||
module, state, action,
|
||||
description, locality, location, platform, os, password, random,
|
||||
@@ -497,8 +554,7 @@ def check_parameters(
|
||||
"os", "password", "random", "mac_address", "sshpubkey",
|
||||
"userclass", "auth_ind", "requires_pre_auth",
|
||||
"ok_as_delegate", "ok_to_auth_as_delegate", "force",
|
||||
"reverse", "ip_address", "update_dns",
|
||||
"update_password"]
|
||||
"reverse", "update_dns", "update_password"]
|
||||
for x in invalid:
|
||||
if vars()[x] is not None:
|
||||
module.fail_json(
|
||||
@@ -510,20 +566,26 @@ def check_parameters(
|
||||
"password", "random", "mac_address", "sshpubkey",
|
||||
"userclass", "auth_ind", "requires_pre_auth",
|
||||
"ok_as_delegate", "ok_to_auth_as_delegate", "force",
|
||||
"reverse", "ip_address", "update_password"]
|
||||
if action == "host":
|
||||
invalid.extend([
|
||||
"certificate", "managedby_host", "principal",
|
||||
"allow_create_keytab_user", "allow_create_keytab_group",
|
||||
"allow_create_keytab_host", "allow_create_keytab_hostgroup",
|
||||
"allow_retrieve_keytab_user", "allow_retrieve_keytab_group",
|
||||
"allow_retrieve_keytab_host",
|
||||
"allow_retrieve_keytab_hostgroup"])
|
||||
"reverse", "update_password"]
|
||||
for x in invalid:
|
||||
if vars()[x] is not None:
|
||||
module.fail_json(
|
||||
msg="Argument '%s' can not be used with state '%s'" %
|
||||
(x, state))
|
||||
if action == "host":
|
||||
invalid = [
|
||||
"certificate", "managedby_host", "principal",
|
||||
"allow_create_keytab_user", "allow_create_keytab_group",
|
||||
"allow_create_keytab_host", "allow_create_keytab_hostgroup",
|
||||
"allow_retrieve_keytab_user", "allow_retrieve_keytab_group",
|
||||
"allow_retrieve_keytab_host",
|
||||
"allow_retrieve_keytab_hostgroup"
|
||||
]
|
||||
for x in invalid:
|
||||
if vars()[x] is not None:
|
||||
module.fail_json(
|
||||
msg="Argument '%s' can only be used with action "
|
||||
"'member' for state '%s'" % (x, state))
|
||||
|
||||
|
||||
def main():
|
||||
@@ -541,9 +603,6 @@ def main():
|
||||
default=None, no_log=True),
|
||||
random=dict(type="bool", aliases=["random_password"],
|
||||
default=None),
|
||||
|
||||
|
||||
|
||||
certificate=dict(type="list", aliases=["usercertificate"],
|
||||
default=None),
|
||||
managedby_host=dict(type="list",
|
||||
@@ -590,7 +649,7 @@ def main():
|
||||
default=None),
|
||||
auth_ind=dict(type='list', aliases=["krbprincipalauthind"],
|
||||
default=None,
|
||||
choices=['password', 'radius', 'otp']),
|
||||
choices=['radius', 'otp', 'pkinit', 'hardened', '']),
|
||||
requires_pre_auth=dict(type="bool", aliases=["ipakrbrequirespreauth"],
|
||||
default=None),
|
||||
ok_as_delegate=dict(type="bool", aliases=["ipakrbokasdelegate"],
|
||||
@@ -600,7 +659,7 @@ def main():
|
||||
default=None),
|
||||
force=dict(type='bool', default=None),
|
||||
reverse=dict(type='bool', default=None),
|
||||
ip_address=dict(type="str", aliases=["ipaddress"],
|
||||
ip_address=dict(type="list", aliases=["ipaddress"],
|
||||
default=None),
|
||||
update_dns=dict(type="bool", aliases=["updatedns"],
|
||||
default=None),
|
||||
@@ -812,6 +871,20 @@ def main():
|
||||
|
||||
# Make sure host exists
|
||||
res_find = find_host(ansible_module, name)
|
||||
try:
|
||||
res_find_dnsrecord = find_dnsrecord(ansible_module, name)
|
||||
except ipalib_errors.NotFound as e:
|
||||
msg = str(e)
|
||||
if ip_address is None and \
|
||||
("DNS is not configured" in msg or \
|
||||
"DNS zone not found" in msg):
|
||||
# IP address(es) not given and no DNS support in IPA
|
||||
# -> Ignore failure
|
||||
# IP address(es) not given and DNS zone is not found
|
||||
# -> Ignore failure
|
||||
res_find_dnsrecord = None
|
||||
else:
|
||||
ansible_module.fail_json(msg="%s: %s" % (host, msg))
|
||||
|
||||
# Create command
|
||||
if state == "present":
|
||||
@@ -821,6 +894,8 @@ def main():
|
||||
random, mac_address, sshpubkey, userclass, auth_ind,
|
||||
requires_pre_auth, ok_as_delegate, ok_to_auth_as_delegate,
|
||||
force, reverse, ip_address, update_dns)
|
||||
dnsrecord_args = gen_dnsrecord_args(
|
||||
ansible_module, ip_address, reverse)
|
||||
|
||||
if action == "host":
|
||||
# Found the host
|
||||
@@ -835,6 +910,13 @@ def main():
|
||||
if x in args:
|
||||
del args[x]
|
||||
|
||||
# Ignore auth_ind if it is empty (for resetting)
|
||||
# and not set in for the host
|
||||
if "krbprincipalauthind" not in res_find and \
|
||||
"krbprincipalauthind" in args and \
|
||||
args["krbprincipalauthind"] == ['']:
|
||||
del args["krbprincipalauthind"]
|
||||
|
||||
# For all settings is args, check if there are
|
||||
# different settings in the find result.
|
||||
# If yes: modify
|
||||
@@ -923,39 +1005,25 @@ def main():
|
||||
res_find.get(
|
||||
"ipaallowedtoperform_read_keys_hostgroup"))
|
||||
|
||||
else:
|
||||
certificate_add = certificate or []
|
||||
certificate_del = []
|
||||
managedby_host_add = managedby_host or []
|
||||
managedby_host_del = []
|
||||
principal_add = principal or []
|
||||
principal_del = []
|
||||
allow_create_keytab_user_add = \
|
||||
allow_create_keytab_user or []
|
||||
allow_create_keytab_user_del = []
|
||||
allow_create_keytab_group_add = \
|
||||
allow_create_keytab_group or []
|
||||
allow_create_keytab_group_del = []
|
||||
allow_create_keytab_host_add = \
|
||||
allow_create_keytab_host or []
|
||||
allow_create_keytab_host_del = []
|
||||
allow_create_keytab_hostgroup_add = \
|
||||
allow_create_keytab_hostgroup or []
|
||||
allow_create_keytab_hostgroup_del = []
|
||||
allow_retrieve_keytab_user_add = \
|
||||
allow_retrieve_keytab_user or []
|
||||
allow_retrieve_keytab_user_del = []
|
||||
allow_retrieve_keytab_group_add = \
|
||||
allow_retrieve_keytab_group or []
|
||||
allow_retrieve_keytab_group_del = []
|
||||
allow_retrieve_keytab_host_add = \
|
||||
allow_retrieve_keytab_host or []
|
||||
allow_retrieve_keytab_host_del = []
|
||||
allow_retrieve_keytab_hostgroup_add = \
|
||||
allow_retrieve_keytab_hostgroup or []
|
||||
allow_retrieve_keytab_hostgroup_del = []
|
||||
# IP addresses are not really a member of hosts, but
|
||||
# we will simply treat it as this to enable the
|
||||
# addition and removal of IPv4 and IPv6 addresses in
|
||||
# a simple way.
|
||||
_dnsrec = res_find_dnsrecord or {}
|
||||
dnsrecord_a_add, dnsrecord_a_del = gen_add_del_lists(
|
||||
dnsrecord_args.get("arecord"),
|
||||
_dnsrec.get("arecord"))
|
||||
dnsrecord_aaaa_add, dnsrecord_aaaa_del = \
|
||||
gen_add_del_lists(
|
||||
dnsrecord_args.get("aaaarecord"),
|
||||
_dnsrec.get("aaaarecord"))
|
||||
|
||||
else:
|
||||
if res_find is None:
|
||||
ansible_module.fail_json(
|
||||
msg="No host '%s'" % name)
|
||||
|
||||
if action != "host" or (action == "host" and res_find is None):
|
||||
certificate_add = certificate or []
|
||||
certificate_del = []
|
||||
managedby_host_add = managedby_host or []
|
||||
@@ -986,6 +1054,10 @@ def main():
|
||||
allow_retrieve_keytab_hostgroup_add = \
|
||||
allow_retrieve_keytab_hostgroup or []
|
||||
allow_retrieve_keytab_hostgroup_del = []
|
||||
dnsrecord_a_add = dnsrecord_args.get("arecord") or []
|
||||
dnsrecord_a_del = []
|
||||
dnsrecord_aaaa_add = dnsrecord_args.get("aaaarecord") or []
|
||||
dnsrecord_aaaa_del = []
|
||||
|
||||
# Remove canonical principal from principal_del
|
||||
canonical_principal = "host/" + name + "@" + server_realm
|
||||
@@ -1120,6 +1192,39 @@ def main():
|
||||
"hostgroup": allow_retrieve_keytab_hostgroup_del,
|
||||
}])
|
||||
|
||||
if len(dnsrecord_a_add) > 0 or len(dnsrecord_aaaa_add) > 0:
|
||||
domain_name = name[name.find(".")+1:]
|
||||
host_name = name[:name.find(".")]
|
||||
|
||||
_args = {"idnsname": host_name}
|
||||
if len(dnsrecord_a_add) > 0:
|
||||
_args["arecord"] = dnsrecord_a_add
|
||||
if reverse is not None:
|
||||
_args["a_extra_create_reverse"] = reverse
|
||||
if len(dnsrecord_aaaa_add) > 0:
|
||||
_args["aaaarecord"] = dnsrecord_aaaa_add
|
||||
if reverse is not None:
|
||||
_args["aaaa_extra_create_reverse"] = reverse
|
||||
|
||||
commands.append([domain_name,
|
||||
"dnsrecord_add", _args])
|
||||
|
||||
if len(dnsrecord_a_del) > 0 or len(dnsrecord_aaaa_del) > 0:
|
||||
domain_name = name[name.find(".")+1:]
|
||||
host_name = name[:name.find(".")]
|
||||
|
||||
# There seems to be an issue with dnsrecord_del (not
|
||||
# for dnsrecord_add) if aaaarecord is an empty list.
|
||||
# Therefore this is done differently here:
|
||||
_args = {"idnsname": host_name}
|
||||
if len(dnsrecord_a_del) > 0:
|
||||
_args["arecord"] = dnsrecord_a_del
|
||||
if len(dnsrecord_aaaa_del) > 0:
|
||||
_args["aaaarecord"] = dnsrecord_aaaa_del
|
||||
|
||||
commands.append([domain_name,
|
||||
"dnsrecord_del", _args])
|
||||
|
||||
elif state == "absent":
|
||||
if action == "host":
|
||||
|
||||
@@ -1200,6 +1305,17 @@ def main():
|
||||
"hostgroup": allow_retrieve_keytab_hostgroup,
|
||||
}])
|
||||
|
||||
dnsrecord_args = gen_dnsrecord_args(ansible_module,
|
||||
ip_address, reverse)
|
||||
if "arecord" in dnsrecord_args or \
|
||||
"aaaarecord" in dnsrecord_args:
|
||||
domain_name = name[name.find(".")+1:]
|
||||
host_name = name[:name.find(".")]
|
||||
dnsrecord_args["idnsname"] = host_name
|
||||
|
||||
commands.append([domain_name, "dnsrecord_del",
|
||||
dnsrecord_args])
|
||||
|
||||
elif state == "disabled":
|
||||
if res_find is not None:
|
||||
commands.append([name, "host_disable", {}])
|
||||
@@ -1244,6 +1360,11 @@ def main():
|
||||
# Host is already disabled, ignore error
|
||||
if "This entry is already disabled" in msg:
|
||||
continue
|
||||
|
||||
# Ignore no modification error.
|
||||
if "no modifications to be performed" in msg:
|
||||
continue
|
||||
|
||||
ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
|
||||
msg))
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ author:
|
||||
EXAMPLES = """
|
||||
# Ensure host-group databases is present
|
||||
- ipahostgroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: databases
|
||||
host:
|
||||
- db.example.com
|
||||
@@ -83,7 +83,7 @@ EXAMPLES = """
|
||||
|
||||
# Ensure hosts and hostgroups are present in existing databases hostgroup
|
||||
- ipahostgroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: databases
|
||||
host:
|
||||
- db.example.com
|
||||
@@ -94,7 +94,7 @@ EXAMPLES = """
|
||||
|
||||
# Ensure hosts and hostgroups are absent in databases hostgroup
|
||||
- ipahostgroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: databases
|
||||
host:
|
||||
- db.example.com
|
||||
@@ -106,7 +106,7 @@ EXAMPLES = """
|
||||
|
||||
# Ensure host-group databases is absent
|
||||
- ipahostgroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: databases
|
||||
state: absent
|
||||
"""
|
||||
|
||||
@@ -98,7 +98,7 @@ author:
|
||||
EXAMPLES = """
|
||||
# Ensure pwpolicy is set for ops
|
||||
- ipapwpolicy:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: ops
|
||||
minlife: 7
|
||||
maxlife: 49
|
||||
@@ -167,7 +167,7 @@ def main():
|
||||
ipaadmin_password=dict(type="str", required=False, no_log=True),
|
||||
|
||||
name=dict(type="list", aliases=["cn"], default=None,
|
||||
required=True),
|
||||
required=False),
|
||||
# present
|
||||
|
||||
maxlife=dict(type="int", aliases=["krbmaxpwdlife"], default=None),
|
||||
@@ -218,6 +218,9 @@ def main():
|
||||
|
||||
# Check parameters
|
||||
|
||||
if names is None:
|
||||
names = ["global_policy"]
|
||||
|
||||
if state == "present":
|
||||
if len(names) != 1:
|
||||
ansible_module.fail_json(
|
||||
@@ -225,8 +228,10 @@ def main():
|
||||
|
||||
if state == "absent":
|
||||
if len(names) < 1:
|
||||
ansible_module.fail_json(msg="No name given.")
|
||||
if "global_policy" in names:
|
||||
ansible_module.fail_json(
|
||||
msg="No name given.")
|
||||
msg="'global_policy' can not be made absent.")
|
||||
invalid = ["maxlife", "minlife", "history", "minclasses",
|
||||
"minlength", "priority", "maxfail", "failinterval",
|
||||
"lockouttime"]
|
||||
|
||||
811
plugins/modules/ipaservice.py
Normal file
811
plugins/modules/ipaservice.py
Normal file
@@ -0,0 +1,811 @@
|
||||
#!/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: ipaservice
|
||||
short description: Manage FreeIPA service
|
||||
description: Manage FreeIPA service
|
||||
options:
|
||||
ipaadmin_principal:
|
||||
description: The admin principal
|
||||
default: admin
|
||||
ipaadmin_password:
|
||||
description: The admin password
|
||||
required: false
|
||||
name:
|
||||
description: The service to manage
|
||||
required: true
|
||||
aliases: ["service"]
|
||||
certificate:
|
||||
description: Base-64 encoded service certificate.
|
||||
required: false
|
||||
type: list
|
||||
aliases=['usercertificate']
|
||||
pac_type:
|
||||
description: Supported PAC type.
|
||||
required: false
|
||||
choices: ["MS-PAC", "PAD", "NONE"]
|
||||
type: list
|
||||
aliases: ["pac_type", "ipakrbauthzdata"]
|
||||
auth_ind:
|
||||
description: Defines a whitelist for Authentication Indicators.
|
||||
required: false
|
||||
choices: ["otp", "radius", "pkinit", "hardened"]
|
||||
aliases: ["krbprincipalauthind"]
|
||||
skip_host_check:
|
||||
description: Skip checking if host object exists.
|
||||
required: False
|
||||
type: bool
|
||||
force:
|
||||
description: Force principal name even if host is not in DNS.
|
||||
required: False
|
||||
type: bool
|
||||
requires_pre_auth:
|
||||
description: Pre-authentication is required for the service.
|
||||
required: false
|
||||
type: bool
|
||||
default: False
|
||||
aliases: ["ipakrbrequirespreauth"]
|
||||
ok_as_delegate:
|
||||
description: Client credentials may be delegated to the service.
|
||||
required: false
|
||||
type: bool
|
||||
default: False
|
||||
aliases: ["ipakrbokasdelegate"]
|
||||
ok_to_auth_as_delegate: Allow service to authenticate on behalf of a client.
|
||||
description: .
|
||||
required: false
|
||||
type: bool
|
||||
default: False
|
||||
aliases:["ipakrboktoauthasdelegate"]
|
||||
principal:
|
||||
description: List of principal aliases for the service.
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["krbprincipalname"]
|
||||
host:
|
||||
description: Host that can manage the service.
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["managedby_host"]
|
||||
allow_create_keytab_user:
|
||||
descrption: Users allowed to create a keytab of this host.
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["ipaallowedtoperform_write_keys_user"]
|
||||
allow_create_keytab_group:
|
||||
descrption: Groups allowed to create a keytab of this host.
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["ipaallowedtoperform_write_keys_group"]
|
||||
allow_create_keytab_host:
|
||||
descrption: Hosts allowed to create a keytab of this host.
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["ipaallowedtoperform_write_keys_host"]
|
||||
allow_create_keytab_hostgroup:
|
||||
descrption: Host group allowed to create a keytab of this host.
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["ipaallowedtoperform_write_keys_hostgroup"]
|
||||
allow_retrieve_keytab_user:
|
||||
descrption: User allowed to retrieve a keytab of this host.
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["ipaallowedtoperform_read_keys_user"]
|
||||
allow_retrieve_keytab_group:
|
||||
descrption: Groups allowed to retrieve a keytab of this host.
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["ipaallowedtoperform_read_keys_group"]
|
||||
allow_retrieve_keytab_host:
|
||||
descrption: Hosts allowed to retrieve a keytab of this host.
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["ipaallowedtoperform_read_keys_host"]
|
||||
allow_retrieve_keytab_hostgroup:
|
||||
descrption: Host groups allowed to retrieve a keytab of this host.
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["ipaallowedtoperform_read_keys_hostgroup"]
|
||||
action:
|
||||
description: Work on service or member level
|
||||
default: service
|
||||
choices: ["member", "service"]
|
||||
state:
|
||||
description: State to ensure
|
||||
default: present
|
||||
choices: ["present", "absent", "enabled", "disabled"]
|
||||
author:
|
||||
- Rafael Jeffman
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
# Ensure service is present
|
||||
- ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: HTTP/www.example.com
|
||||
pac_type:
|
||||
- MS-PAC
|
||||
- PAD
|
||||
auth_ind: otp
|
||||
skip_host_check: true
|
||||
force: false
|
||||
requires_pre_auth: true
|
||||
ok_as_delegate: false
|
||||
ok_to_auth_as_delegate: false
|
||||
|
||||
# Ensure service is absent
|
||||
- ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: HTTP/www.example.com
|
||||
state: absent
|
||||
|
||||
# Ensure service member certificate is present.
|
||||
- ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: HTTP/www.example.com
|
||||
certificate:
|
||||
- MIIC/zCCAeegAwIBAgIUMNHIbn+hhrOVew/2WbkteisV29QwDQYJKoZIhvcNAQELBQAw
|
||||
DzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQxMDhaFw0zMDAyMDExNDQxMDhaMA8xDT
|
||||
ALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+XVVGFYpH
|
||||
VkcDfVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk+OfEkzLUAhWBOwEraELJzM
|
||||
LJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa8KvgRVRyGLoVvGbLJvmjfMXp0nIT
|
||||
oTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0amnvsgparYXhypHaRLnikn0vQINt55YoEd1s
|
||||
4KrvEcD2VdZkIMPbLRu2zFvMprF3cjQQG4LT9ggfEXNIPZ1nQWAnAsu7OJEkNF+E4Mkmpc
|
||||
xj9aGUVt5bsq1D+Tzj3GsidSX0nSNcZ2JltXRnL/5v63g5cZyE+nAgMBAAGjUzBRMB0GA1
|
||||
UdDgQWBBRV0j7JYukuH/r/t9+QeNlRLXDlEDAfBgNVHSMEGDAWgBRV0j7JYukuH/r/t9+Q
|
||||
eNlRLXDlEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgVy1+1kNwHs
|
||||
5y1Zp0WjMWGCJC6/zw7FDG4OW5r2GJiCXZYdJ0UonY9ZtoVLJPrp2/DAv1m5DtnDhBYqic
|
||||
uPgLzEkOS1KdTi20Otm/J4yxLLrZC5W4x0XOeSVPXOJuQWfwQ5pPvKkn6WxYUYkGwIt1OH
|
||||
2nSMngkbami3CbSmKZOCpgQIiSlQeDJ8oGjWFMLDymYSHoVOIXHwNoooyEiaio3693l6no
|
||||
obyGv49zyCVLVR1DC7i6RJ186ql0av+D4vPoiF5mX7+sKC2E8xEj9uKQ5GTWRh59VnRBVC
|
||||
/SiMJ/H78tJnBAvoBwXxSEvj8Z3Kjm/BQqZfv4IBsA5yqV7MVq
|
||||
action: member
|
||||
state: present
|
||||
|
||||
# Ensure principal host/test.example.com present in service.
|
||||
- ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: HTTP/www.example.com
|
||||
principal:
|
||||
- host/test.example.com
|
||||
action: member
|
||||
|
||||
# Ensure host can manage service.
|
||||
- ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: HTTP/www.example.com
|
||||
host:
|
||||
- host1.example.com
|
||||
- host2.example.com
|
||||
action: member
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
|
||||
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
|
||||
encode_certificate, gen_add_del_lists, module_params_get, to_text, \
|
||||
api_check_param
|
||||
|
||||
|
||||
def find_service(module, name):
|
||||
_args = {
|
||||
"all": True,
|
||||
}
|
||||
|
||||
_result = api_command(module, "service_find", to_text(name), _args)
|
||||
|
||||
if len(_result["result"]) > 1:
|
||||
module.fail_json(
|
||||
msg="There is more than one service '%s'" % (name))
|
||||
elif len(_result["result"]) == 1:
|
||||
_res = _result["result"][0]
|
||||
certs = _res.get("usercertificate")
|
||||
if certs is not None:
|
||||
_res["usercertificate"] = [encode_certificate(cert) for
|
||||
cert in certs]
|
||||
return _res
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def gen_args(pac_type, auth_ind, skip_host_check, force, requires_pre_auth,
|
||||
ok_as_delegate, ok_to_auth_as_delegate):
|
||||
_args = {}
|
||||
|
||||
if pac_type is not None:
|
||||
_args['ipakrbauthzdata'] = pac_type
|
||||
if auth_ind is not None:
|
||||
_args['krbprincipalauthind'] = auth_ind
|
||||
if skip_host_check is not None:
|
||||
_args['skip_host_check'] = (skip_host_check)
|
||||
if force is not None:
|
||||
_args['force'] = (force)
|
||||
if requires_pre_auth is not None:
|
||||
_args['ipakrbrequirespreauth'] = (requires_pre_auth)
|
||||
if ok_as_delegate is not None:
|
||||
_args['ipakrbokasdelegate'] = (ok_as_delegate)
|
||||
if ok_to_auth_as_delegate is not None:
|
||||
_args['ipakrboktoauthasdelegate'] = (ok_to_auth_as_delegate)
|
||||
|
||||
return _args
|
||||
|
||||
|
||||
def check_parameters(module, state, action, names, parameters):
|
||||
assert isinstance(parameters, dict)
|
||||
|
||||
# invalid parameters for everything but state 'present', action 'service'.
|
||||
invalid = ['pac_type', 'auth_ind', 'skip_host_check',
|
||||
'force', 'requires_pre_auth', 'ok_as_delegate',
|
||||
'ok_to_auth_as_delegate']
|
||||
|
||||
# invalid parameters when not handling service members.
|
||||
invalid_not_member = \
|
||||
['principal', 'certificate', 'host', 'allow_create_keytab_user',
|
||||
'allow_create_keytab_group', 'allow_create_keytab_host',
|
||||
'allow_create_keytab_hostgroup', 'allow_retrieve_keytab_user',
|
||||
'allow_retrieve_keytab_group', 'allow_retrieve_keytab_host',
|
||||
'allow_retrieve_keytab_hostgroup']
|
||||
|
||||
if state == 'present':
|
||||
if len(names) != 1:
|
||||
module.fail_json(msg="Only one service can be added at a time.")
|
||||
|
||||
if action == 'service':
|
||||
invalid = []
|
||||
|
||||
elif state == 'absent':
|
||||
if len(names) < 1:
|
||||
module.fail_json(msg="No name given.")
|
||||
|
||||
if action == "service":
|
||||
invalid.extend(invalid_not_member)
|
||||
|
||||
elif state == 'disabled':
|
||||
invalid.extend(invalid_not_member)
|
||||
if action != "service":
|
||||
module.fail_json(
|
||||
msg="Invalid action '%s' for state '%s'" % (action, state))
|
||||
|
||||
else:
|
||||
module.fail_json(msg="Invalid state '%s'" % (state))
|
||||
|
||||
for _invalid in invalid:
|
||||
if parameters[_invalid] is not None:
|
||||
module.fail_json(
|
||||
msg="Argument '%s' can not be used with state '%s'" %
|
||||
(_invalid, state))
|
||||
|
||||
|
||||
def init_ansible_module():
|
||||
ansible_module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
# general
|
||||
ipaadmin_principal=dict(type="str", default="admin"),
|
||||
ipaadmin_password=dict(type="str", required=False, no_log=True),
|
||||
|
||||
name=dict(type="list", aliases=["service"], default=None,
|
||||
required=True),
|
||||
# service attributesstr
|
||||
certificate=dict(type="list", aliases=['usercertificate'],
|
||||
default=None, required=False),
|
||||
principal=dict(type="list", aliases=["krbprincipalname"],
|
||||
default=None),
|
||||
pac_type=dict(type="list", aliases=["ipakrbauthzdata"],
|
||||
choices=["MS-PAC", "PAD", "NONE"]),
|
||||
auth_ind=dict(type="str",
|
||||
aliases=["krbprincipalauthind"],
|
||||
choices=["otp", "radius", "pkinit", "hardened"]),
|
||||
skip_host_check=dict(type="bool"),
|
||||
force=dict(type="bool"),
|
||||
requires_pre_auth=dict(
|
||||
type="bool", aliases=["ipakrbrequirespreauth"]),
|
||||
ok_as_delegate=dict(type="bool", aliases=["ipakrbokasdelegate"]),
|
||||
ok_to_auth_as_delegate=dict(type="bool",
|
||||
aliases=["ipakrboktoauthasdelegate"]),
|
||||
host=dict(type="list", aliases=["managedby_host"], required=False),
|
||||
allow_create_keytab_user=dict(
|
||||
type="list", required=False,
|
||||
aliases=['ipaallowedtoperform_write_keys_user']),
|
||||
allow_retrieve_keytab_user=dict(
|
||||
type="list", required=False,
|
||||
aliases=['ipaallowedtoperform_read_keys_user']),
|
||||
allow_create_keytab_group=dict(
|
||||
type="list", required=False,
|
||||
aliases=['ipaallowedtoperform_write_keys_group']),
|
||||
allow_retrieve_keytab_group=dict(
|
||||
type="list", required=False,
|
||||
aliases=['ipaallowedtoperform_read_keys_group']),
|
||||
allow_create_keytab_host=dict(
|
||||
type="list", required=False,
|
||||
aliases=['ipaallowedtoperform_write_keys_host']),
|
||||
allow_retrieve_keytab_host=dict(
|
||||
type="list", required=False,
|
||||
aliases=['ipaallowedtoperform_read_keys_host']),
|
||||
allow_create_keytab_hostgroup=dict(
|
||||
type="list", required=False,
|
||||
aliases=['ipaallowedtoperform_write_keys_hostgroup']),
|
||||
allow_retrieve_keytab_hostgroup=dict(
|
||||
type="list", required=False,
|
||||
aliases=['ipaallowedtoperform_read_keys_hostgroup']),
|
||||
# action
|
||||
action=dict(type="str", default="service",
|
||||
choices=["member", "service"]),
|
||||
# state
|
||||
state=dict(type="str", default="present",
|
||||
choices=["present", "absent",
|
||||
"enabled", "disabled"]),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
|
||||
return ansible_module
|
||||
|
||||
|
||||
def main():
|
||||
ansible_module = init_ansible_module()
|
||||
|
||||
# Get parameters
|
||||
|
||||
# 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")
|
||||
|
||||
# service attributes
|
||||
principal = module_params_get(ansible_module, "principal")
|
||||
certificate = module_params_get(ansible_module, "certificate")
|
||||
pac_type = module_params_get(ansible_module, "pac_type")
|
||||
auth_ind = module_params_get(ansible_module, "auth_ind")
|
||||
skip_host_check = module_params_get(ansible_module, "skip_host_check")
|
||||
force = module_params_get(ansible_module, "force")
|
||||
requires_pre_auth = module_params_get(ansible_module, "requires_pre_auth")
|
||||
ok_as_delegate = module_params_get(ansible_module, "ok_as_delegate")
|
||||
ok_to_auth_as_delegate = module_params_get(ansible_module,
|
||||
"ok_to_auth_as_delegate")
|
||||
|
||||
host = module_params_get(ansible_module, "host")
|
||||
|
||||
allow_create_keytab_user = module_params_get(
|
||||
ansible_module, "allow_create_keytab_user")
|
||||
allow_create_keytab_group = module_params_get(
|
||||
ansible_module, "allow_create_keytab_group")
|
||||
allow_create_keytab_host = module_params_get(
|
||||
ansible_module, "allow_create_keytab_host")
|
||||
allow_create_keytab_hostgroup = module_params_get(
|
||||
ansible_module, "allow_create_keytab_hostgroup")
|
||||
|
||||
allow_retrieve_keytab_user = module_params_get(
|
||||
ansible_module, "allow_retrieve_keytab_user")
|
||||
allow_retrieve_keytab_group = module_params_get(
|
||||
ansible_module, "allow_retrieve_keytab_group")
|
||||
allow_retrieve_keytab_host = module_params_get(
|
||||
ansible_module, "allow_create_keytab_host")
|
||||
allow_retrieve_keytab_hostgroup = module_params_get(
|
||||
ansible_module, "allow_retrieve_keytab_hostgroup")
|
||||
|
||||
# action
|
||||
action = module_params_get(ansible_module, "action")
|
||||
# state
|
||||
state = module_params_get(ansible_module, "state")
|
||||
|
||||
# check parameters
|
||||
check_parameters(ansible_module, state, action, names, vars())
|
||||
|
||||
# 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)
|
||||
api_connect()
|
||||
|
||||
has_skip_host_check = api_check_param(
|
||||
"service_add", "skip_host_check")
|
||||
if skip_host_check and not has_skip_host_check:
|
||||
ansible_module.fail_json(
|
||||
msg="Skipping host check is not supported by your IPA version")
|
||||
|
||||
commands = []
|
||||
|
||||
for name in names:
|
||||
res_find = find_service(ansible_module, name)
|
||||
|
||||
if state == "present":
|
||||
if action == "service":
|
||||
args = gen_args(
|
||||
pac_type, auth_ind, skip_host_check, force,
|
||||
requires_pre_auth, ok_as_delegate,
|
||||
ok_to_auth_as_delegate)
|
||||
if not has_skip_host_check and 'skip_host_check' in args:
|
||||
del args['skip_host_check']
|
||||
|
||||
if res_find is None:
|
||||
commands.append([name, 'service_add', args])
|
||||
|
||||
certificate_add = certificate or []
|
||||
certificate_del = []
|
||||
host_add = host or []
|
||||
host_del = []
|
||||
principal_add = principal or []
|
||||
principal_del = []
|
||||
allow_create_keytab_user_add = \
|
||||
allow_create_keytab_user or []
|
||||
allow_create_keytab_user_del = []
|
||||
allow_create_keytab_group_add = \
|
||||
allow_create_keytab_group or []
|
||||
allow_create_keytab_group_del = []
|
||||
allow_create_keytab_host_add = \
|
||||
allow_create_keytab_host or []
|
||||
allow_create_keytab_host_del = []
|
||||
allow_create_keytab_hostgroup_add = \
|
||||
allow_create_keytab_hostgroup or []
|
||||
allow_create_keytab_hostgroup_del = []
|
||||
allow_retrieve_keytab_user_add = \
|
||||
allow_retrieve_keytab_user or []
|
||||
allow_retrieve_keytab_user_del = []
|
||||
allow_retrieve_keytab_group_add = \
|
||||
allow_retrieve_keytab_group or []
|
||||
allow_retrieve_keytab_group_del = []
|
||||
allow_retrieve_keytab_host_add = \
|
||||
allow_retrieve_keytab_host or []
|
||||
allow_retrieve_keytab_host_del = []
|
||||
allow_retrieve_keytab_hostgroup_add = \
|
||||
allow_retrieve_keytab_hostgroup or []
|
||||
allow_retrieve_keytab_hostgroup_del = []
|
||||
|
||||
else:
|
||||
for remove in ['skip_host_check', 'force']:
|
||||
if remove in args:
|
||||
del args[remove]
|
||||
|
||||
if not compare_args_ipa(ansible_module, args,
|
||||
res_find):
|
||||
commands.append([name, "service_mod", args])
|
||||
|
||||
certificate_add, certificate_del = gen_add_del_lists(
|
||||
certificate, res_find.get("usercertificate"))
|
||||
|
||||
host_add, host_del = gen_add_del_lists(
|
||||
host, res_find.get('managedby_host', []))
|
||||
|
||||
principal_add, principal_del = gen_add_del_lists(
|
||||
principal, res_find.get("principal"))
|
||||
|
||||
(allow_create_keytab_user_add,
|
||||
allow_create_keytab_user_del) = \
|
||||
gen_add_del_lists(
|
||||
allow_create_keytab_user, res_find.get(
|
||||
'ipaallowedtoperform_write_keys_user',
|
||||
[]))
|
||||
(allow_retrieve_keytab_user_add,
|
||||
allow_retrieve_keytab_user_del) = \
|
||||
gen_add_del_lists(
|
||||
allow_retrieve_keytab_user, res_find.get(
|
||||
'ipaallowedtoperform_read_keys_user',
|
||||
[]))
|
||||
(allow_create_keytab_group_add,
|
||||
allow_create_keytab_group_del) = \
|
||||
gen_add_del_lists(
|
||||
allow_create_keytab_group, res_find.get(
|
||||
'ipaallowedtoperform_write_keys_group',
|
||||
[]))
|
||||
(allow_retrieve_keytab_group_add,
|
||||
allow_retrieve_keytab_group_del) = \
|
||||
gen_add_del_lists(
|
||||
allow_retrieve_keytab_group,
|
||||
res_find.get(
|
||||
'ipaallowedtoperform_read_keys_group',
|
||||
[]))
|
||||
(allow_create_keytab_host_add,
|
||||
allow_create_keytab_host_del) = \
|
||||
gen_add_del_lists(
|
||||
allow_create_keytab_host,
|
||||
res_find.get(
|
||||
'ipaallowedtoperform_write_keys_host',
|
||||
[]))
|
||||
(allow_retrieve_keytab_host_add,
|
||||
allow_retrieve_keytab_host_del) = \
|
||||
gen_add_del_lists(
|
||||
allow_retrieve_keytab_host,
|
||||
res_find.get(
|
||||
'ipaallowedtoperform_read_keys_host',
|
||||
[]))
|
||||
(allow_create_keytab_hostgroup_add,
|
||||
allow_create_keytab_hostgroup_del) = \
|
||||
gen_add_del_lists(
|
||||
allow_create_keytab_hostgroup,
|
||||
res_find.get(
|
||||
'ipaallowedtoperform_write_keys_hostgroup',
|
||||
[]))
|
||||
(allow_retrieve_keytab_hostgroup_add,
|
||||
allow_retrieve_keytab_hostgroup_del) = \
|
||||
gen_add_del_lists(
|
||||
allow_retrieve_keytab_hostgroup,
|
||||
res_find.get(
|
||||
'ipaallowedtoperform_read_keys_hostgroup',
|
||||
[]))
|
||||
|
||||
elif action == "member":
|
||||
if res_find is None:
|
||||
ansible_module.fail_json(msg="No service '%s'" % name)
|
||||
|
||||
existing = res_find.get('usercertificate', [])
|
||||
if certificate is None:
|
||||
certificate_add = []
|
||||
else:
|
||||
certificate_add = [c for c in certificate
|
||||
if c not in existing]
|
||||
certificate_del = []
|
||||
host_add = host or []
|
||||
host_del = []
|
||||
principal_add = principal or []
|
||||
principal_del = []
|
||||
|
||||
allow_create_keytab_user_add = \
|
||||
allow_create_keytab_user or []
|
||||
allow_create_keytab_user_del = []
|
||||
allow_create_keytab_group_add = \
|
||||
allow_create_keytab_group or []
|
||||
allow_create_keytab_group_del = []
|
||||
allow_create_keytab_host_add = \
|
||||
allow_create_keytab_host or []
|
||||
allow_create_keytab_host_del = []
|
||||
allow_create_keytab_hostgroup_add = \
|
||||
allow_create_keytab_hostgroup or []
|
||||
allow_create_keytab_hostgroup_del = []
|
||||
allow_retrieve_keytab_user_add = \
|
||||
allow_retrieve_keytab_user or []
|
||||
allow_retrieve_keytab_user_del = []
|
||||
allow_retrieve_keytab_group_add = \
|
||||
allow_retrieve_keytab_group or []
|
||||
allow_retrieve_keytab_group_del = []
|
||||
allow_retrieve_keytab_host_add = \
|
||||
allow_retrieve_keytab_host or []
|
||||
allow_retrieve_keytab_host_del = []
|
||||
allow_retrieve_keytab_hostgroup_add = \
|
||||
allow_retrieve_keytab_hostgroup or []
|
||||
allow_retrieve_keytab_hostgroup_del = []
|
||||
|
||||
# Add principals
|
||||
for _principal in principal_add:
|
||||
commands.append([name, "service_add_principal",
|
||||
{
|
||||
"krbprincipalname":
|
||||
_principal,
|
||||
}])
|
||||
|
||||
# Remove principals
|
||||
for _principal in principal_del:
|
||||
commands.append([name, "service_remove_principal",
|
||||
{
|
||||
"krbprincipalname":
|
||||
_principal,
|
||||
}])
|
||||
|
||||
for _certificate in certificate_add:
|
||||
commands.append([name, "service_add_cert",
|
||||
{
|
||||
"usercertificate":
|
||||
_certificate,
|
||||
}])
|
||||
# Remove certificates
|
||||
for _certificate in certificate_del:
|
||||
commands.append([name, "service_remove_cert",
|
||||
{
|
||||
"usercertificate":
|
||||
_certificate,
|
||||
}])
|
||||
|
||||
# Add hosts.
|
||||
if host is not None and len(host) > 0 and len(host_add) > 0:
|
||||
commands.append([name, "service_add_host",
|
||||
{"host": host_add}])
|
||||
# Remove hosts
|
||||
if host is not None and len(host) > 0 and len(host_del) > 0:
|
||||
commands.append([name, "service_remove_host",
|
||||
{"host": host_del}])
|
||||
|
||||
# Allow create keytab
|
||||
if len(allow_create_keytab_user_add) > 0 or \
|
||||
len(allow_create_keytab_group_add) > 0 or \
|
||||
len(allow_create_keytab_host_add) > 0 or \
|
||||
len(allow_create_keytab_hostgroup_add) > 0:
|
||||
commands.append(
|
||||
[name, "service_allow_create_keytab",
|
||||
{'user': allow_create_keytab_user_add,
|
||||
'group': allow_create_keytab_group_add,
|
||||
'host': allow_create_keytab_host_add,
|
||||
'hostgroup': allow_create_keytab_hostgroup_add
|
||||
}])
|
||||
|
||||
# Disallow create keytab
|
||||
if len(allow_create_keytab_user_del) > 0 or \
|
||||
len(allow_create_keytab_group_del) > 0 or \
|
||||
len(allow_create_keytab_host_del) > 0 or \
|
||||
len(allow_create_keytab_hostgroup_del) > 0:
|
||||
commands.append(
|
||||
[name, "service_disallow_create_keytab",
|
||||
{'user': allow_create_keytab_user_del,
|
||||
'group': allow_create_keytab_group_del,
|
||||
'host': allow_create_keytab_host_del,
|
||||
'hostgroup': allow_create_keytab_hostgroup_del
|
||||
}])
|
||||
|
||||
# Allow retrieve keytab
|
||||
if len(allow_retrieve_keytab_user_add) > 0 or \
|
||||
len(allow_retrieve_keytab_group_add) > 0 or \
|
||||
len(allow_retrieve_keytab_hostgroup_add) > 0 or \
|
||||
len(allow_retrieve_keytab_hostgroup_add) > 0:
|
||||
commands.append(
|
||||
[name, "service_allow_retrieve_keytab",
|
||||
{'user': allow_retrieve_keytab_user_add,
|
||||
'group': allow_retrieve_keytab_group_add,
|
||||
'host': allow_retrieve_keytab_host_add,
|
||||
'hostgroup': allow_retrieve_keytab_hostgroup_add
|
||||
}])
|
||||
|
||||
# Disllow retrieve keytab
|
||||
if len(allow_retrieve_keytab_user_del) > 0 or \
|
||||
len(allow_retrieve_keytab_group_del) > 0 or \
|
||||
len(allow_retrieve_keytab_host_del) > 0 or \
|
||||
len(allow_retrieve_keytab_hostgroup_del) > 0:
|
||||
commands.append(
|
||||
[name, "service_disallow_retrieve_keytab",
|
||||
{'user': allow_retrieve_keytab_user_del,
|
||||
'group': allow_retrieve_keytab_group_del,
|
||||
'host': allow_retrieve_keytab_host_del,
|
||||
'hostgroup': allow_retrieve_keytab_hostgroup_del
|
||||
}])
|
||||
|
||||
elif state == "absent":
|
||||
if action == "service":
|
||||
if res_find is not None:
|
||||
commands.append([name, 'service_del', {}])
|
||||
|
||||
elif action == "member":
|
||||
if res_find is None:
|
||||
ansible_module.fail_json(msg="No service '%s'" % name)
|
||||
|
||||
# Remove principals
|
||||
if principal is not None:
|
||||
for _principal in principal:
|
||||
commands.append([name, "service_remove_principal",
|
||||
{
|
||||
"krbprincipalname":
|
||||
_principal,
|
||||
}])
|
||||
# Remove certificates
|
||||
if certificate is not None:
|
||||
existing = res_find.get('usercertificate', [])
|
||||
for _certificate in certificate:
|
||||
if _certificate in existing:
|
||||
commands.append([name, "service_remove_cert",
|
||||
{
|
||||
"usercertificate":
|
||||
_certificate,
|
||||
}])
|
||||
|
||||
# Add hosts
|
||||
if host is not None:
|
||||
commands.append(
|
||||
[name, "service_remove_host", {"host": host}])
|
||||
|
||||
# Allow create keytab
|
||||
if allow_create_keytab_user is not None or \
|
||||
allow_create_keytab_group is not None or \
|
||||
allow_create_keytab_host is not None or \
|
||||
allow_create_keytab_hostgroup is not None:
|
||||
commands.append(
|
||||
[name, "service_disallow_create_keytab",
|
||||
{'user': allow_create_keytab_user,
|
||||
'group': allow_create_keytab_group,
|
||||
'host': allow_create_keytab_host,
|
||||
'hostgroup': allow_create_keytab_hostgroup
|
||||
}])
|
||||
|
||||
# Allow retriev keytab
|
||||
if allow_retrieve_keytab_user is not None or \
|
||||
allow_retrieve_keytab_group is not None or \
|
||||
allow_retrieve_keytab_host is not None or \
|
||||
allow_retrieve_keytab_hostgroup is not None:
|
||||
commands.append(
|
||||
[name, "service_disallow_retrieve_keytab",
|
||||
{'user': allow_retrieve_keytab_user,
|
||||
'group': allow_retrieve_keytab_group,
|
||||
'host': allow_retrieve_keytab_host,
|
||||
'hostgroup': allow_retrieve_keytab_hostgroup
|
||||
}])
|
||||
|
||||
elif state == "disabled":
|
||||
if action == "service":
|
||||
if res_find is not None and \
|
||||
len(res_find.get('usercertificate', [])) > 0:
|
||||
commands.append([name, 'service_disable', {}])
|
||||
else:
|
||||
ansible_module.fail_json(
|
||||
msg="Invalid action '%s' for state '%s'" %
|
||||
(action, state))
|
||||
else:
|
||||
ansible_module.fail_json(msg="Unkown state '%s'" % state)
|
||||
|
||||
# Execute commands
|
||||
errors = []
|
||||
for name, command, args in commands:
|
||||
try:
|
||||
result = api_command(ansible_module, command, name, args)
|
||||
|
||||
if "completed" in result:
|
||||
if result["completed"] > 0:
|
||||
changed = True
|
||||
else:
|
||||
changed = True
|
||||
except Exception as ex:
|
||||
ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
|
||||
str(ex)))
|
||||
# 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 ex:
|
||||
ansible_module.fail_json(msg=str(ex))
|
||||
|
||||
finally:
|
||||
temp_kdestroy(ccache_dir, ccache_name)
|
||||
|
||||
# Done
|
||||
ansible_module.exit_json(changed=changed, **exit_args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -57,13 +57,13 @@ author:
|
||||
EXAMPLES = """
|
||||
# Ensure sudocmd is present
|
||||
- ipacommand:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: su
|
||||
state: present
|
||||
|
||||
# Ensure sudocmd is absent
|
||||
- ipacommand:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: su
|
||||
state: absent
|
||||
"""
|
||||
@@ -97,7 +97,7 @@ def find_sudocmd(module, name):
|
||||
def gen_args(description):
|
||||
_args = {}
|
||||
if description is not None:
|
||||
_args["description"] = description
|
||||
_args["description"] = to_text(description)
|
||||
|
||||
return _args
|
||||
|
||||
|
||||
@@ -73,13 +73,13 @@ author:
|
||||
EXAMPLES = """
|
||||
# Ensure sudocmd-group 'network' is present
|
||||
- ipasudocmdgroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: network
|
||||
state: present
|
||||
|
||||
# Ensure sudocmdgroup and sudocmd are present in 'network' sudocmdgroup
|
||||
- ipasudocmdgroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: network
|
||||
sudocmd:
|
||||
- /usr/sbin/ifconfig
|
||||
@@ -88,7 +88,7 @@ EXAMPLES = """
|
||||
|
||||
# Ensure sudocmdgroup and sudocmd are absent in 'network' sudocmdgroup
|
||||
- ipasudocmdgroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: network
|
||||
sudocmd:
|
||||
- /usr/sbin/ifconfig
|
||||
@@ -98,7 +98,7 @@ EXAMPLES = """
|
||||
|
||||
# Ensure sudocmd-group 'network' is absent
|
||||
- ipasudocmdgroup:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: network
|
||||
action: member
|
||||
state: absent
|
||||
|
||||
@@ -79,18 +79,43 @@ options:
|
||||
description: Host category the sudo rule applies to.
|
||||
required: false
|
||||
choices: ["all"]
|
||||
cmd:
|
||||
description: List of sudocmds assigned to this sudorule.
|
||||
allow_sudocmd:
|
||||
description: List of allowed sudocmds assigned to this sudorule.
|
||||
required: false
|
||||
type: list
|
||||
cmdgroup:
|
||||
description: List of sudocmd groups assigned to this sudorule.
|
||||
allow_sudocmdgroup:
|
||||
description: List of allowed sudocmd groups assigned to this sudorule.
|
||||
required: false
|
||||
type: list
|
||||
deny_sudocmd:
|
||||
description: List of denied sudocmds assigned to this sudorule.
|
||||
required: false
|
||||
type: list
|
||||
deny_sudocmdgroup:
|
||||
description: List of denied sudocmd groups assigned to this sudorule.
|
||||
required: false
|
||||
type: list
|
||||
cmdcategory:
|
||||
description: Cammand category the sudo rule applies to
|
||||
description: Command category the sudo rule applies to
|
||||
required: false
|
||||
choices: ["all"]
|
||||
order:
|
||||
description: Order to apply this rule.
|
||||
required: false
|
||||
type: int
|
||||
sudooption:
|
||||
description:
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["options"]
|
||||
runasuser:
|
||||
description: List of users for Sudo to execute as.
|
||||
required: false
|
||||
type: list
|
||||
runasgroup:
|
||||
description: List of groups for Sudo to execute as.
|
||||
required: false
|
||||
type: list
|
||||
action:
|
||||
description: Work on sudorule or member level
|
||||
default: sudorule
|
||||
@@ -106,50 +131,50 @@ author:
|
||||
EXAMPLES = """
|
||||
# Ensure Sudo Rule tesrule1 is present
|
||||
- ipasudorule:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testrule1
|
||||
|
||||
# Ensure sudocmd is present in Sudo Rule
|
||||
- ipasudorule:
|
||||
ipaadmin_password: pass1234
|
||||
name: testrule1
|
||||
cmd:
|
||||
- /sbin/ifconfig
|
||||
- /usr/bin/vim
|
||||
action: member
|
||||
state: absent
|
||||
ipaadmin_password: pass1234
|
||||
name: testrule1
|
||||
allow_sudocmd:
|
||||
- /sbin/ifconfig
|
||||
- /usr/bin/vim
|
||||
action: member
|
||||
state: absent
|
||||
|
||||
# Ensure host server is present in Sudo Rule
|
||||
- ipasudorule:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testrule1
|
||||
host: server
|
||||
action: member
|
||||
|
||||
# Ensure hostgroup cluster is present in Sudo Rule
|
||||
- ipasudorule:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testrule1
|
||||
hostgroup: cluster
|
||||
action: member
|
||||
|
||||
# Ensure sudo rule for usercategory "all"
|
||||
- ipasudorule:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: allusers
|
||||
usercategory: all
|
||||
action: enabled
|
||||
|
||||
# Ensure sudo rule for hostcategory "all"
|
||||
- ipasudorule:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: allhosts
|
||||
hostcategory: all
|
||||
action: enabled
|
||||
|
||||
# Ensure Sudo Rule tesrule1 is absent
|
||||
- ipasudorule:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testrule1
|
||||
state: absent
|
||||
"""
|
||||
@@ -160,7 +185,7 @@ RETURN = """
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
|
||||
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
|
||||
module_params_get
|
||||
module_params_get, gen_add_del_lists
|
||||
|
||||
|
||||
def find_sudorule(module, name):
|
||||
@@ -180,14 +205,26 @@ def find_sudorule(module, name):
|
||||
return None
|
||||
|
||||
|
||||
def gen_args(ansible_module):
|
||||
arglist = ['description', 'usercategory', 'hostcategory', 'cmdcategory',
|
||||
'runasusercategory', 'runasgroupcategory', 'nomembers']
|
||||
def gen_args(description, usercat, hostcat, cmdcat, runasusercat,
|
||||
runasgroupcat, order, nomembers):
|
||||
_args = {}
|
||||
for arg in arglist:
|
||||
value = module_params_get(ansible_module, arg)
|
||||
if value is not None:
|
||||
_args[arg] = value
|
||||
|
||||
if description is not None:
|
||||
_args['description'] = description
|
||||
if usercat is not None:
|
||||
_args['usercategory'] = usercat
|
||||
if hostcat is not None:
|
||||
_args['hostcategory'] = hostcat
|
||||
if cmdcat is not None:
|
||||
_args['cmdcategory'] = cmdcat
|
||||
if runasusercat is not None:
|
||||
_args['ipasudorunasusercategory'] = runasusercat
|
||||
if runasgroupcat is not None:
|
||||
_args['ipasudorunasgroupcategory'] = runasgroupcat
|
||||
if order is not None:
|
||||
_args['sudoorder'] = order
|
||||
if nomembers is not None:
|
||||
_args['nomembers'] = nomembers
|
||||
|
||||
return _args
|
||||
|
||||
@@ -212,13 +249,21 @@ def main():
|
||||
hostgroup=dict(required=False, type='list', default=None),
|
||||
user=dict(required=False, type='list', default=None),
|
||||
group=dict(required=False, type='list', default=None),
|
||||
cmd=dict(required=False, type="list", default=None),
|
||||
allow_sudocmd=dict(required=False, type="list", default=None),
|
||||
deny_sudocmd=dict(required=False, type="list", default=None),
|
||||
allow_sudocmdgroup=dict(required=False, type="list", default=None),
|
||||
deny_sudocmdgroup=dict(required=False, type="list", default=None),
|
||||
cmdcategory=dict(required=False, type="str", default=None,
|
||||
choices=["all"]),
|
||||
runasusercategory=dict(required=False, type="str", default=None,
|
||||
choices=["all"]),
|
||||
runasgroupcategory=dict(required=False, type="str", default=None,
|
||||
choices=["all"]),
|
||||
runasuser=dict(required=False, type="list", default=None),
|
||||
runasgroup=dict(required=False, type="list", default=None),
|
||||
order=dict(type="int", required=False, aliases=['sudoorder']),
|
||||
sudooption=dict(required=False, type='list', default=None,
|
||||
aliases=["options"]),
|
||||
action=dict(type="str", default="sudorule",
|
||||
choices=["member", "sudorule"]),
|
||||
# state
|
||||
@@ -256,8 +301,16 @@ def main():
|
||||
hostgroup = module_params_get(ansible_module, "hostgroup")
|
||||
user = module_params_get(ansible_module, "user")
|
||||
group = module_params_get(ansible_module, "group")
|
||||
cmd = module_params_get(ansible_module, 'cmd')
|
||||
cmdgroup = module_params_get(ansible_module, 'cmdgroup')
|
||||
allow_sudocmd = module_params_get(ansible_module, 'allow_sudocmd')
|
||||
allow_sudocmdgroup = module_params_get(ansible_module,
|
||||
'allow_sudocmdgroup')
|
||||
deny_sudocmd = module_params_get(ansible_module, 'deny_sudocmd')
|
||||
deny_sudocmdgroup = module_params_get(ansible_module,
|
||||
'deny_sudocmdgroup')
|
||||
sudooption = module_params_get(ansible_module, "sudooption")
|
||||
order = module_params_get(ansible_module, "order")
|
||||
runasuser = module_params_get(ansible_module, "runasuser")
|
||||
runasgroup = module_params_get(ansible_module, "runasgroup")
|
||||
action = module_params_get(ansible_module, "action")
|
||||
|
||||
# state
|
||||
@@ -272,28 +325,30 @@ def main():
|
||||
if action == "member":
|
||||
invalid = ["description", "usercategory", "hostcategory",
|
||||
"cmdcategory", "runasusercategory",
|
||||
"runasgroupcategory", "nomembers"]
|
||||
"runasgroupcategory", "order", "nomembers"]
|
||||
|
||||
for x in invalid:
|
||||
if x in vars() and vars()[x] is not None:
|
||||
for arg in invalid:
|
||||
if arg in vars() and vars()[arg] is not None:
|
||||
ansible_module.fail_json(
|
||||
msg="Argument '%s' can not be used with action "
|
||||
"'%s'" % (x, action))
|
||||
"'%s'" % (arg, action))
|
||||
|
||||
elif state == "absent":
|
||||
if len(names) < 1:
|
||||
ansible_module.fail_json(msg="No name given.")
|
||||
invalid = ["description", "usercategory", "hostcategory",
|
||||
"cmdcategory", "runasusercategory",
|
||||
"runasgroupcategory", "nomembers"]
|
||||
"runasgroupcategory", "nomembers", "order"]
|
||||
if action == "sudorule":
|
||||
invalid.extend(["host", "hostgroup", "user", "group",
|
||||
"cmd", "cmdgroup"])
|
||||
for x in invalid:
|
||||
if vars()[x] is not None:
|
||||
"runasuser", "runasgroup", "allow_sudocmd",
|
||||
"allow_sudocmdgroup", "deny_sudocmd",
|
||||
"deny_sudocmdgroup", "sudooption"])
|
||||
for arg in invalid:
|
||||
if vars()[arg] is not None:
|
||||
ansible_module.fail_json(
|
||||
msg="Argument '%s' can not be used with state '%s'" %
|
||||
(x, state))
|
||||
(arg, state))
|
||||
|
||||
elif state in ["enabled", "disabled"]:
|
||||
if len(names) < 1:
|
||||
@@ -305,12 +360,14 @@ def main():
|
||||
invalid = ["description", "usercategory", "hostcategory",
|
||||
"cmdcategory", "runasusercategory", "runasgroupcategory",
|
||||
"nomembers", "nomembers", "host", "hostgroup",
|
||||
"user", "group", "cmd", "cmdgroup"]
|
||||
for x in invalid:
|
||||
if vars()[x] is not None:
|
||||
"user", "group", "allow_sudocmd", "allow_sudocmdgroup",
|
||||
"deny_sudocmd", "deny_sudocmdgroup", "runasuser",
|
||||
"runasgroup", "order", "sudooption"]
|
||||
for arg in invalid:
|
||||
if vars()[arg] is not None:
|
||||
ansible_module.fail_json(
|
||||
msg="Argument '%s' can not be used with state '%s'" %
|
||||
(x, state))
|
||||
(arg, state))
|
||||
else:
|
||||
ansible_module.fail_json(msg="Invalid state '%s'" % state)
|
||||
|
||||
@@ -335,7 +392,9 @@ def main():
|
||||
# Create command
|
||||
if state == "present":
|
||||
# Generate args
|
||||
args = gen_args(ansible_module)
|
||||
args = gen_args(description, usercategory, hostcategory,
|
||||
cmdcategory, runasusercategory,
|
||||
runasgroupcategory, order, nomembers)
|
||||
if action == "sudorule":
|
||||
# Found the sudorule
|
||||
if res_find is not None:
|
||||
@@ -351,44 +410,42 @@ def main():
|
||||
res_find = {}
|
||||
|
||||
# Generate addition and removal lists
|
||||
host_add = list(
|
||||
set(host or []) -
|
||||
set(res_find.get("member_host", [])))
|
||||
host_del = list(
|
||||
set(res_find.get("member_host", [])) -
|
||||
set(host or []))
|
||||
hostgroup_add = list(
|
||||
set(hostgroup or []) -
|
||||
set(res_find.get("member_hostgroup", [])))
|
||||
hostgroup_del = list(
|
||||
set(res_find.get("member_hostgroup", [])) -
|
||||
set(hostgroup or []))
|
||||
host_add, host_del = gen_add_del_lists(
|
||||
host, res_find.get('member_host', []))
|
||||
|
||||
user_add = list(
|
||||
set(user or []) -
|
||||
set(res_find.get("member_user", [])))
|
||||
user_del = list(
|
||||
set(res_find.get("member_user", [])) -
|
||||
set(user or []))
|
||||
group_add = list(
|
||||
set(group or []) -
|
||||
set(res_find.get("member_group", [])))
|
||||
group_del = list(
|
||||
set(res_find.get("member_group", [])) -
|
||||
set(group or []))
|
||||
hostgroup_add, hostgroup_del = gen_add_del_lists(
|
||||
hostgroup, res_find.get('member_hostgroup', []))
|
||||
|
||||
cmd_add = list(
|
||||
set(cmd or []) -
|
||||
set(res_find.get("member_cmd", [])))
|
||||
cmd_del = list(
|
||||
set(res_find.get("member_cmd", [])) -
|
||||
set(cmd or []))
|
||||
cmdgroup_add = list(
|
||||
set(cmdgroup or []) -
|
||||
set(res_find.get("member_cmdgroup", [])))
|
||||
cmdgroup_del = list(
|
||||
set(res_find.get("member_cmdgroup", [])) -
|
||||
set(cmdgroup or []))
|
||||
user_add, user_del = gen_add_del_lists(
|
||||
user, res_find.get('member_user', []))
|
||||
|
||||
group_add, group_del = gen_add_del_lists(
|
||||
group, res_find.get('member_group', []))
|
||||
|
||||
allow_cmd_add, allow_cmd_del = gen_add_del_lists(
|
||||
allow_sudocmd,
|
||||
res_find.get('memberallowcmd_sudocmd', []))
|
||||
|
||||
allow_cmdgroup_add, allow_cmdgroup_del = gen_add_del_lists(
|
||||
allow_sudocmdgroup,
|
||||
res_find.get('memberallowcmd_sudocmdgroup', []))
|
||||
|
||||
deny_cmd_add, deny_cmd_del = gen_add_del_lists(
|
||||
deny_sudocmd,
|
||||
res_find.get('memberdenycmd_sudocmd', []))
|
||||
|
||||
deny_cmdgroup_add, deny_cmdgroup_del = gen_add_del_lists(
|
||||
deny_sudocmdgroup,
|
||||
res_find.get('memberdenycmd_sudocmdgroup', []))
|
||||
|
||||
sudooption_add, sudooption_del = gen_add_del_lists(
|
||||
sudooption, res_find.get('ipasudoopt', []))
|
||||
|
||||
runasuser_add, runasuser_del = gen_add_del_lists(
|
||||
runasuser, res_find.get('ipasudorunas_user', []))
|
||||
|
||||
runasgroup_add, runasgroup_del = gen_add_del_lists(
|
||||
runasgroup, res_find.get('ipasudorunas_group', []))
|
||||
|
||||
# Add hosts and hostgroups
|
||||
if len(host_add) > 0 or len(hostgroup_add) > 0:
|
||||
@@ -420,20 +477,59 @@ def main():
|
||||
"group": group_del,
|
||||
}])
|
||||
|
||||
# Add commands
|
||||
if len(cmd_add) > 0 or len(cmdgroup_add) > 0:
|
||||
# Add commands allowed
|
||||
if len(allow_cmd_add) > 0 or len(allow_cmdgroup_add) > 0:
|
||||
commands.append([name, "sudorule_add_allow_command",
|
||||
{
|
||||
"sudocmd": cmd_add,
|
||||
"sudocmdgroup": cmdgroup_add,
|
||||
}])
|
||||
{"sudocmd": allow_cmd_add,
|
||||
"sudocmdgroup": allow_cmdgroup_add,
|
||||
}])
|
||||
|
||||
if len(cmd_del) > 0 or len(cmdgroup_del) > 0:
|
||||
if len(allow_cmd_del) > 0 or len(allow_cmdgroup_del) > 0:
|
||||
commands.append([name, "sudorule_remove_allow_command",
|
||||
{"sudocmd": allow_cmd_del,
|
||||
"sudocmdgroup": allow_cmdgroup_del
|
||||
}])
|
||||
|
||||
# Add commands denied
|
||||
if len(deny_cmd_add) > 0 or len(deny_cmdgroup_add) > 0:
|
||||
commands.append([name, "sudorule_add_deny_command",
|
||||
{
|
||||
"sudocmd": cmd_del,
|
||||
"sudocmdgroup": cmdgroup_del
|
||||
}])
|
||||
{"sudocmd": deny_cmd_add,
|
||||
"sudocmdgroup": deny_cmdgroup_add,
|
||||
}])
|
||||
|
||||
if len(deny_cmd_del) > 0 or len(deny_cmdgroup_del) > 0:
|
||||
commands.append([name, "sudorule_remove_deny_command",
|
||||
{"sudocmd": deny_cmd_del,
|
||||
"sudocmdgroup": deny_cmdgroup_del
|
||||
}])
|
||||
|
||||
# Add RunAS Users
|
||||
if len(runasuser_add) > 0:
|
||||
commands.append([name, "sudorule_add_runasuser",
|
||||
{"user": runasuser_add}])
|
||||
# Remove RunAS Users
|
||||
if len(runasuser_del) > 0:
|
||||
commands.append([name, "sudorule_remove_runasuser",
|
||||
{"user": runasuser_del}])
|
||||
|
||||
# Add RunAS Groups
|
||||
if len(runasgroup_add) > 0:
|
||||
commands.append([name, "sudorule_add_runasgroup",
|
||||
{"group": runasgroup_add}])
|
||||
# Remove RunAS Groups
|
||||
if len(runasgroup_del) > 0:
|
||||
commands.append([name, "sudorule_remove_runasgroup",
|
||||
{"group": runasgroup_del}])
|
||||
|
||||
# Add sudo options
|
||||
for sudoopt in sudooption_add:
|
||||
commands.append([name, "sudorule_add_option",
|
||||
{"ipasudoopt": sudoopt}])
|
||||
|
||||
# Remove sudo options
|
||||
for sudoopt in sudooption_del:
|
||||
commands.append([name, "sudorule_remove_option",
|
||||
{"ipasudoopt": sudoopt}])
|
||||
|
||||
elif action == "member":
|
||||
if res_find is None:
|
||||
@@ -456,11 +552,38 @@ def main():
|
||||
}])
|
||||
|
||||
# Add commands
|
||||
if cmd is not None:
|
||||
if allow_sudocmd is not None \
|
||||
or allow_sudocmdgroup is not None:
|
||||
commands.append([name, "sudorule_add_allow_command",
|
||||
{
|
||||
"sudocmd": cmd,
|
||||
}])
|
||||
{"sudocmd": allow_sudocmd,
|
||||
"sudocmdgroup": allow_sudocmdgroup,
|
||||
}])
|
||||
|
||||
# Add commands
|
||||
if deny_sudocmd is not None \
|
||||
or deny_sudocmdgroup is not None:
|
||||
commands.append([name, "sudorule_add_deny_command",
|
||||
{"sudocmd": deny_sudocmd,
|
||||
"sudocmdgroup": deny_sudocmdgroup,
|
||||
}])
|
||||
|
||||
# Add RunAS Users
|
||||
if runasuser is not None:
|
||||
commands.append([name, "sudorule_add_runasuser",
|
||||
{"user": runasuser}])
|
||||
|
||||
# Add RunAS Groups
|
||||
if runasgroup is not None:
|
||||
commands.append([name, "sudorule_add_runasgroup",
|
||||
{"group": runasgroup}])
|
||||
|
||||
# Add options
|
||||
if sudooption is not None:
|
||||
existing_opts = res_find.get('ipasudoopt', [])
|
||||
for sudoopt in sudooption:
|
||||
if sudoopt not in existing_opts:
|
||||
commands.append([name, "sudorule_add_option",
|
||||
{"ipasudoopt": sudoopt}])
|
||||
|
||||
elif state == "absent":
|
||||
if action == "sudorule":
|
||||
@@ -487,12 +610,40 @@ def main():
|
||||
"group": group,
|
||||
}])
|
||||
|
||||
# Remove commands
|
||||
if cmd is not None:
|
||||
commands.append([name, "sudorule_add_deny_command",
|
||||
{
|
||||
"sudocmd": cmd,
|
||||
}])
|
||||
# Remove allow commands
|
||||
if allow_sudocmd is not None \
|
||||
or allow_sudocmdgroup is not None:
|
||||
commands.append([name, "sudorule_remove_allow_command",
|
||||
{"sudocmd": allow_sudocmd,
|
||||
"sudocmdgroup": allow_sudocmdgroup
|
||||
}])
|
||||
|
||||
# Remove deny commands
|
||||
if deny_sudocmd is not None \
|
||||
or deny_sudocmdgroup is not None:
|
||||
commands.append([name, "sudorule_remove_deny_command",
|
||||
{"sudocmd": deny_sudocmd,
|
||||
"sudocmdgroup": deny_sudocmdgroup
|
||||
}])
|
||||
|
||||
# Remove RunAS Users
|
||||
if runasuser is not None:
|
||||
commands.append([name, "sudorule_remove_runasuser",
|
||||
{"user": runasuser}])
|
||||
|
||||
# Remove RunAS Groups
|
||||
if runasgroup is not None:
|
||||
commands.append([name, "sudorule_remove_runasgroup",
|
||||
{"group": runasgroup}])
|
||||
|
||||
# Remove options
|
||||
if sudooption is not None:
|
||||
existing_opts = res_find.get('ipasudoopt', [])
|
||||
for sudoopt in sudooption:
|
||||
if sudoopt in existing_opts:
|
||||
commands.append([name,
|
||||
"sudorule_remove_option",
|
||||
{"ipasudoopt": sudoopt}])
|
||||
|
||||
elif state == "enabled":
|
||||
if res_find is None:
|
||||
@@ -530,9 +681,9 @@ def main():
|
||||
changed = True
|
||||
else:
|
||||
changed = True
|
||||
except Exception as e:
|
||||
except Exception as ex:
|
||||
ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
|
||||
str(e)))
|
||||
str(ex)))
|
||||
# Get all errors
|
||||
# All "already a member" and "not a member" failures in the
|
||||
# result are ignored. All others are reported.
|
||||
@@ -549,8 +700,8 @@ def main():
|
||||
if len(errors) > 0:
|
||||
ansible_module.fail_json(msg=", ".join(errors))
|
||||
|
||||
except Exception as e:
|
||||
ansible_module.fail_json(msg=str(e))
|
||||
except Exception as ex:
|
||||
ansible_module.fail_json(msg=str(ex))
|
||||
|
||||
finally:
|
||||
temp_kdestroy(ccache_dir, ccache_name)
|
||||
|
||||
@@ -153,9 +153,12 @@ options:
|
||||
required: false
|
||||
aliases: ["ipasshpubkey"]
|
||||
userauthtype:
|
||||
description: List of supported user authentication types
|
||||
choices=['password', 'radius', 'otp']
|
||||
description:
|
||||
List of supported user authentication types
|
||||
Use empty string to reset userauthtype to the initial value.
|
||||
choices=['password', 'radius', 'otp', '']
|
||||
required: false
|
||||
aliases: ["ipauserauthtype"]
|
||||
userclass:
|
||||
description:
|
||||
- User category
|
||||
@@ -310,9 +313,12 @@ options:
|
||||
required: false
|
||||
aliases: ["ipasshpubkey"]
|
||||
userauthtype:
|
||||
description: List of supported user authentication types
|
||||
choices=['password', 'radius', 'otp']
|
||||
description:
|
||||
List of supported user authentication types
|
||||
Use empty string to reset userauthtype to the initial value.
|
||||
choices=['password', 'radius', 'otp', '']
|
||||
required: false
|
||||
aliases: ["ipauserauthtype"]
|
||||
userclass:
|
||||
description:
|
||||
- User category
|
||||
@@ -386,7 +392,7 @@ author:
|
||||
EXAMPLES = """
|
||||
# Create user pinky
|
||||
- ipauser:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: pinky
|
||||
first: pinky
|
||||
last: Acme
|
||||
@@ -400,39 +406,39 @@ EXAMPLES = """
|
||||
|
||||
# Create user brain
|
||||
- ipauser:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: brain
|
||||
first: brain
|
||||
last: Acme
|
||||
|
||||
# Delete user pinky, but preserved
|
||||
- ipauser:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: pinky
|
||||
preserve: yes
|
||||
state: absent
|
||||
|
||||
# Undelete user pinky
|
||||
- ipauser:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: pinky
|
||||
state: undeleted
|
||||
|
||||
# Disable user pinky
|
||||
- ipauser:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: pinky,brain
|
||||
state: disabled
|
||||
|
||||
# Enable user pinky and brain
|
||||
- ipauser:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: pinky,brain
|
||||
state: enabled
|
||||
|
||||
# Remove user pinky and brain
|
||||
- ipauser:
|
||||
ipaadmin_password: MyPassword123
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: pinky,brain
|
||||
state: disabled
|
||||
"""
|
||||
@@ -460,7 +466,8 @@ 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, date_format, \
|
||||
compare_args_ipa, module_params_get, api_check_param, api_get_realm
|
||||
compare_args_ipa, module_params_get, api_check_param, api_get_realm, \
|
||||
api_command_no_name
|
||||
import six
|
||||
|
||||
|
||||
@@ -646,6 +653,14 @@ def check_parameters(module, state, action,
|
||||
module.fail_json(msg="certmapdata: subject is missing")
|
||||
|
||||
|
||||
def extend_emails(email, default_email_domain):
|
||||
if email is not None:
|
||||
return [ "%s@%s" % (_email, default_email_domain)
|
||||
if "@" not in _email else _email
|
||||
for _email in email]
|
||||
return email
|
||||
|
||||
|
||||
def gen_certmapdata_args(certmapdata):
|
||||
certificate = certmapdata.get("certificate")
|
||||
issuer = certmapdata.get("issuer")
|
||||
@@ -701,7 +716,7 @@ def main():
|
||||
default=None),
|
||||
userauthtype=dict(type='list', aliases=["ipauserauthtype"],
|
||||
default=None,
|
||||
choices=['password', 'radius', 'otp']),
|
||||
choices=['password', 'radius', 'otp', '']),
|
||||
userclass=dict(type="list", aliases=["class"],
|
||||
default=None),
|
||||
radius=dict(type="str", aliases=["ipatokenradiusconfiglink"],
|
||||
@@ -845,13 +860,6 @@ def main():
|
||||
if names is not None and len(names) != 1:
|
||||
ansible_module.fail_json(
|
||||
msg="Only one user can be added at a time using name.")
|
||||
if action != "member":
|
||||
# Only check first and last here if names is set
|
||||
if names is not None:
|
||||
if first is None:
|
||||
ansible_module.fail_json(msg="First name is needed")
|
||||
if last is None:
|
||||
ansible_module.fail_json(msg="Last name is needed")
|
||||
|
||||
check_parameters(
|
||||
ansible_module, state, action,
|
||||
@@ -883,6 +891,17 @@ def main():
|
||||
|
||||
server_realm = api_get_realm()
|
||||
|
||||
# Default email domain
|
||||
|
||||
result = api_command_no_name(ansible_module, "config_show", {})
|
||||
default_email_domain = result["result"]["ipadefaultemaildomain"][0]
|
||||
|
||||
# Extend email addresses
|
||||
|
||||
email = extend_emails(email, default_email_domain)
|
||||
|
||||
# commands
|
||||
|
||||
commands = []
|
||||
|
||||
for user in names:
|
||||
@@ -949,6 +968,10 @@ def main():
|
||||
certmapdata, noprivate, nomembers, preserve,
|
||||
update_password)
|
||||
|
||||
# Extend email addresses
|
||||
|
||||
email = extend_emails(email, default_email_domain)
|
||||
|
||||
elif isinstance(user, str) or isinstance(user, unicode):
|
||||
name = user
|
||||
else:
|
||||
@@ -1011,6 +1034,13 @@ def main():
|
||||
if "noprivate" in args:
|
||||
del args["noprivate"]
|
||||
|
||||
# Ignore userauthtype if it is empty (for resetting)
|
||||
# and not set in for the user
|
||||
if "ipauserauthtype" not in res_find and \
|
||||
"ipauserauthtype" in args and \
|
||||
args["ipauserauthtype"] == ['']:
|
||||
del args["ipauserauthtype"]
|
||||
|
||||
# For all settings is args, check if there are
|
||||
# different settings in the find result.
|
||||
# If yes: modify
|
||||
@@ -1019,6 +1049,14 @@ def main():
|
||||
commands.append([name, "user_mod", args])
|
||||
|
||||
else:
|
||||
# Make sure we have a first and last name
|
||||
if first is None:
|
||||
ansible_module.fail_json(
|
||||
msg="First name is needed")
|
||||
if last is None:
|
||||
ansible_module.fail_json(
|
||||
msg="Last name is needed")
|
||||
|
||||
commands.append([name, "user_add", args])
|
||||
|
||||
# Handle members: principal, manager, certificate and
|
||||
|
||||
646
plugins/modules/ipavault.py
Normal file
646
plugins/modules/ipavault.py
Normal file
@@ -0,0 +1,646 @@
|
||||
#!/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
|
||||
vault_public_key:
|
||||
description: Base64 encoded public key.
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["ipavaultpublickey"]
|
||||
vault_salt:
|
||||
description: Vault salt.
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["ipavaultsalt"]
|
||||
vault_password:
|
||||
description: password to be used on symmetric vault.
|
||||
required: false
|
||||
type: string
|
||||
aliases: ["ipavaultpassword"]
|
||||
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
|
||||
vault_data:
|
||||
description: Data to be stored in the vault.
|
||||
required: false
|
||||
type: string
|
||||
aliases: ["ipavaultdata"]
|
||||
owners:
|
||||
description: Users that are owners of the container.
|
||||
required: false
|
||||
type: list
|
||||
users:
|
||||
description: Users that are member of the container.
|
||||
required: false
|
||||
type: list
|
||||
groups:
|
||||
description: Groups that are member of the container.
|
||||
required: false
|
||||
type: list
|
||||
action:
|
||||
description: Work on vault or member level.
|
||||
default: vault
|
||||
choices: ["vault", "member"]
|
||||
state:
|
||||
description: State to ensure
|
||||
default: present
|
||||
choices: ["present", "absent"]
|
||||
author:
|
||||
- Rafael Jeffman
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
# Ensure vault symvault is present
|
||||
- ipavault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: symvault
|
||||
username: admin
|
||||
vault_password: MyVaultPassword123
|
||||
vault_salt: MTIzNDU2Nzg5MAo=
|
||||
vault_type: symmetric
|
||||
|
||||
# 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
|
||||
vault_password: MyVaultPassword123
|
||||
vault_data: >
|
||||
Data archived.
|
||||
More data archived.
|
||||
action: member
|
||||
|
||||
# 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
|
||||
vault_public_key:
|
||||
LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTR
|
||||
HTkFEQ0JpUUtCZ1FDdGFudjRkK3ptSTZ0T3ova1RXdGowY3AxRAowUENoYy8vR0pJMTUzTi
|
||||
9CN3UrN0h3SXlRVlZoNUlXZG1UcCtkWXYzd09yeVpPbzYvbHN5eFJaZ2pZRDRwQ3VGCjlxM
|
||||
295VTFEMnFOZERYeGtSaFFETXBiUEVSWWlHbE1jbzdhN0hIVDk1bGNQbmhObVFkb3VGdHlV
|
||||
bFBUVS96V1kKZldYWTBOeU1UbUtoeFRseUV3SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVk
|
||||
tLS0tLQo=
|
||||
|
||||
# Ensure data is archived in an asymmetric vault
|
||||
- ipavault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: asymvault
|
||||
username: admin
|
||||
vault_data: >
|
||||
Data archived.
|
||||
More data archived.
|
||||
action: member
|
||||
|
||||
# Ensure asymmetric vault is absent.
|
||||
- ipavault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: asymvault
|
||||
username: user01
|
||||
vault_type: asymmetric
|
||||
state: absent
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
"""
|
||||
|
||||
import os
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
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
|
||||
from ipalib.errors import EmptyModlist
|
||||
|
||||
|
||||
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,
|
||||
public_key, vault_data):
|
||||
_args = {}
|
||||
|
||||
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 is not None:
|
||||
_args['ipavaulttype'] = vault_type
|
||||
if salt is not None:
|
||||
_args['ipavaultsalt'] = salt
|
||||
if public_key is not None:
|
||||
_args['ipavaultpublickey'] = public_key
|
||||
if vault_data is not None:
|
||||
_args['data'] = vault_data.encode('utf-8')
|
||||
|
||||
return _args
|
||||
|
||||
|
||||
def gen_member_args(args, users, groups):
|
||||
_args = args.copy()
|
||||
|
||||
for arg in ['ipavaulttype', 'description', 'ipavaultpublickey',
|
||||
'ipavaultsalt']:
|
||||
if arg in _args:
|
||||
del _args[arg]
|
||||
|
||||
_args['user'] = users
|
||||
_args['group'] = groups
|
||||
|
||||
return _args
|
||||
|
||||
|
||||
def data_storage_args(args, data, password):
|
||||
_args = {}
|
||||
|
||||
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 password is not None:
|
||||
_args['password'] = password
|
||||
|
||||
_args['data'] = data
|
||||
|
||||
return _args
|
||||
|
||||
|
||||
def check_parameters(module, state, action, description, username, service,
|
||||
shared, users, groups, owners, ownergroups, vault_type,
|
||||
salt, password, public_key, vault_data):
|
||||
invalid = []
|
||||
if state == "present":
|
||||
if action == "member":
|
||||
invalid = ['description', 'public_key', 'salt']
|
||||
|
||||
for param in invalid:
|
||||
if vars()[param] is not None:
|
||||
module.fail_json(
|
||||
msg="Argument '%s' can not be used with action '%s'" %
|
||||
(param, action))
|
||||
|
||||
elif state == "absent":
|
||||
invalid = ['description', 'salt']
|
||||
|
||||
if action == "vault":
|
||||
invalid.extend(['users', 'groups', 'owners', 'ownergroups',
|
||||
'password', 'public_key'])
|
||||
|
||||
for arg in invalid:
|
||||
if vars()[arg] is not None:
|
||||
module.fail_json(
|
||||
msg="Argument '%s' can not be used with action '%s'" %
|
||||
(arg, state))
|
||||
|
||||
|
||||
def check_encryption_params(module, state, vault_type, password, public_key,
|
||||
vault_data, res_find):
|
||||
if state == "present":
|
||||
if vault_type == "symmetric":
|
||||
if password is None \
|
||||
and (vault_data is not None or res_find is None):
|
||||
module.fail_json(
|
||||
msg="Vault password required for symmetric vault.")
|
||||
|
||||
if vault_type == "asymmetric":
|
||||
if public_key is None and res_find is None:
|
||||
module.fail_json(
|
||||
msg="Public Key required for asymmetric vault.")
|
||||
|
||||
|
||||
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),
|
||||
|
||||
# present
|
||||
|
||||
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']),
|
||||
vault_salt=dict(type="str", required=False, default=None,
|
||||
aliases=['ipavaultsalt']),
|
||||
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),
|
||||
owners=dict(required=False, type='list', default=None),
|
||||
ownergroups=dict(required=False, type='list', default=None),
|
||||
|
||||
vault_data=dict(type="str", required=False, default=None,
|
||||
aliases=['ipavaultdata']),
|
||||
vault_password=dict(type="str", required=False, default=None,
|
||||
no_log=True, aliases=['ipavaultpassword']),
|
||||
|
||||
# state
|
||||
action=dict(type="str", default="vault",
|
||||
choices=["vault", "data", "member"]),
|
||||
state=dict(type="str", default="present",
|
||||
choices=["present", "absent"]),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
mutually_exclusive=[['username', 'service', 'shared']],
|
||||
required_one_of=[['username', 'service', 'shared']]
|
||||
)
|
||||
|
||||
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")
|
||||
owners = module_params_get(ansible_module, "owners")
|
||||
ownergroups = module_params_get(ansible_module, "ownergroups")
|
||||
|
||||
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")
|
||||
public_key = module_params_get(ansible_module, "vault_public_key")
|
||||
|
||||
vault_data = module_params_get(ansible_module, "vault_data")
|
||||
|
||||
action = module_params_get(ansible_module, "action")
|
||||
# state
|
||||
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.")
|
||||
|
||||
else:
|
||||
ansible_module.fail_json(msg="Invalid state '%s'" % state)
|
||||
|
||||
check_parameters(ansible_module, state, action, description, username,
|
||||
service, shared, users, groups, owners, ownergroups,
|
||||
vault_type, salt, password, public_key, vault_data)
|
||||
# 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)
|
||||
|
||||
api_connect(context='ansible-freeipa')
|
||||
|
||||
commands = []
|
||||
|
||||
for name in names:
|
||||
# Make sure vault exists
|
||||
res_find = find_vault(
|
||||
ansible_module, name, username, service, shared)
|
||||
|
||||
# Generate args
|
||||
args = gen_args(description, username, service, shared, vault_type,
|
||||
salt, public_key, vault_data)
|
||||
|
||||
# Set default vault_type if needed.
|
||||
if vault_type is None and vault_data is not None:
|
||||
if res_find is not None:
|
||||
res_vault_type = res_find.get('ipavaulttype')[0]
|
||||
args['ipavaulttype'] = vault_type = res_vault_type
|
||||
else:
|
||||
args['ipavaulttype'] = vault_type = "symmetric"
|
||||
|
||||
# verify data encription args
|
||||
check_encryption_params(ansible_module, state, vault_type,
|
||||
password, public_key, vault_data, res_find)
|
||||
|
||||
# Create command
|
||||
if state == "present":
|
||||
|
||||
# Found the vault
|
||||
if action == "vault":
|
||||
if res_find is not None:
|
||||
# For all settings is args, check if there are
|
||||
# different settings in the find result.
|
||||
# If yes: modify
|
||||
if not compare_args_ipa(ansible_module, args,
|
||||
res_find):
|
||||
commands.append([name, "vault_mod_internal", args])
|
||||
else:
|
||||
if 'ipavaultsault' not in args:
|
||||
args['ipavaultsalt'] = os.urandom(32)
|
||||
commands.append([name, "vault_add_internal", args])
|
||||
# archive empty data to set password
|
||||
pwdargs = data_storage_args(
|
||||
args, args.get('data', ''), password)
|
||||
commands.append([name, "vault_archive", pwdargs])
|
||||
|
||||
# Set res_find to empty dict for next step # noqa
|
||||
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', []))
|
||||
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', []))
|
||||
|
||||
# Add users and groups
|
||||
if len(user_add) > 0 or len(group_add) > 0:
|
||||
user_add_args = gen_member_args(args, user_add,
|
||||
group_add)
|
||||
commands.append([name, 'vault_add_member',
|
||||
user_add_args])
|
||||
|
||||
# Remove users and groups
|
||||
if len(user_del) > 0 or len(group_del) > 0:
|
||||
user_del_args = gen_member_args(args, user_del,
|
||||
group_del)
|
||||
commands.append([name, 'vault_remove_member',
|
||||
user_del_args])
|
||||
|
||||
# Add owner users and groups
|
||||
if len(user_add) > 0 or len(group_add) > 0:
|
||||
owner_add_args = gen_member_args(args, owner_add,
|
||||
ownergroups_add)
|
||||
commands.append([name, 'vault_add_owner',
|
||||
owner_add_args])
|
||||
|
||||
# Remove owner users and groups
|
||||
if len(user_del) > 0 or len(group_del) > 0:
|
||||
owner_del_args = gen_member_args(args, owner_del,
|
||||
ownergroups_del)
|
||||
commands.append([name, 'vault_remove_owner',
|
||||
owner_del_args])
|
||||
|
||||
elif action in "member":
|
||||
# Add users and groups
|
||||
if users is not None or groups is not None:
|
||||
user_args = gen_member_args(args, users, groups)
|
||||
commands.append([name, 'vault_add_member', user_args])
|
||||
if owners is not None or ownergroups is not None:
|
||||
owner_args = gen_member_args(args, owners, ownergroups)
|
||||
commands.append([name, 'vault_add_owner', owner_args])
|
||||
|
||||
if vault_data is not None:
|
||||
data_args = data_storage_args(
|
||||
args, args.get('data', ''), password)
|
||||
commands.append([name, 'vault_archive', data_args])
|
||||
|
||||
elif state == "absent":
|
||||
if 'ipavaulttype' in args:
|
||||
del args['ipavaulttype']
|
||||
|
||||
if action == "vault":
|
||||
if res_find is not None:
|
||||
commands.append([name, "vault_del", args])
|
||||
|
||||
elif action == "member":
|
||||
# remove users and groups
|
||||
if users is not None or groups is not None:
|
||||
user_args = gen_member_args(args, users, groups)
|
||||
commands.append([name, 'vault_remove_member',
|
||||
user_args])
|
||||
|
||||
if owners is not None or ownergroups is not None:
|
||||
owner_args = gen_member_args(args, owners, ownergroups)
|
||||
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="Unkown 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']
|
||||
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
|
||||
ansible_module.exit_json(changed=changed, **exit_args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user