New revision according to Honza's recommendations. Comments inline.

On 1.10.2014 18:15, Petr Vobornik wrote:
Hello list,

Patch for: https://fedorahosted.org/freeipa/ticket/4419

Before I start any work on Web UI and tests I would like to gather
feedback on:
- the new API
- member attributes with subtypes management approach
- ACI

I did not do any ACI work in the patch yet. I assume that we would like
to add the attr into  'System: Read Host|Service' permission. But I
think that write right should have it's own permission.

I have added 2 new permissions. Simo, are they OK?

for services:
'System: Manage Service Keytab Permissions': {
    'ipapermright': {'read', 'search', 'compare', 'write'},
    'ipapermdefaultattr': {'ipaallowedtoperform', 'objectclass'},
'default_privileges': {'Service Administrators', 'Host Administrators'},
},

for hosts:
'System: Manage Host Keytab Permissions': {
    'ipapermright': {'read', 'search', 'compare', 'write'},
    'ipapermdefaultattr': {'ipaallowedtoperform', 'objectclass'},
    'default_privileges': {'Host Administrators'},
},

I'm not sure about the write right for 'objectclass' but it's required in order to add 'ipaallowedoperations' oc.


Patch info:
Adds new API:
   ipa host-add-retrieve-keytab HOSTNAME --users=STR --groups=STR
   ipa host-add-write-keytab HOSTNAME --users=STR --groups=STR
   ipa host-remove-retrieve-keytab HOSTNAME --users=STR --groups=STR
   ipa host-remove-write-keytab HOSTNAME --users=STR --groups=STR

   ipa service-add-retrieve-keytab PRINCIPAL --users=STR --groups=STR
   ipa service-add-write-keytab PRINCIPAL --users=STR --groups=STR
   ipa service-remove-retrieve-keytab PRINCIPAL --users=STR --groups=STR
   ipa service-remove-write-keytab PRINCIPAL --users=STR --groups=STR

*-write-keytab commands were changed to *-create-keytab to be consistent with descriptions


these methods add or remove user or group DNs in `ipaallowedtoperform`
attr with `read_keys` and `write_keys` subtypes.

service|host-mod|show outputs these attrs only with --all option as:

--all is no longer required


   Users allowed to retrieve keytab: user1
   Groups allowed to retrieve keytab: group1
   Users allowed to write keytab: user1
   Groups allowed to write keytab: group1

1) This patch implements subtypes support for attributes members. It's
done to be relatively reusable but it's confined within the RFE
boundaries. I.e. it does not contain support for standard attributes or
is not integrated into LDAPAddMember or LDAPRemoveMember commands. It's
rather as separate opt-ins. One of the reasons was also not to disturb
existing code at the end of 4-1
milestone.

Was replaced by more specific methods more local to a service and a host plugins.


3) Adding of object class is implemented as a reusable method since this
code is used on many places and most likely will be also used in new
features. Older code may be refactored later.

Thanks

RPC tests added in patch #763.
--
Petr Vobornik
From ffc26abbb3f46376369c27c3f6b9dec56ef0c390 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Thu, 2 Oct 2014 16:57:08 +0200
Subject: [PATCH] keytab manipulation permission management

Adds new API:
  ipa host-add-retrieve-keytab HOSTNAME --users=STR --groups=STR
  ipa host-add-create-keytab HOSTNAME --users=STR --groups=STR
  ipa host-remove-retrieve-keytab HOSTNAME --users=STR --groups=STR
  ipa host-remove-create-keytab HOSTNAME --users=STR --groups=STR

  ipa service-add-retrieve-keytab PRINCIPAL --users=STR --groups=STR
  ipa service-add-create-keytab PRINCIPAL --users=STR --groups=STR
  ipa service-remove-retrieve-keytab PRINCIPAL --users=STR --groups=STR
  ipa service-remove-create-keytab PRINCIPAL --users=STR --groups=STR

these methods add or remove user or group DNs in `ipaallowedtoperform` attr with
`read_keys` and `write_keys` subtypes.

service|host-mod|show outputs these attrs only with --all option as:

  Users allowed to retrieve keytab: user1
  Groups allowed to retrieve keytab: group1
  Users allowed to create keytab: user1
  Groups allowed to create keytab: group1

Adding of object class is implemented as a reusable method since this code is
used on many places and most likely will be also used in new features. Older
code may be refactored later.

https://fedorahosted.org/freeipa/ticket/4419
---
 ACI.txt                    |   4 ++
 API.txt                    |  96 ++++++++++++++++++++++++++++++++++
 VERSION                    |   4 +-
 ipalib/plugins/baseldap.py |  17 ++++++
 ipalib/plugins/host.py     | 107 ++++++++++++++++++++++++++++++++++++--
 ipalib/plugins/service.py  | 125 +++++++++++++++++++++++++++++++++++++++++++--
 6 files changed, 342 insertions(+), 11 deletions(-)

