Initial commit

This commit is contained in:
Ansible Core Team 2020-03-09 09:11:07 +00:00
commit aebc1b03fd
4861 changed files with 812621 additions and 0 deletions

View file

@ -0,0 +1,142 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Fran Fitzpatrick <francis.x.fitzpatrick@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: ipa_config
author: Fran Fitzpatrick (@fxfitz)
short_description: Manage Global FreeIPA Configuration Settings
description:
- Modify global configuration settings of a FreeIPA Server.
options:
ipadefaultloginshell:
description: Default shell for new users.
aliases: ["loginshell"]
type: str
ipadefaultemaildomain:
description: Default e-mail domain for new users.
aliases: ["emaildomain"]
type: str
extends_documentation_fragment:
- community.general.ipa.documentation
'''
EXAMPLES = r'''
- name: Ensure the default login shell is bash.
ipa_config:
ipadefaultloginshell: /bin/bash
ipa_host: localhost
ipa_user: admin
ipa_pass: supersecret
- name: Ensure the default e-mail domain is ansible.com.
ipa_config:
ipadefaultemaildomain: ansible.com
ipa_host: localhost
ipa_user: admin
ipa_pass: supersecret
'''
RETURN = r'''
config:
description: Configuration as returned by IPA API.
returned: always
type: dict
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils._text import to_native
class ConfigIPAClient(IPAClient):
def __init__(self, module, host, port, protocol):
super(ConfigIPAClient, self).__init__(module, host, port, protocol)
def config_show(self):
return self._post_json(method='config_show', name=None)
def config_mod(self, name, item):
return self._post_json(method='config_mod', name=name, item=item)
def get_config_dict(ipadefaultloginshell=None, ipadefaultemaildomain=None):
config = {}
if ipadefaultloginshell is not None:
config['ipadefaultloginshell'] = ipadefaultloginshell
if ipadefaultemaildomain is not None:
config['ipadefaultemaildomain'] = ipadefaultemaildomain
return config
def get_config_diff(client, ipa_config, module_config):
return client.get_diff(ipa_data=ipa_config, module_data=module_config)
def ensure(module, client):
module_config = get_config_dict(
ipadefaultloginshell=module.params.get('ipadefaultloginshell'),
ipadefaultemaildomain=module.params.get('ipadefaultemaildomain'),
)
ipa_config = client.config_show()
diff = get_config_diff(client, ipa_config, module_config)
changed = False
new_config = {}
for module_key in diff:
if module_config.get(module_key) != ipa_config.get(module_key, None):
changed = True
new_config.update({module_key: module_config.get(module_key)})
if changed and not module.check_mode:
client.config_mod(name=None, item=new_config)
return changed, client.config_show()
def main():
argument_spec = ipa_argument_spec()
argument_spec.update(
ipadefaultloginshell=dict(type='str', aliases=['loginshell']),
ipadefaultemaildomain=dict(type='str', aliases=['emaildomain']),
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
client = ConfigIPAClient(
module=module,
host=module.params['ipa_host'],
port=module.params['ipa_port'],
protocol=module.params['ipa_prot']
)
try:
client.login(
username=module.params['ipa_user'],
password=module.params['ipa_pass']
)
changed, user = ensure(module, client)
module.exit_json(changed=changed, user=user)
except Exception as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -0,0 +1,326 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Abhijeet Kasurde (akasurde@redhat.com)
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = r'''
---
module: ipa_dnsrecord
author: Abhijeet Kasurde (@Akasurde)
short_description: Manage FreeIPA DNS records
description:
- Add, modify and delete an IPA DNS Record using IPA API.
options:
zone_name:
description:
- The DNS zone name to which DNS record needs to be managed.
required: true
type: str
record_name:
description:
- The DNS record name to manage.
required: true
aliases: ["name"]
type: str
record_type:
description:
- The type of DNS record name.
- Currently, 'A', 'AAAA', 'A6', 'CNAME', 'DNAME', 'PTR', 'TXT', 'SRV' and 'MX' are supported.
- "'A6', 'CNAME', 'DNAME' and 'TXT' are added in version 2.5."
- "'SRV' and 'MX' are added in version 2.8."
required: false
default: 'A'
choices: ['A', 'AAAA', 'A6', 'CNAME', 'DNAME', 'MX', 'PTR', 'SRV', 'TXT']
type: str
record_value:
description:
- Manage DNS record name with this value.
- In the case of 'A' or 'AAAA' record types, this will be the IP address.
- In the case of 'A6' record type, this will be the A6 Record data.
- In the case of 'CNAME' record type, this will be the hostname.
- In the case of 'DNAME' record type, this will be the DNAME target.
- In the case of 'PTR' record type, this will be the hostname.
- In the case of 'TXT' record type, this will be a text.
- In the case of 'SRV' record type, this will be a service record.
- In the case of 'MX' record type, this will be a mail exchanger record.
required: true
type: str
record_ttl:
description:
- Set the TTL for the record.
- Applies only when adding a new or changing the value of record_value.
required: false
type: int
state:
description: State to ensure
required: false
default: present
choices: ["absent", "present"]
type: str
extends_documentation_fragment:
- community.general.ipa.documentation
'''
EXAMPLES = r'''
- name: Ensure dns record is present
ipa_dnsrecord:
ipa_host: spider.example.com
ipa_pass: Passw0rd!
state: present
zone_name: example.com
record_name: vm-001
record_type: 'AAAA'
record_value: '::1'
- name: Ensure that dns record exists with a TTL
ipa_dnsrecord:
name: host02
zone_name: example.com
record_type: 'AAAA'
record_value: '::1'
record_ttl: 300
ipa_host: ipa.example.com
ipa_pass: topsecret
state: present
- name: Ensure a PTR record is present
ipa_dnsrecord:
ipa_host: spider.example.com
ipa_pass: Passw0rd!
state: present
zone_name: 2.168.192.in-addr.arpa
record_name: 5
record_type: 'PTR'
record_value: 'internal.ipa.example.com'
- name: Ensure a TXT record is present
ipa_dnsrecord:
ipa_host: spider.example.com
ipa_pass: Passw0rd!
state: present
zone_name: example.com
record_name: _kerberos
record_type: 'TXT'
record_value: 'EXAMPLE.COM'
- name: Ensure an SRV record is present
ipa_dnsrecord:
ipa_host: spider.example.com
ipa_pass: Passw0rd!
state: present
zone_name: example.com
record_name: _kerberos._udp.example.com
record_type: 'SRV'
record_value: '10 50 88 ipa.example.com'
- name: Ensure an MX record is present
ipa_dnsrecord:
ipa_host: spider.example.com
ipa_pass: Passw0rd!
state: present
zone_name: example.com
record_name: '@'
record_type: 'MX'
record_value: '1 mailserver.example.com'
- name: Ensure that dns record is removed
ipa_dnsrecord:
name: host01
zone_name: example.com
record_type: 'AAAA'
record_value: '::1'
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
state: absent
'''
RETURN = r'''
dnsrecord:
description: DNS record as returned by IPA API.
returned: always
type: dict
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils._text import to_native
class DNSRecordIPAClient(IPAClient):
def __init__(self, module, host, port, protocol):
super(DNSRecordIPAClient, self).__init__(module, host, port, protocol)
def dnsrecord_find(self, zone_name, record_name):
if record_name == '@':
return self._post_json(method='dnsrecord_show', name=zone_name, item={'idnsname': record_name, 'all': True})
else:
return self._post_json(method='dnsrecord_find', name=zone_name, item={'idnsname': record_name, 'all': True})
def dnsrecord_add(self, zone_name=None, record_name=None, details=None):
item = dict(idnsname=record_name)
if details['record_type'] == 'A':
item.update(a_part_ip_address=details['record_value'])
elif details['record_type'] == 'AAAA':
item.update(aaaa_part_ip_address=details['record_value'])
elif details['record_type'] == 'A6':
item.update(a6_part_data=details['record_value'])
elif details['record_type'] == 'CNAME':
item.update(cname_part_hostname=details['record_value'])
elif details['record_type'] == 'DNAME':
item.update(dname_part_target=details['record_value'])
elif details['record_type'] == 'PTR':
item.update(ptr_part_hostname=details['record_value'])
elif details['record_type'] == 'TXT':
item.update(txtrecord=details['record_value'])
elif details['record_type'] == 'SRV':
item.update(srvrecord=details['record_value'])
elif details['record_type'] == 'MX':
item.update(mxrecord=details['record_value'])
if details.get('record_ttl'):
item.update(dnsttl=details['record_ttl'])
return self._post_json(method='dnsrecord_add', name=zone_name, item=item)
def dnsrecord_mod(self, zone_name=None, record_name=None, details=None):
item = get_dnsrecord_dict(details)
item.update(idnsname=record_name)
if details.get('record_ttl'):
item.update(dnsttl=details['record_ttl'])
return self._post_json(method='dnsrecord_mod', name=zone_name, item=item)
def dnsrecord_del(self, zone_name=None, record_name=None, details=None):
item = get_dnsrecord_dict(details)
item.update(idnsname=record_name)
return self._post_json(method='dnsrecord_del', name=zone_name, item=item)
def get_dnsrecord_dict(details=None):
module_dnsrecord = dict()
if details['record_type'] == 'A' and details['record_value']:
module_dnsrecord.update(arecord=details['record_value'])
elif details['record_type'] == 'AAAA' and details['record_value']:
module_dnsrecord.update(aaaarecord=details['record_value'])
elif details['record_type'] == 'A6' and details['record_value']:
module_dnsrecord.update(a6record=details['record_value'])
elif details['record_type'] == 'CNAME' and details['record_value']:
module_dnsrecord.update(cnamerecord=details['record_value'])
elif details['record_type'] == 'DNAME' and details['record_value']:
module_dnsrecord.update(dnamerecord=details['record_value'])
elif details['record_type'] == 'PTR' and details['record_value']:
module_dnsrecord.update(ptrrecord=details['record_value'])
elif details['record_type'] == 'TXT' and details['record_value']:
module_dnsrecord.update(txtrecord=details['record_value'])
elif details['record_type'] == 'SRV' and details['record_value']:
module_dnsrecord.update(srvrecord=details['record_value'])
elif details['record_type'] == 'MX' and details['record_value']:
module_dnsrecord.update(mxrecord=details['record_value'])
if details.get('record_ttl'):
module_dnsrecord.update(dnsttl=details['record_ttl'])
return module_dnsrecord
def get_dnsrecord_diff(client, ipa_dnsrecord, module_dnsrecord):
details = get_dnsrecord_dict(module_dnsrecord)
return client.get_diff(ipa_data=ipa_dnsrecord, module_data=details)
def ensure(module, client):
zone_name = module.params['zone_name']
record_name = module.params['record_name']
record_ttl = module.params.get('record_ttl')
state = module.params['state']
ipa_dnsrecord = client.dnsrecord_find(zone_name, record_name)
module_dnsrecord = dict(
record_type=module.params['record_type'],
record_value=module.params['record_value'],
record_ttl=to_native(record_ttl, nonstring='passthru'),
)
# ttl is not required to change records
if module_dnsrecord['record_ttl'] is None:
module_dnsrecord.pop('record_ttl')
changed = False
if state == 'present':
if not ipa_dnsrecord:
changed = True
if not module.check_mode:
client.dnsrecord_add(zone_name=zone_name,
record_name=record_name,
details=module_dnsrecord)
else:
diff = get_dnsrecord_diff(client, ipa_dnsrecord, module_dnsrecord)
if len(diff) > 0:
changed = True
if not module.check_mode:
client.dnsrecord_mod(zone_name=zone_name,
record_name=record_name,
details=module_dnsrecord)
else:
if ipa_dnsrecord:
changed = True
if not module.check_mode:
client.dnsrecord_del(zone_name=zone_name,
record_name=record_name,
details=module_dnsrecord)
return changed, client.dnsrecord_find(zone_name, record_name)
def main():
record_types = ['A', 'AAAA', 'A6', 'CNAME', 'DNAME', 'PTR', 'TXT', 'SRV', 'MX']
argument_spec = ipa_argument_spec()
argument_spec.update(
zone_name=dict(type='str', required=True),
record_name=dict(type='str', aliases=['name'], required=True),
record_type=dict(type='str', default='A', choices=record_types),
record_value=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['present', 'absent']),
record_ttl=dict(type='int', required=False),
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
client = DNSRecordIPAClient(
module=module,
host=module.params['ipa_host'],
port=module.params['ipa_port'],
protocol=module.params['ipa_prot']
)
try:
client.login(
username=module.params['ipa_user'],
password=module.params['ipa_pass']
)
changed, record = ensure(module, client)
module.exit_json(changed=changed, record=record)
except Exception as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -0,0 +1,167 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Fran Fitzpatrick (francis.x.fitzpatrick@gmail.com)
# Borrowed heavily from other work by Abhijeet Kasurde (akasurde@redhat.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: ipa_dnszone
author: Fran Fitzpatrick (@fxfitz)
short_description: Manage FreeIPA DNS Zones
description:
- Add and delete an IPA DNS Zones using IPA API
options:
zone_name:
description:
- The DNS zone name to which needs to be managed.
required: true
type: str
state:
description: State to ensure
required: false
default: present
choices: ["absent", "present"]
type: str
dynamicupdate:
description: Apply dynamic update to zone
required: false
default: "false"
choices: ["false", "true"]
type: str
extends_documentation_fragment:
- community.general.ipa.documentation
'''
EXAMPLES = r'''
- name: Ensure dns zone is present
ipa_dnszone:
ipa_host: spider.example.com
ipa_pass: Passw0rd!
state: present
zone_name: example.com
- name: Ensure dns zone is present and is dynamic update
ipa_dnszone:
ipa_host: spider.example.com
ipa_pass: Passw0rd!
state: present
zone_name: example.com
dynamicupdate: true
- name: Ensure that dns zone is removed
ipa_dnszone:
zone_name: example.com
ipa_host: localhost
ipa_user: admin
ipa_pass: topsecret
state: absent
'''
RETURN = r'''
zone:
description: DNS zone as returned by IPA API.
returned: always
type: dict
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils._text import to_native
class DNSZoneIPAClient(IPAClient):
def __init__(self, module, host, port, protocol):
super(DNSZoneIPAClient, self).__init__(module, host, port, protocol)
def dnszone_find(self, zone_name, details=None):
itens = {'idnsname': zone_name}
if details is not None:
itens.update(details)
return self._post_json(
method='dnszone_find',
name=zone_name,
item=itens
)
def dnszone_add(self, zone_name=None, details=None):
itens = {}
if details is not None:
itens.update(details)
return self._post_json(
method='dnszone_add',
name=zone_name,
item=itens
)
def dnszone_del(self, zone_name=None, record_name=None, details=None):
return self._post_json(
method='dnszone_del', name=zone_name, item={})
def ensure(module, client):
zone_name = module.params['zone_name']
state = module.params['state']
dynamicupdate = module.params['dynamicupdate']
ipa_dnszone = client.dnszone_find(zone_name)
changed = False
if state == 'present':
if not ipa_dnszone:
changed = True
if not module.check_mode:
client.dnszone_add(zone_name=zone_name, details={'idnsallowdynupdate': dynamicupdate})
else:
changed = False
else:
if ipa_dnszone:
changed = True
if not module.check_mode:
client.dnszone_del(zone_name=zone_name)
return changed, client.dnszone_find(zone_name)
def main():
argument_spec = ipa_argument_spec()
argument_spec.update(zone_name=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['present', 'absent']),
dynamicupdate=dict(type='str', required=False, default='false', choices=['true', 'false']),
)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True,
)
client = DNSZoneIPAClient(
module=module,
host=module.params['ipa_host'],
port=module.params['ipa_port'],
protocol=module.params['ipa_prot']
)
try:
client.login(
username=module.params['ipa_user'],
password=module.params['ipa_pass']
)
changed, zone = ensure(module, client)
module.exit_json(changed=changed, zone=zone)
except Exception as e:
module.fail_json(msg=to_native(e))
if __name__ == '__main__':
main()

View file

@ -0,0 +1,263 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: ipa_group
author: Thomas Krahn (@Nosmoht)
short_description: Manage FreeIPA group
description:
- Add, modify and delete group within IPA server
options:
cn:
description:
- Canonical name.
- Can not be changed as it is the unique identifier.
required: true
aliases: ['name']
type: str
description:
description:
- Description of the group.
type: str
external:
description:
- Allow adding external non-IPA members from trusted domains.
type: bool
gidnumber:
description:
- GID (use this option to set it manually).
aliases: ['gid']
type: str
group:
description:
- List of group names assigned to this group.
- If an empty list is passed all groups will be removed from this group.
- If option is omitted assigned groups will not be checked or changed.
- Groups that are already assigned but not passed will be removed.
type: list
elements: str
nonposix:
description:
- Create as a non-POSIX group.
type: bool
user:
description:
- List of user names assigned to this group.
- If an empty list is passed all users will be removed from this group.
- If option is omitted assigned users will not be checked or changed.
- Users that are already assigned but not passed will be removed.
type: list
elements: str
state:
description:
- State to ensure
default: "present"
choices: ["absent", "present"]
type: str
extends_documentation_fragment:
- community.general.ipa.documentation
'''
EXAMPLES = r'''
- name: Ensure group is present
ipa_group:
name: oinstall
gidnumber: 54321
state: present
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure that groups sysops and appops are assigned to ops but no other group
ipa_group:
name: ops
group:
- sysops
- appops
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure that users linus and larry are assign to the group, but no other user
ipa_group:
name: sysops
user:
- linus
- larry
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure group is absent
ipa_group:
name: sysops
state: absent
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
'''
RETURN = r'''
group:
description: Group as returned by IPA API
returned: always
type: dict
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils._text import to_native
class GroupIPAClient(IPAClient):
def __init__(self, module, host, port, protocol):
super(GroupIPAClient, self).__init__(module, host, port, protocol)
def group_find(self, name):
return self._post_json(method='group_find', name=None, item={'all': True, 'cn': name})
def group_add(self, name, item):
return self._post_json(method='group_add', name=name, item=item)
def group_mod(self, name, item):
return self._post_json(method='group_mod', name=name, item=item)
def group_del(self, name):
return self._post_json(method='group_del', name=name)
def group_add_member(self, name, item):
return self._post_json(method='group_add_member', name=name, item=item)
def group_add_member_group(self, name, item):
return self.group_add_member(name=name, item={'group': item})
def group_add_member_user(self, name, item):
return self.group_add_member(name=name, item={'user': item})
def group_remove_member(self, name, item):
return self._post_json(method='group_remove_member', name=name, item=item)
def group_remove_member_group(self, name, item):
return self.group_remove_member(name=name, item={'group': item})
def group_remove_member_user(self, name, item):
return self.group_remove_member(name=name, item={'user': item})
def get_group_dict(description=None, external=None, gid=None, nonposix=None):
group = {}
if description is not None:
group['description'] = description
if external is not None:
group['external'] = external
if gid is not None:
group['gidnumber'] = gid
if nonposix is not None:
group['nonposix'] = nonposix
return group
def get_group_diff(client, ipa_group, module_group):
data = []
# With group_add attribute nonposix is passed, whereas with group_mod only posix can be passed.
if 'nonposix' in module_group:
# Only non-posix groups can be changed to posix
if not module_group['nonposix'] and ipa_group.get('nonposix'):
module_group['posix'] = True
del module_group['nonposix']
if 'external' in module_group:
if module_group['external'] and 'ipaexternalgroup' in ipa_group.get('objectclass'):
del module_group['external']
return client.get_diff(ipa_data=ipa_group, module_data=module_group)
def ensure(module, client):
state = module.params['state']
name = module.params['cn']
group = module.params['group']
user = module.params['user']
module_group = get_group_dict(description=module.params['description'], external=module.params['external'],
gid=module.params['gidnumber'], nonposix=module.params['nonposix'])
ipa_group = client.group_find(name=name)
changed = False
if state == 'present':
if not ipa_group:
changed = True
if not module.check_mode:
ipa_group = client.group_add(name, item=module_group)
else:
diff = get_group_diff(client, ipa_group, module_group)
if len(diff) > 0:
changed = True
if not module.check_mode:
data = {}
for key in diff:
data[key] = module_group.get(key)
client.group_mod(name=name, item=data)
if group is not None:
changed = client.modify_if_diff(name, ipa_group.get('member_group', []), group,
client.group_add_member_group,
client.group_remove_member_group) or changed
if user is not None:
changed = client.modify_if_diff(name, ipa_group.get('member_user', []), user,
client.group_add_member_user,
client.group_remove_member_user) or changed
else:
if ipa_group:
changed = True
if not module.check_mode:
client.group_del(name)
return changed, client.group_find(name=name)
def main():
argument_spec = ipa_argument_spec()
argument_spec.update(cn=dict(type='str', required=True, aliases=['name']),
description=dict(type='str'),
external=dict(type='bool'),
gidnumber=dict(type='str', aliases=['gid']),
group=dict(type='list', elements='str'),
nonposix=dict(type='bool'),
state=dict(type='str', default='present', choices=['present', 'absent']),
user=dict(type='list', elements='str'))
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True,
)
client = GroupIPAClient(module=module,
host=module.params['ipa_host'],
port=module.params['ipa_port'],
protocol=module.params['ipa_prot'])
try:
client.login(username=module.params['ipa_user'],
password=module.params['ipa_pass'])
changed, group = ensure(module, client)
module.exit_json(changed=changed, group=group)
except Exception as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -0,0 +1,359 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: ipa_hbacrule
author: Thomas Krahn (@Nosmoht)
short_description: Manage FreeIPA HBAC rule
description:
- Add, modify or delete an IPA HBAC rule using IPA API.
options:
cn:
description:
- Canonical name.
- Can not be changed as it is the unique identifier.
required: true
aliases: ["name"]
type: str
description:
description: Description
type: str
host:
description:
- List of host names to assign.
- If an empty list is passed all hosts will be removed from the rule.
- If option is omitted hosts will not be checked or changed.
required: false
type: list
elements: str
hostcategory:
description: Host category
choices: ['all']
type: str
hostgroup:
description:
- List of hostgroup names to assign.
- If an empty list is passed all hostgroups will be removed. from the rule
- If option is omitted hostgroups will not be checked or changed.
type: list
elements: str
service:
description:
- List of service names to assign.
- If an empty list is passed all services will be removed from the rule.
- If option is omitted services will not be checked or changed.
type: list
elements: str
servicecategory:
description: Service category
choices: ['all']
type: str
servicegroup:
description:
- List of service group names to assign.
- If an empty list is passed all assigned service groups will be removed from the rule.
- If option is omitted service groups will not be checked or changed.
type: list
elements: str
sourcehost:
description:
- List of source host names to assign.
- If an empty list if passed all assigned source hosts will be removed from the rule.
- If option is omitted source hosts will not be checked or changed.
type: list
elements: str
sourcehostcategory:
description: Source host category
choices: ['all']
type: str
sourcehostgroup:
description:
- List of source host group names to assign.
- If an empty list if passed all assigned source host groups will be removed from the rule.
- If option is omitted source host groups will not be checked or changed.
type: list
elements: str
state:
description: State to ensure
default: "present"
choices: ["absent", "disabled", "enabled","present"]
type: str
user:
description:
- List of user names to assign.
- If an empty list if passed all assigned users will be removed from the rule.
- If option is omitted users will not be checked or changed.
type: list
elements: str
usercategory:
description: User category
choices: ['all']
type: str
usergroup:
description:
- List of user group names to assign.
- If an empty list if passed all assigned user groups will be removed from the rule.
- If option is omitted user groups will not be checked or changed.
type: list
extends_documentation_fragment:
- community.general.ipa.documentation
'''
EXAMPLES = r'''
- name: Ensure rule to allow all users to access any host from any host
ipa_hbacrule:
name: allow_all
description: Allow all users to access any host from any host
hostcategory: all
servicecategory: all
usercategory: all
state: present
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure rule with certain limitations
ipa_hbacrule:
name: allow_all_developers_access_to_db
description: Allow all developers to access any database from any host
hostgroup:
- db-server
usergroup:
- developers
state: present
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure rule is absent
ipa_hbacrule:
name: rule_to_be_deleted
state: absent
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
'''
RETURN = r'''
hbacrule:
description: HBAC rule as returned by IPA API.
returned: always
type: dict
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils._text import to_native
class HBACRuleIPAClient(IPAClient):
def __init__(self, module, host, port, protocol):
super(HBACRuleIPAClient, self).__init__(module, host, port, protocol)
def hbacrule_find(self, name):
return self._post_json(method='hbacrule_find', name=None, item={'all': True, 'cn': name})
def hbacrule_add(self, name, item):
return self._post_json(method='hbacrule_add', name=name, item=item)
def hbacrule_mod(self, name, item):
return self._post_json(method='hbacrule_mod', name=name, item=item)
def hbacrule_del(self, name):
return self._post_json(method='hbacrule_del', name=name)
def hbacrule_add_host(self, name, item):
return self._post_json(method='hbacrule_add_host', name=name, item=item)
def hbacrule_remove_host(self, name, item):
return self._post_json(method='hbacrule_remove_host', name=name, item=item)
def hbacrule_add_service(self, name, item):
return self._post_json(method='hbacrule_add_service', name=name, item=item)
def hbacrule_remove_service(self, name, item):
return self._post_json(method='hbacrule_remove_service', name=name, item=item)
def hbacrule_add_user(self, name, item):
return self._post_json(method='hbacrule_add_user', name=name, item=item)
def hbacrule_remove_user(self, name, item):
return self._post_json(method='hbacrule_remove_user', name=name, item=item)
def hbacrule_add_sourcehost(self, name, item):
return self._post_json(method='hbacrule_add_sourcehost', name=name, item=item)
def hbacrule_remove_sourcehost(self, name, item):
return self._post_json(method='hbacrule_remove_sourcehost', name=name, item=item)
def get_hbacrule_dict(description=None, hostcategory=None, ipaenabledflag=None, servicecategory=None,
sourcehostcategory=None,
usercategory=None):
data = {}
if description is not None:
data['description'] = description
if hostcategory is not None:
data['hostcategory'] = hostcategory
if ipaenabledflag is not None:
data['ipaenabledflag'] = ipaenabledflag
if servicecategory is not None:
data['servicecategory'] = servicecategory
if sourcehostcategory is not None:
data['sourcehostcategory'] = sourcehostcategory
if usercategory is not None:
data['usercategory'] = usercategory
return data
def get_hbcarule_diff(client, ipa_hbcarule, module_hbcarule):
return client.get_diff(ipa_data=ipa_hbcarule, module_data=module_hbcarule)
def ensure(module, client):
name = module.params['cn']
state = module.params['state']
if state in ['present', 'enabled']:
ipaenabledflag = 'TRUE'
else:
ipaenabledflag = 'FALSE'
host = module.params['host']
hostcategory = module.params['hostcategory']
hostgroup = module.params['hostgroup']
service = module.params['service']
servicecategory = module.params['servicecategory']
servicegroup = module.params['servicegroup']
sourcehost = module.params['sourcehost']
sourcehostcategory = module.params['sourcehostcategory']
sourcehostgroup = module.params['sourcehostgroup']
user = module.params['user']
usercategory = module.params['usercategory']
usergroup = module.params['usergroup']
module_hbacrule = get_hbacrule_dict(description=module.params['description'],
hostcategory=hostcategory,
ipaenabledflag=ipaenabledflag,
servicecategory=servicecategory,
sourcehostcategory=sourcehostcategory,
usercategory=usercategory)
ipa_hbacrule = client.hbacrule_find(name=name)
changed = False
if state in ['present', 'enabled', 'disabled']:
if not ipa_hbacrule:
changed = True
if not module.check_mode:
ipa_hbacrule = client.hbacrule_add(name=name, item=module_hbacrule)
else:
diff = get_hbcarule_diff(client, ipa_hbacrule, module_hbacrule)
if len(diff) > 0:
changed = True
if not module.check_mode:
data = {}
for key in diff:
data[key] = module_hbacrule.get(key)
client.hbacrule_mod(name=name, item=data)
if host is not None:
changed = client.modify_if_diff(name, ipa_hbacrule.get('memberhost_host', []), host,
client.hbacrule_add_host,
client.hbacrule_remove_host, 'host') or changed
if hostgroup is not None:
changed = client.modify_if_diff(name, ipa_hbacrule.get('memberhost_hostgroup', []), hostgroup,
client.hbacrule_add_host,
client.hbacrule_remove_host, 'hostgroup') or changed
if service is not None:
changed = client.modify_if_diff(name, ipa_hbacrule.get('memberservice_hbacsvc', []), service,
client.hbacrule_add_service,
client.hbacrule_remove_service, 'hbacsvc') or changed
if servicegroup is not None:
changed = client.modify_if_diff(name, ipa_hbacrule.get('memberservice_hbacsvcgroup', []),
servicegroup,
client.hbacrule_add_service,
client.hbacrule_remove_service, 'hbacsvcgroup') or changed
if sourcehost is not None:
changed = client.modify_if_diff(name, ipa_hbacrule.get('sourcehost_host', []), sourcehost,
client.hbacrule_add_sourcehost,
client.hbacrule_remove_sourcehost, 'host') or changed
if sourcehostgroup is not None:
changed = client.modify_if_diff(name, ipa_hbacrule.get('sourcehost_group', []), sourcehostgroup,
client.hbacrule_add_sourcehost,
client.hbacrule_remove_sourcehost, 'hostgroup') or changed
if user is not None:
changed = client.modify_if_diff(name, ipa_hbacrule.get('memberuser_user', []), user,
client.hbacrule_add_user,
client.hbacrule_remove_user, 'user') or changed
if usergroup is not None:
changed = client.modify_if_diff(name, ipa_hbacrule.get('memberuser_group', []), usergroup,
client.hbacrule_add_user,
client.hbacrule_remove_user, 'group') or changed
else:
if ipa_hbacrule:
changed = True
if not module.check_mode:
client.hbacrule_del(name=name)
return changed, client.hbacrule_find(name=name)
def main():
argument_spec = ipa_argument_spec()
argument_spec.update(cn=dict(type='str', required=True, aliases=['name']),
description=dict(type='str'),
host=dict(type='list', elements='str'),
hostcategory=dict(type='str', choices=['all']),
hostgroup=dict(type='list', elements='str'),
service=dict(type='list', elements='str'),
servicecategory=dict(type='str', choices=['all']),
servicegroup=dict(type='list', elements='str'),
sourcehost=dict(type='list', elements='str'),
sourcehostcategory=dict(type='str', choices=['all']),
sourcehostgroup=dict(type='list', elements='str'),
state=dict(type='str', default='present', choices=['present', 'absent', 'enabled', 'disabled']),
user=dict(type='list', elements='str'),
usercategory=dict(type='str', choices=['all']),
usergroup=dict(type='list', elements='str'))
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True
)
client = HBACRuleIPAClient(module=module,
host=module.params['ipa_host'],
port=module.params['ipa_port'],
protocol=module.params['ipa_prot'])
try:
client.login(username=module.params['ipa_user'],
password=module.params['ipa_pass'])
changed, hbacrule = ensure(module, client)
module.exit_json(changed=changed, hbacrule=hbacrule)
except Exception as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -0,0 +1,312 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: ipa_host
author: Thomas Krahn (@Nosmoht)
short_description: Manage FreeIPA host
description:
- Add, modify and delete an IPA host using IPA API.
options:
fqdn:
description:
- Full qualified domain name.
- Can not be changed as it is the unique identifier.
required: true
aliases: ["name"]
type: str
description:
description:
- A description of this host.
type: str
force:
description:
- Force host name even if not in DNS.
required: false
type: bool
ip_address:
description:
- Add the host to DNS with this IP address.
type: str
mac_address:
description:
- List of Hardware MAC address(es) off this host.
- If option is omitted MAC addresses will not be checked or changed.
- If an empty list is passed all assigned MAC addresses will be removed.
- MAC addresses that are already assigned but not passed will be removed.
aliases: ["macaddress"]
type: list
elements: str
ns_host_location:
description:
- Host location (e.g. "Lab 2")
aliases: ["nshostlocation"]
type: str
ns_hardware_platform:
description:
- Host hardware platform (e.g. "Lenovo T61")
aliases: ["nshardwareplatform"]
type: str
ns_os_version:
description:
- Host operating system and version (e.g. "Fedora 9")
aliases: ["nsosversion"]
type: str
user_certificate:
description:
- List of Base-64 encoded server certificates.
- If option is omitted certificates will not be checked or changed.
- If an empty list is passed all assigned certificates will be removed.
- Certificates already assigned but not passed will be removed.
aliases: ["usercertificate"]
type: list
elements: str
state:
description: State to ensure.
default: present
choices: ["absent", "disabled", "enabled", "present"]
type: str
update_dns:
description:
- If set C("True") with state as C("absent"), then removes DNS records of the host managed by FreeIPA DNS.
- This option has no effect for states other than "absent".
default: false
type: bool
random_password:
description: Generate a random password to be used in bulk enrollment.
default: False
type: bool
extends_documentation_fragment:
- community.general.ipa.documentation
'''
EXAMPLES = r'''
- name: Ensure host is present
ipa_host:
name: host01.example.com
description: Example host
ip_address: 192.168.0.123
ns_host_location: Lab
ns_os_version: CentOS 7
ns_hardware_platform: Lenovo T61
mac_address:
- "08:00:27:E3:B1:2D"
- "52:54:00:BD:97:1E"
state: present
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Generate a random password for bulk enrolment
ipa_host:
name: host01.example.com
description: Example host
ip_address: 192.168.0.123
state: present
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
validate_certs: False
random_password: True
- name: Ensure host is disabled
ipa_host:
name: host01.example.com
state: disabled
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure that all user certificates are removed
ipa_host:
name: host01.example.com
user_certificate: []
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure host is absent
ipa_host:
name: host01.example.com
state: absent
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure host and its DNS record is absent
ipa_host:
name: host01.example.com
state: absent
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
update_dns: True
'''
RETURN = r'''
host:
description: Host as returned by IPA API.
returned: always
type: dict
host_diff:
description: List of options that differ and would be changed
returned: if check mode and a difference is found
type: list
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils._text import to_native
class HostIPAClient(IPAClient):
def __init__(self, module, host, port, protocol):
super(HostIPAClient, self).__init__(module, host, port, protocol)
def host_show(self, name):
return self._post_json(method='host_show', name=name)
def host_find(self, name):
return self._post_json(method='host_find', name=None, item={'all': True, 'fqdn': name})
def host_add(self, name, host):
return self._post_json(method='host_add', name=name, item=host)
def host_mod(self, name, host):
return self._post_json(method='host_mod', name=name, item=host)
def host_del(self, name, update_dns):
return self._post_json(method='host_del', name=name, item={'updatedns': update_dns})
def host_disable(self, name):
return self._post_json(method='host_disable', name=name)
def get_host_dict(description=None, force=None, ip_address=None, ns_host_location=None, ns_hardware_platform=None,
ns_os_version=None, user_certificate=None, mac_address=None, random_password=None):
data = {}
if description is not None:
data['description'] = description
if force is not None:
data['force'] = force
if ip_address is not None:
data['ip_address'] = ip_address
if ns_host_location is not None:
data['nshostlocation'] = ns_host_location
if ns_hardware_platform is not None:
data['nshardwareplatform'] = ns_hardware_platform
if ns_os_version is not None:
data['nsosversion'] = ns_os_version
if user_certificate is not None:
data['usercertificate'] = [{"__base64__": item} for item in user_certificate]
if mac_address is not None:
data['macaddress'] = mac_address
if random_password is not None:
data['random'] = random_password
return data
def get_host_diff(client, ipa_host, module_host):
non_updateable_keys = ['force', 'ip_address']
if not module_host.get('random'):
non_updateable_keys.append('random')
for key in non_updateable_keys:
if key in module_host:
del module_host[key]
return client.get_diff(ipa_data=ipa_host, module_data=module_host)
def ensure(module, client):
name = module.params['fqdn']
state = module.params['state']
ipa_host = client.host_find(name=name)
module_host = get_host_dict(description=module.params['description'],
force=module.params['force'], ip_address=module.params['ip_address'],
ns_host_location=module.params['ns_host_location'],
ns_hardware_platform=module.params['ns_hardware_platform'],
ns_os_version=module.params['ns_os_version'],
user_certificate=module.params['user_certificate'],
mac_address=module.params['mac_address'],
random_password=module.params.get('random_password'),
)
changed = False
if state in ['present', 'enabled', 'disabled']:
if not ipa_host:
changed = True
if not module.check_mode:
# OTP password generated by FreeIPA is visible only for host_add command
# so, return directly from here.
return changed, client.host_add(name=name, host=module_host)
else:
diff = get_host_diff(client, ipa_host, module_host)
if len(diff) > 0:
changed = True
if not module.check_mode:
data = {}
for key in diff:
data[key] = module_host.get(key)
ipa_host_show = client.host_show(name=name)
if ipa_host_show.get('has_keytab', False) and module.params.get('random_password'):
client.host_disable(name=name)
return changed, client.host_mod(name=name, host=data)
else:
if ipa_host:
changed = True
update_dns = module.params.get('update_dns', False)
if not module.check_mode:
client.host_del(name=name, update_dns=update_dns)
return changed, client.host_find(name=name)
def main():
argument_spec = ipa_argument_spec()
argument_spec.update(description=dict(type='str'),
fqdn=dict(type='str', required=True, aliases=['name']),
force=dict(type='bool'),
ip_address=dict(type='str'),
ns_host_location=dict(type='str', aliases=['nshostlocation']),
ns_hardware_platform=dict(type='str', aliases=['nshardwareplatform']),
ns_os_version=dict(type='str', aliases=['nsosversion']),
user_certificate=dict(type='list', aliases=['usercertificate'], elements='str'),
mac_address=dict(type='list', aliases=['macaddress'], elements='str'),
update_dns=dict(type='bool'),
state=dict(type='str', default='present', choices=['present', 'absent', 'enabled', 'disabled']),
random_password=dict(type='bool'),)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
client = HostIPAClient(module=module,
host=module.params['ipa_host'],
port=module.params['ipa_port'],
protocol=module.params['ipa_prot'])
try:
client.login(username=module.params['ipa_user'],
password=module.params['ipa_pass'])
changed, host = ensure(module, client)
module.exit_json(changed=changed, host=host)
except Exception as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -0,0 +1,213 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: ipa_hostgroup
author: Thomas Krahn (@Nosmoht)
short_description: Manage FreeIPA host-group
description:
- Add, modify and delete an IPA host-group using IPA API.
options:
cn:
description:
- Name of host-group.
- Can not be changed as it is the unique identifier.
required: true
aliases: ["name"]
type: str
description:
description:
- Description.
type: str
host:
description:
- List of hosts that belong to the host-group.
- If an empty list is passed all hosts will be removed from the group.
- If option is omitted hosts will not be checked or changed.
- If option is passed all assigned hosts that are not passed will be unassigned from the group.
type: list
elements: str
hostgroup:
description:
- List of host-groups than belong to that host-group.
- If an empty list is passed all host-groups will be removed from the group.
- If option is omitted host-groups will not be checked or changed.
- If option is passed all assigned hostgroups that are not passed will be unassigned from the group.
type: list
elements: str
state:
description:
- State to ensure.
default: "present"
choices: ["absent", "disabled", "enabled", "present"]
type: str
extends_documentation_fragment:
- community.general.ipa.documentation
'''
EXAMPLES = r'''
- name: Ensure host-group databases is present
ipa_hostgroup:
name: databases
state: present
host:
- db.example.com
hostgroup:
- mysql-server
- oracle-server
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure host-group databases is absent
ipa_hostgroup:
name: databases
state: absent
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
'''
RETURN = r'''
hostgroup:
description: Hostgroup as returned by IPA API.
returned: always
type: dict
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils._text import to_native
class HostGroupIPAClient(IPAClient):
def __init__(self, module, host, port, protocol):
super(HostGroupIPAClient, self).__init__(module, host, port, protocol)
def hostgroup_find(self, name):
return self._post_json(method='hostgroup_find', name=None, item={'all': True, 'cn': name})
def hostgroup_add(self, name, item):
return self._post_json(method='hostgroup_add', name=name, item=item)
def hostgroup_mod(self, name, item):
return self._post_json(method='hostgroup_mod', name=name, item=item)
def hostgroup_del(self, name):
return self._post_json(method='hostgroup_del', name=name)
def hostgroup_add_member(self, name, item):
return self._post_json(method='hostgroup_add_member', name=name, item=item)
def hostgroup_add_host(self, name, item):
return self.hostgroup_add_member(name=name, item={'host': item})
def hostgroup_add_hostgroup(self, name, item):
return self.hostgroup_add_member(name=name, item={'hostgroup': item})
def hostgroup_remove_member(self, name, item):
return self._post_json(method='hostgroup_remove_member', name=name, item=item)
def hostgroup_remove_host(self, name, item):
return self.hostgroup_remove_member(name=name, item={'host': item})
def hostgroup_remove_hostgroup(self, name, item):
return self.hostgroup_remove_member(name=name, item={'hostgroup': item})
def get_hostgroup_dict(description=None):
data = {}
if description is not None:
data['description'] = description
return data
def get_hostgroup_diff(client, ipa_hostgroup, module_hostgroup):
return client.get_diff(ipa_data=ipa_hostgroup, module_data=module_hostgroup)
def ensure(module, client):
name = module.params['cn']
state = module.params['state']
host = module.params['host']
hostgroup = module.params['hostgroup']
ipa_hostgroup = client.hostgroup_find(name=name)
module_hostgroup = get_hostgroup_dict(description=module.params['description'])
changed = False
if state == 'present':
if not ipa_hostgroup:
changed = True
if not module.check_mode:
ipa_hostgroup = client.hostgroup_add(name=name, item=module_hostgroup)
else:
diff = get_hostgroup_diff(client, ipa_hostgroup, module_hostgroup)
if len(diff) > 0:
changed = True
if not module.check_mode:
data = {}
for key in diff:
data[key] = module_hostgroup.get(key)
client.hostgroup_mod(name=name, item=data)
if host is not None:
changed = client.modify_if_diff(name, ipa_hostgroup.get('member_host', []), [item.lower() for item in host],
client.hostgroup_add_host, client.hostgroup_remove_host) or changed
if hostgroup is not None:
changed = client.modify_if_diff(name, ipa_hostgroup.get('member_hostgroup', []),
[item.lower() for item in hostgroup],
client.hostgroup_add_hostgroup,
client.hostgroup_remove_hostgroup) or changed
else:
if ipa_hostgroup:
changed = True
if not module.check_mode:
client.hostgroup_del(name=name)
return changed, client.hostgroup_find(name=name)
def main():
argument_spec = ipa_argument_spec()
argument_spec.update(cn=dict(type='str', required=True, aliases=['name']),
description=dict(type='str'),
host=dict(type='list', elements='str'),
hostgroup=dict(type='list', elements='str'),
state=dict(type='str', default='present', choices=['present', 'absent', 'enabled', 'disabled']))
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
client = HostGroupIPAClient(module=module,
host=module.params['ipa_host'],
port=module.params['ipa_port'],
protocol=module.params['ipa_prot'])
try:
client.login(username=module.params['ipa_user'],
password=module.params['ipa_pass'])
changed, hostgroup = ensure(module, client)
module.exit_json(changed=changed, hostgroup=hostgroup)
except Exception as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -0,0 +1,307 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: ipa_role
author: Thomas Krahn (@Nosmoht)
short_description: Manage FreeIPA role
description:
- Add, modify and delete a role within FreeIPA server using FreeIPA API.
options:
cn:
description:
- Role name.
- Can not be changed as it is the unique identifier.
required: true
aliases: ['name']
type: str
description:
description:
- A description of this role-group.
type: str
group:
description:
- List of group names assign to this role.
- If an empty list is passed all assigned groups will be unassigned from the role.
- If option is omitted groups will not be checked or changed.
- If option is passed all assigned groups that are not passed will be unassigned from the role.
type: list
elements: str
host:
description:
- List of host names to assign.
- If an empty list is passed all assigned hosts will be unassigned from the role.
- If option is omitted hosts will not be checked or changed.
- If option is passed all assigned hosts that are not passed will be unassigned from the role.
type: list
elements: str
hostgroup:
description:
- List of host group names to assign.
- If an empty list is passed all assigned host groups will be removed from the role.
- If option is omitted host groups will not be checked or changed.
- If option is passed all assigned hostgroups that are not passed will be unassigned from the role.
type: list
elements: str
privilege:
description:
- List of privileges granted to the role.
- If an empty list is passed all assigned privileges will be removed.
- If option is omitted privileges will not be checked or changed.
- If option is passed all assigned privileges that are not passed will be removed.
type: list
elements: str
service:
description:
- List of service names to assign.
- If an empty list is passed all assigned services will be removed from the role.
- If option is omitted services will not be checked or changed.
- If option is passed all assigned services that are not passed will be removed from the role.
type: list
elements: str
state:
description: State to ensure.
default: "present"
choices: ["absent", "present"]
type: str
user:
description:
- List of user names to assign.
- If an empty list is passed all assigned users will be removed from the role.
- If option is omitted users will not be checked or changed.
type: list
elements: str
extends_documentation_fragment:
- community.general.ipa.documentation
'''
EXAMPLES = r'''
- name: Ensure role is present
ipa_role:
name: dba
description: Database Administrators
state: present
user:
- pinky
- brain
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure role with certain details
ipa_role:
name: another-role
description: Just another role
group:
- editors
host:
- host01.example.com
hostgroup:
- hostgroup01
privilege:
- Group Administrators
- User Administrators
service:
- service01
- name: Ensure role is absent
ipa_role:
name: dba
state: absent
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
'''
RETURN = r'''
role:
description: Role as returned by IPA API.
returned: always
type: dict
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils._text import to_native
class RoleIPAClient(IPAClient):
def __init__(self, module, host, port, protocol):
super(RoleIPAClient, self).__init__(module, host, port, protocol)
def role_find(self, name):
return self._post_json(method='role_find', name=None, item={'all': True, 'cn': name})
def role_add(self, name, item):
return self._post_json(method='role_add', name=name, item=item)
def role_mod(self, name, item):
return self._post_json(method='role_mod', name=name, item=item)
def role_del(self, name):
return self._post_json(method='role_del', name=name)
def role_add_member(self, name, item):
return self._post_json(method='role_add_member', name=name, item=item)
def role_add_group(self, name, item):
return self.role_add_member(name=name, item={'group': item})
def role_add_host(self, name, item):
return self.role_add_member(name=name, item={'host': item})
def role_add_hostgroup(self, name, item):
return self.role_add_member(name=name, item={'hostgroup': item})
def role_add_service(self, name, item):
return self.role_add_member(name=name, item={'service': item})
def role_add_user(self, name, item):
return self.role_add_member(name=name, item={'user': item})
def role_remove_member(self, name, item):
return self._post_json(method='role_remove_member', name=name, item=item)
def role_remove_group(self, name, item):
return self.role_remove_member(name=name, item={'group': item})
def role_remove_host(self, name, item):
return self.role_remove_member(name=name, item={'host': item})
def role_remove_hostgroup(self, name, item):
return self.role_remove_member(name=name, item={'hostgroup': item})
def role_remove_service(self, name, item):
return self.role_remove_member(name=name, item={'service': item})
def role_remove_user(self, name, item):
return self.role_remove_member(name=name, item={'user': item})
def role_add_privilege(self, name, item):
return self._post_json(method='role_add_privilege', name=name, item={'privilege': item})
def role_remove_privilege(self, name, item):
return self._post_json(method='role_remove_privilege', name=name, item={'privilege': item})
def get_role_dict(description=None):
data = {}
if description is not None:
data['description'] = description
return data
def get_role_diff(client, ipa_role, module_role):
return client.get_diff(ipa_data=ipa_role, module_data=module_role)
def ensure(module, client):
state = module.params['state']
name = module.params['cn']
group = module.params['group']
host = module.params['host']
hostgroup = module.params['hostgroup']
privilege = module.params['privilege']
service = module.params['service']
user = module.params['user']
module_role = get_role_dict(description=module.params['description'])
ipa_role = client.role_find(name=name)
changed = False
if state == 'present':
if not ipa_role:
changed = True
if not module.check_mode:
ipa_role = client.role_add(name=name, item=module_role)
else:
diff = get_role_diff(client, ipa_role, module_role)
if len(diff) > 0:
changed = True
if not module.check_mode:
data = {}
for key in diff:
data[key] = module_role.get(key)
client.role_mod(name=name, item=data)
if group is not None:
changed = client.modify_if_diff(name, ipa_role.get('member_group', []), group,
client.role_add_group,
client.role_remove_group) or changed
if host is not None:
changed = client.modify_if_diff(name, ipa_role.get('member_host', []), host,
client.role_add_host,
client.role_remove_host) or changed
if hostgroup is not None:
changed = client.modify_if_diff(name, ipa_role.get('member_hostgroup', []), hostgroup,
client.role_add_hostgroup,
client.role_remove_hostgroup) or changed
if privilege is not None:
changed = client.modify_if_diff(name, ipa_role.get('memberof_privilege', []), privilege,
client.role_add_privilege,
client.role_remove_privilege) or changed
if service is not None:
changed = client.modify_if_diff(name, ipa_role.get('member_service', []), service,
client.role_add_service,
client.role_remove_service) or changed
if user is not None:
changed = client.modify_if_diff(name, ipa_role.get('member_user', []), user,
client.role_add_user,
client.role_remove_user) or changed
else:
if ipa_role:
changed = True
if not module.check_mode:
client.role_del(name)
return changed, client.role_find(name=name)
def main():
argument_spec = ipa_argument_spec()
argument_spec.update(cn=dict(type='str', required=True, aliases=['name']),
description=dict(type='str'),
group=dict(type='list', elements='str'),
host=dict(type='list', elements='str'),
hostgroup=dict(type='list', elements='str'),
privilege=dict(type='list', elements='str'),
service=dict(type='list', elements='str'),
state=dict(type='str', default='present', choices=['present', 'absent']),
user=dict(type='list', elements='str'))
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
client = RoleIPAClient(module=module,
host=module.params['ipa_host'],
port=module.params['ipa_port'],
protocol=module.params['ipa_prot'])
try:
client.login(username=module.params['ipa_user'],
password=module.params['ipa_pass'])
changed, role = ensure(module, client)
module.exit_json(changed=changed, role=role)
except Exception as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -0,0 +1,213 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: ipa_service
author: Cédric Parent (@cprh)
short_description: Manage FreeIPA service
description:
- Add and delete an IPA service using IPA API.
options:
krbcanonicalname:
description:
- Principal of the service.
- Can not be changed as it is the unique identifier.
required: true
aliases: ["name"]
type: str
hosts:
description:
- Defines the list of 'ManagedBy' hosts.
required: false
type: list
elements: str
force:
description:
- Force principal name even if host is not in DNS.
required: false
type: bool
state:
description: State to ensure.
required: false
default: present
choices: ["absent", "present"]
type: str
extends_documentation_fragment:
- community.general.ipa.documentation
'''
EXAMPLES = r'''
- name: Ensure service is present
ipa_service:
name: http/host01.example.com
state: present
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure service is absent
ipa_service:
name: http/host01.example.com
state: absent
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Changing Managing hosts list
ipa_service:
name: http/host01.example.com
host:
- host01.example.com
- host02.example.com
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
'''
RETURN = r'''
service:
description: Service as returned by IPA API.
returned: always
type: dict
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils._text import to_native
class ServiceIPAClient(IPAClient):
def __init__(self, module, host, port, protocol):
super(ServiceIPAClient, self).__init__(module, host, port, protocol)
def service_find(self, name):
return self._post_json(method='service_find', name=None, item={'all': True, 'krbcanonicalname': name})
def service_add(self, name, service):
return self._post_json(method='service_add', name=name, item=service)
def service_mod(self, name, service):
return self._post_json(method='service_mod', name=name, item=service)
def service_del(self, name):
return self._post_json(method='service_del', name=name)
def service_disable(self, name):
return self._post_json(method='service_disable', name=name)
def service_add_host(self, name, item):
return self._post_json(method='service_add_host', name=name, item={'host': item})
def service_remove_host(self, name, item):
return self._post_json(method='service_remove_host', name=name, item={'host': item})
def get_service_dict(force=None, krbcanonicalname=None):
data = {}
if force is not None:
data['force'] = force
if krbcanonicalname is not None:
data['krbcanonicalname'] = krbcanonicalname
return data
def get_service_diff(client, ipa_host, module_service):
non_updateable_keys = ['force', 'krbcanonicalname']
for key in non_updateable_keys:
if key in module_service:
del module_service[key]
return client.get_diff(ipa_data=ipa_host, module_data=module_service)
def ensure(module, client):
name = module.params['krbcanonicalname']
state = module.params['state']
hosts = module.params['hosts']
ipa_service = client.service_find(name=name)
module_service = get_service_dict(force=module.params['force'])
changed = False
if state in ['present', 'enabled', 'disabled']:
if not ipa_service:
changed = True
if not module.check_mode:
client.service_add(name=name, service=module_service)
else:
diff = get_service_diff(client, ipa_service, module_service)
if len(diff) > 0:
changed = True
if not module.check_mode:
data = {}
for key in diff:
data[key] = module_service.get(key)
client.service_mod(name=name, service=data)
if hosts is not None:
if 'managedby_host' in ipa_service:
for host in ipa_service['managedby_host']:
if host not in hosts:
if not module.check_mode:
client.service_remove_host(name=name, item=host)
changed = True
for host in hosts:
if host not in ipa_service['managedby_host']:
if not module.check_mode:
client.service_add_host(name=name, item=host)
changed = True
else:
for host in hosts:
if not module.check_mode:
client.service_add_host(name=name, item=host)
changed = True
else:
if ipa_service:
changed = True
if not module.check_mode:
client.service_del(name=name)
return changed, client.service_find(name=name)
def main():
argument_spec = ipa_argument_spec()
argument_spec.update(
krbcanonicalname=dict(type='str', required=True, aliases=['name']),
force=dict(type='bool', required=False),
hosts=dict(type='list', required=False, elements='str'),
state=dict(type='str', required=False, default='present',
choices=['present', 'absent']))
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
client = ServiceIPAClient(module=module,
host=module.params['ipa_host'],
port=module.params['ipa_port'],
protocol=module.params['ipa_prot'])
try:
client.login(username=module.params['ipa_user'],
password=module.params['ipa_pass'])
changed, host = ensure(module, client)
module.exit_json(changed=changed, host=host)
except Exception as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -0,0 +1,216 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Abhijeet Kasurde (akasurde@redhat.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: ipa_subca
author: Abhijeet Kasurde (@Akasurde)
short_description: Manage FreeIPA Lightweight Sub Certificate Authorities.
description:
- Add, modify, enable, disable and delete an IPA Lightweight Sub Certificate Authorities using IPA API.
options:
subca_name:
description:
- The Sub Certificate Authority name which needs to be managed.
required: true
aliases: ["name"]
type: str
subca_subject:
description:
- The Sub Certificate Authority's Subject. e.g., 'CN=SampleSubCA1,O=testrelm.test'.
required: true
type: str
subca_desc:
description:
- The Sub Certificate Authority's description.
type: str
state:
description:
- State to ensure.
- State 'disable' and 'enable' is available for FreeIPA 4.4.2 version and onwards.
required: false
default: present
choices: ["absent", "disabled", "enabled", "present"]
type: str
extends_documentation_fragment:
- community.general.ipa.documentation
'''
EXAMPLES = '''
- name: Ensure IPA Sub CA is present
ipa_subca:
ipa_host: spider.example.com
ipa_pass: Passw0rd!
state: present
subca_name: AnsibleSubCA1
subca_subject: 'CN=AnsibleSubCA1,O=example.com'
subca_desc: Ansible Sub CA
- name: Ensure that IPA Sub CA is removed
ipa_subca:
ipa_host: spider.example.com
ipa_pass: Passw0rd!
state: absent
subca_name: AnsibleSubCA1
- name: Ensure that IPA Sub CA is disabled
ipa_subca:
ipa_host: spider.example.com
ipa_pass: Passw0rd!
state: disable
subca_name: AnsibleSubCA1
'''
RETURN = r'''
subca:
description: IPA Sub CA record as returned by IPA API.
returned: always
type: dict
'''
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils._text import to_native
class SubCAIPAClient(IPAClient):
def __init__(self, module, host, port, protocol):
super(SubCAIPAClient, self).__init__(module, host, port, protocol)
def subca_find(self, subca_name):
return self._post_json(method='ca_find', name=subca_name, item=None)
def subca_add(self, subca_name=None, subject_dn=None, details=None):
item = dict(ipacasubjectdn=subject_dn)
subca_desc = details.get('description', None)
if subca_desc is not None:
item.update(description=subca_desc)
return self._post_json(method='ca_add', name=subca_name, item=item)
def subca_mod(self, subca_name=None, diff=None, details=None):
item = get_subca_dict(details)
for change in diff:
update_detail = dict()
if item[change] is not None:
update_detail.update(setattr="{0}={1}".format(change, item[change]))
self._post_json(method='ca_mod', name=subca_name, item=update_detail)
def subca_del(self, subca_name=None):
return self._post_json(method='ca_del', name=subca_name)
def subca_disable(self, subca_name=None):
return self._post_json(method='ca_disable', name=subca_name)
def subca_enable(self, subca_name=None):
return self._post_json(method='ca_enable', name=subca_name)
def get_subca_dict(details=None):
module_subca = dict()
if details['description'] is not None:
module_subca['description'] = details['description']
if details['subca_subject'] is not None:
module_subca['ipacasubjectdn'] = details['subca_subject']
return module_subca
def get_subca_diff(client, ipa_subca, module_subca):
details = get_subca_dict(module_subca)
return client.get_diff(ipa_data=ipa_subca, module_data=details)
def ensure(module, client):
subca_name = module.params['subca_name']
subca_subject_dn = module.params['subca_subject']
subca_desc = module.params['subca_desc']
state = module.params['state']
ipa_subca = client.subca_find(subca_name)
module_subca = dict(description=subca_desc,
subca_subject=subca_subject_dn)
changed = False
if state == 'present':
if not ipa_subca:
changed = True
if not module.check_mode:
client.subca_add(subca_name=subca_name, subject_dn=subca_subject_dn, details=module_subca)
else:
diff = get_subca_diff(client, ipa_subca, module_subca)
# IPA does not allow to modify Sub CA's subject DN
# So skip it for now.
if 'ipacasubjectdn' in diff:
diff.remove('ipacasubjectdn')
del module_subca['subca_subject']
if len(diff) > 0:
changed = True
if not module.check_mode:
client.subca_mod(subca_name=subca_name, diff=diff, details=module_subca)
elif state == 'absent':
if ipa_subca:
changed = True
if not module.check_mode:
client.subca_del(subca_name=subca_name)
elif state == 'disable':
ipa_version = client.get_ipa_version()
if LooseVersion(ipa_version) < LooseVersion('4.4.2'):
module.fail_json(msg="Current version of IPA server [%s] does not support 'CA disable' option. Please upgrade to "
"version greater than 4.4.2")
if ipa_subca:
changed = True
if not module.check_mode:
client.subca_disable(subca_name=subca_name)
elif state == 'enable':
ipa_version = client.get_ipa_version()
if LooseVersion(ipa_version) < LooseVersion('4.4.2'):
module.fail_json(msg="Current version of IPA server [%s] does not support 'CA enable' option. Please upgrade to "
"version greater than 4.4.2")
if ipa_subca:
changed = True
if not module.check_mode:
client.subca_enable(subca_name=subca_name)
return changed, client.subca_find(subca_name)
def main():
argument_spec = ipa_argument_spec()
argument_spec.update(subca_name=dict(type='str', required=True, aliases=['name']),
subca_subject=dict(type='str', required=True),
subca_desc=dict(type='str'),
state=dict(type='str', default='present',
choices=['present', 'absent', 'enabled', 'disabled']),)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True,)
client = SubCAIPAClient(module=module,
host=module.params['ipa_host'],
port=module.params['ipa_port'],
protocol=module.params['ipa_prot'])
try:
client.login(username=module.params['ipa_user'],
password=module.params['ipa_pass'])
changed, record = ensure(module, client)
module.exit_json(changed=changed, record=record)
except Exception as exc:
module.fail_json(msg=to_native(exc))
if __name__ == '__main__':
main()

