diff options
-rw-r--r-- | API.txt | 9 | ||||
-rw-r--r-- | install/share/65ipasudo.ldif (renamed from install/share/60ipasudo.ldif) | 2 | ||||
-rw-r--r-- | install/share/Makefile.am | 2 | ||||
-rw-r--r-- | install/updates/10-sudo.update | 2 | ||||
-rw-r--r-- | ipalib/plugins/sudorule.py | 41 | ||||
-rw-r--r-- | ipaserver/install/dsinstance.py | 2 | ||||
-rw-r--r-- | tests/test_xmlrpc/test_sudorule_plugin.py | 45 |
7 files changed, 94 insertions, 9 deletions
@@ -2831,7 +2831,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) output: Output('value', <type 'unicode'>, None) command: sudorule_add -args: 1,14,3 +args: 1,15,3 arg: Str('cn', attribute=True, cli_name='sudorule_name', multivalue=False, primary_key=True, required=True) option: Str('description', attribute=True, cli_name='desc', multivalue=False, required=False) option: StrEnum('usercategory', attribute=True, cli_name='usercat', multivalue=False, required=False, values=(u'all',)) @@ -2839,6 +2839,7 @@ option: StrEnum('hostcategory', attribute=True, cli_name='hostcat', multivalue=F option: StrEnum('cmdcategory', attribute=True, cli_name='cmdcat', multivalue=False, required=False, values=(u'all',)) option: StrEnum('ipasudorunasusercategory', attribute=True, cli_name='runasusercat', multivalue=False, required=False, values=(u'all',)) option: StrEnum('ipasudorunasgroupcategory', attribute=True, cli_name='runasgroupcat', multivalue=False, required=False, values=(u'all',)) +option: Int('sudoorder', attribute=True, cli_name='order', default=0, multivalue=False, required=False) option: Str('externaluser', attribute=True, cli_name='externaluser', multivalue=False, required=False) option: Str('ipasudorunasextuser', attribute=True, cli_name='runasexternaluser', multivalue=False, required=False) option: Str('ipasudorunasextgroup', attribute=True, cli_name='runasexternalgroup', multivalue=False, required=False) @@ -2936,7 +2937,7 @@ args: 1,0,1 arg: Str('cn', attribute=True, cli_name='sudorule_name', multivalue=False, primary_key=True, query=True, required=True) output: Output('result', None, None) command: sudorule_find -args: 1,16,4 +args: 1,17,4 arg: Str('criteria?', noextrawhitespace=False) option: Str('cn', attribute=True, autofill=False, cli_name='sudorule_name', multivalue=False, primary_key=True, query=True, required=False) option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, query=True, required=False) @@ -2945,6 +2946,7 @@ option: StrEnum('hostcategory', attribute=True, autofill=False, cli_name='hostca option: StrEnum('cmdcategory', attribute=True, autofill=False, cli_name='cmdcat', multivalue=False, query=True, required=False, values=(u'all',)) option: StrEnum('ipasudorunasusercategory', attribute=True, autofill=False, cli_name='runasusercat', multivalue=False, query=True, required=False, values=(u'all',)) option: StrEnum('ipasudorunasgroupcategory', attribute=True, autofill=False, cli_name='runasgroupcat', multivalue=False, query=True, required=False, values=(u'all',)) +option: Int('sudoorder', attribute=True, autofill=False, cli_name='order', default=0, multivalue=False, query=True, required=False) option: Str('externaluser', attribute=True, autofill=False, cli_name='externaluser', multivalue=False, query=True, required=False) option: Str('ipasudorunasextuser', attribute=True, autofill=False, cli_name='runasexternaluser', multivalue=False, query=True, required=False) option: Str('ipasudorunasextgroup', attribute=True, autofill=False, cli_name='runasexternalgroup', multivalue=False, query=True, required=False) @@ -2959,7 +2961,7 @@ output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list output: Output('count', <type 'int'>, None) output: Output('truncated', <type 'bool'>, None) command: sudorule_mod -args: 1,16,3 +args: 1,17,3 arg: Str('cn', attribute=True, cli_name='sudorule_name', multivalue=False, primary_key=True, query=True, required=True) option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, required=False) option: StrEnum('usercategory', attribute=True, autofill=False, cli_name='usercat', multivalue=False, required=False, values=(u'all',)) @@ -2967,6 +2969,7 @@ option: StrEnum('hostcategory', attribute=True, autofill=False, cli_name='hostca option: StrEnum('cmdcategory', attribute=True, autofill=False, cli_name='cmdcat', multivalue=False, required=False, values=(u'all',)) option: StrEnum('ipasudorunasusercategory', attribute=True, autofill=False, cli_name='runasusercat', multivalue=False, required=False, values=(u'all',)) option: StrEnum('ipasudorunasgroupcategory', attribute=True, autofill=False, cli_name='runasgroupcat', multivalue=False, required=False, values=(u'all',)) +option: Int('sudoorder', attribute=True, autofill=False, cli_name='order', default=0, multivalue=False, required=False) option: Str('externaluser', attribute=True, autofill=False, cli_name='externaluser', multivalue=False, required=False) option: Str('ipasudorunasextuser', attribute=True, autofill=False, cli_name='runasexternaluser', multivalue=False, required=False) option: Str('ipasudorunasextgroup', attribute=True, autofill=False, cli_name='runasexternalgroup', multivalue=False, required=False) diff --git a/install/share/60ipasudo.ldif b/install/share/65ipasudo.ldif index 61c73c08..7a85c865 100644 --- a/install/share/60ipasudo.ldif +++ b/install/share/65ipasudo.ldif @@ -32,7 +32,7 @@ attributeTypes: (2.16.840.1.113730.3.8.7.12 NAME 'hostMask' DESC 'IP mask to ide ## Attribute to store sudo command attributeTypes: (2.16.840.1.113730.3.8.7.13 NAME 'sudoCmd' DESC 'Command(s) to be executed by sudo' EQUALITY caseExactMatch ORDERING caseExactMatch SUBSTR caseExactSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' ) ## Object class for SUDO rules -objectClasses: (2.16.840.1.113730.3.8.8.1 NAME 'ipaSudoRule' SUP ipaAssociation STRUCTURAL MAY ( externalUser $ externalHost $ hostMask $ memberAllowCmd $ memberDenyCmd $ cmdCategory $ ipaSudoOpt $ ipaSudoRunAs $ ipaSudoRunAsExtUser $ ipaSudoRunAsUserCategory $ ipaSudoRunAsGroup $ ipaSudoRunAsExtGroup $ ipaSudoRunAsGroupCategory ) X-ORIGIN 'IPA v2' ) +objectClasses: (2.16.840.1.113730.3.8.8.1 NAME 'ipaSudoRule' SUP ipaAssociation STRUCTURAL MAY ( externalUser $ externalHost $ hostMask $ memberAllowCmd $ memberDenyCmd $ cmdCategory $ ipaSudoOpt $ ipaSudoRunAs $ ipaSudoRunAsExtUser $ ipaSudoRunAsUserCategory $ ipaSudoRunAsGroup $ ipaSudoRunAsExtGroup $ ipaSudoRunAsGroupCategory $ sudoNotBefore $ sudoNotAfter $$ sudoOrder ) X-ORIGIN 'IPA v2' ) ## Object class for SUDO commands objectClasses: (2.16.840.1.113730.3.8.8.2 NAME 'ipaSudoCmd' DESC 'IPA object class for SUDO command' STRUCTURAL MUST ( ipaUniqueID $ sudoCmd ) MAY ( memberOf $ description ) X-ORIGIN 'IPA v2' ) ## Object class for groups of the SUDO commands diff --git a/install/share/Makefile.am b/install/share/Makefile.am index eefa3534..243fc2a1 100644 --- a/install/share/Makefile.am +++ b/install/share/Makefile.am @@ -9,7 +9,7 @@ app_DATA = \ 60basev2.ldif \ 60basev3.ldif \ 60ipadns.ldif \ - 60ipasudo.ldif \ + 65ipasudo.ldif \ anonymous-vlv.ldif \ bootstrap-template.ldif \ caJarSigningCert.cfg.template \ diff --git a/install/updates/10-sudo.update b/install/updates/10-sudo.update index 88bdc3ce..a12da004 100644 --- a/install/updates/10-sudo.update +++ b/install/updates/10-sudo.update @@ -38,3 +38,5 @@ add:attributeTypes: ( 1.3.6.1.4.1.15953.9.1.10 SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 X-ORIGIN 'SUDO' ) replace:objectClasses:( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' DESC 'Sudoer Entries' STRUCTURAL MUST cn MAY ( sudoUser $$ sudoHost $$ sudoCommand $$ sudoRunAs $$ sudoOption $$ description ) X-ORIGIN 'SUDO' )::( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' SUP top STRUCTURAL DESC 'Sudoer Entries' MUST ( cn ) MAY ( sudoUser $$ sudoHost $$ sudoCommand $$ sudoRunAs $$ sudoRunAsUser $$ sudoRunAsGroup $$ sudoOption $$ sudoNotBefore $$ sudoNotAfter $$ sudoOrder $$ description ) X-ORIGIN 'SUDO') + +replace:objectClasses: ( 2.16.840.1.113730.3.8.8.1 NAME 'ipaSudoRule' SUP ipaAssociation STRUCTURAL MAY ( externalUser $$ externalHost $$ hostMask $$ memberAllowCmd $$ memberDenyCmd $$ cmdCategory $$ ipaSudoOpt $$ ipaSudoRunAs $$ ipaSudoRunAsExtUser $$ ipaSudoRunAsUserCategory $$ ipaSudoRunAsGroup $$ ipaSudoRunAsExtGroup $$ ipaSudoRunAsGroupCategory ) X-ORIGIN 'IPA v2' )::(2.16.840.1.113730.3.8.8.1 NAME 'ipaSudoRule' SUP ipaAssociation STRUCTURAL MAY ( externalUser $$ externalHost $$ hostMask $$ memberAllowCmd $$ memberDenyCmd $$ cmdCategory $$ ipaSudoOpt $$ ipaSudoRunAs $$ ipaSudoRunAsExtUser $$ ipaSudoRunAsUserCategory $$ ipaSudoRunAsGroup $$ ipaSudoRunAsExtGroup $$ ipaSudoRunAsGroupCategory $$ sudoNotBefore $$ sudoNotAfter $$ sudoOrder) X-ORIGIN 'IPA v2' ) diff --git a/ipalib/plugins/sudorule.py b/ipalib/plugins/sudorule.py index ff7b756a..de7a7af3 100644 --- a/ipalib/plugins/sudorule.py +++ b/ipalib/plugins/sudorule.py @@ -40,6 +40,10 @@ FreeIPA provides a means to configure the various aspects of Sudo: RunAsGroup: The group(s) whose gid rights Sudo will be invoked with. Options: The various Sudoers Options that can modify Sudo's behavior. +An order can be added to a sudorule to control the order in which they +are evaluated (if the client supports it). This order is an integer and +must be unique. + FreeIPA provides a designated binddn to use with Sudo located at: uid=sudo,cn=sysaccounts,cn=etc,dc=example,dc=com @@ -80,6 +84,7 @@ class sudorule(LDAPObject): 'memberallowcmd', 'memberdenycmd', 'ipasudoopt', 'ipasudorunas', 'ipasudorunasgroup', 'ipasudorunasusercategory', 'ipasudorunasgroupcategory', + 'sudoorder', ] uuid_attribute = 'ipauniqueid' rdn_attribute = 'ipauniqueid' @@ -139,6 +144,13 @@ class sudorule(LDAPObject): 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'], @@ -207,6 +219,25 @@ class sudorule(LDAPObject): ), ) + 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 'sudoorder' in options: + 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, + } + ) + api.register(sudorule) @@ -214,6 +245,7 @@ class sudorule_add(LDAPCreate): __doc__ = _('Create new Sudo Rule.') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + self.obj.check_order_uniqueness(*keys, **options) # Sudo Rules are enabled by default entry_attrs['ipaenabledflag'] = 'TRUE' return dn @@ -236,6 +268,15 @@ class sudorule_mod(LDAPUpdate): msg_summary = _('Modified Sudo Rule "%(value)s"') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + if 'sudoorder' in options: + new_order = options.get('sudoorder') + old_entry = self.api.Command.sudorule_show(keys[-1])['result'] + if 'sudoorder' in old_entry: + old_order = int(old_entry['sudoorder'][0]) + if old_order != new_order: + self.obj.check_order_uniqueness(*keys, **options) + else: + self.obj.check_order_uniqueness(*keys, **options) try: (_dn, _entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes) except errors.NotFound: diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 5b5b24ca..e549e13c 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -359,7 +359,7 @@ class DsInstance(service.Service): "60basev2.ldif", "60basev3.ldif", "60ipadns.ldif", - "60ipasudo.ldif"): + "65ipasudo.ldif"): target_fname = schema_dirname(self.serverid) + schema_fname shutil.copyfile(ipautil.SHARE_DIR + schema_fname, target_fname) os.chmod(target_fname, 0440) # read access for dirsrv user/group diff --git a/tests/test_xmlrpc/test_sudorule_plugin.py b/tests/test_xmlrpc/test_sudorule_plugin.py index 4e283c20..bddf00c4 100644 --- a/tests/test_xmlrpc/test_sudorule_plugin.py +++ b/tests/test_xmlrpc/test_sudorule_plugin.py @@ -30,6 +30,7 @@ class test_sudorule(XMLRPC_test): Test the `sudorule` plugin. """ rule_name = u'testing_sudorule1' + rule_name2 = u'testing_sudorule2' rule_command = u'/usr/bin/testsudocmd1' rule_desc = u'description' rule_desc_mod = u'description modified' @@ -38,8 +39,8 @@ class test_sudorule(XMLRPC_test): test_external_user = u'external_test_user' test_group = u'sudorule_test_group' test_external_group = u'external_test_group' - test_host = u'sudorule.test-host' - test_external_host = u'external.test-host' + test_host = u'sudorule.testhost' + test_external_host = u'external.testhost' test_hostgroup = u'sudorule_test_hostgroup' test_sudoallowcmdgroup = u'sudorule_test_allowcmdgroup' test_sudodenycmdgroup = u'sudorule_test_denycmdgroup' @@ -625,8 +626,45 @@ class test_sudorule(XMLRPC_test): api.Command['sudocmdgroup_del'](self.test_sudoallowcmdgroup) api.Command['sudocmdgroup_del'](self.test_sudodenycmdgroup) + def test_l_sudorule_order(self): + """ + Test that order uniqueness is maintained + """ + api.Command['sudorule_mod'](self.rule_name, sudoorder=1) + + api.Command['sudorule_add'](self.rule_name2) + + # mod of rule that has no order and set a duplicate + try: + api.Command['sudorule_mod'](self.rule_name2, sudoorder=1) + except errors.ValidationError: + pass + + # Remove the rule so we can re-add it + api.Command['sudorule_del'](self.rule_name2) + + # add a new rule with a duplicate order + try: + api.Command['sudorule_add'](self.rule_name2, sudoorder=1) + except errors.ValidationError: + pass + + # add a new rule with a unique order + api.Command['sudorule_add'](self.rule_name2, sudoorder=2) + try: + api.Command['sudorule_mod'](self.rule_name2, sudoorder=1) + except errors.ValidationError: + pass + + # Try setting both to 0 + api.Command['sudorule_mod'](self.rule_name2, sudoorder=0) + try: + api.Command['sudorule_mod'](self.rule_name, sudoorder=0) + except errors.ValidationError: + pass + - def test_l_sudorule_del(self): + def test_m_sudorule_del(self): """ Test deleting a Sudo rule using `xmlrpc.sudorule_del`. """ @@ -638,3 +676,4 @@ class test_sudorule(XMLRPC_test): pass else: assert False + api.Command['sudorule_del'](self.rule_name2) |