diff --git a/ACI.txt b/ACI.txt
index cebdc2ccec45db1dbf0d5ea0c7f2b1a3a7feeb6e..c5510dba009afff583e836704a9e03b78f5e28b5 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -95,6 +95,8 @@ aci: (targetattr = "userpassword")(targetfilter = "(objectclass=ipahost)")(versi
 dn: cn=computers,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "krblastpwdchange || krbprincipalkey")(targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System: Manage Host Keytab";allow (write) groupdn = "ldap:///cn=System: Manage Host Keytab,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=computers,cn=accounts,dc=ipa,dc=example
+aci: (targetattr = "createtimestamp || entryusn || ipaallowedtoperform || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System: Manage Host Keytab Permissions";allow (compare,read,search,write) groupdn = "ldap:///cn=System: Manage Host Keytab Permissions,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=computers,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "ipasshpubkey")(targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System: Manage Host SSH Public Keys";allow (write) groupdn = "ldap:///cn=System: Manage Host SSH Public Keys,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=computers,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "description || ipaassignedidview || l || macaddress || nshardwareplatform || nshostlocation || nsosversion || userclass")(targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System: Modify Hosts";allow (write) groupdn = "ldap:///cn=System: Modify Hosts,cn=permissions,cn=pbac,dc=ipa,dc=example";)
@@ -193,6 +195,8 @@ aci: (targetfilter = "(objectclass=ipaservice)")(version 3.0;acl "permission:Sys
 dn: cn=services,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "krblastpwdchange || krbprincipalkey")(targetfilter = "(objectclass=ipaservice)")(version 3.0;acl "permission:System: Manage Service Keytab";allow (write) groupdn = "ldap:///cn=System: Manage Service Keytab,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=services,cn=accounts,dc=ipa,dc=example
+aci: (targetattr = "createtimestamp || entryusn || ipaallowedtoperform || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaservice)")(version 3.0;acl "permission:System: Manage Service Keytab Permissions";allow (compare,read,search,write) groupdn = "ldap:///cn=System: Manage Service Keytab Permissions,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=services,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "usercertificate")(targetfilter = "(objectclass=ipaservice)")(version 3.0;acl "permission:System: Modify Services";allow (write) groupdn = "ldap:///cn=System: Modify Services,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=services,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "createtimestamp || entryusn || ipakrbauthzdata || ipakrbprincipalalias || ipauniqueid || krbcanonicalname || krblastpwdchange || krbobjectreferences || krbpasswordexpiration || krbprincipalaliases || krbprincipalexpiration || krbprincipalname || managedby || memberof || modifytimestamp || objectclass || usercertificate")(targetfilter = "(objectclass=ipaservice)")(version 3.0;acl "permission:System: Read Services";allow (compare,read,search) userdn = "ldap:///all";;)
diff --git a/API.txt b/API.txt
index c5e76c759ec2cb66af2fecd451ca9fa5d1ec9959..2d237792ab0cc8baa12a160daddcbcde89571e43 100644
--- a/API.txt
+++ b/API.txt
@@ -1808,6 +1808,18 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
+command: host_add_create_keytab
+args: 1,6,3
+arg: Str('fqdn', attribute=True, cli_name='hostname', 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)
+option: Flag('no_members', autofill=True, default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('user*', alwaysask=True, cli_name='users', csv=True)
+option: Str('version?', exclude='webui')
+output: Output('completed', <type 'int'>, None)
+output: Output('failed', <type 'dict'>, None)
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: host_add_managedby
 args: 1,5,3
 arg: Str('fqdn', attribute=True, cli_name='hostname', multivalue=False, primary_key=True, query=True, required=True)
@@ -1819,6 +1831,18 @@ option: Str('version?', exclude='webui')
 output: Output('completed', <type 'int'>, None)
 output: Output('failed', <type 'dict'>, None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+command: host_add_retrieve_keytab
+args: 1,6,3
+arg: Str('fqdn', attribute=True, cli_name='hostname', 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)
+option: Flag('no_members', autofill=True, default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('user*', alwaysask=True, cli_name='users', csv=True)
+option: Str('version?', exclude='webui')
+output: Output('completed', <type 'int'>, None)
+output: Output('failed', <type 'dict'>, None)
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: host_del
 args: 1,3,3
 arg: Str('fqdn', attribute=True, cli_name='hostname', multivalue=True, primary_key=True, query=True, required=True)
@@ -1906,6 +1930,18 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
+command: host_remove_create_keytab
+args: 1,6,3
+arg: Str('fqdn', attribute=True, cli_name='hostname', 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)
+option: Flag('no_members', autofill=True, default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('user*', alwaysask=True, cli_name='users', csv=True)
+option: Str('version?', exclude='webui')
+output: Output('completed', <type 'int'>, None)
+output: Output('failed', <type 'dict'>, None)
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: host_remove_managedby
 args: 1,5,3
 arg: Str('fqdn', attribute=True, cli_name='hostname', multivalue=False, primary_key=True, query=True, required=True)
@@ -1917,6 +1953,18 @@ option: Str('version?', exclude='webui')
 output: Output('completed', <type 'int'>, None)
 output: Output('failed', <type 'dict'>, None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+command: host_remove_retrieve_keytab
+args: 1,6,3
+arg: Str('fqdn', attribute=True, cli_name='hostname', 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)
+option: Flag('no_members', autofill=True, default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('user*', alwaysask=True, cli_name='users', csv=True)
+option: Str('version?', exclude='webui')
+output: Output('completed', <type 'int'>, None)
+output: Output('failed', <type 'dict'>, None)
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: host_show
 args: 1,6,3
 arg: Str('fqdn', attribute=True, cli_name='hostname', multivalue=False, primary_key=True, query=True, required=True)
@@ -3451,6 +3499,18 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
+command: service_add_create_keytab
+args: 1,6,3
+arg: Str('krbprincipalname', attribute=True, cli_name='principal', 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)
+option: Flag('no_members', autofill=True, default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('user*', alwaysask=True, cli_name='users', csv=True)
+option: Str('version?', exclude='webui')
+output: Output('completed', <type 'int'>, None)
+output: Output('failed', <type 'dict'>, None)
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: service_add_host
 args: 1,5,3
 arg: Str('krbprincipalname', attribute=True, cli_name='principal', multivalue=False, primary_key=True, query=True, required=True)
@@ -3462,6 +3522,18 @@ option: Str('version?', exclude='webui')
 output: Output('completed', <type 'int'>, None)
 output: Output('failed', <type 'dict'>, None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+command: service_add_retrieve_keytab
+args: 1,6,3
+arg: Str('krbprincipalname', attribute=True, cli_name='principal', 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)
+option: Flag('no_members', autofill=True, default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('user*', alwaysask=True, cli_name='users', csv=True)
+option: Str('version?', exclude='webui')
+output: Output('completed', <type 'int'>, None)
+output: Output('failed', <type 'dict'>, None)
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: service_del
 args: 1,2,3
 arg: Str('krbprincipalname', attribute=True, cli_name='principal', multivalue=True, primary_key=True, query=True, required=True)
@@ -3513,6 +3585,18 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
+command: service_remove_create_keytab
+args: 1,6,3
+arg: Str('krbprincipalname', attribute=True, cli_name='principal', 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)
+option: Flag('no_members', autofill=True, default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('user*', alwaysask=True, cli_name='users', csv=True)
+option: Str('version?', exclude='webui')
+output: Output('completed', <type 'int'>, None)
+output: Output('failed', <type 'dict'>, None)
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: service_remove_host
 args: 1,5,3
 arg: Str('krbprincipalname', attribute=True, cli_name='principal', multivalue=False, primary_key=True, query=True, required=True)
@@ -3524,6 +3608,18 @@ option: Str('version?', exclude='webui')
 output: Output('completed', <type 'int'>, None)
 output: Output('failed', <type 'dict'>, None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+command: service_remove_retrieve_keytab
+args: 1,6,3
+arg: Str('krbprincipalname', attribute=True, cli_name='principal', 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)
+option: Flag('no_members', autofill=True, default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('user*', alwaysask=True, cli_name='users', csv=True)
+option: Str('version?', exclude='webui')
+output: Output('completed', <type 'int'>, None)
+output: Output('failed', <type 'dict'>, None)
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: service_show
 args: 1,6,3
 arg: Str('krbprincipalname', attribute=True, cli_name='principal', multivalue=False, primary_key=True, query=True, required=True)
diff --git a/VERSION b/VERSION
index efd4f615d190ee2e0ed9ca1b310c4fef0cf87adc..217ff574273b0647c35b120835f84d3a11b6127a 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=104
-# Last change: mbasti - autofill --admin-email in DNS zone
+IPA_API_VERSION_MINOR=105
+# Last change: pvoborni - manage authorization of keytab operations
diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py
index e589a53219766a2aa41bbb4abef7fc3a12fcc267..375441c0fd55efe70d5a6f5bed6700e618874082 100644
--- a/ipalib/plugins/baseldap.py
+++ b/ipalib/plugins/baseldap.py
@@ -494,6 +494,23 @@ def host_is_master(ldap, fqdn):
         return
 
 
+def add_missing_object_class(ldap, objectclass, dn, entry_attrs=None, update=True):
+    """
+    Add object class if missing into entry. Fetches entry if not passed. Updates
+    the entry by default.
+
+    Returns the entry
+    """
+
+    if not entry_attrs:
+        entry_attrs = ldap.get_entry(dn, ['objectclass'])
+    if (objectclass.lower() not in (o.lower() for o in entry_attrs['objectclass'])):
+        entry_attrs['objectclass'].append(objectclass)
+        if update:
+            ldap.update_entry(entry_attrs)
+    return entry_attrs
+
+
 class LDAPObject(Object):
     """
     Object representing a LDAP entry.
diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py
index bbee09395dc55730d400944729a5bed48670f785..2a2097f5a78b6e81e165de9da481133c9575e3a1 100644
--- a/ipalib/plugins/host.py
+++ b/ipalib/plugins/host.py
@@ -28,10 +28,11 @@ from ipalib.plugins.baseldap import (LDAPQuery, LDAPObject, LDAPCreate,
                                      LDAPDelete, LDAPUpdate, LDAPSearch,
                                      LDAPRetrieve, LDAPAddMember,
                                      LDAPRemoveMember, host_is_master,
-                                     pkey_to_value)
+                                     pkey_to_value, add_missing_object_class)
 from ipalib.plugins.service import (split_principal, validate_certificate,
     set_certificate_attrs, ticket_flags_params, update_krbticketflags,
-    set_kerberos_attrs)
+    set_kerberos_attrs, rename_ipaallowedtoperform_from_ldap,
+    rename_ipaallowedtoperform_to_ldap)
 from ipalib.plugins.dns import (dns_container_exists, _record_types,
         add_records_for_host_validation, add_records_for_host,
         get_reverse_zone)
@@ -102,6 +103,9 @@ EXAMPLES:
 """) + _("""
  Add a host that can manage this host's keytab and certificate:
    ipa host-add-managedby --hosts=test2 test
+""") + _("""
+ Allow user to create keytab:
+   ipa host-add-create-keytab test2 --users=tuser1
 """)
 
 register = Registry()
@@ -201,6 +205,18 @@ host_output_params = (
     Str('sshpubkeyfp*',
         label=_('SSH public key fingerprint'),
     ),
+    Str('ipaallowedtoperform_read_keys_user',
+        label=_('Users allowed to retrieve keytab'),
+    ),
+    Str('ipaallowedtoperform_read_keys_group',
+        label=_('Groups allowed to retrieve keytab'),
+    ),
+    Str('ipaallowedtoperform_write_keys_user',
+        label=_('Users allowed to create keytab'),
+    ),
+    Str('ipaallowedtoperform_write_keys_group',
+        label=_('Groups allowed to create keytab'),
+    ),
 )
 
 
@@ -241,17 +257,18 @@ class host(LDAPObject):
     object_name = _('host')
     object_name_plural = _('hosts')
     object_class = ['ipaobject', 'nshost', 'ipahost', 'pkiuser', 'ipaservice']
+    possible_objectclasses = ['ipaallowedoperations']
     permission_filter_objectclasses = ['ipahost']
     # object_class_config = 'ipahostobjectclasses'
     search_attributes = [
         'fqdn', 'description', 'l', 'nshostlocation', 'krbprincipalname',
-        'nshardwareplatform', 'nsosversion', 'managedby'
+        'nshardwareplatform', 'nsosversion', 'managedby', 'ipaallowedtoperform'
     ]
     default_attributes = [
         'fqdn', 'description', 'l', 'nshostlocation', 'krbprincipalname',
         'nshardwareplatform', 'nsosversion', 'usercertificate', 'memberof',
         'managedby', 'memberindirect', 'memberofindirect', 'macaddress',
-        'userclass'
+        'userclass', 'ipaallowedtoperform'
     ]
     uuid_attribute = 'ipauniqueid'
     attribute_members = {
@@ -261,6 +278,8 @@ class host(LDAPObject):
         'managing': ['host'],
         'memberofindirect': ['hostgroup', 'netgroup', 'role', 'hbacrule',
         'sudorule'],
+        'ipaallowedtoperform_read_keys': ['user', 'group'],
+        'ipaallowedtoperform_write_keys': ['user', 'group'],
     }
     bindable = True
     relationships = {
@@ -268,6 +287,8 @@ class host(LDAPObject):
         'enrolledby': ('Enrolled by', 'enroll_by_', 'not_enroll_by_'),
         'managedby': ('Managed by', 'man_by_', 'not_man_by_'),
         'managing': ('Managing', 'man_', 'not_man_'),
+        '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 = [('userpassword', 'has_password'),
                            ('krbprincipalkey', 'has_keytab')]
@@ -344,6 +365,11 @@ class host(LDAPObject):
             ],
             'default_privileges': {'Host Administrators', 'Host Enrollment'},
         },
+        'System: Manage Host Keytab Permissions': {
+            'ipapermright': {'read', 'search', 'compare', 'write'},
+            'ipapermdefaultattr': {'ipaallowedtoperform', 'objectclass'},
+            'default_privileges': {'Host Administrators'},
+        },
         'System: Modify Hosts': {
             'ipapermright': {'write'},
             'ipapermdefaultattr': {
@@ -629,6 +655,7 @@ class host_add(LDAPCreate):
             )
         set_certificate_attrs(entry_attrs)
         set_kerberos_attrs(entry_attrs, options)
+        rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
 
         if options.get('all', False):
             entry_attrs['managing'] = self.obj.get_managed_hosts(dn)
@@ -888,6 +915,7 @@ class host_mod(LDAPUpdate):
             entry_attrs['randompassword'] = unicode(getattr(context, 'randompassword'))
         set_certificate_attrs(entry_attrs)
         set_kerberos_attrs(entry_attrs, options)
+        rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
         self.obj.get_password_attributes(ldap, dn, entry_attrs)
         if entry_attrs['has_password']:
             # If an OTP is set there is no keytab, at least not one
@@ -974,6 +1002,7 @@ class host_find(LDAPSearch):
         for entry_attrs in entries:
             set_certificate_attrs(entry_attrs)
             set_kerberos_attrs(entry_attrs, options)
+            rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
             self.obj.get_password_attributes(ldap, entry_attrs.dn, entry_attrs)
             self.obj.suppress_netgroup_memberof(ldap, entry_attrs)
             if entry_attrs['has_password']:
@@ -1012,6 +1041,7 @@ class host_show(LDAPRetrieve):
 
         set_certificate_attrs(entry_attrs)
         set_kerberos_attrs(entry_attrs, options)
+        rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
 
         if options.get('all', False):
             entry_attrs['managing'] = self.obj.get_managed_hosts(dn)
@@ -1159,3 +1189,72 @@ class host_remove_managedby(LDAPRemoveMember):
         self.obj.suppress_netgroup_memberof(ldap, entry_attrs)
         return (completed, dn)
 
+
+@register()
+class host_add_retrieve_keytab(LDAPAddMember):
+    __doc__ = _('Add users or groups who can retrieve keytab of this host.')
+    member_attributes = ['ipaallowedtoperform_read_keys']
+    has_output_params = LDAPAddMember.has_output_params + host_output_params
+
+    def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
+        rename_ipaallowedtoperform_to_ldap(found)
+        rename_ipaallowedtoperform_to_ldap(not_found)
+        add_missing_object_class(ldap, u'ipaallowedoperations', dn)
+        return dn
+
+    def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
+        rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
+        rename_ipaallowedtoperform_from_ldap(failed, options)
+        return (completed, dn)
+
+
+@register()
+class host_remove_retrieve_keytab(LDAPRemoveMember):
+    __doc__ = _('Remove users or groups who can retrieve keytab of this host.')
+    member_attributes = ['ipaallowedtoperform_read_keys']
+    has_output_params = LDAPRemoveMember.has_output_params + host_output_params
+
+    def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
+        rename_ipaallowedtoperform_to_ldap(found)
+        rename_ipaallowedtoperform_to_ldap(not_found)
+        return dn
+
+    def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
+        rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
+        rename_ipaallowedtoperform_from_ldap(failed, options)
+        return (completed, dn)
+
+
+@register()
+class host_add_create_keytab(LDAPAddMember):
+    __doc__ = _('Add users or groups who can create a keytab of this host.')
+    member_attributes = ['ipaallowedtoperform_write_keys']
+    has_output_params = LDAPAddMember.has_output_params + host_output_params
+
+    def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
+        rename_ipaallowedtoperform_to_ldap(found)
+        rename_ipaallowedtoperform_to_ldap(not_found)
+        add_missing_object_class(ldap, u'ipaallowedoperations', dn)
+        return dn
+
+    def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
+        rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
+        rename_ipaallowedtoperform_from_ldap(failed, options)
+        return (completed, dn)
+
+
+@register()
+class host_remove_create_keytab(LDAPRemoveMember):
+    __doc__ = _('Remove users or groups who can create a keytab of this host.')
+    member_attributes = ['ipaallowedtoperform_write_keys']
+    has_output_params = LDAPRemoveMember.has_output_params + host_output_params
+
+    def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
+        rename_ipaallowedtoperform_to_ldap(found)
+        rename_ipaallowedtoperform_to_ldap(not_found)
+        return dn
+
+    def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
+        rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
+        rename_ipaallowedtoperform_from_ldap(failed, options)
+        return (completed, dn)
diff --git a/ipalib/plugins/service.py b/ipalib/plugins/service.py
index 3ca5066f300a004c993660e0a5e220c364e38cbf..3c75ae386450b9b9d77ff46fa2c6ba811e1529bd 100644
--- a/ipalib/plugins/service.py
+++ b/ipalib/plugins/service.py
@@ -86,7 +86,10 @@ EXAMPLES:
 
  Request a certificate for an IPA service:
    ipa cert-request --principal=HTTP/web.example.com example.csr
-
+""") + _("""
+ Allow user to create keytab:
+   ipa service-add-create-keytab HTTP/web.example.com --users=tuser1
+""") + _("""
  Generate and retrieve a keytab for an IPA service:
    ipa-getkeytab -s ipa.example.com -p HTTP/web.example.com -k /etc/httpd/httpd.keytab
 
@@ -127,7 +130,19 @@ output_params = (
     ),
     Str('revocation_reason?',
         label=_('Revocation reason'),
-    )
+    ),
+    Str('ipaallowedtoperform_read_keys_user',
+        label=_('Users allowed to retrieve keytab'),
+    ),
+    Str('ipaallowedtoperform_read_keys_group',
+        label=_('Groups allowed to retrieve keytab'),
+    ),
+    Str('ipaallowedtoperform_write_keys_user',
+        label=_('Users allowed to create keytab'),
+    ),
+    Str('ipaallowedtoperform_write_keys_group',
+        label=_('Groups allowed to create keytab'),
+    ),
 )
 
 ticket_flags_params = (
@@ -290,6 +305,23 @@ def set_kerberos_attrs(entry_attrs, options):
         if name in options or all_opt:
             entry_attrs[name] = bool(ticket_flags & value)
 
+def rename_ipaallowedtoperform_from_ldap(entry_attrs, options):
+    if options.get('raw', False):
+        return
+
+    for subtype in ('read_keys', 'write_keys'):
+        name = 'ipaallowedtoperform;%s' % subtype
+        if name in entry_attrs:
+            new_name = 'ipaallowedtoperform_%s' % subtype
+            entry_attrs[new_name] = entry_attrs.pop(name)
+
+def rename_ipaallowedtoperform_to_ldap(entry_attrs):
+    for subtype in ('read_keys', 'write_keys'):
+        name = 'ipaallowedtoperform_%s' % subtype
+        if name in entry_attrs:
+            new_name = 'ipaallowedtoperform;%s' % subtype
+            entry_attrs[new_name] = entry_attrs.pop(name)
+
 @register()
 class service(LDAPObject):
     """
@@ -302,19 +334,24 @@ class service(LDAPObject):
         'krbprincipal', 'krbprincipalaux', 'krbticketpolicyaux', 'ipaobject',
         'ipaservice', 'pkiuser'
     ]
-    possible_objectclasses = ['ipakrbprincipal']
+    possible_objectclasses = ['ipakrbprincipal', 'ipaallowedoperations']
     permission_filter_objectclasses = ['ipaservice']
-    search_attributes = ['krbprincipalname', 'managedby', 'ipakrbauthzdata']
+    search_attributes = ['krbprincipalname', 'managedby', 'ipakrbauthzdata',
+        'ipaallowedtoperform']
     default_attributes = ['krbprincipalname', 'usercertificate', 'managedby',
-        'ipakrbauthzdata', 'memberof']
+        'ipakrbauthzdata', 'memberof', 'ipaallowedtoperform']
     uuid_attribute = 'ipauniqueid'
     attribute_members = {
         'managedby': ['host'],
         'memberof': ['role'],
+        'ipaallowedtoperform_read_keys': ['user', 'group'],
+        'ipaallowedtoperform_write_keys': ['user', 'group'],
     }
     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 = {
@@ -346,6 +383,11 @@ class service(LDAPObject):
             ],
             'default_privileges': {'Service Administrators', 'Host Administrators'},
         },
+        'System: Manage Service Keytab Permissions': {
+            'ipapermright': {'read', 'search', 'compare', 'write'},
+            'ipapermdefaultattr': {'ipaallowedtoperform', 'objectclass'},
+            'default_privileges': {'Service Administrators', 'Host Administrators'},
+        },
         'System: Modify Services': {
             'ipapermright': {'write'},
             'ipapermdefaultattr': {'usercertificate'},
@@ -469,6 +511,7 @@ class service_add(LDAPCreate):
 
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
         set_kerberos_attrs(entry_attrs, options)
+        rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
         return dn
 
 
@@ -561,6 +604,7 @@ class service_mod(LDAPUpdate):
         assert isinstance(dn, DN)
         set_certificate_attrs(entry_attrs)
         set_kerberos_attrs(entry_attrs, options)
+        rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
         return dn
 
 
@@ -598,6 +642,7 @@ class service_find(LDAPSearch):
             self.obj.get_password_attributes(ldap, entry_attrs.dn, entry_attrs)
             set_certificate_attrs(entry_attrs)
             set_kerberos_attrs(entry_attrs, options)
+            rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
         return truncated
 
 
@@ -620,6 +665,7 @@ class service_show(LDAPRetrieve):
 
         set_certificate_attrs(entry_attrs)
         set_kerberos_attrs(entry_attrs, options)
+        rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
 
         return dn
 
@@ -654,6 +700,75 @@ class service_remove_host(LDAPRemoveMember):
     has_output_params = LDAPRemoveMember.has_output_params + output_params
 
 
+@register()
+class service_add_retrieve_keytab(LDAPAddMember):
+    __doc__ = _('Add users or groups who can retrieve keytab of this service.')
+    member_attributes = ['ipaallowedtoperform_read_keys']
+    has_output_params = LDAPAddMember.has_output_params + output_params
+
+    def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
+        rename_ipaallowedtoperform_to_ldap(found)
+        rename_ipaallowedtoperform_to_ldap(not_found)
+        add_missing_object_class(ldap, u'ipaallowedoperations', dn)
+        return dn
+
+    def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
+        rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
+        rename_ipaallowedtoperform_from_ldap(failed, options)
+        return (completed, dn)
+
+
+@register()
+class service_remove_retrieve_keytab(LDAPRemoveMember):
+    __doc__ = _('Remove users or groups who can retrieve keytab of this service.')
+    member_attributes = ['ipaallowedtoperform_read_keys']
+    has_output_params = LDAPRemoveMember.has_output_params + output_params
+
+    def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
+        rename_ipaallowedtoperform_to_ldap(found)
+        rename_ipaallowedtoperform_to_ldap(not_found)
+        return dn
+
+    def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
+        rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
+        rename_ipaallowedtoperform_from_ldap(failed, options)
+        return (completed, dn)
+
+
+@register()
+class service_add_create_keytab(LDAPAddMember):
+    __doc__ = _('Add users or groups who can create a keytab of this service.')
+    member_attributes = ['ipaallowedtoperform_write_keys']
+    has_output_params = LDAPAddMember.has_output_params + output_params
+
+    def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
+        rename_ipaallowedtoperform_to_ldap(found)
+        rename_ipaallowedtoperform_to_ldap(not_found)
+        add_missing_object_class(ldap, u'ipaallowedoperations', dn)
+        return dn
+
+    def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
+        rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
+        rename_ipaallowedtoperform_from_ldap(failed, options)
+        return (completed, dn)
+
+
+@register()
+class service_remove_create_keytab(LDAPRemoveMember):
+    __doc__ = _('Remove users or groups who can create a keytab of this service.')
+    member_attributes = ['ipaallowedtoperform_write_keys']
+    has_output_params = LDAPRemoveMember.has_output_params + output_params
+
+    def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
+        rename_ipaallowedtoperform_to_ldap(found)
+        rename_ipaallowedtoperform_to_ldap(not_found)
+        return dn
+
+    def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
+        rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
+        rename_ipaallowedtoperform_from_ldap(failed, options)
+        return (completed, dn)
+
 
 @register()
 class service_disable(LDAPQuery):
-- 
1.9.3

From 55efd61cfd7b0b5170452eedab105829cc51e308 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 3 Oct 2014 13:00:57 +0200
Subject: [PATCH] tests: management of keytab permissions

https://fedorahosted.org/freeipa/ticket/4419
---
 ipatests/test_xmlrpc/test_host_plugin.py    | 362 +++++++++++++++++++++++++++
 ipatests/test_xmlrpc/test_service_plugin.py | 368 ++++++++++++++++++++++++++++
 2 files changed, 730 insertions(+)

diff --git a/ipatests/test_xmlrpc/test_host_plugin.py b/ipatests/test_xmlrpc/test_host_plugin.py
index 479afd100e974d1380fc7d940782d4bd5a2f158f..48a6f6b54a4d85b988370a43a06a38ac38b4031e 100644
--- a/ipatests/test_xmlrpc/test_host_plugin.py
+++ b/ipatests/test_xmlrpc/test_host_plugin.py
@@ -34,6 +34,8 @@ from nose.plugins.skip import SkipTest
 from ipatests.test_xmlrpc.xmlrpc_test import (Declarative, XMLRPC_test,
     fuzzy_uuid, fuzzy_digits, fuzzy_hash, fuzzy_date, fuzzy_issuer,
     fuzzy_hex)
+from ipatests.test_xmlrpc.test_user_plugin import (
+    get_user_result, get_user_dn, get_group_dn)
 from ipatests.test_xmlrpc import objectclasses
 from ipatests.test_xmlrpc.testcert import get_testcert
 import base64
@@ -139,6 +141,13 @@ ipv6_fromip_ptr_dn = DN(('idnsname', ipv6_fromip_ptr), revipv6zone_dn)
 sshpubkey = u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGAX3xAeLeaJggwTqMjxNwa6XHBUAikXPGMzEpVrlLDCZtv00djsFTBi38PkgxBJVkgRWMrcBsr/35lq7P6w8KGIwA8GI48Z0qBS2NBMJ2u9WQ2hjLN6GdMlo77O0uJY3251p12pCVIS/bHRSq8kHO2No8g7KA9fGGcagPfQH+ee3t7HUkpbQkFTmbPPN++r3V8oVUk5LxbryB3UIIVzNmcSIn3JrXynlvui4MixvrtX6zx+O/bBo68o8/eZD26QrahVbA09fivrn/4h3TM019Eu/c2jOdckfU3cHUV/3Tno5d6JicibyaoDDK7S/yjdn5jhaz8MSEayQvFkZkiF0L public key test'
 sshpubkeyfp = u'13:67:6B:BF:4E:A2:05:8E:AE:25:8B:A1:31:DE:6F:1B public key test (ssh-rsa)'
 
+user1 = u'tuser1'
+user2 = u'tuser2'
+group1 = u'group1'
+group1_dn = get_group_dn(group1)
+group2 = u'group2'
+group2_dn = get_group_dn(group2)
+
 class test_host(Declarative):
 
     cleanup_commands = [
@@ -1402,3 +1411,356 @@ class test_host_dns(Declarative):
             ),
         ),
     ]
+
+
+class test_host_allowed_to(Declarative):
+    cleanup_commands = [
+        ('user_del', [user1], {}),
+        ('user_del', [user2], {}),
+        ('group_del', [group1], {}),
+        ('group_del', [group2], {}),
+        ('host_del', [fqdn1], {}),
+    ]
+
+    tests = [
+        # prepare entries
+        dict(
+            desc='Create %r' % user1,
+            command=(
+                'user_add', [], dict(givenname=u'Test', sn=u'User1')
+            ),
+            expected=dict(
+                value=user1,
+                summary=u'Added user "%s"' % user1,
+                result=get_user_result(user1, u'Test', u'User1', 'add'),
+            ),
+        ),
+        dict(
+            desc='Create %r' % user2,
+            command=(
+                'user_add', [], dict(givenname=u'Test', sn=u'User2')
+            ),
+            expected=dict(
+                value=user2,
+                summary=u'Added user "%s"' % user2,
+                result=get_user_result(user2, u'Test', u'User2', 'add'),
+            ),
+        ),
+        dict(
+            desc='Create group: %r' % group1,
+            command=(
+                'group_add', [group1], dict()
+            ),
+            expected=dict(
+                value=group1,
+                summary=u'Added group "%s"' % group1,
+                result=dict(
+                    cn=[group1],
+                    objectclass=objectclasses.group + [u'posixgroup'],
+                    ipauniqueid=[fuzzy_uuid],
+                    gidnumber=[fuzzy_digits],
+                    dn=group1_dn
+                ),
+            ),
+        ),
+        dict(
+            desc='Create group: %r' % group2,
+            command=(
+                'group_add', [group2], dict()
+            ),
+            expected=dict(
+                value=group2,
+                summary=u'Added group "%s"' % group2,
+                result=dict(
+                    cn=[group2],
+                    objectclass=objectclasses.group + [u'posixgroup'],
+                    ipauniqueid=[fuzzy_uuid],
+                    gidnumber=[fuzzy_digits],
+                    dn=group2_dn
+                ),
+            ),
+        ),
+        dict(
+            desc='Create %r' % fqdn1,
+            command=(
+                'host_add', [fqdn1],
+                dict(
+                    force=True,
+                ),
+            ),
+            expected=dict(
+                value=fqdn1,
+                summary=u'Added host "%s"' % fqdn1,
+                result=dict(
+                    dn=dn1,
+                    fqdn=[fqdn1],
+                    krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)],
+                    objectclass=objectclasses.host,
+                    ipauniqueid=[fuzzy_uuid],
+                    managedby_host=[fqdn1],
+                    has_keytab=False,
+                    has_password=False,
+                ),
+            ),
+        ),
+
+        # verify
+        dict(
+            desc='Allow %r to a retrieve keytab of %r' % (user1, fqdn1),
+            command=('host_add_retrieve_keytab', [fqdn1],
+                     dict(user=user1)),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_read_keys=dict(
+                        group=[],
+                        user=[],
+                    ),
+                ),
+                completed=1,
+                result=dict(
+                    dn=dn1,
+                    fqdn=[fqdn1],
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Duplicate add: user %r' % (user1),
+            command=('host_add_retrieve_keytab', [fqdn1],
+                     dict(user=user1)),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_read_keys=dict(
+                        group=[],
+                        user=[[user1, u'This entry is already a member']],
+                    ),
+                ),
+                completed=0,
+                result=dict(
+                    dn=dn1,
+                    fqdn=[fqdn1],
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Allow %r, %r to a retrieve keytab of %r' % (
+                group1, group2, fqdn1),
+            command=('host_add_retrieve_keytab', [fqdn1],
+                     dict(group=[group1, group2])),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_read_keys=dict(
+                        group=[],
+                        user=[],
+                    ),
+                ),
+                completed=2,
+                result=dict(
+                    dn=dn1,
+                    fqdn=[fqdn1],
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1, group2],
+                    krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Invalid removal of retrieve keytab %r' % (user2),
+            command=('host_remove_retrieve_keytab', [fqdn1],
+                     dict(user=[user2])),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_read_keys=dict(
+                        group=[],
+                        user=[[user2, u'This entry is not a member']],
+                    ),
+                ),
+                completed=0,
+                result=dict(
+                    dn=dn1,
+                    fqdn=[fqdn1],
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1, group2],
+                    krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Removal of retrieve keytab %r' % (group2),
+            command=('host_remove_retrieve_keytab', [fqdn1],
+                     dict(group=[group2])),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_read_keys=dict(
+                        group=[],
+                        user=[],
+                    ),
+                ),
+                completed=1,
+                result=dict(
+                    dn=dn1,
+                    fqdn=[fqdn1],
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1],
+                    krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Allow %r, %r to a create keytab of %r' % (
+                group1, user1, fqdn1),
+            command=('host_add_create_keytab', [fqdn1],
+                     dict(group=[group1, group2], user=[user1])),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_write_keys=dict(
+                        group=[],
+                        user=[],
+                    ),
+                ),
+                completed=3,
+                result=dict(
+                    dn=dn1,
+                    fqdn=[fqdn1],
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1],
+                    ipaallowedtoperform_write_keys_user=[user1],
+                    ipaallowedtoperform_write_keys_group=[group1, group2],
+                    krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Duplicate add: %r, %r' % (user1, group1),
+            command=('host_add_create_keytab', [fqdn1],
+                     dict(group=[group1], user=[user1])),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_write_keys=dict(
+                        group=[[group1, u'This entry is already a member']],
+                        user=[[user1, u'This entry is already a member']],
+                    ),
+                ),
+                completed=0,
+                result=dict(
+                    dn=dn1,
+                    fqdn=[fqdn1],
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1],
+                    ipaallowedtoperform_write_keys_user=[user1],
+                    ipaallowedtoperform_write_keys_group=[group1, group2],
+                    krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Invalid removal of create keytab %r' % (user2),
+            command=('host_remove_create_keytab', [fqdn1],
+                     dict(user=[user2])),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_write_keys=dict(
+                        group=[],
+                        user=[[user2, u'This entry is not a member']],
+                    ),
+                ),
+                completed=0,
+                result=dict(
+                    dn=dn1,
+                    fqdn=[fqdn1],
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1],
+                    ipaallowedtoperform_write_keys_user=[user1],
+                    ipaallowedtoperform_write_keys_group=[group1, group2],
+                    krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Removal of create keytab %r' % (group2),
+            command=('host_remove_create_keytab', [fqdn1],
+                     dict(group=[group2])),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_write_keys=dict(
+                        group=[],
+                        user=[],
+                    ),
+                ),
+                completed=1,
+                result=dict(
+                    dn=dn1,
+                    fqdn=[fqdn1],
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1],
+                    ipaallowedtoperform_write_keys_user=[user1],
+                    ipaallowedtoperform_write_keys_group=[group1],
+                    krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Presence of ipaallowedtoperform in show output',
+            command=('host_show', [fqdn1], {}),
+            expected=dict(
+                value=fqdn1,
+                summary=None,
+                result=dict(
+                    dn=dn1,
+                    fqdn=[fqdn1],
+                    has_keytab=False,
+                    has_password=False,
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1],
+                    ipaallowedtoperform_write_keys_user=[user1],
+                    ipaallowedtoperform_write_keys_group=[group1],
+                    krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Presence of ipaallowedtoperform in mod output',
+            command=(
+                'host_mod', [fqdn1],
+                dict(description=u"desc")),
+            expected=dict(
+                value=fqdn1,
+                summary=u'Modified host "%s"' % fqdn1,
+                result=dict(
+                    description=[u"desc"],
+                    fqdn=[fqdn1],
+                    has_keytab=False,
+                    has_password=False,
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1],
+                    ipaallowedtoperform_write_keys_user=[user1],
+                    ipaallowedtoperform_write_keys_group=[group1],
+                    krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+    ]
diff --git a/ipatests/test_xmlrpc/test_service_plugin.py b/ipatests/test_xmlrpc/test_service_plugin.py
index c29c94d8619261197e0d445909ec8be4f78cd691..90d54904bff47cfc7434f123861d2df618b382b1 100644
--- a/ipatests/test_xmlrpc/test_service_plugin.py
+++ b/ipatests/test_xmlrpc/test_service_plugin.py
@@ -27,6 +27,8 @@ from ipatests.test_xmlrpc.xmlrpc_test import fuzzy_digits, fuzzy_date, fuzzy_iss
 from ipatests.test_xmlrpc.xmlrpc_test import fuzzy_hex
 from ipatests.test_xmlrpc import objectclasses
 from ipatests.test_xmlrpc.testcert import get_testcert
+from ipatests.test_xmlrpc.test_user_plugin import (
+    get_user_result, get_user_dn, get_group_dn)
 import base64
 from ipapython.dn import DN
 
@@ -46,6 +48,12 @@ 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'
 
+user1 = u'tuser1'
+user2 = u'tuser2'
+group1 = u'group1'
+group1_dn = get_group_dn(group1)
+group2 = u'group2'
+group2_dn = get_group_dn(group2)
 
 class test_service(Declarative):
 
@@ -752,3 +760,363 @@ class test_service_in_role(Declarative):
             ),
         ),
     ]
+
+
+class test_service_allowed_to(Declarative):
+    cleanup_commands = [
+        ('user_del', [user1], {}),
+        ('user_del', [user2], {}),
+        ('group_del', [group1], {}),
+        ('group_del', [group2], {}),
+        ('host_del', [fqdn1], {}),
+        ('service_del', [service1], {}),
+    ]
+
+    tests = [
+        # prepare entries
+        dict(
+            desc='Create %r' % user1,
+            command=(
+                'user_add', [], dict(givenname=u'Test', sn=u'User1')
+            ),
+            expected=dict(
+                value=user1,
+                summary=u'Added user "%s"' % user1,
+                result=get_user_result(user1, u'Test', u'User1', 'add'),
+            ),
+        ),
+        dict(
+            desc='Create %r' % user2,
+            command=(
+                'user_add', [], dict(givenname=u'Test', sn=u'User2')
+            ),
+            expected=dict(
+                value=user2,
+                summary=u'Added user "%s"' % user2,
+                result=get_user_result(user2, u'Test', u'User2', 'add'),
+            ),
+        ),
+        dict(
+            desc='Create group: %r' % group1,
+            command=(
+                'group_add', [group1], dict()
+            ),
+            expected=dict(
+                value=group1,
+                summary=u'Added group "%s"' % group1,
+                result=dict(
+                    cn=[group1],
+                    objectclass=objectclasses.group + [u'posixgroup'],
+                    ipauniqueid=[fuzzy_uuid],
+                    gidnumber=[fuzzy_digits],
+                    dn=group1_dn
+                ),
+            ),
+        ),
+        dict(
+            desc='Create group: %r' % group2,
+            command=(
+                'group_add', [group2], dict()
+            ),
+            expected=dict(
+                value=group2,
+                summary=u'Added group "%s"' % group2,
+                result=dict(
+                    cn=[group2],
+                    objectclass=objectclasses.group + [u'posixgroup'],
+                    ipauniqueid=[fuzzy_uuid],
+                    gidnumber=[fuzzy_digits],
+                    dn=group2_dn
+                ),
+            ),
+        ),
+        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_no_realm], 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],
+                ),
+            ),
+        ),
+
+        # verify
+        dict(
+            desc='Allow %r to a retrieve keytab of %r' % (user1, service1),
+            command=('service_add_retrieve_keytab', [service1],
+                     dict(user=user1)),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_read_keys=dict(
+                        group=[],
+                        user=[],
+                    ),
+                ),
+                completed=1,
+                result=dict(
+                    dn=service1dn,
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    krbprincipalname=[service1],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Duplicate add: user %r' % (user1),
+            command=('service_add_retrieve_keytab', [service1],
+                     dict(user=user1)),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_read_keys=dict(
+                        group=[],
+                        user=[[user1, u'This entry is already a member']],
+                    ),
+                ),
+                completed=0,
+                result=dict(
+                    dn=service1dn,
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    krbprincipalname=[service1],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Allow %r, %r to a retrieve keytab of %r' % (
+                group1, group2, service1),
+            command=('service_add_retrieve_keytab', [service1],
+                     dict(group=[group1, group2])),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_read_keys=dict(
+                        group=[],
+                        user=[],
+                    ),
+                ),
+                completed=2,
+                result=dict(
+                    dn=service1dn,
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1, group2],
+                    krbprincipalname=[service1],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Invalid removal of retrieve keytab %r' % (user2),
+            command=('service_remove_retrieve_keytab', [service1],
+                     dict(user=[user2])),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_read_keys=dict(
+                        group=[],
+                        user=[[user2, u'This entry is not a member']],
+                    ),
+                ),
+                completed=0,
+                result=dict(
+                    dn=service1dn,
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1, group2],
+                    krbprincipalname=[service1],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Removal of retrieve keytab %r' % (group2),
+            command=('service_remove_retrieve_keytab', [service1],
+                     dict(group=[group2])),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_read_keys=dict(
+                        group=[],
+                        user=[],
+                    ),
+                ),
+                completed=1,
+                result=dict(
+                    dn=service1dn,
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1],
+                    krbprincipalname=[service1],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Allow %r, %r to a create keytab of %r' % (
+                group1, user1, service1),
+            command=('service_add_create_keytab', [service1],
+                     dict(group=[group1, group2], user=[user1])),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_write_keys=dict(
+                        group=[],
+                        user=[],
+                    ),
+                ),
+                completed=3,
+                result=dict(
+                    dn=service1dn,
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1],
+                    ipaallowedtoperform_write_keys_user=[user1],
+                    ipaallowedtoperform_write_keys_group=[group1, group2],
+                    krbprincipalname=[service1],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Duplicate add: %r, %r' % (user1, group1),
+            command=('service_add_create_keytab', [service1],
+                     dict(group=[group1], user=[user1])),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_write_keys=dict(
+                        group=[[group1, u'This entry is already a member']],
+                        user=[[user1, u'This entry is already a member']],
+                    ),
+                ),
+                completed=0,
+                result=dict(
+                    dn=service1dn,
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1],
+                    ipaallowedtoperform_write_keys_user=[user1],
+                    ipaallowedtoperform_write_keys_group=[group1, group2],
+                    krbprincipalname=[service1],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Invalid removal of create keytab %r' % (user2),
+            command=('service_remove_create_keytab', [service1],
+                     dict(user=[user2])),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_write_keys=dict(
+                        group=[],
+                        user=[[user2, u'This entry is not a member']],
+                    ),
+                ),
+                completed=0,
+                result=dict(
+                    dn=service1dn,
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1],
+                    ipaallowedtoperform_write_keys_user=[user1],
+                    ipaallowedtoperform_write_keys_group=[group1, group2],
+                    krbprincipalname=[service1],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Removal of create keytab %r' % (group2),
+            command=('service_remove_create_keytab', [service1],
+                     dict(group=[group2])),
+            expected=dict(
+                failed=dict(
+                    ipaallowedtoperform_write_keys=dict(
+                        group=[],
+                        user=[],
+                    ),
+                ),
+                completed=1,
+                result=dict(
+                    dn=service1dn,
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1],
+                    ipaallowedtoperform_write_keys_user=[user1],
+                    ipaallowedtoperform_write_keys_group=[group1],
+                    krbprincipalname=[service1],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Presence of ipaallowedtoperform in show output',
+            command=('service_show', [service1_no_realm], {}),
+            expected=dict(
+                value=service1,
+                summary=None,
+                result=dict(
+                    dn=service1dn,
+                    has_keytab=False,
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1],
+                    ipaallowedtoperform_write_keys_user=[user1],
+                    ipaallowedtoperform_write_keys_group=[group1],
+                    krbprincipalname=[service1],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Presence of ipaallowedtoperform in mod output',
+            command=(
+                'service_mod', [service1_no_realm],
+                dict(ipakrbokasdelegate=True)),
+            expected=dict(
+                value=service1,
+                summary=u'Modified service "%s"' % service1,
+                result=dict(
+                    ipaallowedtoperform_read_keys_user=[user1],
+                    ipaallowedtoperform_read_keys_group=[group1],
+                    ipaallowedtoperform_write_keys_user=[user1],
+                    ipaallowedtoperform_write_keys_group=[group1],
+                    ipakrbokasdelegate=True,
+                    krbprincipalname=[service1],
+                    krbticketflags=[u'1048704'],
+                    managedby_host=[fqdn1],
+                ),
+            ),
+        ),
+    ]
-- 
1.9.3

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to