diff options
-rw-r--r-- | install/share/schema_compat.uldif | 1 | ||||
-rw-r--r-- | ipalib/plugins/sudorule.py | 112 | ||||
-rw-r--r-- | tests/test_xmlrpc/test_sudorule_plugin.py | 55 |
3 files changed, 167 insertions, 1 deletions
diff --git a/install/share/schema_compat.uldif b/install/share/schema_compat.uldif index bfe645dc5..6f70c1de4 100644 --- a/install/share/schema_compat.uldif +++ b/install/share/schema_compat.uldif @@ -87,6 +87,7 @@ add:schema-compat-entry-attribute: 'sudoCommand=%ifeq("cmdCategory","all","ALL", add:schema-compat-entry-attribute: 'sudoCommand=%ifeq("cmdCategory","all","ALL","!%deref_r(\"memberDenyCmd\",\"member\",\"sudoCmd\")")' add:schema-compat-entry-attribute: 'sudoRunAsUser=%{ipaSudoRunAsExtUser}' add:schema-compat-entry-attribute: 'sudoRunAsUser=%deref("ipaSudoRunAs","uid")' +add:schema-compat-entry-attribute: 'sudoRunAsUser=%ifeq("ipaSudoRunAsUserCategory","all","ALL","%%%deref_f(\"ipaSudoRunAs\",\"(objectclass=posixGroup)\",\"cn\")")' add:schema-compat-entry-attribute: 'sudoRunAsGroup=%{ipaSudoRunAsExtGroup}' add:schema-compat-entry-attribute: 'sudoRunAsGroup=%deref("ipaSudoRunAs","cn")' add:schema-compat-entry-attribute: 'sudoOption=%{ipaSudoOpt}' diff --git a/ipalib/plugins/sudorule.py b/ipalib/plugins/sudorule.py index 893f09a57..3361ff5d0 100644 --- a/ipalib/plugins/sudorule.py +++ b/ipalib/plugins/sudorule.py @@ -48,7 +48,7 @@ class sudorule(LDAPObject): 'memberhost': ['host', 'hostgroup'], 'memberallowcmd': ['sudocmd', 'sudocmdgroup'], 'memberdenycmd': ['sudocmd', 'sudocmdgroup'], - 'ipasudorunas': ['user'], + 'ipasudorunas': ['user', 'group'], 'ipasudorunasgroup': ['group'], } @@ -139,6 +139,16 @@ class sudorule(LDAPObject): label=_('External User'), doc=_('External User the rule applies to'), ), + Str('ipasudorunasextuser?', + cli_name='runasexternaluser', + label=_('RunAs External User'), + doc=_('External User the commands can run as'), + ), + Str('ipasudorunasextgroup?', + cli_name='runasexternalgroup', + label=_('RunAs External Group'), + doc=_('External Group the commands can run as'), + ), ) api.register(sudorule) @@ -429,6 +439,32 @@ class sudorule_add_runasuser(LDAPAddMember): member_attributes = ['ipasudorunas'] member_count_out = ('%i object added.', '%i objects added.') + def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + completed_external = 0 + # Sift through the user failures. We assume that these are all + # users that aren't stored in IPA, aka external users. + if 'ipasudorunas' in failed and 'user' in failed['ipasudorunas']: + (dn, entry_attrs_) = ldap.get_entry(dn, ['ipasudorunasextuser']) + members = entry_attrs.get('ipasudorunas', []) + external_users = entry_attrs_.get('ipasudorunasextuser', []) + failed_users = [] + for user in failed['ipasudorunas']['user']: + username = user[0].lower() + user_dn = self.api.Object['user'].get_dn(username) + if username not in external_users and user_dn not in members: + external_users.append(username) + completed_external += 1 + else: + failed_users.append(username) + if completed_external: + try: + ldap.update_entry(dn, {'ipasudorunasextuser': external_users}) + except errors.EmptyModlist: + pass + failed['ipasudorunas']['user'] = failed_users + entry_attrs['ipasudorunasextuser'] = external_users + return (completed + completed_external, dn) + api.register(sudorule_add_runasuser) @@ -439,6 +475,30 @@ class sudorule_remove_runasuser(LDAPRemoveMember): member_attributes = ['ipasudorunas'] member_count_out = ('%i object removed.', '%i objects removed.') + def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + # Run through the user failures and gracefully remove any defined as + # as an externaluser. + if 'ipasudorunas' in failed and 'user' in failed['ipasudorunas']: + (dn, entry_attrs) = ldap.get_entry(dn, ['ipasudorunasextuser']) + external_users = entry_attrs.get('ipasudorunasextuser', []) + failed_users = [] + completed_external = 0 + for user in failed['ipasudorunas']['user']: + username = user[0].lower() + if username in external_users: + external_users.remove(username) + completed_external += 1 + else: + failed_users.append(username) + if completed_external: + try: + ldap.update_entry(dn, {'ipasudorunasextuser': external_users}) + except errors.EmptyModlist: + pass + failed['ipasudorunas']['user'] = failed_users + entry_attrs['ipasudorunasextuser'] = external_users + return (completed + completed_external, dn) + api.register(sudorule_remove_runasuser) @@ -449,6 +509,32 @@ class sudorule_add_runasgroup(LDAPAddMember): member_attributes = ['ipasudorunasgroup'] member_count_out = ('%i object added.', '%i objects added.') + def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + completed_external = 0 + # Sift through the group failures. We assume that these are all + # groups that aren't stored in IPA, aka external groups. + if 'ipasudorunasgroup' in failed and 'group' in failed['ipasudorunasgroup']: + (dn, entry_attrs_) = ldap.get_entry(dn, ['ipasudorunasextgroup']) + members = entry_attrs.get('ipasudorunasgroup', []) + external_groups = entry_attrs_.get('ipasudorunasextgroup', []) + failed_groups = [] + for group in failed['ipasudorunasgroup']['group']: + groupname = group[0].lower() + group_dn = self.api.Object['group'].get_dn(groupname) + if groupname not in external_groups and group_dn not in members: + external_groups.append(groupname) + completed_external += 1 + else: + failed_groups.append(groupname) + if completed_external: + try: + ldap.update_entry(dn, {'ipasudorunasextgroup': external_groups}) + except errors.EmptyModlist: + pass + failed['ipasudorunasgroup']['group'] = failed_groups + entry_attrs['ipasudorunasextgroup'] = external_groups + return (completed + completed_external, dn) + api.register(sudorule_add_runasgroup) @@ -459,6 +545,30 @@ class sudorule_remove_runasgroup(LDAPRemoveMember): member_attributes = ['ipasudorunasgroup'] member_count_out = ('%i object removed.', '%i objects removed.') + def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + # Run through the group failures and gracefully remove any defined as + # as an external group. + if 'ipasudorunasgroup' in failed and 'group' in failed['ipasudorunasgroup']: + (dn, entry_attrs) = ldap.get_entry(dn, ['ipasudorunasextgroup']) + external_groups = entry_attrs.get('ipasudorunasextgroup', []) + failed_groups = [] + completed_external = 0 + for group in failed['ipasudorunasgroup']['group']: + groupname = group[0].lower() + if groupname in external_groups: + external_groups.remove(groupname) + completed_external += 1 + else: + failed_groups.append(groupname) + if completed_external: + try: + ldap.update_entry(dn, {'ipasudorunasextgroup': external_groups}) + except errors.EmptyModlist: + pass + failed['ipasudorunasgroup']['group'] = failed_groups + entry_attrs['ipasudorunasextgroup'] = external_groups + return (completed + completed_external, dn) + api.register(sudorule_remove_runasgroup) diff --git a/tests/test_xmlrpc/test_sudorule_plugin.py b/tests/test_xmlrpc/test_sudorule_plugin.py index 89f0af3a5..4c3ba5a21 100644 --- a/tests/test_xmlrpc/test_sudorule_plugin.py +++ b/tests/test_xmlrpc/test_sudorule_plugin.py @@ -38,6 +38,7 @@ class test_sudorule(XMLRPC_test): test_user = u'sudorule_test_user' 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_hostgroup = u'sudorule_test_hostgroup' @@ -264,6 +265,60 @@ class test_sudorule(XMLRPC_test): entry = ret['result'] assert 'externaluser' not in entry + def test_a_sudorule_add_runasexternaluser(self): + """ + Test adding an external runasuser to Sudo rule using + `xmlrpc.sudorule_add_runasuser`. + """ + ret = api.Command['sudorule_add_runasuser']( + self.rule_name, user=self.test_external_user + ) + assert ret['completed'] == 1 + failed = ret['failed'] + entry = ret['result'] + assert_attr_equal(entry, 'ipasudorunasextuser', self.test_external_user) + + def test_b_sudorule_remove_runasexternaluser(self): + """ + Test removing an external runasuser from Sudo rule using + `xmlrpc.sudorule_remove_runasuser'. + """ + ret = api.Command['sudorule_remove_runasuser']( + self.rule_name, user=self.test_external_user + ) + assert ret['completed'] == 1 + failed = ret['failed'] + entry = ret['result'] + assert 'ipasudorunasextuser' not in entry + + def test_a_sudorule_add_runasexternalgroup(self): + """ + Test adding an external runasgroup to Sudo rule using + `xmlrpc.sudorule_add_runasgroup`. + """ + ret = api.Command['sudorule_add_runasgroup']( + self.rule_name, group=self.test_external_group + ) + print ret + assert ret['completed'] == 1 + failed = ret['failed'] + entry = ret['result'] + assert_attr_equal(entry, 'ipasudorunasextgroup', self.test_external_group) + + def test_b_sudorule_remove_runasexternalgroup(self): + """ + Test removing an external runasgroup from Sudo rule using + `xmlrpc.sudorule_remove_runasgroup'. + """ + ret = api.Command['sudorule_remove_runasgroup']( + self.rule_name, group=self.test_external_group + ) + print ret + assert ret['completed'] == 1 + failed = ret['failed'] + entry = ret['result'] + assert 'ipasudorunasextgroup' not in entry + def test_a_sudorule_add_option(self): """ Test adding an option to Sudo rule using |