def __spawn_instance(self): """ Create and configure a new KRA instance using pkispawn. Creates a configuration file with IPA-specific parameters and passes it to the base class to call pkispawn """ # Create an empty and secured file (cfg_fd, cfg_file) = tempfile.mkstemp() os.close(cfg_fd) pent = pwd.getpwnam(self.service_user) os.chown(cfg_file, pent.pw_uid, pent.pw_gid) self.tmp_agent_db = tempfile.mkdtemp(prefix="tmp-", dir=paths.VAR_LIB_IPA) tmp_agent_pwd = ipautil.ipa_generate_password() # Create a temporary file for the admin PKCS #12 file (admin_p12_fd, admin_p12_file) = tempfile.mkstemp() os.close(admin_p12_fd) # Create KRA configuration config = RawConfigParser() config.optionxform = str config.add_section("KRA") # Security Domain Authentication config.set("KRA", "pki_security_domain_https_port", "443") config.set("KRA", "pki_security_domain_password", self.admin_password) config.set("KRA", "pki_security_domain_user", self.admin_user) # issuing ca config.set("KRA", "pki_issuing_ca_uri", "https://%s" % ipautil.format_netloc(self.fqdn, 443)) # Server config.set("KRA", "pki_enable_proxy", "True") config.set("KRA", "pki_restart_configured_instance", "False") config.set("KRA", "pki_backup_keys", "True") config.set("KRA", "pki_backup_password", self.admin_password) # Client security database config.set("KRA", "pki_client_database_dir", self.tmp_agent_db) config.set("KRA", "pki_client_database_password", tmp_agent_pwd) config.set("KRA", "pki_client_database_purge", "True") config.set("KRA", "pki_client_pkcs12_password", self.admin_password) # Administrator config.set("KRA", "pki_admin_name", self.admin_user) config.set("KRA", "pki_admin_uid", self.admin_user) config.set("KRA", "pki_admin_email", "root@localhost") config.set("KRA", "pki_admin_password", self.admin_password) config.set("KRA", "pki_admin_nickname", "ipa-ca-agent") config.set("KRA", "pki_admin_subject_dn", str(DN(('cn', 'ipa-ca-agent'), self.subject_base))) config.set("KRA", "pki_import_admin_cert", "False") config.set("KRA", "pki_client_admin_cert_p12", admin_p12_file) # Directory server config.set("KRA", "pki_ds_ldap_port", "389") config.set("KRA", "pki_ds_password", self.dm_password) config.set("KRA", "pki_ds_base_dn", six.text_type(self.basedn)) config.set("KRA", "pki_ds_database", "ipaca") config.set("KRA", "pki_ds_create_new_db", "False") self._use_ldaps_during_spawn(config) # Certificate subject DNs config.set("KRA", "pki_subsystem_subject_dn", str(DN(('cn', 'CA Subsystem'), self.subject_base))) config.set("KRA", "pki_sslserver_subject_dn", str(DN(('cn', self.fqdn), self.subject_base))) config.set("KRA", "pki_audit_signing_subject_dn", str(DN(('cn', 'KRA Audit'), self.subject_base))) config.set( "KRA", "pki_transport_subject_dn", str(DN(('cn', 'KRA Transport Certificate'), self.subject_base))) config.set( "KRA", "pki_storage_subject_dn", str(DN(('cn', 'KRA Storage Certificate'), self.subject_base))) # Certificate nicknames # Note that both the server certs and subsystem certs reuse # the ca certs. config.set("KRA", "pki_subsystem_nickname", "subsystemCert cert-pki-ca") config.set("KRA", "pki_sslserver_nickname", "Server-Cert cert-pki-ca") config.set("KRA", "pki_audit_signing_nickname", "auditSigningCert cert-pki-kra") config.set("KRA", "pki_transport_nickname", "transportCert cert-pki-kra") config.set("KRA", "pki_storage_nickname", "storageCert cert-pki-kra") # Shared db settings # Needed because CA and KRA share the same database # We will use the dbuser created for the CA config.set("KRA", "pki_share_db", "True") config.set( "KRA", "pki_share_dbuser_dn", str(DN(('uid', 'pkidbuser'), ('ou', 'people'), ('o', 'ipaca')))) if not (os.path.isdir(paths.PKI_TOMCAT_ALIAS_DIR) and os.path.isfile(paths.PKI_TOMCAT_PASSWORD_CONF)): # generate pin which we know can be used for FIPS NSS database pki_pin = ipautil.ipa_generate_password() config.set("KRA", "pki_pin", pki_pin) else: pki_pin = None _p12_tmpfile_handle, p12_tmpfile_name = tempfile.mkstemp(dir=paths.TMP) if self.clone: krafile = self.pkcs12_info[0] shutil.copy(krafile, p12_tmpfile_name) pent = pwd.getpwnam(self.service_user) os.chown(p12_tmpfile_name, pent.pw_uid, pent.pw_gid) # Security domain registration config.set("KRA", "pki_security_domain_hostname", self.fqdn) config.set("KRA", "pki_security_domain_https_port", "443") config.set("KRA", "pki_security_domain_user", self.admin_user) config.set("KRA", "pki_security_domain_password", self.admin_password) # Clone config.set("KRA", "pki_clone", "True") config.set("KRA", "pki_clone_pkcs12_path", p12_tmpfile_name) config.set("KRA", "pki_clone_pkcs12_password", self.dm_password) config.set("KRA", "pki_clone_setup_replication", "False") config.set( "KRA", "pki_clone_uri", "https://%s" % ipautil.format_netloc(self.master_host, 443)) else: # the admin cert file is needed for the first instance of KRA cert = self.get_admin_cert() # First make sure that the directory exists parentdir = os.path.dirname(paths.ADMIN_CERT_PATH) if not os.path.exists(parentdir): os.makedirs(parentdir) with open(paths.ADMIN_CERT_PATH, "wb") as admin_path: admin_path.write( base64.b64encode(cert.public_bytes(x509.Encoding.DER))) # Generate configuration file with open(cfg_file, "w") as f: config.write(f) try: DogtagInstance.spawn_instance(self, cfg_file, nolog_list=(self.dm_password, self.admin_password, pki_pin, tmp_agent_pwd)) finally: os.remove(p12_tmpfile_name) os.remove(cfg_file) os.remove(admin_p12_file) shutil.move(paths.KRA_BACKUP_KEYS_P12, paths.KRACERT_P12) logger.debug("completed creating KRA instance")
def main(): module = AnsibleModule( argument_spec = dict( servers=dict(required=True, type='list'), domain=dict(required=True), realm=dict(required=True), hostname=dict(required=True), basedn=dict(required=True), principal=dict(required=False), subject_base=dict(required=True), ca_enabled=dict(required=True, type='bool'), mkhomedir=dict(required=False, type='bool'), on_master=dict(required=False, type='bool'), ), supports_check_mode = True, ) module._ansible_debug = True servers = module.params.get('servers') realm = module.params.get('realm') hostname = module.params.get('hostname') basedn = module.params.get('basedn') domain = module.params.get('domain') principal = module.params.get('principal') subject_base = module.params.get('subject_base') ca_enabled = module.params.get('ca_enabled') mkhomedir = module.params.get('mkhomedir') on_master = module.params.get('on_master') fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE) statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE) ########################################################################### os.environ['KRB5CCNAME'] = CCACHE_FILE class Object(object): pass options = Object() options.dns_updates = False options.all_ip_addresses = False options.ip_addresses = None options.request_cert = False options.hostname = hostname options.preserve_sssd = False options.on_master = False options.conf_ssh = True options.conf_sshd = True options.conf_sudo = True options.primary = False options.permit = False options.krb5_offline_passwords = False options.create_sshfp = True ########################################################################## # Create IPA NSS database try: create_ipa_nssdb() except ipautil.CalledProcessError as e: module.fail_json(msg="Failed to create IPA NSS database: %s" % e) # Get CA certificates from the certificate store try: ca_certs = get_certs_from_ldap(servers[0], basedn, realm, ca_enabled) except errors.NoCertificateError: if ca_enabled: ca_subject = DN(('CN', 'Certificate Authority'), subject_base) else: ca_subject = None ca_certs = certstore.make_compat_ca_certs(ca_certs, realm, ca_subject) ca_certs_trust = [(c, n, certstore.key_policy_to_trust_flags(t, True, u)) for (c, n, t, u) in ca_certs] if hasattr(paths, "KDC_CA_BUNDLE_PEM"): x509.write_certificate_list( [c for c, n, t, u in ca_certs if t is not False], paths.KDC_CA_BUNDLE_PEM) if hasattr(paths, "CA_BUNDLE_PEM"): x509.write_certificate_list( [c for c, n, t, u in ca_certs if t is not False], paths.CA_BUNDLE_PEM) # Add the CA certificates to the IPA NSS database module.debug("Adding CA certificates to the IPA NSS database.") ipa_db = certdb.NSSDatabase(paths.IPA_NSSDB_DIR) for cert, nickname, trust_flags in ca_certs_trust: try: ipa_db.add_cert(cert, nickname, trust_flags) except CalledProcessError as e: module.fail_json(msg="Failed to add %s to the IPA NSS database." % nickname) # Add the CA certificates to the platform-dependant systemwide CA store tasks.insert_ca_certs_into_systemwide_ca_store(ca_certs) if not on_master: client_dns(servers[0], hostname, options) configure_certmonger(fstore, subject_base, realm, hostname, options, ca_enabled) if hasattr(paths, "SSH_CONFIG_DIR"): ssh_config_dir = paths.SSH_CONFIG_DIR else: ssh_config_dir = services.knownservices.sshd.get_config_dir() update_ssh_keys(hostname, ssh_config_dir, options.create_sshfp) try: os.remove(CCACHE_FILE) except Exception: pass ########################################################################## # Name Server Caching Daemon. Disable for SSSD, use otherwise # (if installed) nscd = services.knownservices.nscd if nscd.is_installed(): save_state(nscd, statestore) try: nscd_service_action = 'stop' nscd.stop() except Exception: module.warn("Failed to %s the %s daemon" % (nscd_service_action, nscd.service_name)) try: nscd.disable() except Exception: module.warn("Failed to disable %s daemon. Disable it manually." % nscd.service_name) nslcd = services.knownservices.nslcd if nslcd.is_installed(): save_state(nslcd, statestore) retcode, conf = (0, None) ########################################################################## # Modify nsswitch/pam stack tasks.modify_nsswitch_pam_stack(sssd=True, mkhomedir=mkhomedir, statestore=statestore) module.log("SSSD enabled") argspec = inspect.getargspec(services.service) if len(argspec.args) > 1: sssd = services.service('sssd', api) else: sssd = services.service('sssd') try: sssd.restart() except CalledProcessError: module.warn("SSSD service restart was unsuccessful.") try: sssd.enable() except CalledProcessError as e: module.warn( "Failed to enable automatic startup of the SSSD daemon: " "%s", e) if configure_openldap_conf(fstore, basedn, servers): module.log("Configured /etc/openldap/ldap.conf") else: module.log("Failed to configure /etc/openldap/ldap.conf") # Check that nss is working properly if not on_master: user = principal if user is None or user == "": user = "******" % domain module.log("Principal is not set when enrolling with OTP" "; using principal '%s' for 'getent passwd'" % user) elif '@' not in user: user = "******" % (user, domain) n = 0 found = False # Loop for up to 10 seconds to see if nss is working properly. # It can sometimes take a few seconds to connect to the remote # provider. # Particulary, SSSD might take longer than 6-8 seconds. while n < 10 and not found: try: ipautil.run(["getent", "passwd", user]) found = True except Exception as e: time.sleep(1) n = n + 1 if not found: module.fail_json(msg="Unable to find '%s' user with 'getent " "passwd %s'!" % (user.split("@")[0], user)) if conf: module.log("Recognized configuration: %s" % conf) else: module.fail_json(msg= "Unable to reliably detect " "configuration. Check NSS setup manually.") try: hardcode_ldap_server(servers) except Exception as e: module.fail_json(msg="Adding hardcoded server name to " "/etc/ldap.conf failed: %s" % str(e)) ########################################################################## module.exit_json(changed=True, ca_enabled_ra=ca_enabled)
def __call__(self, environ, start_response): logger.info('WSGI change_password.__call__:') # Get the user and password parameters from the request content_type = environ.get('CONTENT_TYPE', '').lower() if not content_type.startswith('application/x-www-form-urlencoded'): return self.bad_request( environ, start_response, "Content-Type must be application/x-www-form-urlencoded") method = environ.get('REQUEST_METHOD', '').upper() if method == 'POST': query_string = read_input(environ) else: return self.bad_request(environ, start_response, "HTTP request method must be POST") try: query_dict = parse_qs(query_string) except Exception as e: return self.bad_request(environ, start_response, "cannot parse query data") data = {} for field in ('user', 'old_password', 'new_password', 'otp'): value = query_dict.get(field, None) if value is not None: if len(value) == 1: data[field] = value[0] else: return self.bad_request( environ, start_response, "more than one %s parameter" % field) elif field != 'otp': # otp is optional return self.bad_request(environ, start_response, "no %s specified" % field) # start building the response logger.info("WSGI change_password: start password change of user '%s'", data['user']) status = HTTP_STATUS_SUCCESS response_headers = [('Content-Type', 'text/html; charset=utf-8')] title = 'Password change rejected' result = 'error' policy_error = None bind_dn = DN((self.api.Object.user.primary_key.name, data['user']), self.api.env.container_user, self.api.env.basedn) try: pw = data['old_password'] if data.get('otp'): pw = data['old_password'] + data['otp'] conn = ldap2(self.api) conn.connect(bind_dn=bind_dn, bind_pw=pw) except (NotFound, ACIError): result = 'invalid-password' message = 'The old password or username is not correct.' except Exception as e: message = "Could not connect to LDAP server." logger.error( "change_password: cannot authenticate '%s' to LDAP " "server: %s", data['user'], str(e)) else: try: conn.modify_password(bind_dn, data['new_password'], data['old_password'], skip_bind=True) except ExecutionError as e: result = 'policy-error' policy_error = escape(str(e)) message = "Password change was rejected: %s" % escape(str(e)) except Exception as e: message = "Could not change the password" logger.error( "change_password: cannot change password of " "'%s': %s", data['user'], str(e)) else: result = 'ok' title = "Password change successful" message = "Password was changed." finally: if conn.isconnected(): conn.disconnect() logger.info('%s: %s', status, message) response_headers.append(('X-IPA-Pwchange-Result', result)) if policy_error: response_headers.append( ('X-IPA-Pwchange-Policy-Error', policy_error)) start_response(status, response_headers) output = _success_template % dict(title=str(title), message=str(message)) return [output.encode('utf-8')]
def _group_dn(group): return DN(('cn', group), OU_GROUPS_DN)
def get_delete_dn(self, *keys, **options): active_dn = self.get_dn(*keys, **options) return DN(active_dn[0], self.delete_container_dn, api.env.basedn)
def __enable_ssl(self): dirname = config_dirname(self.serverid) dsdb = certs.CertDB( self.realm, nssdir=dirname, subject_base=self.subject_base, ca_subject=self.ca_subject, ) if self.pkcs12_info: if self.ca_is_configured: trust_flags = IPA_CA_TRUST_FLAGS else: trust_flags = EXTERNAL_CA_TRUST_FLAGS 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) self.add_cert_to_service() else: dsdb.create_from_cacert() if self.master_fqdn is None: 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: cmd = 'restart_dirsrv %s' % self.serverid certmonger.request_and_wait_for_cert( certpath=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: if prev_helper is not None: certmonger.modify_ca_helper('IPA', prev_helper) # restart_dirsrv in the request above restarts DS, reconnect ldap2 api.Backend.ldap2.disconnect() api.Backend.ldap2.connect() self.dercert = dsdb.get_cert_from_db(self.nickname, pem=False) if prev_helper is not None: self.add_cert_to_service() 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)
from ipaplatform.paths import paths from ipaplatform.tasks import tasks from ipapython import directivesetter from ipapython import ipaldap from ipapython import ipautil from ipapython.dn import DN from ipaserver.install import service from ipaserver.install import sysupgrade from ipaserver.install import replication from ipaserver.install.installutils import stopped_service logger = logging.getLogger(__name__) INTERNAL_TOKEN = "internal" OU_GROUPS_DN = DN(('ou', 'groups'), ('o', 'ipaca')) def _person_dn(uid): return DN(('uid', uid), ('ou', 'people'), ('o', 'ipaca')) def _group_dn(group): return DN(('cn', group), OU_GROUPS_DN) def get_security_domain(): """ Get the security domain from the REST interface on the local Dogtag CA This function will succeed if the local dogtag CA is up. """
def execute(self, **options): ldap = self.api.Backend.ldap2 base_dn = DN(self.api.env.container_ranges, self.api.env.basedn) search_filter = ("(&(objectClass=ipaIDrange)(!(ipaRangeType=*)))") root_logger.debug("update_idrange_type: search for ID ranges with no " "type set") while True: # Run the search in loop to avoid issues when LDAP limits are hit # during update try: (entries, truncated) = ldap.find_entries(search_filter, ['objectclass'], base_dn, time_limit=0, size_limit=0) except errors.NotFound: root_logger.debug("update_idrange_type: no ID range without " "type set found") return False, [] except errors.ExecutionError, e: root_logger.error("update_idrange_type: cannot retrieve list " "of ranges with no type set: %s", e) return False, [] if not entries: # No entry was returned, rather break than continue cycling root_logger.debug("update_idrange_type: no ID range was " "returned") return False, [] root_logger.debug("update_idrange_type: found %d " "idranges to update, truncated: %s", len(entries), truncated) error = False # Set the range type for entry in entries: objectclasses = [o.lower() for o in entry.get('objectclass', [])] if 'ipatrustedaddomainrange' in objectclasses: # NOTICE: assumes every AD range does not use POSIX # attributes entry['ipaRangeType'] = ['ipa-ad-trust'] elif 'ipadomainidrange' in objectclasses: entry['ipaRangeType'] = ['ipa-local'] else: entry['ipaRangeType'] = ['unknown'] root_logger.error("update_idrange_type: could not detect " "range type for entry: %s" % str(entry.dn)) root_logger.error("update_idrange_type: ID range type set " "to 'unknown' for entry: %s" % str(entry.dn)) try: ldap.update_entry(entry) except (errors.EmptyModlist, errors.NotFound): pass except errors.ExecutionError, e: root_logger.debug("update_idrange_type: cannot " "update idrange type: %s", e) error = True
def __init__(self, cn, **kwargs): super(IdpTracker, self).__init__(default_version=None) self.cn = cn self.dn = DN(('cn', cn), api.env.container_idp, api.env.basedn) self.kwargs = kwargs
class test_hostgroup(Declarative): cleanup_commands = [ ('hostgroup_del', [hostgroup1], {}), ('host_del', [fqdn1], {}), ] tests=[ dict( desc='Try to retrieve non-existent %r' % hostgroup1, command=('hostgroup_show', [hostgroup1], {}), expected=errors.NotFound( reason=u'%s: host group not found' % hostgroup1), ), dict( desc='Try to update non-existent %r' % hostgroup1, command=('hostgroup_mod', [hostgroup1], dict(description=u'Updated hostgroup 1') ), expected=errors.NotFound( reason=u'%s: host group not found' % hostgroup1), ), dict( desc='Try to delete non-existent %r' % hostgroup1, command=('hostgroup_del', [hostgroup1], {}), expected=errors.NotFound( reason=u'%s: host group not found' % hostgroup1), ), dict( desc='Test an invalid hostgroup name %r' % invalidhostgroup1, command=('hostgroup_add', [invalidhostgroup1], dict(description=u'Test')), expected=errors.ValidationError(name='hostgroup_name', error=u'may only include letters, numbers, _, -, and .'), ), dict( desc='Create %r' % hostgroup1, command=('hostgroup_add', [hostgroup1], dict(description=u'Test hostgroup 1') ), expected=dict( value=hostgroup1, summary=u'Added hostgroup "testhostgroup1"', result=dict( dn=dn1, cn=[hostgroup1], objectclass=objectclasses.hostgroup, description=[u'Test hostgroup 1'], ipauniqueid=[fuzzy_uuid], mepmanagedentry=[DN(('cn',hostgroup1),('cn','ng'),('cn','alt'), api.env.basedn)], ), ), ), dict( desc='Try to create duplicate %r' % hostgroup1, command=('hostgroup_add', [hostgroup1], dict(description=u'Test hostgroup 1') ), expected=errors.DuplicateEntry(message= u'host group with name "%s" already exists' % hostgroup1), ), dict( desc='Create host %r' % fqdn1, command=('host_add', [fqdn1], dict( description=u'Test host 1', l=u'Undisclosed location 1', force=True, ), ), expected=dict( value=fqdn1, summary=u'Added host "%s"' % fqdn1, result=dict( dn=host_dn1, fqdn=[fqdn1], description=[u'Test host 1'], l=[u'Undisclosed location 1'], krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], objectclass=objectclasses.host, ipauniqueid=[fuzzy_uuid], managedby_host=[fqdn1], has_keytab=False, has_password=False, ), ), ), dict( desc=u'Add host %r to %r' % (fqdn1, hostgroup1), command=( 'hostgroup_add_member', [hostgroup1], dict(host=fqdn1) ), expected=dict( completed=1, failed=dict( member=dict( host=tuple(), hostgroup=tuple(), ), ), result={ 'dn': dn1, 'cn': [hostgroup1], 'description': [u'Test hostgroup 1'], 'member_host': [fqdn1], }, ), ), dict( desc='Retrieve %r' % hostgroup1, command=('hostgroup_show', [hostgroup1], {}), expected=dict( value=hostgroup1, summary=None, result={ 'dn': dn1, 'member_host': [u'testhost1.%s' % api.env.domain], 'cn': [hostgroup1], 'description': [u'Test hostgroup 1'], }, ), ), dict( desc='Search for %r' % hostgroup1, command=('hostgroup_find', [], dict(cn=hostgroup1)), expected=dict( count=1, truncated=False, summary=u'1 hostgroup matched', result=[ { 'dn': dn1, 'member_host': [u'testhost1.%s' % api.env.domain], 'cn': [hostgroup1], 'description': [u'Test hostgroup 1'], }, ], ), ), dict( desc='Update %r' % hostgroup1, command=('hostgroup_mod', [hostgroup1], dict(description=u'Updated hostgroup 1') ), expected=dict( value=hostgroup1, summary=u'Modified hostgroup "testhostgroup1"', result=dict( cn=[hostgroup1], description=[u'Updated hostgroup 1'], member_host=[u'testhost1.%s' % api.env.domain], ), ), ), dict( desc='Retrieve %r to verify update' % hostgroup1, command=('hostgroup_show', [hostgroup1], {}), expected=dict( value=hostgroup1, summary=None, result={ 'dn': dn1, 'member_host': [u'testhost1.%s' % api.env.domain], 'cn': [hostgroup1], 'description': [u'Updated hostgroup 1'], }, ), ), dict( desc='Remove host %r from %r' % (fqdn1, hostgroup1), command=('hostgroup_remove_member', [hostgroup1], dict(host=fqdn1) ), expected=dict( failed=dict( member=dict( host=tuple(), hostgroup=tuple(), ), ), completed=1, result={ 'dn': dn1, 'cn': [hostgroup1], 'description': [u'Updated hostgroup 1'], }, ), ), dict( desc='Delete %r' % hostgroup1, command=('hostgroup_del', [hostgroup1], {}), expected=dict( value=[hostgroup1], summary=u'Deleted hostgroup "testhostgroup1"', result=dict(failed=[]), ), ), dict( desc='Create hostgroup with name containing only one letter: %r' % hostgroup_single, command=('hostgroup_add', [hostgroup_single], dict(description=u'Test hostgroup with single letter in name') ), expected=dict( value=hostgroup_single, summary=u'Added hostgroup "a"', result=dict( dn=dn_single, cn=[hostgroup_single], objectclass=objectclasses.hostgroup, description=[u'Test hostgroup with single letter in name'], ipauniqueid=[fuzzy_uuid], mepmanagedentry=[DN(('cn',hostgroup_single),('cn','ng'),('cn','alt'), api.env.basedn)], ), ), ), dict( desc='Delete %r' % hostgroup_single, command=('hostgroup_del', [hostgroup_single], {}), expected=dict( value=[hostgroup_single], summary=u'Deleted hostgroup "a"', result=dict(failed=[]), ), ), dict( desc='Delete host %r' % fqdn1, command=('host_del', [fqdn1], {}), expected=dict( value=[fqdn1], summary=u'Deleted host "%s"' % fqdn1, result=dict(failed=[]), ), ) ]
def _finalize_core(self, **defaults): """ Complete initialization of standard IPA environment. This method will perform the following steps: 1. Call `Env._bootstrap()` if it hasn't already been called. 2. Merge-in variables from the configuration file ``self.conf`` (if it exists) by calling `Env._merge_from_file()`. 3. Merge-in variables from the defaults configuration file ``self.conf_default`` (if it exists) by calling `Env._merge_from_file()`. 4. Intelligently fill-in the *in_server* , *logdir*, *log*, and *jsonrpc_uri* variables if they haven't already been set. 5. Merge-in the variables in ``defaults`` by calling `Env._merge()`. In normal circumstances ``defaults`` will simply be those specified in `constants.DEFAULT_CONFIG`. After this method is called, all the environment variables used by all the built-in plugins will be available. As such, this method should be called *before* any plugins are loaded. After this method has finished, the `Env` instance is still writable so that 3rd-party plugins can set variables they may require as the plugins are registered. Also see `Env._finalize()`, the final method in the bootstrap sequence. :param defaults: Internal defaults for all built-in variables. """ self.__doing('_finalize_core') self.__do_if_not_done('_bootstrap') # Merge in context config file and then default config file: mode = self.__d.get('mode') # pylint: disable=no-member if mode != 'dummy': self._merge_from_file(self.conf) self._merge_from_file(self.conf_default) # Determine if in_server: if 'in_server' not in self: self.in_server = (self.context == 'server') # Set logdir: if 'logdir' not in self: if self.in_tree or not self.in_server: self.logdir = self._join('dot_ipa', 'log') else: self.logdir = path.join('/', 'var', 'log', 'ipa') # Set log file: if 'log' not in self: self.log = self._join('logdir', '%s.log' % self.context) # Workaround for ipa-server-install --uninstall. When no config file # is available, we set realm, domain, and basedn to RFC 2606 reserved # suffix to suppress attribute errors during uninstallation. if (self.in_server and self.context == 'installer' and not getattr(self, 'config_loaded', False)): if 'realm' not in self: self.realm = 'UNCONFIGURED.INVALID' if 'domain' not in self: self.domain = self.realm.lower() if 'basedn' not in self and 'domain' in self: self.basedn = DN(*(('dc', dc) for dc in self.domain.split('.'))) # Derive xmlrpc_uri from server # (Note that this is done before deriving jsonrpc_uri from xmlrpc_uri # and server from jsonrpc_uri so that when only server or xmlrpc_uri # is specified, all 3 keys have a value.) if 'xmlrpc_uri' not in self and 'server' in self: # pylint: disable=no-member, access-member-before-definition self.xmlrpc_uri = 'https://{}/ipa/xml'.format(self.server) # Derive ldap_uri from server if 'ldap_uri' not in self and 'server' in self: # pylint: disable=no-member, access-member-before-definition self.ldap_uri = 'ldap://{}'.format(self.server) # Derive jsonrpc_uri from xmlrpc_uri if 'jsonrpc_uri' not in self: if 'xmlrpc_uri' in self: xmlrpc_uri = self.xmlrpc_uri else: xmlrpc_uri = defaults.get('xmlrpc_uri') if xmlrpc_uri: (scheme, netloc, uripath, params, query, fragment ) = urlparse(xmlrpc_uri) uripath = uripath.replace('/xml', '/json', 1) self.jsonrpc_uri = urlunparse(( scheme, netloc, uripath, params, query, fragment)) if 'server' not in self: if 'jsonrpc_uri' in self: jsonrpc_uri = self.jsonrpc_uri else: jsonrpc_uri = defaults.get('jsonrpc_uri') if jsonrpc_uri: parsed = urlparse(jsonrpc_uri) self.server = parsed.netloc self._merge(**defaults) # set the best known TLS version if min/max versions are not set if 'tls_version_min' not in self: self.tls_version_min = TLS_VERSIONS[-1] elif self.tls_version_min not in TLS_VERSIONS: raise errors.EnvironmentError( "Unknown TLS version '{ver}' set in tls_version_min." .format(ver=self.tls_version_min)) if 'tls_version_max' not in self: self.tls_version_max = TLS_VERSIONS[-1] elif self.tls_version_max not in TLS_VERSIONS: raise errors.EnvironmentError( "Unknown TLS version '{ver}' set in tls_version_max." .format(ver=self.tls_version_max)) if self.tls_version_max < self.tls_version_min: raise errors.EnvironmentError( "tls_version_min is set to a higher TLS version than " "tls_version_max.")
# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. """ Test the `ipalib.plugins.hostgroup` module. """ from ipalib import api, errors from ipatests.test_xmlrpc.xmlrpc_test import Declarative, fuzzy_uuid from ipatests.test_xmlrpc import objectclasses from ipapython.dn import DN hostgroup1 = u'testhostgroup1' dn1 = DN(('cn',hostgroup1),('cn','hostgroups'),('cn','accounts'), api.env.basedn) hostgroup_single = u'a' dn_single = DN(('cn',hostgroup_single),('cn','hostgroups'),('cn','accounts'), api.env.basedn) fqdn1 = u'testhost1.%s' % api.env.domain host_dn1 = DN(('fqdn',fqdn1),('cn','computers'),('cn','accounts'), api.env.basedn) invalidhostgroup1 = u'@invalid' class test_hostgroup(Declarative): cleanup_commands = [
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)
class idp(LDAPObject): """ Identity Provider object. """ container_dn = api.env.container_idp object_name = _('Identity Provider server') object_name_plural = _('Identity Provider servers') object_class = ['ipaidp'] default_attributes = [ 'cn', 'ipaidpauthendpoint', 'ipaidpdevauthendpoint', 'ipaidpuserinfoendpoint', 'ipaidpkeysendpoint', 'ipaidptokenendpoint', 'ipaidpissuerurl', 'ipaidpclientid', 'ipaidpscope', 'ipaidpsub', ] search_attributes = [ 'cn', 'ipaidpauthendpoint', 'ipaidpdevauthendpoint', 'ipaidptokenendpoint', 'ipaidpuserinfoendpoint', 'ipaidpkeysendpoint', 'ipaidpscope', 'ipaidpsub', ] allow_rename = True label = _('Identity Provider servers') label_singular = _('Identity Provider server') takes_params = ( Str( 'cn', cli_name='name', label=_('Identity Provider server name'), primary_key=True, ), Str( 'ipaidpauthendpoint?', validate_uri, cli_name='auth_uri', label=_('Authorization URI'), doc=_('OAuth 2.0 authorization endpoint'), ), Str( 'ipaidpdevauthendpoint?', validate_uri, cli_name='dev_auth_uri', label=_('Device authorization URI'), doc=_('Device authorization endpoint'), ), Str( 'ipaidptokenendpoint?', validate_uri, cli_name='token_uri', label=_('Token URI'), doc=_('Token endpoint'), ), Str( 'ipaidpuserinfoendpoint?', validate_uri, cli_name='userinfo_uri', label=_('User info URI'), doc=_('User information endpoint'), ), Str( 'ipaidpkeysendpoint?', validate_uri, cli_name='keys_uri', label=_('JWKS URI'), doc=_('JWKS endpoint'), ), Str( 'ipaidpissuerurl?', cli_name='issuer_url', label=_('OIDC URL'), doc=_('The Identity Provider OIDC URL'), ), Str( 'ipaidpclientid', cli_name='client_id', label=_('Client identifier'), doc=_('OAuth 2.0 client identifier'), ), Password( 'ipaidpclientsecret?', cli_name='secret', label=_('Secret'), doc=_('OAuth 2.0 client secret'), confirm=True, flags={'no_display'}, ), Str( 'ipaidpscope?', cli_name='scope', label=_('Scope'), doc=_('OAuth 2.0 scope. Multiple scopes separated by space'), ), Str( 'ipaidpsub?', cli_name='idp_user_id', label=_('External IdP user identifier attribute'), doc=_('Attribute for user identity in OAuth 2.0 userinfo'), ), ) permission_filter_objectclasses = ['ipaidp'] managed_permissions = { 'System: Add External IdP server': { 'ipapermright': {'add'}, 'ipapermlocation': DN(container_dn, api.env.basedn), 'ipapermtargetfilter': {'(objectclass=ipaidp)'}, 'default_privileges': {'External IdP server Administrators'} }, 'System: Read External IdP server': { 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cn', 'objectclass', 'ipaidpauthendpoint', 'ipaidpdevauthendpoint', 'ipaidpuserinfoendpoint', 'ipaidptokenendpoint', 'ipaidpkeysendpoint', 'ipaidpissuerurl', 'ipaidpclientid', 'ipaidpscope', 'ipaidpsub', }, 'ipapermlocation': DN(container_dn, api.env.basedn), 'ipapermtargetfilter': {'(objectclass=ipaidp)'}, 'default_privileges': {'External IdP server Administrators'} }, 'System: Modify External IdP server': { 'ipapermright': {'write'}, 'ipapermlocation': DN(container_dn, api.env.basedn), 'ipapermdefaultattr': { 'cn', 'objectclass', 'ipaidpauthendpoint', 'ipaidpdevauthendpoint', 'ipaidpuserinfoendpoint', 'ipaidptokenendpoint', 'ipaidpkeysendpoint', 'ipaidpissuerurl', 'ipaidpclientid', 'ipaidpscope', 'ipaidpclientsecret', 'ipaidpsub', }, 'default_privileges': {'External IdP server Administrators'} }, 'System: Delete External IdP server': { 'ipapermright': {'delete'}, 'ipapermlocation': DN(container_dn, api.env.basedn), 'ipapermtargetfilter': {'(objectclass=ipaidp)'}, 'default_privileges': {'External IdP server Administrators'} }, 'System: Read External IdP server client secret': { 'ipapermright': {'read', 'search', 'compare'}, 'ipapermlocation': DN(container_dn, api.env.basedn), 'ipapermdefaultattr': { 'cn', 'objectclass', 'ipaidpauthendpoint', 'ipaidpdevauthendpoint', 'ipaidpuserinfoendpoint', 'ipaidptokenendpoint', 'ipaidpissuerurl', 'ipaidpkeysendpoint', 'ipaidpclientid', 'ipaidpscope', 'ipaidpclientsecret', 'ipaidpsub', }, 'ipapermtargetfilter': {'(objectclass=ipaidp)'}, } }
def setup_admin(self): self.admin_user = "******" % self.fqdn self.admin_password = ipautil.ipa_generate_password() self.admin_dn = DN(('uid', self.admin_user), self.ipaca_people) # remove user if left-over exists try: api.Backend.ldap2.delete_entry(self.admin_dn) except errors.NotFound: pass # add user entry = api.Backend.ldap2.make_entry( self.admin_dn, objectclass=[ "top", "person", "organizationalPerson", "inetOrgPerson", "cmsuser" ], uid=[self.admin_user], cn=[self.admin_user], sn=[self.admin_user], usertype=['adminType'], mail=['root@localhost'], userPassword=[self.admin_password], userstate=['1']) api.Backend.ldap2.add_entry(entry) wait_groups = [] for group in self.admin_groups: group_dn = DN(('cn', group), self.ipaca_groups) mod = [(ldap.MOD_ADD, 'uniqueMember', [self.admin_dn])] try: api.Backend.ldap2.modify_s(group_dn, mod) except ldap.TYPE_OR_VALUE_EXISTS: # already there return None else: wait_groups.append(group_dn) # Now wait until the other server gets replicated this data master_conn = ipaldap.LDAPClient.from_hostname_secure(self.master_host) logger.debug("Waiting %s seconds for %s to appear on %s", api.env.replication_wait_timeout, self.admin_dn, master_conn) deadline = time.time() + api.env.replication_wait_timeout while time.time() < deadline: time.sleep(1) try: master_conn.simple_bind(self.admin_dn, self.admin_password) except errors.ACIError: # user not replicated yet pass else: logger.debug("Successfully logged in as %s", self.admin_dn) break else: logger.error("Unable to log in as %s on %s", self.admin_dn, master_conn) logger.info("[hint] tune with replication_wait_timeout") raise errors.NotFound(reason="{} did not replicate to {}".format( self.admin_dn, master_conn)) # wait for group membership for group_dn in wait_groups: replication.wait_for_entry( master_conn, group_dn, timeout=api.env.replication_wait_timeout, attr='uniqueMember', attrvalue=self.admin_dn)
class update_referint(Updater): """ Update referential integrity configuration to new style http://directory.fedoraproject.org/docs/389ds/design/ri-plugin-configuration.html old attr -> new attr nsslapd-pluginArg0 -> referint-update-delay nsslapd-pluginArg1 -> referint-logfile nsslapd-pluginArg2 -> referint-logchanges nsslapd-pluginArg3..N -> referint-membership-attr [3..N] Old and new style cannot be mixed, all nslapd-pluginArg* attrs have to be removed """ referint_dn = DN(('cn', 'referential integrity postoperation'), ('cn', 'plugins'), ('cn', 'config')) def execute(self, **options): root_logger.debug( "Upgrading referential integrity plugin configuration") ldap = self.api.Backend.ldap2 try: entry = ldap.get_entry(self.referint_dn) except errors.NotFound: root_logger.error("Referential integrity configuration not found") return False, [] referint_membership_attrs = [] root_logger.debug("Initial value: %s", repr(entry)) # nsslapd-pluginArg0 -> referint-update-delay update_delay = entry.get('nsslapd-pluginArg0') if update_delay: root_logger.debug("add: referint-update-delay: %s", update_delay) entry['referint-update-delay'] = update_delay entry['nsslapd-pluginArg0'] = None else: root_logger.info("Plugin already uses new style, skipping") return False, [] # nsslapd-pluginArg1 -> referint-logfile logfile = entry.get('nsslapd-pluginArg1') if logfile: root_logger.debug("add: referint-logfile: %s", logfile) entry['referint-logfile'] = logfile entry['nsslapd-pluginArg1'] = None # nsslapd-pluginArg2 -> referint-logchanges logchanges = entry.get('nsslapd-pluginArg2') if logchanges: root_logger.debug("add: referint-logchanges: %s", logchanges) entry['referint-logchanges'] = logchanges entry['nsslapd-pluginArg2'] = None # nsslapd-pluginArg3..N -> referint-membership-attr [3..N] for key in entry.keys(): if key.lower().startswith('nsslapd-pluginarg'): arg_val = entry.single_value[key] if arg_val: referint_membership_attrs.append(arg_val) entry[key] = None if referint_membership_attrs: # entry['referint-membership-attr'] is None, plugin doesn't allow # mixing old and new style entry['referint-membership-attr'] = referint_membership_attrs root_logger.debug("Final value: %s", repr(entry)) try: ldap.update_entry(entry) except errors.EmptyModlist: root_logger.debug("No modifications required") return False, [] return False, []
class DogtagInstance(service.Service): """ This is the base class for a Dogtag 10+ instance, which uses a shared tomcat instance and DS to host the relevant subsystems. It contains functions that will be common to installations of the CA, KRA, and eventually TKS and TPS. """ # Mapping of nicknames for tracking requests, and the profile to # use for that certificate. 'configure_renewal()' reads this # dict. The profile MUST be specified. tracking_reqs = dict() # HSM state is shared between CA and KRA hsm_sstore = 'pki_hsm' # override token for specific nicknames token_names = dict() def get_token_name(self, nickname): """Look up token name for nickname.""" return self.token_names.get(nickname, self.token_name) ipaca_groups = DN(('ou', 'groups'), ('o', 'ipaca')) ipaca_people = DN(('ou', 'people'), ('o', 'ipaca')) groups_aci = ( b'(targetfilter="(objectClass=groupOfUniqueNames)")' b'(targetattr="cn || description || objectclass || uniquemember")' b'(version 3.0; acl "Allow users from o=ipaca to read groups"; ' b'allow (read, search, compare) ' b'userdn="ldap:///uid=*,ou=people,o=ipaca";)') def __init__(self, realm, subsystem, service_desc, host_name=None, nss_db=paths.PKI_TOMCAT_ALIAS_DIR, service_prefix=None, config=None): """Initializer""" super(DogtagInstance, self).__init__('pki-tomcatd', service_desc=service_desc, realm_name=realm, service_user=constants.PKI_USER, service_prefix=service_prefix) self.admin_password = None self.fqdn = host_name self.pkcs12_info = None self.clone = False self.basedn = None self.admin_user = "******" self.admin_dn = DN(('uid', self.admin_user), self.ipaca_people) self.admin_groups = None self.tmp_agent_db = None self.subsystem = subsystem # replication parameters self.master_host = None self.master_replication_port = 389 self.nss_db = nss_db self.config = config # Path to CS.cfg # filled out by configure_instance self.pki_config_override = None self.ca_subject = None self.subject_base = None def is_installed(self): """ Determine if subsystem instance has been installed. Returns True/False """ return os.path.exists( os.path.join(paths.VAR_LIB_PKI_TOMCAT_DIR, self.subsystem.lower())) def spawn_instance(self, cfg_file, nolog_list=()): """ Create and configure a new Dogtag instance using pkispawn. Passes in a configuration file with IPA-specific parameters. """ subsystem = self.subsystem args = [paths.PKISPAWN, "-s", subsystem, "-f", cfg_file] with open(cfg_file) as f: logger.debug('Contents of pkispawn configuration file (%s):\n%s', cfg_file, ipautil.nolog_replace(f.read(), nolog_list)) try: ipautil.run(args, nolog=nolog_list) except ipautil.CalledProcessError as e: self.handle_setup_error(e) def clean_pkispawn_files(self): if self.tmp_agent_db is not None: logger.debug("Removing %s", self.tmp_agent_db) shutil.rmtree(self.tmp_agent_db, ignore_errors=True) client_dir = os.path.join('/root/.dogtag/pki-tomcat/', self.subsystem.lower()) logger.debug("Removing %s", client_dir) shutil.rmtree(client_dir, ignore_errors=True) def restart_instance(self): self.restart('pki-tomcat') def start_instance(self): self.start('pki-tomcat') def stop_instance(self): try: self.stop('pki-tomcat') except Exception: logger.debug("%s", traceback.format_exc()) logger.critical("Failed to stop the Dogtag instance." "See the installation log for details.") def enable_client_auth_to_db(self): """ Enable client auth connection to the internal db. """ sub_system_nickname = "subsystemCert cert-pki-ca" if self.token_name != INTERNAL_TOKEN: # TODO: Dogtag 10.6.9 does not like "internal" prefix. sub_system_nickname = '{}:{}'.format(self.token_name, sub_system_nickname) with stopped_service('pki-tomcatd', 'pki-tomcat'): directivesetter.set_directive( self.config, 'authz.instance.DirAclAuthz.ldap.ldapauth.authtype', 'SslClientAuth', quotes=False, separator='=') directivesetter.set_directive( self.config, 'authz.instance.DirAclAuthz.ldap.ldapauth.clientCertNickname', sub_system_nickname, quotes=False, separator='=') directivesetter.set_directive( self.config, 'authz.instance.DirAclAuthz.ldap.ldapconn.port', '636', quotes=False, separator='=') directivesetter.set_directive( self.config, 'authz.instance.DirAclAuthz.ldap.ldapconn.secureConn', 'true', quotes=False, separator='=') directivesetter.set_directive(self.config, 'internaldb.ldapauth.authtype', 'SslClientAuth', quotes=False, separator='=') directivesetter.set_directive( self.config, 'internaldb.ldapauth.clientCertNickname', sub_system_nickname, quotes=False, separator='=') directivesetter.set_directive(self.config, 'internaldb.ldapconn.port', '636', quotes=False, separator='=') directivesetter.set_directive(self.config, 'internaldb.ldapconn.secureConn', 'true', quotes=False, separator='=') # Remove internaldb password as is not needed anymore directivesetter.set_directive(paths.PKI_TOMCAT_PASSWORD_CONF, 'internaldb', None, separator='=') def uninstall(self): if self.is_installed(): self.print_msg("Unconfiguring %s" % self.subsystem) try: ipautil.run( [paths.PKIDESTROY, "-i", 'pki-tomcat', "-s", self.subsystem]) except ipautil.CalledProcessError as e: logger.critical("failed to uninstall %s instance %s", self.subsystem, e) def http_proxy(self): """ Update the http proxy file """ template_filename = (os.path.join(paths.USR_SHARE_IPA_DIR, "ipa-pki-proxy.conf.template")) sub_dict = dict( DOGTAG_PORT=8009, CLONE='' if self.clone else '#', FQDN=self.fqdn, ) template = ipautil.template_file(template_filename, sub_dict) with open(paths.HTTPD_IPA_PKI_PROXY_CONF, "w") as fd: fd.write(template) os.fchmod(fd.fileno(), 0o644) def configure_certmonger_renewal_helpers(self): """ Create a new CA type for certmonger that will retrieve updated certificates from the dogtag master server. """ cmonger = services.knownservices.certmonger cmonger.enable() if not services.knownservices.dbus.is_running(): # some platforms protect dbus with RefuseManualStart=True services.knownservices.dbus.start() cmonger.start() bus = dbus.SystemBus() obj = bus.get_object('org.fedorahosted.certmonger', '/org/fedorahosted/certmonger') iface = dbus.Interface(obj, 'org.fedorahosted.certmonger') for suffix, args in [ ('', ''), ('-reuse', ' --reuse-existing'), ('-selfsigned', ' --force-self-signed'), ]: name = RENEWAL_CA_NAME + suffix path = iface.find_ca_by_nickname(name) if not path: command = paths.DOGTAG_IPA_CA_RENEW_AGENT_SUBMIT + args iface.add_known_ca( name, command, dbus.Array([], dbus.Signature('s')), # Give dogtag extra time to generate cert timeout=CA_DBUS_TIMEOUT) def __get_pin(self, token_name=INTERNAL_TOKEN): try: return certmonger.get_pin(token_name) except IOError as e: logger.debug('Unable to determine PIN for the Dogtag instance: %s', e) raise RuntimeError(e) def configure_renewal(self): """ Configure certmonger to renew system certs """ for nickname in self.tracking_reqs: token_name = self.get_token_name(nickname) pin = self.__get_pin(token_name) try: certmonger.start_tracking( certpath=self.nss_db, ca=RENEWAL_CA_NAME, nickname=nickname, token_name=token_name, pin=pin, pre_command='stop_pkicad', post_command='renew_ca_cert "%s"' % nickname, profile=self.tracking_reqs[nickname], ) except RuntimeError as e: logger.error( "certmonger failed to start tracking certificate: %s", e) def stop_tracking_certificates(self, stop_certmonger=True): """Stop tracking our certificates. Called on uninstall. """ logger.debug( "Configuring certmonger to stop tracking system certificates " "for %s", self.subsystem) cmonger = services.knownservices.certmonger if not services.knownservices.dbus.is_running(): # some platforms protect dbus with RefuseManualStart=True services.knownservices.dbus.start() cmonger.start() for nickname in self.tracking_reqs: try: certmonger.stop_tracking(self.nss_db, nickname=nickname) except RuntimeError as e: logger.error( "certmonger failed to stop tracking certificate: %s", e) if stop_certmonger: cmonger.stop() def update_cert_cs_cfg(self, directive, cert): """ When renewing a Dogtag subsystem certificate the configuration file needs to get the new certificate as well. ``directive`` is the directive to update in CS.cfg cert is IPACertificate. cs_cfg is the path to the CS.cfg file """ with stopped_service('pki-tomcatd', 'pki-tomcat'): directivesetter.set_directive( self.config, directive, # the cert must be only the base64 string without headers (base64.b64encode(cert.public_bytes( x509.Encoding.DER)).decode('ascii')), quotes=False, separator='=') def get_admin_cert(self): """ Get the certificate for the admin user by checking the ldap entry for the user. There should be only one certificate per user. """ logger.debug('Trying to find the certificate for the admin user') conn = None try: conn = ipaldap.LDAPClient.from_realm(self.realm) conn.external_bind() entry_attrs = conn.get_entry(self.admin_dn, ['usercertificate']) admin_cert = entry_attrs.get('usercertificate')[0] # TODO(edewata) Add check to warn if there is more than one cert. finally: if conn is not None: conn.unbind() return admin_cert def handle_setup_error(self, e): logger.critical("Failed to configure %s instance: %s", self.subsystem, e) logger.critical("See the installation logs and the following " "files/directories for more information:") logger.critical(" %s", paths.TOMCAT_TOPLEVEL_DIR) raise RuntimeError("%s configuration failed." % self.subsystem) def add_ipaca_aci(self): """Add ACI to allow ipaca users to read their own group information Dogtag users aren't allowed to enumerate their own groups. The setup_admin() method needs the permission to wait, until all group information has been replicated. """ dn = self.ipaca_groups mod = [(ldap.MOD_ADD, 'aci', [self.groups_aci])] try: api.Backend.ldap2.modify_s(dn, mod) except ldap.TYPE_OR_VALUE_EXISTS: logger.debug("%s already has ACI to read group information", dn) else: logger.debug("Added ACI to read groups to %s", dn) def setup_admin(self): self.admin_user = "******" % self.fqdn self.admin_password = ipautil.ipa_generate_password() self.admin_dn = DN(('uid', self.admin_user), self.ipaca_people) # remove user if left-over exists try: api.Backend.ldap2.delete_entry(self.admin_dn) except errors.NotFound: pass # add user entry = api.Backend.ldap2.make_entry( self.admin_dn, objectclass=[ "top", "person", "organizationalPerson", "inetOrgPerson", "cmsuser" ], uid=[self.admin_user], cn=[self.admin_user], sn=[self.admin_user], usertype=['adminType'], mail=['root@localhost'], userPassword=[self.admin_password], userstate=['1']) api.Backend.ldap2.add_entry(entry) wait_groups = [] for group in self.admin_groups: group_dn = DN(('cn', group), self.ipaca_groups) mod = [(ldap.MOD_ADD, 'uniqueMember', [self.admin_dn])] try: api.Backend.ldap2.modify_s(group_dn, mod) except ldap.TYPE_OR_VALUE_EXISTS: # already there return None else: wait_groups.append(group_dn) # Now wait until the other server gets replicated this data master_conn = ipaldap.LDAPClient.from_hostname_secure(self.master_host) logger.debug("Waiting %s seconds for %s to appear on %s", api.env.replication_wait_timeout, self.admin_dn, master_conn) deadline = time.time() + api.env.replication_wait_timeout while time.time() < deadline: time.sleep(1) try: master_conn.simple_bind(self.admin_dn, self.admin_password) except errors.ACIError: # user not replicated yet pass else: logger.debug("Successfully logged in as %s", self.admin_dn) break else: logger.error("Unable to log in as %s on %s", self.admin_dn, master_conn) logger.info("[hint] tune with replication_wait_timeout") raise errors.NotFound(reason="{} did not replicate to {}".format( self.admin_dn, master_conn)) # wait for group membership for group_dn in wait_groups: replication.wait_for_entry( master_conn, group_dn, timeout=api.env.replication_wait_timeout, attr='uniqueMember', attrvalue=self.admin_dn) def __remove_admin_from_group(self, group): dn = DN(('cn', group), self.ipaca_groups) mod = [(ldap.MOD_DELETE, 'uniqueMember', self.admin_dn)] try: api.Backend.ldap2.modify_s(dn, mod) except ldap.NO_SUCH_ATTRIBUTE: # already removed pass def teardown_admin(self): for group in self.admin_groups: self.__remove_admin_from_group(group) api.Backend.ldap2.delete_entry(self.admin_dn) def backup_config(self): """ Create a backup copy of CS.cfg """ config = self.config bak = config + '.ipabkp' if services.knownservices['pki_tomcatd'].is_running('pki-tomcat'): raise RuntimeError( "Dogtag must be stopped when creating backup of %s" % config) shutil.copy(config, bak) # shutil.copy() doesn't copy owner s = os.stat(config) os.chown(bak, s.st_uid, s.st_gid) def reindex_task(self, force=False): """Reindex ipaca entries pkispawn sometimes does not run its indextasks. This leads to slow unindexed filters on attributes such as description, which is used to log in with a certificate. Explicitly reindex attribute that should have been reindexed by CA's indextasks.ldif. See https://pagure.io/dogtagpki/issue/3083 """ state_name = 'reindex_task' if not force and sysupgrade.get_upgrade_state('dogtag', state_name): return cn = "indextask_ipaca_{}".format(int(time.time())) dn = DN(('cn', cn), ('cn', 'index'), ('cn', 'tasks'), ('cn', 'config')) entry = api.Backend.ldap2.make_entry( dn, objectClass=['top', 'extensibleObject'], cn=[cn], nsInstance=['ipaca'], # Dogtag PKI database nsIndexAttribute=[ # from pki/base/ca/shared/conf/indextasks.ldif 'archivedBy', 'certstatus', 'clientId', 'dataType', 'dateOfCreate', 'description', 'duration', 'extension', 'issuedby', 'issuername', 'metaInfo', 'notafter', 'notbefore', 'ownername', 'publicKeyData', 'requestid', 'requestowner', 'requestsourceid', 'requeststate', 'requesttype', 'revInfo', 'revokedOn', 'revokedby', 'serialno', 'status', 'subjectname', ], ttl=[10], ) logger.debug('Creating ipaca reindex task %s', dn) api.Backend.ldap2.add_entry(entry) logger.debug('Waiting for task...') exitcode = replication.wait_for_task(api.Backend.ldap2, dn) logger.debug('Task %s has finished with exit code %i', dn, exitcode) sysupgrade.set_upgrade_state('dogtag', state_name, True) def set_hsm_state(self, config): section_name = self.subsystem.upper() assert section_name == 'CA' if config.getboolean(section_name, 'pki_hsm_enable', fallback=False): enable = True token_name = config.get(section_name, 'pki_token_name') else: enable = False token_name = INTERNAL_TOKEN self.sstore.backup_state(self.hsm_sstore, "enabled", enable) self.sstore.backup_state(self.hsm_sstore, "token_name", token_name) def restore_hsm_state(self): return ( self.sstore.restore_state(self.hsm_sstore, "enabled"), self.sstore.restore_state(self.hsm_sstore, "token_name"), ) @property def hsm_enabled(self): """Is HSM support enabled?""" return self.sstore.get_state(self.hsm_sstore, "enabled") @property def token_name(self): """HSM token name""" return self.sstore.get_state(self.hsm_sstore, "token_name") def _configure_clone(self, subsystem_config, security_domain_hostname, clone_pkcs12_path): subsystem_config.update( # Security domain registration pki_security_domain_hostname=security_domain_hostname, pki_security_domain_https_port=443, pki_security_domain_user=self.admin_user, pki_security_domain_password=self.admin_password, # Clone pki_clone=True, pki_clone_pkcs12_path=clone_pkcs12_path, pki_clone_pkcs12_password=self.dm_password, pki_clone_replication_security="TLS", pki_clone_replication_master_port=self.master_replication_port, pki_clone_replication_clone_port=389, pki_clone_replicate_schema=False, pki_clone_uri="https://%s" % ipautil.format_netloc(self.master_host, 443), ) def _create_spawn_config(self, subsystem_config): loader = PKIIniLoader(subsystem=self.subsystem, fqdn=self.fqdn, domain=api.env.domain, subject_base=self.subject_base, ca_subject=self.ca_subject, admin_user=self.admin_user, admin_password=self.admin_password, dm_password=self.dm_password, pki_config_override=self.pki_config_override) return loader.create_spawn_config(subsystem_config)
def _remove_server_principal_references(self, master): """ This method removes information about the replica in parts of the shared tree that expose it, so clients stop trying to use this replica. """ conn = self.Backend.ldap2 env = self.api.env master_principal = "{}@{}".format(master, env.realm).encode('utf-8') # remove replica memberPrincipal from s4u2proxy configuration s4u2proxy_subtree = DN(env.container_s4u2proxy, env.basedn) dn1 = DN(('cn', 'ipa-http-delegation'), s4u2proxy_subtree) member_principal1 = b"HTTP/%s" % master_principal dn2 = DN(('cn', 'ipa-ldap-delegation-targets'), s4u2proxy_subtree) member_principal2 = b"ldap/%s" % master_principal dn3 = DN(('cn', 'ipa-cifs-delegation-targets'), s4u2proxy_subtree) member_principal3 = b"cifs/%s" % master_principal for (dn, member_principal) in ((dn1, member_principal1), (dn2, member_principal2), (dn3, member_principal3)): try: mod = [(ldap.MOD_DELETE, 'memberPrincipal', member_principal)] conn.conn.modify_s(str(dn), mod) except (ldap.NO_SUCH_OBJECT, ldap.NO_SUCH_ATTRIBUTE): logger.debug( "Replica (%s) memberPrincipal (%s) not found in %s", master, member_principal.decode('utf-8'), dn) except Exception as e: self.add_message( messages.ServerRemovalWarning( message=_("Failed to clean memberPrincipal " "%(principal)s from s4u2proxy entry %(dn)s: " "%(err)s") % dict(principal=(member_principal.decode('utf-8')), dn=dn, err=e))) try: etc_basedn = DN(('cn', 'etc'), env.basedn) filter = '(dnaHostname=%s)' % master entries = conn.get_entries(etc_basedn, ldap.SCOPE_SUBTREE, filter=filter) if len(entries) != 0: for entry in entries: conn.delete_entry(entry) except errors.NotFound: pass except Exception as e: self.add_message( messages.ServerRemovalWarning( message=_("Failed to clean up DNA hostname entries for " "%(master)s: %(err)s") % dict(master=master, err=e))) try: dn = DN(('cn', 'default'), ('ou', 'profile'), env.basedn) ret = conn.get_entry(dn) srvlist = ret.single_value.get('defaultServerList', '') srvlist = srvlist.split() if master in srvlist: srvlist.remove(master) attr = ' '.join(srvlist) ret['defaultServerList'] = attr conn.update_entry(ret) except (errors.NotFound, errors.MidairCollision, errors.EmptyModlist): pass except Exception as e: self.add_message( messages.ServerRemovalWarning( message=_("Failed to remove server %(master)s from server " "list: %(err)s") % dict(master=master, err=e)))
ipa deskprofilerule-find --hbacrule="design department access" Remove a rule: ipa deskprofilerule-del "finance" Remove a profile: ipa deskprofile-del "Visual Design" """) register = Registry() notboth_err = _('HBAC rule and local members cannot both be set') PLUGIN_CONFIG = ( ('container_deskprofile', DN(('cn', 'desktop-profile'))), ('container_deskprofilerule', DN(('cn', 'rules'), ('cn', 'desktop-profile'))), ) @register() class deskprofile(LDAPObject): """ Desktop profile object. """ container_dn = None object_name = _('FleetCommander Desktop Profile') object_name_plural = _('FleetCommander Desktop Profiles') object_class = ['ipaassociation', 'ipadeskprofile'] permission_filter_objectclasses = ['ipadeskprofile']
def execute(self, *args, **options): # First receive all needed information: # 1. HBAC rules (whether enabled or disabled) # 2. Required options are (user, target host, service) # 3. Options: rules to test (--rules, --enabled, --disabled), request for detail output rules = [] # Use all enabled IPA rules by default all_enabled = True all_disabled = False # We need a local copy of test rules in order find incorrect ones testrules = {} if 'rules' in options: testrules = list(options['rules']) # When explicit rules are provided, disable assumptions all_enabled = False all_disabled = False sizelimit = None if 'sizelimit' in options: sizelimit = int(options['sizelimit']) # Check if --disabled is specified, include all disabled IPA rules if options['disabled']: all_disabled = True all_enabled = False # Finally, if enabled is specified implicitly, override above decisions if options['enabled']: all_enabled = True hbacset = [] if len(testrules) == 0: hbacset = self.api.Command.hbacrule_find( sizelimit=sizelimit)['result'] else: for rule in testrules: try: hbacset.append( self.api.Command.hbacrule_show(rule)['result']) except: pass # We have some rules, import them # --enabled will import all enabled rules (default) # --disabled will import all disabled rules # --rules will implicitly add the rules from a rule list for rule in hbacset: ipa_rule = convert_to_ipa_rule(rule) if ipa_rule.name in testrules: ipa_rule.enabled = True rules.append(ipa_rule) testrules.remove(ipa_rule.name) elif all_enabled and ipa_rule.enabled: # Option --enabled forces to include all enabled IPA rules into test rules.append(ipa_rule) elif all_disabled and not ipa_rule.enabled: # Option --disabled forces to include all disabled IPA rules into test ipa_rule.enabled = True rules.append(ipa_rule) # Check if there are unresolved rules left if len(testrules) > 0: # Error, unresolved rules are left in --rules return { 'summary': unicode(_(u'Unresolved rules in --rules')), 'error': testrules, 'matched': None, 'notmatched': None, 'warning': None, 'value': False } # Rules are converted to pyhbac format, build request and then test it request = pyhbac.HbacRequest() if options['user'] != u'all': # check first if this is not a trusted domain user if _dcerpc_bindings_installed: is_valid_sid = ipaserver.dcerpc.is_sid_valid(options['user']) else: is_valid_sid = False components = util.normalize_name(options['user']) if is_valid_sid or 'domain' in components or 'flatname' in components: # this is a trusted domain user if not _dcerpc_bindings_installed: raise errors.NotFound(reason=_( 'Cannot perform external member validation without ' 'Samba 4 support installed. Make sure you have installed ' 'server-trust-ad sub-package of IPA on the server')) domain_validator = ipaserver.dcerpc.DomainValidator(self.api) if not domain_validator.is_configured(): raise errors.NotFound(reason=_( 'Cannot search in trusted domains without own domain configured. ' 'Make sure you have run ipa-adtrust-install on the IPA server first' )) user_sid, group_sids = domain_validator.get_trusted_domain_user_and_groups( options['user']) request.user.name = user_sid # Now search for all external groups that have this user or # any of its groups in its external members. Found entires # memberOf links will be then used to gather all groups where # this group is assigned, including the nested ones filter_sids = "(&(objectclass=ipaexternalgroup)(|(ipaExternalMember=%s)))" \ % ")(ipaExternalMember=".join(group_sids + [user_sid]) ldap = self.api.Backend.ldap2 group_container = DN(api.env.container_group, api.env.basedn) try: entries, truncated = ldap.find_entries( filter_sids, ['memberof'], group_container) except errors.NotFound: request.user.groups = [] else: groups = [] for entry in entries: memberof_dns = entry.get('memberof', []) for memberof_dn in memberof_dns: if memberof_dn.endswith(group_container): groups.append(memberof_dn[0][0].value) request.user.groups = sorted(set(groups)) else: # try searching for a local user try: request.user.name = options['user'] search_result = self.api.Command.user_show( request.user.name)['result'] groups = search_result['memberof_group'] if 'memberofindirect_group' in search_result: groups += search_result['memberofindirect_group'] request.user.groups = sorted(set(groups)) except: pass if options['service'] != u'all': try: request.service.name = options['service'] service_result = self.api.Command.hbacsvc_show( request.service.name)['result'] if 'memberof_hbacsvcgroup' in service_result: request.service.groups = service_result[ 'memberof_hbacsvcgroup'] except: pass if options['targethost'] != u'all': try: request.targethost.name = self.canonicalize( options['targethost']) tgthost_result = self.api.Command.host_show( request.targethost.name)['result'] groups = tgthost_result['memberof_hostgroup'] if 'memberofindirect_hostgroup' in tgthost_result: groups += tgthost_result['memberofindirect_hostgroup'] request.targethost.groups = sorted(set(groups)) except: pass matched_rules = [] notmatched_rules = [] error_rules = [] warning_rules = [] result = { 'warning': None, 'matched': None, 'notmatched': None, 'error': None } if not options['nodetail']: # Validate runs rules one-by-one and reports failed ones for ipa_rule in rules: try: res = request.evaluate([ipa_rule]) if res == pyhbac.HBAC_EVAL_ALLOW: matched_rules.append(ipa_rule.name) if res == pyhbac.HBAC_EVAL_DENY: notmatched_rules.append(ipa_rule.name) except pyhbac.HbacError as e: code, rule_name = e.args if code == pyhbac.HBAC_EVAL_ERROR: error_rules.append(rule_name) self.log.info('Native IPA HBAC rule "%s" parsing error: %s' % \ (rule_name, pyhbac.hbac_result_string(code))) except (TypeError, IOError) as info: self.log.error('Native IPA HBAC module error: %s' % info) access_granted = len(matched_rules) > 0 else: res = request.evaluate(rules) access_granted = (res == pyhbac.HBAC_EVAL_ALLOW) result['summary'] = _('Access granted: %s') % (access_granted) if len(matched_rules) > 0: result['matched'] = matched_rules if len(notmatched_rules) > 0: result['notmatched'] = notmatched_rules if len(error_rules) > 0: result['error'] = error_rules if len(warning_rules) > 0: result['warning'] = warning_rules result['value'] = access_granted return result
def _person_dn(uid): return DN(('uid', uid), ('ou', 'people'), ('o', 'ipaca'))
class test_LDAPEntry(object): """ Test the LDAPEntry class """ cn1 = [u'test1'] cn2 = [u'test2'] dn1 = DN(('cn', cn1[0])) dn2 = DN(('cn', cn2[0])) def setup(self): self.ldapuri = api.env.ldap_uri self.conn = ldap2(api) self.conn.connect(autobind=AUTOBIND_DISABLED) self.entry = self.conn.make_entry(self.dn1, cn=self.cn1) def teardown(self): if self.conn and self.conn.isconnected(): self.conn.disconnect() def test_entry(self): e = self.entry assert e.dn is self.dn1 assert u'cn' in e assert u'cn' in e.keys() assert 'CN' in e if six.PY2: assert 'CN' not in e.keys() else: assert 'CN' in e.keys() assert 'commonName' in e if six.PY2: assert 'commonName' not in e.keys() else: assert 'commonName' in e.keys() assert e['CN'] is self.cn1 assert e['CN'] is e[u'cn'] e.dn = self.dn2 assert e.dn is self.dn2 def test_set_attr(self): e = self.entry e['commonName'] = self.cn2 assert u'cn' in e assert u'cn' in e.keys() assert 'CN' in e if six.PY2: assert 'CN' not in e.keys() else: assert 'CN' in e.keys() assert 'commonName' in e if six.PY2: assert 'commonName' not in e.keys() else: assert 'commonName' in e.keys() assert e['CN'] is self.cn2 assert e['CN'] is e[u'cn'] def test_del_attr(self): e = self.entry del e['CN'] assert 'CN' not in e assert 'CN' not in e.keys() assert u'cn' not in e assert u'cn' not in e.keys() assert 'commonName' not in e assert 'commonName' not in e.keys() def test_popitem(self): e = self.entry assert e.popitem() == ('cn', self.cn1) assert list(e) == [] def test_setdefault(self): e = self.entry assert e.setdefault('cn', self.cn2) == self.cn1 assert e['cn'] == self.cn1 assert e.setdefault('xyz', self.cn2) == self.cn2 assert e['xyz'] == self.cn2 def test_update(self): e = self.entry e.update({'cn': self.cn2}, xyz=self.cn2) assert e['cn'] == self.cn2 assert e['xyz'] == self.cn2 def test_pop(self): e = self.entry assert e.pop('cn') == self.cn1 assert 'cn' not in e assert e.pop('cn', 'default') is 'default' with assert_raises(KeyError): e.pop('cn') def test_clear(self): e = self.entry e.clear() assert not e assert 'cn' not in e @pytest.mark.skipif(sys.version_info >= (3, 0), reason="Python 2 only") def test_has_key(self): e = self.entry assert not e.has_key('xyz') assert e.has_key('cn') assert e.has_key('COMMONNAME') def test_in(self): e = self.entry assert 'xyz' not in e assert 'cn' in e assert 'COMMONNAME' in e def test_get(self): e = self.entry assert e.get('cn') == self.cn1 assert e.get('commonname') == self.cn1 assert e.get('COMMONNAME', 'default') == self.cn1 assert e.get('bad key', 'default') == 'default' def test_single_value(self): e = self.entry assert e.single_value['cn'] == self.cn1[0] assert e.single_value['commonname'] == self.cn1[0] assert e.single_value.get('COMMONNAME', 'default') == self.cn1[0] assert e.single_value.get('bad key', 'default') == 'default' def test_sync(self): e = self.entry nice = e['test'] = [1, 2, 3] assert e['test'] is nice raw = e.raw['test'] assert raw == [b'1', b'2', b'3'] nice.remove(1) assert e.raw['test'] is raw assert raw == [b'2', b'3'] raw.append(b'4') assert e['test'] is nice assert nice == [2, 3, u'4'] nice.remove(2) raw.append(b'5') assert nice == [3, u'4'] assert raw == [b'2', b'3', b'4', b'5'] assert e['test'] is nice assert e.raw['test'] is raw assert nice == [3, u'4', u'5'] assert raw == [b'3', b'4', b'5'] nice.insert(0, 2) raw.remove(b'4') assert nice == [2, 3, u'4', u'5'] assert raw == [b'3', b'5'] assert e.raw['test'] is raw assert e['test'] is nice assert nice == [2, 3, u'5'] assert raw == [b'3', b'5', b'2'] raw = [b'a', b'b'] e.raw['test'] = raw assert e['test'] is not nice assert e['test'] == [u'a', u'b'] nice = 'not list' e['test'] = nice assert e['test'] is nice assert e.raw['test'] == [b'not list'] e.raw['test'].append(b'second') assert e['test'] == ['not list', u'second']
class user(baseuser): """ User object. """ container_dn = baseuser.active_container_dn label = _('Users') label_singular = _('User') object_name = _('user') object_name_plural = _('users') managed_permissions = { 'System: Read User Standard Attributes': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'anonymous', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'cn', 'sn', 'description', 'title', 'uid', 'displayname', 'givenname', 'initials', 'manager', 'gecos', 'gidnumber', 'homedirectory', 'loginshell', 'uidnumber', 'ipantsecurityidentifier' }, }, 'System: Read User Addressbook Attributes': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'seealso', 'telephonenumber', 'facsimiletelephonenumber', 'l', 'ou', 'st', 'postalcode', 'street', 'destinationindicator', 'internationalisdnnumber', 'physicaldeliveryofficename', 'postaladdress', 'postofficebox', 'preferreddeliverymethod', 'registeredaddress', 'teletexterminalidentifier', 'telexnumber', 'x121address', 'carlicense', 'departmentnumber', 'employeenumber', 'employeetype', 'preferredlanguage', 'mail', 'mobile', 'pager', 'audio', 'businesscategory', 'homephone', 'homepostaladdress', 'jpegphoto', 'labeleduri', 'o', 'photo', 'roomnumber', 'secretary', 'usercertificate', 'usersmimecertificate', 'x500uniqueidentifier', 'inetuserhttpurl', 'inetuserstatus', 'ipacertmapdata', }, 'fixup_function': fix_addressbook_permission_bindrule, }, 'System: Read User IPA Attributes': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'ipauniqueid', 'ipasshpubkey', 'ipauserauthtype', 'userclass', }, 'fixup_function': fix_addressbook_permission_bindrule, }, 'System: Read User Kerberos Attributes': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'krbprincipalname', 'krbcanonicalname', 'krbprincipalaliases', 'krbprincipalexpiration', 'krbpasswordexpiration', 'krblastpwdchange', 'nsaccountlock', 'krbprincipaltype', }, }, 'System: Read User Kerberos Login Attributes': { 'replaces_global_anonymous_aci': True, 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'krblastsuccessfulauth', 'krblastfailedauth', 'krblastpwdchange', 'krblastadminunlock', 'krbloginfailedcount', 'krbpwdpolicyreference', 'krbticketpolicyreference', 'krbupenabled', }, 'default_privileges': {'User Administrators'}, }, 'System: Read User Membership': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'memberof', }, }, 'System: Read UPG Definition': { # Required for adding users 'replaces_global_anonymous_aci': True, 'non_object': True, 'ipapermlocation': UPG_DEFINITION_DN, 'ipapermtarget': UPG_DEFINITION_DN, 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': {'*'}, 'default_privileges': {'User Administrators'}, }, 'System: Add Users': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add Users";allow (add) groupdn = "ldap:///cn=Add Users,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'User Administrators'}, }, 'System: Add User to default group': { 'non_object': True, 'ipapermright': {'write'}, 'ipapermlocation': DN(api.env.container_group, api.env.basedn), 'ipapermtarget': DN('cn=ipausers', api.env.container_group, api.env.basedn), 'ipapermdefaultattr': {'member'}, 'replaces': [ '(targetattr = "member")(target = "ldap:///cn=ipausers,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add user to default group";allow (write) groupdn = "ldap:///cn=Add user to default group,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'User Administrators'}, }, 'System: Change User password': { 'ipapermright': {'write'}, 'ipapermtargetfilter': [ '(objectclass=posixaccount)', '(!(memberOf=%s))' % DN('cn=admins', api.env.container_group, api.env.basedn), ], 'ipapermdefaultattr': { 'krbprincipalkey', 'passwordhistory', 'sambalmpassword', 'sambantpassword', 'userpassword', 'krbpasswordexpiration' }, 'replaces': [ '(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(targetattr = "userpassword || krbprincipalkey || sambalmpassword || sambantpassword || passwordhistory")(version 3.0;acl "permission:Change a user password";allow (write) groupdn = "ldap:///cn=Change a user password,cn=permissions,cn=pbac,$SUFFIX";)', '(targetfilter = "(!(memberOf=cn=admins,cn=groups,cn=accounts,$SUFFIX))")(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(targetattr = "userpassword || krbprincipalkey || sambalmpassword || sambantpassword || passwordhistory")(version 3.0;acl "permission:Change a user password";allow (write) groupdn = "ldap:///cn=Change a user password,cn=permissions,cn=pbac,$SUFFIX";)', '(targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Windows PassSync service can write passwords"; allow (write) userdn="ldap:///uid=passsync,cn=sysaccounts,cn=etc,$SUFFIX";)', ], 'default_privileges': { 'User Administrators', 'Modify Users and Reset passwords', 'PassSync Service', }, }, 'System: Manage User SSH Public Keys': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'ipasshpubkey'}, 'replaces': [ '(targetattr = "ipasshpubkey")(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Manage User SSH Public Keys";allow (write) groupdn = "ldap:///cn=Manage User SSH Public Keys,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'User Administrators'}, }, 'System: Manage User Certificates': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'usercertificate'}, 'default_privileges': { 'User Administrators', 'Modify Users and Reset passwords', }, }, 'System: Manage User Principals': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'krbprincipalname', 'krbcanonicalname'}, 'default_privileges': { 'User Administrators', 'Modify Users and Reset passwords', }, }, 'System: Modify Users': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'businesscategory', 'carlicense', 'cn', 'departmentnumber', 'description', 'displayname', 'employeetype', 'employeenumber', 'facsimiletelephonenumber', 'gecos', 'givenname', 'homedirectory', 'homephone', 'inetuserhttpurl', 'initials', 'l', 'labeleduri', 'loginshell', 'manager', 'mail', 'mepmanagedentry', 'mobile', 'objectclass', 'ou', 'pager', 'postalcode', 'roomnumber', 'secretary', 'seealso', 'sn', 'st', 'street', 'telephonenumber', 'title', 'userclass', 'preferredlanguage' }, 'replaces': [ '(targetattr = "givenname || sn || cn || displayname || title || initials || loginshell || gecos || homephone || mobile || pager || facsimiletelephonenumber || telephonenumber || street || roomnumber || l || st || postalcode || manager || secretary || description || carlicense || labeleduri || inetuserhttpurl || seealso || employeetype || businesscategory || ou || mepmanagedentry || objectclass")(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Users";allow (write) groupdn = "ldap:///cn=Modify Users,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': { 'User Administrators', 'Modify Users and Reset passwords', }, }, 'System: Remove Users': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Remove Users";allow (delete) groupdn = "ldap:///cn=Remove Users,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'User Administrators'}, }, 'System: Unlock User': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'krblastadminunlock', 'krbloginfailedcount', 'nsaccountlock', }, 'replaces': [ '(targetattr = "krbLastAdminUnlock || krbLoginFailedCount")(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Unlock user accounts";allow (write) groupdn = "ldap:///cn=Unlock user accounts,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'User Administrators'}, }, 'System: Read User Compat Tree': { 'non_object': True, 'ipapermbindruletype': 'anonymous', 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN('cn=users', 'cn=compat', api.env.basedn), 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'uid', 'cn', 'gecos', 'gidnumber', 'uidnumber', 'homedirectory', 'loginshell', }, }, 'System: Read User Views Compat Tree': { 'non_object': True, 'ipapermbindruletype': 'anonymous', 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN('cn=users', 'cn=*', 'cn=views', 'cn=compat', api.env.basedn), 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'uid', 'cn', 'gecos', 'gidnumber', 'uidnumber', 'homedirectory', 'loginshell', }, }, 'System: Read User NT Attributes': { 'ipapermbindruletype': 'permission', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'ntuserdomainid', 'ntuniqueid', 'ntuseracctexpires', 'ntusercodepage', 'ntuserdeleteaccount', 'ntuserlastlogoff', 'ntuserlastlogon', }, 'default_privileges': {'PassSync Service'}, }, 'System: Manage User Certificate Mappings': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'ipacertmapdata', 'objectclass'}, 'default_privileges': {'Certificate Identity Mapping Administrators'}, }, } takes_params = baseuser.takes_params + ( Bool( 'nsaccountlock?', cli_name=('disabled'), default=False, label=_('Account disabled'), ), Bool( 'preserved?', label=_('Preserved user'), default=False, flags=['virtual_attribute', 'no_create', 'no_update'], ), ) def get_delete_dn(self, *keys, **options): active_dn = self.get_dn(*keys, **options) return DN(active_dn[0], self.delete_container_dn, api.env.basedn) def get_either_dn(self, *keys, **options): ''' Returns the DN of a user The user can be active (active container) or delete (delete container) If the user does not exist, returns the Active user DN ''' ldap = self.backend # Check that this value is a Active user try: active_dn = self.get_dn(*keys, **options) ldap.get_entry(active_dn, ['dn']) # The Active user exists dn = active_dn except errors.NotFound: # Check that this value is a Delete user delete_dn = self.get_delete_dn(*keys, **options) try: ldap.get_entry(delete_dn, ['dn']) # The Delete user exists dn = delete_dn except errors.NotFound: # The user is neither Active/Delete -> returns that Active DN dn = active_dn return dn def _normalize_manager(self, manager): """ Given a userid verify the user's existence and return the dn. """ return super(user, self).normalize_manager(manager, self.active_container_dn) def get_preserved_attribute(self, entry, options): if options.get('raw', False): return delete_container_dn = DN(self.delete_container_dn, api.env.basedn) if entry.dn.endswith(delete_container_dn): entry['preserved'] = True elif options.get('all', False): entry['preserved'] = False
def setup(self): self.conn = None self.ldapuri = api.env.ldap_uri self.dn = DN( ('krbprincipalname', 'ldap/%s@%s' % (api.env.host, api.env.realm)), ('cn', 'services'), ('cn', 'accounts'), api.env.basedn)
def _preserve_user(self, pkey, delete_container, **options): assert isinstance(delete_container, DN) dn = self.obj.get_either_dn(pkey, **options) delete_dn = DN(dn[0], delete_container) ldap = self.obj.backend logger.debug("preserve move %s -> %s", dn, delete_dn) if dn.endswith(delete_container): raise errors.ExecutionError( _('%s: user is already preserved' % pkey)) # Check that this value is a Active user try: original_entry_attrs = self._exc_wrapper(pkey, options, ldap.get_entry)(dn, ['dn']) except errors.NotFound: raise self.obj.handle_not_found(pkey) for callback in self.get_callbacks('pre'): dn = callback(self, ldap, dn, pkey, **options) assert isinstance(dn, DN) # start to move the entry to Delete container self._exc_wrapper(pkey, options, ldap.move_entry)(dn, delete_dn, del_old=True) # Then clear the credential attributes attrs_to_clear = [ 'krbPrincipalKey', 'krbLastPwdChange', 'krbPasswordExpiration', 'userPassword' ] entry_attrs = self._exc_wrapper(pkey, options, ldap.get_entry)(delete_dn, attrs_to_clear) clearedCredential = False for attr in attrs_to_clear: if attr.lower() in entry_attrs: del entry_attrs[attr] clearedCredential = True if clearedCredential: self._exc_wrapper(pkey, options, ldap.update_entry)(entry_attrs) # Then restore some original entry attributes attrs_to_restore = [ 'secretary', 'managedby', 'manager', 'ipauniqueid', 'uidnumber', 'gidnumber', 'passwordHistory' ] entry_attrs = self._exc_wrapper(pkey, options, ldap.get_entry)(delete_dn, attrs_to_restore) restoreAttr = False for attr in attrs_to_restore: if ((attr.lower() in original_entry_attrs) and not (attr.lower() in entry_attrs)): restoreAttr = True entry_attrs[attr.lower()] = original_entry_attrs[attr.lower()] if restoreAttr: self._exc_wrapper(pkey, options, ldap.update_entry)(entry_attrs)
def write_ca_certificates_dir(self, directory, ca_certs): # pylint: disable=ipa-forbidden-import from ipalib import x509 # FixMe: break import cycle # pylint: enable=ipa-forbidden-import path = Path(directory) try: path.mkdir(mode=0o755, exist_ok=True) except Exception: logger.error("Could not create %s", path) raise for cert, nickname, trusted, _ext_key_usage in ca_certs: if not trusted: continue # I'm not handling errors here because they have already # been checked by the time we get here subject = DN(cert.subject) issuer = DN(cert.issuer) # Construct the certificate filename using the Subject DN so that # the user can see which CA a particular file is for, and include # the serial number to disambiguate clashes where a subordinate CA # had a new certificate issued. # # Strictly speaking, certificates are uniquely idenified by (Issuer # DN, Serial Number). Do we care about the possibility of a clash # where a subordinate CA had two certificates issued by different # CAs who used the same serial number?) filename = f'{subject.ldap_text()} {cert.serial_number}.crt' # pylint: disable=old-division cert_path = path / filename # pylint: enable=old-division try: f = open(cert_path, 'w') except Exception: logger.error("Could not create %s", cert_path) raise with f: try: os.fchmod(f.fileno(), 0o644) except Exception: logger.error("Could not set mode of %s", cert_path) raise try: f.write(f"""\ This file was created by IPA. Do not edit. Description: {nickname} Subject: {subject.ldap_text()} Issuer: {issuer.ldap_text()} Serial Number (dec): {cert.serial_number} Serial Number (hex): {cert.serial_number:#x} """) pem = cert.public_bytes(x509.Encoding.PEM).decode('ascii') f.write(pem) except Exception: logger.error("Could not write to %s", cert_path) raise return True
# This is a tuple instead of a dict so that it is immutable. # To create a dict with this config, just "d = dict(DEFAULT_CONFIG)". DEFAULT_CONFIG = ( ('api_version', API_VERSION), ('version', VERSION), # Domain, realm, basedn: # Following values do not have any reasonable default. # Do not initialize them so the code which depends on them blows up early # and does not do crazy stuff with default values instead of real ones. # ('domain', 'example.com'), # ('realm', 'EXAMPLE.COM'), # ('basedn', DN(('dc', 'example'), ('dc', 'com'))), # LDAP containers: ('container_accounts', DN(('cn', 'accounts'))), ('container_user', DN(('cn', 'users'), ('cn', 'accounts'))), ('container_deleteuser', DN(('cn', 'deleted users'), ('cn', 'accounts'), ('cn', 'provisioning'))), ('container_stageuser', DN(('cn', 'staged users'), ('cn', 'accounts'), ('cn', 'provisioning'))), ('container_group', DN(('cn', 'groups'), ('cn', 'accounts'))), ('container_service', DN(('cn', 'services'), ('cn', 'accounts'))), ('container_host', DN(('cn', 'computers'), ('cn', 'accounts'))), ('container_hostgroup', DN(('cn', 'hostgroups'), ('cn', 'accounts'))), ('container_rolegroup', DN(('cn', 'roles'), ('cn', 'accounts'))), ('container_permission', DN(('cn', 'permissions'), ('cn', 'pbac'))), ('container_privilege', DN(('cn', 'privileges'), ('cn', 'pbac'))), ('container_automount', DN(('cn', 'automount'))), ('container_policies', DN(('cn', 'policies'))), ('container_configs', DN(('cn', 'configs'), ('cn', 'policies'))), ('container_roles', DN(('cn', 'roles'), ('cn', 'policies'))), ('container_applications', DN(('cn', 'applications'), ('cn', 'configs'), ('cn', 'policies'))),
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.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: 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.dercert = db.get_cert_from_db(self.cert_nickname, pem=False) 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 __call__(self, environ, start_response): # Make sure this is a form request. content_type = environ.get('CONTENT_TYPE', '').lower() if not content_type.startswith('application/x-www-form-urlencoded'): return self.bad_request( environ, start_response, "Content-Type must be application/x-www-form-urlencoded") # Make sure this is a POST request. method = environ.get('REQUEST_METHOD', '').upper() if method == 'POST': query_string = read_input(environ) else: return self.bad_request(environ, start_response, "HTTP request method must be POST") # Parse the query string to a dictionary. try: query_dict = parse_qs(query_string) except Exception as e: return self.bad_request(environ, start_response, "cannot parse query data") data = {} for field in ('user', 'password', 'first_code', 'second_code', 'token'): value = query_dict.get(field, None) if value is not None: if len(value) == 1: data[field] = value[0] else: return self.bad_request( environ, start_response, "more than one %s parameter" % field) elif field != 'token': return self.bad_request(environ, start_response, "no %s specified" % field) # Create the request control. sr = self.OTPSyncRequest() sr.setComponentByName('firstCode', data['first_code']) sr.setComponentByName('secondCode', data['second_code']) if 'token' in data: try: token_dn = DN(data['token']) except ValueError: token_dn = DN( (self.api.Object.otptoken.primary_key.name, data['token']), self.api.env.container_otp, self.api.env.basedn) sr.setComponentByName('tokenDN', str(token_dn)) rc = ldap.controls.RequestControl(sr.OID, True, encoder.encode(sr)) # Resolve the user DN bind_dn = DN((self.api.Object.user.primary_key.name, data['user']), self.api.env.container_user, self.api.env.basedn) # Start building the response. status = HTTP_STATUS_SUCCESS response_headers = [('Content-Type', 'text/html; charset=utf-8')] title = 'Token sync rejected' # Perform the synchronization. conn = ldap2(self.api) try: conn.connect(bind_dn=bind_dn, bind_pw=data['password'], serverctrls=[ rc, ]) result = 'ok' title = "Token sync successful" message = "Token was synchronized." except (NotFound, ACIError): result = 'invalid-credentials' message = 'The username, password or token codes are not correct.' except Exception as e: result = 'error' message = "Could not connect to LDAP server." logger.error( "token_sync: cannot authenticate '%s' to LDAP " "server: %s", data['user'], str(e)) finally: if conn.isconnected(): conn.disconnect() # Report status and return. response_headers.append(('X-IPA-TokenSync-Result', result)) start_response(status, response_headers) output = _success_template % dict(title=str(title), message=str(message)) return [output.encode('utf-8')]
def realm_to_suffix(realm_name): 'Convert a kerberos realm to a IPA suffix.' s = realm_name.split(".") suffix_dn = DN(*[('dc', x.lower()) for x in s]) return suffix_dn