From 3998814d3abb5143f06479d2dbf93bf28285a66e Mon Sep 17 00:00:00 2001 From: Martin Kosek Date: Tue, 13 Jan 2015 18:09:17 +0100 Subject: [PATCH] Allow PassSync user to locate and update NT users Add new PassSync Service privilege that have sufficient access to let AD PassSync service search for NT users and update the password. To make sure existing PassSync user keeps working, it is added as a member of the new privilege. New update plugin is added to add link to the new privilege to the potentially existing PassSync user to avoid breaking the PassSync service. https://fedorahosted.org/freeipa/ticket/4837 Reviewed-By: David Kupka --- ACI.txt | 2 + install/updates/40-delegation.update | 30 +++++++++++ ipalib/plugins/user.py | 12 +++++ ipaserver/install/plugins/Makefile.am | 1 + ipaserver/install/plugins/update_passsync.py | 78 ++++++++++++++++++++++++++++ ipaserver/install/replication.py | 54 ++++++++++--------- 6 files changed, 152 insertions(+), 25 deletions(-) create mode 100644 ipaserver/install/plugins/update_passsync.py diff --git a/ACI.txt b/ACI.txt index 6680f658ee1aa0f961b2681f700557ce6b9238f8..fe45d063e7d48c487e380ca3568b0f9368762c6d 100644 --- a/ACI.txt +++ b/ACI.txt @@ -267,6 +267,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 988de5e1962fabc6787f5914522b8f133e71a8ff..a79f906ea3e29b8b6755a62ac84d318d6abdd6cc 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 e206289248dfe9ae79bd87271ff2c7672fb98b4f..56585b9f86593c0c5879139103bc71707b88e15f 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 d651297ac141b0f05831e7fabbb9b561cdd239c7..ead1d8f7d972c1b016bac8f2b8f7fd1f9a71b563 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 0000000000000000000000000000000000000000..d6595a06f4deb62b853d716012a8c594c6a76451 --- /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 5778cab036ad87ccb5b69254aa307a6bc8dec871..66764c22f69328942fe2e4581cfafb3806438d7c 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 - entry = conn.make_entry( - pass_dn, - objectclass=["account", "simplesecurityobject"], - uid=["passsync"], - userPassword=[password], - ) - conn.add_entry(entry) + # 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) - # 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("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 aci already exists in suffix %s on %s" % (self.suffix, conn.host)) + 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: -- 2.1.0