Blame plugins/module_utils/ansible_freeipa_module.py

Packit 8cb997
#!/usr/bin/python
Packit 8cb997
# -*- coding: utf-8 -*-
Packit 8cb997
Packit 8cb997
# Authors:
Packit 8cb997
#   Thomas Woerner <twoerner@redhat.com>
Packit 8cb997
#
Packit 8cb997
# Copyright (C) 2019  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
Packit 8cb997
import os
Packit 8cb997
import uuid
Packit 8cb997
import tempfile
Packit 8cb997
import shutil
Packit 8cb997
import gssapi
Packit 8cb997
from datetime import datetime
Packit 8cb997
from ipalib import api
Packit 762bda
from ipalib import errors as ipalib_errors
Packit 8cb997
from ipalib.config import Env
Packit 8cb997
from ipalib.constants import DEFAULT_CONFIG, LDAP_GENERALIZED_TIME_FORMAT
Packit 8cb997
try:
Packit 8cb997
    from ipalib.install.kinit import kinit_password, kinit_keytab
Packit 8cb997
except ImportError:
Packit 8cb997
    from ipapython.ipautil import kinit_password, kinit_keytab
Packit 8cb997
from ipapython.ipautil import run
Packit 8cb997
from ipaplatform.paths import paths
Packit 8cb997
from ipalib.krb_utils import get_credentials_if_valid
Packit 8cb997
from ansible.module_utils._text import to_text
Packit 8cb997
try:
Packit 8cb997
    from ipalib.x509 import Encoding
Packit 8cb997
except ImportError:
Packit 8cb997
    from cryptography.hazmat.primitives.serialization import Encoding
Packit e64f81
import socket
Packit 8cb997
import base64
Packit 8cb997
import six
Packit 8cb997
Packit 8cb997
Packit 8cb997
if six.PY3:
Packit 8cb997
    unicode = str
Packit 8cb997
Packit 8cb997
Packit 8cb997
def valid_creds(module, principal):
Packit 8cb997
    """
Packit 8cb997
    Get valid credintials matching the princial, try GSSAPI first
Packit 8cb997
    """
Packit 8cb997
    if "KRB5CCNAME" in os.environ:
Packit 8cb997
        ccache = os.environ["KRB5CCNAME"]
Packit 8cb997
        module.debug('KRB5CCNAME set to %s' % ccache)
Packit 8cb997
Packit 8cb997
        try:
Packit 8cb997
            cred = gssapi.Credentials(usage='initiate',
Packit 8cb997
                                      store={'ccache': ccache})
Packit 8cb997
        except gssapi.raw.misc.GSSError as e:
Packit 8cb997
            module.fail_json(msg='Failed to find default ccache: %s' % e)
Packit 8cb997
        else:
Packit 8cb997
            module.debug("Using principal %s" % str(cred.name))
Packit 8cb997
            return True
Packit 8cb997
Packit 8cb997
    elif "KRB5_CLIENT_KTNAME" in os.environ:
Packit 8cb997
        keytab = os.environ.get('KRB5_CLIENT_KTNAME', None)
Packit 8cb997
        module.debug('KRB5_CLIENT_KTNAME set to %s' % keytab)
Packit 8cb997
Packit 8cb997
        ccache_name = "MEMORY:%s" % str(uuid.uuid4())
Packit 8cb997
        os.environ["KRB5CCNAME"] = ccache_name
Packit 8cb997
Packit 8cb997
        try:
Packit 8cb997
            cred = kinit_keytab(principal, keytab, ccache_name)
Packit 8cb997
        except gssapi.raw.misc.GSSError as e:
Packit 8cb997
            module.fail_json(msg='Kerberos authentication failed : %s' % e)
Packit 8cb997
        else:
Packit 8cb997
            module.debug("Using principal %s" % str(cred.name))
Packit 8cb997
            return True
Packit 8cb997
Packit 8cb997
    creds = get_credentials_if_valid()
Packit 8cb997
    if creds and \
Packit 8cb997
       creds.lifetime > 0 and \
Packit 8cb997
       "%s@" % principal in creds.name.display_as(creds.name.name_type):
Packit 8cb997
        return True
Packit 8cb997
    return False
Packit 8cb997
Packit 8cb997
Packit 8cb997
def temp_kinit(principal, password):
Packit 8cb997
    """
Packit 8cb997
    kinit with password using a temporary ccache
Packit 8cb997
    """
Packit 8cb997
    if not password:
Packit 8cb997
        raise RuntimeError("The password is not set")
Packit 8cb997
    if not principal:
Packit 8cb997
        principal = "admin"
Packit 8cb997
Packit 8cb997
    ccache_dir = tempfile.mkdtemp(prefix='krbcc')
Packit 8cb997
    ccache_name = os.path.join(ccache_dir, 'ccache')
Packit 8cb997
Packit 8cb997
    try:
Packit 8cb997
        kinit_password(principal, password, ccache_name)
Packit 8cb997
    except RuntimeError as e:
