diff --git a/ACI.txt b/ACI.txt index e4b4032d4..fdef43e63 100644 --- a/ACI.txt +++ b/ACI.txt @@ -269,6 +269,8 @@ aci: (targetattr = "krblastadminunlock || krblastfailedauth || krblastpwdchange dn: cn=users,cn=accounts,dc=ipa,dc=example aci: (targetattr = "memberof")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User Membership";allow (compare,read,search) userdn = "ldap:///all";) dn: cn=users,cn=accounts,dc=ipa,dc=example +aci: (targetattr = "ntuniqueid || ntuseracctexpires || ntusercodepage || ntuserdeleteaccount || ntuserdomainid || ntuserlastlogoff || ntuserlastlogon")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User NT Attributes";allow (compare,read,search) groupdn = "ldap:///cn=System: Read User NT Attributes,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=users,cn=accounts,dc=ipa,dc=example aci: (targetattr = "cn || createtimestamp || description || displayname || entryusn || gecos || gidnumber || givenname || homedirectory || initials || ipantsecurityidentifier || loginshell || manager || modifytimestamp || objectclass || sn || title || uid || uidnumber")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User Standard Attributes";allow (compare,read,search) userdn = "ldap:///anyone";) dn: dc=ipa,dc=example aci: (targetattr = "cn || createtimestamp || entryusn || gecos || gidnumber || homedirectory || loginshell || modifytimestamp || objectclass || uid || uidnumber")(target = "ldap:///cn=users,cn=*,cn=views,cn=compat,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read User Views Compat Tree";allow (compare,read,search) userdn = "ldap:///anyone";) diff --git a/install/updates/40-delegation.update b/install/updates/40-delegation.update index 988de5e19..a79f906ea 100644 --- a/install/updates/40-delegation.update +++ b/install/updates/40-delegation.update @@ -184,3 +184,33 @@ 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";)' + +# PassSync +dn: cn=PassSync Service,cn=privileges,cn=pbac,$SUFFIX +default:objectClass: nestedgroup +default:objectClass: groupofnames +default:objectClass: top +default:cn: PassSync Service +default:description: PassSync Service + +dn: cn=Read PassSync Managers Configuration,cn=permissions,cn=pbac,$SUFFIX +default:objectClass: groupofnames +default:objectClass: ipapermission +default:objectClass: top +default:cn: Read PassSync Managers Configuration +default:member: cn=Replication Administrators,cn=privileges,cn=pbac,$SUFFIX +default:ipapermissiontype: SYSTEM + +dn: cn=config +add:aci: '(targetattr = "cn || createtimestamp || entryusn || modifytimestamp || objectclass || passsyncmanagersdns*")(target = "ldap:///cn=ipa_pwd_extop,cn=plugins,cn=config")(version 3.0;acl "permission:Read PassSync Managers Configuration";allow (compare,read,search) groupdn = "ldap:///cn=Read PassSync Managers Configuration,cn=permissions,cn=pbac,$SUFFIX";)' + +dn: cn=Modify PassSync Managers Configuration,cn=permissions,cn=pbac,$SUFFIX +default:objectClass: groupofnames +default:objectClass: ipapermission +default:objectClass: top +default:cn: Modify PassSync Managers Configuration +default:member: cn=Replication Administrators,cn=privileges,cn=pbac,$SUFFIX +default:ipapermissiontype: SYSTEM + +dn: cn=config +add:aci: '(targetattr = "passsyncmanagersdns*")(target = "ldap:///cn=ipa_pwd_extop,cn=plugins,cn=config")(version 3.0;acl "permission:Modify PassSync Managers Configuration";allow (write) groupdn = "ldap:///cn=Modify PassSync Managers Configuration,cn=permissions,cn=pbac,$SUFFIX";)' diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py index e20628924..56585b9f8 100644 --- a/ipalib/plugins/user.py +++ b/ipalib/plugins/user.py @@ -373,10 +373,12 @@ class user(LDAPObject): 'replaces': [ '(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(targetattr = "userpassword || krbprincipalkey || sambalmpassword || sambantpassword || passwordhistory")(version 3.0;acl "permission:Change a user password";allow (write) groupdn = "ldap:///cn=Change a user password,cn=permissions,cn=pbac,$SUFFIX";)', '(targetfilter = "(!(memberOf=cn=admins,cn=groups,cn=accounts,$SUFFIX))")(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(targetattr = "userpassword || krbprincipalkey || sambalmpassword || sambantpassword || passwordhistory")(version 3.0;acl "permission:Change a user password";allow (write) groupdn = "ldap:///cn=Change a user password,cn=permissions,cn=pbac,$SUFFIX";)', + '(targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Windows PassSync service can write passwords"; allow (write) userdn="ldap:///uid=passsync,cn=sysaccounts,cn=etc,$SUFFIX";)', ], 'default_privileges': { 'User Administrators', 'Modify Users and Reset passwords', + 'PassSync Service', }, }, 'System: Manage User SSH Public Keys': { @@ -446,6 +448,16 @@ class user(LDAPObject): 'homedirectory', 'loginshell', }, }, + 'System: Read User NT Attributes': { + 'ipapermbindruletype': 'permission', + 'ipapermright': {'read', 'search', 'compare'}, + 'ipapermdefaultattr': { + 'ntuserdomainid', 'ntuniqueid', 'ntuseracctexpires', + 'ntusercodepage', 'ntuserdeleteaccount', 'ntuserlastlogoff', + 'ntuserlastlogon', + }, + 'default_privileges': {'PassSync Service'}, + }, } label = _('Users') diff --git a/ipaserver/install/plugins/Makefile.am b/ipaserver/install/plugins/Makefile.am index d651297ac..ead1d8f7d 100644 --- a/ipaserver/install/plugins/Makefile.am +++ b/ipaserver/install/plugins/Makefile.am @@ -14,6 +14,7 @@ app_PYTHON = \ update_referint.py \ ca_renewal_master.py \ update_uniqueness.py \ + update_passsync.py \ $(NULL) EXTRA_DIST = \ diff --git a/ipaserver/install/plugins/update_passsync.py b/ipaserver/install/plugins/update_passsync.py new file mode 100644 index 000000000..d6595a06f --- /dev/null +++ b/ipaserver/install/plugins/update_passsync.py @@ -0,0 +1,78 @@ +# +# Copyright (C) 2014 FreeIPA Contributors see COPYING for license +# + +from ipaserver.install.plugins import MIDDLE, LAST +from ipaserver.install.plugins.baseupdate import PreUpdate, PostUpdate +from ipalib import api, errors +from ipapython.dn import DN +from ipapython.ipa_log_manager import root_logger +from ipaserver.install import sysupgrade + +class update_passync_privilege_check(PreUpdate): + order = MIDDLE + + def execute(self, **options): + update_done = sysupgrade.get_upgrade_state('winsync', 'passsync_privilege_updated') + if update_done: + root_logger.debug("PassSync privilege update pre-check not needed") + return False, False, [] + + root_logger.debug("Check if there is existing PassSync privilege") + + passsync_privilege_dn = DN(('cn','PassSync Service'), + self.api.env.container_privilege, + self.api.env.basedn) + + ldap = self.obj.backend + try: + ldap.get_entry(passsync_privilege_dn, ['']) + except errors.NotFound: + root_logger.debug("PassSync privilege not found, this is a new update") + sysupgrade.set_upgrade_state('winsync', 'passsync_privilege_updated', False) + else: + root_logger.debug("PassSync privilege found, skip updating PassSync") + sysupgrade.set_upgrade_state('winsync', 'passsync_privilege_updated', True) + + return False, False, [] + +api.register(update_passync_privilege_check) + +class update_passync_privilege_update(PostUpdate): + """ + Add PassSync user as a member of PassSync privilege, if it exists + """ + + order = LAST + + def execute(self, **options): + update_done = sysupgrade.get_upgrade_state('winsync', 'passsync_privilege_updated') + if update_done: + root_logger.debug("PassSync privilege update not needed") + return False, False, [] + + root_logger.debug("Add PassSync user as a member of PassSync privilege") + ldap = self.obj.backend + passsync_dn = DN(('uid','passsync'), ('cn', 'sysaccounts'), ('cn', 'etc'), + api.env.basedn) + passsync_privilege_dn = DN(('cn','PassSync Service'), + self.api.env.container_privilege, + self.api.env.basedn) + + try: + entry = ldap.get_entry(passsync_dn, ['']) + except errors.NotFound: + root_logger.debug("PassSync user not found, no update needed") + sysupgrade.set_upgrade_state('winsync', 'passsync_privilege_updated', True) + return False, False, [] + else: + root_logger.debug("PassSync user found, do update") + + update = {'dn': passsync_privilege_dn, + 'updates': ["add:member:'%s'" % passsync_dn]} + updates = {passsync_privilege_dn: update} + + sysupgrade.set_upgrade_state('winsync', 'passsync_privilege_updated', True) + return (False, True, [updates]) + +api.register(update_passync_privilege_update) diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py index 5778cab03..66764c22f 100644 --- a/ipaserver/install/replication.py +++ b/ipaserver/install/replication.py @@ -528,39 +528,43 @@ class ReplicationManager(object): print "The user for the Windows PassSync service is %s" % pass_dn try: conn.get_entry(pass_dn) - print "Windows PassSync entry exists, not resetting password" - return + print "Windows PassSync system account exists, not resetting password" except errors.NotFound: - pass + # The user doesn't exist, add it + print "Adding Windows PassSync system account" + entry = conn.make_entry( + pass_dn, + objectclass=["account", "simplesecurityobject"], + uid=["passsync"], + userPassword=[password], + ) + conn.add_entry(entry) - # The user doesn't exist, add it - entry = conn.make_entry( - pass_dn, - objectclass=["account", "simplesecurityobject"], - uid=["passsync"], - userPassword=[password], - ) - conn.add_entry(entry) - - # Add it to the list of users allowed to bypass password policy + # Add the user to the list of users allowed to bypass password policy extop_dn = DN(('cn', 'ipa_pwd_extop'), ('cn', 'plugins'), ('cn', 'config')) entry = conn.get_entry(extop_dn) - pass_mgrs = entry.get('passSyncManagersDNs') - if not pass_mgrs: - pass_mgrs = [] - if not isinstance(pass_mgrs, list): - pass_mgrs = [pass_mgrs] + pass_mgrs = entry.get('passSyncManagersDNs', []) pass_mgrs.append(pass_dn) mod = [(ldap.MOD_REPLACE, 'passSyncManagersDNs', pass_mgrs)] - conn.modify_s(extop_dn, mod) - - # And finally grant it permission to write passwords - mod = [(ldap.MOD_ADD, 'aci', - ['(targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Windows PassSync service can write passwords"; allow (write) userdn="ldap:///%s";)' % pass_dn])] try: - conn.modify_s(self.suffix, mod) + conn.modify_s(extop_dn, mod) except ldap.TYPE_OR_VALUE_EXISTS: - root_logger.debug("passsync aci already exists in suffix %s on %s" % (self.suffix, conn.host)) + root_logger.debug("Plugin '%s' already '%s' in passSyncManagersDNs", + extop_dn, pass_dn) + + # And finally add it is a member of PassSync privilege to allow + # displaying user NT attributes and reset passwords + passsync_privilege_dn = DN(('cn','PassSync Service'), + api.env.container_privilege, + api.env.basedn) + members = entry.get('member', []) + members.append(pass_dn) + mod = [(ldap.MOD_REPLACE, 'member', members)] + try: + conn.modify_s(passsync_privilege_dn, mod) + except ldap.TYPE_OR_VALUE_EXISTS: + root_logger.debug("PassSync service '%s' already have '%s' as member", + passsync_privilege_dn, pass_dn) def setup_winsync_agmt(self, entry, win_subtree=None): if win_subtree is None: