Example #1
0
class ReplicaConfig:
    def __init__(self):
        self.realm_name = ""
        self.domain_name = ""
        self.master_host_name = ""
        self.dirman_password = ""
        self.host_name = ""
        self.dir = ""
        self.subject_base = None
        self.setup_ca = False
        self.version = 0

    subject_base = ipautil.dn_attribute_property('_subject_base')
Example #2
0
class SimpleServiceInstance(Service):
    def create_instance(self,
                        gensvc_name=None,
                        fqdn=None,
                        dm_password=None,
                        ldap_suffix=None,
                        realm=None):
        self.gensvc_name = gensvc_name
        self.fqdn = fqdn
        self.dm_password = dm_password
        self.suffix = ldap_suffix
        self.realm = realm
        if not realm:
            self.ldapi = False

        self.step("starting %s " % self.service_name, self.__start)
        self.step("configuring %s to start on boot" % self.service_name,
                  self.__enable)
        self.start_creation("Configuring %s" % self.service_name)

    suffix = ipautil.dn_attribute_property('_ldap_suffix')

    def __start(self):
        self.backup_state("running", self.is_running())
        self.restart()

    def __enable(self):
        self.backup_state("enabled", self.is_enabled())
        if self.gensvc_name == None:
            self.enable()
        else:
            self.ldap_enable(self.gensvc_name, self.fqdn, self.dm_password,
                             self.suffix)

    def uninstall(self):
        if self.is_configured():
            self.print_msg("Unconfiguring %s" % self.service_name)

        self.stop()
        self.disable()

        running = self.restore_state("running")
        enabled = self.restore_state("enabled")

        # restore the original state of service
        if running:
            self.start()
        if enabled:
            self.enable()
Example #3
0
        # We are going to set the owner of all of the cert
        # files to the owner of the containing directory
        # instead of that of the process. This works when
        # this is called by root for a daemon that runs as
        # a normal user
        mode = os.stat(self.secdir)
        self.uid = mode[stat.ST_UID]
        self.gid = mode[stat.ST_GID]

        if fstore:
            self.fstore = fstore
        else:
            self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore')

    subject_base = ipautil.dn_attribute_property('_subject_base')

    def __del__(self):
        if self.reqdir is not None:
            shutil.rmtree(self.reqdir, ignore_errors=True)
        try:
            os.chdir(self.cwd)
        except:
            pass

    def setup_cert_request(self):
        """
        Create a temporary directory to store certificate requests and
        certificates. This should be called before requesting certificates.

        This is set outside of __init__ to avoid creating a temporary
Example #4
0
class HTTPInstance(service.Service):
    def __init__(self, fstore=None, cert_nickname='Server-Cert'):
        service.Service.__init__(self, "httpd", service_desc="the web interface")
        if fstore:
            self.fstore = fstore
        else:
            self.fstore = sysrestore.FileStore(paths.SYSRESTORE)

        self.cert_nickname = cert_nickname
        self.ca_is_configured = True

    subject_base = ipautil.dn_attribute_property('_subject_base')

    def create_instance(self, realm, fqdn, domain_name, dm_password=None,
                        autoconfig=True, pkcs12_info=None,
                        subject_base=None, auto_redirect=True, ca_file=None,
                        ca_is_configured=None):
        self.fqdn = fqdn
        self.realm = realm
        self.domain = domain_name
        self.dm_password = dm_password
        self.suffix = ipautil.realm_to_suffix(self.realm)
        self.pkcs12_info = pkcs12_info
        self.principal = "HTTP/%s@%s" % (self.fqdn, self.realm)
        self.dercert = None
        self.subject_base = subject_base
        self.sub_dict = dict(
            REALM=realm,
            FQDN=fqdn,
            DOMAIN=self.domain,
            AUTOREDIR='' if auto_redirect else '#',
            CRL_PUBLISH_PATH=dogtag.install_constants.CRL_PUBLISH_PATH,
        )
        self.ca_file = ca_file
        if ca_is_configured is not None:
            self.ca_is_configured = ca_is_configured

        # get a connection to the DS
        self.ldap_connect()


        self.step("setting mod_nss port to 443", self.__set_mod_nss_port)
        self.step("setting mod_nss protocol list to TLSv1.0 - TLSv1.2",
                  self.set_mod_nss_protocol)
        self.step("setting mod_nss password file", self.__set_mod_nss_passwordfile)
        self.step("enabling mod_nss renegotiate", self.enable_mod_nss_renegotiate)
        self.step("adding URL rewriting rules", self.__add_include)
        self.step("configuring httpd", self.__configure_http)
        if self.ca_is_configured:
            self.step("configure certmonger for renewals",
                      self.configure_certmonger_renewal_guard)
        self.step("setting up ssl", self.__setup_ssl)
        self.step("importing CA certificates from LDAP", self.__import_ca_certs)
        if autoconfig:
            self.step("setting up browser autoconfig", self.__setup_autoconfig)
        self.step("publish CA cert", self.__publish_ca_cert)
        self.step("creating a keytab for httpd", self.__create_http_keytab)
        self.step("clean up any existing httpd ccache", self.remove_httpd_ccache)
        self.step("configuring SELinux for httpd", self.configure_selinux_for_httpd)
        self.step("restarting httpd", self.__start)
        self.step("configuring httpd to start on boot", self.__enable)

        self.start_creation(runtime=60)

    def __start(self):
        self.backup_state("running", self.is_running())
        self.restart()

    def __enable(self):
        self.backup_state("enabled", self.is_enabled())
        # We do not let the system start IPA components on its own,
        # Instead we reply on the IPA init script to start only enabled
        # components as found in our LDAP configuration tree
        self.ldap_enable('HTTP', self.fqdn, self.dm_password, self.suffix)

    def configure_selinux_for_httpd(self):
        try:
            tasks.set_selinux_booleans(SELINUX_BOOLEAN_SETTINGS,
                                       self.backup_state)
        except ipapython.errors.SetseboolError as e:
            self.print_msg(e.format_service_warning('web interface'))

    def __create_http_keytab(self):
        installutils.kadmin_addprinc(self.principal)
        installutils.create_keytab(paths.IPA_KEYTAB, self.principal)
        self.move_service(self.principal)
        self.add_cert_to_service()

        pent = pwd.getpwnam("apache")
        os.chown(paths.IPA_KEYTAB, pent.pw_uid, pent.pw_gid)

    def remove_httpd_ccache(self):
        # Clean up existing ccache
        # Make sure that empty env is passed to avoid passing KRB5CCNAME from
        # current env
        ipautil.run(['kdestroy', '-A'], runas='apache', raiseonerr=False, env={})

    def __configure_http(self):
        target_fname = paths.HTTPD_IPA_CONF
        http_txt = ipautil.template_file(ipautil.SHARE_DIR + "ipa.conf", self.sub_dict)
        self.fstore.backup_file(paths.HTTPD_IPA_CONF)
        http_fd = open(target_fname, "w")
        http_fd.write(http_txt)
        http_fd.close()
        os.chmod(target_fname, 0644)

        target_fname = paths.HTTPD_IPA_REWRITE_CONF
        http_txt = ipautil.template_file(ipautil.SHARE_DIR + "ipa-rewrite.conf", self.sub_dict)
        self.fstore.backup_file(paths.HTTPD_IPA_REWRITE_CONF)
        http_fd = open(target_fname, "w")
        http_fd.write(http_txt)
        http_fd.close()
        os.chmod(target_fname, 0644)

    def change_mod_nss_port_from_http(self):
        # mod_ssl enforces SSLEngine on for vhost on 443 even though
        # the listener is mod_nss. This then crashes the httpd as mod_nss
        # listened port obviously does not match mod_ssl requirements.
        #
        # The workaround for this was to change port to http. It is no longer
        # necessary, as mod_nss now ships with default configuration which
        # sets SSLEngine off when mod_ssl is installed.
        #
        # Remove the workaround.
        if sysupgrade.get_upgrade_state('nss.conf', 'listen_port_updated'):
            installutils.set_directive(paths.HTTPD_NSS_CONF, 'Listen', '443', quotes=False)
            sysupgrade.set_upgrade_state('nss.conf', 'listen_port_updated', False)

    def __set_mod_nss_port(self):
        self.fstore.backup_file(paths.HTTPD_NSS_CONF)
        if installutils.update_file(paths.HTTPD_NSS_CONF, '8443', '443') != 0:
            print "Updating port in %s failed." % paths.HTTPD_NSS_CONF

    def __set_mod_nss_nickname(self, nickname):
        installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSNickname', nickname)

    def set_mod_nss_protocol(self):
        installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSProtocol', 'TLSv1.0,TLSv1.1,TLSv1.2', False)

    def enable_mod_nss_renegotiate(self):
        installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSRenegotiation', 'on', False)
        installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSRequireSafeNegotiation', 'on', False)

    def __set_mod_nss_passwordfile(self):
        installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSPassPhraseDialog', 'file:/etc/httpd/conf/password.conf')

    def __add_include(self):
        """This should run after __set_mod_nss_port so is already backed up"""
        if installutils.update_file(paths.HTTPD_NSS_CONF, '</VirtualHost>', 'Include conf.d/ipa-rewrite.conf\n</VirtualHost>') != 0:
            print "Adding Include conf.d/ipa-rewrite to %s failed." % paths.HTTPD_NSS_CONF

    def configure_certmonger_renewal_guard(self):
        certmonger = services.knownservices.certmonger
        certmonger_stopped = not certmonger.is_running()

        if certmonger_stopped:
            certmonger.start()
        try:
            bus = dbus.SystemBus()
            obj = bus.get_object('org.fedorahosted.certmonger',
                                 '/org/fedorahosted/certmonger')
            iface = dbus.Interface(obj, 'org.fedorahosted.certmonger')
            path = iface.find_ca_by_nickname('IPA')
            if path:
                ca_obj = bus.get_object('org.fedorahosted.certmonger', path)
                ca_iface = dbus.Interface(ca_obj,
                                          'org.freedesktop.DBus.Properties')
                helper = ca_iface.Get('org.fedorahosted.certmonger.ca',
                                      'external-helper')
                if helper:
                    args = shlex.split(helper)
                    if args[0] != paths.IPA_SERVER_GUARD:
                        self.backup_state('certmonger_ipa_helper', helper)
                        args = [paths.IPA_SERVER_GUARD] + args
                        helper = ' '.join(pipes.quote(a) for a in args)
                        ca_iface.Set('org.fedorahosted.certmonger.ca',
                                     'external-helper', helper)
        finally:
            if certmonger_stopped:
                certmonger.stop()

    def __setup_ssl(self):
        fqdn = self.fqdn

        ca_db = certs.CertDB(self.realm, host_name=fqdn, subject_base=self.subject_base)

        db = certs.CertDB(self.realm, subject_base=self.subject_base)
        if self.pkcs12_info:
            if self.ca_is_configured:
                trust_flags = 'CT,C,C'
            else:
                trust_flags = None
            db.create_from_pkcs12(self.pkcs12_info[0], self.pkcs12_info[1],
                                  passwd=None, ca_file=self.ca_file,
                                  trust_flags=trust_flags)
            server_certs = db.find_server_certs()
            if len(server_certs) == 0:
                raise RuntimeError("Could not find a suitable server cert in import in %s" % self.pkcs12_info[0])

            db.create_password_conf()

            # We only handle one server cert
            nickname = server_certs[0][0]
            self.dercert = db.get_cert_from_db(nickname, pem=False)

            if self.ca_is_configured:
                db.track_server_cert(nickname, self.principal, db.passwd_fname, 'restart_httpd')

            self.__set_mod_nss_nickname(nickname)
        else:

            db.create_password_conf()
            self.dercert = db.create_server_cert(self.cert_nickname, self.fqdn,
                                                 ca_db)
            db.track_server_cert(self.cert_nickname, self.principal,
                                 db.passwd_fname, 'restart_httpd')
            db.create_signing_cert("Signing-Cert", "Object Signing Cert", ca_db)

        # Fix the database permissions
        os.chmod(certs.NSS_DIR + "/cert8.db", 0660)
        os.chmod(certs.NSS_DIR + "/key3.db", 0660)
        os.chmod(certs.NSS_DIR + "/secmod.db", 0660)
        os.chmod(certs.NSS_DIR + "/pwdfile.txt", 0660)

        pent = pwd.getpwnam("apache")
        os.chown(certs.NSS_DIR + "/cert8.db", 0, pent.pw_gid )
        os.chown(certs.NSS_DIR + "/key3.db", 0, pent.pw_gid )
        os.chown(certs.NSS_DIR + "/secmod.db", 0, pent.pw_gid )
        os.chown(certs.NSS_DIR + "/pwdfile.txt", 0, pent.pw_gid )

        # Fix SELinux permissions on the database
        tasks.restore_context(certs.NSS_DIR + "/cert8.db")
        tasks.restore_context(certs.NSS_DIR + "/key3.db")

    def __import_ca_certs(self):
        db = certs.CertDB(self.realm, subject_base=self.subject_base)
        self.import_ca_certs(db, self.ca_is_configured)

    def __setup_autoconfig(self):
        target_fname = paths.PREFERENCES_HTML
        ipautil.copy_template_file(
            ipautil.SHARE_DIR + "preferences.html.template",
            target_fname, self.sub_dict)
        os.chmod(target_fname, 0644)

        # The signing cert is generated in __setup_ssl
        db = certs.CertDB(self.realm, subject_base=self.subject_base)
        with open(db.passwd_fname) as pwdfile:
            pwd = pwdfile.read()

        # Setup configure.jar
        if db.has_nickname('Signing-Cert'):
            tmpdir = tempfile.mkdtemp(prefix="tmp-")
            target_fname = paths.CONFIGURE_JAR
            shutil.copy(paths.PREFERENCES_HTML, tmpdir)
            db.run_signtool(["-k", "Signing-Cert",
                            "-Z", target_fname,
                            "-e", ".html", "-p", pwd,
                            tmpdir])
            shutil.rmtree(tmpdir)
            os.chmod(target_fname, 0644)
        else:
            root_logger.warning('Object-signing certificate was not found; '
                'therefore, configure.jar was not created.')

        self.setup_firefox_extension(self.realm, self.domain)

    def setup_firefox_extension(self, realm, domain):
        """Set up the signed browser configuration extension
        """

        target_fname = paths.KRB_JS
        sub_dict = dict(REALM=realm, DOMAIN=domain)
        db = certs.CertDB(realm)
        with open(db.passwd_fname) as pwdfile:
            pwd = pwdfile.read()

        ipautil.copy_template_file(ipautil.SHARE_DIR + "krb.js.template",
            target_fname, sub_dict)
        os.chmod(target_fname, 0644)

        # Setup extension
        tmpdir = tempfile.mkdtemp(prefix="tmp-")
        extdir = tmpdir + "/ext"
        target_fname = paths.KERBEROSAUTH_XPI
        shutil.copytree(paths.FFEXTENSION, extdir)
        if db.has_nickname('Signing-Cert'):
            db.run_signtool(["-k", "Signing-Cert",
                                "-p", pwd,
                                "-X", "-Z", target_fname,
                                extdir])
        else:
            root_logger.warning('Object-signing certificate was not found. '
                'Creating unsigned Firefox configuration extension.')
            filenames = os.listdir(extdir)
            ipautil.run([paths.ZIP, '-r', target_fname] + filenames,
                cwd=extdir)
        shutil.rmtree(tmpdir)
        os.chmod(target_fname, 0644)

    def __publish_ca_cert(self):
        ca_db = certs.CertDB(self.realm)
        ca_db.publish_ca_cert(paths.CA_CRT)

    def uninstall(self):
        if self.is_configured():
            self.print_msg("Unconfiguring web server")

        running = self.restore_state("running")
        enabled = self.restore_state("enabled")


        self.stop_tracking_certificates()

        helper = self.restore_state('certmonger_ipa_helper')
        if helper:
            bus = dbus.SystemBus()
            obj = bus.get_object('org.fedorahosted.certmonger',
                                 '/org/fedorahosted/certmonger')
            iface = dbus.Interface(obj, 'org.fedorahosted.certmonger')
            path = iface.find_ca_by_nickname('IPA')
            if path:
                ca_obj = bus.get_object('org.fedorahosted.certmonger', path)
                ca_iface = dbus.Interface(ca_obj,
                                          'org.freedesktop.DBus.Properties')
                ca_iface.Set('org.fedorahosted.certmonger.ca',
                             'external-helper', helper)

        for f in [paths.HTTPD_IPA_CONF, paths.HTTPD_SSL_CONF, paths.HTTPD_NSS_CONF]:
            try:
                self.fstore.restore_file(f)
            except ValueError, error:
                root_logger.debug(error)
                pass

        # Remove the configuration files we create
        installutils.remove_file(paths.HTTPD_IPA_REWRITE_CONF)
        installutils.remove_file(paths.HTTPD_IPA_CONF)
        installutils.remove_file(paths.HTTPD_IPA_PKI_PROXY_CONF)

        # Restore SELinux boolean states
        boolean_states = {name: self.restore_state(name)
                          for name in SELINUX_BOOLEAN_SETTINGS}
        try:
            tasks.set_selinux_booleans(boolean_states)
        except ipapython.errors.SetseboolError as e:
            self.print_msg('WARNING: ' + str(e))

        if running:
            self.restart()

        # disabled by default, by ldap_enable()
        if enabled:
            self.enable()
Example #5
0
class ODSExporterInstance(service.Service):
    def __init__(self, fstore=None):
        super(ODSExporterInstance, self).__init__(
            "ipa-ods-exporter",
            service_desc="IPA OpenDNSSEC exporter daemon",
            fstore=fstore,
            keytab=paths.IPA_ODS_EXPORTER_KEYTAB,
            service_prefix=u'ipa-ods-exporter'
        )
        self.ods_uid = None
        self.ods_gid = None
        self.enable_if_exists = False

    suffix = ipautil.dn_attribute_property('_suffix')

    def create_instance(self, fqdn, realm_name):
        self.backup_state("enabled", self.is_enabled())
        self.backup_state("running", self.is_running())
        self.fqdn = fqdn
        self.realm = realm_name
        self.suffix = ipautil.realm_to_suffix(self.realm)

        try:
            self.stop()
        except Exception:
            pass

        # checking status step must be first
        self.step("checking status", self.__check_dnssec_status)
        self.step("setting up DNS Key Exporter", self.__setup_key_exporter)
        self.step("setting up kerberos principal", self.__setup_principal)
        self.step("disabling default signer daemon", self.__disable_signerd)
        self.step("starting DNS Key Exporter", self.__start)
        self.step("configuring DNS Key Exporter to start on boot", self.__enable)
        self.start_creation()

    def __check_dnssec_status(self):
        try:
            self.ods_uid = pwd.getpwnam(constants.ODS_USER).pw_uid
        except KeyError:
            raise RuntimeError("OpenDNSSEC UID not found")

        try:
            self.ods_gid = grp.getgrnam(constants.ODS_GROUP).gr_gid
        except KeyError:
            raise RuntimeError("OpenDNSSEC GID not found")

    def __enable(self):

        try:
            self.ldap_configure('DNSKeyExporter', self.fqdn, None,
                                self.suffix)
        except errors.DuplicateEntry:
            logger.error("DNSKeyExporter service already exists")

    def __setup_key_exporter(self):
        directivesetter.set_directive(paths.SYSCONFIG_IPA_ODS_EXPORTER,
                                   'SOFTHSM2_CONF',
                                   paths.DNSSEC_SOFTHSM2_CONF,
                                   quotes=False, separator='=')

    def __setup_principal(self):
        assert self.ods_uid is not None

        for f in [paths.IPA_ODS_EXPORTER_CCACHE, self.keytab]:
            try:
                os.remove(f)
            except OSError:
                pass

        installutils.kadmin_addprinc(self.principal)

        # Store the keytab on disk
        installutils.create_keytab(paths.IPA_ODS_EXPORTER_KEYTAB,
                                   self.principal)
        p = self.move_service(self.principal)
        if p is None:
            # the service has already been moved, perhaps we're doing a DNS reinstall
            dns_exporter_principal_dn = DN(
                ('krbprincipalname', self.principal),
                ('cn', 'services'), ('cn', 'accounts'), self.suffix)
        else:
            dns_exporter_principal_dn = p

        # Make sure access is strictly reserved to the ods user
        os.chmod(self.keytab, 0o440)
        os.chown(self.keytab, 0, self.ods_gid)

        dns_group = DN(('cn', 'DNS Servers'), ('cn', 'privileges'),
                       ('cn', 'pbac'), self.suffix)
        mod = [(ldap.MOD_ADD, 'member', dns_exporter_principal_dn)]

        try:
            api.Backend.ldap2.modify_s(dns_group, mod)
        except ldap.TYPE_OR_VALUE_EXISTS:
            pass
        except Exception as e:
            logger.critical("Could not modify principal's %s entry: %s",
                            dns_exporter_principal_dn, str(e))
            raise

        # limit-free connection

        mod = [(ldap.MOD_REPLACE, 'nsTimeLimit', '-1'),
               (ldap.MOD_REPLACE, 'nsSizeLimit', '-1'),
               (ldap.MOD_REPLACE, 'nsIdleTimeout', '-1'),
               (ldap.MOD_REPLACE, 'nsLookThroughLimit', '-1')]
        try:
            api.Backend.ldap2.modify_s(dns_exporter_principal_dn, mod)
        except Exception as e:
            logger.critical("Could not set principal's %s LDAP limits: %s",
                            dns_exporter_principal_dn, str(e))
            raise

    def __disable_signerd(self):
        signerd_service = services.knownservices.ods_signerd
        if self.get_state("singerd_running") is None:
            self.backup_state("singerd_running", signerd_service.is_running())
        if self.get_state("singerd_enabled") is None:
            self.backup_state("singerd_enabled", signerd_service.is_enabled())

        # disable default opendnssec signer daemon
        signerd_service.stop()
        signerd_service.mask()

    def __start(self):
        self.start()

    def remove_service(self):
        try:
            api.Command.service_del(self.principal)
        except errors.NotFound:
            pass

    def uninstall(self):
        if not self.is_configured():
            return

        self.print_msg("Unconfiguring %s" % self.service_name)

        # just eat states
        self.restore_state("running")
        self.restore_state("enabled")

        # stop and disable service (IPA service, we do not need it anymore)
        self.disable()
        self.stop()

        # restore state of dnssec default signer daemon
        signerd_enabled = self.restore_state("singerd_enabled")
        signerd_running = self.restore_state("singerd_running")
        signerd_service = services.knownservices.ods_signerd

        signerd_service.unmask()

        # service was stopped and disabled by setup
        if signerd_enabled:
            signerd_service.enable()

        if signerd_running:
            signerd_service.start()

        installutils.remove_keytab(self.keytab)
        installutils.remove_ccache(ccache_path=paths.IPA_ODS_EXPORTER_CCACHE)
Example #6
0
class DsInstance(service.Service):
    def __init__(self, realm_name=None, domain_name=None, fstore=None,
                 domainlevel=None, config_ldif=None):
        super(DsInstance, self).__init__(
            "dirsrv",
            service_desc="directory server",
            fstore=fstore,
            service_prefix=u'ldap',
            keytab=paths.DS_KEYTAB,
            service_user=DS_USER,
            realm_name=realm_name
        )
        self.nickname = 'Server-Cert'
        self.sub_dict = None
        self.domain = domain_name
        self.serverid = None
        self.master_fqdn = None
        self.pkcs12_info = None
        self.cacert_name = None
        self.ca_is_configured = True
        self.dercert = None
        self.idstart = None
        self.idmax = None
        self.subject_base = None
        self.open_ports = []
        self.run_init_memberof = True
        self.config_ldif = config_ldif  # updates for dse.ldif
        self.domainlevel = domainlevel
        if realm_name:
            self.suffix = ipautil.realm_to_suffix(self.realm)
            self.__setup_sub_dict()
        else:
            self.suffix = DN()

    subject_base = ipautil.dn_attribute_property('_subject_base')

    def __common_setup(self, enable_ssl=False):

        self.step("creating directory server user", create_ds_user)
        self.step("creating directory server instance", self.__create_instance)
        self.step("enabling ldapi", self.__enable_ldapi)
        self.step("configure autobind for root", self.__root_autobind)
        self.step("stopping directory server", self.__stop_instance)
        self.step("updating configuration in dse.ldif", self.__update_dse_ldif)
        self.step("starting directory server", self.__start_instance)
        self.step("adding default schema", self.__add_default_schemas)
        self.step("enabling memberof plugin", self.__add_memberof_module)
        self.step("enabling winsync plugin", self.__add_winsync_module)
        self.step("configuring replication version plugin", self.__config_version_module)
        self.step("enabling IPA enrollment plugin", self.__add_enrollment_module)
        self.step("configuring uniqueness plugin", self.__set_unique_attrs)
        self.step("configuring uuid plugin", self.__config_uuid_module)
        self.step("configuring modrdn plugin", self.__config_modrdn_module)
        self.step("configuring DNS plugin", self.__config_dns_module)
        self.step("enabling entryUSN plugin", self.__enable_entryusn)
        self.step("configuring lockout plugin", self.__config_lockout_module)
        self.step("configuring topology plugin", self.__config_topology_module)
        self.step("creating indices", self.__create_indices)
        self.step("enabling referential integrity plugin", self.__add_referint_module)
        if enable_ssl:
            self.step("configuring ssl for ds instance", self.__enable_ssl)
        self.step("configuring certmap.conf", self.__certmap_conf)
        self.step("configure new location for managed entries", self.__repoint_managed_entries)
        self.step("configure dirsrv ccache", self.configure_dirsrv_ccache)
        self.step("enabling SASL mapping fallback",
                  self.__enable_sasl_mapping_fallback)

    def __common_post_setup(self):
        self.step("initializing group membership", self.init_memberof)
        self.step("adding master entry", self.__add_master_entry)
        self.step("initializing domain level", self.__set_domain_level)
        self.step("configuring Posix uid/gid generation",
                  self.__config_uidgid_gen)
        self.step("adding replication acis", self.__add_replication_acis)
        self.step("enabling compatibility plugin",
                  self.__enable_compat_plugin)
        self.step("activating sidgen plugin", self._add_sidgen_plugin)
        self.step("activating extdom plugin", self._add_extdom_plugin)
        self.step("tuning directory server", self.__tuning)

        self.step("configuring directory to start on boot", self.__enable)

    def init_info(self, realm_name, fqdn, domain_name, dm_password,
                  subject_base, idstart, idmax, pkcs12_info, ca_file=None):
        self.realm = realm_name.upper()
        self.serverid = installutils.realm_to_serverid(self.realm)
        self.suffix = ipautil.realm_to_suffix(self.realm)
        self.fqdn = fqdn
        self.dm_password = dm_password
        self.domain = domain_name
        self.subject_base = subject_base
        self.idstart = idstart
        self.idmax = idmax
        self.pkcs12_info = pkcs12_info
        if pkcs12_info:
            self.ca_is_configured = False
        self.ca_file = ca_file

        self.__setup_sub_dict()

    def create_instance(self, realm_name, fqdn, domain_name,
                        dm_password, pkcs12_info=None,
                        idstart=1100, idmax=999999, subject_base=None,
                        hbac_allow=True, ca_file=None):
        self.init_info(
            realm_name, fqdn, domain_name, dm_password,
            subject_base, idstart, idmax, pkcs12_info, ca_file=ca_file)

        self.__common_setup()
        self.step("restarting directory server", self.__restart_instance)

        self.step("adding sasl mappings to the directory", self.__configure_sasl_mappings)
        self.step("adding default layout", self.__add_default_layout)
        self.step("adding delegation layout", self.__add_delegation_layout)
        self.step("creating container for managed entries", self.__managed_entries)
        self.step("configuring user private groups", self.__user_private_groups)
        self.step("configuring netgroups from hostgroups", self.__host_nis_groups)
        self.step("creating default Sudo bind user", self.__add_sudo_binduser)
        self.step("creating default Auto Member layout", self.__add_automember_config)
        self.step("adding range check plugin", self.__add_range_check_plugin)
        if hbac_allow:
            self.step("creating default HBAC rule allow_all", self.add_hbac)
        self.step("adding sasl mappings to the directory",
                  self.__configure_sasl_mappings)
        self.step("adding entries for topology management", self.__add_topology_entries)

        self.__common_post_setup()

        self.start_creation(runtime=60)

    def enable_ssl(self):
        self.steps = []

        self.step("configuring ssl for ds instance", self.__enable_ssl)
        self.step("restarting directory server", self.__restart_instance)
        self.step("adding CA certificate entry", self.__upload_ca_cert)

        self.start_creation(runtime=10)

    def create_replica(self, realm_name, master_fqdn, fqdn,
                       domain_name, dm_password, subject_base, api,
                       pkcs12_info=None, ca_file=None,
                       ca_is_configured=None, promote=False):
        # 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

        self.init_info(
            realm_name=realm_name,
            fqdn=fqdn,
            domain_name=domain_name,
            dm_password=dm_password,
            subject_base=subject_base,
            idstart=idstart,
            idmax=idmax,
            pkcs12_info=pkcs12_info,
            ca_file=ca_file
        )
        self.master_fqdn = master_fqdn
        if ca_is_configured is not None:
            self.ca_is_configured = ca_is_configured
        self.promote = promote
        self.api = api

        self.__common_setup(enable_ssl=(not self.promote))
        self.step("restarting directory server", self.__restart_instance)

        self.step("creating DS keytab", self._request_service_keytab)
        if self.promote:
            if self.ca_is_configured:
                self.step("retrieving DS Certificate", self.__get_ds_cert)
            self.step("restarting directory server", self.__restart_instance)

        self.step("setting up initial replication", self.__setup_replica)
        self.step("adding sasl mappings to the directory", self.__configure_sasl_mappings)
        self.step("updating schema", self.__update_schema)
        # See LDIFs for automember configuration during replica install
        self.step("setting Auto Member configuration", self.__add_replica_automember_config)
        self.step("enabling S4U2Proxy delegation", self.__setup_s4u2proxy)
        self.step("importing CA certificates from LDAP", self.__import_ca_certs)

        self.__common_post_setup()

        self.start_creation(runtime=60)


    def __setup_replica(self):
        """
        Setup initial replication between replica and remote master.
        GSSAPI is always used as a replication bind method. Note, however,
        that the bind method for the replication differs between domain levels:
            * in domain level 0, Directory Manager credentials are used to bind
              to remote master
            * in domain level 1, GSSAPI using admin/privileged host credentials
              is used (we do not have access to masters' DM password in this
              stage)
        """
        replication.enable_replication_version_checking(
            self.realm,
            self.dm_password)

        # Always connect to self over ldapi
        ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=self.realm)
        conn = ipaldap.LDAPClient(ldap_uri)
        conn.external_bind()
        repl = replication.ReplicationManager(self.realm,
                                              self.fqdn,
                                              self.dm_password, conn=conn)

        if self.dm_password is not None and not self.promote:
            bind_dn = DN(('cn', 'Directory Manager'))
            bind_pw = self.dm_password
        else:
            bind_dn = bind_pw = None

        repl.setup_promote_replication(self.master_fqdn,
                                       r_binddn=bind_dn,
                                       r_bindpw=bind_pw,
                                       cacert=self.ca_file)
        self.run_init_memberof = repl.needs_memberof_fixup()

    def __configure_sasl_mappings(self):
        # we need to remove any existing SASL mappings in the directory as otherwise they
        # they may conflict.

        try:
            res = api.Backend.ldap2.get_entries(
                DN(('cn', 'mapping'), ('cn', 'sasl'), ('cn', 'config')),
                api.Backend.ldap2.SCOPE_ONELEVEL,
                "(objectclass=nsSaslMapping)")
            for r in res:
                try:
                    api.Backend.ldap2.delete_entry(r)
                except Exception as e:
                    root_logger.critical(
                        "Error during SASL mapping removal: %s", e)
                    raise
        except Exception as e:
            root_logger.critical("Error while enumerating SASL mappings %s", e)
            raise

        entry = api.Backend.ldap2.make_entry(
            DN(
                ('cn', 'Full Principal'), ('cn', 'mapping'), ('cn', 'sasl'),
                ('cn', 'config')),
            objectclass=["top", "nsSaslMapping"],
            cn=["Full Principal"],
            nsSaslMapRegexString=['\(.*\)@\(.*\)'],
            nsSaslMapBaseDNTemplate=[self.suffix],
            nsSaslMapFilterTemplate=['(krbPrincipalName=\\1@\\2)'],
            nsSaslMapPriority=['10'],
        )
        api.Backend.ldap2.add_entry(entry)

        entry = api.Backend.ldap2.make_entry(
            DN(
                ('cn', 'Name Only'), ('cn', 'mapping'), ('cn', 'sasl'),
                ('cn', 'config')),
            objectclass=["top", "nsSaslMapping"],
            cn=["Name Only"],
            nsSaslMapRegexString=['^[^:@]+$'],
            nsSaslMapBaseDNTemplate=[self.suffix],
            nsSaslMapFilterTemplate=['(krbPrincipalName=&@%s)' % self.realm],
            nsSaslMapPriority=['10'],
        )
        api.Backend.ldap2.add_entry(entry)

    def __update_schema(self):
        # FIXME: https://fedorahosted.org/389/ticket/47490
        self._ldap_mod("schema-update.ldif")

    def __enable(self):
        self.backup_state("enabled", self.is_enabled())
        # At the end of the installation ipa-server-install will enable the
        # 'ipa' service wich takes care of starting/stopping dirsrv
        self.disable()

    def __setup_sub_dict(self):
        server_root = find_server_root()
        try:
            idrange_size = self.idmax - self.idstart + 1
        except TypeError:
            idrange_size = None
        self.sub_dict = dict(FQDN=self.fqdn, SERVERID=self.serverid,
                             PASSWORD=self.dm_password,
                             RANDOM_PASSWORD=self.generate_random(),
                             SUFFIX=self.suffix,
                             REALM=self.realm, USER=DS_USER,
                             SERVER_ROOT=server_root, DOMAIN=self.domain,
                             TIME=int(time.time()), IDSTART=self.idstart,
                             IDMAX=self.idmax, HOST=self.fqdn,
                             ESCAPED_SUFFIX=str(self.suffix),
                             GROUP=DS_GROUP,
                             IDRANGE_SIZE=idrange_size,
                             DOMAIN_LEVEL=self.domainlevel,
                             MAX_DOMAIN_LEVEL=constants.MAX_DOMAIN_LEVEL,
                             MIN_DOMAIN_LEVEL=constants.MIN_DOMAIN_LEVEL,
                             STRIP_ATTRS=" ".join(replication.STRIP_ATTRS),
                             EXCLUDES='(objectclass=*) $ EXCLUDE ' +
                             ' '.join(replication.EXCLUDES),
                             TOTAL_EXCLUDES='(objectclass=*) $ EXCLUDE ' +
                             ' '.join(replication.TOTAL_EXCLUDES),
                         )

    def __create_instance(self):
        pent = pwd.getpwnam(DS_USER)

        self.backup_state("serverid", self.serverid)
        self.fstore.backup_file(paths.SYSCONFIG_DIRSRV)

        self.sub_dict['BASEDC'] = self.realm.split('.')[0].lower()
        base_txt = ipautil.template_str(BASE_TEMPLATE, self.sub_dict)
        root_logger.debug(base_txt)

        target_fname = paths.DIRSRV_BOOT_LDIF
        base_fd = open(target_fname, "w")
        base_fd.write(base_txt)
        base_fd.close()

        # Must be readable for dirsrv
        os.chmod(target_fname, 0o440)
        os.chown(target_fname, pent.pw_uid, pent.pw_gid)

        inf_txt = ipautil.template_str(INF_TEMPLATE, self.sub_dict)
        root_logger.debug("writing inf template")
        inf_fd = ipautil.write_tmp_file(inf_txt)
        inf_txt = re.sub(r"RootDNPwd=.*\n", "", inf_txt)
        root_logger.debug(inf_txt)
        args = [
            paths.SETUP_DS_PL, "--silent",
            "--logfile", "-",
            "-f", inf_fd.name,
        ]
        root_logger.debug("calling setup-ds.pl")
        try:
            ipautil.run(args)
            root_logger.debug("completed creating ds instance")
        except ipautil.CalledProcessError as e:
            raise RuntimeError("failed to create ds instance %s" % e)

        # check for open port 389 from now on
        self.open_ports.append(389)

        inf_fd.close()
        os.remove(paths.DIRSRV_BOOT_LDIF)

    def __update_dse_ldif(self):
        """
        This method updates dse.ldif right after instance creation. This is
        supposed to allow admin modify configuration of the DS which has to be
        done before IPA is fully installed (for example: settings for
        replication on replicas)
        DS must be turned off.
        """
        dse_filename = os.path.join(
            paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % self.serverid,
            'dse.ldif'
        )

        with tempfile.NamedTemporaryFile(delete=False) as new_dse_ldif:
            temp_filename = new_dse_ldif.name
            with open(dse_filename, "r") as input_file:
                parser = installutils.ModifyLDIF(input_file, new_dse_ldif)
                parser.replace_value(
                        'cn=config,cn=ldbm database,cn=plugins,cn=config',
                        'nsslapd-db-locks',
                        ['50000']
                        )
                if self.config_ldif:
                    # parse modifications from ldif file supplied by the admin
                    with open(self.config_ldif, "r") as config_ldif:
                        parser.modifications_from_ldif(config_ldif)
                parser.parse()
            new_dse_ldif.flush()
        shutil.copy2(temp_filename, dse_filename)
        try:
            os.remove(temp_filename)
        except OSError as e:
            root_logger.debug("Failed to clean temporary file: %s" % e)

    def __add_default_schemas(self):
        pent = pwd.getpwnam(DS_USER)
        for schema_fname in IPA_SCHEMA_FILES:
            target_fname = schema_dirname(self.serverid) + schema_fname
            shutil.copyfile(
                os.path.join(paths.USR_SHARE_IPA_DIR, schema_fname),
                target_fname)
            os.chmod(target_fname, 0o440)    # read access for dirsrv user/group
            os.chown(target_fname, pent.pw_uid, pent.pw_gid)

        try:
            shutil.move(schema_dirname(self.serverid) + "05rfc2247.ldif",
                            schema_dirname(self.serverid) + "05rfc2247.ldif.old")

            target_fname = schema_dirname(self.serverid) + "05rfc2247.ldif"
            shutil.copyfile(
                os.path.join(paths.USR_SHARE_IPA_DIR, "05rfc2247.ldif"),
                target_fname)
            os.chmod(target_fname, 0o440)
            os.chown(target_fname, pent.pw_uid, pent.pw_gid)
        except IOError:
            # Does not apply with newer DS releases
            pass

    def start(self, *args, **kwargs):
        super(DsInstance, self).start(*args, **kwargs)
        api.Backend.ldap2.connect()

    def stop(self, *args, **kwargs):
        if api.Backend.ldap2.isconnected():
            api.Backend.ldap2.disconnect()

        super(DsInstance, self).stop(*args, **kwargs)

    def restart(self, instance=''):
        api.Backend.ldap2.disconnect()
        try:
            super(DsInstance, self).restart(instance)
            if not is_ds_running(instance):
                root_logger.critical("Failed to restart the directory server. See the installation log for details.")
                raise ScriptError()
        except SystemExit as e:
            raise e
        except Exception as e:
            # TODO: roll back here?
            root_logger.critical("Failed to restart the directory server (%s). See the installation log for details." % e)
        api.Backend.ldap2.connect()

    def __start_instance(self):
        self.start(self.serverid)

    def __stop_instance(self):
        self.stop(self.serverid)

    def __restart_instance(self):
        self.restart(self.serverid)

    def __enable_entryusn(self):
        self._ldap_mod("entryusn.ldif")

    def __add_memberof_module(self):
        self._ldap_mod("memberof-conf.ldif")

    def init_memberof(self):

        if not self.run_init_memberof:
            return

        self._ldap_mod("memberof-task.ldif", self.sub_dict)
        # Note, keep dn in sync with dn in install/share/memberof-task.ldif
        dn = DN(('cn', 'IPA install %s' % self.sub_dict["TIME"]), ('cn', 'memberof task'),
                ('cn', 'tasks'), ('cn', 'config'))
        root_logger.debug("Waiting for memberof task to complete.")
        ldap_uri = ipaldap.get_ldap_uri(self.fqdn)
        conn = ipaldap.LDAPClient(ldap_uri)
        if self.dm_password:
            conn.simple_bind(bind_dn=ipaldap.DIRMAN_DN,
                             bind_password=self.dm_password)
        else:
            conn.gssapi_bind()
        replication.wait_for_task(conn, dn)
        conn.unbind()

    def apply_updates(self):
        schema_files = get_all_external_schema_files(paths.EXTERNAL_SCHEMA_DIR)
        data_upgrade = upgradeinstance.IPAUpgrade(self.realm,
                                                  schema_files=schema_files)
        try:
            data_upgrade.create_instance()
        except Exception as e:
            # very fatal errors only will raise exception
            raise RuntimeError("Update failed: %s" % e)
        installutils.store_version()


    def __add_referint_module(self):
        self._ldap_mod("referint-conf.ldif")

    def __set_unique_attrs(self):
        self._ldap_mod("unique-attributes.ldif", self.sub_dict)

    def __config_uidgid_gen(self):
        self._ldap_mod("dna.ldif", self.sub_dict)

    def __add_master_entry(self):
        self._ldap_mod("master-entry.ldif", self.sub_dict)

    def __add_topology_entries(self):
        self._ldap_mod("topology-entries.ldif", self.sub_dict)

    def __add_winsync_module(self):
        self._ldap_mod("ipa-winsync-conf.ldif")

    def __enable_compat_plugin(self):
        ld = ldapupdate.LDAPUpdate(dm_password=self.dm_password, sub_dict=self.sub_dict)
        rv = ld.update([paths.SCHEMA_COMPAT_ULDIF])
        if not rv:
            raise RuntimeError("Enabling compatibility plugin failed")

    def __config_version_module(self):
        self._ldap_mod("version-conf.ldif")

    def __config_uuid_module(self):
        self._ldap_mod("uuid-conf.ldif")
        self._ldap_mod("uuid.ldif", self.sub_dict)

    def __config_modrdn_module(self):
        self._ldap_mod("modrdn-conf.ldif")
        self._ldap_mod("modrdn-krbprinc.ldif", self.sub_dict)

    def __config_dns_module(self):
        # Configure DNS plugin unconditionally as we would otherwise have
        # troubles if other replica just configured DNS with ipa-dns-install
        self._ldap_mod("ipa-dns-conf.ldif")

    def __config_lockout_module(self):
        self._ldap_mod("lockout-conf.ldif")

    def __config_topology_module(self):
        self._ldap_mod("ipa-topology-conf.ldif", self.sub_dict)

    def __repoint_managed_entries(self):
        self._ldap_mod("repoint-managed-entries.ldif", self.sub_dict)

    def configure_dirsrv_ccache(self):
        pent = pwd.getpwnam(platformconstants.DS_USER)
        ccache = paths.TMP_KRB5CC % pent.pw_uid
        filepath = paths.SYSCONFIG_DIRSRV
        if not os.path.exists(filepath):
            # file doesn't exist; create it with correct ownership & mode
            open(filepath, 'a').close()
            os.chmod(filepath,
                stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
            os.chown(filepath, 0, 0)

        replacevars = {'KRB5CCNAME': ccache}
        ipautil.backup_config_and_replace_variables(
            self.fstore, filepath, replacevars=replacevars)
        tasks.restore_context(filepath)

    def __managed_entries(self):
        self._ldap_mod("managed-entries.ldif", self.sub_dict)

    def __user_private_groups(self):
        self._ldap_mod("user_private_groups.ldif", self.sub_dict)

    def __host_nis_groups(self):
        self._ldap_mod("host_nis_groups.ldif", self.sub_dict)

    def __add_enrollment_module(self):
        self._ldap_mod("enrollment-conf.ldif", self.sub_dict)

    def generate_random(self):
        return ipautil.ipa_generate_password()

    def __enable_ssl(self):
        dirname = config_dirname(self.serverid)
        dsdb = certs.CertDB(self.realm, nssdir=dirname, subject_base=self.subject_base)
        if self.pkcs12_info:
            if self.ca_is_configured:
                trust_flags = 'CT,C,C'
            else:
                trust_flags = None
            dsdb.create_from_pkcs12(self.pkcs12_info[0], self.pkcs12_info[1],
                                    ca_file=self.ca_file,
                                    trust_flags=trust_flags)
            server_certs = dsdb.find_server_certs()
            if len(server_certs) == 0:
                raise RuntimeError("Could not find a suitable server cert in import in %s" % self.pkcs12_info[0])

            # We only handle one server cert
            self.nickname = server_certs[0][0]
            self.dercert = dsdb.get_cert_from_db(self.nickname, pem=False)

            if self.ca_is_configured:
                dsdb.track_server_cert(
                    self.nickname, self.principal, dsdb.passwd_fname,
                    'restart_dirsrv %s' % self.serverid)
        else:
            cadb = certs.CertDB(self.realm, host_name=self.fqdn, subject_base=self.subject_base)

            # FIXME, need to set this nickname in the RA plugin
            cadb.export_ca_cert('ipaCert', False)
            dsdb.create_from_cacert(cadb.cacert_fname, passwd=None)
            ca_args = ['/usr/libexec/certmonger/dogtag-submit',
                       '--ee-url', 'https://%s:8443/ca/ee/ca' % self.fqdn,
                       '--dbdir', paths.HTTPD_ALIAS_DIR,
                       '--nickname', 'ipaCert',
                       '--sslpinfile', paths.ALIAS_PWDFILE_TXT,
                       '--agent-submit']
            helper = " ".join(ca_args)
            prev_helper = certmonger.modify_ca_helper('IPA', helper)
            try:
                cmd = 'restart_dirsrv %s' % self.serverid
                certmonger.request_and_wait_for_cert(
                    nssdb=dirname,
                    nickname=self.nickname,
                    principal=self.principal,
                    passwd_fname=dsdb.passwd_fname,
                    subject=str(DN(('CN', self.fqdn), self.subject_base)),
                    ca='IPA',
                    profile=dogtag.DEFAULT_PROFILE,
                    dns=[self.fqdn],
                    post_command=cmd)
            finally:
                certmonger.modify_ca_helper('IPA', prev_helper)

            self.dercert = dsdb.get_cert_from_db(self.nickname, pem=False)
            dsdb.create_pin_file()

        self.cacert_name = dsdb.cacert_name

        ldap_uri = ipaldap.get_ldap_uri(self.fqdn)
        conn = ipaldap.LDAPClient(ldap_uri)
        conn.simple_bind(bind_dn=ipaldap.DIRMAN_DN,
                         bind_password=self.dm_password)

        mod = [(ldap.MOD_REPLACE, "nsSSLClientAuth", "allowed"),
               (ldap.MOD_REPLACE, "nsSSL3Ciphers", "default"),
               (ldap.MOD_REPLACE, "allowWeakCipher", "off")]
        conn.modify_s(DN(('cn', 'encryption'), ('cn', 'config')), mod)

        mod = [(ldap.MOD_ADD, "nsslapd-security", "on")]
        conn.modify_s(DN(('cn', 'config')), mod)

        entry = conn.make_entry(
            DN(('cn', 'RSA'), ('cn', 'encryption'), ('cn', 'config')),
            objectclass=["top", "nsEncryptionModule"],
            cn=["RSA"],
            nsSSLPersonalitySSL=[self.nickname],
            nsSSLToken=["internal (software)"],
            nsSSLActivation=["on"],
        )
        conn.add_entry(entry)

        conn.unbind()

        # check for open secure port 636 from now on
        self.open_ports.append(636)

    def __upload_ca_cert(self):
        """
        Upload the CA certificate from the NSS database to the LDAP directory.
        """

        dirname = config_dirname(self.serverid)
        dsdb = certs.CertDB(self.realm, nssdir=dirname,
                            subject_base=self.subject_base)
        trust_flags = dict(reversed(dsdb.list_certs()))

        ldap_uri = ipaldap.get_ldap_uri(self.fqdn)
        conn = ipaldap.LDAPClient(ldap_uri)
        conn.simple_bind(bind_dn=ipaldap.DIRMAN_DN,
                         bind_password=self.dm_password)

        nicknames = dsdb.find_root_cert(self.cacert_name)[:-1]
        for nickname in nicknames:
            cert = dsdb.get_cert_from_db(nickname, pem=False)
            certstore.put_ca_cert_nss(conn, self.suffix, cert, nickname,
                                      trust_flags[nickname])

        nickname = self.cacert_name
        cert = dsdb.get_cert_from_db(nickname, pem=False)
        certstore.put_ca_cert_nss(conn, self.suffix, cert, nickname,
                                  trust_flags[nickname],
                                  config_ipa=self.ca_is_configured,
                                  config_compat=self.master_fqdn is None)

        conn.unbind()

    def __import_ca_certs(self):
        dirname = config_dirname(self.serverid)
        dsdb = certs.CertDB(self.realm, nssdir=dirname,
                            subject_base=self.subject_base)

        ldap_uri = ipaldap.get_ldap_uri(self.fqdn)
        conn = ipaldap.LDAPClient(ldap_uri)
        conn.simple_bind(bind_dn=ipaldap.DIRMAN_DN,
                         bind_password=self.dm_password)

        self.import_ca_certs(dsdb, self.ca_is_configured, conn)

        conn.unbind()

    def __add_default_layout(self):
        self._ldap_mod("bootstrap-template.ldif", self.sub_dict)

    def __add_delegation_layout(self):
        self._ldap_mod("delegation.ldif", self.sub_dict)

    def __add_replication_acis(self):
        self._ldap_mod("replica-acis.ldif", self.sub_dict)

    def __setup_s4u2proxy(self):
        self._ldap_mod("replica-s4u2proxy.ldif", self.sub_dict)

    def __create_indices(self):
        self._ldap_mod("indices.ldif")

    def __certmap_conf(self):
        shutil.copyfile(
            os.path.join(paths.USR_SHARE_IPA_DIR, "certmap.conf.template"),
            os.path.join(config_dirname(self.serverid), "certmap.conf"))
        installutils.update_file(config_dirname(self.serverid) + "certmap.conf",
                                 '$SUBJECT_BASE', str(self.subject_base))
        sysupgrade.set_upgrade_state(
            'certmap.conf',
            'subject_base',
            str(self.subject_base)
        )

    def __enable_ldapi(self):
        self._ldap_mod("ldapi.ldif", self.sub_dict,
                       ldap_uri="ldap://localhost",
                       dm_password=self.dm_password)

    def __enable_sasl_mapping_fallback(self):
        self._ldap_mod("sasl-mapping-fallback.ldif", self.sub_dict)

    def add_hbac(self):
        self._ldap_mod("default-hbac.ldif", self.sub_dict)

    def change_admin_password(self, password):
        root_logger.debug("Changing admin password")
        dmpwdfile = ""
        admpwdfile = ""

        try:
            (dmpwdfd, dmpwdfile) = tempfile.mkstemp(dir=paths.VAR_LIB_IPA)
            os.write(dmpwdfd, self.dm_password)
            os.close(dmpwdfd)

            (admpwdfd, admpwdfile) = tempfile.mkstemp(dir=paths.VAR_LIB_IPA)
            os.write(admpwdfd, password)
            os.close(admpwdfd)

            args = [paths.LDAPPASSWD, "-h", self.fqdn,
                    "-ZZ", "-x", "-D", str(DN(('cn', 'Directory Manager'))),
                    "-y", dmpwdfile, "-T", admpwdfile,
                    str(DN(('uid', 'admin'), ('cn', 'users'), ('cn', 'accounts'), self.suffix))]
            try:
                env = {'LDAPTLS_CACERTDIR': os.path.dirname(paths.IPA_CA_CRT),
                       'LDAPTLS_CACERT': paths.IPA_CA_CRT}
                ipautil.run(args, env=env)
                root_logger.debug("ldappasswd done")
            except ipautil.CalledProcessError as e:
                print("Unable to set admin password", e)
                root_logger.debug("Unable to set admin password %s" % e)

        finally:
            if os.path.isfile(dmpwdfile):
                os.remove(dmpwdfile)
            if os.path.isfile(admpwdfile):
                os.remove(admpwdfile)

    def uninstall(self):
        if self.is_configured():
            self.print_msg("Unconfiguring directory server")

        enabled = self.restore_state("enabled")

        # Just eat this state if it exists
        self.restore_state("running")

        try:
            self.fstore.restore_file(paths.LIMITS_CONF)
            self.fstore.restore_file(paths.SYSCONFIG_DIRSRV)
        except ValueError as error:
            root_logger.debug(error)

        # disabled during IPA installation
        if enabled:
            self.enable()

        serverid = self.restore_state("serverid")
        if serverid is not None:
            self.stop_tracking_certificates(serverid)
            root_logger.debug("Removing DS instance %s" % serverid)
            try:
                remove_ds_instance(serverid)
                installutils.remove_keytab(paths.DS_KEYTAB)
                installutils.remove_ccache(run_as=DS_USER)
            except ipautil.CalledProcessError:
                root_logger.error("Failed to remove DS instance. You may "
                                  "need to remove instance data manually")

        # Just eat this state
        self.restore_state("user_exists")

        # Make sure some upgrade-related state is removed. This could cause
        # re-installation problems.
        self.restore_state('nsslapd-port')
        self.restore_state('nsslapd-security')
        self.restore_state('nsslapd-ldapiautobind')

        # If any dirsrv instances remain after we've removed ours then
        # (re)start them.
        for ds_instance in get_ds_instances():
            try:
                services.knownservices.dirsrv.restart(ds_instance, wait=False)
            except Exception as e:
                root_logger.error('Unable to restart ds instance %s: %s', ds_instance, e)

    def stop_tracking_certificates(self, serverid=None):
        if serverid is None:
            serverid = self.get_state("serverid")
        if not serverid is None:
            # drop the trailing / off the config_dirname so the directory
            # will match what is in certmonger
            dirname = config_dirname(serverid)[:-1]
            dsdb = certs.CertDB(self.realm, nssdir=dirname)
            dsdb.untrack_server_cert(self.nickname)

    def start_tracking_certificates(self, serverid):
        dirname = config_dirname(serverid)[:-1]
        dsdb = certs.CertDB(self.realm, nssdir=dirname)
        dsdb.track_server_cert(self.nickname, self.principal,
                               dsdb.passwd_fname,
                               'restart_dirsrv %s' % serverid)

    # we could probably move this function into the service.Service
    # class - it's very generic - all we need is a way to get an
    # instance of a particular Service
    def add_ca_cert(self, cacert_fname, cacert_name=''):
        """Add a CA certificate to the directory server cert db.  We
        first have to shut down the directory server in case it has
        opened the cert db read-only.  Then we use the CertDB class
        to add the CA cert.  We have to provide a nickname, and we
        do not use 'IPA CA' since that's the default, so
        we use 'Imported CA' if none specified.  Then we restart
        the server."""
        # first make sure we have a valid cacert_fname
        try:
            if not os.access(cacert_fname, os.R_OK):
                root_logger.critical("The given CA cert file named [%s] could not be read" %
                                             cacert_fname)
                return False
        except OSError as e:
            root_logger.critical("The given CA cert file named [%s] could not be read: %s" %
                                         (cacert_fname, str(e)))
            return False
        # ok - ca cert file can be read
        # shutdown the server
        self.stop()

        dirname = config_dirname(installutils.realm_to_serverid(self.realm))
        certdb = certs.CertDB(self.realm, nssdir=dirname, subject_base=self.subject_base)
        if not cacert_name or len(cacert_name) == 0:
            cacert_name = "Imported CA"
        # we can't pass in the nickname, so we set the instance variable
        certdb.cacert_name = cacert_name
        status = True
        try:
            certdb.load_cacert(cacert_fname, 'C,,')
        except ipautil.CalledProcessError as e:
            root_logger.critical("Error importing CA cert file named [%s]: %s" %
                                         (cacert_fname, str(e)))
            status = False
        # restart the directory server
        self.start()

        return status

    def tune_nofile(self, num=8192):
        """
        Increase the number of files descriptors available to directory server
        from the default 1024 to 8192. This will allow to support a greater
        number of clients out of the box.
        """

        # Do the platform-specific changes
        proceed = services.knownservices.dirsrv.tune_nofile_platform(
                    num=num, fstore=self.fstore)

        if proceed:
            # finally change also DS configuration
            # NOTE: dirsrv will not allow you to set max file descriptors unless
            # the user limits allow it, so we have to restart dirsrv before
            # attempting to change them in cn=config
            self.__restart_instance()

            nf_sub_dict = dict(NOFILES=str(num))
            self._ldap_mod("ds-nfiles.ldif", nf_sub_dict)

    def __tuning(self):
        self.tune_nofile(8192)

    def __root_autobind(self):
        self._ldap_mod("root-autobind.ldif",
                       ldap_uri="ldap://localhost",
                       dm_password=self.dm_password)

    def __add_sudo_binduser(self):
        self._ldap_mod("sudobind.ldif", self.sub_dict)

    def __add_automember_config(self):
        self._ldap_mod("automember.ldif", self.sub_dict)

    def __add_replica_automember_config(self):
        self._ldap_mod("replica-automember.ldif", self.sub_dict)

    def __add_range_check_plugin(self):
        self._ldap_mod("range-check-conf.ldif", self.sub_dict)

    def _add_sidgen_plugin(self):
        """
        Add sidgen directory server plugin configuration if it does not already exist.
        """
        self.add_sidgen_plugin(self.sub_dict['SUFFIX'])

    def add_sidgen_plugin(self, suffix):
        """
        Add sidgen plugin configuration only if it does not already exist.
        """
        dn = DN('cn=IPA SIDGEN,cn=plugins,cn=config')
        try:
            api.Backend.ldap2.get_entry(dn)
        except errors.NotFound:
            self._ldap_mod('ipa-sidgen-conf.ldif', dict(SUFFIX=suffix))
        else:
            root_logger.debug("sidgen plugin is already configured")

    def _add_extdom_plugin(self):
        """
        Add directory server configuration for the extdom extended operation.
        """
        self.add_extdom_plugin(self.sub_dict['SUFFIX'])

    def add_extdom_plugin(self, suffix):
        """
        Add extdom configuration if it does not already exist.
        """
        dn = DN('cn=ipa_extdom_extop,cn=plugins,cn=config')
        try:
            api.Backend.ldap2.get_entry(dn)
        except errors.NotFound:
            self._ldap_mod('ipa-extdom-extop-conf.ldif', dict(SUFFIX=suffix))
        else:
            root_logger.debug("extdom plugin is already configured")

    def find_subject_base(self):
        """
        Try to find the current value of certificate subject base.
        1) Look in sysupgrade first
        2) If no value is found there, look in DS (start DS if necessary)
        3) Last resort, look in the certmap.conf itself
        4) If all fails, log loudly and return None

        Note that this method can only be executed AFTER the ipa server
        is configured, the api is initialized elsewhere and
        that a ticket already have been acquired.
        """
        root_logger.debug(
            'Trying to find certificate subject base in sysupgrade')
        subject_base = sysupgrade.get_upgrade_state(
            'certmap.conf', 'subject_base')

        if subject_base:
            root_logger.debug(
                'Found certificate subject base in sysupgrade: %s',
                subject_base)
            return subject_base

        root_logger.debug(
            'Unable to find certificate subject base in sysupgrade')
        root_logger.debug(
            'Trying to find certificate subject base in DS')

        ds_is_running = is_ds_running()
        if not ds_is_running:
            try:
                self.start()
                ds_is_running = True
            except ipautil.CalledProcessError as e:
                root_logger.error('Cannot start DS to find certificate '
                                  'subject base: %s', e)

        if ds_is_running:
            try:
                ret = api.Command['config_show']()
                subject_base = str(
                    ret['result']['ipacertificatesubjectbase'][0])
                root_logger.debug(
                    'Found certificate subject base in DS: %s', subject_base)
            except errors.PublicError as e:
                root_logger.error('Cannot connect to DS to find certificate '
                                  'subject base: %s', e)

        if not subject_base:
            root_logger.debug('Unable to find certificate subject base in DS')
            root_logger.debug('Trying to find certificate subject base in '
                              'certmap.conf')

            certmap_dir = config_dirname(
                installutils.realm_to_serverid(api.env.realm)
            )
            try:
                with open(os.path.join(certmap_dir, 'certmap.conf')) as f:
                    for line in f:
                        if line.startswith('certmap ipaca'):
                            subject_base = line.strip().split(',')[-1]
                            root_logger.debug(
                                'Found certificate subject base in certmap.conf: '
                                '%s', subject_base)

            except IOError as e:
                root_logger.error('Cannot open certmap.conf to find certificate '
                                  'subject base: %s', e.strerror)

        if subject_base:
            return subject_base

        root_logger.debug('Unable to find certificate subject base in '
                          'certmap.conf')
        return None

    def __set_domain_level(self):
        # Create global domain level entry and set the domain level
        if self.domainlevel is not None:
            self._ldap_mod("domainlevel.ldif", self.sub_dict)

    def _request_service_keytab(self):
        super(DsInstance, self)._request_service_keytab()

        # Configure DS to use the keytab
        vardict = {"KRB5_KTNAME": self.keytab}
        ipautil.config_replace_variables(paths.SYSCONFIG_DIRSRV,
                                         replacevars=vardict)

    def __get_ds_cert(self):
        subject = self.subject_base or DN(('O', self.realm))
        nssdb_dir = config_dirname(self.serverid)
        db = certs.CertDB(self.realm, nssdir=nssdb_dir, subject_base=subject)
        db.create_from_cacert(paths.IPA_CA_CRT)
        db.request_service_cert(self.nickname, self.principal, self.fqdn)
        db.create_pin_file()

        # Connect to self over ldapi as Directory Manager and configure SSL
        ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=self.realm)
        conn = ipaldap.LDAPClient(ldap_uri)
        conn.external_bind()

        mod = [(ldap.MOD_REPLACE, "nsSSLClientAuth", "allowed"),
               (ldap.MOD_REPLACE, "nsSSL3Ciphers", "default"),
               (ldap.MOD_REPLACE, "allowWeakCipher", "off")]
        conn.modify_s(DN(('cn', 'encryption'), ('cn', 'config')), mod)

        mod = [(ldap.MOD_ADD, "nsslapd-security", "on")]
        conn.modify_s(DN(('cn', 'config')), mod)

        entry = conn.make_entry(
            DN(('cn', 'RSA'), ('cn', 'encryption'), ('cn', 'config')),
            objectclass=["top", "nsEncryptionModule"],
            cn=["RSA"],
            nsSSLPersonalitySSL=[self.nickname],
            nsSSLToken=["internal (software)"],
            nsSSLActivation=["on"],
        )
        conn.add_entry(entry)

        conn.unbind()

        # check for open secure port 636 from now on
        self.open_ports.append(636)
Example #7
0
class KrbInstance(service.Service):
    def __init__(self, fstore=None):
        super(KrbInstance, self).__init__("krb5kdc",
                                          service_desc="Kerberos KDC",
                                          fstore=fstore)
        self.fqdn = None
        self.realm = None
        self.domain = None
        self.host = None
        self.admin_password = None
        self.master_password = None
        self.suffix = None
        self.subject_base = None
        self.kdc_password = None
        self.sub_dict = None
        self.pkcs12_info = None
        self.master_fqdn = None
        self.config_pkinit = None

    suffix = ipautil.dn_attribute_property('_suffix')
    subject_base = ipautil.dn_attribute_property('_subject_base')

    def init_info(self,
                  realm_name,
                  host_name,
                  setup_pkinit=False,
                  subject_base=None):
        self.fqdn = host_name
        self.realm = realm_name
        self.suffix = ipautil.realm_to_suffix(realm_name)
        self.subject_base = subject_base
        self.config_pkinit = setup_pkinit

    def get_realm_suffix(self):
        return DN(('cn', self.realm), ('cn', 'kerberos'), self.suffix)

    def move_service_to_host(self, principal):
        """
        Used to move a host/ service principal created by kadmin.local from
        cn=kerberos to reside under the host entry.
        """

        service_dn = DN(('krbprincipalname', principal),
                        self.get_realm_suffix())
        service_entry = api.Backend.ldap2.get_entry(service_dn)
        api.Backend.ldap2.delete_entry(service_entry)

        # Create a host entry for this master
        host_dn = DN(('fqdn', self.fqdn), ('cn', 'computers'),
                     ('cn', 'accounts'), self.suffix)
        host_entry = api.Backend.ldap2.make_entry(
            host_dn,
            objectclass=[
                'top', 'ipaobject', 'nshost', 'ipahost', 'ipaservice',
                'pkiuser', 'krbprincipalaux', 'krbprincipal',
                'krbticketpolicyaux', 'ipasshhost'
            ],
            krbextradata=service_entry['krbextradata'],
            krblastpwdchange=service_entry['krblastpwdchange'],
            krbprincipalname=service_entry['krbprincipalname'],
            krbcanonicalname=service_entry['krbcanonicalname'],
            krbprincipalkey=service_entry['krbprincipalkey'],
            serverhostname=[self.fqdn.split('.', 1)[0]],
            cn=[self.fqdn],
            fqdn=[self.fqdn],
            ipauniqueid=['autogenerate'],
            managedby=[host_dn],
        )
        if 'krbpasswordexpiration' in service_entry:
            host_entry['krbpasswordexpiration'] = service_entry[
                'krbpasswordexpiration']
        if 'krbticketflags' in service_entry:
            host_entry['krbticketflags'] = service_entry['krbticketflags']
        api.Backend.ldap2.add_entry(host_entry)

        # Add the host to the ipaserver host group
        ld = ldapupdate.LDAPUpdate(ldapi=True)
        ld.update([
            os.path.join(paths.UPDATES_DIR, '20-ipaservers_hostgroup.update')
        ])

    def __common_setup(self, realm_name, host_name, domain_name,
                       admin_password):
        self.fqdn = host_name
        self.realm = realm_name.upper()
        self.host = host_name.split(".")[0]
        self.ip = socket.getaddrinfo(host_name, None, socket.AF_UNSPEC,
                                     socket.SOCK_STREAM)[0][4][0]
        self.domain = domain_name
        self.suffix = ipautil.realm_to_suffix(self.realm)
        self.kdc_password = ipautil.ipa_generate_password()
        self.admin_password = admin_password
        self.dm_password = admin_password

        self.__setup_sub_dict()

        self.backup_state("running", self.is_running())
        try:
            self.stop()
        except Exception:
            # It could have been not running
            pass

    def __common_post_setup(self):
        self.step("creating anonymous principal", self.add_anonymous_principal)
        self.step("starting the KDC", self.__start_instance)
        self.step("configuring KDC to start on boot", self.__enable)

    def create_instance(self,
                        realm_name,
                        host_name,
                        domain_name,
                        admin_password,
                        master_password,
                        setup_pkinit=False,
                        pkcs12_info=None,
                        subject_base=None):
        self.master_password = master_password
        self.pkcs12_info = pkcs12_info
        self.subject_base = subject_base
        self.config_pkinit = setup_pkinit

        self.__common_setup(realm_name, host_name, domain_name, admin_password)

        self.step("adding kerberos container to the directory",
                  self.__add_krb_container)
        self.step("configuring KDC", self.__configure_instance)
        self.step("initialize kerberos container", self.__init_ipa_kdb)
        self.step("adding default ACIs", self.__add_default_acis)
        self.step("creating a keytab for the directory",
                  self.__create_ds_keytab)
        self.step("creating a keytab for the machine",
                  self.__create_host_keytab)
        self.step("adding the password extension to the directory",
                  self.__add_pwd_extop_module)

        self.__common_post_setup()

        self.start_creation()

        self.kpasswd = KpasswdInstance()
        self.kpasswd.create_instance('KPASSWD',
                                     self.fqdn,
                                     self.suffix,
                                     realm=self.realm)

    def create_replica(self,
                       realm_name,
                       master_fqdn,
                       host_name,
                       domain_name,
                       admin_password,
                       setup_pkinit=False,
                       pkcs12_info=None,
                       subject_base=None,
                       promote=False):
        self.pkcs12_info = pkcs12_info
        self.subject_base = subject_base
        self.master_fqdn = master_fqdn
        self.config_pkinit = setup_pkinit

        self.__common_setup(realm_name, host_name, domain_name, admin_password)

        self.step("configuring KDC", self.__configure_instance)
        self.step("adding the password extension to the directory",
                  self.__add_pwd_extop_module)

        self.__common_post_setup()

        self.start_creation()

        self.kpasswd = KpasswdInstance()
        self.kpasswd.create_instance('KPASSWD', self.fqdn, self.suffix)

    def __enable(self):
        self.backup_state("enabled", self.is_enabled())
        # We do not let the system start IPA components on its own,
        # Instead we reply on the IPA init script to start only enabled
        # components as found in our LDAP configuration tree
        self.ldap_enable('KDC', self.fqdn, None, self.suffix)

    def __start_instance(self):
        try:
            self.start()
        except Exception:
            logger.critical("krb5kdc service failed to start")

    def __setup_sub_dict(self):
        if os.path.exists(paths.COMMON_KRB5_CONF_DIR):
            includes = 'includedir {}'.format(paths.COMMON_KRB5_CONF_DIR)
        else:
            includes = ''

        self.sub_dict = dict(FQDN=self.fqdn,
                             IP=self.ip,
                             PASSWORD=self.kdc_password,
                             SUFFIX=self.suffix,
                             DOMAIN=self.domain,
                             HOST=self.host,
                             SERVER_ID=installutils.realm_to_serverid(
                                 self.realm),
                             REALM=self.realm,
                             KRB5KDC_KADM5_ACL=paths.KRB5KDC_KADM5_ACL,
                             DICT_WORDS=paths.DICT_WORDS,
                             KRB5KDC_KADM5_KEYTAB=paths.KRB5KDC_KADM5_KEYTAB,
                             KDC_CERT=paths.KDC_CERT,
                             KDC_KEY=paths.KDC_KEY,
                             CACERT_PEM=paths.CACERT_PEM,
                             KDC_CA_BUNDLE_PEM=paths.KDC_CA_BUNDLE_PEM,
                             CA_BUNDLE_PEM=paths.CA_BUNDLE_PEM,
                             INCLUDES=includes)

        # IPA server/KDC is not a subdomain of default domain
        # Proper domain-realm mapping needs to be specified
        domain = dns.name.from_text(self.domain)
        fqdn = dns.name.from_text(self.fqdn)
        if not fqdn.is_subdomain(domain):
            logger.debug("IPA FQDN '%s' is not located in default domain '%s'",
                         fqdn, domain)
            server_domain = fqdn.parent().to_unicode(omit_final_dot=True)
            logger.debug("Domain '%s' needs additional mapping in krb5.conf",
                         server_domain)
            dr_map = " .%(domain)s = %(realm)s\n %(domain)s = %(realm)s\n" \
                        % dict(domain=server_domain, realm=self.realm)
        else:
            dr_map = ""
        self.sub_dict['OTHER_DOMAIN_REALM_MAPS'] = dr_map

        # Configure KEYRING CCACHE if supported
        if kernel_keyring.is_persistent_keyring_supported():
            logger.debug("Enabling persistent keyring CCACHE")
            self.sub_dict['OTHER_LIBDEFAULTS'] = \
                " default_ccache_name = KEYRING:persistent:%{uid}\n"
        else:
            logger.debug("Persistent keyring CCACHE is not enabled")
            self.sub_dict['OTHER_LIBDEFAULTS'] = ''

    def __add_krb_container(self):
        self._ldap_mod("kerberos.ldif", self.sub_dict)

    def __add_default_acis(self):
        self._ldap_mod("default-aci.ldif", self.sub_dict)

    def __template_file(self, path, chmod=0o644):
        template = os.path.join(paths.USR_SHARE_IPA_DIR,
                                os.path.basename(path) + ".template")
        conf = ipautil.template_file(template, self.sub_dict)
        self.fstore.backup_file(path)
        fd = open(path, "w+")
        fd.write(conf)
        fd.close()
        if chmod is not None:
            os.chmod(path, chmod)

    def __init_ipa_kdb(self):
        # kdb5_util may take a very long time when entropy is low
        installutils.check_entropy()

        #populate the directory with the realm structure
        args = [
            "kdb5_util", "create", "-s", "-r", self.realm, "-x",
            "ipa-setup-override-restrictions"
        ]
        dialogue = (
            # Enter KDC database master key:
            self.master_password + '\n',
            # Re-enter KDC database master key to verify:
            self.master_password + '\n',
        )
        try:
            ipautil.run(args,
                        nolog=(self.master_password, ),
                        stdin=''.join(dialogue))
        except ipautil.CalledProcessError:
            print("Failed to initialize the realm container")

    def __configure_instance(self):
        self.__template_file(paths.KRB5KDC_KDC_CONF, chmod=None)
        self.__template_file(paths.KRB5_CONF)
        self.__template_file(paths.HTML_KRB5_INI)
        self.__template_file(paths.KRB_CON)
        self.__template_file(paths.HTML_KRBREALM_CON)

        MIN_KRB5KDC_WITH_WORKERS = "1.9"
        cpus = os.sysconf('SC_NPROCESSORS_ONLN')
        workers = False
        result = ipautil.run([paths.KLIST, '-V'],
                             raiseonerr=False,
                             capture_output=True)
        if result.returncode == 0:
            verstr = result.output.split()[-1]
            ver = tasks.parse_ipa_version(verstr)
            min = tasks.parse_ipa_version(MIN_KRB5KDC_WITH_WORKERS)
            if ver >= min:
                workers = True
        # Write down config file
        # We write realm and also number of workers (for multi-CPU systems)
        replacevars = {'KRB5REALM': self.realm}
        appendvars = {}
        if workers and cpus > 1:
            appendvars = {'KRB5KDC_ARGS': "'-w %s'" % str(cpus)}
        ipautil.backup_config_and_replace_variables(
            self.fstore,
            paths.SYSCONFIG_KRB5KDC_DIR,
            replacevars=replacevars,
            appendvars=appendvars)
        tasks.restore_context(paths.SYSCONFIG_KRB5KDC_DIR)

    #add the password extop module
    def __add_pwd_extop_module(self):
        self._ldap_mod("pwd-extop-conf.ldif", self.sub_dict)

    def __create_ds_keytab(self):
        ldap_principal = "ldap/" + self.fqdn + "@" + self.realm
        installutils.kadmin_addprinc(ldap_principal)
        self.move_service(ldap_principal)

        self.fstore.backup_file(paths.DS_KEYTAB)
        installutils.create_keytab(paths.DS_KEYTAB, ldap_principal)

        vardict = {"KRB5_KTNAME": paths.DS_KEYTAB}
        ipautil.config_replace_variables(paths.SYSCONFIG_DIRSRV,
                                         replacevars=vardict)
        pent = pwd.getpwnam(constants.DS_USER)
        os.chown(paths.DS_KEYTAB, pent.pw_uid, pent.pw_gid)

    def __create_host_keytab(self):
        host_principal = "host/" + self.fqdn + "@" + self.realm
        installutils.kadmin_addprinc(host_principal)

        self.fstore.backup_file(paths.KRB5_KEYTAB)
        installutils.create_keytab(paths.KRB5_KEYTAB, host_principal)

        # Make sure access is strictly reserved to root only for now
        os.chown(paths.KRB5_KEYTAB, 0, 0)
        os.chmod(paths.KRB5_KEYTAB, 0o600)

        self.move_service_to_host(host_principal)

    def _wait_for_replica_kdc_entry(self):
        master_dn = self.api.Object.server.get_dn(self.fqdn)
        kdc_dn = DN(('cn', 'KDC'), master_dn)

        ldap_uri = 'ldap://{}'.format(self.master_fqdn)

        with ipaldap.LDAPClient(ldap_uri,
                                cacert=paths.IPA_CA_CRT) as remote_ldap:
            remote_ldap.gssapi_bind()
            replication.wait_for_entry(remote_ldap, kdc_dn, timeout=60)

    def _call_certmonger(self, certmonger_ca='IPA'):
        subject = str(DN(('cn', self.fqdn), self.subject_base))
        krbtgt = "krbtgt/" + self.realm + "@" + self.realm
        certpath = (paths.KDC_CERT, paths.KDC_KEY)

        try:
            prev_helper = None
            # on the first CA-ful master without '--no-pkinit', we issue the
            # certificate by contacting Dogtag directly
            use_dogtag_submit = all([
                self.master_fqdn is None, self.pkcs12_info is None,
                self.config_pkinit
            ])

            if use_dogtag_submit:
                ca_args = [
                    paths.CERTMONGER_DOGTAG_SUBMIT, '--ee-url',
                    'https://%s:8443/ca/ee/ca' % self.fqdn, '--certfile',
                    paths.RA_AGENT_PEM, '--keyfile', paths.RA_AGENT_KEY,
                    '--cafile', paths.IPA_CA_CRT, '--agent-submit'
                ]
                helper = " ".join(ca_args)
                prev_helper = certmonger.modify_ca_helper(
                    certmonger_ca, helper)

            certmonger.request_and_wait_for_cert(certpath=certpath,
                                                 subject=subject,
                                                 principal=krbtgt,
                                                 ca=certmonger_ca,
                                                 dns=self.fqdn,
                                                 storage='FILE',
                                                 profile=KDC_PROFILE,
                                                 post_command='renew_kdc_cert',
                                                 perms=(0o644, 0o600))
        except dbus.DBusException as e:
            # if the certificate is already tracked, ignore the error
            name = e.get_dbus_name()
            if name != 'org.fedorahosted.certmonger.duplicate':
                logger.error("Failed to initiate the request: %s", e)
            return
        finally:
            if prev_helper is not None:
                certmonger.modify_ca_helper(certmonger_ca, prev_helper)

    def pkinit_enable(self):
        """
        advertise enabled PKINIT feature in master's KDC entry in LDAP
        """
        service.set_service_entry_config('KDC', self.fqdn, [PKINIT_ENABLED],
                                         self.suffix)

    def pkinit_disable(self):
        """
        unadvertise enabled PKINIT feature in master's KDC entry in LDAP
        """
        ldap = api.Backend.ldap2
        dn = DN(('cn', 'KDC'), ('cn', self.fqdn), ('cn', 'masters'),
                ('cn', 'ipa'), ('cn', 'etc'), self.suffix)

        entry = ldap.get_entry(dn, ['ipaConfigString'])

        config = entry.setdefault('ipaConfigString', [])
        config = [
            value for value in config
            if value.lower() != PKINIT_ENABLED.lower()
        ]
        entry['ipaConfigString'][:] = config

        try:
            ldap.update_entry(entry)
        except errors.EmptyModlist:
            pass

    def _install_pkinit_ca_bundle(self):
        ca_certs = certstore.get_ca_certs(self.api.Backend.ldap2,
                                          self.api.env.basedn,
                                          self.api.env.realm, False)
        ca_certs = [c for c, _n, t, _u in ca_certs if t is not False]
        x509.write_certificate_list(ca_certs, paths.CACERT_PEM)

    def issue_selfsigned_pkinit_certs(self):
        self._call_certmonger(certmonger_ca="SelfSign")
        with open(paths.CACERT_PEM, 'w'):
            pass

    def issue_ipa_ca_signed_pkinit_certs(self):
        try:
            self._call_certmonger()
            self._install_pkinit_ca_bundle()
            self.pkinit_enable()
        except RuntimeError as e:
            logger.warning("PKINIT certificate request failed: %s", e)
            logger.warning("Failed to configure PKINIT")

            self.print_msg("Full PKINIT configuration did not succeed")
            self.print_msg("The setup will only install bits "
                           "essential to the server functionality")
            self.print_msg("You can enable PKINIT after the "
                           "setup completed using 'ipa-pkinit-manage'")

            self.stop_tracking_certs()
            self.issue_selfsigned_pkinit_certs()

    def install_external_pkinit_certs(self):
        certs.install_pem_from_p12(self.pkcs12_info[0], self.pkcs12_info[1],
                                   paths.KDC_CERT)
        certs.install_key_from_p12(self.pkcs12_info[0], self.pkcs12_info[1],
                                   paths.KDC_KEY)
        self._install_pkinit_ca_bundle()
        self.pkinit_enable()

    def setup_pkinit(self):
        if self.pkcs12_info:
            self.install_external_pkinit_certs()
        elif self.config_pkinit:
            self.issue_ipa_ca_signed_pkinit_certs()

    def enable_ssl(self):
        """
        generate PKINIT certificate for KDC. If `--no-pkinit` was specified,
        only configure local self-signed KDC certificate for use as a FAST
        channel generator for WebUI. Do not advertise the installation steps in
        this case.
        """
        if self.master_fqdn is not None:
            self._wait_for_replica_kdc_entry()

        if self.config_pkinit:
            self.steps = []
            self.step("installing X509 Certificate for PKINIT",
                      self.setup_pkinit)
            self.start_creation()
        else:
            self.issue_selfsigned_pkinit_certs()

        try:
            self.restart()
        except Exception:
            logger.critical("krb5kdc service failed to restart")
            raise

    def get_anonymous_principal_name(self):
        return "%s@%s" % (ANON_USER, self.realm)

    def add_anonymous_principal(self):
        # Create the special anonymous principal
        princ_realm = self.get_anonymous_principal_name()
        dn = DN(('krbprincipalname', princ_realm), self.get_realm_suffix())
        try:
            self.api.Backend.ldap2.get_entry(dn)
        except errors.NotFound:
            installutils.kadmin_addprinc(princ_realm)
            self._ldap_mod("anon-princ-aci.ldif", self.sub_dict)

        try:
            self.api.Backend.ldap2.set_entry_active(dn, True)
        except errors.AlreadyActive:
            pass

    def __convert_to_gssapi_replication(self):
        repl = replication.ReplicationManager(self.realm, self.fqdn,
                                              self.dm_password)
        repl.convert_to_gssapi_replication(self.master_fqdn,
                                           r_binddn=DN(
                                               ('cn', 'Directory Manager')),
                                           r_bindpw=self.dm_password)

    def stop_tracking_certs(self):
        certmonger.stop_tracking(certfile=paths.KDC_CERT)

    def uninstall(self):
        if self.is_configured():
            self.print_msg("Unconfiguring %s" % self.service_name)

        running = self.restore_state("running")
        enabled = self.restore_state("enabled")

        try:
            self.stop()
        except Exception:
            pass

        for f in [paths.KRB5KDC_KDC_CONF, paths.KRB5_CONF]:
            try:
                self.fstore.restore_file(f)
            except ValueError as error:
                logger.debug("%s", error)

        # disabled by default, by ldap_enable()
        if enabled:
            self.enable()

        # stop tracking and remove certificates
        self.stop_tracking_certs()
        installutils.remove_file(paths.CACERT_PEM)
        installutils.remove_file(paths.KDC_CERT)
        installutils.remove_file(paths.KDC_KEY)

        if running:
            self.restart()

        self.kpasswd = KpasswdInstance()
        self.kpasswd.uninstall()
Example #8
0
class HTTPInstance(service.Service):
    def __init__(self, fstore=None, cert_nickname='Server-Cert', api=api):
        super(HTTPInstance, self).__init__("httpd",
                                           service_desc="the web interface",
                                           fstore=fstore,
                                           api=api,
                                           service_prefix=u'HTTP',
                                           service_user=HTTPD_USER,
                                           keytab=paths.HTTP_KEYTAB)

        self.cacert_nickname = None
        self.cert_nickname = cert_nickname
        self.ca_is_configured = True
        self.keytab_user = constants.GSSPROXY_USER

    subject_base = ipautil.dn_attribute_property('_subject_base')

    def create_instance(self,
                        realm,
                        fqdn,
                        domain_name,
                        dm_password=None,
                        pkcs12_info=None,
                        subject_base=None,
                        auto_redirect=True,
                        ca_file=None,
                        ca_is_configured=None,
                        promote=False,
                        master_fqdn=None):
        self.fqdn = fqdn
        self.realm = realm
        self.domain = domain_name
        self.dm_password = dm_password
        self.suffix = ipautil.realm_to_suffix(self.realm)
        self.pkcs12_info = pkcs12_info
        self.cert = None
        self.subject_base = subject_base
        self.sub_dict = dict(
            REALM=realm,
            FQDN=fqdn,
            DOMAIN=self.domain,
            AUTOREDIR='' if auto_redirect else '#',
            CRL_PUBLISH_PATH=paths.PKI_CA_PUBLISH_DIR,
            FONTS_DIR=paths.FONTS_DIR,
            GSSAPI_SESSION_KEY=paths.GSSAPI_SESSION_KEY,
            IPA_CUSTODIA_SOCKET=paths.IPA_CUSTODIA_SOCKET,
            IPA_CCACHES=paths.IPA_CCACHES,
            WSGI_PREFIX_DIR=paths.WSGI_PREFIX_DIR,
        )
        self.ca_file = ca_file
        if ca_is_configured is not None:
            self.ca_is_configured = ca_is_configured
        self.promote = promote
        self.master_fqdn = master_fqdn

        self.step("stopping httpd", self.__stop)
        self.step("backing up ssl.conf", self.backup_ssl_conf)
        self.step("disabling nss.conf", self.disable_nss_conf)
        self.step("configuring mod_ssl certificate paths",
                  self.configure_mod_ssl_certs)
        self.step("setting mod_ssl protocol list to TLSv1.0 - TLSv1.2",
                  self.set_mod_ssl_protocol)
        self.step("configuring mod_ssl log directory", self.set_mod_ssl_logdir)
        self.step("disabling mod_ssl OCSP", self.disable_mod_ssl_ocsp)
        self.step("adding URL rewriting rules", self.__add_include)
        self.step("configuring httpd", self.__configure_http)
        self.step("setting up httpd keytab", self.request_service_keytab)
        self.step("configuring Gssproxy", self.configure_gssproxy)
        self.step("setting up ssl", self.__setup_ssl)
        if self.ca_is_configured:
            self.step("configure certmonger for renewals",
                      self.configure_certmonger_renewal_guard)
        self.step("publish CA cert", self.__publish_ca_cert)
        self.step("clean up any existing httpd ccaches",
                  self.remove_httpd_ccaches)
        self.step("configuring SELinux for httpd",
                  self.configure_selinux_for_httpd)
        if not self.is_kdcproxy_configured():
            self.step("create KDC proxy config", self.create_kdcproxy_conf)
            self.step("enable KDC proxy", self.enable_kdcproxy)
        self.step("starting httpd", self.start)
        self.step("configuring httpd to start on boot", self.__enable)
        self.step("enabling oddjobd", self.enable_and_start_oddjobd)

        self.start_creation()

    def __stop(self):
        self.backup_state("running", self.is_running())
        self.stop()

    def __enable(self):
        self.backup_state("enabled", self.is_enabled())
        # We do not let the system start IPA components on its own,
        # Instead we reply on the IPA init script to start only enabled
        # components as found in our LDAP configuration tree
        self.ldap_enable('HTTP', self.fqdn, None, self.suffix)

    def configure_selinux_for_httpd(self):
        try:
            tasks.set_selinux_booleans(constants.SELINUX_BOOLEAN_HTTPD,
                                       self.backup_state)
        except ipapython.errors.SetseboolError as e:
            self.print_msg(e.format_service_warning('web interface'))

    def remove_httpd_ccaches(self):
        # Clean up existing ccaches
        # Make sure that empty env is passed to avoid passing KRB5CCNAME from
        # current env
        installutils.remove_file(paths.HTTP_CCACHE)
        for f in os.listdir(paths.IPA_CCACHES):
            os.remove(os.path.join(paths.IPA_CCACHES, f))

    def __configure_http(self):
        self.update_httpd_service_ipa_conf()
        self.update_httpd_wsgi_conf()

        # create /etc/httpd/alias, see https://pagure.io/freeipa/issue/7529
        session_dir = os.path.dirname(self.sub_dict['GSSAPI_SESSION_KEY'])
        if not os.path.isdir(session_dir):
            os.makedirs(session_dir)
            os.chmod(session_dir, 0o755)

        target_fname = paths.HTTPD_IPA_CONF
        http_txt = ipautil.template_file(
            os.path.join(paths.USR_SHARE_IPA_DIR, "ipa.conf.template"),
            self.sub_dict)
        self.fstore.backup_file(paths.HTTPD_IPA_CONF)
        http_fd = open(target_fname, "w")
        http_fd.write(http_txt)
        http_fd.close()
        os.chmod(target_fname, 0o644)

        target_fname = paths.HTTPD_IPA_REWRITE_CONF
        http_txt = ipautil.template_file(
            os.path.join(paths.USR_SHARE_IPA_DIR, "ipa-rewrite.conf.template"),
            self.sub_dict)
        self.fstore.backup_file(paths.HTTPD_IPA_REWRITE_CONF)
        http_fd = open(target_fname, "w")
        http_fd.write(http_txt)
        http_fd.close()
        os.chmod(target_fname, 0o644)

    def configure_gssproxy(self):
        tasks.configure_http_gssproxy_conf(IPAAPI_USER)
        services.knownservices.gssproxy.restart()

    def get_mod_nss_nickname(self):
        cert = directivesetter.get_directive(paths.HTTPD_NSS_CONF,
                                             'NSSNickname')
        nickname = directivesetter.unquote_directive_value(cert,
                                                           quote_char="'")
        return nickname

    def backup_ssl_conf(self):
        self.fstore.backup_file(paths.HTTPD_SSL_CONF)
        self.fstore.backup_file(paths.HTTPD_SSL_SITE_CONF)

    def disable_nss_conf(self):
        """
        Backs up and removes the original nss.conf file.

        There is no safe way to co-exist since there is no safe port
        to make mod_nss use, disable it completely.
        """
        if os.path.exists(paths.HTTPD_NSS_CONF):
            # check that we don't have a backup already
            # (mod_nss -> mod_ssl upgrade scenario)
            if not self.fstore.has_file(paths.HTTPD_NSS_CONF):
                self.fstore.backup_file(paths.HTTPD_NSS_CONF)
            installutils.remove_file(paths.HTTPD_NSS_CONF)

    def set_mod_ssl_protocol(self):
        directivesetter.set_directive(paths.HTTPD_SSL_CONF, 'SSLProtocol',
                                      '+TLSv1 +TLSv1.1 +TLSv1.2', False)

    def set_mod_ssl_logdir(self):
        tasks.setup_httpd_logging()

    def disable_mod_ssl_ocsp(self):
        if sysupgrade.get_upgrade_state('http', OCSP_ENABLED) is None:
            self.__disable_mod_ssl_ocsp()
            sysupgrade.set_upgrade_state('http', OCSP_ENABLED, False)

    def __disable_mod_ssl_ocsp(self):
        aug = Augeas(flags=Augeas.NO_LOAD | Augeas.NO_MODL_AUTOLOAD)

        aug.set('/augeas/load/Httpd/lens', 'Httpd.lns')
        aug.set('/augeas/load/Httpd/incl', paths.HTTPD_SSL_CONF)
        aug.load()

        path = '/files{}/VirtualHost'.format(paths.HTTPD_SSL_CONF)
        ocsp_path = '{}/directive[.="{}"]'.format(path, OCSP_DIRECTIVE)
        ocsp_arg = '{}/arg'.format(ocsp_path)
        ocsp_comment = '{}/#comment[.="{}"]'.format(path, OCSP_DIRECTIVE)

        ocsp_dir = aug.get(ocsp_path)

        # there is SSLOCSPEnable directive in nss.conf file, comment it
        # otherwise just do nothing
        if ocsp_dir is not None:
            ocsp_state = aug.get(ocsp_arg)
            aug.remove(ocsp_arg)
            aug.rename(ocsp_path, '#comment')
            aug.set(ocsp_comment, '{} {}'.format(OCSP_DIRECTIVE, ocsp_state))
            aug.save()

    def __add_include(self):
        """This should run after __set_mod_nss_port so is already backed up"""
        if installutils.update_file(
                paths.HTTPD_SSL_SITE_CONF, '</VirtualHost>', 'Include {path}\n'
                '</VirtualHost>'.format(
                    path=paths.HTTPD_IPA_REWRITE_CONF)) != 0:
            self.print_msg("Adding Include conf.d/ipa-rewrite to "
                           "%s failed." % paths.HTTPD_SSL_SITE_CONF)

    def configure_certmonger_renewal_guard(self):
        certmonger = services.knownservices.certmonger
        certmonger_stopped = not certmonger.is_running()

        if certmonger_stopped:
            certmonger.start()
        try:
            bus = dbus.SystemBus()
            obj = bus.get_object('org.fedorahosted.certmonger',
                                 '/org/fedorahosted/certmonger')
            iface = dbus.Interface(obj, 'org.fedorahosted.certmonger')
            path = iface.find_ca_by_nickname('IPA')
            if path:
                ca_obj = bus.get_object('org.fedorahosted.certmonger', path)
                ca_iface = dbus.Interface(ca_obj,
                                          'org.freedesktop.DBus.Properties')
                helper = ca_iface.Get('org.fedorahosted.certmonger.ca',
                                      'external-helper')
                if helper:
                    args = shlex.split(helper)
                    if args[0] != paths.IPA_SERVER_GUARD:
                        self.backup_state('certmonger_ipa_helper', helper)
                        args = [paths.IPA_SERVER_GUARD] + args
                        helper = ' '.join(pipes.quote(a) for a in args)
                        ca_iface.Set('org.fedorahosted.certmonger.ca',
                                     'external-helper', helper)
        finally:
            if certmonger_stopped:
                certmonger.stop()

    def __setup_ssl(self):
        key_passwd_file = paths.HTTPD_PASSWD_FILE_FMT.format(host=api.env.host)
        with open(key_passwd_file, 'wb') as f:
            os.fchmod(f.fileno(), 0o600)
            pkey_passwd = ipautil.ipa_generate_password().encode('utf-8')
            f.write(pkey_passwd)

        if self.pkcs12_info:
            p12_certs, p12_priv_keys = certs.pkcs12_to_certkeys(
                *self.pkcs12_info)
            keys_dict = {
                k.public_key().public_numbers(): k
                for k in p12_priv_keys
            }
            certs_keys = [(c, keys_dict.get(c.public_key().public_numbers()))
                          for c in p12_certs]
            server_certs_keys = [(c, k) for c, k in certs_keys
                                 if k is not None]

            if not server_certs_keys:
                raise RuntimeError(
                    "Could not find a suitable server cert in import in %s" %
                    self.pkcs12_info[0])

            # We only handle one server cert
            self.cert = server_certs_keys[0][0]
            x509.write_certificate(self.cert, paths.HTTPD_CERT_FILE)
            x509.write_pem_private_key(server_certs_keys[0][1],
                                       paths.HTTPD_KEY_FILE,
                                       passwd=pkey_passwd)

            if self.ca_is_configured:
                self.start_tracking_certificates()

            self.add_cert_to_service()

        else:
            if not self.promote:
                ca_args = [
                    paths.CERTMONGER_DOGTAG_SUBMIT, '--ee-url',
                    'https://%s:8443/ca/ee/ca' % self.fqdn, '--certfile',
                    paths.RA_AGENT_PEM, '--keyfile', paths.RA_AGENT_KEY,
                    '--cafile', paths.IPA_CA_CRT, '--agent-submit'
                ]
                helper = " ".join(ca_args)
                prev_helper = certmonger.modify_ca_helper('IPA', helper)
            else:
                prev_helper = None
            try:
                certmonger.request_and_wait_for_cert(
                    certpath=(paths.HTTPD_CERT_FILE, paths.HTTPD_KEY_FILE),
                    principal=self.principal,
                    subject=str(DN(('CN', self.fqdn), self.subject_base)),
                    ca='IPA',
                    profile=dogtag.DEFAULT_PROFILE,
                    dns=[self.fqdn],
                    post_command='restart_httpd',
                    storage='FILE',
                    passwd_fname=key_passwd_file)
            finally:
                if prev_helper is not None:
                    certmonger.modify_ca_helper('IPA', prev_helper)
            self.cert = x509.load_certificate_from_file(paths.HTTPD_CERT_FILE)

            if prev_helper is not None:
                self.add_cert_to_service()

            with open(paths.HTTPD_KEY_FILE, 'rb') as f:
                priv_key = x509.load_pem_private_key(
                    f.read(), pkey_passwd, backend=x509.default_backend())

            # Verify we have a valid server cert
            if (priv_key.public_key().public_numbers() !=
                    self.cert.public_key().public_numbers()):
                raise RuntimeError(
                    "The public key of the issued HTTPD service certificate "
                    "does not match its private key.")

        sysupgrade.set_upgrade_state('ssl.conf', 'migrated_to_mod_ssl', True)

    def configure_mod_ssl_certs(self):
        """Configure the mod_ssl certificate directives"""
        directivesetter.set_directive(paths.HTTPD_SSL_SITE_CONF,
                                      'SSLCertificateFile',
                                      paths.HTTPD_CERT_FILE, False)
        directivesetter.set_directive(paths.HTTPD_SSL_SITE_CONF,
                                      'SSLCertificateKeyFile',
                                      paths.HTTPD_KEY_FILE, False)
        directivesetter.set_directive(
            paths.HTTPD_SSL_CONF, 'SSLPassPhraseDialog',
            'exec:{passread}'.format(passread=paths.IPA_HTTPD_PASSWD_READER),
            False)
        directivesetter.set_directive(paths.HTTPD_SSL_SITE_CONF,
                                      'SSLCACertificateFile', paths.IPA_CA_CRT,
                                      False)
        # set SSLVerifyDepth for external CA installations
        directivesetter.set_directive(paths.HTTPD_SSL_CONF,
                                      'SSLVerifyDepth',
                                      MOD_SSL_VERIFY_DEPTH,
                                      quotes=False)

    def __publish_ca_cert(self):
        ca_subject = self.cert.issuer
        certlist = x509.load_certificate_list_from_file(paths.IPA_CA_CRT)
        ca_certs = [c for c in certlist if c.subject == ca_subject]
        if not ca_certs:
            raise RuntimeError("HTTPD cert was issued by an unknown CA.")
        # at this time we can assume any CA cert will be valid since this is
        # only run during installation
        x509.write_certificate_list(certlist, paths.CA_CRT)

    def is_kdcproxy_configured(self):
        """Check if KDC proxy has already been configured in the past"""
        return os.path.isfile(paths.HTTPD_IPA_KDCPROXY_CONF)

    def enable_kdcproxy(self):
        """Add ipaConfigString=kdcProxyEnabled to cn=KDC"""
        service.set_service_entry_config('KDC', self.fqdn,
                                         [u'kdcProxyEnabled'], self.suffix)

    def create_kdcproxy_conf(self):
        """Create ipa-kdc-proxy.conf in /etc/ipa/kdcproxy"""
        target_fname = paths.HTTPD_IPA_KDCPROXY_CONF
        sub_dict = dict(KDCPROXY_CONFIG=paths.KDCPROXY_CONFIG)
        http_txt = ipautil.template_file(
            os.path.join(paths.USR_SHARE_IPA_DIR,
                         "ipa-kdc-proxy.conf.template"), sub_dict)
        self.fstore.backup_file(target_fname)
        with open(target_fname, 'w') as f:
            f.write(http_txt)
        os.chmod(target_fname, 0o644)

    def enable_and_start_oddjobd(self):
        oddjobd = services.service('oddjobd', api)
        self.sstore.backup_state('oddjobd', 'running', oddjobd.is_running())
        self.sstore.backup_state('oddjobd', 'enabled', oddjobd.is_enabled())

        try:
            oddjobd.enable()
            oddjobd.start()
        except Exception as e:
            logger.critical("Unable to start oddjobd: %s", str(e))

    def update_httpd_service_ipa_conf(self):
        tasks.configure_httpd_service_ipa_conf()

    def update_httpd_wsgi_conf(self):
        tasks.configure_httpd_wsgi_conf()

    def uninstall(self):
        if self.is_configured():
            self.print_msg("Unconfiguring web server")

        running = self.restore_state("running")
        enabled = self.restore_state("enabled")

        # Restore oddjobd to its original state
        oddjobd = services.service('oddjobd', api)

        if not self.sstore.restore_state('oddjobd', 'running'):
            try:
                oddjobd.stop()
            except Exception:
                pass

        if not self.sstore.restore_state('oddjobd', 'enabled'):
            try:
                oddjobd.disable()
            except Exception:
                pass

        self.stop_tracking_certificates()

        helper = self.restore_state('certmonger_ipa_helper')
        if helper:
            bus = dbus.SystemBus()
            obj = bus.get_object('org.fedorahosted.certmonger',
                                 '/org/fedorahosted/certmonger')
            iface = dbus.Interface(obj, 'org.fedorahosted.certmonger')
            path = iface.find_ca_by_nickname('IPA')
            if path:
                ca_obj = bus.get_object('org.fedorahosted.certmonger', path)
                ca_iface = dbus.Interface(ca_obj,
                                          'org.freedesktop.DBus.Properties')
                ca_iface.Set('org.fedorahosted.certmonger.ca',
                             'external-helper', helper)

        for f in [
                paths.HTTPD_IPA_CONF, paths.HTTPD_SSL_CONF,
                paths.HTTPD_SSL_SITE_CONF, paths.HTTPD_NSS_CONF
        ]:
            try:
                self.fstore.restore_file(f)
            except ValueError as error:
                logger.debug("%s", error)

        # Remove the configuration files we create
        installutils.remove_keytab(self.keytab)
        remove_files = [
            paths.HTTP_CCACHE,
            paths.HTTPD_CERT_FILE,
            paths.HTTPD_KEY_FILE,
            paths.HTTPD_PASSWD_FILE_FMT.format(host=api.env.host),
            paths.HTTPD_IPA_REWRITE_CONF,
            paths.HTTPD_IPA_CONF,
            paths.HTTPD_IPA_PKI_PROXY_CONF,
            paths.HTTPD_IPA_KDCPROXY_CONF_SYMLINK,
            paths.HTTPD_IPA_KDCPROXY_CONF,
            paths.GSSPROXY_CONF,
            paths.GSSAPI_SESSION_KEY,
            paths.HTTPD_PASSWORD_CONF,
            paths.SYSTEMD_SYSTEM_HTTPD_IPA_CONF,
        ]
        # NSS DB backups
        remove_files.extend(
            glob.glob(os.path.join(paths.HTTPD_ALIAS_DIR, '*.ipasave')))
        if paths.HTTPD_IPA_WSGI_MODULES_CONF is not None:
            remove_files.append(paths.HTTPD_IPA_WSGI_MODULES_CONF)

        for filename in remove_files:
            installutils.remove_file(filename)

        try:
            os.rmdir(paths.SYSTEMD_SYSTEM_HTTPD_D_DIR)
        except OSError as e:
            if e.errno not in {errno.ENOENT, errno.ENOTEMPTY}:
                logger.error("Failed to remove directory %s",
                             paths.SYSTEMD_SYSTEM_HTTPD_D_DIR)

        # Restore SELinux boolean states
        boolean_states = {
            name: self.restore_state(name)
            for name in constants.SELINUX_BOOLEAN_HTTPD
        }
        try:
            tasks.set_selinux_booleans(boolean_states)
        except ipapython.errors.SetseboolError as e:
            self.print_msg('WARNING: ' + str(e))

        if running:
            self.restart()

        # disabled by default, by ldap_enable()
        if enabled:
            self.enable()

    def stop_tracking_certificates(self):
        try:
            certmonger.stop_tracking(certfile=paths.HTTPD_CERT_FILE)
        except RuntimeError as e:
            logger.error("certmonger failed to stop tracking certificate: %s",
                         str(e))

    def start_tracking_certificates(self):
        cert = x509.load_certificate_from_file(paths.HTTPD_CERT_FILE)
        if certs.is_ipa_issued_cert(api, cert):
            request_id = certmonger.start_tracking(
                certpath=(paths.HTTPD_CERT_FILE, paths.HTTPD_KEY_FILE),
                post_command='restart_httpd',
                storage='FILE')
            subject = str(DN(cert.subject))
            certmonger.add_principal(request_id, self.principal)
            certmonger.add_subject(request_id, subject)
        else:
            logger.debug(
                "Will not track HTTP server cert %s as it is not "
                "issued by IPA", cert.subject)

    def request_service_keytab(self):
        super(HTTPInstance, self).request_service_keytab()

        if self.master_fqdn is not None:
            service_dn = DN(('krbprincipalname', self.principal),
                            api.env.container_service, self.suffix)

            ldap_uri = ipaldap.get_ldap_uri(self.master_fqdn)
            with ipaldap.LDAPClient(ldap_uri,
                                    start_tls=not self.promote,
                                    cacert=paths.IPA_CA_CRT) as remote_ldap:
                if self.promote:
                    remote_ldap.gssapi_bind()
                else:
                    remote_ldap.simple_bind(ipaldap.DIRMAN_DN,
                                            self.dm_password)
                replication.wait_for_entry(remote_ldap, service_dn, timeout=60)

    def migrate_to_mod_ssl(self):
        """For upgrades only, migrate from mod_nss to mod_ssl"""
        db = certs.CertDB(api.env.realm, nssdir=paths.HTTPD_ALIAS_DIR)
        nickname = self.get_mod_nss_nickname()
        with tempfile.NamedTemporaryFile() as temp:
            pk12_password = ipautil.ipa_generate_password()
            pk12_pwdfile = ipautil.write_tmp_file(pk12_password)
            db.export_pkcs12(temp.name, pk12_pwdfile.name, nickname)
            certs.install_pem_from_p12(temp.name, pk12_password,
                                       paths.HTTPD_CERT_FILE)

            passwd_fname = paths.HTTPD_PASSWD_FILE_FMT.format(
                host=api.env.host)
            with open(passwd_fname, 'wb') as passwd_file:
                os.fchmod(passwd_file.fileno(), 0o600)
                passwd_file.write(
                    ipautil.ipa_generate_password().encode('utf-8'))

            certs.install_key_from_p12(temp.name,
                                       pk12_password,
                                       paths.HTTPD_KEY_FILE,
                                       out_passwd_fname=passwd_fname)

        self.backup_ssl_conf()
        self.configure_mod_ssl_certs()
        self.set_mod_ssl_protocol()
        self.set_mod_ssl_logdir()
        self.__add_include()

        self.cert = x509.load_certificate_from_file(paths.HTTPD_CERT_FILE)

        if self.ca_is_configured:
            db.untrack_server_cert(nickname)
            self.start_tracking_certificates()

        # remove nickname and CA certs from NSS db

        self.disable_nss_conf()
Example #9
0
class HTTPInstance(service.Service):
    def __init__(self, fstore=None, cert_nickname='Server-Cert', api=api):
        super(HTTPInstance, self).__init__("httpd",
                                           service_desc="the web interface",
                                           fstore=fstore,
                                           api=api,
                                           service_prefix=u'HTTP',
                                           service_user=HTTPD_USER,
                                           keytab=paths.IPA_KEYTAB)

        self.cert_nickname = cert_nickname
        self.ca_is_configured = True

    subject_base = ipautil.dn_attribute_property('_subject_base')

    def create_instance(self,
                        realm,
                        fqdn,
                        domain_name,
                        pkcs12_info=None,
                        subject_base=None,
                        auto_redirect=True,
                        ca_file=None,
                        ca_is_configured=None,
                        promote=False):
        self.fqdn = fqdn
        self.realm = realm
        self.domain = domain_name
        self.suffix = ipautil.realm_to_suffix(self.realm)
        self.pkcs12_info = pkcs12_info
        self.dercert = None
        self.subject_base = subject_base
        self.sub_dict = dict(
            REALM=realm,
            FQDN=fqdn,
            DOMAIN=self.domain,
            AUTOREDIR='' if auto_redirect else '#',
            CRL_PUBLISH_PATH=paths.PKI_CA_PUBLISH_DIR,
        )
        self.ca_file = ca_file
        if ca_is_configured is not None:
            self.ca_is_configured = ca_is_configured
        self.promote = promote

        self.step("setting mod_nss port to 443", self.__set_mod_nss_port)
        self.step("setting mod_nss cipher suite",
                  self.set_mod_nss_cipher_suite)
        self.step("setting mod_nss protocol list to TLSv1.0 - TLSv1.2",
                  self.set_mod_nss_protocol)
        self.step("setting mod_nss password file",
                  self.__set_mod_nss_passwordfile)
        self.step("enabling mod_nss renegotiate",
                  self.enable_mod_nss_renegotiate)
        self.step("adding URL rewriting rules", self.__add_include)
        self.step("configuring httpd", self.__configure_http)
        self.step("setting up httpd keytab", self._request_service_keytab)
        self.step("setting up ssl", self.__setup_ssl)
        if self.ca_is_configured:
            self.step("configure certmonger for renewals",
                      self.configure_certmonger_renewal_guard)
        self.step("importing CA certificates from LDAP",
                  self.__import_ca_certs)
        self.step("publish CA cert", self.__publish_ca_cert)
        self.step("clean up any existing httpd ccache",
                  self.remove_httpd_ccache)
        self.step("configuring SELinux for httpd",
                  self.configure_selinux_for_httpd)
        if not self.is_kdcproxy_configured():
            self.step("create KDC proxy user", create_kdcproxy_user)
            self.step("create KDC proxy config", self.create_kdcproxy_conf)
            self.step("enable KDC proxy", self.enable_kdcproxy)
        self.step("restarting httpd", self.__start)
        self.step("configuring httpd to start on boot", self.__enable)
        self.step("enabling oddjobd", self.enable_and_start_oddjobd)

        self.start_creation(runtime=60)

    def __start(self):
        self.backup_state("running", self.is_running())
        self.restart()

    def __enable(self):
        self.backup_state("enabled", self.is_enabled())
        # We do not let the system start IPA components on its own,
        # Instead we reply on the IPA init script to start only enabled
        # components as found in our LDAP configuration tree
        self.ldap_enable('HTTP', self.fqdn, None, self.suffix)

    def configure_selinux_for_httpd(self):
        try:
            tasks.set_selinux_booleans(SELINUX_BOOLEAN_SETTINGS,
                                       self.backup_state)
        except ipapython.errors.SetseboolError as e:
            self.print_msg(e.format_service_warning('web interface'))

    def remove_httpd_ccache(self):
        # Clean up existing ccache
        # Make sure that empty env is passed to avoid passing KRB5CCNAME from
        # current env
        ipautil.run([paths.KDESTROY, '-A'],
                    runas=self.service_user,
                    raiseonerr=False,
                    env={})

    def __configure_http(self):
        self.update_httpd_service_ipa_conf()

        target_fname = paths.HTTPD_IPA_CONF
        http_txt = ipautil.template_file(ipautil.SHARE_DIR + "ipa.conf",
                                         self.sub_dict)
        self.fstore.backup_file(paths.HTTPD_IPA_CONF)
        http_fd = open(target_fname, "w")
        http_fd.write(http_txt)
        http_fd.close()
        os.chmod(target_fname, 0o644)

        target_fname = paths.HTTPD_IPA_REWRITE_CONF
        http_txt = ipautil.template_file(
            ipautil.SHARE_DIR + "ipa-rewrite.conf", self.sub_dict)
        self.fstore.backup_file(paths.HTTPD_IPA_REWRITE_CONF)
        http_fd = open(target_fname, "w")
        http_fd.write(http_txt)
        http_fd.close()
        os.chmod(target_fname, 0o644)

    def change_mod_nss_port_from_http(self):
        # mod_ssl enforces SSLEngine on for vhost on 443 even though
        # the listener is mod_nss. This then crashes the httpd as mod_nss
        # listened port obviously does not match mod_ssl requirements.
        #
        # The workaround for this was to change port to http. It is no longer
        # necessary, as mod_nss now ships with default configuration which
        # sets SSLEngine off when mod_ssl is installed.
        #
        # Remove the workaround.
        if sysupgrade.get_upgrade_state('nss.conf', 'listen_port_updated'):
            installutils.set_directive(paths.HTTPD_NSS_CONF,
                                       'Listen',
                                       '443',
                                       quotes=False)
            sysupgrade.set_upgrade_state('nss.conf', 'listen_port_updated',
                                         False)

    def __set_mod_nss_port(self):
        self.fstore.backup_file(paths.HTTPD_NSS_CONF)
        if installutils.update_file(paths.HTTPD_NSS_CONF, '8443', '443') != 0:
            print("Updating port in %s failed." % paths.HTTPD_NSS_CONF)

    def __set_mod_nss_nickname(self, nickname):
        installutils.set_directive(paths.HTTPD_NSS_CONF,
                                   'NSSNickname',
                                   nickname,
                                   quote_char="'")

    def set_mod_nss_protocol(self):
        installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSProtocol',
                                   'TLSv1.0,TLSv1.1,TLSv1.2', False)

    def enable_mod_nss_renegotiate(self):
        installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSRenegotiation',
                                   'on', False)
        installutils.set_directive(paths.HTTPD_NSS_CONF,
                                   'NSSRequireSafeNegotiation', 'on', False)

    def set_mod_nss_cipher_suite(self):
        ciphers = ','.join(NSS_CIPHER_SUITE)
        installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSCipherSuite',
                                   ciphers, False)

    def __set_mod_nss_passwordfile(self):
        installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSPassPhraseDialog',
                                   'file:' + paths.HTTPD_PASSWORD_CONF)

    def __add_include(self):
        """This should run after __set_mod_nss_port so is already backed up"""
        if installutils.update_file(
                paths.HTTPD_NSS_CONF, '</VirtualHost>',
                'Include {path}\n</VirtualHost>'.format(
                    path=paths.HTTPD_IPA_REWRITE_CONF)) != 0:
            print("Adding Include conf.d/ipa-rewrite to %s failed." %
                  paths.HTTPD_NSS_CONF)

    def configure_certmonger_renewal_guard(self):
        certmonger = services.knownservices.certmonger
        certmonger_stopped = not certmonger.is_running()

        if certmonger_stopped:
            certmonger.start()
        try:
            bus = dbus.SystemBus()
            obj = bus.get_object('org.fedorahosted.certmonger',
                                 '/org/fedorahosted/certmonger')
            iface = dbus.Interface(obj, 'org.fedorahosted.certmonger')
            path = iface.find_ca_by_nickname('IPA')
            if path:
                ca_obj = bus.get_object('org.fedorahosted.certmonger', path)
                ca_iface = dbus.Interface(ca_obj,
                                          'org.freedesktop.DBus.Properties')
                helper = ca_iface.Get('org.fedorahosted.certmonger.ca',
                                      'external-helper')
                if helper:
                    args = shlex.split(helper)
                    if args[0] != paths.IPA_SERVER_GUARD:
                        self.backup_state('certmonger_ipa_helper', helper)
                        args = [paths.IPA_SERVER_GUARD] + args
                        helper = ' '.join(pipes.quote(a) for a in args)
                        ca_iface.Set('org.fedorahosted.certmonger.ca',
                                     'external-helper', helper)
        finally:
            if certmonger_stopped:
                certmonger.stop()

    def create_cert_db(self):
        database = certs.NSS_DIR
        pwd_file = os.path.join(database, 'pwdfile.txt')

        for p in NSS_FILES:
            nss_path = os.path.join(database, p)
            ipautil.backup_file(nss_path)

        # Create the password file for this db
        hex_str = binascii.hexlify(os.urandom(10))
        f = os.open(pwd_file, os.O_CREAT | os.O_RDWR)
        os.write(f, hex_str)
        os.close(f)

        ipautil.run([paths.CERTUTIL, "-d", database, "-f", pwd_file, "-N"])

        self.fix_cert_db_perms()

    def fix_cert_db_perms(self):
        pent = pwd.getpwnam(self.service_user)

        for filename in NSS_FILES:
            nss_path = os.path.join(certs.NSS_DIR, filename)
            os.chmod(nss_path, 0o640)
            os.chown(nss_path, 0, pent.pw_gid)
            tasks.restore_context(nss_path)

    def __setup_ssl(self):
        db = certs.CertDB(self.realm, subject_base=self.subject_base)
        if self.pkcs12_info:
            if self.ca_is_configured:
                trust_flags = 'CT,C,C'
            else:
                trust_flags = None
            db.init_from_pkcs12(self.pkcs12_info[0],
                                self.pkcs12_info[1],
                                ca_file=self.ca_file,
                                trust_flags=trust_flags)
            server_certs = db.find_server_certs()
            if len(server_certs) == 0:
                raise RuntimeError(
                    "Could not find a suitable server cert in import in %s" %
                    self.pkcs12_info[0])

            db.create_password_conf()

            # We only handle one server cert
            nickname = server_certs[0][0]
            if nickname == 'ipaCert':
                nickname = server_certs[1][0]
            self.dercert = db.get_cert_from_db(nickname, pem=False)

            if self.ca_is_configured:
                db.track_server_cert(nickname, self.principal, db.passwd_fname,
                                     'restart_httpd')

            self.__set_mod_nss_nickname(nickname)
            self.add_cert_to_service()

        else:
            if not self.promote:
                db.create_password_conf()
                ca_args = [
                    '/usr/libexec/certmonger/dogtag-submit', '--ee-url',
                    'https://%s:8443/ca/ee/ca' % self.fqdn, '--dbdir',
                    paths.HTTPD_ALIAS_DIR, '--nickname', 'ipaCert',
                    '--sslpinfile', paths.ALIAS_PWDFILE_TXT, '--agent-submit'
                ]
                helper = " ".join(ca_args)
                prev_helper = certmonger.modify_ca_helper('IPA', helper)

                try:
                    certmonger.request_and_wait_for_cert(
                        nssdb=db.secdir,
                        nickname=self.cert_nickname,
                        principal=self.principal,
                        passwd_fname=db.passwd_fname,
                        subject=str(DN(('CN', self.fqdn), self.subject_base)),
                        ca='IPA',
                        profile=dogtag.DEFAULT_PROFILE,
                        dns=[self.fqdn],
                        post_command='restart_httpd')
                    self.dercert = db.get_cert_from_db(self.cert_nickname,
                                                       pem=False)
                finally:
                    certmonger.modify_ca_helper('IPA', prev_helper)

                self.add_cert_to_service()

            server_certs = db.find_server_certs()
            if not server_certs:
                raise RuntimeError("Could not find a suitable server cert.")

            # We only handle one server cert
            nickname = server_certs[0][0]
            db.export_ca_cert(nickname)

    def __import_ca_certs(self):
        db = certs.CertDB(self.realm, subject_base=self.subject_base)
        self.import_ca_certs(db, self.ca_is_configured)

    def __publish_ca_cert(self):
        ca_db = certs.CertDB(self.realm)
        ca_db.publish_ca_cert(paths.CA_CRT)

    def is_kdcproxy_configured(self):
        """Check if KDC proxy has already been configured in the past"""
        return os.path.isfile(paths.HTTPD_IPA_KDCPROXY_CONF)

    def enable_kdcproxy(self):
        """Add ipaConfigString=kdcProxyEnabled to cn=KDC"""
        entry_name = DN(('cn', 'KDC'), ('cn', self.fqdn), ('cn', 'masters'),
                        ('cn', 'ipa'), ('cn', 'etc'), self.suffix)
        attr_name = 'kdcProxyEnabled'

        try:
            entry = self.admin_conn.get_entry(entry_name, ['ipaConfigString'])
        except errors.NotFound:
            pass
        else:
            if any(attr_name.lower() == val.lower()
                   for val in entry.get('ipaConfigString', [])):
                root_logger.debug("service KDCPROXY already enabled")
                return

            entry.setdefault('ipaConfigString', []).append(attr_name)
            try:
                self.admin_conn.update_entry(entry)
            except errors.EmptyModlist:
                root_logger.debug("service KDCPROXY already enabled")
                return
            except:
                root_logger.debug("failed to enable service KDCPROXY")
                raise

            root_logger.debug("service KDCPROXY enabled")
            return

        entry = self.admin_conn.make_entry(
            entry_name,
            objectclass=["nsContainer", "ipaConfigObject"],
            cn=['KDC'],
            ipaconfigstring=[attr_name])

        try:
            self.admin_conn.add_entry(entry)
        except errors.DuplicateEntry:
            root_logger.debug("failed to add service KDCPROXY entry")
            raise

    def create_kdcproxy_conf(self):
        """Create ipa-kdc-proxy.conf in /etc/ipa/kdcproxy"""
        target_fname = paths.HTTPD_IPA_KDCPROXY_CONF
        sub_dict = dict(KDCPROXY_CONFIG=paths.KDCPROXY_CONFIG)
        http_txt = ipautil.template_file(
            ipautil.SHARE_DIR + "ipa-kdc-proxy.conf.template", sub_dict)
        self.fstore.backup_file(target_fname)
        with open(target_fname, 'w') as f:
            f.write(http_txt)
        os.chmod(target_fname, 0o644)

    def enable_and_start_oddjobd(self):
        oddjobd = services.service('oddjobd')
        self.sstore.backup_state('oddjobd', 'running', oddjobd.is_running())
        self.sstore.backup_state('oddjobd', 'enabled', oddjobd.is_enabled())

        try:
            oddjobd.enable()
            oddjobd.start()
        except Exception as e:
            root_logger.critical("Unable to start oddjobd: {0}".format(str(e)))

    def update_httpd_service_ipa_conf(self):
        tasks.configure_httpd_service_ipa_conf()

    def uninstall(self):
        if self.is_configured():
            self.print_msg("Unconfiguring web server")

        running = self.restore_state("running")
        enabled = self.restore_state("enabled")

        # Restore oddjobd to its original state
        oddjobd = services.service('oddjobd')

        if not self.sstore.restore_state('oddjobd', 'running'):
            try:
                oddjobd.stop()
            except Exception:
                pass

        if not self.sstore.restore_state('oddjobd', 'enabled'):
            try:
                oddjobd.disable()
            except Exception:
                pass

        self.stop_tracking_certificates()

        helper = self.restore_state('certmonger_ipa_helper')
        if helper:
            bus = dbus.SystemBus()
            obj = bus.get_object('org.fedorahosted.certmonger',
                                 '/org/fedorahosted/certmonger')
            iface = dbus.Interface(obj, 'org.fedorahosted.certmonger')
            path = iface.find_ca_by_nickname('IPA')
            if path:
                ca_obj = bus.get_object('org.fedorahosted.certmonger', path)
                ca_iface = dbus.Interface(ca_obj,
                                          'org.freedesktop.DBus.Properties')
                ca_iface.Set('org.fedorahosted.certmonger.ca',
                             'external-helper', helper)

        for f in [
                paths.HTTPD_IPA_CONF, paths.HTTPD_SSL_CONF,
                paths.HTTPD_NSS_CONF
        ]:
            try:
                self.fstore.restore_file(f)
            except ValueError as error:
                root_logger.debug(error)

        installutils.remove_keytab(self.keytab)
        installutils.remove_ccache(ccache_path=paths.KRB5CC_HTTPD,
                                   run_as=self.service_user)

        # Remove the configuration files we create
        installutils.remove_file(paths.HTTPD_IPA_REWRITE_CONF)
        installutils.remove_file(paths.HTTPD_IPA_CONF)
        installutils.remove_file(paths.HTTPD_IPA_PKI_PROXY_CONF)
        installutils.remove_file(paths.HTTPD_IPA_KDCPROXY_CONF_SYMLINK)
        installutils.remove_file(paths.HTTPD_IPA_KDCPROXY_CONF)
        tasks.remove_httpd_service_ipa_conf()

        # Restore SELinux boolean states
        boolean_states = {
            name: self.restore_state(name)
            for name in SELINUX_BOOLEAN_SETTINGS
        }
        try:
            tasks.set_selinux_booleans(boolean_states)
        except ipapython.errors.SetseboolError as e:
            self.print_msg('WARNING: ' + str(e))

        if running:
            self.restart()

        # disabled by default, by ldap_enable()
        if enabled:
            self.enable()

    def stop_tracking_certificates(self):
        db = certs.CertDB(api.env.realm)
        db.untrack_server_cert(self.cert_nickname)

    def start_tracking_certificates(self):
        db = certs.CertDB(self.realm)
        db.track_server_cert(self.cert_nickname, self.principal,
                             db.passwd_fname, 'restart_httpd')
Example #10
0
class ODSExporterInstance(service.Service):
    def __init__(self, fstore=None, dm_password=None, ldapi=False,
                 start_tls=False, autobind=ipaldap.AUTOBIND_DISABLED):
        service.Service.__init__(
            self, "ipa-ods-exporter",
            service_desc="IPA OpenDNSSEC exporter daemon",
            dm_password=dm_password,
            ldapi=ldapi,
            autobind=autobind,
            start_tls=start_tls
        )
        self.dm_password = dm_password
        self.ods_uid = None
        self.ods_gid = None
        self.enable_if_exists = False

        if fstore:
            self.fstore = fstore
        else:
            self.fstore = sysrestore.FileStore(paths.SYSRESTORE)

    suffix = ipautil.dn_attribute_property('_suffix')

    def create_instance(self, fqdn, realm_name):
        self.backup_state("enabled", self.is_enabled())
        self.backup_state("running", self.is_running())
        self.fqdn = fqdn
        self.realm = realm_name
        self.suffix = ipautil.realm_to_suffix(self.realm)

        try:
            self.stop()
        except:
            pass

        # get a connection to the DS
        self.ldap_connect()
        # checking status step must be first
        self.step("checking status", self.__check_dnssec_status)
        self.step("setting up DNS Key Exporter", self.__setup_key_exporter)
        self.step("setting up kerberos principal", self.__setup_principal)
        self.step("disabling default signer daemon", self.__disable_signerd)
        self.step("starting DNS Key Exporter", self.__start)
        self.step("configuring DNS Key Exporter to start on boot", self.__enable)
        self.start_creation()

    def __check_dnssec_status(self):
        ods_enforcerd = services.knownservices.ods_enforcerd

        try:
            self.ods_uid = pwd.getpwnam(ods_enforcerd.get_user_name()).pw_uid
        except KeyError:
            raise RuntimeError("OpenDNSSEC UID not found")

        try:
            self.ods_gid = grp.getgrnam(ods_enforcerd.get_group_name()).gr_gid
        except KeyError:
            raise RuntimeError("OpenDNSSEC GID not found")

    def __enable(self):

        try:
            self.ldap_enable('DNSKeyExporter', self.fqdn, self.dm_password,
                             self.suffix)
        except errors.DuplicateEntry:
            root_logger.error("DNSKeyExporter service already exists")

    def __setup_key_exporter(self):
        installutils.set_directive(paths.SYSOCNFIG_IPA_ODS_EXPORTER,
                                   'SOFTHSM2_CONF',
                                   paths.DNSSEC_SOFTHSM2_CONF,
                                   quotes=False, separator='=')

    def __setup_principal(self):
        assert self.ods_uid is not None
        dns_exporter_principal = "ipa-ods-exporter/" + self.fqdn + "@" + self.realm
        installutils.kadmin_addprinc(dns_exporter_principal)

        # Store the keytab on disk
        installutils.create_keytab(paths.IPA_ODS_EXPORTER_KEYTAB, dns_exporter_principal)
        p = self.move_service(dns_exporter_principal)
        if p is None:
            # the service has already been moved, perhaps we're doing a DNS reinstall
            dns_exporter_principal_dn = DN(
                ('krbprincipalname', dns_exporter_principal),
                ('cn', 'services'), ('cn', 'accounts'), self.suffix)
        else:
            dns_exporter_principal_dn = p

        # Make sure access is strictly reserved to the ods user
        os.chmod(paths.IPA_ODS_EXPORTER_KEYTAB, 0440)
        os.chown(paths.IPA_ODS_EXPORTER_KEYTAB, 0, self.ods_gid)

        dns_group = DN(('cn', 'DNS Servers'), ('cn', 'privileges'),
                       ('cn', 'pbac'), self.suffix)
        mod = [(ldap.MOD_ADD, 'member', dns_exporter_principal_dn)]

        try:
            self.admin_conn.modify_s(dns_group, mod)
        except ldap.TYPE_OR_VALUE_EXISTS:
            pass
        except Exception, e:
            root_logger.critical("Could not modify principal's %s entry: %s"
                                 % (dns_exporter_principal_dn, str(e)))
            raise

        # limit-free connection

        mod = [(ldap.MOD_REPLACE, 'nsTimeLimit', '-1'),
               (ldap.MOD_REPLACE, 'nsSizeLimit', '-1'),
               (ldap.MOD_REPLACE, 'nsIdleTimeout', '-1'),
               (ldap.MOD_REPLACE, 'nsLookThroughLimit', '-1')]
        try:
            self.admin_conn.modify_s(dns_exporter_principal_dn, mod)
        except Exception, e:
            root_logger.critical("Could not set principal's %s LDAP limits: %s"
                                 % (dns_exporter_principal_dn, str(e)))
            raise
Example #11
0
class OpenDNSSECInstance(service.Service):
    def __init__(self,
                 fstore=None,
                 dm_password=None,
                 ldapi=False,
                 start_tls=False,
                 autobind=ipaldap.AUTOBIND_DISABLED):
        service.Service.__init__(self,
                                 "ods-enforcerd",
                                 service_desc="OpenDNSSEC enforcer daemon",
                                 dm_password=dm_password,
                                 ldapi=ldapi,
                                 autobind=autobind,
                                 start_tls=start_tls)
        self.dm_password = dm_password
        self.ods_uid = None
        self.ods_gid = None
        self.conf_file_dict = {
            'SOFTHSM_LIB': paths.LIBSOFTHSM2_SO,
            'TOKEN_LABEL': dnskeysyncinstance.softhsm_token_label,
            'KASP_DB': paths.OPENDNSSEC_KASP_DB,
        }
        self.kasp_file_dict = {}
        self.extra_config = [KEYMASTER]

        if fstore:
            self.fstore = fstore
        else:
            self.fstore = sysrestore.FileStore(paths.SYSRESTORE)

    suffix = ipautil.dn_attribute_property('_suffix')

    def get_masters(self):
        if not self.admin_conn:
            self.ldap_connect()
        return get_dnssec_key_masters(self.admin_conn)

    def create_instance(self, fqdn, realm_name, generate_master_key=True):
        self.backup_state("enabled", self.is_enabled())
        self.backup_state("running", self.is_running())
        self.fqdn = fqdn
        self.realm = realm_name
        self.suffix = ipautil.realm_to_suffix(self.realm)

        try:
            self.stop()
        except Exception:
            pass

        # get a connection to the DS
        if not self.admin_conn:
            self.ldap_connect()
        # checking status must be first
        self.step("checking status", self.__check_dnssec_status)
        self.step("setting up configuration files", self.__setup_conf_files)
        self.step("setting up ownership and file mode bits",
                  self.__setup_ownership_file_modes)
        if generate_master_key:
            self.step("generating master key", self.__generate_master_key)
        self.step("setting up OpenDNSSEC", self.__setup_dnssec)
        self.step("setting up ipa-dnskeysyncd", self.__setup_dnskeysyncd)
        self.step("starting OpenDNSSEC enforcer", self.__start)
        self.step("configuring OpenDNSSEC enforcer to start on boot",
                  self.__enable)
        self.start_creation()

    def __check_dnssec_status(self):
        named = services.knownservices.named
        ods_enforcerd = services.knownservices.ods_enforcerd

        try:
            self.named_uid = pwd.getpwnam(named.get_user_name()).pw_uid
        except KeyError:
            raise RuntimeError("Named UID not found")

        try:
            self.named_gid = grp.getgrnam(named.get_group_name()).gr_gid
        except KeyError:
            raise RuntimeError("Named GID not found")

        try:
            self.ods_uid = pwd.getpwnam(ods_enforcerd.get_user_name()).pw_uid
        except KeyError:
            raise RuntimeError("OpenDNSSEC UID not found")

        try:
            self.ods_gid = grp.getgrnam(ods_enforcerd.get_group_name()).gr_gid
        except KeyError:
            raise RuntimeError("OpenDNSSEC GID not found")

    def __enable(self):
        try:
            self.ldap_enable('DNSSEC', self.fqdn, self.dm_password,
                             self.suffix, self.extra_config)
        except errors.DuplicateEntry:
            root_logger.error("DNSSEC service already exists")

    def __setup_conf_files(self):
        if not self.fstore.has_file(paths.OPENDNSSEC_CONF_FILE):
            self.fstore.backup_file(paths.OPENDNSSEC_CONF_FILE)

        if not self.fstore.has_file(paths.OPENDNSSEC_KASP_FILE):
            self.fstore.backup_file(paths.OPENDNSSEC_KASP_FILE)

        pin_fd = open(paths.DNSSEC_SOFTHSM_PIN, "r")
        pin = pin_fd.read()
        pin_fd.close()

        # add pin to template
        sub_conf_dict = self.conf_file_dict
        sub_conf_dict['PIN'] = pin

        ods_conf_txt = ipautil.template_file(
            ipautil.SHARE_DIR + "opendnssec_conf.template", sub_conf_dict)
        ods_conf_fd = open(paths.OPENDNSSEC_CONF_FILE, 'w')
        ods_conf_fd.seek(0)
        ods_conf_fd.truncate(0)
        ods_conf_fd.write(ods_conf_txt)
        ods_conf_fd.close()

        ods_kasp_txt = ipautil.template_file(
            ipautil.SHARE_DIR + "opendnssec_kasp.template",
            self.kasp_file_dict)
        ods_kasp_fd = open(paths.OPENDNSSEC_KASP_FILE, 'w')
        ods_kasp_fd.seek(0)
        ods_kasp_fd.truncate(0)
        ods_kasp_fd.write(ods_kasp_txt)
        ods_kasp_fd.close()

        if not self.fstore.has_file(paths.SYSCONFIG_ODS):
            self.fstore.backup_file(paths.SYSCONFIG_ODS)

        installutils.set_directive(paths.SYSCONFIG_ODS,
                                   'SOFTHSM2_CONF',
                                   paths.DNSSEC_SOFTHSM2_CONF,
                                   quotes=False,
                                   separator='=')

    def __setup_ownership_file_modes(self):
        assert self.ods_uid is not None
        assert self.ods_gid is not None

        # workarounds for packaging bugs in opendnssec-1.4.5-2.fc20.x86_64
        # https://bugzilla.redhat.com/show_bug.cgi?id=1098188
        for (root, dirs, files) in os.walk(paths.ETC_OPENDNSSEC_DIR):
            for directory in dirs:
                dir_path = os.path.join(root, directory)
                os.chmod(dir_path, 0770)
                # chown to root:ods
                os.chown(dir_path, 0, self.ods_gid)
            for filename in files:
                file_path = os.path.join(root, filename)
                os.chmod(file_path, 0660)
                # chown to root:ods
                os.chown(file_path, 0, self.ods_gid)

        for (root, dirs, files) in os.walk(paths.VAR_OPENDNSSEC_DIR):
            for directory in dirs:
                dir_path = os.path.join(root, directory)
                os.chmod(dir_path, 0770)
                # chown to ods:ods
                os.chown(dir_path, self.ods_uid, self.ods_gid)
            for filename in files:
                file_path = os.path.join(root, filename)
                os.chmod(file_path, 0660)
                # chown to ods:ods
                os.chown(file_path, self.ods_uid, self.ods_gid)

    def __generate_master_key(self):

        with open(paths.DNSSEC_SOFTHSM_PIN, "r") as f:
            pin = f.read()

        os.environ["SOFTHSM2_CONF"] = paths.DNSSEC_SOFTHSM2_CONF
        p11 = _ipap11helper.P11_Helper(softhsm_slot, pin, paths.LIBSOFTHSM2_SO)
        try:
            # generate master key
            root_logger.debug("Creating master key")
            p11helper.generate_master_key(p11)

            # change tokens mod/owner
            root_logger.debug("Changing ownership of token files")
            for (root, dirs, files) in os.walk(paths.DNSSEC_TOKENS_DIR):
                for directory in dirs:
                    dir_path = os.path.join(root, directory)
                    os.chmod(dir_path, 0770 | stat.S_ISGID)
                    os.chown(dir_path, self.ods_uid,
                             self.named_gid)  # chown to ods:named
                for filename in files:
                    file_path = os.path.join(root, filename)
                    os.chmod(file_path, 0770 | stat.S_ISGID)
                    os.chown(file_path, self.ods_uid,
                             self.named_gid)  # chown to ods:named

        finally:
            p11.finalize()

    def __setup_dnssec(self):
        # run once only
        if self.get_state("KASP_DB_configured"):
            root_logger.debug("Already configured, skipping step")

        self.backup_state("KASP_DB_configured", True)

        if not self.fstore.has_file(paths.OPENDNSSEC_KASP_DB):
            self.fstore.backup_file(paths.OPENDNSSEC_KASP_DB)

        command = [paths.ODS_KSMUTIL, 'setup']

        ods_enforcerd = services.knownservices.ods_enforcerd
        ipautil.run(command, stdin="y", runas=ods_enforcerd.get_user_name())

    def __setup_dnskeysyncd(self):
        # set up dnskeysyncd this is DNSSEC master
        installutils.set_directive(paths.SYSCONFIG_IPA_DNSKEYSYNCD,
                                   'ISMASTER',
                                   '1',
                                   quotes=False,
                                   separator='=')

    def __start(self):
        self.restart()  # needed to reload conf files

    def uninstall(self):
        if not self.is_configured():
            return

        self.print_msg("Unconfiguring %s" % self.service_name)

        running = self.restore_state("running")
        enabled = self.restore_state("enabled")

        for f in [
                paths.OPENDNSSEC_CONF_FILE, paths.OPENDNSSEC_KASP_FILE,
                paths.OPENDNSSEC_KASP_DB, paths.SYSCONFIG_ODS
        ]:
            try:
                self.fstore.restore_file(f)
            except ValueError, error:
                root_logger.debug(error)
                pass

        # disabled by default, by ldap_enable()
        if enabled:
            self.enable()

        if running:
            self.restart()
Example #12
0
class Entry:
    """
    This class represents an LDAP Entry object.  An LDAP entry consists of
    a DN and a list of attributes.  Each attribute consists of a name and
    a list of values.  In python-ldap, entries are returned as a list of
    2-tuples.  Instance variables:

        * dn - DN object - the DN of the entry
        * data - CIDict - case insensitive dict of the attributes and values
    """
    def __init__(self,entrydata):
        """data is the raw data returned from the python-ldap result method, which is
        a search result entry or a reference or None.
        If creating a new empty entry, data is the string DN."""
        if entrydata:
            if isinstance(entrydata,tuple):
                self.dn = entrydata[0]
                self.data = ipautil.CIDict(entrydata[1])
            elif isinstance(entrydata,DN):
                self.dn = entrydata
                self.data = ipautil.CIDict()
            elif isinstance(entrydata, basestring):
                self.dn = DN(entrydata)
                self.data = ipautil.CIDict()
            else:
                raise TypeError("entrydata must be 2-tuple, DN, or basestring, got %s" % type(entrydata))
        else:
            self.dn = DN()
            self.data = ipautil.CIDict()

        assert isinstance(self.dn, DN)

    dn = ipautil.dn_attribute_property('_dn')

    def __nonzero__(self):
        """This allows us to do tests like if entry: returns false if there is no data,
        true otherwise"""
        return self.data != None and len(self.data) > 0

    def hasAttr(self,name):
        """Return True if this entry has an attribute named name, False otherwise"""
        return self.data and self.data.has_key(name)

    def getValues(self,name):
        """Get the list (array) of values for the attribute named name"""
        return self.data.get(name)

    def getValue(self,name, default=None):
        """Get the first value for the attribute named name"""
        value = self.data.get(name, default)
        if isinstance(value, (list, tuple)):
            return value[0]
        return value

    def setValue(self, name, *value):
        """
        Set a value on this entry.

        The value passed in may be a single value, several values, or a
        single sequence.  For example:

           * ent.setValue('name', 'value')
           * ent.setValue('name', 'value1', 'value2', ..., 'valueN')
           * ent.setValue('name', ['value1', 'value2', ..., 'valueN'])
           * ent.setValue('name', ('value1', 'value2', ..., 'valueN'))

        Since value is a tuple, we may have to extract a list or tuple from
        that tuple as in the last two examples above.
        """
        if isinstance(value[0],list) or isinstance(value[0],tuple):
            self.data[name] = value[0]
        else:
            self.data[name] = value

    setValues = setValue

    def delAttr(self, name):
        """
        Entirely remove an attribute of this entry.
        """
        if self.hasAttr(name):
            del self.data[name]

    def toTupleList(self):
        """Convert the attrs and values to a list of 2-tuples.  The first element
        of the tuple is the attribute name.  The second element is either a
        single value or a list of values."""
        r = []
        for i in self.data.iteritems():
            n = ipautil.utf8_encode_values(i[1])
            r.append((i[0], n))
        return r

    def toDict(self):
        """Convert the attrs and values to a dict. The dict is keyed on the
        attribute name.  The value is either single value or a list of values."""
        assert isinstance(self.dn, DN)
        result = ipautil.CIDict(self.data)
        for i in result.keys():
            result[i] = ipautil.utf8_encode_values(result[i])
        result['dn'] = self.dn
        return result

    def __str__(self):
        """Convert the Entry to its LDIF representation"""
        return self.__repr__()

    # the ldif class base64 encodes some attrs which I would rather see in
    # raw form - to encode specific attrs as base64, add them to the list below
    ldif.safe_string_re = re.compile('^$')
    base64_attrs = ['nsstate', 'krbprincipalkey', 'krbExtraData']

    def __repr__(self):
        """Convert the Entry to its LDIF representation"""
        sio = cStringIO.StringIO()
        # what's all this then?  the unparse method will currently only accept
        # a list or a dict, not a class derived from them.  self.data is a
        # cidict, so unparse barfs on it.  I've filed a bug against python-ldap,
        # but in the meantime, we have to convert to a plain old dict for
        # printing
        # I also don't want to see wrapping, so set the line width really high
        # (1000)
        newdata = {}
        newdata.update(self.data)
        ldif.LDIFWriter(sio,Entry.base64_attrs,1000).unparse(str(self.dn),newdata)
        return sio.getvalue()
Example #13
0
class HTTPInstance(service.Service):
    def __init__(self, fstore=None):
        service.Service.__init__(self,
                                 "httpd",
                                 service_desc="the web interface")
        if fstore:
            self.fstore = fstore
        else:
            self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore')

    subject_base = ipautil.dn_attribute_property('_subject_base')

    def create_instance(self,
                        realm,
                        fqdn,
                        domain_name,
                        dm_password=None,
                        autoconfig=True,
                        pkcs12_info=None,
                        self_signed_ca=False,
                        subject_base=None,
                        auto_redirect=True):
        self.fqdn = fqdn
        self.realm = realm
        self.domain = domain_name
        self.dm_password = dm_password
        self.suffix = ipautil.realm_to_suffix(self.realm)
        self.pkcs12_info = pkcs12_info
        self.self_signed_ca = self_signed_ca
        self.principal = "HTTP/%s@%s" % (self.fqdn, self.realm)
        self.dercert = None
        self.subject_base = subject_base
        self.sub_dict = dict(
            REALM=realm,
            FQDN=fqdn,
            DOMAIN=self.domain,
            AUTOREDIR='' if auto_redirect else '#',
            CRL_PUBLISH_PATH=dogtag.install_constants.CRL_PUBLISH_PATH,
        )

        # get a connection to the DS
        self.ldap_connect()

        self.step("setting mod_nss port to 443", self.__set_mod_nss_port)
        self.step("setting mod_nss protocol list to TLSv1.0 - TLSv1.2",
                  self.set_mod_nss_protocol)
        self.step("setting mod_nss password file",
                  self.__set_mod_nss_passwordfile)
        self.step("enabling mod_nss renegotiate",
                  self.enable_mod_nss_renegotiate)
        self.step("adding URL rewriting rules", self.__add_include)
        self.step("configuring httpd", self.__configure_http)
        self.step("setting up ssl", self.__setup_ssl)
        if autoconfig:
            self.step("setting up browser autoconfig", self.__setup_autoconfig)
        self.step("publish CA cert", self.__publish_ca_cert)
        self.step("creating a keytab for httpd", self.__create_http_keytab)
        self.step("clean up any existing httpd ccache",
                  self.remove_httpd_ccache)
        self.step("configuring SELinux for httpd",
                  self.configure_selinux_for_httpd)
        self.step("restarting httpd", self.__start)
        self.step("configuring httpd to start on boot", self.__enable)

        self.start_creation(runtime=60)

    def __start(self):
        self.backup_state("running", self.is_running())
        self.restart()

    def __enable(self):
        self.backup_state("enabled", self.is_running())
        # We do not let the system start IPA components on its own,
        # Instead we reply on the IPA init script to start only enabled
        # components as found in our LDAP configuration tree
        self.ldap_enable('HTTP', self.fqdn, self.dm_password, self.suffix)

    def configure_selinux_for_httpd(self):
        def get_setsebool_args(changes):
            if len(changes) == 1:
                # workaround https://bugzilla.redhat.com/show_bug.cgi?id=825163
                updates = changes.items()[0]
            else:
                updates = ["%s=%s" % update for update in changes.iteritems()]

            args = ["/usr/sbin/setsebool", "-P"]
            args.extend(updates)

            return args

        selinux = False
        try:
            if (os.path.exists('/usr/sbin/selinuxenabled')):
                ipautil.run(["/usr/sbin/selinuxenabled"])
                selinux = True
        except ipautil.CalledProcessError:
            # selinuxenabled returns 1 if not enabled
            pass

        if selinux:
            # Don't assume all vars are available
            updated_vars = {}
            failed_vars = {}
            required_settings = (("httpd_can_network_connect", "on"),
                                 ("httpd_manage_ipa", "on"))
            for setting, state in required_settings:
                try:
                    (stdout, stderr, returncode) = ipautil.run(
                        ["/usr/sbin/getsebool", setting])
                    original_state = stdout.split()[2]
                    self.backup_state(setting, original_state)

                    if original_state != state:
                        updated_vars[setting] = state
                except ipautil.CalledProcessError, e:
                    root_logger.debug("Cannot get SELinux boolean '%s': %s",
                                      setting, e)
                    failed_vars[setting] = state

            # Allow apache to connect to the dogtag UI and the session cache
            # This can still fail even if selinux is enabled. Execute these
            # together so it is speedier.
            if updated_vars:
                args = get_setsebool_args(updated_vars)
                try:
                    ipautil.run(args)
                except ipautil.CalledProcessError:
                    failed_vars.update(updated_vars)

            if failed_vars:
                args = get_setsebool_args(failed_vars)
                names = [update[0] for update in updated_vars]
                message = [
                    'WARNING: could not set the following SELinux boolean(s):'
                ]
                for update in failed_vars.iteritems():
                    message.append('  %s -> %s' % update)
                message.append(
                    'The web interface may not function correctly until the booleans'
                )
                message.append('are successfully changed with the command:')
                message.append(' '.join(args))
                message.append(
                    'Try updating the policycoreutils and selinux-policy packages.'
                )

                self.print_msg("\n".join(message))
Example #14
0
class DsInstance(service.Service):
    def __init__(self,
                 realm_name=None,
                 domain_name=None,
                 dm_password=None,
                 fstore=None):
        service.Service.__init__(self,
                                 "dirsrv",
                                 service_desc="directory server",
                                 dm_password=dm_password,
                                 ldapi=False,
                                 autobind=service.DISABLED)
        self.realm_name = realm_name
        self.sub_dict = None
        self.domain = domain_name
        self.serverid = None
        self.fqdn = None
        self.pkcs12_info = None
        self.dercert = None
        self.idstart = None
        self.idmax = None
        self.subject_base = None
        self.open_ports = []
        self.run_init_memberof = True
        if realm_name:
            self.suffix = ipautil.realm_to_suffix(self.realm_name)
            self.__setup_sub_dict()
        else:
            self.suffix = DN()

        if fstore:
            self.fstore = fstore
        else:
            self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore')

    subject_base = ipautil.dn_attribute_property('_subject_base')

    def __common_setup(self):

        self.step("creating directory server user", self.__create_ds_user)
        self.step("creating directory server instance", self.__create_instance)
        self.step("adding default schema", self.__add_default_schemas)
        self.step("enabling memberof plugin", self.__add_memberof_module)
        self.step("enabling winsync plugin", self.__add_winsync_module)
        self.step("configuring replication version plugin",
                  self.__config_version_module)
        self.step("enabling IPA enrollment plugin",
                  self.__add_enrollment_module)
        self.step("enabling ldapi", self.__enable_ldapi)
        self.step("disabling betxn plugins", self.__disable_betxn)
        self.step("configuring uniqueness plugin", self.__set_unique_attrs)
        self.step("configuring uuid plugin", self.__config_uuid_module)
        self.step("configuring modrdn plugin", self.__config_modrdn_module)
        self.step("enabling entryUSN plugin", self.__enable_entryusn)
        self.step("configuring lockout plugin", self.__config_lockout_module)
        self.step("creating indices", self.__create_indices)
        self.step("enabling referential integrity plugin",
                  self.__add_referint_module)
        self.step("configuring ssl for ds instance", self.__enable_ssl)
        self.step("configuring certmap.conf", self.__certmap_conf)
        self.step("configure autobind for root", self.__root_autobind)
        self.step("configure new location for managed entries",
                  self.__repoint_managed_entries)
        self.step("restarting directory server", self.__restart_instance)

    def __common_post_setup(self):
        self.step("initializing group membership", self.init_memberof)
        self.step("adding master entry", self.__add_master_entry)
        self.step("configuring Posix uid/gid generation",
                  self.__config_uidgid_gen)
        self.step("enabling compatibility plugin", self.__enable_compat_plugin)
        self.step("tuning directory server", self.__tuning)

        self.step("configuring directory to start on boot", self.__enable)

    def create_instance(self,
                        realm_name,
                        fqdn,
                        domain_name,
                        dm_password,
                        pkcs12_info=None,
                        self_signed_ca=False,
                        idstart=1100,
                        idmax=999999,
                        subject_base=None,
                        hbac_allow=True):
        self.realm_name = realm_name.upper()
        self.serverid = realm_to_serverid(self.realm_name)
        self.suffix = ipautil.realm_to_suffix(self.realm_name)
        self.fqdn = fqdn
        self.dm_password = dm_password
        self.domain = domain_name
        self.pkcs12_info = pkcs12_info
        self.self_signed_ca = self_signed_ca
        self.idstart = idstart
        self.idmax = idmax
        self.principal = "ldap/%s@%s" % (self.fqdn, self.realm_name)
        self.subject_base = subject_base

        self.__setup_sub_dict()
        self.__common_setup()

        self.step("adding default layout", self.__add_default_layout)
        self.step("adding delegation layout", self.__add_delegation_layout)
        self.step("adding replication acis", self.__add_replication_acis)
        self.step("creating container for managed entries",
                  self.__managed_entries)
        self.step("configuring user private groups",
                  self.__user_private_groups)
        self.step("configuring netgroups from hostgroups",
                  self.__host_nis_groups)
        self.step("creating default Sudo bind user", self.__add_sudo_binduser)
        self.step("creating default Auto Member layout",
                  self.__add_automember_config)
        self.step("adding range check plugin", self.__add_range_check_plugin)
        if hbac_allow:
            self.step("creating default HBAC rule allow_all", self.add_hbac)
        self.step("Upload CA cert to the directory", self.__upload_ca_cert)

        self.__common_post_setup()

        self.start_creation(runtime=60)

    def create_replica(self,
                       realm_name,
                       master_fqdn,
                       fqdn,
                       domain_name,
                       dm_password,
                       pkcs12_info=None):
        self.realm_name = realm_name.upper()
        self.serverid = realm_to_serverid(self.realm_name)
        self.suffix = ipautil.realm_to_suffix(self.realm_name)
        self.master_fqdn = master_fqdn
        self.fqdn = fqdn
        self.dm_password = dm_password
        self.domain = domain_name
        self.pkcs12_info = pkcs12_info
        self.principal = "ldap/%s@%s" % (self.fqdn, self.realm_name)

        self.self_signed_ca = False
        self.subject_base = None
        # 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.
        self.idstart = 1101
        self.idmax = 1100

        self.__setup_sub_dict()
        self.__common_setup()

        self.step("setting up initial replication", self.__setup_replica)
        self.step("adding replication acis", self.__add_replication_acis)
        # See LDIFs for automember configuration during replica install
        self.step("setting Auto Member configuration",
                  self.__add_replica_automember_config)
        self.step("enabling S4U2Proxy delegation", self.__setup_s4u2proxy)

        self.__common_post_setup()

        self.start_creation(runtime=60)

    def __setup_replica(self):
        replication.enable_replication_version_checking(
            self.fqdn, self.realm_name, self.dm_password)

        repl = replication.ReplicationManager(self.realm_name, self.fqdn,
                                              self.dm_password)
        repl.setup_replication(self.master_fqdn,
                               r_binddn=DN(('cn', 'Directory Manager')),
                               r_bindpw=self.dm_password)
        self.run_init_memberof = repl.needs_memberof_fixup()

    def __enable(self):
        self.backup_state("enabled", self.is_enabled())
        # At the end of the installation ipa-server-install will enable the
        # 'ipa' service wich takes care of starting/stopping dirsrv
        self.disable()

    def __setup_sub_dict(self):
        server_root = find_server_root()
        try:
            idrange_size = self.idmax - self.idstart + 1
        except TypeError:
            idrange_size = None
        self.sub_dict = dict(FQDN=self.fqdn,
                             SERVERID=self.serverid,
                             PASSWORD=self.dm_password,
                             RANDOM_PASSWORD=self.generate_random(),
                             SUFFIX=self.suffix,
                             REALM=self.realm_name,
                             USER=DS_USER,
                             SERVER_ROOT=server_root,
                             DOMAIN=self.domain,
                             TIME=int(time.time()),
                             IDSTART=self.idstart,
                             IDMAX=self.idmax,
                             HOST=self.fqdn,
                             ESCAPED_SUFFIX=str(self.suffix),
                             GROUP=DS_GROUP,
                             IDRANGE_SIZE=idrange_size)

    def __create_ds_user(self):
        try:
            pwd.getpwnam(DS_USER)
            root_logger.debug("ds user %s exists" % DS_USER)
        except KeyError:
            root_logger.debug("adding ds user %s" % DS_USER)
            args = [
                "/usr/sbin/useradd", "-g", DS_GROUP, "-c", "DS System User",
                "-d", "/var/lib/dirsrv", "-s", "/sbin/nologin", "-M", "-r",
                DS_USER
            ]
            try:
                ipautil.run(args)
                root_logger.debug("done adding user")
            except ipautil.CalledProcessError, e:
                root_logger.critical("failed to add user %s" % e)
Example #15
0
class BindInstance(service.Service):
    def __init__(self, fstore=None, dm_password=None):
        service.Service.__init__(self, "named",
            service_desc="DNS",
            dm_password=dm_password,
            ldapi=False,
            autobind=service.DISABLED
            )
        self.dns_backup = DnsBackup(self)
        self.named_user = None
        self.domain = None
        self.host = None
        self.ip_address = None
        self.realm = None
        self.forwarders = None
        self.sub_dict = None
        self.reverse_zone = None
        self.dm_password = dm_password

        if fstore:
            self.fstore = fstore
        else:
            self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore')

    suffix = ipautil.dn_attribute_property('_suffix')

    def setup(self, fqdn, ip_address, realm_name, domain_name, forwarders, ntp,
              reverse_zone, named_user="******", zonemgr=None,
              zone_refresh=0, persistent_search=True, serial_autoincrement=True):
        self.named_user = named_user
        self.fqdn = fqdn
        self.ip_address = ip_address
        self.realm = realm_name
        self.domain = domain_name
        self.forwarders = forwarders
        self.host = fqdn.split(".")[0]
        self.suffix = ipautil.realm_to_suffix(self.realm)
        self.ntp = ntp
        self.reverse_zone = reverse_zone
        self.zone_refresh = zone_refresh
        self.persistent_search = persistent_search
        self.serial_autoincrement = serial_autoincrement

        if not zonemgr:
            self.zonemgr = 'hostmaster.%s' % self.domain
        else:
            self.zonemgr = normalize_zonemgr(zonemgr)

        self.__setup_sub_dict()

    @property
    def host_domain(self):
        return '.'.join(self.fqdn.split(".")[1:])

    @property
    def host_in_rr(self):
        # when a host is not in a default domain, it needs to be referred
        # with FQDN and not in a domain-relative host name
        if not self.host_in_default_domain():
            return normalize_zone(self.fqdn)
        return self.host

    def host_in_default_domain(self):
        return normalize_zone(self.host_domain) == normalize_zone(self.domain)

    def create_sample_bind_zone(self):
        bind_txt = ipautil.template_file(ipautil.SHARE_DIR + "bind.zone.db.template", self.sub_dict)
        [bind_fd, bind_name] = tempfile.mkstemp(".db","sample.zone.")
        os.write(bind_fd, bind_txt)
        os.close(bind_fd)
        print "Sample zone file for bind has been created in "+bind_name

    def create_instance(self):

        try:
            self.stop()
        except:
            pass

        # get a connection to the DS
        self.ldap_connect()

        if installutils.record_in_hosts(self.ip_address, self.fqdn) is None:
            installutils.add_record_to_hosts(self.ip_address, self.fqdn)

        if not dns_container_exists(self.fqdn, self.suffix, realm=self.realm,
                                    ldapi=True, dm_password=self.dm_password):
            self.step("adding DNS container", self.__setup_dns_container)
        if dns_zone_exists(self.domain):
            self.step("adding NS record to the zone", self.__add_self_ns)
        else:
            self.step("setting up our zone", self.__setup_zone)
        if self.reverse_zone is not None:
            self.step("setting up reverse zone", self.__setup_reverse_zone)
        self.step("setting up our own record", self.__add_self)

        self.step("setting up kerberos principal", self.__setup_principal)
        self.step("setting up named.conf", self.__setup_named_conf)

        self.step("restarting named", self.__start)
        self.step("configuring named to start on boot", self.__enable)

        self.step("changing resolv.conf to point to ourselves", self.__setup_resolv_conf)
        self.start_creation()

    def __start(self):
        try:
            self.backup_state("running", self.is_running())
            self.restart()
        except:
            print "named service failed to start"

    def __enable(self):
        self.backup_state("enabled", self.is_running())
        # We do not let the system start IPA components on its own,
        # Instead we reply on the IPA init script to start only enabled
        # components as found in our LDAP configuration tree
        try:
            self.ldap_enable('DNS', self.fqdn, self.dm_password, self.suffix)
        except errors.DuplicateEntry:
            # service already exists (forced DNS reinstall)
            # don't crash, just report error
            root_logger.error("DNS service already exists")

    def __setup_sub_dict(self):
        if self.forwarders:
            fwds = "\n"
            for forwarder in self.forwarders:
                fwds += "\t\t%s;\n" % forwarder
            fwds += "\t"
        else:
            fwds = " "

        if self.ntp:
            optional_ntp =  "\n;ntp server\n"
            optional_ntp += "_ntp._udp\t\tIN SRV 0 100 123\t%s" % self.host_in_rr
        else:
            optional_ntp = ""

        boolean_var = {}
        for var in ('persistent_search', 'serial_autoincrement'):
            boolean_var[var] = "yes" if getattr(self, var, False) else "no"

        self.sub_dict = dict(FQDN=self.fqdn,
                             IP=self.ip_address,
                             DOMAIN=self.domain,
                             HOST=self.host,
                             REALM=self.realm,
                             SERVER_ID=realm_to_serverid(self.realm),
                             FORWARDERS=fwds,
                             SUFFIX=self.suffix,
                             OPTIONAL_NTP=optional_ntp,
                             ZONEMGR=self.zonemgr,
                             ZONE_REFRESH=self.zone_refresh,
                             PERSISTENT_SEARCH=boolean_var['persistent_search'],
                             SERIAL_AUTOINCREMENT=boolean_var['serial_autoincrement'],)

    def __setup_dns_container(self):
        self._ldap_mod("dns.ldif", self.sub_dict)

    def __setup_zone(self):
        nameserver_ip_address = self.ip_address
        if not self.host_in_default_domain():
            # add DNS domain for host first
            root_logger.debug("Host domain (%s) is different from DNS domain (%s)!" \
                    % (self.host_domain, self.domain))
            root_logger.debug("Add DNS zone for host first.")

            add_zone(self.host_domain, self.zonemgr, dns_backup=self.dns_backup,
                    ns_hostname=api.env.host, ns_ip_address=self.ip_address, force=True)
            # Nameserver is in self.host_domain, no forward record added to self.domain
            nameserver_ip_address = None
        # Always use force=True as named is not set up yet
        add_zone(self.domain, self.zonemgr, dns_backup=self.dns_backup,
                ns_hostname=api.env.host, ns_ip_address=nameserver_ip_address,
                force=True)

    def __add_self_ns(self):
        add_ns_rr(self.domain, api.env.host, self.dns_backup, force=True)

    def __add_self(self):
        zone = self.domain
        resource_records = (
            ("_ldap._tcp", "SRV", "0 100 389 %s" % self.host_in_rr),
            ("_kerberos", "TXT", self.realm),
            ("_kerberos._tcp", "SRV", "0 100 88 %s" % self.host_in_rr),
            ("_kerberos._udp", "SRV", "0 100 88 %s" % self.host_in_rr),
            ("_kerberos-master._tcp", "SRV", "0 100 88 %s" % self.host_in_rr),
            ("_kerberos-master._udp", "SRV", "0 100 88 %s" % self.host_in_rr),
            ("_kpasswd._tcp", "SRV", "0 100 464 %s" % self.host_in_rr),
            ("_kpasswd._udp", "SRV", "0 100 464 %s" % self.host_in_rr),
        )

        for (host, type, rdata) in resource_records:
            if type == "SRV":
                add_rr(zone, host, type, rdata, self.dns_backup)
            else:
                add_rr(zone, host, type, rdata)
        if self.ntp:
            add_rr(zone, "_ntp._udp", "SRV", "0 100 123 %s" % self.host_in_rr)

        # Add forward and reverse records to self
        add_fwd_rr(self.host_domain, self.host, self.ip_address)
        if self.reverse_zone is not None and dns_zone_exists(self.reverse_zone):
            add_ptr_rr(self.reverse_zone, self.ip_address, self.fqdn)

    def __setup_reverse_zone(self):
        # Always use force=True as named is not set up yet
        add_zone(self.reverse_zone, self.zonemgr, ns_hostname=api.env.host,
                dns_backup=self.dns_backup, force=True)

    def __setup_principal(self):
        dns_principal = "DNS/" + self.fqdn + "@" + self.realm
        installutils.kadmin_addprinc(dns_principal)

        # Store the keytab on disk
        self.fstore.backup_file("/etc/named.keytab")
        installutils.create_keytab("/etc/named.keytab", dns_principal)
        p = self.move_service(dns_principal)
        if p is None:
            # the service has already been moved, perhaps we're doing a DNS reinstall
            dns_principal = DN(('krbprincipalname', dns_principal),
                               ('cn', 'services'), ('cn', 'accounts'), self.suffix)
        else:
            dns_principal = p

        # Make sure access is strictly reserved to the named user
        pent = pwd.getpwnam(self.named_user)
        os.chown("/etc/named.keytab", pent.pw_uid, pent.pw_gid)
        os.chmod("/etc/named.keytab", 0400)

        # modify the principal so that it is marked as an ipa service so that
        # it can host the memberof attribute, then also add it to the
        # dnsserver role group, this way the DNS is allowed to perform
        # DNS Updates
        dns_group = DN(('cn', 'DNS Servers'), ('cn', 'privileges'), ('cn', 'pbac'), self.suffix)
        mod = [(ldap.MOD_ADD, 'member', dns_principal)]

        try:
            self.admin_conn.modify_s(dns_group, mod)
        except ldap.TYPE_OR_VALUE_EXISTS:
            pass
        except Exception, e:
            root_logger.critical("Could not modify principal's %s entry: %s" \
                    % (dns_principal, str(e)))
            raise

        # bind-dyndb-ldap persistent search feature requires both size and time
        # limit-free connection
        mod = [(ldap.MOD_REPLACE, 'nsTimeLimit', '-1'),
               (ldap.MOD_REPLACE, 'nsSizeLimit', '-1'),
               (ldap.MOD_REPLACE, 'nsIdleTimeout', '-1'),
               (ldap.MOD_REPLACE, 'nsLookThroughLimit', '-1')]
        try:
            self.admin_conn.modify_s(dns_principal, mod)
        except Exception, e:
            root_logger.critical("Could not set principal's %s LDAP limits: %s" \
                    % (dns_principal, str(e)))
            raise
Example #16
0
class CertDB(object):
    """An IPA-server-specific wrapper around NSS

    This class knows IPA-specific details such as nssdir location, or the
    CA cert name.

    ``subject_base``
      Realm subject base DN.  This argument is required when creating
      server or object signing certs.
    ``ca_subject``
      IPA CA subject DN.  This argument is required when importing
      CA certificates into the certificate database.

    """
    # TODO: Remove all selfsign code
    def __init__(self, realm, nssdir=paths.IPA_RADB_DIR, fstore=None,
                 host_name=None, subject_base=None, ca_subject=None,
                 user=None, group=None, mode=None, truncate=False):
        self.nssdb = NSSDatabase(nssdir)

        self.secdir = nssdir
        self.realm = realm

        self.noise_fname = self.secdir + "/noise.txt"
        self.certdb_fname = self.secdir + "/cert8.db"
        self.keydb_fname = self.secdir + "/key3.db"
        self.secmod_fname = self.secdir + "/secmod.db"
        self.pk12_fname = self.secdir + "/cacert.p12"
        self.pin_fname = self.secdir + "/pin.txt"
        self.reqdir = None
        self.certreq_fname = None
        self.certder_fname = None
        self.host_name = host_name
        self.ca_subject = ca_subject
        self.subject_base = subject_base
        try:
            self.cwd = os.getcwd()
        except OSError as e:
            raise RuntimeError("Unable to determine the current directory: %s" % str(e))

        self.cacert_name = get_ca_nickname(self.realm)

        self.user = user
        self.group = group
        self.mode = mode
        self.uid = 0
        self.gid = 0

        if not truncate and os.path.exists(self.secdir):
            # We are going to set the owner of all of the cert
            # files to the owner of the containing directory
            # instead of that of the process. This works when
            # this is called by root for a daemon that runs as
            # a normal user
            mode = os.stat(self.secdir)
            self.uid = mode[stat.ST_UID]
            self.gid = mode[stat.ST_GID]
        else:
            if user is not None:
                pu = pwd.getpwnam(user)
                self.uid = pu.pw_uid
                self.gid = pu.pw_gid
            if group is not None:
                self.gid = grp.getgrnam(group).gr_gid
            self.create_certdbs()

        if fstore:
            self.fstore = fstore
        else:
            self.fstore = sysrestore.FileStore(paths.SYSRESTORE)

    ca_subject = ipautil.dn_attribute_property('_ca_subject')
    subject_base = ipautil.dn_attribute_property('_subject_base')

    @property
    def passwd_fname(self):
        return self.nssdb.pwd_file

    def __del__(self):
        if self.reqdir is not None:
            shutil.rmtree(self.reqdir, ignore_errors=True)
        try:
            os.chdir(self.cwd)
        except OSError:
            pass

    def setup_cert_request(self):
        """
        Create a temporary directory to store certificate requests and
        certificates. This should be called before requesting certificates.

        This is set outside of __init__ to avoid creating a temporary
        directory every time we open a cert DB.
        """
        if self.reqdir is not None:
            return

        self.reqdir = tempfile.mkdtemp('', 'ipa-', paths.VAR_LIB_IPA)
        self.certreq_fname = self.reqdir + "/tmpcertreq"
        self.certder_fname = self.reqdir + "/tmpcert.der"

        # When certutil makes a request it creates a file in the cwd, make
        # sure we are in a unique place when this happens
        os.chdir(self.reqdir)

    def set_perms(self, fname, write=False, uid=None):
        if uid:
            pent = pwd.getpwnam(uid)
            os.chown(fname, pent.pw_uid, pent.pw_gid)
        else:
            os.chown(fname, self.uid, self.gid)
        perms = stat.S_IRUSR
        if write:
            perms |= stat.S_IWUSR
        os.chmod(fname, perms)

    def run_certutil(self, args, stdin=None, **kwargs):
        return self.nssdb.run_certutil(args, stdin, **kwargs)

    def run_signtool(self, args, stdin=None):
        with open(self.passwd_fname, "r") as f:
            password = f.readline()
        new_args = [paths.SIGNTOOL, "-d", self.secdir, "-p", password]

        new_args = new_args + args
        ipautil.run(new_args, stdin)

    def create_noise_file(self):
        if ipautil.file_exists(self.noise_fname):
            os.remove(self.noise_fname)
        f = open(self.noise_fname, "w")
        f.write(ipautil.ipa_generate_password())
        self.set_perms(self.noise_fname)

    def create_passwd_file(self, passwd=None):
        ipautil.backup_file(self.passwd_fname)
        f = open(self.passwd_fname, "w")
        if passwd is not None:
            f.write("%s\n" % passwd)
        else:
            f.write(ipautil.ipa_generate_password())
        f.close()
        self.set_perms(self.passwd_fname)

    def create_certdbs(self):
        self.nssdb.create_db(user=self.user, group=self.group, mode=self.mode,
                             backup=True)
        self.set_perms(self.passwd_fname, write=True)

    def list_certs(self):
        """
        Return a tuple of tuples containing (nickname, trust)
        """
        return self.nssdb.list_certs()

    def has_nickname(self, nickname):
        """
        Returns True if nickname exists in the certdb, False otherwise.

        This could also be done directly with:
            certutil -L -d -n <nickname> ...
        """

        certs = self.list_certs()

        for cert in certs:
            if nickname == cert[0]:
                return True

        return False

    def export_ca_cert(self, nickname, create_pkcs12=False):
        """create_pkcs12 tells us whether we should create a PKCS#12 file
           of the CA or not. If we are running on a replica then we won't
           have the private key to make a PKCS#12 file so we don't need to
           do that step."""
        cacert_fname = paths.IPA_CA_CRT
        # export the CA cert for use with other apps
        ipautil.backup_file(cacert_fname)
        root_nicknames = self.find_root_cert(nickname)[:-1]
        fd = open(cacert_fname, "w")
        for root in root_nicknames:
            result = self.run_certutil(["-L", "-n", root, "-a"],
                                       capture_output=True)
            fd.write(result.output)
        fd.close()
        os.chmod(cacert_fname, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
        if create_pkcs12:
            ipautil.backup_file(self.pk12_fname)
            ipautil.run([paths.PK12UTIL, "-d", self.secdir,
                         "-o", self.pk12_fname,
                         "-n", self.cacert_name,
                         "-w", self.passwd_fname,
                         "-k", self.passwd_fname])
            self.set_perms(self.pk12_fname)

    def load_cacert(self, cacert_fname, trust_flags):
        """
        Load all the certificates from a given file. It is assumed that
        this file creates CA certificates.
        """
        fd = open(cacert_fname)
        certs = fd.read()
        fd.close()

        st = 0
        while True:
            try:
                (cert, st) = find_cert_from_txt(certs, st)
                _rdn, subject_dn = get_cert_nickname(cert)
                if subject_dn == self.ca_subject:
                    nick = get_ca_nickname(self.realm)
                else:
                    nick = str(subject_dn)
                self.nssdb.add_cert(cert, nick, trust_flags, pem=True)
            except RuntimeError:
                break

    def get_cert_from_db(self, nickname, pem=True):
        """
        Retrieve a certificate from the current NSS database for nickname.

        pem controls whether the value returned PEM or DER-encoded. The
        default is the data straight from certutil -a.
        """
        try:
            args = ["-L", "-n", nickname, "-a"]
            result = self.run_certutil(args, capture_output=True)
            cert = result.output
            if pem:
                return cert
            else:
                cert, _start = find_cert_from_txt(cert, start=0)
                cert = x509.strip_header(cert)
                dercert = base64.b64decode(cert)
                return dercert
        except ipautil.CalledProcessError:
            return ''

    def track_server_cert(self, nickname, principal, password_file=None, command=None):
        """
        Tell certmonger to track the given certificate nickname.

        If command is not a full path then it is prefixed with
        /usr/lib[64]/ipa/certmonger.
        """
        if command is not None and not os.path.isabs(command):
            command = paths.CERTMONGER_COMMAND_TEMPLATE % (command)
        try:
            request_id = certmonger.start_tracking(nickname, self.secdir, password_file, command)
        except RuntimeError as e:
            root_logger.error("certmonger failed starting to track certificate: %s" % str(e))
            return

        cert = self.get_cert_from_db(nickname)
        cert_obj = x509.load_certificate(cert)
        subject = str(DN(cert_obj.subject))
        certmonger.add_principal(request_id, principal)
        certmonger.add_subject(request_id, subject)

    def untrack_server_cert(self, nickname):
        """
        Tell certmonger to stop tracking the given certificate nickname.
        """
        try:
            certmonger.stop_tracking(self.secdir, nickname=nickname)
        except RuntimeError as e:
            root_logger.error("certmonger failed to stop tracking certificate: %s" % str(e))

    def create_server_cert(self, nickname, hostname, other_certdb=None, subject=None):
        """
        If we are using a dogtag CA then other_certdb contains the RA agent key
        that will issue our cert.

        You can override the certificate Subject by specifying a subject.

        Returns a certificate in DER format.
        """
        cdb = other_certdb
        if not cdb:
            cdb = self
        if subject is None:
            subject=DN(('CN', hostname), self.subject_base)
        self.request_cert(subject, san_dnsnames=[hostname])
        cdb.issue_server_cert(self.certreq_fname, self.certder_fname)
        self.import_cert(self.certder_fname, nickname)
        fd = open(self.certder_fname, "r")
        dercert = fd.read()
        fd.close()

        os.unlink(self.certreq_fname)
        os.unlink(self.certder_fname)

        return dercert

    def request_cert(
            self, subject, certtype="rsa", keysize="2048",
            san_dnsnames=None):
        assert isinstance(subject, DN)
        self.create_noise_file()
        self.setup_cert_request()
        args = ["-R", "-s", str(subject),
                "-o", self.certreq_fname,
                "-k", certtype,
                "-g", keysize,
                "-z", self.noise_fname,
                "-f", self.passwd_fname,
                "-a"]
        if san_dnsnames is not None and len(san_dnsnames) > 0:
            args += ['-8', ','.join(san_dnsnames)]
        result = self.run_certutil(args,
                                   capture_output=True, capture_error=True)
        os.remove(self.noise_fname)
        return (result.output, result.error_output)

    def issue_server_cert(self, certreq_fname, cert_fname):
        self.setup_cert_request()

        if self.host_name is None:
            raise RuntimeError("CA Host is not set.")

        f = open(certreq_fname, "r")
        csr = f.readlines()
        f.close()
        csr = "".join(csr)

        # We just want the CSR bits, make sure there is nothing else
        csr = pkcs10.strip_header(csr)

        params = {'profileId': dogtag.DEFAULT_PROFILE,
                'cert_request_type': 'pkcs10',
                'requestor_name': 'IPA Installer',
                'cert_request': csr,
                'xmlOutput': 'true'}

        # Send the request to the CA
        f = open(self.passwd_fname, "r")
        password = f.readline()
        f.close()
        result = dogtag.https_request(
            self.host_name, 8443, "/ca/ee/ca/profileSubmitSSLClient",
            self.secdir, password, "ipaCert", **params)
        http_status, _http_headers, http_body = result
        root_logger.debug("CA answer: %s", http_body)

        if http_status != 200:
            raise CertificateOperationError(
                error=_('Unable to communicate with CMS (status %d)') % http_status)

        # The result is an XML blob. Pull the certificate out of that
        doc = xml.dom.minidom.parseString(http_body)
        item_node = doc.getElementsByTagName("b64")
        try:
            try:
                cert = item_node[0].childNodes[0].data
            except IndexError:
                raise RuntimeError("Certificate issuance failed")
        finally:
            doc.unlink()

        # base64-decode the result for uniformity
        cert = base64.b64decode(cert)

        # Write the certificate to a file. It will be imported in a later
        # step. This file will be read later to be imported.
        f = open(cert_fname, "w")
        f.write(cert)
        f.close()

    def issue_signing_cert(self, certreq_fname, cert_fname):
        self.setup_cert_request()

        if self.host_name is None:
            raise RuntimeError("CA Host is not set.")

        f = open(certreq_fname, "r")
        csr = f.readlines()
        f.close()
        csr = "".join(csr)

        # We just want the CSR bits, make sure there is no thing else
        csr = pkcs10.strip_header(csr)

        params = {'profileId': 'caJarSigningCert',
                'cert_request_type': 'pkcs10',
                'requestor_name': 'IPA Installer',
                'cert_request': csr,
                'xmlOutput': 'true'}

        # Send the request to the CA
        f = open(self.passwd_fname, "r")
        password = f.readline()
        f.close()
        result = dogtag.https_request(
            self.host_name, 8443, "/ca/ee/ca/profileSubmitSSLClient",
            self.secdir, password, "ipaCert", **params)
        http_status, _http_headers, http_body = result
        if http_status != 200:
            raise RuntimeError("Unable to submit cert request")

        # The result is an XML blob. Pull the certificate out of that
        doc = xml.dom.minidom.parseString(http_body)
        item_node = doc.getElementsByTagName("b64")
        cert = item_node[0].childNodes[0].data
        doc.unlink()

        # base64-decode the cert for uniformity
        cert = base64.b64decode(cert)

        # Write the certificate to a file. It will be imported in a later
        # step. This file will be read later to be imported.
        f = open(cert_fname, "w")
        f.write(cert)
        f.close()

    def add_cert(self, cert, nick, flags, pem=False):
        self.nssdb.add_cert(cert, nick, flags, pem)

    def import_cert(self, cert_fname, nickname):
        """
        Load a certificate from a PEM file and add minimal trust.
        """
        args = ["-A", "-n", nickname,
                "-t", "u,u,u",
                "-i", cert_fname,
                "-f", self.passwd_fname]
        self.run_certutil(args)

    def delete_cert(self, nickname):
        self.nssdb.delete_cert(nickname)

    def create_pin_file(self):
        """
        This is the format of Directory Server pin files.
        """
        ipautil.backup_file(self.pin_fname)
        f = open(self.pin_fname, "w")
        f.write("Internal (Software) Token:")
        pwdfile = open(self.passwd_fname)
        f.write(pwdfile.read())
        f.close()
        pwdfile.close()
        self.set_perms(self.pin_fname)

    def find_root_cert(self, nickname):
        """
        Given a nickname, return a list of the certificates that make up
        the trust chain.
        """
        root_nicknames = self.nssdb.get_trust_chain(nickname)

        return root_nicknames

    def trust_root_cert(self, root_nickname, trust_flags=None):
        if root_nickname is None:
            root_logger.debug("Unable to identify root certificate to trust. Continuing but things are likely to fail.")
            return

        try:
            self.nssdb.trust_root_cert(root_nickname, trust_flags)
        except RuntimeError:
            pass

    def find_server_certs(self):
        return self.nssdb.find_server_certs()

    def import_pkcs12(self, pkcs12_fname, pkcs12_passwd=None):
        return self.nssdb.import_pkcs12(pkcs12_fname,
                                        pkcs12_passwd=pkcs12_passwd)

    def export_pkcs12(self, pkcs12_fname, pkcs12_pwd_fname, nickname=None):
        if nickname is None:
            nickname = get_ca_nickname(api.env.realm)

        ipautil.run([paths.PK12UTIL, "-d", self.secdir,
                     "-o", pkcs12_fname,
                     "-n", nickname,
                     "-k", self.passwd_fname,
                     "-w", pkcs12_pwd_fname])

    def export_pem_p12(self, pkcs12_fname, pkcs12_pwd_fname,
                       nickname, pem_fname):
        ipautil.run([paths.OPENSSL, "pkcs12",
                     "-export", "-name", nickname,
                     "-in", pem_fname, "-out", pkcs12_fname,
                     "-passout", "file:" + pkcs12_pwd_fname])

    def create_from_cacert(self):
        cacert_fname = paths.IPA_CA_CRT
        if ipautil.file_exists(self.certdb_fname):
            # We already have a cert db, see if it is for the same CA.
            # If it is we leave things as they are.
            f = open(cacert_fname, "r")
            newca = f.readlines()
            f.close()
            newca = "".join(newca)
            newca, _st = find_cert_from_txt(newca)

            cacert = self.get_cert_from_db(self.cacert_name)
            if cacert != '':
                cacert, _st = find_cert_from_txt(cacert)

            if newca == cacert:
                return

        # The CA certificates are different or something went wrong. Start with
        # a new certificate database.
        self.create_passwd_file()
        self.create_certdbs()
        self.load_cacert(cacert_fname, 'CT,C,C')

    def create_from_pkcs12(self, pkcs12_fname, pkcs12_passwd, passwd=None,
                           ca_file=None, trust_flags=None):
        """Create a new NSS database using the certificates in a PKCS#12 file.

           pkcs12_fname: the filename of the PKCS#12 file
           pkcs12_pwd_fname: the file containing the pin for the PKCS#12 file
           nickname: the nickname/friendly-name of the cert we are loading
           passwd: The password to use for the new NSS database we are creating

           The global CA may be added as well in case it wasn't included in the
           PKCS#12 file. Extra certs won't hurt in any case.

           The global CA may be specified in ca_file, as a PEM filename.
        """
        self.create_noise_file()
        self.create_passwd_file(passwd)
        self.create_certdbs()
        self.init_from_pkcs12(
            pkcs12_fname,
            pkcs12_passwd,
            ca_file=ca_file,
            trust_flags=trust_flags)

    def init_from_pkcs12(self, pkcs12_fname, pkcs12_passwd,
                         ca_file=None, trust_flags=None):
        self.import_pkcs12(pkcs12_fname, pkcs12_passwd)
        server_certs = self.find_server_certs()
        if len(server_certs) == 0:
            raise RuntimeError("Could not find a suitable server cert in import in %s" % pkcs12_fname)

        if ca_file:
            try:
                with open(ca_file) as fd:
                    certs = fd.read()
            except IOError as e:
                raise RuntimeError(
                    "Failed to open %s: %s" % (ca_file, e.strerror))
            st = 0
            num = 1
            while True:
                try:
                    cert, st = find_cert_from_txt(certs, st)
                except RuntimeError:
                    break
                self.add_cert(cert, 'CA %s' % num, ',,', pem=True)
                num += 1

        # We only handle one server cert
        nickname = server_certs[0][0]

        ca_names = self.find_root_cert(nickname)[:-1]
        if len(ca_names) == 0:
            raise RuntimeError("Could not find a CA cert in %s" % pkcs12_fname)

        self.cacert_name = ca_names[-1]
        self.trust_root_cert(self.cacert_name, trust_flags)

        self.create_pin_file()
        self.export_ca_cert(nickname, False)

    def install_pem_from_p12(self, p12_fname, p12_passwd, pem_fname):
        pwd = ipautil.write_tmp_file(p12_passwd)
        ipautil.run([paths.OPENSSL, "pkcs12", "-nokeys",
                     "-in", p12_fname, "-out", pem_fname,
                     "-passin", "file:" + pwd.name])

    def install_key_from_p12(self, p12_fname, p12_passwd, pem_fname):
        pwd = ipautil.write_tmp_file(p12_passwd)
        ipautil.run([paths.OPENSSL, "pkcs12", "-nodes", "-nocerts",
                     "-in", p12_fname, "-out", pem_fname,
                     "-passin", "file:" + pwd.name])

    def publish_ca_cert(self, location):
        self.nssdb.publish_ca_cert(self.cacert_name, location)

    def export_pem_cert(self, nickname, location):
        return self.nssdb.export_pem_cert(nickname, location)

    def request_service_cert(self, nickname, principal, host):
        certmonger.request_and_wait_for_cert(certpath=self.secdir,
                                             nickname=nickname,
                                             principal=principal,
                                             subject=host,
                                             passwd_fname=self.passwd_fname)
Example #17
0
class BindInstance(service.Service):
    def __init__(self, fstore=None, dm_password=None, api=api, ldapi=False,
                 start_tls=False, autobind=ipaldap.AUTOBIND_DISABLED):
        service.Service.__init__(
            self, "named",
            service_desc="DNS",
            dm_password=dm_password,
            ldapi=ldapi,
            autobind=autobind,
            start_tls=start_tls
        )
        self.dns_backup = DnsBackup(self)
        self.named_user = None
        self.domain = None
        self.host = None
        self.ip_addresses = []
        self.realm = None
        self.forwarders = None
        self.sub_dict = None
        self.reverse_zones = []
        self.dm_password = dm_password
        self.api = api
        self.named_regular = services.service('named-regular')

        if fstore:
            self.fstore = fstore
        else:
            self.fstore = sysrestore.FileStore(paths.SYSRESTORE)

    suffix = ipautil.dn_attribute_property('_suffix')

    def setup(self, fqdn, ip_addresses, realm_name, domain_name, forwarders, ntp,
              reverse_zones, named_user="******", zonemgr=None,
              ca_configured=None, no_dnssec_validation=False):
        self.named_user = named_user
        self.fqdn = fqdn
        self.ip_addresses = ip_addresses
        self.realm = realm_name
        self.domain = domain_name
        self.forwarders = forwarders
        self.host = fqdn.split(".")[0]
        self.suffix = ipautil.realm_to_suffix(self.realm)
        self.ntp = ntp
        self.reverse_zones = reverse_zones
        self.ca_configured = ca_configured
        self.no_dnssec_validation=no_dnssec_validation

        if not zonemgr:
            self.zonemgr = 'hostmaster.%s' % normalize_zone(self.domain)
        else:
            self.zonemgr = normalize_zonemgr(zonemgr)

        self.first_instance = not dns_container_exists(
            self.fqdn, self.suffix, realm=self.realm, ldapi=True,
            dm_password=self.dm_password, autobind=self.autobind)

        self.__setup_sub_dict()

    @property
    def host_domain(self):
        return self.fqdn.split(".", 1)[1]

    @property
    def host_in_rr(self):
        # when a host is not in a default domain, it needs to be referred
        # with FQDN and not in a domain-relative host name
        if not self.host_in_default_domain():
            return normalize_zone(self.fqdn)
        return self.host

    def host_in_default_domain(self):
        return normalize_zone(self.host_domain) == normalize_zone(self.domain)

    def create_sample_bind_zone(self):
        bind_txt = ipautil.template_file(ipautil.SHARE_DIR + "bind.zone.db.template", self.sub_dict)
        [bind_fd, bind_name] = tempfile.mkstemp(".db","sample.zone.")
        os.write(bind_fd, bind_txt)
        os.close(bind_fd)
        print "Sample zone file for bind has been created in "+bind_name

    def create_instance(self):

        try:
            self.stop()
        except:
            pass

        # get a connection to the DS
        self.ldap_connect()

        for ip_address in self.ip_addresses:
            if installutils.record_in_hosts(str(ip_address), self.fqdn) is None:
                installutils.add_record_to_hosts(str(ip_address), self.fqdn)

        # Make sure generate-rndc-key.sh runs before named restart
        self.step("generating rndc key file", self.__generate_rndc_key)

        if self.first_instance:
            self.step("adding DNS container", self.__setup_dns_container)

        if not dns_zone_exists(self.domain):
            self.step("setting up our zone", self.__setup_zone)
        if self.reverse_zones:
            self.step("setting up reverse zone", self.__setup_reverse_zone)

        self.step("setting up our own record", self.__add_self)
        if self.first_instance:
            self.step("setting up records for other masters", self.__add_others)
        # all zones must be created before this step
        self.step("adding NS record to the zones", self.__add_self_ns)
        self.step("setting up CA record", self.__add_ipa_ca_record)

        self.step("setting up kerberos principal", self.__setup_principal)
        self.step("setting up named.conf", self.__setup_named_conf)

        # named has to be started after softhsm initialization
        # self.step("restarting named", self.__start)

        self.step("configuring named to start on boot", self.__enable)
        self.step("changing resolv.conf to point to ourselves", self.__setup_resolv_conf)
        self.start_creation()

    def start_named(self):
        self.print_msg("Restarting named")
        self.__start()

    def __start(self):
        try:
            if self.get_state("running") is None:
                # first time store status
                self.backup_state("running", self.is_running())
            self.restart()
        except Exception as e:
            root_logger.error("Named service failed to start (%s)", e)
            print "named service failed to start"

    def __enable(self):
        if self.get_state("enabled") is None:
            self.backup_state("enabled", self.is_running())
            self.backup_state("named-regular-enabled",
                              self.named_regular.is_running())
        # We do not let the system start IPA components on its own,
        # Instead we reply on the IPA init script to start only enabled
        # components as found in our LDAP configuration tree
        try:
            self.ldap_enable('DNS', self.fqdn, self.dm_password, self.suffix)
        except errors.DuplicateEntry:
            # service already exists (forced DNS reinstall)
            # don't crash, just report error
            root_logger.error("DNS service already exists")

        # disable named, we need to run named-pkcs11 only
        if self.get_state("named-regular-running") is None:
            # first time store status
            self.backup_state("named-regular-running",
                              self.named_regular.is_running())
        try:
            self.named_regular.stop()
        except Exception as e:
            root_logger.debug("Unable to stop named (%s)", e)

        try:
            self.named_regular.mask()
        except Exception as e:
            root_logger.debug("Unable to mask named (%s)", e)

    def __setup_sub_dict(self):
        if self.forwarders:
            fwds = "\n"
            for forwarder in self.forwarders:
                fwds += "\t\t%s;\n" % forwarder
            fwds += "\t"
        else:
            fwds = " "

        if self.ntp:
            optional_ntp =  "\n;ntp server\n"
            optional_ntp += "_ntp._udp\t\tIN SRV 0 100 123\t%s" % self.host_in_rr
        else:
            optional_ntp = ""

        ipa_ca = ""
        for addr in self.ip_addresses:
            if addr.version in (4, 6):
                ipa_ca += "%s\t\t\tIN %s\t\t\t%s\n" % (
                    IPA_CA_RECORD,
                    "A" if addr.version == 4 else "AAAA",
                    str(addr))

        self.sub_dict = dict(
            FQDN=self.fqdn,
            IP=[str(ip) for ip in self.ip_addresses],
            DOMAIN=self.domain,
            HOST=self.host,
            REALM=self.realm,
            SERVER_ID=installutils.realm_to_serverid(self.realm),
            FORWARDERS=fwds,
            SUFFIX=self.suffix,
            OPTIONAL_NTP=optional_ntp,
            ZONEMGR=self.zonemgr,
            IPA_CA_RECORD=ipa_ca,
            BINDKEYS_FILE=paths.NAMED_BINDKEYS_FILE,
            MANAGED_KEYS_DIR=paths.NAMED_MANAGED_KEYS_DIR,
            ROOT_KEY=paths.NAMED_ROOT_KEY,
            NAMED_KEYTAB=paths.NAMED_KEYTAB,
            RFC1912_ZONES=paths.NAMED_RFC1912_ZONES,
            NAMED_PID=paths.NAMED_PID,
            NAMED_VAR_DIR=paths.NAMED_VAR_DIR,
            )

    def __setup_dns_container(self):
        self._ldap_mod("dns.ldif", self.sub_dict)
        self.__fix_dns_privilege_members()

    def __fix_dns_privilege_members(self):
        ldap = api.Backend.ldap2

        cn = 'Update PBAC memberOf %s' % time.time()
        task_dn = DN(('cn', cn), ('cn', 'memberof task'), ('cn', 'tasks'),
                     ('cn', 'config'))
        basedn = DN(api.env.container_privilege, api.env.basedn)
        entry = ldap.make_entry(
            task_dn,
            objectclass=['top', 'extensibleObject'],
            cn=[cn],
            basedn=[basedn],
            filter=['(objectclass=*)'],
            ttl=[10])
        ldap.add_entry(entry)

        start_time = time.time()
        while True:
            try:
                task = ldap.get_entry(task_dn)
            except errors.NotFound:
                break
            if 'nstaskexitcode' in task:
                break
            time.sleep(1)
            if time.time() > (start_time + 60):
                raise errors.TaskTimeout(task='memberof', task_dn=task_dn)

    def __setup_zone(self):
        # Always use force=True as named is not set up yet
        add_zone(self.domain, self.zonemgr, dns_backup=self.dns_backup,
                ns_hostname=api.env.host, force=True)

        add_rr(self.domain, "_kerberos", "TXT", self.realm)

    def __add_self_ns(self):
        # add NS record to all zones
        ns_hostname = normalize_zone(api.env.host)
        result = api.Command.dnszone_find()
        for zone in result['result']:
            zone = unicode(zone['idnsname'][0])  # we need unicode due to backup
            root_logger.debug("adding self NS to zone %s apex", zone)
            add_ns_rr(zone, ns_hostname, self.dns_backup, force=True)

    def __setup_reverse_zone(self):
        # Always use force=True as named is not set up yet
        for reverse_zone in self.reverse_zones:
            add_zone(reverse_zone, self.zonemgr, ns_hostname=api.env.host,
                dns_backup=self.dns_backup, force=True)

    def __add_master_records(self, fqdn, addrs):
        host, zone = fqdn.split(".", 1)

        if normalize_zone(zone) == normalize_zone(self.domain):
            host_in_rr = host
        else:
            host_in_rr = normalize_zone(fqdn)

        srv_records = (
            ("_ldap._tcp", "0 100 389 %s" % host_in_rr),
            ("_kerberos._tcp", "0 100 88 %s" % host_in_rr),
            ("_kerberos._udp", "0 100 88 %s" % host_in_rr),
            ("_kerberos-master._tcp", "0 100 88 %s" % host_in_rr),
            ("_kerberos-master._udp", "0 100 88 %s" % host_in_rr),
            ("_kpasswd._tcp", "0 100 464 %s" % host_in_rr),
            ("_kpasswd._udp", "0 100 464 %s" % host_in_rr),
        )
        if self.ntp:
            srv_records += (
                ("_ntp._udp", "0 100 123 %s" % host_in_rr),
            )

        for (rname, rdata) in srv_records:
            add_rr(self.domain, rname, "SRV", rdata, self.dns_backup, self.api)

        if not dns_zone_exists(zone, self.api):
            # add DNS domain for host first
            root_logger.debug(
                "Host domain (%s) is different from DNS domain (%s)!" % (
                    zone, self.domain))
            root_logger.debug("Add DNS zone for host first.")

            add_zone(zone, self.zonemgr, dns_backup=self.dns_backup,
                     ns_hostname=self.fqdn, force=True, api=self.api)

        # Add forward and reverse records to self
        for addr in addrs:
            add_fwd_rr(zone, host, addr, self.api)

            reverse_zone = find_reverse_zone(addr, self.api)
            if reverse_zone:
                add_ptr_rr(reverse_zone, addr, fqdn, None, self.api)

    def __add_self(self):
        self.__add_master_records(self.fqdn, self.ip_addresses)

    def __add_others(self):
        entries = self.admin_conn.get_entries(
            DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
               self.suffix),
            self.admin_conn.SCOPE_ONELEVEL, None, ['dn'])

        for entry in entries:
            fqdn = entry.dn[0]['cn']
            if fqdn == self.fqdn:
                continue

            addrs = installutils.resolve_host(fqdn)

            root_logger.debug("Adding DNS records for master %s" % fqdn)
            self.__add_master_records(fqdn, addrs)

    def __add_ipa_ca_records(self, fqdn, addrs, ca_configured):
        if ca_configured is False:
            root_logger.debug("CA is not configured")
            return
        elif ca_configured is None:
            # we do not know if CA is configured for this host and we can
            # add the CA record. So we need to find out
            root_logger.debug("Check if CA is enabled for this host")
            base_dn = DN(('cn', fqdn), ('cn', 'masters'), ('cn', 'ipa'),
                         ('cn', 'etc'), self.api.env.basedn)
            ldap_filter = '(&(objectClass=ipaConfigObject)(cn=CA))'
            try:
                self.api.Backend.ldap2.find_entries(filter=ldap_filter, base_dn=base_dn)
            except ipalib.errors.NotFound:
                root_logger.debug("CA is not configured")
                return
            else:
                root_logger.debug("CA is configured for this host")

        try:
            for addr in addrs:
                add_fwd_rr(self.domain, IPA_CA_RECORD, addr, self.api)
        except errors.ValidationError:
            # there is a CNAME record in ipa-ca, we can't add A/AAAA records
            pass

    def __add_ipa_ca_record(self):
        self.__add_ipa_ca_records(self.fqdn, self.ip_addresses,
                                  self.ca_configured)

        if self.first_instance:
            ldap = self.api.Backend.ldap2
            try:
                entries = ldap.get_entries(
                    DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
                       api.env.basedn),
                    ldap.SCOPE_SUBTREE, '(&(objectClass=ipaConfigObject)(cn=CA))',
                    ['dn'])
            except errors.NotFound:
                root_logger.debug('No server with CA found')
                entries = []

            for entry in entries:
                fqdn = entry.dn[1]['cn']
                if fqdn == self.fqdn:
                    continue

                host, zone = fqdn.split('.', 1)
                if dns_zone_exists(zone, self.api):
                    addrs = get_fwd_rr(zone, host, self.api)
                else:
                    addrs = installutils.resolve_host(fqdn)

                self.__add_ipa_ca_records(fqdn, addrs, True)

    def __setup_principal(self):
        dns_principal = "DNS/" + self.fqdn + "@" + self.realm
        installutils.kadmin_addprinc(dns_principal)

        # Store the keytab on disk
        self.fstore.backup_file(paths.NAMED_KEYTAB)
        installutils.create_keytab(paths.NAMED_KEYTAB, dns_principal)
        p = self.move_service(dns_principal)
        if p is None:
            # the service has already been moved, perhaps we're doing a DNS reinstall
            dns_principal = DN(('krbprincipalname', dns_principal),
                               ('cn', 'services'), ('cn', 'accounts'), self.suffix)
        else:
            dns_principal = p

        # Make sure access is strictly reserved to the named user
        pent = pwd.getpwnam(self.named_user)
        os.chown(paths.NAMED_KEYTAB, pent.pw_uid, pent.pw_gid)
        os.chmod(paths.NAMED_KEYTAB, 0400)

        # modify the principal so that it is marked as an ipa service so that
        # it can host the memberof attribute, then also add it to the
        # dnsserver role group, this way the DNS is allowed to perform
        # DNS Updates
        dns_group = DN(('cn', 'DNS Servers'), ('cn', 'privileges'), ('cn', 'pbac'), self.suffix)
        mod = [(ldap.MOD_ADD, 'member', dns_principal)]

        try:
            self.admin_conn.modify_s(dns_group, mod)
        except ldap.TYPE_OR_VALUE_EXISTS:
            pass
        except Exception, e:
            root_logger.critical("Could not modify principal's %s entry: %s" \
                    % (dns_principal, str(e)))
            raise

        # bind-dyndb-ldap persistent search feature requires both size and time
        # limit-free connection
        mod = [(ldap.MOD_REPLACE, 'nsTimeLimit', '-1'),
               (ldap.MOD_REPLACE, 'nsSizeLimit', '-1'),
               (ldap.MOD_REPLACE, 'nsIdleTimeout', '-1'),
               (ldap.MOD_REPLACE, 'nsLookThroughLimit', '-1')]
        try:
            self.admin_conn.modify_s(dns_principal, mod)
        except Exception, e:
            root_logger.critical("Could not set principal's %s LDAP limits: %s" \
                    % (dns_principal, str(e)))
            raise
Example #18
0
class DNSKeySyncInstance(service.Service):
    def __init__(self, fstore=None, logger=logger):
        super(DNSKeySyncInstance,
              self).__init__("ipa-dnskeysyncd",
                             service_desc="DNS key synchronization service",
                             fstore=fstore,
                             service_prefix=u'ipa-dnskeysyncd',
                             keytab=paths.IPA_DNSKEYSYNCD_KEYTAB)
        self.extra_config = [
            u'dnssecVersion 1',
        ]  # DNSSEC enabled

    suffix = ipautil.dn_attribute_property('_suffix')

    def set_dyndb_ldap_workdir_permissions(self):
        """
        Setting up correct permissions to allow write/read access for daemons
        """
        directories = [
            paths.BIND_LDAP_DNS_IPA_WORKDIR,
            paths.BIND_LDAP_DNS_ZONE_WORKDIR,
        ]
        for directory in directories:
            try:
                os.mkdir(directory, 0o770)
            except FileExistsError:
                pass
            else:
                os.chmod(directory, 0o770)
            # dnssec daemons require to have access into the directory
            constants.NAMED_USER.chown(directory,
                                       gid=constants.NAMED_GROUP.gid)

    def remove_replica_public_keys(self, replica_fqdn):
        ldap = api.Backend.ldap2
        dn_base = DN(('cn', 'keys'), ('cn', 'sec'), ('cn', 'dns'),
                     api.env.basedn)
        keylabel = replica_keylabel_template % DNSName(replica_fqdn).\
            make_absolute().canonicalize().ToASCII()
        # get old keys from LDAP
        search_kw = {
            'objectclass': u"ipaPublicKeyObject",
            'ipk11Label': keylabel,
            'ipk11Wrap': True,
        }
        filter = ldap.make_filter(search_kw, rules=ldap.MATCH_ALL)
        entries, _truncated = ldap.find_entries(filter=filter, base_dn=dn_base)
        for entry in entries:
            ldap.delete_entry(entry)

    def start_dnskeysyncd(self):
        print("Restarting ipa-dnskeysyncd")
        self.__start()

    def create_instance(self, fqdn, realm_name):
        self.fqdn = fqdn
        self.realm = realm_name
        self.suffix = ipautil.realm_to_suffix(self.realm)
        try:
            self.stop()
        except Exception:
            pass

        # checking status step must be first
        self.step("checking status", self.__check_dnssec_status)
        self.step("setting up bind-dyndb-ldap working directory",
                  self.set_dyndb_ldap_workdir_permissions)
        self.step("setting up kerberos principal", self.__setup_principal)
        self.step("setting up SoftHSM", self.__setup_softhsm)
        self.step("adding DNSSEC containers", self.__setup_dnssec_containers)
        self.step("creating replica keys", self.__setup_replica_keys)
        self.step("configuring ipa-dnskeysyncd to start on boot",
                  self.__enable)
        # we need restart named after setting up this service
        self.start_creation()

    def __check_dnssec_status(self):
        if not dns_container_exists(self.suffix):
            raise RuntimeError("DNS container does not exist")

        # ready to be installed, storing a state is required to run uninstall
        self.backup_state("configured", True)

    def __setup_dnssec_containers(self):
        """
        Setup LDAP containers for DNSSEC
        """
        if dnssec_container_exists(self.suffix):

            logger.info("DNSSEC container exists (step skipped)")
            return

        self._ldap_mod("dnssec.ldif", {
            'SUFFIX': self.suffix,
        })

    def _are_named_options_configured(self, options):
        """Check whether the sysconfig of named is patched

        Additional command line options for named are passed
        via OPTIONS env variable. Since custom options can be
        supplied by a vendor, at least, the base parsing of such
        is required.
        Current named command line options:
        NS_MAIN_ARGS "46A:c:C:d:D:E:fFgi:lL:M:m:n:N:p:P:sS:t:T:U:u:vVx:X:"
        If there are several same options the last passed wins.
        """
        if options:
            pattern = r"[ ]*-[a-zA-Z46]*E[ ]*(.*?)(?: |$)"
            engines = re.findall(pattern, options)
            if engines and engines[-1] == constants.NAMED_OPENSSL_ENGINE:
                return True

        return False

    def setup_named_openssl_conf(self):
        if constants.NAMED_OPENSSL_ENGINE is not None:
            logger.debug("Setup OpenSSL config for BIND")
            # setup OpenSSL config for BIND,
            # this one is needed because FreeIPA installation
            # disables p11-kit-proxy PKCS11 module
            conf_file_dict = {
                'OPENSSL_ENGINE': constants.NAMED_OPENSSL_ENGINE,
                'SOFTHSM_MODULE': paths.LIBSOFTHSM2_SO,
                'CRYPTO_POLICY_FILE': paths.CRYPTO_POLICY_OPENSSLCNF_FILE,
            }
            if paths.CRYPTO_POLICY_OPENSSLCNF_FILE is None:
                opensslcnf_tmpl = "bind.openssl.cnf.template"
            else:
                opensslcnf_tmpl = "bind.openssl.cryptopolicy.cnf.template"

            named_openssl_txt = ipautil.template_file(
                os.path.join(paths.USR_SHARE_IPA_DIR, opensslcnf_tmpl),
                conf_file_dict)
            with open(paths.DNSSEC_OPENSSL_CONF, 'w') as f:
                os.fchmod(f.fileno(), 0o640)
                os.fchown(f.fileno(), 0, gid=constants.NAMED_GROUP.gid)
                f.write(named_openssl_txt)

    def setup_named_sysconfig(self):
        logger.debug("Setup BIND sysconfig")
        sysconfig = paths.SYSCONFIG_NAMED
        self.fstore.backup_file(sysconfig)

        directivesetter.set_directive(sysconfig,
                                      'SOFTHSM2_CONF',
                                      paths.DNSSEC_SOFTHSM2_CONF,
                                      quotes=False,
                                      separator='=')

        if constants.NAMED_OPENSSL_ENGINE is not None:
            directivesetter.set_directive(sysconfig,
                                          'OPENSSL_CONF',
                                          paths.DNSSEC_OPENSSL_CONF,
                                          quotes=False,
                                          separator='=')

            options = directivesetter.get_directive(
                paths.SYSCONFIG_NAMED,
                constants.NAMED_OPTIONS_VAR,
                separator="=") or ''
            if not self._are_named_options_configured(options):
                engine_cmd = "-E {}".format(constants.NAMED_OPENSSL_ENGINE)
                new_options = ' '.join([options, engine_cmd])
                directivesetter.set_directive(sysconfig,
                                              constants.NAMED_OPTIONS_VAR,
                                              new_options,
                                              quotes=True,
                                              separator='=')

    def setup_ipa_dnskeysyncd_sysconfig(self):
        logger.debug("Setup ipa-dnskeysyncd sysconfig")
        sysconfig = paths.SYSCONFIG_IPA_DNSKEYSYNCD
        directivesetter.set_directive(sysconfig,
                                      'SOFTHSM2_CONF',
                                      paths.DNSSEC_SOFTHSM2_CONF,
                                      quotes=False,
                                      separator='=')

        if constants.NAMED_OPENSSL_ENGINE is not None:
            directivesetter.set_directive(sysconfig,
                                          'OPENSSL_CONF',
                                          paths.DNSSEC_OPENSSL_CONF,
                                          quotes=False,
                                          separator='=')

    def __setup_softhsm(self):
        token_dir_exists = os.path.exists(paths.DNSSEC_TOKENS_DIR)

        # create dnssec directory
        if not os.path.exists(paths.IPA_DNSSEC_DIR):
            logger.debug("Creating %s directory", paths.IPA_DNSSEC_DIR)
            os.mkdir(paths.IPA_DNSSEC_DIR)
            os.chmod(paths.IPA_DNSSEC_DIR, 0o770)
            # chown ods:named
            constants.ODS_USER.chown(paths.IPA_DNSSEC_DIR,
                                     gid=constants.NAMED_GROUP.gid)

        # setup softhsm2 config file
        softhsm_conf_txt = ("# SoftHSM v2 configuration file \n"
                            "# File generated by IPA instalation\n"
                            "directories.tokendir = %(tokens_dir)s\n"
                            "objectstore.backend = file") % {
                                'tokens_dir': paths.DNSSEC_TOKENS_DIR
                            }
        logger.debug("Creating new softhsm config file")
        with open(paths.DNSSEC_SOFTHSM2_CONF, 'w') as f:
            os.fchmod(f.fileno(), 0o644)
            f.write(softhsm_conf_txt)

        # setting up named and ipa-dnskeysyncd to use our softhsm2 and
        # openssl configs
        self.setup_named_openssl_conf()
        self.setup_named_sysconfig()
        self.setup_ipa_dnskeysyncd_sysconfig()

        if (token_dir_exists and os.path.exists(paths.DNSSEC_SOFTHSM_PIN)
                and os.path.exists(paths.DNSSEC_SOFTHSM_PIN_SO)):
            # there is initialized softhsm
            return

        # remove old tokens
        if token_dir_exists:
            logger.debug('Removing old tokens directory %s',
                         paths.DNSSEC_TOKENS_DIR)
            shutil.rmtree(paths.DNSSEC_TOKENS_DIR)

        # create tokens subdirectory
        logger.debug('Creating tokens %s directory', paths.DNSSEC_TOKENS_DIR)
        # sticky bit is required by daemon
        os.mkdir(paths.DNSSEC_TOKENS_DIR)
        os.chmod(paths.DNSSEC_TOKENS_DIR, 0o770 | stat.S_ISGID)
        # chown to ods:named
        constants.ODS_USER.chown(paths.DNSSEC_TOKENS_DIR,
                                 gid=constants.NAMED_GROUP.gid)

        # generate PINs for softhsm
        pin_length = 30  # Bind allows max 32 bytes including ending '\0'
        pin = ipautil.ipa_generate_password(entropy_bits=0,
                                            special=None,
                                            min_len=pin_length)
        pin_so = ipautil.ipa_generate_password(entropy_bits=0,
                                               special=None,
                                               min_len=pin_length)

        logger.debug("Saving user PIN to %s", paths.DNSSEC_SOFTHSM_PIN)
        with open(paths.DNSSEC_SOFTHSM_PIN, 'w') as f:
            # chown to ods:named
            constants.ODS_USER.chown(f.fileno(), gid=constants.NAMED_GROUP.gid)
            os.fchmod(f.fileno(), 0o660)
            f.write(pin)

        logger.debug("Saving SO PIN to %s", paths.DNSSEC_SOFTHSM_PIN_SO)
        with open(paths.DNSSEC_SOFTHSM_PIN_SO, 'w') as f:
            # owner must be root
            os.fchmod(f.fileno(), 0o400)
            f.write(pin_so)

        # initialize SoftHSM

        command = [
            paths.SOFTHSM2_UTIL,
            '--init-token',
            '--free',  # use random free slot
            '--label',
            SOFTHSM_DNSSEC_TOKEN_LABEL,
            '--pin',
            pin,
            '--so-pin',
            pin_so,
        ]
        logger.debug("Initializing tokens")
        os.environ["SOFTHSM2_CONF"] = paths.DNSSEC_SOFTHSM2_CONF
        ipautil.run(command, nolog=(
            pin,
            pin_so,
        ))

    def __setup_replica_keys(self):
        keylabel = replica_keylabel_template % DNSName(self.fqdn).\
            make_absolute().canonicalize().ToASCII()

        ldap = api.Backend.ldap2
        dn_base = DN(('cn', 'keys'), ('cn', 'sec'), ('cn', 'dns'),
                     api.env.basedn)

        with open(paths.DNSSEC_SOFTHSM_PIN, "r") as f:
            pin = f.read()

        os.environ["SOFTHSM2_CONF"] = paths.DNSSEC_SOFTHSM2_CONF
        p11 = _ipap11helper.P11_Helper(SOFTHSM_DNSSEC_TOKEN_LABEL, pin,
                                       paths.LIBSOFTHSM2_SO)

        try:
            # generate replica keypair
            logger.debug("Creating replica's key pair")
            key_id = None
            while True:
                # check if key with this ID exist in softHSM
                key_id = _ipap11helper.gen_key_id()
                replica_pubkey_dn = DN(('ipk11UniqueId', 'autogenerate'),
                                       dn_base)

                pub_keys = p11.find_keys(_ipap11helper.KEY_CLASS_PUBLIC_KEY,
                                         label=keylabel,
                                         id=key_id)
                if pub_keys:
                    # key with id exists
                    continue

                priv_keys = p11.find_keys(_ipap11helper.KEY_CLASS_PRIVATE_KEY,
                                          label=keylabel,
                                          id=key_id)
                if not priv_keys:
                    break  # we found unique id

            public_key_handle, _privkey_handle = p11.generate_replica_key_pair(
                keylabel,
                key_id,
                pub_cka_verify=False,
                pub_cka_verify_recover=False,
                pub_cka_wrap=True,
                priv_cka_unwrap=True,
                priv_cka_sensitive=True,
                priv_cka_extractable=False)

            # export public key
            public_key_blob = p11.export_public_key(public_key_handle)

            # save key to LDAP
            replica_pubkey_objectclass = [
                'ipk11Object', 'ipk11PublicKey', 'ipaPublicKeyObject', 'top'
            ]
            kw = {
                'objectclass': replica_pubkey_objectclass,
                'ipk11UniqueId': [u'autogenerate'],
                'ipk11Label': [keylabel],
                'ipaPublicKey': [public_key_blob],
                'ipk11Id': [key_id],
                'ipk11Wrap': [True],
                'ipk11Verify': [False],
                'ipk11VerifyRecover': [False],
            }

            logger.debug("Storing replica public key to LDAP, %s",
                         replica_pubkey_dn)

            entry = ldap.make_entry(replica_pubkey_dn, **kw)
            ldap.add_entry(entry)
            logger.debug("Replica public key stored")

            logger.debug("Setting CKA_WRAP=False for old replica keys")
            # first create new keys, we don't want disable keys before, we
            # have new keys in softhsm and LDAP

            # get replica pub keys with CKA_WRAP=True
            replica_pub_keys = p11.find_keys(
                _ipap11helper.KEY_CLASS_PUBLIC_KEY,
                label=keylabel,
                cka_wrap=True)
            # old keys in softHSM
            for handle in replica_pub_keys:
                # don't disable wrapping for new key
                # compare IDs not handle
                if key_id != p11.get_attribute(handle, _ipap11helper.CKA_ID):
                    p11.set_attribute(handle, _ipap11helper.CKA_WRAP, False)

            # get old keys from LDAP
            search_kw = {
                'objectclass': u"ipaPublicKeyObject",
                'ipk11Label': keylabel,
                'ipk11Wrap': True,
            }
            filter = ldap.make_filter(search_kw, rules=ldap.MATCH_ALL)
            entries, _truncated = ldap.find_entries(filter=filter,
                                                    base_dn=dn_base)
            for entry in entries:
                # don't disable wrapping for new key
                if entry.single_value['ipk11Id'] != key_id:
                    entry['ipk11Wrap'] = [False]
                    ldap.update_entry(entry)

        finally:
            p11.finalize()

        # change tokens mod/owner
        logger.debug("Changing ownership of token files")
        for (root, dirs, files) in os.walk(paths.DNSSEC_TOKENS_DIR):
            for directory in dirs:
                dir_path = os.path.join(root, directory)
                os.chmod(dir_path, 0o770 | stat.S_ISGID)
                # chown to ods:named
                constants.ODS_USER.chown(dir_path,
                                         gid=constants.NAMED_GROUP.gid)
            for filename in files:
                file_path = os.path.join(root, filename)
                os.chmod(file_path, 0o660 | stat.S_ISGID)
                # chown to ods:named
                constants.ODS_USER.chown(file_path,
                                         gid=constants.NAMED_GROUP.gid)

    def __enable(self):
        try:
            self.ldap_configure('DNSKeySync', self.fqdn, None, self.suffix,
                                self.extra_config)
        except errors.DuplicateEntry:
            logger.error("DNSKeySync service already exists")

    def __setup_principal(self):
        ipautil.remove_keytab(self.keytab)
        installutils.kadmin_addprinc(self.principal)

        # Store the keytab on disk
        installutils.create_keytab(self.keytab, self.principal)
        p = self.move_service(self.principal)
        if p is None:
            # the service has already been moved, perhaps we're doing a DNS reinstall
            dnssynckey_principal_dn = DN(('krbprincipalname', self.principal),
                                         ('cn', 'services'),
                                         ('cn', 'accounts'), self.suffix)
        else:
            dnssynckey_principal_dn = p

        # Make sure access is strictly reserved to the named user
        os.chown(self.keytab, 0, constants.ODS_GROUP.gid)
        os.chmod(self.keytab, 0o440)

        dns_group = DN(('cn', 'DNS Servers'), ('cn', 'privileges'),
                       ('cn', 'pbac'), self.suffix)
        mod = [(ldap.MOD_ADD, 'member', dnssynckey_principal_dn)]

        try:
            api.Backend.ldap2.modify_s(dns_group, mod)
        except ldap.TYPE_OR_VALUE_EXISTS:
            pass
        except Exception as e:
            logger.critical("Could not modify principal's %s entry: %s",
                            dnssynckey_principal_dn, str(e))
            raise

        # bind-dyndb-ldap persistent search feature requires both size and time
        # limit-free connection

        mod = [(ldap.MOD_REPLACE, 'nsTimeLimit', '-1'),
               (ldap.MOD_REPLACE, 'nsSizeLimit', '-1'),
               (ldap.MOD_REPLACE, 'nsIdleTimeout', '-1'),
               (ldap.MOD_REPLACE, 'nsLookThroughLimit', '-1')]
        try:
            api.Backend.ldap2.modify_s(dnssynckey_principal_dn, mod)
        except Exception as e:
            logger.critical("Could not set principal's %s LDAP limits: %s",
                            dnssynckey_principal_dn, str(e))
            raise

    def __start(self):
        try:
            self.restart()
        except Exception as e:
            print("Failed to start ipa-dnskeysyncd")
            logger.debug("Failed to start ipa-dnskeysyncd: %s", e)

    def uninstall(self):
        if self.is_configured():
            self.print_msg("Unconfiguring %s" % self.service_name)

        # Just eat states
        self.restore_state("running")
        self.restore_state("enabled")
        self.restore_state("configured")

        # stop and disable service (IPA service, we do not need it anymore)
        self.stop()
        self.disable()

        for f in [paths.SYSCONFIG_NAMED]:
            try:
                self.fstore.restore_file(f)
            except ValueError as error:
                logger.debug('%s', error)

        # remove softhsm pin, to make sure new installation will generate
        # new token database
        # do not delete *so pin*, user can need it to get token data
        ipautil.remove_file(paths.DNSSEC_SOFTHSM_PIN)
        ipautil.remove_file(paths.DNSSEC_SOFTHSM2_CONF)

        try:
            shutil.rmtree(paths.DNSSEC_TOKENS_DIR)
        except OSError as e:
            if e.errno != errno.ENOENT:
                logger.exception("Failed to remove %s",
                                 paths.DNSSEC_TOKENS_DIR)

        ipautil.remove_keytab(self.keytab)
Example #19
0
class KrbInstance(service.Service):
    def __init__(self, fstore=None):
        service.Service.__init__(self, "krb5kdc", service_desc="Kerberos KDC")
        self.fqdn = None
        self.realm = None
        self.domain = None
        self.host = None
        self.admin_password = None
        self.master_password = None
        self.suffix = None
        self.subject_base = None
        self.kdc_password = None
        self.sub_dict = None
        self.pkcs12_info = None

        if fstore:
            self.fstore = fstore
        else:
            self.fstore = sysrestore.FileStore(paths.SYSRESTORE)

    suffix = ipautil.dn_attribute_property('_suffix')
    subject_base = ipautil.dn_attribute_property('_subject_base')

    def get_realm_suffix(self):
        return DN(('cn', self.realm), ('cn', 'kerberos'), self.suffix)

    def move_service_to_host(self, principal):
        """
        Used to move a host/ service principal created by kadmin.local from
        cn=kerberos to reside under the host entry.
        """

        service_dn = DN(('krbprincipalname', principal),
                        self.get_realm_suffix())
        service_entry = self.admin_conn.get_entry(service_dn)
        self.admin_conn.delete_entry(service_entry)

        # Create a host entry for this master
        host_dn = DN(('fqdn', self.fqdn), ('cn', 'computers'),
                     ('cn', 'accounts'), self.suffix)
        host_entry = self.admin_conn.make_entry(
            host_dn,
            objectclass=[
                'top', 'ipaobject', 'nshost', 'ipahost', 'ipaservice',
                'pkiuser', 'krbprincipalaux', 'krbprincipal',
                'krbticketpolicyaux', 'ipasshhost'
            ],
            krbextradata=service_entry['krbextradata'],
            krblastpwdchange=service_entry['krblastpwdchange'],
            krbprincipalname=service_entry['krbprincipalname'],
            krbcanonicalname=service_entry['krbcanonicalname'],
            krbprincipalkey=service_entry['krbprincipalkey'],
            serverhostname=[self.fqdn.split('.', 1)[0]],
            cn=[self.fqdn],
            fqdn=[self.fqdn],
            ipauniqueid=['autogenerate'],
            managedby=[host_dn],
        )
        if 'krbpasswordexpiration' in service_entry:
            host_entry['krbpasswordexpiration'] = service_entry[
                'krbpasswordexpiration']
        if 'krbticketflags' in service_entry:
            host_entry['krbticketflags'] = service_entry['krbticketflags']
        self.admin_conn.add_entry(host_entry)

        # Add the host to the ipaserver host group
        ld = ldapupdate.LDAPUpdate(ldapi=True)
        ld.update([
            os.path.join(paths.UPDATES_DIR, '20-ipaservers_hostgroup.update')
        ])

    def __common_setup(self, realm_name, host_name, domain_name,
                       admin_password):
        self.fqdn = host_name
        self.realm = realm_name.upper()
        self.host = host_name.split(".")[0]
        self.ip = socket.getaddrinfo(host_name, None, socket.AF_UNSPEC,
                                     socket.SOCK_STREAM)[0][4][0]
        self.domain = domain_name
        self.suffix = ipautil.realm_to_suffix(self.realm)
        self.kdc_password = ipautil.ipa_generate_password()
        self.admin_password = admin_password
        self.dm_password = admin_password

        self.__setup_sub_dict()

        # get a connection to the DS
        self.ldap_connect()

        self.backup_state("running", self.is_running())
        try:
            self.stop()
        except Exception:
            # It could have been not running
            pass

    def __common_post_setup(self):
        self.step("starting the KDC", self.__start_instance)
        self.step("configuring KDC to start on boot", self.__enable)

    def create_instance(self,
                        realm_name,
                        host_name,
                        domain_name,
                        admin_password,
                        master_password,
                        setup_pkinit=False,
                        pkcs12_info=None,
                        subject_base=None):
        self.master_password = master_password
        self.pkcs12_info = pkcs12_info
        self.subject_base = subject_base

        self.__common_setup(realm_name, host_name, domain_name, admin_password)

        self.step("adding kerberos container to the directory",
                  self.__add_krb_container)
        self.step("configuring KDC", self.__configure_instance)
        self.step("initialize kerberos container", self.__init_ipa_kdb)
        self.step("adding default ACIs", self.__add_default_acis)
        self.step("creating a keytab for the directory",
                  self.__create_ds_keytab)
        self.step("creating a keytab for the machine",
                  self.__create_host_keytab)
        self.step("adding the password extension to the directory",
                  self.__add_pwd_extop_module)
        if setup_pkinit:
            self.step("creating X509 Certificate for PKINIT",
                      self.__setup_pkinit)
            self.step("creating principal for anonymous PKINIT",
                      self.__add_anonymous_pkinit_principal)

        self.__common_post_setup()

        self.start_creation(runtime=30)

        self.kpasswd = KpasswdInstance()
        self.kpasswd.create_instance('KPASSWD',
                                     self.fqdn,
                                     self.admin_password,
                                     self.suffix,
                                     realm=self.realm)

    def create_replica(self,
                       realm_name,
                       master_fqdn,
                       host_name,
                       domain_name,
                       admin_password,
                       setup_pkinit=False,
                       pkcs12_info=None,
                       subject_base=None,
                       promote=False):
        self.pkcs12_info = pkcs12_info
        self.subject_base = subject_base
        self.master_fqdn = master_fqdn

        self.__common_setup(realm_name, host_name, domain_name, admin_password)

        self.step("configuring KDC", self.__configure_instance)
        if not promote:
            self.step("creating a keytab for the directory",
                      self.__create_ds_keytab)
            self.step("creating a keytab for the machine",
                      self.__create_host_keytab)
        self.step("adding the password extension to the directory",
                  self.__add_pwd_extop_module)
        if setup_pkinit:
            self.step("installing X509 Certificate for PKINIT",
                      self.__setup_pkinit)
        if not promote:
            self.step("enable GSSAPI for replication",
                      self.__convert_to_gssapi_replication)

        self.__common_post_setup()

        self.start_creation(runtime=30)

        self.kpasswd = KpasswdInstance()
        self.kpasswd.create_instance('KPASSWD', self.fqdn, self.admin_password,
                                     self.suffix)

    def __enable(self):
        self.backup_state("enabled", self.is_enabled())
        # We do not let the system start IPA components on its own,
        # Instead we reply on the IPA init script to start only enabled
        # components as found in our LDAP configuration tree
        self.ldap_enable('KDC', self.fqdn, self.admin_password, self.suffix)

    def __start_instance(self):
        try:
            self.start()
        except Exception:
            root_logger.critical("krb5kdc service failed to start")

    def __setup_sub_dict(self):
        self.sub_dict = dict(FQDN=self.fqdn,
                             IP=self.ip,
                             PASSWORD=self.kdc_password,
                             SUFFIX=self.suffix,
                             DOMAIN=self.domain,
                             HOST=self.host,
                             SERVER_ID=installutils.realm_to_serverid(
                                 self.realm),
                             REALM=self.realm,
                             KRB5KDC_KADM5_ACL=paths.KRB5KDC_KADM5_ACL,
                             DICT_WORDS=paths.DICT_WORDS,
                             KRB5KDC_KADM5_KEYTAB=paths.KRB5KDC_KADM5_KEYTAB,
                             KDC_PEM=paths.KDC_PEM,
                             CACERT_PEM=paths.CACERT_PEM)

        # IPA server/KDC is not a subdomain of default domain
        # Proper domain-realm mapping needs to be specified
        domain = dns.name.from_text(self.domain)
        fqdn = dns.name.from_text(self.fqdn)
        if not fqdn.is_subdomain(domain):
            root_logger.debug(
                "IPA FQDN '%s' is not located in default domain '%s'", fqdn,
                domain)
            server_domain = fqdn.parent().to_unicode(omit_final_dot=True)
            root_logger.debug(
                "Domain '%s' needs additional mapping in krb5.conf",
                server_domain)
            dr_map = " .%(domain)s = %(realm)s\n %(domain)s = %(realm)s\n" \
                        % dict(domain=server_domain, realm=self.realm)
        else:
            dr_map = ""
        self.sub_dict['OTHER_DOMAIN_REALM_MAPS'] = dr_map

        # Configure KEYRING CCACHE if supported
        if kernel_keyring.is_persistent_keyring_supported():
            root_logger.debug("Enabling persistent keyring CCACHE")
            self.sub_dict['OTHER_LIBDEFAULTS'] = \
                " default_ccache_name = KEYRING:persistent:%{uid}\n"
        else:
            root_logger.debug("Persistent keyring CCACHE is not enabled")
            self.sub_dict['OTHER_LIBDEFAULTS'] = ''

    def __add_krb_container(self):
        self._ldap_mod("kerberos.ldif", self.sub_dict)

    def __add_default_acis(self):
        self._ldap_mod("default-aci.ldif", self.sub_dict)

    def __template_file(self, path, chmod=0o644):
        template = os.path.join(ipautil.SHARE_DIR,
                                os.path.basename(path) + ".template")
        conf = ipautil.template_file(template, self.sub_dict)
        self.fstore.backup_file(path)
        fd = open(path, "w+")
        fd.write(conf)
        fd.close()
        if chmod is not None:
            os.chmod(path, chmod)

    def __init_ipa_kdb(self):
        # kdb5_util may take a very long time when entropy is low
        installutils.check_entropy()

        #populate the directory with the realm structure
        args = [
            "kdb5_util", "create", "-s", "-r", self.realm, "-x",
            "ipa-setup-override-restrictions"
        ]
        dialogue = (
            # Enter KDC database master key:
            self.master_password + '\n',
            # Re-enter KDC database master key to verify:
            self.master_password + '\n',
        )
        try:
            ipautil.run(args,
                        nolog=(self.master_password, ),
                        stdin=''.join(dialogue))
        except ipautil.CalledProcessError:
            print("Failed to initialize the realm container")

    def __configure_instance(self):
        self.__template_file(paths.KRB5KDC_KDC_CONF, chmod=None)
        self.__template_file(paths.KRB5_CONF)
        self.__template_file(paths.HTML_KRB5_INI)
        self.__template_file(paths.KRB_CON)
        self.__template_file(paths.HTML_KRBREALM_CON)

        MIN_KRB5KDC_WITH_WORKERS = "1.9"
        cpus = os.sysconf('SC_NPROCESSORS_ONLN')
        workers = False
        result = ipautil.run(['klist', '-V'],
                             raiseonerr=False,
                             capture_output=True)
        if result.returncode == 0:
            verstr = result.output.split()[-1]
            ver = version.LooseVersion(verstr)
            min = version.LooseVersion(MIN_KRB5KDC_WITH_WORKERS)
            if ver >= min:
                workers = True
        # Write down config file
        # We write realm and also number of workers (for multi-CPU systems)
        replacevars = {'KRB5REALM': self.realm}
        appendvars = {}
        if workers and cpus > 1:
            appendvars = {'KRB5KDC_ARGS': "'-w %s'" % str(cpus)}
        ipautil.backup_config_and_replace_variables(
            self.fstore,
            paths.SYSCONFIG_KRB5KDC_DIR,
            replacevars=replacevars,
            appendvars=appendvars)
        tasks.restore_context(paths.SYSCONFIG_KRB5KDC_DIR)

    #add the password extop module
    def __add_pwd_extop_module(self):
        self._ldap_mod("pwd-extop-conf.ldif", self.sub_dict)

    def __create_ds_keytab(self):
        ldap_principal = "ldap/" + self.fqdn + "@" + self.realm
        installutils.kadmin_addprinc(ldap_principal)
        self.move_service(ldap_principal)

        self.fstore.backup_file(paths.DS_KEYTAB)
        installutils.create_keytab(paths.DS_KEYTAB, ldap_principal)

        vardict = {"KRB5_KTNAME": paths.DS_KEYTAB}
        ipautil.config_replace_variables(paths.SYSCONFIG_DIRSRV,
                                         replacevars=vardict)
        pent = pwd.getpwnam(constants.DS_USER)
        os.chown(paths.DS_KEYTAB, pent.pw_uid, pent.pw_gid)

    def __create_host_keytab(self):
        host_principal = "host/" + self.fqdn + "@" + self.realm
        installutils.kadmin_addprinc(host_principal)

        self.fstore.backup_file(paths.KRB5_KEYTAB)
        installutils.create_keytab(paths.KRB5_KEYTAB, host_principal)

        # Make sure access is strictly reserved to root only for now
        os.chown(paths.KRB5_KEYTAB, 0, 0)
        os.chmod(paths.KRB5_KEYTAB, 0o600)

        self.move_service_to_host(host_principal)

    def __setup_pkinit(self):
        ca_db = certs.CertDB(self.realm,
                             host_name=self.fqdn,
                             subject_base=self.subject_base)

        if self.pkcs12_info:
            ca_db.install_pem_from_p12(self.pkcs12_info[0],
                                       self.pkcs12_info[1], paths.KDC_PEM)
        else:
            raise RuntimeError("PKI not supported yet\n")

        # Finally copy the cacert in the krb directory so we don't
        # have any selinux issues with the file context
        shutil.copyfile(CACERT, paths.CACERT_PEM)

    def __add_anonymous_pkinit_principal(self):
        princ = "WELLKNOWN/ANONYMOUS"
        princ_realm = "%s@%s" % (princ, self.realm)

        # Create the special anonymous principal
        installutils.kadmin_addprinc(princ_realm)
        dn = DN(('krbprincipalname', princ_realm), self.get_realm_suffix())
        entry = self.admin_conn.get_entry(dn)
        entry['nsAccountlock'] = ['TRUE']
        self.admin_conn.update_entry(entry)

    def __convert_to_gssapi_replication(self):
        repl = replication.ReplicationManager(self.realm, self.fqdn,
                                              self.dm_password)
        repl.convert_to_gssapi_replication(self.master_fqdn,
                                           r_binddn=DN(
                                               ('cn', 'Directory Manager')),
                                           r_bindpw=self.dm_password)

    def uninstall(self):
        if self.is_configured():
            self.print_msg("Unconfiguring %s" % self.service_name)

        running = self.restore_state("running")
        enabled = self.restore_state("enabled")

        try:
            self.stop()
        except Exception:
            pass

        for f in [paths.KRB5KDC_KDC_CONF, paths.KRB5_CONF]:
            try:
                self.fstore.restore_file(f)
            except ValueError as error:
                root_logger.debug(error)

        # disabled by default, by ldap_enable()
        if enabled:
            self.enable()

        if running:
            self.restart()

        self.kpasswd = KpasswdInstance()
        self.kpasswd.uninstall()
Example #20
0
class BindInstance(service.Service):
    def __init__(self, fstore=None, api=api):
        super(BindInstance, self).__init__(
            "named",
            service_desc="DNS",
            fstore=fstore,
            api=api,
            service_user=constants.NAMED_USER,
            service_prefix=u'DNS',
            keytab=paths.NAMED_KEYTAB
        )
        self.dns_backup = DnsBackup(self)
        self.domain = None
        self.host = None
        self.ip_addresses = ()
        self.forwarders = ()
        self.forward_policy = None
        self.zonemgr = None
        self.no_dnssec_validation = False
        self.sub_dict = None
        self.reverse_zones = ()
        self.named_conflict = services.service('named-conflict', api)

    suffix = ipautil.dn_attribute_property('_suffix')

    def setup(self, fqdn, ip_addresses, realm_name, domain_name, forwarders,
              forward_policy, reverse_zones, zonemgr=None,
              no_dnssec_validation=False):
        """Setup bindinstance for installation
        """
        self.setup_templating(
            fqdn=fqdn,
            realm_name=realm_name,
            domain_name=domain_name,
            no_dnssec_validation=no_dnssec_validation
        )
        self.ip_addresses = ip_addresses
        self.forwarders = forwarders
        self.forward_policy = forward_policy
        self.reverse_zones = reverse_zones

        if not zonemgr:
            self.zonemgr = 'hostmaster.%s' % normalize_zone(self.domain)
        else:
            self.zonemgr = normalize_zonemgr(zonemgr)

    def setup_templating(
        self, fqdn, realm_name, domain_name, no_dnssec_validation=None
    ):
        """Setup bindinstance for templating
        """
        self.fqdn = fqdn
        self.realm = realm_name
        self.domain = domain_name
        self.host = fqdn.split(".")[0]
        self.suffix = ipautil.realm_to_suffix(self.realm)
        self.no_dnssec_validation = no_dnssec_validation
        self._setup_sub_dict()

    @property
    def host_domain(self):
        return self.fqdn.split(".", 1)[1]

    @property
    def first_instance(self):
        return not dns_container_exists(self.suffix)

    @property
    def host_in_rr(self):
        # when a host is not in a default domain, it needs to be referred
        # with FQDN and not in a domain-relative host name
        if not self.host_in_default_domain():
            return normalize_zone(self.fqdn)
        return self.host

    def host_in_default_domain(self):
        return normalize_zone(self.host_domain) == normalize_zone(self.domain)

    def create_file_with_system_records(self):
        system_records = IPASystemRecords(self.api, all_servers=True)
        text = u'\n'.join(
            IPASystemRecords.records_list_from_zone(
                system_records.get_base_records()
            )
        )
        with tempfile.NamedTemporaryFile(
                mode="w", prefix="ipa.system.records.",
                suffix=".db", delete=False
        ) as f:
            f.write(text)
            print("Please add records in this file to your DNS system:",
                  f.name)

    def create_instance(self):
        try:
            self.stop()
        except Exception:
            pass

        for ip_address in self.ip_addresses:
            if installutils.record_in_hosts(str(ip_address), self.fqdn) is None:
                installutils.add_record_to_hosts(str(ip_address), self.fqdn)

        # Make sure generate-rndc-key.sh runs before named restart
        self.step("generating rndc key file", self.__generate_rndc_key)

        if self.first_instance:
            self.step("adding DNS container", self.__setup_dns_container)

        if not dns_zone_exists(self.domain, self.api):
            self.step("setting up our zone", self.__setup_zone)
        if self.reverse_zones:
            self.step("setting up reverse zone", self.__setup_reverse_zone)

        self.step("setting up our own record", self.__add_self)
        if self.first_instance:
            self.step("setting up records for other masters", self.__add_others)
        # all zones must be created before this step
        self.step("adding NS record to the zones", self.__add_self_ns)

        self.step("setting up kerberos principal", self.__setup_principal)
        self.step("setting up named.conf", self.setup_named_conf)
        self.step("setting up server configuration",
            self.__setup_server_configuration)

        # named has to be started after softhsm initialization
        # self.step("restarting named", self.__start)

        self.step("configuring named to start on boot", self.switch_service)
        self.step(
            "changing resolv.conf to point to ourselves",
            self.setup_resolv_conf
        )
        self.start_creation()

    def start_named(self):
        self.print_msg("Restarting named")
        self.__start()

    def __start(self):
        try:
            self.restart()
        except Exception as e:
            logger.error("Named service failed to start (%s)", e)
            print("named service failed to start")

    def switch_service(self):
        self.mask_conflict()
        self.__enable()

    def __enable(self):
        # We do not let the system start IPA components on its own,
        # Instead we reply on the IPA init script to start only enabled
        # components as found in our LDAP configuration tree
        try:
            self.ldap_configure('DNS', self.fqdn, None, self.suffix)
        except errors.DuplicateEntry:
            # service already exists (forced DNS reinstall)
            # don't crash, just report error
            logger.error("DNS service already exists")

    def mask_conflict(self):
        # disable named-conflict (either named or named-pkcs11)
        try:
            self.named_conflict.stop()
        except Exception as e:
            logger.debug("Unable to stop %s (%s)",
                         self.named_conflict.systemd_name, e)

        try:
            self.named_conflict.mask()
        except Exception as e:
            logger.debug("Unable to mask %s (%s)",
                         self.named_conflict.systemd_name, e)

    def _get_dnssec_validation(self):
        """get dnssec-validation value

        1) command line overwrite --no-dnssec-validation
        2) setting dnssec-enabled or dnssec-validation from named.conf
        3) "yes" by default

        Note: The dnssec-enabled is deprecated and defaults to "yes". If the
        setting is "no", then it is migrated as "dnssec-validation no".
        """
        dnssec_validation = "yes"
        if self.no_dnssec_validation:
            # command line overwrite
            logger.debug(
                "dnssec-validation 'no' command line overwrite"
            )
            dnssec_validation = "no"
        elif os.path.isfile(paths.NAMED_CONF):
            # get prev_ value from /etc/named.conf
            prev_dnssec_validation = named_conf_get_directive(
                "dnssec-validation",
                NAMED_SECTION_OPTIONS,
                str_val=False
            )
            prev_dnssec_enable = named_conf_get_directive(
                "dnssec-enable",
                NAMED_SECTION_OPTIONS,
                str_val=False
            )
            if prev_dnssec_validation == "no" or prev_dnssec_enable == "no":
                logger.debug(
                    "Setting dnssec-validation 'no' from existing %s",
                    paths.NAMED_CONF
                )
                logger.debug(
                    "dnssec-enabled was %s (None is yes)", prev_dnssec_enable
                )
                logger.debug(
                    "dnssec-validation was %s", prev_dnssec_validation
                )
                dnssec_validation = "no"
        assert dnssec_validation in {"yes", "no"}
        logger.info("dnssec-validation %s", dnssec_validation)
        return dnssec_validation

    def _setup_sub_dict(self):
        if paths.NAMED_CRYPTO_POLICY_FILE is not None:
            crypto_policy = 'include "{}";'.format(
                paths.NAMED_CRYPTO_POLICY_FILE
            )
        else:
            crypto_policy = "// not available"

        self.sub_dict = dict(
            FQDN=self.fqdn,
            SERVER_ID=ipaldap.realm_to_serverid(self.realm),
            SUFFIX=self.suffix,
            MANAGED_KEYS_DIR=paths.NAMED_MANAGED_KEYS_DIR,
            ROOT_KEY=paths.NAMED_ROOT_KEY,
            NAMED_KEYTAB=self.keytab,
            RFC1912_ZONES=paths.NAMED_RFC1912_ZONES,
            NAMED_PID=paths.NAMED_PID,
            NAMED_VAR_DIR=paths.NAMED_VAR_DIR,
            BIND_LDAP_SO=paths.BIND_LDAP_SO,
            INCLUDE_CRYPTO_POLICY=crypto_policy,
            NAMED_CONF=paths.NAMED_CONF,
            NAMED_CUSTOM_CONF=paths.NAMED_CUSTOM_CONF,
            NAMED_CUSTOM_OPTIONS_CONF=paths.NAMED_CUSTOM_OPTIONS_CONF,
            NAMED_LOGGING_OPTIONS_CONF=paths.NAMED_LOGGING_OPTIONS_CONF,
            NAMED_DATA_DIR=constants.NAMED_DATA_DIR,
            NAMED_ZONE_COMMENT=constants.NAMED_ZONE_COMMENT,
            NAMED_DNSSEC_VALIDATION=self._get_dnssec_validation(),
        )

    def __setup_dns_container(self):
        self._ldap_mod("dns.ldif", self.sub_dict)
        self.__fix_dns_privilege_members()

    def __fix_dns_privilege_members(self):
        ldap = self.api.Backend.ldap2

        cn = 'Update PBAC memberOf %s' % time.time()
        task_dn = DN(('cn', cn), ('cn', 'memberof task'), ('cn', 'tasks'),
                     ('cn', 'config'))
        basedn = DN(self.api.env.container_privilege, self.api.env.basedn)
        entry = ldap.make_entry(
            task_dn,
            objectclass=['top', 'extensibleObject'],
            cn=[cn],
            basedn=[basedn],
            filter=['(objectclass=*)'],
            ttl=[10])
        ldap.add_entry(entry)

        start_time = time.time()
        while True:
            try:
                task = ldap.get_entry(task_dn)
            except errors.NotFound:
                break
            if 'nstaskexitcode' in task:
                break
            time.sleep(1)
            if time.time() > (start_time + 60):
                raise errors.TaskTimeout(task='memberof', task_dn=task_dn)

    def __setup_zone(self):
        # Always use force=True as named is not set up yet
        add_zone(self.domain, self.zonemgr, dns_backup=self.dns_backup,
                 ns_hostname=self.api.env.host, force=True,
                 skip_overlap_check=True, api=self.api)

        add_rr(self.domain, "_kerberos", "TXT", self.realm, api=self.api)

    def __add_self_ns(self):
        # add NS record to all zones
        ns_hostname = normalize_zone(self.api.env.host)
        result = self.api.Command.dnszone_find()
        for zone in result['result']:
            zone = unicode(zone['idnsname'][0])  # we need unicode due to backup
            logger.debug("adding self NS to zone %s apex", zone)
            add_ns_rr(zone, ns_hostname, self.dns_backup, force=True,
                      api=self.api)

    def __setup_reverse_zone(self):
        # Always use force=True as named is not set up yet
        for reverse_zone in self.reverse_zones:
            add_zone(reverse_zone, self.zonemgr, ns_hostname=self.api.env.host,
                     dns_backup=self.dns_backup, force=True,
                     skip_overlap_check=True, api=self.api)

    def __add_master_records(self, fqdn, addrs):
        host, zone = fqdn.split(".", 1)

        # Add forward and reverse records to self
        for addr in addrs:
            # Check first if the zone is a master zone
            # (if it is a forward zone, dns_zone_exists will return False)
            if dns_zone_exists(zone, api=self.api):
                add_fwd_rr(zone, host, addr, self.api)
            else:
                logger.debug("Skip adding record %s to a zone %s "
                             "not managed by IPA", addr, zone)

            reverse_zone = find_reverse_zone(addr, self.api)
            if reverse_zone:
                add_ptr_rr(reverse_zone, addr, fqdn, None, api=self.api)

    def __add_self(self):
        self.__add_master_records(self.fqdn, self.ip_addresses)

    def __add_others(self):
        entries = api.Backend.ldap2.get_entries(
            DN(api.env.container_masters, self.suffix),
            api.Backend.ldap2.SCOPE_ONELEVEL, None, ['dn'])

        for entry in entries:
            fqdn = entry.dn[0]['cn']
            if fqdn == self.fqdn:
                continue

            addrs = installutils.resolve_ip_addresses_nss(fqdn)

            logger.debug("Adding DNS records for master %s", fqdn)
            self.__add_master_records(fqdn, addrs)

    def __setup_principal(self):
        installutils.kadmin_addprinc(self.principal)

        # Store the keytab on disk
        self.fstore.backup_file(self.keytab)
        installutils.create_keytab(self.keytab, self.principal)
        p = self.move_service(self.principal)
        if p is None:
            # the service has already been moved, perhaps we're doing a DNS reinstall
            dns_principal = DN(('krbprincipalname', self.principal),
                               ('cn', 'services'), ('cn', 'accounts'), self.suffix)
        else:
            dns_principal = p

        # Make sure access is strictly reserved to the named user
        self.service_user.chown(self.keytab)
        os.chmod(self.keytab, 0o400)

        # modify the principal so that it is marked as an ipa service so that
        # it can host the memberof attribute, then also add it to the
        # dnsserver role group, this way the DNS is allowed to perform
        # DNS Updates
        dns_group = DN(('cn', 'DNS Servers'), ('cn', 'privileges'), ('cn', 'pbac'), self.suffix)
        mod = [(ldap.MOD_ADD, 'member', dns_principal)]

        try:
            api.Backend.ldap2.modify_s(dns_group, mod)
        except ldap.TYPE_OR_VALUE_EXISTS:
            pass
        except Exception as e:
            logger.critical("Could not modify principal's %s entry: %s",
                            dns_principal, str(e))
            raise

        # bind-dyndb-ldap persistent search feature requires both size and time
        # limit-free connection
        mod = [(ldap.MOD_REPLACE, 'nsTimeLimit', '-1'),
               (ldap.MOD_REPLACE, 'nsSizeLimit', '-1'),
               (ldap.MOD_REPLACE, 'nsIdleTimeout', '-1'),
               (ldap.MOD_REPLACE, 'nsLookThroughLimit', '-1')]
        try:
            api.Backend.ldap2.modify_s(dns_principal, mod)
        except Exception as e:
            logger.critical("Could not set principal's %s LDAP limits: %s",
                            dns_principal, str(e))
            raise

    def setup_named_conf(self, backup=False):
        """Create, update, or migrate named configuration files

        The method is used by installer and upgrade process. The named.conf
        is backed up the first time and overwritten every time. The user
        specific config files are created once and not modified in subsequent
        calls.

        The "dnssec-validation" option is migrated

        :returns: True if any config file was modified, else False
        """
        # files are owned by root:named and are readable by user and group
        uid = 0
        gid = constants.NAMED_GROUP.gid
        mode = 0o640

        changed = False

        if not self.fstore.has_file(paths.NAMED_CONF):
            self.fstore.backup_file(paths.NAMED_CONF)

        # named.conf
        txt = ipautil.template_file(
            os.path.join(paths.NAMED_CONF_SRC), self.sub_dict
        )
        with open(paths.NAMED_CONF) as f:
            old_txt = f.read()
        if txt == old_txt:
            logger.debug("%s is unmodified", paths.NAMED_CONF)
        else:
            if backup:
                if not os.path.isfile(paths.NAMED_CONF_BAK):
                    shutil.copyfile(paths.NAMED_CONF, paths.NAMED_CONF_BAK)
                    logger.info("created backup %s", paths.NAMED_CONF_BAK)
                else:
                    logger.warning(
                        "backup %s already exists", paths.NAMED_CONF_BAK
                    )

            with open(paths.NAMED_CONF, "w") as f:
                os.fchmod(f.fileno(), mode)
                os.fchown(f.fileno(), uid, gid)
                f.write(txt)

            logger.info("created new %s", paths.NAMED_CONF)
            changed = True

        # user configurations
        user_configs = (
            (paths.NAMED_CUSTOM_CONF_SRC, paths.NAMED_CUSTOM_CONF),
            (
                paths.NAMED_CUSTOM_OPTIONS_CONF_SRC,
                paths.NAMED_CUSTOM_OPTIONS_CONF
            ),
            (
                paths.NAMED_LOGGING_OPTIONS_CONF_SRC,
                paths.NAMED_LOGGING_OPTIONS_CONF,
            ),
        )
        for src, dest in user_configs:
            if not os.path.exists(dest):
                txt = ipautil.template_file(src, self.sub_dict)
                with open(dest, "w") as f:
                    os.fchmod(f.fileno(), mode)
                    os.fchown(f.fileno(), uid, gid)
                    f.write(txt)
                logger.info("created named user config '%s'", dest)
                changed = True
            else:
                logger.info("named user config '%s' already exists", dest)

        return changed

    def __setup_server_configuration(self):
        ensure_dnsserver_container_exists(api.Backend.ldap2, self.api)
        try:
            self.api.Command.dnsserver_add(
                self.fqdn, idnssoamname=DNSName(self.fqdn).make_absolute(),
            )
        except errors.DuplicateEntry:
            # probably reinstallation of DNS
            pass

        try:
            self.api.Command.dnsserver_mod(
                self.fqdn,
                idnsforwarders=[unicode(f) for f in self.forwarders],
                idnsforwardpolicy=unicode(self.forward_policy)
            )
        except errors.EmptyModlist:
            pass

        sysupgrade.set_upgrade_state('dns', 'server_config_to_ldap', True)

    def setup_resolv_conf(self):
        searchdomains = [self.domain]
        nameservers = set()
        resolve1_enabled = dnsforwarders.detect_resolve1_resolv_conf()

        for ip_address in self.ip_addresses:
            if ip_address.version == 4:
                nameservers.add("127.0.0.1")
            elif ip_address.version == 6:
                nameservers.add("::1")

        try:
            tasks.configure_dns_resolver(
                sorted(nameservers), searchdomains,
                resolve1_enabled=resolve1_enabled, fstore=self.fstore
            )
        except IOError as e:
            logger.error('Could not update DNS config: %s', e)
        else:
            # python DNS might have global resolver cached in this variable
            # we have to re-initialize it because resolv.conf has changed
            dnsutil.reset_default_resolver()

    def __generate_rndc_key(self):
        installutils.check_entropy()
        ipautil.run([paths.GENERATE_RNDC_KEY])

    def add_master_dns_records(self, fqdn, ip_addresses, realm_name, domain_name,
                               reverse_zones):
        self.fqdn = fqdn
        self.ip_addresses = ip_addresses
        self.realm = realm_name
        self.domain = domain_name
        self.host = fqdn.split(".")[0]
        self.suffix = ipautil.realm_to_suffix(self.realm)
        self.reverse_zones = reverse_zones
        self.zonemgr = 'hostmaster.%s' % self.domain

        self.__add_self()

    def remove_ipa_ca_cnames(self, domain_name):
        # get ipa-ca CNAMEs
        try:
            cnames = get_rr(domain_name, IPA_CA_RECORD, "CNAME", api=self.api)
        except errors.NotFound:
            # zone does not exists
            cnames = None
        if not cnames:
            return

        logger.info('Removing IPA CA CNAME records')

        # create CNAME to FQDN mapping
        cname_fqdn = {}
        for cname in cnames:
            if cname.endswith('.'):
                fqdn = cname[:-1]
            else:
                fqdn = '%s.%s' % (cname, domain_name)
            cname_fqdn[cname] = fqdn

        # get FQDNs of all IPA masters
        try:
            masters = set(get_masters(self.api.Backend.ldap2))
        except errors.NotFound:
            masters = set()

        # check if all CNAMEs point to IPA masters
        for cname in cnames:
            fqdn = cname_fqdn[cname]
            if fqdn not in masters:
                logger.warning(
                    "Cannot remove IPA CA CNAME please remove them manually "
                    "if necessary")
                return

        # delete all CNAMEs
        for cname in cnames:
            del_rr(domain_name, IPA_CA_RECORD, "CNAME", cname, api=self.api)

    def remove_master_dns_records(self, fqdn, realm_name, domain_name):
        host, zone = fqdn.split(".", 1)
        self.host = host
        self.fqdn = fqdn
        self.domain = domain_name

        if not dns_zone_exists(zone, api=self.api):
            # Zone may be a forward zone, skip update
            return

        areclist = get_fwd_rr(zone, host, api=self.api)
        for rdata in areclist:
            del_fwd_rr(zone, host, rdata, api=self.api)

            rzone = find_reverse_zone(rdata)
            if rzone is not None:
                record = get_reverse_record_name(rzone, rdata)
                del_rr(rzone, record, "PTR", normalize_zone(fqdn),
                       api=self.api)
        self.update_system_records()

    def remove_server_ns_records(self, fqdn):
        """
        Remove all NS records pointing to this server
        """
        ldap = self.api.Backend.ldap2
        ns_rdata = normalize_zone(fqdn)

        # find all NS records pointing to this server
        search_kw = {}
        search_kw['nsrecord'] = ns_rdata
        attr_filter = ldap.make_filter(search_kw, rules=ldap.MATCH_ALL)
        attributes = ['idnsname', 'objectclass']
        dn = DN(self.api.env.container_dns, self.api.env.basedn)

        entries, _truncated = ldap.find_entries(
            attr_filter, attributes, base_dn=dn)

        # remove records
        if entries:
            logger.debug("Removing all NS records pointing to %s:", ns_rdata)

        for entry in entries:
            if 'idnszone' in entry['objectclass']:
                # zone record
                zone = entry.single_value['idnsname']
                logger.debug("zone record %s", zone)
                del_ns_rr(zone, u'@', ns_rdata, api=self.api)
            else:
                zone = entry.dn[1].value  # get zone from DN
                record = entry.single_value['idnsname']
                logger.debug("record %s in zone %s", record, zone)
                del_ns_rr(zone, record, ns_rdata, api=self.api)

    def update_system_records(self):
        self.print_msg("Updating DNS system records")
        system_records = IPASystemRecords(self.api)
        try:
            (
                (_ipa_rec, failed_ipa_rec),
                (_loc_rec, failed_loc_rec)
            ) = system_records.update_dns_records()
        except IPADomainIsNotManagedByIPAError:
            logger.error(
                "IPA domain is not managed by IPA, please update records "
                "manually")
        else:
            if failed_ipa_rec or failed_loc_rec:
                logger.error("Update of following records failed:")
                for attr in (failed_ipa_rec, failed_loc_rec):
                    for rname, node, error in attr:
                        for record in IPASystemRecords.records_list_from_node(
                                rname, node
                        ):
                            logger.error("%s (%s)", record, error)

    def check_global_configuration(self):
        """
        Check global DNS configuration in LDAP server and inform user when it
        set and thus overrides his configured options in named.conf.
        """
        result = self.api.Command.dnsconfig_show()

        global_conf_set = any(
            param.name in result['result'] for param in
            self.api.Object['dnsconfig'].params() if
            u'virtual_attribute' not in param.flags
        )

        if not global_conf_set:
            print("Global DNS configuration in LDAP server is empty")
            print("You can use 'dnsconfig-mod' command to set global DNS options that")
            print("would override settings in local named.conf files")
            return

        print("Global DNS configuration in LDAP server is not empty")
        print("The following configuration options override local settings in named.conf:")
        print("")
        textui = ipalib.cli.textui(self.api)
        self.api.Command.dnsconfig_show.output_for_cli(textui, result, None,
                                                       reverse=False)

    def is_configured(self):
        """
        Override the default logic querying StateFile for configuration status
        and look whether named.conf was already modified by IPA installer.
        """
        return named_conf_exists()

    def uninstall(self):
        if self.is_configured():
            self.print_msg("Unconfiguring %s" % self.service_name)

        self.dns_backup.clear_records(self.api.Backend.ldap2.isconnected())

        try:
            self.fstore.restore_file(paths.NAMED_CONF)
        except ValueError as error:
            logger.debug('%s', error)

        try:
            tasks.unconfigure_dns_resolver(fstore=self.fstore)
        except Exception:
            logger.exception("Failed to unconfigure DNS resolver")

        ipautil.rmtree(paths.BIND_LDAP_DNS_IPA_WORKDIR)

        self.disable()
        self.stop()

        self.named_conflict.unmask()

        ipautil.remove_file(paths.NAMED_CONF_BAK)
        ipautil.remove_file(paths.NAMED_CUSTOM_CONF)
        ipautil.remove_file(paths.NAMED_CUSTOM_OPTIONS_CONF)
        ipautil.remove_keytab(self.keytab)
        ipautil.remove_ccache(run_as=self.service_user)
Example #21
0
class OpenDNSSECInstance(service.Service):
    def __init__(self, fstore=None):
        service.Service.__init__(
            self,
            "ods-enforcerd",
            service_desc="OpenDNSSEC enforcer daemon",
        )
        self.ods_uid = None
        self.ods_gid = None
        self.conf_file_dict = {
            'SOFTHSM_LIB': paths.LIBSOFTHSM2_SO,
            'TOKEN_LABEL': dnskeysyncinstance.softhsm_token_label,
            'KASP_DB': paths.OPENDNSSEC_KASP_DB,
            'ODS_USER': constants.ODS_USER,
            'ODS_GROUP': constants.ODS_GROUP,
        }
        self.kasp_file_dict = {}
        self.extra_config = [KEYMASTER]

        if fstore:
            self.fstore = fstore
        else:
            self.fstore = sysrestore.FileStore(paths.SYSRESTORE)

    suffix = ipautil.dn_attribute_property('_suffix')

    def get_masters(self):
        return get_dnssec_key_masters(api.Backend.ldap2)

    def create_instance(self,
                        fqdn,
                        realm_name,
                        generate_master_key=True,
                        kasp_db_file=None):
        if self.get_state("enabled") is None:
            self.backup_state("enabled", self.is_enabled())
        if self.get_state("running") is None:
            self.backup_state("running", self.is_running())
        self.fqdn = fqdn
        self.realm = realm_name
        self.suffix = ipautil.realm_to_suffix(self.realm)
        self.kasp_db_file = kasp_db_file

        try:
            self.stop()
        except Exception:
            pass

        # checking status must be first
        self.step("checking status", self.__check_dnssec_status)
        self.step("setting up configuration files", self.__setup_conf_files)
        self.step("setting up ownership and file mode bits",
                  self.__setup_ownership_file_modes)
        if generate_master_key:
            self.step("generating master key", self.__generate_master_key)
        self.step("setting up OpenDNSSEC", self.__setup_dnssec)
        self.step("setting up ipa-dnskeysyncd", self.__setup_dnskeysyncd)
        self.step("starting OpenDNSSEC enforcer", self.__start)
        self.step("configuring OpenDNSSEC enforcer to start on boot",
                  self.__enable)
        self.start_creation()

    def __check_dnssec_status(self):
        try:
            self.named_uid = pwd.getpwnam(constants.NAMED_USER).pw_uid
        except KeyError:
            raise RuntimeError("Named UID not found")

        try:
            self.named_gid = grp.getgrnam(constants.NAMED_GROUP).gr_gid
        except KeyError:
            raise RuntimeError("Named GID not found")

        try:
            self.ods_uid = pwd.getpwnam(constants.ODS_USER).pw_uid
        except KeyError:
            raise RuntimeError("OpenDNSSEC UID not found")

        try:
            self.ods_gid = grp.getgrnam(constants.ODS_GROUP).gr_gid
        except KeyError:
            raise RuntimeError("OpenDNSSEC GID not found")

    def __enable(self):
        try:
            self.ldap_enable('DNSSEC', self.fqdn, None, self.suffix,
                             self.extra_config)
        except errors.DuplicateEntry:
            root_logger.error("DNSSEC service already exists")

        # add the KEYMASTER identifier into ipaConfigString
        # this is needed for the re-enabled DNSSEC master
        dn = DN(('cn', 'DNSSEC'), ('cn', self.fqdn), api.env.container_masters,
                api.env.basedn)
        try:
            entry = api.Backend.ldap2.get_entry(dn, ['ipaConfigString'])
        except errors.NotFound as e:
            root_logger.error(
                "DNSSEC service entry not found in the LDAP (%s)", e)
        else:
            config = entry.setdefault('ipaConfigString', [])
            if KEYMASTER not in config:
                config.append(KEYMASTER)
                api.Backend.ldap2.update_entry(entry)

    def __setup_conf_files(self):
        if not self.fstore.has_file(paths.OPENDNSSEC_CONF_FILE):
            self.fstore.backup_file(paths.OPENDNSSEC_CONF_FILE)

        if not self.fstore.has_file(paths.OPENDNSSEC_KASP_FILE):
            self.fstore.backup_file(paths.OPENDNSSEC_KASP_FILE)

        if not self.fstore.has_file(paths.OPENDNSSEC_ZONELIST_FILE):
            self.fstore.backup_file(paths.OPENDNSSEC_ZONELIST_FILE)

        pin_fd = open(paths.DNSSEC_SOFTHSM_PIN, "r")
        pin = pin_fd.read()
        pin_fd.close()

        # add pin to template
        sub_conf_dict = self.conf_file_dict
        sub_conf_dict['PIN'] = pin

        ods_conf_txt = ipautil.template_file(
            os.path.join(paths.USR_SHARE_IPA_DIR, "opendnssec_conf.template"),
            sub_conf_dict)
        ods_conf_fd = open(paths.OPENDNSSEC_CONF_FILE, 'w')
        ods_conf_fd.seek(0)
        ods_conf_fd.truncate(0)
        ods_conf_fd.write(ods_conf_txt)
        ods_conf_fd.close()

        ods_kasp_txt = ipautil.template_file(
            os.path.join(paths.USR_SHARE_IPA_DIR, "opendnssec_kasp.template"),
            self.kasp_file_dict)
        ods_kasp_fd = open(paths.OPENDNSSEC_KASP_FILE, 'w')
        ods_kasp_fd.seek(0)
        ods_kasp_fd.truncate(0)
        ods_kasp_fd.write(ods_kasp_txt)
        ods_kasp_fd.close()

        if not self.fstore.has_file(paths.SYSCONFIG_ODS):
            self.fstore.backup_file(paths.SYSCONFIG_ODS)

        installutils.set_directive(paths.SYSCONFIG_ODS,
                                   'SOFTHSM2_CONF',
                                   paths.DNSSEC_SOFTHSM2_CONF,
                                   quotes=False,
                                   separator='=')

    def __setup_ownership_file_modes(self):
        assert self.ods_uid is not None
        assert self.ods_gid is not None

        # workarounds for packaging bugs in opendnssec-1.4.5-2.fc20.x86_64
        # https://bugzilla.redhat.com/show_bug.cgi?id=1098188
        for (root, dirs, files) in os.walk(paths.ETC_OPENDNSSEC_DIR):
            for directory in dirs:
                dir_path = os.path.join(root, directory)
                os.chmod(dir_path, 0o770)
                # chown to root:ods
                os.chown(dir_path, 0, self.ods_gid)
            for filename in files:
                file_path = os.path.join(root, filename)
                os.chmod(file_path, 0o660)
                # chown to root:ods
                os.chown(file_path, 0, self.ods_gid)

        for (root, dirs, files) in os.walk(paths.VAR_OPENDNSSEC_DIR):
            for directory in dirs:
                dir_path = os.path.join(root, directory)
                os.chmod(dir_path, 0o770)
                # chown to ods:ods
                os.chown(dir_path, self.ods_uid, self.ods_gid)
            for filename in files:
                file_path = os.path.join(root, filename)
                os.chmod(file_path, 0o660)
                # chown to ods:ods
                os.chown(file_path, self.ods_uid, self.ods_gid)

    def __generate_master_key(self):

        with open(paths.DNSSEC_SOFTHSM_PIN, "r") as f:
            pin = f.read()

        os.environ["SOFTHSM2_CONF"] = paths.DNSSEC_SOFTHSM2_CONF
        p11 = p11helper.P11_Helper(softhsm_slot, pin, paths.LIBSOFTHSM2_SO)
        try:
            # generate master key
            root_logger.debug("Creating master key")
            p11helper.generate_master_key(p11)

            # change tokens mod/owner
            root_logger.debug("Changing ownership of token files")
            for (root, dirs, files) in os.walk(paths.DNSSEC_TOKENS_DIR):
                for directory in dirs:
                    dir_path = os.path.join(root, directory)
                    os.chmod(dir_path, 0o770 | stat.S_ISGID)
                    os.chown(dir_path, self.ods_uid,
                             self.named_gid)  # chown to ods:named
                for filename in files:
                    file_path = os.path.join(root, filename)
                    os.chmod(file_path, 0o770 | stat.S_ISGID)
                    os.chown(file_path, self.ods_uid,
                             self.named_gid)  # chown to ods:named

        finally:
            p11.finalize()

    def __setup_dnssec(self):
        # run once only
        if self.get_state("kasp_db_configured") and not self.kasp_db_file:
            root_logger.debug("Already configured, skipping step")
            return

        self.backup_state("kasp_db_configured", True)

        if not self.fstore.has_file(paths.OPENDNSSEC_KASP_DB):
            self.fstore.backup_file(paths.OPENDNSSEC_KASP_DB)

        if self.kasp_db_file:
            # copy user specified kasp.db to proper location and set proper
            # privileges
            shutil.copy(self.kasp_db_file, paths.OPENDNSSEC_KASP_DB)
            os.chown(paths.OPENDNSSEC_KASP_DB, self.ods_uid, self.ods_gid)
            os.chmod(paths.OPENDNSSEC_KASP_DB, 0o660)

            # regenerate zonelist.xml
            cmd = [paths.ODS_KSMUTIL, 'zonelist', 'export']
            result = ipautil.run(cmd,
                                 runas=constants.ODS_USER,
                                 capture_output=True)
            with open(paths.OPENDNSSEC_ZONELIST_FILE, 'w') as zonelistf:
                zonelistf.write(result.output)
                os.chown(paths.OPENDNSSEC_ZONELIST_FILE, self.ods_uid,
                         self.ods_gid)
                os.chmod(paths.OPENDNSSEC_ZONELIST_FILE, 0o660)

        else:
            # initialize new kasp.db
            command = [paths.ODS_KSMUTIL, 'setup']

            ipautil.run(command, stdin="y", runas=constants.ODS_USER)

    def __setup_dnskeysyncd(self):
        # set up dnskeysyncd this is DNSSEC master
        installutils.set_directive(paths.SYSCONFIG_IPA_DNSKEYSYNCD,
                                   'ISMASTER',
                                   '1',
                                   quotes=False,
                                   separator='=')

    def __start(self):
        self.restart()  # needed to reload conf files

    def uninstall(self):
        if not self.is_configured():
            return

        self.print_msg("Unconfiguring %s" % self.service_name)

        running = self.restore_state("running")
        enabled = self.restore_state("enabled")

        # stop DNSSEC services before backing up kasp.db
        try:
            self.stop()
        except Exception:
            pass

        ods_exporter = services.service('ipa-ods-exporter', api)
        try:
            ods_exporter.stop()
        except Exception:
            pass

        # remove directive from ipa-dnskeysyncd, this server is not DNSSEC
        # master anymore
        installutils.set_directive(paths.SYSCONFIG_IPA_DNSKEYSYNCD,
                                   'ISMASTER',
                                   None,
                                   quotes=False,
                                   separator='=')

        restore_list = [
            paths.OPENDNSSEC_CONF_FILE, paths.OPENDNSSEC_KASP_FILE,
            paths.SYSCONFIG_ODS, paths.OPENDNSSEC_ZONELIST_FILE
        ]

        if ipautil.file_exists(paths.OPENDNSSEC_KASP_DB):

            # force to export data
            cmd = [paths.IPA_ODS_EXPORTER, 'ipa-full-update']
            try:
                self.print_msg("Exporting DNSSEC data before uninstallation")
                ipautil.run(cmd, runas=constants.ODS_USER)
            except CalledProcessError:
                root_logger.error("DNSSEC data export failed")

            try:
                shutil.copy(paths.OPENDNSSEC_KASP_DB, paths.IPA_KASP_DB_BACKUP)
            except IOError as e:
                root_logger.error(
                    "Unable to backup OpenDNSSEC database %s, "
                    "restore will be skipped: %s", paths.OPENDNSSEC_KASP_DB, e)
            else:
                root_logger.info("OpenDNSSEC database backed up in %s",
                                 paths.IPA_KASP_DB_BACKUP)
                # restore OpenDNSSEC's KASP DB only if backup succeeded
                # removing the file without backup could totally break DNSSEC
                restore_list.append(paths.OPENDNSSEC_KASP_DB)

        for f in restore_list:
            try:
                self.fstore.restore_file(f)
            except ValueError as error:
                root_logger.debug(error)

        self.restore_state("kasp_db_configured")  # just eat state

        # disabled by default, by ldap_enable()
        if enabled:
            self.enable()

        if running:
            self.restart()
Example #22
0
class BindInstance(service.Service):
    def __init__(self, fstore=None, api=api):
        super(BindInstance, self).__init__("named",
                                           service_desc="DNS",
                                           fstore=fstore,
                                           api=api,
                                           service_user=constants.NAMED_USER,
                                           service_prefix=u'DNS',
                                           keytab=paths.NAMED_KEYTAB)
        self.dns_backup = DnsBackup(self)
        self.domain = None
        self.host = None
        self.ip_addresses = []
        self.forwarders = None
        self.sub_dict = None
        self.reverse_zones = []
        self.named_regular = services.service('named-regular', api)

    suffix = ipautil.dn_attribute_property('_suffix')

    def setup(self,
              fqdn,
              ip_addresses,
              realm_name,
              domain_name,
              forwarders,
              forward_policy,
              reverse_zones,
              named_user=constants.NAMED_USER,
              zonemgr=None,
              no_dnssec_validation=False):
        self.service_user = named_user
        self.fqdn = fqdn
        self.ip_addresses = ip_addresses
        self.realm = realm_name
        self.domain = domain_name
        self.forwarders = forwarders
        self.forward_policy = forward_policy
        self.host = fqdn.split(".")[0]
        self.suffix = ipautil.realm_to_suffix(self.realm)
        self.reverse_zones = reverse_zones
        self.no_dnssec_validation = no_dnssec_validation

        if not zonemgr:
            self.zonemgr = 'hostmaster.%s' % normalize_zone(self.domain)
        else:
            self.zonemgr = normalize_zonemgr(zonemgr)

        self.first_instance = not dns_container_exists(self.suffix)

        self.__setup_sub_dict()

    @property
    def host_domain(self):
        return self.fqdn.split(".", 1)[1]

    @property
    def host_in_rr(self):
        # when a host is not in a default domain, it needs to be referred
        # with FQDN and not in a domain-relative host name
        if not self.host_in_default_domain():
            return normalize_zone(self.fqdn)
        return self.host

    def host_in_default_domain(self):
        return normalize_zone(self.host_domain) == normalize_zone(self.domain)

    def create_file_with_system_records(self):
        system_records = IPASystemRecords(self.api)
        text = u'\n'.join(
            IPASystemRecords.records_list_from_zone(
                system_records.get_base_records()))
        [fd, name] = tempfile.mkstemp(".db", "ipa.system.records.")
        os.write(fd, text)
        os.close(fd)
        print("Please add records in this file to your DNS system:", name)

    def create_instance(self):

        try:
            self.stop()
        except Exception:
            pass

        for ip_address in self.ip_addresses:
            if installutils.record_in_hosts(str(ip_address),
                                            self.fqdn) is None:
                installutils.add_record_to_hosts(str(ip_address), self.fqdn)

        # Make sure generate-rndc-key.sh runs before named restart
        self.step("generating rndc key file", self.__generate_rndc_key)

        if self.first_instance:
            self.step("adding DNS container", self.__setup_dns_container)

        if not dns_zone_exists(self.domain, self.api):
            self.step("setting up our zone", self.__setup_zone)
        if self.reverse_zones:
            self.step("setting up reverse zone", self.__setup_reverse_zone)

        self.step("setting up our own record", self.__add_self)
        if self.first_instance:
            self.step("setting up records for other masters",
                      self.__add_others)
        # all zones must be created before this step
        self.step("adding NS record to the zones", self.__add_self_ns)

        self.step("setting up kerberos principal", self.__setup_principal)
        self.step("setting up named.conf", self.__setup_named_conf)
        self.step("setting up server configuration",
                  self.__setup_server_configuration)

        # named has to be started after softhsm initialization
        # self.step("restarting named", self.__start)

        self.step("configuring named to start on boot", self.__enable)
        self.step("changing resolv.conf to point to ourselves",
                  self.__setup_resolv_conf)
        self.start_creation()

    def start_named(self):
        self.print_msg("Restarting named")
        self.__start()

    def __start(self):
        try:
            if self.get_state("running") is None:
                # first time store status
                self.backup_state("running", self.is_running())
            self.restart()
        except Exception as e:
            root_logger.error("Named service failed to start (%s)", e)
            print("named service failed to start")

    def __enable(self):
        if self.get_state("enabled") is None:
            self.backup_state("enabled", self.is_running())
            self.backup_state("named-regular-enabled",
                              self.named_regular.is_running())
        # We do not let the system start IPA components on its own,
        # Instead we reply on the IPA init script to start only enabled
        # components as found in our LDAP configuration tree
        try:
            self.ldap_enable('DNS', self.fqdn, None, self.suffix)
        except errors.DuplicateEntry:
            # service already exists (forced DNS reinstall)
            # don't crash, just report error
            root_logger.error("DNS service already exists")

        # disable named, we need to run named-pkcs11 only
        if self.get_state("named-regular-running") is None:
            # first time store status
            self.backup_state("named-regular-running",
                              self.named_regular.is_running())
        try:
            self.named_regular.stop()
        except Exception as e:
            root_logger.debug("Unable to stop named (%s)", e)

        try:
            self.named_regular.mask()
        except Exception as e:
            root_logger.debug("Unable to mask named (%s)", e)

    def __setup_sub_dict(self):
        self.sub_dict = dict(
            FQDN=self.fqdn,
            SERVER_ID=installutils.realm_to_serverid(self.realm),
            SUFFIX=self.suffix,
            BINDKEYS_FILE=paths.NAMED_BINDKEYS_FILE,
            MANAGED_KEYS_DIR=paths.NAMED_MANAGED_KEYS_DIR,
            ROOT_KEY=paths.NAMED_ROOT_KEY,
            NAMED_KEYTAB=self.keytab,
            RFC1912_ZONES=paths.NAMED_RFC1912_ZONES,
            NAMED_PID=paths.NAMED_PID,
            NAMED_VAR_DIR=paths.NAMED_VAR_DIR,
        )

    def __setup_dns_container(self):
        self._ldap_mod("dns.ldif", self.sub_dict)
        self.__fix_dns_privilege_members()

    def __fix_dns_privilege_members(self):
        ldap = self.api.Backend.ldap2

        cn = 'Update PBAC memberOf %s' % time.time()
        task_dn = DN(('cn', cn), ('cn', 'memberof task'), ('cn', 'tasks'),
                     ('cn', 'config'))
        basedn = DN(self.api.env.container_privilege, self.api.env.basedn)
        entry = ldap.make_entry(task_dn,
                                objectclass=['top', 'extensibleObject'],
                                cn=[cn],
                                basedn=[basedn],
                                filter=['(objectclass=*)'],
                                ttl=[10])
        ldap.add_entry(entry)

        start_time = time.time()
        while True:
            try:
                task = ldap.get_entry(task_dn)
            except errors.NotFound:
                break
            if 'nstaskexitcode' in task:
                break
            time.sleep(1)
            if time.time() > (start_time + 60):
                raise errors.TaskTimeout(task='memberof', task_dn=task_dn)

    def __setup_zone(self):
        # Always use force=True as named is not set up yet
        add_zone(self.domain,
                 self.zonemgr,
                 dns_backup=self.dns_backup,
                 ns_hostname=self.api.env.host,
                 force=True,
                 skip_overlap_check=True,
                 api=self.api)

        add_rr(self.domain, "_kerberos", "TXT", self.realm, api=self.api)

    def __add_self_ns(self):
        # add NS record to all zones
        ns_hostname = normalize_zone(self.api.env.host)
        result = self.api.Command.dnszone_find()
        for zone in result['result']:
            zone = unicode(
                zone['idnsname'][0])  # we need unicode due to backup
            root_logger.debug("adding self NS to zone %s apex", zone)
            add_ns_rr(zone,
                      ns_hostname,
                      self.dns_backup,
                      force=True,
                      api=self.api)

    def __setup_reverse_zone(self):
        # Always use force=True as named is not set up yet
        for reverse_zone in self.reverse_zones:
            add_zone(reverse_zone,
                     self.zonemgr,
                     ns_hostname=self.api.env.host,
                     dns_backup=self.dns_backup,
                     force=True,
                     skip_overlap_check=True,
                     api=self.api)

    def __add_master_records(self, fqdn, addrs):
        host, zone = fqdn.split(".", 1)

        # Add forward and reverse records to self
        for addr in addrs:
            try:
                add_fwd_rr(zone, host, addr, self.api)
            except errors.NotFound:
                pass

            reverse_zone = find_reverse_zone(addr, self.api)
            if reverse_zone:
                add_ptr_rr(reverse_zone, addr, fqdn, None, api=self.api)

    def __add_self(self):
        self.__add_master_records(self.fqdn, self.ip_addresses)

    def __add_others(self):
        entries = api.Backend.ldap2.get_entries(
            DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), self.suffix),
            api.Backend.ldap2.SCOPE_ONELEVEL, None, ['dn'])

        for entry in entries:
            fqdn = entry.dn[0]['cn']
            if fqdn == self.fqdn:
                continue

            addrs = installutils.resolve_ip_addresses_nss(fqdn)

            root_logger.debug("Adding DNS records for master %s" % fqdn)
            self.__add_master_records(fqdn, addrs)

    def __setup_principal(self):
        installutils.kadmin_addprinc(self.principal)

        # Store the keytab on disk
        self.fstore.backup_file(self.keytab)
        installutils.create_keytab(self.keytab, self.principal)
        p = self.move_service(self.principal)
        if p is None:
            # the service has already been moved, perhaps we're doing a DNS reinstall
            dns_principal = DN(('krbprincipalname', self.principal),
                               ('cn', 'services'), ('cn', 'accounts'),
                               self.suffix)
        else:
            dns_principal = p

        # Make sure access is strictly reserved to the named user
        pent = pwd.getpwnam(self.service_user)
        os.chown(self.keytab, pent.pw_uid, pent.pw_gid)
        os.chmod(self.keytab, 0o400)

        # modify the principal so that it is marked as an ipa service so that
        # it can host the memberof attribute, then also add it to the
        # dnsserver role group, this way the DNS is allowed to perform
        # DNS Updates
        dns_group = DN(('cn', 'DNS Servers'), ('cn', 'privileges'),
                       ('cn', 'pbac'), self.suffix)
        mod = [(ldap.MOD_ADD, 'member', dns_principal)]

        try:
            api.Backend.ldap2.modify_s(dns_group, mod)
        except ldap.TYPE_OR_VALUE_EXISTS:
            pass
        except Exception as e:
            root_logger.critical("Could not modify principal's %s entry: %s" \
                    % (dns_principal, str(e)))
            raise

        # bind-dyndb-ldap persistent search feature requires both size and time
        # limit-free connection
        mod = [(ldap.MOD_REPLACE, 'nsTimeLimit', '-1'),
               (ldap.MOD_REPLACE, 'nsSizeLimit', '-1'),
               (ldap.MOD_REPLACE, 'nsIdleTimeout', '-1'),
               (ldap.MOD_REPLACE, 'nsLookThroughLimit', '-1')]
        try:
            api.Backend.ldap2.modify_s(dns_principal, mod)
        except Exception as e:
            root_logger.critical("Could not set principal's %s LDAP limits: %s" \
                    % (dns_principal, str(e)))
            raise

    def __setup_named_conf(self):
        if not self.fstore.has_file(NAMED_CONF):
            self.fstore.backup_file(NAMED_CONF)

        named_txt = ipautil.template_file(
            os.path.join(paths.USR_SHARE_IPA_DIR, "bind.named.conf.template"),
            self.sub_dict)
        named_fd = open(NAMED_CONF, 'w')
        named_fd.seek(0)
        named_fd.truncate(0)
        named_fd.write(named_txt)
        named_fd.close()

        if self.no_dnssec_validation:
            # disable validation
            named_conf_set_directive("dnssec-validation",
                                     "no",
                                     section=NAMED_SECTION_OPTIONS,
                                     str_val=False)

        # prevent repeated upgrade on new installs
        sysupgrade.set_upgrade_state(
            'named.conf', 'forward_policy_conflict_with_empty_zones_handled',
            True)

    def __setup_server_configuration(self):
        ensure_dnsserver_container_exists(api.Backend.ldap2, self.api)
        try:
            self.api.Command.dnsserver_add(
                self.fqdn,
                idnssoamname=DNSName(self.fqdn).make_absolute(),
            )
        except errors.DuplicateEntry:
            # probably reinstallation of DNS
            pass

        try:
            self.api.Command.dnsserver_mod(
                self.fqdn,
                idnsforwarders=[unicode(f) for f in self.forwarders],
                idnsforwardpolicy=unicode(self.forward_policy))
        except errors.EmptyModlist:
            pass

        sysupgrade.set_upgrade_state('dns', 'server_config_to_ldap', True)

    def __setup_resolv_conf(self):
        if not self.fstore.has_file(RESOLV_CONF):
            self.fstore.backup_file(RESOLV_CONF)

        resolv_txt = "search " + self.domain + "\n"

        for ip_address in self.ip_addresses:
            if ip_address.version == 4:
                resolv_txt += "nameserver 127.0.0.1\n"
                break

        for ip_address in self.ip_addresses:
            if ip_address.version == 6:
                resolv_txt += "nameserver ::1\n"
                break
        try:
            resolv_fd = open(RESOLV_CONF, 'w')
            resolv_fd.seek(0)
            resolv_fd.truncate(0)
            resolv_fd.write(resolv_txt)
            resolv_fd.close()
        except IOError as e:
            root_logger.error('Could not write to resolv.conf: %s', e)
        else:
            # python DNS might have global resolver cached in this variable
            # we have to re-initialize it because resolv.conf has changed
            dns.resolver.default_resolver = None

    def __generate_rndc_key(self):
        installutils.check_entropy()
        ipautil.run([paths.GENERATE_RNDC_KEY])

    def add_master_dns_records(self, fqdn, ip_addresses, realm_name,
                               domain_name, reverse_zones):
        self.fqdn = fqdn
        self.ip_addresses = ip_addresses
        self.realm = realm_name
        self.domain = domain_name
        self.host = fqdn.split(".")[0]
        self.suffix = ipautil.realm_to_suffix(self.realm)
        self.reverse_zones = reverse_zones
        self.first_instance = False
        self.zonemgr = 'hostmaster.%s' % self.domain

        self.__add_self()

    def remove_ipa_ca_cnames(self, domain_name):
        # get ipa-ca CNAMEs
        try:
            cnames = get_rr(domain_name, IPA_CA_RECORD, "CNAME", api=self.api)
        except errors.NotFound:
            # zone does not exists
            cnames = None
        if not cnames:
            return

        root_logger.info('Removing IPA CA CNAME records')

        # create CNAME to FQDN mapping
        cname_fqdn = {}
        for cname in cnames:
            if cname.endswith('.'):
                fqdn = cname[:-1]
            else:
                fqdn = '%s.%s' % (cname, domain_name)
            cname_fqdn[cname] = fqdn

        # get FQDNs of all IPA masters
        ldap = self.api.Backend.ldap2
        try:
            entries = ldap.get_entries(
                DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
                   self.api.env.basedn), ldap.SCOPE_ONELEVEL, None, ['cn'])
            masters = set(e['cn'][0] for e in entries)
        except errors.NotFound:
            masters = set()

        # check if all CNAMEs point to IPA masters
        for cname in cnames:
            fqdn = cname_fqdn[cname]
            if fqdn not in masters:
                root_logger.warning(
                    "Cannot remove IPA CA CNAME please remove them manually "
                    "if necessary")
                return

        # delete all CNAMEs
        for cname in cnames:
            del_rr(domain_name, IPA_CA_RECORD, "CNAME", cname, api=self.api)

    def remove_master_dns_records(self, fqdn, realm_name, domain_name):
        host, zone = fqdn.split(".", 1)
        self.host = host
        self.fqdn = fqdn
        self.domain = domain_name

        areclist = get_fwd_rr(zone, host, api=self.api)
        for rdata in areclist:
            del_fwd_rr(zone, host, rdata, api=self.api)

            rzone = find_reverse_zone(rdata)
            if rzone is not None:
                record = get_reverse_record_name(rzone, rdata)
                del_rr(rzone,
                       record,
                       "PTR",
                       normalize_zone(fqdn),
                       api=self.api)
        self.update_system_records()

    def remove_server_ns_records(self, fqdn):
        """
        Remove all NS records pointing to this server
        """
        ldap = self.api.Backend.ldap2
        ns_rdata = normalize_zone(fqdn)

        # find all NS records pointing to this server
        search_kw = {}
        search_kw['nsrecord'] = ns_rdata
        attr_filter = ldap.make_filter(search_kw, rules=ldap.MATCH_ALL)
        attributes = ['idnsname', 'objectclass']
        dn = DN(self.api.env.container_dns, self.api.env.basedn)

        entries, _truncated = ldap.find_entries(attr_filter,
                                                attributes,
                                                base_dn=dn)

        # remove records
        if entries:
            root_logger.debug("Removing all NS records pointing to %s:",
                              ns_rdata)

        for entry in entries:
            if 'idnszone' in entry['objectclass']:
                # zone record
                zone = entry.single_value['idnsname']
                root_logger.debug("zone record %s", zone)
                del_ns_rr(zone, u'@', ns_rdata, api=self.api)
            else:
                zone = entry.dn[1].value  # get zone from DN
                record = entry.single_value['idnsname']
                root_logger.debug("record %s in zone %s", record, zone)
                del_ns_rr(zone, record, ns_rdata, api=self.api)

    def update_system_records(self):
        self.print_msg("Updating DNS system records")
        system_records = IPASystemRecords(self.api)
        try:
            ((_ipa_rec, failed_ipa_rec),
             (_loc_rec, failed_loc_rec)) = system_records.update_dns_records()
        except IPADomainIsNotManagedByIPAError:
            root_logger.error(
                "IPA domain is not managed by IPA, please update records "
                "manually")
        else:
            if failed_ipa_rec or failed_loc_rec:
                root_logger.error("Update of following records failed:")
                for attr in (failed_ipa_rec, failed_loc_rec):
                    for rname, node, error in attr:
                        for record in IPASystemRecords.records_list_from_node(
                                rname, node):
                            root_logger.error("%s (%s)", record, error)

    def check_global_configuration(self):
        """
        Check global DNS configuration in LDAP server and inform user when it
        set and thus overrides his configured options in named.conf.
        """
        result = self.api.Command.dnsconfig_show()

        global_conf_set = any(
            param.name in result['result']
            for param in self.api.Object['dnsconfig'].params()
            if u'virtual_attribute' not in param.flags)

        if not global_conf_set:
            print("Global DNS configuration in LDAP server is empty")
            print(
                "You can use 'dnsconfig-mod' command to set global DNS options that"
            )
            print("would override settings in local named.conf files")
            return

        print("Global DNS configuration in LDAP server is not empty")
        print(
            "The following configuration options override local settings in named.conf:"
        )
        print("")
        textui = ipalib.cli.textui(self.api)
        self.api.Command.dnsconfig_show.output_for_cli(textui,
                                                       result,
                                                       None,
                                                       reverse=False)

    def is_configured(self):
        """
        Override the default logic querying StateFile for configuration status
        and look whether named.conf was already modified by IPA installer.
        """
        return named_conf_exists()

    def uninstall(self):
        if self.is_configured():
            self.print_msg("Unconfiguring %s" % self.service_name)

        running = self.restore_state("running")
        enabled = self.restore_state("enabled")
        named_regular_running = self.restore_state("named-regular-running")
        named_regular_enabled = self.restore_state("named-regular-enabled")

        self.dns_backup.clear_records(self.api.Backend.ldap2.isconnected())

        for f in [NAMED_CONF, RESOLV_CONF]:
            try:
                self.fstore.restore_file(f)
            except ValueError as error:
                root_logger.debug(error)

        # disabled by default, by ldap_enable()
        if enabled:
            self.enable()

        if running:
            self.restart()

        self.named_regular.unmask()
        if named_regular_enabled:
            self.named_regular.enable()

        if named_regular_running:
            self.named_regular.start()

        installutils.remove_keytab(self.keytab)
        installutils.remove_ccache(run_as=self.service_user)
Example #23
0
        # We are going to set the owner of all of the cert
        # files to the owner of the containing directory
        # instead of that of the process. This works when
        # this is called by root for a daemon that runs as
        # a normal user
        mode = os.stat(self.secdir)
        self.uid = mode[stat.ST_UID]
        self.gid = mode[stat.ST_GID]

        if fstore:
            self.fstore = fstore
        else:
            self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore')

    subject_base = ipautil.dn_attribute_property('_subject_base')

    def __del__(self):
        if self.reqdir is not None:
            shutil.rmtree(self.reqdir, ignore_errors=True)
        try:
            os.chdir(self.cwd)
        except:
            pass

    def setup_cert_request(self):
        """
        Create a temporary directory to store certificate requests and
        certificates. This should be called before requesting certificates.

        This is set outside of __init__ to avoid creating a temporary
Example #24
0
class DNSKeySyncInstance(service.Service):
    def __init__(self, fstore=None, logger=logger):
        super(DNSKeySyncInstance,
              self).__init__("ipa-dnskeysyncd",
                             service_desc="DNS key synchronization service",
                             fstore=fstore,
                             service_prefix=u'ipa-dnskeysyncd',
                             keytab=paths.IPA_DNSKEYSYNCD_KEYTAB)
        self.extra_config = [
            u'dnssecVersion 1',
        ]  # DNSSEC enabled
        self.named_uid = None
        self.named_gid = None
        self.ods_uid = None
        self.ods_gid = None

    suffix = ipautil.dn_attribute_property('_suffix')

    def set_dyndb_ldap_workdir_permissions(self):
        """
        Setting up correct permissions to allow write/read access for daemons
        """
        if self.named_uid is None:
            self.named_uid = self.__get_named_uid()

        if self.named_gid is None:
            self.named_gid = self.__get_named_gid()

        if not os.path.exists(paths.BIND_LDAP_DNS_IPA_WORKDIR):
            os.mkdir(paths.BIND_LDAP_DNS_IPA_WORKDIR, 0o770)
        # dnssec daemons require to have access into the directory
        os.chmod(paths.BIND_LDAP_DNS_IPA_WORKDIR, 0o770)
        os.chown(paths.BIND_LDAP_DNS_IPA_WORKDIR, self.named_uid,
                 self.named_gid)

    def remove_replica_public_keys(self, replica_fqdn):
        ldap = api.Backend.ldap2
        dn_base = DN(('cn', 'keys'), ('cn', 'sec'), ('cn', 'dns'),
                     api.env.basedn)
        keylabel = replica_keylabel_template % DNSName(replica_fqdn).\
            make_absolute().canonicalize().ToASCII()
        # get old keys from LDAP
        search_kw = {
            'objectclass': u"ipaPublicKeyObject",
            'ipk11Label': keylabel,
            'ipk11Wrap': True,
        }
        filter = ldap.make_filter(search_kw, rules=ldap.MATCH_ALL)
        entries, _truncated = ldap.find_entries(filter=filter, base_dn=dn_base)
        for entry in entries:
            ldap.delete_entry(entry)

    def start_dnskeysyncd(self):
        print("Restarting ipa-dnskeysyncd")
        self.__start()

    def create_instance(self, fqdn, realm_name):
        self.fqdn = fqdn
        self.realm = realm_name
        self.suffix = ipautil.realm_to_suffix(self.realm)
        try:
            self.stop()
        except Exception:
            pass

        # checking status step must be first
        self.step("checking status", self.__check_dnssec_status)
        self.step("setting up bind-dyndb-ldap working directory",
                  self.set_dyndb_ldap_workdir_permissions)
        self.step("setting up kerberos principal", self.__setup_principal)
        self.step("setting up SoftHSM", self.__setup_softhsm)
        self.step("adding DNSSEC containers", self.__setup_dnssec_containers)
        self.step("creating replica keys", self.__setup_replica_keys)
        self.step("configuring ipa-dnskeysyncd to start on boot",
                  self.__enable)
        # we need restart named after setting up this service
        self.start_creation()

    def __get_named_uid(self):
        try:
            return pwd.getpwnam(constants.NAMED_USER).pw_uid
        except KeyError:
            raise RuntimeError("Named UID not found")

    def __get_named_gid(self):
        try:
            return grp.getgrnam(constants.NAMED_GROUP).gr_gid
        except KeyError:
            raise RuntimeError("Named GID not found")

    def __check_dnssec_status(self):
        self.named_uid = self.__get_named_uid()
        self.named_gid = self.__get_named_gid()

        try:
            self.ods_uid = pwd.getpwnam(constants.ODS_USER).pw_uid
        except KeyError:
            raise RuntimeError("OpenDNSSEC UID not found")

        try:
            self.ods_gid = grp.getgrnam(constants.ODS_GROUP).gr_gid
        except KeyError:
            raise RuntimeError("OpenDNSSEC GID not found")

        if not dns_container_exists(self.suffix):
            raise RuntimeError("DNS container does not exist")

        # ready to be installed, storing a state is required to run uninstall
        self.backup_state("configured", True)

    def __setup_dnssec_containers(self):
        """
        Setup LDAP containers for DNSSEC
        """
        if dnssec_container_exists(self.suffix):

            logger.info("DNSSEC container exists (step skipped)")
            return

        self._ldap_mod("dnssec.ldif", {
            'SUFFIX': self.suffix,
        })

    def __setup_softhsm(self):
        assert self.ods_uid is not None
        assert self.named_gid is not None

        token_dir_exists = os.path.exists(paths.DNSSEC_TOKENS_DIR)

        # create dnssec directory
        if not os.path.exists(paths.IPA_DNSSEC_DIR):
            logger.debug("Creating %s directory", paths.IPA_DNSSEC_DIR)
            os.mkdir(paths.IPA_DNSSEC_DIR)
            os.chmod(paths.IPA_DNSSEC_DIR, 0o770)
            # chown ods:named
            os.chown(paths.IPA_DNSSEC_DIR, self.ods_uid, self.named_gid)

        # setup softhsm2 config file
        softhsm_conf_txt = ("# SoftHSM v2 configuration file \n"
                            "# File generated by IPA instalation\n"
                            "directories.tokendir = %(tokens_dir)s\n"
                            "objectstore.backend = file") % {
                                'tokens_dir': paths.DNSSEC_TOKENS_DIR
                            }
        logger.debug("Creating new softhsm config file")
        named_fd = open(paths.DNSSEC_SOFTHSM2_CONF, 'w')
        named_fd.seek(0)
        named_fd.truncate(0)
        named_fd.write(softhsm_conf_txt)
        named_fd.close()
        os.chmod(paths.DNSSEC_SOFTHSM2_CONF, 0o644)

        # setting up named to use softhsm2
        if not self.fstore.has_file(paths.SYSCONFIG_NAMED):
            self.fstore.backup_file(paths.SYSCONFIG_NAMED)

        # setting up named and ipa-dnskeysyncd to use our softhsm2 config
        for sysconfig in [
                paths.SYSCONFIG_NAMED, paths.SYSCONFIG_IPA_DNSKEYSYNCD
        ]:
            directivesetter.set_directive(sysconfig,
                                          'SOFTHSM2_CONF',
                                          paths.DNSSEC_SOFTHSM2_CONF,
                                          quotes=False,
                                          separator='=')

        if (token_dir_exists and os.path.exists(paths.DNSSEC_SOFTHSM_PIN)
                and os.path.exists(paths.DNSSEC_SOFTHSM_PIN_SO)):
            # there is initialized softhsm
            return

        # remove old tokens
        if token_dir_exists:
            logger.debug('Removing old tokens directory %s',
                         paths.DNSSEC_TOKENS_DIR)
            shutil.rmtree(paths.DNSSEC_TOKENS_DIR)

        # create tokens subdirectory
        logger.debug('Creating tokens %s directory', paths.DNSSEC_TOKENS_DIR)
        # sticky bit is required by daemon
        os.mkdir(paths.DNSSEC_TOKENS_DIR)
        os.chmod(paths.DNSSEC_TOKENS_DIR, 0o770 | stat.S_ISGID)
        # chown to ods:named
        os.chown(paths.DNSSEC_TOKENS_DIR, self.ods_uid, self.named_gid)

        # generate PINs for softhsm
        pin_length = 30  # Bind allows max 32 bytes including ending '\0'
        pin = ipautil.ipa_generate_password(entropy_bits=0,
                                            special=None,
                                            min_len=pin_length)
        pin_so = ipautil.ipa_generate_password(entropy_bits=0,
                                               special=None,
                                               min_len=pin_length)

        logger.debug("Saving user PIN to %s", paths.DNSSEC_SOFTHSM_PIN)
        named_fd = open(paths.DNSSEC_SOFTHSM_PIN, 'w')
        named_fd.seek(0)
        named_fd.truncate(0)
        named_fd.write(pin)
        named_fd.close()
        os.chmod(paths.DNSSEC_SOFTHSM_PIN, 0o770)
        # chown to ods:named
        os.chown(paths.DNSSEC_SOFTHSM_PIN, self.ods_uid, self.named_gid)

        logger.debug("Saving SO PIN to %s", paths.DNSSEC_SOFTHSM_PIN_SO)
        named_fd = open(paths.DNSSEC_SOFTHSM_PIN_SO, 'w')
        named_fd.seek(0)
        named_fd.truncate(0)
        named_fd.write(pin_so)
        named_fd.close()
        # owner must be root
        os.chmod(paths.DNSSEC_SOFTHSM_PIN_SO, 0o400)

        # initialize SoftHSM

        command = [
            paths.SOFTHSM2_UTIL,
            '--init-token',
            '--free',  # use random free slot
            '--label',
            SOFTHSM_DNSSEC_TOKEN_LABEL,
            '--pin',
            pin,
            '--so-pin',
            pin_so,
        ]
        logger.debug("Initializing tokens")
        os.environ["SOFTHSM2_CONF"] = paths.DNSSEC_SOFTHSM2_CONF
        ipautil.run(command, nolog=(
            pin,
            pin_so,
        ))

    def __setup_replica_keys(self):
        keylabel = replica_keylabel_template % DNSName(self.fqdn).\
            make_absolute().canonicalize().ToASCII()

        ldap = api.Backend.ldap2
        dn_base = DN(('cn', 'keys'), ('cn', 'sec'), ('cn', 'dns'),
                     api.env.basedn)

        with open(paths.DNSSEC_SOFTHSM_PIN, "r") as f:
            pin = f.read()

        os.environ["SOFTHSM2_CONF"] = paths.DNSSEC_SOFTHSM2_CONF
        p11 = _ipap11helper.P11_Helper(SOFTHSM_DNSSEC_TOKEN_LABEL, pin,
                                       paths.LIBSOFTHSM2_SO)

        try:
            # generate replica keypair
            logger.debug("Creating replica's key pair")
            key_id = None
            while True:
                # check if key with this ID exist in softHSM
                key_id = _ipap11helper.gen_key_id()
                replica_pubkey_dn = DN(('ipk11UniqueId', 'autogenerate'),
                                       dn_base)

                pub_keys = p11.find_keys(_ipap11helper.KEY_CLASS_PUBLIC_KEY,
                                         label=keylabel,
                                         id=key_id)
                if pub_keys:
                    # key with id exists
                    continue

                priv_keys = p11.find_keys(_ipap11helper.KEY_CLASS_PRIVATE_KEY,
                                          label=keylabel,
                                          id=key_id)
                if not priv_keys:
                    break  # we found unique id

            public_key_handle, _privkey_handle = p11.generate_replica_key_pair(
                keylabel,
                key_id,
                pub_cka_verify=False,
                pub_cka_verify_recover=False,
                pub_cka_wrap=True,
                priv_cka_unwrap=True,
                priv_cka_sensitive=True,
                priv_cka_extractable=False)

            # export public key
            public_key_blob = p11.export_public_key(public_key_handle)

            # save key to LDAP
            replica_pubkey_objectclass = [
                'ipk11Object', 'ipk11PublicKey', 'ipaPublicKeyObject', 'top'
            ]
            kw = {
                'objectclass': replica_pubkey_objectclass,
                'ipk11UniqueId': [u'autogenerate'],
                'ipk11Label': [keylabel],
                'ipaPublicKey': [public_key_blob],
                'ipk11Id': [key_id],
                'ipk11Wrap': [True],
                'ipk11Verify': [False],
                'ipk11VerifyRecover': [False],
            }

            logger.debug("Storing replica public key to LDAP, %s",
                         replica_pubkey_dn)

            entry = ldap.make_entry(replica_pubkey_dn, **kw)
            ldap.add_entry(entry)
            logger.debug("Replica public key stored")

            logger.debug("Setting CKA_WRAP=False for old replica keys")
            # first create new keys, we don't want disable keys before, we
            # have new keys in softhsm and LDAP

            # get replica pub keys with CKA_WRAP=True
            replica_pub_keys = p11.find_keys(
                _ipap11helper.KEY_CLASS_PUBLIC_KEY,
                label=keylabel,
                cka_wrap=True)
            # old keys in softHSM
            for handle in replica_pub_keys:
                # don't disable wrapping for new key
                # compare IDs not handle
                if key_id != p11.get_attribute(handle, _ipap11helper.CKA_ID):
                    p11.set_attribute(handle, _ipap11helper.CKA_WRAP, False)

            # get old keys from LDAP
            search_kw = {
                'objectclass': u"ipaPublicKeyObject",
                'ipk11Label': keylabel,
                'ipk11Wrap': True,
            }
            filter = ldap.make_filter(search_kw, rules=ldap.MATCH_ALL)
            entries, _truncated = ldap.find_entries(filter=filter,
                                                    base_dn=dn_base)
            for entry in entries:
                # don't disable wrapping for new key
                if entry.single_value['ipk11Id'] != key_id:
                    entry['ipk11Wrap'] = [False]
                    ldap.update_entry(entry)

        finally:
            p11.finalize()

        # change tokens mod/owner
        logger.debug("Changing ownership of token files")
        for (root, dirs, files) in os.walk(paths.DNSSEC_TOKENS_DIR):
            for directory in dirs:
                dir_path = os.path.join(root, directory)
                os.chmod(dir_path, 0o770 | stat.S_ISGID)
                # chown to ods:named
                os.chown(dir_path, self.ods_uid, self.named_gid)
            for filename in files:
                file_path = os.path.join(root, filename)
                os.chmod(file_path, 0o770 | stat.S_ISGID)
                # chown to ods:named
                os.chown(file_path, self.ods_uid, self.named_gid)

    def __enable(self):
        try:
            self.ldap_configure('DNSKeySync', self.fqdn, None, self.suffix,
                                self.extra_config)
        except errors.DuplicateEntry:
            logger.error("DNSKeySync service already exists")

    def __setup_principal(self):
        assert self.ods_gid is not None
        installutils.remove_keytab(self.keytab)
        installutils.kadmin_addprinc(self.principal)

        # Store the keytab on disk
        installutils.create_keytab(self.keytab, self.principal)
        p = self.move_service(self.principal)
        if p is None:
            # the service has already been moved, perhaps we're doing a DNS reinstall
            dnssynckey_principal_dn = DN(('krbprincipalname', self.principal),
                                         ('cn', 'services'),
                                         ('cn', 'accounts'), self.suffix)
        else:
            dnssynckey_principal_dn = p

        # Make sure access is strictly reserved to the named user
        os.chown(self.keytab, 0, self.ods_gid)
        os.chmod(self.keytab, 0o440)

        dns_group = DN(('cn', 'DNS Servers'), ('cn', 'privileges'),
                       ('cn', 'pbac'), self.suffix)
        mod = [(ldap.MOD_ADD, 'member', dnssynckey_principal_dn)]

        try:
            api.Backend.ldap2.modify_s(dns_group, mod)
        except ldap.TYPE_OR_VALUE_EXISTS:
            pass
        except Exception as e:
            logger.critical("Could not modify principal's %s entry: %s",
                            dnssynckey_principal_dn, str(e))
            raise

        # bind-dyndb-ldap persistent search feature requires both size and time
        # limit-free connection

        mod = [(ldap.MOD_REPLACE, 'nsTimeLimit', '-1'),
               (ldap.MOD_REPLACE, 'nsSizeLimit', '-1'),
               (ldap.MOD_REPLACE, 'nsIdleTimeout', '-1'),
               (ldap.MOD_REPLACE, 'nsLookThroughLimit', '-1')]
        try:
            api.Backend.ldap2.modify_s(dnssynckey_principal_dn, mod)
        except Exception as e:
            logger.critical("Could not set principal's %s LDAP limits: %s",
                            dnssynckey_principal_dn, str(e))
            raise

    def __start(self):
        try:
            self.restart()
        except Exception as e:
            print("Failed to start ipa-dnskeysyncd")
            logger.debug("Failed to start ipa-dnskeysyncd: %s", e)

    def uninstall(self):
        if self.is_configured():
            self.print_msg("Unconfiguring %s" % self.service_name)

        # Just eat states
        self.restore_state("running")
        self.restore_state("enabled")
        self.restore_state("configured")

        # stop and disable service (IPA service, we do not need it anymore)
        self.stop()
        self.disable()

        for f in [paths.SYSCONFIG_NAMED]:
            try:
                self.fstore.restore_file(f)
            except ValueError as error:
                logger.debug('%s', error)

        # remove softhsm pin, to make sure new installation will generate
        # new token database
        # do not delete *so pin*, user can need it to get token data
        installutils.remove_file(paths.DNSSEC_SOFTHSM_PIN)
        installutils.remove_file(paths.DNSSEC_SOFTHSM2_CONF)

        try:
            shutil.rmtree(paths.DNSSEC_TOKENS_DIR)
        except OSError as e:
            if e.errno != errno.ENOENT:
                logger.exception("Failed to remove %s",
                                 paths.DNSSEC_TOKENS_DIR)

        installutils.remove_keytab(self.keytab)
Example #25
0
class HTTPInstance(service.Service):
    def __init__(self, fstore=None, cert_nickname='Server-Cert', api=api):
        super(HTTPInstance, self).__init__("httpd",
                                           service_desc="the web interface",
                                           fstore=fstore,
                                           api=api,
                                           service_prefix=u'HTTP',
                                           service_user=HTTPD_USER,
                                           keytab=paths.HTTP_KEYTAB)

        self.cacert_nickname = None
        self.cert_nickname = cert_nickname
        self.ca_is_configured = True
        self.keytab_user = constants.GSSPROXY_USER

    subject_base = ipautil.dn_attribute_property('_subject_base')

    def create_instance(self,
                        realm,
                        fqdn,
                        domain_name,
                        dm_password=None,
                        pkcs12_info=None,
                        subject_base=None,
                        auto_redirect=True,
                        ca_file=None,
                        ca_is_configured=None,
                        promote=False,
                        master_fqdn=None):
        self.fqdn = fqdn
        self.realm = realm
        self.domain = domain_name
        self.dm_password = dm_password
        self.suffix = ipautil.realm_to_suffix(self.realm)
        self.pkcs12_info = pkcs12_info
        self.cert = None
        self.subject_base = subject_base
        self.sub_dict = dict(
            REALM=realm,
            FQDN=fqdn,
            DOMAIN=self.domain,
            AUTOREDIR='' if auto_redirect else '#',
            CRL_PUBLISH_PATH=paths.PKI_CA_PUBLISH_DIR,
            FONTS_DIR=paths.FONTS_DIR,
            GSSAPI_SESSION_KEY=paths.GSSAPI_SESSION_KEY,
            IPA_CUSTODIA_SOCKET=paths.IPA_CUSTODIA_SOCKET,
            IPA_CCACHES=paths.IPA_CCACHES,
            WSGI_PREFIX_DIR=paths.WSGI_PREFIX_DIR,
        )
        self.ca_file = ca_file
        if ca_is_configured is not None:
            self.ca_is_configured = ca_is_configured
        self.promote = promote
        self.master_fqdn = master_fqdn

        self.step("stopping httpd", self.__stop)
        self.step("setting mod_nss port to 443", self.__set_mod_nss_port)
        self.step("setting mod_nss cipher suite",
                  self.set_mod_nss_cipher_suite)
        self.step("setting mod_nss protocol list to TLSv1.0 - TLSv1.2",
                  self.set_mod_nss_protocol)
        self.step("setting mod_nss password file",
                  self.__set_mod_nss_passwordfile)
        self.step("enabling mod_nss renegotiate",
                  self.enable_mod_nss_renegotiate)
        self.step("disabling mod_nss OCSP", self.disable_mod_nss_ocsp)
        self.step("adding URL rewriting rules", self.__add_include)
        self.step("configuring httpd", self.__configure_http)
        self.step("setting up httpd keytab", self.request_service_keytab)
        self.step("configuring Gssproxy", self.configure_gssproxy)
        self.step("setting up ssl", self.__setup_ssl)
        if self.ca_is_configured:
            self.step("configure certmonger for renewals",
                      self.configure_certmonger_renewal_guard)
        self.step("importing CA certificates from LDAP",
                  self.__import_ca_certs)
        self.step("publish CA cert", self.__publish_ca_cert)
        self.step("clean up any existing httpd ccaches",
                  self.remove_httpd_ccaches)
        self.step("configuring SELinux for httpd",
                  self.configure_selinux_for_httpd)
        if not self.is_kdcproxy_configured():
            self.step("create KDC proxy config", self.create_kdcproxy_conf)
            self.step("enable KDC proxy", self.enable_kdcproxy)
        self.step("starting httpd", self.start)
        self.step("configuring httpd to start on boot", self.__enable)
        self.step("enabling oddjobd", self.enable_and_start_oddjobd)

        self.start_creation()

    def __stop(self):
        self.backup_state("running", self.is_running())
        self.stop()

    def __enable(self):
        self.backup_state("enabled", self.is_enabled())
        # We do not let the system start IPA components on its own,
        # Instead we reply on the IPA init script to start only enabled
        # components as found in our LDAP configuration tree
        self.ldap_enable('HTTP', self.fqdn, None, self.suffix)

    def configure_selinux_for_httpd(self):
        try:
            tasks.set_selinux_booleans(constants.SELINUX_BOOLEAN_HTTPD,
                                       self.backup_state)
        except ipapython.errors.SetseboolError as e:
            self.print_msg(e.format_service_warning('web interface'))

    def remove_httpd_ccaches(self):
        # Clean up existing ccaches
        # Make sure that empty env is passed to avoid passing KRB5CCNAME from
        # current env
        installutils.remove_file(paths.HTTP_CCACHE)
        for f in os.listdir(paths.IPA_CCACHES):
            os.remove(os.path.join(paths.IPA_CCACHES, f))

    def __configure_http(self):
        self.update_httpd_service_ipa_conf()
        self.update_httpd_wsgi_conf()

        target_fname = paths.HTTPD_IPA_CONF
        http_txt = ipautil.template_file(
            os.path.join(paths.USR_SHARE_IPA_DIR, "ipa.conf.template"),
            self.sub_dict)
        self.fstore.backup_file(paths.HTTPD_IPA_CONF)
        http_fd = open(target_fname, "w")
        http_fd.write(http_txt)
        http_fd.close()
        os.chmod(target_fname, 0o644)

        target_fname = paths.HTTPD_IPA_REWRITE_CONF
        http_txt = ipautil.template_file(
            os.path.join(paths.USR_SHARE_IPA_DIR, "ipa-rewrite.conf.template"),
            self.sub_dict)
        self.fstore.backup_file(paths.HTTPD_IPA_REWRITE_CONF)
        http_fd = open(target_fname, "w")
        http_fd.write(http_txt)
        http_fd.close()
        os.chmod(target_fname, 0o644)

    def configure_gssproxy(self):
        tasks.configure_http_gssproxy_conf(IPAAPI_USER)
        services.knownservices.gssproxy.restart()

    def change_mod_nss_port_from_http(self):
        # mod_ssl enforces SSLEngine on for vhost on 443 even though
        # the listener is mod_nss. This then crashes the httpd as mod_nss
        # listened port obviously does not match mod_ssl requirements.
        #
        # The workaround for this was to change port to http. It is no longer
        # necessary, as mod_nss now ships with default configuration which
        # sets SSLEngine off when mod_ssl is installed.
        #
        # Remove the workaround.
        if sysupgrade.get_upgrade_state('nss.conf', 'listen_port_updated'):
            installutils.set_directive(paths.HTTPD_NSS_CONF,
                                       'Listen',
                                       '443',
                                       quotes=False)
            sysupgrade.set_upgrade_state('nss.conf', 'listen_port_updated',
                                         False)

    def __set_mod_nss_port(self):
        self.fstore.backup_file(paths.HTTPD_NSS_CONF)
        if installutils.update_file(paths.HTTPD_NSS_CONF, '8443', '443') != 0:
            print("Updating port in %s failed." % paths.HTTPD_NSS_CONF)

    def __set_mod_nss_nickname(self, nickname):
        quoted_nickname = installutils.quote_directive_value(nickname,
                                                             quote_char="'")
        installutils.set_directive(paths.HTTPD_NSS_CONF,
                                   'NSSNickname',
                                   quoted_nickname,
                                   quotes=False)

    def get_mod_nss_nickname(self):
        cert = installutils.get_directive(paths.HTTPD_NSS_CONF, 'NSSNickname')
        nickname = installutils.unquote_directive_value(cert, quote_char="'")
        return nickname

    def set_mod_nss_protocol(self):
        installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSProtocol',
                                   'TLSv1.0,TLSv1.1,TLSv1.2', False)

    def enable_mod_nss_renegotiate(self):
        installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSRenegotiation',
                                   'on', False)
        installutils.set_directive(paths.HTTPD_NSS_CONF,
                                   'NSSRequireSafeNegotiation', 'on', False)

    def disable_mod_nss_ocsp(self):
        if sysupgrade.get_upgrade_state('http', NSS_OCSP_ENABLED) is None:
            self.__disable_mod_nss_ocsp()
            sysupgrade.set_upgrade_state('http', NSS_OCSP_ENABLED, False)

    def __disable_mod_nss_ocsp(self):
        aug = Augeas(flags=Augeas.NO_LOAD | Augeas.NO_MODL_AUTOLOAD)

        aug.set('/augeas/load/Httpd/lens', 'Httpd.lns')
        aug.set('/augeas/load/Httpd/incl', paths.HTTPD_NSS_CONF)
        aug.load()

        path = '/files{}/VirtualHost'.format(paths.HTTPD_NSS_CONF)
        ocsp_path = '{}/directive[.="{}"]'.format(path, OCSP_DIRECTIVE)
        ocsp_arg = '{}/arg'.format(ocsp_path)
        ocsp_comment = '{}/#comment[.="{}"]'.format(path, OCSP_DIRECTIVE)

        ocsp_dir = aug.get(ocsp_path)

        # there is NSSOCSP directive in nss.conf file, comment it
        # otherwise just do nothing
        if ocsp_dir is not None:
            ocsp_state = aug.get(ocsp_arg)
            aug.remove(ocsp_arg)
            aug.rename(ocsp_path, '#comment')
            aug.set(ocsp_comment, '{} {}'.format(OCSP_DIRECTIVE, ocsp_state))
            aug.save()

    def set_mod_nss_cipher_suite(self):
        ciphers = ','.join(NSS_CIPHER_SUITE)
        installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSCipherSuite',
                                   ciphers, False)

    def __set_mod_nss_passwordfile(self):
        installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSPassPhraseDialog',
                                   'file:' + paths.HTTPD_PASSWORD_CONF)

    def __add_include(self):
        """This should run after __set_mod_nss_port so is already backed up"""
        if installutils.update_file(
                paths.HTTPD_NSS_CONF, '</VirtualHost>',
                'Include {path}\n</VirtualHost>'.format(
                    path=paths.HTTPD_IPA_REWRITE_CONF)) != 0:
            print("Adding Include conf.d/ipa-rewrite to %s failed." %
                  paths.HTTPD_NSS_CONF)

    def configure_certmonger_renewal_guard(self):
        certmonger = services.knownservices.certmonger
        certmonger_stopped = not certmonger.is_running()

        if certmonger_stopped:
            certmonger.start()
        try:
            bus = dbus.SystemBus()
            obj = bus.get_object('org.fedorahosted.certmonger',
                                 '/org/fedorahosted/certmonger')
            iface = dbus.Interface(obj, 'org.fedorahosted.certmonger')
            path = iface.find_ca_by_nickname('IPA')
            if path:
                ca_obj = bus.get_object('org.fedorahosted.certmonger', path)
                ca_iface = dbus.Interface(ca_obj,
                                          'org.freedesktop.DBus.Properties')
                helper = ca_iface.Get('org.fedorahosted.certmonger.ca',
                                      'external-helper')
                if helper:
                    args = shlex.split(helper)
                    if args[0] != paths.IPA_SERVER_GUARD:
                        self.backup_state('certmonger_ipa_helper', helper)
                        args = [paths.IPA_SERVER_GUARD] + args
                        helper = ' '.join(pipes.quote(a) for a in args)
                        ca_iface.Set('org.fedorahosted.certmonger.ca',
                                     'external-helper', helper)
        finally:
            if certmonger_stopped:
                certmonger.stop()

    def create_password_conf(self):
        """
        This is the format of mod_nss pin files.
        """
        pwd_conf = paths.HTTPD_PASSWORD_CONF
        ipautil.backup_file(pwd_conf)

        passwd_fname = os.path.join(paths.HTTPD_ALIAS_DIR, 'pwdfile.txt')
        with open(passwd_fname, 'r') as pwdfile:
            password = pwdfile.read()

        with open(pwd_conf, "w") as f:
            f.write("internal:")
            f.write(password)
            f.write("\nNSS FIPS 140-2 Certificate DB:")
            f.write(password)
            # make sure other processes can access the file contents ASAP
            f.flush()
        pent = pwd.getpwnam(constants.HTTPD_USER)
        os.chown(pwd_conf, pent.pw_uid, pent.pw_gid)
        os.chmod(pwd_conf, 0o400)

    def disable_system_trust(self):
        name = 'Root Certs'
        args = [paths.MODUTIL, '-dbdir', paths.HTTPD_ALIAS_DIR, '-force']

        try:
            result = ipautil.run(args + ['-list', name],
                                 env={},
                                 capture_output=True)
        except ipautil.CalledProcessError as e:
            if e.returncode == 29:  # ERROR: Module not found in database.
                logger.debug('Module %s not available, treating as disabled',
                             name)
                return False
            raise

        if 'Status: Enabled' in result.output:
            ipautil.run(args + ['-disable', name], env={})
            return True

        return False

    def __setup_ssl(self):
        db = certs.CertDB(self.realm,
                          nssdir=paths.HTTPD_ALIAS_DIR,
                          subject_base=self.subject_base,
                          user="******",
                          group=constants.HTTPD_GROUP,
                          create=True)
        self.disable_system_trust()
        self.create_password_conf()

        if self.pkcs12_info:
            if self.ca_is_configured:
                trust_flags = IPA_CA_TRUST_FLAGS
            else:
                trust_flags = EXTERNAL_CA_TRUST_FLAGS
            db.init_from_pkcs12(self.pkcs12_info[0],
                                self.pkcs12_info[1],
                                ca_file=self.ca_file,
                                trust_flags=trust_flags)
            server_certs = db.find_server_certs()
            if len(server_certs) == 0:
                raise RuntimeError(
                    "Could not find a suitable server cert in import in %s" %
                    self.pkcs12_info[0])

            # We only handle one server cert
            nickname = server_certs[0][0]
            if nickname == 'ipaCert':
                nickname = server_certs[1][0]
            self.cert = db.get_cert_from_db(nickname)

            if self.ca_is_configured:
                db.track_server_cert(nickname, self.principal, db.passwd_fname,
                                     'restart_httpd')

            self.__set_mod_nss_nickname(nickname)
            self.add_cert_to_service()

        else:
            if not self.promote:
                ca_args = [
                    paths.CERTMONGER_DOGTAG_SUBMIT, '--ee-url',
                    'https://%s:8443/ca/ee/ca' % self.fqdn, '--certfile',
                    paths.RA_AGENT_PEM, '--keyfile', paths.RA_AGENT_KEY,
                    '--cafile', paths.IPA_CA_CRT, '--agent-submit'
                ]
                helper = " ".join(ca_args)
                prev_helper = certmonger.modify_ca_helper('IPA', helper)
            else:
                prev_helper = None
            try:
                certmonger.request_and_wait_for_cert(
                    certpath=db.secdir,
                    nickname=self.cert_nickname,
                    principal=self.principal,
                    passwd_fname=db.passwd_fname,
                    subject=str(DN(('CN', self.fqdn), self.subject_base)),
                    ca='IPA',
                    profile=dogtag.DEFAULT_PROFILE,
                    dns=[self.fqdn],
                    post_command='restart_httpd')
            finally:
                if prev_helper is not None:
                    certmonger.modify_ca_helper('IPA', prev_helper)

            self.cert = db.get_cert_from_db(self.cert_nickname)

            if prev_helper is not None:
                self.add_cert_to_service()

            # Verify we have a valid server cert
            server_certs = db.find_server_certs()
            if not server_certs:
                raise RuntimeError("Could not find a suitable server cert.")

        # store the CA cert nickname so that we can publish it later on
        self.cacert_nickname = db.cacert_name

    def __import_ca_certs(self):
        db = certs.CertDB(self.realm,
                          nssdir=paths.HTTPD_ALIAS_DIR,
                          subject_base=self.subject_base)
        self.import_ca_certs(db, self.ca_is_configured)

    def __publish_ca_cert(self):
        ca_db = certs.CertDB(self.realm,
                             nssdir=paths.HTTPD_ALIAS_DIR,
                             subject_base=self.subject_base)
        ca_db.export_pem_cert(self.cacert_nickname, paths.CA_CRT)

    def is_kdcproxy_configured(self):
        """Check if KDC proxy has already been configured in the past"""
        return os.path.isfile(paths.HTTPD_IPA_KDCPROXY_CONF)

    def enable_kdcproxy(self):
        """Add ipaConfigString=kdcProxyEnabled to cn=KDC"""
        service.set_service_entry_config('KDC', self.fqdn,
                                         [u'kdcProxyEnabled'], self.suffix)

    def create_kdcproxy_conf(self):
        """Create ipa-kdc-proxy.conf in /etc/ipa/kdcproxy"""
        target_fname = paths.HTTPD_IPA_KDCPROXY_CONF
        sub_dict = dict(KDCPROXY_CONFIG=paths.KDCPROXY_CONFIG)
        http_txt = ipautil.template_file(
            os.path.join(paths.USR_SHARE_IPA_DIR,
                         "ipa-kdc-proxy.conf.template"), sub_dict)
        self.fstore.backup_file(target_fname)
        with open(target_fname, 'w') as f:
            f.write(http_txt)
        os.chmod(target_fname, 0o644)

    def enable_and_start_oddjobd(self):
        oddjobd = services.service('oddjobd', api)
        self.sstore.backup_state('oddjobd', 'running', oddjobd.is_running())
        self.sstore.backup_state('oddjobd', 'enabled', oddjobd.is_enabled())

        try:
            oddjobd.enable()
            oddjobd.start()
        except Exception as e:
            logger.critical("Unable to start oddjobd: %s", str(e))

    def update_httpd_service_ipa_conf(self):
        tasks.configure_httpd_service_ipa_conf()

    def update_httpd_wsgi_conf(self):
        tasks.configure_httpd_wsgi_conf()

    def uninstall(self):
        if self.is_configured():
            self.print_msg("Unconfiguring web server")

        running = self.restore_state("running")
        enabled = self.restore_state("enabled")

        # Restore oddjobd to its original state
        oddjobd = services.service('oddjobd', api)

        if not self.sstore.restore_state('oddjobd', 'running'):
            try:
                oddjobd.stop()
            except Exception:
                pass

        if not self.sstore.restore_state('oddjobd', 'enabled'):
            try:
                oddjobd.disable()
            except Exception:
                pass

        self.stop_tracking_certificates()

        helper = self.restore_state('certmonger_ipa_helper')
        if helper:
            bus = dbus.SystemBus()
            obj = bus.get_object('org.fedorahosted.certmonger',
                                 '/org/fedorahosted/certmonger')
            iface = dbus.Interface(obj, 'org.fedorahosted.certmonger')
            path = iface.find_ca_by_nickname('IPA')
            if path:
                ca_obj = bus.get_object('org.fedorahosted.certmonger', path)
                ca_iface = dbus.Interface(ca_obj,
                                          'org.freedesktop.DBus.Properties')
                ca_iface.Set('org.fedorahosted.certmonger.ca',
                             'external-helper', helper)

        db = certs.CertDB(self.realm, paths.HTTPD_ALIAS_DIR)
        db.restore()

        for f in [
                paths.HTTPD_IPA_CONF, paths.HTTPD_SSL_CONF,
                paths.HTTPD_NSS_CONF
        ]:
            try:
                self.fstore.restore_file(f)
            except ValueError as error:
                logger.debug("%s", error)

        installutils.remove_keytab(self.keytab)
        installutils.remove_file(paths.HTTP_CCACHE)

        # Remove the configuration files we create
        installutils.remove_file(paths.HTTPD_IPA_REWRITE_CONF)
        installutils.remove_file(paths.HTTPD_IPA_CONF)
        installutils.remove_file(paths.HTTPD_IPA_PKI_PROXY_CONF)
        installutils.remove_file(paths.HTTPD_IPA_KDCPROXY_CONF_SYMLINK)
        installutils.remove_file(paths.HTTPD_IPA_KDCPROXY_CONF)
        if paths.HTTPD_IPA_WSGI_MODULES_CONF is not None:
            installutils.remove_file(paths.HTTPD_IPA_WSGI_MODULES_CONF)

        # Restore SELinux boolean states
        boolean_states = {
            name: self.restore_state(name)
            for name in constants.SELINUX_BOOLEAN_HTTPD
        }
        try:
            tasks.set_selinux_booleans(boolean_states)
        except ipapython.errors.SetseboolError as e:
            self.print_msg('WARNING: ' + str(e))

        if running:
            self.restart()

        # disabled by default, by ldap_enable()
        if enabled:
            self.enable()

    def stop_tracking_certificates(self):
        db = certs.CertDB(api.env.realm, nssdir=paths.HTTPD_ALIAS_DIR)
        db.untrack_server_cert(self.get_mod_nss_nickname())

    def start_tracking_certificates(self):
        db = certs.CertDB(self.realm, nssdir=paths.HTTPD_ALIAS_DIR)
        nickname = self.get_mod_nss_nickname()
        if db.is_ipa_issued_cert(api, nickname):
            db.track_server_cert(nickname, self.principal, db.passwd_fname,
                                 'restart_httpd')
        else:
            logger.debug(
                "Will not track HTTP server cert %s as it is not "
                "issued by IPA", nickname)

    def request_service_keytab(self):
        super(HTTPInstance, self).request_service_keytab()

        if self.master_fqdn is not None:
            service_dn = DN(('krbprincipalname', self.principal),
                            api.env.container_service, self.suffix)

            ldap_uri = ipaldap.get_ldap_uri(self.master_fqdn)
            with ipaldap.LDAPClient(ldap_uri,
                                    start_tls=not self.promote,
                                    cacert=paths.IPA_CA_CRT) as remote_ldap:
                if self.promote:
                    remote_ldap.gssapi_bind()
                else:
                    remote_ldap.simple_bind(ipaldap.DIRMAN_DN,
                                            self.dm_password)
                replication.wait_for_entry(remote_ldap, service_dn, timeout=60)
Example #26
0
class CertDB(object):
    """An IPA-server-specific wrapper around NSS

    This class knows IPA-specific details such as nssdir location, or the
    CA cert name.

    ``subject_base``
      Realm subject base DN.  This argument is required when creating
      server or object signing certs.
    ``ca_subject``
      IPA CA subject DN.  This argument is required when importing
      CA certificates into the certificate database.

    """

    # TODO: Remove all selfsign code
    def __init__(self,
                 realm,
                 nssdir,
                 fstore=None,
                 host_name=None,
                 subject_base=None,
                 ca_subject=None,
                 user=None,
                 group=None,
                 mode=None,
                 create=False,
                 dbtype='auto'):
        self.nssdb = NSSDatabase(nssdir, dbtype=dbtype)

        self.realm = realm

        self.noise_fname = os.path.join(self.secdir, "noise.txt")

        self.pk12_fname = os.path.join(self.secdir, "cacert.p12")
        self.pin_fname = os.path.join(self.secdir + "pin.txt")
        self.reqdir = None
        self.certreq_fname = None
        self.certder_fname = None
        self.host_name = host_name
        self.ca_subject = ca_subject
        self.subject_base = subject_base

        self.cacert_name = get_ca_nickname(self.realm)

        self.user = user
        self.group = group
        self.mode = mode
        self.uid = 0
        self.gid = 0

        if not create:
            if os.path.isdir(self.secdir):
                # We are going to set the owner of all of the cert
                # files to the owner of the containing directory
                # instead of that of the process. This works when
                # this is called by root for a daemon that runs as
                # a normal user
                mode = os.stat(self.secdir)
                self.uid = mode[stat.ST_UID]
                self.gid = mode[stat.ST_GID]
        else:
            if user is not None:
                pu = pwd.getpwnam(user)
                self.uid = pu.pw_uid
                self.gid = pu.pw_gid
            if group is not None:
                self.gid = grp.getgrnam(group).gr_gid
            self.create_certdbs()

        if fstore:
            self.fstore = fstore
        else:
            self.fstore = sysrestore.FileStore(paths.SYSRESTORE)

    ca_subject = ipautil.dn_attribute_property('_ca_subject')
    subject_base = ipautil.dn_attribute_property('_subject_base')

    # migration changes paths, just forward attribute lookup to nssdb
    @property
    def secdir(self):
        return self.nssdb.secdir

    @property
    def dbtype(self):
        return self.nssdb.dbtype

    @property
    def certdb_fname(self):
        return self.nssdb.certdb

    @property
    def keydb_fname(self):
        return self.nssdb.keydb

    @property
    def secmod_fname(self):
        return self.nssdb.secmod

    @property
    def passwd_fname(self):
        return self.nssdb.pwd_file

    def exists(self):
        """
        Checks whether all NSS database files + our pwd_file exist
        """
        return self.nssdb.exists()

    def __del__(self):
        if self.reqdir is not None:
            shutil.rmtree(self.reqdir, ignore_errors=True)
            self.reqdir = None
        self.nssdb.close()

    def setup_cert_request(self):
        """
        Create a temporary directory to store certificate requests and
        certificates. This should be called before requesting certificates.

        This is set outside of __init__ to avoid creating a temporary
        directory every time we open a cert DB.
        """
        if self.reqdir is not None:
            return

        self.reqdir = tempfile.mkdtemp('', 'ipa-', paths.VAR_LIB_IPA)
        self.certreq_fname = self.reqdir + "/tmpcertreq"
        self.certder_fname = self.reqdir + "/tmpcert.der"

    def set_perms(self, fname, write=False):
        perms = stat.S_IRUSR
        if write:
            perms |= stat.S_IWUSR
        if hasattr(fname, 'fileno'):
            os.fchown(fname.fileno(), self.uid, self.gid)
            os.fchmod(fname.fileno(), perms)
        else:
            os.chown(fname, self.uid, self.gid)
            os.chmod(fname, perms)

    def run_certutil(self, args, stdin=None, **kwargs):
        return self.nssdb.run_certutil(args, stdin, **kwargs)

    def create_noise_file(self):
        if os.path.isfile(self.noise_fname):
            os.remove(self.noise_fname)
        with open(self.noise_fname, "w") as f:
            self.set_perms(f)
            f.write(ipautil.ipa_generate_password())

    def create_passwd_file(self, passwd=None):
        ipautil.backup_file(self.passwd_fname)
        with open(self.passwd_fname, "w") as f:
            self.set_perms(f)
            if passwd is not None:
                f.write("%s\n" % passwd)
            else:
                f.write(ipautil.ipa_generate_password())

    def create_certdbs(self):
        self.nssdb.create_db(user=self.user,
                             group=self.group,
                             mode=self.mode,
                             backup=True)
        self.set_perms(self.passwd_fname, write=True)

    def restore(self):
        self.nssdb.restore()

    def list_certs(self):
        """
        Return a tuple of tuples containing (nickname, trust)
        """
        return self.nssdb.list_certs()

    def has_nickname(self, nickname):
        """
        Returns True if nickname exists in the certdb, False otherwise.

        This could also be done directly with:
            certutil -L -d -n <nickname> ...
        """

        certs = self.list_certs()

        for cert in certs:
            if nickname == cert[0]:
                return True

        return False

    def export_ca_cert(self, nickname, create_pkcs12=False):
        """create_pkcs12 tells us whether we should create a PKCS#12 file
           of the CA or not. If we are running on a replica then we won't
           have the private key to make a PKCS#12 file so we don't need to
           do that step."""
        cacert_fname = paths.IPA_CA_CRT
        # export the CA cert for use with other apps
        ipautil.backup_file(cacert_fname)
        root_nicknames = self.find_root_cert(nickname)[:-1]
        with open(cacert_fname, "w") as f:
            os.fchmod(f.fileno(), stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
            for root in root_nicknames:
                result = self.run_certutil(["-L", "-n", root, "-a"],
                                           capture_output=True)
                f.write(result.output)

        if create_pkcs12:
            ipautil.backup_file(self.pk12_fname)
            self.nssdb.run_pk12util([
                "-o",
                self.pk12_fname,
                "-n",
                self.cacert_name,
                "-k",
                self.passwd_fname,
                "-w",
                self.passwd_fname,
            ])
            self.set_perms(self.pk12_fname)

    def load_cacert(self, cacert_fname, trust_flags):
        """
        Load all the certificates from a given file. It is assumed that
        this file creates CA certificates.
        """
        with open(cacert_fname) as f:
            certs = f.read()

        st = 0
        while True:
            try:
                (cert, st) = find_cert_from_txt(certs, st)
                _rdn, subject_dn = get_cert_nickname(cert)
                if subject_dn == self.ca_subject:
                    nick = get_ca_nickname(self.realm)
                else:
                    nick = str(subject_dn)
                self.nssdb.add_cert(cert, nick, trust_flags)
            except RuntimeError:
                break

    def get_cert_from_db(self, nickname):
        """
        Retrieve a certificate from the current NSS database for nickname.
        """
        try:
            args = ["-L", "-n", nickname, "-a"]
            result = self.run_certutil(args, capture_output=True)
            return x509.load_pem_x509_certificate(result.raw_output)
        except ipautil.CalledProcessError:
            return None

    def track_server_cert(self,
                          nickname,
                          principal,
                          password_file=None,
                          command=None):
        """
        Tell certmonger to track the given certificate nickname.
        """
        try:
            request_id = certmonger.start_tracking(self.secdir,
                                                   nickname=nickname,
                                                   pinfile=password_file,
                                                   post_command=command)
        except RuntimeError as e:
            logger.error("certmonger failed starting to track certificate: %s",
                         str(e))
            return

        cert = self.get_cert_from_db(nickname)
        subject = str(DN(cert.subject))
        certmonger.add_principal(request_id, principal)
        certmonger.add_subject(request_id, subject)

    def untrack_server_cert(self, nickname):
        """
        Tell certmonger to stop tracking the given certificate nickname.
        """
        try:
            certmonger.stop_tracking(self.secdir, nickname=nickname)
        except RuntimeError as e:
            logger.error("certmonger failed to stop tracking certificate: %s",
                         str(e))

    def create_server_cert(self, nickname, hostname, subject=None):
        """
        If we are using a dogtag CA then other_certdb contains the RA agent key
        that will issue our cert.

        You can override the certificate Subject by specifying a subject.

        Returns a certificate in DER format.
        """
        if subject is None:
            subject = DN(('CN', hostname), self.subject_base)
        self.request_cert(subject, san_dnsnames=[hostname])
        try:
            self.issue_server_cert(self.certreq_fname, self.certder_fname)
            self.import_cert(self.certder_fname, nickname)

            with open(self.certder_fname, "rb") as f:
                dercert = f.read()
                return x509.load_der_x509_certificate(dercert)
        finally:
            for fname in (self.certreq_fname, self.certder_fname):
                try:
                    os.unlink(fname)
                except OSError:
                    pass

    def request_cert(self,
                     subject,
                     certtype="rsa",
                     keysize="2048",
                     san_dnsnames=None):
        assert isinstance(subject, DN)
        self.create_noise_file()
        self.setup_cert_request()
        args = [
            "-R", "-s",
            str(subject), "-o", self.certreq_fname, "-k", certtype, "-g",
            keysize, "-z", self.noise_fname, "-f", self.passwd_fname, "-a"
        ]
        if san_dnsnames is not None and len(san_dnsnames) > 0:
            args += ['-8', ','.join(san_dnsnames)]
        result = self.run_certutil(args,
                                   capture_output=True,
                                   capture_error=True)
        os.remove(self.noise_fname)
        return (result.output, result.error_output)

    def issue_server_cert(self, certreq_fname, cert_fname):
        self.setup_cert_request()

        if self.host_name is None:
            raise RuntimeError("CA Host is not set.")

        with open(certreq_fname, "rb") as f:
            csr = f.read()

        # We just want the CSR bits, make sure there is no thing else
        csr = strip_csr_header(csr).decode('utf8')

        params = {
            'profileId': dogtag.DEFAULT_PROFILE,
            'cert_request_type': 'pkcs10',
            'requestor_name': 'IPA Installer',
            'cert_request': csr,
            'xmlOutput': 'true'
        }

        # Send the request to the CA
        result = dogtag.https_request(self.host_name,
                                      8443,
                                      url="/ca/ee/ca/profileSubmitSSLClient",
                                      cafile=api.env.tls_ca_cert,
                                      client_certfile=paths.RA_AGENT_PEM,
                                      client_keyfile=paths.RA_AGENT_KEY,
                                      **params)
        http_status, _http_headers, http_body = result
        logger.debug("CA answer: %r", http_body)

        if http_status != 200:
            raise CertificateOperationError(
                error=_('Unable to communicate with CMS (status %d)') %
                http_status)

        # The result is an XML blob. Pull the certificate out of that
        doc = xml.dom.minidom.parseString(http_body)
        item_node = doc.getElementsByTagName("b64")
        try:
            try:
                cert = item_node[0].childNodes[0].data
            except IndexError:
                raise RuntimeError("Certificate issuance failed")
        finally:
            doc.unlink()

        # base64-decode the result for uniformity
        cert = base64.b64decode(cert)

        # Write the certificate to a file. It will be imported in a later
        # step. This file will be read later to be imported.
        with open(cert_fname, "wb") as f:
            f.write(cert)

    def add_cert(self, cert, nick, flags):
        self.nssdb.add_cert(cert, nick, flags)

    def import_cert(self, cert_fname, nickname):
        """
        Load a certificate from a PEM file and add minimal trust.
        """
        args = [
            "-A", "-n", nickname, "-t", "u,u,u", "-i", cert_fname, "-f",
            self.passwd_fname
        ]
        self.run_certutil(args)

    def delete_cert(self, nickname):
        self.nssdb.delete_cert(nickname)

    def create_pin_file(self):
        """
        This is the format of Directory Server pin files.
        """
        ipautil.backup_file(self.pin_fname)
        with open(self.pin_fname, "w") as pinfile:
            self.set_perms(pinfile)
            pinfile.write("Internal (Software) Token:")
            with open(self.passwd_fname) as pwdfile:
                pinfile.write(pwdfile.read())

    def find_root_cert(self, nickname):
        """
        Given a nickname, return a list of the certificates that make up
        the trust chain.
        """
        root_nicknames = self.nssdb.get_trust_chain(nickname)

        return root_nicknames

    def trust_root_cert(self, root_nickname, trust_flags):
        if root_nickname is None:
            logger.debug("Unable to identify root certificate to trust. "
                         "Continuing but things are likely to fail.")
            return

        try:
            self.nssdb.trust_root_cert(root_nickname, trust_flags)
        except RuntimeError:
            pass

    def find_server_certs(self):
        return self.nssdb.find_server_certs()

    def import_pkcs12(self, pkcs12_fname, pkcs12_passwd=None):
        return self.nssdb.import_pkcs12(pkcs12_fname,
                                        pkcs12_passwd=pkcs12_passwd)

    def export_pkcs12(self, pkcs12_fname, pkcs12_pwd_fname, nickname=None):
        if nickname is None:
            nickname = get_ca_nickname(api.env.realm)

        self.nssdb.run_pk12util([
            "-o", pkcs12_fname, "-n", nickname, "-k", self.passwd_fname, "-w",
            pkcs12_pwd_fname
        ])

    def create_from_cacert(self):
        cacert_fname = paths.IPA_CA_CRT
        if os.path.isfile(self.certdb_fname):
            # We already have a cert db, see if it is for the same CA.
            # If it is we leave things as they are.
            with open(cacert_fname, "r") as f:
                newca = f.read()

            newca, _st = find_cert_from_txt(newca)

            cacert = self.get_cert_from_db(self.cacert_name)

            if newca == cacert:
                return

        # The CA certificates are different or something went wrong. Start with
        # a new certificate database.
        self.create_passwd_file()
        self.create_certdbs()
        self.load_cacert(cacert_fname, IPA_CA_TRUST_FLAGS)

    def create_from_pkcs12(self, pkcs12_fname, pkcs12_passwd, ca_file,
                           trust_flags):
        """Create a new NSS database using the certificates in a PKCS#12 file.

           pkcs12_fname: the filename of the PKCS#12 file
           pkcs12_pwd_fname: the file containing the pin for the PKCS#12 file
           nickname: the nickname/friendly-name of the cert we are loading

           The global CA may be added as well in case it wasn't included in the
           PKCS#12 file. Extra certs won't hurt in any case.

           The global CA may be specified in ca_file, as a PEM filename.
        """
        self.create_noise_file()
        self.create_passwd_file()
        self.create_certdbs()
        self.init_from_pkcs12(pkcs12_fname,
                              pkcs12_passwd,
                              ca_file=ca_file,
                              trust_flags=trust_flags)

    def init_from_pkcs12(self, pkcs12_fname, pkcs12_passwd, ca_file,
                         trust_flags):
        self.import_pkcs12(pkcs12_fname, pkcs12_passwd)
        server_certs = self.find_server_certs()
        if len(server_certs) == 0:
            raise RuntimeError(
                "Could not find a suitable server cert in import in %s" %
                pkcs12_fname)

        if ca_file:
            try:
                with open(ca_file) as fd:
                    certs = fd.read()
            except IOError as e:
                raise RuntimeError("Failed to open %s: %s" %
                                   (ca_file, e.strerror))
            st = 0
            num = 1
            while True:
                try:
                    cert, st = find_cert_from_txt(certs, st)
                except RuntimeError:
                    break
                self.add_cert(cert, 'CA %s' % num, EMPTY_TRUST_FLAGS)
                num += 1

        # We only handle one server cert
        nickname = server_certs[0][0]

        ca_names = self.find_root_cert(nickname)[:-1]
        if len(ca_names) == 0:
            raise RuntimeError("Could not find a CA cert in %s" % pkcs12_fname)

        self.cacert_name = ca_names[-1]
        self.trust_root_cert(self.cacert_name, trust_flags)

        self.export_ca_cert(nickname, False)

    def export_pem_cert(self, nickname, location):
        return self.nssdb.export_pem_cert(nickname, location)

    def request_service_cert(self, nickname, principal, host):
        certmonger.request_and_wait_for_cert(certpath=self.secdir,
                                             storage='NSSDB',
                                             nickname=nickname,
                                             principal=principal,
                                             subject=host,
                                             passwd_fname=self.passwd_fname)

    def is_ipa_issued_cert(self, api, nickname):
        """
        Return True if the certificate contained in the CertDB with the
        provided nickname has been issued by IPA.

        Note that this method can only be executed if the api has been
        initialized.

        This method needs to compare the cert issuer (from the NSS DB
        and the subject from the CA (from LDAP), because nicknames are not
        always aligned.

        The cert can be issued directly by IPA. In this case, the cert
        issuer is IPA CA subject.
        """
        cert = self.get_cert_from_db(nickname)
        if cert is None:
            raise RuntimeError("Could not find the cert %s in %s" %
                               (nickname, self.secdir))

        return is_ipa_issued_cert(api, cert)

    def needs_upgrade_format(self):
        """Check if NSSDB file format needs upgrade

        Only upgrade if it's an existing dbm database and default
        database type is no 'dbm'.
        """
        return (self.nssdb.dbtype == 'dbm' and self.exists())

    def upgrade_format(self):
        """Upgrade NSSDB to new file format
        """
        self.nssdb.convert_db()
Example #27
0
class Entity:
    """This class represents an IPA user.  An LDAP entry consists of a DN
    and a list of attributes.  Each attribute consists of a name and a list of
    values. For the time being I will maintain this.

    In python-ldap, entries are returned as a list of 2-tuples.
    Instance variables:
    dn - string - the string DN of the entry
    data - CIDict - case insensitive dict of the attributes and values
    orig_data - CIDict - case insentiive dict of the original attributes and values"""
    def __init__(self, entrydata=None):
        """data is the raw data returned from the python-ldap result method,
        which is a search result entry or a reference or None.
        If creating a new empty entry, data is the string DN."""
        if entrydata:
            if isinstance(entrydata, tuple):
                self.dn = entrydata[0]
                self.data = ipautil.CIDict(entrydata[1])
            elif isinstance(entrydata, DN):
                self.dn = entrydata
                self.data = ipautil.CIDict()
            elif isinstance(entrydata, basestring):
                self.dn = DN(entrydata)
                self.data = ipautil.CIDict()
            elif isinstance(entrydata, dict):
                self.dn = entrydata['dn']
                del entrydata['dn']
                self.data = ipautil.CIDict(entrydata)
        else:
            self.dn = DN()
            self.data = ipautil.CIDict()

        assert isinstance(self.dn, DN)
        self.orig_data = ipautil.CIDict(copy_CIDict(self.data))

    dn = ipautil.dn_attribute_property('_dn')

    def __nonzero__(self):
        """This allows us to do tests like if entry: returns false if there is no data,
        true otherwise"""
        return self.data != None and len(self.data) > 0

    def hasAttr(self, name):
        """Return True if this entry has an attribute named name, False otherwise"""
        return self.data and self.data.has_key(name)

    def __str__(self):
        return "dn: %s data: %s" % (self.dn, self.data)

    def getValues(self, name):
        """Get the list (array) of values for the attribute named name"""
        return self.data.get(name)

    def getValue(self, name, default=None):
        """Get the first value for the attribute named name"""
        value = self.data.get(name, default)
        if isinstance(value, list) or isinstance(value, tuple):
            return value[0]
        else:
            return value

    def setValue(self, name, *value):
        """Value passed in may be a single value, several values, or a single sequence.
        For example:
           ent.setValue('name', 'value')
           ent.setValue('name', 'value1', 'value2', ..., 'valueN')
           ent.setValue('name', ['value1', 'value2', ..., 'valueN'])
           ent.setValue('name', ('value1', 'value2', ..., 'valueN'))
        Since *value is a tuple, we may have to extract a list or tuple from that
        tuple as in the last two examples above"""
        if (len(value) < 1):
            return
        if (len(value) == 1):
            self.data[name] = ipautil.utf8_encode_values(value[0])
        else:
            self.data[name] = ipautil.utf8_encode_values(value)

    setValues = setValue

    def setValueNotEmpty(self, name, *value):
        """Similar to setValue() but will not set an empty field. This
           is an attempt to avoid adding empty attributes."""
        if (len(value) >= 1) and value[0] and len(value[0]) > 0:
            if isinstance(value[0], list):
                if len(value[0][0]) > 0:
                    self.setValue(name, *value)
                    return
            else:
                self.setValue(name, *value)
                return

        # At this point we have an empty incoming value. See if they are
        # trying to erase the current value. If so we'll delete it so
        # it gets marked as removed in the modlist.
        v = self.getValues(name)
        if v:
            self.delValue(name)

        return

    def delValue(self, name):
        """Remove the attribute named name."""
        if self.data.get(name, None):
            del self.data[name]

    def toTupleList(self):
        """Convert the attrs and values to a list of 2-tuples.  The first element
        of the tuple is the attribute name.  The second element is either a
        single value or a list of values."""
        return self.data.items()

    def toDict(self):
        """Convert the attrs and values to a dict. The dict is keyed on the
        attribute name.  The value is either single value or a list of values."""
        assert isinstance(self.dn, DN)
        result = ipautil.CIDict(self.data)
        result['dn'] = self.dn
        return result

    def attrList(self):
        """Return a list of all attributes in the entry"""
        return self.data.keys()

    def origDataDict(self):
        """Returns a dict of the original values of the user.  Used for updates."""
        assert isinstance(self.dn, DN)
        result = ipautil.CIDict(self.orig_data)
        result['dn'] = self.dn
        return result