|
Packit |
8cb997 |
#!/usr/bin/python
|
|
Packit |
8cb997 |
# -*- coding: utf-8 -*-
|
|
Packit |
8cb997 |
|
|
Packit |
8cb997 |
# Authors:
|
|
Packit Service |
0f71a7 |
# Sergio Oliveira Campos <seocam@redhat.com>
|
|
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 Service |
0f71a7 |
from pprint import pformat
|
|
Packit |
8cb997 |
from ipalib import api
|
|
Packit Service |
0f71a7 |
from ipalib import errors as ipalib_errors # noqa
|
|
Packit |
8cb997 |
from ipalib.config import Env
|
|
Packit |
8cb997 |
from ipalib.constants import DEFAULT_CONFIG, LDAP_GENERALIZED_TIME_FORMAT
|
|
Packit Service |
0f71a7 |
|
|
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 Service |
0f71a7 |
from ipapython.dn import DN
|
|
Packit |
8cb997 |
from ipaplatform.paths import paths
|
|
Packit |
8cb997 |
from ipalib.krb_utils import get_credentials_if_valid
|
|
Packit Service |
0f71a7 |
from ansible.module_utils.basic import AnsibleModule
|
|
Packit |
8cb997 |
from ansible.module_utils._text import to_text
|
|
Packit Service |
0f71a7 |
|
|
Packit |
8cb997 |
try:
|
|
Packit |
8cb997 |
from ipalib.x509 import Encoding
|
|
Packit |
8cb997 |
except ImportError:
|
|
Packit |
8cb997 |
from cryptography.hazmat.primitives.serialization import Encoding
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
try:
|
|
Packit Service |
0f71a7 |
from ipalib.x509 import load_pem_x509_certificate
|
|
Packit Service |
0f71a7 |
except ImportError:
|
|
Packit Service |
0f71a7 |
from ipalib.x509 import load_certificate
|
|
Packit Service |
0f71a7 |
load_pem_x509_certificate = None
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
import socket
|
|
Packit |
8cb997 |
import base64
|
|
Packit |
8cb997 |
import six
|
|
Packit |
8cb997 |
|
|
Packit Service |
0f71a7 |
try:
|
|
Packit Service |
0f71a7 |
from collections.abc import Mapping # noqa
|
|
Packit Service |
0f71a7 |
except ImportError:
|
|
Packit Service |
0f71a7 |
from collections import Mapping # noqa
|
|
Packit Service |
0f71a7 |
|
|
Packit |
8cb997 |
|
|
Packit |
8cb997 |
if six.PY3:
|
|
Packit |
8cb997 |
unicode = str
|
|
Packit |
8cb997 |
|
|
Packit |
8cb997 |
|
|
Packit Service |
0f71a7 |
def valid_creds(module, principal): # noqa
|
|
Packit Service |
0f71a7 |
"""Get valid credentials matching the princial, try GSSAPI first."""
|
|
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 Service |
0f71a7 |
"""Kinit with password using a temporary ccache."""
|
|
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 Service |
0f71a7 |
os.environ["KRB5CCNAME"] = ccache_name
|
|
Packit |
8cb997 |
return ccache_dir, ccache_name
|
|
Packit |
8cb997 |
|
|
Packit |
8cb997 |
|
|
Packit |
8cb997 |
def temp_kdestroy(ccache_dir, ccache_name):
|
|
Packit Service |
0f71a7 |
"""Destroy temporary ticket and remove temporary ccache."""
|
|
Packit |
8cb997 |
if ccache_name is not None:
|
|
Packit |
8cb997 |
run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False)
|
|
Packit Service |
0f71a7 |
del os.environ['KRB5CCNAME']
|
|
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 Service |
0f71a7 |
Initialize IPA API with the provided context.
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
`context` can be any of:
|
|
Packit Service |
0f71a7 |
* `server` (default)
|
|
Packit Service |
0f71a7 |
* `ansible-freeipa`
|
|
Packit Service |
0f71a7 |
* `cli_installer`
|
|
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 Service |
0f71a7 |
backend.connect(ccache=os.environ.get('KRB5CCNAME', None))
|
|
Packit |
8cb997 |
|
|
Packit |
8cb997 |
|
|
Packit |
8cb997 |
def api_command(module, command, name, args):
|
|
Packit Service |
0f71a7 |
"""Call ipa.Command."""
|
|
Packit |
8cb997 |
return api.Command[command](name, **args)
|
|
Packit |
8cb997 |
|
|
Packit |
8cb997 |
|
|
Packit |
8cb997 |
def api_command_no_name(module, command, args):
|
|
Packit Service |
0f71a7 |
"""Call ipa.Command without a name."""
|
|
Packit |
8cb997 |
return api.Command[command](**args)
|
|
Packit |
8cb997 |
|
|
Packit |
8cb997 |
|
|
Packit Service |
0f71a7 |
def api_check_command(command):
|
|
Packit Service |
0f71a7 |
"""Return if command exists in command list."""
|
|
Packit Service |
0f71a7 |
return command in api.Command
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
|
|
Packit |
8cb997 |
def api_check_param(command, name):
|
|
Packit Service |
0f71a7 |
"""Check if param exists in command param list."""
|
|
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 Service |
0f71a7 |
Execute an API command.
|
|
Packit Service |
0f71a7 |
|
|
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 Service |
0f71a7 |
def compare_args_ipa(module, args, ipa): # noqa
|
|
Packit Service |
0f71a7 |
"""Compare IPA obj attrs with the command args.
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
This function compares IPA objects attributes with the args the
|
|
Packit Service |
0f71a7 |
module is intending to use to call a command. This is useful to know
|
|
Packit Service |
0f71a7 |
if call to IPA server will be needed or not.
|
|
Packit Service |
0f71a7 |
In other to compare we have to prepare the perform slight changes in
|
|
Packit Service |
0f71a7 |
data formats.
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
Returns True if they are the same and False otherwise.
|
|
Packit Service |
0f71a7 |
"""
|
|
Packit Service |
0f71a7 |
base_debug_msg = "Ansible arguments and IPA commands differed. "
|
|
Packit Service |
0f71a7 |
|
|
Packit |
8cb997 |
for key in args.keys():
|
|
Packit |
8cb997 |
if key not in ipa:
|
|
Packit Service |
0f71a7 |
module.debug(
|
|
Packit Service |
0f71a7 |
base_debug_msg + "Command key not present in IPA: %s" % key
|
|
Packit Service |
0f71a7 |
)
|
|
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 Service |
0f71a7 |
if len(ipa_arg) != len(arg):
|
|
Packit Service |
0f71a7 |
module.debug(
|
|
Packit Service |
0f71a7 |
base_debug_msg
|
|
Packit Service |
0f71a7 |
+ "List length doesn't match for key %s: %d %d"
|
|
Packit Service |
0f71a7 |
% (key, len(arg), len(ipa_arg),)
|
|
Packit Service |
0f71a7 |
)
|
|
Packit Service |
0f71a7 |
return False
|
|
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 Service |
0f71a7 |
try:
|
|
Packit Service |
0f71a7 |
arg_set = set(arg)
|
|
Packit Service |
0f71a7 |
ipa_arg_set = set(ipa_arg)
|
|
Packit Service |
0f71a7 |
except TypeError:
|
|
Packit Service |
0f71a7 |
if arg != ipa_arg:
|
|
Packit Service |
0f71a7 |
module.debug(
|
|
Packit Service |
0f71a7 |
base_debug_msg
|
|
Packit Service |
0f71a7 |
+ "Different values: %s %s" % (arg, ipa_arg)
|
|
Packit Service |
0f71a7 |
)
|
|
Packit Service |
0f71a7 |
return False
|
|
Packit Service |
0f71a7 |
else:
|
|
Packit Service |
0f71a7 |
if arg_set != ipa_arg_set:
|
|
Packit Service |
0f71a7 |
module.debug(
|
|
Packit Service |
0f71a7 |
base_debug_msg
|
|
Packit Service |
0f71a7 |
+ "Different set content: %s %s"
|
|
Packit Service |
0f71a7 |
% (arg_set, ipa_arg_set,)
|
|
Packit Service |
0f71a7 |
)
|
|
Packit Service |
0f71a7 |
return False
|
|
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 Service |
0f71a7 |
"""Generate the lists for the addition and removal of members."""
|
|
Packit Service |
0f71a7 |
# The user list is None, therefore the parameter should not be touched
|
|
Packit Service |
0f71a7 |
if user_list is None:
|
|
Packit Service |
0f71a7 |
return [], []
|
|
Packit Service |
0f71a7 |
|
|
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 Service |
0f71a7 |
Encode a certificate using base64.
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
It also takes FreeIPA and Python versions into account.
|
|
Packit |
8cb997 |
"""
|
|
Packit Service |
0f71a7 |
if isinstance(cert, (str, unicode, bytes)):
|
|
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 Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def load_cert_from_str(cert):
|
|
Packit Service |
0f71a7 |
cert = cert.strip()
|
|
Packit Service |
0f71a7 |
if not cert.startswith("-----BEGIN CERTIFICATE-----"):
|
|
Packit Service |
0f71a7 |
cert = "-----BEGIN CERTIFICATE-----\n" + cert
|
|
Packit Service |
0f71a7 |
if not cert.endswith("-----END CERTIFICATE-----"):
|
|
Packit Service |
0f71a7 |
cert += "\n-----END CERTIFICATE-----"
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
if load_pem_x509_certificate is not None:
|
|
Packit Service |
0f71a7 |
cert = load_pem_x509_certificate(cert.encode('utf-8'))
|
|
Packit Service |
0f71a7 |
else:
|
|
Packit Service |
0f71a7 |
cert = load_certificate(cert.encode('utf-8'))
|
|
Packit Service |
0f71a7 |
return cert
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def DN_x500_text(text):
|
|
Packit Service |
0f71a7 |
if hasattr(DN, "x500_text"):
|
|
Packit Service |
0f71a7 |
return DN(text).x500_text()
|
|
Packit Service |
0f71a7 |
else:
|
|
Packit Service |
0f71a7 |
# Emulate x500_text
|
|
Packit Service |
0f71a7 |
dn = DN(text)
|
|
Packit Service |
0f71a7 |
dn.rdns = reversed(dn.rdns)
|
|
Packit Service |
0f71a7 |
return str(dn)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def is_valid_port(port):
|
|
Packit Service |
0f71a7 |
if not isinstance(port, int):
|
|
Packit Service |
0f71a7 |
return False
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
if 1 <= port <= 65535:
|
|
Packit Service |
0f71a7 |
return True
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
return False
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def is_ipv4_addr(ipaddr):
|
|
Packit Service |
0f71a7 |
"""Test if given IP address is a valid IPv4 address."""
|
|
Packit Service |
0f71a7 |
try:
|
|
Packit Service |
0f71a7 |
socket.inet_pton(socket.AF_INET, ipaddr)
|
|
Packit Service |
0f71a7 |
except socket.error:
|
|
Packit Service |
0f71a7 |
return False
|
|
Packit Service |
0f71a7 |
return True
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def is_ipv6_addr(ipaddr):
|
|
Packit Service |
0f71a7 |
"""Test if given IP address is a valid IPv6 address."""
|
|
Packit Service |
0f71a7 |
try:
|
|
Packit Service |
0f71a7 |
socket.inet_pton(socket.AF_INET6, ipaddr)
|
|
Packit Service |
0f71a7 |
except socket.error:
|
|
Packit Service |
0f71a7 |
return False
|
|
Packit Service |
0f71a7 |
return True
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
class AnsibleFreeIPAParams(Mapping):
|
|
Packit Service |
0f71a7 |
def __init__(self, ansible_module):
|
|
Packit Service |
0f71a7 |
self.mapping = ansible_module.params
|
|
Packit Service |
0f71a7 |
self.ansible_module = ansible_module
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def __getitem__(self, key):
|
|
Packit Service |
0f71a7 |
param = self.mapping[key]
|
|
Packit Service |
0f71a7 |
if param is not None:
|
|
Packit Service |
0f71a7 |
return _afm_convert(param)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def __iter__(self):
|
|
Packit Service |
0f71a7 |
return iter(self.mapping)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def __len__(self):
|
|
Packit Service |
0f71a7 |
return len(self.mapping)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
@property
|
|
Packit Service |
0f71a7 |
def names(self):
|
|
Packit Service |
0f71a7 |
return self.name
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def __getattr__(self, name):
|
|
Packit Service |
0f71a7 |
return self.get(name)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
class FreeIPABaseModule(AnsibleModule):
|
|
Packit Service |
0f71a7 |
"""
|
|
Packit Service |
0f71a7 |
Base class for FreeIPA Ansible modules.
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
Provides methods useful methods to be used by our modules.
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
This class should be overriten and instantiated for the module.
|
|
Packit Service |
0f71a7 |
A basic implementation of an Ansible FreeIPA module expects its
|
|
Packit Service |
0f71a7 |
class to:
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
1. Define a class attribute ``ipa_param_mapping``
|
|
Packit Service |
0f71a7 |
2. Implement the method ``define_ipa_commands()``
|
|
Packit Service |
0f71a7 |
3. Implement the method ``check_ipa_params()`` (optional)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
After instantiating the class the method ``ipa_run()`` should be called.
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
Example (ansible-freeipa/plugins/modules/ipasomemodule.py):
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
class SomeIPAModule(FreeIPABaseModule):
|
|
Packit Service |
0f71a7 |
ipa_param_mapping = {
|
|
Packit Service |
0f71a7 |
"arg_to_be_passed_to_ipa_command": "module_param",
|
|
Packit Service |
0f71a7 |
"another_arg": "get_another_module_param",
|
|
Packit Service |
0f71a7 |
}
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def get_another_module_param(self):
|
|
Packit Service |
0f71a7 |
another_module_param = self.ipa_params.another_module_param
|
|
Packit Service |
0f71a7 |
# Validate or modify another_module_param
|
|
Packit Service |
0f71a7 |
# ...
|
|
Packit Service |
0f71a7 |
return another_module_param
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def check_ipa_params(self):
|
|
Packit Service |
0f71a7 |
# Validate your params here
|
|
Packit Service |
0f71a7 |
# Example:
|
|
Packit Service |
0f71a7 |
if not self.ipa_params.module_param in VALID_OPTIONS:
|
|
Packit Service |
0f71a7 |
self.fail_json(msg="Invalid value for argument module_param")
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def define_ipa_commands(self):
|
|
Packit Service |
0f71a7 |
args = self.get_ipa_command_args()
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
self.add_ipa_command(
|
|
Packit Service |
0f71a7 |
"some_ipa_command",
|
|
Packit Service |
0f71a7 |
name="obj-name",
|
|
Packit Service |
0f71a7 |
args=args,
|
|
Packit Service |
0f71a7 |
)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def main():
|
|
Packit Service |
0f71a7 |
ipa_module = SomeIPAModule(argument_spec=dict(
|
|
Packit Service |
0f71a7 |
module_param=dict(
|
|
Packit Service |
0f71a7 |
type="str",
|
|
Packit Service |
0f71a7 |
default=None,
|
|
Packit Service |
0f71a7 |
required=False,
|
|
Packit Service |
0f71a7 |
),
|
|
Packit Service |
0f71a7 |
another_module_param=dict(
|
|
Packit Service |
0f71a7 |
type="str",
|
|
Packit Service |
0f71a7 |
default=None,
|
|
Packit Service |
0f71a7 |
required=False,
|
|
Packit Service |
0f71a7 |
),
|
|
Packit Service |
0f71a7 |
))
|
|
Packit Service |
0f71a7 |
ipa_module.ipa_run()
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
if __name__ == "__main__":
|
|
Packit Service |
0f71a7 |
main()
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
"""
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
ipa_param_mapping = None
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def __init__(self, *args, **kwargs):
|
|
Packit Service |
0f71a7 |
super(FreeIPABaseModule, self).__init__(*args, **kwargs)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
# Attributes to store kerberos credentials (if needed)
|
|
Packit Service |
0f71a7 |
self.ccache_dir = None
|
|
Packit Service |
0f71a7 |
self.ccache_name = None
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
# Status of an execution. Will be changed to True
|
|
Packit Service |
0f71a7 |
# if something is actually peformed.
|
|
Packit Service |
0f71a7 |
self.changed = False
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
# Status of the connection with the IPA server.
|
|
Packit Service |
0f71a7 |
# We need to know if the connection was actually stablished
|
|
Packit Service |
0f71a7 |
# before we start sending commands.
|
|
Packit Service |
0f71a7 |
self.ipa_connected = False
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
# Commands to be executed
|
|
Packit Service |
0f71a7 |
self.ipa_commands = []
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
# Module exit arguments.
|
|
Packit Service |
0f71a7 |
self.exit_args = {}
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
# Wrapper around the AnsibleModule.params.
|
|
Packit Service |
0f71a7 |
# Return the actual params but performing transformations
|
|
Packit Service |
0f71a7 |
# when needed.
|
|
Packit Service |
0f71a7 |
self.ipa_params = AnsibleFreeIPAParams(self)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
68089a |
def get_ipa_command_args(self, **kwargs):
|
|
Packit Service |
0f71a7 |
"""
|
|
Packit Service |
0f71a7 |
Return a dict to be passed to an IPA command.
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
The keys of ``ipa_param_mapping`` are also the keys of the return dict.
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
The values of ``ipa_param_mapping`` needs to be either:
|
|
Packit Service |
0f71a7 |
* A str with the name of a defined method; or
|
|
Packit Service |
0f71a7 |
* A key of ``AnsibleModule.param``.
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
In case of a method the return of the method will be set as value
|
|
Packit Service |
0f71a7 |
for the return dict.
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
In case of a AnsibleModule.param the value of the param will be
|
|
Packit Service |
0f71a7 |
set in the return dict. In addition to that boolean values will be
|
|
Packit Service |
0f71a7 |
automaticaly converted to uppercase strings (as required by FreeIPA
|
|
Packit Service |
0f71a7 |
server).
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
"""
|
|
Packit Service |
0f71a7 |
args = {}
|
|
Packit Service |
0f71a7 |
for ipa_param_name, param_name in self.ipa_param_mapping.items():
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
# Check if param_name is actually a param
|
|
Packit Service |
0f71a7 |
if param_name in self.ipa_params:
|
|
Packit Service |
0f71a7 |
value = self.ipa_params.get(param_name)
|
|
Packit Service |
0f71a7 |
if isinstance(value, bool):
|
|
Packit Service |
0f71a7 |
value = "TRUE" if value else "FALSE"
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
# Since param wasn't a param check if it's a method name
|
|
Packit Service |
0f71a7 |
elif hasattr(self, param_name):
|
|
Packit Service |
0f71a7 |
method = getattr(self, param_name)
|
|
Packit Service |
0f71a7 |
if callable(method):
|
|
Packit Service |
68089a |
value = method(**kwargs)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
# We don't have a way to guess the value so fail.
|
|
Packit Service |
0f71a7 |
else:
|
|
Packit Service |
0f71a7 |
self.fail_json(
|
|
Packit Service |
0f71a7 |
msg=(
|
|
Packit Service |
0f71a7 |
"Couldn't get a value for '%s'. Option '%s' is not "
|
|
Packit Service |
0f71a7 |
"a module argument neither a defined method."
|
|
Packit Service |
0f71a7 |
)
|
|
Packit Service |
0f71a7 |
% (ipa_param_name, param_name)
|
|
Packit Service |
0f71a7 |
)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
if value is not None:
|
|
Packit Service |
0f71a7 |
args[ipa_param_name] = value
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
return args
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def check_ipa_params(self):
|
|
Packit Service |
0f71a7 |
"""Validate ipa_params before command is called."""
|
|
Packit Service |
0f71a7 |
pass
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def define_ipa_commands(self):
|
|
Packit Service |
0f71a7 |
"""Define commands that will be run in IPA server."""
|
|
Packit Service |
0f71a7 |
raise NotImplementedError
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def api_command(self, command, name=None, args=None):
|
|
Packit Service |
0f71a7 |
"""Execute a single command in IPA server."""
|
|
Packit Service |
0f71a7 |
if args is None:
|
|
Packit Service |
0f71a7 |
args = {}
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
if name is None:
|
|
Packit Service |
0f71a7 |
return api_command_no_name(self, command, args)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
return api_command(self, command, name, args)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def __enter__(self):
|
|
Packit Service |
0f71a7 |
"""
|
|
Packit Service |
0f71a7 |
Connect to IPA server.
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
Check the there are working Kerberos credentials to connect to
|
|
Packit Service |
0f71a7 |
IPA server. If there are not we perform a temporary kinit
|
|
Packit Service |
0f71a7 |
that will be terminated when exiting the context.
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
If the connection fails ``ipa_connected`` attribute will be set
|
|
Packit Service |
0f71a7 |
to False.
|
|
Packit Service |
0f71a7 |
"""
|
|
Packit Service |
0f71a7 |
principal = self.ipa_params.ipaadmin_principal
|
|
Packit Service |
0f71a7 |
password = self.ipa_params.ipaadmin_password
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
try:
|
|
Packit Service |
0f71a7 |
if not valid_creds(self, principal):
|
|
Packit Service |
0f71a7 |
self.ccache_dir, self.ccache_name = temp_kinit(
|
|
Packit Service |
0f71a7 |
principal, password,
|
|
Packit Service |
0f71a7 |
)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
api_connect()
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
except Exception as excpt:
|
|
Packit Service |
0f71a7 |
self.fail_json(msg=str(excpt))
|
|
Packit Service |
0f71a7 |
else:
|
|
Packit Service |
0f71a7 |
self.ipa_connected = True
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
return self
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
Packit Service |
0f71a7 |
"""
|
|
Packit Service |
0f71a7 |
Terminate a connection with the IPA server.
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
Deal with exceptions, destroy temporary kinit credentials and
|
|
Packit Service |
0f71a7 |
exit the module with proper arguments.
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
"""
|
|
Packit Service |
0f71a7 |
# TODO: shouldn't we also disconnect from api backend?
|
|
Packit Service |
0f71a7 |
temp_kdestroy(self.ccache_dir, self.ccache_name)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
b7ed33 |
if exc_type == SystemExit:
|
|
Packit Service |
b7ed33 |
raise
|
|
Packit Service |
b7ed33 |
|
|
Packit Service |
b7ed33 |
if exc_val:
|
|
Packit Service |
b7ed33 |
self.fail_json(msg=str(exc_val))
|
|
Packit Service |
b7ed33 |
|
|
Packit Service |
0f71a7 |
self.exit_json(changed=self.changed, user=self.exit_args)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def get_command_errors(self, command, result):
|
|
Packit Service |
0f71a7 |
"""Look for erros into command results."""
|
|
Packit Service |
0f71a7 |
# Get all errors
|
|
Packit Service |
0f71a7 |
# All "already a member" and "not a member" failures in the
|
|
Packit Service |
0f71a7 |
# result are ignored. All others are reported.
|
|
Packit Service |
0f71a7 |
errors = []
|
|
Packit Service |
0f71a7 |
for item in result.get("failed", tuple()):
|
|
Packit Service |
0f71a7 |
failed_item = result["failed"][item]
|
|
Packit Service |
0f71a7 |
for member_type in failed_item:
|
|
Packit Service |
0f71a7 |
for member, failure in failed_item[member_type]:
|
|
Packit Service |
0f71a7 |
if (
|
|
Packit Service |
0f71a7 |
"already a member" in failure
|
|
Packit Service |
0f71a7 |
or "not a member" in failure
|
|
Packit Service |
0f71a7 |
):
|
|
Packit Service |
0f71a7 |
continue
|
|
Packit Service |
0f71a7 |
errors.append(
|
|
Packit Service |
0f71a7 |
"%s: %s %s: %s"
|
|
Packit Service |
0f71a7 |
% (command, member_type, member, failure)
|
|
Packit Service |
0f71a7 |
)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
if len(errors) > 0:
|
|
Packit Service |
0f71a7 |
self.fail_json(", ".join("errors"))
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def add_ipa_command(self, command, name=None, args=None):
|
|
Packit Service |
0f71a7 |
"""Add a command to the list of commands to be executed."""
|
|
Packit Service |
0f71a7 |
self.ipa_commands.append((name, command, args or {}))
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def _run_ipa_commands(self):
|
|
Packit Service |
0f71a7 |
"""Execute commands in self.ipa_commands."""
|
|
Packit Service |
0f71a7 |
result = None
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
for name, command, args in self.ipa_commands:
|
|
Packit Service |
0f71a7 |
try:
|
|
Packit Service |
0f71a7 |
result = self.api_command(command, name, args)
|
|
Packit Service |
0f71a7 |
except Exception as excpt:
|
|
Packit Service |
0f71a7 |
self.fail_json(msg="%s: %s: %s" % (command, name, str(excpt)))
|
|
Packit Service |
0f71a7 |
else:
|
|
Packit Service |
0f71a7 |
if "completed" in result:
|
|
Packit Service |
0f71a7 |
if result["completed"] > 0:
|
|
Packit Service |
0f71a7 |
self.changed = True
|
|
Packit Service |
0f71a7 |
else:
|
|
Packit Service |
0f71a7 |
self.changed = True
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
self.get_command_errors(command, result)
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def require_ipa_attrs_change(self, command_args, ipa_attrs):
|
|
Packit Service |
0f71a7 |
"""
|
|
Packit Service |
0f71a7 |
Compare given args with current object attributes.
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
Returns True in case current IPA object attributes differ from
|
|
Packit Service |
0f71a7 |
args passed to the module.
|
|
Packit Service |
0f71a7 |
"""
|
|
Packit Service |
0f71a7 |
equal = compare_args_ipa(self, command_args, ipa_attrs)
|
|
Packit Service |
0f71a7 |
return not equal
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def pdebug(self, value):
|
|
Packit Service |
0f71a7 |
"""Debug with pretty formatting."""
|
|
Packit Service |
0f71a7 |
self.debug(pformat(value))
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
def ipa_run(self):
|
|
Packit Service |
0f71a7 |
"""Execute module actions."""
|
|
Packit Service |
0f71a7 |
with self:
|
|
Packit Service |
0f71a7 |
if not self.ipa_connected:
|
|
Packit Service |
0f71a7 |
return
|
|
Packit Service |
0f71a7 |
|
|
Packit Service |
0f71a7 |
self.check_ipa_params()
|
|
Packit Service |
0f71a7 |
self.define_ipa_commands()
|
|
Packit Service |
0f71a7 |
self._run_ipa_commands()
|