Packit 8cb997
        raise RuntimeError("Kerberos authentication failed: {}".format(e))
Packit 8cb997
Packit 8cb997
    return ccache_dir, ccache_name
Packit 8cb997
Packit 8cb997
Packit 8cb997
def temp_kdestroy(ccache_dir, ccache_name):
Packit 8cb997
    """
Packit 8cb997
    Destroy temporary ticket and remove temporary ccache
Packit 8cb997
    """
Packit 8cb997
    if ccache_name is not None:
Packit 8cb997
        run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False)
Packit 8cb997
    if ccache_dir is not None:
Packit 8cb997
        shutil.rmtree(ccache_dir, ignore_errors=True)
Packit 8cb997
Packit 8cb997
Packit 8cb997
def api_connect(context=None):
Packit 8cb997
    """
Packit 8cb997
    Create environment, initialize api and connect to ldap2
Packit 8cb997
    """
Packit 8cb997
    env = Env()
Packit 8cb997
    env._bootstrap()
Packit 8cb997
    env._finalize_core(**dict(DEFAULT_CONFIG))
Packit 8cb997
Packit 8cb997
    # available contexts are 'server', 'ansible-freeipa' and 'cli_installer'
Packit 8cb997
    if context is None:
Packit 8cb997
        context = 'server'
Packit 8cb997
Packit 8cb997
    api.bootstrap(context=context, debug=env.debug, log=None)
Packit 8cb997
    api.finalize()
Packit 8cb997
Packit 8cb997
    if api.env.in_server:
Packit 8cb997
        backend = api.Backend.ldap2
Packit 8cb997
    else:
Packit 8cb997
        backend = api.Backend.rpcclient
Packit 8cb997
Packit 8cb997
    if not backend.isconnected():
Packit 8cb997
        backend.connect()
Packit 8cb997
Packit 8cb997
Packit 8cb997
def api_command(module, command, name, args):
Packit 8cb997
    """
Packit 8cb997
    Call ipa.Command
Packit 8cb997
    """
Packit 8cb997
    return api.Command[command](name, **args)
Packit 8cb997
Packit 8cb997
Packit 8cb997
def api_command_no_name(module, command, args):
Packit 8cb997
    """
Packit 8cb997
    Call ipa.Command without a name.
Packit 8cb997
    """
Packit 8cb997
    return api.Command[command](**args)
Packit 8cb997
Packit 8cb997
Packit 8cb997
def api_check_param(command, name):
Packit 8cb997
    """
Packit 8cb997
    Return if param exists in command param list
Packit 8cb997
    """
Packit 8cb997
    return name in api.Command[command].params
Packit 8cb997
Packit 8cb997
Packit 8cb997
def execute_api_command(module, principal, password, command, name, args):
Packit 8cb997
    """
Packit 8cb997
    Get KRB ticket if not already there, initialize api, connect,
Packit 8cb997
    execute command and destroy ticket again if it has been created also.
Packit 8cb997
    """
Packit 8cb997
    ccache_dir = None
Packit 8cb997
    ccache_name = None
Packit 8cb997
    try:
Packit 8cb997
        if not valid_creds(module, principal):
Packit 8cb997
            ccache_dir, ccache_name = temp_kinit(principal, password)
Packit 8cb997
        api_connect()
Packit 8cb997
Packit 8cb997
        return api_command(module, command, name, args)
Packit 8cb997
    except Exception as e:
Packit 8cb997
        module.fail_json(msg=str(e))
Packit 8cb997
Packit 8cb997
    finally:
Packit 8cb997
        temp_kdestroy(ccache_dir, ccache_name)
Packit 8cb997
Packit 8cb997
Packit 8cb997
def date_format(value):
Packit 8cb997
    accepted_date_formats = [
Packit 8cb997
        LDAP_GENERALIZED_TIME_FORMAT,  # generalized time
Packit 8cb997
        '%Y-%m-%dT%H:%M:%SZ',  # ISO 8601, second precision
Packit 8cb997
        '%Y-%m-%dT%H:%MZ',     # ISO 8601, minute precision
Packit 8cb997
        '%Y-%m-%dZ',           # ISO 8601, date only
Packit 8cb997
        '%Y-%m-%d %H:%M:%SZ',  # non-ISO 8601, second precision
Packit 8cb997
        '%Y-%m-%d %H:%MZ',     # non-ISO 8601, minute precision
Packit 8cb997
    ]
Packit 8cb997
Packit 8cb997
    for date_format in accepted_date_formats:
Packit 8cb997
        try:
Packit 8cb997
            return datetime.strptime(value, date_format)
Packit 8cb997
        except ValueError:
Packit 8cb997
            pass
Packit 8cb997
    raise ValueError("Invalid date '%s'" % value)
Packit 8cb997
Packit 8cb997
Packit 8cb997
def compare_args_ipa(module, args, ipa):
Packit 8cb997
    for key in args.keys():
Packit 8cb997
        if key not in ipa:
Packit 8cb997
            return False
Packit 8cb997
        else:
