diff --git a/README-trust.md b/README-trust.md new file mode 100644 index 0000000000..bc16bd3060 --- /dev/null +++ b/README-trust.md @@ -0,0 +1,119 @@ +Trust module +============ + +Description +----------- + +The trust module allows to ensure presence and absence of a domain trust. + +Features +-------- + +* Trust management + +Supported FreeIPA Versions +-------------------------- + +FreeIPA versions 4.4.0 and up are supported by the ipatrust module. + +Requirements +------------ + +**Controller** + +* Ansible version: 2.8+ + +**Node** + +* Supported FreeIPA version (see above) +* samba-4 +* ipa-server-trust-ad + +Usage +===== + +Example inventory file + +```ini +[ipaserver] +ipaserver.test.local +``` + +Example playbook to ensure a one-way trust is present: +Omitting the two_way option implies the default of one-way + +```yaml +--- +- name: Playbook to ensure a one-way trust is present + hosts: ipaserver + become: true + + tasks: + - name: ensure the one-way trust present + ipatrust: + realm: ad.example.test + admin: Administrator + password: secret_password + state: present +``` + +Example playbook to ensure a two-way trust is present using a shared-secret: + +```yaml +--- +- name: Playbook to ensure a two-way trust is present + hosts: ipaserver + become: true + + tasks: + - name: ensure the two-way trust is present + ipatrust: + realm: ad.example.test + trust_secret: my_share_Secret + two_way: True + state: present +``` + +Example playbook to ensure a trust is absent: + +```yaml +--- +- name: Playbook to ensure a trust is absent + hosts: ipaserver + become: true + + tasks: + - name: ensure the trust is absent + ipatrust: + realm: ad.example.test + state: absent +``` + +This will only delete the ipa-side of the trust and it does NOT delete the id-range that matches the trust, + +Variables +========= + +ipatrust +------- + +Variable | Description | Required +-------- | ----------- | -------- +`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no +`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no +`realm` | The realm name string. | yes +`admin` | Active Directory domain administrator string. | no +`password` | Active Directory domain administrator's password string. | no +`server` | Domain controller for the Active Directory domain string. | no +`trust_secret` | Shared secret for the trust string. | no +`base_id` | First posix id for the trusted domain integer. | no +`range_size` | Size of the ID range reserved for the trusted domain integer. | no +`range_type` | Type of trusted domain ID range, It can be one of `ipa-ad-trust` or `ipa-ad-trust-posix`and defaults to `ipa-ad-trust`. | no +`two_way` | Establish bi-directional trust. By default trust is inbound one-way only. (bool) | no +`external` | Establish external trust to a domain in another forest. The trust is not transitive beyond the domain. (bool) | no +`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | yes + +Authors +======= + +Rob Verduijn diff --git a/README.md b/README.md index 246a8b4c01..073f224931 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Features * Modules for sudocmdgroup management * Modules for sudorule management * Modules for topology management +* Modules fot trust management * Modules for user management * Modules for vault management @@ -427,6 +428,7 @@ Modules in plugin/modules * [ipasudorule](README-sudorule.md) * [ipatopologysegment](README-topology.md) * [ipatopologysuffix](README-topology.md) +* [ipatrust](README-trust.md) * [ipauser](README-user.md) * [ipavault](README-vault.md) diff --git a/playbooks/trust/add-trust.yml b/playbooks/trust/add-trust.yml new file mode 100644 index 0000000000..4ceedf5dc1 --- /dev/null +++ b/playbooks/trust/add-trust.yml @@ -0,0 +1,12 @@ +--- +- name: Playbook to create a trust + hosts: ipaserver + become: true + + tasks: + - name: ensure the trust is present + ipatrust: + realm: windows.local + admin: Administrator + password: secret_password + state: present diff --git a/playbooks/trust/del-trust.yml b/playbooks/trust/del-trust.yml new file mode 100644 index 0000000000..9a8514d0ab --- /dev/null +++ b/playbooks/trust/del-trust.yml @@ -0,0 +1,10 @@ +--- +- name: Playbook to delete trust + hosts: ipaserver + become: true + + tasks: + - name: ensure the trust is absent + ipatrust: + realm: windows.local + state: absent diff --git a/plugins/modules/ipatrust.py b/plugins/modules/ipatrust.py new file mode 100644 index 0000000000..4dc144fd8f --- /dev/null +++ b/plugins/modules/ipatrust.py @@ -0,0 +1,274 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Authors: +# Rob Verduijn +# +# Copyright (C) 2019 By Rob Verduijn +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from ansible.module_utils.ansible_freeipa_module import temp_kinit, \ + temp_kdestroy, valid_creds, api_connect, api_command, module_params_get +from ansible.module_utils.basic import AnsibleModule +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['preview'], + } + +DOCUMENTATION = """ +--- +module: ipatrust +short_description: Manage FreeIPA Domain Trusts. +description: Manage FreeIPA Domain Trusts. +options: + realm: + description: + - Realm name + required: true + trust_type: + description: + - Trust type (ad for Active Directory, default) + default: ad + required: true + admin: + description: + - Active Directory domain administrator + required: false + password: + description: + - Active Directory domain administrator's password + required: false + server: + description: + - Domain controller for the Active Directory domain (optional) + required: false + trust_secret: + description: + - Shared secret for the trust + required: false + base_id: + description: + - First Posix ID of the range reserved for the trusted domain + required: false + range_size: + description: + - Size of the ID range reserved for the trusted domain + range_type: + description: + - Type of trusted domain ID range, one of ipa-ad-trust, ipa-ad-trust-posix + default: ipa-ad-trust + required: false + two_way: + description: + - Establish bi-directional trust. By default trust is inbound one-way only. + default: false + required: false + choices: ["true", "false"] + external: + description: + - Establish external trust to a domain in another forest. + - The trust is not transitive beyond the domain. + default: false + required: false + choices: ["true", "false"] + state: + description: State to ensure + default: present + required: true + choices: ["present", "absent"] + +author: + - Rob Verduijn +""" + +EXAMPLES = """ +# add ad-trust +- ipatrust: + realm: ad.example.test + trust_type: ad + admin: Administrator + password: Welcome2020! + state: present + +# delete ad-trust +- ipatrust: + realm: ad.example.test + state: absent +""" + +RETURN = """ +""" + + +def find_trust(module, realm): + _args = { + "all": True, + "cn": realm, + } + + _result = api_command(module, "trust_find", realm, _args) + + if len(_result["result"]) > 1: + module.fail_json(msg="There is more than one realm '%s'" % (realm)) + elif len(_result["result"]) == 1: + return _result["result"][0] + else: + return None + + +def del_trust(module, realm): + _args = {} + + _result = api_command(module, "trust_del", realm, _args) + if len(_result["result"]["failed"]) > 0: + module.fail_json( + msg="Trust deletion has failed for '%s'" % (realm)) + else: + return None + + +def add_trust(module, realm, args): + _args = args + + _result = api_command(module, "trust_add", realm, _args) + + if "cn" not in _result["result"]: + module.fail_json( + msg="Trust add has failed for '%s'" % (realm)) + else: + return None + + +def gen_args(trust_type, admin, password, server, trust_secret, base_id, + range_size, range_type, two_way, external): + _args = {} + if trust_type is not None: + _args["trust_type"] = trust_type + if admin is not None: + _args["realm_admin"] = admin + if password is not None: + _args["realm_passwd"] = password + if server is not None: + _args["realm_server"] = server + if trust_secret is not None: + _args["trust_secret"] = trust_secret + if base_id is not None: + _args["base_id"] = base_id + if range_size is not None: + _args["range_size"] = range_size + if two_way is not None: + _args["bidirectional"] = two_way + if external is not None: + _args["external"] = external + + return _args + + +def main(): + ansible_module = AnsibleModule( + argument_spec=dict( + # general + ipaadmin_principal=dict(type="str", default="admin"), + ipaadmin_password=dict(type="str", required=False, no_log=True), + realm=dict(type="str", default=None, required=True), + # state + state=dict(type="str", default="present", + choices=["present", "absent"]), + # present + trust_type=dict(type="str", default="ad", required=False), + admin=dict(type="str", default=None, required=False), + password=dict(type="str", default=None, + required=False, no_log=True), + server=dict(type="str", default=None, required=False), + trust_secret=dict(type="str", default=None, + required=False, no_log=True), + base_id=dict(type="int", default=None, required=False), + range_size=dict(type="int", default=200000, required=False), + range_type=dict(type="str", default="ipa-ad-trust", + required=False, choices=["ipa-ad-trust-posix", + "ipa-ad-trust"]), + two_way=dict(type="bool", default=False, required=False), + external=dict(type="bool", default=False, required=False), + ), + mutually_exclusive=[["trust_secret", "admin"]], + required_together=[["admin", "password"]], + supports_check_mode=True + ) + + ansible_module._ansible_debug = True + + # general + ipaadmin_principal = module_params_get( + ansible_module, "ipaadmin_principal") + ipaadmin_password = module_params_get(ansible_module, "ipaadmin_password") + realm = module_params_get(ansible_module, "realm") + + # state + state = module_params_get(ansible_module, "state") + + # trust + trust_type = module_params_get(ansible_module, "trust_type") + admin = module_params_get(ansible_module, "admin") + password = module_params_get(ansible_module, "password") + server = module_params_get(ansible_module, "server") + trust_secret = module_params_get(ansible_module, "trust_secret") + base_id = module_params_get(ansible_module, "base_id") + range_size = module_params_get(ansible_module, "range_size") + range_type = module_params_get(ansible_module, "range_type") + two_way = module_params_get(ansible_module, "two_way") + external = module_params_get(ansible_module, "external") + + changed = False + exit_args = {} + ccache_dir = None + ccache_name = None + try: + if not valid_creds(ansible_module, ipaadmin_principal): + ccache_dir, ccache_name = temp_kinit( + ipaadmin_principal, ipaadmin_password) + api_connect() + res_find = find_trust(ansible_module, realm) + + if state == "absent": + if res_find is not None: + del_trust(ansible_module, realm) + changed = True + elif res_find is None: + if admin is None and trust_secret is None: + ansible_module.fail_json( + msg="one of admin or trust_secret is required when state " + "is present") + else: + args = gen_args(trust_type, admin, password, server, + trust_secret, base_id, range_size, range_type, + two_way, external) + + add_trust(ansible_module, realm, args) + changed = True + + except Exception as e: + ansible_module.fail_json(msg=str(e)) + + finally: + temp_kdestroy(ccache_dir, ccache_name) + + # Done + + ansible_module.exit_json(changed=changed, **exit_args) + + +if __name__ == "__main__": + main() diff --git a/tests/trust/test_trust.yml b/tests/trust/test_trust.yml new file mode 100644 index 0000000000..dd93fdfe1c --- /dev/null +++ b/tests/trust/test_trust.yml @@ -0,0 +1,51 @@ +--- +- name: find trust + hosts: ipaserver + become: true + gather_facts: false + + tasks: + + - block: + + - name: delete trust + ipatrust: + realm: windows.local + state: absent + register: del_trust + + - name: check for trust + shell: | + echo 'SomeADMINpassword' | kinit admin + ipa trust-find windows.local + register: check_find_trust + failed_when: "'0 trusts matched' not in check_find_trust.stdout" + + - name: delete id range + shell: | + echo 'SomeADMINpassword' | kinit admin + ipa idrange-del WINDOWS.LOCAL_id_range + when: del_trust['changed'] | bool + + - name: check for range + shell: | + echo 'SomeADMINpassword' | kinit admin + ipa idrange-find WINDOWS.LOCAL_id_range + register: check_del_idrange + failed_when: "'0 ranges matched' not in check_del_idrange.stdout" + + - name: add trust + ipatrust: + realm: windows.local + admin: Administrator + password: secret_ad_pw + state: present + + - name: check for trust + shell: | + echo 'SomeADMINpassword' | kinit admin + ipa trust-find windows.local + register: check_add_trust + failed_when: "'1 trust matched' not in check_add_trust.stdout" + + when: trust_test_is_supported | default(false) \ No newline at end of file