Blame roles/ipaclient/library/ipaclient_get_otp.py

Packit 8cb997
# -*- coding: utf-8 -*-
Packit 8cb997
Packit 8cb997
# Authors:
Packit 8cb997
#   Florence Blanc-Renaud <frenaud@redhat.com>
Packit 8cb997
#
Packit 8cb997
# Copyright (C) 2017  Red Hat
Packit 8cb997
# see file 'COPYING' for use and warranty information
Packit 8cb997
#
Packit 8cb997
# This program is free software; you can redistribute it and/or modify
Packit 8cb997
# it under the terms of the GNU General Public License as published by
Packit 8cb997
# the Free Software Foundation, either version 3 of the License, or
Packit 8cb997
# (at your option) any later version.
Packit 8cb997
#
Packit 8cb997
# This program is distributed in the hope that it will be useful,
Packit 8cb997
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 8cb997
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 8cb997
# GNU General Public License for more details.
Packit 8cb997
#
Packit 8cb997
# You should have received a copy of the GNU General Public License
Packit 8cb997
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
Packit 8cb997
Packit 8cb997
ANSIBLE_METADATA = {'metadata_version': '1.0',
Packit 8cb997
                    'status': ['preview'],
Packit 8cb997
                    'supported_by': 'community'}
Packit 8cb997
Packit 8cb997
DOCUMENTATION = '''
Packit 8cb997
---
Packit 8cb997
module: ipaclient_get_otp
Packit 8cb997
short description: Manage IPA hosts
Packit 8cb997
description:
Packit 8cb997
  Manage hosts in a IPA domain.
Packit 8cb997
  The operation needs to be authenticated with Kerberos either by providing
Packit 8cb997
  a password or a keytab corresponding to a principal allowed to perform
Packit 8cb997
  host operations.
Packit 8cb997
options:
Packit 8cb997
  principal:
Packit 8cb997
    description:
Packit 8cb997
      User Principal allowed to promote replicas and join IPA realm
Packit 8cb997
    required: yes
Packit 8cb997
  ccache:
Packit 8cb997
    description: The local ccache
Packit 8cb997
    required: yes
Packit 8cb997
  fqdn:
Packit 8cb997
    description:
Packit 8cb997
      The fully-qualified hostname of the host to add/modify/remove
Packit 8cb997
    required: no
Packit 8cb997
  certificates:
Packit 8cb997
    description: A list of host certificates
Packit 8cb997
    required: yes
Packit 8cb997
  sshpubkey:
Packit 8cb997
    description: The SSH public key for the host
Packit 8cb997
    required: yes
Packit 8cb997
  ipaddress:
Packit 8cb997
    description: The IP address for the host
Packit 8cb997
    required: yes
Packit 8cb997
  random:
Packit 8cb997
    description: Generate a random password to be used in bulk enrollment
Packit 8cb997
    required: yes
Packit 8cb997
  state:
Packit 8cb997
    description: The desired host state
Packit 8cb997
    required: yes
Packit 8cb997
author:
Packit 8cb997
    - "Florence Blanc-Renaud"
Packit 8cb997
'''
Packit 8cb997
Packit 8cb997
EXAMPLES = '''
Packit 8cb997
# Example from Ansible Playbooks
Packit 8cb997
# Add a new host with a random OTP, authenticate using principal/password
Packit 8cb997
- ipaclient_get_otp:
Packit 8cb997
    principal: admin
Packit 8cb997
    password: MySecretPassword
Packit 8cb997
    fqdn: ipaclient.ipa.domain.com
Packit 8cb997
    ipaddress: 192.168.100.23
Packit 8cb997
    random: True
Packit 8cb997
  register: result_ipaclient_get_otp
Packit 8cb997
'''
Packit 8cb997
Packit 8cb997
RETURN = '''
Packit 8cb997
host:
Packit 8cb997
  description: the host structure as returned from IPA API
Packit 8cb997
  returned: always
Packit 8cb997
  type: complex
Packit 8cb997
  contains:
Packit 8cb997
    dn:
Packit 8cb997
      description: the DN of the host entry
Packit 8cb997
      type: string
Packit 8cb997
      returned: always
Packit 8cb997
    fqdn:
Packit 8cb997
      description: the fully qualified host name
Packit 8cb997
      type: string
Packit 8cb997
      returned: always
Packit 8cb997
    has_keytab:
Packit 8cb997
      description: whether the host entry contains a keytab
Packit 8cb997
      type: bool
Packit 8cb997
      returned: always
Packit 8cb997
    has_password:
Packit 8cb997
      description: whether the host entry contains a password
Packit 8cb997
      type: bool
Packit 8cb997
      returned: always
Packit 8cb997
    managedby_host:
Packit 8cb997
      description: the list of hosts managing the host
Packit 8cb997
      type: list
Packit 8cb997
      returned: always
Packit 8cb997
    randompassword:
Packit 8cb997
      description: the OneTimePassword generated for this host
Packit 8cb997
      type: string
Packit 8cb997
      returned: changed
Packit 8cb997
    certificates:
Packit 8cb997
      description: the list of host certificates
Packit 8cb997
      type: list
Packit 8cb997
      returned: when present
Packit 8cb997
    sshpubkey:
Packit 8cb997
      description: the SSH public key for the host
Packit 8cb997
      type: string
Packit 8cb997
      returned: when present
Packit 8cb997
    ipaddress:
Packit 8cb997
      description: the IP address for the host
Packit 8cb997
      type: string
Packit 8cb997
      returned: when present
Packit 8cb997
'''
Packit 8cb997
Packit 8cb997
import os
Packit 8cb997
import six
Packit 8cb997
Packit 8cb997
from ansible.module_utils.basic import AnsibleModule
Packit 8cb997
Packit 8cb997
from ipalib import api, errors
Packit 8cb997
from ipaplatform.paths import paths
Packit 8cb997
from ipapython.ipautil import run
Packit 8cb997
Packit 8cb997
if six.PY3:
Packit 8cb997
    unicode = str
