summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--API.txt9
-rw-r--r--install/share/65ipasudo.ldif (renamed from install/share/60ipasudo.ldif)2
-rw-r--r--install/share/Makefile.am2
-rw-r--r--install/updates/10-sudo.update2
-rw-r--r--ipalib/plugins/sudorule.py41
-rw-r--r--ipaserver/install/dsinstance.py2
-rw-r--r--tests/test_xmlrpc/test_sudorule_plugin.py45
7 files changed, 94 insertions, 9 deletions
diff --git a/API.txt b/API.txt
index 88684dfad..9ba3ce4ac 100644
--- a/API.txt
+++ b/API.txt
@@ -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 61c73c084..7a85c8659 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 eefa35343..243fc2a11 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 88bdc3ce1..a12da0043 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 ff7b756a3..de7a7af37 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 5b5b24caa..e549e13cc 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 4e283c208..bddf00c4f 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)