Esempio n. 1
0
 def __init__(self):
     super(API, self).__init__()
     self.__plugins = set()
     self.__plugins_by_key = {}
     self.__default_map = {}
     self.__instances = {}
     self.__next = {}
     self.__done = set()
     self.env = Env()
Esempio n. 2
0
 def __init__(self):
     super(API, self).__init__()
     self.__plugins = set()
     self.__plugins_by_key = {}
     self.__default_map = {}
     self.__instances = {}
     self.__next = {}
     self.__done = set()
     self.env = Env()
Esempio n. 3
0
def api_connect(context=None):
    """
    Initialize IPA API with the provided context.

    `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'
    if context is None:
        context = 'server'

    api.bootstrap(context=context, debug=env.debug, log=None)
    api.finalize()

    if api.env.in_server:
        backend = api.Backend.ldap2
    else:
        backend = api.Backend.rpcclient

    if not backend.isconnected():
        backend.connect(ccache=os.environ.get('KRB5CCNAME', 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
Esempio n. 5
0
def api_connect():
    """
    Create environment, initialize api and connect to ldap2
    """
    env = Env()
    env._bootstrap()
    env._finalize_core(**dict(DEFAULT_CONFIG))

    api.bootstrap(context='server', debug=env.debug, log=None)
    api.finalize()
    api.Backend.ldap2.connect()
def api_connect(context=None):
    """
    Create environment, initialize api and connect to ldap2
    """
    env = Env()
    env._bootstrap()
    env._finalize_core(**dict(DEFAULT_CONFIG))

    # available contexts are 'server', 'ansible-freeipa' and 'cli_installer'
    if context is None:
        context = 'server'

    api.bootstrap(context=context, debug=env.debug, log=None)
    api.finalize()

    if api.env.in_server:
        backend = api.Backend.ldap2
    else:
        backend = api.Backend.rpcclient

    if not backend.isconnected():
        backend.connect()
Esempio n. 7
0
class API(ReadOnly):
    """
    Dynamic API object through which `Plugin` instances are accessed.
    """
    def __init__(self):
        super(API, self).__init__()
        self.__plugins = set()
        self.__plugins_by_key = {}
        self.__default_map = {}
        self.__instances = {}
        self.__next = {}
        self.__done = set()
        self.env = Env()

    @property
    def bases(self):
        raise NotImplementedError

    @property
    def packages(self):
        raise NotImplementedError

    def __len__(self):
        """
        Return the number of plugin namespaces in this API object.
        """
        return len(self.bases)

    def __iter__(self):
        """
        Iterate (in ascending order) through plugin namespace names.
        """
        return (base.__name__ for base in self.bases)

    def __contains__(self, name):
        """
        Return True if this API object contains plugin namespace ``name``.

        :param name: The plugin namespace name to test for membership.
        """
        return name in set(self)

    def __getitem__(self, name):
        """
        Return the plugin namespace corresponding to ``name``.

        :param name: The name of the plugin namespace you wish to retrieve.
        """
        if name in self:
            try:
                return getattr(self, name)
            except AttributeError:
                pass

        raise KeyError(name)

    def __call__(self):
        """
        Iterate (in ascending order by name) through plugin namespaces.
        """
        for name in self:
            try:
                yield getattr(self, name)
            except AttributeError:
                raise KeyError(name)

    def is_production_mode(self):
        """
        If the object has self.env.mode defined and that mode is
        production return True, otherwise return False.
        """
        return getattr(self.env, 'mode', None) == 'production'

    def __doing(self, name):
        if name in self.__done:
            raise Exception('%s.%s() already called' %
                            (self.__class__.__name__, name))
        self.__done.add(name)

    def __do_if_not_done(self, name):
        if name not in self.__done:
            getattr(self, name)()

    def isdone(self, name):
        return name in self.__done

    def bootstrap(self, parser=None, **overrides):
        """
        Initialize environment variables and logging.
        """
        self.__doing('bootstrap')
        self.log = log_mgr.get_logger(self)
        self.env._bootstrap(**overrides)
        self.env._finalize_core(**dict(DEFAULT_CONFIG))

        # Add the argument parser
        if not parser:
            parser = self.build_global_parser()
        self.parser = parser

        root_logger = logging.getLogger()

        # If logging has already been configured somewhere else (like in the
        # installer), don't add handlers or change levels:
        if root_logger.handlers or self.env.validate_api:
            return

        if self.env.debug:
            level = logging.DEBUG
        else:
            level = logging.INFO
        root_logger.setLevel(level)

        for attr in self.env:
            match = re.match(
                r'^log_logger_level_'
                r'(debug|info|warn|warning|error|critical|\d+)$', attr)
            if not match:
                continue

            level = ipa_log_manager.convert_log_level(match.group(1))

            value = getattr(self.env, attr)
            regexps = re.split('\s*,\s*', value)

            # Add the regexp, it maps to the configured level
            for regexp in regexps:
                root_logger.addFilter(ipa_log_manager.Filter(regexp, level))

        # Add stderr handler:
        level = logging.INFO
        if self.env.debug:
            level = logging.DEBUG
        else:
            if self.env.context == 'cli':
                if self.env.verbose > 0:
                    level = logging.INFO
                else:
                    level = logging.WARNING
        handler = logging.StreamHandler()
        handler.setLevel(level)
        handler.setFormatter(ipa_log_manager.Formatter(LOGGING_FORMAT_STDERR))
        root_logger.addHandler(handler)

        # Add file handler:
        if self.env.mode in ('dummy', 'unit_test'):
            return  # But not if in unit-test mode
        if self.env.log is None:
            return
        log_dir = path.dirname(self.env.log)
        if not path.isdir(log_dir):
            try:
                os.makedirs(log_dir)
            except OSError:
                logger.error('Could not create log_dir %r', log_dir)
                return

        level = logging.INFO
        if self.env.debug:
            level = logging.DEBUG
        try:
            handler = logging.FileHandler(self.env.log)
        except IOError as e:
            logger.error('Cannot open log file %r: %s', self.env.log, e)
            return
        handler.setLevel(level)
        handler.setFormatter(ipa_log_manager.Formatter(LOGGING_FORMAT_FILE))
        root_logger.addHandler(handler)

    def build_global_parser(self, parser=None, context=None):
        """
        Add global options to an optparse.OptionParser instance.
        """
        def config_file_callback(option, opt, value, parser):
            if not ipautil.file_exists(value):
                parser.error(
                    _("%(filename)s: file not found") % dict(filename=value))

            parser.values.conf = value

        if parser is None:
            parser = optparse.OptionParser(
                add_help_option=False,
                formatter=IPAHelpFormatter(),
                usage='%prog [global-options] COMMAND [command-options]',
                description='Manage an IPA domain',
                version=('VERSION: %s, API_VERSION: %s' %
                         (VERSION, API_VERSION)),
                epilog='\n'.join([
                    'See "ipa help topics" for available help topics.',
                    'See "ipa help <TOPIC>" for more information on a '
                    'specific topic.',
                    'See "ipa help commands" for the full list of commands.',
                    'See "ipa <COMMAND> --help" for more information on a '
                    'specific command.',
                ]))
            parser.disable_interspersed_args()
            parser.add_option("-h",
                              "--help",
                              action="help",
                              help='Show this help message and exit')

        parser.add_option(
            '-e',
            dest='env',
            metavar='KEY=VAL',
            action='append',
            help='Set environment variable KEY to VAL',
        )
        parser.add_option(
            '-c',
            dest='conf',
            metavar='FILE',
            action='callback',
            callback=config_file_callback,
            type='string',
            help='Load configuration from FILE.',
        )
        parser.add_option(
            '-d',
            '--debug',
            action='store_true',
            help='Produce full debuging output',
        )
        parser.add_option(
            '--delegate',
            action='store_true',
            help='Delegate the TGT to the IPA server',
        )
        parser.add_option(
            '-v',
            '--verbose',
            action='count',
            help=
            'Produce more verbose output. A second -v displays the XML-RPC request',
        )
        if context == 'cli':
            parser.add_option('-a',
                              '--prompt-all',
                              action='store_true',
                              help='Prompt for ALL values (even if optional)')
            parser.add_option('-n',
                              '--no-prompt',
                              action='store_false',
                              dest='interactive',
                              help='Prompt for NO values (even if required)')
            parser.add_option(
                '-f',
                '--no-fallback',
                action='store_false',
                dest='fallback',
                help='Only use the server configured in /etc/ipa/default.conf')

        return parser

    def bootstrap_with_global_options(self, parser=None, context=None):
        parser = self.build_global_parser(parser, context)
        (options, args) = parser.parse_args()
        overrides = {}
        if options.env is not None:
            assert type(options.env) is list
            for item in options.env:
                try:
                    (key, value) = item.split('=', 1)
                except ValueError:
                    # FIXME: this should raise an IPA exception with an
                    # error code.
                    # --Jason, 2008-10-31
                    pass
                overrides[str(key.strip())] = value.strip()
        for key in ('conf', 'debug', 'verbose', 'prompt_all', 'interactive',
                    'fallback', 'delegate'):
            value = getattr(options, key, None)
            if value is not None:
                overrides[key] = value
        if hasattr(options, 'prod'):
            overrides['webui_prod'] = options.prod
        if context is not None:
            overrides['context'] = context
        self.bootstrap(parser, **overrides)
        return (options, args)

    def load_plugins(self):
        """
        Load plugins from all standard locations.

        `API.bootstrap` will automatically be called if it hasn't been
        already.
        """
        self.__doing('load_plugins')
        self.__do_if_not_done('bootstrap')
        if self.env.mode in ('dummy', 'unit_test'):
            return
        for package in self.packages:
            self.add_package(package)

    # FIXME: This method has no unit test
    def add_package(self, package):
        """
        Add plugin modules from the ``package``.

        :param package: A package from which to add modules.
        """
        package_name = package.__name__
        package_file = package.__file__
        package_dir = path.dirname(path.abspath(package_file))

        parent = sys.modules[package_name.rpartition('.')[0]]
        parent_dir = path.dirname(path.abspath(parent.__file__))
        if parent_dir == package_dir:
            raise errors.PluginsPackageError(name=package_name,
                                             file=package_file)

        logger.debug("importing all plugin modules in %s...", package_name)
        modules = getattr(package, 'modules', find_modules_in_dir(package_dir))
        modules = ['.'.join((package_name, name)) for name in modules]

        for name in modules:
            logger.debug("importing plugin module %s", name)
            try:
                module = importlib.import_module(name)
            except errors.SkipPluginModule as e:
                logger.debug("skipping plugin module %s: %s", name, e.reason)
                continue
            except Exception as e:
                if self.env.startup_traceback:
                    logger.exception("could not load plugin module %s", name)
                raise

            try:
                self.add_module(module)
            except errors.PluginModuleError as e:
                logger.debug("%s", e)

    def add_module(self, module):
        """
        Add plugins from the ``module``.

        :param module: A module from which to add plugins.
        """
        try:
            register = module.register
        except AttributeError:
            pass
        else:
            if isinstance(register, Registry):
                for kwargs in register:
                    self.add_plugin(**kwargs)
                return

        raise errors.PluginModuleError(name=module.__name__)

    def add_plugin(self, plugin, override=False, no_fail=False):
        """
        Add the plugin ``plugin``.

        :param plugin: A subclass of `Plugin` to attempt to add.
        :param override: If true, override an already added plugin.
        """
        if not callable(plugin):
            raise TypeError('plugin must be callable; got %r' % plugin)

        # Find the base class or raise SubclassError:
        for base in plugin.bases:
            if issubclass(base, self.bases):
                break
        else:
            raise errors.PluginSubclassError(
                plugin=plugin,
                bases=self.bases,
            )

        # Check override:
        prev = self.__plugins_by_key.get(plugin.full_name)
        if prev:
            if not override:
                if no_fail:
                    return
                else:
                    # Must use override=True to override:
                    raise errors.PluginOverrideError(
                        base=base.__name__,
                        name=plugin.name,
                        plugin=plugin,
                    )

            self.__plugins.remove(prev)
            self.__next[plugin] = prev
        else:
            if override:
                if no_fail:
                    return
                else:
                    # There was nothing already registered to override:
                    raise errors.PluginMissingOverrideError(
                        base=base.__name__,
                        name=plugin.name,
                        plugin=plugin,
                    )

        # The plugin is okay, add to sub_d:
        self.__plugins.add(plugin)
        self.__plugins_by_key[plugin.full_name] = plugin

    def finalize(self):
        """
        Finalize the registration, instantiate the plugins.

        `API.bootstrap` will automatically be called if it hasn't been
        already.
        """
        self.__doing('finalize')
        self.__do_if_not_done('load_plugins')

        if self.env.env_confdir is not None:
            if self.env.env_confdir == self.env.confdir:
                logger.info("IPA_CONFDIR env sets confdir to '%s'.",
                            self.env.confdir)

        for plugin in self.__plugins:
            if not self.env.validate_api:
                if plugin.full_name not in DEFAULT_PLUGINS:
                    continue
            else:
                try:
                    default_version = self.__default_map[plugin.name]
                except KeyError:
                    pass
                else:
                    # Technicall plugin.version is not an API version. The
                    # APIVersion class can handle plugin versions. It's more
                    # lean than pkg_resource.parse_version().
                    version = ipautil.APIVersion(plugin.version)
                    default_version = ipautil.APIVersion(default_version)
                    if version < default_version:
                        continue
            self.__default_map[plugin.name] = plugin.version

        production_mode = self.is_production_mode()

        for base in self.bases:
            for plugin in self.__plugins:
                if not any(issubclass(b, base) for b in plugin.bases):
                    continue
                if not self.env.plugins_on_demand:
                    self._get(plugin)

            name = base.__name__
            if not production_mode:
                assert not hasattr(self, name)
            setattr(self, name, APINameSpace(self, base))

        for instance in six.itervalues(self.__instances):
            if not production_mode:
                assert instance.api is self
            if not self.env.plugins_on_demand:
                instance.ensure_finalized()
                if not production_mode:
                    assert islocked(instance)

        self.__finalized = True

        if not production_mode:
            lock(self)

    def _get(self, plugin):
        if not callable(plugin):
            raise TypeError('plugin must be callable; got %r' % plugin)
        if plugin not in self.__plugins:
            raise KeyError(plugin)

        try:
            instance = self.__instances[plugin]
        except KeyError:
            instance = self.__instances[plugin] = plugin(self)

        return instance

    def get_plugin_next(self, plugin):
        if not callable(plugin):
            raise TypeError('plugin must be callable; got %r' % plugin)

        return self.__next[plugin]
Esempio n. 8
0
def promote_check(installer):
    options = installer
    installer._enrollment_performed = False
    installer._top_dir = tempfile.mkdtemp("ipa")

    # check selinux status, http and DS ports, NTP conflicting services
    common_check(options.no_ntp)

    client_fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
    if not client_fstore.has_files():
        ensure_enrolled(installer)
    else:
        if (options.domain_name or options.server or options.realm_name
                or options.host_name or options.password or options.keytab):
            print("IPA client is already configured on this system, ignoring "
                  "the --domain, --server, --realm, --hostname, --password "
                  "and --keytab options.")

        # The NTP configuration can not be touched on pre-installed client:
        if options.no_ntp or options.ntp_servers or options.ntp_pool:
            raise ScriptError(
                "NTP configuration cannot be updated during promotion")

    sstore = sysrestore.StateFile(paths.SYSRESTORE)

    fstore = sysrestore.FileStore(paths.SYSRESTORE)

    env = Env()
    env._bootstrap(context='installer', confdir=paths.ETC_IPA, log=None)
    env._finalize_core(**dict(constants.DEFAULT_CONFIG))

    # 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()

    config = ReplicaConfig()
    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 = installer._top_dir
    config.basedn = api.env.basedn

    http_pkcs12_file = None
    http_pkcs12_info = None
    http_ca_cert = None
    dirsrv_pkcs12_file = None
    dirsrv_pkcs12_info = None
    dirsrv_ca_cert = None
    pkinit_pkcs12_file = None
    pkinit_pkcs12_info = None
    pkinit_ca_cert = None

    if options.http_cert_files:
        if options.http_pin is None:
            options.http_pin = installutils.read_password(
                "Enter Apache Server private key unlock",
                confirm=False,
                validate=False,
                retry=False)
            if options.http_pin is None:
                raise ScriptError(
                    "Apache Server private key unlock password required")
        http_pkcs12_file, http_pin, http_ca_cert = load_pkcs12(
            cert_files=options.http_cert_files,
            key_password=options.http_pin,
            key_nickname=options.http_cert_name,
            ca_cert_files=options.ca_cert_files,
            host_name=config.host_name)
        http_pkcs12_info = (http_pkcs12_file.name, http_pin)

    if options.dirsrv_cert_files:
        if options.dirsrv_pin is None:
            options.dirsrv_pin = installutils.read_password(
                "Enter Directory Server private key unlock",
                confirm=False,
                validate=False,
                retry=False)
            if options.dirsrv_pin is None:
                raise ScriptError(
                    "Directory Server private key unlock password required")
        dirsrv_pkcs12_file, dirsrv_pin, dirsrv_ca_cert = load_pkcs12(
            cert_files=options.dirsrv_cert_files,
            key_password=options.dirsrv_pin,
            key_nickname=options.dirsrv_cert_name,
            ca_cert_files=options.ca_cert_files,
            host_name=config.host_name)
        dirsrv_pkcs12_info = (dirsrv_pkcs12_file.name, dirsrv_pin)

    if options.pkinit_cert_files:
        if options.pkinit_pin is None:
            options.pkinit_pin = installutils.read_password(
                "Enter Kerberos KDC private key unlock",
                confirm=False,
                validate=False,
                retry=False)
            if options.pkinit_pin is None:
                raise ScriptError(
                    "Kerberos KDC private key unlock password required")
        pkinit_pkcs12_file, pkinit_pin, pkinit_ca_cert = load_pkcs12(
            cert_files=options.pkinit_cert_files,
            key_password=options.pkinit_pin,
            key_nickname=options.pkinit_cert_name,
            ca_cert_files=options.ca_cert_files,
            realm_name=config.realm_name)
        pkinit_pkcs12_info = (pkinit_pkcs12_file.name, pkinit_pin)

    if (options.http_cert_files and options.dirsrv_cert_files
            and http_ca_cert != dirsrv_ca_cert):
        raise RuntimeError("Apache Server SSL certificate and Directory "
                           "Server SSL certificate are not signed by the same"
                           " CA certificate")

    if (options.http_cert_files and options.pkinit_cert_files
            and http_ca_cert != pkinit_ca_cert):
        raise RuntimeError("Apache Server SSL certificate and PKINIT KDC "
                           "certificate are not signed by the same CA "
                           "certificate")

    installutils.verify_fqdn(config.host_name, options.no_host_dns)
    installutils.verify_fqdn(config.master_host_name, options.no_host_dns)

    ccache = os.environ['KRB5CCNAME']
    kinit_keytab('host/{env.host}@{env.realm}'.format(env=api.env),
                 paths.KRB5_KEYTAB, ccache)

    cafile = paths.IPA_CA_CRT
    if not os.path.isfile(cafile):
        raise RuntimeError("CA cert file is not available! Please reinstall"
                           "the client and try again.")

    ldapuri = 'ldaps://%s' % ipautil.format_netloc(config.master_host_name)
    xmlrpc_uri = 'https://{}/ipa/xml'.format(
        ipautil.format_netloc(config.master_host_name))
    remote_api = create_api(mode=None)
    remote_api.bootstrap(in_server=True,
                         context='installer',
                         confdir=paths.ETC_IPA,
                         ldap_uri=ldapuri,
                         xmlrpc_uri=xmlrpc_uri)
    remote_api.finalize()
    installer._remote_api = remote_api

    with rpc_client(remote_api) as client:
        check_remote_version(client, parse_version(api.env.version))
        check_remote_fips_mode(client, api.env.fips_mode)

    conn = remote_api.Backend.ldap2
    replman = None
    try:
        # Try out authentication
        conn.connect(ccache=ccache)
        replman = ReplicationManager(config.realm_name,
                                     config.master_host_name, None)

        promotion_check_ipa_domain(conn, remote_api.env.basedn)

        # Make sure that domain fulfills minimal domain level
        # requirement
        domain_level = current_domain_level(remote_api)
        check_domain_level_is_supported(domain_level)
        if domain_level < constants.MIN_DOMAIN_LEVEL:
            raise RuntimeError(
                "Cannot promote this client to a replica. The domain level "
                "must be raised to {mindomainlevel} before the replica can be "
                "installed".format(mindomainlevel=constants.MIN_DOMAIN_LEVEL))

        # Check authorization
        result = remote_api.Command['hostgroup_find'](
            cn=u'ipaservers', host=[unicode(api.env.host)])['result']
        add_to_ipaservers = not result

        if add_to_ipaservers:
            if options.password and not options.admin_password:
                raise errors.ACIError(info="Not authorized")

            if installer._ccache is None:
                del os.environ['KRB5CCNAME']
            else:
                os.environ['KRB5CCNAME'] = installer._ccache

            try:
                installutils.check_creds(options, config.realm_name)
                installer._ccache = os.environ.get('KRB5CCNAME')
            finally:
                os.environ['KRB5CCNAME'] = ccache

            conn.disconnect()
            conn.connect(ccache=installer._ccache)

            try:
                result = remote_api.Command['hostgroup_show'](
                    u'ipaservers', all=True, rights=True)['result']

                if 'w' not in result['attributelevelrights']['member']:
                    raise errors.ACIError(info="Not authorized")
            finally:
                conn.disconnect()
                conn.connect(ccache=ccache)

        # Check that we don't already have a replication agreement
        if replman.get_replication_agreement(config.host_name):
            msg = ("A replication agreement for this host already exists. "
                   "It needs to be removed.\n"
                   "Run this command:\n"
                   "    %% ipa-replica-manage del {host} --force".format(
                       host=config.host_name))
            raise ScriptError(msg, rval=3)

        # Detect if the other master can handle replication managers
        # cn=replication managers,cn=sysaccounts,cn=etc,$SUFFIX
        dn = DN(('cn', 'replication managers'), ('cn', 'sysaccounts'),
                ('cn', 'etc'), ipautil.realm_to_suffix(config.realm_name))
        try:
            conn.get_entry(dn)
        except errors.NotFound:
            msg = ("The Replication Managers group is not available in "
                   "the domain. Replica promotion requires the use of "
                   "Replication Managers to be able to replicate data. "
                   "Upgrade the peer master or use the ipa-replica-prepare "
                   "command on the master and use a prep file to install "
                   "this replica.")
            logger.error("%s", msg)
            raise ScriptError(rval=3)

        dns_masters = remote_api.Object['dnsrecord'].get_dns_masters()
        if dns_masters:
            if not options.no_host_dns:
                logger.debug('Check forward/reverse DNS resolution')
                resolution_ok = (
                    check_dns_resolution(config.master_host_name, dns_masters)
                    and check_dns_resolution(config.host_name, dns_masters))
                if not resolution_ok and installer.interactive:
                    if not ipautil.user_input("Continue?", False):
                        raise ScriptError(rval=0)
        else:
            logger.debug('No IPA DNS servers, '
                         'skipping forward/reverse resolution check')

        entry_attrs = conn.get_ipa_config()
        subject_base = entry_attrs.get('ipacertificatesubjectbase', [None])[0]
        if subject_base is not None:
            config.subject_base = DN(subject_base)

        # Find if any server has a CA
        ca_host = service.find_providing_server('CA', conn,
                                                config.ca_host_name)
        if ca_host is not None:
            config.ca_host_name = ca_host
            ca_enabled = True
            if options.dirsrv_cert_files:
                logger.error("Certificates could not be provided when "
                             "CA is present on some master.")
                raise ScriptError(rval=3)
        else:
            if options.setup_ca:
                logger.error("The remote master does not have a CA "
                             "installed, can't set up CA")
                raise ScriptError(rval=3)
            ca_enabled = False
            if not options.dirsrv_cert_files:
                logger.error("Cannot issue certificates: a CA is not "
                             "installed. Use the --http-cert-file, "
                             "--dirsrv-cert-file options to provide "
                             "custom certificates.")
                raise ScriptError(rval=3)

        kra_host = service.find_providing_server('KRA', conn,
                                                 config.kra_host_name)
        if kra_host is not None:
            config.kra_host_name = kra_host
            kra_enabled = True
        else:
            if options.setup_kra:
                logger.error("There is no KRA server in the domain, "
                             "can't setup a KRA clone")
                raise ScriptError(rval=3)
            kra_enabled = False

        if ca_enabled:
            options.realm_name = config.realm_name
            options.host_name = config.host_name
            ca.install_check(False, config, options)

        if kra_enabled:
            try:
                kra.install_check(remote_api, config, options)
            except RuntimeError as e:
                raise ScriptError(e)

        if options.setup_dns:
            dns.install_check(False, remote_api, True, options,
                              config.host_name)
            config.ips = dns.ip_addresses
        else:
            config.ips = installutils.get_server_ip_address(
                config.host_name, not installer.interactive, False,
                options.ip_addresses)

            # check addresses here, dns module is doing own check
            no_matching_interface_for_ip_address_warning(config.ips)

        if options.setup_adtrust:
            adtrust.install_check(False, options, remote_api)

    except errors.ACIError:
        logger.debug("%s", traceback.format_exc())
        raise ScriptError("\nInsufficient privileges to promote the server."
                          "\nPossible issues:"
                          "\n- A user has insufficient privileges"
                          "\n- This client has insufficient privileges "
                          "to become an IPA replica")
    except errors.LDAPError:
        logger.debug("%s", traceback.format_exc())
        raise ScriptError("\nUnable to connect to LDAP server %s" %
                          config.master_host_name)
    finally:
        if replman and replman.conn:
            replman.conn.unbind()
        if conn.isconnected():
            conn.disconnect()

    # check connection
    if not options.skip_conncheck:
        if add_to_ipaservers:
            # use user's credentials when the server host is not ipaservers
            if installer._ccache is None:
                del os.environ['KRB5CCNAME']
            else:
                os.environ['KRB5CCNAME'] = installer._ccache

        try:
            replica_conn_check(config.master_host_name,
                               config.host_name,
                               config.realm_name,
                               options.setup_ca,
                               389,
                               options.admin_password,
                               principal=options.principal,
                               ca_cert_file=cafile)
        finally:
            if add_to_ipaservers:
                os.environ['KRB5CCNAME'] = ccache

    installer._ca_enabled = ca_enabled
    installer._kra_enabled = kra_enabled
    installer._ca_file = cafile
    installer._fstore = fstore
    installer._sstore = sstore
    installer._config = config
    installer._add_to_ipaservers = add_to_ipaservers
    installer._dirsrv_pkcs12_file = dirsrv_pkcs12_file
    installer._dirsrv_pkcs12_info = dirsrv_pkcs12_info
    installer._http_pkcs12_file = http_pkcs12_file
    installer._http_pkcs12_info = http_pkcs12_info
    installer._pkinit_pkcs12_file = pkinit_pkcs12_file
    installer._pkinit_pkcs12_info = pkinit_pkcs12_info
Esempio n. 9
0
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

"""
WSGI appliction for IPA server.
"""
from ipalib import api
from ipalib.config import Env
from ipalib.constants import DEFAULT_CONFIG

# Determine what debug level is configured. We can only do this
# by reading in the configuration file(s). The server always reads
# default.conf and will also read in `context'.conf.
env = Env()
env._bootstrap(context='server', log=None)
env._finalize_core(**dict(DEFAULT_CONFIG))

# Initialize the API with the proper debug level
api.bootstrap(context='server', debug=env.debug, log=None)
try:
    api.finalize()
except Exception as e:
    api.log.error('Failed to start IPA: %s' % e)
else:
    api.log.info('*** PROCESS START ***')

    # This is the WSGI callable:
    def application(environ, start_response):
        if not environ['wsgi.multithread']:
Esempio n. 10
0
 def __init__(self):
     super(API, self).__init__()
     self.__plugins = {}
     self.__done = set()
     self.env = Env()
Esempio n. 11
0
class API(ReadOnly):
    """
    Dynamic API object through which `Plugin` instances are accessed.
    """

    register = Registrar()

    def __init__(self):
        super(API, self).__init__()
        self.__plugins = {}
        self.__done = set()
        self.env = Env()

    @property
    def bases(self):
        raise NotImplementedError

    @property
    def modules(self):
        raise NotImplementedError

    def __len__(self):
        """
        Return the number of plugin namespaces in this API object.
        """
        return len(self.bases)

    def __iter__(self):
        """
        Iterate (in ascending order) through plugin namespace names.
        """
        return (base.__name__ for base in self.bases)

    def __contains__(self, name):
        """
        Return True if this API object contains plugin namespace ``name``.

        :param name: The plugin namespace name to test for membership.
        """
        return name in set(self)

    def __getitem__(self, name):
        """
        Return the plugin namespace corresponding to ``name``.

        :param name: The name of the plugin namespace you wish to retrieve.
        """
        if name in self:
            try:
                return getattr(self, name)
            except AttributeError:
                pass

        raise KeyError(name)

    def __call__(self):
        """
        Iterate (in ascending order by name) through plugin namespaces.
        """
        for name in self:
            try:
                yield getattr(self, name)
            except AttributeError:
                raise KeyError(name)

    def is_production_mode(self):
        """
        If the object has self.env.mode defined and that mode is
        production return True, otherwise return False.
        """
        return getattr(self.env, 'mode', None) == 'production'

    def __doing(self, name):
        if name in self.__done:
            raise Exception(
                '%s.%s() already called' % (self.__class__.__name__, name)
            )
        self.__done.add(name)

    def __do_if_not_done(self, name):
        if name not in self.__done:
            getattr(self, name)()

    def isdone(self, name):
        return name in self.__done

    def bootstrap(self, parser=None, **overrides):
        """
        Initialize environment variables and logging.
        """
        self.__doing('bootstrap')
        self.log_mgr = log_mgr
        log = log_mgr.root_logger
        self.log = log
        self.env._bootstrap(**overrides)
        self.env._finalize_core(**dict(DEFAULT_CONFIG))

        # Add the argument parser
        if not parser:
            parser = self.build_global_parser()
        self.parser = parser

        # If logging has already been configured somewhere else (like in the
        # installer), don't add handlers or change levels:
        if log_mgr.configure_state != 'default' or self.env.validate_api:
            return

        log_mgr.default_level = 'info'
        log_mgr.configure_from_env(self.env, configure_state='api')
        # Add stderr handler:
        level = 'info'
        if self.env.debug:
            level = 'debug'
        else:
            if self.env.context == 'cli':
                if self.env.verbose > 0:
                    level = 'info'
                else:
                    level = 'warning'

        if 'console' in log_mgr.handlers:
            log_mgr.remove_handler('console')
        log_mgr.create_log_handlers([dict(name='console',
                                          stream=sys.stderr,
                                          level=level,
                                          format=LOGGING_FORMAT_STDERR)])

        # Add file handler:
        if self.env.mode in ('dummy', 'unit_test'):
            return  # But not if in unit-test mode
        if self.env.log is None:
            return
        log_dir = path.dirname(self.env.log)
        if not path.isdir(log_dir):
            try:
                os.makedirs(log_dir)
            except OSError:
                log.error('Could not create log_dir %r', log_dir)
                return

        level = 'info'
        if self.env.debug:
            level = 'debug'
        try:
            log_mgr.create_log_handlers([dict(name='file',
                                              filename=self.env.log,
                                              level=level,
                                              format=LOGGING_FORMAT_FILE)])
        except IOError as e:
            log.error('Cannot open log file %r: %s', self.env.log, e)
            return

    def build_global_parser(self, parser=None, context=None):
        """
        Add global options to an optparse.OptionParser instance.
        """
        if parser is None:
            parser = optparse.OptionParser(
                add_help_option=False,
                formatter=IPAHelpFormatter(),
                usage='%prog [global-options] COMMAND [command-options]',
                description='Manage an IPA domain',
                version=('VERSION: %s, API_VERSION: %s'
                                % (VERSION, API_VERSION)),
                epilog='\n'.join([
                    'See "ipa help topics" for available help topics.',
                    'See "ipa help <TOPIC>" for more information on a '
                        'specific topic.',
                    'See "ipa help commands" for the full list of commands.',
                    'See "ipa <COMMAND> --help" for more information on a '
                        'specific command.',
                ]))
            parser.disable_interspersed_args()
            parser.add_option("-h", "--help", action="help",
                help='Show this help message and exit')

        parser.add_option('-e', dest='env', metavar='KEY=VAL', action='append',
            help='Set environment variable KEY to VAL',
        )
        parser.add_option('-c', dest='conf', metavar='FILE',
            help='Load configuration from FILE',
        )
        parser.add_option('-d', '--debug', action='store_true',
            help='Produce full debuging output',
        )
        parser.add_option('--delegate', action='store_true',
            help='Delegate the TGT to the IPA server',
        )
        parser.add_option('-v', '--verbose', action='count',
            help='Produce more verbose output. A second -v displays the XML-RPC request',
        )
        if context == 'cli':
            parser.add_option('-a', '--prompt-all', action='store_true',
                help='Prompt for ALL values (even if optional)'
            )
            parser.add_option('-n', '--no-prompt', action='store_false',
                dest='interactive',
                help='Prompt for NO values (even if required)'
            )
            parser.add_option('-f', '--no-fallback', action='store_false',
                dest='fallback',
                help='Only use the server configured in /etc/ipa/default.conf'
            )

        return parser

    def bootstrap_with_global_options(self, parser=None, context=None):
        parser = self.build_global_parser(parser, context)
        (options, args) = parser.parse_args()
        overrides = {}
        if options.env is not None:
            assert type(options.env) is list
            for item in options.env:
                try:
                    (key, value) = item.split('=', 1)
                except ValueError:
                    # FIXME: this should raise an IPA exception with an
                    # error code.
                    # --Jason, 2008-10-31
                    pass
                overrides[str(key.strip())] = value.strip()
        for key in ('conf', 'debug', 'verbose', 'prompt_all', 'interactive',
            'fallback', 'delegate'):
            value = getattr(options, key, None)
            if value is not None:
                overrides[key] = value
        if hasattr(options, 'prod'):
            overrides['webui_prod'] = options.prod
        if context is not None:
            overrides['context'] = context
        self.bootstrap(parser, **overrides)
        return (options, args)

    def load_plugins(self):
        """
        Load plugins from all standard locations.

        `API.bootstrap` will automatically be called if it hasn't been
        already.
        """
        self.__doing('load_plugins')
        self.__do_if_not_done('bootstrap')
        if self.env.mode in ('dummy', 'unit_test'):
            return
        for module in self.modules:
            self.import_plugins(module)

    # FIXME: This method has no unit test
    def import_plugins(self, module):
        """
        Import plugins from ``module``.

        :param module: Name of the module to import. This might be a wildcard
                       in the form ```package.*``` in which case all modules
                       from the given package are loaded.
        """
        if module.endswith('.*'):
            subpackage = module[:-2]
            try:
                plugins = importlib.import_module(subpackage)
            except ImportError as e:
                self.log.error("cannot import plugins sub-package %s: %s",
                               subpackage, e)
                raise
            package, dot, part = subpackage.rpartition('.')
            parent = sys.modules[package]

            parent_dir = path.dirname(path.abspath(parent.__file__))
            plugins_dir = path.dirname(path.abspath(plugins.__file__))
            if parent_dir == plugins_dir:
                raise errors.PluginsPackageError(
                    name=subpackage, file=plugins.__file__
                )

            self.log.debug("importing all plugin modules in %s...", subpackage)
            modules = find_modules_in_dir(plugins_dir)
            modules = ['.'.join((subpackage, name)) for name in modules]
        else:
            modules = [module]

        for name in modules:
            self.log.debug("importing plugin module %s", name)
            try:
                module = importlib.import_module(name)
            except errors.SkipPluginModule as e:
                self.log.debug("skipping plugin module %s: %s", name, e.reason)
            except Exception as e:
                if self.env.startup_traceback:
                    import traceback
                    self.log.error("could not load plugin module %s\n%s", name,
                                   traceback.format_exc())
                raise
            else:
                self.add_module(module)

    def add_module(self, module):
        """
        Add plugins from the ``module``.

        :param module: A module from which to add plugins.
        """
        for name in dir(module):
            klass = getattr(module, name)
            if not inspect.isclass(klass):
                continue
            if klass not in self.register:
                continue
            kwargs = self.register[klass]
            self.add_plugin(klass, **kwargs)

    def add_plugin(self, klass, override=False):
        """
        Add the plugin ``klass``.

        :param klass: A subclass of `Plugin` to attempt to add.
        :param override: If true, override an already added plugin.
        """
        if not inspect.isclass(klass):
            raise TypeError('plugin must be a class; got %r' % klass)

        # Find the base class or raise SubclassError:
        found = False
        for base in self.bases:
            if not issubclass(klass, base):
                continue

            sub_d = self.__plugins.setdefault(base, {})
            found = True

            if sub_d.get(klass.__name__) is klass:
                continue

            # Check override:
            if klass.__name__ in sub_d:
                if not override:
                    # Must use override=True to override:
                    raise errors.PluginOverrideError(
                        base=base.__name__,
                        name=klass.__name__,
                        plugin=klass,
                    )
            else:
                if override:
                    # There was nothing already registered to override:
                    raise errors.PluginMissingOverrideError(
                        base=base.__name__,
                        name=klass.__name__,
                        plugin=klass,
                    )

            # The plugin is okay, add to sub_d:
            sub_d[klass.__name__] = klass

        if not found:
            raise errors.PluginSubclassError(
                plugin=klass,
                bases=self.bases,
            )

    def finalize(self):
        """
        Finalize the registration, instantiate the plugins.

        `API.bootstrap` will automatically be called if it hasn't been
        already.
        """
        self.__doing('finalize')
        self.__do_if_not_done('load_plugins')

        production_mode = self.is_production_mode()
        plugins = {}
        plugin_info = {}

        for base in self.bases:
            name = base.__name__
            sub_d = self.__plugins.get(base, {})

            members = []
            for klass in sub_d.values():
                try:
                    instance = plugins[klass]
                except KeyError:
                    instance = plugins[klass] = klass(self)
                members.append(instance)
                plugin_info.setdefault(
                    '%s.%s' % (klass.__module__, klass.__name__),
                    []).append(name)

            if not production_mode:
                assert not hasattr(self, name)
            setattr(self, name, NameSpace(members))

        for klass, instance in plugins.items():
            if not production_mode:
                assert instance.api is self
            if klass.finalize_early or not self.env.plugins_on_demand:
                instance.ensure_finalized()
                if not production_mode:
                    assert islocked(instance)

        self.__finalized = True
        self.plugins = tuple((k, tuple(v)) for k, v in plugin_info.items())

        if not production_mode:
            lock(self)
Esempio n. 12
0
def promote_check(installer):
    options = installer
    installer._enrollment_performed = False
    installer._top_dir = tempfile.mkdtemp("ipa")

    # check selinux status, http and DS ports, NTP conflicting services
    common_check(options.no_ntp)

    client_fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
    if not client_fstore.has_files():
        # One-step replica installation
        if options.password and options.admin_password:
            raise ScriptError("--password and --admin-password options are "
                              "mutually exclusive")
        ensure_enrolled(installer)
    else:
        if (options.domain_name or options.server or options.realm_name or
                options.host_name or options.password or options.keytab):
            print("IPA client is already configured on this system, ignoring "
                  "the --domain, --server, --realm, --hostname, --password "
                  "and --keytab options.")

        # The NTP configuration can not be touched on pre-installed client:
        if options.no_ntp or options.ntp_servers or options.ntp_pool:
                raise ScriptError(
                    "NTP configuration cannot be updated during promotion")

    sstore = sysrestore.StateFile(paths.SYSRESTORE)

    fstore = sysrestore.FileStore(paths.SYSRESTORE)

    env = Env()
    env._bootstrap(context='installer', confdir=paths.ETC_IPA, log=None)
    env._finalize_core(**dict(constants.DEFAULT_CONFIG))

    # 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=ipaldap.realm_to_ldapi_uri(env.realm),
                  xmlrpc_uri=xmlrpc_uri)
    # pylint: enable=no-member
    api.finalize()

    config = ReplicaConfig()
    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
    if not api.env.ca_host or api.env.ca_host == api.env.host:
        # ca_host has not been configured explicitly, prefer source master
        config.ca_host_name = api.env.server
    else:
        # default to ca_host from IPA config
        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 = installer._top_dir
    config.basedn = api.env.basedn
    config.hidden_replica = options.hidden_replica

    http_pkcs12_file = None
    http_pkcs12_info = None
    http_ca_cert = None
    dirsrv_pkcs12_file = None
    dirsrv_pkcs12_info = None
    dirsrv_ca_cert = None
    pkinit_pkcs12_file = None
    pkinit_pkcs12_info = None
    pkinit_ca_cert = None

    if options.http_cert_files:
        if options.http_pin is None:
            options.http_pin = installutils.read_password(
                "Enter Apache Server private key unlock",
                confirm=False, validate=False, retry=False)
            if options.http_pin is None:
                raise ScriptError(
                    "Apache Server private key unlock password required")
        http_pkcs12_file, http_pin, http_ca_cert = load_pkcs12(
            cert_files=options.http_cert_files,
            key_password=options.http_pin,
            key_nickname=options.http_cert_name,
            ca_cert_files=options.ca_cert_files,
            host_name=config.host_name)
        http_pkcs12_info = (http_pkcs12_file.name, http_pin)

    if options.dirsrv_cert_files:
        if options.dirsrv_pin is None:
            options.dirsrv_pin = installutils.read_password(
                "Enter Directory Server private key unlock",
                confirm=False, validate=False, retry=False)
            if options.dirsrv_pin is None:
                raise ScriptError(
                    "Directory Server private key unlock password required")
        dirsrv_pkcs12_file, dirsrv_pin, dirsrv_ca_cert = load_pkcs12(
            cert_files=options.dirsrv_cert_files,
            key_password=options.dirsrv_pin,
            key_nickname=options.dirsrv_cert_name,
            ca_cert_files=options.ca_cert_files,
            host_name=config.host_name)
        dirsrv_pkcs12_info = (dirsrv_pkcs12_file.name, dirsrv_pin)

    if options.pkinit_cert_files:
        if options.pkinit_pin is None:
            options.pkinit_pin = installutils.read_password(
                "Enter Kerberos KDC private key unlock",
                confirm=False, validate=False, retry=False)
            if options.pkinit_pin is None:
                raise ScriptError(
                    "Kerberos KDC private key unlock password required")
        pkinit_pkcs12_file, pkinit_pin, pkinit_ca_cert = load_pkcs12(
            cert_files=options.pkinit_cert_files,
            key_password=options.pkinit_pin,
            key_nickname=options.pkinit_cert_name,
            ca_cert_files=options.ca_cert_files,
            realm_name=config.realm_name)
        pkinit_pkcs12_info = (pkinit_pkcs12_file.name, pkinit_pin)

    if (options.http_cert_files and options.dirsrv_cert_files and
            http_ca_cert != dirsrv_ca_cert):
        raise RuntimeError("Apache Server SSL certificate and Directory "
                           "Server SSL certificate are not signed by the same"
                           " CA certificate")

    if (options.http_cert_files and
            options.pkinit_cert_files and
            http_ca_cert != pkinit_ca_cert):
        raise RuntimeError("Apache Server SSL certificate and PKINIT KDC "
                           "certificate are not signed by the same CA "
                           "certificate")

    installutils.verify_fqdn(config.host_name, options.no_host_dns)
    installutils.verify_fqdn(config.master_host_name, options.no_host_dns)

    ccache = os.environ['KRB5CCNAME']
    kinit_keytab('host/{env.host}@{env.realm}'.format(env=api.env),
                 paths.KRB5_KEYTAB,
                 ccache)

    cafile = paths.IPA_CA_CRT
    if not os.path.isfile(cafile):
        raise RuntimeError("CA cert file is not available! Please reinstall"
                           "the client and try again.")

    ldapuri = 'ldaps://%s' % ipautil.format_netloc(config.master_host_name)
    xmlrpc_uri = 'https://{}/ipa/xml'.format(
        ipautil.format_netloc(config.master_host_name))
    remote_api = create_api(mode=None)
    remote_api.bootstrap(in_server=True,
                         context='installer',
                         confdir=paths.ETC_IPA,
                         ldap_uri=ldapuri,
                         xmlrpc_uri=xmlrpc_uri)
    remote_api.finalize()
    installer._remote_api = remote_api

    with rpc_client(remote_api) as client:
        check_remote_version(client, parse_version(api.env.version))
        check_remote_fips_mode(client, api.env.fips_mode)

    conn = remote_api.Backend.ldap2
    replman = None
    try:
        # Try out authentication
        conn.connect(ccache=ccache)
        replman = ReplicationManager(config.realm_name,
                                     config.master_host_name, None)

        promotion_check_ipa_domain(conn, remote_api.env.basedn)

        # Make sure that domain fulfills minimal domain level
        # requirement
        domain_level = current_domain_level(remote_api)
        check_domain_level_is_supported(domain_level)
        if domain_level < constants.MIN_DOMAIN_LEVEL:
            raise RuntimeError(
                "Cannot promote this client to a replica. The domain level "
                "must be raised to {mindomainlevel} before the replica can be "
                "installed".format(
                    mindomainlevel=constants.MIN_DOMAIN_LEVEL
                ))

        # Check authorization
        result = remote_api.Command['hostgroup_find'](
            cn=u'ipaservers',
            host=[unicode(api.env.host)]
        )['result']
        add_to_ipaservers = not result

        if add_to_ipaservers:
            if options.password and not options.admin_password:
                raise errors.ACIError(info="Not authorized")

            if installer._ccache is None:
                del os.environ['KRB5CCNAME']
            else:
                os.environ['KRB5CCNAME'] = installer._ccache

            try:
                installutils.check_creds(options, config.realm_name)
                installer._ccache = os.environ.get('KRB5CCNAME')
            finally:
                os.environ['KRB5CCNAME'] = ccache

            conn.disconnect()
            conn.connect(ccache=installer._ccache)

            try:
                result = remote_api.Command['hostgroup_show'](
                    u'ipaservers',
                    all=True,
                    rights=True
                )['result']

                if 'w' not in result['attributelevelrights']['member']:
                    raise errors.ACIError(info="Not authorized")
            finally:
                conn.disconnect()
                conn.connect(ccache=ccache)


        # Check that we don't already have a replication agreement
        if replman.get_replication_agreement(config.host_name):
            msg = ("A replication agreement for this host already exists. "
                   "It needs to be removed.\n"
                   "Run this command:\n"
                   "    %% ipa-replica-manage del {host} --force"
                   .format(host=config.host_name))
            raise ScriptError(msg, rval=3)

        # Detect if the other master can handle replication managers
        # cn=replication managers,cn=sysaccounts,cn=etc,$SUFFIX
        dn = DN(('cn', 'replication managers'), ('cn', 'sysaccounts'),
                ('cn', 'etc'), ipautil.realm_to_suffix(config.realm_name))
        try:
            conn.get_entry(dn)
        except errors.NotFound:
            msg = ("The Replication Managers group is not available in "
                   "the domain. Replica promotion requires the use of "
                   "Replication Managers to be able to replicate data. "
                   "Upgrade the peer master or use the ipa-replica-prepare "
                   "command on the master and use a prep file to install "
                   "this replica.")
            logger.error("%s", msg)
            raise ScriptError(rval=3)

        dns_masters = remote_api.Object['dnsrecord'].get_dns_masters()
        if dns_masters:
            if not options.no_host_dns:
                logger.debug('Check forward/reverse DNS resolution')
                resolution_ok = (
                    check_dns_resolution(config.master_host_name,
                                         dns_masters) and
                    check_dns_resolution(config.host_name, dns_masters))
                if not resolution_ok and installer.interactive:
                    if not ipautil.user_input("Continue?", False):
                        raise ScriptError(rval=0)
        else:
            logger.debug('No IPA DNS servers, '
                         'skipping forward/reverse resolution check')

        entry_attrs = conn.get_ipa_config()
        subject_base = entry_attrs.get('ipacertificatesubjectbase', [None])[0]
        if subject_base is not None:
            config.subject_base = DN(subject_base)

        # Find any server with a CA
        ca_host = find_providing_server(
            'CA', conn, [config.ca_host_name]
        )
        if ca_host is not None:
            config.ca_host_name = ca_host
            ca_enabled = True
            if options.dirsrv_cert_files:
                logger.error("Certificates could not be provided when "
                             "CA is present on some master.")
                raise ScriptError(rval=3)
        else:
            if options.setup_ca:
                logger.error("The remote master does not have a CA "
                             "installed, can't set up CA")
                raise ScriptError(rval=3)
            ca_enabled = False
            if not options.dirsrv_cert_files:
                logger.error("Cannot issue certificates: a CA is not "
                             "installed. Use the --http-cert-file, "
                             "--dirsrv-cert-file options to provide "
                             "custom certificates.")
                raise ScriptError(rval=3)

        # Find any server with a KRA
        kra_host = find_providing_server(
            'KRA', conn, [config.kra_host_name]
        )
        if kra_host is not None:
            config.kra_host_name = kra_host
            kra_enabled = True
        else:
            if options.setup_kra:
                logger.error("There is no active KRA server in the domain, "
                             "can't setup a KRA clone")
                raise ScriptError(rval=3)
            kra_enabled = False

        if ca_enabled:
            options.realm_name = config.realm_name
            options.host_name = config.host_name
            ca.install_check(False, config, options)

        if kra_enabled:
            try:
                kra.install_check(remote_api, config, options)
            except RuntimeError as e:
                raise ScriptError(e)

        if options.setup_dns:
            dns.install_check(False, remote_api, True, options,
                              config.host_name)
            config.ips = dns.ip_addresses
        else:
            config.ips = installutils.get_server_ip_address(
                config.host_name, not installer.interactive,
                False, options.ip_addresses)

            # check addresses here, dns module is doing own check
            no_matching_interface_for_ip_address_warning(config.ips)

        if options.setup_adtrust:
            adtrust.install_check(False, options, remote_api)

    except errors.ACIError:
        logger.debug("%s", traceback.format_exc())
        raise ScriptError("\nInsufficient privileges to promote the server."
                          "\nPossible issues:"
                          "\n- A user has insufficient privileges"
                          "\n- This client has insufficient privileges "
                          "to become an IPA replica")
    except errors.LDAPError:
        logger.debug("%s", traceback.format_exc())
        raise ScriptError("\nUnable to connect to LDAP server %s" %
                          config.master_host_name)
    finally:
        if replman and replman.conn:
            replman.conn.unbind()
        if conn.isconnected():
            conn.disconnect()

    # check connection
    if not options.skip_conncheck:
        if add_to_ipaservers:
            # use user's credentials when the server host is not ipaservers
            if installer._ccache is None:
                del os.environ['KRB5CCNAME']
            else:
                os.environ['KRB5CCNAME'] = installer._ccache

        try:
            replica_conn_check(
                config.master_host_name, config.host_name, config.realm_name,
                options.setup_ca, 389,
                options.admin_password, principal=options.principal,
                ca_cert_file=cafile)
        finally:
            if add_to_ipaservers:
                os.environ['KRB5CCNAME'] = ccache

    installer._ca_enabled = ca_enabled
    installer._kra_enabled = kra_enabled
    installer._ca_file = cafile
    installer._fstore = fstore
    installer._sstore = sstore
    installer._config = config
    installer._add_to_ipaservers = add_to_ipaservers
    installer._dirsrv_pkcs12_file = dirsrv_pkcs12_file
    installer._dirsrv_pkcs12_info = dirsrv_pkcs12_info
    installer._http_pkcs12_file = http_pkcs12_file
    installer._http_pkcs12_info = http_pkcs12_info
    installer._pkinit_pkcs12_file = pkinit_pkcs12_file
    installer._pkinit_pkcs12_info = pkinit_pkcs12_info
Esempio n. 13
0
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

"""
WSGI appliction for IPA server.
"""
from ipaplatform.paths import paths
from ipalib import api
from ipalib.config import Env
from ipalib.constants import DEFAULT_CONFIG

# Determine what debug level is configured. We can only do this
# by reading in the configuration file(s). The server always reads
# default.conf and will also read in `context'.conf.
env = Env()
env._bootstrap(context='server', log=None, confdir=paths.ETC_IPA)
env._finalize_core(**dict(DEFAULT_CONFIG))

# Initialize the API with the proper debug level
api.bootstrap(context='server', confdir=paths.ETC_IPA,
              debug=env.debug, log=None)
try:
    api.finalize()
except Exception as e:
    api.log.error('Failed to start IPA: %s' % e)
else:
    api.log.info('*** PROCESS START ***')

    # This is the WSGI callable:
    def application(environ, start_response):
Esempio n. 14
0
class API(ReadOnly):
    """
    Dynamic API object through which `Plugin` instances are accessed.
    """

    def __init__(self):
        super(API, self).__init__()
        self.__plugins = set()
        self.__plugins_by_key = {}
        self.__default_map = {}
        self.__instances = {}
        self.__next = {}
        self.__done = set()
        self.env = Env()

    @property
    def bases(self):
        raise NotImplementedError

    @property
    def packages(self):
        raise NotImplementedError

    def __len__(self):
        """
        Return the number of plugin namespaces in this API object.
        """
        return len(self.bases)

    def __iter__(self):
        """
        Iterate (in ascending order) through plugin namespace names.
        """
        return (base.__name__ for base in self.bases)

    def __contains__(self, name):
        """
        Return True if this API object contains plugin namespace ``name``.

        :param name: The plugin namespace name to test for membership.
        """
        return name in set(self)

    def __getitem__(self, name):
        """
        Return the plugin namespace corresponding to ``name``.

        :param name: The name of the plugin namespace you wish to retrieve.
        """
        if name in self:
            try:
                return getattr(self, name)
            except AttributeError:
                pass

        raise KeyError(name)

    def __call__(self):
        """
        Iterate (in ascending order by name) through plugin namespaces.
        """
        for name in self:
            try:
                yield getattr(self, name)
            except AttributeError:
                raise KeyError(name)

    def is_production_mode(self):
        """
        If the object has self.env.mode defined and that mode is
        production return True, otherwise return False.
        """
        return getattr(self.env, 'mode', None) == 'production'

    def __doing(self, name):
        if name in self.__done:
            raise Exception(
                '%s.%s() already called' % (self.__class__.__name__, name)
            )
        self.__done.add(name)

    def __do_if_not_done(self, name):
        if name not in self.__done:
            getattr(self, name)()

    def isdone(self, name):
        return name in self.__done

    def bootstrap(self, parser=None, **overrides):
        """
        Initialize environment variables and logging.
        """
        self.__doing('bootstrap')
        self.log_mgr = log_mgr
        log = log_mgr.root_logger
        self.log = log
        self.env._bootstrap(**overrides)
        self.env._finalize_core(**dict(DEFAULT_CONFIG))

        # Add the argument parser
        if not parser:
            parser = self.build_global_parser()
        self.parser = parser

        # If logging has already been configured somewhere else (like in the
        # installer), don't add handlers or change levels:
        if log_mgr.configure_state != 'default' or self.env.validate_api:
            return

        log_mgr.default_level = 'info'
        log_mgr.configure_from_env(self.env, configure_state='api')
        # Add stderr handler:
        level = 'info'
        if self.env.debug:
            level = 'debug'
        else:
            if self.env.context == 'cli':
                if self.env.verbose > 0:
                    level = 'info'
                else:
                    level = 'warning'

        if 'console' in log_mgr.handlers:
            log_mgr.remove_handler('console')
        log_mgr.create_log_handlers([dict(name='console',
                                          stream=sys.stderr,
                                          level=level,
                                          format=LOGGING_FORMAT_STDERR)])

        # Add file handler:
        if self.env.mode in ('dummy', 'unit_test'):
            return  # But not if in unit-test mode
        if self.env.log is None:
            return
        log_dir = path.dirname(self.env.log)
        if not path.isdir(log_dir):
            try:
                os.makedirs(log_dir)
            except OSError:
                log.error('Could not create log_dir %r', log_dir)
                return

        level = 'info'
        if self.env.debug:
            level = 'debug'
        try:
            log_mgr.create_log_handlers([dict(name='file',
                                              filename=self.env.log,
                                              level=level,
                                              format=LOGGING_FORMAT_FILE)])
        except IOError as e:
            log.error('Cannot open log file %r: %s', self.env.log, e)
            return

    def build_global_parser(self, parser=None, context=None):
        """
        Add global options to an optparse.OptionParser instance.
        """
        def config_file_callback(option, opt, value, parser):
            if not ipautil.file_exists(value):
                parser.error(
                    _("%(filename)s: file not found") % dict(filename=value))

            parser.values.conf = value

        if parser is None:
            parser = optparse.OptionParser(
                add_help_option=False,
                formatter=IPAHelpFormatter(),
                usage='%prog [global-options] COMMAND [command-options]',
                description='Manage an IPA domain',
                version=('VERSION: %s, API_VERSION: %s'
                                % (VERSION, API_VERSION)),
                epilog='\n'.join([
                    'See "ipa help topics" for available help topics.',
                    'See "ipa help <TOPIC>" for more information on a '
                        'specific topic.',
                    'See "ipa help commands" for the full list of commands.',
                    'See "ipa <COMMAND> --help" for more information on a '
                        'specific command.',
                ]))
            parser.disable_interspersed_args()
            parser.add_option("-h", "--help", action="help",
                help='Show this help message and exit')

        parser.add_option('-e', dest='env', metavar='KEY=VAL', action='append',
            help='Set environment variable KEY to VAL',
        )
        parser.add_option('-c', dest='conf', metavar='FILE', action='callback',
            callback=config_file_callback, type='string',
            help='Load configuration from FILE.',
        )
        parser.add_option('-d', '--debug', action='store_true',
            help='Produce full debuging output',
        )
        parser.add_option('--delegate', action='store_true',
            help='Delegate the TGT to the IPA server',
        )
        parser.add_option('-v', '--verbose', action='count',
            help='Produce more verbose output. A second -v displays the XML-RPC request',
        )
        if context == 'cli':
            parser.add_option('-a', '--prompt-all', action='store_true',
                help='Prompt for ALL values (even if optional)'
            )
            parser.add_option('-n', '--no-prompt', action='store_false',
                dest='interactive',
                help='Prompt for NO values (even if required)'
            )
            parser.add_option('-f', '--no-fallback', action='store_false',
                dest='fallback',
                help='Only use the server configured in /etc/ipa/default.conf'
            )

        return parser

    def bootstrap_with_global_options(self, parser=None, context=None):
        parser = self.build_global_parser(parser, context)
        (options, args) = parser.parse_args()
        overrides = {}
        if options.env is not None:
            assert type(options.env) is list
            for item in options.env:
                try:
                    (key, value) = item.split('=', 1)
                except ValueError:
                    # FIXME: this should raise an IPA exception with an
                    # error code.
                    # --Jason, 2008-10-31
                    pass
                overrides[str(key.strip())] = value.strip()
        for key in ('conf', 'debug', 'verbose', 'prompt_all', 'interactive',
            'fallback', 'delegate'):
            value = getattr(options, key, None)
            if value is not None:
                overrides[key] = value
        if hasattr(options, 'prod'):
            overrides['webui_prod'] = options.prod
        if context is not None:
            overrides['context'] = context
        self.bootstrap(parser, **overrides)
        return (options, args)

    def load_plugins(self):
        """
        Load plugins from all standard locations.

        `API.bootstrap` will automatically be called if it hasn't been
        already.
        """
        self.__doing('load_plugins')
        self.__do_if_not_done('bootstrap')
        if self.env.mode in ('dummy', 'unit_test'):
            return
        for package in self.packages:
            self.add_package(package)

    # FIXME: This method has no unit test
    def add_package(self, package):
        """
        Add plugin modules from the ``package``.

        :param package: A package from which to add modules.
        """
        package_name = package.__name__
        package_file = package.__file__
        package_dir = path.dirname(path.abspath(package_file))

        parent = sys.modules[package_name.rpartition('.')[0]]
        parent_dir = path.dirname(path.abspath(parent.__file__))
        if parent_dir == package_dir:
            raise errors.PluginsPackageError(
                name=package_name, file=package_file
            )

        self.log.debug("importing all plugin modules in %s...", package_name)
        modules = getattr(package, 'modules', find_modules_in_dir(package_dir))
        modules = ['.'.join((package_name, name)) for name in modules]

        for name in modules:
            self.log.debug("importing plugin module %s", name)
            try:
                module = importlib.import_module(name)
            except errors.SkipPluginModule as e:
                self.log.debug("skipping plugin module %s: %s", name, e.reason)
                continue
            except Exception as e:
                if self.env.startup_traceback:
                    import traceback
                    self.log.error("could not load plugin module %s\n%s", name,
                                   traceback.format_exc())
                raise

            try:
                self.add_module(module)
            except errors.PluginModuleError as e:
                self.log.debug("%s", e)

    def add_module(self, module):
        """
        Add plugins from the ``module``.

        :param module: A module from which to add plugins.
        """
        try:
            register = module.register
        except AttributeError:
            pass
        else:
            if isinstance(register, Registry):
                for kwargs in register:
                    self.add_plugin(**kwargs)
                return

        raise errors.PluginModuleError(name=module.__name__)

    def add_plugin(self, plugin, override=False, no_fail=False):
        """
        Add the plugin ``plugin``.

        :param plugin: A subclass of `Plugin` to attempt to add.
        :param override: If true, override an already added plugin.
        """
        if not callable(plugin):
            raise TypeError('plugin must be callable; got %r' % plugin)

        # Find the base class or raise SubclassError:
        for base in plugin.bases:
            if issubclass(base, self.bases):
                break
        else:
            raise errors.PluginSubclassError(
                plugin=plugin,
                bases=self.bases,
            )

        # Check override:
        prev = self.__plugins_by_key.get(plugin.full_name)
        if prev:
            if not override:
                if no_fail:
                    return
                else:
                    # Must use override=True to override:
                    raise errors.PluginOverrideError(
                        base=base.__name__,
                        name=plugin.name,
                        plugin=plugin,
                    )

            self.__plugins.remove(prev)
            self.__next[plugin] = prev
        else:
            if override:
                if no_fail:
                    return
                else:
                    # There was nothing already registered to override:
                    raise errors.PluginMissingOverrideError(
                        base=base.__name__,
                        name=plugin.name,
                        plugin=plugin,
                    )

        # The plugin is okay, add to sub_d:
        self.__plugins.add(plugin)
        self.__plugins_by_key[plugin.full_name] = plugin

    def finalize(self):
        """
        Finalize the registration, instantiate the plugins.

        `API.bootstrap` will automatically be called if it hasn't been
        already.
        """
        self.__doing('finalize')
        self.__do_if_not_done('load_plugins')

        if self.env.env_confdir is not None:
            if self.env.env_confdir == self.env.confdir:
                self.log.info(
                    "IPA_CONFDIR env sets confdir to '%s'.", self.env.confdir)

        for plugin in self.__plugins:
            if not self.env.validate_api:
                if plugin.full_name not in DEFAULT_PLUGINS:
                    continue
            else:
                try:
                    default_version = self.__default_map[plugin.name]
                except KeyError:
                    pass
                else:
                    # Technicall plugin.version is not an API version. The
                    # APIVersion class can handle plugin versions. It's more
                    # lean than pkg_resource.parse_version().
                    version = ipautil.APIVersion(plugin.version)
                    default_version = ipautil.APIVersion(default_version)
                    if version < default_version:
                        continue
            self.__default_map[plugin.name] = plugin.version

        production_mode = self.is_production_mode()

        for base in self.bases:
            for plugin in self.__plugins:
                if not any(issubclass(b, base) for b in plugin.bases):
                    continue
                if not self.env.plugins_on_demand:
                    self._get(plugin)

            name = base.__name__
            if not production_mode:
                assert not hasattr(self, name)
            setattr(self, name, APINameSpace(self, base))

        for instance in six.itervalues(self.__instances):
            if not production_mode:
                assert instance.api is self
            if not self.env.plugins_on_demand:
                instance.ensure_finalized()
                if not production_mode:
                    assert islocked(instance)

        self.__finalized = True

        if not production_mode:
            lock(self)

    def _get(self, plugin):
        if not callable(plugin):
            raise TypeError('plugin must be callable; got %r' % plugin)
        if plugin not in self.__plugins:
            raise KeyError(plugin)

        try:
            instance = self.__instances[plugin]
        except KeyError:
            instance = self.__instances[plugin] = plugin(self)

        return instance

    def get_plugin_next(self, plugin):
        if not callable(plugin):
            raise TypeError('plugin must be callable; got %r' % plugin)

        return self.__next[plugin]
Esempio n. 15
0
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
"""
WSGI appliction for IPA server.
"""
from ipaplatform.paths import paths
from ipalib import api
from ipalib.config import Env
from ipalib.constants import DEFAULT_CONFIG

# Determine what debug level is configured. We can only do this
# by reading in the configuration file(s). The server always reads
# default.conf and will also read in `context'.conf.
env = Env()
env._bootstrap(context='server', log=None, confdir=paths.ETC_IPA)
env._finalize_core(**dict(DEFAULT_CONFIG))

# Initialize the API with the proper debug level
api.bootstrap(context='server',
              confdir=paths.ETC_IPA,
              debug=env.debug,
              log=None)
try:
    api.finalize()
except Exception as e:
    api.log.error('Failed to start IPA: %s' % e)
else:
    api.log.info('*** PROCESS START ***')