Packit 8cb997
Packit 8cb997
Packit 8cb997
def get_host_diff(ipa_host, module_host):
Packit 8cb997
    """
Packit Service 0f71a7
    Build a dict with the differences from two host dicts.
Packit 8cb997
Packit 8cb997
    :param ipa_host: the host structure seen from IPA
Packit 8cb997
    :param module_host: the target host structure seen from the module params
Packit 8cb997
Packit 8cb997
    :return: a dict representing the host attributes to apply
Packit 8cb997
    """
Packit 8cb997
    non_updateable_keys = ['ip_address']
Packit 8cb997
    data = dict()
Packit 8cb997
    for key in non_updateable_keys:
Packit 8cb997
        if key in module_host:
Packit 8cb997
            del module_host[key]
Packit 8cb997
Packit 8cb997
    for key in module_host.keys():
Packit 8cb997
        ipa_value = ipa_host.get(key, None)
Packit 8cb997
        module_value = module_host.get(key, None)
Packit 8cb997
        if isinstance(ipa_value, list) and not isinstance(module_value, list):
Packit 8cb997
            module_value = [module_value]
Packit 8cb997
        if isinstance(ipa_value, list) and isinstance(module_value, list):
Packit 8cb997
            ipa_value = sorted(ipa_value)
Packit 8cb997
            module_value = sorted(module_value)
Packit 8cb997
        if ipa_value != module_value:
Packit 8cb997
            data[key] = unicode(module_value)
Packit 8cb997
    return data
Packit 8cb997
Packit 8cb997
Packit 8cb997
def get_module_host(module):
Packit 8cb997
    """
Packit Service 0f71a7
    Create a structure representing the host information.
Packit 8cb997
Packit 8cb997
    Reads the module parameters and builds the host structure as expected from
Packit 8cb997
    the module
Packit 8cb997
    :param module: the ansible module
Packit 8cb997
    :returns: a dict representing the host attributes
Packit 8cb997
    """
