Hi,

the attached patches fix <https://fedorahosted.org/freeipa/ticket/3416> and <https://fedorahosted.org/freeipa/ticket/5401>.


I worked around the issue of checking if the user is privileged to perform replica promotion by using host credentials instead. The host must be a member of the IPA servers host group "ipaservers" in order to be able to promote itself. Using host credentials will also allow replica install using one-time password.

User credentials are still used for connection check and to automatically add the host to ipaservers if the user is privileged to do that.

Simo, is this approach OK? Could you check the new ACIs in patches 510 and 513?

I have a couple of questions:

1) Why are custodia keys for the replica added to LDAP using connection to the remote master instead of local ldapi connection? Is it to eliminate race conditions caused by replication timeout from the replica to the remote master?

If the code was changed to use ldapi and wait until the key appears in custodia on the remote master, we could lose the "IPA server hosts can create own Custodia secrets" and "IPA server hosts can manage own Custodia secrets" ACIs from patch 510. Not sure if it's worth the change though.

2) Why is 'memberPrincipal' used in cn=custodia instead of 'member'?

If 'member' was used instead, we would gain referential integrity and the ability to add ACIs based on the attribute (think userattr="member#USERDN").

3) Why is 'memberPrincipal' used in cn=custodia at all?

The hostname of the replica is already in 'cn', so instead of searching cn=custodia for entries matching (memberPrincipal=host/$HOSTNAME), we could get cn={enc,sig}/$HOSTNAME,cn=custodia directly.

Honza

--
Jan Cholasta
From fe5cf036513648a6b9398fb118ade33f4cd1b25a Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 11 Nov 2015 11:01:57 +0100
Subject: [PATCH] aci: add IPA servers host group 'ipaservers'

https://fedorahosted.org/freeipa/ticket/3416
---
 ACI.txt                                        |   4 +-
 install/share/bootstrap-template.ldif          |  11 +++
 install/share/default-aci.ldif                 |  11 ---
 install/updates/20-ipaservers_hostgroup.update |  10 +++
 install/updates/40-delegation.update           |  18 ++--
 install/updates/Makefile.am                    |   1 +
 ipalib/plugins/host.py                         |   6 ++
 ipalib/plugins/hostgroup.py                    |  26 ++++++
 ipaserver/install/krbinstance.py               |   7 ++
 ipaserver/install/replication.py               | 111 -------------------------
 10 files changed, 75 insertions(+), 130 deletions(-)
 create mode 100644 install/updates/20-ipaservers_hostgroup.update

