diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py index 02e02630e2d2106ea4b9cdcbfd07745fa7379ac4..32098b351a05c16fb0456481730d19a2cba93f3d 100644 --- a/plugins/module_utils/ansible_freeipa_module.py +++ b/plugins/module_utils/ansible_freeipa_module.py @@ -22,780 +22,776 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -import sys -import operator -import os -import uuid -import tempfile -import shutil -import netaddr -import gssapi -from datetime import datetime -from pprint import pformat - -try: - from packaging import version -except ImportError: - # If `packaging` not found, split version string for creating version - # object. Although it is not PEP 440 compliant, it will work for stable - # FreeIPA releases. - import re - - class version: - @staticmethod - def parse(version_str): - """ - Split a version string A.B.C, into a tuple. - - This will not work for `rc`, `dev` or similar version string. - """ - return tuple(re.split("[-_\.]", version_str)) # noqa: W605 - -from ipalib import api -from ipalib import errors as ipalib_errors # noqa -from ipalib.config import Env -from ipalib.constants import DEFAULT_CONFIG, LDAP_GENERALIZED_TIME_FORMAT - -try: - from ipalib.install.kinit import kinit_password, kinit_keytab -except ImportError: - from ipapython.ipautil import kinit_password, kinit_keytab -from ipapython.ipautil import run -from ipapython.dn import DN -from ipapython.version import VERSION -from ipaplatform.paths import paths -from ipalib.krb_utils import get_credentials_if_valid -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_text -from ansible.module_utils.common.text.converters import jsonify - -try: - from ipalib.x509 import Encoding -except ImportError: - from cryptography.hazmat.primitives.serialization import Encoding - -try: - from ipalib.x509 import load_pem_x509_certificate -except ImportError: - from ipalib.x509 import load_certificate - load_pem_x509_certificate = None - -import socket -import base64 -import six - -try: - from collections.abc import Mapping # noqa -except ImportError: - from collections import Mapping # noqa - - -if six.PY3: - unicode = str - -# ansible-freeipa requires locale to be C, IPA requires utf-8. -os.environ["LANGUAGE"] = "C" - - -def valid_creds(module, principal): # noqa - """Get valid credentials matching the princial, try GSSAPI first.""" - if "KRB5CCNAME" in os.environ: - ccache = os.environ["KRB5CCNAME"] - module.debug('KRB5CCNAME set to %s' % ccache) - - try: - cred = gssapi.Credentials(usage='initiate', - store={'ccache': ccache}) - except gssapi.raw.misc.GSSError as e: - module.fail_json(msg='Failed to find default ccache: %s' % e) - else: - module.debug("Using principal %s" % str(cred.name)) - return True - - elif "KRB5_CLIENT_KTNAME" in os.environ: - keytab = os.environ.get('KRB5_CLIENT_KTNAME', None) - module.debug('KRB5_CLIENT_KTNAME set to %s' % keytab) - - ccache_name = "MEMORY:%s" % str(uuid.uuid4()) - os.environ["KRB5CCNAME"] = ccache_name +__all__ = ["gssapi", "netaddr", "api", "ipalib_errors", "Env", + "DEFAULT_CONFIG", "LDAP_GENERALIZED_TIME_FORMAT", + "kinit_password", "kinit_keytab", "run", "DN", "VERSION", + "paths", "get_credentials_if_valid", "Encoding", + "load_pem_x509_certificate"] - try: - cred = kinit_keytab(principal, keytab, ccache_name) - except gssapi.raw.misc.GSSError as e: - module.fail_json(msg='Kerberos authentication failed : %s' % e) - else: - module.debug("Using principal %s" % str(cred.name)) - return True +import sys - creds = get_credentials_if_valid() - if creds and \ - creds.lifetime > 0 and \ - "%s@" % principal in creds.name.display_as(creds.name.name_type): - return True - return False +# HACK: workaround for Ansible 2.9 +# https://github.com/ansible/ansible/issues/68361 +if 'ansible.executor' in sys.modules: + for attr in __all__: + setattr(sys.modules[__name__], attr, None) +else: + import operator + import os + import uuid + import tempfile + import shutil + import netaddr + import gssapi + from datetime import datetime + from pprint import pformat + + # ansible-freeipa requires locale to be C, IPA requires utf-8. + os.environ["LANGUAGE"] = "C" + try: + from packaging import version + except ImportError: + # If `packaging` not found, split version string for creating version + # object. Although it is not PEP 440 compliant, it will work for stable + # FreeIPA releases. + import re + + class version: + @staticmethod + def parse(version_str): + """ + Split a version string A.B.C, into a tuple. + + This will not work for `rc`, `dev` or similar version string. + """ + return tuple(re.split("[-_\.]", version_str)) # noqa: W605 + + from ipalib import api + from ipalib import errors as ipalib_errors # noqa + from ipalib.config import Env + from ipalib.constants import DEFAULT_CONFIG, LDAP_GENERALIZED_TIME_FORMAT -def temp_kinit(principal, password): - """Kinit with password using a temporary ccache.""" - if not password: - raise RuntimeError("The password is not set") - if not principal: - principal = "admin" + try: + from ipalib.install.kinit import kinit_password, kinit_keytab + except ImportError: + from ipapython.ipautil import kinit_password, kinit_keytab + from ipapython.ipautil import run + from ipapython.dn import DN + from ipapython.version import VERSION + from ipaplatform.paths import paths + from ipalib.krb_utils import get_credentials_if_valid + from ansible.module_utils.basic import AnsibleModule + from ansible.module_utils._text import to_text + from ansible.module_utils.common.text.converters import jsonify - ccache_dir = tempfile.mkdtemp(prefix='krbcc') - ccache_name = os.path.join(ccache_dir, 'ccache') + try: + from ipalib.x509 import Encoding + except ImportError: + from cryptography.hazmat.primitives.serialization import Encoding try: - kinit_password(principal, password, ccache_name) - except RuntimeError as e: - raise RuntimeError("Kerberos authentication failed: {}".format(e)) + from ipalib.x509 import load_pem_x509_certificate + except ImportError: + from ipalib.x509 import load_certificate + load_pem_x509_certificate = None - os.environ["KRB5CCNAME"] = ccache_name - return ccache_dir, ccache_name + import socket + import base64 + import six + try: + from collections.abc import Mapping # noqa + except ImportError: + from collections import Mapping # noqa -def temp_kdestroy(ccache_dir, ccache_name): - """Destroy temporary ticket and remove temporary ccache.""" - if ccache_name is not None: - run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False) - del os.environ['KRB5CCNAME'] - if ccache_dir is not None: - shutil.rmtree(ccache_dir, ignore_errors=True) + if six.PY3: + unicode = str + def valid_creds(module, principal): # noqa + """Get valid credentials matching the princial, try GSSAPI first.""" + if "KRB5CCNAME" in os.environ: + ccache = os.environ["KRB5CCNAME"] + module.debug('KRB5CCNAME set to %s' % ccache) -def api_connect(context=None): - """ - Initialize IPA API with the provided context. + try: + cred = gssapi.Credentials(usage='initiate', + store={'ccache': ccache}) + except gssapi.raw.misc.GSSError as e: + module.fail_json(msg='Failed to find default ccache: %s' % e) + else: + module.debug("Using principal %s" % str(cred.name)) + return True - `context` can be any of: - * `server` (default) - * `ansible-freeipa` - * `cli_installer` - """ - env = Env() - env._bootstrap() - env._finalize_core(**dict(DEFAULT_CONFIG)) + elif "KRB5_CLIENT_KTNAME" in os.environ: + keytab = os.environ.get('KRB5_CLIENT_KTNAME', None) + module.debug('KRB5_CLIENT_KTNAME set to %s' % keytab) - # available contexts are 'server', 'ansible-freeipa' and 'cli_installer' - if context is None: - context = 'server' + ccache_name = "MEMORY:%s" % str(uuid.uuid4()) + os.environ["KRB5CCNAME"] = ccache_name - api.bootstrap(context=context, debug=env.debug, log=None) - api.finalize() + try: + cred = kinit_keytab(principal, keytab, ccache_name) + except gssapi.raw.misc.GSSError as e: + module.fail_json(msg='Kerberos authentication failed : %s' % e) + else: + module.debug("Using principal %s" % str(cred.name)) + return True - if api.env.in_server: - backend = api.Backend.ldap2 - else: - backend = api.Backend.rpcclient + creds = get_credentials_if_valid() + if creds and \ + creds.lifetime > 0 and \ + "%s@" % principal in creds.name.display_as(creds.name.name_type): + return True + return False - if not backend.isconnected(): - backend.connect(ccache=os.environ.get('KRB5CCNAME', None)) + def temp_kinit(principal, password): + """Kinit with password using a temporary ccache.""" + if not password: + raise RuntimeError("The password is not set") + if not principal: + principal = "admin" + ccache_dir = tempfile.mkdtemp(prefix='krbcc') + ccache_name = os.path.join(ccache_dir, 'ccache') -def api_command(module, command, name, args): - """Call ipa.Command.""" - return api.Command[command](name, **args) + try: + kinit_password(principal, password, ccache_name) + except RuntimeError as e: + raise RuntimeError("Kerberos authentication failed: {}".format(e)) + os.environ["KRB5CCNAME"] = ccache_name + return ccache_dir, ccache_name -def api_command_no_name(module, command, args): - """Call ipa.Command without a name.""" - return api.Command[command](**args) + def temp_kdestroy(ccache_dir, ccache_name): + """Destroy temporary ticket and remove temporary ccache.""" + if ccache_name is not None: + run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False) + del os.environ['KRB5CCNAME'] + if ccache_dir is not None: + shutil.rmtree(ccache_dir, ignore_errors=True) + def api_connect(context=None): + """ + Initialize IPA API with the provided context. -def api_check_command(command): - """Return if command exists in command list.""" - return command in api.Command + `context` can be any of: + * `server` (default) + * `ansible-freeipa` + * `cli_installer` + """ + env = Env() + env._bootstrap() + env._finalize_core(**dict(DEFAULT_CONFIG)) + # available contexts are 'server', 'ansible-freeipa' and + # 'cli_installer' -def api_check_param(command, name): - """Check if param exists in command param list.""" - return name in api.Command[command].params + if context is None: + context = 'server' + api.bootstrap(context=context, debug=env.debug, log=None) + api.finalize() -def api_check_ipa_version(oper, requested_version): - """ - Compare the installed IPA version against a requested version. + if api.env.in_server: + backend = api.Backend.ldap2 + else: + backend = api.Backend.rpcclient - The valid operators are: <, <=, >, >=, ==, != - """ - oper_map = { - "<": operator.lt, - "<=": operator.le, - ">": operator.gt, - ">=": operator.ge, - "==": operator.eq, - "!=": operator.ne, - } - operation = oper_map.get(oper) - if not(operation): - raise NotImplementedError("Invalid operator: %s" % oper) - return operation(version.parse(VERSION), version.parse(requested_version)) + if not backend.isconnected(): + backend.connect(ccache=os.environ.get('KRB5CCNAME', None)) + def api_command(module, command, name, args): + """Call ipa.Command.""" + return api.Command[command](name, **args) -def execute_api_command(module, principal, password, command, name, args): - """ - Execute an API command. + def api_command_no_name(module, command, args): + """Call ipa.Command without a name.""" + return api.Command[command](**args) - Get KRB ticket if not already there, initialize api, connect, - execute command and destroy ticket again if it has been created also. - """ - ccache_dir = None - ccache_name = None - try: - if not valid_creds(module, principal): - ccache_dir, ccache_name = temp_kinit(principal, password) - api_connect() + def api_check_command(command): + """Return if command exists in command list.""" + return command in api.Command - return api_command(module, command, name, args) - except Exception as e: - module.fail_json(msg=str(e)) + def api_check_param(command, name): + """Check if param exists in command param list.""" + return name in api.Command[command].params - finally: - temp_kdestroy(ccache_dir, ccache_name) + def api_check_ipa_version(oper, requested_version): + """ + Compare the installed IPA version against a requested version. + The valid operators are: <, <=, >, >=, ==, != + """ + oper_map = { + "<": operator.lt, + "<=": operator.le, + ">": operator.gt, + ">=": operator.ge, + "==": operator.eq, + "!=": operator.ne, + } + operation = oper_map.get(oper) + if not(operation): + raise NotImplementedError("Invalid operator: %s" % oper) + return operation(version.parse(VERSION), + version.parse(requested_version)) -def date_format(value): - accepted_date_formats = [ - LDAP_GENERALIZED_TIME_FORMAT, # generalized time - '%Y-%m-%dT%H:%M:%SZ', # ISO 8601, second precision - '%Y-%m-%dT%H:%MZ', # ISO 8601, minute precision - '%Y-%m-%dZ', # ISO 8601, date only - '%Y-%m-%d %H:%M:%SZ', # non-ISO 8601, second precision - '%Y-%m-%d %H:%MZ', # non-ISO 8601, minute precision - ] + def execute_api_command(module, principal, password, command, name, args): + """ + Execute an API command. - for date_format in accepted_date_formats: + Get KRB ticket if not already there, initialize api, connect, + execute command and destroy ticket again if it has been created also. + """ + ccache_dir = None + ccache_name = None try: - return datetime.strptime(value, date_format) - except ValueError: - pass - raise ValueError("Invalid date '%s'" % value) + if not valid_creds(module, principal): + ccache_dir, ccache_name = temp_kinit(principal, password) + api_connect() + return api_command(module, command, name, args) + except Exception as e: + module.fail_json(msg=str(e)) -def compare_args_ipa(module, args, ipa): # noqa - """Compare IPA obj attrs with the command args. + finally: + temp_kdestroy(ccache_dir, ccache_name) - This function compares IPA objects attributes with the args the - module is intending to use to call a command. This is useful to know - if call to IPA server will be needed or not. - In other to compare we have to prepare the perform slight changes in - data formats. + def date_format(value): + accepted_date_formats = [ + LDAP_GENERALIZED_TIME_FORMAT, # generalized time + '%Y-%m-%dT%H:%M:%SZ', # ISO 8601, second precision + '%Y-%m-%dT%H:%MZ', # ISO 8601, minute precision + '%Y-%m-%dZ', # ISO 8601, date only + '%Y-%m-%d %H:%M:%SZ', # non-ISO 8601, second precision + '%Y-%m-%d %H:%MZ', # non-ISO 8601, minute precision + ] - Returns True if they are the same and False otherwise. - """ - base_debug_msg = "Ansible arguments and IPA commands differed. " + for date_format in accepted_date_formats: + try: + return datetime.strptime(value, date_format) + except ValueError: + pass + raise ValueError("Invalid date '%s'" % value) - # If both args and ipa are None, return there's no difference. - # If only one is None, return there is a difference. - # This tests avoid unecessary invalid access to attributes. - if args is None and ipa is None: - return True - if args is None or ipa is None: - module.debug( - base_debug_msg + "args is%s None an ipa is%s None" % ( - "" if args is None else " not", "" if ipa is None else " not", - ) - ) - return False + def compare_args_ipa(module, args, ipa): # noqa + """Compare IPA obj attrs with the command args. + + This function compares IPA objects attributes with the args the + module is intending to use to call a command. This is useful to know + if call to IPA server will be needed or not. + In other to compare we have to prepare the perform slight changes in + data formats. - # Fail if args or ipa are not dicts. - if not (isinstance(args, dict) and isinstance(ipa, dict)): - raise TypeError("Expected 'dicts' to compare.") + Returns True if they are the same and False otherwise. + """ + base_debug_msg = "Ansible arguments and IPA commands differed. " - for key in args.keys(): - if key not in ipa: + # If both args and ipa are None, return there's no difference. + # If only one is None, return there is a difference. + # This tests avoid unecessary invalid access to attributes. + if args is None and ipa is None: + return True + if args is None or ipa is None: module.debug( - base_debug_msg + "Command key not present in IPA: %s" % key + base_debug_msg + "args is%s None an ipa is%s None" % ( + "" if args is None else " not", + "" if ipa is None else " not", + ) ) return False - else: - arg = args[key] - ipa_arg = ipa[key] - # If ipa_arg is a list and arg is not, replace arg - # with list containing arg. Most args in a find result - # are lists, but not all. - if isinstance(ipa_arg, tuple): - ipa_arg = list(ipa_arg) - if isinstance(ipa_arg, list): - if not isinstance(arg, list): - arg = [arg] - if len(ipa_arg) != len(arg): - module.debug( - base_debug_msg - + "List length doesn't match for key %s: %d %d" - % (key, len(arg), len(ipa_arg),) - ) - return False - if isinstance(ipa_arg[0], str) and isinstance(arg[0], int): - arg = [to_text(_arg) for _arg in arg] - if isinstance(ipa_arg[0], unicode) and isinstance(arg[0], int): - arg = [to_text(_arg) for _arg in arg] - try: - arg_set = set(arg) - ipa_arg_set = set(ipa_arg) - except TypeError: - if arg != ipa_arg: - module.debug( - base_debug_msg - + "Different values: %s %s" % (arg, ipa_arg) - ) - return False - else: - if arg_set != ipa_arg_set: - module.debug( - base_debug_msg - + "Different set content: %s %s" - % (arg_set, ipa_arg_set,) - ) - return False - return True - - -def _afm_convert(value): - if value is not None: - if isinstance(value, list): - return [_afm_convert(x) for x in value] - elif isinstance(value, dict): - return {_afm_convert(k): _afm_convert(v) for k, v in value.items()} - elif isinstance(value, str): - return to_text(value) - else: - return value - else: - return value - - -def module_params_get(module, name): - return _afm_convert(module.params.get(name)) - - -def api_get_realm(): - return api.env.realm + # Fail if args or ipa are not dicts. + if not (isinstance(args, dict) and isinstance(ipa, dict)): + raise TypeError("Expected 'dicts' to compare.") -def gen_add_del_lists(user_list, res_list): - """Generate the lists for the addition and removal of members.""" - # The user list is None, therefore the parameter should not be touched - if user_list is None: - return [], [] + for key in args.keys(): + if key not in ipa: + module.debug( + base_debug_msg + "Command key not present in IPA: %s" % key + ) + return False + else: + arg = args[key] + ipa_arg = ipa[key] + # If ipa_arg is a list and arg is not, replace arg + # with list containing arg. Most args in a find result + # are lists, but not all. + if isinstance(ipa_arg, tuple): + ipa_arg = list(ipa_arg) + if isinstance(ipa_arg, list): + if not isinstance(arg, list): + arg = [arg] + if len(ipa_arg) != len(arg): + module.debug( + base_debug_msg + + "List length doesn't match for key %s: %d %d" + % (key, len(arg), len(ipa_arg),) + ) + return False + if isinstance(ipa_arg[0], str) and isinstance(arg[0], int): + arg = [to_text(_arg) for _arg in arg] + if isinstance(ipa_arg[0], unicode) \ + and isinstance(arg[0], int): + arg = [to_text(_arg) for _arg in arg] + try: + arg_set = set(arg) + ipa_arg_set = set(ipa_arg) + except TypeError: + if arg != ipa_arg: + module.debug( + base_debug_msg + + "Different values: %s %s" % (arg, ipa_arg) + ) + return False + else: + if arg_set != ipa_arg_set: + module.debug( + base_debug_msg + + "Different set content: %s %s" + % (arg_set, ipa_arg_set,) + ) + return False + return True - add_list = list(set(user_list or []) - set(res_list or [])) - del_list = list(set(res_list or []) - set(user_list or [])) + def _afm_convert(value): + if value is not None: + if isinstance(value, list): + return [_afm_convert(x) for x in value] + elif isinstance(value, dict): + return {_afm_convert(k): _afm_convert(v) + for k, v in value.items()} + elif isinstance(value, str): + return to_text(value) + else: + return value + else: + return value - return add_list, del_list + def module_params_get(module, name): + return _afm_convert(module.params.get(name)) + def api_get_realm(): + return api.env.realm -def encode_certificate(cert): - """ - Encode a certificate using base64. + def gen_add_del_lists(user_list, res_list): + """Generate the lists for the addition and removal of members.""" + # The user list is None, therefore the parameter should not be touched + if user_list is None: + return [], [] - It also takes FreeIPA and Python versions into account. - """ - if isinstance(cert, (str, unicode, bytes)): - encoded = base64.b64encode(cert) - else: - encoded = base64.b64encode(cert.public_bytes(Encoding.DER)) - if not six.PY2: - encoded = encoded.decode('ascii') - return encoded + add_list = list(set(user_list or []) - set(res_list or [])) + del_list = list(set(res_list or []) - set(user_list or [])) + return add_list, del_list -def load_cert_from_str(cert): - cert = cert.strip() - if not cert.startswith("-----BEGIN CERTIFICATE-----"): - cert = "-----BEGIN CERTIFICATE-----\n" + cert - if not cert.endswith("-----END CERTIFICATE-----"): - cert += "\n-----END CERTIFICATE-----" + def encode_certificate(cert): + """ + Encode a certificate using base64. - if load_pem_x509_certificate is not None: - cert = load_pem_x509_certificate(cert.encode('utf-8')) - else: - cert = load_certificate(cert.encode('utf-8')) - return cert + It also takes FreeIPA and Python versions into account. + """ + if isinstance(cert, (str, unicode, bytes)): + encoded = base64.b64encode(cert) + else: + encoded = base64.b64encode(cert.public_bytes(Encoding.DER)) + if not six.PY2: + encoded = encoded.decode('ascii') + return encoded + + def load_cert_from_str(cert): + cert = cert.strip() + if not cert.startswith("-----BEGIN CERTIFICATE-----"): + cert = "-----BEGIN CERTIFICATE-----\n" + cert + if not cert.endswith("-----END CERTIFICATE-----"): + cert += "\n-----END CERTIFICATE-----" + + if load_pem_x509_certificate is not None: + cert = load_pem_x509_certificate(cert.encode('utf-8')) + else: + cert = load_certificate(cert.encode('utf-8')) + return cert + def DN_x500_text(text): + if hasattr(DN, "x500_text"): + return DN(text).x500_text() + else: + # Emulate x500_text + dn = DN(text) + dn.rdns = reversed(dn.rdns) + return str(dn) -def DN_x500_text(text): - if hasattr(DN, "x500_text"): - return DN(text).x500_text() - else: - # Emulate x500_text - dn = DN(text) - dn.rdns = reversed(dn.rdns) - return str(dn) + def is_valid_port(port): + if not isinstance(port, int): + return False + if 1 <= port <= 65535: + return True -def is_valid_port(port): - if not isinstance(port, int): return False - if 1 <= port <= 65535: + def is_ip_address(ipaddr): + """Test if given IP address is a valid IPv4 or IPv6 address.""" + try: + netaddr.IPAddress(str(ipaddr)) + except (netaddr.AddrFormatError, ValueError): + return False return True - return False - - -def is_ip_address(ipaddr): - """Test if given IP address is a valid IPv4 or IPv6 address.""" - try: - netaddr.IPAddress(str(ipaddr)) - except (netaddr.AddrFormatError, ValueError): - return False - return True - - -def is_ip_network_address(ipaddr): - """Test if given IP address is a valid IPv4 or IPv6 address.""" - try: - netaddr.IPNetwork(str(ipaddr)) - except (netaddr.AddrFormatError, ValueError): - return False - return True - - -def is_ipv4_addr(ipaddr): - """Test if given IP address is a valid IPv4 address.""" - try: - socket.inet_pton(socket.AF_INET, ipaddr) - except socket.error: - return False - return True - - -def is_ipv6_addr(ipaddr): - """Test if given IP address is a valid IPv6 address.""" - try: - socket.inet_pton(socket.AF_INET6, ipaddr) - except socket.error: - return False - return True - - -def exit_raw_json(module, **kwargs): - """ - Print the raw parameters in JSON format, without masking. - - Due to Ansible filtering out values in the output that match values - in variables which has `no_log` set, if a module need to return user - defined dato to the controller, it cannot rely on - AnsibleModule.exit_json, as there is a chance that a partial match may - occur, masking the data returned. - - This method is a replacement for AnsibleModule.exit_json. It has - nearly the same implementation as exit_json, but does not filter - data. Beware that this data will be logged by Ansible, and if it - contains sensible data, it will be appear in the logs. - """ - module.do_cleanup_files() - print(jsonify(kwargs)) - sys.exit(0) + def is_ip_network_address(ipaddr): + """Test if given IP address is a valid IPv4 or IPv6 address.""" + try: + netaddr.IPNetwork(str(ipaddr)) + except (netaddr.AddrFormatError, ValueError): + return False + return True + def is_ipv4_addr(ipaddr): + """Test if given IP address is a valid IPv4 address.""" + try: + socket.inet_pton(socket.AF_INET, ipaddr) + except socket.error: + return False + return True -class AnsibleFreeIPAParams(Mapping): - def __init__(self, ansible_module): - self.mapping = ansible_module.params - self.ansible_module = ansible_module + def is_ipv6_addr(ipaddr): + """Test if given IP address is a valid IPv6 address.""" + try: + socket.inet_pton(socket.AF_INET6, ipaddr) + except socket.error: + return False + return True - def __getitem__(self, key): - param = self.mapping[key] - if param is not None: - return _afm_convert(param) + def exit_raw_json(module, **kwargs): + """ + Print the raw parameters in JSON format, without masking. + + Due to Ansible filtering out values in the output that match values + in variables which has `no_log` set, if a module need to return user + defined dato to the controller, it cannot rely on + AnsibleModule.exit_json, as there is a chance that a partial match may + occur, masking the data returned. + + This method is a replacement for AnsibleModule.exit_json. It has + nearly the same implementation as exit_json, but does not filter + data. Beware that this data will be logged by Ansible, and if it + contains sensible data, it will be appear in the logs. + """ + module.do_cleanup_files() + print(jsonify(kwargs)) + sys.exit(0) - def __iter__(self): - return iter(self.mapping) + class AnsibleFreeIPAParams(Mapping): + def __init__(self, ansible_module): + self.mapping = ansible_module.params + self.ansible_module = ansible_module - def __len__(self): - return len(self.mapping) + def __getitem__(self, key): + param = self.mapping[key] + if param is not None: + return _afm_convert(param) - @property - def names(self): - return self.name + def __iter__(self): + return iter(self.mapping) - def __getattr__(self, name): - return self.get(name) + def __len__(self): + return len(self.mapping) + @property + def names(self): + return self.name -class FreeIPABaseModule(AnsibleModule): - """ - Base class for FreeIPA Ansible modules. + def __getattr__(self, name): + return self.get(name) - Provides methods useful methods to be used by our modules. + class FreeIPABaseModule(AnsibleModule): + """ + Base class for FreeIPA Ansible modules. - This class should be overriten and instantiated for the module. - A basic implementation of an Ansible FreeIPA module expects its - class to: + Provides methods useful methods to be used by our modules. - 1. Define a class attribute ``ipa_param_mapping`` - 2. Implement the method ``define_ipa_commands()`` - 3. Implement the method ``check_ipa_params()`` (optional) + This class should be overriten and instantiated for the module. + A basic implementation of an Ansible FreeIPA module expects its + class to: - After instantiating the class the method ``ipa_run()`` should be called. + 1. Define a class attribute ``ipa_param_mapping`` + 2. Implement the method ``define_ipa_commands()`` + 3. Implement the method ``check_ipa_params()`` (optional) - Example (ansible-freeipa/plugins/modules/ipasomemodule.py): + After instantiating the class the method ``ipa_run()`` should be + called. - class SomeIPAModule(FreeIPABaseModule): - ipa_param_mapping = { - "arg_to_be_passed_to_ipa_command": "module_param", - "another_arg": "get_another_module_param", - } + Example (ansible-freeipa/plugins/modules/ipasomemodule.py): - def get_another_module_param(self): - another_module_param = self.ipa_params.another_module_param - # Validate or modify another_module_param - # ... - return another_module_param + class SomeIPAModule(FreeIPABaseModule): + ipa_param_mapping = { + "arg_to_be_passed_to_ipa_command": "module_param", + "another_arg": "get_another_module_param", + } - def check_ipa_params(self): - # Validate your params here - # Example: - if not self.ipa_params.module_param in VALID_OPTIONS: - self.fail_json(msg="Invalid value for argument module_param") + def get_another_module_param(self): + another_module_param = self.ipa_params.another_module_param + # Validate or modify another_module_param + # ... + return another_module_param - def define_ipa_commands(self): - args = self.get_ipa_command_args() + def check_ipa_params(self): + # Validate your params here + # Example: + if not self.ipa_params.module_param in VALID_OPTIONS: + self.fail_json( + msg="Invalid value for argument module_param") - self.add_ipa_command( - "some_ipa_command", - name="obj-name", - args=args, - ) + def define_ipa_commands(self): + args = self.get_ipa_command_args() - def main(): - ipa_module = SomeIPAModule(argument_spec=dict( - module_param=dict( - type="str", - default=None, - required=False, - ), - another_module_param=dict( - type="str", - default=None, - required=False, - ), - )) - ipa_module.ipa_run() + self.add_ipa_command( + "some_ipa_command", + name="obj-name", + args=args, + ) - if __name__ == "__main__": - main() + def main(): + ipa_module = SomeIPAModule(argument_spec=dict( + module_param=dict( + type="str", + default=None, + required=False, + ), + another_module_param=dict( + type="str", + default=None, + required=False, + ), + )) + ipa_module.ipa_run() + + if __name__ == "__main__": + main() - """ + """ - ipa_param_mapping = None + ipa_param_mapping = None - def __init__(self, *args, **kwargs): - super(FreeIPABaseModule, self).__init__(*args, **kwargs) + def __init__(self, *args, **kwargs): + super(FreeIPABaseModule, self).__init__(*args, **kwargs) - # Attributes to store kerberos credentials (if needed) - self.ccache_dir = None - self.ccache_name = None + # Attributes to store kerberos credentials (if needed) + self.ccache_dir = None + self.ccache_name = None - # Status of an execution. Will be changed to True - # if something is actually peformed. - self.changed = False + # Status of an execution. Will be changed to True + # if something is actually peformed. + self.changed = False - # Status of the connection with the IPA server. - # We need to know if the connection was actually stablished - # before we start sending commands. - self.ipa_connected = False + # Status of the connection with the IPA server. + # We need to know if the connection was actually stablished + # before we start sending commands. + self.ipa_connected = False - # Commands to be executed - self.ipa_commands = [] + # Commands to be executed + self.ipa_commands = [] - # Module exit arguments. - self.exit_args = {} + # Module exit arguments. + self.exit_args = {} - # Wrapper around the AnsibleModule.params. - # Return the actual params but performing transformations - # when needed. - self.ipa_params = AnsibleFreeIPAParams(self) + # Wrapper around the AnsibleModule.params. + # Return the actual params but performing transformations + # when needed. + self.ipa_params = AnsibleFreeIPAParams(self) - def get_ipa_command_args(self, **kwargs): - """ - Return a dict to be passed to an IPA command. + def get_ipa_command_args(self, **kwargs): + """ + Return a dict to be passed to an IPA command. - The keys of ``ipa_param_mapping`` are also the keys of the return dict. + The keys of ``ipa_param_mapping`` are also the keys of the return + dict. - The values of ``ipa_param_mapping`` needs to be either: - * A str with the name of a defined method; or - * A key of ``AnsibleModule.param``. + The values of ``ipa_param_mapping`` needs to be either: + * A str with the name of a defined method; or + * A key of ``AnsibleModule.param``. - In case of a method the return of the method will be set as value - for the return dict. + In case of a method the return of the method will be set as value + for the return dict. - In case of a AnsibleModule.param the value of the param will be - set in the return dict. In addition to that boolean values will be - automaticaly converted to uppercase strings (as required by FreeIPA - server). + In case of a AnsibleModule.param the value of the param will be + set in the return dict. In addition to that boolean values will be + automaticaly converted to uppercase strings (as required by FreeIPA + server). - """ - args = {} - for ipa_param_name, param_name in self.ipa_param_mapping.items(): - - # Check if param_name is actually a param - if param_name in self.ipa_params: - value = self.ipa_params.get(param_name) - if isinstance(value, bool): - value = "TRUE" if value else "FALSE" - - # Since param wasn't a param check if it's a method name - elif hasattr(self, param_name): - method = getattr(self, param_name) - if callable(method): - value = method(**kwargs) - - # We don't have a way to guess the value so fail. - else: - self.fail_json( - msg=( - "Couldn't get a value for '%s'. Option '%s' is not " - "a module argument neither a defined method." + """ + args = {} + for ipa_param_name, param_name in self.ipa_param_mapping.items(): + + # Check if param_name is actually a param + if param_name in self.ipa_params: + value = self.ipa_params.get(param_name) + if isinstance(value, bool): + value = "TRUE" if value else "FALSE" + + # Since param wasn't a param check if it's a method name + elif hasattr(self, param_name): + method = getattr(self, param_name) + if callable(method): + value = method(**kwargs) + + # We don't have a way to guess the value so fail. + else: + self.fail_json( + msg=( + "Couldn't get a value for '%s'. Option '%s' is " + "not a module argument neither a defined method." + ) + % (ipa_param_name, param_name) ) - % (ipa_param_name, param_name) - ) - - if value is not None: - args[ipa_param_name] = value - - return args - - def check_ipa_params(self): - """Validate ipa_params before command is called.""" - pass - def define_ipa_commands(self): - """Define commands that will be run in IPA server.""" - raise NotImplementedError + if value is not None: + args[ipa_param_name] = value - def api_command(self, command, name=None, args=None): - """Execute a single command in IPA server.""" - if args is None: - args = {} + return args - if name is None: - return api_command_no_name(self, command, args) + def check_ipa_params(self): + """Validate ipa_params before command is called.""" + pass - return api_command(self, command, name, args) + def define_ipa_commands(self): + """Define commands that will be run in IPA server.""" + raise NotImplementedError - def __enter__(self): - """ - Connect to IPA server. + def api_command(self, command, name=None, args=None): + """Execute a single command in IPA server.""" + if args is None: + args = {} - Check the there are working Kerberos credentials to connect to - IPA server. If there are not we perform a temporary kinit - that will be terminated when exiting the context. + if name is None: + return api_command_no_name(self, command, args) - If the connection fails ``ipa_connected`` attribute will be set - to False. - """ - principal = self.ipa_params.ipaadmin_principal - password = self.ipa_params.ipaadmin_password + return api_command(self, command, name, args) - try: - if not valid_creds(self, principal): - self.ccache_dir, self.ccache_name = temp_kinit( - principal, password, - ) + def __enter__(self): + """ + Connect to IPA server. - api_connect() + Check the there are working Kerberos credentials to connect to + IPA server. If there are not we perform a temporary kinit + that will be terminated when exiting the context. - except Exception as excpt: - self.fail_json(msg=str(excpt)) - else: - self.ipa_connected = True + If the connection fails ``ipa_connected`` attribute will be set + to False. + """ + principal = self.ipa_params.ipaadmin_principal + password = self.ipa_params.ipaadmin_password - return self + try: + if not valid_creds(self, principal): + self.ccache_dir, self.ccache_name = temp_kinit( + principal, password, + ) - def __exit__(self, exc_type, exc_val, exc_tb): - """ - Terminate a connection with the IPA server. + api_connect() - Deal with exceptions, destroy temporary kinit credentials and - exit the module with proper arguments. + except Exception as excpt: + self.fail_json(msg=str(excpt)) + else: + self.ipa_connected = True - """ - # TODO: shouldn't we also disconnect from api backend? - temp_kdestroy(self.ccache_dir, self.ccache_name) - - if exc_type == SystemExit: - raise - - if exc_val: - self.fail_json(msg=str(exc_val)) - - self.exit_json(changed=self.changed, **self.exit_args) - - def get_command_errors(self, command, result): - """Look for erros into command results.""" - # Get all errors - # All "already a member" and "not a member" failures in the - # result are ignored. All others are reported. - errors = [] - for item in result.get("failed", tuple()): - failed_item = result["failed"][item] - for member_type in failed_item: - for member, failure in failed_item[member_type]: - if ( - "already a member" in failure - or "not a member" in failure - ): - continue - errors.append( - "%s: %s %s: %s" - % (command, member_type, member, failure) - ) + return self - if len(errors) > 0: - self.fail_json(", ".join("errors")) + def __exit__(self, exc_type, exc_val, exc_tb): + """ + Terminate a connection with the IPA server. - def add_ipa_command(self, command, name=None, args=None): - """Add a command to the list of commands to be executed.""" - self.ipa_commands.append((name, command, args or {})) + Deal with exceptions, destroy temporary kinit credentials and + exit the module with proper arguments. - def _run_ipa_commands(self): - """Execute commands in self.ipa_commands.""" - result = None + """ + # TODO: shouldn't we also disconnect from api backend? + temp_kdestroy(self.ccache_dir, self.ccache_name) + + if exc_type == SystemExit: + raise + + if exc_val: + self.fail_json(msg=str(exc_val)) + + self.exit_json(changed=self.changed, **self.exit_args) + + def get_command_errors(self, command, result): + """Look for erros into command results.""" + # Get all errors + # All "already a member" and "not a member" failures in the + # result are ignored. All others are reported. + errors = [] + for item in result.get("failed", tuple()): + failed_item = result["failed"][item] + for member_type in failed_item: + for member, failure in failed_item[member_type]: + if ( + "already a member" in failure + or "not a member" in failure + ): + continue + errors.append( + "%s: %s %s: %s" + % (command, member_type, member, failure) + ) + + if len(errors) > 0: + self.fail_json(", ".join("errors")) + + def add_ipa_command(self, command, name=None, args=None): + """Add a command to the list of commands to be executed.""" + self.ipa_commands.append((name, command, args or {})) + + def _run_ipa_commands(self): + """Execute commands in self.ipa_commands.""" + result = None + + for name, command, args in self.ipa_commands: + try: + result = self.api_command(command, name, args) + except Exception as excpt: + self.fail_json(msg="%s: %s: %s" % (command, name, + str(excpt))) + else: + self.process_command_result(name, command, args, result) + self.get_command_errors(command, result) + + def process_command_result(self, name, command, args, result): + """ + Process an API command result. - for name, command, args in self.ipa_commands: - try: - result = self.api_command(command, name, args) - except Exception as excpt: - self.fail_json(msg="%s: %s: %s" % (command, name, str(excpt))) + This method can be overriden in subclasses, and + change self.exit_values + to return data in the result for the controller. + """ + if "completed" in result: + if result["completed"] > 0: + self.changed = True else: - self.process_command_result(name, command, args, result) - self.get_command_errors(command, result) - - def process_command_result(self, name, command, args, result): - """ - Process an API command result. - - This method can be overriden in subclasses, and change self.exit_values - to return data in the result for the controller. - """ - if "completed" in result: - if result["completed"] > 0: self.changed = True - else: - self.changed = True - def require_ipa_attrs_change(self, command_args, ipa_attrs): - """ - Compare given args with current object attributes. + def require_ipa_attrs_change(self, command_args, ipa_attrs): + """ + Compare given args with current object attributes. - Returns True in case current IPA object attributes differ from - args passed to the module. - """ - equal = compare_args_ipa(self, command_args, ipa_attrs) - return not equal - - def pdebug(self, value): - """Debug with pretty formatting.""" - self.debug(pformat(value)) - - def ipa_run(self): - """Execute module actions.""" - with self: - if not self.ipa_connected: - return - - self.check_ipa_params() - self.define_ipa_commands() - self._run_ipa_commands() + Returns True in case current IPA object attributes differ from + args passed to the module. + """ + equal = compare_args_ipa(self, command_args, ipa_attrs) + return not equal + + def pdebug(self, value): + """Debug with pretty formatting.""" + self.debug(pformat(value)) + + def ipa_run(self): + """Execute module actions.""" + with self: + if not self.ipa_connected: + return + + self.check_ipa_params() + self.define_ipa_commands() + self._run_ipa_commands() diff --git a/roles/ipaclient/module_utils/ansible_ipa_client.py b/roles/ipaclient/module_utils/ansible_ipa_client.py index fac8123481e33b24e8bff97d64170e26dd85c4bd..33531c51a32be8233e3488424e97a7ecd07183e0 100644 --- a/roles/ipaclient/module_utils/ansible_ipa_client.py +++ b/roles/ipaclient/module_utils/ansible_ipa_client.py @@ -45,229 +45,244 @@ __all__ = ["gssapi", "version", "ipadiscovery", "api", "errors", "x509", "configure_firefox", "sync_time", "check_ldap_conf", "sssd_enable_ifp"] -from ipapython.version import NUM_VERSION, VERSION +import sys + +# HACK: workaround for Ansible 2.9 +# https://github.com/ansible/ansible/issues/68361 +if 'ansible.executor' in sys.modules: + for attr in __all__: + setattr(sys.modules[__name__], attr, None) -if NUM_VERSION < 30201: - # See ipapython/version.py - IPA_MAJOR, IPA_MINOR, IPA_RELEASE = [int(x) for x in VERSION.split(".", 2)] - IPA_PYTHON_VERSION = IPA_MAJOR*10000 + IPA_MINOR*100 + IPA_RELEASE else: - IPA_PYTHON_VERSION = NUM_VERSION - - -class installer_obj(object): - def __init__(self): - pass - - def set_logger(self, logger): - self.logger = logger - - # def __getattribute__(self, attr): - # value = super(installer_obj, self).__getattribute__(attr) - # if not attr.startswith("--") and not attr.endswith("--"): - # logger.debug( - # " <-- Accessing installer.%s (%s)" % (attr, repr(value))) - # return value - - # def __getattr__(self, attr): - # # logger.info(" --> ADDING missing installer.%s" % attr) - # self.logger.warn(" --> ADDING missing installer.%s" % attr) - # setattr(self, attr, None) - # return getattr(self, attr) - - # def __setattr__(self, attr, value): - # logger.debug(" --> Setting installer.%s to %s" % (attr, repr(value))) - # return super(installer_obj, self).__setattr__(attr, value) - - def knobs(self): - for name in self.__dict__: - yield self, name - - -# Initialize installer settings -installer = installer_obj() -# Create options -options = installer -options.interactive = False -options.unattended = not options.interactive - -if NUM_VERSION >= 40400: - # IPA version >= 4.4 - - import sys - import inspect - import gssapi - import logging - - from ipapython import version - try: - from ipaclient.install import ipadiscovery - except ImportError: - from ipaclient import ipadiscovery - from ipalib import api, errors, x509 - from ipalib import constants - try: - from ipalib import sysrestore - except ImportError: + from ipapython.version import NUM_VERSION, VERSION + + if NUM_VERSION < 30201: + # See ipapython/version.py + IPA_MAJOR, IPA_MINOR, IPA_RELEASE = [int(x) for x in + VERSION.split(".", 2)] + IPA_PYTHON_VERSION = IPA_MAJOR*10000 + IPA_MINOR*100 + IPA_RELEASE + else: + IPA_PYTHON_VERSION = NUM_VERSION + + class installer_obj(object): + def __init__(self): + pass + + def set_logger(self, logger): + self.logger = logger + + # def __getattribute__(self, attr): + # value = super(installer_obj, self).__getattribute__(attr) + # if not attr.startswith("--") and not attr.endswith("--"): + # logger.debug( + # " <-- Accessing installer.%s (%s)" % (attr, repr(value))) + # return value + + # def __getattr__(self, attr): + # # logger.info(" --> ADDING missing installer.%s" % attr) + # self.logger.warn(" --> ADDING missing installer.%s" % attr) + # setattr(self, attr, None) + # return getattr(self, attr) + + # def __setattr__(self, attr, value): + # logger.debug(" --> Setting installer.%s to %s" % + # (attr, repr(value))) + # return super(installer_obj, self).__setattr__(attr, value) + + def knobs(self): + for name in self.__dict__: + yield self, name + + # Initialize installer settings + installer = installer_obj() + # Create options + options = installer + options.interactive = False + options.unattended = not options.interactive + + if NUM_VERSION >= 40400: + # IPA version >= 4.4 + + import sys + import inspect + import gssapi + import logging + + from ipapython import version + try: + from ipaclient.install import ipadiscovery + except ImportError: + from ipaclient import ipadiscovery + from ipalib import api, errors, x509 + from ipalib import constants + try: + from ipalib import sysrestore + except ImportError: + try: + from ipalib.install import sysrestore + except ImportError: + from ipapython import sysrestore + try: + from ipalib.install import certmonger + except ImportError: + from ipapython import certmonger + try: + from ipalib.install import certstore + except ImportError: + from ipalib import certstore + from ipalib.rpc import delete_persistent_client_session_data + from ipapython import certdb, ipautil + from ipapython.admintool import ScriptError + from ipapython.ipautil import CheckedIPAddress + from ipalib.util import validate_domain_name, normalize_hostname, \ + validate_hostname + from ipaplatform import services + from ipaplatform.paths import paths + from ipaplatform.tasks import tasks + try: + from cryptography.hazmat.primitives import serialization + except ImportError: + serialization = None + from ipapython.ipautil import CalledProcessError, write_tmp_file, \ + ipa_generate_password + from ipapython.dn import DN try: - from ipalib.install import sysrestore + from ipalib.install.kinit import kinit_keytab, kinit_password except ImportError: - from ipapython import sysrestore - try: - from ipalib.install import certmonger - except ImportError: - from ipapython import certmonger - try: - from ipalib.install import certstore - except ImportError: - from ipalib import certstore - from ipalib.rpc import delete_persistent_client_session_data - from ipapython import certdb, ipautil - from ipapython.admintool import ScriptError - from ipapython.ipautil import CheckedIPAddress - from ipalib.util import validate_domain_name, normalize_hostname, \ - validate_hostname - from ipaplatform import services - from ipaplatform.paths import paths - from ipaplatform.tasks import tasks - try: - from cryptography.hazmat.primitives import serialization - except ImportError: - serialization = None - from ipapython.ipautil import CalledProcessError, write_tmp_file, \ - ipa_generate_password - from ipapython.dn import DN - try: - from ipalib.install.kinit import kinit_keytab, kinit_password - except ImportError: - from ipapython.ipautil import kinit_keytab, kinit_password - from ipapython.ipa_log_manager import standard_logging_setup - from gssapi.exceptions import GSSError - try: - from ipaclient.install.client import configure_krb5_conf, \ - get_ca_certs, SECURE_PATH, get_server_connection_interface, \ - disable_ra, client_dns, \ - configure_certmonger, update_ssh_keys, configure_openldap_conf, \ - hardcode_ldap_server, get_certs_from_ldap, save_state, \ - create_ipa_nssdb, configure_ssh_config, configure_sshd_config, \ - configure_automount, configure_firefox, configure_nisdomain, \ - CLIENT_INSTALL_ERROR, is_ipa_client_installed, \ - CLIENT_ALREADY_CONFIGURED, nssldap_exists, remove_file, \ - check_ip_addresses, print_port_conf_info, configure_ipa_conf, \ - purge_host_keytab, configure_sssd_conf, configure_ldap_conf, \ - configure_nslcd_conf, nosssd_files - get_ca_cert = None - except ImportError: - # Create temporary copy of ipa-client-install script (as - # ipa_client_install.py) to be able to import the script easily - # and also to remove the global finally clause in which the - # generated ccache file gets removed. The ccache file will be - # needed in the next step. - # This is done in a temporary directory that gets removed right - # after ipa_client_install has been imported. - import shutil - import tempfile - temp_dir = tempfile.mkdtemp(dir="/tmp") - sys.path.append(temp_dir) - temp_file = "%s/ipa_client_install.py" % temp_dir - - with open("/usr/sbin/ipa-client-install", "r") as f_in: - with open(temp_file, "w") as f_out: - for line in f_in: - if line.startswith("finally:"): - break - f_out.write(line) - import ipa_client_install - - shutil.rmtree(temp_dir, ignore_errors=True) - sys.path.remove(temp_dir) - - argspec = inspect.getargspec(ipa_client_install.configure_krb5_conf) - if argspec.keywords is None: - def configure_krb5_conf( - cli_realm, cli_domain, cli_server, cli_kdc, dnsok, - filename, client_domain, client_hostname, force=False, - configure_sssd=True): - global options - options.force = force - options.sssd = configure_sssd - return ipa_client_install.configure_krb5_conf( - cli_realm, cli_domain, cli_server, cli_kdc, dnsok, options, - filename, client_domain, client_hostname) - else: - configure_krb5_conf = ipa_client_install.configure_krb5_conf - if NUM_VERSION < 40100: - get_ca_cert = ipa_client_install.get_ca_cert - get_ca_certs = None - else: + from ipapython.ipautil import kinit_keytab, kinit_password + from ipapython.ipa_log_manager import standard_logging_setup + from gssapi.exceptions import GSSError + try: + from ipaclient.install.client import configure_krb5_conf, \ + get_ca_certs, SECURE_PATH, get_server_connection_interface, \ + disable_ra, client_dns, \ + configure_certmonger, update_ssh_keys, \ + configure_openldap_conf, \ + hardcode_ldap_server, get_certs_from_ldap, save_state, \ + create_ipa_nssdb, configure_ssh_config, \ + configure_sshd_config, \ + configure_automount, configure_firefox, configure_nisdomain, \ + CLIENT_INSTALL_ERROR, is_ipa_client_installed, \ + CLIENT_ALREADY_CONFIGURED, nssldap_exists, remove_file, \ + check_ip_addresses, print_port_conf_info, configure_ipa_conf, \ + purge_host_keytab, configure_sssd_conf, configure_ldap_conf, \ + configure_nslcd_conf, nosssd_files get_ca_cert = None - get_ca_certs = ipa_client_install.get_ca_certs - SECURE_PATH = ("/bin:/sbin:/usr/kerberos/bin:/usr/kerberos/sbin:" - "/usr/bin:/usr/sbin") - - get_server_connection_interface = \ - ipa_client_install.get_server_connection_interface - disable_ra = ipa_client_install.disable_ra - client_dns = ipa_client_install.client_dns - configure_certmonger = ipa_client_install.configure_certmonger - update_ssh_keys = ipa_client_install.update_ssh_keys - configure_openldap_conf = ipa_client_install.configure_openldap_conf - hardcode_ldap_server = ipa_client_install.hardcode_ldap_server - get_certs_from_ldap = ipa_client_install.get_certs_from_ldap - save_state = ipa_client_install.save_state - - create_ipa_nssdb = certdb.create_ipa_nssdb - - argspec = inspect.getargspec(ipa_client_install.configure_nisdomain) - if len(argspec.args) == 3: - configure_nisdomain = ipa_client_install.configure_nisdomain - else: - def configure_nisdomain(options, domain, statestore=None): - return ipa_client_install.configure_nisdomain(options, domain) - - configure_ldap_conf = ipa_client_install.configure_ldap_conf - configure_nslcd_conf = ipa_client_install.configure_nslcd_conf - nosssd_files = ipa_client_install.nosssd_files - - configure_ssh_config = ipa_client_install.configure_ssh_config - configure_sshd_config = ipa_client_install.configure_sshd_config - configure_automount = ipa_client_install.configure_automount - configure_firefox = ipa_client_install.configure_firefox - - from ipapython.ipautil import realm_to_suffix, run - - try: - from ipaclient.install import timeconf - time_service = "chronyd" - except ImportError: + except ImportError: + # Create temporary copy of ipa-client-install script (as + # ipa_client_install.py) to be able to import the script easily + # and also to remove the global finally clause in which the + # generated ccache file gets removed. The ccache file will be + # needed in the next step. + # This is done in a temporary directory that gets removed right + # after ipa_client_install has been imported. + import shutil + import tempfile + temp_dir = tempfile.mkdtemp(dir="/tmp") + sys.path.append(temp_dir) + temp_file = "%s/ipa_client_install.py" % temp_dir + + with open("/usr/sbin/ipa-client-install", "r") as f_in: + with open(temp_file, "w") as f_out: + for line in f_in: + if line.startswith("finally:"): + break + f_out.write(line) + import ipa_client_install + + shutil.rmtree(temp_dir, ignore_errors=True) + sys.path.remove(temp_dir) + + argspec = inspect.getargspec( + ipa_client_install.configure_krb5_conf) + if argspec.keywords is None: + def configure_krb5_conf( + cli_realm, cli_domain, cli_server, cli_kdc, dnsok, + filename, client_domain, client_hostname, force=False, + configure_sssd=True): + global options + options.force = force + options.sssd = configure_sssd + return ipa_client_install.configure_krb5_conf( + cli_realm, cli_domain, cli_server, cli_kdc, dnsok, + options, filename, client_domain, client_hostname) + else: + configure_krb5_conf = ipa_client_install.configure_krb5_conf + if NUM_VERSION < 40100: + get_ca_cert = ipa_client_install.get_ca_cert + get_ca_certs = None + else: + get_ca_cert = None + get_ca_certs = ipa_client_install.get_ca_certs + SECURE_PATH = ("/bin:/sbin:/usr/kerberos/bin:/usr/kerberos/sbin:" + "/usr/bin:/usr/sbin") + + get_server_connection_interface = \ + ipa_client_install.get_server_connection_interface + disable_ra = ipa_client_install.disable_ra + client_dns = ipa_client_install.client_dns + configure_certmonger = ipa_client_install.configure_certmonger + update_ssh_keys = ipa_client_install.update_ssh_keys + configure_openldap_conf = \ + ipa_client_install.configure_openldap_conf + hardcode_ldap_server = ipa_client_install.hardcode_ldap_server + get_certs_from_ldap = ipa_client_install.get_certs_from_ldap + save_state = ipa_client_install.save_state + + create_ipa_nssdb = certdb.create_ipa_nssdb + + argspec = \ + inspect.getargspec(ipa_client_install.configure_nisdomain) + if len(argspec.args) == 3: + configure_nisdomain = ipa_client_install.configure_nisdomain + else: + def configure_nisdomain(options, domain, statestore=None): + return ipa_client_install.configure_nisdomain(options, + domain) + + configure_ldap_conf = ipa_client_install.configure_ldap_conf + configure_nslcd_conf = ipa_client_install.configure_nslcd_conf + nosssd_files = ipa_client_install.nosssd_files + + configure_ssh_config = ipa_client_install.configure_ssh_config + configure_sshd_config = ipa_client_install.configure_sshd_config + configure_automount = ipa_client_install.configure_automount + configure_firefox = ipa_client_install.configure_firefox + + from ipapython.ipautil import realm_to_suffix, run + try: - from ipaclient.install import ntpconf as timeconf + from ipaclient.install import timeconf + time_service = "chronyd" except ImportError: - from ipaclient import ntpconf as timeconf - time_service = "ntpd" + try: + from ipaclient.install import ntpconf as timeconf + except ImportError: + from ipaclient import ntpconf as timeconf + time_service = "ntpd" - try: - from ipaclient.install.client import sync_time - except ImportError: - sync_time = None + try: + from ipaclient.install.client import sync_time + except ImportError: + sync_time = None - try: - from ipaclient.install.client import check_ldap_conf - except ImportError: - check_ldap_conf = None + try: + from ipaclient.install.client import check_ldap_conf + except ImportError: + check_ldap_conf = None - try: - from ipaclient.install.client import sssd_enable_ifp - except ImportError: - sssd_enable_ifp = None + try: + from ipaclient.install.client import sssd_enable_ifp + except ImportError: + sssd_enable_ifp = None - logger = logging.getLogger("ipa-client-install") - root_logger = logger + logger = logging.getLogger("ipa-client-install") + root_logger = logger -else: - # IPA version < 4.4 + else: + # IPA version < 4.4 - raise Exception("freeipa version '%s' is too old" % VERSION) + raise Exception("freeipa version '%s' is too old" % VERSION) def setup_logging(): diff --git a/roles/ipareplica/module_utils/ansible_ipa_replica.py b/roles/ipareplica/module_utils/ansible_ipa_replica.py index b0f0d03456a66afaf11b3ba87da2ec6c686df391..1de75e1a46576fa882476ffe573240533d36edf0 100644 --- a/roles/ipareplica/module_utils/ansible_ipa_replica.py +++ b/roles/ipareplica/module_utils/ansible_ipa_replica.py @@ -46,379 +46,383 @@ __all__ = ["contextlib", "dnsexception", "dnsresolver", "dnsreversename", "dnsname", "kernel_keyring", "krbinstance"] import sys -import logging -from contextlib import contextmanager as contextlib_contextmanager - -from ipapython.version import NUM_VERSION, VERSION - -if NUM_VERSION < 30201: - # See ipapython/version.py - IPA_MAJOR, IPA_MINOR, IPA_RELEASE = [int(x) for x in VERSION.split(".", 2)] - IPA_PYTHON_VERSION = IPA_MAJOR*10000 + IPA_MINOR*100 + IPA_RELEASE +# HACK: workaround for Ansible 2.9 +# https://github.com/ansible/ansible/issues/68361 +if 'ansible.executor' in sys.modules: + for attr in __all__: + setattr(sys.modules[__name__], attr, None) else: - IPA_PYTHON_VERSION = NUM_VERSION - - -if NUM_VERSION >= 40600: - # IPA version >= 4.6 - - import contextlib - - import dns.exception as dnsexception - import dns.name as dnsname - import dns.resolver as dnsresolver - import dns.reversename as dnsreversename - - from pkg_resources import parse_version - - from ipaclient.install.ipachangeconf import IPAChangeConf - from ipalib.install import certstore, sysrestore - from ipapython.ipautil import ipa_generate_password - from ipalib.install.kinit import kinit_keytab - from ipapython import ipaldap, ipautil, kernel_keyring - from ipapython.certdb import IPA_CA_TRUST_FLAGS, EXTERNAL_CA_TRUST_FLAGS - from ipapython.dn import DN - from ipapython.admintool import ScriptError - from ipapython.ipa_log_manager import standard_logging_setup - from ipaplatform import services - from ipaplatform.tasks import tasks - from ipaplatform.paths import paths - from ipalib import api, constants, create_api, errors, rpc, x509 - from ipalib.config import Env - from ipalib.util import ( - validate_domain_name, - no_matching_interface_for_ip_address_warning) - from ipaclient.install.client import configure_krb5_conf, purge_host_keytab - from ipaserver.install import ( - adtrust, bindinstance, ca, certs, dns, dsinstance, httpinstance, - installutils, kra, krbinstance, - otpdinstance, custodiainstance, service, upgradeinstance) - try: - from ipaserver.masters import ( - find_providing_servers, find_providing_server) - except ImportError: - from ipaserver.install.service import ( - find_providing_servers, find_providing_server) - from ipaserver.install.installutils import ( - ReplicaConfig, load_pkcs12) - try: - from ipalib.facts import is_ipa_configured - except ImportError: - from ipaserver.install.installutils import is_ipa_configured - from ipaserver.install.replication import ( - ReplicationManager, replica_conn_check) - from ipaserver.install.server.replicainstall import ( - make_pkcs12_info, install_replica_ds, install_krb, install_ca_cert, - install_http, install_dns_records, create_ipa_conf, check_dirsrv, - check_dns_resolution, configure_certmonger, remove_replica_info_dir, - # common_cleanup, - preserve_enrollment_state, uninstall_client, - promote_sssd, promote_openldap_conf, rpc_client, - check_remote_fips_mode, check_remote_version, common_check, - current_domain_level, check_domain_level_is_supported, - # enroll_dl0_replica, - # ensure_enrolled, - promotion_check_ipa_domain - ) - import SSSDConfig - from subprocess import CalledProcessError - - try: - from ipaclient.install import timeconf - time_service = "chronyd" - ntpinstance = None - except ImportError: + import logging + from contextlib import contextmanager as contextlib_contextmanager + + from ipapython.version import NUM_VERSION, VERSION + + if NUM_VERSION < 30201: + # See ipapython/version.py + IPA_MAJOR, IPA_MINOR, IPA_RELEASE = [int(x) for x in + VERSION.split(".", 2)] + IPA_PYTHON_VERSION = IPA_MAJOR*10000 + IPA_MINOR*100 + IPA_RELEASE + else: + IPA_PYTHON_VERSION = NUM_VERSION + + if NUM_VERSION >= 40600: + # IPA version >= 4.6 + + import contextlib + + import dns.exception as dnsexception + import dns.name as dnsname + import dns.resolver as dnsresolver + import dns.reversename as dnsreversename + + from pkg_resources import parse_version + + from ipaclient.install.ipachangeconf import IPAChangeConf + from ipalib.install import certstore, sysrestore + from ipapython.ipautil import ipa_generate_password + from ipalib.install.kinit import kinit_keytab + from ipapython import ipaldap, ipautil, kernel_keyring + from ipapython.certdb import IPA_CA_TRUST_FLAGS, \ + EXTERNAL_CA_TRUST_FLAGS + from ipapython.dn import DN + from ipapython.admintool import ScriptError + from ipapython.ipa_log_manager import standard_logging_setup + from ipaplatform import services + from ipaplatform.tasks import tasks + from ipaplatform.paths import paths + from ipalib import api, constants, create_api, errors, rpc, x509 + from ipalib.config import Env + from ipalib.util import ( + validate_domain_name, + no_matching_interface_for_ip_address_warning) + from ipaclient.install.client import configure_krb5_conf, \ + purge_host_keytab + from ipaserver.install import ( + adtrust, bindinstance, ca, certs, dns, dsinstance, httpinstance, + installutils, kra, krbinstance, + otpdinstance, custodiainstance, service, upgradeinstance) + try: + from ipaserver.masters import ( + find_providing_servers, find_providing_server) + except ImportError: + from ipaserver.install.service import ( + find_providing_servers, find_providing_server) + from ipaserver.install.installutils import ( + ReplicaConfig, load_pkcs12) + try: + from ipalib.facts import is_ipa_configured + except ImportError: + from ipaserver.install.installutils import is_ipa_configured + from ipaserver.install.replication import ( + ReplicationManager, replica_conn_check) + from ipaserver.install.server.replicainstall import ( + make_pkcs12_info, install_replica_ds, install_krb, install_ca_cert, + install_http, install_dns_records, create_ipa_conf, check_dirsrv, + check_dns_resolution, configure_certmonger, + remove_replica_info_dir, + # common_cleanup, + preserve_enrollment_state, uninstall_client, + promote_sssd, promote_openldap_conf, rpc_client, + check_remote_fips_mode, check_remote_version, common_check, + current_domain_level, check_domain_level_is_supported, + # enroll_dl0_replica, + # ensure_enrolled, + promotion_check_ipa_domain + ) + import SSSDConfig + from subprocess import CalledProcessError + try: - from ipaclient.install import ntpconf as timeconf + from ipaclient.install import timeconf + time_service = "chronyd" + ntpinstance = None except ImportError: - from ipaclient import ntpconf as timeconf - from ipaserver.install import ntpinstance - time_service = "ntpd" + try: + from ipaclient.install import ntpconf as timeconf + except ImportError: + from ipaclient import ntpconf as timeconf + from ipaserver.install import ntpinstance + time_service = "ntpd" + else: + # IPA version < 4.6 -else: - # IPA version < 4.6 - - raise Exception("freeipa version '%s' is too old" % VERSION) - - -logger = logging.getLogger("ipa-server-install") - - -def setup_logging(): - # logger.setLevel(logging.DEBUG) - standard_logging_setup( - paths.IPAREPLICA_INSTALL_LOG, verbose=False, debug=False, - filemode='a', console_format='%(message)s') - - -@contextlib_contextmanager -def redirect_stdout(f): - sys.stdout = f - try: - yield f - finally: - sys.stdout = sys.__stdout__ - - -class AnsibleModuleLog(): - def __init__(self, module): - self.module = module - _ansible_module_log = self - - class AnsibleLoggingHandler(logging.Handler): - def emit(self, record): - _ansible_module_log.write(self.format(record)) - - self.logging_handler = AnsibleLoggingHandler() - logger.setLevel(logging.DEBUG) - logger.root.addHandler(self.logging_handler) - - def close(self): - self.flush() - - def flush(self): - pass - - def log(self, msg): - # self.write(msg+"\n") - self.write(msg) - - def debug(self, msg): - self.module.debug(msg) - - def info(self, msg): - self.module.debug(msg) - - def write(self, msg): - self.module.debug(msg) - # self.module.warn(msg) - - -class installer_obj(object): - def __init__(self): - # CompatServerReplicaInstall - self.ca_cert_files = None - self.all_ip_addresses = False - self.no_wait_for_dns = True - self.nisdomain = None - self.no_nisdomain = False - self.no_sudo = False - self.request_cert = False - self.ca_file = None - self.zonemgr = None - self.replica_file = None - # ServerReplicaInstall - self.subject_base = None - self.ca_subject = None - # others - self._ccache = None - self.password = None - self.reverse_zones = [] - # def _is_promote(self): - # return self.replica_file is None - # self.skip_conncheck = False - self._replica_install = False - # self.dnssec_master = False # future unknown - # self.disable_dnssec_master = False # future unknown - # self.domainlevel = MAX_DOMAIN_LEVEL # deprecated - # self.domain_level = self.domainlevel # deprecated - self.interactive = False - self.unattended = not self.interactive - # self.promote = self.replica_file is None - self.promote = True - self.skip_schema_check = None - - # def __getattribute__(self, attr): - # value = super(installer_obj, self).__getattribute__(attr) - # if not attr.startswith("--") and not attr.endswith("--"): - # logger.debug( - # " <-- Accessing installer.%s (%s)" % (attr, repr(value))) - # return value - - def __getattr__(self, attr): - logger.info(" --> ADDING missing installer.%s", attr) - setattr(self, attr, None) - return getattr(self, attr) - - # def __setattr__(self, attr, value): - # logger.debug(" --> Setting installer.%s to %s" % (attr, repr(value))) - # return super(installer_obj, self).__setattr__(attr, value) - - def knobs(self): - for name in self.__dict__: - yield self, name - - -installer = installer_obj() -options = installer - -# DNSInstallInterface -options.dnssec_master = False -options.disable_dnssec_master = False -options.kasp_db_file = None -options.force = False - -# ServerMasterInstall -options.add_sids = False -options.add_agents = False - -# ServerReplicaInstall -options.subject_base = None -options.ca_subject = None - - -def gen_env_boostrap_finalize_core(etc_ipa, default_config): - env = Env() - # env._bootstrap(context='installer', confdir=paths.ETC_IPA, log=None) - # env._finalize_core(**dict(constants.DEFAULT_CONFIG)) - env._bootstrap(context='installer', confdir=etc_ipa, log=None) - env._finalize_core(**dict(default_config)) - return env - - -def api_bootstrap_finalize(env): - # pylint: disable=no-member - xmlrpc_uri = 'https://{}/ipa/xml'.format(ipautil.format_netloc(env.host)) - api.bootstrap(in_server=True, - context='installer', - confdir=paths.ETC_IPA, - ldap_uri=installutils.realm_to_ldapi_uri(env.realm), - xmlrpc_uri=xmlrpc_uri) - # pylint: enable=no-member - api.finalize() - - -def gen_ReplicaConfig(): - class ExtendedReplicaConfig(ReplicaConfig): - def __init__(self, top_dir=None): - super(ExtendedReplicaConfig, self).__init__(top_dir) + raise Exception("freeipa version '%s' is too old" % VERSION) + + logger = logging.getLogger("ipa-server-install") + + def setup_logging(): + # logger.setLevel(logging.DEBUG) + standard_logging_setup( + paths.IPAREPLICA_INSTALL_LOG, verbose=False, debug=False, + filemode='a', console_format='%(message)s') + + @contextlib_contextmanager + def redirect_stdout(f): + sys.stdout = f + try: + yield f + finally: + sys.stdout = sys.__stdout__ + + class AnsibleModuleLog(): + def __init__(self, module): + self.module = module + _ansible_module_log = self + + class AnsibleLoggingHandler(logging.Handler): + def emit(self, record): + _ansible_module_log.write(self.format(record)) + + self.logging_handler = AnsibleLoggingHandler() + logger.setLevel(logging.DEBUG) + logger.root.addHandler(self.logging_handler) + + def close(self): + self.flush() + + def flush(self): + pass + + def log(self, msg): + # self.write(msg+"\n") + self.write(msg) + + def debug(self, msg): + self.module.debug(msg) + + def info(self, msg): + self.module.debug(msg) + + def write(self, msg): + self.module.debug(msg) + # self.module.warn(msg) + + class installer_obj(object): + def __init__(self): + # CompatServerReplicaInstall + self.ca_cert_files = None + self.all_ip_addresses = False + self.no_wait_for_dns = True + self.nisdomain = None + self.no_nisdomain = False + self.no_sudo = False + self.request_cert = False + self.ca_file = None + self.zonemgr = None + self.replica_file = None + # ServerReplicaInstall + self.subject_base = None + self.ca_subject = None + # others + self._ccache = None + self.password = None + self.reverse_zones = [] + # def _is_promote(self): + # return self.replica_file is None + # self.skip_conncheck = False + self._replica_install = False + # self.dnssec_master = False # future unknown + # self.disable_dnssec_master = False # future unknown + # self.domainlevel = MAX_DOMAIN_LEVEL # deprecated + # self.domain_level = self.domainlevel # deprecated + self.interactive = False + self.unattended = not self.interactive + # self.promote = self.replica_file is None + self.promote = True + self.skip_schema_check = None # def __getattribute__(self, attr): - # value = super(ExtendedReplicaConfig, self).__getattribute__(attr) - # if attr not in ["__dict__", "knobs"]: - # logger.debug(" <== Accessing config.%s (%s)" % - # (attr, repr(value))) - # return value + # value = super(installer_obj, self).__getattribute__(attr) + # if not attr.startswith("--") and not attr.endswith("--"): + # logger.debug( + # " <-- Accessing installer.%s (%s)" % + # (attr, repr(value))) + # return value def __getattr__(self, attr): - logger.info(" ==> ADDING missing config.%s", attr) + logger.info(" --> ADDING missing installer.%s", attr) setattr(self, attr, None) return getattr(self, attr) # def __setattr__(self, attr, value): - # logger.debug(" ==> Setting config.%s to %s" % (attr, repr(value))) - # return super(ExtendedReplicaConfig, self).__setattr__(attr, value) + # logger.debug(" --> Setting installer.%s to %s" % + # (attr, repr(value))) + # return super(installer_obj, self).__setattr__(attr, value) def knobs(self): for name in self.__dict__: yield self, name - # config = ReplicaConfig() - config = ExtendedReplicaConfig() - config.realm_name = api.env.realm - config.host_name = api.env.host - config.domain_name = api.env.domain - config.master_host_name = api.env.server - config.ca_host_name = api.env.ca_host - config.kra_host_name = config.ca_host_name - config.ca_ds_port = 389 - config.setup_ca = options.setup_ca - config.setup_kra = options.setup_kra - config.dir = options._top_dir - config.basedn = api.env.basedn - # config.subject_base = options.subject_base - - return config - - -def replica_ds_init_info(ansible_log, - config, options, ca_is_configured, remote_api, - ds_ca_subject, ca_file, - promote=False, pkcs12_info=None): - - dsinstance.check_ports() - - # if we have a pkcs12 file, create the cert db from - # that. Otherwise the ds setup will create the CA - # cert - if pkcs12_info is None: - pkcs12_info = make_pkcs12_info(config.dir, "dscert.p12", - "dirsrv_pin.txt") - - # during replica install, this gets invoked before local DS is - # available, so use the remote api. - # if ca_is_configured: - # ca_subject = ca.lookup_ca_subject(_api, config.subject_base) - # else: - # ca_subject = installutils.default_ca_subject_dn(config.subject_base) - ca_subject = ds_ca_subject - - ds = dsinstance.DsInstance( - config_ldif=options.dirsrv_config_file) - ds.set_output(ansible_log) - - # Source: ipaserver/install/dsinstance.py - - # idstart and idmax are configured so that the range is seen as - # depleted by the DNA plugin and the replica will go and get a - # new range from the master. - # This way all servers use the initially defined range by default. - idstart = 1101 - idmax = 1100 - - with redirect_stdout(ansible_log): - ds.init_info( - realm_name=config.realm_name, - fqdn=config.host_name, - domain_name=config.domain_name, - dm_password=config.dirman_password, - subject_base=config.subject_base, - ca_subject=ca_subject, - idstart=idstart, - idmax=idmax, - pkcs12_info=pkcs12_info, - ca_file=ca_file, - setup_pkinit=not options.no_pkinit, - ) - ds.master_fqdn = config.master_host_name - if ca_is_configured is not None: - ds.ca_is_configured = ca_is_configured - ds.promote = promote - ds.api = remote_api - - # from __setup_replica - - # Always connect to ds over ldapi - ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=ds.realm) - conn = ipaldap.LDAPClient(ldap_uri) - conn.external_bind() - - return ds - - -def ansible_module_get_parsed_ip_addresses(ansible_module, - param='ip_addresses'): - ip_addrs = [] - for ip in ansible_module.params.get(param): - try: - ip_parsed = ipautil.CheckedIPAddress(ip) - except Exception as e: - ansible_module.fail_json(msg="Invalid IP Address %s: %s" % (ip, e)) - ip_addrs.append(ip_parsed) - return ip_addrs - - -def gen_remote_api(master_host_name, etc_ipa): - ldapuri = 'ldaps://%s' % ipautil.format_netloc(master_host_name) - xmlrpc_uri = 'https://{}/ipa/xml'.format( - ipautil.format_netloc(master_host_name)) - remote_api = create_api(mode=None) - remote_api.bootstrap(in_server=True, - context='installer', - confdir=etc_ipa, - ldap_uri=ldapuri, - xmlrpc_uri=xmlrpc_uri) - remote_api.finalize() - return remote_api + installer = installer_obj() + options = installer + + # DNSInstallInterface + options.dnssec_master = False + options.disable_dnssec_master = False + options.kasp_db_file = None + options.force = False + + # ServerMasterInstall + options.add_sids = False + options.add_agents = False + + # ServerReplicaInstall + options.subject_base = None + options.ca_subject = None + + def gen_env_boostrap_finalize_core(etc_ipa, default_config): + env = Env() + # env._bootstrap(context='installer', confdir=paths.ETC_IPA, log=None) + # env._finalize_core(**dict(constants.DEFAULT_CONFIG)) + env._bootstrap(context='installer', confdir=etc_ipa, log=None) + env._finalize_core(**dict(default_config)) + return env + + def api_bootstrap_finalize(env): + # pylint: disable=no-member + xmlrpc_uri = \ + 'https://{}/ipa/xml'.format(ipautil.format_netloc(env.host)) + api.bootstrap(in_server=True, + context='installer', + confdir=paths.ETC_IPA, + ldap_uri=installutils.realm_to_ldapi_uri(env.realm), + xmlrpc_uri=xmlrpc_uri) + # pylint: enable=no-member + api.finalize() + + def gen_ReplicaConfig(): + class ExtendedReplicaConfig(ReplicaConfig): + def __init__(self, top_dir=None): + super(ExtendedReplicaConfig, self).__init__(top_dir) + + # def __getattribute__(self, attr): + # value = super(ExtendedReplicaConfig, self).__getattribute__( + # attr) + # if attr not in ["__dict__", "knobs"]: + # logger.debug(" <== Accessing config.%s (%s)" % + # (attr, repr(value))) + # return value + + def __getattr__(self, attr): + logger.info(" ==> ADDING missing config.%s", attr) + setattr(self, attr, None) + return getattr(self, attr) + + # def __setattr__(self, attr, value): + # logger.debug(" ==> Setting config.%s to %s" % + # (attr, repr(value))) + # return super(ExtendedReplicaConfig, self).__setattr__(attr, + # value) + + def knobs(self): + for name in self.__dict__: + yield self, name + + # config = ReplicaConfig() + config = ExtendedReplicaConfig() + config.realm_name = api.env.realm + config.host_name = api.env.host + config.domain_name = api.env.domain + config.master_host_name = api.env.server + config.ca_host_name = api.env.ca_host + config.kra_host_name = config.ca_host_name + config.ca_ds_port = 389 + config.setup_ca = options.setup_ca + config.setup_kra = options.setup_kra + config.dir = options._top_dir + config.basedn = api.env.basedn + # config.subject_base = options.subject_base + + return config + + def replica_ds_init_info(ansible_log, + config, options, ca_is_configured, remote_api, + ds_ca_subject, ca_file, + promote=False, pkcs12_info=None): + + dsinstance.check_ports() + + # if we have a pkcs12 file, create the cert db from + # that. Otherwise the ds setup will create the CA + # cert + if pkcs12_info is None: + pkcs12_info = make_pkcs12_info(config.dir, "dscert.p12", + "dirsrv_pin.txt") + + # during replica install, this gets invoked before local DS is + # available, so use the remote api. + # if ca_is_configured: + # ca_subject = ca.lookup_ca_subject(_api, config.subject_base) + # else: + # ca_subject = installutils.default_ca_subject_dn( + # config.subject_base) + ca_subject = ds_ca_subject + + ds = dsinstance.DsInstance( + config_ldif=options.dirsrv_config_file) + ds.set_output(ansible_log) + + # Source: ipaserver/install/dsinstance.py + + # idstart and idmax are configured so that the range is seen as + # depleted by the DNA plugin and the replica will go and get a + # new range from the master. + # This way all servers use the initially defined range by default. + idstart = 1101 + idmax = 1100 + + with redirect_stdout(ansible_log): + ds.init_info( + realm_name=config.realm_name, + fqdn=config.host_name, + domain_name=config.domain_name, + dm_password=config.dirman_password, + subject_base=config.subject_base, + ca_subject=ca_subject, + idstart=idstart, + idmax=idmax, + pkcs12_info=pkcs12_info, + ca_file=ca_file, + setup_pkinit=not options.no_pkinit, + ) + ds.master_fqdn = config.master_host_name + if ca_is_configured is not None: + ds.ca_is_configured = ca_is_configured + ds.promote = promote + ds.api = remote_api + + # from __setup_replica + + # Always connect to ds over ldapi + ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=ds.realm) + conn = ipaldap.LDAPClient(ldap_uri) + conn.external_bind() + + return ds + + def ansible_module_get_parsed_ip_addresses(ansible_module, + param='ip_addresses'): + ip_addrs = [] + for ip in ansible_module.params.get(param): + try: + ip_parsed = ipautil.CheckedIPAddress(ip) + except Exception as e: + ansible_module.fail_json( + msg="Invalid IP Address %s: %s" % (ip, e)) + ip_addrs.append(ip_parsed) + return ip_addrs + + def gen_remote_api(master_host_name, etc_ipa): + ldapuri = 'ldaps://%s' % ipautil.format_netloc(master_host_name) + xmlrpc_uri = 'https://{}/ipa/xml'.format( + ipautil.format_netloc(master_host_name)) + remote_api = create_api(mode=None) + remote_api.bootstrap(in_server=True, + context='installer', + confdir=etc_ipa, + ldap_uri=ldapuri, + xmlrpc_uri=xmlrpc_uri) + remote_api.finalize() + return remote_api diff --git a/roles/ipaserver/module_utils/ansible_ipa_server.py b/roles/ipaserver/module_utils/ansible_ipa_server.py index fcecd0df21061dbdc2f9f7534b69fc3a32f058a4..a5310f88c3ad89d081182a456e5fffde6d811645 100644 --- a/roles/ipaserver/module_utils/ansible_ipa_server.py +++ b/roles/ipaserver/module_utils/ansible_ipa_server.py @@ -41,352 +41,349 @@ __all__ = ["IPAChangeConf", "certmonger", "sysrestore", "root_logger", "check_available_memory"] import sys -import logging -from contextlib import contextmanager as contextlib_contextmanager -import six -import base64 - -from ipapython.version import NUM_VERSION, VERSION - -if NUM_VERSION < 30201: - # See ipapython/version.py - IPA_MAJOR, IPA_MINOR, IPA_RELEASE = [int(x) for x in VERSION.split(".", 2)] - IPA_PYTHON_VERSION = IPA_MAJOR*10000 + IPA_MINOR*100 + IPA_RELEASE -else: - IPA_PYTHON_VERSION = NUM_VERSION - - -if NUM_VERSION >= 40500: - # IPA version >= 4.5 - - from ipaclient.install.ipachangeconf import IPAChangeConf - from ipalib.install import certmonger - try: - from ipalib import sysrestore - except ImportError: - from ipalib.install import sysrestore - from ipapython import ipautil - from ipapython.ipa_log_manager import standard_logging_setup - try: - from ipapython.ipa_log_manager import root_logger - except ImportError: - root_logger = None - from ipapython.ipautil import ( - ipa_generate_password, run) - from ipapython.admintool import ScriptError - from ipaplatform import services - from ipaplatform.paths import paths - from ipaplatform.tasks import tasks - from ipalib import api, errors, x509 - from ipalib.constants import DOMAIN_LEVEL_0, MIN_DOMAIN_LEVEL, \ - MAX_DOMAIN_LEVEL - try: - from ipalib.constants import IPAAPI_USER - except ImportError: - IPAAPI_USER = None - from ipalib.util import ( - validate_domain_name, - no_matching_interface_for_ip_address_warning, - ) - from ipapython.dnsutil import check_zone_overlap - from ipapython.dn import DN - try: - from ipaclient.install import timeconf - from ipaclient.install.client import sync_time - time_service = "chronyd" - ntpinstance = None - except ImportError: - try: - from ipaclient.install import ntpconf as timeconf - except ImportError: - from ipaclient import ntpconf as timeconf - from ipaserver.install import ntpinstance - time_service = "ntpd" - sync_time = None - from ipaserver.install import ( - adtrust, bindinstance, ca, dns, dsinstance, - httpinstance, installutils, kra, krbinstance, - otpdinstance, custodiainstance, replication, service, - sysupgrade) - adtrust_imported = True - kra_imported = True - from ipaserver.install.installutils import ( - BadHostError, get_fqdn, get_server_ip_address, - load_pkcs12, read_password, verify_fqdn, - update_hosts_file) - try: - from ipalib.facts import is_ipa_configured - except ImportError: - from ipaserver.install.installutils import is_ipa_configured - from ipaserver.install.server.install import ( - check_dirsrv, validate_admin_password, validate_dm_password, - read_cache, write_cache) - try: - from ipaserver.install.dogtaginstance import PKIIniLoader - except ImportError: - PKIIniLoader = None - try: - from ipaserver.install.installutils import default_subject_base - except ImportError: - def default_subject_base(realm_name): - return DN(('O', realm_name)) - try: - from ipalib.facts import IPA_MODULES - except ImportError: - from ipaserver.install.installutils import IPA_MODULES - try: - from ipaserver.install.installutils import default_ca_subject_dn - except ImportError: - def default_ca_subject_dn(subject_base): - return DN(('CN', 'Certificate Authority'), subject_base) - try: - from ipaserver.install.installutils import check_available_memory - except ImportError: - check_available_memory = None - - try: - from ipaserver.install import adtrustinstance - _server_trust_ad_installed = True - except ImportError: - _server_trust_ad_installed = False - - try: - from ipaclient.install.client import check_ldap_conf - except ImportError: - check_ldap_conf = None - - try: - from ipalib.x509 import Encoding - except ImportError: - from cryptography.hazmat.primitives.serialization import Encoding - - try: - from ipalib.x509 import load_pem_x509_certificate - except ImportError: - from ipalib.x509 import load_certificate - load_pem_x509_certificate = None +# HACK: workaround for Ansible 2.9 +# https://github.com/ansible/ansible/issues/68361 +if 'ansible.executor' in sys.modules: + for attr in __all__: + setattr(sys.modules[__name__], attr, None) else: - # IPA version < 4.5 - - raise Exception("freeipa version '%s' is too old" % VERSION) - - -logger = logging.getLogger("ipa-server-install") - - -def setup_logging(): - # logger.setLevel(logging.DEBUG) - standard_logging_setup( - paths.IPASERVER_INSTALL_LOG, verbose=False, debug=False, - filemode='a', console_format='%(message)s') - - -@contextlib_contextmanager -def redirect_stdout(f): - sys.stdout = f - try: - yield f - finally: - sys.stdout = sys.__stdout__ - - -class AnsibleModuleLog(): - def __init__(self, module): - self.module = module - _ansible_module_log = self - - class AnsibleLoggingHandler(logging.Handler): - def emit(self, record): - _ansible_module_log.write(self.format(record)) - - self.logging_handler = AnsibleLoggingHandler() - logger.setLevel(logging.DEBUG) - logger.root.addHandler(self.logging_handler) - - def close(self): - self.flush() - def flush(self): - pass + import logging + from contextlib import contextmanager as contextlib_contextmanager + import six + import base64 - def log(self, msg): - # self.write(msg+"\n") - self.write(msg) + from ipapython.version import NUM_VERSION, VERSION - def debug(self, msg): - self.module.debug(msg) - - def info(self, msg): - self.module.debug(msg) - - def write(self, msg): - self.module.debug(msg) - # self.module.warn(msg) - - -class options_obj(object): - def __init__(self): - self._replica_install = False - self.dnssec_master = False # future unknown - self.disable_dnssec_master = False # future unknown - self.domainlevel = MAX_DOMAIN_LEVEL # deprecated - self.domain_level = self.domainlevel # deprecated - self.interactive = False - self.unattended = not self.interactive - - # def __getattribute__(self, attr): - # logger.info(" <-- Accessing options.%s" % attr) - # return super(options_obj, self).__getattribute__(attr) - - # def __getattr__(self, attr): - # logger.info(" --> Adding missing options.%s" % attr) - # setattr(self, attr, None) - # return getattr(self, attr) - - def knobs(self): - for name in self.__dict__: - yield self, name - - -options = options_obj() -installer = options - -# ServerMasterInstall -options.add_sids = True -options.add_agents = False - - -# Installable -options.uninstalling = False + if NUM_VERSION < 30201: + # See ipapython/version.py + IPA_MAJOR, IPA_MINOR, IPA_RELEASE = [int(x) for x in + VERSION.split(".", 2)] + IPA_PYTHON_VERSION = IPA_MAJOR*10000 + IPA_MINOR*100 + IPA_RELEASE + else: + IPA_PYTHON_VERSION = NUM_VERSION -# ServerInstallInterface -options.description = "Server" + if NUM_VERSION >= 40500: + # IPA version >= 4.5 -options.kinit_attempts = 1 -options.fixed_primary = True -options.permit = False -options.enable_dns_updates = False -options.no_krb5_offline_passwords = False -options.preserve_sssd = False -options.no_sssd = False + from ipaclient.install.ipachangeconf import IPAChangeConf + from ipalib.install import certmonger + try: + from ipalib import sysrestore + except ImportError: + from ipalib.install import sysrestore + from ipapython import ipautil + from ipapython.ipa_log_manager import standard_logging_setup + try: + from ipapython.ipa_log_manager import root_logger + except ImportError: + root_logger = None + from ipapython.ipautil import ( + ipa_generate_password, run) + from ipapython.admintool import ScriptError + from ipaplatform import services + from ipaplatform.paths import paths + from ipaplatform.tasks import tasks + from ipalib import api, errors, x509 + from ipalib.constants import DOMAIN_LEVEL_0, MIN_DOMAIN_LEVEL, \ + MAX_DOMAIN_LEVEL + try: + from ipalib.constants import IPAAPI_USER + except ImportError: + IPAAPI_USER = None + from ipalib.util import ( + validate_domain_name, + no_matching_interface_for_ip_address_warning, + ) + from ipapython.dnsutil import check_zone_overlap + from ipapython.dn import DN + try: + from ipaclient.install import timeconf + from ipaclient.install.client import sync_time + time_service = "chronyd" + ntpinstance = None + except ImportError: + try: + from ipaclient.install import ntpconf as timeconf + except ImportError: + from ipaclient import ntpconf as timeconf + from ipaserver.install import ntpinstance + time_service = "ntpd" + sync_time = None + from ipaserver.install import ( + adtrust, bindinstance, ca, dns, dsinstance, + httpinstance, installutils, kra, krbinstance, + otpdinstance, custodiainstance, replication, service, + sysupgrade) + adtrust_imported = True + kra_imported = True + from ipaserver.install.installutils import ( + BadHostError, get_fqdn, get_server_ip_address, + load_pkcs12, read_password, verify_fqdn, + update_hosts_file) + try: + from ipalib.facts import is_ipa_configured + except ImportError: + from ipaserver.install.installutils import is_ipa_configured + from ipaserver.install.server.install import ( + check_dirsrv, validate_admin_password, validate_dm_password, + read_cache, write_cache) + try: + from ipaserver.install.dogtaginstance import PKIIniLoader + except ImportError: + PKIIniLoader = None + try: + from ipaserver.install.installutils import default_subject_base + except ImportError: + def default_subject_base(realm_name): + return DN(('O', realm_name)) + try: + from ipalib.facts import IPA_MODULES + except ImportError: + from ipaserver.install.installutils import IPA_MODULES + try: + from ipaserver.install.installutils import default_ca_subject_dn + except ImportError: + def default_ca_subject_dn(subject_base): + return DN(('CN', 'Certificate Authority'), subject_base) + try: + from ipaserver.install.installutils import check_available_memory + except ImportError: + check_available_memory = None -# ServerMasterInstall -options.force_join = False -options.servers = None -options.no_wait_for_dns = True -options.host_password = None -options.keytab = None -options.setup_ca = True -# always run sidgen task and do not allow adding agents on first master -options.add_sids = True -options.add_agents = False - -# ADTrustInstallInterface -# no_msdcs is deprecated -options.no_msdcs = False - -# For pylint -options.external_cert_files = None -options.dirsrv_cert_files = None - -# Uninstall -options.ignore_topology_disconnect = False -options.ignore_last_of_role = False + try: + from ipaserver.install import adtrustinstance + _server_trust_ad_installed = True + except ImportError: + _server_trust_ad_installed = False + try: + from ipaclient.install.client import check_ldap_conf + except ImportError: + check_ldap_conf = None -def api_Backend_ldap2(host_name, setup_ca, connect=False): - # we are sure we have the configuration file ready. - cfg = dict(context='installer', confdir=paths.ETC_IPA, in_server=True, - host=host_name) - if setup_ca: - # we have an IPA-integrated CA - cfg['ca_host'] = host_name + try: + from ipalib.x509 import Encoding + except ImportError: + from cryptography.hazmat.primitives.serialization import Encoding - api.bootstrap(**cfg) - api.finalize() - if connect: - api.Backend.ldap2.connect() - - -def ds_init_info(ansible_log, fstore, domainlevel, dirsrv_config_file, - realm_name, host_name, domain_name, dm_password, - idstart, idmax, subject_base, ca_subject, - no_hbac_allow, dirsrv_pkcs12_info, no_pkinit): - - if not options.external_cert_files: - ds = dsinstance.DsInstance(fstore=fstore, domainlevel=domainlevel, - config_ldif=dirsrv_config_file) - ds.set_output(ansible_log) - - if options.dirsrv_cert_files: - _dirsrv_pkcs12_info = dirsrv_pkcs12_info - else: - _dirsrv_pkcs12_info = None + try: + from ipalib.x509 import load_pem_x509_certificate + except ImportError: + from ipalib.x509 import load_certificate + load_pem_x509_certificate = None - with redirect_stdout(ansible_log): - ds.init_info(realm_name, host_name, domain_name, dm_password, - subject_base, ca_subject, idstart, idmax, - # hbac_allow=not no_hbac_allow, - _dirsrv_pkcs12_info, setup_pkinit=not no_pkinit) else: - ds = dsinstance.DsInstance(fstore=fstore, domainlevel=domainlevel) - ds.set_output(ansible_log) + # IPA version < 4.5 - with redirect_stdout(ansible_log): - ds.init_info(realm_name, host_name, domain_name, dm_password, - subject_base, ca_subject, 1101, 1100, None, - setup_pkinit=not no_pkinit) + raise Exception("freeipa version '%s' is too old" % VERSION) - return ds + logger = logging.getLogger("ipa-server-install") + def setup_logging(): + # logger.setLevel(logging.DEBUG) + standard_logging_setup( + paths.IPASERVER_INSTALL_LOG, verbose=False, debug=False, + filemode='a', console_format='%(message)s') -def ansible_module_get_parsed_ip_addresses(ansible_module, - param='ip_addresses'): - ip_addrs = [] - for ip in ansible_module.params.get(param): + @contextlib_contextmanager + def redirect_stdout(f): + sys.stdout = f try: - ip_parsed = ipautil.CheckedIPAddress(ip) - except Exception as e: - ansible_module.fail_json(msg="Invalid IP Address %s: %s" % (ip, e)) - ip_addrs.append(ip_parsed) - return ip_addrs - - -def encode_certificate(cert): - """ - Encode a certificate using base64. - - It also takes FreeIPA and Python versions into account. - """ - if isinstance(cert, (str, bytes)): - encoded = base64.b64encode(cert) - else: - encoded = base64.b64encode(cert.public_bytes(Encoding.DER)) - if not six.PY2: - encoded = encoded.decode('ascii') - return encoded - - -def decode_certificate(cert): - """ - Decode a certificate using base64. - - It also takes FreeIPA versions into account and returns a IPACertificate - for newer IPA versions. - """ - if hasattr(x509, "IPACertificate"): - cert = cert.strip() - if not cert.startswith("-----BEGIN CERTIFICATE-----"): - cert = "-----BEGIN CERTIFICATE-----\n" + cert - if not cert.endswith("-----END CERTIFICATE-----"): - cert += "\n-----END CERTIFICATE-----" - - if load_pem_x509_certificate is not None: - cert = load_pem_x509_certificate(cert.encode('utf-8')) + yield f + finally: + sys.stdout = sys.__stdout__ + + class AnsibleModuleLog(): + def __init__(self, module): + self.module = module + _ansible_module_log = self + + class AnsibleLoggingHandler(logging.Handler): + def emit(self, record): + _ansible_module_log.write(self.format(record)) + + self.logging_handler = AnsibleLoggingHandler() + logger.setLevel(logging.DEBUG) + logger.root.addHandler(self.logging_handler) + + def close(self): + self.flush() + + def flush(self): + pass + + def log(self, msg): + # self.write(msg+"\n") + self.write(msg) + + def debug(self, msg): + self.module.debug(msg) + + def info(self, msg): + self.module.debug(msg) + + def write(self, msg): + self.module.debug(msg) + # self.module.warn(msg) + + class options_obj(object): + def __init__(self): + self._replica_install = False + self.dnssec_master = False # future unknown + self.disable_dnssec_master = False # future unknown + self.domainlevel = MAX_DOMAIN_LEVEL # deprecated + self.domain_level = self.domainlevel # deprecated + self.interactive = False + self.unattended = not self.interactive + + # def __getattribute__(self, attr): + # logger.info(" <-- Accessing options.%s" % attr) + # return super(options_obj, self).__getattribute__(attr) + + # def __getattr__(self, attr): + # logger.info(" --> Adding missing options.%s" % attr) + # setattr(self, attr, None) + # return getattr(self, attr) + + def knobs(self): + for name in self.__dict__: + yield self, name + + options = options_obj() + installer = options + + # ServerMasterInstall + options.add_sids = True + options.add_agents = False + + # Installable + options.uninstalling = False + + # ServerInstallInterface + options.description = "Server" + + options.kinit_attempts = 1 + options.fixed_primary = True + options.permit = False + options.enable_dns_updates = False + options.no_krb5_offline_passwords = False + options.preserve_sssd = False + options.no_sssd = False + + # ServerMasterInstall + options.force_join = False + options.servers = None + options.no_wait_for_dns = True + options.host_password = None + options.keytab = None + options.setup_ca = True + # always run sidgen task and do not allow adding agents on first master + options.add_sids = True + options.add_agents = False + + # ADTrustInstallInterface + # no_msdcs is deprecated + options.no_msdcs = False + + # For pylint + options.external_cert_files = None + options.dirsrv_cert_files = None + + # Uninstall + options.ignore_topology_disconnect = False + options.ignore_last_of_role = False + + def api_Backend_ldap2(host_name, setup_ca, connect=False): + # we are sure we have the configuration file ready. + cfg = dict(context='installer', confdir=paths.ETC_IPA, in_server=True, + host=host_name) + if setup_ca: + # we have an IPA-integrated CA + cfg['ca_host'] = host_name + + api.bootstrap(**cfg) + api.finalize() + if connect: + api.Backend.ldap2.connect() + + def ds_init_info(ansible_log, fstore, domainlevel, dirsrv_config_file, + realm_name, host_name, domain_name, dm_password, + idstart, idmax, subject_base, ca_subject, + no_hbac_allow, dirsrv_pkcs12_info, no_pkinit): + + if not options.external_cert_files: + ds = dsinstance.DsInstance(fstore=fstore, domainlevel=domainlevel, + config_ldif=dirsrv_config_file) + ds.set_output(ansible_log) + + if options.dirsrv_cert_files: + _dirsrv_pkcs12_info = dirsrv_pkcs12_info + else: + _dirsrv_pkcs12_info = None + + with redirect_stdout(ansible_log): + ds.init_info(realm_name, host_name, domain_name, dm_password, + subject_base, ca_subject, idstart, idmax, + # hbac_allow=not no_hbac_allow, + _dirsrv_pkcs12_info, setup_pkinit=not no_pkinit) else: - cert = load_certificate(cert.encode('utf-8')) - else: - cert = base64.b64decode(cert) - return cert + ds = dsinstance.DsInstance(fstore=fstore, domainlevel=domainlevel) + ds.set_output(ansible_log) + + with redirect_stdout(ansible_log): + ds.init_info(realm_name, host_name, domain_name, dm_password, + subject_base, ca_subject, 1101, 1100, None, + setup_pkinit=not no_pkinit) + + return ds + + def ansible_module_get_parsed_ip_addresses(ansible_module, + param='ip_addresses'): + ip_addrs = [] + for ip in ansible_module.params.get(param): + try: + ip_parsed = ipautil.CheckedIPAddress(ip) + except Exception as e: + ansible_module.fail_json( + msg="Invalid IP Address %s: %s" % (ip, e)) + ip_addrs.append(ip_parsed) + return ip_addrs + + def encode_certificate(cert): + """ + Encode a certificate using base64. + + It also takes FreeIPA and Python versions into account. + """ + if isinstance(cert, (str, bytes)): + encoded = base64.b64encode(cert) + else: + encoded = base64.b64encode(cert.public_bytes(Encoding.DER)) + if not six.PY2: + encoded = encoded.decode('ascii') + return encoded + + def decode_certificate(cert): + """ + Decode a certificate using base64. + + It also takes FreeIPA versions into account and returns a + IPACertificate for newer IPA versions. + """ + if hasattr(x509, "IPACertificate"): + cert = cert.strip() + if not cert.startswith("-----BEGIN CERTIFICATE-----"): + cert = "-----BEGIN CERTIFICATE-----\n" + cert + if not cert.endswith("-----END CERTIFICATE-----"): + cert += "\n-----END CERTIFICATE-----" + + if load_pem_x509_certificate is not None: + cert = load_pem_x509_certificate(cert.encode('utf-8')) + else: + cert = load_certificate(cert.encode('utf-8')) + else: + cert = base64.b64decode(cert) + return cert