Packit 8cb997
    data = dict()
Packit 8cb997
    certificates = module.params.get('certificates')
Packit 8cb997
    if certificates:
Packit 8cb997
        data['usercertificate'] = certificates
Packit 8cb997
    sshpubkey = module.params.get('sshpubkey')
Packit 8cb997
    if sshpubkey:
Packit 8cb997
        data['ipasshpubkey'] = unicode(sshpubkey)
Packit 8cb997
    ipaddress = module.params.get('ipaddress')
Packit 8cb997
    if ipaddress:
Packit 8cb997
        data['ip_address'] = unicode(ipaddress)
Packit 8cb997
    random = module.params.get('random')
Packit 8cb997
    if random:
Packit 8cb997
        data['random'] = random
Packit 8cb997
    return data
Packit 8cb997
Packit 8cb997
Packit 8cb997
def ensure_host_present(module, api, ipahost):
Packit 8cb997
    """
Packit Service 0f71a7
    Ensure host exists in IPA and has the same attributes.
Packit 8cb997
Packit 8cb997
    :param module: the ansible module
Packit 8cb997
    :param api: IPA api handle
Packit 8cb997
    :param ipahost: the host information present in IPA, can be none if the
Packit 8cb997
                    host does not exist
Packit 8cb997
    """
Packit 8cb997
    fqdn = unicode(module.params.get('fqdn'))
Packit 8cb997
    if ipahost:
Packit 8cb997
        # Host already present, need to compare the attributes
Packit 8cb997
        module_host = get_module_host(module)
Packit 8cb997
        diffs = get_host_diff(ipahost, module_host)
Packit 8cb997
Packit 8cb997
        if not diffs:
Packit 8cb997
            # Same attributes, success
Packit 8cb997
            module.exit_json(changed=False, host=ipahost)
Packit 8cb997
Packit 8cb997
        # Need to modify the host - only if not in check_mode
Packit 8cb997
        if module.check_mode:
Packit 8cb997
            module.exit_json(changed=True)
Packit 8cb997
Packit 8cb997
        # If we want to create a random password, and the host
Packit 8cb997
        # already has Keytab: true, then we need first to run
Packit 8cb997
        # ipa host-disable in order to remove OTP and keytab
Packit 8cb997
        if module.params.get('random') and ipahost['has_keytab'] is True:
Packit 8cb997
            api.Command.host_disable(fqdn)
Packit 8cb997
Packit 8cb997
        result = api.Command.host_mod(fqdn, **diffs)
Packit 8cb997
        # Save random password as it is not displayed by host-show
Packit 8cb997
        if module.params.get('random'):
Packit 8cb997
            randompassword = result['result']['randompassword']
Packit 8cb997
        result = api.Command.host_show(fqdn)
Packit 8cb997
        if module.params.get('random'):
Packit 8cb997
            result['result']['randompassword'] = randompassword
Packit 8cb997
        module.exit_json(changed=True, host=result['result'])
Packit 8cb997
Packit 8cb997
    if not ipahost:
Packit 8cb997
        # Need to add the user, only if not in check_mode
Packit 8cb997
        if module.check_mode:
Packit 8cb997
            module.exit_json(changed=True)
Packit 8cb997
Packit 8cb997
        # Must add the user
Packit 8cb997
        module_host = get_module_host(module)
Packit 8cb997
        # force creation of host even if there is no DNS record
Packit 8cb997
        module_host["force"] = True
Packit 8cb997
        result = api.Command.host_add(fqdn, **module_host)
Packit 8cb997
        # Save random password as it is not displayed by host-show
Packit 8cb997
        if module.params.get('random'):
Packit 8cb997
            randompassword = result['result']['randompassword']
Packit 8cb997
        result = api.Command.host_show(fqdn)
Packit 8cb997
        if module.params.get('random'):
Packit 8cb997
            result['result']['randompassword'] = randompassword
Packit 8cb997
        module.exit_json(changed=True, host=result['result'])