View file

@ -0,0 +1,156 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: ipa_sudocmd
author: Thomas Krahn (@Nosmoht)
short_description: Manage FreeIPA sudo command
description:
- Add, modify or delete sudo command within FreeIPA server using FreeIPA API.
options:
sudocmd:
description:
- Sudo command.
aliases: ['name']
required: true
type: str
description:
description:
- A description of this command.
type: str
state:
description: State to ensure.
default: present
choices: ['absent', 'disabled', 'enabled', 'present']
type: str
extends_documentation_fragment:
- community.general.ipa.documentation
'''
EXAMPLES = r'''
- name: Ensure sudo command exists
ipa_sudocmd:
name: su
description: Allow to run su via sudo
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure sudo command does not exist
ipa_sudocmd:
name: su
state: absent
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
'''
RETURN = r'''
sudocmd:
description: Sudo command as return from IPA API
returned: always
type: dict
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils._text import to_native
class SudoCmdIPAClient(IPAClient):
def __init__(self, module, host, port, protocol):
super(SudoCmdIPAClient, self).__init__(module, host, port, protocol)
def sudocmd_find(self, name):
return self._post_json(method='sudocmd_find', name=None, item={'all': True, 'sudocmd': name})
def sudocmd_add(self, name, item):
return self._post_json(method='sudocmd_add', name=name, item=item)
def sudocmd_mod(self, name, item):
return self._post_json(method='sudocmd_mod', name=name, item=item)
def sudocmd_del(self, name):
return self._post_json(method='sudocmd_del', name=name)
def get_sudocmd_dict(description=None):
data = {}
if description is not None:
data['description'] = description
return data
def get_sudocmd_diff(client, ipa_sudocmd, module_sudocmd):
return client.get_diff(ipa_data=ipa_sudocmd, module_data=module_sudocmd)
def ensure(module, client):
name = module.params['sudocmd']
state = module.params['state']
module_sudocmd = get_sudocmd_dict(description=module.params['description'])
ipa_sudocmd = client.sudocmd_find(name=name)
changed = False
if state == 'present':
if not ipa_sudocmd:
changed = True
if not module.check_mode:
client.sudocmd_add(name=name, item=module_sudocmd)
else:
diff = get_sudocmd_diff(client, ipa_sudocmd, module_sudocmd)
if len(diff) > 0:
changed = True
if not module.check_mode:
data = {}
for key in diff:
data[key] = module_sudocmd.get(key)
client.sudocmd_mod(name=name, item=data)
else:
if ipa_sudocmd:
changed = True
if not module.check_mode:
client.sudocmd_del(name=name)
return changed, client.sudocmd_find(name=name)
def main():
argument_spec = ipa_argument_spec()
argument_spec.update(description=dict(type='str'),
state=dict(type='str', default='present', choices=['present', 'absent', 'enabled', 'disabled']),
sudocmd=dict(type='str', required=True, aliases=['name']))
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
client = SudoCmdIPAClient(module=module,
host=module.params['ipa_host'],
port=module.params['ipa_port'],
protocol=module.params['ipa_prot'])
try:
client.login(username=module.params['ipa_user'],
password=module.params['ipa_pass'])
changed, sudocmd = ensure(module, client)
module.exit_json(changed=changed, sudocmd=sudocmd)
except Exception as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -0,0 +1,184 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: ipa_sudocmdgroup
author: Thomas Krahn (@Nosmoht)
short_description: Manage FreeIPA sudo command group
description:
- Add, modify or delete sudo command group within IPA server using IPA API.
options:
cn:
description:
- Sudo Command Group.
aliases: ['name']
required: true
type: str
description:
description:
- Group description.
type: str
state:
description: State to ensure.
default: present
choices: ['absent', 'disabled', 'enabled', 'present']
type: str
sudocmd:
description:
- List of sudo commands to assign to the group.
- If an empty list is passed all assigned commands will be removed from the group.
- If option is omitted sudo commands will not be checked or changed.
type: list
elements: str
extends_documentation_fragment:
- community.general.ipa.documentation
'''
EXAMPLES = r'''
- name: Ensure sudo command group exists
ipa_sudocmdgroup:
name: group01
description: Group of important commands
sudocmd:
- su
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure sudo command group does not exist
ipa_sudocmdgroup:
name: group01
state: absent
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
'''
RETURN = r'''
sudocmdgroup:
description: Sudo command group as returned by IPA API
returned: always
type: dict
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils._text import to_native
class SudoCmdGroupIPAClient(IPAClient):
def __init__(self, module, host, port, protocol):
super(SudoCmdGroupIPAClient, self).__init__(module, host, port, protocol)
def sudocmdgroup_find(self, name):
return self._post_json(method='sudocmdgroup_find', name=None, item={'all': True, 'cn': name})
def sudocmdgroup_add(self, name, item):
return self._post_json(method='sudocmdgroup_add', name=name, item=item)
def sudocmdgroup_mod(self, name, item):
return self._post_json(method='sudocmdgroup_mod', name=name, item=item)
def sudocmdgroup_del(self, name):
return self._post_json(method='sudocmdgroup_del', name=name)
def sudocmdgroup_add_member(self, name, item):
return self._post_json(method='sudocmdgroup_add_member', name=name, item=item)
def sudocmdgroup_add_member_sudocmd(self, name, item):
return self.sudocmdgroup_add_member(name=name, item={'sudocmd': item})
def sudocmdgroup_remove_member(self, name, item):
return self._post_json(method='sudocmdgroup_remove_member', name=name, item=item)
def sudocmdgroup_remove_member_sudocmd(self, name, item):
return self.sudocmdgroup_remove_member(name=name, item={'sudocmd': item})
def get_sudocmdgroup_dict(description=None):
data = {}
if description is not None:
data['description'] = description
return data
def get_sudocmdgroup_diff(client, ipa_sudocmdgroup, module_sudocmdgroup):
return client.get_diff(ipa_data=ipa_sudocmdgroup, module_data=module_sudocmdgroup)
def ensure(module, client):
name = module.params['cn']
state = module.params['state']
sudocmd = module.params['sudocmd']
module_sudocmdgroup = get_sudocmdgroup_dict(description=module.params['description'])
ipa_sudocmdgroup = client.sudocmdgroup_find(name=name)
changed = False
if state == 'present':
if not ipa_sudocmdgroup:
changed = True
if not module.check_mode:
ipa_sudocmdgroup = client.sudocmdgroup_add(name=name, item=module_sudocmdgroup)
else:
diff = get_sudocmdgroup_diff(client, ipa_sudocmdgroup, module_sudocmdgroup)
if len(diff) > 0:
changed = True
if not module.check_mode:
data = {}
for key in diff:
data[key] = module_sudocmdgroup.get(key)
client.sudocmdgroup_mod(name=name, item=data)
if sudocmd is not None:
changed = client.modify_if_diff(name, ipa_sudocmdgroup.get('member_sudocmd', []), sudocmd,
client.sudocmdgroup_add_member_sudocmd,
client.sudocmdgroup_remove_member_sudocmd)
else:
if ipa_sudocmdgroup:
changed = True
if not module.check_mode:
client.sudocmdgroup_del(name=name)
return changed, client.sudocmdgroup_find(name=name)
def main():
argument_spec = ipa_argument_spec()
argument_spec.update(cn=dict(type='str', required=True, aliases=['name']),
description=dict(type='str'),
state=dict(type='str', default='present', choices=['present', 'absent', 'enabled', 'disabled']),
sudocmd=dict(type='list', elements='str'))
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
client = SudoCmdGroupIPAClient(module=module,
host=module.params['ipa_host'],
port=module.params['ipa_port'],
protocol=module.params['ipa_prot'])
try:
client.login(username=module.params['ipa_user'],
password=module.params['ipa_pass'])
changed, sudocmdgroup = ensure(module, client)
module.exit_json(changed=changed, sudorule=sudocmdgroup)
except Exception as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -0,0 +1,405 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: ipa_sudorule
author: Thomas Krahn (@Nosmoht)
short_description: Manage FreeIPA sudo rule
description:
- Add, modify or delete sudo rule within IPA server using IPA API.
options:
cn:
description:
- Canonical name.
- Can not be changed as it is the unique identifier.
required: true
aliases: ['name']
type: str
cmdcategory:
description:
- Command category the rule applies to.
choices: ['all']
type: str
cmd:
description:
- List of commands assigned to the rule.
- If an empty list is passed all commands will be removed from the rule.
- If option is omitted commands will not be checked or changed.
type: list
elements: str
description:
description:
- Description of the sudo rule.
type: str
host:
description:
- List of hosts assigned to the rule.
- If an empty list is passed all hosts will be removed from the rule.
- If option is omitted hosts will not be checked or changed.
- Option C(hostcategory) must be omitted to assign hosts.
type: list
elements: str
hostcategory:
description:
- Host category the rule applies to.
- If 'all' is passed one must omit C(host) and C(hostgroup).
- Option C(host) and C(hostgroup) must be omitted to assign 'all'.
choices: ['all']
type: str
hostgroup:
description:
- List of host groups assigned to the rule.
- If an empty list is passed all host groups will be removed from the rule.
- If option is omitted host groups will not be checked or changed.
- Option C(hostcategory) must be omitted to assign host groups.
type: list
elements: str
runasusercategory:
description:
- RunAs User category the rule applies to.
choices: ['all']
type: str
runasgroupcategory:
description:
- RunAs Group category the rule applies to.
choices: ['all']
type: str
sudoopt:
description:
- List of options to add to the sudo rule.
type: list
elements: str
user:
description:
- List of users assigned to the rule.
- If an empty list is passed all users will be removed from the rule.
- If option is omitted users will not be checked or changed.
type: list
elements: str
usercategory:
description:
- User category the rule applies to.
choices: ['all']
type: str
usergroup:
description:
- List of user groups assigned to the rule.
- If an empty list is passed all user groups will be removed from the rule.
- If option is omitted user groups will not be checked or changed.
type: list
elements: str
state:
description: State to ensure.
default: present
choices: ['absent', 'disabled', 'enabled', 'present']
type: str
extends_documentation_fragment:
- community.general.ipa.documentation
'''
EXAMPLES = r'''
- name: Ensure sudo rule is present that's allows all every body to execute any command on any host without being asked for a password.
ipa_sudorule:
name: sudo_all_nopasswd
cmdcategory: all
description: Allow to run every command with sudo without password
hostcategory: all
sudoopt:
- '!authenticate'
usercategory: all
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure user group developers can run every command on host group db-server as well as on host db01.example.com.
ipa_sudorule:
name: sudo_dev_dbserver
description: Allow developers to run every command with sudo on all database server
cmdcategory: all
host:
- db01.example.com
hostgroup:
- db-server
sudoopt:
- '!authenticate'
usergroup:
- developers
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
'''
RETURN = r'''
sudorule:
description: Sudorule as returned by IPA
returned: always
type: dict
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils._text import to_native
class SudoRuleIPAClient(IPAClient):
def __init__(self, module, host, port, protocol):
super(SudoRuleIPAClient, self).__init__(module, host, port, protocol)
def sudorule_find(self, name):
return self._post_json(method='sudorule_find', name=None, item={'all': True, 'cn': name})
def sudorule_add(self, name, item):
return self._post_json(method='sudorule_add', name=name, item=item)
def sudorule_mod(self, name, item):
return self._post_json(method='sudorule_mod', name=name, item=item)
def sudorule_del(self, name):
return self._post_json(method='sudorule_del', name=name)
def sudorule_add_option(self, name, item):
return self._post_json(method='sudorule_add_option', name=name, item=item)
def sudorule_add_option_ipasudoopt(self, name, item):
return self.sudorule_add_option(name=name, item={'ipasudoopt': item})
def sudorule_remove_option(self, name, item):
return self._post_json(method='sudorule_remove_option', name=name, item=item)
def sudorule_remove_option_ipasudoopt(self, name, item):
return self.sudorule_remove_option(name=name, item={'ipasudoopt': item})
def sudorule_add_host(self, name, item):
return self._post_json(method='sudorule_add_host', name=name, item=item)
def sudorule_add_host_host(self, name, item):
return self.sudorule_add_host(name=name, item={'host': item})
def sudorule_add_host_hostgroup(self, name, item):
return self.sudorule_add_host(name=name, item={'hostgroup': item})
def sudorule_remove_host(self, name, item):
return self._post_json(method='sudorule_remove_host', name=name, item=item)
def sudorule_remove_host_host(self, name, item):
return self.sudorule_remove_host(name=name, item={'host': item})
def sudorule_remove_host_hostgroup(self, name, item):
return self.sudorule_remove_host(name=name, item={'hostgroup': item})
def sudorule_add_allow_command(self, name, item):
return self._post_json(method='sudorule_add_allow_command', name=name, item={'sudocmd': item})
def sudorule_remove_allow_command(self, name, item):
return self._post_json(method='sudorule_remove_allow_command', name=name, item=item)
def sudorule_add_user(self, name, item):
return self._post_json(method='sudorule_add_user', name=name, item=item)
def sudorule_add_user_user(self, name, item):
return self.sudorule_add_user(name=name, item={'user': item})
def sudorule_add_user_group(self, name, item):
return self.sudorule_add_user(name=name, item={'group': item})
def sudorule_remove_user(self, name, item):
return self._post_json(method='sudorule_remove_user', name=name, item=item)
def sudorule_remove_user_user(self, name, item):
return self.sudorule_remove_user(name=name, item={'user': item})
def sudorule_remove_user_group(self, name, item):
return self.sudorule_remove_user(name=name, item={'group': item})
def get_sudorule_dict(cmdcategory=None, description=None, hostcategory=None, ipaenabledflag=None, usercategory=None,
runasgroupcategory=None, runasusercategory=None):
data = {}
if cmdcategory is not None:
data['cmdcategory'] = cmdcategory
if description is not None:
data['description'] = description
if hostcategory is not None:
data['hostcategory'] = hostcategory
if ipaenabledflag is not None:
data['ipaenabledflag'] = ipaenabledflag
if usercategory is not None:
data['usercategory'] = usercategory
if runasusercategory is not None:
data['ipasudorunasusercategory'] = runasusercategory
if runasgroupcategory is not None:
data['ipasudorunasgroupcategory'] = runasgroupcategory
return data
def category_changed(module, client, category_name, ipa_sudorule):
if ipa_sudorule.get(category_name, None) == ['all']:
if not module.check_mode:
# cn is returned as list even with only a single value.
client.sudorule_mod(name=ipa_sudorule.get('cn')[0], item={category_name: None})
return True
return False
def ensure(module, client):
state = module.params['state']
name = module.params['cn']
cmd = module.params['cmd']
cmdcategory = module.params['cmdcategory']
host = module.params['host']
hostcategory = module.params['hostcategory']
hostgroup = module.params['hostgroup']
runasusercategory = module.params['runasusercategory']
runasgroupcategory = module.params['runasgroupcategory']
if state in ['present', 'enabled']:
ipaenabledflag = 'TRUE'
else:
ipaenabledflag = 'FALSE'
sudoopt = module.params['sudoopt']
user = module.params['user']
usercategory = module.params['usercategory']
usergroup = module.params['usergroup']
module_sudorule = get_sudorule_dict(cmdcategory=cmdcategory,
description=module.params['description'],
hostcategory=hostcategory,
ipaenabledflag=ipaenabledflag,
usercategory=usercategory,
runasusercategory=runasusercategory,
runasgroupcategory=runasgroupcategory)
ipa_sudorule = client.sudorule_find(name=name)
changed = False
if state in ['present', 'disabled', 'enabled']:
if not ipa_sudorule:
changed = True
if not module.check_mode:
ipa_sudorule = client.sudorule_add(name=name, item=module_sudorule)
else:
diff = client.get_diff(ipa_sudorule, module_sudorule)
if len(diff) > 0:
changed = True
if not module.check_mode:
if 'hostcategory' in diff:
if ipa_sudorule.get('memberhost_host', None) is not None:
client.sudorule_remove_host_host(name=name, item=ipa_sudorule.get('memberhost_host'))
if ipa_sudorule.get('memberhost_hostgroup', None) is not None:
client.sudorule_remove_host_hostgroup(name=name,
item=ipa_sudorule.get('memberhost_hostgroup'))
client.sudorule_mod(name=name, item=module_sudorule)
if cmd is not None:
changed = category_changed(module, client, 'cmdcategory', ipa_sudorule) or changed
if not module.check_mode:
client.sudorule_add_allow_command(name=name, item=cmd)
if runasusercategory is not None:
changed = category_changed(module, client, 'iparunasusercategory', ipa_sudorule) or changed
if runasgroupcategory is not None:
changed = category_changed(module, client, 'iparunasgroupcategory', ipa_sudorule) or changed
if host is not None:
changed = category_changed(module, client, 'hostcategory', ipa_sudorule) or changed
changed = client.modify_if_diff(name, ipa_sudorule.get('memberhost_host', []), host,
client.sudorule_add_host_host,
client.sudorule_remove_host_host) or changed
if hostgroup is not None:
changed = category_changed(module, client, 'hostcategory', ipa_sudorule) or changed
changed = client.modify_if_diff(name, ipa_sudorule.get('memberhost_hostgroup', []), hostgroup,
client.sudorule_add_host_hostgroup,
client.sudorule_remove_host_hostgroup) or changed
if sudoopt is not None:
# client.modify_if_diff does not work as each option must be removed/added by its own
ipa_list = ipa_sudorule.get('ipasudoopt', [])
module_list = sudoopt
diff = list(set(ipa_list) - set(module_list))
if len(diff) > 0:
changed = True
if not module.check_mode:
for item in diff:
client.sudorule_remove_option_ipasudoopt(name, item)
diff = list(set(module_list) - set(ipa_list))
if len(diff) > 0:
changed = True
if not module.check_mode:
for item in diff:
client.sudorule_add_option_ipasudoopt(name, item)
if user is not None:
changed = category_changed(module, client, 'usercategory', ipa_sudorule) or changed
changed = client.modify_if_diff(name, ipa_sudorule.get('memberuser_user', []), user,
client.sudorule_add_user_user,
client.sudorule_remove_user_user) or changed
if usergroup is not None:
changed = category_changed(module, client, 'usercategory', ipa_sudorule) or changed
changed = client.modify_if_diff(name, ipa_sudorule.get('memberuser_group', []), usergroup,
client.sudorule_add_user_group,
client.sudorule_remove_user_group) or changed
else:
if ipa_sudorule:
changed = True
if not module.check_mode:
client.sudorule_del(name)
return changed, client.sudorule_find(name)
def main():
argument_spec = ipa_argument_spec()
argument_spec.update(cmd=dict(type='list', elements='str'),
cmdcategory=dict(type='str', choices=['all']),
cn=dict(type='str', required=True, aliases=['name']),
description=dict(type='str'),
host=dict(type='list', elements='str'),
hostcategory=dict(type='str', choices=['all']),
hostgroup=dict(type='list', elements='str'),
runasusercategory=dict(type='str', choices=['all']),
runasgroupcategory=dict(type='str', choices=['all']),
sudoopt=dict(type='list', elements='str'),
state=dict(type='str', default='present', choices=['present', 'absent', 'enabled', 'disabled']),
user=dict(type='list', elements='str'),
usercategory=dict(type='str', choices=['all']),
usergroup=dict(type='list', elements='str'))
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=[['cmdcategory', 'cmd'],
['hostcategory', 'host'],
['hostcategory', 'hostgroup'],
['usercategory', 'user'],
['usercategory', 'usergroup']],
supports_check_mode=True)
client = SudoRuleIPAClient(module=module,
host=module.params['ipa_host'],
port=module.params['ipa_port'],
protocol=module.params['ipa_prot'])
try:
client.login(username=module.params['ipa_user'],
password=module.params['ipa_pass'])
changed, sudorule = ensure(module, client)
module.exit_json(changed=changed, sudorule=sudorule)
except Exception as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -0,0 +1,374 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: ipa_user
author: Thomas Krahn (@Nosmoht)
short_description: Manage FreeIPA users
description:
- Add, modify and delete user within IPA server.
options:
displayname:
description: Display name.
type: str
update_password:
description:
- Set password for a user.
type: str
default: 'always'
choices: [ always, on_create ]
givenname:
description: First name.
type: str
krbpasswordexpiration:
description:
- Date at which the user password will expire.
- In the format YYYYMMddHHmmss.
- e.g. 20180121182022 will expire on 21 January 2018 at 18:20:22.
type: str
loginshell:
description: Login shell.
type: str
mail:
description:
- List of mail addresses assigned to the user.
- If an empty list is passed all assigned email addresses will be deleted.
- If None is passed email addresses will not be checked or changed.
type: list
elements: str
password:
description:
- Password for a user.
- Will not be set for an existing user unless I(update_password=always), which is the default.
type: str
sn:
description: Surname.
type: str
sshpubkey:
description:
- List of public SSH key.
- If an empty list is passed all assigned public keys will be deleted.
- If None is passed SSH public keys will not be checked or changed.
type: list
elements: str
state:
description: State to ensure.
default: "present"
choices: ["absent", "disabled", "enabled", "present"]
type: str
telephonenumber:
description:
- List of telephone numbers assigned to the user.
- If an empty list is passed all assigned telephone numbers will be deleted.
- If None is passed telephone numbers will not be checked or changed.
type: list
elements: str
title:
description: Title.
type: str
uid:
description: uid of the user.
required: true
aliases: ["name"]
type: str
uidnumber:
description:
- Account Settings UID/Posix User ID number.
type: str
gidnumber:
description:
- Posix Group ID.
type: str
homedirectory:
description:
- Default home directory of the user.
type: str
extends_documentation_fragment:
- community.general.ipa.documentation
requirements:
- base64
- hashlib
'''
EXAMPLES = r'''
- name: Ensure pinky is present and always reset password
ipa_user:
name: pinky
state: present
krbpasswordexpiration: 20200119235959
givenname: Pinky
sn: Acme
mail:
- pinky@acme.com
telephonenumber:
- '+555123456'
sshpubkey:
- ssh-rsa ....
- ssh-dsa ....
uidnumber: 1001
gidnumber: 100
homedirectory: /home/pinky
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure brain is absent
ipa_user:
name: brain
state: absent
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure pinky is present but don't reset password if already exists
ipa_user:
name: pinky
state: present
givenname: Pinky
sn: Acme
password: zounds
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
update_password: on_create
'''
RETURN = r'''
user:
description: User as returned by IPA API
returned: always
type: dict
'''
import base64
import hashlib
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils._text import to_native
class UserIPAClient(IPAClient):
def __init__(self, module, host, port, protocol):
super(UserIPAClient, self).__init__(module, host, port, protocol)
def user_find(self, name):
return self._post_json(method='user_find', name=None, item={'all': True, 'uid': name})
def user_add(self, name, item):
return self._post_json(method='user_add', name=name, item=item)
def user_mod(self, name, item):
return self._post_json(method='user_mod', name=name, item=item)
def user_del(self, name):
return self._post_json(method='user_del', name=name)
def user_disable(self, name):
return self._post_json(method='user_disable', name=name)
def user_enable(self, name):
return self._post_json(method='user_enable', name=name)
def get_user_dict(displayname=None, givenname=None, krbpasswordexpiration=None, loginshell=None,
mail=None, nsaccountlock=False, sn=None, sshpubkey=None, telephonenumber=None,
title=None, userpassword=None, gidnumber=None, uidnumber=None, homedirectory=None):
user = {}
if displayname is not None:
user['displayname'] = displayname
if krbpasswordexpiration is not None:
user['krbpasswordexpiration'] = krbpasswordexpiration + "Z"
if givenname is not None:
user['givenname'] = givenname
if loginshell is not None:
user['loginshell'] = loginshell
if mail is not None:
user['mail'] = mail
user['nsaccountlock'] = nsaccountlock
if sn is not None:
user['sn'] = sn
if sshpubkey is not None:
user['ipasshpubkey'] = sshpubkey
if telephonenumber is not None:
user['telephonenumber'] = telephonenumber
if title is not None:
user['title'] = title
if userpassword is not None:
user['userpassword'] = userpassword
if gidnumber is not None:
user['gidnumber'] = gidnumber
if uidnumber is not None:
user['uidnumber'] = uidnumber
if homedirectory is not None:
user['homedirectory'] = homedirectory
return user
def get_user_diff(client, ipa_user, module_user):
"""
Return the keys of each dict whereas values are different. Unfortunately the IPA
API returns everything as a list even if only a single value is possible.
Therefore some more complexity is needed.
The method will check if the value type of module_user.attr is not a list and
create a list with that element if the same attribute in ipa_user is list. In this way I hope that the method
must not be changed if the returned API dict is changed.
:param ipa_user:
:param module_user:
:return:
"""
# sshpubkeyfp is the list of ssh key fingerprints. IPA doesn't return the keys itself but instead the fingerprints.
# These are used for comparison.
sshpubkey = None
if 'ipasshpubkey' in module_user:
hash_algo = 'md5'
if 'sshpubkeyfp' in ipa_user and ipa_user['sshpubkeyfp'][0][:7].upper() == 'SHA256:':
hash_algo = 'sha256'
module_user['sshpubkeyfp'] = [get_ssh_key_fingerprint(pubkey, hash_algo) for pubkey in module_user['ipasshpubkey']]
# Remove the ipasshpubkey element as it is not returned from IPA but save it's value to be used later on
sshpubkey = module_user['ipasshpubkey']
del module_user['ipasshpubkey']
result = client.get_diff(ipa_data=ipa_user, module_data=module_user)
# If there are public keys, remove the fingerprints and add them back to the dict
if sshpubkey is not None:
del module_user['sshpubkeyfp']
module_user['ipasshpubkey'] = sshpubkey
return result
def get_ssh_key_fingerprint(ssh_key, hash_algo='sha256'):
"""
Return the public key fingerprint of a given public SSH key
in format "[fp] [user@host] (ssh-rsa)" where fp is of the format:
FB:0C:AC:0A:07:94:5B:CE:75:6E:63:32:13:AD:AD:D7
for md5 or
SHA256:[base64]
for sha256
:param ssh_key:
:param hash_algo:
:return:
"""
parts = ssh_key.strip().split()
if len(parts) == 0:
return None
key_type = parts[0]
key = base64.b64decode(parts[1].encode('ascii'))
if hash_algo == 'md5':
fp_plain = hashlib.md5(key).hexdigest()
key_fp = ':'.join(a + b for a, b in zip(fp_plain[::2], fp_plain[1::2])).upper()
elif hash_algo == 'sha256':
fp_plain = base64.b64encode(hashlib.sha256(key).digest()).decode('ascii').rstrip('=')
key_fp = 'SHA256:{fp}'.format(fp=fp_plain)
if len(parts) < 3:
return "%s (%s)" % (key_fp, key_type)
else:
user_host = parts[2]
return "%s %s (%s)" % (key_fp, user_host, key_type)
def ensure(module, client):
state = module.params['state']
name = module.params['uid']
nsaccountlock = state == 'disabled'
module_user = get_user_dict(displayname=module.params.get('displayname'),
krbpasswordexpiration=module.params.get('krbpasswordexpiration'),
givenname=module.params.get('givenname'),
loginshell=module.params['loginshell'],
mail=module.params['mail'], sn=module.params['sn'],
sshpubkey=module.params['sshpubkey'], nsaccountlock=nsaccountlock,
telephonenumber=module.params['telephonenumber'], title=module.params['title'],
userpassword=module.params['password'],
gidnumber=module.params.get('gidnumber'), uidnumber=module.params.get('uidnumber'),
homedirectory=module.params.get('homedirectory'))
update_password = module.params.get('update_password')
ipa_user = client.user_find(name=name)
changed = False
if state in ['present', 'enabled', 'disabled']:
if not ipa_user:
changed = True
if not module.check_mode:
ipa_user = client.user_add(name=name, item=module_user)
else:
if update_password == 'on_create':
module_user.pop('userpassword', None)
diff = get_user_diff(client, ipa_user, module_user)
if len(diff) > 0:
changed = True
if not module.check_mode:
ipa_user = client.user_mod(name=name, item=module_user)
else:
if ipa_user:
changed = True
if not module.check_mode:
client.user_del(name)
return changed, ipa_user
def main():
argument_spec = ipa_argument_spec()
argument_spec.update(displayname=dict(type='str'),
givenname=dict(type='str'),
update_password=dict(type='str', default="always",
choices=['always', 'on_create']),
krbpasswordexpiration=dict(type='str'),
loginshell=dict(type='str'),
mail=dict(type='list', elements='str'),
sn=dict(type='str'),
uid=dict(type='str', required=True, aliases=['name']),
gidnumber=dict(type='str'),
uidnumber=dict(type='str'),
password=dict(type='str', no_log=True),
sshpubkey=dict(type='list', elements='str'),
state=dict(type='str', default='present',
choices=['present', 'absent', 'enabled', 'disabled']),
telephonenumber=dict(type='list', elements='str'),
title=dict(type='str'),
homedirectory=dict(type='str'))
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
client = UserIPAClient(module=module,
host=module.params['ipa_host'],
port=module.params['ipa_port'],
protocol=module.params['ipa_prot'])
# If sshpubkey is defined as None than module.params['sshpubkey'] is [None]. IPA itself returns None (not a list).
# Therefore a small check here to replace list(None) by None. Otherwise get_user_diff() would return sshpubkey
# as different which should be avoided.
if module.params['sshpubkey'] is not None:
if len(module.params['sshpubkey']) == 1 and module.params['sshpubkey'][0] == "":
module.params['sshpubkey'] = None
try:
client.login(username=module.params['ipa_user'],
password=module.params['ipa_pass'])
changed, user = ensure(module, client)
module.exit_json(changed=changed, user=user)
except Exception as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -0,0 +1,253 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Juan Manuel Parrilla <jparrill@redhat.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: ipa_vault
author: Juan Manuel Parrilla (@jparrill)
short_description: Manage FreeIPA vaults
description:
- Add, modify and delete vaults and secret vaults.
- KRA service should be enabled to use this module.
options:
cn:
description:
- Vault name.
- Can not be changed as it is the unique identifier.
required: true
aliases: ["name"]
type: str
description:
description:
- Description.
type: str
ipavaulttype:
description:
- Vault types are based on security level.
default: "symmetric"
choices: ["asymmetric", "standard", "symmetric"]
aliases: ["vault_type"]
type: str
ipavaultpublickey:
description:
- Public key.
aliases: ["vault_public_key"]
type: str
ipavaultsalt:
description:
- Vault Salt.
aliases: ["vault_salt"]
type: str
username:
description:
- Any user can own one or more user vaults.
- Mutually exclusive with service.
aliases: ["user"]
type: list
elements: str
service:
description:
- Any service can own one or more service vaults.
- Mutually exclusive with user.
type: str
state:
description:
- State to ensure.
default: "present"
choices: ["absent", "present"]
type: str
replace:
description:
- Force replace the existant vault on IPA server.
type: bool
default: False
choices: ["True", "False"]
validate_certs:
description:
- Validate IPA server certificates.
type: bool
default: true
extends_documentation_fragment:
- community.general.ipa.documentation
'''
EXAMPLES = r'''
- name: Ensure vault is present
ipa_vault:
name: vault01
vault_type: standard
user: user01
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
validate_certs: false
- name: Ensure vault is present for Admin user
ipa_vault:
name: vault01
vault_type: standard
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Ensure vault is absent
ipa_vault:
name: vault01
vault_type: standard
user: user01
state: absent
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
- name: Modify vault if already exists
ipa_vault:
name: vault01
vault_type: standard
description: "Vault for test"
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
replace: True
- name: Get vault info if already exists
ipa_vault:
name: vault01
ipa_host: ipa.example.com
ipa_user: admin
ipa_pass: topsecret
'''
RETURN = r'''
vault:
description: Vault as returned by IPA API
returned: always
type: dict
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils._text import to_native
class VaultIPAClient(IPAClient):
def __init__(self, module, host, port, protocol):
super(VaultIPAClient, self).__init__(module, host, port, protocol)
def vault_find(self, name):
return self._post_json(method='vault_find', name=None, item={'all': True, 'cn': name})
def vault_add_internal(self, name, item):
return self._post_json(method='vault_add_internal', name=name, item=item)
def vault_mod_internal(self, name, item):
return self._post_json(method='vault_mod_internal', name=name, item=item)
def vault_del(self, name):
return self._post_json(method='vault_del', name=name)
def get_vault_dict(description=None, vault_type=None, vault_salt=None, vault_public_key=None, service=None):
vault = {}
if description is not None:
vault['description'] = description
if vault_type is not None:
vault['ipavaulttype'] = vault_type
if vault_salt is not None:
vault['ipavaultsalt'] = vault_salt
if vault_public_key is not None:
vault['ipavaultpublickey'] = vault_public_key
if service is not None:
vault['service'] = service
return vault
def get_vault_diff(client, ipa_vault, module_vault, module):
return client.get_diff(ipa_data=ipa_vault, module_data=module_vault)
def ensure(module, client):
state = module.params['state']
name = module.params['cn']
user = module.params['username']
replace = module.params['replace']
module_vault = get_vault_dict(description=module.params['description'], vault_type=module.params['ipavaulttype'],
vault_salt=module.params['ipavaultsalt'],
vault_public_key=module.params['ipavaultpublickey'],
service=module.params['service'])
ipa_vault = client.vault_find(name=name)
changed = False
if state == 'present':
if not ipa_vault:
# New vault
changed = True
if not module.check_mode:
ipa_vault = client.vault_add_internal(name, item=module_vault)
else:
# Already exists
if replace:
diff = get_vault_diff(client, ipa_vault, module_vault, module)
if len(diff) > 0:
changed = True
if not module.check_mode:
data = {}
for key in diff:
data[key] = module_vault.get(key)
client.vault_mod_internal(name=name, item=data)
else:
if ipa_vault:
changed = True
if not module.check_mode:
client.vault_del(name)
return changed, client.vault_find(name=name)
def main():
argument_spec = ipa_argument_spec()
argument_spec.update(cn=dict(type='str', required=True, aliases=['name']),
description=dict(type='str'),
ipavaulttype=dict(type='str', default='symmetric',
choices=['standard', 'symmetric', 'asymmetric'], aliases=['vault_type']),
ipavaultsalt=dict(type='str', aliases=['vault_salt']),
ipavaultpublickey=dict(type='str', aliases=['vault_public_key']),
service=dict(type='str'),
replace=dict(type='bool', default=False, choices=[True, False]),
state=dict(type='str', default='present', choices=['present', 'absent']),
username=dict(type='list', elements='str', aliases=['user']))
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True,
mutually_exclusive=[['username', 'service']])
client = VaultIPAClient(module=module,
host=module.params['ipa_host'],
port=module.params['ipa_port'],
protocol=module.params['ipa_prot'])
try:
client.login(username=module.params['ipa_user'],
password=module.params['ipa_pass'])
changed, vault = ensure(module, client)
module.exit_json(changed=changed, vault=vault)
except Exception as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()