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