Packit 8cb997
Packit 8cb997
Packit 8cb997
def ensure_host_absent(module, api, host):
Packit 8cb997
    """
Packit Service 0f71a7
    Ensure host does not exist in IPA.
Packit 8cb997
Packit 8cb997
    :param module: the ansible module
Packit 8cb997
    :param api: the IPA API handle
Packit 8cb997
    :param host: the host information present in IPA, can be none if the
Packit 8cb997
                 host does not exist
Packit 8cb997
    """
Packit 8cb997
    if not host:
Packit 8cb997
        # Nothing to do, host already removed
Packit 8cb997
        module.exit_json(changed=False)
Packit 8cb997
Packit 8cb997
    # Need to remove the host - only if not in check_mode
Packit 8cb997
    if module.check_mode:
Packit 8cb997
        module.exit_json(changed=True, host=host)
Packit 8cb997
Packit 8cb997
    fqdn = unicode(module.params.get('fqdn'))
Packit 8cb997
    try:
Packit 8cb997
        api.Command.host_del(fqdn)
Packit 8cb997
    except Exception as e:
Packit 8cb997
        module.fail_json(msg="Failed to remove host: %s" % e)
Packit 8cb997
Packit 8cb997
    module.exit_json(changed=True)
Packit 8cb997
Packit 8cb997
Packit 8cb997
def main():
Packit Service 0f71a7
Packit 8cb997
    module = AnsibleModule(
Packit 8cb997
        argument_spec=dict(
Packit 8cb997
            principal=dict(default='admin'),
Packit 8cb997
            ccache=dict(required=False, type='path'),
Packit 8cb997
            fqdn=dict(required=True),
Packit 8cb997
            certificates=dict(required=False, type='list'),
Packit 8cb997
            sshpubkey=dict(required=False),
Packit 8cb997
            ipaddress=dict(required=False),
Packit 8cb997
            random=dict(default=False, type='bool'),
Packit 8cb997
            state=dict(default='present', choices=['present', 'absent']),
Packit 8cb997
        ),
Packit 8cb997
        supports_check_mode=True,
Packit 8cb997
    )
Packit 8cb997
Packit 8cb997
    ccache = module.params.get('ccache')
Packit 8cb997
    fqdn = unicode(module.params.get('fqdn'))
Packit 8cb997
    state = module.params.get('state')
Packit 8cb997
Packit 8cb997
    try:
Packit 8cb997
        os.environ['KRB5CCNAME'] = ccache
Packit 8cb997
Packit 8cb997
        cfg = dict(
Packit 8cb997
            context='ansible_module',
Packit 8cb997
            confdir=paths.ETC_IPA,
Packit 8cb997
            in_server=False,
Packit 8cb997
            debug=False,
Packit 8cb997
            verbose=0,
Packit 8cb997
        )
Packit 8cb997
        api.bootstrap(**cfg)
Packit 8cb997
        api.finalize()
Packit 8cb997
        api.Backend.rpcclient.connect()
Packit 8cb997
Packit 8cb997
        try:
Packit 8cb997
            result = api.Command.host_show(fqdn, all=True)
Packit 8cb997
            host = result['result']
Packit 8cb997
        except errors.NotFound:
Packit 8cb997
            host = None
Packit 8cb997
Packit 8cb997
        if state in ['present', 'disabled']:
Packit 8cb997
            ensure_host_present(module, api, host)
Packit 8cb997
        elif state == 'absent':
Packit 8cb997
            ensure_host_absent(module, api, host)
Packit 8cb997
Packit 8cb997
    except Exception as e:
Packit 8cb997
        module.fail_json(msg="ipaclient_get_otp module failed : %s" % str(e))
Packit 8cb997
    finally:
Packit 8cb997
        run([paths.KDESTROY], raiseonerr=False, env=os.environ)
Packit 8cb997
Packit 8cb997
    module.exit_json(changed=False, host=host)
Packit 8cb997
Packit 8cb997
Packit 8cb997
if __name__ == '__main__':
Packit 8cb997
    main()