diff --git a/ACI.txt b/ACI.txt
index 40fa822..bbc2e66 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -119,7 +119,7 @@ aci: (targetattr = "usercertificate")(targetfilter = "(objectclass=ipahost)")(ve
 dn: cn=computers,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "userpassword")(targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System: Manage Host Enrollment Password";allow (write) groupdn = "ldap:///cn=System: Manage Host Enrollment Password,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 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";)
+aci: (targetattr = "krblastpwdchange || krbprincipalkey")(targetfilter = "(&(!(memberOf=cn=ipaservers,cn=hostgroups,cn=accounts,dc=ipa,dc=example))(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;read_keys || ipaallowedtoperform;write_keys || 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
@@ -137,7 +137,7 @@ aci: (targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System
 dn: cn=hostgroups,cn=accounts,dc=ipa,dc=example
 aci: (targetfilter = "(objectclass=ipahostgroup)")(version 3.0;acl "permission:System: Add Hostgroups";allow (add) groupdn = "ldap:///cn=System: Add Hostgroups,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=hostgroups,cn=accounts,dc=ipa,dc=example
-aci: (targetattr = "member")(targetfilter = "(objectclass=ipahostgroup)")(version 3.0;acl "permission:System: Modify Hostgroup Membership";allow (write) groupdn = "ldap:///cn=System: Modify Hostgroup Membership,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+aci: (targetattr = "member")(targetfilter = "(&(!(cn=ipaservers))(objectclass=ipahostgroup))")(version 3.0;acl "permission:System: Modify Hostgroup Membership";allow (write) groupdn = "ldap:///cn=System: Modify Hostgroup Membership,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=hostgroups,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "cn || description")(targetfilter = "(objectclass=ipahostgroup)")(version 3.0;acl "permission:System: Modify Hostgroups";allow (write) groupdn = "ldap:///cn=System: Modify Hostgroups,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=hostgroups,cn=accounts,dc=ipa,dc=example
diff --git a/install/share/bootstrap-template.ldif b/install/share/bootstrap-template.ldif
index 3570627..628a8e2 100644
--- a/install/share/bootstrap-template.ldif
+++ b/install/share/bootstrap-template.ldif
@@ -261,6 +261,17 @@ description: Limited admins who can edit other users
 cn: editors
 ipaUniqueID: autogenerate
 
+dn: cn=ipaservers,cn=hostgroups,cn=accounts,$SUFFIX
+changetype: add
+objectClass: top
+objectClass: groupOfNames
+objectClass: nestedGroup
+objectClass: ipaobject
+objectClass: ipahostgroup
+description: IPA server hosts
+cn: ipaservers
+ipaUniqueID: autogenerate
+
 dn: cn=sshd,cn=hbacservices,cn=hbac,$SUFFIX
 changetype: add
 objectclass: ipahbacservice
diff --git a/install/share/default-aci.ldif b/install/share/default-aci.ldif
index 7b174e7..dd15cbe 100644
--- a/install/share/default-aci.ldif
+++ b/install/share/default-aci.ldif
@@ -77,17 +77,6 @@ changetype: modify
 add: aci
 aci: (targetattr="userPassword || krbPrincipalKey")(version 3.0; acl "Search existence of password and kerberos keys"; allow(search) userdn = "ldap:///all";;)
 
-# Let host add and update CA renewal certificates
-dn: cn=ipa,cn=etc,$SUFFIX
-changetype: modify
-add: aci
-aci: (target="ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX";)(version 3.0; acl "Add CA Certificates for renewals"; allow(add) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)
-
-dn: cn=ipa,cn=etc,$SUFFIX
-changetype: modify
-add: aci
-aci: (target="ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX";)(targetattr="userCertificate")(version 3.0; acl "Modify CA Certificates for renewals"; allow(write) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)
-
 # Let users manage their own tokens
 dn: $SUFFIX
 changetype: modify
diff --git a/install/updates/20-ipaservers_hostgroup.update b/install/updates/20-ipaservers_hostgroup.update
new file mode 100644
index 0000000..9d2a7cc
--- /dev/null
+++ b/install/updates/20-ipaservers_hostgroup.update
@@ -0,0 +1,10 @@
+dn: cn=ipaservers,cn=hostgroups,cn=accounts,$SUFFIX
+default: objectClass: top
+default: objectClass: groupOfNames
+default: objectClass: nestedGroup
+default: objectClass: ipaobject
+default: objectClass: ipahostgroup
+default: description: IPA server hosts
+default: cn: ipaservers
+default: ipaUniqueID: autogenerate
+add: member: fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX
diff --git a/install/updates/40-delegation.update b/install/updates/40-delegation.update
index 08906a6..f0431b9 100644
--- a/install/updates/40-delegation.update
+++ b/install/updates/40-delegation.update
@@ -60,8 +60,10 @@ default:cn: SELinux User Map Administrators
 default:description: SELinux User Map Administrators
 
 dn: cn=ipa,cn=etc,$SUFFIX
-add:aci:(target = "ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX";)(version 3.0; acl "Add CA Certificates for renewals"; allow(add) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)
-add:aci:(target = "ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX";)(targetattr = "userCertificate")(version 3.0; acl "Modify CA Certificates for renewals"; allow(write) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)
+remove:aci:(target = "ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX";)(version 3.0; acl "Add CA Certificates for renewals"; allow(add) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)
+remove:aci:(target = "ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX";)(targetattr = "userCertificate")(version 3.0; acl "Modify CA Certificates for renewals"; allow(write) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)
+add:aci:(target = "ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX";)(version 3.0; acl "Add CA Certificates for renewals"; allow(add) groupdn = "ldap:///cn=ipaservers,cn=hostgroups,cn=accounts,$SUFFIX";;)
+add:aci:(target = "ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX";)(targetattr = "userCertificate")(version 3.0; acl "Modify CA Certificates for renewals"; allow(write) groupdn = "ldap:///cn=ipaservers,cn=hostgroups,cn=accounts,$SUFFIX";;)
 
 # Add permissions "Retrieve Certificates from the CA" and "Revoke Certificate"
 # to privilege "Host Administrators"
@@ -72,10 +74,12 @@ dn: cn=Revoke Certificate,cn=permissions,cn=pbac,$SUFFIX
 add: member: cn=Host Administrators,cn=privileges,cn=pbac,$SUFFIX
 
 dn: cn=ipa,cn=etc,$SUFFIX
-add:aci:(target = "ldap:///cn=CAcert,cn=ipa,cn=etc,$SUFFIX";)(targetattr = cACertificate)(version 3.0; acl "Modify CA Certificate"; allow (write) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)
+remove:aci:(target = "ldap:///cn=CAcert,cn=ipa,cn=etc,$SUFFIX";)(targetattr = cACertificate)(version 3.0; acl "Modify CA Certificate"; allow (write) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)
+add:aci:(target = "ldap:///cn=CAcert,cn=ipa,cn=etc,$SUFFIX";)(targetattr = cACertificate)(version 3.0; acl "Modify CA Certificate"; allow (write) groupdn = "ldap:///cn=ipaservers,cn=hostgroups,cn=accounts,$SUFFIX";;)
 
 dn: cn=certificates,cn=ipa,cn=etc,$SUFFIX
-add:aci:(targetfilter = "(&(objectClass=ipaCertificate)(ipaConfigString=ipaCA))")(targetattr = "ipaCertIssuerSerial || cACertificate")(version 3.0; acl "Modify CA Certificate Store Entry"; allow (write) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)
+remove:aci:(targetfilter = "(&(objectClass=ipaCertificate)(ipaConfigString=ipaCA))")(targetattr = "ipaCertIssuerSerial || cACertificate")(version 3.0; acl "Modify CA Certificate Store Entry"; allow (write) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)
+add:aci:(targetfilter = "(&(objectClass=ipaCertificate)(ipaConfigString=ipaCA))")(targetattr = "ipaCertIssuerSerial || cACertificate")(version 3.0; acl "Modify CA Certificate Store Entry"; allow (write) groupdn = "ldap:///cn=ipaservers,cn=hostgroups,cn=accounts,$SUFFIX";;)
 
 # Automember tasks
 dn: cn=Automember Task Administrator,cn=privileges,cn=pbac,$SUFFIX
@@ -197,8 +201,10 @@ default:cn: IPA Masters Readers
 default:description: Read list of IPA masters
 
 dn: cn=masters,cn=ipa,cn=etc,$SUFFIX
-add:aci:(targetfilter = "(objectClass=nsContainer)")(targetattr = "cn || objectClass || ipaConfigString")(version 3.0; acl "Read IPA Masters"; allow (read, search, compare) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)
-add:aci:(targetfilter = "(objectClass=nsContainer)")(targetattr = "ipaConfigString")(version 3.0; acl "Modify IPA Masters"; allow (write) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)
+remove:aci:(targetfilter = "(objectClass=nsContainer)")(targetattr = "cn || objectClass || ipaConfigString")(version 3.0; acl "Read IPA Masters"; allow (read, search, compare) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)
+remove:aci:(targetfilter = "(objectClass=nsContainer)")(targetattr = "ipaConfigString")(version 3.0; acl "Modify IPA Masters"; allow (write) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)
+add:aci:(targetfilter = "(objectClass=nsContainer)")(targetattr = "cn || objectClass || ipaConfigString")(version 3.0; acl "Read IPA Masters"; allow (read, search, compare) groupdn = "ldap:///cn=ipaservers,cn=hostgroups,cn=accounts,$SUFFIX";;)
+add:aci:(targetfilter = "(objectClass=nsContainer)")(targetattr = "ipaConfigString")(version 3.0; acl "Modify IPA Masters"; allow (write) groupdn = "ldap:///cn=ipaservers,cn=hostgroups,cn=accounts,$SUFFIX";;)
 
 # PassSync
 dn: cn=PassSync Service,cn=privileges,cn=pbac,$SUFFIX
diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am
index 6c8fa11..b04ab48 100644
--- a/install/updates/Makefile.am
+++ b/install/updates/Makefile.am
@@ -14,6 +14,7 @@ app_DATA =				\
 	20-dna.update			\
 	20-host_nis_groups.update	\
 	20-indices.update		\
+	20-ipaservers_hostgroup.update	\
 	20-nss_ldap.update		\
 	20-replication.update		\
 	20-sslciphers.update		\
diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py
index bceab31..be7e2f2 100644
--- a/ipalib/plugins/host.py
+++ b/ipalib/plugins/host.py
@@ -395,6 +395,12 @@ class host(LDAPObject):
         },
         'System: Manage Host Keytab': {
             'ipapermright': {'write'},
+            'ipapermtargetfilter': [
+                '(objectclass=ipahost)',
+                '(!(memberOf=%s))' % DN('cn=ipaservers',
+                                        api.env.container_hostgroup,
+                                        api.env.basedn),
+            ],
             'ipapermdefaultattr': {'krblastpwdchange', 'krbprincipalkey'},
             'replaces': [
                 '(targetattr = "krbprincipalkey || krblastpwdchange")(target = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX";)(version 3.0;acl "permission:Manage host keytab";allow (write) groupdn = "ldap:///cn=Manage host keytab,cn=permissions,cn=pbac,$SUFFIX";)',
diff --git a/ipalib/plugins/hostgroup.py b/ipalib/plugins/hostgroup.py
index 596290f..f3e0d72 100644
--- a/ipalib/plugins/hostgroup.py
+++ b/ipalib/plugins/hostgroup.py
@@ -72,6 +72,8 @@ def get_complete_hostgroup_member_list(hostgroup):
 
 register = Registry()
 
+PROTECTED_HOSTGROUPS = (u'ipaservers',)
+
 
 @register()
 class hostgroup(LDAPObject):
@@ -121,6 +123,10 @@ class hostgroup(LDAPObject):
         },
         'System: Modify Hostgroup Membership': {
             'ipapermright': {'write'},
+            'ipapermtargetfilter': [
+                '(objectclass=ipahostgroup)',
+                '(!(cn=ipaservers))',
+            ],
             'ipapermdefaultattr': {'member'},
             'replaces': [
                 '(targetattr = "member")(target = "ldap:///cn=*,cn=hostgroups,cn=accounts,$SUFFIX";)(version 3.0;acl "permission:Modify Hostgroup membership";allow (write) groupdn = "ldap:///cn=Modify Hostgroup membership,cn=permissions,cn=pbac,$SUFFIX";)',
@@ -229,6 +235,14 @@ class hostgroup_del(LDAPDelete):
 
     msg_summary = _('Deleted hostgroup "%(value)s"')
 
+    def pre_callback(self, ldap, dn, *keys, **options):
+        if keys[0] in PROTECTED_HOSTGROUPS:
+            raise errors.ProtectedEntryError(label=_(u'hostgroup'),
+                                             key=keys[0],
+                                             reason=_(u'privileged hostgroup'))
+
+        return dn
+
 
 @register()
 class hostgroup_mod(LDAPUpdate):
@@ -283,6 +297,18 @@ class hostgroup_add_member(LDAPAddMember):
 class hostgroup_remove_member(LDAPRemoveMember):
     __doc__ = _('Remove members from a hostgroup.')
 
+    def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
+        if keys[0] in PROTECTED_HOSTGROUPS and 'host' in options:
+            result = api.Command.hostgroup_show(keys[0])
+            hosts_left = set(result['result'].get('member_host', []))
+            hosts_deleted = set(options['host'])
+            if hosts_left.issubset(hosts_deleted):
+                raise errors.LastMemberError(key=sorted(hosts_deleted)[0],
+                                             label=_(u'hostgroup'),
+                                             container=keys[0])
+
+        return dn
+
     def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
         assert isinstance(dn, DN)
         self.obj.suppress_netgroup_memberof(ldap, dn, entry_attrs)
diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py
index 1dd807c..f928e50 100644
--- a/ipaserver/install/krbinstance.py
+++ b/ipaserver/install/krbinstance.py
@@ -117,6 +117,13 @@ class KrbInstance(service.Service):
             host_entry['krbticketflags'] = service_entry['krbticketflags']
         self.admin_conn.add_entry(host_entry)
 
+        # Add the host to the ipaserver host group
+        hostgroup_dn = DN(('cn', 'ipaservers'), ('cn', 'hostgroups'),
+                          ('cn', 'accounts'), self.suffix)
+        hostgroup_entry = self.admin_conn.get_entry(hostgroup_dn, ['member'])
+        hostgroup_entry.setdefault('member', []).append(host_dn)
+        self.admin_conn.update_entry(hostgroup_entry)
+
     def __common_setup(self, realm_name, host_name, domain_name, admin_password):
         self.fqdn = host_name
         self.realm = realm_name.upper()
diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
index 443f7ca..a4a55be 100644
--- a/ipaserver/install/replication.py
+++ b/ipaserver/install/replication.py
@@ -1266,117 +1266,6 @@ class ReplicationManager(object):
                 err = e
 
         try:
-            entry = self.conn.get_entry(
-                DN(('cn', 'ipa'), ('cn', 'etc'), self.suffix), ['aci'])
-
-            sub = {'suffix': self.suffix, 'fqdn': replica}
-            try:
-                entry.raw['aci'].remove(
-                    b'(target = "ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,'
-                    b'%(suffix)s")(version 3.0; acl "Add CA Certificates for '
-                    b'renewals"; allow(add) userdn = "ldap:///fqdn=%(fqdn)s,'
-                    b'cn=computers,cn=accounts,%(suffix)s";)' % sub)
-            except ValueError:
-                pass
-            try:
-                entry.raw['aci'].remove(
-                    b'(target = "ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,'
-                    b'%(suffix)s")(targetattr = "userCertificate")'
-                    b'(version 3.0; acl "Modify CA Certificates for renewals"; '
-                    b'allow(write) userdn = "ldap:///fqdn=%(fqdn)s,'
-                    b'cn=computers,cn=accounts,%(suffix)s";)' % sub)
-            except ValueError:
-                pass
-            try:
-                entry.raw['aci'].remove(
-                    b'(target = "ldap:///cn=CAcert,cn=ipa,cn=etc,%(suffix)s")'
-                    b'(targetattr = cACertificate)(version 3.0; acl "Modify CA '
-                    b'Certificate"; allow (write) userdn = "ldap:///fqdn='
-                    b'%(fqdn)s,cn=computers,cn=accounts,%(suffix)s";)' % sub)
-            except ValueError:
-                pass
-
-            try:
-                self.conn.update_entry(entry)
-            except errors.EmptyModlist:
-                pass
-        except errors.NotFound:
-            pass
-        except Exception as e:
-            if not force:
-                raise e
-            elif not err:
-                err = e
-
-        try:
-            entry = self.conn.get_entry(
-                DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
-                   self.suffix),
-                ['aci'])
-
-            sub = {'suffix': self.suffix, 'fqdn': replica}
-            try:
-                entry.raw['aci'].remove(
-                    b'(targetfilter = "(objectClass=nsContainer)")'
-                    b'(targetattr = "cn || objectClass || ipaConfigString")'
-                    b'(version 3.0; acl "Read IPA Masters"; allow (read, '
-                    b'search, compare) userdn = "ldap:///fqdn=%(fqdn)s,'
-                    b'cn=computers,cn=accounts,%(suffix)s";)' % sub)
-            except ValueError:
-                pass
-            try:
-                entry.raw['aci'].remove(
-                    b'(targetfilter = "(objectClass=nsContainer)")'
-                    b'(targetattr = "ipaConfigString")(version 3.0; acl '
-                    b'"Modify IPA Masters"; allow (write) userdn = '
-                    b'"ldap:///fqdn=%(fqdn)s,cn=computers,cn=accounts,'
-                    b'%(suffix)s";)' % sub)
-            except ValueError:
-                pass
-
-            try:
-                self.conn.update_entry(entry)
-            except errors.EmptyModlist:
-                pass
-        except errors.NotFound:
-            pass
-        except Exception as e:
-            if not force:
-                raise e
-            elif not err:
-                err = e
-
-        try:
-            entry = self.conn.get_entry(
-                DN(('cn', 'certificates'), ('cn', 'ipa'), ('cn', 'etc'),
-                   self.suffix),
-                ['aci'])
-
-            sub = {'suffix': self.suffix, 'fqdn': replica}
-            try:
-                entry.raw['aci'].remove(
-                    b'(targetfilter = "(&(objectClass=ipaCertificate)'
-                    b'(ipaConfigString=ipaCA))")(targetattr = '
-                    b'"ipaCertIssuerSerial || cACertificate")(version 3.0; acl '
-                    b'"Modify CA Certificate Store Entry"; allow (write) '
-                    b'userdn = "ldap:///fqdn=%(fqdn)s,cn=computers,cn=accounts,'
-                    b'%(suffix)s";)' % sub)
-            except ValueError:
-                pass
-
-            try:
-                self.conn.update_entry(entry)
-            except errors.EmptyModlist:
-                pass
-        except errors.NotFound:
-            pass
-        except Exception as e:
-            if not force:
-                raise e
-            elif not err:
-                err = e
-
-        try:
             basedn = DN(('cn', 'etc'), self.suffix)
             filter = '(dnaHostname=%s)' % replica
             entries = self.conn.get_entries(
-- 
2.4.3

From bc95ad2c01bbd44173e73e2ddb6d81c5f9f7b4c0 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Fri, 13 Nov 2015 08:15:55 +0100
Subject: [PATCH 1/5] aci: allow members of ipaservers to set up replication

Add ACIs which allow the members of the ipaservers host group to set up
replication. This allows IPA hosts to perform replica promotion on
themselves.

https://fedorahosted.org/freeipa/ticket/5401
---
 install/updates/20-aci.update   | 13 +++++++++++++
 install/updates/45-roles.update |  1 +
 2 files changed, 14 insertions(+)

diff --git a/install/updates/20-aci.update b/install/updates/20-aci.update
index cba1897..d8555e7 100644
--- a/install/updates/20-aci.update
+++ b/install/updates/20-aci.update
@@ -91,3 +91,16 @@ add:aci: (target = "ldap:///krbprincipalname=*/($$dn)@$REALM,cn=services,cn=acco
 # CIFS service on the master can manage ID ranges
 dn: cn=ranges,cn=etc,$SUFFIX
 add:aci: (target = "ldap:///cn=*,cn=ranges,cn=etc,$SUFFIX";)(targetfilter = "(objectClass=ipaIDrange)")(version 3.0;acl "CIFS service can manage ID ranges for trust"; allow(all) userdn="ldap:///krbprincipalname=cifs/*@$REALM,cn=services,cn=accounts,$SUFFIX"; and groupdn="ldap:///cn=adtrust agents,cn=sysaccounts,cn=etc,$SUFFIX";)
+
+# IPA server hosts can modify replication managers members
+dn: cn=sysaccounts,cn=etc,$SUFFIX
+add:aci: (target = "ldap:///cn=replication managers,cn=sysaccounts,cn=etc,$SUFFIX")(targetattr = "member")(version 3.0; acl "IPA server hosts can modify replication managers members"; allow(read, search, compare, write) groupdn = "ldap:///cn=ipaservers,cn=hostgroups,cn=accounts,$SUFFIX";;)
+
+# IPA server hosts can change replica ID
+dn: cn=etc,$SUFFIX
+add:aci: (target = "ldap:///cn=replication,cn=etc,$SUFFIX";)(targetattr = "nsDS5ReplicaId")(version 3.0; acl "IPA server hosts can change replica ID"; allow(write) groupdn = "ldap:///cn=ipaservers,cn=hostgroups,cn=accounts,$SUFFIX";;)
+
+# IPA server hosts can create and manage own Custodia secrets
+dn: cn=custodia,cn=ipa,cn=etc,$SUFFIX
+add:aci: (target = "ldap:///cn=*/($$dn),cn=custodia,cn=ipa,cn=etc,$SUFFIX")(version 3.0; acl "IPA server hosts can create own Custodia secrets"; allow(add) userdn = "ldap:///fqdn=($$dn),cn=computers,cn=accounts,$SUFFIX" and groupdn = "ldap:///cn=ipaservers,cn=hostgroups,cn=accounts,$SUFFIX";;)
+add:aci: (target = "ldap:///cn=*/($$dn),cn=custodia,cn=ipa,cn=etc,$SUFFIX")(targetattr = "memberPrincipal || ipaPublicKey")(version 3.0; acl "IPA server hosts can manage own Custodia secrets"; allow(write) userdn = "ldap:///fqdn=($$dn),cn=computers,cn=accounts,$SUFFIX" and groupdn = "ldap:///cn=ipaservers,cn=hostgroups,cn=accounts,$SUFFIX";;)
diff --git a/install/updates/45-roles.update b/install/updates/45-roles.update
index dd4549f..fb28464 100644
--- a/install/updates/45-roles.update
+++ b/install/updates/45-roles.update
@@ -82,6 +82,7 @@ dn: cn=Delegation Administrator,cn=privileges,cn=pbac,$SUFFIX
 add:member: cn=Security Architect,cn=roles,cn=accounts,$SUFFIX
 
 dn: cn=Replication Administrators,cn=privileges,cn=pbac,$SUFFIX
+add:member: cn=ipaservers,cn=hostgroups,cn=accounts,$SUFFIX
 add:member: cn=Security Architect,cn=roles,cn=accounts,$SUFFIX
 
 dn: cn=Write IPA Configuration,cn=privileges,cn=pbac,$SUFFIX
-- 
2.4.3

From f2dd0c89eeeb55f176c17e72c4b8b6a30722d416 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 18 Nov 2015 08:34:35 +0100
Subject: [PATCH 2/5] ipautil: use file in a temporary dir as ccache in
 private_ccache

python-gssapi chokes on empty ccache files, so instead of creating an empty
temporary ccache file in private_ccache, create a temporary directory and
use a non-existent file in that directory as the ccache.

https://fedorahosted.org/freeipa/ticket/5401
---
 ipapython/ipautil.py | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index 4acdd1a..b42581c 100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -1354,8 +1354,10 @@ def posixify(string):
 def private_ccache(path=None):
 
     if path is None:
-        (desc, path) = tempfile.mkstemp(prefix='krbcc')
-        os.close(desc)
+        dir_path = tempfile.mkdtemp(prefix='krbcc')
+        path = os.path.join(dir_path, 'ccache')
+    else:
+        dir_path = None
 
     original_value = os.environ.get('KRB5CCNAME', None)
 
@@ -1371,6 +1373,8 @@ def private_ccache(path=None):
 
         if os.path.exists(path):
             os.remove(path)
+        if dir_path is not None:
+            os.rmdir(dir_path)
 
 
 if six.PY2:
-- 
2.4.3

From 472ffcc52b2ed1bb29b2fb9677e0663d3574d01d Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 18 Nov 2015 08:51:34 +0100
Subject: [PATCH 3/5] replica promotion: use host credentials when setting up
 replication

Use the local host credentials rather than the user credentials when
setting up replication. The host must be a member of the ipaservers host
group. The user credentials are still required for connection check.

https://fedorahosted.org/freeipa/ticket/5401
---
 install/tools/ipa-replica-install          |  1 -
 ipaserver/install/server/replicainstall.py | 56 ++++++++++++++++++++++++------
 2 files changed, 45 insertions(+), 12 deletions(-)

diff --git a/install/tools/ipa-replica-install b/install/tools/ipa-replica-install
index 60a853b..10a1082 100755
--- a/install/tools/ipa-replica-install
+++ b/install/tools/ipa-replica-install
@@ -30,7 +30,6 @@ ReplicaInstall = cli.install_tool(
     usage='%prog [options] REPLICA_FILE',
     log_file_name=paths.IPAREPLICA_INSTALL_LOG,
     debug_option=True,
-    use_private_ccache=False,
 )
 
 
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
index 5ce9eb7..8a20ed4 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -804,7 +804,23 @@ def promote_check(installer):
 
     installutils.verify_fqdn(config.host_name, options.no_host_dns)
     installutils.verify_fqdn(config.master_host_name, options.no_host_dns)
-    installutils.check_creds(options, config.realm_name)
+
+    ccache = os.environ['KRB5CCNAME']
+    ipautil.kinit_keytab('host/{env.host}@{env.realm}'.format(env=api.env),
+                         paths.KRB5_KEYTAB,
+                         ccache)
+
+    if not options.skip_conncheck:
+        if installer._ccache is None:
+            del os.environ['KRB5CCNAME']
+        else:
+            os.environ['KRB5CCNAME'] = installer._ccache
+
+        try:
+            installutils.check_creds(options, config.realm_name)
+            installer._ccache = os.environ.get('KRB5CCNAME')
+        finally:
+            os.environ['KRB5CCNAME'] = ccache
 
     cafile = paths.IPA_CA_CRT
     if not ipautil.file_exists(cafile):
@@ -820,10 +836,19 @@ def promote_check(installer):
     replman = None
     try:
         # Try out authentication
-        conn.connect(ccache=os.environ.get('KRB5CCNAME'))
+        conn.connect(ccache=ccache)
         replman = ReplicationManager(config.realm_name,
                                      config.master_host_name, None)
 
+        # Check authorization
+        result = remote_api.Command['hostgroup_find'](
+            cn=u'ipaservers',
+            host=[unicode(api.env.host)]
+        )['result']
+
+        if not result:
+            raise errors.ACIError(info="Not authorized")
+
         # Check that we don't already have a replication agreement
         try:
             (acn, adn) = replman.agreement_dn(config.host_name)
@@ -941,7 +966,7 @@ def promote_check(installer):
                 print(str(e))
                 sys.exit(1)
     except errors.ACIError:
-        sys.exit("\nInsufficiently privileges to promote the server.")
+        sys.exit("\nInsufficient privileges to promote the server.")
     except errors.LDAPError:
         sys.exit("\nUnable to connect to LDAP server %s" %
                  config.master_host_name)
@@ -960,10 +985,18 @@ def promote_check(installer):
 
     # check connection
     if not options.skip_conncheck:
-        replica_conn_check(
-            config.master_host_name, config.host_name, config.realm_name,
-            options.setup_ca, dogtag.Dogtag10Constants.DS_PORT,
-            options.admin_password, principal=options.principal)
+        if installer._ccache is None:
+            del os.environ['KRB5CCNAME']
+        else:
+            os.environ['KRB5CCNAME'] = installer._ccache
+
+        try:
+            replica_conn_check(
+                config.master_host_name, config.host_name, config.realm_name,
+                options.setup_ca, dogtag.Dogtag10Constants.DS_PORT,
+                options.admin_password, principal=options.principal)
+        finally:
+            os.environ['KRB5CCNAME'] = ccache
 
     if not ipautil.file_exists(cafile):
         raise RuntimeError("CA cert file is not available.")
@@ -1190,6 +1223,8 @@ class Replica(BaseServer):
     def __init__(self, **kwargs):
         super(Replica, self).__init__(**kwargs)
 
+        self._ccache = os.environ.get('KRB5CCNAME')
+
         self._top_dir = None
         self._config = None
         self._update_hosts_file = False
@@ -1217,7 +1252,6 @@ class Replica(BaseServer):
             yield
             promote(self)
         else:
-            with ipautil.private_ccache():
-                install_check(self)
-                yield
-                install(self)
+            install_check(self)
+            yield
+            install(self)
-- 
2.4.3

From 827e848745caada63cfb59895b4c5415144eddd0 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 18 Nov 2015 08:14:06 +0100
Subject: [PATCH 4/5] aci: allow hosts to do replica promotion checks

A number of checks which need read access to certain LDAP entries is done
during replica promotion. Add ACIs to allow these checks to be done using
any valid IPA host credentials.

https://fedorahosted.org/freeipa/ticket/5401
---
 install/updates/20-aci.update | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/install/updates/20-aci.update b/install/updates/20-aci.update
index d8555e7..dcbef07 100644
--- a/install/updates/20-aci.update
+++ b/install/updates/20-aci.update
@@ -32,6 +32,13 @@ remove:aci:(targetfilter="(objectclass=nsContainer)")(version 3.0; acl "Deny rea
 dn: cn=masters,cn=ipa,cn=etc,$SUFFIX
 add:aci:(targetfilter="(objectclass=nsContainer)")(targetattr="objectclass || cn")(version 3.0; acl "Read access to masters"; allow(read, search, compare) userdn = "ldap:///all";;)
 
+# Allow hosts to read masters service configuration
+add:aci:(targetfilter = "(objectclass=nsContainer)")(targetattr = "ipaConfigString")(version 3.0; acl "Allow hosts to read masters service configuration"; allow(read, search, compare) userdn = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX";;)
+
+# Allow hosts to read replication managers
+dn: cn=sysaccounts,cn=etc,$SUFFIX
+add:aci: (target = "ldap:///cn=replication managers,cn=sysaccounts,cn=etc,$SUFFIX")(targetattr = "objectClass || cn")(version 3.0; acl "Allow hosts to read replication managers"; allow(read, search, compare) userdn = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX";;)
+
 # Read access to Kerberos container (cn=kerberos) and realm containers (cn=$REALM,cn=kerberos)
 dn: cn=kerberos,$SUFFIX
 add:aci:(targetattr = "cn || objectclass")(targetfilter = "(|(objectclass=krbrealmcontainer)(objectclass=krbcontainer))")(version 3.0;acl "Anonymous read access to Kerberos containers";allow (read,compare,search) userdn = "ldap:///anyone";;)
@@ -54,6 +61,9 @@ add:aci:(targetattr="ipaUniqueId || memberOf || enrolledBy || krbExtraData || kr
 dn: cn=tasks,cn=config
 add:aci:(targetattr="*")(version 3.0; acl "Admin can read all tasks"; allow (read, compare, search) groupdn = "ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";;)
 
+dn: cn=mapping tree,cn=config
+add:aci: (target = "ldap:///cn=meTo($$dn),cn=*,cn=mapping tree,cn=config")(targetattr = "objectclass || cn")(version 3.0; acl "Allow hosts to read their replication agreements"; allow(read, search, compare) userdn = "ldap:///fqdn=($$dn),cn=computers,cn=accounts,$SUFFIX";)
+
 # Removal of obsolete ACIs
 dn: cn=config
 # Replaced by 'System: Read Replication Agreements'
-- 
2.4.3

From f12a25c0b3a8ce3ce113db633bb1248375889a98 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 18 Nov 2015 09:00:34 +0100
Subject: [PATCH 5/5] replica promotion: automatically add the local host to
 ipaservers

If the user is authorized to modify members of the ipaservers host group,
add the local host to ipaservers automatically.

https://fedorahosted.org/freeipa/ticket/5401
---
 ipaserver/install/server/replicainstall.py | 48 ++++++++++++++++++++++++++++--
 1 file changed, 46 insertions(+), 2 deletions(-)

diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
index 8a20ed4..057aa35 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -845,9 +845,35 @@ def promote_check(installer):
             cn=u'ipaservers',
             host=[unicode(api.env.host)]
         )['result']
+        add_to_ipaservers = not result
 
-        if not result:
-            raise errors.ACIError(info="Not authorized")
+        if add_to_ipaservers:
+            if installer._ccache is None:
+                del os.environ['KRB5CCNAME']
+            else:
+                os.environ['KRB5CCNAME'] = installer._ccache
+
+            try:
+                installutils.check_creds(options, config.realm_name)
+                installer._ccache = os.environ.get('KRB5CCNAME')
+            finally:
+                os.environ['KRB5CCNAME'] = ccache
+
+            conn.disconnect()
+            conn.connect(ccache=installer._ccache)
+
+            try:
+                result = remote_api.Command['hostgroup_show'](
+                    u'ipaservers',
+                    all=True,
+                    rights=True
+                )['result']
+
+                if 'w' not in result['attributelevelrights']['member']:
+                    raise errors.ACIError(info="Not authorized")
+            finally:
+                conn.disconnect()
+                conn.connect(ccache=ccache)
 
         # Check that we don't already have a replication agreement
         try:
@@ -1005,6 +1031,8 @@ def promote_check(installer):
     installer._fstore = fstore
     installer._sstore = sstore
     installer._config = config
+    installer._remote_api = remote_api
+    installer._add_to_ipaservers = add_to_ipaservers
 
 
 @common_cleanup
@@ -1014,6 +1042,22 @@ def promote(installer):
     sstore = installer._sstore
     config = installer._config
 
+    if installer._add_to_ipaservers:
+        ccache = os.environ['KRB5CCNAME']
+        remote_api = installer._remote_api
+        conn = remote_api.Backend.ldap2
+        try:
+            conn.connect(ccache=installer._ccache)
+
+            remote_api.Command['hostgroup_add_member'](
+                u'ipaservers',
+                host=[unicode(api.env.host)],
+            )
+        finally:
+            if conn.isconnected():
+                conn.disconnect()
+            os.environ['KRB5CCNAME'] = ccache
+
     # Save client file and merge in server directives
     target_fname = paths.IPA_DEFAULT_CONF
     fstore.backup_file(target_fname)
-- 
2.4.3

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to