From 8fabd6dde152fc394bd4f093d93c8a46e5b2851b Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 18 Aug 2014 16:49:40 +0200 Subject: Support delegating RBAC roles to service principals https://fedorahosted.org/freeipa/ticket/3164 Reviewed-By: Martin Kosek --- API.txt | 6 +- VERSION | 4 +- ipalib/plugins/role.py | 2 +- ipalib/plugins/service.py | 3 +- .../test_integration/test_service_permissions.py | 82 ++++++++++++++ ipatests/test_xmlrpc/test_role_plugin.py | 2 + ipatests/test_xmlrpc/test_service_plugin.py | 124 +++++++++++++++++++++ 7 files changed, 217 insertions(+), 6 deletions(-) create mode 100644 ipatests/test_integration/test_service_permissions.py diff --git a/API.txt b/API.txt index d731881ee..4fa275592 100644 --- a/API.txt +++ b/API.txt @@ -2908,7 +2908,7 @@ output: Entry('result', , Gettext('A dictionary representing an LDA output: Output('summary', (, ), None) output: PrimaryKey('value', None, None) command: role_add_member -args: 1,8,3 +args: 1,9,3 arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True) option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui') option: Str('group*', alwaysask=True, cli_name='groups', csv=True) @@ -2916,6 +2916,7 @@ option: Str('host*', alwaysask=True, cli_name='hosts', csv=True) option: Str('hostgroup*', alwaysask=True, cli_name='hostgroups', csv=True) option: Flag('no_members', autofill=True, default=False, exclude='webui') option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui') +option: Str('service*', alwaysask=True, cli_name='services', csv=True) option: Str('user*', alwaysask=True, cli_name='users', csv=True) option: Str('version?', exclude='webui') output: Output('completed', , None) @@ -2973,7 +2974,7 @@ output: Entry('result', , Gettext('A dictionary representing an LDA output: Output('summary', (, ), None) output: PrimaryKey('value', None, None) command: role_remove_member -args: 1,8,3 +args: 1,9,3 arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True) option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui') option: Str('group*', alwaysask=True, cli_name='groups', csv=True) @@ -2981,6 +2982,7 @@ option: Str('host*', alwaysask=True, cli_name='hosts', csv=True) option: Str('hostgroup*', alwaysask=True, cli_name='hostgroups', csv=True) option: Flag('no_members', autofill=True, default=False, exclude='webui') option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui') +option: Str('service*', alwaysask=True, cli_name='services', csv=True) option: Str('user*', alwaysask=True, cli_name='users', csv=True) option: Str('version?', exclude='webui') output: Output('completed', , None) diff --git a/VERSION b/VERSION index 2a2789a8a..379ead756 100644 --- a/VERSION +++ b/VERSION @@ -89,5 +89,5 @@ IPA_DATA_VERSION=20100614120000 # # ######################################################## IPA_API_VERSION_MAJOR=2 -IPA_API_VERSION_MINOR=101 -# Last change: mbasti - Allow '/' in permission name +IPA_API_VERSION_MINOR=102 +# Last change: pviktori - allow adding services to roles diff --git a/ipalib/plugins/role.py b/ipalib/plugins/role.py index b290ceeb1..f2021d3fa 100644 --- a/ipalib/plugins/role.py +++ b/ipalib/plugins/role.py @@ -75,7 +75,7 @@ class role(LDAPObject): 'memberindirect', 'memberofindirect', ] attribute_members = { - 'member': ['user', 'group', 'host', 'hostgroup'], + 'member': ['user', 'group', 'host', 'hostgroup', 'service'], 'memberof': ['privilege'], } reverse_members = { diff --git a/ipalib/plugins/service.py b/ipalib/plugins/service.py index 9f3791aab..69b2cc6c3 100644 --- a/ipalib/plugins/service.py +++ b/ipalib/plugins/service.py @@ -306,10 +306,11 @@ class service(LDAPObject): permission_filter_objectclasses = ['ipaservice'] search_attributes = ['krbprincipalname', 'managedby', 'ipakrbauthzdata'] default_attributes = ['krbprincipalname', 'usercertificate', 'managedby', - 'ipakrbauthzdata',] + 'ipakrbauthzdata', 'memberof'] uuid_attribute = 'ipauniqueid' attribute_members = { 'managedby': ['host'], + 'memberof': ['role'], } bindable = True relationships = { diff --git a/ipatests/test_integration/test_service_permissions.py b/ipatests/test_integration/test_service_permissions.py new file mode 100644 index 000000000..3d4a50d32 --- /dev/null +++ b/ipatests/test_integration/test_service_permissions.py @@ -0,0 +1,82 @@ +# Authors: +# Petr Viktorin +# +# Copyright (C) 2014 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from ipatests.test_integration.base import IntegrationTest +from ipatests.test_integration import tasks + + +class TestServicePermissions(IntegrationTest): + topology = 'star' + + def test_service_as_user_admin(self): + """Test that a service in User Administrator role can manage users""" + + service_name = 'testservice/%s@%s' % (self.master.hostname, + self.master.domain.realm) + keytab_file = os.path.join(self.master.config.test_dir, + 'testservice_keytab') + + # Prepare a service + + self.master.run_command(['ipa', 'service-add', service_name]) + + self.master.run_command(['ipa-getkeytab', + '-p', service_name, + '-k', keytab_file, + '-s', self.master.hostname]) + + # Check that the service cannot add a user + + self.master.run_command(['kdestroy']) + self.master.run_command(['kinit', '-k', service_name, + '-t', keytab_file]) + + result = self.master.run_command(['ipa', 'role-add-member', + 'User Administrator', + '--service', service_name], + raiseonerr=False) + assert result.returncode > 0 + + # Add service to User Administrator role + + self.master.run_command(['kdestroy']) + tasks.kinit_admin(self.master) + + self.master.run_command(['ipa', 'role-add-member', + 'User Administrator', + '--service', service_name]) + + # Check that the service now can add a user + + self.master.run_command(['kdestroy']) + self.master.run_command(['kinit', '-k', service_name, + '-t', keytab_file]) + + self.master.run_command(['ipa', 'user-add', 'tuser', + '--first', 'a', '--last', 'b', '--random']) + + # Clean up + + self.master.run_command(['kdestroy']) + tasks.kinit_admin(self.master) + + self.master.run_command(['ipa', 'service-del', service_name]) + self.master.run_command(['ipa', 'user-del', 'tuser']) diff --git a/ipatests/test_xmlrpc/test_role_plugin.py b/ipatests/test_xmlrpc/test_role_plugin.py index f400b0fa8..9a1c0748d 100644 --- a/ipatests/test_xmlrpc/test_role_plugin.py +++ b/ipatests/test_xmlrpc/test_role_plugin.py @@ -257,6 +257,7 @@ class test_role(Declarative): group=[], host=[], hostgroup=[], + service=[], ), ), result={ @@ -436,6 +437,7 @@ class test_role(Declarative): group=[], host=[], hostgroup=[], + service=[], ), ), result={ diff --git a/ipatests/test_xmlrpc/test_service_plugin.py b/ipatests/test_xmlrpc/test_service_plugin.py index 532d9888c..5be27af9e 100644 --- a/ipatests/test_xmlrpc/test_service_plugin.py +++ b/ipatests/test_xmlrpc/test_service_plugin.py @@ -40,6 +40,8 @@ host1dn = DN(('fqdn',fqdn1),('cn','computers'),('cn','accounts'),api.env.basedn) host2dn = DN(('fqdn',fqdn2),('cn','computers'),('cn','accounts'),api.env.basedn) host3dn = DN(('fqdn',fqdn3),('cn','computers'),('cn','accounts'),api.env.basedn) +role1 = u'Test Role' +role1_dn = DN(('cn', role1), api.env.container_rolegroup, api.env.basedn) badservercert = 'MIICbzCCAdigAwIBAgICA/4wDQYJKoZIhvcNAQEFBQAwKTEnMCUGA1UEAxMeSVBBIFRlc3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTEwMDgwOTE1MDIyN1oXDTIwMDgwOTE1MDIyN1owKTEMMAoGA1UEChMDSVBBMRkwFwYDVQQDExBwdW1hLmdyZXlvYWsuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwYbfEOQPgGenPn9vt1JFKvWm/Je3y2tawGWA3LXDuqfFJyYtZ8ib3TcBUOnLk9WK5g2qCwHaNlei7bj8ggIfr5hegAVe10cun+wYErjnYo7hsHYd+57VZezeipWrXu+7NoNd4+c4A5lk4A/xJay9j3bYx2oOM8BEox4xWYoWge1ljPrc5JK46f0X7AGW4F2VhnKPnf8rwSuzI1U8VGjutyM9TWNy3m9KMWeScjyG/ggIpOjUDMV7HkJL0Di61lznR9jXubpiEC7gWGbTp84eGl/Nn9bgK1AwHfJ2lHwfoY4uiL7ge1gyP6EvuUlHoBzdb7pekiX28iePjW3iEG9IawIDAQABoyIwIDARBglghkgBhvhCAQEEBAMCBkAwCwYDVR0PBAQDAgUgMA0GCSqGSIb3DQEBBQUAA4GBACRESLemRV9BPxfEgbALuxH5oE8jQm8WZ3pm2pALbpDlAd9wQc3yVf6RtkfVthyDnM18bg7IhxKpd77/p3H8eCnS8w5MLVRda6ktUC6tGhFTS4QKAf0WyDGTcIgkXbeDw0OPAoNHivoXbIXIIRxlw/XgaSaMzJQDBG8iROsN4kCv' @@ -626,3 +628,125 @@ class test_service(Declarative): ] + + +class test_service_in_role(Declarative): + cleanup_commands = [ + ('host_del', [fqdn1], {}), + ('service_del', [service1], {}), + ('role_del', [role1], {}), + ] + + tests = [ + dict( + desc='Create %r' % fqdn1, + command=('host_add', [fqdn1], + dict( + description=u'Test host 1', + l=u'Undisclosed location 1', + force=True, + ), + ), + expected=dict( + value=fqdn1, + summary=u'Added host "%s"' % fqdn1, + result=dict( + dn=host1dn, + fqdn=[fqdn1], + description=[u'Test host 1'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[u'%s' % fqdn1], + has_keytab=False, + has_password=False, + ), + ), + ), + + dict( + desc='Create %r' % service1, + command=('service_add', [service1], dict(force=True)), + expected=dict( + value=service1, + summary=u'Added service "%s"' % service1, + result=dict( + dn=service1dn, + krbprincipalname=[service1], + objectclass=objectclasses.service, + ipauniqueid=[fuzzy_uuid], + managedby_host=[fqdn1], + ), + ), + ), + + dict( + desc='Create %r' % role1, + command=('role_add', [role1], dict(description=u'role desc 1')), + expected=dict( + value=role1, + summary=u'Added role "%s"' % role1, + result=dict( + dn=role1_dn, + cn=[role1], + description=[u'role desc 1'], + objectclass=objectclasses.role, + ), + ), + ), + + dict( + desc='Add %r to %r' % (service1, role1), + command=('role_add_member', [role1], dict(service=service1)), + expected=dict( + failed=dict( + member=dict( + host=[], + group=[], + hostgroup=[], + service=[], + user=[], + ), + ), + completed=1, + result=dict( + dn=role1_dn, + cn=[role1], + description=[u'role desc 1'], + member_service=[service1], + ), + ), + ), + + dict( + desc='Verify %r is member of %r' % (service1, role1), + command=('service_show', [service1], {}), + expected=dict( + value=service1, + summary=None, + result=dict( + dn=service1dn, + krbprincipalname=[service1], + managedby_host=[fqdn1], + memberof_role=[role1.lower()], + has_keytab=False, + ), + ), + ), + + dict( + desc='Verify %r has member %r' % (role1, service1), + command=('role_show', [role1], {}), + expected=dict( + value=role1, + summary=None, + result=dict( + dn=role1_dn, + cn=[role1], + description=[u'role desc 1'], + member_service=[service1], + ), + ), + ), + ] -- cgit