def check_access(self, operation=None): """ Perform an LDAP query to determine authorization. This should be executed before any actual work is done. """ if self.operation is None and operation is None: raise errors.ACIError(info=_('operation not defined')) if operation is None: operation = self.operation ldap = self.api.Backend.ldap2 self.log.debug("IPA: virtual verify %s" % operation) operationdn = DN(('cn', operation), self.api.env.container_virtual, self.api.env.basedn) try: if not ldap.can_write(operationdn, "objectclass"): raise errors.ACIError( info=_('not allowed to perform this command')) except errors.NotFound: raise errors.ACIError(info=_('No such virtual command')) return True
def post_callback(self, ldap, dn, entry, *keys, **options): default_entry = None rights = None for attrname in self.obj.default_attributes: if attrname not in entry: if keys[-1] is not None: # User entry doesn't override the attribute. # Check if this is caused by insufficient read rights if rights is None: rights = baseldap.get_effective_rights( ldap, dn, self.obj.default_attributes) if 'r' not in rights.get(attrname.lower(), ''): raise errors.ACIError( info=_('Ticket policy for %s could not be read') % keys[-1]) # Fallback to the default if default_entry is None: try: default_dn = self.obj.get_dn(None) default_entry = ldap.get_entry(default_dn) except errors.NotFound: default_entry = {} if attrname in default_entry: entry[attrname] = default_entry[attrname] if attrname not in entry: raise errors.ACIError( info=_('Default ticket policy could not be read')) return dn
def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): assert isinstance(dn, DN) # Allow an existing OTP to be reset but don't allow a OTP to be # added to an enrolled host. if options.get('userpassword') or options.get('random'): entry = {} self.obj.get_password_attributes(ldap, dn, entry) if not entry['has_password'] and entry['has_keytab']: raise errors.ValidationError(name='password', error=_('Password cannot be set on enrolled host.')) # Once a principal name is set it cannot be changed if 'cn' in entry_attrs: raise errors.ACIError(info=_('cn is immutable')) if 'locality' in entry_attrs: entry_attrs['l'] = entry_attrs['locality'] del entry_attrs['locality'] if 'krbprincipalname' in entry_attrs: (dn, entry_attrs_old) = ldap.get_entry( dn, ['objectclass', 'krbprincipalname'] ) if 'krbprincipalname' in entry_attrs_old: msg = 'Principal name already set, it is unchangeable.' raise errors.ACIError(info=msg) obj_classes = entry_attrs_old['objectclass'] if 'krbprincipalaux' not in obj_classes: obj_classes.append('krbprincipalaux') entry_attrs['objectclass'] = obj_classes cert = x509.normalize_certificate(entry_attrs.get('usercertificate')) if cert: x509.verify_cert_subject(ldap, keys[-1], cert) (dn, entry_attrs_old) = ldap.get_entry(dn, ['usercertificate']) if 'usercertificate' in entry_attrs_old: oldcert = x509.normalize_certificate(entry_attrs_old.get('usercertificate')[0]) try: serial = unicode(x509.get_serial_number(oldcert, x509.DER)) try: result = api.Command['cert_show'](unicode(serial))['result'] if 'revocation_reason' not in result: try: api.Command['cert_revoke'](unicode(serial), revocation_reason=4) except errors.NotImplementedError: # some CA's might not implement revoke pass except errors.NotImplementedError: # some CA's might not implement revoke pass except NSPRError, nsprerr: if nsprerr.errno == -8183: # If we can't decode the cert them proceed with # modifying the host. self.log.info("Problem decoding certificate %s" % nsprerr.args[1]) else: raise nsprerr entry_attrs['usercertificate'] = cert
def pre_callback(self, ldap, dn, *keys, **options): ca_enabled_check(self.api) # ensure operator has permission to delete CA # before contacting Dogtag if not ldap.can_delete(dn): raise errors.ACIError(info=_( "Insufficient privilege to delete a CA.")) if keys[0] == IPA_CA_CN: raise errors.ProtectedEntryError( label=_("CA"), key=keys[0], reason=_("IPA CA cannot be deleted")) ca_id = self.api.Command.ca_show(keys[0])['result']['ipacaid'][0] with self.api.Backend.ra_lightweight_ca as ca_api: data = ca_api.read_ca(ca_id) if data['enabled']: raise errors.ProtectedEntryError( label=_("CA"), key=keys[0], reason=_("Must be disabled first")) ca_api.delete_ca(ca_id) return dn
def __kinit_as_trusted_account(self, info, password): """ Initializes ccache with trusted domain account credentials. Applies session code defaults for ccache directory and naming prefix. Session code uses krbccache_prefix+<pid>, we use krbccache_prefix+<TD>+<domain netbios name> so there is no clash Returns tuple (ccache name, principal) where (None, None) signifes an error on ccache initialization """ ccache_name = os.path.join( krbccache_dir, "%sTD%s" % (krbccache_prefix, info['name'][0])) principal = '%s$@%s' % (self.flatname, info['dns_domain'].upper()) (stdout, stderr, returncode) = ipautil.run(['/usr/bin/kinit', principal], env={'KRB5CCNAME': ccache_name}, stdin=password, raiseonerr=False) if returncode == 0: return (ccache_name, principal) else: if returncode == 1: raise errors.ACIError(info=_( "KDC for %(domain)s denied trust account for IPA domain with a message '%(message)s'" ) % dict(domain=info['dns_domain'], message=stderr.strip())) return (None, None)
def execute(self, principal, password, current_password): """ Execute the passwd operation. The dn should not be passed as a keyword argument as it is constructed by this method. Returns the entry :param principal: The login name or principal of the user :param password: the new password :param current_password: the existing password, if applicable """ ldap = self.api.Backend.ldap2 (dn, entry_attrs) = ldap.find_entry_by_attr( 'krbprincipalname', principal, 'posixaccount', [''], DN(api.env.container_user, api.env.basedn)) if principal == getattr(context, 'principal') and \ current_password == MAGIC_VALUE: # No cheating self.log.warn( 'User attempted to change password using magic value') raise errors.ACIError(info=_('Invalid credentials')) if current_password == MAGIC_VALUE: ldap.modify_password(dn, password) else: ldap.modify_password(dn, password, current_password) return dict( result=True, value=principal, )
def pre_callback(self, ldap, dn, entry, entry_attrs, *keys, **options): ca_enabled_check() if not ldap.can_add(dn[1:]): raise errors.ACIError( info=_("Insufficient 'add' privilege for entry '%s'.") % dn) # check for name collision before creating CA in Dogtag try: api.Object.ca.get_dn_if_exists(keys[-1]) self.obj.handle_duplicate_entry(*keys) except errors.NotFound: pass # check for subject collision before creating CA in Dogtag result = api.Command.ca_find(ipacasubjectdn=options['ipacasubjectdn']) if result['count'] > 0: raise errors.DuplicateEntry( message=_("Subject DN is already used by CA '%s'") % result['result'][0]['cn'][0]) # Create the CA in Dogtag. with self.api.Backend.ra_lightweight_ca as ca_api: resp = ca_api.create_ca(options['ipacasubjectdn']) entry['ipacaid'] = [resp['id']] entry['ipacaissuerdn'] = [resp['issuerDN']] # In the event that the issued certificate's subject DN # differs from what was requested, record the actual DN. # entry['ipacasubjectdn'] = [resp['dn']] return dn
def has_upg(self): """Returns True/False whether User-Private Groups are enabled. This is determined based on whether the UPG Definition's originfilter contains "(objectclass=disable)". If the UPG Definition or its originfilter is not readable, an ACI error is raised. """ upg_dn = DN(('cn', 'UPG Definition'), ('cn', 'Definitions'), ('cn', 'Managed Entries'), ('cn', 'etc'), self.api.env.basedn) try: with self.error_handler(): upg_entries = self.conn.search_s(str(upg_dn), _ldap.SCOPE_BASE, attrlist=['*']) upg_entries = self._convert_result(upg_entries) except errors.NotFound: upg_entries = None if not upg_entries or 'originfilter' not in upg_entries[0]: raise errors.ACIError( info=_('Could not read UPG Definition originfilter. ' 'Check your permissions.')) org_filter = upg_entries[0].single_value['originfilter'] return '(objectclass=disable)' not in org_filter
def caacl_check(principal_type, principal, ca, profile_id): principal_type_map = {USER: '******', HOST: 'host', SERVICE: 'service'} if not acl_evaluate(principal_type_map[principal_type], principal, ca, profile_id): raise errors.ACIError( info=_("Principal '%(principal)s' " "is not permitted to use CA '%(ca)s' " "with profile '%(profile_id)s' for certificate issuance.") % dict(principal=unicode(principal), ca=ca, profile_id=profile_id))
def execute(self, hostname, **kw): """ Execute the machine join operation. Returns the entry as it will be created in LDAP. :param hostname: The name of the host joined :param kw: Keyword arguments for the other attributes. """ assert 'cn' not in kw ldap = self.api.Backend.ldap2 # realm parameter is not supported by host_{add,mod} kw.pop('realm', None) try: # First see if the host exists show_kw = {'fqdn': hostname, 'all': True} attrs_list = api.Command['host_show'](**show_kw)['result'] dn = attrs_list['dn'] # No error raised so far means that host entry exists logger.info( 'Host entry for %s already exists, ' 'joining may fail on the client side ' 'if not forced', hostname) # If no principal name is set yet we need to try to add # one. if 'krbprincipalname' not in attrs_list: service = "host/%s@%s" % (hostname, api.env.realm) api.Command['host_mod'](hostname, **kw, krbprincipalname=service) logger.info('No principal set, setting to %s', service) # It exists, can we write the password attributes? allowed = ldap.can_write(dn, 'krblastpwdchange') if not allowed: raise errors.ACIError(info=_( "Insufficient 'write' privilege " "to the 'krbLastPwdChange' attribute of entry '%s'.") % dn) # Reload the attrs_list and dn so that we return update values attrs_list = api.Command['host_show'](**show_kw)['result'] dn = attrs_list['dn'] except errors.NotFound: attrs_list = api.Command['host_add'](hostname, **kw, force=True)['result'] dn = attrs_list['dn'] config = api.Command['config_show']()['result'] attrs_list['ipacertificatesubjectbase'] =\ config['ipacertificatesubjectbase'] return dn, attrs_list
def _enable_sid(self, ldap, options): # the user must have the Replication Administrators privilege privilege = 'Replication Administrators' if not principal_has_privilege(self.api, context.principal, privilege): raise errors.ACIError( info=_("not allowed to enable SID generation")) # NetBIOS name is either taken from options or generated try: netbios_name, reset_netbios_name = set_and_check_netbios_name( options.get('netbios_name', None), True, self.api) except ScriptError: raise errors.ValidationError( name="NetBIOS name", error=_('Up to 15 characters and only uppercase ASCII letters' ', digits and dashes are allowed. Empty string is ' 'not allowed.')) _ret = 0 _stdout = '' _stderr = '' dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) method_options = [] if options.get('add_sids', False): method_options.extend(["--add-sids"]) method_options.extend(["--netbios-name", netbios_name]) if reset_netbios_name: method_options.append("--reset-netbios-name") # Dbus definition expects up to 10 arguments method_options.extend([''] * (10 - len(method_options))) try: bus = dbus.SystemBus() obj = bus.get_object('org.freeipa.server', '/', follow_name_owner_changes=True) server = dbus.Interface(obj, 'org.freeipa.server') _ret, _stdout, _stderr = server.config_enable_sid(*method_options) except dbus.DBusException as e: logger.error( 'Failed to call org.freeipa.server.config_enable_sid.' 'DBus exception is %s', str(e)) raise errors.ExecutionError(message=_('Failed to call DBus')) # The oddjob restarts dirsrv, we need to re-establish the conn if self.api.Backend.ldap2.isconnected(): self.api.Backend.ldap2.disconnect() self.api.Backend.ldap2.connect(ccache=context.ccache_name) if _ret != 0: logger.error("Helper config_enable_sid return code is %d", _ret) raise errors.ExecutionError( message=_('Configuration of SID failed. ' 'See details in the error log'))
def check_operation_access(api, operation): """ Check access of bound principal to given operation. :return: ``True`` :raises: ``ACIError`` on access denied or ``NotFound`` for unknown virtual operation """ operationdn = DN(('cn', operation), api.env.container_virtual, api.env.basedn) try: if not api.Backend.ldap2.can_write(operationdn, "objectclass"): raise errors.ACIError( info=_('not allowed to perform operation: %s') % operation) except errors.NotFound: raise errors.ACIError(info=_('No such virtual command')) return True
def pre_callback(self, ldap, dn, *keys, **options): assert isinstance(dn, DN) if not options.get('force') and not self.obj.check_system( ldap, dn, *keys): raise errors.ACIError( info=_('A SYSTEM permission may not be removed')) # remove permission even when the underlying ACI is missing try: self.api.Command.aci_del(keys[-1], aciprefix=ACI_PREFIX) except errors.NotFound: pass return dn
def post_callback(self, ldap, dn, entry, *keys, **options): default_entry = None rights = None for attrname in self.obj.default_attributes: if attrname not in entry: if keys[-1] is not None: # User entry doesn't override the attribute. # Check if this is caused by insufficient read rights if rights is None: rights = baseldap.get_effective_rights( ldap, dn, self.obj.default_attributes) if 'r' not in rights.get(attrname.lower(), ''): raise errors.ACIError( info=_('Ticket policy for %s could not be read') % keys[-1]) # Fallback to the default if default_entry is None: try: default_dn = self.obj.get_dn(None) default_entry = ldap.get_entry(default_dn) except errors.NotFound: default_entry = {} if attrname in default_entry: entry[attrname] = default_entry[attrname] elif attrname in _option_based_attrs: # If default entry contains option-based default attrs, # copy the options explicitly attrs = [(a, a.split(';')[0]) for a in default_entry] for a in attrs: if a[1] == attrname and a[0] not in entry: entry[a[0]] = default_entry[a[0]] if attrname not in entry and attrname not in _option_based_attrs: raise errors.ACIError( info=_('Default ticket policy could not be read')) # Rename authentication indicator-specific policy elements from LDAP rename_authind_options_from_ldap(entry, options) return dn
def execute(self, *keys, **options): # the server must be the local host if keys[-2] != api.env.host: raise errors.ValidationError(name='cn', error=_("must be \"%s\"") % api.env.host) # the server entry must exist try: self.obj.get_dn_if_exists(*keys[:-1]) except errors.NotFound: raise self.obj.handle_not_found(keys[-2]) # the user must have the Replication Administrators privilege privilege = u'Replication Administrators' privilege_dn = self.api.Object.privilege.get_dn(privilege) ldap = self.obj.backend filter = ldap.make_filter( { 'krbprincipalname': context.principal, # pylint: disable=no-member 'memberof': privilege_dn }, rules=ldap.MATCH_ALL) try: ldap.find_entries(base_dn=self.api.env.basedn, filter=filter) except errors.NotFound: raise errors.ACIError( info=_("not allowed to perform server connection check")) dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SystemBus() obj = bus.get_object('org.freeipa.server', '/', follow_name_owner_changes=True) server = dbus.Interface(obj, 'org.freeipa.server') ret, stdout, _stderr = server.conncheck(keys[-1]) result = dict( result=(ret == 0), value=keys[-2], ) for line in stdout.splitlines(): messages.add_message(options['version'], result, messages.ExternalCommandOutput(line=line)) return result
def pre_callback(self, ldap, dn, entry, entry_attrs, *keys, **options): ca_enabled_check(self.api) if not ldap.can_add(dn[1:]): raise errors.ACIError( info=_("Insufficient 'add' privilege for entry '%s'.") % dn) # check that DN only includes standard naming attributes dn_attrs = { ava.attr.lower() for rdn in options['ipacasubjectdn'] for ava in rdn } x509_attrs = { attr.lower() for attr in six.viewvalues(ATTR_NAME_BY_OID) } unknown_attrs = dn_attrs - x509_attrs if len(unknown_attrs) > 0: raise errors.ValidationError( name=_("Subject DN"), error=_("Unrecognized attributes: %(attrs)s") % dict(attrs=", ".join(unknown_attrs)) ) # check for name collision before creating CA in Dogtag try: api.Object.ca.get_dn_if_exists(keys[-1]) self.obj.handle_duplicate_entry(*keys) except errors.NotFound: pass # check for subject collision before creating CA in Dogtag result = api.Command.ca_find(ipacasubjectdn=options['ipacasubjectdn']) if result['count'] > 0: raise errors.DuplicateEntry(message=_( "Subject DN is already used by CA '%s'" ) % result['result'][0]['cn'][0]) # Create the CA in Dogtag. with self.api.Backend.ra_lightweight_ca as ca_api: resp = ca_api.create_ca(options['ipacasubjectdn']) entry['ipacaid'] = [resp['id']] entry['ipacaissuerdn'] = [resp['issuerDN']] # In the event that the issued certificate's subject DN # differs from what was requested, record the actual DN. # entry['ipacasubjectdn'] = [resp['dn']] return dn
def check_access(self, operation=None): """ Perform an LDAP query to determine authorization. This should be executed before any actual work is done. """ if self.operation is None and operation is None: raise errors.ACIError(info=_('operation not defined')) if operation is None: operation = self.operation logger.debug("IPA: virtual verify %s", operation) return check_operation_access(self.api, operation)
def pre_callback(self, ldap, dn, *keys, **options): assert isinstance(dn, DN) try: entry = ldap.get_entry(dn, attrs_list=["member"]) except errors.NotFound: raise self.obj.handle_not_found(*keys) members = entry.get("member", []) if members: raise errors.ACIError( info=_( "Not allowed to delete User Agreement with linked groups" ) ) return dn
def execute(self, cn, **options): ca_enabled_check(self.api) ca_obj = self.api.Command.ca_show(cn)['result'] # ensure operator has permission to modify CAs if not self.api.Backend.ldap2.can_write(ca_obj['dn'], 'description'): raise errors.ACIError( info=_("Insufficient privilege to modify a CA.")) with self.api.Backend.ra_lightweight_ca as ca_api: self.perform_action(ca_api, ca_obj['ipacaid'][0]) return dict( result=True, value=pkey_to_value(cn, options), )
def execute(self, hostname, **kw): """ Execute the machine join operation. Returns the entry as it will be created in LDAP. :param hostname: The name of the host joined :param kw: Keyword arguments for the other attributes. """ assert 'cn' not in kw ldap = self.api.Backend.ldap2 host = None try: # First see if the host exists kw = {'fqdn': hostname, 'all': True} attrs_list = api.Command['host_show'](**kw)['result'] dn = attrs_list['dn'] # If no principal name is set yet we need to try to add # one. if 'krbprincipalname' not in attrs_list: service = "host/%s@%s" % (hostname, api.env.realm) api.Command['host_mod'](hostname, krbprincipalname=service) # It exists, can we write the password attributes? allowed = ldap.can_write(dn, 'krblastpwdchange') if not allowed: raise errors.ACIError(info=_( "Insufficient 'write' privilege to the 'krbLastPwdChange' attribute of entry '%s'." ) % dn) kw = {'fqdn': hostname, 'all': True} attrs_list = api.Command['host_show'](**kw)['result'] dn = attrs_list['dn'] except errors.NotFound: attrs_list = api.Command['host_add'](hostname, force=True)['result'] dn = attrs_list['dn'] config = api.Command['config_show']()['result'] attrs_list['ipacertificatesubjectbase'] = config[ 'ipacertificatesubjectbase'] return (dn, attrs_list)
def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): ca_enabled_check(self.api) # Once a profile id is set it cannot be changed if 'cn' in entry_attrs: raise errors.ProtectedEntryError(label='certprofile', key=keys[0], reason=_('Certificate profiles cannot be renamed')) if 'file' in options: # ensure operator has permission to update a certprofile if not ldap.can_write(dn, 'ipacertprofilestoreissued'): raise errors.ACIError(info=_( "Insufficient privilege to modify a certificate profile.")) with self.api.Backend.ra_certprofile as profile_api: profile_api.disable_profile(keys[0]) try: profile_api.update_profile(keys[0], options['file']) finally: profile_api.enable_profile(keys[0]) return dn
def execute(self, *keys, **options): # the server must be the local host if keys[-2] != api.env.host: raise errors.ValidationError(name='cn', error=_("must be \"%s\"") % api.env.host) # the server entry must exist try: self.obj.get_dn_if_exists(*keys[:-1]) except errors.NotFound: raise self.obj.handle_not_found(keys[-2]) # the user must have the Replication Administrators privilege privilege = u'Replication Administrators' if not principal_has_privilege(self.api, context.principal, privilege): raise errors.ACIError( info=_("not allowed to perform server connection check")) dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SystemBus() obj = bus.get_object('org.freeipa.server', '/', follow_name_owner_changes=True) server = dbus.Interface(obj, 'org.freeipa.server') ret, stdout, _stderr = server.conncheck(keys[-1]) result = dict( result=(ret == 0), value=keys[-2], ) for line in stdout.splitlines(): messages.add_message(options['version'], result, messages.ExternalCommandOutput(line=line)) return result
def pre_callback(self, ldap, dn, found, not_found, *keys, **options): """Remove users from linked groups """ user_uids = [ user_dn["uid"] for user_dn in found["memberuser"]["user"] ] if not user_uids: # no users found return dn # check that current user has write access to modify member user # attribute of the agreement. # Note: This will fail the entire operation, not just individual # removals. if not ldap.can_write(dn, "memberuser"): raise errors.ACIError( info=( "Insufficient 'write' privilege to the 'memberuser' " "attribute of entry '{}'." ).format(dn) ) # get group primary keys for agreement without loading all users group_obj = self.api.Object.group group_container_dn = DN(group_obj.container_dn, self.api.env.basedn) try: entry = ldap.get_entry(dn, ["member"]) except errors.NotFound: raise self.obj.handle_not_found(*keys) group_names = [ group_obj.get_primary_key_from_dn(m) for m in entry["member"] if m.endswith(group_container_dn) ] # remove users group groups for group_name in group_names: self.api.Command.group_remove_member(group_name, user=user_uids) return dn
def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): assert isinstance(dn, DN) if not self.obj.check_system(ldap, dn, *keys): raise errors.ACIError( info=_('A SYSTEM permission may not be modified')) # check if permission is in LDAP try: (dn, attrs) = ldap.get_entry(dn, attrs_list, normalize=self.obj.normalize_dn) except errors.NotFound: self.obj.handle_not_found(*keys) # when renaming permission, check if the target permission does not # exists already. Then, make changes to underlying ACI if 'rename' in options: if options['rename']: try: try: new_dn = EditableDN(dn) new_dn[0][ 'cn'] # assure the first RDN has cn as it's type except (IndexError, KeyError), e: raise ValueError( "expected dn starting with 'cn=' but got '%s'" % dn) new_dn[0].value = options['rename'] (new_dn, attrs) = ldap.get_entry(new_dn, attrs_list, normalize=self.obj.normalize_dn) raise errors.DuplicateEntry() except errors.NotFound: pass # permission may be renamed, continue
def execute(self, *keys, **options): """ This requires updating both the user and the group. We first need to verify that both the user and group can be updated, then we go about our work. We don't want a situation where only the user or group can be modified and we're left in a bad state. """ ldap = self.obj.backend group_dn = self.obj.get_dn(*keys, **options) user_dn = self.api.Object['user'].get_dn(*keys) try: user_attrs = ldap.get_entry(user_dn) except errors.NotFound: raise self.obj.handle_not_found(*keys) is_managed = self.obj.has_objectclass(user_attrs['objectclass'], 'mepmanagedentry') if (not ldap.can_write(user_dn, "objectclass") or not ldap.can_write(user_dn, "mepManagedEntry") and is_managed): raise errors.ACIError(info=_('not allowed to modify user entries')) group_attrs = ldap.get_entry(group_dn) is_managed = self.obj.has_objectclass(group_attrs['objectclass'], 'mepmanagedby') if (not ldap.can_write(group_dn, "objectclass") or not ldap.can_write(group_dn, "mepManagedBy") and is_managed): raise errors.ACIError( info=_('not allowed to modify group entries')) objectclasses = user_attrs['objectclass'] try: i = objectclasses.index('mepOriginEntry') del objectclasses[i] user_attrs['mepManagedEntry'] = None ldap.update_entry(user_attrs) except ValueError: # Somehow the user isn't managed, let it pass for now. We'll # let the group throw "Not managed". pass group_attrs = ldap.get_entry(group_dn) objectclasses = group_attrs['objectclass'] try: i = objectclasses.index('mepManagedEntry') except ValueError: # this should never happen raise errors.NotFound(reason=_('Not a managed group')) del objectclasses[i] # Make sure the resulting group has the default group objectclasses config = ldap.get_ipa_config() def_objectclass = config.get(self.obj.object_class_config, objectclasses) objectclasses = list(set(def_objectclass + objectclasses)) group_attrs['mepManagedBy'] = None group_attrs['objectclass'] = objectclasses ldap.update_entry(group_attrs) return dict( result=True, value=pkey_to_value(keys[0], options), )
def execute(self, csr, **kw): ca_enabled_check() ldap = self.api.Backend.ldap2 add = kw.get('add') request_type = kw.get('request_type') profile_id = kw.get('profile_id', self.Backend.ra.DEFAULT_PROFILE) ca = '.' # top-level CA hardcoded until subca plugin implemented """ Access control is partially handled by the ACI titled 'Hosts can modify service userCertificate'. This is for the case where a machine binds using a host/ prinicpal. It can only do the request if the target hostname is in the managedBy attribute which is managed using the add/del member commands. Binding with a user principal one needs to be in the request_certs taskgroup (directly or indirectly via role membership). """ principal_string = kw.get('principal') principal = split_any_principal(principal_string) servicename, principal_name, realm = principal if servicename is None: principal_type = USER elif servicename == 'host': principal_type = HOST else: principal_type = SERVICE bind_principal = split_any_principal(getattr(context, 'principal')) bind_service, bind_name, bind_realm = bind_principal if bind_service is None: bind_principal_type = USER elif bind_service == 'host': bind_principal_type = HOST else: bind_principal_type = SERVICE if bind_principal != principal and bind_principal_type != HOST: # Can the bound principal request certs for another principal? self.check_access() try: self.check_access("request certificate ignore caacl") bypass_caacl = True except errors.ACIError: bypass_caacl = False if not bypass_caacl: caacl_check(principal_type, principal_string, ca, profile_id) try: subject = pkcs10.get_subject(csr) extensions = pkcs10.get_extensions(csr) subjectaltname = pkcs10.get_subjectaltname(csr) or () except (NSPRError, PyAsn1Error, ValueError) as e: raise errors.CertificateOperationError( error=_("Failure decoding Certificate Signing Request: %s") % e) # self-service and host principals may bypass SAN permission check if bind_principal != principal and bind_principal_type != HOST: if '2.5.29.17' in extensions: self.check_access('request certificate with subjectaltname') dn = None principal_obj = None # See if the service exists and punt if it doesn't and we aren't # going to add it try: if principal_type == SERVICE: principal_obj = api.Command['service_show'](principal_string, all=True) elif principal_type == HOST: principal_obj = api.Command['host_show'](principal_name, all=True) elif principal_type == USER: principal_obj = api.Command['user_show'](principal_name, all=True) except errors.NotFound as e: if principal_type == SERVICE and add: principal_obj = api.Command['service_add'](principal_string, force=True) else: raise errors.NotFound( reason=_("The principal for this request doesn't exist.")) principal_obj = principal_obj['result'] dn = principal_obj['dn'] # Ensure that the DN in the CSR matches the principal cn = subject.common_name #pylint: disable=E1101 if not cn: raise errors.ValidationError( name='csr', error=_("No Common Name was found in subject of request.")) if principal_type in (SERVICE, HOST): if cn.lower() != principal_name.lower(): raise errors.ACIError(info=_( "hostname in subject of request '%(cn)s' " "does not match principal hostname '%(hostname)s'") % dict(cn=cn, hostname=principal_name)) elif principal_type == USER: # check user name if cn != principal_name: raise errors.ValidationError( name='csr', error=_("DN commonName does not match user's login")) # check email address mail = subject.email_address #pylint: disable=E1101 if mail is not None and mail not in principal_obj.get('mail', []): raise errors.ValidationError( name='csr', error=_("DN emailAddress does not match " "any of user's email addresses")) # We got this far so the principal entry exists, can we write it? if not ldap.can_write(dn, "usercertificate"): raise errors.ACIError( info=_("Insufficient 'write' privilege " "to the 'userCertificate' attribute of entry '%s'.") % dn) # Validate the subject alt name, if any for name_type, name in subjectaltname: if name_type == pkcs10.SAN_DNSNAME: name = unicode(name) alt_principal_obj = None alt_principal_string = None try: if principal_type == HOST: alt_principal_string = 'host/%s@%s' % (name, realm) alt_principal_obj = api.Command['host_show'](name, all=True) elif principal_type == SERVICE: alt_principal_string = '%s/%s@%s' % (servicename, name, realm) alt_principal_obj = api.Command['service_show']( alt_principal_string, all=True) elif principal_type == USER: raise errors.ValidationError( name='csr', error=_("subject alt name type %s is forbidden " "for user principals") % name_type) except errors.NotFound: # We don't want to issue any certificates referencing # machines we don't know about. Nothing is stored in this # host record related to this certificate. raise errors.NotFound(reason=_( 'The service principal for ' 'subject alt name %s in certificate request does not ' 'exist') % name) if alt_principal_obj is not None: altdn = alt_principal_obj['result']['dn'] if not ldap.can_write(altdn, "usercertificate"): raise errors.ACIError(info=_( "Insufficient privilege to create a certificate " "with subject alt name '%s'.") % name) if alt_principal_string is not None and not bypass_caacl: caacl_check(principal_type, alt_principal_string, ca, profile_id) elif name_type in (pkcs10.SAN_OTHERNAME_KRB5PRINCIPALNAME, pkcs10.SAN_OTHERNAME_UPN): if split_any_principal(name) != principal: raise errors.ACIError( info=_("Principal '%s' in subject alt name does not " "match requested principal") % name) elif name_type == pkcs10.SAN_RFC822NAME: if principal_type == USER: if name not in principal_obj.get('mail', []): raise errors.ValidationError( name='csr', error=_("RFC822Name does not match " "any of user's email addresses")) else: raise errors.ValidationError( name='csr', error=_("subject alt name type %s is forbidden " "for non-user principals") % name_type) else: raise errors.ACIError( info=_("Subject alt name type %s is forbidden") % name_type) # Request the certificate result = self.Backend.ra.request_certificate(csr, profile_id, request_type=request_type) cert = x509.load_certificate(result['certificate']) result['issuer'] = unicode(cert.issuer) result['valid_not_before'] = unicode(cert.valid_not_before_str) result['valid_not_after'] = unicode(cert.valid_not_after_str) result['md5_fingerprint'] = unicode( nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) result['sha1_fingerprint'] = unicode( nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) # Success? Then add it to the principal's entry # (unless the profile tells us not to) profile = api.Command['certprofile_show'](profile_id) store = profile['result']['ipacertprofilestoreissued'][0] == 'TRUE' if store and 'certificate' in result: cert = str(result.get('certificate')) kwargs = dict(addattr=u'usercertificate={}'.format(cert)) if principal_type == SERVICE: api.Command['service_mod'](principal_string, **kwargs) elif principal_type == HOST: api.Command['host_mod'](principal_name, **kwargs) elif principal_type == USER: api.Command['user_mod'](principal_name, **kwargs) return dict(result=result)
def execute(self, csr, all=False, raw=False, **kw): ca_enabled_check() ldap = self.api.Backend.ldap2 add = kw.get('add') request_type = kw.get('request_type') profile_id = kw.get('profile_id', self.Backend.ra.DEFAULT_PROFILE) # Check that requested authority exists (done before CA ACL # enforcement so that user gets better error message if # referencing nonexistant CA) and look up authority ID. # ca = kw['cacn'] ca_obj = api.Command.ca_show(ca)['result'] ca_id = ca_obj['ipacaid'][0] """ Access control is partially handled by the ACI titled 'Hosts can modify service userCertificate'. This is for the case where a machine binds using a host/ prinicpal. It can only do the request if the target hostname is in the managedBy attribute which is managed using the add/del member commands. Binding with a user principal one needs to be in the request_certs taskgroup (directly or indirectly via role membership). """ principal = kw.get('principal') principal_string = unicode(principal) if principal.is_user: principal_type = USER elif principal.is_host: principal_type = HOST else: principal_type = SERVICE bind_principal = kerberos.Principal(getattr(context, 'principal')) bind_principal_string = unicode(bind_principal) if bind_principal.is_user: bind_principal_type = USER elif bind_principal.is_host: bind_principal_type = HOST else: bind_principal_type = SERVICE if (bind_principal_string != principal_string and bind_principal_type != HOST): # Can the bound principal request certs for another principal? self.check_access() try: self.check_access("request certificate ignore caacl") bypass_caacl = True except errors.ACIError: bypass_caacl = False if not bypass_caacl: caacl_check(principal_type, principal, ca, profile_id) try: subject = pkcs10.get_subject(csr) extensions = pkcs10.get_extensions(csr) subjectaltname = pkcs10.get_subjectaltname(csr) or () except (NSPRError, PyAsn1Error, ValueError) as e: raise errors.CertificateOperationError( error=_("Failure decoding Certificate Signing Request: %s") % e) # self-service and host principals may bypass SAN permission check if (bind_principal_string != principal_string and bind_principal_type != HOST): if '2.5.29.17' in extensions: self.check_access('request certificate with subjectaltname') dn = None principal_obj = None # See if the service exists and punt if it doesn't and we aren't # going to add it try: if principal_type == SERVICE: principal_obj = api.Command['service_show'](principal_string, all=True) elif principal_type == HOST: principal_obj = api.Command['host_show'](principal.hostname, all=True) elif principal_type == USER: principal_obj = api.Command['user_show'](principal.username, all=True) except errors.NotFound as e: if add: if principal_type == SERVICE: principal_obj = api.Command['service_add']( principal_string, force=True) else: princtype_str = PRINCIPAL_TYPE_STRING_MAP[principal_type] raise errors.OperationNotSupportedForPrincipalType( operation=_("'add' option"), principal_type=princtype_str) else: raise errors.NotFound( reason=_("The principal for this request doesn't exist.")) principal_obj = principal_obj['result'] dn = principal_obj['dn'] # Ensure that the DN in the CSR matches the principal cn = subject.common_name #pylint: disable=E1101 if not cn: raise errors.ValidationError( name='csr', error=_("No Common Name was found in subject of request.")) if principal_type in (SERVICE, HOST): if cn.lower() != principal.hostname.lower(): raise errors.ACIError(info=_( "hostname in subject of request '%(cn)s' " "does not match principal hostname '%(hostname)s'") % dict(cn=cn, hostname=principal.hostname)) elif principal_type == USER: # check user name if cn != principal.username: raise errors.ValidationError( name='csr', error=_("DN commonName does not match user's login")) # check email address mail = subject.email_address #pylint: disable=E1101 if mail is not None and mail not in principal_obj.get('mail', []): raise errors.ValidationError( name='csr', error=_("DN emailAddress does not match " "any of user's email addresses")) # We got this far so the principal entry exists, can we write it? if not ldap.can_write(dn, "usercertificate"): raise errors.ACIError( info=_("Insufficient 'write' privilege " "to the 'userCertificate' attribute of entry '%s'.") % dn) # Validate the subject alt name, if any for name_type, desc, name, _der_name in subjectaltname: if name_type == nss.certDNSName: name = unicode(name) alt_principal = None alt_principal_obj = None try: if principal_type == HOST: alt_principal = kerberos.Principal((u'host', name), principal.realm) alt_principal_obj = api.Command['host_show'](name, all=True) elif principal_type == SERVICE: alt_principal = kerberos.Principal( (principal.service_name, name), principal.realm) alt_principal_obj = api.Command['service_show']( alt_principal, all=True) elif principal_type == USER: raise errors.ValidationError( name='csr', error=_("subject alt name type %s is forbidden " "for user principals") % desc) except errors.NotFound: # We don't want to issue any certificates referencing # machines we don't know about. Nothing is stored in this # host record related to this certificate. raise errors.NotFound(reason=_( 'The service principal for ' 'subject alt name %s in certificate request does not ' 'exist') % name) if alt_principal_obj is not None: altdn = alt_principal_obj['result']['dn'] if not ldap.can_write(altdn, "usercertificate"): raise errors.ACIError(info=_( "Insufficient privilege to create a certificate " "with subject alt name '%s'.") % name) if alt_principal is not None and not bypass_caacl: caacl_check(principal_type, alt_principal, ca, profile_id) elif name_type in [ (nss.certOtherName, x509.SAN_UPN), (nss.certOtherName, x509.SAN_KRB5PRINCIPALNAME), ]: if name != principal_string: raise errors.ACIError( info=_("Principal '%s' in subject alt name does not " "match requested principal") % name) elif name_type == nss.certRFC822Name: if principal_type == USER: if name not in principal_obj.get('mail', []): raise errors.ValidationError( name='csr', error=_("RFC822Name does not match " "any of user's email addresses")) else: raise errors.ValidationError( name='csr', error=_("subject alt name type %s is forbidden " "for non-user principals") % desc) else: raise errors.ACIError( info=_("Subject alt name type %s is forbidden") % desc) # Request the certificate try: result = self.Backend.ra.request_certificate( csr, profile_id, ca_id, request_type=request_type) except errors.HTTPRequestError as e: if e.status == 409: # pylint: disable=no-member raise errors.CertificateOperationError( error=_("CA '%s' is disabled") % ca) else: raise e if not raw: self.obj._parse(result, all) result['request_id'] = int(result['request_id']) result['cacn'] = ca_obj['cn'][0] # Success? Then add it to the principal's entry # (unless the profile tells us not to) profile = api.Command['certprofile_show'](profile_id) store = profile['result']['ipacertprofilestoreissued'][0] == 'TRUE' if store and 'certificate' in result: cert = str(result.get('certificate')) kwargs = dict(addattr=u'usercertificate={}'.format(cert)) if principal_type == SERVICE: api.Command['service_mod'](principal_string, **kwargs) elif principal_type == HOST: api.Command['host_mod'](principal.hostname, **kwargs) elif principal_type == USER: api.Command['user_mod'](principal.username, **kwargs) return dict( result=result, value=pkey_to_value(int(result['request_id']), kw), )
from ldap.controls import RequestControl as LDAPControl #pylint: disable=F0401 except ImportError: from ldap.controls import LDAPControl as LDAPControl #pylint: disable=F0401 import ldap as _ldap from ipaserver.ipaldap import IPAdmin from ipalib.session import krbccache_dir, krbccache_prefix from ipapython import dnsclient __doc__ = _(""" Classes to manage trust joins using DCE-RPC calls The code in this module relies heavily on samba4-python package and Samba4 python bindings. """) access_denied_error = errors.ACIError( info=_('CIFS server denied your credentials')) dcerpc_error_codes = { -1073741823: errors.RemoteRetrieveError( reason=_('communication with CIFS server was unsuccessful')), -1073741790: access_denied_error, -1073741715: access_denied_error, -1073741614: access_denied_error, -1073741603: errors.ValidationError(name=_('AD domain controller'), error=_('unsupported functional level')), }
def promote_check(installer): options = installer installer._enrollment_performed = False installer._top_dir = tempfile.mkdtemp("ipa") # check selinux status, http and DS ports, NTP conflicting services common_check(options.no_ntp) client_fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE) if not client_fstore.has_files(): ensure_enrolled(installer) else: if (options.domain_name or options.server or options.realm_name or options.host_name or options.password or options.keytab): print("IPA client is already configured on this system, ignoring " "the --domain, --server, --realm, --hostname, --password " "and --keytab options.") # The NTP configuration can not be touched on pre-installed client: if options.no_ntp or options.ntp_servers or options.ntp_pool: raise ScriptError( "NTP configuration cannot be updated during promotion") sstore = sysrestore.StateFile(paths.SYSRESTORE) fstore = sysrestore.FileStore(paths.SYSRESTORE) env = Env() env._bootstrap(context='installer', confdir=paths.ETC_IPA, log=None) env._finalize_core(**dict(constants.DEFAULT_CONFIG)) # pylint: disable=no-member xmlrpc_uri = 'https://{}/ipa/xml'.format(ipautil.format_netloc(env.host)) api.bootstrap(in_server=True, context='installer', confdir=paths.ETC_IPA, ldap_uri=installutils.realm_to_ldapi_uri(env.realm), xmlrpc_uri=xmlrpc_uri) # pylint: enable=no-member api.finalize() config = ReplicaConfig() config.realm_name = api.env.realm config.host_name = api.env.host config.domain_name = api.env.domain config.master_host_name = api.env.server config.ca_host_name = api.env.ca_host config.kra_host_name = config.ca_host_name config.ca_ds_port = 389 config.setup_ca = options.setup_ca config.setup_kra = options.setup_kra config.dir = installer._top_dir config.basedn = api.env.basedn http_pkcs12_file = None http_pkcs12_info = None http_ca_cert = None dirsrv_pkcs12_file = None dirsrv_pkcs12_info = None dirsrv_ca_cert = None pkinit_pkcs12_file = None pkinit_pkcs12_info = None pkinit_ca_cert = None if options.http_cert_files: if options.http_pin is None: options.http_pin = installutils.read_password( "Enter Apache Server private key unlock", confirm=False, validate=False, retry=False) if options.http_pin is None: raise ScriptError( "Apache Server private key unlock password required") http_pkcs12_file, http_pin, http_ca_cert = load_pkcs12( cert_files=options.http_cert_files, key_password=options.http_pin, key_nickname=options.http_cert_name, ca_cert_files=options.ca_cert_files, host_name=config.host_name) http_pkcs12_info = (http_pkcs12_file.name, http_pin) if options.dirsrv_cert_files: if options.dirsrv_pin is None: options.dirsrv_pin = installutils.read_password( "Enter Directory Server private key unlock", confirm=False, validate=False, retry=False) if options.dirsrv_pin is None: raise ScriptError( "Directory Server private key unlock password required") dirsrv_pkcs12_file, dirsrv_pin, dirsrv_ca_cert = load_pkcs12( cert_files=options.dirsrv_cert_files, key_password=options.dirsrv_pin, key_nickname=options.dirsrv_cert_name, ca_cert_files=options.ca_cert_files, host_name=config.host_name) dirsrv_pkcs12_info = (dirsrv_pkcs12_file.name, dirsrv_pin) if options.pkinit_cert_files: if options.pkinit_pin is None: options.pkinit_pin = installutils.read_password( "Enter Kerberos KDC private key unlock", confirm=False, validate=False, retry=False) if options.pkinit_pin is None: raise ScriptError( "Kerberos KDC private key unlock password required") pkinit_pkcs12_file, pkinit_pin, pkinit_ca_cert = load_pkcs12( cert_files=options.pkinit_cert_files, key_password=options.pkinit_pin, key_nickname=options.pkinit_cert_name, ca_cert_files=options.ca_cert_files, realm_name=config.realm_name) pkinit_pkcs12_info = (pkinit_pkcs12_file.name, pkinit_pin) if (options.http_cert_files and options.dirsrv_cert_files and http_ca_cert != dirsrv_ca_cert): raise RuntimeError("Apache Server SSL certificate and Directory " "Server SSL certificate are not signed by the same" " CA certificate") if (options.http_cert_files and options.pkinit_cert_files and http_ca_cert != pkinit_ca_cert): raise RuntimeError("Apache Server SSL certificate and PKINIT KDC " "certificate are not signed by the same CA " "certificate") installutils.verify_fqdn(config.host_name, options.no_host_dns) installutils.verify_fqdn(config.master_host_name, options.no_host_dns) ccache = os.environ['KRB5CCNAME'] kinit_keytab('host/{env.host}@{env.realm}'.format(env=api.env), paths.KRB5_KEYTAB, ccache) cafile = paths.IPA_CA_CRT if not os.path.isfile(cafile): raise RuntimeError("CA cert file is not available! Please reinstall" "the client and try again.") ldapuri = 'ldaps://%s' % ipautil.format_netloc(config.master_host_name) xmlrpc_uri = 'https://{}/ipa/xml'.format( ipautil.format_netloc(config.master_host_name)) remote_api = create_api(mode=None) remote_api.bootstrap(in_server=True, context='installer', confdir=paths.ETC_IPA, ldap_uri=ldapuri, xmlrpc_uri=xmlrpc_uri) remote_api.finalize() installer._remote_api = remote_api with rpc_client(remote_api) as client: check_remote_version(client, parse_version(api.env.version)) check_remote_fips_mode(client, api.env.fips_mode) conn = remote_api.Backend.ldap2 replman = None try: # Try out authentication conn.connect(ccache=ccache) replman = ReplicationManager(config.realm_name, config.master_host_name, None) promotion_check_ipa_domain(conn, remote_api.env.basedn) # Make sure that domain fulfills minimal domain level # requirement domain_level = current_domain_level(remote_api) check_domain_level_is_supported(domain_level) if domain_level < constants.MIN_DOMAIN_LEVEL: raise RuntimeError( "Cannot promote this client to a replica. The domain level " "must be raised to {mindomainlevel} before the replica can be " "installed".format(mindomainlevel=constants.MIN_DOMAIN_LEVEL)) # Check authorization result = remote_api.Command['hostgroup_find']( cn=u'ipaservers', host=[unicode(api.env.host)])['result'] add_to_ipaservers = not result if add_to_ipaservers: if options.password and not options.admin_password: raise errors.ACIError(info="Not authorized") if installer._ccache is None: del os.environ['KRB5CCNAME'] else: os.environ['KRB5CCNAME'] = installer._ccache try: installutils.check_creds(options, config.realm_name) installer._ccache = os.environ.get('KRB5CCNAME') finally: os.environ['KRB5CCNAME'] = ccache conn.disconnect() conn.connect(ccache=installer._ccache) try: result = remote_api.Command['hostgroup_show']( u'ipaservers', all=True, rights=True)['result'] if 'w' not in result['attributelevelrights']['member']: raise errors.ACIError(info="Not authorized") finally: conn.disconnect() conn.connect(ccache=ccache) # Check that we don't already have a replication agreement if replman.get_replication_agreement(config.host_name): msg = ("A replication agreement for this host already exists. " "It needs to be removed.\n" "Run this command:\n" " %% ipa-replica-manage del {host} --force".format( host=config.host_name)) raise ScriptError(msg, rval=3) # Detect if the other master can handle replication managers # cn=replication managers,cn=sysaccounts,cn=etc,$SUFFIX dn = DN(('cn', 'replication managers'), ('cn', 'sysaccounts'), ('cn', 'etc'), ipautil.realm_to_suffix(config.realm_name)) try: conn.get_entry(dn) except errors.NotFound: msg = ("The Replication Managers group is not available in " "the domain. Replica promotion requires the use of " "Replication Managers to be able to replicate data. " "Upgrade the peer master or use the ipa-replica-prepare " "command on the master and use a prep file to install " "this replica.") logger.error("%s", msg) raise ScriptError(rval=3) dns_masters = remote_api.Object['dnsrecord'].get_dns_masters() if dns_masters: if not options.no_host_dns: logger.debug('Check forward/reverse DNS resolution') resolution_ok = ( check_dns_resolution(config.master_host_name, dns_masters) and check_dns_resolution(config.host_name, dns_masters)) if not resolution_ok and installer.interactive: if not ipautil.user_input("Continue?", False): raise ScriptError(rval=0) else: logger.debug('No IPA DNS servers, ' 'skipping forward/reverse resolution check') entry_attrs = conn.get_ipa_config() subject_base = entry_attrs.get('ipacertificatesubjectbase', [None])[0] if subject_base is not None: config.subject_base = DN(subject_base) # Find if any server has a CA ca_host = service.find_providing_server('CA', conn, config.ca_host_name) if ca_host is not None: config.ca_host_name = ca_host ca_enabled = True if options.dirsrv_cert_files: logger.error("Certificates could not be provided when " "CA is present on some master.") raise ScriptError(rval=3) else: if options.setup_ca: logger.error("The remote master does not have a CA " "installed, can't set up CA") raise ScriptError(rval=3) ca_enabled = False if not options.dirsrv_cert_files: logger.error("Cannot issue certificates: a CA is not " "installed. Use the --http-cert-file, " "--dirsrv-cert-file options to provide " "custom certificates.") raise ScriptError(rval=3) kra_host = service.find_providing_server('KRA', conn, config.kra_host_name) if kra_host is not None: config.kra_host_name = kra_host kra_enabled = True else: if options.setup_kra: logger.error("There is no KRA server in the domain, " "can't setup a KRA clone") raise ScriptError(rval=3) kra_enabled = False if ca_enabled: options.realm_name = config.realm_name options.host_name = config.host_name ca.install_check(False, config, options) if kra_enabled: try: kra.install_check(remote_api, config, options) except RuntimeError as e: raise ScriptError(e) if options.setup_dns: dns.install_check(False, remote_api, True, options, config.host_name) config.ips = dns.ip_addresses else: config.ips = installutils.get_server_ip_address( config.host_name, not installer.interactive, False, options.ip_addresses) # check addresses here, dns module is doing own check no_matching_interface_for_ip_address_warning(config.ips) if options.setup_adtrust: adtrust.install_check(False, options, remote_api) except errors.ACIError: logger.debug("%s", traceback.format_exc()) raise ScriptError("\nInsufficient privileges to promote the server." "\nPossible issues:" "\n- A user has insufficient privileges" "\n- This client has insufficient privileges " "to become an IPA replica") except errors.LDAPError: logger.debug("%s", traceback.format_exc()) raise ScriptError("\nUnable to connect to LDAP server %s" % config.master_host_name) finally: if replman and replman.conn: replman.conn.unbind() if conn.isconnected(): conn.disconnect() # check connection if not options.skip_conncheck: if add_to_ipaservers: # use user's credentials when the server host is not ipaservers if installer._ccache is None: del os.environ['KRB5CCNAME'] else: os.environ['KRB5CCNAME'] = installer._ccache try: replica_conn_check(config.master_host_name, config.host_name, config.realm_name, options.setup_ca, 389, options.admin_password, principal=options.principal, ca_cert_file=cafile) finally: if add_to_ipaservers: os.environ['KRB5CCNAME'] = ccache installer._ca_enabled = ca_enabled installer._kra_enabled = kra_enabled installer._ca_file = cafile installer._fstore = fstore installer._sstore = sstore installer._config = config installer._add_to_ipaservers = add_to_ipaservers installer._dirsrv_pkcs12_file = dirsrv_pkcs12_file installer._dirsrv_pkcs12_info = dirsrv_pkcs12_info installer._http_pkcs12_file = http_pkcs12_file installer._http_pkcs12_info = http_pkcs12_info installer._pkinit_pkcs12_file = pkinit_pkcs12_file installer._pkinit_pkcs12_info = pkinit_pkcs12_info
def execute(self, csr, all=False, raw=False, **kw): ca_enabled_check() ldap = self.api.Backend.ldap2 add = kw.get('add') request_type = kw.get('request_type') profile_id = kw.get('profile_id', self.Backend.ra.DEFAULT_PROFILE) # Check that requested authority exists (done before CA ACL # enforcement so that user gets better error message if # referencing nonexistant CA) and look up authority ID. # ca = kw['cacn'] ca_obj = api.Command.ca_show(ca)['result'] ca_id = ca_obj['ipacaid'][0] """ Access control is partially handled by the ACI titled 'Hosts can modify service userCertificate'. This is for the case where a machine binds using a host/ prinicpal. It can only do the request if the target hostname is in the managedBy attribute which is managed using the add/del member commands. Binding with a user principal one needs to be in the request_certs taskgroup (directly or indirectly via role membership). """ principal = kw.get('principal') principal_string = unicode(principal) if principal.is_user: principal_type = USER elif principal.is_host: principal_type = HOST else: principal_type = SERVICE bind_principal = kerberos.Principal(getattr(context, 'principal')) bind_principal_string = unicode(bind_principal) if bind_principal.is_user: bind_principal_type = USER elif bind_principal.is_host: bind_principal_type = HOST else: bind_principal_type = SERVICE if (bind_principal_string != principal_string and bind_principal_type != HOST): # Can the bound principal request certs for another principal? self.check_access() try: self.check_access("request certificate ignore caacl") bypass_caacl = True except errors.ACIError: bypass_caacl = False if not bypass_caacl: caacl_check(principal_type, principal, ca, profile_id) try: csr_obj = pkcs10.load_certificate_request(csr) except ValueError as e: raise errors.CertificateOperationError( error=_("Failure decoding Certificate Signing Request: %s") % e) try: ext_san = csr_obj.extensions.get_extension_for_oid( cryptography.x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME) except cryptography.x509.extensions.ExtensionNotFound: ext_san = None # self-service and host principals may bypass SAN permission check if (bind_principal_string != principal_string and bind_principal_type != HOST): if ext_san is not None: self.check_access('request certificate with subjectaltname') dn = None principal_obj = None # See if the service exists and punt if it doesn't and we aren't # going to add it try: if principal_type == SERVICE: principal_obj = api.Command['service_show'](principal_string, all=True) elif principal_type == HOST: principal_obj = api.Command['host_show'](principal.hostname, all=True) elif principal_type == USER: principal_obj = api.Command['user_show'](principal.username, all=True) except errors.NotFound as e: if add: if principal_type == SERVICE: principal_obj = api.Command['service_add']( principal_string, force=True) else: princtype_str = PRINCIPAL_TYPE_STRING_MAP[principal_type] raise errors.OperationNotSupportedForPrincipalType( operation=_("'add' option"), principal_type=princtype_str) else: raise errors.NotFound( reason=_("The principal for this request doesn't exist.")) principal_obj = principal_obj['result'] dn = principal_obj['dn'] # Ensure that the DN in the CSR matches the principal # # We only look at the "most specific" CN value cns = csr_obj.subject.get_attributes_for_oid( cryptography.x509.oid.NameOID.COMMON_NAME) if len(cns) == 0: raise errors.ValidationError( name='csr', error=_("No Common Name was found in subject of request.")) cn = cns[-1].value # "most specific" is end of list if principal_type in (SERVICE, HOST): if cn.lower() != principal.hostname.lower(): raise errors.ACIError(info=_( "hostname in subject of request '%(cn)s' " "does not match principal hostname '%(hostname)s'") % dict(cn=cn, hostname=principal.hostname)) elif principal_type == USER: # check user name if cn != principal.username: raise errors.ValidationError( name='csr', error=_("DN commonName does not match user's login")) # check email address # # fail if any email addr from DN does not appear in ldap entry email_addrs = csr_obj.subject.get_attributes_for_oid( cryptography.x509.oid.NameOID.EMAIL_ADDRESS) if len(set(email_addrs) - set(principal_obj.get('mail', []))) > 0: raise errors.ValidationError( name='csr', error=_("DN emailAddress does not match " "any of user's email addresses")) # We got this far so the principal entry exists, can we write it? if not ldap.can_write(dn, "usercertificate"): raise errors.ACIError( info=_("Insufficient 'write' privilege " "to the 'userCertificate' attribute of entry '%s'.") % dn) # Validate the subject alt name, if any generalnames = [] if ext_san is not None: generalnames = x509.process_othernames(ext_san.value) for gn in generalnames: if isinstance(gn, cryptography.x509.general_name.DNSName): name = gn.value alt_principal = None alt_principal_obj = None try: if principal_type == HOST: alt_principal = kerberos.Principal((u'host', name), principal.realm) alt_principal_obj = api.Command['host_show'](name, all=True) elif principal_type == SERVICE: alt_principal = kerberos.Principal( (principal.service_name, name), principal.realm) alt_principal_obj = api.Command['service_show']( alt_principal, all=True) elif principal_type == USER: raise errors.ValidationError( name='csr', error=_("subject alt name type %s is forbidden " "for user principals") % "DNSName") except errors.NotFound: # We don't want to issue any certificates referencing # machines we don't know about. Nothing is stored in this # host record related to this certificate. raise errors.NotFound(reason=_( 'The service principal for ' 'subject alt name %s in certificate request does not ' 'exist') % name) if alt_principal_obj is not None: altdn = alt_principal_obj['result']['dn'] if not ldap.can_write(altdn, "usercertificate"): raise errors.ACIError(info=_( "Insufficient privilege to create a certificate " "with subject alt name '%s'.") % name) if alt_principal is not None and not bypass_caacl: caacl_check(principal_type, alt_principal, ca, profile_id) elif isinstance(gn, (x509.KRB5PrincipalName, x509.UPN)): if gn.name != principal_string: raise errors.ACIError( info=_("Principal '%s' in subject alt name does not " "match requested principal") % gn.name) elif isinstance(gn, cryptography.x509.general_name.RFC822Name): if principal_type == USER: if gn.value not in principal_obj.get('mail', []): raise errors.ValidationError( name='csr', error=_("RFC822Name does not match " "any of user's email addresses")) else: raise errors.ValidationError( name='csr', error=_("subject alt name type %s is forbidden " "for non-user principals") % "RFC822Name") else: raise errors.ACIError( info=_("Subject alt name type %s is forbidden") % type(gn).__name__) # Request the certificate try: # re-serialise to PEM, in case the user-supplied data has # extraneous material that will cause Dogtag to freak out csr_pem = csr_obj.public_bytes(serialization.Encoding.PEM) result = self.Backend.ra.request_certificate( csr_pem, profile_id, ca_id, request_type=request_type) except errors.HTTPRequestError as e: if e.status == 409: # pylint: disable=no-member raise errors.CertificateOperationError( error=_("CA '%s' is disabled") % ca) else: raise e if not raw: self.obj._parse(result, all) result['request_id'] = int(result['request_id']) result['cacn'] = ca_obj['cn'][0] # Success? Then add it to the principal's entry # (unless the profile tells us not to) profile = api.Command['certprofile_show'](profile_id) store = profile['result']['ipacertprofilestoreissued'][0] == 'TRUE' if store and 'certificate' in result: cert = str(result.get('certificate')) kwargs = dict(addattr=u'usercertificate={}'.format(cert)) if principal_type == SERVICE: api.Command['service_mod'](principal_string, **kwargs) elif principal_type == HOST: api.Command['host_mod'](principal.hostname, **kwargs) elif principal_type == USER: api.Command['user_mod'](principal.username, **kwargs) return dict( result=result, value=pkey_to_value(int(result['request_id']), kw), )