class permission_add_noaci(LDAPCreate): __doc__ = _('Add a system permission without an ACI') msg_summary = _('Added permission "%(value)s"') has_output_params = LDAPCreate.has_output_params + output_params NO_CLI = True takes_options = (StrEnum( 'permissiontype?', label=_('Permission type'), values=(u'SYSTEM', ), ), ) def get_args(self): # do not validate system permission names yield self.obj.primary_key.clone(pattern=None, pattern_errmsg=None) def get_options(self): for option in super(permission_add_noaci, self).get_options(): # filter out ACI options if option.name in self.obj.aci_attributes: continue yield option def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): assert isinstance(dn, DN) permission_type = options.get('permissiontype') if permission_type: entry_attrs['ipapermissiontype'] = [permission_type] return dn
class sudorule(LDAPObject): """ Sudo Rule object. """ container_dn = api.env.container_sudorule object_name = _('sudo rule') object_name_plural = _('sudo rules') object_class = ['ipaassociation', 'ipasudorule'] permission_filter_objectclasses = ['ipasudorule'] default_attributes = [ 'cn', 'ipaenabledflag', 'externaluser', 'description', 'usercategory', 'hostcategory', 'cmdcategory', 'memberuser', 'memberhost', 'memberallowcmd', 'memberdenycmd', 'ipasudoopt', 'ipasudorunas', 'ipasudorunasgroup', 'ipasudorunasusercategory', 'ipasudorunasgroupcategory', 'sudoorder', 'hostmask', 'externalhost', 'ipasudorunasextusergroup', 'ipasudorunasextgroup', 'ipasudorunasextuser' ] uuid_attribute = 'ipauniqueid' rdn_attribute = 'ipauniqueid' allow_rename = True attribute_members = { 'memberuser': ['user', 'group'], 'memberhost': ['host', 'hostgroup'], 'memberallowcmd': ['sudocmd', 'sudocmdgroup'], 'memberdenycmd': ['sudocmd', 'sudocmdgroup'], 'ipasudorunas': ['user', 'group'], 'ipasudorunasgroup': ['group'], } managed_permissions = { 'System: Read Sudo Rules': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cmdcategory', 'cn', 'description', 'externalhost', 'externaluser', 'hostcategory', 'hostmask', 'ipaenabledflag', 'ipasudoopt', 'ipasudorunas', 'ipasudorunasextgroup', 'ipasudorunasextuser', 'ipasudorunasextusergroup', 'ipasudorunasgroup', 'ipasudorunasgroupcategory', 'ipasudorunasusercategory', 'ipauniqueid', 'memberallowcmd', 'memberdenycmd', 'memberhost', 'memberuser', 'sudonotafter', 'sudonotbefore', 'sudoorder', 'usercategory', 'objectclass', 'member', }, }, 'System: Read Sudoers compat tree': { 'non_object': True, 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN('ou=sudoers', api.env.basedn), 'ipapermbindruletype': 'anonymous', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'cn', 'ou', 'sudouser', 'sudohost', 'sudocommand', 'sudorunas', 'sudorunasuser', 'sudorunasgroup', 'sudooption', 'sudonotbefore', 'sudonotafter', 'sudoorder', 'description', }, }, 'System: Add Sudo rule': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=sudorules,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Add Sudo rule";allow (add) groupdn = "ldap:///cn=Add Sudo rule,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Sudo Administrator'}, }, 'System: Delete Sudo rule': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=sudorules,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Delete Sudo rule";allow (delete) groupdn = "ldap:///cn=Delete Sudo rule,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Sudo Administrator'}, }, 'System: Modify Sudo rule': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'description', 'ipaenabledflag', 'usercategory', 'hostcategory', 'cmdcategory', 'ipasudorunasusercategory', 'ipasudorunasgroupcategory', 'externaluser', 'ipasudorunasextusergroup', 'ipasudorunasextuser', 'ipasudorunasextgroup', 'memberdenycmd', 'memberallowcmd', 'memberuser', 'memberhost', 'externalhost', 'sudonotafter', 'hostmask', 'sudoorder', 'sudonotbefore', 'ipasudorunas', 'externalhost', 'ipasudorunasgroup', 'ipasudoopt', 'memberhost', }, 'replaces': [ '(targetattr = "description || ipaenabledflag || usercategory || hostcategory || cmdcategory || ipasudorunasusercategory || ipasudorunasgroupcategory || externaluser || ipasudorunasextuser || ipasudorunasextgroup || memberdenycmd || memberallowcmd || memberuser")(target = "ldap:///ipauniqueid=*,cn=sudorules,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Modify Sudo rule";allow (write) groupdn = "ldap:///cn=Modify Sudo rule,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Sudo Administrator'}, }, } label = _('Sudo Rules') label_singular = _('Sudo Rule') takes_params = ( Str( 'cn', cli_name='sudorule_name', label=_('Rule name'), primary_key=True, ), Str( 'description?', cli_name='desc', label=_('Description'), ), Bool( 'ipaenabledflag?', label=_('Enabled'), flags=['no_option'], ), StrEnum( 'usercategory?', cli_name='usercat', label=_('User category'), doc=_('User category the rule applies to'), values=(u'all', ), ), StrEnum( 'hostcategory?', cli_name='hostcat', label=_('Host category'), doc=_('Host category the rule applies to'), values=(u'all', ), ), StrEnum( 'cmdcategory?', cli_name='cmdcat', label=_('Command category'), doc=_('Command category the rule applies to'), values=(u'all', ), ), StrEnum( 'ipasudorunasusercategory?', cli_name='runasusercat', label=_('RunAs User category'), doc=_('RunAs User category the rule applies to'), values=(u'all', ), ), StrEnum( 'ipasudorunasgroupcategory?', cli_name='runasgroupcat', label=_('RunAs Group category'), doc=_('RunAs Group category the rule applies to'), values=(u'all', ), ), Int( 'sudoorder?', cli_name='order', label=_('Sudo order'), doc=_('integer to order the Sudo rules'), default=0, minvalue=0, ), Str( 'memberuser_user?', label=_('Users'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberuser_group?', label=_('User Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'externaluser?', validate_externaluser, cli_name='externaluser', label=_('External User'), doc=_('External User the rule applies to (sudorule-find only)'), ), Str( 'memberhost_host?', label=_('Hosts'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberhost_hostgroup?', label=_('Host Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'hostmask', validate_hostmask, normalizer=lambda x: unicode(netaddr.IPNetwork(x).cidr), label=_('Host Masks'), flags=['no_create', 'no_update', 'no_search'], multivalue=True, ), external_host_param, Str( 'memberallowcmd_sudocmd?', label=_('Sudo Allow Commands'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberdenycmd_sudocmd?', label=_('Sudo Deny Commands'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberallowcmd_sudocmdgroup?', label=_('Sudo Allow Command Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberdenycmd_sudocmdgroup?', label=_('Sudo Deny Command Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'ipasudorunas_user?', label=_('RunAs Users'), doc=_('Run as a user'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'ipasudorunas_group?', label=_('Groups of RunAs Users'), doc=_('Run as any user within a specified group'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'ipasudorunasextuser?', validate_runasextuser, cli_name='runasexternaluser', label=_('RunAs External User'), doc=_( 'External User the commands can run as (sudorule-find only)'), ), Str( 'ipasudorunasextusergroup?', cli_name='runasexternalusergroup', label=_('External Groups of RunAs Users'), doc=_('External Groups of users that the command can run as'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'ipasudorunasgroup_group?', label=_('RunAs Groups'), doc=_('Run with the gid of a specified POSIX group'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'ipasudorunasextgroup?', validate_runasextgroup, cli_name='runasexternalgroup', label=_('RunAs External Group'), doc=_( 'External Group the commands can run as (sudorule-find only)'), ), Str( 'ipasudoopt*', label=_('Sudo Option'), flags=['no_create', 'no_update', 'no_search'], ), ) order_not_unique_msg = _( 'order must be a unique value (%(order)d already used by %(rule)s)') def check_order_uniqueness(self, *keys, **options): if options.get('sudoorder') is not None: entries = self.methods.find( sudoorder=options['sudoorder'])['result'] if len(entries) > 0: rule_name = entries[0]['cn'][0] raise errors.ValidationError(name='order', error=self.order_not_unique_msg % { 'order': options['sudoorder'], 'rule': rule_name, })
class topologysegment(LDAPObject): """ Topology segment. """ parent_object = 'topologysuffix' container_dn = api.env.container_topology object_name = _('segment') object_name_plural = _('segments') object_class = ['iparepltoposegment'] default_attributes = [ 'cn', 'ipaReplTopoSegmentdirection', 'ipaReplTopoSegmentrightNode', 'ipaReplTopoSegmentLeftNode', 'nsds5replicastripattrs', 'nsds5replicatedattributelist', 'nsds5replicatedattributelisttotal', 'nsds5replicatimeout', 'nsds5replicaenabled' ] search_display_attributes = [ 'cn', 'ipaReplTopoSegmentdirection', 'ipaReplTopoSegmentrightNode', 'ipaReplTopoSegmentLeftNode' ] label = _('Topology Segments') label_singular = _('Topology Segment') takes_params = ( Str( 'cn', maxlength=255, cli_name='name', primary_key=True, label=_('Segment name'), default_from=lambda iparepltoposegmentleftnode, iparepltoposegmentrightnode: '%s-to-%s' % (iparepltoposegmentleftnode, iparepltoposegmentrightnode), normalizer=lambda value: value.lower(), doc=_('Arbitrary string identifying the segment'), ), Str( 'iparepltoposegmentleftnode', pattern='^[a-zA-Z0-9.][a-zA-Z0-9.-]*[a-zA-Z0-9.$-]?$', pattern_errmsg='may only include letters, numbers, -, . and $', maxlength=255, cli_name='leftnode', label=_('Left node'), normalizer=lambda value: value.lower(), doc=_('Left replication node - an IPA server'), flags={'no_update'}, ), Str( 'iparepltoposegmentrightnode', pattern='^[a-zA-Z0-9.][a-zA-Z0-9.-]*[a-zA-Z0-9.$-]?$', pattern_errmsg='may only include letters, numbers, -, . and $', maxlength=255, cli_name='rightnode', label=_('Right node'), normalizer=lambda value: value.lower(), doc=_('Right replication node - an IPA server'), flags={'no_update'}, ), StrEnum( 'iparepltoposegmentdirection', cli_name='direction', label=_('Connectivity'), values=(u'both', u'left-right', u'right-left'), default=u'both', autofill=True, doc=_( 'Direction of replication between left and right replication ' 'node'), flags={'no_option', 'no_update'}, ), Str('nsds5replicastripattrs?', cli_name='stripattrs', label=_('Attributes to strip'), normalizer=lambda value: value.lower(), doc=_( 'A space separated list of attributes which are removed from ' 'replication updates.')), Str( 'nsds5replicatedattributelist?', cli_name='replattrs', label='Attributes to replicate', doc=_('Attributes that are not replicated to a consumer server ' 'during a fractional update. E.g., `(objectclass=*) ' '$ EXCLUDE accountlockout memberof'), ), Str( 'nsds5replicatedattributelisttotal?', cli_name='replattrstotal', label=_('Attributes for total update'), doc=_('Attributes that are not replicated to a consumer server ' 'during a total update. E.g. (objectclass=*) $ EXCLUDE ' 'accountlockout'), ), Int( 'nsds5replicatimeout?', cli_name='timeout', label=_('Session timeout'), minvalue=0, doc=_('Number of seconds outbound LDAP operations waits for a ' 'response from the remote replica before timing out and ' 'failing'), ), StrEnum( 'nsds5replicaenabled?', cli_name='enabled', label=_('Replication agreement enabled'), doc=_('Whether a replication agreement is active, meaning whether ' 'replication is occurring per that agreement'), values=(u'on', u'off'), flags={'no_option'}, ), ) def validate_nodes(self, ldap, dn, entry_attrs, suffix): leftnode = entry_attrs.get('iparepltoposegmentleftnode') rightnode = entry_attrs.get('iparepltoposegmentrightnode') if not leftnode and not rightnode: return # nothing to check # check if nodes are IPA servers masters = self.api.Command.server_find('', sizelimit=0, no_members=False)['result'] m_hostnames = [master['cn'][0].lower() for master in masters] if leftnode and leftnode not in m_hostnames: raise errors.ValidationError( name='leftnode', error=_('left node is not a topology node: %(leftnode)s') % dict(leftnode=leftnode)) if rightnode and rightnode not in m_hostnames: raise errors.ValidationError( name='rightnode', error=_('right node is not a topology node: %(rightnode)s') % dict(rightnode=rightnode)) # prevent creation of reflexive relation key = 'leftnode' if not leftnode or not rightnode: # get missing end _entry_attrs = ldap.get_entry(dn, ['*']) if not leftnode: key = 'rightnode' leftnode = _entry_attrs['iparepltoposegmentleftnode'][0] else: rightnode = _entry_attrs['iparepltoposegmentrightnode'][0] if leftnode == rightnode: raise errors.ValidationError( name=key, error=_('left node and right node must not be the same')) # don't allow segment between nodes where both don't have the suffix masters_to_suffix = map_masters_to_suffixes(masters) suffix_masters = masters_to_suffix.get(suffix, []) suffix_m_hostnames = [m['cn'][0].lower() for m in suffix_masters] if leftnode not in suffix_m_hostnames: raise errors.ValidationError( name='leftnode', error=_("left node ({host}) does not support " "suffix '{suff}'").format(host=leftnode, suff=suffix)) if rightnode not in suffix_m_hostnames: raise errors.ValidationError( name='rightnode', error=_("right node ({host}) does not support " "suffix '{suff}'").format(host=rightnode, suff=suffix))
class config(LDAPObject): """ IPA configuration object """ object_name = _('configuration options') default_attributes = [ 'ipamaxusernamelength', 'ipahomesrootdir', 'ipadefaultloginshell', 'ipadefaultprimarygroup', 'ipadefaultemaildomain', 'ipasearchtimelimit', 'ipasearchrecordslimit', 'ipausersearchfields', 'ipagroupsearchfields', 'ipamigrationenabled', 'ipacertificatesubjectbase', 'ipapwdexpadvnotify', 'ipaselinuxusermaporder', 'ipaselinuxusermapdefault', 'ipaconfigstring', 'ipakrbauthzdata', 'ipauserauthtype' ] container_dn = DN(('cn', 'ipaconfig'), ('cn', 'etc')) permission_filter_objectclasses = ['ipaguiconfig'] managed_permissions = { 'System: Read Global Configuration': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cn', 'objectclass', 'ipacertificatesubjectbase', 'ipaconfigstring', 'ipadefaultemaildomain', 'ipadefaultloginshell', 'ipadefaultprimarygroup', 'ipagroupobjectclasses', 'ipagroupsearchfields', 'ipahomesrootdir', 'ipakrbauthzdata', 'ipamaxusernamelength', 'ipamigrationenabled', 'ipapwdexpadvnotify', 'ipaselinuxusermapdefault', 'ipaselinuxusermaporder', 'ipasearchrecordslimit', 'ipasearchtimelimit', 'ipauserauthtype', 'ipauserobjectclasses', 'ipausersearchfields', 'ipacustomfields', }, }, } label = _('Configuration') label_singular = _('Configuration') takes_params = ( Int('ipamaxusernamelength', cli_name='maxusername', label=_('Maximum username length'), minvalue=1, maxvalue=255, ), IA5Str('ipahomesrootdir', cli_name='homedirectory', label=_('Home directory base'), doc=_('Default location of home directories'), ), Str('ipadefaultloginshell', cli_name='defaultshell', label=_('Default shell'), doc=_('Default shell for new users'), ), Str('ipadefaultprimarygroup', cli_name='defaultgroup', label=_('Default users group'), doc=_('Default group for new users'), ), Str('ipadefaultemaildomain?', cli_name='emaildomain', label=_('Default e-mail domain'), doc=_('Default e-mail domain'), ), Int('ipasearchtimelimit', cli_name='searchtimelimit', label=_('Search time limit'), doc=_('Maximum amount of time (seconds) for a search (-1 or 0 is unlimited)'), minvalue=-1, ), Int('ipasearchrecordslimit', cli_name='searchrecordslimit', label=_('Search size limit'), doc=_('Maximum number of records to search (-1 or 0 is unlimited)'), minvalue=-1, ), IA5Str('ipausersearchfields', cli_name='usersearch', label=_('User search fields'), doc=_('A comma-separated list of fields to search in when searching for users'), ), IA5Str('ipagroupsearchfields', cli_name='groupsearch', label='Group search fields', doc=_('A comma-separated list of fields to search in when searching for groups'), ), Bool('ipamigrationenabled', cli_name='enable_migration', label=_('Enable migration mode'), doc=_('Enable migration mode'), ), DNParam('ipacertificatesubjectbase', cli_name='subject', label=_('Certificate Subject base'), doc=_('Base for certificate subjects (OU=Test,O=Example)'), flags=['no_update'], ), Str('ipagroupobjectclasses+', cli_name='groupobjectclasses', label=_('Default group objectclasses'), doc=_('Default group objectclasses (comma-separated list)'), ), Str('ipauserobjectclasses+', cli_name='userobjectclasses', label=_('Default user objectclasses'), doc=_('Default user objectclasses (comma-separated list)'), ), Int('ipapwdexpadvnotify', cli_name='pwdexpnotify', label=_('Password Expiration Notification (days)'), doc=_('Number of days\'s notice of impending password expiration'), minvalue=0, ), StrEnum('ipaconfigstring*', cli_name='ipaconfigstring', label=_('Password plugin features'), doc=_('Extra hashes to generate in password plug-in'), values=(u'AllowNThash', u'KDC:Disable Last Success', u'KDC:Disable Lockout', u'KDC:Disable Default Preauth for SPNs'), ), Str('ipaselinuxusermaporder', label=_('SELinux user map order'), doc=_('Order in increasing priority of SELinux users, delimited by $'), ), Str('ipaselinuxusermapdefault?', label=_('Default SELinux user'), doc=_('Default SELinux user when no match is found in SELinux map rule'), ), StrEnum('ipakrbauthzdata*', cli_name='pac_type', label=_('Default PAC types'), doc=_('Default types of PAC supported for services'), values=(u'MS-PAC', u'PAD', u'nfs:NONE'), ), StrEnum('ipauserauthtype*', cli_name='user_auth_type', label=_('Default user authentication types'), doc=_('Default types of supported user authentication'), values=(u'password', u'radius', u'otp', u'disabled'), ), Str( 'ipa_master_server*', label=_('IPA masters'), doc=_('List of all IPA masters'), flags={'virtual_attribute', 'no_create', 'no_update'} ), Str( 'ca_server_server*', label=_('IPA CA servers'), doc=_('IPA servers configured as certificate authority'), flags={'virtual_attribute', 'no_create', 'no_update'} ), Str( 'ntp_server_server*', label=_('IPA NTP servers'), doc=_('IPA servers with enabled NTP'), flags={'virtual_attribute', 'no_create', 'no_update'} ), Str( 'ca_renewal_master_server?', label=_('IPA CA renewal master'), doc=_('Renewal master for IPA certificate authority'), flags={'virtual_attribute', 'no_create'} ) ) def get_dn(self, *keys, **kwargs): return DN(('cn', 'ipaconfig'), ('cn', 'etc'), api.env.basedn) def show_servroles_attributes(self, entry_attrs, **options): if options.get('raw', False): return backend = self.api.Backend.serverroles for role in ("CA server", "IPA master", "NTP server"): config = backend.config_retrieve(role) entry_attrs.update(config)
class netgroup(LDAPObject): """ Netgroup object. """ container_dn = api.env.container_netgroup object_name = _('netgroup') object_name_plural = _('netgroups') object_class = ['ipaobject', 'ipaassociation', 'ipanisnetgroup'] default_attributes = [ 'cn', 'description', 'memberof', 'externalhost', 'nisdomainname', 'memberuser', 'memberhost', 'member', 'memberindirect', 'usercategory', 'hostcategory', ] uuid_attribute = 'ipauniqueid' rdn_attribute = 'ipauniqueid' attribute_members = { 'member': ['netgroup'], 'memberof': ['netgroup'], 'memberindirect': ['netgroup'], 'memberuser': ['user', 'group'], 'memberhost': ['host', 'hostgroup'], } relationships = { 'member': ('Member', '', 'no_'), 'memberof': ('Member Of', 'in_', 'not_in_'), 'memberindirect': ('Indirect Member', None, 'no_indirect_'), 'memberuser': ('Member', '', 'no_'), 'memberhost': ('Member', '', 'no_'), } label = _('Netgroups') label_singular = _('Netgroup') takes_params = ( Str( 'cn', pattern=NETGROUP_PATTERN, pattern_errmsg=NETGROUP_PATTERN_ERRMSG, cli_name='name', label=_('Netgroup name'), primary_key=True, normalizer=lambda value: value.lower(), ), Str( 'description', cli_name='desc', label=_('Description'), doc=_('Netgroup description'), ), Str( 'nisdomainname?', pattern=NISDOMAIN_PATTERN, pattern_errmsg=NISDOMAIN_PATTERN_ERRMSG, cli_name='nisdomain', label=_('NIS domain name'), ), Str( 'ipauniqueid?', cli_name='uuid', label='IPA unique ID', doc=_('IPA unique ID'), flags=['no_create', 'no_update'], ), StrEnum( 'usercategory?', cli_name='usercat', label=_('User category'), doc=_('User category the rule applies to'), values=(u'all', ), ), StrEnum( 'hostcategory?', cli_name='hostcat', label=_('Host category'), doc=_('Host category the rule applies to'), values=(u'all', ), ), external_host_param, )
csv=True, alwaysask=True, ), Str('key', label=_('Attribute Key'), doc= _('Attribute to filter via regex. For example fqdn for a host, or manager for a user' ), flags=['no_create', 'no_update', 'no_search']), ) group_type = (StrEnum( 'type', label=_('Grouping Type'), doc=_('Grouping to which the rule applies'), values=( u'group', u'hostgroup', ), ), ) automember_rule = (Str( 'cn', cli_name='automember_rule', label=_('Automember Rule'), doc=_('Automember Rule'), normalizer=lambda value: value.lower(), ), ) @register()
class otptoken(LDAPObject): """ OTP Token object. """ container_dn = api.env.container_otp object_name = _('OTP token') object_name_plural = _('OTP tokens') object_class = ['ipatoken'] possible_objectclasses = ['ipatokentotp', 'ipatokenhotp'] default_attributes = [ 'ipatokenuniqueid', 'description', 'ipatokenowner', 'ipatokendisabled', 'ipatokennotbefore', 'ipatokennotafter', 'ipatokenvendor', 'ipatokenmodel', 'ipatokenserial', 'managedby' ] attribute_members = { 'managedby': ['user'], } relationships = { 'managedby': ('Managed by', 'man_by_', 'not_man_by_'), } allow_rename = True label = _('OTP Tokens') label_singular = _('OTP Token') takes_params = ( Str( 'ipatokenuniqueid', cli_name='id', label=_('Unique ID'), primary_key=True, flags=('optional_create'), ), StrEnum( 'type?', label=_('Type'), doc=_('Type of the token'), default=u'totp', autofill=True, values=tuple(list(TOKEN_TYPES) + [x.upper() for x in TOKEN_TYPES]), flags=('virtual_attribute', 'no_update'), ), Str( 'description?', cli_name='desc', label=_('Description'), doc=_('Token description (informational only)'), ), Str( 'ipatokenowner?', cli_name='owner', label=_('Owner'), doc=_('Assigned user of the token (default: self)'), ), Str( 'managedby_user?', label=_('Manager'), doc=_('Assigned manager of the token (default: self)'), flags=['no_create', 'no_update', 'no_search'], ), Bool('ipatokendisabled?', cli_name='disabled', label=_('Disabled'), doc=_('Mark the token as disabled (default: false)')), DateTime( 'ipatokennotbefore?', cli_name='not_before', label=_('Validity start'), doc=_('First date/time the token can be used'), ), DateTime( 'ipatokennotafter?', cli_name='not_after', label=_('Validity end'), doc=_('Last date/time the token can be used'), ), Str( 'ipatokenvendor?', cli_name='vendor', label=_('Vendor'), doc=_('Token vendor name (informational only)'), ), Str( 'ipatokenmodel?', cli_name='model', label=_('Model'), doc=_('Token model (informational only)'), ), Str( 'ipatokenserial?', cli_name='serial', label=_('Serial'), doc=_('Token serial (informational only)'), ), OTPTokenKey( 'ipatokenotpkey?', cli_name='key', label=_('Key'), doc=_('Token secret (Base32; default: random)'), default_from=lambda: os.urandom(KEY_LENGTH), autofill=True, # force server-side conversion normalizer=lambda x: x, flags=('no_display', 'no_update', 'no_search'), ), StrEnum( 'ipatokenotpalgorithm?', cli_name='algo', label=_('Algorithm'), doc=_('Token hash algorithm'), default=u'sha1', autofill=True, flags=('no_update'), values=(u'sha1', u'sha256', u'sha384', u'sha512'), ), IntEnum( 'ipatokenotpdigits?', cli_name='digits', label=_('Digits'), doc=_('Number of digits each token code will have'), values=(6, 8), default=6, autofill=True, flags=('no_update'), ), Int( 'ipatokentotpclockoffset?', cli_name='offset', label=_('Clock offset'), doc=_('TOTP token / FreeIPA server time difference'), default=0, autofill=True, flags=('no_update'), ), Int( 'ipatokentotptimestep?', cli_name='interval', label=_('Clock interval'), doc=_('Length of TOTP token code validity'), default=30, autofill=True, minvalue=5, flags=('no_update'), ), Int( 'ipatokenhotpcounter?', cli_name='counter', label=_('Counter'), doc=_('Initial counter for the HOTP token'), default=0, autofill=True, minvalue=0, flags=('no_update'), ), Str( 'uri?', label=_('URI'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), )
class service(LDAPObject): """ Service object. """ container_dn = api.env.container_service object_name = _('service') object_name_plural = _('services') object_class = [ 'krbprincipal', 'krbprincipalaux', 'krbticketpolicyaux', 'ipaobject', 'ipaservice', 'pkiuser' ] possible_objectclasses = ['ipakrbprincipal', 'ipaallowedoperations'] permission_filter_objectclasses = ['ipaservice'] search_attributes = ['krbprincipalname', 'managedby', 'ipakrbauthzdata'] default_attributes = [ 'krbprincipalname', 'krbcanonicalname', 'usercertificate', 'managedby', 'ipakrbauthzdata', 'memberof', 'ipaallowedtoperform', 'krbprincipalauthind'] uuid_attribute = 'ipauniqueid' attribute_members = { 'managedby': ['host'], 'memberof': ['role'], 'ipaallowedtoperform_read_keys': ['user', 'group', 'host', 'hostgroup'], 'ipaallowedtoperform_write_keys': ['user', 'group', 'host', 'hostgroup'], } bindable = True relationships = { 'managedby': ('Managed by', 'man_by_', 'not_man_by_'), 'ipaallowedtoperform_read_keys': ('Allow to retrieve keytab by', 'retrieve_keytab_by_', 'not_retrieve_keytab_by_'), 'ipaallowedtoperform_write_keys': ('Allow to create keytab by', 'write_keytab_by_', 'not_write_keytab_by'), } password_attributes = [('krbprincipalkey', 'has_keytab')] managed_permissions = { 'System: Read Services': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'ipauniqueid', 'managedby', 'memberof', 'usercertificate', 'krbprincipalname', 'krbcanonicalname', 'krbprincipalaliases', 'krbprincipalexpiration', 'krbpasswordexpiration', 'krblastpwdchange', 'ipakrbauthzdata', 'ipakrbprincipalalias', 'krbobjectreferences', 'krbprincipalauthind', }, }, 'System: Add Services': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///krbprincipalname=*,cn=services,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add Services";allow (add) groupdn = "ldap:///cn=Add Services,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Service Administrators'}, }, 'System: Manage Service Keytab': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'krblastpwdchange', 'krbprincipalkey'}, 'replaces': [ '(targetattr = "krbprincipalkey || krblastpwdchange")(target = "ldap:///krbprincipalname=*,cn=services,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Manage service keytab";allow (write) groupdn = "ldap:///cn=Manage service keytab,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Service Administrators', 'Host Administrators'}, }, 'System: Manage Service Keytab Permissions': { 'ipapermright': {'read', 'search', 'compare', 'write'}, 'ipapermdefaultattr': { 'ipaallowedtoperform;write_keys', 'ipaallowedtoperform;read_keys', 'objectclass' }, 'default_privileges': {'Service Administrators', 'Host Administrators'}, }, 'System: Modify Services': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'usercertificate', 'krbprincipalauthind'}, 'replaces': [ '(targetattr = "usercertificate")(target = "ldap:///krbprincipalname=*,cn=services,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Services";allow (write) groupdn = "ldap:///cn=Modify Services,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Service Administrators'}, }, 'System: Manage Service Principals': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'krbprincipalname', 'krbcanonicalname'}, 'default_privileges': { 'Service Administrators', }, }, 'System: Remove Services': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///krbprincipalname=*,cn=services,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Remove Services";allow (delete) groupdn = "ldap:///cn=Remove Services,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Service Administrators'}, }, } label = _('Services') label_singular = _('Service') takes_params = ( Principal( 'krbcanonicalname', validate_realm, cli_name='canonical_principal', label=_('Principal name'), doc=_('Service principal'), primary_key=True, normalizer=normalize_principal, require_service=True ), Principal( 'krbprincipalname*', validate_realm, cli_name='principal', label=_('Principal alias'), doc=_('Service principal alias'), normalizer=normalize_principal, require_service=True, flags={'no_create'} ), Certificate('usercertificate*', cli_name='certificate', label=_('Certificate'), doc=_('Base-64 encoded service certificate'), flags=['no_search',], ), Str('subject', label=_('Subject'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str('serial_number', label=_('Serial Number'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str('serial_number_hex', label=_('Serial Number (hex)'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str('issuer', label=_('Issuer'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str('valid_not_before', label=_('Not Before'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str('valid_not_after', label=_('Not After'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str('sha1_fingerprint', label=_('Fingerprint (SHA1)'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str('sha256_fingerprint', label=_('Fingerprint (SHA256)'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str('revocation_reason?', label=_('Revocation reason'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), StrEnum('ipakrbauthzdata*', cli_name='pac_type', label=_('PAC type'), doc=_("Override default list of supported PAC types." " Use 'NONE' to disable PAC support for this service," " e.g. this might be necessary for NFS services."), values=(u'MS-PAC', u'PAD', u'NONE'), ), Str('krbprincipalauthind*', cli_name='auth_ind', label=_('Authentication Indicators'), doc=_("Defines a whitelist for Authentication Indicators." " Use 'otp' to allow OTP-based 2FA authentications." " Use 'radius' to allow RADIUS-based 2FA authentications." " Other values may be used for custom configurations."), ), ) + ticket_flags_params def validate_ipakrbauthzdata(self, entry): new_value = entry.get('ipakrbauthzdata', []) if not new_value: return if not isinstance(new_value, (list, tuple)): new_value = set([new_value]) else: new_value = set(new_value) if u'NONE' in new_value and len(new_value) > 1: raise errors.ValidationError(name='ipakrbauthzdata', error=_('NONE value cannot be combined with other PAC types')) def get_dn(self, *keys, **kwargs): key = keys[0] if isinstance(key, str): key = kerberos.Principal(key) key = unicode(normalize_principal(key)) parent_dn = DN(self.container_dn, self.api.env.basedn) true_rdn = 'krbprincipalname' return self.backend.make_dn_from_attr( true_rdn, key, parent_dn ) def get_primary_key_from_dn(self, dn): """ If the entry has krbcanonicalname set return the value of the attribute. If the attribute is not found, assume old-style entry which should have only single value of krbprincipalname and return it. Otherwise return input DN. """ assert isinstance(dn, DN) try: entry_attrs = self.backend.get_entry( dn, [self.primary_key.name] ) try: return entry_attrs[self.primary_key.name][0] except (KeyError, IndexError): return '' except errors.NotFound: pass try: return dn['krbprincipalname'] except KeyError: return unicode(dn) def populate_krbcanonicalname(self, entry_attrs, options): if options.get('raw', False): return entry_attrs.setdefault( 'krbcanonicalname', entry_attrs['krbprincipalname'])
class selinuxusermap(LDAPObject): """ SELinux User Map object. """ container_dn = api.env.container_selinux object_name = _('SELinux User Map rule') object_name_plural = _('SELinux User Map rules') object_class = ['ipaassociation', 'ipaselinuxusermap'] default_attributes = [ 'cn', 'ipaenabledflag', 'description', 'usercategory', 'hostcategory', 'ipaenabledflag', 'memberuser', 'memberhost', 'memberhostgroup', 'seealso', 'ipaselinuxuser', ] uuid_attribute = 'ipauniqueid' rdn_attribute = 'ipauniqueid' attribute_members = { 'memberuser': ['user', 'group'], 'memberhost': ['host', 'hostgroup'], } # These maps will not show as members of other entries label = _('SELinux User Maps') label_singular = _('SELinux User Map') takes_params = ( Str( 'cn', cli_name='name', label=_('Rule name'), primary_key=True, ), Str( 'ipaselinuxuser', validate_selinuxuser, cli_name='selinuxuser', label=_('SELinux User'), ), Str( 'seealso?', cli_name='hbacrule', label=_('HBAC Rule'), doc=_('HBAC Rule that defines the users, groups and hostgroups'), ), StrEnum( 'usercategory?', cli_name='usercat', label=_('User category'), doc=_('User category the rule applies to'), values=(u'all', ), ), StrEnum( 'hostcategory?', cli_name='hostcat', label=_('Host category'), doc=_('Host category the rule applies to'), values=(u'all', ), ), Str( 'description?', cli_name='desc', label=_('Description'), ), Bool( 'ipaenabledflag?', label=_('Enabled'), flags=['no_option'], ), Str( 'memberuser_user?', label=_('Users'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberuser_group?', label=_('User Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberhost_host?', label=_('Hosts'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberhost_hostgroup?', label=_('Host Groups'), flags=['no_create', 'no_update', 'no_search'], ), ) def _normalize_seealso(self, seealso): """ Given a HBAC rule name verify its existence and return the dn. """ if not seealso: return None try: dn = DN(seealso) return str(dn) except ValueError: try: (dn, entry_attrs) = self.backend.find_entry_by_attr( self.api.Object['hbacrule'].primary_key.name, seealso, self.api.Object['hbacrule'].object_class, [''], self.api.Object['hbacrule'].container_dn) seealso = dn except errors.NotFound: raise errors.NotFound( reason=_('HBAC rule %(rule)s not found') % dict(rule=seealso)) return seealso def _convert_seealso(self, ldap, entry_attrs, **options): """ Convert an HBAC rule dn into a name """ if options.get('raw', False): return if 'seealso' in entry_attrs: (hbac_dn, hbac_attrs) = ldap.get_entry(entry_attrs['seealso'][0], ['cn']) entry_attrs['seealso'] = hbac_attrs['cn'][0]
class service(LDAPObject): """ Service object. """ container_dn = api.env.container_service object_name = _('service') object_name_plural = _('services') object_class = [ 'krbprincipal', 'krbprincipalaux', 'krbticketpolicyaux', 'ipaobject', 'ipaservice', 'pkiuser' ] possible_objectclasses = ['ipakrbprincipal'] search_attributes = ['krbprincipalname', 'managedby', 'ipakrbauthzdata'] default_attributes = [ 'krbprincipalname', 'usercertificate', 'managedby', 'ipakrbauthzdata', ] uuid_attribute = 'ipauniqueid' attribute_members = { 'managedby': ['host'], } bindable = True relationships = { 'managedby': ('Managed by', 'man_by_', 'not_man_by_'), } password_attributes = [('krbprincipalkey', 'has_keytab')] label = _('Services') label_singular = _('Service') takes_params = ( Str( 'krbprincipalname', validate_principal, cli_name='principal', label=_('Principal'), doc=_('Service principal'), primary_key=True, normalizer=lambda value: normalize_principal(value), ), Bytes( 'usercertificate?', validate_certificate, cli_name='certificate', label=_('Certificate'), doc=_('Base-64 encoded server certificate'), flags=[ 'no_search', ], ), StrEnum( 'ipakrbauthzdata*', cli_name='pac_type', label=_('PAC type'), doc=_("Override default list of supported PAC types." " Use 'NONE' to disable PAC support for this service"), values=(u'MS-PAC', u'PAD', u'NONE'), csv=True, ), ) def validate_ipakrbauthzdata(self, entry): new_value = entry.get('ipakrbauthzdata', []) if not new_value: return if not isinstance(new_value, (list, tuple)): new_value = set([new_value]) else: new_value = set(new_value) if u'NONE' in new_value and len(new_value) > 1: raise errors.ValidationError( name='ipakrbauthzdata', error=_('NONE value cannot be combined with other PAC types'))
class config(LDAPObject): """ IPA configuration object """ object_name = _('configuration options') default_attributes = [ 'ipamaxusernamelength', 'ipahomesrootdir', 'ipadefaultloginshell', 'ipadefaultprimarygroup', 'ipadefaultemaildomain', 'ipasearchtimelimit', 'ipasearchrecordslimit', 'ipausersearchfields', 'ipagroupsearchfields', 'ipamigrationenabled', 'ipacertificatesubjectbase', 'ipapwdexpadvnotify', 'ipaselinuxusermaporder', 'ipaselinuxusermapdefault', 'ipaconfigstring', 'ipakrbauthzdata', ] label = _('Configuration') label_singular = _('Configuration') takes_params = ( Int( 'ipamaxusernamelength', cli_name='maxusername', label=_('Maximum username length'), minvalue=1, ), IA5Str( 'ipahomesrootdir', cli_name='homedirectory', label=_('Home directory base'), doc=_('Default location of home directories'), ), Str( 'ipadefaultloginshell', cli_name='defaultshell', label=_('Default shell'), doc=_('Default shell for new users'), ), Str( 'ipadefaultprimarygroup', cli_name='defaultgroup', label=_('Default users group'), doc=_('Default group for new users'), ), Str( 'ipadefaultemaildomain?', cli_name='emaildomain', label=_('Default e-mail domain'), doc=_('Default e-mail domain'), ), Int( 'ipasearchtimelimit', validate_searchtimelimit, cli_name='searchtimelimit', label=_('Search time limit'), doc= _('Maximum amount of time (seconds) for a search (> 0, or -1 for unlimited)' ), minvalue=-1, ), Int( 'ipasearchrecordslimit', cli_name='searchrecordslimit', label=_('Search size limit'), doc=_('Maximum number of records to search (-1 is unlimited)'), minvalue=-1, ), IA5Str( 'ipausersearchfields', cli_name='usersearch', label=_('User search fields'), doc= _('A comma-separated list of fields to search in when searching for users' ), ), IA5Str( 'ipagroupsearchfields', cli_name='groupsearch', label='Group search fields', doc= _('A comma-separated list of fields to search in when searching for groups' ), ), Bool( 'ipamigrationenabled', cli_name='enable_migration', label=_('Enable migration mode'), doc=_('Enable migration mode'), ), DNParam( 'ipacertificatesubjectbase', cli_name='subject', label=_('Certificate Subject base'), doc=_('Base for certificate subjects (OU=Test,O=Example)'), flags=['no_update'], ), Str( 'ipagroupobjectclasses+', cli_name='groupobjectclasses', label=_('Default group objectclasses'), doc=_('Default group objectclasses (comma-separated list)'), csv=True, ), Str( 'ipauserobjectclasses+', cli_name='userobjectclasses', label=_('Default user objectclasses'), doc=_('Default user objectclasses (comma-separated list)'), csv=True, ), Int( 'ipapwdexpadvnotify', cli_name='pwdexpnotify', label=_('Password Expiration Notification (days)'), doc=_('Number of days\'s notice of impending password expiration'), minvalue=0, ), StrEnum( 'ipaconfigstring*', cli_name='ipaconfigstring', label=_('Password plugin features'), doc=_('Extra hashes to generate in password plug-in'), values=(u'AllowLMhash', u'AllowNThash', u'KDC:Disable Last Success', u'KDC:Disable Lockout'), csv=True, ), Str( 'ipaselinuxusermaporder', label=_('SELinux user map order'), doc=_( 'Order in increasing priority of SELinux users, delimited by $' ), ), Str( 'ipaselinuxusermapdefault?', label=_('Default SELinux user'), doc= _('Default SELinux user when no match is found in SELinux map rule' ), ), StrEnum( 'ipakrbauthzdata*', cli_name='pac_type', label=_('Default PAC types'), doc=_('Default types of PAC supported for services'), values=(u'MS-PAC', u'PAD'), csv=True, ), ) def get_dn(self, *keys, **kwargs): return DN(('cn', 'ipaconfig'), ('cn', 'etc'))
class caacl(LDAPObject): """ CA ACL object. """ container_dn = api.env.container_caacl object_name = _('CA ACL') object_name_plural = _('CA ACLs') object_class = ['ipaassociation', 'ipacaacl'] permission_filter_objectclasses = ['ipacaacl'] default_attributes = [ 'cn', 'description', 'ipaenabledflag', 'ipacacategory', 'ipamemberca', 'ipacertprofilecategory', 'ipamembercertprofile', 'usercategory', 'memberuser', 'hostcategory', 'memberhost', 'servicecategory', 'memberservice', ] uuid_attribute = 'ipauniqueid' rdn_attribute = 'ipauniqueid' attribute_members = { 'memberuser': ['user', 'group'], 'memberhost': ['host', 'hostgroup'], 'memberservice': ['service'], 'ipamemberca': ['ca'], 'ipamembercertprofile': ['certprofile'], } managed_permissions = { 'System: Read CA ACLs': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cn', 'description', 'ipaenabledflag', 'ipacacategory', 'ipamemberca', 'ipacertprofilecategory', 'ipamembercertprofile', 'usercategory', 'memberuser', 'hostcategory', 'memberhost', 'servicecategory', 'memberservice', 'ipauniqueid', 'objectclass', 'member', }, }, 'System: Add CA ACL': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=caacls,cn=ca,$SUFFIX")(version 3.0;acl "permission:Add CA ACL";allow (add) groupdn = "ldap:///cn=Add CA ACL,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'CA Administrator'}, }, 'System: Delete CA ACL': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=caacls,cn=ca,$SUFFIX")(version 3.0;acl "permission:Delete CA ACL";allow (delete) groupdn = "ldap:///cn=Delete CA ACL,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'CA Administrator'}, }, 'System: Manage CA ACL Membership': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'ipacacategory', 'ipamemberca', 'ipacertprofilecategory', 'ipamembercertprofile', 'usercategory', 'memberuser', 'hostcategory', 'memberhost', 'servicecategory', 'memberservice' }, 'replaces': [ '(targetattr = "ipamemberca || ipamembercertprofile || memberuser || memberservice || memberhost || ipacacategory || ipacertprofilecategory || usercategory || hostcategory || servicecategory")(target = "ldap:///ipauniqueid=*,cn=caacls,cn=ca,$SUFFIX")(version 3.0;acl "permission:Manage CA ACL membership";allow (write) groupdn = "ldap:///cn=Manage CA ACL membership,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'CA Administrator'}, }, 'System: Modify CA ACL': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'cn', 'description', 'ipaenabledflag', }, 'replaces': [ '(targetattr = "cn || description || ipaenabledflag")(target = "ldap:///ipauniqueid=*,cn=caacls,cn=ca,$SUFFIX")(version 3.0;acl "permission:Modify CA ACL";allow (write) groupdn = "ldap:///cn=Modify CA ACL,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'CA Administrator'}, }, } label = _('CA ACLs') label_singular = _('CA ACL') takes_params = ( Str('cn', cli_name='name', label=_('ACL name'), primary_key=True, ), Str('description?', cli_name='desc', label=_('Description'), ), Bool('ipaenabledflag?', label=_('Enabled'), flags=['no_option'], ), StrEnum('ipacacategory?', cli_name='cacat', label=_('CA category'), doc=_('CA category the ACL applies to'), values=(u'all', ), ), StrEnum('ipacertprofilecategory?', cli_name='profilecat', label=_('Profile category'), doc=_('Profile category the ACL applies to'), values=(u'all', ), ), StrEnum('usercategory?', cli_name='usercat', label=_('User category'), doc=_('User category the ACL applies to'), values=(u'all', ), ), StrEnum('hostcategory?', cli_name='hostcat', label=_('Host category'), doc=_('Host category the ACL applies to'), values=(u'all', ), ), StrEnum('servicecategory?', cli_name='servicecat', label=_('Service category'), doc=_('Service category the ACL applies to'), values=(u'all', ), ), Str('ipamemberca_ca?', label=_('CAs'), flags=['no_create', 'no_update', 'no_search'], ), Str('ipamembercertprofile_certprofile?', label=_('Profiles'), flags=['no_create', 'no_update', 'no_search'], ), Str('memberuser_user?', label=_('Users'), flags=['no_create', 'no_update', 'no_search'], ), Str('memberuser_group?', label=_('User Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str('memberhost_host?', label=_('Hosts'), flags=['no_create', 'no_update', 'no_search'], ), Str('memberhost_hostgroup?', label=_('Host Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str('memberservice_service?', label=_('Services'), flags=['no_create', 'no_update', 'no_search'], ), )
class trust_add(LDAPCreate): __doc__ = _(''' Add new trust to use. This command establishes trust relationship to another domain which becomes 'trusted'. As result, users of the trusted domain may access resources of this domain. Only trusts to Active Directory domains are supported right now. The command can be safely run multiple times against the same domain, this will cause change to trust relationship credentials on both sides. ''') takes_options = LDAPCreate.takes_options + ( StrEnum( 'trust_type', cli_name='type', label=_('Trust type (ad for Active Directory, default)'), values=(u'ad', ), default=u'ad', autofill=True, ), Str( 'realm_admin?', cli_name='admin', label=_("Active Directory domain administrator"), ), Password( 'realm_passwd?', cli_name='password', label=_("Active directory domain administrator's password"), confirm=False, ), Str( 'realm_server?', cli_name='server', label=_( 'Domain controller for the Active Directory domain (optional)' ), ), Password( 'trust_secret?', cli_name='trust_secret', label=_('Shared secret for the trust'), confirm=False, ), Int( 'base_id?', cli_name='base_id', label=_( 'First Posix ID of the range reserved for the trusted domain'), ), Int('range_size?', cli_name='range_size', label=_('Size of the ID range reserved for the trusted domain'), default=200000, autofill=True), ) msg_summary = _('Added Active Directory trust for realm "%(value)s"') has_output_params = LDAPCreate.has_output_params + trust_output_params def execute(self, *keys, **options): if not _murmur_installed and 'base_id' not in options: raise errors.ValidationError(name=_('missing base_id'), error=_('pysss_murmur is not available on the server ' \ 'and no base-id is given.')) if 'trust_type' in options: if options['trust_type'] == u'ad': result = self.execute_ad(*keys, **options) else: raise errors.ValidationError(name=_('trust type'), error=_('only "ad" is supported')) else: raise errors.RequirementError(name=_('trust type')) self.add_range(*keys, **options) trust_filter = "cn=%s" % result['value'] ldap = self.obj.backend (trusts, truncated) = ldap.find_entries(base_dn=DN(api.env.container_trusts, api.env.basedn), filter=trust_filter) result['result'] = trusts[0][1] result['result']['trusttype'] = [ trust_type_string(result['result']['ipanttrusttype'][0]) ] result['result']['trustdirection'] = [ trust_direction_string(result['result']['ipanttrustdirection'][0]) ] result['result']['truststatus'] = [ trust_status_string(result['verified']) ] del result['verified'] return result def add_range(self, *keys, **options): new_obj = api.Command['trust_show'](keys[-1]) dom_sid = new_obj['result']['ipanttrusteddomainsid'][0] range_name = keys[-1].upper() + '_id_range' try: old_range = api.Command['idrange_show'](range_name) except errors.NotFound, e: old_range = None if old_range: old_dom_sid = old_range['result']['ipanttrusteddomainsid'][0] if old_dom_sid == dom_sid: return raise errors.ValidationError(name=_('range exists'), error=_('ID range with the same name but different ' \ 'domain SID already exists. The ID range for ' \ 'the new trusted domain must be created manually.')) if 'base_id' in options: base_id = options['base_id'] else: base_id = 200000 + (pysss_murmur.murmurhash3( dom_sid, len(dom_sid), 0xdeadbeef) % 10000) * 200000 try: new_range = api.Command['idrange_add']( range_name, ipabaseid=base_id, ipaidrangesize=options['range_size'], ipabaserid=0, ipanttrusteddomainsid=dom_sid) except Exception, e: raise errors.ValidationError( name=_('ID range exists'), error=_('ID range already exists, must be added manually'))
class hbacrule(LDAPObject): """ HBAC object. """ container_dn = api.env.container_hbac object_name = _('HBAC rule') object_name_plural = _('HBAC rules') object_class = ['ipaassociation', 'ipahbacrule'] default_attributes = [ 'cn', 'ipaenabledflag', 'description', 'usercategory', 'hostcategory', 'sourcehostcategory', 'servicecategory', 'ipaenabledflag', 'memberuser', 'sourcehost', 'memberhost', 'memberservice', 'memberhostgroup', 'externalhost', ] uuid_attribute = 'ipauniqueid' rdn_attribute = 'ipauniqueid' attribute_members = { 'memberuser': ['user', 'group'], 'memberhost': ['host', 'hostgroup'], 'sourcehost': ['host', 'hostgroup'], 'memberservice': ['hbacsvc', 'hbacsvcgroup'], } label = _('HBAC Rules') label_singular = _('HBAC Rule') takes_params = ( Str( 'cn', cli_name='name', label=_('Rule name'), primary_key=True, ), StrEnum( 'accessruletype', validate_type, cli_name='type', doc=_('Rule type (allow)'), label=_('Rule type'), values=(u'allow', u'deny'), default=u'allow', autofill=True, exclude='webui', flags=['no_option', 'no_output'], ), # FIXME: {user,host,service}categories should expand in the future StrEnum( 'usercategory?', cli_name='usercat', label=_('User category'), doc=_('User category the rule applies to'), values=(u'all', ), ), StrEnum( 'hostcategory?', cli_name='hostcat', label=_('Host category'), doc=_('Host category the rule applies to'), values=(u'all', ), ), DeprecatedParam('sourcehostcategory?'), StrEnum( 'servicecategory?', cli_name='servicecat', label=_('Service category'), doc=_('Service category the rule applies to'), values=(u'all', ), ), # AccessTime('accesstime?', # cli_name='time', # label=_('Access time'), # ), Str( 'description?', cli_name='desc', label=_('Description'), ), Bool( 'ipaenabledflag?', label=_('Enabled'), flags=['no_option'], ), Str( 'memberuser_user?', label=_('Users'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberuser_group?', label=_('User Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberhost_host?', label=_('Hosts'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberhost_hostgroup?', label=_('Host Groups'), flags=['no_create', 'no_update', 'no_search'], ), DeprecatedParam('sourcehost_host?'), DeprecatedParam('sourcehost_hostgroup?'), Str( 'memberservice_hbacsvc?', label=_('Services'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberservice_hbacsvcgroup?', label=_('Service Groups'), flags=['no_create', 'no_update', 'no_search'], ), external_host_param, )
perm = perm.strip().lower() if perm not in _valid_permissions_values: return '"%s" is not a valid permission' % perm def _normalize_permissions(perm): valid_permissions = [] perm = perm.strip().lower() if perm not in valid_permissions: valid_permissions.append(perm) return ','.join(valid_permissions) _prefix_option = StrEnum('aciprefix', cli_name='prefix', label=_('ACI prefix'), doc=_('Prefix used to distinguish ACI types ' \ '(permission, delegation, selfservice, none)'), values=_valid_prefix_values, ) @register() class aci(Object): """ ACI object. """ NO_CLI = True label = _('ACIs') takes_params = (
class migrate_ds(Command): __doc__ = _('Migrate users and groups from DS to IPA.') migrate_objects = { # OBJECT_NAME: (search_filter, pre_callback, post_callback) # # OBJECT_NAME - is the name of an LDAPObject subclass # search_filter - is the filter to retrieve objects from DS # pre_callback - is called for each object just after it was # retrieved from DS and before being added to IPA # post_callback - is called for each object after it was added to IPA # exc_callback - is called when adding entry to IPA raises an exception # # {pre, post}_callback parameters: # ldap - ldap2 instance connected to IPA # pkey - primary key value of the object (uid for users, etc.) # dn - dn of the object as it (will be/is) stored in IPA # entry_attrs - attributes of the object # failed - a list of so-far failed objects # config - IPA config entry attributes # ctx - object context, used to pass data between callbacks # # If pre_callback return value evaluates to False, migration # of the current object is aborted. 'user': { 'filter_template': '(&(|%s)(uid=*))', 'oc_option': 'userobjectclass', 'oc_blacklist_option': 'userignoreobjectclass', 'attr_blacklist_option': 'userignoreattribute', 'pre_callback': _pre_migrate_user, 'post_callback': _post_migrate_user, 'exc_callback': None }, 'group': { 'filter_template': '(&(|%s)(cn=*))', 'oc_option': 'groupobjectclass', 'oc_blacklist_option': 'groupignoreobjectclass', 'attr_blacklist_option': 'groupignoreattribute', 'pre_callback': _pre_migrate_group, 'post_callback': None, 'exc_callback': _group_exc_callback, }, } migrate_order = ('user', 'group') takes_args = ( Str( 'ldapuri', validate_ldapuri, cli_name='ldap_uri', label=_('LDAP URI'), doc=_('LDAP URI of DS server to migrate from'), ), Password( 'bindpw', cli_name='password', label=_('Password'), confirm=False, doc=_('bind password'), ), ) takes_options = ( DNParam('binddn?', cli_name='bind_dn', label=_('Bind DN'), default=DN(('cn', 'directory manager')), autofill=True, ), DNParam('usercontainer', cli_name='user_container', label=_('User container'), doc=_('DN of container for users in DS relative to base DN'), default=DN(('ou', 'people')), autofill=True, ), DNParam('groupcontainer', cli_name='group_container', label=_('Group container'), doc=_('DN of container for groups in DS relative to base DN'), default=DN(('ou', 'groups')), autofill=True, ), Str('userobjectclass+', cli_name='user_objectclass', label=_('User object class'), doc=_('Comma-separated list of objectclasses used to search for user entries in DS'), csv=True, default=(u'person',), autofill=True, ), Str('groupobjectclass+', cli_name='group_objectclass', label=_('Group object class'), doc=_('Comma-separated list of objectclasses used to search for group entries in DS'), csv=True, default=(u'groupOfUniqueNames', u'groupOfNames'), autofill=True, ), Str('userignoreobjectclass*', cli_name='user_ignore_objectclass', label=_('Ignore user object class'), doc=_('Comma-separated list of objectclasses to be ignored for user entries in DS'), csv=True, default=tuple(), autofill=True, ), Str('userignoreattribute*', cli_name='user_ignore_attribute', label=_('Ignore user attribute'), doc=_('Comma-separated list of attributes to be ignored for user entries in DS'), csv=True, default=tuple(), autofill=True, ), Str('groupignoreobjectclass*', cli_name='group_ignore_objectclass', label=_('Ignore group object class'), doc=_('Comma-separated list of objectclasses to be ignored for group entries in DS'), csv=True, default=tuple(), autofill=True, ), Str('groupignoreattribute*', cli_name='group_ignore_attribute', label=_('Ignore group attribute'), doc=_('Comma-separated list of attributes to be ignored for group entries in DS'), csv=True, default=tuple(), autofill=True, ), Flag('groupoverwritegid', cli_name='group_overwrite_gid', label=_('Overwrite GID'), doc=_('When migrating a group already existing in IPA domain overwrite the '\ 'group GID and report as success'), ), StrEnum('schema?', cli_name='schema', label=_('LDAP schema'), doc=_('The schema used on the LDAP server. Supported values are RFC2307 and RFC2307bis. The default is RFC2307bis'), values=_supported_schemas, default=_supported_schemas[0], autofill=True, ), Flag('continue?', label=_('Continue'), doc=_('Continuous operation mode. Errors are reported but the process continues'), default=False, ), DNParam('basedn?', cli_name='base_dn', label=_('Base DN'), doc=_('Base DN on remote LDAP server'), ), Flag('compat?', cli_name='with_compat', label=_('Ignore compat plugin'), doc=_('Allows migration despite the usage of compat plugin'), default=False, ), ) has_output = ( output.Output( 'result', type=dict, doc=_('Lists of objects migrated; categorized by type.'), ), output.Output( 'failed', type=dict, doc= _('Lists of objects that could not be migrated; categorized by type.' ), ), output.Output( 'enabled', type=bool, doc=_('False if migration mode was disabled.'), ), output.Output( 'compat', type=bool, doc= _('False if migration fails because the compatibility plug-in is enabled.' ), ), ) exclude_doc = _('comma-separated list of %s to exclude from migration') truncated_err_msg = _('''\ search results for objects to be migrated have been truncated by the server; migration process might be incomplete\n''') migration_disabled_msg = _('''\ Migration mode is disabled. Use \'ipa config-mod\' to enable it.''') pwd_migration_msg = _('''\ Passwords have been migrated in pre-hashed format. IPA is unable to generate Kerberos keys unless provided with clear text passwords. All migrated users need to login at https://your.domain/ipa/migration/ before they can use their Kerberos accounts.''') def get_options(self): """ Call get_options of the baseclass and add "exclude" options for each type of object being migrated. """ for option in super(migrate_ds, self).get_options(): yield option for ldap_obj_name in self.migrate_objects: ldap_obj = self.api.Object[ldap_obj_name] name = 'exclude_%ss' % to_cli(ldap_obj_name) doc = self.exclude_doc % ldap_obj.object_name_plural yield Str('%s*' % name, cli_name=name, doc=doc, csv=True, default=tuple(), autofill=True) def normalize_options(self, options): """ Convert all "exclude" option values to lower-case. Also, empty List parameters are converted to None, but the migration plugin doesn't like that - convert back to empty lists. """ for p in self.params(): if p.csv: if options[p.name]: options[p.name] = tuple(v.lower() for v in options[p.name]) else: options[p.name] = tuple() def _get_search_bases(self, options, ds_base_dn, migrate_order): search_bases = dict() for ldap_obj_name in migrate_order: container = options.get('%scontainer' % to_cli(ldap_obj_name)) if container: # Don't append base dn if user already appended it in the container dn if container.endswith(ds_base_dn): search_base = container else: search_base = DN(container, ds_base_dn) else: search_base = ds_base_dn search_bases[ldap_obj_name] = search_base return search_bases def migrate(self, ldap, config, ds_ldap, ds_base_dn, options): """ Migrate objects from DS to LDAP. """ assert isinstance(ds_base_dn, DN) migrated = {} # {'OBJ': ['PKEY1', 'PKEY2', ...], ...} failed = {} # {'OBJ': {'PKEY1': 'Failed 'cos blabla', ...}, ...} search_bases = self._get_search_bases(options, ds_base_dn, self.migrate_order) migration_start = datetime.datetime.now() for ldap_obj_name in self.migrate_order: ldap_obj = self.api.Object[ldap_obj_name] template = self.migrate_objects[ldap_obj_name]['filter_template'] oc_list = options[to_cli( self.migrate_objects[ldap_obj_name]['oc_option'])] search_filter = construct_filter(template, oc_list) exclude = options['exclude_%ss' % to_cli(ldap_obj_name)] context = dict(ds_ldap=ds_ldap) migrated[ldap_obj_name] = [] failed[ldap_obj_name] = {} try: (entries, truncated) = ds_ldap.find_entries( search_filter, ['*'], search_bases[ldap_obj_name], _ldap.SCOPE_ONELEVEL, time_limit=0, size_limit=-1, search_refs=True # migrated DS may contain search references ) except errors.NotFound: if not options.get('continue', False): raise errors.NotFound( reason= _('%(container)s LDAP search did not return any result ' '(search base: %(search_base)s, ' 'objectclass: %(objectclass)s)') % { 'container': ldap_obj_name, 'search_base': search_bases[ldap_obj_name], 'objectclass': ', '.join(oc_list) }) else: truncated = False entries = [] if truncated: self.log.error('%s: %s' % (ldap_obj.name, self.truncated_err_msg)) blacklists = {} for blacklist in ('oc_blacklist', 'attr_blacklist'): blacklist_option = self.migrate_objects[ldap_obj_name][ blacklist + '_option'] if blacklist_option is not None: blacklists[blacklist] = options.get( blacklist_option, tuple()) else: blacklists[blacklist] = tuple() # get default primary group for new users if 'def_group_dn' not in context: def_group = config.get('ipadefaultprimarygroup') context['def_group_dn'] = api.Object.group.get_dn(def_group) try: (g_dn, g_attrs) = ldap.get_entry(context['def_group_dn'], ['gidnumber', 'cn']) except errors.NotFound: error_msg = _('Default group for new users not found') raise errors.NotFound(reason=error_msg) if 'gidnumber' in g_attrs: context['def_group_gid'] = g_attrs['gidnumber'][0] context['has_upg'] = ldap.has_upg() valid_gids = [] invalid_gids = [] context['migrate_cnt'] = 0 migrate_cnt = 0 for (dn, entry_attrs) in entries: context['migrate_cnt'] = migrate_cnt s = datetime.datetime.now() if dn is None: # LDAP search reference failed[ldap_obj_name][entry_attrs[0]] = unicode( _ref_err_msg) continue try: dn = DN(dn) except ValueError: failed[ldap_obj_name][dn] = unicode(_dn_err_msg) continue ava = dn[0][0] if ava.attr == ldap_obj.primary_key.name: # In case if pkey attribute is in the migrated object DN # and the original LDAP is multivalued, make sure that # we pick the correct value (the unique one stored in DN) pkey = ava.value.lower() else: pkey = entry_attrs[ldap_obj.primary_key.name][0].lower() if pkey in exclude: continue dn = ldap_obj.get_dn(pkey) assert isinstance(dn, DN) entry_attrs['objectclass'] = list( set( config.get(ldap_obj.object_class_config, ldap_obj.object_class) + [o.lower() for o in entry_attrs['objectclass']])) entry_attrs[ldap_obj.primary_key.name][0] = entry_attrs[ ldap_obj.primary_key.name][0].lower() callback = self.migrate_objects[ldap_obj_name]['pre_callback'] if callable(callback): try: dn = callback(ldap, pkey, dn, entry_attrs, failed[ldap_obj_name], config, context, schema=options['schema'], search_bases=search_bases, valid_gids=valid_gids, invalid_gids=invalid_gids, **blacklists) assert isinstance(dn, DN) if not dn: continue except errors.NotFound, e: failed[ldap_obj_name][pkey] = unicode(e.reason) continue try: ldap.add_entry(dn, entry_attrs) except errors.ExecutionError, e: callback = self.migrate_objects[ldap_obj_name][ 'exc_callback'] if callable(callback): try: callback(ldap, dn, entry_attrs, e, options) except errors.ExecutionError, e: failed[ldap_obj_name][pkey] = unicode(e) continue else: failed[ldap_obj_name][pkey] = unicode(e) continue migrated[ldap_obj_name].append(pkey) callback = self.migrate_objects[ldap_obj_name]['post_callback'] if callable(callback): callback( ldap, pkey, dn, entry_attrs, failed[ldap_obj_name], config, context, ) e = datetime.datetime.now() d = e - s total_dur = e - migration_start migrate_cnt += 1 if migrate_cnt > 0 and migrate_cnt % 100 == 0: api.log.info("%d %ss migrated. %s elapsed." % (migrate_cnt, ldap_obj_name, total_dur)) api.log.debug("%d %ss migrated, duration: %s (total %s)" % (migrate_cnt, ldap_obj_name, d, total_dur))
class server_state(crud.PKQuery): __doc__ = _("Set enabled/hidden state of a server.") takes_options = (StrEnum( 'state', values=(u'enabled', u'hidden'), label=_('State'), doc=_('Server state'), flags={'virtual_attribute', 'no_create', 'no_search'}, ), ) msg_summary = _('Changed server state of "%(value)s".') has_output = output.standard_boolean def _check_hide_server(self, fqdn): result = self.api.Command.config_show()['result'] err = [] # single value entries if result.get("ca_renewal_master_server") == fqdn: err.append(_("Cannot hide CA renewal master.")) if result.get("dnssec_key_master_server") == fqdn: err.append(_("Cannot hide DNSSec key master.")) # multi value entries, only fail if we are the last one checks = [ ("ca_server_server", "CA"), ("dns_server_server", "DNS"), ("ipa_master_server", "IPA"), ("kra_server_server", "KRA"), ] for key, name in checks: values = result.get(key, []) if values == [fqdn]: # fqdn is the only entry err.append( _("Cannot hide last enabled %(name)s server.") % {'name': name}) if err: raise errors.ValidationError(name=fqdn, error=' '.join(str(e) for e in err)) def execute(self, *keys, **options): fqdn = keys[0] if options['state'] == u'enabled': to_status = ENABLED from_status = HIDDEN else: to_status = HIDDEN from_status = ENABLED roles = self.api.Command.server_role_find( server_server=fqdn, status=from_status, include_master=True, )['result'] from_roles = [r[u'role_servrole'] for r in roles] if not from_roles: # no server role is in source status raise errors.EmptyModlist if to_status == ENABLED: enable_services(fqdn) else: self._check_hide_server(fqdn) hide_services(fqdn) # update system roles result = self.api.Command.dns_update_system_records() if not result.get('value'): self.add_message(messages.AutomaticDNSRecordsUpdateFailed()) return { 'value': fqdn, 'result': True, }
class dnsserver(LDAPObject): """ DNS Servers """ container_dn = api.env.container_dnsservers object_name = _('DNS server') object_name_plural = _('DNS servers') object_class = dnsserver_object_class default_attributes = [ 'idnsServerId', 'idnsSOAmName', 'idnsForwarders', 'idnsForwardPolicy', ] label = _('DNS Servers') label_singular = _('DNS Server') permission_filter_objectclasses = ['idnsServerConfigObject'] managed_permissions = { 'System: Read DNS Servers Configuration': { 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'idnsServerId', 'idnsSOAmName', 'idnsForwarders', 'idnsForwardPolicy', 'idnsSubstitutionVariable', }, 'ipapermlocation': api.env.basedn, 'default_privileges': {'DNS Servers', 'DNS Administrators'}, }, 'System: Modify DNS Servers Configuration': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'idnsSOAmName', 'idnsForwarders', 'idnsForwardPolicy', 'idnsSubstitutionVariable', }, 'ipapermlocation': api.env.basedn, 'default_privileges': {'DNS Administrators'}, }, } takes_params = ( Str( 'idnsserverid', hostname_validator, cli_name='hostname', primary_key=True, label=_('Server name'), doc=_('DNS Server name'), normalizer=normalize_hostname, ), DNSNameParam( 'idnssoamname?', cli_name='soa_mname_override', label=_('SOA mname override'), doc=_('SOA mname (authoritative server) override'), ), Str( 'idnsforwarders*', validate_bind_forwarder, cli_name='forwarder', label=_('Forwarders'), doc=_('Per-server forwarders. A custom port can be specified ' 'for each forwarder using a standard format ' '"IP_ADDRESS port PORT"'), ), StrEnum( 'idnsforwardpolicy?', cli_name='forward_policy', label=_('Forward policy'), doc=_('Per-server conditional forwarding policy. Set to "none" to ' 'disable forwarding to global forwarder for this zone. In ' 'that case, conditional zone forwarders are disregarded.'), values=(u'only', u'first', u'none'), ), ) def get_dn(self, *keys, **options): if not dns_container_exists(self.api.Backend.ldap2): raise errors.NotFound(reason=_('DNS is not configured')) return super(dnsserver, self).get_dn(*keys, **options)
class migrate_ds(Command): __doc__ = _('Migrate users and groups from DS to IPA.') migrate_objects = { # OBJECT_NAME: (search_filter, pre_callback, post_callback) # # OBJECT_NAME - is the name of an LDAPObject subclass # search_filter - is the filter to retrieve objects from DS # pre_callback - is called for each object just after it was # retrieved from DS and before being added to IPA # post_callback - is called for each object after it was added to IPA # exc_callback - is called when adding entry to IPA raises an exception # # {pre, post}_callback parameters: # ldap - ldap2 instance connected to IPA # pkey - primary key value of the object (uid for users, etc.) # dn - dn of the object as it (will be/is) stored in IPA # entry_attrs - attributes of the object # failed - a list of so-far failed objects # config - IPA config entry attributes # ctx - object context, used to pass data between callbacks # # If pre_callback return value evaluates to False, migration # of the current object is aborted. 'user': { 'filter_template': '(&(|%s)(uid=*))', 'oc_option': 'userobjectclass', 'oc_blocklist_option': 'userignoreobjectclass', 'attr_blocklist_option': 'userignoreattribute', 'pre_callback': _pre_migrate_user, 'post_callback': _post_migrate_user, 'exc_callback': None }, 'group': { 'filter_template': '(&(|%s)(cn=*))', 'oc_option': 'groupobjectclass', 'oc_blocklist_option': 'groupignoreobjectclass', 'attr_blocklist_option': 'groupignoreattribute', 'pre_callback': _pre_migrate_group, 'post_callback': None, 'exc_callback': _group_exc_callback, }, } migrate_order = ('user', 'group') takes_args = ( Str( 'ldapuri', validate_ldapuri, cli_name='ldap_uri', label=_('LDAP URI'), doc=_('LDAP URI of DS server to migrate from'), ), Password( 'bindpw', cli_name='password', label=_('Password'), confirm=False, doc=_('bind password'), ), ) takes_options = ( DNParam('binddn?', cli_name='bind_dn', label=_('Bind DN'), default=DN(('cn', 'directory manager')), autofill=True, ), DNParam('usercontainer', cli_name='user_container', label=_('User container'), doc=_('DN of container for users in DS relative to base DN'), default=DN(('ou', 'people')), autofill=True, ), DNParam('groupcontainer', cli_name='group_container', label=_('Group container'), doc=_('DN of container for groups in DS relative to base DN'), default=DN(('ou', 'groups')), autofill=True, ), Str('userobjectclass+', cli_name='user_objectclass', label=_('User object class'), doc=_('Objectclasses used to search for user entries in DS'), default=(u'person',), autofill=True, ), Str('groupobjectclass+', cli_name='group_objectclass', label=_('Group object class'), doc=_('Objectclasses used to search for group entries in DS'), default=(u'groupOfUniqueNames', u'groupOfNames'), autofill=True, ), Str('userignoreobjectclass*', cli_name='user_ignore_objectclass', label=_('Ignore user object class'), doc=_('Objectclasses to be ignored for user entries in DS'), default=tuple(), autofill=True, ), Str('userignoreattribute*', cli_name='user_ignore_attribute', label=_('Ignore user attribute'), doc=_('Attributes to be ignored for user entries in DS'), default=tuple(), autofill=True, ), Str('groupignoreobjectclass*', cli_name='group_ignore_objectclass', label=_('Ignore group object class'), doc=_('Objectclasses to be ignored for group entries in DS'), default=tuple(), autofill=True, ), Str('groupignoreattribute*', cli_name='group_ignore_attribute', label=_('Ignore group attribute'), doc=_('Attributes to be ignored for group entries in DS'), default=tuple(), autofill=True, ), Flag('groupoverwritegid', cli_name='group_overwrite_gid', label=_('Overwrite GID'), doc=_('When migrating a group already existing in IPA domain overwrite the '\ 'group GID and report as success'), ), StrEnum('schema?', cli_name='schema', label=_('LDAP schema'), doc=_('The schema used on the LDAP server. Supported values are RFC2307 and RFC2307bis. The default is RFC2307bis'), values=_supported_schemas, default=_supported_schemas[0], autofill=True, ), Flag('continue?', label=_('Continue'), doc=_('Continuous operation mode. Errors are reported but the process continues'), default=False, ), DNParam('basedn?', cli_name='base_dn', label=_('Base DN'), doc=_('Base DN on remote LDAP server'), ), Flag('compat?', cli_name='with_compat', label=_('Ignore compat plugin'), doc=_('Allows migration despite the usage of compat plugin'), default=False, ), Str('cacertfile?', cli_name='ca_cert_file', label=_('CA certificate'), doc=_('Load CA certificate of LDAP server from FILE'), default=None, noextrawhitespace=False, ), Bool('use_def_group?', cli_name='use_default_group', label=_('Add to default group'), doc=_('Add migrated users without a group to a default group ' '(default: true)'), default=True, autofill=True, ), StrEnum('scope', cli_name='scope', label=_('Search scope'), doc=_('LDAP search scope for users and groups: base, ' 'onelevel, or subtree. Defaults to onelevel'), values=sorted(_supported_scopes), default=_default_scope, autofill=True, ), ) has_output = ( output.Output( 'result', type=dict, doc=_('Lists of objects migrated; categorized by type.'), ), output.Output( 'failed', type=dict, doc= _('Lists of objects that could not be migrated; categorized by type.' ), ), output.Output( 'enabled', type=bool, doc=_('False if migration mode was disabled.'), ), output.Output( 'compat', type=bool, doc= _('False if migration fails because the compatibility plug-in is enabled.' ), ), ) exclude_doc = _('%s to exclude from migration') truncated_err_msg = _('''\ search results for objects to be migrated have been truncated by the server; migration process might be incomplete\n''') def get_options(self): """ Call get_options of the baseclass and add "exclude" options for each type of object being migrated. """ for option in super(migrate_ds, self).get_options(): yield option for ldap_obj_name in self.migrate_objects: ldap_obj = self.api.Object[ldap_obj_name] name = 'exclude_%ss' % to_cli(ldap_obj_name) doc = self.exclude_doc % ldap_obj.object_name_plural yield Str('%s*' % name, cli_name=name, doc=doc, default=tuple(), autofill=True) def normalize_options(self, options): """ Convert all "exclude" option values to lower-case. Also, empty List parameters are converted to None, but the migration plugin doesn't like that - convert back to empty lists. """ names = [ 'userobjectclass', 'groupobjectclass', 'userignoreobjectclass', 'userignoreattribute', 'groupignoreobjectclass', 'groupignoreattribute' ] names.extend('exclude_%ss' % to_cli(n) for n in self.migrate_objects) for name in names: if options[name]: options[name] = tuple(v.lower() for v in options[name]) else: options[name] = tuple() def _get_search_bases(self, options, ds_base_dn, migrate_order): search_bases = dict() for ldap_obj_name in migrate_order: container = options.get('%scontainer' % to_cli(ldap_obj_name)) if container: # Don't append base dn if user already appended it in the container dn if container.endswith(ds_base_dn): search_base = container else: search_base = DN(container, ds_base_dn) else: search_base = ds_base_dn search_bases[ldap_obj_name] = search_base return search_bases def migrate(self, ldap, config, ds_ldap, ds_base_dn, options): """ Migrate objects from DS to LDAP. """ assert isinstance(ds_base_dn, DN) migrated = {} # {'OBJ': ['PKEY1', 'PKEY2', ...], ...} failed = {} # {'OBJ': {'PKEY1': 'Failed 'cos blabla', ...}, ...} search_bases = self._get_search_bases(options, ds_base_dn, self.migrate_order) migration_start = datetime.datetime.now() scope = _supported_scopes[options.get('scope')] for ldap_obj_name in self.migrate_order: ldap_obj = self.api.Object[ldap_obj_name] template = self.migrate_objects[ldap_obj_name]['filter_template'] oc_list = options[to_cli( self.migrate_objects[ldap_obj_name]['oc_option'])] search_filter = construct_filter(template, oc_list) exclude = options['exclude_%ss' % to_cli(ldap_obj_name)] context = dict(ds_ldap=ds_ldap) migrated[ldap_obj_name] = [] failed[ldap_obj_name] = {} try: entries, truncated = ds_ldap.find_entries( search_filter, ['*'], search_bases[ldap_obj_name], scope, time_limit=0, size_limit=-1) except errors.NotFound: if not options.get('continue', False): raise errors.NotFound( reason= _('%(container)s LDAP search did not return any result ' '(search base: %(search_base)s, ' 'objectclass: %(objectclass)s)') % { 'container': ldap_obj_name, 'search_base': search_bases[ldap_obj_name], 'objectclass': ', '.join(oc_list) }) else: truncated = False entries = [] if truncated: logger.error('%s: %s', ldap_obj.name, self.truncated_err_msg) blocklists = {} for blocklist in ('oc_blocklist', 'attr_blocklist'): blocklist_option = ( self.migrate_objects[ldap_obj_name][blocklist + '_option']) if blocklist_option is not None: blocklists[blocklist] = options.get( blocklist_option, tuple()) else: blocklists[blocklist] = tuple() # get default primary group for new users if 'def_group_dn' not in context and options.get('use_def_group'): def_group = config.get('ipadefaultprimarygroup') context['def_group_dn'] = api.Object.group.get_dn(def_group) try: ldap.get_entry(context['def_group_dn'], ['gidnumber', 'cn']) except errors.NotFound: error_msg = _('Default group for new users not found') raise errors.NotFound(reason=error_msg) context['has_upg'] = ldap.has_upg() valid_gids = set() invalid_gids = set() migrate_cnt = 0 context['migrate_cnt'] = 0 for entry_attrs in entries: context['migrate_cnt'] = migrate_cnt s = datetime.datetime.now() ava = entry_attrs.dn[0][0] if ava.attr == ldap_obj.primary_key.name: # In case if pkey attribute is in the migrated object DN # and the original LDAP is multivalued, make sure that # we pick the correct value (the unique one stored in DN) pkey = ava.value.lower() else: pkey = entry_attrs[ldap_obj.primary_key.name][0].lower() if pkey in exclude: continue entry_attrs.dn = ldap_obj.get_dn(pkey) entry_attrs['objectclass'] = list( set( config.get(ldap_obj.object_class_config, ldap_obj.object_class) + [o.lower() for o in entry_attrs['objectclass']])) entry_attrs[ldap_obj.primary_key.name][0] = entry_attrs[ ldap_obj.primary_key.name][0].lower() callback = self.migrate_objects[ldap_obj_name]['pre_callback'] if callable(callback): try: entry_attrs.dn = callback(ldap, pkey, entry_attrs.dn, entry_attrs, failed[ldap_obj_name], config, context, schema=options['schema'], search_bases=search_bases, valid_gids=valid_gids, invalid_gids=invalid_gids, **blocklists) if not entry_attrs.dn: continue except errors.NotFound as e: failed[ldap_obj_name][pkey] = unicode(e.reason) continue try: ldap.add_entry(entry_attrs) except errors.ExecutionError as e: callback = self.migrate_objects[ldap_obj_name][ 'exc_callback'] if callable(callback): try: callback(ldap, entry_attrs.dn, entry_attrs, e, options) except errors.ExecutionError as e2: failed[ldap_obj_name][pkey] = unicode(e2) continue else: failed[ldap_obj_name][pkey] = unicode(e) continue migrated[ldap_obj_name].append(pkey) callback = self.migrate_objects[ldap_obj_name]['post_callback'] if callable(callback): callback(ldap, pkey, entry_attrs.dn, entry_attrs, failed[ldap_obj_name], config, context) e = datetime.datetime.now() d = e - s total_dur = e - migration_start migrate_cnt += 1 if migrate_cnt > 0 and migrate_cnt % 100 == 0: logger.info("%d %ss migrated. %s elapsed.", migrate_cnt, ldap_obj_name, total_dur) logger.debug("%d %ss migrated, duration: %s (total %s)", migrate_cnt, ldap_obj_name, d, total_dur) if 'def_group_dn' in context: _update_default_group(ldap, context, True) return (migrated, failed) def execute(self, ldapuri, bindpw, **options): ldap = self.api.Backend.ldap2 self.normalize_options(options) config = ldap.get_ipa_config() ds_base_dn = options.get('basedn') if ds_base_dn is not None: assert isinstance(ds_base_dn, DN) # check if migration mode is enabled if config.get('ipamigrationenabled', ('FALSE', ))[0] == 'FALSE': return dict(result={}, failed={}, enabled=False, compat=True) # connect to DS if options.get('cacertfile') is not None: # store CA cert into file tmp_ca_cert_f = write_tmp_file(options['cacertfile']) cacert = tmp_ca_cert_f.name # start TLS connection or STARTTLS ds_ldap = LDAPClient(ldapuri, cacert=cacert, start_tls=True) ds_ldap.simple_bind(options['binddn'], bindpw) tmp_ca_cert_f.close() else: ds_ldap = LDAPClient(ldapuri) ds_ldap.simple_bind(options['binddn'], bindpw, insecure_bind=True) # check whether the compat plugin is enabled if not options.get('compat'): try: ldap.get_entry( DN(('cn', 'users'), ('cn', 'compat'), (api.env.basedn))) return dict(result={}, failed={}, enabled=True, compat=False) except errors.NotFound: pass if not ds_base_dn: # retrieve base DN from remote LDAP server entries, _truncated = ds_ldap.find_entries( '', ['namingcontexts', 'defaultnamingcontext'], DN(''), ds_ldap.SCOPE_BASE, size_limit=-1, time_limit=0, ) if 'defaultnamingcontext' in entries[0]: ds_base_dn = DN(entries[0]['defaultnamingcontext'][0]) assert isinstance(ds_base_dn, DN) else: try: ds_base_dn = DN(entries[0]['namingcontexts'][0]) assert isinstance(ds_base_dn, DN) except (IndexError, KeyError) as e: raise Exception(str(e)) # migrate! (migrated, failed) = self.migrate(ldap, config, ds_ldap, ds_base_dn, options) return dict(result=migrated, failed=failed, enabled=True, compat=True)
class netgroup(LDAPObject): """ Netgroup object. """ container_dn = api.env.container_netgroup object_name = _('netgroup') object_name_plural = _('netgroups') object_class = ['ipaobject', 'ipaassociation', 'ipanisnetgroup'] permission_filter_objectclasses = ['ipanisnetgroup'] default_attributes = [ 'cn', 'description', 'memberof', 'externalhost', 'nisdomainname', 'memberuser', 'memberhost', 'member', 'memberindirect', 'usercategory', 'hostcategory', ] uuid_attribute = 'ipauniqueid' rdn_attribute = 'ipauniqueid' attribute_members = { 'member': ['netgroup'], 'memberof': ['netgroup'], 'memberindirect': ['netgroup'], 'memberuser': ['user', 'group'], 'memberhost': ['host', 'hostgroup'], } relationships = { 'member': ('Member', '', 'no_'), 'memberof': ('Member Of', 'in_', 'not_in_'), 'memberindirect': ('Indirect Member', None, 'no_indirect_'), 'memberuser': ('Member', '', 'no_'), 'memberhost': ('Member', '', 'no_'), } managed_permissions = { 'System: Read Netgroups': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cn', 'description', 'hostcategory', 'ipaenabledflag', 'ipauniqueid', 'nisdomainname', 'usercategory', 'objectclass', }, }, 'System: Read Netgroup Membership': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'externalhost', 'member', 'memberof', 'memberuser', 'memberhost', 'objectclass', }, }, 'System: Add Netgroups': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=ng,cn=alt,$SUFFIX")(version 3.0;acl "permission:Add netgroups";allow (add) groupdn = "ldap:///cn=Add netgroups,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Netgroups Administrators'}, }, 'System: Modify Netgroup Membership': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'externalhost', 'member', 'memberhost', 'memberuser'}, 'replaces': [ '(targetattr = "memberhost || externalhost || memberuser || member")(target = "ldap:///ipauniqueid=*,cn=ng,cn=alt,$SUFFIX")(version 3.0;acl "permission:Modify netgroup membership";allow (write) groupdn = "ldap:///cn=Modify netgroup membership,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Netgroups Administrators'}, }, 'System: Modify Netgroups': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'description'}, 'replaces': [ '(targetattr = "description")(target = "ldap:///ipauniqueid=*,cn=ng,cn=alt,$SUFFIX")(version 3.0; acl "permission:Modify netgroups";allow (write) groupdn = "ldap:///cn=Modify netgroups,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Netgroups Administrators'}, }, 'System: Remove Netgroups': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=ng,cn=alt,$SUFFIX")(version 3.0;acl "permission:Remove netgroups";allow (delete) groupdn = "ldap:///cn=Remove netgroups,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Netgroups Administrators'}, }, 'System: Read Netgroup Compat Tree': { 'non_object': True, 'ipapermbindruletype': 'anonymous', 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN('cn=ng', 'cn=compat', api.env.basedn), 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'cn', 'membernisnetgroup', 'nisnetgrouptriple', }, }, } label = _('Netgroups') label_singular = _('Netgroup') takes_params = ( Str( 'cn', pattern=NETGROUP_PATTERN, pattern_errmsg=NETGROUP_PATTERN_ERRMSG, cli_name='name', label=_('Netgroup name'), primary_key=True, normalizer=lambda value: value.lower(), ), Str( 'description?', cli_name='desc', label=_('Description'), doc=_('Netgroup description'), ), Str( 'nisdomainname?', pattern=NISDOMAIN_PATTERN, pattern_errmsg=NISDOMAIN_PATTERN_ERRMSG, cli_name='nisdomain', label=_('NIS domain name'), ), Str( 'ipauniqueid?', cli_name='uuid', label='IPA unique ID', doc=_('IPA unique ID'), flags=['no_create', 'no_update'], ), StrEnum( 'usercategory?', cli_name='usercat', label=_('User category'), doc=_('User category the rule applies to'), values=(u'all', ), ), StrEnum( 'hostcategory?', cli_name='hostcat', label=_('Host category'), doc=_('Host category the rule applies to'), values=(u'all', ), ), external_host_param, )
class config(LDAPObject): """ IPA configuration object """ object_name = _('configuration options') default_attributes = [ 'ipamaxusernamelength', 'ipahomesrootdir', 'ipadefaultloginshell', 'ipadefaultprimarygroup', 'ipadefaultemaildomain', 'ipasearchtimelimit', 'ipasearchrecordslimit', 'ipausersearchfields', 'ipagroupsearchfields', 'ipamigrationenabled', 'ipacertificatesubjectbase', 'ipapwdexpadvnotify', 'ipaselinuxusermaporder', 'ipaselinuxusermapdefault', 'ipaconfigstring', 'ipakrbauthzdata', 'ipauserauthtype', 'ipadomainresolutionorder' ] container_dn = DN(('cn', 'ipaconfig'), ('cn', 'etc')) permission_filter_objectclasses = ['ipaguiconfig'] managed_permissions = { 'System: Read Global Configuration': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cn', 'objectclass', 'ipacertificatesubjectbase', 'ipaconfigstring', 'ipadefaultemaildomain', 'ipadefaultloginshell', 'ipadefaultprimarygroup', 'ipadomainresolutionorder', 'ipagroupobjectclasses', 'ipagroupsearchfields', 'ipahomesrootdir', 'ipakrbauthzdata', 'ipamaxusernamelength', 'ipamigrationenabled', 'ipapwdexpadvnotify', 'ipaselinuxusermapdefault', 'ipaselinuxusermaporder', 'ipasearchrecordslimit', 'ipasearchtimelimit', 'ipauserauthtype', 'ipauserobjectclasses', 'ipausersearchfields', 'ipacustomfields', }, }, } label = _('Configuration') label_singular = _('Configuration') takes_params = ( Int('ipamaxusernamelength', cli_name='maxusername', label=_('Maximum username length'), minvalue=1, maxvalue=255, ), IA5Str('ipahomesrootdir', cli_name='homedirectory', label=_('Home directory base'), doc=_('Default location of home directories'), ), Str('ipadefaultloginshell', cli_name='defaultshell', label=_('Default shell'), doc=_('Default shell for new users'), ), Str('ipadefaultprimarygroup', cli_name='defaultgroup', label=_('Default users group'), doc=_('Default group for new users'), ), Str('ipadefaultemaildomain?', cli_name='emaildomain', label=_('Default e-mail domain'), doc=_('Default e-mail domain'), ), Int('ipasearchtimelimit', cli_name='searchtimelimit', label=_('Search time limit'), doc=_('Maximum amount of time (seconds) for a search (-1 or 0 is unlimited)'), minvalue=-1, ), Int('ipasearchrecordslimit', cli_name='searchrecordslimit', label=_('Search size limit'), doc=_('Maximum number of records to search (-1 or 0 is unlimited)'), minvalue=-1, ), IA5Str('ipausersearchfields', cli_name='usersearch', label=_('User search fields'), doc=_('A comma-separated list of fields to search in when searching for users'), ), IA5Str('ipagroupsearchfields', cli_name='groupsearch', label=_('Group search fields'), doc=_('A comma-separated list of fields to search in when searching for groups'), ), Bool('ipamigrationenabled', cli_name='enable_migration', label=_('Enable migration mode'), doc=_('Enable migration mode'), ), DNParam('ipacertificatesubjectbase', cli_name='subject', label=_('Certificate Subject base'), doc=_('Base for certificate subjects (OU=Test,O=Example)'), flags=['no_update'], ), Str('ipagroupobjectclasses+', cli_name='groupobjectclasses', label=_('Default group objectclasses'), doc=_('Default group objectclasses (comma-separated list)'), ), Str('ipauserobjectclasses+', cli_name='userobjectclasses', label=_('Default user objectclasses'), doc=_('Default user objectclasses (comma-separated list)'), ), Int('ipapwdexpadvnotify', cli_name='pwdexpnotify', label=_('Password Expiration Notification (days)'), doc=_('Number of days\'s notice of impending password expiration'), minvalue=0, ), StrEnum('ipaconfigstring*', cli_name='ipaconfigstring', label=_('Password plugin features'), doc=_('Extra hashes to generate in password plug-in'), values=(u'AllowNThash', u'KDC:Disable Last Success', u'KDC:Disable Lockout', u'KDC:Disable Default Preauth for SPNs'), ), Str('ipaselinuxusermaporder', label=_('SELinux user map order'), doc=_('Order in increasing priority of SELinux users, delimited by $'), ), Str('ipaselinuxusermapdefault?', label=_('Default SELinux user'), doc=_('Default SELinux user when no match is found in SELinux map rule'), ), StrEnum('ipakrbauthzdata*', cli_name='pac_type', label=_('Default PAC types'), doc=_('Default types of PAC supported for services'), values=(u'MS-PAC', u'PAD', u'nfs:NONE'), ), StrEnum('ipauserauthtype*', cli_name='user_auth_type', label=_('Default user authentication types'), doc=_('Default types of supported user authentication'), values=(u'password', u'radius', u'otp', u'disabled'), ), Str( 'ipa_master_server*', label=_('IPA masters'), doc=_('List of all IPA masters'), flags={'virtual_attribute', 'no_create', 'no_update'} ), Str( 'ca_server_server*', label=_('IPA CA servers'), doc=_('IPA servers configured as certificate authority'), flags={'virtual_attribute', 'no_create', 'no_update'} ), Str( 'ntp_server_server*', label=_('IPA NTP servers'), doc=_('IPA servers with enabled NTP'), flags={'virtual_attribute', 'no_create', 'no_update'} ), Str( 'ca_renewal_master_server?', label=_('IPA CA renewal master'), doc=_('Renewal master for IPA certificate authority'), flags={'virtual_attribute', 'no_create'} ), Str( 'pkinit_server_server*', label=_('IPA master capable of PKINIT'), doc=_('IPA master which can process PKINIT requests'), flags={'virtual_attribute', 'no_create', 'no_update'} ), Str( 'ipadomainresolutionorder?', cli_name='domain_resolution_order', label=_('Domain resolution order'), doc=_('colon-separated list of domains used for short name' ' qualification') ) ) def get_dn(self, *keys, **kwargs): return DN(('cn', 'ipaconfig'), ('cn', 'etc'), api.env.basedn) def update_entry_with_role_config(self, role_name, entry_attrs): backend = self.api.Backend.serverroles try: role_config = backend.config_retrieve(role_name) except errors.EmptyResult: # No role config means current user identity # has no rights to see it, return with no action return for key, value in role_config.items(): try: entry_attrs.update({key: value}) except errors.EmptyResult: # An update that doesn't change an entry is fine here # Just ignore and move to the next key pair pass def show_servroles_attributes(self, entry_attrs, *roles, **options): if options.get('raw', False): return for role in roles: self.update_entry_with_role_config(role, entry_attrs) def gather_trusted_domains(self): """ Aggregate all trusted domains into a dict keyed by domain names with values corresponding to domain status (enabled/disabled) """ command = self.api.Command try: ad_forests = command.trust_find(sizelimit=0)['result'] except errors.NotFound: return {} trusted_domains = {} for forest_name in [a['cn'][0] for a in ad_forests]: forest_domains = command.trustdomain_find( forest_name, sizelimit=0)['result'] trusted_domains.update( { dom['cn'][0]: dom['domain_enabled'][0] for dom in forest_domains if 'domain_enabled' in dom } ) return trusted_domains def _validate_single_domain(self, attr_name, domain, known_domains): """ Validate a single domain from domain resolution order :param attr_name: name of attribute that holds domain resolution order :param domain: domain name :param known_domains: dict of domains known to IPA keyed by domain name and valued by boolean value corresponding to domain status (enabled/disabled) :raises: ValidationError if the domain name is empty, syntactically invalid or corresponds to a disable domain NotFound if a syntactically correct domain name unknown to IPA is supplied (not IPA domain and not any of trusted domains) """ if not domain: raise errors.ValidationError( name=attr_name, error=_("Empty domain is not allowed") ) try: validate_domain_name(domain) except ValueError as e: raise errors.ValidationError( name=attr_name, error=_("Invalid domain name '%(domain)s': %(e)s") % dict(domain=domain, e=e)) if domain not in known_domains: raise errors.NotFound( reason=_("Server has no information about domain '%(domain)s'") % dict(domain=domain) ) if not known_domains[domain]: raise errors.ValidationError( name=attr_name, error=_("Disabled domain '%(domain)s' is not allowed") % dict(domain=domain) ) def validate_domain_resolution_order(self, entry_attrs): """ Validate domain resolution order, e.g. split by the delimiter (colon) and check each domain name for non-emptiness, syntactic correctness, and status (enabled/disabled). supplying empty order (':') bypasses validations and allows to specify empty attribute value. """ attr_name = 'ipadomainresolutionorder' if attr_name not in entry_attrs: return domain_resolution_order = entry_attrs[attr_name] # setting up an empty string means that the previous configuration has # to be cleaned up/removed. So, do nothing and let it pass if not domain_resolution_order: return # empty resolution order is signalized by single separator, do nothing # and let it pass if domain_resolution_order == DOMAIN_RESOLUTION_ORDER_SEPARATOR: return submitted_domains = domain_resolution_order.split( DOMAIN_RESOLUTION_ORDER_SEPARATOR) known_domains = self.gather_trusted_domains() # add FreeIPA domain to the list of domains. This one is always enabled known_domains.update({self.api.env.domain: True}) for domain in submitted_domains: self._validate_single_domain(attr_name, domain, known_domains)
class vault_archive_internal(PKQuery): __doc__ = _('Archive data into a vault.') NO_CLI = True takes_options = vault_options + ( Bytes( 'session_key', doc=_('Session key wrapped with transport certificate'), ), Bytes( 'vault_data', doc=_('Vault data encrypted with session key'), ), Bytes( 'nonce', doc=_('Nonce'), ), StrEnum( 'wrapping_algo?', doc=_('Key wrapping algorithm'), values=VAULT_WRAPPING_SUPPORTED_ALGOS, default=VAULT_WRAPPING_DEFAULT_ALGO, autofill=True, ), ) has_output = output.standard_entry msg_summary = _('Archived data into vault "%(value)s"') def execute(self, *args, **options): if not self.api.Command.kra_is_enabled()['result']: raise errors.InvocationError( format=_('KRA service is not enabled')) wrapped_vault_data = options.pop('vault_data') nonce = options.pop('nonce') wrapped_session_key = options.pop('session_key') wrapping_algo = options.pop('wrapping_algo', None) algorithm_oid = self.obj._translate_algorithm(wrapping_algo) # retrieve vault info vault = self.api.Command.vault_show(*args, **options)['result'] # connect to KRA with self.api.Backend.kra.get_client() as kra_client: kra_account = pki.account.AccountClient(kra_client.connection) kra_account.login() client_key_id = self.obj.get_key_id(vault['dn']) # deactivate existing vault record in KRA response = kra_client.keys.list_keys( client_key_id, pki.key.KeyClient.KEY_STATUS_ACTIVE) for key_info in response.key_infos: kra_client.keys.modify_key_status( key_info.get_key_id(), pki.key.KeyClient.KEY_STATUS_INACTIVE) # forward wrapped data to KRA kra_client.keys.archive_encrypted_data( client_key_id, pki.key.KeyClient.PASS_PHRASE_TYPE, wrapped_vault_data, wrapped_session_key, algorithm_oid=algorithm_oid, nonce_iv=nonce, ) kra_account.logout() response = { 'value': args[-1], 'result': {}, } response['summary'] = self.msg_summary % response return response
class selinuxusermap(LDAPObject): """ SELinux User Map object. """ container_dn = api.env.container_selinux object_name = _('SELinux User Map rule') object_name_plural = _('SELinux User Map rules') object_class = ['ipaassociation', 'ipaselinuxusermap'] permission_filter_objectclasses = ['ipaselinuxusermap'] default_attributes = [ 'cn', 'ipaenabledflag', 'description', 'usercategory', 'hostcategory', 'ipaenabledflag', 'memberuser', 'memberhost', 'seealso', 'ipaselinuxuser', ] uuid_attribute = 'ipauniqueid' rdn_attribute = 'ipauniqueid' attribute_members = { 'memberuser': ['user', 'group'], 'memberhost': ['host', 'hostgroup'], } managed_permissions = { 'System: Read SELinux User Maps': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'accesstime', 'cn', 'description', 'hostcategory', 'ipaenabledflag', 'ipaselinuxuser', 'ipauniqueid', 'memberhost', 'memberuser', 'seealso', 'usercategory', 'objectclass', 'member', }, }, 'System: Add SELinux User Maps': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=usermap,cn=selinux,$SUFFIX")(version 3.0;acl "permission:Add SELinux User Maps";allow (add) groupdn = "ldap:///cn=Add SELinux User Maps,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'SELinux User Map Administrators'}, }, 'System: Modify SELinux User Maps': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'cn', 'ipaenabledflag', 'ipaselinuxuser', 'memberhost', 'memberuser', 'seealso' }, 'replaces': [ '(targetattr = "cn || memberuser || memberhost || seealso || ipaselinuxuser || ipaenabledflag")(target = "ldap:///ipauniqueid=*,cn=usermap,cn=selinux,$SUFFIX")(version 3.0;acl "permission:Modify SELinux User Maps";allow (write) groupdn = "ldap:///cn=Modify SELinux User Maps,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'SELinux User Map Administrators'}, }, 'System: Remove SELinux User Maps': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=usermap,cn=selinux,$SUFFIX")(version 3.0;acl "permission:Remove SELinux User Maps";allow (delete) groupdn = "ldap:///cn=Remove SELinux User Maps,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'SELinux User Map Administrators'}, }, } # These maps will not show as members of other entries label = _('SELinux User Maps') label_singular = _('SELinux User Map') takes_params = ( Str( 'cn', cli_name='name', label=_('Rule name'), primary_key=True, ), Str( 'ipaselinuxuser', validate_selinuxuser, cli_name='selinuxuser', label=_('SELinux User'), ), Str( 'seealso?', cli_name='hbacrule', label=_('HBAC Rule'), doc=_('HBAC Rule that defines the users, groups and hostgroups'), ), StrEnum( 'usercategory?', cli_name='usercat', label=_('User category'), doc=_('User category the rule applies to'), values=(u'all', ), ), StrEnum( 'hostcategory?', cli_name='hostcat', label=_('Host category'), doc=_('Host category the rule applies to'), values=(u'all', ), ), Str( 'description?', cli_name='desc', label=_('Description'), ), Bool( 'ipaenabledflag?', label=_('Enabled'), flags=['no_option'], ), Str( 'memberuser_user?', label=_('Users'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberuser_group?', label=_('User Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberhost_host?', label=_('Hosts'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberhost_hostgroup?', label=_('Host Groups'), flags=['no_create', 'no_update', 'no_search'], ), ) def _normalize_seealso(self, seealso): """ Given a HBAC rule name verify its existence and return the dn. """ if not seealso: return None try: dn = DN(seealso) return str(dn) except ValueError: try: entry_attrs = self.backend.find_entry_by_attr( self.api.Object['hbacrule'].primary_key.name, seealso, self.api.Object['hbacrule'].object_class, [''], DN(self.api.Object['hbacrule'].container_dn, api.env.basedn)) seealso = entry_attrs.dn except errors.NotFound: raise errors.NotFound( reason=_('HBAC rule %(rule)s not found') % dict(rule=seealso)) return seealso def _convert_seealso(self, ldap, entry_attrs, **options): """ Convert an HBAC rule dn into a name """ if options.get('raw', False): return if 'seealso' in entry_attrs: hbac_attrs = ldap.get_entry(entry_attrs['seealso'][0], ['cn']) entry_attrs['seealso'] = hbac_attrs['cn'][0]
class vault_retrieve_internal(PKQuery): __doc__ = _('Retrieve data from a vault.') NO_CLI = True takes_options = vault_options + ( Bytes( 'session_key', doc=_('Session key wrapped with transport certificate'), ), StrEnum( 'wrapping_algo?', doc=_('Key wrapping algorithm'), values=VAULT_WRAPPING_SUPPORTED_ALGOS, default=VAULT_WRAPPING_DEFAULT_ALGO, autofill=True, ), ) has_output = output.standard_entry msg_summary = _('Retrieved data from vault "%(value)s"') def execute(self, *args, **options): if not self.api.Command.kra_is_enabled()['result']: raise errors.InvocationError( format=_('KRA service is not enabled')) wrapped_session_key = options.pop('session_key') wrapping_algo = options.pop('wrapping_algo', None) algorithm_oid = self.obj._translate_algorithm(wrapping_algo) # retrieve vault info vault = self.api.Command.vault_show(*args, **options)['result'] # connect to KRA with self.api.Backend.kra.get_client() as kra_client: kra_account = pki.account.AccountClient(kra_client.connection) kra_account.login() client_key_id = self.obj.get_key_id(vault['dn']) # find vault record in KRA response = kra_client.keys.list_keys( client_key_id, pki.key.KeyClient.KEY_STATUS_ACTIVE) if not len(response.key_infos): raise errors.NotFound(reason=_('No archived data.')) key_info = response.key_infos[0] # XXX hack kra_client.keys.encrypt_alg_oid = algorithm_oid # retrieve encrypted data from KRA key = kra_client.keys.retrieve_key( key_info.get_key_id(), wrapped_session_key) kra_account.logout() response = { 'value': args[-1], 'result': { 'vault_data': key.encrypted_data, 'nonce': key.nonce_data, }, } response['summary'] = self.msg_summary % response return response
class idrange(LDAPObject): """ Range object. """ range_type = ('domain', 'ad', 'ipa') container_dn = api.env.container_ranges object_name = ('range') object_name_plural = ('ranges') object_class = ['ipaIDrange'] permission_filter_objectclasses = ['ipaidrange'] possible_objectclasses = ['ipadomainidrange', 'ipatrustedaddomainrange'] default_attributes = [ 'cn', 'ipabaseid', 'ipaidrangesize', 'ipabaserid', 'ipasecondarybaserid', 'ipanttrusteddomainsid', 'iparangetype' ] managed_permissions = { 'System: Read ID Ranges': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cn', 'objectclass', 'ipabaseid', 'ipaidrangesize', 'iparangetype', 'ipabaserid', 'ipasecondarybaserid', 'ipanttrusteddomainsid', }, }, } label = _('ID Ranges') label_singular = _('ID Range') # The commented range types are planned but not yet supported range_types = { u'ipa-local': unicode(_('local domain range')), # u'ipa-ad-winsync': unicode(_('Active Directory winsync range')), u'ipa-ad-trust': unicode(_('Active Directory domain range')), u'ipa-ad-trust-posix': unicode(_('Active Directory trust range with ' 'POSIX attributes')), # u'ipa-ipa-trust': unicode(_('IPA trust range')), } takes_params = (Str( 'cn', cli_name='name', label=_('Range name'), primary_key=True, ), Int( 'ipabaseid', cli_name='base_id', label=_("First Posix ID of the range"), ), Int( 'ipaidrangesize', cli_name='range_size', label=_("Number of IDs in the range"), ), Int( 'ipabaserid?', cli_name='rid_base', label=_('First RID of the corresponding RID range'), ), Int( 'ipasecondarybaserid?', cli_name='secondary_rid_base', label=_('First RID of the secondary RID range'), ), Str( 'ipanttrusteddomainsid?', cli_name='dom_sid', flags=('no_update', ), label=_('Domain SID of the trusted domain'), ), Str( 'ipanttrusteddomainname?', cli_name='dom_name', flags=('no_search', 'virtual_attribute', 'no_update'), label=_('Name of the trusted domain'), ), StrEnum( 'iparangetype?', label=_('Range type'), cli_name='type', doc=(_('ID range type, one of {vals}'.format( vals=', '.join(sorted(range_types))))), values=sorted(range_types), flags=['no_update'], )) def handle_iparangetype(self, entry_attrs, options, keep_objectclass=False): if not any( (options.get('pkey_only', False), options.get('raw', False))): range_type = entry_attrs['iparangetype'][0] entry_attrs['iparangetyperaw'] = [range_type] entry_attrs['iparangetype'] = [ self.range_types.get(range_type, None) ] # Remove the objectclass if not keep_objectclass: if not options.get('all', False) or options.get( 'pkey_only', False): entry_attrs.pop('objectclass', None) def handle_ipabaserid(self, entry_attrs, options): if any((options.get('pkey_only', False), options.get('raw', False))): return if entry_attrs['iparangetype'][0] == u'ipa-ad-trust-posix': entry_attrs.pop('ipabaserid', None) def check_ids_in_modified_range(self, old_base, old_size, new_base, new_size): if new_base is None and new_size is None: # nothing to check return if new_base is None: new_base = old_base if new_size is None: new_size = old_size old_interval = (old_base, old_base + old_size - 1) new_interval = (new_base, new_base + new_size - 1) checked_intervals = [] low_diff = new_interval[0] - old_interval[0] if low_diff > 0: checked_intervals.append( (old_interval[0], min(old_interval[1], new_interval[0] - 1))) high_diff = old_interval[1] - new_interval[1] if high_diff > 0: checked_intervals.append( (max(old_interval[0], new_interval[1] + 1), old_interval[1])) if not checked_intervals: # range is equal or covers the entire old range, nothing to check return ldap = self.backend id_filter_base = [ "(objectclass=posixAccount)", "(objectclass=posixGroup)", "(objectclass=ipaIDObject)" ] id_filter_ids = [] for id_low, id_high in checked_intervals: id_filter_ids.append( "(&(uidNumber>=%(low)d)(uidNumber<=%(high)d))" % dict(low=id_low, high=id_high)) id_filter_ids.append( "(&(gidNumber>=%(low)d)(gidNumber<=%(high)d))" % dict(low=id_low, high=id_high)) id_filter = ldap.combine_filters([ ldap.combine_filters(id_filter_base, "|"), ldap.combine_filters(id_filter_ids, "|") ], "&") try: ldap.find_entries(filter=id_filter, attrs_list=['uid', 'cn'], base_dn=DN(api.env.container_accounts, api.env.basedn)) except errors.NotFound: # no objects in this range found, allow the command pass else: raise errors.ValidationError( name="ipabaseid,ipaidrangesize", error=_('range modification leaving objects with ID out ' 'of the defined range is not allowed')) def get_domain_validator(self): if not _dcerpc_bindings_installed: raise errors.NotFound(reason=_( 'Cannot perform SID 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=_( 'Cross-realm trusts are not ' 'configured. Make sure you have run ipa-adtrust-install ' 'on the IPA server first')) return domain_validator def validate_trusted_domain_sid(self, sid): domain_validator = self.get_domain_validator() if not domain_validator.is_trusted_domain_sid_valid(sid): raise errors.ValidationError( name='domain SID', error=_('SID is not recognized as a valid SID for a ' 'trusted domain')) def get_trusted_domain_sid_from_name(self, name): """ Returns unicode string representation for given trusted domain name or None if SID forthe given trusted domain name could not be found.""" domain_validator = self.get_domain_validator() sid = domain_validator.get_sid_from_domain_name(name) if sid is not None: sid = unicode(sid) return sid # checks that primary and secondary rid ranges do not overlap def are_rid_ranges_overlapping(self, rid_base, secondary_rid_base, size): # if any of these is None, the check does not apply if any(attr is None for attr in (rid_base, secondary_rid_base, size)): return False # sort the bases if rid_base > secondary_rid_base: rid_base, secondary_rid_base = secondary_rid_base, rid_base # rid_base is now <= secondary_rid_base, # so the following check is sufficient if rid_base + size <= secondary_rid_base: return False else: return True
class vault(LDAPObject): __doc__ = _(""" Vault object. """) container_dn = api.env.container_vault object_name = _('vault') object_name_plural = _('vaults') object_class = ['ipaVault'] permission_filter_objectclasses = ['ipaVault'] default_attributes = [ 'cn', 'description', 'ipavaulttype', 'ipavaultsalt', 'ipavaultpublickey', 'owner', 'member', ] search_display_attributes = [ 'cn', 'description', 'ipavaulttype', ] attribute_members = { 'owner': ['user', 'group', 'service'], 'member': ['user', 'group', 'service'], } label = _('Vaults') label_singular = _('Vault') managed_permissions = { 'System: Read Vaults': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'cn', 'description', 'ipavaulttype', 'ipavaultsalt', 'ipavaultpublickey', 'owner', 'member', 'memberuser', 'memberhost', }, 'default_privileges': {'Vault Administrators'}, }, 'System: Add Vaults': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'add'}, 'default_privileges': {'Vault Administrators'}, }, 'System: Delete Vaults': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'delete'}, 'default_privileges': {'Vault Administrators'}, }, 'System: Modify Vaults': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'objectclass', 'cn', 'description', 'ipavaulttype', 'ipavaultsalt', 'ipavaultpublickey', }, 'default_privileges': {'Vault Administrators'}, }, 'System: Manage Vault Ownership': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'owner', }, 'default_privileges': {'Vault Administrators'}, }, 'System: Manage Vault Membership': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'member', }, 'default_privileges': {'Vault Administrators'}, }, } takes_params = ( Str( 'cn', cli_name='name', label=_('Vault name'), primary_key=True, pattern='^[a-zA-Z0-9_.-]+$', pattern_errmsg='may only include letters, numbers, _, ., and -', maxlength=255, ), Str( 'description?', cli_name='desc', label=_('Description'), doc=_('Vault description'), ), StrEnum( 'ipavaulttype?', cli_name='type', label=_('Type'), doc=_('Vault type'), values=(u'standard', u'symmetric', u'asymmetric', ), default=u'symmetric', autofill=True, ), Bytes( 'ipavaultsalt?', cli_name='salt', label=_('Salt'), doc=_('Vault salt'), flags=['no_search'], ), Bytes( 'ipavaultpublickey?', cli_name='public_key', label=_('Public key'), doc=_('Vault public key'), flags=['no_search'], ), Str( 'owner_user?', label=_('Owner users'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'owner_group?', label=_('Owner groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'owner_service?', label=_('Owner services'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'owner?', label=_('Failed owners'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'service?', label=_('Vault service'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Flag( 'shared?', label=_('Shared vault'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'username?', label=_('Vault user'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), ) def _translate_algorithm(self, name): if name is None: name = VAULT_WRAPPING_DEFAULT_ALGO if name not in VAULT_WRAPPING_SUPPORTED_ALGOS: msg = _("{algo} is not a supported vault wrapping algorithm") raise errors.ValidationError(msg.format(algo=name)) if name == VAULT_WRAPPING_3DES: return DES_EDE3_CBC_OID elif name == VAULT_WRAPPING_AES128_CBC: return AES_128_CBC_OID else: # unreachable raise ValueError(name) def get_dn(self, *keys, **options): """ Generates vault DN from parameters. """ service = options.get('service') shared = options.get('shared') user = options.get('username') count = (bool(service) + bool(shared) + bool(user)) if count > 1: raise errors.MutuallyExclusiveError( reason=_('Service, shared, and user options ' + 'cannot be specified simultaneously')) # TODO: create container_dn after object initialization then reuse it container_dn = DN(self.container_dn, self.api.env.basedn) dn = super(vault, self).get_dn(*keys, **options) assert dn.endswith(container_dn) rdns = DN(*dn[:-len(container_dn)]) if not count: principal = kerberos.Principal(getattr(context, 'principal')) if principal.is_host: raise errors.NotImplementedError( reason=_('Host is not supported')) elif principal.is_service: service = unicode(principal) else: user = principal.username if service: parent_dn = DN(('cn', service), ('cn', 'services'), container_dn) elif shared: parent_dn = DN(('cn', 'shared'), container_dn) elif user: parent_dn = DN(('cn', user), ('cn', 'users'), container_dn) else: raise RuntimeError return DN(rdns, parent_dn) def create_container(self, dn, owner_dn): """ Creates vault container and its parents. """ # TODO: create container_dn after object initialization then reuse it container_dn = DN(self.container_dn, self.api.env.basedn) entries = [] while dn: assert dn.endswith(container_dn) rdn = dn[0] entry = self.backend.make_entry( dn, { 'objectclass': ['ipaVaultContainer'], 'cn': rdn['cn'], 'owner': [owner_dn], }) # if entry can be added, return try: self.backend.add_entry(entry) break except errors.NotFound: pass # otherwise, create parent entry first dn = DN(*dn[1:]) entries.insert(0, entry) # then create the entries again for entry in entries: self.backend.add_entry(entry) def get_key_id(self, dn): """ Generates a client key ID to archive/retrieve data in KRA. """ # TODO: create container_dn after object initialization then reuse it container_dn = DN(self.container_dn, self.api.env.basedn) # make sure the DN is a vault DN if not dn.endswith(container_dn, 1): raise ValueError('Invalid vault DN: %s' % dn) # construct the vault ID from the bottom up id = u'' for rdn in dn[:-len(container_dn)]: name = rdn['cn'] id = u'/' + name + id return 'ipa:' + id def get_container_attribute(self, entry, options): if options.get('raw', False): return container_dn = DN(self.container_dn, self.api.env.basedn) if entry.dn.endswith(DN(('cn', 'services'), container_dn)): entry['service'] = entry.dn[1]['cn'] elif entry.dn.endswith(DN(('cn', 'shared'), container_dn)): entry['shared'] = True elif entry.dn.endswith(DN(('cn', 'users'), container_dn)): entry['username'] = entry.dn[1]['cn']
class hbacrule(LDAPObject): """ HBAC object. """ container_dn = api.env.container_hbac object_name = _('HBAC rule') object_name_plural = _('HBAC rules') object_class = ['ipaassociation', 'ipahbacrule'] permission_filter_objectclasses = ['ipahbacrule'] default_attributes = [ 'cn', 'ipaenabledflag', 'description', 'usercategory', 'hostcategory', 'servicecategory', 'ipaenabledflag', 'memberuser', 'sourcehost', 'memberhost', 'memberservice', 'externalhost', ] uuid_attribute = 'ipauniqueid' rdn_attribute = 'ipauniqueid' allow_rename = True attribute_members = { 'memberuser': ['user', 'group'], 'memberhost': ['host', 'hostgroup'], 'sourcehost': ['host', 'hostgroup'], 'memberservice': ['hbacsvc', 'hbacsvcgroup'], } managed_permissions = { 'System: Read HBAC Rules': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'accessruletype', 'accesstime', 'cn', 'description', 'externalhost', 'hostcategory', 'ipaenabledflag', 'ipauniqueid', 'memberhost', 'memberservice', 'memberuser', 'servicecategory', 'sourcehost', 'sourcehostcategory', 'usercategory', 'objectclass', 'member', }, }, 'System: Add HBAC Rule': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Add HBAC rule";allow (add) groupdn = "ldap:///cn=Add HBAC rule,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'HBAC Administrator'}, }, 'System: Delete HBAC Rule': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Delete HBAC rule";allow (delete) groupdn = "ldap:///cn=Delete HBAC rule,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'HBAC Administrator'}, }, 'System: Manage HBAC Rule Membership': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'externalhost', 'memberhost', 'memberservice', 'memberuser'}, 'replaces': [ '(targetattr = "memberuser || externalhost || memberservice || memberhost")(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Manage HBAC rule membership";allow (write) groupdn = "ldap:///cn=Manage HBAC rule membership,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'HBAC Administrator'}, }, 'System: Modify HBAC Rule': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'accessruletype', 'accesstime', 'cn', 'description', 'hostcategory', 'ipaenabledflag', 'servicecategory', 'sourcehost', 'sourcehostcategory', 'usercategory' }, 'replaces': [ '(targetattr = "servicecategory || sourcehostcategory || cn || description || ipaenabledflag || accesstime || usercategory || hostcategory || accessruletype || sourcehost")(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Modify HBAC rule";allow (write) groupdn = "ldap:///cn=Modify HBAC rule,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'HBAC Administrator'}, }, } label = _('HBAC Rules') label_singular = _('HBAC Rule') takes_params = ( Str( 'cn', cli_name='name', label=_('Rule name'), primary_key=True, ), StrEnum( 'accessruletype', validate_type, cli_name='type', doc=_('Rule type (allow)'), label=_('Rule type'), values=(u'allow', u'deny'), default=u'allow', autofill=True, exclude='webui', flags=['no_option', 'no_output'], ), # FIXME: {user,host,service}categories should expand in the future StrEnum( 'usercategory?', cli_name='usercat', label=_('User category'), doc=_('User category the rule applies to'), values=(u'all', ), ), StrEnum( 'hostcategory?', cli_name='hostcat', label=_('Host category'), doc=_('Host category the rule applies to'), values=(u'all', ), ), StrEnum( 'sourcehostcategory?', deprecated=True, cli_name='srchostcat', label=_('Source host category'), doc=_('Source host category the rule applies to'), values=(u'all', ), flags={'no_option'}, ), StrEnum( 'servicecategory?', cli_name='servicecat', label=_('Service category'), doc=_('Service category the rule applies to'), values=(u'all', ), ), # AccessTime('accesstime?', # cli_name='time', # label=_('Access time'), # ), Str( 'description?', cli_name='desc', label=_('Description'), ), Bool( 'ipaenabledflag?', label=_('Enabled'), flags=['no_option'], ), Str( 'memberuser_user?', label=_('Users'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberuser_group?', label=_('User Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberhost_host?', label=_('Hosts'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberhost_hostgroup?', label=_('Host Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'sourcehost_host?', deprecated=True, label=_('Source Hosts'), flags=['no_create', 'no_update', 'no_search', 'no_option'], ), Str( 'sourcehost_hostgroup?', deprecated=True, label=_('Source Host Groups'), flags=['no_create', 'no_update', 'no_search', 'no_option'], ), Str( 'memberservice_hbacsvc?', label=_('Services'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberservice_hbacsvcgroup?', label=_('Service Groups'), flags=['no_create', 'no_update', 'no_search'], ), external_host_param, )
perm = perm.strip().lower() if perm not in _valid_permissions_values: return '"%s" is not a valid permission' % perm def _normalize_permissions(perm): valid_permissions = [] perm = perm.strip().lower() if perm not in valid_permissions: valid_permissions.append(perm) return ','.join(valid_permissions) _prefix_option = StrEnum('aciprefix', cli_name='prefix', label=_('ACI prefix'), doc=_('Prefix used to distinguish ACI types ' \ '(permission, delegation, selfservice, none)'), values=_valid_prefix_values, flags={'no_create', 'no_update', 'no_search'}, ) @register() class aci(Object): """ ACI object. """ NO_CLI = True label = _('ACIs') takes_params = (
class baseuser(LDAPObject): """ baseuser object. """ stage_container_dn = api.env.container_stageuser active_container_dn = api.env.container_user delete_container_dn = api.env.container_deleteuser object_class = ['posixaccount'] object_class_config = 'ipauserobjectclasses' possible_objectclasses = [ 'meporiginentry', 'ipauserauthtypeclass', 'ipauser', 'ipatokenradiusproxyuser', 'ipacertmapobject' ] disallow_object_classes = ['krbticketpolicyaux'] permission_filter_objectclasses = ['posixaccount'] search_attributes_config = 'ipausersearchfields' default_attributes = [ 'uid', 'givenname', 'sn', 'homedirectory', 'loginshell', 'uidnumber', 'gidnumber', 'mail', 'ou', 'telephonenumber', 'title', 'memberof', 'nsaccountlock', 'memberofindirect', 'ipauserauthtype', 'userclass', 'ipatokenradiusconfiglink', 'ipatokenradiususername', 'krbprincipalexpiration', 'usercertificate;binary', 'krbprincipalname', 'krbcanonicalname', 'ipacertmapdata' ] search_display_attributes = [ 'uid', 'givenname', 'sn', 'homedirectory', 'krbcanonicalname', 'krbprincipalname', 'loginshell', 'mail', 'telephonenumber', 'title', 'nsaccountlock', 'uidnumber', 'gidnumber', 'sshpubkeyfp', ] uuid_attribute = 'ipauniqueid' attribute_members = { 'manager': ['user'], 'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'], 'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'], } allow_rename = True bindable = True password_attributes = [('userpassword', 'has_password'), ('krbprincipalkey', 'has_keytab')] label = _('Users') label_singular = _('User') takes_params = ( Str('uid', pattern=PATTERN_GROUPUSER_NAME, pattern_errmsg='may only include letters, numbers, _, -, . and $', maxlength=255, cli_name='login', label=_('User login'), primary_key=True, default_from=lambda givenname, sn: givenname[0] + sn, normalizer=lambda value: value.lower(), ), Str('givenname', cli_name='first', label=_('First name'), ), Str('sn', cli_name='last', label=_('Last name'), ), Str('cn', label=_('Full name'), default_from=lambda givenname, sn: '%s %s' % (givenname, sn), autofill=True, ), Str('displayname?', label=_('Display name'), default_from=lambda givenname, sn: '%s %s' % (givenname, sn), autofill=True, ), Str('initials?', label=_('Initials'), default_from=lambda givenname, sn: '%c%c' % (givenname[0], sn[0]), autofill=True, ), Str('homedirectory?', cli_name='homedir', label=_('Home directory'), ), Str('gecos?', label=_('GECOS'), default_from=lambda givenname, sn: '%s %s' % (givenname, sn), autofill=True, ), Str('loginshell?', cli_name='shell', label=_('Login shell'), ), Principal( 'krbcanonicalname?', validate_realm, label=_('Principal name'), flags={'no_option', 'no_create', 'no_update', 'no_search'}, normalizer=normalize_user_principal ), Principal( 'krbprincipalname*', validate_realm, cli_name='principal', label=_('Principal alias'), default_from=lambda uid: kerberos.Principal( uid.lower(), realm=api.env.realm), autofill=True, normalizer=normalize_user_principal, ), DateTime('krbprincipalexpiration?', cli_name='principal_expiration', label=_('Kerberos principal expiration'), ), DateTime('krbpasswordexpiration?', cli_name='password_expiration', label=_('User password expiration'), ), Str('mail*', cli_name='email', label=_('Email address'), ), Password('userpassword?', cli_name='password', label=_('Password'), doc=_('Prompt to set the user password'), # FIXME: This is temporary till bug is fixed causing updates to # bomb out via the webUI. exclude='webui', ), Flag('random?', doc=_('Generate a random user password'), flags=('no_search', 'virtual_attribute'), default=False, ), Str('randompassword?', label=_('Random password'), flags=('no_create', 'no_update', 'no_search', 'virtual_attribute'), ), Int('uidnumber?', cli_name='uid', label=_('UID'), doc=_('User ID Number (system will assign one if not provided)'), minvalue=1, ), Int('gidnumber?', label=_('GID'), doc=_('Group ID Number'), minvalue=1, ), Str('street?', cli_name='street', label=_('Street address'), ), Str('l?', cli_name='city', label=_('City'), ), Str('st?', cli_name='state', label=_('State/Province'), ), Str('postalcode?', label=_('ZIP'), ), Str('telephonenumber*', cli_name='phone', label=_('Telephone Number') ), Str('mobile*', label=_('Mobile Telephone Number') ), Str('pager*', label=_('Pager Number') ), Str('facsimiletelephonenumber*', cli_name='fax', label=_('Fax Number'), ), Str('ou?', cli_name='orgunit', label=_('Org. Unit'), ), Str('title?', label=_('Job Title'), ), # keep backward compatibility using single value manager option Str('manager?', label=_('Manager'), ), Str('carlicense*', label=_('Car License'), ), Str('ipasshpubkey*', validate_sshpubkey, cli_name='sshpubkey', label=_('SSH public key'), normalizer=normalize_sshpubkey, flags=['no_search'], ), Str('sshpubkeyfp*', label=_('SSH public key fingerprint'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), StrEnum('ipauserauthtype*', cli_name='user_auth_type', label=_('User authentication types'), doc=_('Types of supported user authentication'), values=(u'password', u'radius', u'otp'), ), Str('userclass*', cli_name='class', label=_('Class'), doc=_('User category (semantics placed on this attribute are for ' 'local interpretation)'), ), Str('ipatokenradiusconfiglink?', cli_name='radius', label=_('RADIUS proxy configuration'), ), Str('ipatokenradiususername?', cli_name='radius_username', label=_('RADIUS proxy username'), ), Str('departmentnumber*', label=_('Department Number'), ), Str('employeenumber?', label=_('Employee Number'), ), Str('employeetype?', label=_('Employee Type'), ), Str('preferredlanguage?', label=_('Preferred Language'), pattern='^(([a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?(;q\=((0(\.[0-9]{0,3})?)|(1(\.0{0,3})?)))?' \ + '(\s*,\s*[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?(;q\=((0(\.[0-9]{0,3})?)|(1(\.0{0,3})?)))?)*)|(\*))$', pattern_errmsg='must match RFC 2068 - 14.4, e.g., "da, en-gb;q=0.8, en;q=0.7"', ), Bytes('usercertificate*', validate_certificate, cli_name='certificate', label=_('Certificate'), doc=_('Base-64 encoded user certificate'), ), Str( 'ipacertmapdata*', cli_name='certmapdata', label=_('Certificate mapping data'), doc=_('Certificate mapping data'), flags=['no_create', 'no_update', 'no_search'], ), ) def normalize_and_validate_email(self, email, config=None): if not config: config = self.backend.get_ipa_config() # check if default email domain should be added defaultdomain = config.get('ipadefaultemaildomain', [None])[0] if email: norm_email = [] if not isinstance(email, (list, tuple)): email = [email] for m in email: if isinstance(m, six.string_types): if '@' not in m and defaultdomain: m = m + u'@' + defaultdomain if not Email(m): raise errors.ValidationError(name='email', error=_('invalid e-mail format: %(email)s') % dict(email=m)) norm_email.append(m) else: if not Email(m): raise errors.ValidationError(name='email', error=_('invalid e-mail format: %(email)s') % dict(email=m)) norm_email.append(m) return norm_email return email def normalize_manager(self, manager, container): """ Given a userid verify the user's existence (in the appropriate containter) and return the dn. """ if not manager: return None if not isinstance(manager, list): manager = [manager] try: container_dn = DN(container, api.env.basedn) for i, mgr in enumerate(manager): if isinstance(mgr, DN) and mgr.endswith(container_dn): continue entry_attrs = self.backend.find_entry_by_attr( self.primary_key.name, mgr, self.object_class, [''], container_dn ) manager[i] = entry_attrs.dn except errors.NotFound: raise errors.NotFound(reason=_('manager %(manager)s not found') % dict(manager=mgr)) return manager def _user_status(self, user, container): assert isinstance(user, DN) return user.endswith(container) def active_user(self, user): assert isinstance(user, DN) return self._user_status(user, DN(self.active_container_dn, api.env.basedn)) def stage_user(self, user): assert isinstance(user, DN) return self._user_status(user, DN(self.stage_container_dn, api.env.basedn)) def delete_user(self, user): assert isinstance(user, DN) return self._user_status(user, DN(self.delete_container_dn, api.env.basedn)) def convert_usercertificate_pre(self, entry_attrs): if 'usercertificate' in entry_attrs: entry_attrs['usercertificate;binary'] = entry_attrs.pop( 'usercertificate') def convert_usercertificate_post(self, entry_attrs, **options): if 'usercertificate;binary' in entry_attrs: entry_attrs['usercertificate'] = entry_attrs.pop( 'usercertificate;binary') def convert_attribute_members(self, entry_attrs, *keys, **options): super(baseuser, self).convert_attribute_members( entry_attrs, *keys, **options) if options.get("raw", False): return # due the backward compatibility, managers have to be returned in # 'manager' attribute instead of 'manager_user' try: entry_attrs['failed_manager'] = entry_attrs.pop('manager') except KeyError: pass try: entry_attrs['manager'] = entry_attrs.pop('manager_user') except KeyError: pass
class aci(Object): """ ACI object. """ NO_CLI = True label = _('ACIs') takes_params = ( Str('aciname', cli_name='name', label=_('ACI name'), primary_key=True, flags=('virtual_attribute',), ), Str('permission?', cli_name='permission', label=_('Permission'), doc=_('Permission ACI grants access to'), flags=('virtual_attribute',), ), Str('group?', cli_name='group', label=_('User group'), doc=_('User group ACI grants access to'), flags=('virtual_attribute',), ), Str('permissions+', validate_permissions, cli_name='permissions', label=_('Permissions'), doc=_('Permissions to grant' \ '(read, write, add, delete, all)'), normalizer=_normalize_permissions, flags=('virtual_attribute',), ), Str('attrs*', cli_name='attrs', label=_('Attributes to which the permission applies'), doc=_('Attributes'), flags=('virtual_attribute',), ), StrEnum('type?', cli_name='type', label=_('Type'), doc=_('type of IPA object (user, group, host, hostgroup, service, netgroup)'), values=(u'user', u'group', u'host', u'service', u'hostgroup', u'netgroup', u'dnsrecord'), flags=('virtual_attribute',), ), Str('memberof?', cli_name='memberof', label=_('Member of'), # FIXME: Does this label make sense? doc=_('Member of a group'), flags=('virtual_attribute',), ), Str('filter?', cli_name='filter', label=_('Filter'), doc=_('Legal LDAP filter (e.g. ou=Engineering)'), flags=('virtual_attribute',), ), Str('subtree?', cli_name='subtree', label=_('Subtree'), doc=_('Subtree to apply ACI to'), flags=('virtual_attribute',), ), Str('targetgroup?', cli_name='targetgroup', label=_('Target group'), doc=_('Group to apply ACI to'), flags=('virtual_attribute',), ), Flag('selfaci?', cli_name='self', label=_('Target your own entry (self)'), doc=_('Apply ACI to your own entry (self)'), flags=('virtual_attribute',), ), _prefix_option, Str('aci', label=_('ACI'), flags={'no_create', 'no_update', 'no_search'}, ), )