Packit 8cb997
            arg = args[key]
Packit 8cb997
            ipa_arg = ipa[key]
Packit 8cb997
            # If ipa_arg is a list and arg is not, replace arg
Packit 8cb997
            # with list containing arg. Most args in a find result
Packit 8cb997
            # are lists, but not all.
Packit 8cb997
            if isinstance(ipa_arg, tuple):
Packit 8cb997
                ipa_arg = list(ipa_arg)
Packit 8cb997
            if isinstance(ipa_arg, list):
Packit 8cb997
                if not isinstance(arg, list):
Packit 8cb997
                    arg = [arg]
Packit 8cb997
                if isinstance(ipa_arg[0], str) and isinstance(arg[0], int):
Packit 8cb997
                    arg = [to_text(_arg) for _arg in arg]
Packit 8cb997
                if isinstance(ipa_arg[0], unicode) and isinstance(arg[0], int):
Packit 8cb997
                    arg = [to_text(_arg) for _arg in arg]
Packit 24e690
            # module.warn("%s <=> %s" % (repr(arg), repr(ipa_arg)))
Packit 24e690
            try:
Packit 24e690
                arg_set = set(arg)
Packit 24e690
                ipa_arg_set = set(ipa_arg)
Packit 24e690
            except TypeError:
Packit 24e690
                if arg != ipa_arg:
Packit 24e690
                    # module.warn("%s != %s" % (repr(arg), repr(ipa_arg)))
Packit 24e690
                    return False
Packit 24e690
            else:
Packit 24e690
                if arg_set != ipa_arg_set:
Packit 24e690
                    # module.warn("%s != %s" % (repr(arg), repr(ipa_arg)))
Packit 24e690
                    return False
Packit 24e690
Packit 24e690
        # module.warn("%s == %s" % (repr(arg), repr(ipa_arg)))
Packit 8cb997
Packit 8cb997
    return True
Packit 8cb997
Packit 8cb997
Packit 8cb997
def _afm_convert(value):
Packit 8cb997
    if value is not None:
Packit 8cb997
        if isinstance(value, list):
Packit 8cb997
            return [_afm_convert(x) for x in value]
Packit 8cb997
        elif isinstance(value, dict):
Packit 8cb997
            return {_afm_convert(k): _afm_convert(v) for k, v in value.items()}
Packit 8cb997
        elif isinstance(value, str):
Packit 8cb997
            return to_text(value)
Packit 8cb997
        else:
Packit 8cb997
            return value
Packit 8cb997
    else:
Packit 8cb997
        return value
Packit 8cb997
Packit 8cb997
Packit 8cb997
def module_params_get(module, name):
Packit 8cb997
    return _afm_convert(module.params.get(name))
Packit 8cb997
Packit 8cb997
Packit 8cb997
def api_get_realm():
Packit 8cb997
    return api.env.realm
Packit 8cb997
Packit 8cb997
Packit 8cb997
def gen_add_del_lists(user_list, res_list):
Packit 8cb997
    """
Packit 8cb997
    Generate the lists for the addition and removal of members using the
Packit 8cb997
    provided user and ipa settings
Packit 8cb997
    """
Packit 8cb997
    add_list = list(set(user_list or []) - set(res_list or []))
Packit 8cb997
    del_list = list(set(res_list or []) - set(user_list or []))
Packit 8cb997
Packit 8cb997
    return add_list, del_list
Packit 8cb997
Packit 8cb997
Packit 8cb997
def encode_certificate(cert):
Packit 8cb997
    """
Packit 8cb997
    Encode a certificate using base64 with also taking FreeIPA and Python
Packit 8cb997
    versions into account
Packit 8cb997
    """
Packit 8cb997
    if isinstance(cert, str) or isinstance(cert, unicode):
Packit 8cb997
        encoded = base64.b64encode(cert)
Packit 8cb997
    else:
Packit 8cb997
        encoded = base64.b64encode(cert.public_bytes(Encoding.DER))
Packit 8cb997
    if not six.PY2:
Packit 8cb997
        encoded = encoded.decode('ascii')
Packit 8cb997
    return encoded
Packit e64f81
Packit e64f81
Packit e64f81
def is_ipv4_addr(ipaddr):
Packit e64f81
    """
Packit e64f81
    Test if figen IP address is a valid IPv4 address
Packit e64f81
    """
Packit e64f81
    try:
Packit e64f81
        socket.inet_pton(socket.AF_INET, ipaddr)
Packit e64f81
    except socket.error:
Packit e64f81
        return False
Packit e64f81
    return True
Packit e64f81
Packit e64f81
Packit e64f81
def is_ipv6_addr(ipaddr):
Packit e64f81
    """
Packit e64f81
    Test if figen IP address is a valid IPv6 address
Packit e64f81
    """
Packit e64f81
    try:
Packit e64f81
        socket.inet_pton(socket.AF_INET6, ipaddr)
Packit e64f81
    except socket.error:
Packit e64f81
        return False
Packit e64f81
    return True