From 5440614ab729575dddcb773d67148569222704f4 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Nov 05 2019 19:05:47 +0000 Subject: import ipa-4.8.0-11.module+el8.1.0+4247+9f3fd721 --- diff --git a/.gitignore b/.gitignore index df10876..1aa675e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/freeipa-4.7.1.tar.gz +SOURCES/freeipa-4.8.0.tar.gz diff --git a/.ipa.metadata b/.ipa.metadata index 1251b08..965cc09 100644 --- a/.ipa.metadata +++ b/.ipa.metadata @@ -1 +1 @@ -7c147ac996f43e83901de707958f72f795b2ce30 SOURCES/freeipa-4.7.1.tar.gz +4cbc1d041eef6d9f5381bdfcfebf9a96d40b94cb SOURCES/freeipa-4.8.0.tar.gz diff --git a/README.debrand b/README.debrand deleted file mode 100644 index 01c46d2..0000000 --- a/README.debrand +++ /dev/null @@ -1,2 +0,0 @@ -Warning: This package was configured for automatic debranding, but the changes -failed to apply. diff --git a/SOURCES/0002-Fix-test_webui.test_selinuxusermap.patch b/SOURCES/0002-Fix-test_webui.test_selinuxusermap.patch new file mode 100644 index 0000000..ab3d133 --- /dev/null +++ b/SOURCES/0002-Fix-test_webui.test_selinuxusermap.patch @@ -0,0 +1,124 @@ +From 96af5394c210e637a5ab81d6925be3b0a429fc08 Mon Sep 17 00:00:00 2001 +From: Stanislav Levin +Date: Fri, 5 Jul 2019 14:39:17 +0300 +Subject: [PATCH] Fix `test_webui.test_selinuxusermap` + +A previous refactoring of SELinux tests has have a wrong +assumption about the user field separator within +ipaSELinuxUserMapOrder. That was '$$', but should be just '$'. + +Actually, '.ldif' and '.update' files are passed through +Python template string substitution: + +> $$ is an escape; it is replaced with a single $. +> $identifier names a substitution placeholder matching +> a mapping key of "identifier" + +This means that the text to be substituted on should not be escaped. +The wrong ipaSELinuxUserMapOrder previously set will be replaced on +upgrade. + +Fixes: https://pagure.io/freeipa/issue/7996 +Fixes: https://pagure.io/freeipa/issue/8005 +Signed-off-by: Stanislav Levin +Reviewed-By: Florence Blanc-Renaud +--- + install/updates/50-ipaconfig.update | 1 + + ipaplatform/base/constants.py | 10 +++++----- + ipaserver/install/ldapupdate.py | 3 +++ + ipatests/test_integration/test_winsyncmigrate.py | 2 +- + ipatests/test_webui/data_selinuxusermap.py | 4 ++-- + ipatests/test_xmlrpc/test_selinuxusermap_plugin.py | 4 ++-- + 6 files changed, 14 insertions(+), 10 deletions(-) + +diff --git a/install/updates/50-ipaconfig.update b/install/updates/50-ipaconfig.update +index 2e1c5c357..35e154b4e 100644 +--- a/install/updates/50-ipaconfig.update ++++ b/install/updates/50-ipaconfig.update +@@ -1,4 +1,5 @@ + dn: cn=ipaConfig,cn=etc,$SUFFIX ++replace: ipaSELinuxUserMapOrder: guest_u:s0$$$$xguest_u:s0$$$$user_u:s0$$$$staff_u:s0-s0:c0.c1023$$$$sysadm_u:s0-s0:c0.c1023$$$$unconfined_u:s0-s0:c0.c1023::$SELINUX_USERMAP_ORDER + replace: ipaSELinuxUserMapOrder: ipaSELinuxUserMapOrder: guest_u:s0$$xguest_u:s0$$user_u:s0$$staff_u:s0-s0:c0.c1023$$unconfined_u:s0-s0:c0.c1023::guest_u:s0$$xguest_u:s0$$user_u:s0$$staff_u:s0-s0:c0.c1023$$unconfined_u:s0-s0:c0.c1023 + replace: ipaSELinuxUserMapOrder: guest_u:s0$$xguest_u:s0$$user_u:s0-s0:c0.c1023$$staff_u:s0-s0:c0.c1023$$unconfined_u:s0-s0:c0.c1023::guest_u:s0$$xguest_u:s0$$user_u:s0$$staff_u:s0-s0:c0.c1023$$unconfined_u:s0-s0:c0.c1023 + add:ipaSELinuxUserMapDefault: $SELINUX_USERMAP_DEFAULT +diff --git a/ipaplatform/base/constants.py b/ipaplatform/base/constants.py +index cdb72e74a..eac60cac3 100644 +--- a/ipaplatform/base/constants.py ++++ b/ipaplatform/base/constants.py +@@ -62,11 +62,11 @@ class BaseConstantsNamespace: + SELINUX_USERMAP_DEFAULT = "unconfined_u:s0-s0:c0.c1023" + SELINUX_USERMAP_ORDER = ( + "guest_u:s0" +- "$$xguest_u:s0" +- "$$user_u:s0" +- "$$staff_u:s0-s0:c0.c1023" +- "$$sysadm_u:s0-s0:c0.c1023" +- "$$unconfined_u:s0-s0:c0.c1023" ++ "$xguest_u:s0" ++ "$user_u:s0" ++ "$staff_u:s0-s0:c0.c1023" ++ "$sysadm_u:s0-s0:c0.c1023" ++ "$unconfined_u:s0-s0:c0.c1023" + ) + SSSD_USER = "sssd" + # WSGI module override, only used on Fedora +diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py +index d9e47dcc0..0cdea6a82 100644 +--- a/ipaserver/install/ldapupdate.py ++++ b/ipaserver/install/ldapupdate.py +@@ -322,6 +322,9 @@ class LDAPUpdate: + if not self.sub_dict.get("SELINUX_USERMAP_DEFAULT"): + self.sub_dict["SELINUX_USERMAP_DEFAULT"] = \ + platformconstants.SELINUX_USERMAP_DEFAULT ++ if not self.sub_dict.get("SELINUX_USERMAP_ORDER"): ++ self.sub_dict["SELINUX_USERMAP_ORDER"] = \ ++ platformconstants.SELINUX_USERMAP_ORDER + self.api = create_api(mode=None) + self.api.bootstrap(in_server=True, + context='updates', +diff --git a/ipatests/test_integration/test_winsyncmigrate.py b/ipatests/test_integration/test_winsyncmigrate.py +index 593fc2065..be9f44072 100644 +--- a/ipatests/test_integration/test_winsyncmigrate.py ++++ b/ipatests/test_integration/test_winsyncmigrate.py +@@ -59,7 +59,7 @@ class TestWinsyncMigrate(IntegrationTest): + ipa_group = 'ipa_group' + ad_user = 'testuser' + default_shell = platformconstants.DEFAULT_SHELL +- selinuxuser = platformconstants.SELINUX_USERMAP_ORDER.split("$$")[0] ++ selinuxuser = platformconstants.SELINUX_USERMAP_ORDER.split("$")[0] + test_role = 'test_role' + test_hbac_rule = 'test_hbac_rule' + test_selinux_map = 'test_selinux_map' +diff --git a/ipatests/test_webui/data_selinuxusermap.py b/ipatests/test_webui/data_selinuxusermap.py +index ca7b1dcdd..312e7592f 100644 +--- a/ipatests/test_webui/data_selinuxusermap.py ++++ b/ipatests/test_webui/data_selinuxusermap.py +@@ -5,8 +5,8 @@ + from ipaplatform.constants import constants as platformconstants + + # for example, user_u:s0 +-selinuxuser1 = platformconstants.SELINUX_USERMAP_ORDER.split("$$")[0] +-selinuxuser2 = platformconstants.SELINUX_USERMAP_ORDER.split("$$")[1] ++selinuxuser1 = platformconstants.SELINUX_USERMAP_ORDER.split("$")[0] ++selinuxuser2 = platformconstants.SELINUX_USERMAP_ORDER.split("$")[1] + + selinux_mcs_max = platformconstants.SELINUX_MCS_MAX + selinux_mls_max = platformconstants.SELINUX_MLS_MAX +diff --git a/ipatests/test_xmlrpc/test_selinuxusermap_plugin.py b/ipatests/test_xmlrpc/test_selinuxusermap_plugin.py +index 0b73992aa..e5b23bd4d 100644 +--- a/ipatests/test_xmlrpc/test_selinuxusermap_plugin.py ++++ b/ipatests/test_xmlrpc/test_selinuxusermap_plugin.py +@@ -32,8 +32,8 @@ from ipatests.test_xmlrpc.test_user_plugin import get_user_result + import pytest + + rule1 = u'selinuxrule1' +-selinuxuser1 = platformconstants.SELINUX_USERMAP_ORDER.split("$$")[0] +-selinuxuser2 = platformconstants.SELINUX_USERMAP_ORDER.split("$$")[1] ++selinuxuser1 = platformconstants.SELINUX_USERMAP_ORDER.split("$")[0] ++selinuxuser2 = platformconstants.SELINUX_USERMAP_ORDER.split("$")[1] + + INVALID_MCS = "Invalid MCS value, must match {}, where max category {}".format( + platformconstants.SELINUX_MCS_REGEX, +-- +2.21.0 + diff --git a/SOURCES/0002-freeipa-4.7.0-ipaclient-Remove-no-sssd-and-noac-options_rhbz#1614301.patch b/SOURCES/0002-freeipa-4.7.0-ipaclient-Remove-no-sssd-and-noac-options_rhbz#1614301.patch deleted file mode 100644 index 7447a08..0000000 --- a/SOURCES/0002-freeipa-4.7.0-ipaclient-Remove-no-sssd-and-noac-options_rhbz#1614301.patch +++ /dev/null @@ -1,77 +0,0 @@ -From c5cdd5a5f01306b3a70354d34079efe64565aa69 Mon Sep 17 00:00:00 2001 -From: Thomas Woerner -Date: Thu, 9 Aug 2018 12:05:26 +0200 -Subject: ipaclient: Remove --no-sssd and --no-ac options - -Client installation with --no-sssd option has already beeen deprecated -with https://pagure.io/freeipa/issue/5860. Authconfig support has been -removed, therefore --no-ac option can be removed also. - -ipatests/test_integration/test_authselect.py: Skip no_sssd and no_ac tests. - -See: https://pagure.io/freeipa/issue/7671 -Signed-off-by: Thomas Woerner -Reviewed-By: Christian Heimes ---- - ipaclient/install/client.py | 6 +----- - ipaclient/install/sssd.py | 9 +-------- - ipatests/test_integration/test_authselect.py | 2 ++ - 3 files changed, 4 insertions(+), 13 deletions(-) - -diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py -index 627351ac2..800a46734 100644 ---- a/ipaclient/install/client.py -+++ b/ipaclient/install/client.py -@@ -3709,11 +3709,7 @@ class ClientInstall(ClientInstallInterface, - def prompt_password(self): - return self.interactive - -- no_ac = knob( -- None, -- description="do not modify the nsswitch.conf and PAM configuration", -- cli_names='--noac', -- ) -+ no_ac = False - - force = knob( - None, -diff --git a/ipaclient/install/sssd.py b/ipaclient/install/sssd.py -index b20abde56..98b850464 100644 ---- a/ipaclient/install/sssd.py -+++ b/ipaclient/install/sssd.py -@@ -43,11 +43,4 @@ class SSSDInstallInterface(service.ServiceInstallInterface): - ) - preserve_sssd = enroll_only(preserve_sssd) - -- no_sssd = knob( -- None, -- deprecated=True, -- description="Do not configure the client to use SSSD for " -- "authentication", -- cli_names=[None, '-S'], -- ) -- no_sssd = enroll_only(no_sssd) -+ no_sssd = False -diff --git a/ipatests/test_integration/test_authselect.py b/ipatests/test_integration/test_authselect.py -index fa9b20265..ebf3d9892 100644 ---- a/ipatests/test_integration/test_authselect.py -+++ b/ipatests/test_integration/test_authselect.py -@@ -88,6 +88,7 @@ class TestClientInstallation(IntegrationTest): - ['ipa-client-install', '--uninstall', '-U'], - raiseonerr=False) - -+ @pytest.mark.skip(reason="Option --no-sssd has been removed") - def test_install_client_no_sssd(self): - """ - Test client installation with --no-sssd option. -@@ -98,6 +99,7 @@ class TestClientInstallation(IntegrationTest): - msg = "Option '--no-sssd' is incompatible with the 'authselect' tool" - assert msg in result.stderr_text - -+ @pytest.mark.skip(reason="Option --noac has been removed") - def test_install_client_no_ac(self): - """ - Test client installation with --noac option. --- -2.17.1 - diff --git a/SOURCES/0003-Remove-posixAccount-from-service_find-search-filter-2f9cbff_rhbz#1731437.patch b/SOURCES/0003-Remove-posixAccount-from-service_find-search-filter-2f9cbff_rhbz#1731437.patch new file mode 100644 index 0000000..17c769a --- /dev/null +++ b/SOURCES/0003-Remove-posixAccount-from-service_find-search-filter-2f9cbff_rhbz#1731437.patch @@ -0,0 +1,33 @@ +From 2f9cbffb6e57ded2d0107f457241f33b17869a96 Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Jul 19 2019 19:16:16 +0000 +Subject: Remove posixAccount from service_find search filter + + +This will allow cifs principals to be found. They were suppressed +because they include objectclass=posixAccount. + +This is a bit of a historical anomaly. This was included in the +filter from the initial commit (though it was person, not +posixAccount). I believe it was a mistake from the beginning but +it wasn't noticed because it didn't cause any obvious issues. + +https://pagure.io/freeipa/issue/8013 + +Reviewed-By: Alexander Bokovoy + +--- + +diff --git a/ipaserver/plugins/service.py b/ipaserver/plugins/service.py +index f58fe4b..c118b80 100644 +--- a/ipaserver/plugins/service.py ++++ b/ipaserver/plugins/service.py +@@ -889,7 +889,6 @@ class service_find(LDAPSearch): + assert isinstance(base_dn, DN) + # lisp style! + custom_filter = '(&(objectclass=ipaService)' \ +- '(!(objectClass=posixAccount))' \ + '(!(|(krbprincipalname=kadmin/*)' \ + '(krbprincipalname=K/M@*)' \ + '(krbprincipalname=krbtgt/*))' \ + diff --git a/SOURCES/0003-adtrust-define-Guests-mapping.patch b/SOURCES/0003-adtrust-define-Guests-mapping.patch deleted file mode 100644 index ab55aeb..0000000 --- a/SOURCES/0003-adtrust-define-Guests-mapping.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 1ef0fe8bb824282c2f48417efda3a60e7c1bf580 Mon Sep 17 00:00:00 2001 -From: Alexander Bokovoy -Date: Tue, 9 Oct 2018 17:21:37 +0300 -Subject: [PATCH] adtrust: define Guests mapping after creating cifs/ principal - -All Samba utilities load passdb modules from the configuration file. As -result, 'net groupmap' call would try to initialize ipasam passdb module -and that one would try to connect to LDAP using Kerberos authentication. - -We should be running it after cifs/ principal is actually created in -ipa-adtrust-install or otherwise setting up group mapping will fail. - -This only affects new installations. For older ones 'net groupmap' would -work just fine because adtrust is already configured and all principals -exist already. - -A re-run of 'ipa-server-upgrade' is a workaround too but better to fix -the initial setup. - -Related: https://pagure.io/freeipa/issue/7705 -Reviewed-By: Rob Crittenden ---- - ipaserver/install/adtrustinstance.py | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py -index 46c4ad663..da16748cf 100644 ---- a/ipaserver/install/adtrustinstance.py -+++ b/ipaserver/install/adtrustinstance.py -@@ -837,8 +837,6 @@ class ADTRUSTInstance(service.Service): - self.__create_samba_domain_object) - self.step("creating samba config registry", self.__write_smb_registry) - self.step("writing samba config file", self.__write_smb_conf) -- self.step("map BUILTIN\\Guests to nobody group", -- self.__map_Guests_to_nobody) - self.step("adding cifs Kerberos principal", - self.request_service_keytab) - self.step("adding cifs and host Kerberos principals to the adtrust agents group", \ -@@ -850,6 +848,8 @@ class ADTRUSTInstance(service.Service): - self.step("updating Kerberos config", self.__update_krb5_conf) - self.step("activating CLDAP plugin", self.__add_cldap_module) - self.step("activating sidgen task", self.__add_sidgen_task) -+ self.step("map BUILTIN\\Guests to nobody group", -+ self.__map_Guests_to_nobody) - self.step("configuring smbd to start on boot", self.__enable) - self.step("adding special DNS service records", \ - self.__add_dns_service_records) --- -2.17.1 - diff --git a/SOURCES/0004-Repeated-uninstallation-of-ipa-client-samba-crashes_rhbz#1732529.patch b/SOURCES/0004-Repeated-uninstallation-of-ipa-client-samba-crashes_rhbz#1732529.patch new file mode 100644 index 0000000..374534b --- /dev/null +++ b/SOURCES/0004-Repeated-uninstallation-of-ipa-client-samba-crashes_rhbz#1732529.patch @@ -0,0 +1,146 @@ +From b9b98097a47f27b56500edc972c438597e6609b1 Mon Sep 17 00:00:00 2001 +From: François Cami +Date: Jul 26 2019 13:09:42 +0000 +Subject: ipatests: test multiple invocations of ipa-client-samba --uninstall + + +Related-to: https://pagure.io/freeipa/issue/8019 +Signed-off-by: François Cami +Reviewed-By: Alexander Bokovoy +Reviewed-By: Sergey Orlov + +--- + +diff --git a/ipatests/test_integration/test_smb.py b/ipatests/test_integration/test_smb.py +index 4e295c0..26d70b3 100644 +--- a/ipatests/test_integration/test_smb.py ++++ b/ipatests/test_integration/test_smb.py +@@ -150,3 +150,6 @@ class TestSMB(IntegrationTest): + + smbsrv = self.replicas[0] + smbsrv.run_command(['ipa-client-samba', '--uninstall', '-U']) ++ # test for https://pagure.io/freeipa/issue/8019 ++ # try another uninstall after the first one: ++ smbsrv.run_command(['ipa-client-samba', '--uninstall', '-U']) + +From 256a6a879061d2b97c11e9cd97b2427579610fa1 Mon Sep 17 00:00:00 2001 +From: François Cami +Date: Jul 26 2019 13:09:42 +0000 +Subject: ipa-client-samba: remove and restore smb.conf only on first uninstall + + +Fixes: https://pagure.io/freeipa/issue/8019 +Signed-off-by: François Cami +Reviewed-By: Alexander Bokovoy +Reviewed-By: Sergey Orlov + +--- + +diff --git a/ipaclient/install/ipa_client_samba.py b/ipaclient/install/ipa_client_samba.py +index e2be67d..6a3c3bd 100755 +--- a/ipaclient/install/ipa_client_samba.py ++++ b/ipaclient/install/ipa_client_samba.py +@@ -433,8 +433,9 @@ def uninstall(fstore, statestore, options): + ipautil.remove_ccache(ccache_path=paths.KRB5CC_SAMBA) + + # Remove samba's configuration file +- ipautil.remove_file(paths.SMB_CONF) +- fstore.restore_file(paths.SMB_CONF) ++ if fstore.has_file(paths.SMB_CONF): ++ ipautil.remove_file(paths.SMB_CONF) ++ fstore.restore_file(paths.SMB_CONF) + + # Remove samba's persistent and temporary tdb files + tdb_files = [ +@@ -624,7 +625,7 @@ def run(): + api.Command.service_del(api.env.smb_princ) + except AttributeError: + logger.error( +- "Chosen IPA master %s does not have support to" ++ "Chosen IPA master %s does not have support to " + "set up Samba domain members", server, + ) + return 1 + +From 00ba2ae6681dafa92d3f00f2a4e11adaa477ea0e Mon Sep 17 00:00:00 2001 +From: François Cami +Date: Jul 26 2019 13:09:42 +0000 +Subject: ipatests: test ipa-client-samba after --uninstall + + +Related-to: https://pagure.io/freeipa/issue/8021 +Signed-off-by: François Cami +Reviewed-By: Alexander Bokovoy +Reviewed-By: Sergey Orlov + +--- + +diff --git a/ipatests/test_integration/test_smb.py b/ipatests/test_integration/test_smb.py +index 26d70b3..933d468 100644 +--- a/ipatests/test_integration/test_smb.py ++++ b/ipatests/test_integration/test_smb.py +@@ -153,3 +153,8 @@ class TestSMB(IntegrationTest): + # test for https://pagure.io/freeipa/issue/8019 + # try another uninstall after the first one: + smbsrv.run_command(['ipa-client-samba', '--uninstall', '-U']) ++ # test for https://pagure.io/freeipa/issue/8021 ++ # try to install again: ++ smbsrv.run_command(["ipa-client-samba", "-U"]) ++ # cleanup: ++ smbsrv.run_command(['ipa-client-samba', '--uninstall', '-U']) + +From 551cd68d0959b1ee761ead6338dc06c544c0c5da Mon Sep 17 00:00:00 2001 +From: François Cami +Date: Jul 26 2019 13:09:42 +0000 +Subject: ipa-client-samba: remove state on uninstall + + +The "domain_member" state was not removed at uninstall time. +Remove it so that future invocations of ipa-client-samba work. + +Fixes: https://pagure.io/freeipa/issue/8021 +Signed-off-by: François Cami + +https://pagure.io/freeipa/issue/8021 + +Reviewed-By: Alexander Bokovoy +Reviewed-By: Sergey Orlov + +--- + +diff --git a/ipaclient/install/ipa_client_samba.py b/ipaclient/install/ipa_client_samba.py +index 6a3c3bd..126ef32 100755 +--- a/ipaclient/install/ipa_client_samba.py ++++ b/ipaclient/install/ipa_client_samba.py +@@ -523,11 +523,25 @@ def run(): + if options.uninstall: + if statestore.has_state("domain_member"): + uninstall(fstore, statestore, options) +- print( +- "Samba configuration is reverted. " +- "However, Samba databases were fully cleaned and " +- "old configuration file will not be usable anymore." +- ) ++ try: ++ keys = ( ++ "configured", "hardening", "groupmap", "tdb", ++ "service.principal", "smb.conf" ++ ) ++ for key in keys: ++ statestore.delete_state("domain_member", key) ++ except Exception as e: ++ print( ++ "Error: Failed to remove the domain_member statestores: " ++ "%s" % e ++ ) ++ return 1 ++ else: ++ print( ++ "Samba configuration is reverted. " ++ "However, Samba databases were fully cleaned and " ++ "old configuration file will not be usable anymore." ++ ) + else: + print("Samba domain member is not configured yet") + return 0 + diff --git a/SOURCES/0004-freeipa-4.7.1-Find_orphan_automember_rules_rhbz#1638373.patch b/SOURCES/0004-freeipa-4.7.1-Find_orphan_automember_rules_rhbz#1638373.patch deleted file mode 100644 index d650aef..0000000 --- a/SOURCES/0004-freeipa-4.7.1-Find_orphan_automember_rules_rhbz#1638373.patch +++ /dev/null @@ -1,209 +0,0 @@ -From 67875c3b75ad1af493ff5930f9c5fd5e9797b775 Mon Sep 17 00:00:00 2001 -From: Thomas Woerner -Date: Oct 12 2018 07:50:29 +0000 -Subject: Find orphan automember rules - - -If groups or hostgroups have been removed after automember rules have been -created using them, then automember-rebuild, automember-add, host-add and -more commands could fail. - -A new command has been added to the ipa tool: - - ipa automember-find-orphans --type={hostgroup,group} [--remove] - -This command retuns the list of orphan automember rules in the same way as -automember-find. With the --remove option the orphan rules are also removed. - -The IPA API version has been increased and a test case has been added. - -Using ideas from a patch by: Rob Crittenden - -See: https://pagure.io/freeipa/issue/6476 -Signed-off-by: Thomas Woerner -Reviewed-By: Christian Heimes -Reviewed-By: Florence Blanc-Renaud -Reviewed-By: Florence Blanc-Renaud - ---- - -diff --git a/API.txt b/API.txt -index 49216cb..93e1a38 100644 ---- a/API.txt -+++ b/API.txt -@@ -186,6 +186,20 @@ output: Output('count', type=[]) - output: ListOfEntries('result') - output: Output('summary', type=[, ]) - output: Output('truncated', type=[]) -+command: automember_find_orphans/1 -+args: 1,7,4 -+arg: Str('criteria?') -+option: Flag('all', autofill=True, cli_name='all', default=False) -+option: Str('description?', autofill=False, cli_name='desc') -+option: Flag('pkey_only?', autofill=True, default=False) -+option: Flag('raw', autofill=True, cli_name='raw', default=False) -+option: Flag('remove?', autofill=True, default=False) -+option: StrEnum('type', values=[u'group', u'hostgroup']) -+option: Str('version?') -+output: Output('count', type=[]) -+output: ListOfEntries('result') -+output: Output('summary', type=[, ]) -+output: Output('truncated', type=[]) - command: automember_mod/1 - args: 1,9,3 - arg: Str('cn', cli_name='automember_rule') -@@ -6503,6 +6517,7 @@ default: automember_default_group_set/1 - default: automember_default_group_show/1 - default: automember_del/1 - default: automember_find/1 -+default: automember_find_orphans/1 - default: automember_mod/1 - default: automember_rebuild/1 - default: automember_remove_condition/1 -diff --git a/VERSION.m4 b/VERSION.m4 -index f437ef0..9d5532c 100644 ---- a/VERSION.m4 -+++ b/VERSION.m4 -@@ -83,8 +83,8 @@ define(IPA_DATA_VERSION, 20100614120000) - # # - ######################################################## - define(IPA_API_VERSION_MAJOR, 2) --define(IPA_API_VERSION_MINOR, 229) --# Last change: Added the Certificate parameter -+define(IPA_API_VERSION_MINOR, 230) -+# Last change: Added `automember-find-orphans' command - - - ######################################################## -diff --git a/ipaserver/plugins/automember.py b/ipaserver/plugins/automember.py -index a502aea..a7f468d 100644 ---- a/ipaserver/plugins/automember.py -+++ b/ipaserver/plugins/automember.py -@@ -117,6 +117,11 @@ EXAMPLES: - Find all of the automember rules: - ipa automember-find - """) + _(""" -+ Find all of the orphan automember rules: -+ ipa automember-find-orphans --type=hostgroup -+ Find all of the orphan automember rules and remove them: -+ ipa automember-find-orphans --type=hostgroup --remove -+""") + _(""" - Display a automember rule: - ipa automember-show --type=hostgroup webservers - ipa automember-show --type=group devel -@@ -817,3 +822,58 @@ class automember_rebuild(Method): - result=result, - summary=unicode(summary), - value=pkey_to_value(None, options)) -+ -+ -+@register() -+class automember_find_orphans(LDAPSearch): -+ __doc__ = _(""" -+ Search for orphan automember rules. The command might need to be run as -+ a privileged user user to get all orphan rules. -+ """) -+ takes_options = group_type + ( -+ Flag( -+ 'remove?', -+ doc=_("Remove orphan automember rules"), -+ ), -+ ) -+ -+ msg_summary = ngettext( -+ '%(count)d rules matched', '%(count)d rules matched', 0 -+ ) -+ -+ def execute(self, *keys, **options): -+ results = super(automember_find_orphans, self).execute(*keys, -+ **options) -+ -+ remove_option = options.get('remove') -+ pkey_only = options.get('pkey_only', False) -+ ldap = self.obj.backend -+ orphans = [] -+ for entry in results["result"]: -+ am_dn_entry = entry['automembertargetgroup'][0] -+ # Make DN for --raw option -+ if not isinstance(am_dn_entry, DN): -+ am_dn_entry = DN(am_dn_entry) -+ try: -+ ldap.get_entry(am_dn_entry) -+ except errors.NotFound: -+ if pkey_only: -+ # For pkey_only remove automembertargetgroup -+ del(entry['automembertargetgroup']) -+ orphans.append(entry) -+ if remove_option: -+ ldap.delete_entry(entry['dn']) -+ -+ results["result"][:] = orphans -+ results["count"] = len(orphans) -+ return results -+ -+ def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, -+ **options): -+ assert isinstance(base_dn, DN) -+ scope = ldap.SCOPE_SUBTREE -+ ndn = DN(('cn', options['type']), base_dn) -+ if options.get('pkey_only', False): -+ # For pkey_only add automembertargetgroup -+ attrs_list.append('automembertargetgroup') -+ return filters, ndn, scope -diff --git a/ipatests/test_xmlrpc/test_automember_plugin.py b/ipatests/test_xmlrpc/test_automember_plugin.py -index ffbc911..c83e11a 100644 ---- a/ipatests/test_xmlrpc/test_automember_plugin.py -+++ b/ipatests/test_xmlrpc/test_automember_plugin.py -@@ -715,3 +715,51 @@ class TestMultipleAutomemberConditions(XMLRPC_test): - - defaultgroup1.ensure_missing() - defaulthostgroup1.ensure_missing() -+ -+ -+@pytest.mark.tier1 -+class TestAutomemberFindOrphans(XMLRPC_test): -+ def test_create_deps_for_find_orphans(self, hostgroup1, host1, -+ automember_hostgroup): -+ """ Create host, hostgroup, and automember tracker for this class -+ of tests. """ -+ -+ # Create hostgroup1 and automember rule with condition -+ hostgroup1.ensure_exists() -+ host1.ensure_exists() -+ -+ # Manually create automember rule and condition, racker will try to -+ # remove the automember rule in the end, which is failing as the rule -+ # is already removed -+ api.Command['automember_add'](hostgroup1.cn, type=u'hostgroup') -+ api.Command['automember_add_condition']( -+ hostgroup1.cn, -+ key=u'fqdn', type=u'hostgroup', -+ automemberinclusiveregex=[hostgroup_include_regex] -+ ) -+ -+ hostgroup1.retrieve() -+ -+ def test_find_orphan_automember_rules(self, hostgroup1): -+ """ Remove hostgroup1, find and remove obsolete automember rules. """ -+ # Remove hostgroup1 -+ -+ hostgroup1.ensure_missing() -+ -+ # Find obsolete automember rules -+ result = api.Command['automember_find_orphans'](type=u'hostgroup') -+ assert result['count'] == 1 -+ -+ # Find and remove obsolete automember rules -+ result = api.Command['automember_find_orphans'](type=u'hostgroup', -+ remove=True) -+ assert result['count'] == 1 -+ -+ # Find obsolete automember rules -+ result = api.Command['automember_find_orphans'](type=u'hostgroup') -+ assert result['count'] == 0 -+ -+ # Final cleanup of automember rule if it still exists -+ with raises_exact(errors.NotFound( -+ reason=u'%s: Automember rule not found' % hostgroup1.cn)): -+ api.Command['automember_del'](hostgroup1.cn, type=u'hostgroup') - diff --git a/SOURCES/0005-WebUI-Add-PKINIT-status-field-to-Configuration-page-a46383f_rhbz#1518153.patch b/SOURCES/0005-WebUI-Add-PKINIT-status-field-to-Configuration-page-a46383f_rhbz#1518153.patch new file mode 100644 index 0000000..1060da1 --- /dev/null +++ b/SOURCES/0005-WebUI-Add-PKINIT-status-field-to-Configuration-page-a46383f_rhbz#1518153.patch @@ -0,0 +1,72 @@ +From a46383ffe414f703264f8a81450f44abbd95d78e Mon Sep 17 00:00:00 2001 +From: Serhii Tsymbaliuk +Date: Jul 26 2019 20:36:58 +0000 +Subject: WebUI: Add PKINIT status field to 'Configuration' page + + +- Add 'Server Options' section to the page +- Add 'IPA master capable of PKINIT' field to the 'Server Options' + +Ticket: https://pagure.io/freeipa/issue/7305 + +Signed-off-by: Serhii Tsymbaliuk +Reviewed-By: Florence Blanc-Renaud + +--- + +diff --git a/install/ui/src/freeipa/serverconfig.js b/install/ui/src/freeipa/serverconfig.js +index 25f484a..6c82b40 100644 +--- a/install/ui/src/freeipa/serverconfig.js ++++ b/install/ui/src/freeipa/serverconfig.js +@@ -50,6 +50,24 @@ return { + ] + }, + { ++ name: 'server', ++ label: '@i18n:objects.config.server', ++ fields: [ ++ { ++ $type: 'entity_select', ++ name: 'ca_renewal_master_server', ++ other_entity: 'server', ++ other_field: 'cn', ++ flags: ['w_if_no_aci'] ++ }, ++ { ++ $type: 'multivalued', ++ name: 'pkinit_server_server', ++ read_only: true ++ } ++ ] ++ }, ++ { + name: 'user', + label: '@i18n:objects.config.user', + fields: [ +@@ -99,13 +117,6 @@ return { + { + $type: 'multivalued', + name: 'ipauserobjectclasses' +- }, +- { +- $type: 'entity_select', +- name: 'ca_renewal_master_server', +- other_entity: 'server', +- other_field: 'cn', +- flags: ['w_if_no_aci'] + } + ] + }, +diff --git a/ipaserver/plugins/internal.py b/ipaserver/plugins/internal.py +index 0f0ad3a..19957d7 100644 +--- a/ipaserver/plugins/internal.py ++++ b/ipaserver/plugins/internal.py +@@ -726,6 +726,7 @@ class i18n_messages(Command): + "group": _("Group Options"), + "search": _("Search Options"), + "selinux": _("SELinux Options"), ++ "server": _("Server Options"), + "service": _("Service Options"), + "user": _("User Options"), + }, + diff --git a/SOURCES/0005-net-groupmap-force-using-empty-config.patch b/SOURCES/0005-net-groupmap-force-using-empty-config.patch deleted file mode 100644 index 2d07145..0000000 --- a/SOURCES/0005-net-groupmap-force-using-empty-config.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 7f8858f8632d77497765bab79922f1762ce46d50 Mon Sep 17 00:00:00 2001 -From: Alexander Bokovoy -Date: Tue, 16 Oct 2018 17:54:09 +0300 -Subject: [PATCH] net groupmap: force using empty config when mapping Guests - -When we define a group mapping for BUILTIN\Guests to 'nobody' group in -we run 'net groupmap add ...' with a default /etc/samba/smb.conf which -is now configured to use ipasam passdb module. We authenticate to LDAP -with GSSAPI in ipasam passdb module initialization. - -If GSSAPI authentication failed (KDC is offline, for example, during -server upgrade), 'net groupmap add' crashes after ~10 attempts to -re-authenticate. This is intended behavior in smbd/winbindd as they -cannot work anymore. However, for the command line tools there are -plenty of operations where passdb module is not needed. - -Additionally, GSSAPI authentication uses the default ccache in the -environment and a key from /etc/samba/samba.keytab keytab. This means -that if you'd run 'net *' as root, it will replace whatever Kerberos -tickets you have with a TGT for cifs/`hostname` and a service ticket to -ldap/`hostname` of IPA master. - -Apply a simple solution to avoid using /etc/samba/smb.conf when we -set up the group mapping by specifying '-s /dev/null' in 'net groupmap' -call. - -For upgrade code this is enough as in -a678336b8b36cdbea2512e79c09e475fdc249569 we enforce use of empty -credentials cache during upgrade to prevent tripping on individual -ccaches from KEYRING: or KCM: cache collections. - -Related: https://pagure.io/freeipa/issue/7705 -(cherry picked from commit e48f5a4d64d95c4c5cb5f8ede39cae5c7c1e512c) ---- - ipaserver/install/adtrustinstance.py | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py -index da16748cf..3a751ccb2 100644 ---- a/ipaserver/install/adtrustinstance.py -+++ b/ipaserver/install/adtrustinstance.py -@@ -114,8 +114,8 @@ def make_netbios_name(s): - - def map_Guests_to_nobody(): - env = {'LC_ALL': 'C'} -- args = [paths.NET, 'groupmap', 'add', 'sid=S-1-5-32-546', -- 'unixgroup=nobody', 'type=builtin'] -+ args = [paths.NET, '-s', '/dev/null', 'groupmap', 'add', -+ 'sid=S-1-5-32-546', 'unixgroup=nobody', 'type=builtin'] - - logger.debug("Map BUILTIN\\Guests to a group 'nobody'") - ipautil.run(args, env=env, raiseonerr=False, capture_error=True) --- -2.17.2 - diff --git a/SOURCES/0006-Keep-dogtags-client-db-in-external-ca-step-1.patch b/SOURCES/0006-Keep-dogtags-client-db-in-external-ca-step-1.patch deleted file mode 100644 index 4394a2b..0000000 --- a/SOURCES/0006-Keep-dogtags-client-db-in-external-ca-step-1.patch +++ /dev/null @@ -1,126 +0,0 @@ -From 78bf80e55dd74fc0279cf6a76345865b0d5e5d32 Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Fri, 26 Oct 2018 18:12:29 +0200 -Subject: [PATCH] Keep Dogtag's client db in external CA step 1 - -Don't remove /root/.dogtag/pki-tomcat when performing step 1 of external -CA installation process. Dogtag 10.6.7 changed behavior and no longer -re-creates the client database in step 2. - -Fixes: https://pagure.io/freeipa/issue/7742 -Signed-off-by: Christian Heimes -Reviewed-By: Rob Crittenden - -diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py -index 59c0eadf1..61ccb6dff 100644 ---- a/ipaserver/install/cainstance.py -+++ b/ipaserver/install/cainstance.py -@@ -483,7 +483,12 @@ class CAInstance(DogtagInstance): - try: - self.start_creation(runtime=runtime) - finally: -- self.clean_pkispawn_files() -+ if self.external == 1: -+ # Don't remove client DB in external CA step 1 -+ # https://pagure.io/freeipa/issue/7742 -+ logger.debug("Keep pkispawn files for step 2") -+ else: -+ self.clean_pkispawn_files() - - def __spawn_instance(self): - """ -diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py -index e71bf2900..142a8c0d7 100644 ---- a/ipaserver/install/dogtaginstance.py -+++ b/ipaserver/install/dogtaginstance.py -@@ -167,11 +167,13 @@ class DogtagInstance(service.Service): - - def clean_pkispawn_files(self): - if self.tmp_agent_db is not None: -+ logger.debug("Removing %s", self.tmp_agent_db) - shutil.rmtree(self.tmp_agent_db, ignore_errors=True) - -- shutil.rmtree('/root/.dogtag/pki-tomcat/{subsystem}/' -- .format(subsystem=self.subsystem.lower()), -- ignore_errors=True) -+ client_dir = os.path.join( -+ '/root/.dogtag/pki-tomcat/', self.subsystem.lower()) -+ logger.debug("Removing %s", client_dir) -+ shutil.rmtree(client_dir, ignore_errors=True) - - def restart_instance(self): - self.restart('pki-tomcat') - -From 6214fc51789dcfc70d4df18c0153877b92625ad2 Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Fri, 26 Oct 2018 10:11:31 +0200 -Subject: [PATCH] Use tasks.install_master() in external_ca tests - -The install_master() function performs additional steps besides just -installing a server. It also sets up log collection and performs -additional tests. - -Signed-off-by: Christian Heimes -Reviewed-By: Rob Crittenden - -diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py -index 620ed28c9..9889636ba 100644 ---- a/ipatests/pytest_ipa/integration/tasks.py -+++ b/ipatests/pytest_ipa/integration/tasks.py -@@ -292,7 +292,7 @@ def set_default_ttl_for_ipa_dns_zone(host, raiseonerr=True): - - def install_master(host, setup_dns=True, setup_kra=False, setup_adtrust=False, - extra_args=(), domain_level=None, unattended=True, -- stdin_text=None, raiseonerr=True): -+ external_ca=False, stdin_text=None, raiseonerr=True): - if domain_level is None: - domain_level = host.config.domain_level - check_domain_level(domain_level) -@@ -321,11 +321,14 @@ def install_master(host, setup_dns=True, setup_kra=False, setup_adtrust=False, - args.append('--setup-kra') - if setup_adtrust: - args.append('--setup-adtrust') -+ if external_ca: -+ args.append('--external-ca') - - args.extend(extra_args) - result = host.run_command(args, raiseonerr=raiseonerr, - stdin_text=stdin_text) -- if result.returncode == 0: -+ if result.returncode == 0 and not external_ca: -+ # external CA step 1 doesn't have DS and KDC fully configured, yet - enable_replication_debugging(host) - setup_sssd_debugging(host) - kinit_admin(host) -diff --git a/ipatests/test_integration/test_external_ca.py b/ipatests/test_integration/test_external_ca.py -index 33ba70f98..a8e0ea0bf 100644 ---- a/ipatests/test_integration/test_external_ca.py -+++ b/ipatests/test_integration/test_external_ca.py -@@ -70,24 +70,12 @@ def match_in_journal(host, string, since='today', services=('certmonger',)): - - - def install_server_external_ca_step1(host): -- """funtion for step 1 to install the ipa server with external ca""" -- -- args = ['ipa-server-install', '-U', -- '-a', host.config.admin_password, -- '-p', host.config.dirman_password, -- '--setup-dns', '--no-forwarders', -- '-n', host.domain.name, -- '-r', host.domain.realm, -- '--domain-level=%i' % host.config.domain_level, -- '--external-ca'] -- -- cmd = host.run_command(args) -- return cmd -+ """Step 1 to install the ipa server with external ca""" -+ return tasks.install_master(host, external_ca=True) - - - def install_server_external_ca_step2(host, ipa_ca_cert, root_ca_cert): -- """funtion for step 2 to install the ipa server with external ca""" -- -+ """Step 2 to install the ipa server with external ca""" - args = ['ipa-server-install', - '-a', host.config.admin_password, - '-p', host.config.dirman_password, diff --git a/SOURCES/0006-external-ca-profile-fix_rhbz#1731813.patch b/SOURCES/0006-external-ca-profile-fix_rhbz#1731813.patch new file mode 100644 index 0000000..ca70f28 --- /dev/null +++ b/SOURCES/0006-external-ca-profile-fix_rhbz#1731813.patch @@ -0,0 +1,1339 @@ +From d0d29ccc324bb9f95bffbe3162ee5c3c61c6086a Mon Sep 17 00:00:00 2001 +From: Fraser Tweedale +Date: Thu, 11 Jul 2019 15:17:04 +1000 +Subject: [PATCH] move MSCSTemplate classes to ipalib + +As we expand the integration tests for external CA functionality, it +is helpful (and avoids duplication) to use the MSCSTemplate* +classes. These currently live in ipaserver.install.cainstance, but +ipatests is no longer permitted to import from ipaserver (see commit +81714976e5e13131654c78eb734746a20237c933). So move these classes to +ipalib. + +Part of: https://pagure.io/freeipa/issue/7548 + +Reviewed-By: Florence Blanc-Renaud +--- + install/tools/ipa-ca-install.in | 6 +- + ipalib/x509.py | 171 +++++++++++++++++ + ipaserver/install/ca.py | 11 +- + ipaserver/install/cainstance.py | 180 +----------------- + ipaserver/install/ipa_cacert_manage.py | 14 +- + ipatests/test_integration/test_external_ca.py | 11 +- + ipatests/test_ipalib/test_x509.py | 115 +++++++++++ + .../test_install/test_cainstance.py | 123 ------------ + 8 files changed, 307 insertions(+), 324 deletions(-) + delete mode 100644 ipatests/test_ipaserver/test_install/test_cainstance.py + +diff --git a/install/tools/ipa-ca-install.in b/install/tools/ipa-ca-install.in +index 0700c0c38b..ce6d5fcb52 100644 +--- a/install/tools/ipa-ca-install.in ++++ b/install/tools/ipa-ca-install.in +@@ -37,7 +37,7 @@ from ipaserver.install import cainstance, service + from ipaserver.install import custodiainstance + from ipaserver.masters import find_providing_server + from ipapython import version +-from ipalib import api ++from ipalib import api, x509 + from ipalib.constants import DOMAIN_LEVEL_1 + from ipapython.config import IPAOptionParser + from ipapython.ipa_log_manager import standard_logging_setup +@@ -68,13 +68,13 @@ def parse_options(): + default=False, help="unattended installation never prompts the user") + parser.add_option("--external-ca", dest="external_ca", action="store_true", + default=False, help="Generate a CSR to be signed by an external CA") +- ext_cas = tuple(x.value for x in cainstance.ExternalCAType) ++ ext_cas = tuple(x.value for x in x509.ExternalCAType) + parser.add_option("--external-ca-type", dest="external_ca_type", + type="choice", choices=ext_cas, + metavar="{{{0}}}".format(",".join(ext_cas)), + help="Type of the external CA. Default: generic") + parser.add_option("--external-ca-profile", dest="external_ca_profile", +- type='constructor', constructor=cainstance.ExternalCAProfile, ++ type='constructor', constructor=x509.ExternalCAProfile, + default=None, metavar="PROFILE-SPEC", + help="Specify the certificate profile/template to use " + "at the external CA") +diff --git a/ipalib/x509.py b/ipalib/x509.py +index ab3c5f553d..1f612a3797 100644 +--- a/ipalib/x509.py ++++ b/ipalib/x509.py +@@ -34,6 +34,7 @@ + import os + import binascii + import datetime ++import enum + import ipaddress + import ssl + import base64 +@@ -47,6 +48,7 @@ + Encoding, PublicFormat, PrivateFormat, load_pem_private_key + ) + import pyasn1 ++import pyasn1.error + from pyasn1.type import univ, char, namedtype, tag + from pyasn1.codec.der import decoder, encoder + from pyasn1_modules import rfc2315, rfc2459 +@@ -745,3 +747,172 @@ def format_datetime(t): + if t.tzinfo is None: + t = t.replace(tzinfo=UTC()) + return unicode(t.strftime("%a %b %d %H:%M:%S %Y %Z")) ++ ++ ++class ExternalCAType(enum.Enum): ++ GENERIC = 'generic' ++ MS_CS = 'ms-cs' ++ ++ ++class ExternalCAProfile: ++ """ ++ An external CA profile configuration. Currently the only ++ subclasses are for Microsoft CAs, for providing data in the ++ "Certificate Template" extension. ++ ++ Constructing this class will actually return an instance of a ++ subclass. ++ ++ Subclasses MUST set ``valid_for``. ++ ++ """ ++ def __init__(self, s=None): ++ self.unparsed_input = s ++ ++ # Which external CA types is the data valid for? ++ # A set of VALUES of the ExternalCAType enum. ++ valid_for = set() ++ ++ def __new__(cls, s=None): ++ """Construct the ExternalCAProfile value. ++ ++ Return an instance of a subclass determined by ++ the format of the argument. ++ ++ """ ++ # we are directly constructing a subclass; instantiate ++ # it and be done ++ if cls is not ExternalCAProfile: ++ return super(ExternalCAProfile, cls).__new__(cls) ++ ++ # construction via the base class; therefore the string ++ # argument is required, and is used to determine which ++ # subclass to construct ++ if s is None: ++ raise ValueError('string argument is required') ++ ++ parts = s.split(':') ++ ++ try: ++ # Is the first part on OID? ++ _oid = univ.ObjectIdentifier(parts[0]) ++ ++ # It is; construct a V2 template ++ # pylint: disable=too-many-function-args ++ return MSCSTemplateV2.__new__(MSCSTemplateV2, s) ++ ++ except pyasn1.error.PyAsn1Error: ++ # It is not an OID; treat as a template name ++ # pylint: disable=too-many-function-args ++ return MSCSTemplateV1.__new__(MSCSTemplateV1, s) ++ ++ def __getstate__(self): ++ return self.unparsed_input ++ ++ def __setstate__(self, state): ++ # explicitly call __init__ method to initialise object ++ self.__init__(state) ++ ++ ++class MSCSTemplate(ExternalCAProfile): ++ """ ++ An Microsoft AD-CS Template specifier. ++ ++ Subclasses MUST set ext_oid. ++ ++ Subclass constructors MUST set asn1obj. ++ ++ """ ++ valid_for = set([ExternalCAType.MS_CS.value]) ++ ++ ext_oid = None # extension OID, as a Python str ++ asn1obj = None # unencoded extension data ++ ++ def get_ext_data(self): ++ """Return DER-encoded extension data.""" ++ return encoder.encode(self.asn1obj) ++ ++ ++class MSCSTemplateV1(MSCSTemplate): ++ """ ++ A v1 template specifier, per ++ https://msdn.microsoft.com/en-us/library/cc250011.aspx. ++ ++ :: ++ ++ CertificateTemplateName ::= SEQUENCE { ++ Name UTF8String ++ } ++ ++ But note that a bare BMPString is used in practice. ++ ++ """ ++ ext_oid = "1.3.6.1.4.1.311.20.2" ++ ++ def __init__(self, s): ++ super(MSCSTemplateV1, self).__init__(s) ++ parts = s.split(':') ++ if len(parts) > 1: ++ raise ValueError( ++ "Cannot specify certificate template version when using name.") ++ self.asn1obj = char.BMPString(str(parts[0])) ++ ++ ++class MSCSTemplateV2(MSCSTemplate): ++ """ ++ A v2 template specifier, per ++ https://msdn.microsoft.com/en-us/library/windows/desktop/aa378274(v=vs.85).aspx ++ ++ :: ++ ++ CertificateTemplate ::= SEQUENCE { ++ templateID EncodedObjectID, ++ templateMajorVersion TemplateVersion, ++ templateMinorVersion TemplateVersion OPTIONAL ++ } ++ ++ TemplateVersion ::= INTEGER (0..4294967295) ++ ++ """ ++ ext_oid = "1.3.6.1.4.1.311.21.7" ++ ++ @staticmethod ++ def check_version_in_range(desc, n): ++ if n < 0 or n >= 2**32: ++ raise ValueError( ++ "Template {} version must be in range 0..4294967295" ++ .format(desc)) ++ ++ def __init__(self, s): ++ super(MSCSTemplateV2, self).__init__(s) ++ ++ parts = s.split(':') ++ ++ obj = CertificateTemplateV2() ++ if len(parts) < 2 or len(parts) > 3: ++ raise ValueError( ++ "Incorrect template specification; required format is: " ++ ":[:]") ++ try: ++ obj['templateID'] = univ.ObjectIdentifier(parts[0]) ++ ++ major = int(parts[1]) ++ self.check_version_in_range("major", major) ++ obj['templateMajorVersion'] = major ++ ++ if len(parts) > 2: ++ minor = int(parts[2]) ++ self.check_version_in_range("minor", minor) ++ obj['templateMinorVersion'] = int(parts[2]) ++ ++ except pyasn1.error.PyAsn1Error: ++ raise ValueError("Could not parse certificate template specifier.") ++ self.asn1obj = obj ++ ++ ++class CertificateTemplateV2(univ.Sequence): ++ componentType = namedtype.NamedTypes( ++ namedtype.NamedType('templateID', univ.ObjectIdentifier()), ++ namedtype.NamedType('templateMajorVersion', univ.Integer()), ++ namedtype.OptionalNamedType('templateMinorVersion', univ.Integer()) ++ ) +diff --git a/ipaserver/install/ca.py b/ipaserver/install/ca.py +index 6b040b311a..8fb5e3ec91 100644 +--- a/ipaserver/install/ca.py ++++ b/ipaserver/install/ca.py +@@ -28,7 +28,7 @@ + from ipaplatform.paths import paths + from ipaserver.install import installutils, certs + from ipaserver.install.replication import replica_conn_check +-from ipalib import api, errors ++from ipalib import api, errors, x509 + from ipapython.dn import DN + + from . import conncheck, dogtag, cainstance +@@ -216,8 +216,7 @@ def install_check(standalone, replica_config, options): + paths.ROOT_IPA_CSR) + + if not options.external_ca_type: +- options.external_ca_type = \ +- cainstance.ExternalCAType.GENERIC.value ++ options.external_ca_type = x509.ExternalCAType.GENERIC.value + + if options.external_ca_profile is not None: + # check that profile is valid for the external ca type +@@ -478,13 +477,11 @@ class CAInstallInterface(dogtag.DogtagInstallInterface, + external_ca = master_install_only(external_ca) + + external_ca_type = knob( +- cainstance.ExternalCAType, None, +- description="Type of the external CA", +- ) ++ x509.ExternalCAType, None, description="Type of the external CA") + external_ca_type = master_install_only(external_ca_type) + + external_ca_profile = knob( +- type=cainstance.ExternalCAProfile, ++ type=x509.ExternalCAProfile, + default=None, + description=( + "Specify the certificate profile/template to use at the " +diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py +index 6e1fc724db..2295581870 100644 +--- a/ipaserver/install/cainstance.py ++++ b/ipaserver/install/cainstance.py +@@ -26,7 +26,6 @@ + import logging + + import dbus +-import enum + import ldap + import os + import pwd +@@ -39,10 +38,6 @@ + import tempfile + from configparser import RawConfigParser + +-from pyasn1.codec.der import encoder +-from pyasn1.type import char, univ, namedtype +-import pyasn1.error +- + from ipalib import api + from ipalib import x509 + from ipalib import errors +@@ -80,11 +75,6 @@ + ] + + +-class ExternalCAType(enum.Enum): +- GENERIC = 'generic' +- MS_CS = 'ms-cs' +- +- + def check_ports(): + """Check that dogtag ports (8080, 8443) are available. + +@@ -367,7 +357,7 @@ def configure_instance(self, host_name, dm_password, admin_password, + if ca_type is not None: + self.ca_type = ca_type + else: +- self.ca_type = ExternalCAType.GENERIC.value ++ self.ca_type = x509.ExternalCAType.GENERIC.value + self.external_ca_profile = external_ca_profile + + self.no_db_setup = promote +@@ -537,12 +527,12 @@ def __spawn_instance(self): + pki_ca_signing_csr_path=self.csr_file, + ) + +- if self.ca_type == ExternalCAType.MS_CS.value: ++ if self.ca_type == x509.ExternalCAType.MS_CS.value: + # Include MS template name extension in the CSR + template = self.external_ca_profile + if template is None: + # default template name +- template = MSCSTemplateV1(u"SubCA") ++ template = x509.MSCSTemplateV1(u"SubCA") + + ext_data = binascii.hexlify(template.get_ext_data()) + cfg.update( +@@ -2081,170 +2071,6 @@ def update_ipa_conf(): + parser.write(f) + + +-class ExternalCAProfile: +- """ +- An external CA profile configuration. Currently the only +- subclasses are for Microsoft CAs, for providing data in the +- "Certificate Template" extension. +- +- Constructing this class will actually return an instance of a +- subclass. +- +- Subclasses MUST set ``valid_for``. +- +- """ +- def __init__(self, s=None): +- self.unparsed_input = s +- +- # Which external CA types is the data valid for? +- # A set of VALUES of the ExternalCAType enum. +- valid_for = set() +- +- def __new__(cls, s=None): +- """Construct the ExternalCAProfile value. +- +- Return an instance of a subclass determined by +- the format of the argument. +- +- """ +- # we are directly constructing a subclass; instantiate +- # it and be done +- if cls is not ExternalCAProfile: +- return super(ExternalCAProfile, cls).__new__(cls) +- +- # construction via the base class; therefore the string +- # argument is required, and is used to determine which +- # subclass to construct +- if s is None: +- raise ValueError('string argument is required') +- +- parts = s.split(':') +- +- try: +- # Is the first part on OID? +- _oid = univ.ObjectIdentifier(parts[0]) +- +- # It is; construct a V2 template +- # pylint: disable=too-many-function-args +- return MSCSTemplateV2.__new__(MSCSTemplateV2, s) +- +- except pyasn1.error.PyAsn1Error: +- # It is not an OID; treat as a template name +- # pylint: disable=too-many-function-args +- return MSCSTemplateV1.__new__(MSCSTemplateV1, s) +- +- def __getstate__(self): +- return self.unparsed_input +- +- def __setstate__(self, state): +- # explicitly call __init__ method to initialise object +- self.__init__(state) +- +- +-class MSCSTemplate(ExternalCAProfile): +- """ +- An Microsoft AD-CS Template specifier. +- +- Subclasses MUST set ext_oid. +- +- Subclass constructors MUST set asn1obj. +- +- """ +- valid_for = set([ExternalCAType.MS_CS.value]) +- +- ext_oid = None # extension OID, as a Python str +- asn1obj = None # unencoded extension data +- +- def get_ext_data(self): +- """Return DER-encoded extension data.""" +- return encoder.encode(self.asn1obj) +- +- +-class MSCSTemplateV1(MSCSTemplate): +- """ +- A v1 template specifier, per +- https://msdn.microsoft.com/en-us/library/cc250011.aspx. +- +- :: +- +- CertificateTemplateName ::= SEQUENCE { +- Name UTF8String +- } +- +- But note that a bare BMPString is used in practice. +- +- """ +- ext_oid = "1.3.6.1.4.1.311.20.2" +- +- def __init__(self, s): +- super(MSCSTemplateV1, self).__init__(s) +- parts = s.split(':') +- if len(parts) > 1: +- raise ValueError( +- "Cannot specify certificate template version when using name.") +- self.asn1obj = char.BMPString(str(parts[0])) +- +- +-class MSCSTemplateV2(MSCSTemplate): +- """ +- A v2 template specifier, per +- https://msdn.microsoft.com/en-us/library/windows/desktop/aa378274(v=vs.85).aspx +- +- :: +- +- CertificateTemplate ::= SEQUENCE { +- templateID EncodedObjectID, +- templateMajorVersion TemplateVersion, +- templateMinorVersion TemplateVersion OPTIONAL +- } +- +- TemplateVersion ::= INTEGER (0..4294967295) +- +- """ +- ext_oid = "1.3.6.1.4.1.311.21.7" +- +- @staticmethod +- def check_version_in_range(desc, n): +- if n < 0 or n >= 2**32: +- raise ValueError( +- "Template {} version must be in range 0..4294967295" +- .format(desc)) +- +- def __init__(self, s): +- super(MSCSTemplateV2, self).__init__(s) +- +- parts = s.split(':') +- +- obj = CertificateTemplateV2() +- if len(parts) < 2 or len(parts) > 3: +- raise ValueError( +- "Incorrect template specification; required format is: " +- ":[:]") +- try: +- obj['templateID'] = univ.ObjectIdentifier(parts[0]) +- +- major = int(parts[1]) +- self.check_version_in_range("major", major) +- obj['templateMajorVersion'] = major +- +- if len(parts) > 2: +- minor = int(parts[2]) +- self.check_version_in_range("minor", minor) +- obj['templateMinorVersion'] = int(parts[2]) +- +- except pyasn1.error.PyAsn1Error: +- raise ValueError("Could not parse certificate template specifier.") +- self.asn1obj = obj +- +- +-class CertificateTemplateV2(univ.Sequence): +- componentType = namedtype.NamedTypes( +- namedtype.NamedType('templateID', univ.ObjectIdentifier()), +- namedtype.NamedType('templateMajorVersion', univ.Integer()), +- namedtype.OptionalNamedType('templateMinorVersion', univ.Integer()) +- ) +- +- + if __name__ == "__main__": + standard_logging_setup("install.log") + ds = dsinstance.DsInstance() +diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py +index 3f113c35bf..37dcc2befa 100644 +--- a/ipaserver/install/ipa_cacert_manage.py ++++ b/ipaserver/install/ipa_cacert_manage.py +@@ -65,7 +65,7 @@ def add_options(cls, parser): + "--external-ca", dest='self_signed', + action='store_false', + help="Sign the renewed certificate by external CA") +- ext_cas = tuple(x.value for x in cainstance.ExternalCAType) ++ ext_cas = tuple(x.value for x in x509.ExternalCAType) + renew_group.add_option( + "--external-ca-type", dest="external_ca_type", + type="choice", choices=ext_cas, +@@ -73,7 +73,7 @@ def add_options(cls, parser): + help="Type of the external CA. Default: generic") + renew_group.add_option( + "--external-ca-profile", dest="external_ca_profile", +- type='constructor', constructor=cainstance.ExternalCAProfile, ++ type='constructor', constructor=x509.ExternalCAProfile, + default=None, metavar="PROFILE-SPEC", + help="Specify the certificate profile/template to use " + "at the external CA") +@@ -224,11 +224,11 @@ def renew_external_step_1(self, ca): + options = self.options + + if not options.external_ca_type: +- options.external_ca_type = cainstance.ExternalCAType.GENERIC.value ++ options.external_ca_type = x509.ExternalCAType.GENERIC.value + +- if options.external_ca_type == cainstance.ExternalCAType.MS_CS.value \ ++ if options.external_ca_type == x509.ExternalCAType.MS_CS.value \ + and options.external_ca_profile is None: +- options.external_ca_profile = cainstance.MSCSTemplateV1(u"SubCA") ++ options.external_ca_profile = x509.MSCSTemplateV1(u"SubCA") + + if options.external_ca_profile is not None: + # check that profile is valid for the external ca type +@@ -352,11 +352,11 @@ def resubmit_request(self, ca=RENEWAL_CA_NAME, profile=None): + timeout = api.env.startup_timeout + 60 + + cm_profile = None +- if isinstance(profile, cainstance.MSCSTemplateV1): ++ if isinstance(profile, x509.MSCSTemplateV1): + cm_profile = profile.unparsed_input + + cm_template = None +- if isinstance(profile, cainstance.MSCSTemplateV2): ++ if isinstance(profile, x509.MSCSTemplateV2): + cm_template = profile.unparsed_input + + logger.debug("resubmitting certmonger request '%s'", self.request_id) +diff --git a/ipatests/test_integration/test_external_ca.py b/ipatests/test_integration/test_external_ca.py +index a42355217d..5aa2b7bba0 100644 +--- a/ipatests/test_integration/test_external_ca.py ++++ b/ipatests/test_integration/test_external_ca.py +@@ -108,14 +108,14 @@ def check_ipaca_issuerDN(host, expected_dn): + assert "Issuer DN: {}".format(expected_dn) in result.stdout_text + + +-def check_mscs_extension(ipa_csr, oid, value): ++def check_mscs_extension(ipa_csr, template): + csr = x509.load_pem_x509_csr(ipa_csr, default_backend()) + extensions = [ + ext for ext in csr.extensions +- if ext.oid.dotted_string == oid ++ if ext.oid.dotted_string == template.ext_oid + ] + assert extensions +- assert extensions[0].value.value == value ++ assert extensions[0].value.value == template.get_ext_data() + + + class TestExternalCA(IntegrationTest): +@@ -134,10 +134,7 @@ def test_external_ca(self): + + # check CSR for extension + ipa_csr = self.master.get_file_contents(paths.ROOT_IPA_CSR) +- # Values for MSCSTemplateV1('SubCA') +- oid = "1.3.6.1.4.1.311.20.2" +- value = b'\x1e\n\x00S\x00u\x00b\x00C\x00A' +- check_mscs_extension(ipa_csr, oid, value) ++ check_mscs_extension(ipa_csr, ipa_x509.MSCSTemplateV1(u'SubCA')) + + # Sign CA, transport it to the host and get ipa a root ca paths. + root_ca_fname, ipa_ca_fname = tasks.sign_ca_and_transport( +diff --git a/ipatests/test_ipalib/test_x509.py b/ipatests/test_ipalib/test_x509.py +index ff7e6de2f7..284b998316 100644 +--- a/ipatests/test_ipalib/test_x509.py ++++ b/ipatests/test_ipalib/test_x509.py +@@ -22,7 +22,11 @@ + """ + + import base64 ++from binascii import hexlify ++from configparser import RawConfigParser + import datetime ++from io import StringIO ++import pickle + + import pytest + +@@ -268,3 +272,114 @@ def test_ipa_demo_letsencrypt(self): + b'0 \x06\x03U\x1d%\x01\x01\xff\x04\x160\x14\x06\x08+\x06\x01' + b'\x05\x05\x07\x03\x01\x06\x08+\x06\x01\x05\x05\x07\x03\x02' + ) ++ ++ ++class test_ExternalCAProfile: ++ def test_MSCSTemplateV1_good(self): ++ o = x509.MSCSTemplateV1("MySubCA") ++ assert hexlify(o.get_ext_data()) == b'1e0e004d007900530075006200430041' ++ ++ def test_MSCSTemplateV1_bad(self): ++ with pytest.raises(ValueError): ++ x509.MSCSTemplateV1("MySubCA:1") ++ ++ def test_MSCSTemplateV1_pickle_roundtrip(self): ++ o = x509.MSCSTemplateV1("MySubCA") ++ s = pickle.dumps(o) ++ assert o.get_ext_data() == pickle.loads(s).get_ext_data() ++ ++ def test_MSCSTemplateV2_too_few_parts(self): ++ with pytest.raises(ValueError): ++ x509.MSCSTemplateV2("1.2.3.4") ++ ++ def test_MSCSTemplateV2_too_many_parts(self): ++ with pytest.raises(ValueError): ++ x509.MSCSTemplateV2("1.2.3.4:100:200:300") ++ ++ def test_MSCSTemplateV2_bad_oid(self): ++ with pytest.raises(ValueError): ++ x509.MSCSTemplateV2("not_an_oid:1") ++ ++ def test_MSCSTemplateV2_non_numeric_major_version(self): ++ with pytest.raises(ValueError): ++ x509.MSCSTemplateV2("1.2.3.4:major:200") ++ ++ def test_MSCSTemplateV2_non_numeric_minor_version(self): ++ with pytest.raises(ValueError): ++ x509.MSCSTemplateV2("1.2.3.4:100:minor") ++ ++ def test_MSCSTemplateV2_major_version_lt_zero(self): ++ with pytest.raises(ValueError): ++ x509.MSCSTemplateV2("1.2.3.4:-1:200") ++ ++ def test_MSCSTemplateV2_minor_version_lt_zero(self): ++ with pytest.raises(ValueError): ++ x509.MSCSTemplateV2("1.2.3.4:100:-1") ++ ++ def test_MSCSTemplateV2_major_version_gt_max(self): ++ with pytest.raises(ValueError): ++ x509.MSCSTemplateV2("1.2.3.4:4294967296:200") ++ ++ def test_MSCSTemplateV2_minor_version_gt_max(self): ++ with pytest.raises(ValueError): ++ x509.MSCSTemplateV2("1.2.3.4:100:4294967296") ++ ++ def test_MSCSTemplateV2_good_major(self): ++ o = x509.MSCSTemplateV2("1.2.3.4:4294967295") ++ assert hexlify(o.get_ext_data()) == b'300c06032a0304020500ffffffff' ++ ++ def test_MSCSTemplateV2_good_major_minor(self): ++ o = x509.MSCSTemplateV2("1.2.3.4:4294967295:0") ++ assert hexlify(o.get_ext_data()) \ ++ == b'300f06032a0304020500ffffffff020100' ++ ++ def test_MSCSTemplateV2_pickle_roundtrip(self): ++ o = x509.MSCSTemplateV2("1.2.3.4:4294967295:0") ++ s = pickle.dumps(o) ++ assert o.get_ext_data() == pickle.loads(s).get_ext_data() ++ ++ def test_ExternalCAProfile_dispatch(self): ++ """ ++ Test that constructing ExternalCAProfile actually returns an ++ instance of the appropriate subclass. ++ """ ++ assert isinstance( ++ x509.ExternalCAProfile("MySubCA"), ++ x509.MSCSTemplateV1) ++ assert isinstance( ++ x509.ExternalCAProfile("1.2.3.4:100"), ++ x509.MSCSTemplateV2) ++ ++ def test_write_pkispawn_config_file_MSCSTemplateV1(self): ++ template = x509.MSCSTemplateV1(u"SubCA") ++ expected = ( ++ '[CA]\n' ++ 'pki_req_ext_oid = 1.3.6.1.4.1.311.20.2\n' ++ 'pki_req_ext_data = 1e0a00530075006200430041\n\n' ++ ) ++ self._test_write_pkispawn_config_file(template, expected) ++ ++ def test_write_pkispawn_config_file_MSCSTemplateV2(self): ++ template = x509.MSCSTemplateV2(u"1.2.3.4:4294967295") ++ expected = ( ++ '[CA]\n' ++ 'pki_req_ext_oid = 1.3.6.1.4.1.311.21.7\n' ++ 'pki_req_ext_data = 300c06032a0304020500ffffffff\n\n' ++ ) ++ self._test_write_pkispawn_config_file(template, expected) ++ ++ def _test_write_pkispawn_config_file(self, template, expected): ++ """ ++ Test that the values we read from an ExternalCAProfile ++ object can be used to produce a reasonable-looking pkispawn ++ configuration. ++ """ ++ config = RawConfigParser() ++ config.optionxform = str ++ config.add_section("CA") ++ config.set("CA", "pki_req_ext_oid", template.ext_oid) ++ config.set("CA", "pki_req_ext_data", ++ hexlify(template.get_ext_data()).decode('ascii')) ++ out = StringIO() ++ config.write(out) ++ assert out.getvalue() == expected +diff --git a/ipatests/test_ipaserver/test_install/test_cainstance.py b/ipatests/test_ipaserver/test_install/test_cainstance.py +deleted file mode 100644 +index 02d9758e4a..0000000000 +--- a/ipatests/test_ipaserver/test_install/test_cainstance.py ++++ /dev/null +@@ -1,123 +0,0 @@ +-# +-# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +-# +- +-from binascii import hexlify +-from io import StringIO +-import pickle +-from configparser import RawConfigParser +-import pytest +-from ipaserver.install import cainstance +- +-pytestmark = pytest.mark.tier0 +- +- +-class test_ExternalCAProfile: +- def test_MSCSTemplateV1_good(self): +- o = cainstance.MSCSTemplateV1("MySubCA") +- assert hexlify(o.get_ext_data()) == b'1e0e004d007900530075006200430041' +- +- def test_MSCSTemplateV1_bad(self): +- with pytest.raises(ValueError): +- cainstance.MSCSTemplateV1("MySubCA:1") +- +- def test_MSCSTemplateV1_pickle_roundtrip(self): +- o = cainstance.MSCSTemplateV1("MySubCA") +- s = pickle.dumps(o) +- assert o.get_ext_data() == pickle.loads(s).get_ext_data() +- +- def test_MSCSTemplateV2_too_few_parts(self): +- with pytest.raises(ValueError): +- cainstance.MSCSTemplateV2("1.2.3.4") +- +- def test_MSCSTemplateV2_too_many_parts(self): +- with pytest.raises(ValueError): +- cainstance.MSCSTemplateV2("1.2.3.4:100:200:300") +- +- def test_MSCSTemplateV2_bad_oid(self): +- with pytest.raises(ValueError): +- cainstance.MSCSTemplateV2("not_an_oid:1") +- +- def test_MSCSTemplateV2_non_numeric_major_version(self): +- with pytest.raises(ValueError): +- cainstance.MSCSTemplateV2("1.2.3.4:major:200") +- +- def test_MSCSTemplateV2_non_numeric_minor_version(self): +- with pytest.raises(ValueError): +- cainstance.MSCSTemplateV2("1.2.3.4:100:minor") +- +- def test_MSCSTemplateV2_major_version_lt_zero(self): +- with pytest.raises(ValueError): +- cainstance.MSCSTemplateV2("1.2.3.4:-1:200") +- +- def test_MSCSTemplateV2_minor_version_lt_zero(self): +- with pytest.raises(ValueError): +- cainstance.MSCSTemplateV2("1.2.3.4:100:-1") +- +- def test_MSCSTemplateV2_major_version_gt_max(self): +- with pytest.raises(ValueError): +- cainstance.MSCSTemplateV2("1.2.3.4:4294967296:200") +- +- def test_MSCSTemplateV2_minor_version_gt_max(self): +- with pytest.raises(ValueError): +- cainstance.MSCSTemplateV2("1.2.3.4:100:4294967296") +- +- def test_MSCSTemplateV2_good_major(self): +- o = cainstance.MSCSTemplateV2("1.2.3.4:4294967295") +- assert hexlify(o.get_ext_data()) == b'300c06032a0304020500ffffffff' +- +- def test_MSCSTemplateV2_good_major_minor(self): +- o = cainstance.MSCSTemplateV2("1.2.3.4:4294967295:0") +- assert hexlify(o.get_ext_data()) \ +- == b'300f06032a0304020500ffffffff020100' +- +- def test_MSCSTemplateV2_pickle_roundtrip(self): +- o = cainstance.MSCSTemplateV2("1.2.3.4:4294967295:0") +- s = pickle.dumps(o) +- assert o.get_ext_data() == pickle.loads(s).get_ext_data() +- +- def test_ExternalCAProfile_dispatch(self): +- """ +- Test that constructing ExternalCAProfile actually returns an +- instance of the appropriate subclass. +- """ +- assert isinstance( +- cainstance.ExternalCAProfile("MySubCA"), +- cainstance.MSCSTemplateV1) +- assert isinstance( +- cainstance.ExternalCAProfile("1.2.3.4:100"), +- cainstance.MSCSTemplateV2) +- +- def test_write_pkispawn_config_file_MSCSTemplateV1(self): +- template = cainstance.MSCSTemplateV1(u"SubCA") +- expected = ( +- '[CA]\n' +- 'pki_req_ext_oid = 1.3.6.1.4.1.311.20.2\n' +- 'pki_req_ext_data = 1e0a00530075006200430041\n\n' +- ) +- self._test_write_pkispawn_config_file(template, expected) +- +- def test_write_pkispawn_config_file_MSCSTemplateV2(self): +- template = cainstance.MSCSTemplateV2(u"1.2.3.4:4294967295") +- expected = ( +- '[CA]\n' +- 'pki_req_ext_oid = 1.3.6.1.4.1.311.21.7\n' +- 'pki_req_ext_data = 300c06032a0304020500ffffffff\n\n' +- ) +- self._test_write_pkispawn_config_file(template, expected) +- +- def _test_write_pkispawn_config_file(self, template, expected): +- """ +- Test that the values we read from an ExternalCAProfile +- object can be used to produce a reasonable-looking pkispawn +- configuration. +- """ +- config = RawConfigParser() +- config.optionxform = str +- config.add_section("CA") +- config.set("CA", "pki_req_ext_oid", template.ext_oid) +- config.set("CA", "pki_req_ext_data", +- hexlify(template.get_ext_data()).decode('ascii')) +- out = StringIO() +- config.write(out) +- assert out.getvalue() == expected +From e632b220798833bcd65c6b266610c800ed0914d7 Mon Sep 17 00:00:00 2001 +From: Fraser Tweedale +Date: Fri, 12 Jul 2019 13:13:02 +1000 +Subject: [PATCH] install: fix --external-ca-profile option + +Commit dd47cfc75a69618f486abefb70f2649ebf8264e7 removed the ability +to set pki_req_ext_oid and pki_req_ext_data in the pkispawn config. +This results in the --external-ca-profile option never setting the +requested values in the CSR (the default V1 template type specifying +"SubCA" is always used). + +Remove relevant fields from both ipaca_default.ini and +ipaca_customize.ini. This allows the IPA framework to set the +values (i.e. when --external-ca-type=ms-cs and +--external-ca-profile=... demand it). It also allows users to +override the pki_req_ext_* settings. + +Part of: https://pagure.io/freeipa/issue/7548 +Related: https://pagure.io/freeipa/issue/5608 +Reviewed-By: Florence Blanc-Renaud +--- + install/share/ipaca_customize.ini | 5 ----- + install/share/ipaca_default.ini | 1 - + 2 files changed, 6 deletions(-) + +diff --git a/install/share/ipaca_customize.ini b/install/share/ipaca_customize.ini +index 130ec2c102..6d58579af8 100644 +--- a/install/share/ipaca_customize.ini ++++ b/install/share/ipaca_customize.ini +@@ -93,11 +93,6 @@ pki_ca_signing_key_type=%(ipa_ca_key_type)s + pki_ca_signing_signing_algorithm=%(ipa_ca_signing_algorithm)s + pki_ca_signing_token=%(pki_token_name)s + +-# MS subca request ext data +-pki_req_ext_oid=1.3.6.1.4.1.311.20.2 +-pki_req_ext_critical=False +-pki_req_ext_data=1E0A00530075006200430041 +- + ## ocspSigningCert cert-pki-ca + pki_ocsp_signing_key_algorithm=%(ipa_key_algorithm)s + pki_ocsp_signing_key_size=%(ipa_key_size)s +diff --git a/install/share/ipaca_default.ini b/install/share/ipaca_default.ini +index fedc1b9a74..2b9900286e 100644 +--- a/install/share/ipaca_default.ini ++++ b/install/share/ipaca_default.ini +@@ -115,7 +115,6 @@ pki_ca_starting_crl_number=0 + + pki_external=False + pki_external_step_two=False +-pki_req_ext_add=False + + pki_external_pkcs12_path=%(pki_pkcs12_path)s + pki_external_pkcs12_password=%(pki_pkcs12_password)s +From 71af731b3069fa1b2c0b51a3b917b5bc4da54350 Mon Sep 17 00:00:00 2001 +From: Fraser Tweedale +Date: Fri, 12 Jul 2019 13:24:51 +1000 +Subject: [PATCH] Fix use of incorrect variable + +Part of: https://pagure.io/freeipa/issue/7548 +Related: https://pagure.io/freeipa/issue/5608 +Reviewed-By: Florence Blanc-Renaud +--- + ipaserver/install/dogtaginstance.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py +index cc75d89746..5dca721d6c 100644 +--- a/ipaserver/install/dogtaginstance.py ++++ b/ipaserver/install/dogtaginstance.py +@@ -853,7 +853,7 @@ def _verify_immutable(self, config, immutable_settings, filename): + if errs: + raise ValueError( + '{} overrides immutable options:\n{}'.format( +- filename, '\n'.join(errors) ++ filename, '\n'.join(errs) + ) + ) + +From 83ed05725110de19a7098678274ecaaaf6a2c9c9 Mon Sep 17 00:00:00 2001 +From: Fraser Tweedale +Date: Wed, 20 Feb 2019 18:34:33 +1100 +Subject: [PATCH] Add more tests for --external-ca-profile handling + +Add tests for remaining untested scenarios of --external-ca-profile +handling in ipa-server-install. + +ipa-ca-install and ipa-cacert-manage remain untested at present. + +Fixes: https://pagure.io/freeipa/issue/7548 +Reviewed-By: Florence Blanc-Renaud +--- + ipatests/test_integration/test_external_ca.py | 97 ++++++++++++++++++- + 1 file changed, 95 insertions(+), 2 deletions(-) + +diff --git a/ipatests/test_integration/test_external_ca.py b/ipatests/test_integration/test_external_ca.py +index 5aa2b7bba0..dc9a09b43b 100644 +--- a/ipatests/test_integration/test_external_ca.py ++++ b/ipatests/test_integration/test_external_ca.py +@@ -74,10 +74,10 @@ def match_in_journal(host, string, since='today', services=('certmonger',)): + return match + + +-def install_server_external_ca_step1(host, extra_args=()): ++def install_server_external_ca_step1(host, extra_args=(), raiseonerr=True): + """Step 1 to install the ipa server with external ca""" + return tasks.install_master( +- host, external_ca=True, extra_args=extra_args ++ host, external_ca=True, extra_args=extra_args, raiseonerr=raiseonerr, + ) + + +@@ -478,3 +478,96 @@ def test_master_install_ca2(self): + 'certutil', '-L', '-d', paths.PKI_TOMCAT_ALIAS_DIR, + '-n', cert_nick]) + assert "CN=RootCA2" in result.stdout_text ++ ++ ++def _step1_profile(master, s): ++ return install_server_external_ca_step1( ++ master, ++ extra_args=['--external-ca-type=ms-cs', f'--external-ca-profile={s}'], ++ raiseonerr=False, ++ ) ++ ++ ++def _test_invalid_profile(master, profile): ++ result = _step1_profile(master, profile) ++ assert result.returncode != 0 ++ assert '--external-ca-profile' in result.stderr_text ++ ++ ++def _test_valid_profile(master, profile_cls, profile): ++ result = _step1_profile(master, profile) ++ assert result.returncode == 0 ++ ipa_csr = master.get_file_contents(paths.ROOT_IPA_CSR) ++ check_mscs_extension(ipa_csr, profile_cls(profile)) ++ ++ ++class TestExternalCAProfileV1(IntegrationTest): ++ """ ++ Test that --external-ca-profile=Foo gets propagated to the CSR. ++ ++ The default template extension when --external-ca-type=ms-cs, ++ a V1 extension with value "SubCA", already gets tested by the ++ ``TestExternalCA`` class. ++ ++ We only need to do Step 1 of installation, then check the CSR. ++ ++ """ ++ def test_invalid_v1_template(self): ++ _test_invalid_profile(self.master, 'NotAnOid:1') ++ ++ def test_valid_v1_template(self): ++ _test_valid_profile( ++ self.master, ipa_x509.MSCSTemplateV1, 'TemplateOfAwesome') ++ ++ ++class TestExternalCAProfileV2MajorOnly(IntegrationTest): ++ """ ++ Test that V2 template specifiers without minor version get ++ propagated to CSR. This class also tests all error modes in ++ specifying a V2 template, those being: ++ ++ - no major version specified ++ - too many parts specified (i.e. major, minor, and then some more) ++ - major version is not an int ++ - major version is negative ++ - minor version is not an int ++ - minor version is negative ++ ++ We only need to do Step 1 of installation, then check the CSR. ++ ++ """ ++ def test_v2_template_too_few_parts(self): ++ _test_invalid_profile(self.master, '1.2.3.4') ++ ++ def test_v2_template_too_many_parts(self): ++ _test_invalid_profile(self.master, '1.2.3.4:100:200:300') ++ ++ def test_v2_template_major_version_not_int(self): ++ _test_invalid_profile(self.master, '1.2.3.4:wat:200') ++ ++ def test_v2_template_major_version_negative(self): ++ _test_invalid_profile(self.master, '1.2.3.4:-1:200') ++ ++ def test_v2_template_minor_version_not_int(self): ++ _test_invalid_profile(self.master, '1.2.3.4:100:wat') ++ ++ def test_v2_template_minor_version_negative(self): ++ _test_invalid_profile(self.master, '1.2.3.4:100:-2') ++ ++ def test_v2_template_valid_major_only(self): ++ _test_valid_profile( ++ self.master, ipa_x509.MSCSTemplateV2, '1.2.3.4:100') ++ ++ ++class TestExternalCAProfileV2MajorMinor(IntegrationTest): ++ """ ++ Test that V2 template specifiers _with_ minor version get ++ propagated to CSR. All error modes of V2 template specifiers ++ were tested in ``TestExternalCAProfileV2Major``. ++ ++ We only need to do Step 1 of installation, then check the CSR. ++ ++ """ ++ def test_v2_template_valid_major_minor(self): ++ _test_valid_profile( ++ self.master, ipa_x509.MSCSTemplateV2, '1.2.3.4:100:200') +From a627df87c31e4d8399bd9fab43c0c4772ddd8955 Mon Sep 17 00:00:00 2001 +From: Fraser Tweedale +Date: Thu, 11 Jul 2019 20:22:33 +1000 +Subject: [PATCH] Collapse --external-ca-profile tests into single class + +To avoid having to spawn new CI hosts for each kind of +--external-ca-profile argument we are testing, collapse the three +separate test classes into one. Uninstall the half-installed IPA +after each section of tests. + +This change is in response to review comment +https://github.com/freeipa/freeipa/pull/2852#pullrequestreview-220442170. + +Part of: https://pagure.io/freeipa/issue/7548 + +Reviewed-By: Florence Blanc-Renaud +--- + ipatests/test_integration/test_external_ca.py | 34 ++++++++++++++----- + 1 file changed, 26 insertions(+), 8 deletions(-) + +diff --git a/ipatests/test_integration/test_external_ca.py b/ipatests/test_integration/test_external_ca.py +index dc9a09b43b..714aebd4a8 100644 +--- a/ipatests/test_integration/test_external_ca.py ++++ b/ipatests/test_integration/test_external_ca.py +@@ -501,8 +501,18 @@ def _test_valid_profile(master, profile_cls, profile): + check_mscs_extension(ipa_csr, profile_cls(profile)) + + +-class TestExternalCAProfileV1(IntegrationTest): ++class TestExternalCAProfileScenarios(IntegrationTest): + """ ++ Test the various --external-ca-profile scenarios. ++ This test is broken into sections, with each section first ++ testing invalid arguments, then a valid argument, and finally ++ uninstalling the half-installed IPA. ++ ++ """ ++ ++ ''' ++ Tranche 1: version 1 templates. ++ + Test that --external-ca-profile=Foo gets propagated to the CSR. + + The default template extension when --external-ca-type=ms-cs, +@@ -511,7 +521,7 @@ class TestExternalCAProfileV1(IntegrationTest): + + We only need to do Step 1 of installation, then check the CSR. + +- """ ++ ''' + def test_invalid_v1_template(self): + _test_invalid_profile(self.master, 'NotAnOid:1') + +@@ -519,9 +529,12 @@ def test_valid_v1_template(self): + _test_valid_profile( + self.master, ipa_x509.MSCSTemplateV1, 'TemplateOfAwesome') + ++ def test_uninstall_1(self): ++ tasks.uninstall_master(self.master) ++ ++ ''' ++ Tranche 2: V2 templates without minor version. + +-class TestExternalCAProfileV2MajorOnly(IntegrationTest): +- """ + Test that V2 template specifiers without minor version get + propagated to CSR. This class also tests all error modes in + specifying a V2 template, those being: +@@ -535,7 +548,7 @@ class TestExternalCAProfileV2MajorOnly(IntegrationTest): + + We only need to do Step 1 of installation, then check the CSR. + +- """ ++ ''' + def test_v2_template_too_few_parts(self): + _test_invalid_profile(self.master, '1.2.3.4') + +@@ -558,16 +571,21 @@ def test_v2_template_valid_major_only(self): + _test_valid_profile( + self.master, ipa_x509.MSCSTemplateV2, '1.2.3.4:100') + ++ def test_uninstall_2(self): ++ tasks.uninstall_master(self.master) ++ ++ ''' ++ Tranche 3: V2 templates with minor version. + +-class TestExternalCAProfileV2MajorMinor(IntegrationTest): +- """ + Test that V2 template specifiers _with_ minor version get + propagated to CSR. All error modes of V2 template specifiers + were tested in ``TestExternalCAProfileV2Major``. + + We only need to do Step 1 of installation, then check the CSR. + +- """ ++ ''' + def test_v2_template_valid_major_minor(self): + _test_valid_profile( + self.master, ipa_x509.MSCSTemplateV2, '1.2.3.4:100:200') ++ ++ # this is the end; no need to uninstall. +From 740964c3c47fd2cd216c233d8d9df1840eaa01ee Mon Sep 17 00:00:00 2001 +From: Fraser Tweedale +Date: Thu, 11 Jul 2019 20:27:02 +1000 +Subject: [PATCH] ci: add --external-ca-profile tests to nightly + +Part of: https://pagure.io/freeipa/issue/7548 + +Reviewed-By: Florence Blanc-Renaud +--- + ipatests/prci_definitions/nightly_f28.yaml | 12 ++++++++++++ + ipatests/prci_definitions/nightly_f29.yaml | 12 ++++++++++++ + ipatests/prci_definitions/nightly_master.yaml | 12 ++++++++++++ + ipatests/prci_definitions/nightly_master_pki.yaml | 12 ++++++++++++ + ipatests/prci_definitions/nightly_rawhide.yaml | 12 ++++++++++++ + 5 files changed, 60 insertions(+) + +diff --git a/ipatests/prci_definitions/nightly_f28.yaml b/ipatests/prci_definitions/nightly_f28.yaml +index fe86730444..d1605e6b5c 100644 +--- a/ipatests/prci_definitions/nightly_f28.yaml ++++ b/ipatests/prci_definitions/nightly_f28.yaml +@@ -75,6 +75,18 @@ jobs: + timeout: 3600 + topology: *master_1repl + ++ fedora-28/external_ca_templates: ++ requires: [fedora-28/build] ++ priority: 50 ++ job: ++ class: RunPytest ++ args: ++ build_url: '{fedora-28/build_url}' ++ test_suite: test_integration/test_external_ca.py::TestExternalCAProfileScenarios ++ template: *ci-master-f28 ++ timeout: 3600 ++ topology: *master_1repl ++ + fedora-28/test_topologies: + requires: [fedora-28/build] + priority: 50 +diff --git a/ipatests/prci_definitions/nightly_f29.yaml b/ipatests/prci_definitions/nightly_f29.yaml +index 57c1b624fe..ed88eb15c8 100644 +--- a/ipatests/prci_definitions/nightly_f29.yaml ++++ b/ipatests/prci_definitions/nightly_f29.yaml +@@ -75,6 +75,18 @@ jobs: + timeout: 3600 + topology: *master_1repl + ++ fedora-29/external_ca_templates: ++ requires: [fedora-29/build] ++ priority: 50 ++ job: ++ class: RunPytest ++ args: ++ build_url: '{fedora-29/build_url}' ++ test_suite: test_integration/test_external_ca.py::TestExternalCAProfileScenarios ++ template: *ci-master-f29 ++ timeout: 3600 ++ topology: *master_1repl ++ + fedora-29/test_topologies: + requires: [fedora-29/build] + priority: 50 +diff --git a/ipatests/prci_definitions/nightly_master.yaml b/ipatests/prci_definitions/nightly_master.yaml +index dc63f37426..0a66a13490 100644 +--- a/ipatests/prci_definitions/nightly_master.yaml ++++ b/ipatests/prci_definitions/nightly_master.yaml +@@ -75,6 +75,18 @@ jobs: + timeout: 3600 + topology: *master_1repl + ++ fedora-30/external_ca_templates: ++ requires: [fedora-30/build] ++ priority: 50 ++ job: ++ class: RunPytest ++ args: ++ build_url: '{fedora-30/build_url}' ++ test_suite: test_integration/test_external_ca.py::TestExternalCAProfileScenarios ++ template: *ci-master-f30 ++ timeout: 3600 ++ topology: *master_1repl ++ + fedora-30/test_topologies: + requires: [fedora-30/build] + priority: 50 +diff --git a/ipatests/prci_definitions/nightly_master_pki.yaml b/ipatests/prci_definitions/nightly_master_pki.yaml +index 1bb0af0244..ed2e38d3ed 100644 +--- a/ipatests/prci_definitions/nightly_master_pki.yaml ++++ b/ipatests/prci_definitions/nightly_master_pki.yaml +@@ -75,6 +75,18 @@ jobs: + timeout: 3600 + topology: *master_1repl + ++ fedora-29/external_ca_templates: ++ requires: [fedora-29/build] ++ priority: 50 ++ job: ++ class: RunPytest ++ args: ++ build_url: '{fedora-29/build_url}' ++ test_suite: test_integration/test_external_ca.py::TestExternalCAProfileScenarios ++ template: *pki-master-f29 ++ timeout: 3600 ++ topology: *master_1repl ++ + fedora-29/test_vault: + requires: [fedora-29/build] + priority: 50 +diff --git a/ipatests/prci_definitions/nightly_rawhide.yaml b/ipatests/prci_definitions/nightly_rawhide.yaml +index 301878467c..14433fcc0a 100644 +--- a/ipatests/prci_definitions/nightly_rawhide.yaml ++++ b/ipatests/prci_definitions/nightly_rawhide.yaml +@@ -75,6 +75,18 @@ jobs: + timeout: 3600 + topology: *master_1repl + ++ fedora-rawhide/external_ca_templates: ++ requires: [fedora-rawhide/build] ++ priority: 50 ++ job: ++ class: RunPytest ++ args: ++ build_url: '{fedora-rawhide/build_url}' ++ test_suite: test_integration/test_external_ca.py::TestExternalCAProfileScenarios ++ template: *ci-master-frawhide ++ timeout: 3600 ++ topology: *master_1repl ++ + fedora-rawhide/test_topologies: + requires: [fedora-rawhide/build] + priority: 50 +From 011c5283cec28ea4361eff5d2ee98da9cd3db41a Mon Sep 17 00:00:00 2001 +From: Fraser Tweedale +Date: Thu, 11 Jul 2019 20:27:02 +1000 +Subject: [PATCH] ci: add --external-ca-profile tests to gating + +Part of: https://pagure.io/freeipa/issue/7548 + +Reviewed-By: Florence Blanc-Renaud +--- + ipatests/prci_definitions/gating.yaml | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/ipatests/prci_definitions/gating.yaml b/ipatests/prci_definitions/gating.yaml +index 4d0107d956..81fa4bba10 100644 +--- a/ipatests/prci_definitions/gating.yaml ++++ b/ipatests/prci_definitions/gating.yaml +@@ -87,6 +87,18 @@ jobs: + timeout: 3600 + topology: *master_1repl + ++ fedora-30/external_ca_templates: ++ requires: [fedora-30/build] ++ priority: 50 ++ job: ++ class: RunPytest ++ args: ++ build_url: '{fedora-30/build_url}' ++ test_suite: test_integration/test_external_ca.py::TestExternalCAProfileScenarios ++ template: *ci-master-f30 ++ timeout: 3600 ++ topology: *master_1repl ++ + fedora-30/test_topologies: + requires: [fedora-30/build] + priority: 50 diff --git a/SOURCES/0007-Allow-insecure-binds-for-migration-8e207fd3_rhbz#1731963.patch b/SOURCES/0007-Allow-insecure-binds-for-migration-8e207fd3_rhbz#1731963.patch new file mode 100644 index 0000000..57b2ba4 --- /dev/null +++ b/SOURCES/0007-Allow-insecure-binds-for-migration-8e207fd3_rhbz#1731963.patch @@ -0,0 +1,72 @@ +From 8e207fd33d524f5cde2dfd8a41a08926a328a92b Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Tue, 13 Aug 2019 17:22:01 +0200 +Subject: [PATCH] Allow insecure binds for migration + +Commit 5be9341fbabaf7bcb396a2ce40f17e1ccfa54b77 disallowed simple bind +over an insecure connection. Password logins were only allowed over LDAPS +or LDAP+STARTTLS. The restriction broke 'ipa migrate-ds' in some cases. + +This commit lifts the restriction and permits insecure binds over plain +LDAP. It also makes the migrate-ds plugin use STARTTLS when a CA +certificate is configured with a plain LDAP connection. + +Fixes: https://pagure.io/freeipa/issue/8040 +Signed-off-by: Christian Heimes +Reviewed-By: Thomas Woerner +--- + ipapython/ipaldap.py | 8 +++++--- + ipaserver/plugins/migration.py | 9 ++++----- + 2 files changed, 9 insertions(+), 8 deletions(-) + +diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py +index 9ff443fe4f..f40858e27f 100644 +--- a/ipapython/ipaldap.py ++++ b/ipapython/ipaldap.py +@@ -1206,12 +1206,14 @@ def _connect(self): + return conn + + def simple_bind(self, bind_dn, bind_password, server_controls=None, +- client_controls=None): ++ client_controls=None, insecure_bind=False): + """ + Perform simple bind operation. + """ +- if self.protocol == 'ldap' and not self._start_tls and bind_password: +- # non-empty bind must use a secure connection ++ if (self.protocol == 'ldap' and not self._start_tls and ++ bind_password and not insecure_bind): ++ # non-empty bind must use a secure connection unless ++ # insecure bind is explicitly enabled + raise ValueError('simple_bind over insecure LDAP connection') + with self.error_handler(): + self._flush_schema() +diff --git a/ipaserver/plugins/migration.py b/ipaserver/plugins/migration.py +index d0ca8369ae..b025c46cc5 100644 +--- a/ipaserver/plugins/migration.py ++++ b/ipaserver/plugins/migration.py +@@ -901,20 +901,19 @@ def execute(self, ldapuri, bindpw, **options): + return dict(result={}, failed={}, enabled=False, compat=True) + + # connect to DS +- cacert = None + if options.get('cacertfile') is not None: + # store CA cert into file + tmp_ca_cert_f = write_tmp_file(options['cacertfile']) + cacert = tmp_ca_cert_f.name + +- # start TLS connection +- ds_ldap = LDAPClient(ldapuri, cacert=cacert) ++ # start TLS connection or STARTTLS ++ ds_ldap = LDAPClient(ldapuri, cacert=cacert, start_tls=True) + ds_ldap.simple_bind(options['binddn'], bindpw) + + tmp_ca_cert_f.close() + else: +- ds_ldap = LDAPClient(ldapuri, cacert=cacert) +- ds_ldap.simple_bind(options['binddn'], bindpw) ++ ds_ldap = LDAPClient(ldapuri) ++ ds_ldap.simple_bind(options['binddn'], bindpw, insecure_bind=True) + + # check whether the compat plugin is enabled + if not options.get('compat'): diff --git a/SOURCES/0007-Replace-hard-coded-interpreter-with-sys.executable.patch b/SOURCES/0007-Replace-hard-coded-interpreter-with-sys.executable.patch deleted file mode 100644 index 541a4c2..0000000 --- a/SOURCES/0007-Replace-hard-coded-interpreter-with-sys.executable.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 64045c5dbaf24340dea5cf0bdb629c29f70a4a9d Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Wed, 24 Oct 2018 16:08:16 +0200 -Subject: [PATCH] Replace hard-coded interpreter with sys.executable - -Instead of hard-coding python3, the smart card advise script now uses -the current executable path from sys.executable as interpreter. - -Fixes: https://pagure.io/freeipa/issue/7741 -Signed-off-by: Christian Heimes -Reviewed-By: Alexander Bokovoy - -diff --git a/ipaserver/advise/plugins/smart_card_auth.py b/ipaserver/advise/plugins/smart_card_auth.py -index 2f2e7aec9..97e23303b 100644 ---- a/ipaserver/advise/plugins/smart_card_auth.py -+++ b/ipaserver/advise/plugins/smart_card_auth.py -@@ -4,6 +4,8 @@ - - from __future__ import absolute_import - -+import sys -+ - from ipalib.plugable import Registry - from ipaplatform import services - from ipaplatform.paths import paths -@@ -186,9 +188,9 @@ class config_server_for_smart_card_auth(common_smart_card_auth_config): - def record_httpd_ocsp_status(self): - self.log.comment('store the OCSP upgrade state') - self.log.command( -- "python3 -c 'from ipaserver.install import sysupgrade; " -+ "{} -c 'from ipaserver.install import sysupgrade; " - "sysupgrade.set_upgrade_state(\"httpd\", " -- "\"{}\", True)'".format(OCSP_ENABLED)) -+ "\"{}\", True)'".format(sys.executable, OCSP_ENABLED)) - - def check_and_enable_pkinit(self): - self.log.comment('check whether PKINIT is configured on the master') -@@ -310,10 +312,10 @@ class config_client_for_smart_card_auth(common_smart_card_auth_config): - def configure_pam_cert_auth(self): - self.log.comment('Set pam_cert_auth=True in /etc/sssd/sssd.conf') - self.log.command( -- "python3 -c 'from SSSDConfig import SSSDConfig; " -+ "{} -c 'from SSSDConfig import SSSDConfig; " - "c = SSSDConfig(); c.import_config(); " - "c.set(\"pam\", \"pam_cert_auth\", \"True\"); " -- "c.write()'") -+ "c.write()'".format(sys.executable)) - - def restart_sssd(self): - self.log.command('systemctl restart sssd.service') diff --git a/SOURCES/0008-Fix_misleading_errors_during_client_install_rollback_rhbz#1658283.patch b/SOURCES/0008-Fix_misleading_errors_during_client_install_rollback_rhbz#1658283.patch deleted file mode 100644 index 6a9dfdf..0000000 --- a/SOURCES/0008-Fix_misleading_errors_during_client_install_rollback_rhbz#1658283.patch +++ /dev/null @@ -1,213 +0,0 @@ -From c64030a357401467d74e77d610d3bc268412220d Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Tue, 16 Oct 2018 13:58:00 -0400 -Subject: [PATCH] Remove the authselect profile warning if sssd was not - configured. - -On a plain uninstall there should not be a bunch of confusing -warning/error messages. - -Related to https://pagure.io/freeipa/issue/7729 - -Signed-off-by: Rob Crittenden -Reviewed-By: Christian Heimes ---- - ipatests/test_integration/test_authselect.py | 1 - - 1 file changed, 1 deletion(-) - -diff --git a/ipatests/test_integration/test_authselect.py b/ipatests/test_integration/test_authselect.py -index 5eb3fdbf02..5ce56fa21e 100644 ---- a/ipatests/test_integration/test_authselect.py -+++ b/ipatests/test_integration/test_authselect.py -@@ -136,7 +136,6 @@ def test_uninstall_client_no_preconfigured_profile(self): - # by default - result = self._uninstall_client() - assert result.returncode == 0 -- assert self.msg_warn_uninstall in result.stderr_text - check_authselect_profile(self.client, default_profile) - - def test_install_client_preconfigured_profile(self): -From ec5e821f05cbc20517af6c9578e813f1963a9e8c Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Wed, 10 Oct 2018 14:07:33 -0400 -Subject: [PATCH] Fix misleading errors during client install rollback - -Some incorrect errors are possible if a client installation -fails and a configuration rollback is required. - -These include: - -1. Unconfigured automount client failed: CalledProcessError(Command -['/usr/sbin/ipa-client-automount', '--uninstall', '--debug'] -returned non-zero exit status 1: '') - -Caused by check_client_configuration() not returning the correct -return value (2). - -2. WARNING: Unable to revert to the pre-installation state ('authconfig' -tool has been deprecated in favor of 'authselect'). The default sssd -profile will be used instead. -The authconfig arguments would have been: authconfig --disableldap ---disablekrb5 --disablesssdauth --disablemkhomedir - -If installation fails before SSSD is configured there is no state -to roll back to. Detect this condition. - -3. An error occurred while removing SSSD's cache.Please remove the -cache manually by executing sssctl cache-remove -o. - -Again, if SSSD is not configured yet then there is no cache to -remove. Also correct the missing space after the period. - -https://pagure.io/freeipa/issue/7729 - -Signed-off-by: Rob Crittenden -Reviewed-By: Christian Heimes ---- - ipaclient/install/client.py | 18 ++++++----- - ipalib/util.py | 5 +++- - ipaplatform/redhat/authconfig.py | 2 +- - .../test_replica_promotion.py | 30 +++++++++++++++++++ - 4 files changed, 45 insertions(+), 10 deletions(-) - -diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py -index 0dcd1ec744..05255fe61b 100644 ---- a/ipaclient/install/client.py -+++ b/ipaclient/install/client.py -@@ -3284,13 +3284,14 @@ def uninstall(options): - remove_file(paths.SSSD_MC_GROUP) - remove_file(paths.SSSD_MC_PASSWD) - -- try: -- run([paths.SSSCTL, "cache-remove", "-o", "--stop", "--start"]) -- except Exception: -- logger.info( -- "An error occurred while removing SSSD's cache." -- "Please remove the cache manually by executing " -- "sssctl cache-remove -o.") -+ if was_sssd_installed: -+ try: -+ run([paths.SSSCTL, "cache-remove", "-o", "--stop", "--start"]) -+ except Exception: -+ logger.info( -+ "An error occurred while removing SSSD's cache." -+ "Please remove the cache manually by executing " -+ "sssctl cache-remove -o.") - - if ipa_domain: - sssd_domain_ldb = "cache_" + ipa_domain + ".ldb" -@@ -3354,7 +3355,8 @@ def uninstall(options): - - # SSSD was not installed before our installation, and no other domains - # than IPA are configured in sssd.conf - make sure config file is removed -- elif not was_sssd_installed and not was_sssd_configured: -+ elif not was_sssd_installed and not was_sssd_configured \ -+ and os.path.exists(paths.SSSD_CONF): - try: - os.rename(paths.SSSD_CONF, paths.SSSD_CONF_DELETED) - except OSError: -diff --git a/ipalib/util.py b/ipalib/util.py -index 3e8fab49d6..68857baec7 100644 ---- a/ipalib/util.py -+++ b/ipalib/util.py -@@ -1125,11 +1125,14 @@ def ensure_krbcanonicalname_set(ldap, entry_attrs): - def check_client_configuration(): - """ - Check if IPA client is configured on the system. -+ -+ Hardcode return code to avoid recursive imports - """ - if (not os.path.isfile(paths.IPA_DEFAULT_CONF) or - not os.path.isdir(paths.IPA_CLIENT_SYSRESTORE) or - not os.listdir(paths.IPA_CLIENT_SYSRESTORE)): -- raise ScriptError('IPA client is not configured on this system') -+ raise ScriptError('IPA client is not configured on this system', -+ 2) # CLIENT_NOT_CONFIGURED - - - def check_principal_realm_in_trust_namespace(api_instance, *keys): -diff --git a/ipaplatform/redhat/authconfig.py b/ipaplatform/redhat/authconfig.py -index ab3775e9e9..e456d9ec6e 100644 ---- a/ipaplatform/redhat/authconfig.py -+++ b/ipaplatform/redhat/authconfig.py -@@ -141,7 +141,7 @@ def configure(self, sssd, mkhomedir, statestore, sudo=True): - def unconfigure( - self, fstore, statestore, was_sssd_installed, was_sssd_configured - ): -- if not statestore.has_state('authselect'): -+ if not statestore.has_state('authselect') and was_sssd_installed: - logger.warning( - "WARNING: Unable to revert to the pre-installation state " - "('authconfig' tool has been deprecated in favor of " -diff --git a/ipatests/test_integration/test_replica_promotion.py b/ipatests/test_integration/test_replica_promotion.py -index 265cbfb139..7803c34dcc 100644 ---- a/ipatests/test_integration/test_replica_promotion.py -+++ b/ipatests/test_integration/test_replica_promotion.py -@@ -207,6 +207,36 @@ def test_upcase_client_domain(self): - assert(result1.returncode == 0), ( - 'Failed to promote the client installed with the upcase domain name') - -+ def test_client_rollback(self): -+ """Test that bogus error msgs are not in output on rollback. -+ -+ FIXME: including in this suite to avoid setting up a -+ master just to test a client install failure. If -+ a pure client install suite is added this can be -+ moved. -+ -+ Ticket https://pagure.io/freeipa/issue/7729 -+ """ -+ client = self.replicas[0] -+ -+ # Cleanup previous run -+ client.run_command(['ipa-server-install', -+ '--uninstall', '-U'], raiseonerr=False) -+ -+ result = client.run_command(['ipa-client-install', '-U', -+ '--server', self.master.hostname, -+ '--domain', client.domain.name, -+ '-w', 'foo'], raiseonerr=False) -+ -+ assert(result.returncode == 1) -+ -+ assert("Unconfigured automount client failed" not in -+ result.stdout_text) -+ -+ assert("WARNING: Unable to revert" not in result.stdout_text) -+ -+ assert("An error occurred while removing SSSD" not in -+ result.stdout_text) - - class TestRenewalMaster(IntegrationTest): - -From db960e32f155412c34807e204add4858090d3e94 Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Tue, 16 Oct 2018 14:07:25 -0400 -Subject: [PATCH] Collect the client and server uninstall logs in tests - -When running the integration tests capture the uninstallation -logs as well as the installation logs. - -Reviewed-By: Christian Heimes ---- - ipatests/pytest_ipa/integration/tasks.py | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) - -diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py -index f0c61381b6..620ed28c96 100644 ---- a/ipatests/pytest_ipa/integration/tasks.py -+++ b/ipatests/pytest_ipa/integration/tasks.py -@@ -69,11 +69,12 @@ def setup_server_logs_collecting(host): - - # IPA install logs - host.collect_log(paths.IPASERVER_INSTALL_LOG) -+ host.collect_log(paths.IPASERVER_UNINSTALL_LOG) - host.collect_log(paths.IPACLIENT_INSTALL_LOG) -+ host.collect_log(paths.IPACLIENT_UNINSTALL_LOG) - host.collect_log(paths.IPAREPLICA_INSTALL_LOG) - host.collect_log(paths.IPAREPLICA_CONNCHECK_LOG) - host.collect_log(paths.IPAREPLICA_CA_INSTALL_LOG) -- host.collect_log(paths.IPACLIENT_INSTALL_LOG) - host.collect_log(paths.IPASERVER_KRA_INSTALL_LOG) - host.collect_log(paths.IPA_CUSTODIA_AUDIT_LOG) - diff --git a/SOURCES/0008-install-Add-missing-scripts-to-app_DATA_rhbz#1741170.patch b/SOURCES/0008-install-Add-missing-scripts-to-app_DATA_rhbz#1741170.patch new file mode 100644 index 0000000..4b5ecd9 --- /dev/null +++ b/SOURCES/0008-install-Add-missing-scripts-to-app_DATA_rhbz#1741170.patch @@ -0,0 +1,104 @@ +install/updates/30-ipservices.update from 39eaf2fa as it is not part of the +release tarball of 4.8.0 but needed for 27586cb7: + +commit 39eaf2fab5e27bd12edfb2a24c439a8ea5fb26f0 +Author: Christian Heimes +Date: Fri Dec 7 13:08:49 2018 +0100 + + Add index and container for RFC 2307 IP services + + IPA doesn't officially support RFC 2307 IP services. However SSSD has a + nsswitch plugin to provide service lookups. The subtree search for + (&(ipserviceport=$PORT)(ipserviceprotocol=$SRV)(objectclass=ipservice)) in + cn=accounts,$SUFFIX has caused performance issues on large + installations. + + This patch introduced a dedicated container + cn=ipservices,cn=accounts,$SUFFIX for IP services for future use or 3rd + party extensions. SSSD will be change its search base in an upcoming + release, too. + + A new ipServicePort index is added to optimize searches for an IP + service by port. There is no index on ipServiceProtocol because the index + would have poor selectivity. An ipService entry has either 'tcp' or 'udp' + as protocol. + + Fixes: https://pagure.io/freeipa/issue/7797 + See: https://pagure.io/freeipa/issue/7786 + Signed-off-by: Christian Heimes + Reviewed-By: Alexander Bokovoy + +diff --git a/install/updates/30-ipservices.update b/install/updates/30-ipservices.update +new file mode 100644 +index 000000000..01a6d52f8 +--- /dev/null ++++ b/install/updates/30-ipservices.update +@@ -0,0 +1,6 @@ ++# container for RFC 2307 IP services ++ ++dn: cn=ipservices,cn=accounts,$SUFFIX ++default: objectClass: top ++default: objectClass: nsContainer ++default: cn: ipservices +install/updates/75-user-trust-attributes.update from c18ee9b6 as it is not +part of the release tarball of 4.8.0 but needed for 27586cb7: + +commit c18ee9b641ddc1e6b52d0413caa1fb98ac13785d +Author: Tibor Dudlák +Date: Tue Apr 2 16:23:09 2019 +0200 + + Add SMB attributes for users + + SMB attributes are used by Samba domain controller when reporting + details about IPA users via LSA DCE RPC calls. + + Based on the initial work from the external plugin: + https://github.com/abbra/freeipa-user-trust-attributes + + Related: https://pagure.io/freeipa/issue/3999 + + Signed-off-by: Alexander Bokovoy + Signed-off-by: Tibor Dudlák + Reviewed-By: Alexander Bokovoy + Reviewed-By: Tibor Dudlak + +diff --git a/install/updates/75-user-trust-attributes.update b/install/updates/75-user-trust-attributes.update +new file mode 100644 +index 000000000..43bb40c7d +--- /dev/null ++++ b/install/updates/75-user-trust-attributes.update +@@ -0,0 +1,5 @@ ++# Add an explicit self-service ACI to allow writing to manage trust attributes ++# for the owner of the object ++dn: cn=users,cn=accounts,$SUFFIX ++add:aci:(targetattr = "ipantlogonscript || ipantprofilepath || ipanthomedirectory || ipanthomedirectorydrive")(version 3.0;acl "system:Allow trust agents to read user SMB attributes";allow (read) groupdn = "ldap:///cn=adtrust agents,cn=sysaccounts,cn=etc,$SUFFIX";) ++add:aci:(targetattr = "ipantlogonscript || ipantprofilepath || ipanthomedirectory || ipanthomedirectorydrive")(version 3.0;acl "selfservice:Users can manage their SMB attributes";allow (write) userdn = "ldap:///self";) +commit 27586cb7ae32af191cb8a3c36fc8856957300f08 +Author: Timo Aaltonen +Date: Fri Aug 9 23:03:25 2019 +0300 + + install: Add missing scripts to app_DATA. + + Signed-off-by: Timo Aaltonen + Reviewed-By: Alexander Bokovoy + +diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am +index bce8a56b1..68facbaf2 100644 +--- a/install/updates/Makefile.am ++++ b/install/updates/Makefile.am +@@ -30,6 +30,7 @@ app_DATA = \ + 21-ca_renewal_container.update \ + 21-certstore_container.update \ + 25-referint.update \ ++ 30-ipservices.update \ + 30-provisioning.update \ + 30-s4u2proxy.update \ + 37-locations.update \ +@@ -63,6 +64,7 @@ app_DATA = \ + 73-custodia.update \ + 73-winsync.update \ + 73-certmap.update \ ++ 75-user-trust-attributes.update \ + 80-schema_compat.update \ + 90-post_upgrade_plugins.update \ + $(NULL) diff --git a/SOURCES/0009-extdom-unify-error-code-handling-especially-LDAP_NO_SUCH_OBJECT_rhbz#1741530.patch b/SOURCES/0009-extdom-unify-error-code-handling-especially-LDAP_NO_SUCH_OBJECT_rhbz#1741530.patch new file mode 100644 index 0000000..237532d --- /dev/null +++ b/SOURCES/0009-extdom-unify-error-code-handling-especially-LDAP_NO_SUCH_OBJECT_rhbz#1741530.patch @@ -0,0 +1,345 @@ +From 3bb72545fc337564e0843b0c72906a9a1e3f6a06 Mon Sep 17 00:00:00 2001 +From: Sumit Bose +Date: Fri, 14 Jun 2019 11:13:54 +0200 +Subject: [PATCH] extdom: unify error code handling especially + LDAP_NO_SUCH_OBJECT + +A return code LDAP_NO_SUCH_OBJECT will tell SSSD on the IPA client to +remove the searched object from the cache. As a consequence +LDAP_NO_SUCH_OBJECT should only be returned if the object really does +not exists otherwise the data of existing objects might be removed form +the cache of the clients causing unexpected behaviour like +authentication errors. + +Currently some code-paths use LDAP_NO_SUCH_OBJECT as default error code. +With this patch LDAP_NO_SUCH_OBJECT is only returned if the related +lookup functions return ENOENT. Timeout related error code will lead to +LDAP_TIMELIMIT_EXCEEDED and LDAP_OPERATIONS_ERROR is used as default +error code. + +Fixes: https://pagure.io/freeipa/issue/8044 +Reviewed-By: Alexander Bokovoy +--- + .../ipa-extdom-extop/back_extdom_sss_idmap.c | 4 +- + .../ipa-extdom-extop/ipa_extdom_common.c | 77 ++++++++++++++----- + .../ipa-extdom-extop/ipa_extdom_extop.c | 2 + + 3 files changed, 61 insertions(+), 22 deletions(-) + +diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_sss_idmap.c b/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_sss_idmap.c +index ef552a9a37..163e8e1371 100644 +--- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_sss_idmap.c ++++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_sss_idmap.c +@@ -62,10 +62,10 @@ static enum nss_status __convert_sss_nss2nss_status(int errcode) { + return NSS_STATUS_SUCCESS; + case ENOENT: + return NSS_STATUS_NOTFOUND; +- case ETIME: +- /* fall-through */ + case ERANGE: + return NSS_STATUS_TRYAGAIN; ++ case ETIME: ++ /* fall-through */ + case ETIMEDOUT: + /* fall-through */ + default: +diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c +index 525487c9e4..65c723ce65 100644 +--- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c ++++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c +@@ -523,7 +523,7 @@ int pack_ber_user(struct ipa_extdom_ctx *ctx, + if (strcasecmp(locat+1, domain_name) == 0 ) { + locat[0] = '\0'; + } else { +- ret = LDAP_NO_SUCH_OBJECT; ++ ret = LDAP_INVALID_SYNTAX; + goto done; + } + } +@@ -568,10 +568,12 @@ int pack_ber_user(struct ipa_extdom_ctx *ctx, + ret = getgrgid_r_wrapper(ctx, + groups[c], &grp, &buf, &buf_len); + if (ret != 0) { +- if (ret == ENOMEM || ret == ERANGE) { +- ret = LDAP_OPERATIONS_ERROR; +- } else { ++ if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; ++ } else { ++ ret = LDAP_OPERATIONS_ERROR; + } + goto done; + } +@@ -634,7 +636,7 @@ int pack_ber_group(enum response_types response_type, + if (strcasecmp(locat+1, domain_name) == 0 ) { + locat[0] = '\0'; + } else { +- ret = LDAP_NO_SUCH_OBJECT; ++ ret = LDAP_INVALID_SYNTAX; + goto done; + } + } +@@ -836,6 +838,8 @@ static int handle_uid_request(struct ipa_extdom_ctx *ctx, + || id_type == SSS_ID_TYPE_BOTH)) { + if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT || ret == ETIME) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; + } else { + set_err_msg(req, "Failed to lookup SID by UID"); + ret = LDAP_OPERATIONS_ERROR; +@@ -847,10 +851,12 @@ static int handle_uid_request(struct ipa_extdom_ctx *ctx, + } else { + ret = getpwuid_r_wrapper(ctx, uid, &pwd, &buf, &buf_len); + if (ret != 0) { +- if (ret == ENOMEM || ret == ERANGE) { +- ret = LDAP_OPERATIONS_ERROR; +- } else { ++ if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; ++ } else { ++ ret = LDAP_OPERATIONS_ERROR; + } + goto done; + } +@@ -862,6 +868,8 @@ static int handle_uid_request(struct ipa_extdom_ctx *ctx, + set_err_msg(req, "Failed to read original data"); + if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT || ret == ETIME) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; + } else { + ret = LDAP_OPERATIONS_ERROR; + } +@@ -907,6 +915,8 @@ static int handle_gid_request(struct ipa_extdom_ctx *ctx, + if (ret != 0 || id_type != SSS_ID_TYPE_GID) { + if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT || ret == ETIME) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; + } else { + set_err_msg(req, "Failed to lookup SID by GID"); + ret = LDAP_OPERATIONS_ERROR; +@@ -918,10 +928,12 @@ static int handle_gid_request(struct ipa_extdom_ctx *ctx, + } else { + ret = getgrgid_r_wrapper(ctx, gid, &grp, &buf, &buf_len); + if (ret != 0) { +- if (ret == ENOMEM || ret == ERANGE) { +- ret = LDAP_OPERATIONS_ERROR; +- } else { ++ if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; ++ } else { ++ ret = LDAP_OPERATIONS_ERROR; + } + goto done; + } +@@ -933,6 +945,8 @@ static int handle_gid_request(struct ipa_extdom_ctx *ctx, + set_err_msg(req, "Failed to read original data"); + if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT || ret == ETIME) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; + } else { + ret = LDAP_OPERATIONS_ERROR; + } +@@ -976,6 +990,8 @@ static int handle_cert_request(struct ipa_extdom_ctx *ctx, + if (ret != 0) { + if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT || ret == ETIME) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; + } else { + set_err_msg(req, "Failed to lookup name by certificate"); + ret = LDAP_OPERATIONS_ERROR; +@@ -1020,6 +1036,8 @@ static int handle_sid_request(struct ipa_extdom_ctx *ctx, + if (ret != 0) { + if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT || ret == ETIME) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; + } else { + set_err_msg(req, "Failed to lookup name by SID"); + ret = LDAP_OPERATIONS_ERROR; +@@ -1057,10 +1075,12 @@ static int handle_sid_request(struct ipa_extdom_ctx *ctx, + case SSS_ID_TYPE_BOTH: + ret = getpwnam_r_wrapper(ctx, fq_name, &pwd, &buf, &buf_len); + if (ret != 0) { +- if (ret == ENOMEM || ret == ERANGE) { +- ret = LDAP_OPERATIONS_ERROR; +- } else { ++ if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; ++ } else { ++ ret = LDAP_OPERATIONS_ERROR; + } + goto done; + } +@@ -1072,6 +1092,8 @@ static int handle_sid_request(struct ipa_extdom_ctx *ctx, + set_err_msg(req, "Failed to read original data"); + if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT || ret == ETIME) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; + } else { + ret = LDAP_OPERATIONS_ERROR; + } +@@ -1089,10 +1111,12 @@ static int handle_sid_request(struct ipa_extdom_ctx *ctx, + case SSS_ID_TYPE_GID: + ret = getgrnam_r_wrapper(ctx, fq_name, &grp, &buf, &buf_len); + if (ret != 0) { +- if (ret == ENOMEM || ret == ERANGE) { +- ret = LDAP_OPERATIONS_ERROR; +- } else { ++ if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; ++ } else { ++ ret = LDAP_OPERATIONS_ERROR; + } + goto done; + } +@@ -1104,6 +1128,8 @@ static int handle_sid_request(struct ipa_extdom_ctx *ctx, + set_err_msg(req, "Failed to read original data"); + if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT || ret == ETIME) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; + } else { + ret = LDAP_OPERATIONS_ERROR; + } +@@ -1167,6 +1193,8 @@ static int handle_name_request(struct ipa_extdom_ctx *ctx, + if (ret != 0) { + if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT || ret == ETIME) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; + } else { + set_err_msg(req, "Failed to lookup SID by name"); + ret = LDAP_OPERATIONS_ERROR; +@@ -1190,6 +1218,8 @@ static int handle_name_request(struct ipa_extdom_ctx *ctx, + set_err_msg(req, "Failed to read original data"); + if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT || ret == ETIME) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; + } else { + ret = LDAP_OPERATIONS_ERROR; + } +@@ -1205,6 +1235,9 @@ static int handle_name_request(struct ipa_extdom_ctx *ctx, + } else if (ret == ENOMEM || ret == ERANGE) { + ret = LDAP_OPERATIONS_ERROR; + goto done; ++ } else if (ret == ETIMEDOUT) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; ++ goto done; + } else { /* no user entry found */ + /* according to the getpwnam() man page there are a couple of + * error codes which can indicate that the user was not found. To +@@ -1212,10 +1245,12 @@ static int handle_name_request(struct ipa_extdom_ctx *ctx, + * errors. */ + ret = getgrnam_r_wrapper(ctx, fq_name, &grp, &buf, &buf_len); + if (ret != 0) { +- if (ret == ENOMEM || ret == ERANGE) { +- ret = LDAP_OPERATIONS_ERROR; +- } else { ++ if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; ++ } else { ++ ret = LDAP_OPERATIONS_ERROR; + } + goto done; + } +@@ -1226,6 +1261,8 @@ static int handle_name_request(struct ipa_extdom_ctx *ctx, + || id_type == SSS_ID_TYPE_BOTH)) { + if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == ETIMEDOUT || ret == ETIME) { ++ ret = LDAP_TIMELIMIT_EXCEEDED; + } else { + set_err_msg(req, "Failed to read original data"); + ret = LDAP_OPERATIONS_ERROR; +diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c +index 10d3f86eba..48fcecc1ee 100644 +--- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c ++++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c +@@ -242,6 +242,8 @@ static int ipa_extdom_extop(Slapi_PBlock *pb) + if (ret != LDAP_SUCCESS) { + if (ret == LDAP_NO_SUCH_OBJECT) { + rc = LDAP_NO_SUCH_OBJECT; ++ } else if (ret == LDAP_TIMELIMIT_EXCEEDED) { ++ rc = LDAP_TIMELIMIT_EXCEEDED; + } else { + rc = LDAP_OPERATIONS_ERROR; + err_msg = "Failed to handle the request.\n"; +From 0ead6f59732e8b3370c5d8d05acd29f2d56c52bb Mon Sep 17 00:00:00 2001 +From: Alexander Bokovoy +Date: Mon, 19 Aug 2019 10:15:50 +0300 +Subject: [PATCH] ipa-extdom-extop: test timed out getgrgid_r + +Simulate getgrgid_r() timeout when packing list of groups user is a +member of in pack_ber_user(). + +Related: https://pagure.io/freeipa/issue/8044 +Reviewed-By: Alexander Bokovoy +--- + .../ipa_extdom_cmocka_tests.c | 29 +++++++++++++++++++ + 1 file changed, 29 insertions(+) + +diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_cmocka_tests.c b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_cmocka_tests.c +index 29699cfa39..1fa4c6af82 100644 +--- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_cmocka_tests.c ++++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_cmocka_tests.c +@@ -493,6 +493,34 @@ void test_set_err_msg(void **state) + #define TEST_SID "S-1-2-3-4" + #define TEST_DOMAIN_NAME "DOMAIN" + ++/* Always time out for test */ ++static ++enum nss_status getgrgid_r_timeout(gid_t gid, struct group *result, ++ char *buffer, size_t buflen, int *errnop) { ++ return NSS_STATUS_UNAVAIL; ++} ++ ++void test_pack_ber_user_timeout(void **state) ++{ ++ int ret; ++ struct berval *resp_val = NULL; ++ struct test_data *test_data; ++ enum nss_status (*oldgetgrgid_r)(gid_t gid, struct group *result, ++ char *buffer, size_t buflen, int *errnop); ++ ++ test_data = (struct test_data *) *state; ++ ++ oldgetgrgid_r = test_data->ctx->nss_ctx->getgrgid_r; ++ test_data->ctx->nss_ctx->getgrgid_r = getgrgid_r_timeout; ++ ++ ret = pack_ber_user(test_data->ctx, RESP_USER_GROUPLIST, ++ TEST_DOMAIN_NAME, "member001", 12345, 54321, ++ "gecos", "homedir", "shell", NULL, &resp_val); ++ test_data->ctx->nss_ctx->getgrgid_r = oldgetgrgid_r; ++ assert_int_equal(ret, LDAP_TIMELIMIT_EXCEEDED); ++ ber_bvfree(resp_val); ++} ++ + char res_sid[] = {0x30, 0x0e, 0x0a, 0x01, 0x01, 0x04, 0x09, 0x53, 0x2d, 0x31, \ + 0x2d, 0x32, 0x2d, 0x33, 0x2d, 0x34}; + char res_nam[] = {0x30, 0x13, 0x0a, 0x01, 0x02, 0x30, 0x0e, 0x04, 0x06, 0x44, \ +@@ -614,6 +642,7 @@ void test_decode(void **state) + int main(int argc, const char *argv[]) + { + const struct CMUnitTest tests[] = { ++ cmocka_unit_test(test_pack_ber_user_timeout), + cmocka_unit_test(test_getpwnam_r_wrapper), + cmocka_unit_test(test_getpwuid_r_wrapper), + cmocka_unit_test(test_getgrnam_r_wrapper), diff --git a/SOURCES/0009-ipa-advise_update_url_of_cacerdir_rehash_tool_9cfd07e_rhbz#1658287.patch b/SOURCES/0009-ipa-advise_update_url_of_cacerdir_rehash_tool_9cfd07e_rhbz#1658287.patch deleted file mode 100644 index 40b5db5..0000000 --- a/SOURCES/0009-ipa-advise_update_url_of_cacerdir_rehash_tool_9cfd07e_rhbz#1658287.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 9cfd07e87964f37465dd699a50444e6953291f59 Mon Sep 17 00:00:00 2001 -From: Petr Vobornik -Date: Oct 23 2018 14:48:53 +0000 -Subject: ipa-advise: update url of cacerdir_rehash tool - - -On legacy systems which don't have cacerdir_rehash tool (provided by authconfig) -the generated advise script downloads this tool from project page and uses it. - -After decommision of Fedorahosted and move of authconfig project to Pagure, -this url was not updated in FreeIPA project. - -This patch updates the url. - -https://pagure.io/freeipa/issue/7731 - -Signed-off-by: Petr Vobornik -Reviewed-By: Christian Heimes - ---- - -diff --git a/ipaserver/advise/plugins/legacy_clients.py b/ipaserver/advise/plugins/legacy_clients.py -index 7916965..2a56922 100644 ---- a/ipaserver/advise/plugins/legacy_clients.py -+++ b/ipaserver/advise/plugins/legacy_clients.py -@@ -28,6 +28,9 @@ from ipapython.ipautil import template_file - - register = Registry() - -+CACERTDIR_REHASH_URL = ('https://pagure.io/authconfig/raw/master/f/' -+ 'cacertdir_rehash') -+ - - class config_base_legacy_client(Advice): - def get_uri_and_base(self): -@@ -50,8 +53,6 @@ class config_base_legacy_client(Advice): - 'location. If this value is different on your system ' - 'the script needs to be modified accordingly.\n') - -- cacertdir_rehash = ('https://fedorahosted.org/authconfig/browser/' -- 'cacertdir_rehash?format=txt') - self.log.comment('Download the CA certificate of the IPA server') - self.log.command('mkdir -p -m 755 /etc/openldap/cacerts') - self.log.command('curl http://%s/ipa/config/ca.crt -o ' -@@ -60,7 +61,8 @@ class config_base_legacy_client(Advice): - self.log.comment('Generate hashes for the openldap library') - self.log.command('command -v cacertdir_rehash') - self.log.command('if [ $? -ne 0 ] ; then') -- self.log.command(' curl "%s" -o cacertdir_rehash ;' % cacertdir_rehash) -+ self.log.command(' curl "%s" -o cacertdir_rehash ;' % -+ CACERTDIR_REHASH_URL) - self.log.command(' chmod 755 ./cacertdir_rehash ;') - self.log.command(' ./cacertdir_rehash /etc/openldap/cacerts/ ;') - self.log.command('else') - diff --git a/SOURCES/0010-Fix-automount-behavior-with-authselect_rhbz#1740167.patch b/SOURCES/0010-Fix-automount-behavior-with-authselect_rhbz#1740167.patch new file mode 100644 index 0000000..60e22bb --- /dev/null +++ b/SOURCES/0010-Fix-automount-behavior-with-authselect_rhbz#1740167.patch @@ -0,0 +1,2004 @@ +From abea98a9b918c0771ad10b314238b32c570f0372 Mon Sep 17 00:00:00 2001 +From: François Cami +Date: Aug 29 2019 06:45:12 +0000 +Subject: ipatests: check that ipa-client-automount restores nsswitch.conf at uninstall time + + +Check that using ipa-client-install, ipa-client-automount --no-ssd, then uninstalling +both properly restores nsswitch.conf sequentially. + +Related-to:: https://pagure.io/freeipa/issue/8038 +Signed-off-by: François Cami +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +Reviewed-By: Rob Critenden +Reviewed-By: François Cami + +--- + +#diff --git a/ipatests/prci_definitions/nightly_ipa-4-8.yaml b/ipatests/prci_definitions/nightly_ipa-4-8.yaml +#index ef5d2c6..f39e4b4 100644 +#--- a/ipatests/prci_definitions/nightly_ipa-4-8.yaml +#+++ b/ipatests/prci_definitions/nightly_ipa-4-8.yaml +#@@ -1257,6 +1257,18 @@ jobs: +# timeout: 9000 +# topology: *master_3client +# +#+ fedora-30/nfs_nsswitch_restore: +#+ requires: [fedora-30/build] +#+ priority: 50 +#+ job: +#+ class: RunPytest +#+ args: +#+ build_url: '{fedora-30/build_url}' +#+ test_suite: test_integration/test_nfs.py::TestIpaClientAutomountFileRestore +#+ template: *ci-master-f30 +#+ timeout: 3600 +#+ topology: *master_3client +#+ +# fedora-30/mask: +# requires: [fedora-30/build] +# priority: 50 +diff --git a/ipatests/test_integration/test_nfs.py b/ipatests/test_integration/test_nfs.py +index adfc19f..0e1ef6a 100644 +--- a/ipatests/test_integration/test_nfs.py ++++ b/ipatests/test_integration/test_nfs.py +@@ -15,6 +15,7 @@ + + from __future__ import absolute_import + ++import pytest + import os + import re + import time +@@ -258,3 +259,74 @@ class TestNFS(IntegrationTest): + time.sleep(WAIT_AFTER_UNINSTALL) + + self.cleanup() ++ ++ ++class TestIpaClientAutomountFileRestore(IntegrationTest): ++ ++ num_clients = 1 ++ topology = 'line' ++ ++ @classmethod ++ def install(cls, mh): ++ tasks.install_master(cls.master, setup_dns=True) ++ ++ def teardown_method(self, method): ++ tasks.uninstall_client(self.clients[0]) ++ ++ def nsswitch_backup_restore( ++ self, ++ no_sssd=False, ++ ): ++ ++ # In order to get a more pure sum, one that ignores the Generated ++ # header and any white space we have to do a bit of work... ++ sha256nsswitch_cmd = \ ++ 'egrep -v "Generated|^$" /etc/nsswitch.conf | sed "s/\\s//g" ' \ ++ '| sort | sha256sum' ++ ++ cmd = self.clients[0].run_command(sha256nsswitch_cmd) ++ orig_sha256 = cmd.stdout_text ++ ++ grep_automount_command = \ ++ "grep automount /etc/nsswitch.conf | cut -d: -f2" ++ ++ tasks.install_client(self.master, self.clients[0]) ++ cmd = self.clients[0].run_command(grep_automount_command) ++ after_ipa_client_install = cmd.stdout_text.split() ++ ++ if no_sssd: ++ ipa_client_automount_command = [ ++ "ipa-client-automount", "--no-sssd", "-U" ++ ] ++ else: ++ ipa_client_automount_command = [ ++ "ipa-client-automount", "-U" ++ ] ++ self.clients[0].run_command(ipa_client_automount_command) ++ cmd = self.clients[0].run_command(grep_automount_command) ++ after_ipa_client_automount = cmd.stdout_text.split() ++ if no_sssd: ++ assert after_ipa_client_automount == ['files', 'ldap'] ++ else: ++ assert after_ipa_client_automount == ['sss', 'files'] ++ ++ cmd = self.clients[0].run_command(grep_automount_command) ++ assert cmd.stdout_text.split() == after_ipa_client_automount ++ ++ self.clients[0].run_command([ ++ "ipa-client-automount", "--uninstall", "-U" ++ ]) ++ ++ cmd = self.clients[0].run_command(grep_automount_command) ++ assert cmd.stdout_text.split() == after_ipa_client_install ++ ++ tasks.uninstall_client(self.clients[0]) ++ cmd = self.clients[0].run_command(sha256nsswitch_cmd) ++ assert cmd.stdout_text == orig_sha256 ++ ++ @pytest.mark.xfail(reason='freeipa ticket 8054', strict=True) ++ def test_nsswitch_backup_restore_sssd(self): ++ self.nsswitch_backup_restore() ++ ++ def test_nsswitch_backup_restore_no_sssd(self): ++ self.nsswitch_backup_restore(no_sssd=True) + +From 2f0afeda6e66fcca5c184a4036112fcd315f2f6e Mon Sep 17 00:00:00 2001 +From: François Cami +Date: Aug 29 2019 06:45:12 +0000 +Subject: ipa-client-automount: always restore nsswitch.conf at uninstall time + + +ipa-client-automount used to only restore nsswitch.conf when sssd was not +used. However authselect's default profile is now sssd so always restore +nsswitch.conf's automount configuration to 'files sssd'. +Note that the behavior seen before commit: +a0e846f56c8de3b549d1d284087131da13135e34 +would always restore nsswitch.conf to the previous state which in some cases +was wrong. + +Fixes: https://pagure.io/freeipa/issue/8038 +Signed-off-by: François Cami +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +Reviewed-By: Rob Critenden +Reviewed-By: François Cami + +--- + +diff --git a/ipaclient/install/ipa_client_automount.py b/ipaclient/install/ipa_client_automount.py +index fa07598..a1dc2a1 100644 +--- a/ipaclient/install/ipa_client_automount.py ++++ b/ipaclient/install/ipa_client_automount.py +@@ -177,18 +177,30 @@ def configure_xml(fstore): + print("Configured %s" % authconf) + + +-def configure_nsswitch(fstore, options): ++def configure_nsswitch(statestore, options): + """ +- Point automount to ldap in nsswitch.conf. This function is for non-SSSD +- setups only ++ Point automount to ldap in nsswitch.conf. ++ This function is for non-SSSD setups only. + """ +- fstore.backup_file(paths.NSSWITCH_CONF) +- + conf = ipachangeconf.IPAChangeConf("IPA Installer") + conf.setOptionAssignment(':') + +- nss_value = ' files ldap' ++ with open(paths.NSSWITCH_CONF, 'r') as f: ++ current_opts = conf.parse(f) ++ current_nss_value = conf.findOpts( ++ current_opts, name='automount', type='option' ++ )[1] ++ if current_nss_value is None: ++ # no automount database present ++ current_nss_value = False # None cannot be backed up ++ else: ++ current_nss_value = current_nss_value['value'] ++ statestore.backup_state( ++ 'ipa-client-automount-nsswitch', 'previous-automount', ++ current_nss_value ++ ) + ++ nss_value = ' files ldap' + opts = [ + { + 'name': 'automount', +@@ -198,7 +210,6 @@ def configure_nsswitch(fstore, options): + }, + {'name': 'empty', 'type': 'empty'}, + ] +- + conf.changeConf(paths.NSSWITCH_CONF, opts) + + print("Configured %s" % paths.NSSWITCH_CONF) +@@ -322,19 +333,47 @@ def configure_autofs_common(fstore, statestore, options): + def uninstall(fstore, statestore): + RESTORE_FILES = [ + paths.SYSCONFIG_AUTOFS, +- paths.NSSWITCH_CONF, + paths.AUTOFS_LDAP_AUTH_CONF, + paths.SYSCONFIG_NFS, + paths.IDMAPD_CONF, + ] + STATES = ['autofs', 'rpcidmapd', 'rpcgssd'] + +- # automount only touches /etc/nsswitch.conf if LDAP is +- # used. Don't restore it otherwise. +- if statestore.get_state('authconfig', 'sssd') or ( +- statestore.get_state('authselect', 'profile') == 'sssd' +- ): +- RESTORE_FILES.remove(paths.NSSWITCH_CONF) ++ if statestore.get_state( ++ 'ipa-client-automount-nsswitch', 'previous-automount' ++ ) is False: ++ # Previous nsswitch.conf had no automount database configured ++ # so remove it. ++ conf = ipachangeconf.IPAChangeConf("IPA automount installer") ++ conf.setOptionAssignment(':') ++ changes = [conf.rmOption('automount')] ++ conf.changeConf(paths.NSSWITCH_CONF, changes) ++ tasks.restore_context(paths.NSSWITCH_CONF) ++ statestore.delete_state( ++ 'ipa-client-automount-nsswitch', 'previous-automount' ++ ) ++ elif statestore.get_state( ++ 'ipa-client-automount-nsswitch', 'previous-automount' ++ ) is not None: ++ nss_value = statestore.get_state( ++ 'ipa-client-automount-nsswitch', 'previous-automount' ++ ) ++ opts = [ ++ { ++ 'name': 'automount', ++ 'type': 'option', ++ 'action': 'set', ++ 'value': nss_value, ++ }, ++ {'name': 'empty', 'type': 'empty'}, ++ ] ++ conf = ipachangeconf.IPAChangeConf("IPA automount installer") ++ conf.setOptionAssignment(':') ++ conf.changeConf(paths.NSSWITCH_CONF, opts) ++ tasks.restore_context(paths.NSSWITCH_CONF) ++ statestore.delete_state( ++ 'ipa-client-automount-nsswitch', 'previous-automount' ++ ) + + if not any(fstore.has_file(f) for f in RESTORE_FILES) or not any( + statestore.has_state(s) for s in STATES +@@ -588,7 +627,7 @@ def configure_automount(): + + try: + if not options.sssd: +- configure_nsswitch(fstore, options) ++ configure_nsswitch(statestore, options) + configure_nfs(fstore, statestore, options) + if options.sssd: + configure_autofs_sssd(fstore, statestore, autodiscover, options) + +From 6e92776bfc199e9ca92e11ef3315dcecad3c9307 Mon Sep 17 00:00:00 2001 +From: Rob Critenden +Date: Aug 29 2019 06:45:12 +0000 +Subject: Move ipachangeconf from ipaclient.install to ipapython + + +This will let us call it from ipaplatform. + +Mark the original location as deprecated. + +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +Reviewed-By: Rob Critenden +Reviewed-By: François Cami + +--- + +diff --git a/install/tools/ipa-replica-conncheck.in b/install/tools/ipa-replica-conncheck.in +index 9208076..b22db11 100644 +--- a/install/tools/ipa-replica-conncheck.in ++++ b/install/tools/ipa-replica-conncheck.in +@@ -22,7 +22,7 @@ from __future__ import print_function + + import logging + +-import ipaclient.install.ipachangeconf ++from ipapython import ipachangeconf + from ipapython.config import IPAOptionParser + from ipapython.dn import DN + from ipapython import version +@@ -229,7 +229,7 @@ def sigterm_handler(signum, frame): + + def configure_krb5_conf(realm, kdc, filename): + +- krbconf = ipaclient.install.ipachangeconf.IPAChangeConf("IPA Installer") ++ krbconf = ipachangeconf.IPAChangeConf("IPA Installer") + krbconf.setOptionAssignment((" = ", " ")) + krbconf.setSectionNameDelimiters(("[","]")) + krbconf.setSubSectionDelimiters(("{","}")) +diff --git a/ipaclient/install/ipachangeconf.py b/ipaclient/install/ipachangeconf.py +index a13e0ea..c51e42e 100644 +--- a/ipaclient/install/ipachangeconf.py ++++ b/ipaclient/install/ipachangeconf.py +@@ -18,566 +18,18 @@ + # along with this program. If not, see . + # + +-import fcntl +-import logging +-import os +-import shutil ++import warnings ++from ipapython.ipachangeconf import IPAChangeConf as realIPAChangeConf + +-import six + +-if six.PY3: +- unicode = str ++class IPAChangeConf(realIPAChangeConf): ++ """Advertise the old name""" + +-logger = logging.getLogger(__name__) +- +-def openLocked(filename, perms): +- fd = -1 +- try: +- fd = os.open(filename, os.O_RDWR | os.O_CREAT, perms) +- +- fcntl.lockf(fd, fcntl.LOCK_EX) +- except OSError as e: +- if fd != -1: +- try: +- os.close(fd) +- except OSError: +- pass +- raise IOError(e.errno, e.strerror) +- return os.fdopen(fd, "r+") +- +- +- #TODO: add subsection as a concept +- # (ex. REALM.NAME = { foo = x bar = y } ) +- #TODO: put section delimiters as separating element of the list +- # so that we can process multiple sections in one go +- #TODO: add a comment all but provided options as a section option +-class IPAChangeConf: + def __init__(self, name): +- self.progname = name +- self.indent = ("", "", "") +- self.assign = (" = ", "=") +- self.dassign = self.assign[0] +- self.comment = ("#",) +- self.dcomment = self.comment[0] +- self.eol = ("\n",) +- self.deol = self.eol[0] +- self.sectnamdel = ("[", "]") +- self.subsectdel = ("{", "}") +- self.case_insensitive_sections = True +- +- def setProgName(self, name): +- self.progname = name +- +- def setIndent(self, indent): +- if type(indent) is tuple: +- self.indent = indent +- elif type(indent) is str: +- self.indent = (indent, ) +- else: +- raise ValueError('Indent must be a list of strings') +- +- def setOptionAssignment(self, assign): +- if type(assign) is tuple: +- self.assign = assign +- else: +- self.assign = (assign, ) +- self.dassign = self.assign[0] +- +- def setCommentPrefix(self, comment): +- if type(comment) is tuple: +- self.comment = comment +- else: +- self.comment = (comment, ) +- self.dcomment = self.comment[0] +- +- def setEndLine(self, eol): +- if type(eol) is tuple: +- self.eol = eol +- else: +- self.eol = (eol, ) +- self.deol = self.eol[0] +- +- def setSectionNameDelimiters(self, delims): +- self.sectnamdel = delims +- +- def setSubSectionDelimiters(self, delims): +- self.subsectdel = delims +- +- def matchComment(self, line): +- for v in self.comment: +- if line.lstrip().startswith(v): +- return line.lstrip()[len(v):] +- return False +- +- def matchEmpty(self, line): +- if line.strip() == "": +- return True +- return False +- +- def matchSection(self, line): +- cl = "".join(line.strip().split()) +- cl = cl.lower() if self.case_insensitive_sections else cl +- +- if len(self.sectnamdel) != 2: +- return False +- if not cl.startswith(self.sectnamdel[0]): +- return False +- if not cl.endswith(self.sectnamdel[1]): +- return False +- return cl[len(self.sectnamdel[0]):-len(self.sectnamdel[1])] +- +- def matchSubSection(self, line): +- if self.matchComment(line): +- return False +- +- parts = line.split(self.dassign, 1) +- if len(parts) < 2: +- return False +- +- if parts[1].strip() == self.subsectdel[0]: +- return parts[0].strip() +- +- return False +- +- def matchSubSectionEnd(self, line): +- if self.matchComment(line): +- return False +- +- if line.strip() == self.subsectdel[1]: +- return True +- +- return False +- +- def getSectionLine(self, section): +- if len(self.sectnamdel) != 2: +- return section +- return self._dump_line(self.sectnamdel[0], +- section, +- self.sectnamdel[1], +- self.deol) +- +- def _dump_line(self, *args): +- return u"".join(unicode(x) for x in args) +- +- def dump(self, options, level=0): +- output = [] +- if level >= len(self.indent): +- level = len(self.indent) - 1 +- +- for o in options: +- if o['type'] == "section": +- output.append(self._dump_line(self.sectnamdel[0], +- o['name'], +- self.sectnamdel[1])) +- output.append(self.dump(o['value'], (level + 1))) +- continue +- if o['type'] == "subsection": +- output.append(self._dump_line(self.indent[level], +- o['name'], +- self.dassign, +- self.subsectdel[0])) +- output.append(self.dump(o['value'], (level + 1))) +- output.append(self._dump_line(self.indent[level], +- self.subsectdel[1])) +- continue +- if o['type'] == "option": +- delim = o.get('delim', self.dassign) +- if delim not in self.assign: +- raise ValueError('Unknown delim "%s" must be one of "%s"' % (delim, " ".join([d for d in self.assign]))) +- output.append(self._dump_line(self.indent[level], +- o['name'], +- delim, +- o['value'])) +- continue +- if o['type'] == "comment": +- output.append(self._dump_line(self.dcomment, o['value'])) +- continue +- if o['type'] == "empty": +- output.append('') +- continue +- raise SyntaxError('Unknown type: [%s]' % o['type']) +- +- # append an empty string to the output so that we add eol to the end +- # of the file contents in a single join() +- output.append('') +- return self.deol.join(output) +- +- def parseLine(self, line): +- +- if self.matchEmpty(line): +- return {'name': 'empty', 'type': 'empty'} +- +- value = self.matchComment(line) +- if value: +- return {'name': 'comment', +- 'type': 'comment', +- 'value': value.rstrip()} # pylint: disable=E1103 +- +- o = dict() +- parts = line.split(self.dassign, 1) +- if len(parts) < 2: +- # The default assign didn't match, try the non-default +- for d in self.assign[1:]: +- parts = line.split(d, 1) +- if len(parts) >= 2: +- o['delim'] = d +- break +- +- if 'delim' not in o: +- raise SyntaxError('Syntax Error: Unknown line format') +- +- o.update({'name':parts[0].strip(), 'type':'option', 'value':parts[1].rstrip()}) +- return o +- +- def findOpts(self, opts, type, name, exclude_sections=False): +- +- num = 0 +- for o in opts: +- if o['type'] == type and o['name'] == name: +- return (num, o) +- if exclude_sections and (o['type'] == "section" or +- o['type'] == "subsection"): +- return (num, None) +- num += 1 +- return (num, None) +- +- def commentOpts(self, inopts, level=0): +- +- opts = [] +- +- if level >= len(self.indent): +- level = len(self.indent) - 1 +- +- for o in inopts: +- if o['type'] == 'section': +- no = self.commentOpts(o['value'], (level + 1)) +- val = self._dump_line(self.dcomment, +- self.sectnamdel[0], +- o['name'], +- self.sectnamdel[1]) +- opts.append({'name': 'comment', +- 'type': 'comment', +- 'value': val}) +- for n in no: +- opts.append(n) +- continue +- if o['type'] == 'subsection': +- no = self.commentOpts(o['value'], (level + 1)) +- val = self._dump_line(self.indent[level], +- o['name'], +- self.dassign, +- self.subsectdel[0]) +- opts.append({'name': 'comment', +- 'type': 'comment', +- 'value': val}) +- opts.extend(no) +- val = self._dump_line(self.indent[level], self.subsectdel[1]) +- opts.append({'name': 'comment', +- 'type': 'comment', +- 'value': val}) +- continue +- if o['type'] == 'option': +- delim = o.get('delim', self.dassign) +- if delim not in self.assign: +- val = self._dump_line(self.indent[level], +- o['name'], +- delim, +- o['value']) +- opts.append({'name':'comment', 'type':'comment', 'value':val}) +- continue +- if o['type'] == 'comment': +- opts.append(o) +- continue +- if o['type'] == 'empty': +- opts.append({'name': 'comment', +- 'type': 'comment', +- 'value': ''}) +- continue +- raise SyntaxError('Unknown type: [%s]' % o['type']) +- +- return opts +- +- def mergeOld(self, oldopts, newopts): +- +- opts = [] +- +- for o in oldopts: +- if o['type'] == "section" or o['type'] == "subsection": +- _num, no = self.findOpts(newopts, o['type'], o['name']) +- if not no: +- opts.append(o) +- continue +- if no['action'] == "set": +- mo = self.mergeOld(o['value'], no['value']) +- opts.append({'name': o['name'], +- 'type': o['type'], +- 'value': mo}) +- continue +- if no['action'] == "comment": +- co = self.commentOpts(o['value']) +- for c in co: +- opts.append(c) +- continue +- if no['action'] == "remove": +- continue +- raise SyntaxError('Unknown action: [%s]' % no['action']) +- +- if o['type'] == "comment" or o['type'] == "empty": +- opts.append(o) +- continue +- +- if o['type'] == "option": +- _num, no = self.findOpts(newopts, 'option', o['name'], True) +- if not no: +- opts.append(o) +- continue +- if no['action'] == 'comment' or no['action'] == 'remove': +- if (no['value'] is not None and +- o['value'] is not no['value']): +- opts.append(o) +- continue +- if no['action'] == 'comment': +- value = self._dump_line(self.dcomment, +- o['name'], +- self.dassign, +- o['value']) +- opts.append({'name': 'comment', +- 'type': 'comment', +- 'value': value}) +- continue +- if no['action'] == 'set': +- opts.append(no) +- continue +- if no['action'] == 'addifnotset': +- opts.append({ +- 'name': 'comment', +- 'type': 'comment', +- 'value': self._dump_line( +- ' ', no['name'], ' modified by IPA' +- ), +- }) +- opts.append({'name': 'comment', 'type': 'comment', +- 'value': self._dump_line(no['name'], +- self.dassign, +- no['value'], +- )}) +- opts.append(o) +- continue +- raise SyntaxError('Unknown action: [%s]' % no['action']) +- +- raise SyntaxError('Unknown type: [%s]' % o['type']) +- +- return opts +- +- def mergeNew(self, opts, newopts): +- +- cline = 0 +- +- for no in newopts: +- +- if no['type'] == "section" or no['type'] == "subsection": +- (num, o) = self.findOpts(opts, no['type'], no['name']) +- if not o: +- if no['action'] == 'set': +- opts.append(no) +- continue +- if no['action'] == "set": +- self.mergeNew(o['value'], no['value']) +- continue +- cline = num + 1 +- continue +- +- if no['type'] == "option": +- (num, o) = self.findOpts(opts, no['type'], no['name'], True) +- if not o: +- if no['action'] == 'set' or no['action'] == 'addifnotset': +- opts.append(no) +- continue +- cline = num + 1 +- continue +- +- if no['type'] == "comment" or no['type'] == "empty": +- opts.insert(cline, no) +- cline += 1 +- continue +- +- raise SyntaxError('Unknown type: [%s]' % no['type']) +- +- def merge(self, oldopts, newopts): +- """ +- Uses a two pass strategy: +- First we create a new opts tree from oldopts removing/commenting +- the options as indicated by the contents of newopts +- Second we fill in the new opts tree with options as indicated +- in the newopts tree (this is becaus eentire (sub)sections may +- in the newopts tree (this is becaus entire (sub)sections may +- exist in the newopts that do not exist in oldopts) +- """ +- opts = self.mergeOld(oldopts, newopts) +- self.mergeNew(opts, newopts) +- return opts +- +- #TODO: Make parse() recursive? +- def parse(self, f): +- +- opts = [] +- sectopts = [] +- section = None +- subsectopts = [] +- subsection = None +- curopts = opts +- fatheropts = opts +- +- # Read in the old file. +- for line in f: +- +- # It's a section start. +- value = self.matchSection(line) +- if value: +- if section is not None: +- opts.append({'name': section, +- 'type': 'section', +- 'value': sectopts}) +- sectopts = [] +- curopts = sectopts +- fatheropts = sectopts +- section = value +- continue +- +- # It's a subsection start. +- value = self.matchSubSection(line) +- if value: +- if subsection is not None: +- raise SyntaxError('nested subsections are not ' +- 'supported yet') +- subsectopts = [] +- curopts = subsectopts +- subsection = value +- continue +- +- value = self.matchSubSectionEnd(line) +- if value: +- if subsection is None: +- raise SyntaxError('Unmatched end subsection terminator ' +- 'found') +- fatheropts.append({'name': subsection, +- 'type': 'subsection', +- 'value': subsectopts}) +- subsection = None +- curopts = fatheropts +- continue +- +- # Copy anything else as is. +- try: +- curopts.append(self.parseLine(line)) +- except SyntaxError as e: +- raise SyntaxError('{error} in file {fname}: [{line}]'.format( +- error=e, fname=f.name, line=line.rstrip())) +- +- #Add last section if any +- if len(sectopts) is not 0: +- opts.append({'name': section, +- 'type': 'section', +- 'value': sectopts}) +- +- return opts +- +- def changeConf(self, file, newopts): +- """ +- Write settings to configuration file +- :param file: path to the file +- :param options: set of dictionaries in the form: +- {'name': 'foo', 'value': 'bar', 'action': 'set/comment'} +- :param section: section name like 'global' +- """ +- output = "" +- f = None +- try: +- # Do not catch an unexisting file error +- # we want to fail in that case +- shutil.copy2(file, (file + ".ipabkp")) +- +- f = openLocked(file, 0o644) +- +- oldopts = self.parse(f) +- +- options = self.merge(oldopts, newopts) +- +- output = self.dump(options) +- +- # Write it out and close it. +- f.seek(0) +- f.truncate(0) +- f.write(output) +- finally: +- try: +- if f: +- f.close() +- except IOError: +- pass +- logger.debug("Updating configuration file %s", file) +- logger.debug(output) +- return True +- +- def newConf(self, file, options, file_perms=0o644): +- """" +- Write settings to a new file, backup the old +- :param file: path to the file +- :param options: a set of dictionaries in the form: +- {'name': 'foo', 'value': 'bar', 'action': 'set/comment'} +- :param file_perms: number defining the new file's permissions +- """ +- output = "" +- f = None +- try: +- try: +- shutil.copy2(file, (file + ".ipabkp")) +- except IOError as err: +- if err.errno == 2: +- # The orign file did not exist +- pass +- +- f = openLocked(file, file_perms) +- +- # Trunkate +- f.seek(0) +- f.truncate(0) +- +- output = self.dump(options) +- +- f.write(output) +- finally: +- try: +- if f: +- f.close() +- except IOError: +- pass +- logger.debug("Writing configuration file %s", file) +- logger.debug(output) +- return True +- +- @staticmethod +- def setOption(name, value): +- return {'name': name, +- 'type': 'option', +- 'action': 'set', +- 'value': value} +- +- @staticmethod +- def rmOption(name): +- return {'name': name, +- 'type': 'option', +- 'action': 'remove', +- 'value': None} +- +- @staticmethod +- def setSection(name, options): +- return {'name': name, +- 'type': 'section', +- 'action': 'set', +- 'value': options} +- +- @staticmethod +- def emptyLine(): +- return {'name': 'empty', +- 'type': 'empty'} ++ """something""" ++ warnings.warn( ++ "Use 'ipapython.ipachangeconf.IPAChangeConfg'", ++ DeprecationWarning, ++ stacklevel=2 ++ ) ++ super(IPAChangeConf, self).__init__(name) +diff --git a/ipapython/ipachangeconf.py b/ipapython/ipachangeconf.py +new file mode 100644 +index 0000000..cfb4a6e +--- /dev/null ++++ b/ipapython/ipachangeconf.py +@@ -0,0 +1,590 @@ ++# ++# ipachangeconf - configuration file manipulation classes and functions ++# partially based on authconfig code ++# Copyright (c) 1999-2007 Red Hat, Inc. ++# Author: Simo Sorce ++# ++# This program is free software; you can redistribute it and/or modify ++# it under the terms of the GNU General Public License as published by ++# the Free Software Foundation, either version 3 of the License, or ++# (at your option) any later version. ++# ++# This program is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++# ++# You should have received a copy of the GNU General Public License ++# along with this program. If not, see . ++# ++ ++import fcntl ++import logging ++import os ++import shutil ++ ++import six ++ ++if six.PY3: ++ unicode = str ++ ++logger = logging.getLogger(__name__) ++ ++ ++def openLocked(filename, perms): ++ fd = -1 ++ try: ++ fd = os.open(filename, os.O_RDWR | os.O_CREAT, perms) ++ ++ fcntl.lockf(fd, fcntl.LOCK_EX) ++ except OSError as e: ++ if fd != -1: ++ try: ++ os.close(fd) ++ except OSError: ++ pass ++ raise IOError(e.errno, e.strerror) ++ return os.fdopen(fd, "r+") ++ ++ # TODO: add subsection as a concept ++ # (ex. REALM.NAME = { foo = x bar = y } ) ++ # TODO: put section delimiters as separating element of the list ++ # so that we can process multiple sections in one go ++ # TODO: add a comment all but provided options as a section option ++ ++ ++class IPAChangeConf: ++ def __init__(self, name): ++ self.progname = name ++ self.indent = ("", "", "") ++ self.assign = (" = ", "=") ++ self.dassign = self.assign[0] ++ self.comment = ("#",) ++ self.dcomment = self.comment[0] ++ self.eol = ("\n",) ++ self.deol = self.eol[0] ++ self.sectnamdel = ("[", "]") ++ self.subsectdel = ("{", "}") ++ self.case_insensitive_sections = True ++ ++ def setProgName(self, name): ++ self.progname = name ++ ++ def setIndent(self, indent): ++ if type(indent) is tuple: ++ self.indent = indent ++ elif type(indent) is str: ++ self.indent = (indent, ) ++ else: ++ raise ValueError('Indent must be a list of strings') ++ ++ def setOptionAssignment(self, assign): ++ if type(assign) is tuple: ++ self.assign = assign ++ else: ++ self.assign = (assign, ) ++ self.dassign = self.assign[0] ++ ++ def setCommentPrefix(self, comment): ++ if type(comment) is tuple: ++ self.comment = comment ++ else: ++ self.comment = (comment, ) ++ self.dcomment = self.comment[0] ++ ++ def setEndLine(self, eol): ++ if type(eol) is tuple: ++ self.eol = eol ++ else: ++ self.eol = (eol, ) ++ self.deol = self.eol[0] ++ ++ def setSectionNameDelimiters(self, delims): ++ self.sectnamdel = delims ++ ++ def setSubSectionDelimiters(self, delims): ++ self.subsectdel = delims ++ ++ def matchComment(self, line): ++ for v in self.comment: ++ if line.lstrip().startswith(v): ++ return line.lstrip()[len(v):] ++ return False ++ ++ def matchEmpty(self, line): ++ if line.strip() == "": ++ return True ++ return False ++ ++ def matchSection(self, line): ++ cl = "".join(line.strip().split()) ++ cl = cl.lower() if self.case_insensitive_sections else cl ++ ++ if len(self.sectnamdel) != 2: ++ return False ++ if not cl.startswith(self.sectnamdel[0]): ++ return False ++ if not cl.endswith(self.sectnamdel[1]): ++ return False ++ return cl[len(self.sectnamdel[0]):-len(self.sectnamdel[1])] ++ ++ def matchSubSection(self, line): ++ if self.matchComment(line): ++ return False ++ ++ parts = line.split(self.dassign, 1) ++ if len(parts) < 2: ++ return False ++ ++ if parts[1].strip() == self.subsectdel[0]: ++ return parts[0].strip() ++ ++ return False ++ ++ def matchSubSectionEnd(self, line): ++ if self.matchComment(line): ++ return False ++ ++ if line.strip() == self.subsectdel[1]: ++ return True ++ ++ return False ++ ++ def getSectionLine(self, section): ++ if len(self.sectnamdel) != 2: ++ return section ++ return self._dump_line(self.sectnamdel[0], ++ section, ++ self.sectnamdel[1], ++ self.deol) ++ ++ def _dump_line(self, *args): ++ return u"".join(unicode(x) for x in args) ++ ++ def dump(self, options, level=0): ++ output = [] ++ if level >= len(self.indent): ++ level = len(self.indent) - 1 ++ ++ for o in options: ++ if o['type'] == "section": ++ output.append(self._dump_line(self.sectnamdel[0], ++ o['name'], ++ self.sectnamdel[1])) ++ output.append(self.dump(o['value'], (level + 1))) ++ continue ++ if o['type'] == "subsection": ++ output.append(self._dump_line(self.indent[level], ++ o['name'], ++ self.dassign, ++ self.subsectdel[0])) ++ output.append(self.dump(o['value'], (level + 1))) ++ output.append(self._dump_line(self.indent[level], ++ self.subsectdel[1])) ++ continue ++ if o['type'] == "option": ++ delim = o.get('delim', self.dassign) ++ if delim not in self.assign: ++ raise ValueError( ++ 'Unknown delim "%s" must be one of "%s"' % ++ (delim, " ".join([d for d in self.assign])) ++ ) ++ output.append(self._dump_line(self.indent[level], ++ o['name'], ++ delim, ++ o['value'])) ++ continue ++ if o['type'] == "comment": ++ output.append(self._dump_line(self.dcomment, o['value'])) ++ continue ++ if o['type'] == "empty": ++ output.append('') ++ continue ++ raise SyntaxError('Unknown type: [%s]' % o['type']) ++ ++ # append an empty string to the output so that we add eol to the end ++ # of the file contents in a single join() ++ output.append('') ++ return self.deol.join(output) ++ ++ def parseLine(self, line): ++ ++ if self.matchEmpty(line): ++ return {'name': 'empty', 'type': 'empty'} ++ ++ value = self.matchComment(line) ++ if value: ++ return {'name': 'comment', ++ 'type': 'comment', ++ 'value': value.rstrip()} # pylint: disable=E1103 ++ ++ o = dict() ++ parts = line.split(self.dassign, 1) ++ if len(parts) < 2: ++ # The default assign didn't match, try the non-default ++ for d in self.assign[1:]: ++ parts = line.split(d, 1) ++ if len(parts) >= 2: ++ o['delim'] = d ++ break ++ ++ if 'delim' not in o: ++ raise SyntaxError('Syntax Error: Unknown line format') ++ ++ o.update({'name': parts[0].strip(), 'type': 'option', ++ 'value': parts[1].rstrip()}) ++ return o ++ ++ def findOpts(self, opts, type, name, exclude_sections=False): ++ ++ num = 0 ++ for o in opts: ++ if o['type'] == type and o['name'] == name: ++ return (num, o) ++ if exclude_sections and (o['type'] == "section" or ++ o['type'] == "subsection"): ++ return (num, None) ++ num += 1 ++ return (num, None) ++ ++ def commentOpts(self, inopts, level=0): ++ ++ opts = [] ++ ++ if level >= len(self.indent): ++ level = len(self.indent) - 1 ++ ++ for o in inopts: ++ if o['type'] == 'section': ++ no = self.commentOpts(o['value'], (level + 1)) ++ val = self._dump_line(self.dcomment, ++ self.sectnamdel[0], ++ o['name'], ++ self.sectnamdel[1]) ++ opts.append({'name': 'comment', ++ 'type': 'comment', ++ 'value': val}) ++ for n in no: ++ opts.append(n) ++ continue ++ if o['type'] == 'subsection': ++ no = self.commentOpts(o['value'], (level + 1)) ++ val = self._dump_line(self.indent[level], ++ o['name'], ++ self.dassign, ++ self.subsectdel[0]) ++ opts.append({'name': 'comment', ++ 'type': 'comment', ++ 'value': val}) ++ opts.extend(no) ++ val = self._dump_line(self.indent[level], self.subsectdel[1]) ++ opts.append({'name': 'comment', ++ 'type': 'comment', ++ 'value': val}) ++ continue ++ if o['type'] == 'option': ++ delim = o.get('delim', self.dassign) ++ if delim not in self.assign: ++ val = self._dump_line(self.indent[level], ++ o['name'], ++ delim, ++ o['value']) ++ opts.append({'name': 'comment', 'type': 'comment', ++ 'value': val}) ++ continue ++ if o['type'] == 'comment': ++ opts.append(o) ++ continue ++ if o['type'] == 'empty': ++ opts.append({'name': 'comment', ++ 'type': 'comment', ++ 'value': ''}) ++ continue ++ raise SyntaxError('Unknown type: [%s]' % o['type']) ++ ++ return opts ++ ++ def mergeOld(self, oldopts, newopts): ++ ++ opts = [] ++ ++ for o in oldopts: ++ if o['type'] == "section" or o['type'] == "subsection": ++ _num, no = self.findOpts(newopts, o['type'], o['name']) ++ if not no: ++ opts.append(o) ++ continue ++ if no['action'] == "set": ++ mo = self.mergeOld(o['value'], no['value']) ++ opts.append({'name': o['name'], ++ 'type': o['type'], ++ 'value': mo}) ++ continue ++ if no['action'] == "comment": ++ co = self.commentOpts(o['value']) ++ for c in co: ++ opts.append(c) ++ continue ++ if no['action'] == "remove": ++ continue ++ raise SyntaxError('Unknown action: [%s]' % no['action']) ++ ++ if o['type'] == "comment" or o['type'] == "empty": ++ opts.append(o) ++ continue ++ ++ if o['type'] == "option": ++ _num, no = self.findOpts(newopts, 'option', o['name'], True) ++ if not no: ++ opts.append(o) ++ continue ++ if no['action'] == 'comment' or no['action'] == 'remove': ++ if (no['value'] is not None and ++ o['value'] is not no['value']): ++ opts.append(o) ++ continue ++ if no['action'] == 'comment': ++ value = self._dump_line(self.dcomment, ++ o['name'], ++ self.dassign, ++ o['value']) ++ opts.append({'name': 'comment', ++ 'type': 'comment', ++ 'value': value}) ++ continue ++ if no['action'] == 'set': ++ opts.append(no) ++ continue ++ if no['action'] == 'addifnotset': ++ opts.append({ ++ 'name': 'comment', ++ 'type': 'comment', ++ 'value': self._dump_line( ++ ' ', no['name'], ' modified by IPA' ++ ), ++ }) ++ opts.append({'name': 'comment', 'type': 'comment', ++ 'value': self._dump_line(no['name'], ++ self.dassign, ++ no['value'], ++ )}) ++ opts.append(o) ++ continue ++ raise SyntaxError('Unknown action: [%s]' % no['action']) ++ ++ raise SyntaxError('Unknown type: [%s]' % o['type']) ++ ++ return opts ++ ++ def mergeNew(self, opts, newopts): ++ ++ cline = 0 ++ ++ for no in newopts: ++ ++ if no['type'] == "section" or no['type'] == "subsection": ++ (num, o) = self.findOpts(opts, no['type'], no['name']) ++ if not o: ++ if no['action'] == 'set': ++ opts.append(no) ++ continue ++ if no['action'] == "set": ++ self.mergeNew(o['value'], no['value']) ++ continue ++ cline = num + 1 ++ continue ++ ++ if no['type'] == "option": ++ (num, o) = self.findOpts(opts, no['type'], no['name'], True) ++ if not o: ++ if no['action'] == 'set' or no['action'] == 'addifnotset': ++ opts.append(no) ++ continue ++ cline = num + 1 ++ continue ++ ++ if no['type'] == "comment" or no['type'] == "empty": ++ opts.insert(cline, no) ++ cline += 1 ++ continue ++ ++ raise SyntaxError('Unknown type: [%s]' % no['type']) ++ ++ def merge(self, oldopts, newopts): ++ """ ++ Uses a two pass strategy: ++ First we create a new opts tree from oldopts removing/commenting ++ the options as indicated by the contents of newopts ++ Second we fill in the new opts tree with options as indicated ++ in the newopts tree (this is becaus eentire (sub)sections may ++ in the newopts tree (this is becaus entire (sub)sections may ++ exist in the newopts that do not exist in oldopts) ++ """ ++ opts = self.mergeOld(oldopts, newopts) ++ self.mergeNew(opts, newopts) ++ return opts ++ ++ # TODO: Make parse() recursive? ++ def parse(self, f): ++ ++ opts = [] ++ sectopts = [] ++ section = None ++ subsectopts = [] ++ subsection = None ++ curopts = opts ++ fatheropts = opts ++ ++ # Read in the old file. ++ for line in f: ++ ++ # It's a section start. ++ value = self.matchSection(line) ++ if value: ++ if section is not None: ++ opts.append({'name': section, ++ 'type': 'section', ++ 'value': sectopts}) ++ sectopts = [] ++ curopts = sectopts ++ fatheropts = sectopts ++ section = value ++ continue ++ ++ # It's a subsection start. ++ value = self.matchSubSection(line) ++ if value: ++ if subsection is not None: ++ raise SyntaxError('nested subsections are not ' ++ 'supported yet') ++ subsectopts = [] ++ curopts = subsectopts ++ subsection = value ++ continue ++ ++ value = self.matchSubSectionEnd(line) ++ if value: ++ if subsection is None: ++ raise SyntaxError('Unmatched end subsection terminator ' ++ 'found') ++ fatheropts.append({'name': subsection, ++ 'type': 'subsection', ++ 'value': subsectopts}) ++ subsection = None ++ curopts = fatheropts ++ continue ++ ++ # Copy anything else as is. ++ try: ++ curopts.append(self.parseLine(line)) ++ except SyntaxError as e: ++ raise SyntaxError('{error} in file {fname}: [{line}]'.format( ++ error=e, fname=f.name, line=line.rstrip())) ++ ++ # Add last section if any ++ if len(sectopts) is not 0: ++ opts.append({'name': section, ++ 'type': 'section', ++ 'value': sectopts}) ++ ++ return opts ++ ++ def changeConf(self, file, newopts): ++ """ ++ Write settings to configuration file ++ :param file: path to the file ++ :param options: set of dictionaries in the form: ++ {'name': 'foo', 'value': 'bar', 'action': 'set/comment'} ++ :param section: section name like 'global' ++ """ ++ output = "" ++ f = None ++ try: ++ # Do not catch an unexisting file error ++ # we want to fail in that case ++ shutil.copy2(file, (file + ".ipabkp")) ++ ++ f = openLocked(file, 0o644) ++ ++ oldopts = self.parse(f) ++ ++ options = self.merge(oldopts, newopts) ++ ++ output = self.dump(options) ++ ++ # Write it out and close it. ++ f.seek(0) ++ f.truncate(0) ++ f.write(output) ++ finally: ++ try: ++ if f: ++ f.close() ++ except IOError: ++ pass ++ logger.debug("Updating configuration file %s", file) ++ logger.debug(output) ++ return True ++ ++ def newConf(self, file, options, file_perms=0o644): ++ """" ++ Write settings to a new file, backup the old ++ :param file: path to the file ++ :param options: a set of dictionaries in the form: ++ {'name': 'foo', 'value': 'bar', 'action': 'set/comment'} ++ :param file_perms: number defining the new file's permissions ++ """ ++ output = "" ++ f = None ++ try: ++ try: ++ shutil.copy2(file, (file + ".ipabkp")) ++ except IOError as err: ++ if err.errno == 2: ++ # The orign file did not exist ++ pass ++ ++ f = openLocked(file, file_perms) ++ ++ # Trunkate ++ f.seek(0) ++ f.truncate(0) ++ ++ output = self.dump(options) ++ ++ f.write(output) ++ finally: ++ try: ++ if f: ++ f.close() ++ except IOError: ++ pass ++ logger.debug("Writing configuration file %s", file) ++ logger.debug(output) ++ return True ++ ++ @staticmethod ++ def setOption(name, value): ++ return {'name': name, ++ 'type': 'option', ++ 'action': 'set', ++ 'value': value} ++ ++ @staticmethod ++ def rmOption(name): ++ return {'name': name, ++ 'type': 'option', ++ 'action': 'remove', ++ 'value': None} ++ ++ @staticmethod ++ def setSection(name, options): ++ return {'name': name, ++ 'type': 'section', ++ 'action': 'set', ++ 'value': options} ++ ++ @staticmethod ++ def emptyLine(): ++ return {'name': 'empty', ++ 'type': 'empty'} +diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py +index 7bb9431..47a5a92 100644 +--- a/ipaserver/install/adtrustinstance.py ++++ b/ipaserver/install/adtrustinstance.py +@@ -40,11 +40,11 @@ from ipaserver.install.replication import wait_for_task + from ipalib import errors, api + from ipalib.util import normalize_zone + from ipapython.dn import DN ++from ipapython import ipachangeconf + from ipapython import ipaldap + from ipapython import ipautil + import ipapython.errors + +-import ipaclient.install.ipachangeconf + from ipaplatform import services + from ipaplatform.constants import constants + from ipaplatform.paths import paths +@@ -639,7 +639,7 @@ class ADTRUSTInstance(service.Service): + self.print_msg("Cannot modify /etc/krb5.conf") + + krbconf = ( +- ipaclient.install.ipachangeconf.IPAChangeConf("IPA Installer")) ++ ipachangeconf.IPAChangeConf("IPA Installer")) + krbconf.setOptionAssignment((" = ", " ")) + krbconf.setSectionNameDelimiters(("[", "]")) + krbconf.setSubSectionDelimiters(("{", "}")) +diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py +index 02c8f4d..6a81d57 100644 +--- a/ipaserver/install/server/install.py ++++ b/ipaserver/install/server/install.py +@@ -19,7 +19,7 @@ import six + from ipaclient.install import timeconf + from ipaclient.install.client import ( + check_ldap_conf, sync_time, restore_time_sync) +-from ipaclient.install.ipachangeconf import IPAChangeConf ++from ipapython.ipachangeconf import IPAChangeConf + from ipalib.install import certmonger, sysrestore + from ipapython import ipautil, version + from ipapython.ipautil import ( +diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py +index 6da6804..7272640 100644 +--- a/ipaserver/install/server/replicainstall.py ++++ b/ipaserver/install/server/replicainstall.py +@@ -23,13 +23,13 @@ from pkg_resources import parse_version + import six + + from ipaclient.install.client import check_ldap_conf +-from ipaclient.install.ipachangeconf import IPAChangeConf + import ipaclient.install.timeconf + from ipalib.install import certstore, sysrestore + from ipalib.install.kinit import kinit_keytab + from ipapython import ipaldap, ipautil + from ipapython.dn import DN + from ipapython.admintool import ScriptError ++from ipapython.ipachangeconf import IPAChangeConf + from ipaplatform import services + from ipaplatform.tasks import tasks + from ipaplatform.paths import paths +diff --git a/ipatests/test_install/test_changeconf.py b/ipatests/test_install/test_changeconf.py +index 2dc2b7d..40c8a1d 100644 +--- a/ipatests/test_install/test_changeconf.py ++++ b/ipatests/test_install/test_changeconf.py +@@ -3,7 +3,7 @@ + from __future__ import absolute_import + + import pytest +-from ipaclient.install.ipachangeconf import IPAChangeConf ++from ipapython.ipachangeconf import IPAChangeConf + + + @pytest.fixture(scope='function') + +From 2da90887632c764a73866c9ad3824ebb53c0aa73 Mon Sep 17 00:00:00 2001 +From: Rob Critenden +Date: Aug 29 2019 06:45:12 +0000 +Subject: Use tasks to configure automount nsswitch settings + + +authselect doesn't allow one to directly write to +/etc/nsswitch.conf. It will complain bitterly if it +detects it and will refuse to work until reset. + +Instead it wants the user to write to +/etc/authselect/user-nsswitch.conf and then it will handle +merging in any differences. + +To complicate matters some databases are not user configurable +like passwd, group and of course, automount. There are some +undocumented options to allow one to override these though so +we utilize that. + +tasks are used so that authselect-based installations can still +write directly to /etc/nsswitch.conf and operate as it used to. + +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +Reviewed-By: Rob Critenden +Reviewed-By: François Cami + +--- + +diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py +index 9492ca4..1e88ba1 100644 +--- a/ipaclient/install/client.py ++++ b/ipaclient/install/client.py +@@ -66,7 +66,7 @@ from ipapython import version + + from . import automount, timeconf, sssd + from ipaclient import discovery +-from .ipachangeconf import IPAChangeConf ++from ipapython.ipachangeconf import IPAChangeConf + + NoneType = type(None) + +@@ -281,72 +281,6 @@ def is_ipa_client_installed(fstore, on_master=False): + return installed + + +-def configure_nsswitch_database(fstore, database, services, preserve=True, +- append=True, default_value=()): +- """ +- Edits the specified nsswitch.conf database (e.g. passwd, group, sudoers) +- to use the specified service(s). +- +- Arguments: +- fstore - FileStore to backup the nsswitch.conf +- database - database configuration that should be ammended, +- e.g. 'sudoers' +- service - list of services that should be added, e.g. ['sss'] +- preserve - if True, the already configured services will be preserved +- +- The next arguments modify the behaviour if preserve=True: +- append - if True, the services will be appended, if False, prepended +- default_value - list of services that are considered as default (if +- the database is not mentioned in nsswitch.conf), e.g. +- ['files'] +- """ +- +- # Backup the original version of nsswitch.conf, we're going to edit it now +- if not fstore.has_file(paths.NSSWITCH_CONF): +- fstore.backup_file(paths.NSSWITCH_CONF) +- +- conf = IPAChangeConf("IPA Installer") +- conf.setOptionAssignment(':') +- +- if preserve: +- # Read the existing configuration +- with open(paths.NSSWITCH_CONF, 'r') as f: +- opts = conf.parse(f) +- raw_database_entry = conf.findOpts(opts, 'option', database)[1] +- +- # Detect the list of already configured services +- if not raw_database_entry: +- # If there is no database entry, database is not present in +- # the nsswitch.conf. Set the list of services to the +- # default list, if passed. +- configured_services = list(default_value) +- else: +- configured_services = raw_database_entry['value'].strip().split() +- +- # Make sure no service is added if already mentioned in the list +- added_services = [s for s in services +- if s not in configured_services] +- +- # Prepend / append the list of new services +- if append: +- new_value = ' ' + ' '.join(configured_services + added_services) +- else: +- new_value = ' ' + ' '.join(added_services + configured_services) +- +- else: +- # Preserve not set, let's rewrite existing configuration +- new_value = ' ' + ' '.join(services) +- +- # Set new services as sources for database +- opts = [ +- conf.setOption(database, new_value), +- conf.emptyLine(), +- ] +- +- conf.changeConf(paths.NSSWITCH_CONF, opts) +- logger.info("Configured %s in %s", database, paths.NSSWITCH_CONF) +- +- + def configure_ipa_conf( + fstore, cli_basedn, cli_realm, cli_domain, cli_server, hostname): + ipaconf = IPAChangeConf("IPA Installer") +@@ -948,9 +882,7 @@ def configure_sssd_conf( + "Unable to activate the SUDO service in SSSD config.") + + sssdconfig.activate_service('sudo') +- configure_nsswitch_database( +- fstore, 'sudoers', ['sss'], +- default_value=['files']) ++ tasks.enable_sssd_sudo(fstore) + + domain.add_provider('ipa', 'id') + +diff --git a/ipaclient/install/ipa_client_automount.py b/ipaclient/install/ipa_client_automount.py +index a1dc2a1..3a0896b 100644 +--- a/ipaclient/install/ipa_client_automount.py ++++ b/ipaclient/install/ipa_client_automount.py +@@ -41,7 +41,8 @@ from six.moves.urllib.parse import urlsplit + + # pylint: enable=import-error + from optparse import OptionParser # pylint: disable=deprecated-module +-from ipaclient.install import ipachangeconf, ipadiscovery ++from ipapython import ipachangeconf ++from ipaclient.install import ipadiscovery + from ipaclient.install.client import ( + CLIENT_NOT_CONFIGURED, + CLIENT_ALREADY_CONFIGURED, +@@ -177,44 +178,6 @@ def configure_xml(fstore): + print("Configured %s" % authconf) + + +-def configure_nsswitch(statestore, options): +- """ +- Point automount to ldap in nsswitch.conf. +- This function is for non-SSSD setups only. +- """ +- conf = ipachangeconf.IPAChangeConf("IPA Installer") +- conf.setOptionAssignment(':') +- +- with open(paths.NSSWITCH_CONF, 'r') as f: +- current_opts = conf.parse(f) +- current_nss_value = conf.findOpts( +- current_opts, name='automount', type='option' +- )[1] +- if current_nss_value is None: +- # no automount database present +- current_nss_value = False # None cannot be backed up +- else: +- current_nss_value = current_nss_value['value'] +- statestore.backup_state( +- 'ipa-client-automount-nsswitch', 'previous-automount', +- current_nss_value +- ) +- +- nss_value = ' files ldap' +- opts = [ +- { +- 'name': 'automount', +- 'type': 'option', +- 'action': 'set', +- 'value': nss_value, +- }, +- {'name': 'empty', 'type': 'empty'}, +- ] +- conf.changeConf(paths.NSSWITCH_CONF, opts) +- +- print("Configured %s" % paths.NSSWITCH_CONF) +- +- + def configure_autofs_sssd(fstore, statestore, autodiscover, options): + try: + sssdconfig = SSSDConfig.SSSDConfig() +@@ -339,41 +302,8 @@ def uninstall(fstore, statestore): + ] + STATES = ['autofs', 'rpcidmapd', 'rpcgssd'] + +- if statestore.get_state( +- 'ipa-client-automount-nsswitch', 'previous-automount' +- ) is False: +- # Previous nsswitch.conf had no automount database configured +- # so remove it. +- conf = ipachangeconf.IPAChangeConf("IPA automount installer") +- conf.setOptionAssignment(':') +- changes = [conf.rmOption('automount')] +- conf.changeConf(paths.NSSWITCH_CONF, changes) +- tasks.restore_context(paths.NSSWITCH_CONF) +- statestore.delete_state( +- 'ipa-client-automount-nsswitch', 'previous-automount' +- ) +- elif statestore.get_state( +- 'ipa-client-automount-nsswitch', 'previous-automount' +- ) is not None: +- nss_value = statestore.get_state( +- 'ipa-client-automount-nsswitch', 'previous-automount' +- ) +- opts = [ +- { +- 'name': 'automount', +- 'type': 'option', +- 'action': 'set', +- 'value': nss_value, +- }, +- {'name': 'empty', 'type': 'empty'}, +- ] +- conf = ipachangeconf.IPAChangeConf("IPA automount installer") +- conf.setOptionAssignment(':') +- conf.changeConf(paths.NSSWITCH_CONF, opts) +- tasks.restore_context(paths.NSSWITCH_CONF) +- statestore.delete_state( +- 'ipa-client-automount-nsswitch', 'previous-automount' +- ) ++ if not statestore.get_state('autofs', 'sssd'): ++ tasks.disable_ldap_automount(statestore) + + if not any(fstore.has_file(f) for f in RESTORE_FILES) or not any( + statestore.has_state(s) for s in STATES +@@ -627,7 +557,7 @@ def configure_automount(): + + try: + if not options.sssd: +- configure_nsswitch(statestore, options) ++ tasks.enable_ldap_automount(statestore) + configure_nfs(fstore, statestore, options) + if options.sssd: + configure_autofs_sssd(fstore, statestore, autodiscover, options) +diff --git a/ipaplatform/base/tasks.py b/ipaplatform/base/tasks.py +index 8aa9c5c..7fd7d57 100644 +--- a/ipaplatform/base/tasks.py ++++ b/ipaplatform/base/tasks.py +@@ -32,6 +32,7 @@ from pkg_resources import parse_version + from ipaplatform.constants import constants + from ipaplatform.paths import paths + from ipapython import ipautil ++from ipapython.ipachangeconf import IPAChangeConf + + logger = logging.getLogger(__name__) + +@@ -337,5 +338,157 @@ class BaseTaskNamespace: + """ + raise NotImplementedError + ++ def configure_nsswitch_database(self, fstore, database, services, ++ preserve=True, append=True, ++ default_value=()): ++ """ ++ Edits the specified nsswitch.conf database (e.g. passwd, group, ++ sudoers) to use the specified service(s). ++ ++ Arguments: ++ fstore - FileStore to backup the nsswitch.conf ++ database - database configuration that should be ammended, ++ e.g. 'sudoers' ++ service - list of services that should be added, e.g. ['sss'] ++ preserve - if True, the already configured services will be ++ preserved ++ ++ The next arguments modify the behaviour if preserve=True: ++ append - if True, the services will be appended, if False, ++ prepended ++ default_value - list of services that are considered as default (if ++ the database is not mentioned in nsswitch.conf), ++ e.g. ['files'] ++ """ ++ ++ # Backup the original version of nsswitch.conf, we're going to edit it ++ # now ++ if not fstore.has_file(paths.NSSWITCH_CONF): ++ fstore.backup_file(paths.NSSWITCH_CONF) ++ ++ conf = IPAChangeConf("IPA Installer") ++ conf.setOptionAssignment(':') ++ ++ if preserve: ++ # Read the existing configuration ++ with open(paths.NSSWITCH_CONF, 'r') as f: ++ opts = conf.parse(f) ++ raw_database_entry = conf.findOpts(opts, 'option', database)[1] ++ ++ # Detect the list of already configured services ++ if not raw_database_entry: ++ # If there is no database entry, database is not present in ++ # the nsswitch.conf. Set the list of services to the ++ # default list, if passed. ++ configured_services = list(default_value) ++ else: ++ configured_services = raw_database_entry[ ++ 'value'].strip().split() ++ ++ # Make sure no service is added if already mentioned in the list ++ added_services = [s for s in services ++ if s not in configured_services] ++ ++ # Prepend / append the list of new services ++ if append: ++ new_value = ' ' + ' '.join(configured_services + ++ added_services) ++ else: ++ new_value = ' ' + ' '.join(added_services + ++ configured_services) ++ ++ else: ++ # Preserve not set, let's rewrite existing configuration ++ new_value = ' ' + ' '.join(services) ++ ++ # Set new services as sources for database ++ opts = [ ++ conf.setOption(database, new_value), ++ conf.emptyLine(), ++ ] ++ ++ conf.changeConf(paths.NSSWITCH_CONF, opts) ++ logger.info("Configured %s in %s", database, paths.NSSWITCH_CONF) ++ ++ def enable_sssd_sudo(self, fstore): ++ """Configure nsswitch.conf to use sssd for sudo""" ++ self.configure_nsswitch_database( ++ fstore, 'sudoers', ['sss'], ++ default_value=['files']) ++ ++ def enable_ldap_automount(self, statestore): ++ """ ++ Point automount to ldap in nsswitch.conf. ++ This function is for non-SSSD setups only. ++ """ ++ conf = IPAChangeConf("IPA Installer") ++ conf.setOptionAssignment(':') ++ ++ with open(paths.NSSWITCH_CONF, 'r') as f: ++ current_opts = conf.parse(f) ++ current_nss_value = conf.findOpts( ++ current_opts, name='automount', type='option' ++ )[1] ++ if current_nss_value is None: ++ # no automount database present ++ current_nss_value = False # None cannot be backed up ++ else: ++ current_nss_value = current_nss_value['value'] ++ statestore.backup_state( ++ 'ipa-client-automount-nsswitch', 'previous-automount', ++ current_nss_value ++ ) ++ ++ nss_value = ' files ldap' ++ opts = [ ++ { ++ 'name': 'automount', ++ 'type': 'option', ++ 'action': 'set', ++ 'value': nss_value, ++ }, ++ {'name': 'empty', 'type': 'empty'}, ++ ] ++ conf.changeConf(paths.NSSWITCH_CONF, opts) ++ ++ logger.info("Configured %s", paths.NSSWITCH_CONF) ++ ++ def disable_ldap_automount(self, statestore): ++ """Disable automount using LDAP""" ++ if statestore.get_state( ++ 'ipa-client-automount-nsswitch', 'previous-automount' ++ ) is False: ++ # Previous nsswitch.conf had no automount database configured ++ # so remove it. ++ conf = IPAChangeConf("IPA automount installer") ++ conf.setOptionAssignment(':') ++ changes = [conf.rmOption('automount')] ++ conf.changeConf(paths.NSSWITCH_CONF, changes) ++ self.restore_context(paths.NSSWITCH_CONF) ++ statestore.delete_state( ++ 'ipa-client-automount-nsswitch', 'previous-automount' ++ ) ++ elif statestore.get_state( ++ 'ipa-client-automount-nsswitch', 'previous-automount' ++ ) is not None: ++ nss_value = statestore.get_state( ++ 'ipa-client-automount-nsswitch', 'previous-automount' ++ ) ++ opts = [ ++ { ++ 'name': 'automount', ++ 'type': 'option', ++ 'action': 'set', ++ 'value': nss_value, ++ }, ++ {'name': 'empty', 'type': 'empty'}, ++ ] ++ conf = IPAChangeConf("IPA automount installer") ++ conf.setOptionAssignment(':') ++ conf.changeConf(paths.NSSWITCH_CONF, opts) ++ self.restore_context(paths.NSSWITCH_CONF) ++ statestore.delete_state( ++ 'ipa-client-automount-nsswitch', 'previous-automount' ++ ) + + tasks = BaseTaskNamespace() +diff --git a/ipaplatform/redhat/paths.py b/ipaplatform/redhat/paths.py +index 8ccd04b..15bdef6 100644 +--- a/ipaplatform/redhat/paths.py ++++ b/ipaplatform/redhat/paths.py +@@ -39,6 +39,7 @@ class RedHatPathNamespace(BasePathNamespace): + AUTHCONFIG = '/usr/sbin/authconfig' + AUTHSELECT = '/usr/bin/authselect' + SYSCONF_NETWORK = '/etc/sysconfig/network' ++ NSSWITCH_CONF = '/etc/authselect/user-nsswitch.conf' + + + paths = RedHatPathNamespace() +diff --git a/ipaplatform/redhat/tasks.py b/ipaplatform/redhat/tasks.py +index be0b641..e18f6fa 100644 +--- a/ipaplatform/redhat/tasks.py ++++ b/ipaplatform/redhat/tasks.py +@@ -744,4 +744,23 @@ class RedHatTaskNamespace(BaseTaskNamespace): + + return filenames + ++ def enable_ldap_automount(self, statestore): ++ """ ++ Point automount to ldap in nsswitch.conf. ++ This function is for non-SSSD setups only. ++ """ ++ super(RedHatTaskNamespace, self).enable_ldap_automount(statestore) ++ ++ authselect_cmd = [paths.AUTHSELECT, "enable-feature", ++ "with-custom-automount"] ++ ipautil.run(authselect_cmd) ++ ++ def disable_ldap_automount(self, statestore): ++ """Disable ldap-based automount""" ++ super(RedHatTaskNamespace, self).disable_ldap_automount(statestore) ++ ++ authselect_cmd = [paths.AUTHSELECT, "disable-feature", ++ "with-custom-automount"] ++ ipautil.run(authselect_cmd) ++ + tasks = RedHatTaskNamespace() + diff --git a/SOURCES/0010-Handle_NTP_configuration_in_replica_server_installation_f3e3da5_rhbz#1651679.patch b/SOURCES/0010-Handle_NTP_configuration_in_replica_server_installation_f3e3da5_rhbz#1651679.patch deleted file mode 100644 index b5d4f50..0000000 --- a/SOURCES/0010-Handle_NTP_configuration_in_replica_server_installation_f3e3da5_rhbz#1651679.patch +++ /dev/null @@ -1,69 +0,0 @@ -From f3e3da509329881c4ba770d1f9418ad180ee98ae Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Oct 19 2018 17:35:05 +0000 -Subject: Handle NTP configuration in a replica server installation - - -There were two separate issues: - -1. If not enrolling on a pre-configured client then the ntp-server and - ntp-pool options are not being passed down to the client installer - invocation. -2. If the client is already enrolled then the ntp options are ignored - altogether. - -In the first case simply pass down the options to the client -installer invocation. - -If the client is pre-enrolled and NTP options are provided then -raise an exception. - -https://pagure.io/freeipa/issue/7723 - -Signed-off-by: Rob Crittenden -Reviewed-By: Florence Blanc-Renaud - ---- - -diff --git a/install/tools/man/ipa-replica-install.1 b/install/tools/man/ipa-replica-install.1 -index 7f6ca57..c63107d 100644 ---- a/install/tools/man/ipa-replica-install.1 -+++ b/install/tools/man/ipa-replica-install.1 -@@ -14,7 +14,7 @@ Domain level 0 is not supported anymore. - - To create a replica, the machine only needs to be enrolled in the FreeIPA domain first. This process of turning the IPA client into a replica is also referred to as replica promotion. - --If you're starting with an existing IPA client, simply run ipa\-replica\-install to have it promoted into a replica. -+If you're starting with an existing IPA client, simply run ipa\-replica\-install to have it promoted into a replica. The NTP configuration cannot be updated during client promotion. - - To promote a blank machine into a replica, you have two options, you can either run ipa\-client\-install in a separate step, or pass the enrollment related options to the ipa\-replica\-install (see CLIENT ENROLLMENT OPTIONS). In the latter case, ipa\-replica\-install will join the machine to the IPA realm automatically and will proceed with the promotion step. - -diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py -index aaa1943..3022057 100644 ---- a/ipaserver/install/server/replicainstall.py -+++ b/ipaserver/install/server/replicainstall.py -@@ -717,6 +717,11 @@ def ensure_enrolled(installer): - for ip in installer.ip_addresses: - # installer.ip_addresses is of type [CheckedIPAddress] - args.extend(("--ip-address", str(ip))) -+ if installer.ntp_servers: -+ for server in installer.ntp_servers: -+ args.extend(("--ntp-server", server)) -+ if installer.ntp_pool: -+ args.extend(("--ntp-pool", installer.ntp_pool)) - - try: - # Call client install script -@@ -774,6 +779,11 @@ def promote_check(installer): - "the --domain, --server, --realm, --hostname, --password " - "and --keytab options.") - -+ # The NTP configuration can not be touched on pre-installed client: -+ if options.no_ntp or options.ntp_servers or options.ntp_pool: -+ raise ScriptError( -+ "NTP configuration cannot be updated during promotion") -+ - sstore = sysrestore.StateFile(paths.SYSRESTORE) - - fstore = sysrestore.FileStore(paths.SYSRESTORE) - diff --git a/SOURCES/0011-Fix_defects_found_by_static_analysis_rhbz#1658182.patch b/SOURCES/0011-Fix_defects_found_by_static_analysis_rhbz#1658182.patch deleted file mode 100644 index 539a57c..0000000 --- a/SOURCES/0011-Fix_defects_found_by_static_analysis_rhbz#1658182.patch +++ /dev/null @@ -1,449 +0,0 @@ -From 705e280eafb13b1b55fc0b91001e4721ce79fbdf Mon Sep 17 00:00:00 2001 -From: Thomas Woerner -Date: Mon, 22 Oct 2018 13:57:11 +0200 -Subject: [PATCH] Fix ressource leak in client/config.c get_config_entry - -The leak happens due to using strndup to create a temporary string without -freeing it afterwards. - -See: https://pagure.io/freeipa/issue/7738 -Signed-off-by: Thomas Woerner -Reviewed-By: Christian Heimes ---- - client/config.c | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) - -diff --git a/client/config.c b/client/config.c -index ecc126ff47..a09564b702 100644 ---- a/client/config.c -+++ b/client/config.c -@@ -123,17 +123,18 @@ get_config_entry(char * in_data, const char *section, const char *key) - line++; - p = strchr(line, ']'); - if (p) { -- tmp = strndup(line, p - line); - if (in_section) { - /* We exited the matching section without a match */ - free(data); - return NULL; - } -+ tmp = strndup(line, p - line); - if (strcmp(section, tmp) == 0) { - free(tmp); - in_section = 1; - continue; - } -+ free(tmp); - } - } /* [ */ - -From ebb14ed6f57c5504dc2f44339274b108483efd16 Mon Sep 17 00:00:00 2001 -From: Thomas Woerner -Date: Mon, 22 Oct 2018 15:18:23 +0200 -Subject: [PATCH] Fix ressource leak in - daemons/ipa-slapi-plugins/ipa-cldap/ipa_cldap_netlogon.c ipa_cldap_netlogon - -The leak happens due to using strndup in a for loop to create a temporary -string without freeing it in all cases. - -See: https://pagure.io/freeipa/issue/7738 -Signed-off-by: Thomas Woerner -Reviewed-By: Christian Heimes ---- - daemons/ipa-slapi-plugins/ipa-cldap/ipa_cldap_netlogon.c | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/daemons/ipa-slapi-plugins/ipa-cldap/ipa_cldap_netlogon.c b/daemons/ipa-slapi-plugins/ipa-cldap/ipa_cldap_netlogon.c -index 5863f667ea..460f96cd59 100644 ---- a/daemons/ipa-slapi-plugins/ipa-cldap/ipa_cldap_netlogon.c -+++ b/daemons/ipa-slapi-plugins/ipa-cldap/ipa_cldap_netlogon.c -@@ -260,6 +260,10 @@ int ipa_cldap_netlogon(struct ipa_cldap_ctx *ctx, - if (req->kvps.pairs[i].value.bv_val[len-1] == '.') { - len--; - } -+ if (domain != NULL) { -+ free(domain); -+ domain = NULL; -+ } - domain = strndup(req->kvps.pairs[i].value.bv_val, len); - if (!domain) { - ret = ENOMEM; -From 305150416429b85d46ad4162bac492db303cf9cf Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Wed, 24 Oct 2018 10:12:39 +0200 -Subject: [PATCH] Fix ipadb_multires resource handling - -* ipadb_get_pwd_policy() initializes struct ipadb_multires *res to NULL. -* ipadb_multires_free() supports NULL as no-op. -* ipadb_multibase_search() consistently frees and NULLs - struct ipadb_multires **res on error. - -See: https://pagure.io/freeipa/issue/7738 -Signed-off-by: Christian Heimes -Reviewed-By: Alexander Bokovoy ---- - daemons/ipa-kdb/ipa_kdb_common.c | 13 +++++++++---- - daemons/ipa-kdb/ipa_kdb_pwdpolicy.c | 2 +- - 2 files changed, 10 insertions(+), 5 deletions(-) - -diff --git a/daemons/ipa-kdb/ipa_kdb_common.c b/daemons/ipa-kdb/ipa_kdb_common.c -index 5995efe6b1..e2592cea3f 100644 ---- a/daemons/ipa-kdb/ipa_kdb_common.c -+++ b/daemons/ipa-kdb/ipa_kdb_common.c -@@ -634,10 +634,12 @@ krb5_error_code ipadb_multires_init(LDAP *lcontext, struct ipadb_multires **r) - - void ipadb_multires_free(struct ipadb_multires *r) - { -- for (int i = 0; i < r->count; i++) { -- ldap_msgfree(r->res[i]); -+ if (r != NULL) { -+ for (int i = 0; i < r->count; i++) { -+ ldap_msgfree(r->res[i]); -+ } -+ free(r); - } -- free(r); - } - - LDAPMessage *ipadb_multires_next_entry(struct ipadb_multires *r) -@@ -670,8 +672,11 @@ krb5_error_code ipadb_multibase_search(struct ipadb_context *ipactx, - if (ret != 0) return ret; - - ret = ipadb_check_connection(ipactx); -- if (ret != 0) -+ if (ret != 0) { -+ ipadb_multires_free(*res); -+ *res = NULL; - return ipadb_simple_ldap_to_kerr(ret); -+ } - - for (int b = 0; basedns[b]; b++) { - LDAPMessage *r; -diff --git a/daemons/ipa-kdb/ipa_kdb_pwdpolicy.c b/daemons/ipa-kdb/ipa_kdb_pwdpolicy.c -index 1ec584612b..10f128700b 100644 ---- a/daemons/ipa-kdb/ipa_kdb_pwdpolicy.c -+++ b/daemons/ipa-kdb/ipa_kdb_pwdpolicy.c -@@ -141,7 +141,7 @@ krb5_error_code ipadb_get_pwd_policy(krb5_context kcontext, char *name, - char *esc_name = NULL; - char *src_filter = NULL; - krb5_error_code kerr; -- struct ipadb_multires *res; -+ struct ipadb_multires *res = NULL; - LDAPMessage *lentry; - osa_policy_ent_t pentry = NULL; - uint32_t result; -From 4ca3120b9a09ad48866446af29b38ca7c005b0d0 Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Wed, 24 Oct 2018 10:19:14 +0200 -Subject: [PATCH] Don't abuse strncpy() length limitation -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -On two occasions C code abused strncpy()'s length limitation to copy a -string of known length without the trailing NULL byte. Recent GCC is -raising the compiler warning: - - warning: ‘strncpy’ output truncated before terminating nul copying as - many bytes from a string as its length [-Wstringop-truncation] - -Use memcpy() instead if strncpy() to copy data of known size. - -See: https://pagure.io/freeipa/issue/7738 -Signed-off-by: Christian Heimes -Reviewed-By: Alexander Bokovoy ---- - daemons/ipa-kdb/ipa_kdb.c | 2 +- - daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c | 2 +- - 2 files changed, 2 insertions(+), 2 deletions(-) - -diff --git a/daemons/ipa-kdb/ipa_kdb.c b/daemons/ipa-kdb/ipa_kdb.c -index 00c732624b..20967316ed 100644 ---- a/daemons/ipa-kdb/ipa_kdb.c -+++ b/daemons/ipa-kdb/ipa_kdb.c -@@ -110,7 +110,7 @@ static char *ipadb_realm_to_ldapi_uri(char *realm) - /* copy path and escape '/' to '%2f' */ - for (q = LDAPIDIR; *q; q++) { - if (*q == '/') { -- strncpy(p, "%2f", 3); -+ memcpy(p, "%2f", 3); - p += 3; - } else { - *p = *q; -diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c -index db7183bf2b..61b46904ab 100644 ---- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c -+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c -@@ -1003,7 +1003,7 @@ int ipapwd_set_extradata(const char *dn, - xdata[5] = (unixtime & 0xff000000) >> 24; - - /* append the principal name */ -- strncpy(&xdata[6], principal, p_len); -+ memcpy(&xdata[6], principal, p_len); - - xdata[xd_len -1] = 0; - -From a06fb8d0f7b7c6aba942186b93d87823398f5337 Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Thu, 1 Nov 2018 11:41:29 +0100 -Subject: [PATCH] has_krbprincipalkey: avoid double free - -Set keys to NULL after free rder to avoid potential double free. - -See: https://pagure.io/freeipa/issue/7738 -Signed-off-by: Christian Heimes -Reviewed-By: Alexander Bokovoy ---- - daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c | 6 +++++- - 1 file changed, 5 insertions(+), 1 deletion(-) - -diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c -index 209d596255..3c3c7e8845 100644 ---- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c -+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c -@@ -176,7 +176,11 @@ static bool has_krbprincipalkey(Slapi_Entry *entry) { - - if (rc || (num_keys <= 0)) { - /* this one is not valid, ignore it */ -- if (keys) ipa_krb5_free_key_data(keys, num_keys); -+ if (keys) { -+ ipa_krb5_free_key_data(keys, num_keys); -+ keys = NULL; -+ num_keys = 0; -+ } - } else { - /* It exists at least this one that is valid, no need to continue */ - if (keys) ipa_krb5_free_key_data(keys, num_keys); -From 2884ab69babfd7d40f951ba814234ce4763b0cd8 Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Thu, 1 Nov 2018 11:41:41 +0100 -Subject: [PATCH] ipadb_mspac_get_trusted_domains: NULL ptr deref - -Fix potential NULL pointer deref in ipadb_mspac_get_trusted_domains(). -In theory, dn could be empty and rdn NULL. The man page for ldap_str2dn() -does not guarantee that it returns a non-empty result. - -See: https://pagure.io/freeipa/issue/7738 -Signed-off-by: Christian Heimes -Reviewed-By: Alexander Bokovoy ---- - daemons/ipa-kdb/ipa_kdb_mspac.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c -index 11e036986a..329a5c1158 100644 ---- a/daemons/ipa-kdb/ipa_kdb_mspac.c -+++ b/daemons/ipa-kdb/ipa_kdb_mspac.c -@@ -2586,6 +2586,12 @@ krb5_error_code ipadb_mspac_get_trusted_domains(struct ipadb_context *ipactx) - } - - /* We should have a single AVA in the domain RDN */ -+ if (rdn == NULL) { -+ ldap_dnfree(dn); -+ ret = EINVAL; -+ goto done; -+ } -+ - t[n].parent_name = strndup(rdn[0]->la_value.bv_val, rdn[0]->la_value.bv_len); - - ldap_dnfree(dn); -From 28b89df5ed8a9a060227433e8eeebf7eea844bb9 Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Thu, 1 Nov 2018 11:41:47 +0100 -Subject: [PATCH] ipapwd_pre_mod: NULL ptr deref - -In ipapwd_pre_mod, check userpw for NULL before dereferencing its first -element. - -See: https://pagure.io/freeipa/issue/7738 -Signed-off-by: Christian Heimes -Reviewed-By: Alexander Bokovoy ---- - daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c -index 3c3c7e8845..9aef2f7d7d 100644 ---- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c -+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c -@@ -766,7 +766,7 @@ static int ipapwd_pre_mod(Slapi_PBlock *pb) - /* Check this is a clear text password, or refuse operation (only if we need - * to comput other hashes */ - if (! unhashedpw && (gen_krb_keys || is_smb || is_ipant)) { -- if ('{' == userpw[0]) { -+ if ((userpw != NULL) && ('{' == userpw[0])) { - if (0 == strncasecmp(userpw, "{CLEAR}", strlen("{CLEAR}"))) { - unhashedpw = slapi_ch_strdup(&userpw[strlen("{CLEAR}")]); - if (NULL == unhashedpw) { -From 5abe3d9feff3c0e66a43fa3799611521f83ee893 Mon Sep 17 00:00:00 2001 -From: Alexander Bokovoy -Date: Wed, 7 Nov 2018 11:57:53 +0200 -Subject: [PATCH] ipaserver.install.adtrust: fix CID 323644 - -Fix Coverity finding CID 323644: logically dead code path - -The code to determine whether NetBIOS name was already set or need to be -set after deriving it from a domain or asking a user for an interactive -input, was refactored at some point to avoid retrieving the whole LDAP -entry. Instead, it was provided with the actual NetBIOS name retrieved. - -As result, a part of the code got neglected and was never executed. - -Fix this code and provide a test that tries to test predefined, -interactively provided and automatically derived NetBIOS name depending -on how the installer is being run. - -We mock up the actual execution so that no access to LDAP or Samba is -needed. - -Backport to ipa-4-7 takes into account Python 2.7 differences: - - uses mock instead of unittest.mock if the latter is not available - - derives ApiMockup from object - -Fixes: https://pagure.io/freeipa/issue/7753 -Reviewed-By: Christian Heimes -(cherry picked from commit 82af034023b03ae64f005c8160b9e961e7b9fd55) - -Reviewed-By: Christian Heimes ---- - ipaserver/install/adtrust.py | 3 +- - .../test_ipaserver/test_adtrust_mockup.py | 58 +++++++++++++++++++ - 2 files changed, 59 insertions(+), 2 deletions(-) - create mode 100644 ipatests/test_ipaserver/test_adtrust_mockup.py - -diff --git a/ipaserver/install/adtrust.py b/ipaserver/install/adtrust.py -index e9ae3fa3ed..75194eed8f 100644 ---- a/ipaserver/install/adtrust.py -+++ b/ipaserver/install/adtrust.py -@@ -95,7 +95,6 @@ def set_and_check_netbios_name(netbios_name, unattended, api): - cur_netbios_name = None - gen_netbios_name = None - reset_netbios_name = False -- entry = None - - if api.Backend.ldap2.isconnected(): - cur_netbios_name = retrieve_netbios_name(api) -@@ -133,7 +132,7 @@ def set_and_check_netbios_name(netbios_name, unattended, api): - gen_netbios_name = adtrustinstance.make_netbios_name( - api.env.domain) - -- if entry is not None: -+ if gen_netbios_name is not None: - # Fix existing trust configuration - print("Trust is configured but no NetBIOS domain name found, " - "setting it now.") -diff --git a/ipatests/test_ipaserver/test_adtrust_mockup.py b/ipatests/test_ipaserver/test_adtrust_mockup.py -new file mode 100644 -index 0000000000..614a06f8c8 ---- /dev/null -+++ b/ipatests/test_ipaserver/test_adtrust_mockup.py -@@ -0,0 +1,58 @@ -+# Copyright (C) 2018 FreeIPA Project Contributors - see LICENSE file -+ -+from __future__ import print_function -+import ipaserver.install.adtrust as adtr -+from ipaserver.install.adtrust import set_and_check_netbios_name -+from collections import namedtuple -+from unittest import TestCase -+try: -+ from unittest import mock -+except ImportError: -+ import mock -+from io import StringIO -+ -+ -+class ApiMockup(object): -+ Backend = namedtuple('Backend', 'ldap2') -+ Calls = namedtuple('Callbacks', 'retrieve_netbios_name') -+ env = namedtuple('Environment', 'domain') -+ -+ -+class TestNetbiosName(TestCase): -+ @classmethod -+ def setUpClass(cls): -+ api = ApiMockup() -+ ldap2 = namedtuple('LDAP', 'isconnected') -+ ldap2.isconnected = mock.MagicMock(return_value=True) -+ api.Backend.ldap2 = ldap2 -+ api.Calls.retrieve_netbios_name = adtr.retrieve_netbios_name -+ adtr.retrieve_netbios_name = mock.MagicMock(return_value=None) -+ cls.api = api -+ -+ @classmethod -+ def tearDownClass(cls): -+ adtr.retrieve_netbios_name = cls.api.Calls.retrieve_netbios_name -+ -+ def test_NetbiosName(self): -+ """ -+ Test set_and_check_netbios_name() using permutation of two inputs: -+ - predefined and not defined NetBIOS name -+ - unattended and interactive run -+ As result, the function has to return expected NetBIOS name in -+ all cases. For interactive run we override input to force what -+ we expect. -+ """ -+ self.api.env.domain = 'example.com' -+ expected_nname = 'EXAMPLE' -+ # NetBIOS name, unattended, should set the name? -+ tests = ((expected_nname, True, False), -+ (None, True, True), -+ (None, False, True), -+ (expected_nname, False, False)) -+ with mock.patch('sys.stdin', new_callable=StringIO) as stdin: -+ stdin.write(expected_nname + '\r') -+ for test in tests: -+ nname, setname = set_and_check_netbios_name( -+ test[0], test[1], self.api) -+ assert expected_nname == nname -+ assert setname == test[2] -From 48a6048be2a3c6cf496a67a2732b8aaee91af620 Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Thu, 8 Nov 2018 10:42:43 +0100 -Subject: [PATCH] Copy-paste error in permssions plugin, CID 323649 - -Address a bug in the code block for attributeLevelRights for old clients. -The backward compatibility code for deprecated options was not triggered, -because the new name was checked against wrong dict. - -Coverity Scan issue 323649, Copy-paste error - - The copied code will not have its intended effect. - In postprocess_result: A copied piece of code is inconsistent with the - original (CWE-398) - -See: Fixes: https://pagure.io/freeipa/issue/7753 -Signed-off-by: Christian Heimes -Reviewed-By: Alexander Bokovoy ---- - ipaserver/plugins/permission.py | 2 +- - ipatests/test_xmlrpc/test_old_permission_plugin.py | 4 ++-- - 2 files changed, 3 insertions(+), 3 deletions(-) - -diff --git a/ipaserver/plugins/permission.py b/ipaserver/plugins/permission.py -index 2127d8234e..8ffe01bd88 100644 ---- a/ipaserver/plugins/permission.py -+++ b/ipaserver/plugins/permission.py -@@ -486,7 +486,7 @@ def postprocess_result(self, entry, options): - - if old_client: - for old_name, new_name in _DEPRECATED_OPTION_ALIASES.items(): -- if new_name in entry: -+ if new_name in rights: - rights[old_name] = rights[new_name] - del rights[new_name] - -diff --git a/ipatests/test_xmlrpc/test_old_permission_plugin.py b/ipatests/test_xmlrpc/test_old_permission_plugin.py -index 6d1117b6b3..600e449421 100644 ---- a/ipatests/test_xmlrpc/test_old_permission_plugin.py -+++ b/ipatests/test_xmlrpc/test_old_permission_plugin.py -@@ -73,8 +73,8 @@ - 'ipapermbindruletype': u'rscwo', - 'ipapermdefaultattr': u'rscwo', - 'ipapermexcludedattr': u'rscwo', -- 'ipapermlocation': u'rscwo', -- 'ipapermright': u'rscwo', -+ 'subtree': u'rscwo', # old -+ 'permissions': u'rscwo', # old - 'ipapermtarget': u'rscwo', - 'ipapermtargetfilter': u'rscwo', - 'ipapermtargetto': u'rscwo', diff --git a/SOURCES/0011-adtrust-avoid-using-timestamp-in-klist-output_ed1c1626-rhbz#1750242.patch b/SOURCES/0011-adtrust-avoid-using-timestamp-in-klist-output_ed1c1626-rhbz#1750242.patch new file mode 100644 index 0000000..eb61338 --- /dev/null +++ b/SOURCES/0011-adtrust-avoid-using-timestamp-in-klist-output_ed1c1626-rhbz#1750242.patch @@ -0,0 +1,50 @@ +From ed1c1626a607a5292c08836d13c32464d1b71859 Mon Sep 17 00:00:00 2001 +From: Alexander Bokovoy +Date: Mon, 9 Sep 2019 11:02:29 +0300 +Subject: [PATCH] adtrust: avoid using timestamp in klist output + +When parsing a keytab to copy keys to a different keytab, we don't need +the timestamp, so don't ask klist to output it. In some locales (en_IN, +for example), the timestamp is output in a single field without a space +between date and time. In other locales it can be represented with date +and time separated by a space. + +Fixes: https://pagure.io/freeipa/issue/8066 +Reviewed-By: Thomas Woerner +--- + ipaserver/install/plugins/adtrust.py | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/ipaserver/install/plugins/adtrust.py b/ipaserver/install/plugins/adtrust.py +index 28932e6c52..3b2e49bc05 100644 +--- a/ipaserver/install/plugins/adtrust.py ++++ b/ipaserver/install/plugins/adtrust.py +@@ -721,7 +721,7 @@ def execute(self, **options): + + + KeyEntry = namedtuple('KeyEntry', +- ['kvno', 'date', 'time', 'principal', 'etype', 'key']) ++ ['kvno', 'principal', 'etype', 'key']) + + + @register() +@@ -741,7 +741,7 @@ class update_host_cifs_keytabs(Updater): + def extract_key_refs(self, keytab): + host_princ = self.host_princ_template.format( + master=self.api.env.host, realm=self.api.env.realm) +- result = ipautil.run([paths.KLIST, "-etK", "-k", keytab], ++ result = ipautil.run([paths.KLIST, "-eK", "-k", keytab], + capture_output=True, raiseonerr=False, + nolog_output=True) + if result.returncode != 0: +@@ -752,8 +752,8 @@ def extract_key_refs(self, keytab): + if (host_princ in l and any(e in l for e in self.valid_etypes)): + + els = l.split() +- els[4] = els[4].strip('()') +- els[5] = els[5].strip('()') ++ els[-2] = els[-2].strip('()') ++ els[-1] = els[-1].strip('()') + keys_to_sync.append(KeyEntry._make(els)) + + return keys_to_sync diff --git a/SOURCES/0012-add-default-access-control-configuration-to-trusted-domain-objects_rhbz#1751707.patch b/SOURCES/0012-add-default-access-control-configuration-to-trusted-domain-objects_rhbz#1751707.patch new file mode 100644 index 0000000..beffd5d --- /dev/null +++ b/SOURCES/0012-add-default-access-control-configuration-to-trusted-domain-objects_rhbz#1751707.patch @@ -0,0 +1,151 @@ +From 0deea83e93665404bb536d181ae54ad7cff45336 Mon Sep 17 00:00:00 2001 +From: Alexander Bokovoy +Date: Sep 13 2019 07:34:35 +0000 +Subject: add default access control when migrating trust objects + + +It looks like for some cases we do not have proper set up keytab +retrieval configuration in the old trusted domain object. This mostly +affects two-way trust cases. In such cases, create default configuration +as ipasam would have created when trust was established. + +Resolves: https://pagure.io/freeipa/issue/8067 + +Signed-off-by: Alexander Bokovoy +Reviewed-By: Florence Blanc-Renaud + +--- + +diff --git a/ipaserver/install/plugins/adtrust.py b/ipaserver/install/plugins/adtrust.py +index 3b2e49b..7e6b5c3 100644 +--- a/ipaserver/install/plugins/adtrust.py ++++ b/ipaserver/install/plugins/adtrust.py +@@ -29,6 +29,9 @@ logger = logging.getLogger(__name__) + register = Registry() + + DEFAULT_ID_RANGE_SIZE = 200000 ++trust_read_keys_template = \ ++ ["cn=adtrust agents,cn=sysaccounts,cn=etc,{basedn}", ++ "cn=trust admins,cn=groups,cn=accounts,{basedn}"] + + + @register() +@@ -576,8 +579,15 @@ class update_tdo_to_new_layout(Updater): + 'krbprincipalkey') + entry_data['krbextradata'] = en.single_value.get( + 'krbextradata') +- entry_data['ipaAllowedToPerform;read_keys'] = en.get( +- 'ipaAllowedToPerform;read_keys', []) ++ read_keys = en.get('ipaAllowedToPerform;read_keys', []) ++ if not read_keys: ++ # Old style, no ipaAllowedToPerform;read_keys in the entry, ++ # use defaults that ipasam should have set when creating a ++ # trust ++ read_keys = list(map( ++ lambda x: x.format(basedn=self.api.env.basedn), ++ trust_read_keys_template)) ++ entry_data['ipaAllowedToPerform;read_keys'] = read_keys + + entry.update(entry_data) + try: + +From b32510d67d2bd64e77659c6766d3f9647629acec Mon Sep 17 00:00:00 2001 +From: Alexander Bokovoy +Date: Sep 13 2019 07:34:35 +0000 +Subject: adtrust: add default read_keys permission for TDO objects + + +If trusted domain object (TDO) is lacking ipaAllowedToPerform;read_keys +attribute values, it cannot be used by SSSD to retrieve TDO keys and the +whole communication with Active Directory domain controllers will not be +possible. + +This seems to affect trusts which were created before +ipaAllowedToPerform;read_keys permission granting was introduced +(FreeIPA 4.2). Add back the default setting for the permissions which +grants access to trust agents and trust admins. + +Resolves: https://pagure.io/freeipa/issue/8067 + +Signed-off-by: Alexander Bokovoy +Reviewed-By: Florence Blanc-Renaud + +--- + +diff --git a/install/updates/90-post_upgrade_plugins.update b/install/updates/90-post_upgrade_plugins.update +index f5f428d..8eb1977 100644 +--- a/install/updates/90-post_upgrade_plugins.update ++++ b/install/updates/90-post_upgrade_plugins.update +@@ -13,6 +13,7 @@ plugin: update_default_trust_view + plugin: update_tdo_gidnumber + plugin: update_tdo_to_new_layout + plugin: update_host_cifs_keytabs ++plugin: update_tdo_default_read_keys_permissions + plugin: update_ca_renewal_master + plugin: update_idrange_type + plugin: update_pacs +diff --git a/ipaserver/install/plugins/adtrust.py b/ipaserver/install/plugins/adtrust.py +index 7e6b5c3..386fe53 100644 +--- a/ipaserver/install/plugins/adtrust.py ++++ b/ipaserver/install/plugins/adtrust.py +@@ -821,3 +821,59 @@ class update_host_cifs_keytabs(Updater): + self.copy_key(paths.SAMBA_KEYTAB, hostkey) + + return False, [] ++ ++ ++@register() ++class update_tdo_default_read_keys_permissions(Updater): ++ trust_filter = \ ++ "(&(objectClass=krbPrincipal)(krbPrincipalName=krbtgt/{nbt}@*))" ++ ++ def execute(self, **options): ++ ldap = self.api.Backend.ldap2 ++ ++ # First, see if trusts are enabled on the server ++ if not self.api.Command.adtrust_is_enabled()['result']: ++ logger.debug('AD Trusts are not enabled on this server') ++ return False, [] ++ ++ result = self.api.Command.trustconfig_show()['result'] ++ our_nbt_name = result.get('ipantflatname', [None])[0] ++ if not our_nbt_name: ++ return False, [] ++ ++ trusts_dn = self.api.env.container_adtrusts + self.api.env.basedn ++ trust_filter = self.trust_filter.format(nbt=our_nbt_name) ++ ++ # We might be in a situation when no trusts exist yet ++ # In such case there is nothing to upgrade but we have to catch ++ # an exception or it will abort the whole upgrade process ++ try: ++ tdos = ldap.get_entries( ++ base_dn=trusts_dn, ++ scope=ldap.SCOPE_SUBTREE, ++ filter=trust_filter, ++ attrs_list=['*']) ++ except errors.EmptyResult: ++ tdos = [] ++ ++ for tdo in tdos: ++ updates = dict() ++ oc = tdo.get('objectClass', []) ++ if 'ipaAllowedOperations' not in oc: ++ updates['objectClass'] = oc + ['ipaAllowedOperations'] ++ ++ read_keys = tdo.get('ipaAllowedToPerform;read_keys', []) ++ if not read_keys: ++ read_keys_values = list(map( ++ lambda x: x.format(basedn=self.api.env.basedn), ++ trust_read_keys_template)) ++ updates['ipaAllowedToPerform;read_keys'] = read_keys_values ++ ++ tdo.update(updates) ++ try: ++ ldap.update_entry(tdo) ++ except errors.EmptyModlist: ++ logger.debug("No update was required for TDO %s", ++ tdo.single_value.get('krbCanonicalName')) ++ ++ return False, [] + diff --git a/SOURCES/0012-ipa-replica-install_--setup-adtrust_check_for_package_ipa-server-trust-ad_rhbz#1658294.patch b/SOURCES/0012-ipa-replica-install_--setup-adtrust_check_for_package_ipa-server-trust-ad_rhbz#1658294.patch deleted file mode 100644 index c5bc02d..0000000 --- a/SOURCES/0012-ipa-replica-install_--setup-adtrust_check_for_package_ipa-server-trust-ad_rhbz#1658294.patch +++ /dev/null @@ -1,65 +0,0 @@ -From be968ea01adf1721b0afd7393872a8d311d89d0c Mon Sep 17 00:00:00 2001 -From: Florence Blanc-Renaud -Date: Oct 24 2018 14:21:47 +0000 -Subject: ipa-replica-install --setup-adtrust: check for package ipa-server-trust-ad - - -When adding the option --setup-adtrust to ipa-replica-install, -we need to check that the package freeipa-server-trust-ad is -installed. -To avoid relying on OS-specific commands like yum, the check is instead -ensuring that the file /usr/share/ipa/smb.conf.empty is present -(this file is delivered by the package). -When the check is unsuccessful, ipa-replica-install exits with an error -message. - -Fixes: https://pagure.io/freeipa/issue/7602 -Reviewed-By: Alexander Bokovoy - ---- - -diff --git a/ipaplatform/base/constants.py b/ipaplatform/base/constants.py -index be832fe..c67b991 100644 ---- a/ipaplatform/base/constants.py -+++ b/ipaplatform/base/constants.py -@@ -15,6 +15,7 @@ class BaseConstantsNamespace(object): - HTTPD_USER = "apache" - HTTPD_GROUP = "apache" - GSSPROXY_USER = "root" -+ IPA_ADTRUST_PACKAGE_NAME = "freeipa-server-trust-ad" - IPA_DNS_PACKAGE_NAME = "freeipa-server-dns" - KDCPROXY_USER = "kdcproxy" - NAMED_USER = "named" -diff --git a/ipaplatform/rhel/constants.py b/ipaplatform/rhel/constants.py -index 945f3dc..72335ac 100644 ---- a/ipaplatform/rhel/constants.py -+++ b/ipaplatform/rhel/constants.py -@@ -13,6 +13,7 @@ from ipaplatform.redhat.constants import RedHatConstantsNamespace - - - class RHELConstantsNamespace(RedHatConstantsNamespace): -+ IPA_ADTRUST_PACKAGE_NAME = "ipa-server-trust-ad" - IPA_DNS_PACKAGE_NAME = "ipa-server-dns" - - constants = RHELConstantsNamespace() -diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py -index 3a751cc..67317ee 100644 ---- a/ipaserver/install/adtrustinstance.py -+++ b/ipaserver/install/adtrustinstance.py -@@ -72,6 +72,15 @@ def check_inst(): - "start the installation again") - return False - -+ # Check that ipa-server-trust-ad package is installed, -+ # by looking for the file /usr/share/ipa/smb.conf.empty -+ if not os.path.exists(os.path.join(paths.USR_SHARE_IPA_DIR, -+ "smb.conf.empty")): -+ print("AD Trust requires the '%s' package" % -+ constants.IPA_ADTRUST_PACKAGE_NAME) -+ print("Please install the package and start the installation again") -+ return False -+ - #TODO: Add check for needed samba4 libraries - - return True - diff --git a/SOURCES/0013-ipaldap_invalid_modlist_when_attribute_encoding_can_vary_rhbz#1658302.patch b/SOURCES/0013-ipaldap_invalid_modlist_when_attribute_encoding_can_vary_rhbz#1658302.patch deleted file mode 100644 index 270cbdf..0000000 --- a/SOURCES/0013-ipaldap_invalid_modlist_when_attribute_encoding_can_vary_rhbz#1658302.patch +++ /dev/null @@ -1,72 +0,0 @@ -From 9e7e9c1014c10f838b341a45436aba0840ad5b07 Mon Sep 17 00:00:00 2001 -From: Fraser Tweedale -Date: Nov 07 2018 13:51:59 +0000 -Subject: ipaldap: avoid invalid modlist when attribute encoding differs - - -ipaldap does not take into account the possibility of the attribute -encoding returned by python-ldap differing from the attribute -encoding produced by FreeIPA. In particular this can occur with DNs -with special characters that require escaping. For example, -python-ldap (or the underlying LDAP library) escapes special -characters using hex encoding: - - CN=Test Sub-CA 201604041620,OU=ftweedal,O=Red Hat\2C Inc.,L=Brisbane,C=AU - -Whereas FreeIPA, when encoding the DN, escapes the character -directly: - - CN=Test Sub-CA 201604041620,OU=ftweedal,O=Red Hat\, Inc.,L=Brisbane,C=AU - -Therefore it is possible to generate an invalid modlist. For -example, during external CA certificate renewal, if the issuer DN -includes a comma in one of the attribute values (as above), an -invalid modlist will be generated: - - [ (ldap.MOD_ADD, 'ipacaissuerdn', - [b'CN=Test Sub-CA 201604041620,OU=ftweedal,O=Red Hat\, Inc.,L=Brisbane,C=AU']) - , (ldap.MOD_DELETE, 'ipacaissuerdn', - [b'CN=Test Sub-CA 201604041620,OU=ftweedal,O=Red Hat\2C Inc.,L=Brisbane,C=AU']) - ] - -Although encoded differently, these are the same value. If this -modification is applied to the object, attributeOrValueExists (error -20) occurs. - -To avoid the issue, put deletes before adds in the modlist. If a -value is present (with different encodings) as both an addition and -a deletion, it must be because the original object contained the -value with a different encoding. Therefore it is safe to delete it, -then add it back. - -Note that the modlist is not optimal. In the simplest case (like -above example), there should be no modification to perform. It is -considerably more complex (and more computation) to implement this -because the raw attribute values must be decoded before comparison. - -Fixes: https://pagure.io/freeipa/issue/7750 -Reviewed-By: Christian Heimes - ---- - -diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py -index fbc824e..cf1e60b 100644 ---- a/ipapython/ipaldap.py -+++ b/ipapython/ipaldap.py -@@ -565,10 +565,13 @@ class LDAPEntry(MutableMapping): - raise errors.OnlyOneValueAllowed(attr=name) - modlist.append((ldap.MOD_REPLACE, name, adds)) - else: -- if adds: -- modlist.append((ldap.MOD_ADD, name, adds)) -+ # dels before adds, in case the same value occurs in -+ # both due to encoding differences -+ # (https://pagure.io/freeipa/issue/7750) - if dels: - modlist.append((ldap.MOD_DELETE, name, dels)) -+ if adds: -+ modlist.append((ldap.MOD_ADD, name, adds)) - - # Usually the modlist order does not matter. - # However, for schema updates, we want 'attributetypes' before - diff --git a/SOURCES/0014-Allow_ipaapi_and_Apache_user_to_access_SSSD_IFP_rhbz#1639910.patch b/SOURCES/0014-Allow_ipaapi_and_Apache_user_to_access_SSSD_IFP_rhbz#1639910.patch deleted file mode 100644 index 9a6f2b7..0000000 --- a/SOURCES/0014-Allow_ipaapi_and_Apache_user_to_access_SSSD_IFP_rhbz#1639910.patch +++ /dev/null @@ -1,816 +0,0 @@ -From 785c496dceb76a8f628249ce598e0540b1dfec6e Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Tue, 6 Nov 2018 13:57:14 +0100 -Subject: [PATCH] Allow ipaapi user to access SSSD's info pipe - -For smart card authentication, ipaapi must be able to access to sss-ifp. -During installation and upgrade, the ipaapi user is now added to -[ifp]allowed_uids. - -The commit also fixes two related issues: - -* The server upgrade code now enables ifp service in sssd.conf. The - existing code modified sssd.conf but never wrote the changes to disk. -* sssd_enable_service() no longer fails after it has detected an - unrecognized service. - -Fixes: https://pagure.io/freeipa/issue/7751 -Signed-off-by: Christian Heimes -Reviewed-By: Rob Crittenden ---- - ipaclient/install/client.py | 41 ++++++++++++++++++---- - ipaserver/install/server/upgrade.py | 27 +++++++++----- - ipatests/test_integration/test_commands.py | 31 ++++++++++++++++ - 3 files changed, 83 insertions(+), 16 deletions(-) - -diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py -index 05255fe61b..f9b003ef57 100644 ---- a/ipaclient/install/client.py -+++ b/ipaclient/install/client.py -@@ -35,6 +35,7 @@ - # pylint: enable=import-error - - from ipalib import api, errors, x509 -+from ipalib.constants import IPAAPI_USER - from ipalib.install import certmonger, certstore, service, sysrestore - from ipalib.install import hostname as hostname_ - from ipalib.install.kinit import kinit_keytab, kinit_password -@@ -914,7 +915,7 @@ def configure_sssd_conf( - domain = sssdconfig.new_domain(cli_domain) - - if options.on_master: -- sssd_enable_service(sssdconfig, 'ifp') -+ sssd_enable_ifp(sssdconfig) - - if ( - (options.conf_ssh and os.path.isfile(paths.SSH_CONFIG)) or -@@ -1018,21 +1019,47 @@ def configure_sssd_conf( - return 0 - - --def sssd_enable_service(sssdconfig, service): -+def sssd_enable_service(sssdconfig, name): - try: -- sssdconfig.new_service(service) -+ sssdconfig.new_service(name) - except SSSDConfig.ServiceAlreadyExists: - pass - except SSSDConfig.ServiceNotRecognizedError: - logger.error( -- "Unable to activate the %s service in SSSD config.", service) -+ "Unable to activate the '%s' service in SSSD config.", name) - logger.info( - "Please make sure you have SSSD built with %s support " -- "installed.", service) -+ "installed.", name) - logger.info( -- "Configure %s support manually in /etc/sssd/sssd.conf.", service) -+ "Configure %s support manually in /etc/sssd/sssd.conf.", name) -+ return None - -- sssdconfig.activate_service(service) -+ sssdconfig.activate_service(name) -+ return sssdconfig.get_service(name) -+ -+ -+def sssd_enable_ifp(sssdconfig): -+ """Enable and configure libsss_simpleifp plugin -+ """ -+ service = sssd_enable_service(sssdconfig, 'ifp') -+ if service is None: -+ # unrecognized service -+ return -+ -+ try: -+ uids = service.get_option('allowed_uids') -+ except SSSDConfig.NoOptionError: -+ uids = set() -+ else: -+ uids = {s.strip() for s in uids.split(',') if s.strip()} -+ # SSSD supports numeric and string UIDs -+ # ensure that root is allowed to access IFP, might be 0 or root -+ if uids.isdisjoint({'0', 'root'}): -+ uids.add('root') -+ # allow IPA API to access IFP -+ uids.add(IPAAPI_USER) -+ service.set_option('allowed_uids', ', '.join(sorted(uids))) -+ sssdconfig.save_service(service) - - - def change_ssh_config(filename, changes, sections): -diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py -index 96c95b7a07..698afd347e 100644 ---- a/ipaserver/install/server/upgrade.py -+++ b/ipaserver/install/server/upgrade.py -@@ -23,7 +23,7 @@ - import ipalib.util - import ipalib.errors - from ipaclient.install import timeconf --from ipaclient.install.client import sssd_enable_service -+from ipaclient.install.client import sssd_enable_ifp - from ipaplatform import services - from ipaplatform.tasks import tasks - from ipapython import ipautil, version -@@ -1408,6 +1408,22 @@ def set_sssd_domain_option(option, value): - sssdconfig.write(paths.SSSD_CONF) - - -+def sssd_update(): -+ sssdconfig = SSSDConfig.SSSDConfig() -+ sssdconfig.import_config() -+ # upgrade domain -+ domain = sssdconfig.get_domain(str(api.env.domain)) -+ domain.set_option('ipa_server_mode', 'True') -+ domain.set_option('ipa_server', api.env.host) -+ sssdconfig.save_domain(domain) -+ # enable and configure IFP plugin -+ sssd_enable_ifp(sssdconfig) -+ # write config and restart service -+ sssdconfig.write(paths.SSSD_CONF) -+ sssd = services.service('sssd', api) -+ sssd.restart() -+ -+ - def remove_ds_ra_cert(subject_base): - logger.info('[Removing RA cert from DS NSS database]') - -@@ -2017,15 +2033,8 @@ def upgrade_configuration(): - cainstance.ensure_ipa_authority_entry() - - migrate_to_authselect() -- set_sssd_domain_option('ipa_server_mode', 'True') -- set_sssd_domain_option('ipa_server', api.env.host) - -- sssdconfig = SSSDConfig.SSSDConfig() -- sssdconfig.import_config() -- sssd_enable_service(sssdconfig, 'ifp') -- -- sssd = services.service('sssd', api) -- sssd.restart() -+ sssd_update() - - krb = krbinstance.KrbInstance(fstore) - krb.fqdn = fqdn -diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py -index 640eacfa06..1aa1bb3313 100644 ---- a/ipatests/test_integration/test_commands.py -+++ b/ipatests/test_integration/test_commands.py -@@ -20,6 +20,8 @@ - from cryptography.hazmat.backends import default_backend - from cryptography import x509 - -+from ipalib.constants import IPAAPI_USER -+ - from ipaplatform.paths import paths - - from ipatests.test_integration.base import IntegrationTest -@@ -28,6 +30,7 @@ - - logger = logging.getLogger(__name__) - -+ - class TestIPACommand(IntegrationTest): - """ - A lot of commands can be executed against a single IPA installation -@@ -429,3 +432,31 @@ def test_certificate_out_write_to_file(self): - x509.load_pem_x509_certificate(data, backend=default_backend()) - - self.master.run_command(['rm', '-f', filename]) -+ -+ def test_sssd_ifp_access_ipaapi(self): -+ # check that ipaapi is allowed to access sssd-ifp for smartcard auth -+ # https://pagure.io/freeipa/issue/7751 -+ username = 'admin' -+ # get UID for user -+ result = self.master.run_command(['ipa', 'user-show', username]) -+ mo = re.search(r'UID: (\d+)', result.stdout_text) -+ assert mo is not None, result.stdout_text -+ uid = mo.group(1) -+ -+ cmd = [ -+ 'dbus-send', -+ '--print-reply', '--system', -+ '--dest=org.freedesktop.sssd.infopipe', -+ '/org/freedesktop/sssd/infopipe/Users', -+ 'org.freedesktop.sssd.infopipe.Users.FindByName', -+ 'string:{}'.format(username) -+ ] -+ # test IFP as root -+ result = self.master.run_command(cmd) -+ assert uid in result.stdout_text -+ -+ # test IFP as ipaapi -+ result = self.master.run_command( -+ ['sudo', '-u', IPAAPI_USER, '--'] + cmd -+ ) -+ assert uid in result.stdout_text -From eb0136ea3438b6fb1145456478f401b9b7467cba Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Fri, 16 Nov 2018 14:11:16 +0100 -Subject: [PATCH] Remove dead code - -set_sssd_domain_option() is no longer used. Changes are handled by -sssd_update(). - -See: https://pagure.io/freeipa/issue/7751 -Reviewed-By: Alexander Bokovoy -Reviewed-By: Rob Crittenden ---- - ipaserver/install/server/upgrade.py | 9 --------- - 1 file changed, 9 deletions(-) - -diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py -index f1e78beb27..71bdd3670c 100644 ---- a/ipaserver/install/server/upgrade.py -+++ b/ipaserver/install/server/upgrade.py -@@ -1399,15 +1399,6 @@ def fix_schema_file_syntax(): - sysupgrade.set_upgrade_state('ds', 'fix_schema_syntax', True) - - --def set_sssd_domain_option(option, value): -- sssdconfig = SSSDConfig.SSSDConfig() -- sssdconfig.import_config() -- domain = sssdconfig.get_domain(str(api.env.domain)) -- domain.set_option(option, value) -- sssdconfig.save_domain(domain) -- sssdconfig.write(paths.SSSD_CONF) -- -- - def sssd_update(): - sssdconfig = SSSDConfig.SSSDConfig() - sssdconfig.import_config() -From 415295a6f68f4c797529e19a3f0cf956619d4bed Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Fri, 16 Nov 2018 14:51:23 +0100 -Subject: [PATCH] Allow HTTPd user to access SSSD IFP - -For smart card and certificate authentication, Apache's -mod_lookup_identity module must be able to acess SSSD IFP. The module -accesses IFP as Apache user, not as ipaapi user. - -Apache is not allowed to use IFP by default. The update code uses the -service's ok-to-auth-as-delegate flag to detect smart card / cert auth. - -See: https://pagure.io/freeipa/issue/7751 -Signed-off-by: Christian Heimes -Reviewed-By: Alexander Bokovoy -Reviewed-By: Rob Crittenden ---- - ipaclient/install/client.py | 10 +++++++++- - ipaserver/install/server/upgrade.py | 11 ++++++++++- - 2 files changed, 19 insertions(+), 2 deletions(-) - -diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py -index f9b003ef57..6125588802 100644 ---- a/ipaclient/install/client.py -+++ b/ipaclient/install/client.py -@@ -47,6 +47,7 @@ - verify_host_resolvable, - ) - from ipaplatform import services -+from ipaplatform.constants import constants - from ipaplatform.paths import paths - from ipaplatform.tasks import tasks - from ipapython import certdb, kernel_keyring, ipaldap, ipautil -@@ -1038,8 +1039,13 @@ def sssd_enable_service(sssdconfig, name): - return sssdconfig.get_service(name) - - --def sssd_enable_ifp(sssdconfig): -+def sssd_enable_ifp(sssdconfig, allow_httpd=False): - """Enable and configure libsss_simpleifp plugin -+ -+ Allow the ``ipaapi`` user to access IFP. In case allow_httpd is true, -+ the Apache HTTPd user is also allowed to access IFP. For smart card -+ authentication, mod_lookup_identity must be allowed to access user -+ information. - """ - service = sssd_enable_service(sssdconfig, 'ifp') - if service is None: -@@ -1058,6 +1064,8 @@ def sssd_enable_ifp(sssdconfig): - uids.add('root') - # allow IPA API to access IFP - uids.add(IPAAPI_USER) -+ if allow_httpd: -+ uids.add(constants.HTTPD_USER) - service.set_option('allowed_uids', ', '.join(sorted(uids))) - sssdconfig.save_service(service) - -diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py -index 71bdd3670c..4de7fd974d 100644 ---- a/ipaserver/install/server/upgrade.py -+++ b/ipaserver/install/server/upgrade.py -@@ -1407,8 +1407,17 @@ def sssd_update(): - domain.set_option('ipa_server_mode', 'True') - domain.set_option('ipa_server', api.env.host) - sssdconfig.save_domain(domain) -+ # check if service has ok_to_auth_as_delegate -+ service = 'HTTP/{}'.format(api.env.host) -+ result = api.Command.service_show(service, all=True) -+ flag = result['result'].get('ipakrboktoauthasdelegate', False) -+ if flag: -+ logger.debug( -+ "%s has ok_to_auth_as_delegate, allow Apache to access IFP", -+ services -+ ) - # enable and configure IFP plugin -- sssd_enable_ifp(sssdconfig) -+ sssd_enable_ifp(sssdconfig, allow_httpd=flag) - # write config and restart service - sssdconfig.write(paths.SSSD_CONF) - sssd = services.service('sssd', api) -From d7d17ece57ae1322c8368b7853f24d56b1d6a150 Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Fri, 16 Nov 2018 14:54:32 +0100 -Subject: [PATCH] Smart card auth advise: Allow Apache user - -Modify the smard card auth advise script to use sssd_enable_ifp() in -order to allow Apache to access SSSD IFP. - -See: https://pagure.io/freeipa/issue/7751 -Signed-off-by: Christian Heimes -Reviewed-By: Alexander Bokovoy -Reviewed-By: Rob Crittenden ---- - ipaserver/advise/plugins/smart_card_auth.py | 21 ++++++++++++++++++++- - 1 file changed, 20 insertions(+), 1 deletion(-) - -diff --git a/ipaserver/advise/plugins/smart_card_auth.py b/ipaserver/advise/plugins/smart_card_auth.py -index 97e23303b0..9a7a315ed5 100644 ---- a/ipaserver/advise/plugins/smart_card_auth.py -+++ b/ipaserver/advise/plugins/smart_card_auth.py -@@ -105,6 +105,7 @@ class config_server_for_smart_card_auth(common_smart_card_auth_config): - ssl_conf = paths.HTTPD_SSL_CONF - ssl_ocsp_directive = OCSP_DIRECTIVE - kdc_service_name = services.knownservices.krb5kdc.systemd_name -+ httpd_service_name = services.knownservices.httpd.systemd_name - - def get_info(self): - self.log.exit_on_nonroot_euid() -@@ -117,6 +118,7 @@ def get_info(self): - self.record_httpd_ocsp_status() - self.check_and_enable_pkinit() - self.enable_ok_to_auth_as_delegate_on_http_principal() -+ self.allow_httpd_ifp() - self.upload_smartcard_ca_certificates_to_systemwide_db() - self.install_smart_card_signing_ca_certs() - self.update_ipa_ca_certificate_store() -@@ -183,7 +185,9 @@ def _format_command(self, fmt_line, directive, filename): - - def restart_httpd(self): - self.log.comment('finally restart apache') -- self.log.command('systemctl restart httpd') -+ self.log.command( -+ 'systemctl restart {}'.format(self.httpd_service_name) -+ ) - - def record_httpd_ocsp_status(self): - self.log.comment('store the OCSP upgrade state') -@@ -214,6 +218,21 @@ def enable_ok_to_auth_as_delegate_on_http_principal(self): - ["Failed to set OK_AS_AUTH_AS_DELEGATE flag on HTTP principal"] - ) - -+ def allow_httpd_ifp(self): -+ self.log.comment('Allow Apache to access SSSD IFP') -+ self.log.exit_on_failed_command( -+ '{} -c "import SSSDConfig; ' -+ 'from ipaclient.install.client import sssd_enable_ifp; ' -+ 'from ipaplatform.paths import paths; ' -+ 'c = SSSDConfig.SSSDConfig(); ' -+ 'c.import_config(); ' -+ 'sssd_enable_ifp(c, allow_httpd=True); ' -+ 'c.write(paths.SSSD_CONF)"'.format(sys.executable), -+ ['Failed to modify SSSD config'] -+ ) -+ self.log.comment('Restart sssd') -+ self.log.command('systemctl restart sssd') -+ - def restart_kdc(self): - self.log.exit_on_failed_command( - 'systemctl restart {}'.format(self.kdc_service_name), -From b56db8daa704782c44683412b85a454654eabc19 Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Mon, 19 Nov 2018 14:19:16 +0100 -Subject: [PATCH] Log stderr in run_command - -pytest_multihost's run_command() does not log stderr when a command -fails. Wrap the function call to log stderr so it's easier to debug -failing tests. - -Signed-off-by: Christian Heimes -Reviewed-By: Alexander Bokovoy -Reviewed-By: Rob Crittenden ---- - ipatests/pytest_ipa/integration/host.py | 19 +++++++++++++++++++ - 1 file changed, 19 insertions(+) - -diff --git a/ipatests/pytest_ipa/integration/host.py b/ipatests/pytest_ipa/integration/host.py -index 28f6e2cd32..6aed58ae96 100644 ---- a/ipatests/pytest_ipa/integration/host.py -+++ b/ipatests/pytest_ipa/integration/host.py -@@ -18,6 +18,7 @@ - # along with this program. If not, see . - - """Host class for integration testing""" -+import subprocess - - import pytest_multihost.host - -@@ -60,6 +61,24 @@ def to_env(self, **kwargs): - from ipatests.pytest_ipa.integration.env_config import host_to_env - return host_to_env(self, **kwargs) - -+ def run_command(self, argv, set_env=True, stdin_text=None, -+ log_stdout=True, raiseonerr=True, -+ cwd=None, bg=False, encoding='utf-8'): -+ # Wrap run_command to log stderr on raiseonerr=True -+ result = super().run_command( -+ argv, set_env=set_env, stdin_text=stdin_text, -+ log_stdout=log_stdout, raiseonerr=False, cwd=cwd, bg=bg, -+ encoding=encoding -+ ) -+ if result.returncode and raiseonerr: -+ result.log.error('stderr: %s', result.stderr_text) -+ raise subprocess.CalledProcessError( -+ result.returncode, argv, -+ result.stdout_text, result.stderr_text -+ ) -+ else: -+ return result -+ - - class WinHost(pytest_multihost.host.WinHost): - """ -From 97776d2c4eed5de73780476bb11a635a2e47ebc5 Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Wed, 21 Nov 2018 10:00:20 +0100 -Subject: [PATCH] Test smart card advise scripts - -Create and execute the server and client smart card advise scripts. - -See: See: https://pagure.io/freeipa/issue/7751 -Signed-off-by: Christian Heimes -Reviewed-By: Alexander Bokovoy -Reviewed-By: Rob Crittenden ---- - ipatests/prci_definitions/gating.yaml | 2 +- - ipatests/test_integration/test_advise.py | 99 ++++++++++++++++++------ - 2 files changed, 75 insertions(+), 26 deletions(-) - -diff --git a/ipatests/prci_definitions/gating.yaml b/ipatests/prci_definitions/gating.yaml -index 7a9b612dea..13f8851d02 100644 ---- a/ipatests/prci_definitions/gating.yaml -+++ b/ipatests/prci_definitions/gating.yaml -@@ -157,7 +157,7 @@ jobs: - test_suite: test_integration/test_advise.py - template: *ci-master-f29 - timeout: 3600 -- topology: *master_1repl -+ topology: *master_1repl_1client - - fedora-29/test_testconfig: - requires: [fedora-29/build] -diff --git a/ipatests/test_integration/test_advise.py b/ipatests/test_integration/test_advise.py -index 3b821c8797..b548614922 100644 ---- a/ipatests/test_integration/test_advise.py -+++ b/ipatests/test_integration/test_advise.py -@@ -21,11 +21,17 @@ - # pylint: disable=no-member - - import re -+ -+from ipalib.constants import IPAAPI_USER -+from ipaplatform.paths import paths -+from ipaplatform.constants import constants -+ -+from ipatests.create_external_ca import ExternalCA - from ipatests.pytest_ipa.integration import tasks - from ipatests.test_integration.base import IntegrationTest - - --def run_advice(master, advice_id, advice_regex, raiseerr): -+def run_advice(master, advice_id, advice_regex, raiseerr=True): - # Obtain the advice from the server - tasks.kinit_admin(master) - result = master.run_command(['ipa-advise', advice_id], -@@ -43,28 +49,38 @@ class TestAdvice(IntegrationTest): - """ - Tests ipa-advise output. - """ -- advice_id = None -- raiseerr = None -- advice_regex = '' - topology = 'line' -+ num_replicas = 0 -+ num_clients = 1 -+ -+ def execute_advise(self, host, advice_id, *args): -+ # ipa-advise script is only available on a server -+ tasks.kinit_admin(self.master) -+ advice = self.master.run_command(['ipa-advise', advice_id]) -+ # execute script on host (client or master) -+ if host is not self.master: -+ tasks.kinit_admin(host) -+ filename = tasks.upload_temp_contents(host, advice.stdout_text) -+ cmd = ['sh', filename] -+ cmd.extend(args) -+ try: -+ result = host.run_command(cmd) -+ finally: -+ host.run_command(['rm', '-f', filename]) -+ return advice, result - - def test_invalid_advice(self): - advice_id = r'invalid-advise-param' - advice_regex = r"invalid[\s]+\'advice\'.*" -- raiseerr = False -- -- run_advice(self.master, advice_id, advice_regex, raiseerr) -- -+ run_advice(self.master, advice_id, advice_regex, raiseerr=False) - - def test_advice_FreeBSDNSSPAM(self): - advice_id = 'config-freebsd-nss-pam-ldapd' - advice_regex = r"\#\!\/bin\/sh.*" \ - r"pkg_add[\s]+\-r[\s]+nss\-pam\-ldapd[\s]+curl.*" \ - r"\/usr\/local\/etc\/rc\.d\/nslcd[\s]+restart" -- raiseerr = True -- -- run_advice(self.master, advice_id, advice_regex, raiseerr) - -+ run_advice(self.master, advice_id, advice_regex) - - def test_advice_GenericNSSPAM(self): - advice_id = 'config-generic-linux-nss-pam-ldapd' -@@ -75,20 +91,16 @@ def test_advice_GenericNSSPAM(self): - r"service[\s]+nscd[\s]+stop[\s]+\&\&[\s]+service[\s]+" - r"nslcd[\s]+restart" - ) -- raiseerr = True -- -- run_advice(self.master, advice_id, advice_regex, raiseerr) - -+ run_advice(self.master, advice_id, advice_regex) - - def test_advice_GenericSSSDBefore19(self): - advice_id = r'config-generic-linux-sssd-before-1-9' - advice_regex = r"\#\!\/bin\/sh.*" \ - r"apt\-get[\s]+\-y[\s]+install sssd curl openssl.*" \ - r"service[\s]+sssd[\s]+start" -- raiseerr = True -- -- run_advice(self.master, advice_id, advice_regex, raiseerr) - -+ run_advice(self.master, advice_id, advice_regex) - - def test_advice_RedHatNSS(self): - advice_id = 'config-redhat-nss-ldap' -@@ -100,10 +112,8 @@ def test_advice_RedHatNSS(self): - r"[\s]+\-\-enableldapauth[\s]+" - r"\-\-ldapserver=.*[\s]+\-\-ldapbasedn=.*" - ) -- raiseerr = True -- -- run_advice(self.master, advice_id, advice_regex, raiseerr) - -+ run_advice(self.master, advice_id, advice_regex) - - def test_advice_RedHatNSSPAM(self): - advice_id = 'config-redhat-nss-pam-ldapd' -@@ -113,10 +123,8 @@ def test_advice_RedHatNSSPAM(self): - r"authconfig[\s]+\-\-updateall[\s]+\-\-enableldap"\ - r"[\s]+\-\-enableldaptls[\s]+\-\-enableldapauth[\s]+" \ - r"\-\-ldapserver=.*[\s]+\-\-ldapbasedn=.*" -- raiseerr = True -- -- run_advice(self.master, advice_id, advice_regex, raiseerr) - -+ run_advice(self.master, advice_id, advice_regex) - - def test_advice_RedHatSSSDBefore19(self): - advice_id = 'config-redhat-sssd-before-1-9' -@@ -125,6 +133,47 @@ def test_advice_RedHatSSSDBefore19(self): - r"yum[\s]+install[\s]+\-y[\s]+sssd[\s]+authconfig[\s]+" - r"curl[\s]+openssl.*service[\s]+sssd[\s]+start" - ) -- raiseerr = True - -- run_advice(self.master, advice_id, advice_regex, raiseerr) -+ run_advice(self.master, advice_id, advice_regex) -+ -+ # trivial checks -+ def test_advice_enable_admins_sudo(self): -+ advice_id = 'enable_admins_sudo' -+ advice_regex = r"\#\!\/bin\/sh.*" -+ run_advice(self.master, advice_id, advice_regex) -+ -+ def test_advice_config_server_for_smart_card_auth(self): -+ advice_id = 'config_server_for_smart_card_auth' -+ advice_regex = r"\#\!\/bin\/sh.*" -+ run_advice(self.master, advice_id, advice_regex) -+ -+ ca_pem = ExternalCA().create_ca() -+ ca_file = tasks.upload_temp_contents(self.master, ca_pem) -+ try: -+ self.execute_advise(self.master, advice_id, ca_file) -+ except Exception: -+ # debug: sometimes ipa-certupdate times out in -+ # "Resubmitting certmonger request" -+ self.master.run_command(['getcert', 'list']) -+ raise -+ finally: -+ self.master.run_command(['rm', '-f', ca_file]) -+ sssd_conf = self.master.get_file_contents( -+ paths.SSSD_CONF, encoding='utf-8' -+ ) -+ assert constants.HTTPD_USER in sssd_conf -+ assert IPAAPI_USER in sssd_conf -+ -+ def test_advice_config_client_for_smart_card_auth(self): -+ advice_id = 'config_client_for_smart_card_auth' -+ advice_regex = r"\#\!\/bin\/sh.*" -+ run_advice(self.master, advice_id, advice_regex) -+ -+ client = self.clients[0] -+ -+ ca_pem = ExternalCA().create_ca() -+ ca_file = tasks.upload_temp_contents(client, ca_pem) -+ try: -+ self.execute_advise(client, advice_id, ca_file) -+ finally: -+ client.run_command(['rm', '-f', ca_file]) -From 6ed90a2ac08c070e8e5c47a1eb3c52d7d30cabb8 Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Wed, 21 Nov 2018 10:44:55 +0100 -Subject: [PATCH] Add install/remove package helpers to advise - -The smart card advise scripts assume that yum is installed. However -Fedora has dnf and the yum wrapper is not installed by default. -Installation and removal of packages is now provided by two helper -methods that detect the package manager. - -Signed-off-by: Christian Heimes -Reviewed-By: Alexander Bokovoy -Reviewed-By: Rob Crittenden ---- - ipaserver/advise/base.py | 36 +++++++++++++++++++++ - ipaserver/advise/plugins/smart_card_auth.py | 24 +++++++------- - 2 files changed, 47 insertions(+), 13 deletions(-) - -diff --git a/ipaserver/advise/base.py b/ipaserver/advise/base.py -index 07b1431e84..ec65113e34 100644 ---- a/ipaserver/advise/base.py -+++ b/ipaserver/advise/base.py -@@ -227,6 +227,7 @@ def __init__(self): - self.content = [] - self.prefix = '# ' - self.options = None -+ self.pkgmgr_detected = False - self._indentation_tracker = _IndentationTracker( - spaces_per_indent=DEFAULT_INDENTATION_INCREMENT) - -@@ -312,6 +313,41 @@ def exit_on_predicate(self, predicate, error_message_lines): - - self.command('exit 1') - -+ def detect_pkgmgr(self): -+ self.commands_on_predicate( -+ 'which yum >/dev/null', -+ commands_to_run_when_true=['PKGMGR=yum'], -+ commands_to_run_when_false=['PKGMGR=dnf'] -+ ) -+ self.pkgmgr_detected = True -+ -+ def install_packages(self, names, error_message_lines): -+ assert isinstance(names, list) -+ self.detect_pkgmgr() -+ self.command('rpm -qi {} > /dev/null'.format(' '.join(names))) -+ self.commands_on_predicate( -+ '[ "$?" -ne "0" ]', -+ ['$PKGMGR install -y {}'.format(' '.join(names))] -+ ) -+ self.exit_on_predicate( -+ '[ "$?" -ne "0" ]', -+ error_message_lines -+ ) -+ -+ def remove_package(self, name, error_message_lines): -+ # remove only supports one package name -+ assert ' ' not in name -+ self.detect_pkgmgr() -+ self.command('rpm -qi {} > /dev/null'.format(name)) -+ self.commands_on_predicate( -+ '[ "$?" -eq "0" ]', -+ ['$PKGMGR remove -y {} || exit 1'.format(name)] -+ ) -+ self.exit_on_predicate( -+ '[ "$?" -ne "0" ]', -+ error_message_lines -+ ) -+ - @contextmanager - def unbranched_if(self, predicate): - with self._compound_statement(UnbranchedIfStatement, predicate): -diff --git a/ipaserver/advise/plugins/smart_card_auth.py b/ipaserver/advise/plugins/smart_card_auth.py -index 9a7a315ed5..411124f935 100644 ---- a/ipaserver/advise/plugins/smart_card_auth.py -+++ b/ipaserver/advise/plugins/smart_card_auth.py -@@ -135,9 +135,10 @@ def resolve_ipaca_records(self): - - self.log.comment('make sure bind-utils are installed so that we can ' - 'dig for ipa-ca records') -- self.log.exit_on_failed_command( -- 'yum install -y bind-utils', -- ['Failed to install bind-utils']) -+ self.log.install_packages( -+ ['bind-utils'], -+ ['Failed to install bind-utils'] -+ ) - - self.log.comment('make sure ipa-ca records are resolvable, ' - 'otherwise error out and instruct') -@@ -272,26 +273,23 @@ def get_info(self): - self.restart_sssd() - - def check_and_remove_pam_pkcs11(self): -- self.log.command('rpm -qi pam_pkcs11 > /dev/null') -- self.log.commands_on_predicate( -- '[ "$?" -eq "0" ]', -- [ -- 'yum remove -y pam_pkcs11' -- ] -+ self.log.remove_package( -+ 'pam_pkcs11', -+ ['Could not remove pam_pkcs11 package'] - ) - - def install_opensc_and_dconf_packages(self): - self.log.comment( - 'authconfig often complains about missing dconf, ' - 'install it explicitly') -- self.log.exit_on_failed_command( -- 'yum install -y {} dconf'.format(self.opensc_module_name.lower()), -+ self.log.install_packages( -+ [self.opensc_module_name.lower(), 'dconf'], - ['Could not install OpenSC package'] - ) - - def install_krb5_client_dependencies(self): -- self.log.exit_on_failed_command( -- 'yum install -y krb5-pkinit-openssl', -+ self.log.install_packages( -+ ['krb5-pkinit-openssl'], - ['Failed to install Kerberos client PKINIT extensions.'] - ) - -From e05ce4a20d2395179580db7e3db75c601c8f364c Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Thu, 13 Dec 2018 14:40:44 +0100 -Subject: [PATCH] Python 2 compatibility - -Make new test helpers and test code compatible with Python 2.7. - -See: https://pagure.io/freeipa/issue/7751 -Signed-off-by: Christian Heimes -Reviewed-By: Alexander Bokovoy -Reviewed-By: Rob Crittenden ---- - ipatests/pytest_ipa/integration/host.py | 4 ++-- - ipatests/test_integration/test_advise.py | 2 ++ - 2 files changed, 4 insertions(+), 2 deletions(-) - -diff --git a/ipatests/pytest_ipa/integration/host.py b/ipatests/pytest_ipa/integration/host.py -index 6aed58ae96..eb05872467 100644 ---- a/ipatests/pytest_ipa/integration/host.py -+++ b/ipatests/pytest_ipa/integration/host.py -@@ -65,7 +65,7 @@ def run_command(self, argv, set_env=True, stdin_text=None, - log_stdout=True, raiseonerr=True, - cwd=None, bg=False, encoding='utf-8'): - # Wrap run_command to log stderr on raiseonerr=True -- result = super().run_command( -+ result = super(Host, self).run_command( - argv, set_env=set_env, stdin_text=stdin_text, - log_stdout=log_stdout, raiseonerr=False, cwd=cwd, bg=bg, - encoding=encoding -@@ -74,7 +74,7 @@ def run_command(self, argv, set_env=True, stdin_text=None, - result.log.error('stderr: %s', result.stderr_text) - raise subprocess.CalledProcessError( - result.returncode, argv, -- result.stdout_text, result.stderr_text -+ result.stderr_text - ) - else: - return result -diff --git a/ipatests/test_integration/test_advise.py b/ipatests/test_integration/test_advise.py -index b548614922..761f278238 100644 ---- a/ipatests/test_integration/test_advise.py -+++ b/ipatests/test_integration/test_advise.py -@@ -20,6 +20,8 @@ - # FIXME: Pylint errors - # pylint: disable=no-member - -+from __future__ import absolute_import -+ - import re - - from ipalib.constants import IPAAPI_USER diff --git a/SOURCES/0015-Add_sysadm_r_to_default_SELinux_user_map_order_1853e2e_rhbz#1658303.patch b/SOURCES/0015-Add_sysadm_r_to_default_SELinux_user_map_order_1853e2e_rhbz#1658303.patch deleted file mode 100644 index 47c9744..0000000 --- a/SOURCES/0015-Add_sysadm_r_to_default_SELinux_user_map_order_1853e2e_rhbz#1658303.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 1853e2ecd6b5cbe389507a8c3fc751deaf512bb6 Mon Sep 17 00:00:00 2001 -From: François Cami -Date: Nov 12 2018 07:52:27 +0000 -Subject: Add sysadm_r to default SELinux user map order - - -It is a standard SELinux user role included in RHEL (like -user_r, staff_r, guest_r) and used quite often. - -Fixes: https://pagure.io/freeipa/issue/7658 -Signed-off-by: François Cami -Reviewed-By: Rob Crittenden - ---- - -diff --git a/install/share/bootstrap-template.ldif b/install/share/bootstrap-template.ldif -index ea1e5b2..8cc79d1 100644 ---- a/install/share/bootstrap-template.ldif -+++ b/install/share/bootstrap-template.ldif -@@ -411,7 +411,7 @@ ipaDefaultEmailDomain: $DOMAIN - ipaMigrationEnabled: FALSE - ipaConfigString: AllowNThash - ipaConfigString: KDC:Disable Last Success --ipaSELinuxUserMapOrder: guest_u:s0$$xguest_u:s0$$user_u:s0$$staff_u:s0-s0:c0.c1023$$unconfined_u:s0-s0:c0.c1023 -+ipaSELinuxUserMapOrder: guest_u:s0$$xguest_u:s0$$user_u:s0$$staff_u:s0-s0:c0.c1023$$sysadm_u:s0-s0:c0.c1023$$unconfined_u:s0-s0:c0.c1023 - ipaSELinuxUserMapDefault: unconfined_u:s0-s0:c0.c1023 - - dn: cn=cosTemplates,cn=accounts,$SUFFIX -diff --git a/install/ui/test/data/ipa_init.json b/install/ui/test/data/ipa_init.json -index 71c9d73..4298f7d 100644 ---- a/install/ui/test/data/ipa_init.json -+++ b/install/ui/test/data/ipa_init.json -@@ -36,7 +36,7 @@ - "ipausers" - ], - "ipaselinuxusermaporder" : [ -- "guest_u:s0$xguest_u:s0$user_u:s0$staff_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023" -+ "guest_u:s0$xguest_u:s0$user_u:s0$staff_u:s0-s0:c0.c1023$sysadm_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023" - ], - "ca_renewal_master_server" : [ - "vm.example.com" -diff --git a/ipatests/test_xmlrpc/test_config_plugin.py b/ipatests/test_xmlrpc/test_config_plugin.py -index 049e44d..cb8cdeb 100644 ---- a/ipatests/test_xmlrpc/test_config_plugin.py -+++ b/ipatests/test_xmlrpc/test_config_plugin.py -@@ -148,8 +148,12 @@ class test_config(Declarative): - - dict( - desc='Try to set new selinux order and invalid default user', -- command=('config_mod', [], -- dict(ipaselinuxusermaporder=u'xguest_u:s0$guest_u:s0$user_u:s0-s0:c0.c1023$staff_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023', -+ command=( -+ 'config_mod', [], -+ dict( -+ ipaselinuxusermaporder=u'xguest_u:s0$guest_u:s0' -+ u'$user_u:s0-s0:c0.c1023$staff_u:s0-s0:c0.c1023' -+ u'$sysadm_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023', - ipaselinuxusermapdefault=u'unknown_u:s0')), - expected=errors.ValidationError(name='ipaselinuxusermapdefault', - error='SELinux user map default user not in order list'), - diff --git a/SOURCES/0016-certdb_non-empty_Subject_Key_Identifier_and_validate_server_cert_sig_rhbz#1641988.patch b/SOURCES/0016-certdb_non-empty_Subject_Key_Identifier_and_validate_server_cert_sig_rhbz#1641988.patch deleted file mode 100644 index 4ad95c1..0000000 --- a/SOURCES/0016-certdb_non-empty_Subject_Key_Identifier_and_validate_server_cert_sig_rhbz#1641988.patch +++ /dev/null @@ -1,78 +0,0 @@ -From c7cc9896e89b3214c439e5601bf93b405dc1c72b Mon Sep 17 00:00:00 2001 -From: Fraser Tweedale -Date: Mon, 12 Nov 2018 16:40:38 +1100 -Subject: [PATCH] certdb: ensure non-empty Subject Key Identifier - -Installation or IPA CA renewal with externally-signed CA accepts an -IPA CA certificate with empty Subject Key Identifier. This is -technically legal in X.509, but is an operational issue. -Furthermore, due to an extant bug in Dogtag -(https://pagure.io/dogtagpki/issue/3079) it will cause Dogtag -startup failure. - -Reject CA certificates with empty Subject Key Identifier. - -Fixes: https://pagure.io/freeipa/issue/7762 -Reviewed-By: Christian Heimes ---- - ipapython/certdb.py | 5 ++++- - 1 file changed, 4 insertions(+), 1 deletion(-) - -diff --git a/ipapython/certdb.py b/ipapython/certdb.py -index e3f00c2561..bef6809b0f 100644 ---- a/ipapython/certdb.py -+++ b/ipapython/certdb.py -@@ -919,10 +919,13 @@ def verify_ca_cert_validity(self, nickname): - raise ValueError("not a CA certificate") - - try: -- cert.extensions.get_extension_for_class( -+ ski = cert.extensions.get_extension_for_class( - cryptography.x509.SubjectKeyIdentifier) - except cryptography.x509.ExtensionNotFound: - raise ValueError("missing subject key identifier extension") -+ else: -+ if len(ski.value.digest) == 0: -+ raise ValueError("subject key identifier must not be empty") - - try: - self.run_certutil(['-V', '-n', nickname, '-u', 'L'], -From c2ae6380b3f6b3804ebd2a7dd2b159b779eb756c Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Tue, 13 Nov 2018 12:21:21 +0100 -Subject: [PATCH] certdb: validate server cert signature - -PR https://github.com/freeipa/freeipa/pull/2554 added the '-e' option for CA -cert validation. Let's also verify signature, key size, and signing algorithm -of server certs. With the '-e' option, the installer and other -tools will catch weak certs early. - -Fixes: pagure.io/freeipa/issue/7761 -Signed-off-by: Christian Heimes -Reviewed-By: Fraser Tweedale ---- - ipapython/certdb.py | 11 +++++++++-- - 1 file changed, 9 insertions(+), 2 deletions(-) - -diff --git a/ipapython/certdb.py b/ipapython/certdb.py -index 05ec932985..1a92a12c50 100644 ---- a/ipapython/certdb.py -+++ b/ipapython/certdb.py -@@ -891,8 +891,15 @@ def verify_server_cert_validity(self, nickname, hostname): - cert = self.get_cert(nickname) - - try: -- self.run_certutil(['-V', '-n', nickname, '-u', 'V'], -- capture_output=True) -+ self.run_certutil( -+ [ -+ '-V', # check validity of cert and attrs -+ '-n', nickname, -+ '-u', 'V', # usage; 'V' means "SSL server" -+ '-e', # check signature(s); this checks -+ # key sizes, sig algorithm, etc. -+ ], -+ capture_output=True) - except ipautil.CalledProcessError as e: - # certutil output in case of error is - # 'certutil: certificate is invalid: \n' diff --git a/SOURCES/0017-ipa-replica-install_password_and_admin-password_options_mutually_exclusive_rhbz#1658309.patch b/SOURCES/0017-ipa-replica-install_password_and_admin-password_options_mutually_exclusive_rhbz#1658309.patch deleted file mode 100644 index 8015afd..0000000 --- a/SOURCES/0017-ipa-replica-install_password_and_admin-password_options_mutually_exclusive_rhbz#1658309.patch +++ /dev/null @@ -1,84 +0,0 @@ -From fd3f5153beb3221be077f277b07d886b6ca53b10 Mon Sep 17 00:00:00 2001 -From: Florence Blanc-Renaud -Date: Nov 21 2018 03:21:29 +0000 -Subject: ipa-replica-install: password and admin-password options mutually exclusive - - -Currently it is possible to run ipa-replica-install in one step, -and provide --password and --admin-password simultaneously. -This is confusing as --password is intended for one-time pwd -when the ipa-replica-install command is delegated to a user -who doesn't know the admin password. - -The fix makes --password and --admin-password options -mutually exclusive. - -Fixes https://pagure.io/freeipa/issue/6353 - -Reviewed-By: Christian Heimes - ---- - -diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py -index 03d096d..d3e28a1 100644 ---- a/ipaserver/install/server/replicainstall.py -+++ b/ipaserver/install/server/replicainstall.py -@@ -771,6 +771,10 @@ def promote_check(installer): - - client_fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE) - if not client_fstore.has_files(): -+ # One-step replica installation -+ if options.password and options.admin_password: -+ raise ScriptError("--password and --admin-password options are " -+ "mutually exclusive") - ensure_enrolled(installer) - else: - if (options.domain_name or options.server or options.realm_name or - -From 2b155f98e7b9ced739233242ff53e2d4b4c7f063 Mon Sep 17 00:00:00 2001 -From: Florence Blanc-Renaud -Date: Nov 21 2018 03:21:29 +0000 -Subject: ipatests: add test for ipa-replica-install options - - -Add a test checking that --password and --admin-password -options are mutually exclusive. - -Related to https://pagure.io/freeipa/issue/6353 - -Reviewed-By: Christian Heimes - ---- - -diff --git a/ipatests/test_integration/test_replica_promotion.py b/ipatests/test_integration/test_replica_promotion.py -index 7803c34..e665318 100644 ---- a/ipatests/test_integration/test_replica_promotion.py -+++ b/ipatests/test_integration/test_replica_promotion.py -@@ -50,6 +50,26 @@ class TestReplicaPromotionLevel1(ReplicaPromotionBase): - domain_level = DOMAIN_LEVEL_1 - - @replicas_cleanup -+ def test_one_step_install_pwd_and_admin_pwd(self): -+ """--password and --admin-password options are mutually exclusive -+ -+ Test for ticket 6353 -+ """ -+ expected_err = "--password and --admin-password options are " \ -+ "mutually exclusive" -+ result = self.replicas[0].run_command([ -+ 'ipa-replica-install', '-w', -+ self.master.config.admin_password, -+ '-p', 'OTPpwd', -+ '-n', self.master.domain.name, -+ '-r', self.master.domain.realm, -+ '--server', self.master.hostname, -+ '-U'], -+ raiseonerr=False) -+ assert result.returncode == 1 -+ assert expected_err in result.stderr_text -+ -+ @replicas_cleanup - def test_one_command_installation(self): - """ - TestCase: - diff --git a/SOURCES/0018-ipa_upgrade_handle_double-encoded_certificates_rhbz#1658310.patch b/SOURCES/0018-ipa_upgrade_handle_double-encoded_certificates_rhbz#1658310.patch deleted file mode 100644 index 19ab348..0000000 --- a/SOURCES/0018-ipa_upgrade_handle_double-encoded_certificates_rhbz#1658310.patch +++ /dev/null @@ -1,153 +0,0 @@ -From 8ee3779ded64ff55c3981fb8c2db50cdcd3abc5b Mon Sep 17 00:00:00 2001 -From: Florence Blanc-Renaud -Date: Nov 30 2018 14:20:59 +0000 -Subject: ipa upgrade: handle double-encoded certificates - - -Issue is linked to the ticket - #3477 LDAP upload CA cert sometimes double-encodes the value -In old FreeIPA releases (< 3.2), the upgrade plugin was encoding twice -the value of the certificate in cn=cacert,cn=ipa,cn=etc,$BASEDN. - -The fix for 3477 is only partial as it prevents double-encoding when a -new cert is uploaded but does not fix wrong values already present in LDAP. - -With this commit, the code first tries to read a der cert. If it fails, -it logs a debug message and re-writes the value caCertificate;binary -to repair the entry. - -Fixes https://pagure.io/freeipa/issue/7775 -Signed-off-by: Florence Blanc-Renaud -Reviewed-By: Christian Heimes - ---- - -diff --git a/ipaserver/install/plugins/upload_cacrt.py b/ipaserver/install/plugins/upload_cacrt.py -index 85c67e7..763da1e 100644 ---- a/ipaserver/install/plugins/upload_cacrt.py -+++ b/ipaserver/install/plugins/upload_cacrt.py -@@ -115,7 +115,18 @@ class update_upload_cacrt(Updater): - entry.single_value['cACertificate;binary'] = ca_cert - ldap.add_entry(entry) - else: -- if b'' in entry['cACertificate;binary']: -+ force_write = False -+ try: -+ _cert_bin = entry['cACertificate;binary'] -+ except ValueError: -+ # BZ 1644874 -+ # sometimes the cert is badly stored, twice encoded -+ # force write to fix the value -+ logger.debug('Fixing the value of cACertificate;binary ' -+ 'in entry %s', entry.dn) -+ force_write = True -+ -+ if force_write or b'' in entry['cACertificate;binary']: - entry.single_value['cACertificate;binary'] = ca_cert - ldap.update_entry(entry) - - -From 2b0f3a1abb9067a0a5ba8e59762bc41dc51608e2 Mon Sep 17 00:00:00 2001 -From: Florence Blanc-Renaud -Date: Nov 30 2018 14:20:59 +0000 -Subject: ipatests: add upgrade test for double-encoded cacert - - -Create a test for upgrade with the following scenario: -- install master -- write a double-encoded cert in the entry -cn=cacert,,cn=ipa,cn=etc,$basedn -to simulate bug 7775 -- call ipa-server-upgrade -- check that the upgrade fixed the value - -The upgrade should finish successfully and repair -the double-encoded cert. - -Related to https://pagure.io/freeipa/issue/7775 - -Reviewed-By: Christian Heimes - ---- - -diff --git a/ipatests/test_integration/test_upgrade.py b/ipatests/test_integration/test_upgrade.py -index cbf5f39..e0175bc 100644 ---- a/ipatests/test_integration/test_upgrade.py -+++ b/ipatests/test_integration/test_upgrade.py -@@ -6,6 +6,9 @@ - Module provides tests to verify that the upgrade script works. - """ - -+import base64 -+from cryptography.hazmat.primitives import serialization -+from ipapython.dn import DN - from ipatests.test_integration.base import IntegrationTest - from ipatests.pytest_ipa.integration import tasks - -@@ -21,3 +24,35 @@ class TestUpgrade(IntegrationTest): - assert ("DN: cn=Schema Compatibility,cn=plugins,cn=config does not \ - exists or haven't been updated" not in cmd.stdout_text) - assert cmd.returncode == 0 -+ -+ def test_double_encoded_cacert(self): -+ """Test for BZ 1644874 -+ -+ In old IPA version, the entry cn=CAcert,cn=ipa,cn=etc,$basedn -+ could contain a double-encoded cert, which leads to ipa-server-upgrade -+ failure. -+ Force a double-encoded value then call upgrade to check the fix. -+ """ -+ # Read the current entry from LDAP -+ ldap = self.master.ldap_connect() -+ basedn = self.master.domain.basedn # pylint: disable=no-member -+ dn = DN(('cn', 'CAcert'), ('cn', 'ipa'), ('cn', 'etc'), basedn) -+ entry = ldap.get_entry(dn) # pylint: disable=no-member -+ # Extract the certificate as DER then double-encode -+ cacert = entry['cacertificate;binary'][0] -+ cacert_der = cacert.public_bytes(serialization.Encoding.DER) -+ cacert_b64 = base64.b64encode(cacert_der) -+ # overwrite the value with double-encoded cert -+ entry.single_value['cACertificate;binary'] = cacert_b64 -+ ldap.update_entry(entry) # pylint: disable=no-member -+ -+ # try the upgrade -+ self.master.run_command(['ipa-server-upgrade']) -+ -+ # read the value after upgrade, should be fixed -+ entry = ldap.get_entry(dn) # pylint: disable=no-member -+ try: -+ _cacert = entry['cacertificate;binary'] -+ except ValueError: -+ raise AssertionError('%s contains a double-encoded cert' -+ % entry.dn) - -From 2a299c786f93e67446d5fd227fe14884b4e0d293 Mon Sep 17 00:00:00 2001 -From: Florence Blanc-Renaud -Date: Dec 06 2018 10:37:26 +0000 -Subject: ipatests: fix TestUpgrade::test_double_encoded_cacert - - -The test is using a stale ldap connection to the master -(obtained before calling upgrade, and the upgrade stops -and starts 389-ds, breaking the connection). - -The fix re-connects before using the ldap handle. - -Related to https://pagure.io/freeipa/issue/7775 - ---- - -diff --git a/ipatests/test_integration/test_upgrade.py b/ipatests/test_integration/test_upgrade.py -index e0175bc..5cc890e 100644 ---- a/ipatests/test_integration/test_upgrade.py -+++ b/ipatests/test_integration/test_upgrade.py -@@ -49,6 +49,8 @@ class TestUpgrade(IntegrationTest): - # try the upgrade - self.master.run_command(['ipa-server-upgrade']) - -+ # reconnect to the master (upgrade stops 389-ds) -+ ldap = self.master.ldap_connect() - # read the value after upgrade, should be fixed - entry = ldap.get_entry(dn) # pylint: disable=no-member - try: - diff --git a/SOURCES/0019-PKINIT_fix_ipa-pkinit-manage_enable_disable_rhbz#1658313.patch b/SOURCES/0019-PKINIT_fix_ipa-pkinit-manage_enable_disable_rhbz#1658313.patch deleted file mode 100644 index 58c0d41..0000000 --- a/SOURCES/0019-PKINIT_fix_ipa-pkinit-manage_enable_disable_rhbz#1658313.patch +++ /dev/null @@ -1,279 +0,0 @@ -From 940755e37b06ea95c32abd056277da19fb05ed3e Mon Sep 17 00:00:00 2001 -From: Florence Blanc-Renaud -Date: Dec 06 2018 10:40:02 +0000 -Subject: ipatest: add test for ipa-pkinit-manage enable|disable - - -Add a test for ipa-pkinit-manage with the following scenario: -- install master with option --no-pkinit -- call ipa-pkinit-manage enable -- call ipa-pkinit-manage disable -- call ipa-pkinit-manage enable - -At each step, check that the PKINIT cert is consistent with the -expectations: when pkinit is enabled, the cert is signed by IPA -CA and tracked by 'IPA' ca helper, but when pkinit is disabled, -the cert is self-signed and tracked by 'SelfSign' CA helper. - -The new test is added in the nightly definitons. - -Related to https://pagure.io/freeipa/issue/7200 - -Reviewed-By: Alexander Bokovoy -Reviewed-By: Christian Heimes - ---- - -#diff --git a/ipatests/prci_definitions/nightly_f28.yaml b/ipatests/prci_definitions/nightly_f28.yaml -#index ae8cacc..8462c14 100644 -#--- a/ipatests/prci_definitions/nightly_f28.yaml -#+++ b/ipatests/prci_definitions/nightly_f28.yaml -#@@ -639,3 +639,15 @@ jobs: -# template: *ci-master-f28 -# timeout: 16000 -# topology: *ipaserver -#+ -#+ fedora-28/test_pkinit_manage: -#+ requires: [fedora-28/build] -#+ priority: 50 -#+ job: -#+ class: RunPytest -#+ args: -#+ build_url: '{fedora-28/build_url}' -#+ test_suite: test_integration/test_pkinit_manage.py -#+ template: *ci-master-f28 -#+ timeout: 3600 -#+ topology: *master_1repl -diff --git a/ipatests/prci_definitions/nightly_master.yaml b/ipatests/prci_definitions/nightly_master.yaml -index 66921b6..3f2b346 100644 ---- a/ipatests/prci_definitions/nightly_master.yaml -+++ b/ipatests/prci_definitions/nightly_master.yaml -@@ -639,3 +639,15 @@ jobs: - template: *ci-master-f29 - timeout: 16000 - topology: *ipaserver -+ -+ fedora-29/test_pkinit_manage: -+ requires: [fedora-29/build] -+ priority: 50 -+ job: -+ class: RunPytest -+ args: -+ build_url: '{fedora-29/build_url}' -+ test_suite: test_integration/test_pkinit_manage.py -+ template: *ci-master-f29 -+ timeout: 3600 -+ topology: *master_1repl -diff --git a/ipatests/prci_definitions/nightly_rawhide.yaml b/ipatests/prci_definitions/nightly_rawhide.yaml -index 24c26be..bdc34d2 100644 ---- a/ipatests/prci_definitions/nightly_rawhide.yaml -+++ b/ipatests/prci_definitions/nightly_rawhide.yaml -@@ -627,3 +627,15 @@ jobs: - template: *ci-master-frawhide - timeout: 7200 - topology: *ipaserver -+ -+ fedora-rawhide/test_pkinit_manage: -+ requires: [fedora-rawhide/build] -+ priority: 50 -+ job: -+ class: RunPytest -+ args: -+ build_url: '{fedora-rawhide/build_url}' -+ test_suite: test_integration/test_pkinit_manage.py -+ template: *ci-master-frawhide -+ timeout: 3600 -+ topology: *master_1repl -diff --git a/ipatests/test_integration/test_pkinit_manage.py b/ipatests/test_integration/test_pkinit_manage.py -new file mode 100644 -index 0000000..bc1d9e3 ---- /dev/null -+++ b/ipatests/test_integration/test_pkinit_manage.py -@@ -0,0 +1,111 @@ -+# -+# Copyright (C) 2018 FreeIPA Contributors see COPYING for license -+# -+ -+""" -+Module provides tests for the ipa-pkinit-manage command. -+""" -+ -+from __future__ import absolute_import -+ -+from ipalib import x509 -+from ipaplatform.paths import paths -+from ipapython.dn import DN -+from ipatests.test_integration.base import IntegrationTest -+from ipatests.pytest_ipa.integration import tasks -+ -+ -+SELFSIGNED_CA_HELPER = 'SelfSign' -+IPA_CA_HELPER = 'IPA' -+PKINIT_STATUS_ENABLED = 'enabled' -+PKINIT_STATUS_DISABLED = 'disabled' -+ -+ -+def check_pkinit_status(host, status): -+ """Ensures that ipa-pkinit-manage status returns the expected state""" -+ result = host.run_command(['ipa-pkinit-manage', 'status'], -+ raiseonerr=False) -+ assert result.returncode == 0 -+ assert 'PKINIT is {}'.format(status) in result.stdout_text -+ -+ -+def check_pkinit_tracking(host, ca_helper): -+ """Ensures that the PKINIT cert is tracked by the expected helper""" -+ result = host.run_command(['getcert', 'list', '-f', paths.KDC_CERT], -+ raiseonerr=False) -+ assert result.returncode == 0 -+ # Make sure that only one request exists -+ assert result.stdout_text.count('Request ID') == 1 -+ # Make sure that the right CA helper is used to track the cert -+ assert 'CA: {}'.format(ca_helper) in result.stdout_text -+ -+ -+def check_pkinit_cert_issuer(host, issuer): -+ """Ensures that the PKINIT cert is signed by the expected issuer""" -+ data = host.get_file_contents(paths.KDC_CERT) -+ pkinit_cert = x509.load_pem_x509_certificate(data) -+ # Make sure that the issuer is the expected one -+ assert DN(pkinit_cert.issuer) == DN(issuer) -+ -+ -+def check_pkinit(host, enabled=True): -+ """Checks that PKINIT is configured as expected -+ -+ If enabled: -+ ipa-pkinit-manage status must return 'PKINIT is enabled' -+ the certificate must be tracked by IPA CA helper -+ the certificate must be signed by IPA CA -+ If disabled: -+ ipa-pkinit-manage status must return 'PKINIT is disabled' -+ the certificate must be tracked by SelfSign CA helper -+ the certificate must be self-signed -+ """ -+ if enabled: -+ # When pkinit is enabled: -+ # cert is tracked by IPA CA helper -+ # cert is signed by IPA CA -+ check_pkinit_status(host, PKINIT_STATUS_ENABLED) -+ check_pkinit_tracking(host, IPA_CA_HELPER) -+ check_pkinit_cert_issuer( -+ host, -+ 'CN=Certificate Authority,O={}'.format(host.domain.realm)) -+ else: -+ # When pkinit is disabled -+ # cert is tracked by 'SelfSign' CA helper -+ # cert is self-signed -+ check_pkinit_status(host, PKINIT_STATUS_DISABLED) -+ check_pkinit_tracking(host, SELFSIGNED_CA_HELPER) -+ check_pkinit_cert_issuer( -+ host, -+ 'CN={},O={}'.format(host.hostname, host.domain.realm)) -+ -+ -+class TestPkinitManage(IntegrationTest): -+ """Tests the ipa-pkinit-manage command. -+ -+ ipa-pkinit-manage can be used to enable, disable or check -+ the status of PKINIT. -+ When pkinit is enabled, the kerberos server is using a certificate -+ signed either externally or by IPA CA. In the latter case, certmonger -+ is tracking the cert with IPA helper. -+ When pkinit is disabled, the kerberos server is using a self-signed -+ certificate that is tracked by certmonger with the SelfSigned helper. -+ """ -+ -+ @classmethod -+ def install(cls, mh): -+ # Install the master with PKINIT disabled -+ tasks.install_master(cls.master, extra_args=['--no-pkinit']) -+ check_pkinit(cls.master, enabled=False) -+ -+ def test_pkinit_enable(self): -+ self.master.run_command(['ipa-pkinit-manage', 'enable']) -+ check_pkinit(self.master, enabled=True) -+ -+ def test_pkinit_disable(self): -+ self.master.run_command(['ipa-pkinit-manage', 'disable']) -+ check_pkinit(self.master, enabled=False) -+ -+ def test_pkinit_reenable(self): -+ self.master.run_command(['ipa-pkinit-manage', 'enable']) -+ check_pkinit(self.master, enabled=True) - -From ffa04a1862be198b9e1a5f6205d1ae0909ac5a4d Mon Sep 17 00:00:00 2001 -From: Florence Blanc-Renaud -Date: Dec 06 2018 10:40:02 +0000 -Subject: PKINIT: fix ipa-pkinit-manage enable|disable - - -The command ipa-pkinit-manage enable|disable is reporting -success even though the PKINIT cert is not re-issued. -The command triggers the request of a new certificate -(signed by IPA CA when state=enable, selfsigned when disabled), -but as the cert file is still present, certmonger does not create -a new request and the existing certificate is kept. - -The fix consists in deleting the cert and key file before calling -certmonger to request a new cert. - -There was also an issue in the is_pkinit_enabled() function: -if no tracking request was found for the PKINIT cert, -is_pkinit_enabled() was returning True while it should not. - -Fixes https://pagure.io/freeipa/issue/7200 - -Reviewed-By: Alexander Bokovoy -Reviewed-By: Christian Heimes - ---- - -diff --git a/ipaserver/install/ipa_pkinit_manage.py b/ipaserver/install/ipa_pkinit_manage.py -index 4a79bba..86bd1ba 100644 ---- a/ipaserver/install/ipa_pkinit_manage.py -+++ b/ipaserver/install/ipa_pkinit_manage.py -@@ -72,6 +72,8 @@ class PKINITManage(AdminTool): - if ca_enabled: - logger.warning( - "Failed to stop tracking certificates: %s", e) -+ # remove the cert and key -+ krb.delete_pkinit_cert() - - krb.enable_ssl() - -diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py -index 4ead1c5..850946a 100644 ---- a/ipaserver/install/krbinstance.py -+++ b/ipaserver/install/krbinstance.py -@@ -77,7 +77,7 @@ def is_pkinit_enabled(): - if os.path.exists(paths.KDC_CERT): - pkinit_request_ca = get_pkinit_request_ca() - -- if pkinit_request_ca != "SelfSign": -+ if pkinit_request_ca and pkinit_request_ca != "SelfSign": - return True - - return False -@@ -602,6 +602,10 @@ class KrbInstance(service.Service): - def stop_tracking_certs(self): - certmonger.stop_tracking(certfile=paths.KDC_CERT) - -+ def delete_pkinit_cert(self): -+ installutils.remove_file(paths.KDC_CERT) -+ installutils.remove_file(paths.KDC_KEY) -+ - def uninstall(self): - if self.is_configured(): - self.print_msg("Unconfiguring %s" % self.service_name) -@@ -627,8 +631,7 @@ class KrbInstance(service.Service): - # stop tracking and remove certificates - self.stop_tracking_certs() - installutils.remove_file(paths.CACERT_PEM) -- installutils.remove_file(paths.KDC_CERT) -- installutils.remove_file(paths.KDC_KEY) -+ self.delete_pkinit_cert() - - if running: - self.restart() - diff --git a/SOURCES/0020-Enable_LDAP_debug_output_in_client_to_display_TLS_errors_in_join_rhbz#1658316.patch b/SOURCES/0020-Enable_LDAP_debug_output_in_client_to_display_TLS_errors_in_join_rhbz#1658316.patch deleted file mode 100644 index 6b42e53..0000000 --- a/SOURCES/0020-Enable_LDAP_debug_output_in_client_to_display_TLS_errors_in_join_rhbz#1658316.patch +++ /dev/null @@ -1,134 +0,0 @@ -From be5513ba7d70cecba5aa7654b66c1aa4015f7de2 Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Tue, 9 Oct 2018 17:13:36 -0400 -Subject: [PATCH] Enable LDAP debug output in client to display TLS errors in - join - -If ipa-join fails due to a TLS connection error when doing an -LDAP-based enroll then nothing is logged by default except an -Invalid Password error which is misleading (because the failure -occurs during the bind). - -The only way that debugging would have been sufficient is if -the user passed --debug to ipa-client-install which is not great. - -This log level is otherwise very quiet and only logs one or two -lines on errors which is perfect. - -https://pagure.io/freeipa/issue/7728 - -Signed-off-by: Rob Crittenden -Reviewed-By: Christian Heimes ---- - client/ipa-join.c | 64 ++++++++++++++++++++++++++--------------------- - 1 file changed, 35 insertions(+), 29 deletions(-) - -diff --git a/client/ipa-join.c b/client/ipa-join.c -index 7f454f723d..750114896f 100644 ---- a/client/ipa-join.c -+++ b/client/ipa-join.c -@@ -197,33 +197,31 @@ callRPC(char * user_agent, - - /* The caller is responsible for unbinding the connection if ld is not NULL */ - static LDAP * --connect_ldap(const char *hostname, const char *binddn, const char *bindpw) { -+connect_ldap(const char *hostname, const char *binddn, const char *bindpw, -+ int *ret) { - LDAP *ld = NULL; -- int ret; -- int ldapdebug = 0; -- char *uri; -+ int ldapdebug = 2; -+ char *uri = NULL; - struct berval bindpw_bv; - -- if (debug) { -- ldapdebug = 2; -- ret = ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &ldapdebug); -- if (ret != LDAP_OPT_SUCCESS) { -- goto fail; -- } -+ *ret = ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &ldapdebug); -+ if (*ret != LDAP_OPT_SUCCESS) { -+ goto fail; - } - -- ret = asprintf(&uri, "ldaps://%s:636", hostname); -- if (ret == -1) { -+ *ret = asprintf(&uri, "ldaps://%s:636", hostname); -+ if (*ret == -1) { - fprintf(stderr, _("Out of memory!")); -+ *ret = LDAP_NO_MEMORY; - goto fail; - } - -- ret = ipa_ldap_init(&ld, uri); -- if (ret != LDAP_SUCCESS) { -+ *ret = ipa_ldap_init(&ld, uri); -+ if (*ret != LDAP_SUCCESS) { - goto fail; - } -- ret = ipa_tls_ssl_init(ld, uri, DEFAULT_CA_CERT_FILE); -- if (ret != LDAP_SUCCESS) { -+ *ret = ipa_tls_ssl_init(ld, uri, DEFAULT_CA_CERT_FILE); -+ if (*ret != LDAP_SUCCESS) { - fprintf(stderr, _("Unable to enable SSL in LDAP\n")); - goto fail; - } -@@ -238,15 +236,11 @@ connect_ldap(const char *hostname, const char *binddn, const char *bindpw) { - bindpw_bv.bv_len = 0; - } - -- ret = ldap_sasl_bind_s(ld, binddn, LDAP_SASL_SIMPLE, &bindpw_bv, -- NULL, NULL, NULL); -- -- if (ret != LDAP_SUCCESS) { -- int err; -+ *ret = ldap_sasl_bind_s(ld, binddn, LDAP_SASL_SIMPLE, &bindpw_bv, -+ NULL, NULL, NULL); - -- ldap_get_option(ld, LDAP_OPT_RESULT_CODE, &err); -- if (debug) -- fprintf(stderr, _("Bind failed: %s\n"), ldap_err2string(err)); -+ if (*ret != LDAP_SUCCESS) { -+ fprintf(stderr, _("Bind failed: %s\n"), ldap_err2string(*ret)); - goto fail; - } - -@@ -309,7 +303,7 @@ get_root_dn(const char *ipaserver, char **ldap_base) - struct berval **defvals; - int ret, rval = 0; - -- ld = connect_ldap(ipaserver, NULL, NULL); -+ ld = connect_ldap(ipaserver, NULL, NULL, &ret); - if (!ld) { - rval = 14; - goto done; -@@ -429,11 +423,23 @@ join_ldap(const char *ipaserver, char *hostname, char ** binddn, const char *bin - rval = 3; - goto done; - } -- ld = connect_ldap(ipaserver, *binddn, bindpw); -+ ld = connect_ldap(ipaserver, *binddn, bindpw, &ret); - if (!ld) { -- if (!quiet) -- fprintf(stderr, _("Incorrect password.\n")); -- rval = 15; -+ if (quiet) -+ goto done; -+ -+ switch(ret) { -+ case LDAP_NO_MEMORY: -+ rval = 3; -+ break; -+ case LDAP_INVALID_CREDENTIALS: /* incorrect password */ -+ case LDAP_INAPPROPRIATE_AUTH: /* no password set */ -+ rval = 15; -+ break; -+ default: /* LDAP connection error catch-all */ -+ rval = 14; -+ break; -+ } - goto done; - } - diff --git a/SOURCES/0021-rpc_always_read_response_rhbz#1639890.patch b/SOURCES/0021-rpc_always_read_response_rhbz#1639890.patch deleted file mode 100644 index 627d233..0000000 --- a/SOURCES/0021-rpc_always_read_response_rhbz#1639890.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 4c0e7d69e461a28a254e7c7a27c2768be3163a3d Mon Sep 17 00:00:00 2001 -From: Fraser Tweedale -Date: Wed, 7 Nov 2018 17:06:47 +1100 -Subject: [PATCH] rpc: always read response - -If the server responds 401 and the response body is empty, the -client raises ResponseNotReady. This occurs because: - -1. For a non-200 response, the response read only if the - Content-Length header occurs. - -2. The response must be read before another request (e.g. the - follow-up request with WWW-Authenticate header set), and this - condition was not met. For details see - https://github.com/python/cpython/blob/v3.6.7/Lib/http/client.py#L1305-L1321. - -This situation should not arise in regular use, because the client -either has a session cookie, or, knowing the details of the server -it is contacting, it establishes the GSS-API context and includes -the WWW-Authenticate header in the initial request. - -Nevertheless, this problem has been observed in the wild. I do not -know its ordinary cause(s), but one can force the issue by removing -an authenticated user's session cache from /run/ipa/ccaches, then -performing a request. - -Resolve the issue by always reading the response. It is safe to -call response.read() regardless of whether the Content-Length header -appears, or whether the body is empty. - -Fixes: https://pagure.io/freeipa/issue/7752 -Reviewed-By: Christian Heimes ---- - ipalib/rpc.py | 11 +++++++++-- - 1 file changed, 9 insertions(+), 2 deletions(-) - -diff --git a/ipalib/rpc.py b/ipalib/rpc.py -index b27f3cef9c..23841d0a4c 100644 ---- a/ipalib/rpc.py -+++ b/ipalib/rpc.py -@@ -712,8 +712,15 @@ def single_request(self, host, handler, request_body, verbose=0): - response = h.getresponse() - - if response.status != 200: -- if (response.getheader("content-length", 0)): -- response.read() -+ # Must read response (even if it is empty) -+ # before sending another request. -+ # -+ # https://docs.python.org/3/library/http.client.html -+ # #http.client.HTTPConnection.getresponse -+ # -+ # https://pagure.io/freeipa/issue/7752 -+ # -+ response.read() - - if response.status == 401: - if not self._auth_complete(response): diff --git a/SOURCES/0022-ipa_vault-retrieve_fix_internal_error_rhbz#1658485.patch b/SOURCES/0022-ipa_vault-retrieve_fix_internal_error_rhbz#1658485.patch deleted file mode 100644 index 16ea8b9..0000000 --- a/SOURCES/0022-ipa_vault-retrieve_fix_internal_error_rhbz#1658485.patch +++ /dev/null @@ -1,136 +0,0 @@ -From 858859187a1353cbaa893642cc7b27f9f644b18b Mon Sep 17 00:00:00 2001 -From: François Cami -Date: Nov 23 2018 09:54:46 +0000 -Subject: Add a shared-vault-retrieve test - - -Add a shared-vault-retrieve test when: -* master has KRA installed -* replica has no KRA -This currently fails because of issue#7691 - -Related-to: https://pagure.io/freeipa/issue/7691 -Signed-off-by: François Cami -Reviewed-By: Christian Heimes - ---- - -diff --git a/ipatests/test_integration/test_vault.py b/ipatests/test_integration/test_vault.py -index ea2591b..e5b3ad1 100644 ---- a/ipatests/test_integration/test_vault.py -+++ b/ipatests/test_integration/test_vault.py -@@ -20,14 +20,17 @@ class TestInstallKRA(IntegrationTest): - - vault_password = "password" - vault_data = "SSBsb3ZlIENJIHRlc3RzCg==" -+ vault_user = "vault_user" -+ vault_user_password = "vault_user_password" - vault_name_master = "ci_test_vault_master" - vault_name_master2 = "ci_test_vault_master2" - vault_name_master3 = "ci_test_vault_master3" - vault_name_replica_without_KRA = "ci_test_vault_replica_without_kra" -+ shared_vault_name_replica_without_KRA = ("ci_test_shared" -+ "_vault_replica_without_kra") - vault_name_replica_with_KRA = "ci_test_vault_replica_with_kra" - vault_name_replica_KRA_uninstalled = "ci_test_vault_replica_KRA_uninstalled" - -- - @classmethod - def install(cls, mh): - tasks.install_master(cls.master, setup_kra=True) -@@ -89,6 +92,66 @@ class TestInstallKRA(IntegrationTest): - - self._retrieve_secret([self.vault_name_replica_without_KRA]) - -+ def test_create_and_retrieve_shared_vault_replica_without_kra(self): -+ # create vault -+ self.replicas[0].run_command([ -+ "ipa", "vault-add", -+ self.shared_vault_name_replica_without_KRA, -+ "--shared", -+ "--type", "standard", -+ ]) -+ -+ # archive secret -+ self.replicas[0].run_command([ -+ "ipa", "vault-archive", -+ self.shared_vault_name_replica_without_KRA, -+ "--shared", -+ "--data", self.vault_data, -+ ]) -+ time.sleep(WAIT_AFTER_ARCHIVE) -+ -+ # add non-admin user -+ self.replicas[0].run_command([ -+ 'ipa', 'user-add', self.vault_user, -+ '--first', self.vault_user, -+ '--last', self.vault_user, -+ '--password'], -+ stdin_text=self.vault_user_password) -+ -+ # add it to vault -+ self.replicas[0].run_command([ -+ "ipa", "vault-add-member", -+ self.shared_vault_name_replica_without_KRA, -+ "--shared", -+ "--users", self.vault_user, -+ ]) -+ -+ self.replicas[0].run_command([ -+ 'kdestroy', '-A']) -+ -+ user_kinit = "%s\n%s\n%s\n" % (self.vault_user_password, -+ self.vault_user_password, -+ self.vault_user_password) -+ -+ self.replicas[0].run_command([ -+ 'kinit', self.vault_user], -+ stdin_text=user_kinit) -+ -+ # TODO: possibly refactor with: -+ # self._retrieve_secret([self.vault_name_replica_without_KRA]) -+ -+ self.replicas[0].run_command([ -+ "ipa", "vault-retrieve", -+ "--shared", -+ self.shared_vault_name_replica_without_KRA, -+ "--out=test.txt"]) -+ -+ self.replicas[0].run_command([ -+ 'kdestroy', '-A']) -+ -+ tasks.kinit_admin(self.replicas[0]) -+ -+ - def test_create_and_retrieve_vault_replica_with_kra(self): - - # install KRA on replica - -From d57d97ea7f911e18ac75d532e19833c4efaafa96 Mon Sep 17 00:00:00 2001 -From: François Cami -Date: Nov 23 2018 09:54:46 +0000 -Subject: Add a "Find enabled services" ACI in 20-aci.update so that all users can find IPA servers and services. ACI suggested by Christian Heimes. - - -Fixes: https://pagure.io/freeipa/issue/7691 -Signed-off-by: François Cami -Reviewed-By: Christian Heimes - ---- - -diff --git a/install/updates/20-aci.update b/install/updates/20-aci.update -index 184749d..7650cb4 100644 ---- a/install/updates/20-aci.update -+++ b/install/updates/20-aci.update -@@ -36,6 +36,10 @@ 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 users to discover enabled services -+dn: cn=masters,cn=ipa,cn=etc,$SUFFIX -+add:aci:(targetfilter = "(ipaConfigString=enabledService)")(targetattrs = "ipaConfigString")(version 3.0; acl "Find enabled services"; allow(read, search, compare) userdn = "ldap:///all";) -+ - # Allow hosts to read masters service configuration - dn: cn=masters,cn=ipa,cn=etc,$SUFFIX - 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";) - diff --git a/SOURCES/0023-Move_ipa_s_systemd_tmpfiles_from_var_run_to_run_rhbz#1658487.patch b/SOURCES/0023-Move_ipa_s_systemd_tmpfiles_from_var_run_to_run_rhbz#1658487.patch deleted file mode 100644 index d4ec1e8..0000000 --- a/SOURCES/0023-Move_ipa_s_systemd_tmpfiles_from_var_run_to_run_rhbz#1658487.patch +++ /dev/null @@ -1,47 +0,0 @@ -From 13b6fec04582d43bfc057a4cc3dbb7e652c8a64f Mon Sep 17 00:00:00 2001 -From: Stanislav Levin -Date: Oct 15 2018 12:07:12 +0000 -Subject: Move ipa's systemd tmpfiles from /var/run to /run - - -systemd 239 complains about the legacy of ipa's tmpfiles which -are located on /var/run. - -Fixes: https://pagure.io/freeipa/issue/7732 -Reviewed-By: Christian Heimes - ---- - -diff --git a/init/tmpfilesd/Makefile.am b/init/tmpfilesd/Makefile.am -index b2d91c3..3ea4533 100644 ---- a/init/tmpfilesd/Makefile.am -+++ b/init/tmpfilesd/Makefile.am -@@ -7,4 +7,4 @@ systemdtmpfiles_DATA = \ - CLEANFILES = $(systemdtmpfiles_DATA) - - %: %.in Makefile -- sed -e 's|@localstatedir[@]|$(localstatedir)|g' '$(srcdir)/$@.in' >$@ -+ cp '$(srcdir)/$@.in' $@ -diff --git a/init/tmpfilesd/ipa.conf.in b/init/tmpfilesd/ipa.conf.in -index df66bef..183ceed 100644 ---- a/init/tmpfilesd/ipa.conf.in -+++ b/init/tmpfilesd/ipa.conf.in -@@ -1,2 +1,2 @@ --d @localstatedir@/run/ipa 0711 root root --d @localstatedir@/run/ipa/ccaches 0770 ipaapi ipaapi -+d /run/ipa 0711 root root -+d /run/ipa/ccaches 0770 ipaapi ipaapi -diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py -index bc04964..8c1d44f 100644 ---- a/ipaplatform/base/paths.py -+++ b/ipaplatform/base/paths.py -@@ -350,7 +350,7 @@ class BasePathNamespace(object): - OPENDNSSEC_KASP_DB = "/var/opendnssec/kasp.db" - IPA_ODS_EXPORTER_CCACHE = "/var/opendnssec/tmp/ipa-ods-exporter.ccache" - VAR_RUN_DIRSRV_DIR = "/var/run/dirsrv" -- IPA_CCACHES = "/var/run/ipa/ccaches" -+ IPA_CCACHES = "/run/ipa/ccaches" - HTTP_CCACHE = "/var/lib/ipa/gssproxy/http.ccache" - CA_BUNDLE_PEM = "/var/lib/ipa-client/pki/ca-bundle.pem" - KDC_CA_BUNDLE_PEM = "/var/lib/ipa-client/pki/kdc-ca-bundle.pem" - diff --git a/SOURCES/0024-Fix_authselect_invocations_to_work_with_1.0.2_rhbz#1654291.patch b/SOURCES/0024-Fix_authselect_invocations_to_work_with_1.0.2_rhbz#1654291.patch deleted file mode 100644 index 63c6f05..0000000 --- a/SOURCES/0024-Fix_authselect_invocations_to_work_with_1.0.2_rhbz#1654291.patch +++ /dev/null @@ -1,78 +0,0 @@ -From 05c5be1b1c5ae63c5547d248d926b3411bff2733 Mon Sep 17 00:00:00 2001 -From: Adam Williamson -Date: Nov 29 2018 15:58:38 +0000 -Subject: Fix authselect invocations to work with 1.0.2 - - -Since authselect 1.0.2, invoking an authselect command sequence -like this: - -['authselect', 'sssd', '', '--force'] - -does not work: authselect barfs on the empty string arg and -errors out. We must only pass a features arg if we actually have -some text to go in it. - -This broke uninstallation. - -In all cases, features are now passed as separate arguments instead of one -argument separated by space. - -Fixes: https://pagure.io/freeipa/issue/7776 -Signed-off-by: Adam Williamson -Reviewed-By: Alexander Bokovoy - ---- - -diff --git a/ipaplatform/redhat/authconfig.py b/ipaplatform/redhat/authconfig.py -index 77ccc36..58cf7df 100644 ---- a/ipaplatform/redhat/authconfig.py -+++ b/ipaplatform/redhat/authconfig.py -@@ -158,15 +158,26 @@ class RedHatAuthSelect(RedHatAuthToolBase): - " ".join(args)) - - profile = 'sssd' -- features = '' -+ features = [] - else: -- profile = \ -- statestore.restore_state('authselect', 'profile') or 'sssd' -- features = \ -- statestore.restore_state('authselect', 'features_list') or '' -+ profile = statestore.restore_state('authselect', 'profile') -+ if not profile: -+ profile = 'sssd' -+ features_state = statestore.restore_state( -+ 'authselect', 'features_list' -+ ) - statestore.delete_state('authselect', 'mkhomedir') -+ # only non-empty features, https://pagure.io/freeipa/issue/7776 -+ if features_state is not None: -+ features = [ -+ f.strip() for f in features_state.split(' ') if f.strip() -+ ] -+ else: -+ features = [] - -- cmd = [paths.AUTHSELECT, "select", profile, features, "--force"] -+ cmd = [paths.AUTHSELECT, "select", profile] -+ cmd.extend(features) -+ cmd.append("--force") - ipautil.run(cmd) - - def backup(self, path): -@@ -186,10 +197,9 @@ class RedHatAuthSelect(RedHatAuthToolBase): - - if cfg: - profile = cfg[0] -- -- cmd = [ -- paths.AUTHSELECT, "select", profile, -- " ".join(cfg[1]), "--force"] -+ cmd = [paths.AUTHSELECT, "select", profile] -+ cmd.extend(cfg[1]) -+ cmd.append("--force") - ipautil.run(cmd) - - def set_nisdomain(self, nisdomain): - diff --git a/SOURCES/0025-ipa-client-automount_and_NFS_unit_name_changes_rhbz#1645501.patch b/SOURCES/0025-ipa-client-automount_and_NFS_unit_name_changes_rhbz#1645501.patch deleted file mode 100644 index 935d25d..0000000 --- a/SOURCES/0025-ipa-client-automount_and_NFS_unit_name_changes_rhbz#1645501.patch +++ /dev/null @@ -1,291 +0,0 @@ -From 65f6c8dc2585144b17ff89e63e4ba300971996dd Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fran=C3=A7ois=20Cami?= -Date: Thu, 6 Dec 2018 16:10:00 +0100 -Subject: [PATCH] Fix NFS unit names -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -NFS unit names were renamed. -Compatibility was maintained with older unit names -through symlinks. When these symlinks are removed -only new unit names work, so changing to using non- -symlink unit names is required. - -Fixes: https://pagure.io/freeipa/issue/7783 -Signed-off-by: François Cami -Reviewed-By: Alexander Bokovoy ---- - ipaplatform/redhat/services.py | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/ipaplatform/redhat/services.py b/ipaplatform/redhat/services.py -index 390bbb0231..20395aee44 100644 ---- a/ipaplatform/redhat/services.py -+++ b/ipaplatform/redhat/services.py -@@ -45,8 +45,8 @@ - redhat_system_units = dict((x, "%s.service" % x) - for x in base_services.wellknownservices) - --redhat_system_units['rpcgssd'] = 'nfs-secure.service' --redhat_system_units['rpcidmapd'] = 'nfs-idmap.service' -+redhat_system_units['rpcgssd'] = 'rpc-gssd.service' -+redhat_system_units['rpcidmapd'] = 'nfs-idmapd.service' - redhat_system_units['domainname'] = 'nis-domainname.service' - - # Rewrite dirsrv and pki-tomcatd services as they support instances via separate -From 0687e4869995842a90d5d656749de42daceb2ad4 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fran=C3=A7ois=20Cami?= -Date: Thu, 6 Dec 2018 17:29:26 +0100 -Subject: [PATCH] ipa-client-automount: use nfs-utils unit -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -- remove nfs-idmapd from units we enable & start as: - - it is not used on NFS clients anymore - - it is a static unit -- remove rpc-gssd as well as it is a static unit -- restart nfs-utils and rpc-gssd -- manage systemctl-related exceptions during uninstall - -Fixes: https://pagure.io/freeipa/issue/7780 -Fixes: https://pagure.io/freeipa/issue/7781 -Signed-off-by: François Cami -Reviewed-By: Alexander Bokovoy ---- - client/ipa-client-automount.in | 55 +++++++++++----------------------- - ipaplatform/base/services.py | 3 +- - 2 files changed, 20 insertions(+), 38 deletions(-) - mode change 100644 => 100755 client/ipa-client-automount.in - -diff --git a/client/ipa-client-automount.in b/client/ipa-client-automount.in -old mode 100644 -new mode 100755 -index 7348e20775..15926bd028 ---- a/client/ipa-client-automount.in -+++ b/client/ipa-client-automount.in -@@ -314,23 +314,21 @@ def uninstall(fstore, statestore): - print('Unable to restore SSSD configuration: %s' % str(e)) - logger.debug('Unable to restore SSSD configuration: %s', - str(e)) -+ -+ # rpcidmapd and rpcgssd are static units now - if statestore.has_state('rpcidmapd'): -- enabled = statestore.restore_state('rpcidmapd', 'enabled') -- running = statestore.restore_state('rpcidmapd', 'running') -- rpcidmapd = services.knownservices.rpcidmapd -- if not enabled: -- rpcidmapd.disable() -- if not running: -- rpcidmapd.stop() -+ statestore.delete_state('rpcidmapd','enabled') -+ statestore.delete_state('rpcidmapd','running') - if statestore.has_state('rpcgssd'): -- enabled = statestore.restore_state('rpcgssd', 'enabled') -- running = statestore.restore_state('rpcgssd', 'running') -- rpcgssd = services.knownservices.rpcgssd -- if not enabled: -- rpcgssd.disable() -- if not running: -- rpcgssd.stop() -+ statestore.delete_state('rpcgssd','enabled') -+ statestore.delete_state('rpcgssd','running') - -+ nfsutils = services.knownservices['nfs-utils'] -+ try: -+ nfsutils.restart() -+ except Exception as e: -+ logger.error("Failed to restart nfs client services (%s)" % str(e)) -+ return 1 - return 0 - - def configure_nfs(fstore, statestore): -@@ -365,35 +363,18 @@ def configure_nfs(fstore, statestore): - - print("Configured %s" % paths.IDMAPD_CONF) - -- rpcidmapd = services.knownservices.rpcidmapd -- statestore.backup_state('rpcidmapd', 'enabled', rpcidmapd.is_enabled()) -- statestore.backup_state('rpcidmapd', 'running', rpcidmapd.is_running()) -- try: -- rpcidmapd.restart() -- print("Started %s" % rpcidmapd.service_name) -- except Exception as e: -- logger.error("%s failed to restart: %s", rpcidmapd.service_name, e) -- try: -- rpcidmapd.enable() -- except Exception as e: -- print("Failed to configure automatic startup of the %s daemon" % (rpcidmapd.service_name)) -- logger.error("Failed to enable automatic startup of the %s daemon: %s", -- rpcidmapd.service_name, str(e)) -- - rpcgssd = services.knownservices.rpcgssd -- statestore.backup_state('rpcgssd', 'enabled', rpcgssd.is_enabled()) -- statestore.backup_state('rpcgssd', 'running', rpcgssd.is_running()) - try: - rpcgssd.restart() -- print("Started %s" % rpcgssd.service_name) - except Exception as e: -- logger.error("%s failed to restart: %s", rpcgssd.service_name, e) -+ logger.error("Failed to restart rpc-gssd (%s)" % str(e)) -+ return 1 -+ nfsutils = services.knownservices['nfs-utils'] - try: -- rpcgssd.enable() -+ nfsutils.restart() - except Exception as e: -- print("Failed to configure automatic startup of the %s daemon" % (rpcgssd.service_name)) -- logger.error("Failed to enable automatic startup of the %s daemon: %s", -- rpcgssd.service_name, str(e)) -+ logger.error("Failed to restart nfs client services (%s)" % str(e)) -+ return 1 - - def main(): - try: -diff --git a/ipaplatform/base/services.py b/ipaplatform/base/services.py -index 4533ad5b34..51c27848d7 100644 ---- a/ipaplatform/base/services.py -+++ b/ipaplatform/base/services.py -@@ -53,7 +53,8 @@ - 'dbus', 'nslcd', 'nscd', 'ntpd', 'portmap', - 'rpcbind', 'kadmin', 'sshd', 'autofs', 'rpcgssd', - 'rpcidmapd', 'pki_tomcatd', 'chronyd', 'domainname', -- 'named', 'ods_enforcerd', 'ods_signerd', 'gssproxy'] -+ 'named', 'ods_enforcerd', 'ods_signerd', 'gssproxy', -+ 'nfs-utils'] - - # The common ports for these services. This is used to wait for the - # service to become available. -From dfd741d3cd9c9d695e7ad6f88dcd4432fb73c126 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fran=C3=A7ois=20Cami?= -Date: Mon, 10 Dec 2018 17:12:03 +0100 -Subject: [PATCH] ipatests: add a test for ipa-client-automount -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Add an automount location then configure a client -to use it. Only runs nightly. - -Related-to: https://pagure.io/freeipa/issue/7780 -Related-to: https://pagure.io/freeipa/issue/7781 -Related to: https://pagure.io/freeipa/issue/7783 -Signed-off-by: François Cami -Reviewed-By: Alexander Bokovoy ---- - ipatests/prci_definitions/nightly_master.yaml | 12 +++ - .../test_automount_locations.py | 84 +++++++++++++++++++ - 2 files changed, 96 insertions(+) - create mode 100644 ipatests/test_integration/test_automount_locations.py - -diff --git a/ipatests/prci_definitions/nightly_master.yaml b/ipatests/prci_definitions/nightly_master.yaml -index 154e4c945d..b4dcc0870e 100644 ---- a/ipatests/prci_definitions/nightly_master.yaml -+++ b/ipatests/prci_definitions/nightly_master.yaml -@@ -663,3 +663,15 @@ jobs: - template: *ci-master-f29 - timeout: 3600 - topology: *master_1repl -+ -+ fedora-29/test_automount_locations: -+ requires: [fedora-29/build] -+ priority: 50 -+ job: -+ class: RunPytest -+ args: -+ build_url: '{fedora-29/build_url}' -+ test_suite: test_integration/test_automount_locations.py -+ template: *ci-master-f29 -+ timeout: 6300 -+ topology: *master_1repl -diff --git a/ipatests/test_integration/test_automount_locations.py b/ipatests/test_integration/test_automount_locations.py -new file mode 100644 -index 0000000000..646d1d07a0 ---- /dev/null -+++ b/ipatests/test_integration/test_automount_locations.py -@@ -0,0 +1,84 @@ -+# -+# Copyright (C) 2018 FreeIPA Contributors see COPYING for license -+# -+ -+"""This module provides tests for the automount location feature. -+""" -+ -+from __future__ import absolute_import -+ -+import time -+import re -+ -+from ipatests.test_integration.base import IntegrationTest -+from ipatests.pytest_ipa.integration import tasks -+ -+# give some time for units to stabilize -+# otherwise we get transient errors -+WAIT_AFTER_INSTALL = 5 -+WAIT_AFTER_UNINSTALL = WAIT_AFTER_INSTALL -+ -+ -+class TestAutomountInstallUninstall(IntegrationTest): -+ """ -+ Test if ipa-client-automount behaves as expected -+ """ -+ -+ num_replicas = 1 -+ topology = 'star' -+ -+ @classmethod -+ def install(cls, mh): -+ tasks.install_master(cls.master, setup_dns=False) -+ client = cls.replicas[0] -+ tasks.install_client(cls.master, client) -+ -+ def test_use_automount_location(self): -+ -+ client = self.replicas[0] -+ -+ self.master.run_command([ -+ "ipa", "automountlocation-add", "baltimore" -+ ]) -+ -+ self.master.run_command([ -+ "ipa", "host-mod", client.hostname, -+ "--location", "baltimore" -+ ]) -+ -+ # systemctl non-fatal errors will only be displayed -+ # if ipa-client-automount is launched with --debug -+ result1 = client.run_command([ -+ 'ipa-client-automount', '--location', 'baltimore', -+ '-U', '--debug' -+ ]) -+ -+ # systemctl non-fatal errors will show up like this: -+ # stderr=Failed to restart nfs-secure.service: \ -+ # Unit nfs-secure.service not found. -+ # normal output: -+ # stderr= -+ m1 = re.search(r'(?<=stderr\=Failed).+', result1.stderr_text) -+ # maybe re-use m1.group(0) if it exists. -+ assert m1 is None -+ -+ time.sleep(WAIT_AFTER_INSTALL) -+ -+ result2 = client.run_command([ -+ 'ipa-client-automount', '--uninstall', -+ '-U', '--debug' -+ ]) -+ -+ m2 = re.search(r'(?<=stderr\=Failed).+', result2.stderr_text) -+ assert m2 is None -+ -+ time.sleep(WAIT_AFTER_UNINSTALL) -+ -+ self.master.run_command([ -+ "ipa", "host-mod", client.hostname, -+ "--location", "''" -+ ]) -+ -+ self.master.run_command([ -+ "ipa", "automountlocation-del", "baltimore" -+ ]) diff --git a/SOURCES/0026-Fix_compile_issue_with_new_389-ds_rhbz#1659448.patch b/SOURCES/0026-Fix_compile_issue_with_new_389-ds_rhbz#1659448.patch deleted file mode 100644 index 640a3dd..0000000 --- a/SOURCES/0026-Fix_compile_issue_with_new_389-ds_rhbz#1659448.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 2b30b637561eb56a1fb73164322c9a74c8365c0b Mon Sep 17 00:00:00 2001 -From: Alexander Bokovoy -Date: Fri, 14 Dec 2018 14:02:26 +0200 -Subject: [PATCH] ipa-sidgen: make internal fetch_attr helper really internal - -With 389-ds landing a change for -https://pagure.io/389-ds-base/issue/49950, fetch_attr() helper function -is exposed in slapi-plugin.h. However, in order to be able to build -FreeIPA plugins against older 389-ds versions, prefer using a local -variant of it. - -Rename fetch_attr() to ipa_sidgen_fetch_attr() so that it doesn't -conflict at all. - -Fixes: https://pagure.io/freeipa/issue/7811 -Reviewed-By: Christian Heimes ---- - daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen_task.c | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen_task.c b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen_task.c -index 9e474e83dd..007b1c945d 100644 ---- a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen_task.c -+++ b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen_task.c -@@ -63,7 +63,7 @@ struct worker_ctx { - struct range_info **ranges; - }; - --static const char *fetch_attr(Slapi_Entry *e, const char *attrname, -+static const char *ipa_sidgen_fetch_attr(Slapi_Entry *e, const char *attrname, - const char *default_val) - { - Slapi_Attr *attr; -@@ -242,7 +242,7 @@ int sidgen_task_add(Slapi_PBlock *pb, Slapi_Entry *e, - - worker_ctx->plugin_id = global_sidgen_plugin_id; - -- str = fetch_attr(e, "delay", NULL); -+ str = ipa_sidgen_fetch_attr(e, "delay", NULL); - if (str != NULL) { - errno = 0; - worker_ctx->delay = strtol(str, &endptr, 10); -@@ -255,7 +255,7 @@ int sidgen_task_add(Slapi_PBlock *pb, Slapi_Entry *e, - } - LOG("delay is [%li].\n", worker_ctx->delay); - -- str = fetch_attr(e, "nsslapd-basedn", NULL); -+ str = ipa_sidgen_fetch_attr(e, "nsslapd-basedn", NULL); - if (str == NULL) { - LOG_FATAL("Missing nsslapd-basedn!\n"); - *returncode = LDAP_CONSTRAINT_VIOLATION; diff --git a/SOURCES/0027-ipaserver-dcerpc-fix-exclusion-entry-with-a-forest-t.patch b/SOURCES/0027-ipaserver-dcerpc-fix-exclusion-entry-with-a-forest-t.patch deleted file mode 100644 index 50cc957..0000000 --- a/SOURCES/0027-ipaserver-dcerpc-fix-exclusion-entry-with-a-forest-t.patch +++ /dev/null @@ -1,166 +0,0 @@ -From e5471e66c6a718ffa28433813b8a8d7896b16d9e Mon Sep 17 00:00:00 2001 -From: Alexander Bokovoy -Date: Mon, 7 Jan 2019 15:28:29 +0200 -Subject: [PATCH] ipaserver/dcerpc: fix exclusion entry with a forest trust - domain info returned - -When looking through the topology of a trusted forest, we should support -all types of forest trust records. Since Samba Python bindings parse the -data into a typed structure, a type of the record has to be taken into -account or there will be type mismatch when accessing elements of the -union: - - typedef [switch_type(lsa_ForestTrustRecordType)] union { - [case(LSA_FOREST_TRUST_TOP_LEVEL_NAME)] lsa_StringLarge top_level_name; - [case(LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX)] lsa_StringLarge top_level_name_ex; - [case(LSA_FOREST_TRUST_DOMAIN_INFO)] lsa_ForestTrustDomainInfo domain_info; - [default] lsa_ForestTrustBinaryData data; - } lsa_ForestTrustData; - - typedef struct { - lsa_ForestTrustRecordFlags flags; - lsa_ForestTrustRecordType type; - NTTIME_hyper time; - [switch_is(type)] lsa_ForestTrustData forest_trust_data; - } lsa_ForestTrustRecord; - - typedef [public] struct { - [range(0,4000)] uint32 count; - [size_is(count)] lsa_ForestTrustRecord **entries; - } lsa_ForestTrustInformation; - -Each entry in the lsa_ForestTrustInformation has forest_trust_data -member but its content depends on the value of a type member -(forest_trust_data is a union of all possible structures). - -Previously we assumed only TLN or TLN exclusion record which were -of the same type (lsa_StringLarge). Access to forest_trust_data.string -fails when forest_trust_data's type is lsa_ForestTrustDomainInfo as it -has no string member. - -Fix the code by properly accessing the dns_domain_name from the -lsa_ForestTrustDomainInfo structure. - -Fixes: https://pagure.io/freeipa/issue/7828 -Reviewed-By: Christian Heimes ---- - ipaserver/dcerpc.py | 64 ++++++++++++++++++++++++++++++++++++++------- - 1 file changed, 55 insertions(+), 9 deletions(-) - -diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py -index 125493657..51a8e82e7 100644 ---- a/ipaserver/dcerpc.py -+++ b/ipaserver/dcerpc.py -@@ -51,6 +51,7 @@ from samba.dcerpc import security, lsa, drsblobs, nbt, netlogon - from samba.ndr import ndr_pack, ndr_print - from samba import net - from samba import arcfour_encrypt -+from samba import ntstatus - import samba - - import ldap as _ldap -@@ -1105,6 +1106,25 @@ class TrustDomainInstance(object): - original forest. - """ - -+ def domain_name_from_ftinfo(ftinfo): -+ """ -+ Returns a domain name string from a ForestTrustRecord -+ -+ :param ftinfo: LSA ForestTrustRecord to parse -+ """ -+ if ftinfo.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO: -+ return ftinfo.forest_trust_data.dns_domain_name.string -+ elif ftinfo.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME: -+ return ftinfo.forest_trust_data.string -+ elif ftinfo.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX: -+ # We should ignore TLN exclusion record because it -+ # is already an exclusion so we aren't going to -+ # change anything here -+ return None -+ else: -+ # Ignore binary blobs we don't know about -+ return None -+ - # List of entries for unsolved conflicts - result = [] - -@@ -1145,18 +1165,26 @@ class TrustDomainInstance(object): - e1.time = e.time - e1.forest_trust_data = e.forest_trust_data - -+ # We either have a domain struct, a TLN name, -+ # or a TLN exclusion name in the list. -+ # The rest we should skip, those are binary blobs -+ dns_domain_name = domain_name_from_ftinfo(e) -+ - # Search for a match in the topology of another domain - # if there is a match, we have to convert a record - # into a TLN exclusion to allow its routing to the - # another domain - for r in another_domain.ftinfo_records: -- if r['rec_name'] == e.forest_trust_data.string: -+ # r['rec_name'] cannot be None, thus we can ignore -+ # the case when dns_domain_name is None -+ if r['rec_name'] == dns_domain_name: - is_our_record = True - - # Convert e1 into an exclusion record - e1.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX - e1.flags = 0 - e1.time = trust_timestamp -+ e1.forest_trust_data.string = dns_domain_name - break - entries.append(e1) - -@@ -1180,11 +1208,29 @@ class TrustDomainInstance(object): - # Update the forest trust information now - ldname = lsa.StringLarge() - ldname.string = rec.name.string -- cninfo = self._pipe.lsaRSetForestTrustInformation( -- self._policy_handle, -- ldname, -- lsa.LSA_FOREST_TRUST_DOMAIN_INFO, -- fti, 0) -+ cninfo = None -+ try: -+ cninfo = self._pipe.lsaRSetForestTrustInformation( -+ self._policy_handle, -+ ldname, -+ lsa.LSA_FOREST_TRUST_DOMAIN_INFO, -+ fti, 0) -+ except samba.NTSTATUSError as error: -+ # Handle NT_STATUS_INVALID_PARAMETER separately -+ if ntstatus.NT_STATUS_INVALID_PARAMETER == error.args[0]: -+ result.append(rec) -+ logger.error("Unable to resolve conflict for " -+ "DNS domain %s in the forest %s " -+ "for in-forest domain %s. Trust cannot " -+ "be established unless this conflict " -+ "is fixed manually.", -+ another_domain.info['dns_domain'], -+ self.info['dns_domain'], -+ rec.name.string) -+ else: -+ raise assess_dcerpc_error(error) -+ -+ - if cninfo: - result.append(rec) - logger.error("When defining exception for DNS " -@@ -1213,9 +1259,9 @@ class TrustDomainInstance(object): - # Otherwise, raise TrustTopologyConflictError() exception - domains = [x.name.string for x in result] - raise errors.TrustTopologyConflictError( -- target=self.info['dns_domain'], -- conflict=another_domain.info['dns_domain'], -- domains=domains) -+ forest=self.info['dns_domain'], -+ conflict=another_domain.info['dns_domain'], -+ domains=domains) - - - --- -2.20.1 - diff --git a/SOURCES/0028-Create-systemd-user-HBAC-service-and-rule.patch b/SOURCES/0028-Create-systemd-user-HBAC-service-and-rule.patch deleted file mode 100644 index 188d163..0000000 --- a/SOURCES/0028-Create-systemd-user-HBAC-service-and-rule.patch +++ /dev/null @@ -1,191 +0,0 @@ -From aaf938307acbe987f5e1effc2392894c22235013 Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Fri, 11 Jan 2019 11:18:05 +0100 -Subject: [PATCH] Create systemd-user HBAC service and rule - -authselect changed pam_systemd session from optional to required. When -the HBAC rule allow_all is disabled and replaced with more fine grained -rules, loginsi now to fail, because systemd's user@.service is able to -create a systemd session. - -Add systemd-user HBAC service and a HBAC rule that allows systemd-user -to run on all hosts for all users by default. ipa-server-upgrade creates -the service and rule, too. In case the service already exists, no -attempt is made to create the rule. This allows admins to delete the -rule permanently. - -See: https://bugzilla.redhat.com/show_bug.cgi?id=1643928 -Fixes: https://pagure.io/freeipa/issue/7831 -Signed-off-by: Christian Heimes -Reviewed-By: Alexander Bokovoy ---- - install/share/bootstrap-template.ldif | 8 +++ - install/share/default-hbac.ldif | 13 +++++ - ipaserver/install/server/upgrade.py | 36 +++++++++++++ - ipatests/test_integration/test_commands.py | 59 ++++++++++++++++++++++ - 4 files changed, 116 insertions(+) - -diff --git a/install/share/bootstrap-template.ldif b/install/share/bootstrap-template.ldif -index d48c4fafc..6cd17e37e 100644 ---- a/install/share/bootstrap-template.ldif -+++ b/install/share/bootstrap-template.ldif -@@ -346,6 +346,14 @@ cn: sudo-i - description: sudo-i - ipauniqueid:autogenerate - -+dn: cn=systemd-user,cn=hbacservices,cn=hbac,$SUFFIX -+changetype: add -+objectclass: ipahbacservice -+objectclass: ipaobject -+cn: systemd-user -+description: pam_systemd and systemd user@.service -+ipauniqueid:autogenerate -+ - dn: cn=gdm,cn=hbacservices,cn=hbac,$SUFFIX - changetype: add - objectclass: ipahbacservice -diff --git a/install/share/default-hbac.ldif b/install/share/default-hbac.ldif -index 52fd30ec9..8dd90685c 100644 ---- a/install/share/default-hbac.ldif -+++ b/install/share/default-hbac.ldif -@@ -12,3 +12,16 @@ ipaenabledflag: TRUE - description: Allow all users to access any host from any host - ipauniqueid: autogenerate - -+# default HBAC policy for pam_systemd -+dn: ipauniqueid=autogenerate,cn=hbac,$SUFFIX -+changetype: add -+objectclass: ipaassociation -+objectclass: ipahbacrule -+cn: allow_systemd-user -+accessruletype: allow -+usercategory: all -+hostcategory: all -+servicecategory: systemd-user -+ipaenabledflag: TRUE -+description: Allow pam_systemd to run user@.service to create a system user session -+ipauniqueid: autogenerate -diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py -index ae6fcc77e..3869bae3c 100644 ---- a/ipaserver/install/server/upgrade.py -+++ b/ipaserver/install/server/upgrade.py -@@ -1735,6 +1735,41 @@ def migrate_to_authselect(): - sysupgrade.set_upgrade_state('authcfg', 'migrated_to_authselect', True) - - -+def add_systemd_user_hbac(): -+ logger.info('[Create systemd-user hbac service and rule]') -+ rule = 'allow_systemd-user' -+ service = 'systemd-user' -+ try: -+ api.Command.hbacsvc_add( -+ service, -+ description='pam_systemd and systemd user@.service' -+ ) -+ except ipalib.errors.DuplicateEntry: -+ logger.info('hbac service %s already exists', service) -+ # Don't create hbac rule when hbacsvc already exists, so the rule -+ # does not get re-created after it has been deleted by an admin. -+ return -+ else: -+ logger.info('Created hbacsvc %s', service) -+ -+ try: -+ api.Command.hbacrule_add( -+ rule, -+ description=('Allow pam_systemd to run user@.service to create ' -+ 'a system user session'), -+ usercategory='all', -+ hostcategory='all', -+ ) -+ except ipalib.errors.DuplicateEntry: -+ logger.info('hbac rule %s already exists', rule) -+ else: -+ api.Command.hbacrule_add_service( -+ rule, -+ hbacsvc=(service,) -+ ) -+ logger.info('Created hbac rule %s with hbacsvc=%s', rule, service) -+ -+ - def fix_permissions(): - """Fix permission of public accessible files and directories - -@@ -2050,6 +2085,7 @@ def upgrade_configuration(): - cainstance.ensure_ipa_authority_entry() - - migrate_to_authselect() -+ add_systemd_user_hbac() - - sssd_update() - -diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py -index cfb2fa48d..1fb6450a2 100644 ---- a/ipatests/test_integration/test_commands.py -+++ b/ipatests/test_integration/test_commands.py -@@ -462,3 +462,62 @@ class TestIPACommand(IntegrationTest): - ['sudo', '-u', IPAAPI_USER, '--'] + cmd - ) - assert uid in result.stdout_text -+ -+ def test_hbac_systemd_user(self): -+ # https://pagure.io/freeipa/issue/7831 -+ tasks.kinit_admin(self.master) -+ # check for presence -+ self.master.run_command( -+ ['ipa', 'hbacrule-show', 'allow_systemd-user'] -+ ) -+ self.master.run_command( -+ ['ipa', 'hbacsvc-show', 'systemd-user'] -+ ) -+ -+ # delete both -+ self.master.run_command( -+ ['ipa', 'hbacrule-del', 'allow_systemd-user'] -+ ) -+ self.master.run_command( -+ ['ipa', 'hbacsvc-del', 'systemd-user'] -+ ) -+ -+ # run upgrade -+ result = self.master.run_command(['ipa-server-upgrade']) -+ assert 'Created hbacsvc systemd-user' in result.stderr_text -+ assert 'Created hbac rule allow_systemd-user' in result.stderr_text -+ -+ # check for presence -+ result = self.master.run_command( -+ ['ipa', 'hbacrule-show', 'allow_systemd-user', '--all'] -+ ) -+ lines = set(l.strip() for l in result.stdout_text.split('\n')) -+ assert 'User category: all' in lines -+ assert 'Host category: all' in lines -+ assert 'Enabled: TRUE' in lines -+ assert 'Services: systemd-user' in lines -+ assert 'accessruletype: allow' in lines -+ -+ self.master.run_command( -+ ['ipa', 'hbacsvc-show', 'systemd-user'] -+ ) -+ -+ # only delete rule -+ self.master.run_command( -+ ['ipa', 'hbacrule-del', 'allow_systemd-user'] -+ ) -+ -+ # run upgrade -+ result = self.master.run_command(['ipa-server-upgrade']) -+ assert ( -+ 'hbac service systemd-user already exists' in result.stderr_text -+ ) -+ assert ( -+ 'Created hbac rule allow_systemd-user' not in result.stderr_text -+ ) -+ result = self.master.run_command( -+ ['ipa', 'hbacrule-show', 'allow_systemd-user'], -+ raiseonerr=False -+ ) -+ assert result.returncode != 0 -+ assert 'HBAC rule not found' in result.stderr_text --- -2.20.1 - diff --git a/SOURCES/0029-Resolve_user_group_names_in_idoverride_-find_rhbz#1657745.patch b/SOURCES/0029-Resolve_user_group_names_in_idoverride_-find_rhbz#1657745.patch deleted file mode 100644 index 9d2cda5..0000000 --- a/SOURCES/0029-Resolve_user_group_names_in_idoverride_-find_rhbz#1657745.patch +++ /dev/null @@ -1,405 +0,0 @@ -BEGIN EXCERPT from 8182ebc6c3ca636276fc277186cfbff4ea9cf5c6 to have user_add -in ipatests/pytest_ipa/integration/tasks.py to be able to apply the patch set. - -commit 8182ebc6c3ca636276fc277186cfbff4ea9cf5c6 -Author: Sergey Orlov -Date: Wed Nov 7 11:23:05 2018 +0100 - - ipatests: add test for ipa-restore in multi-master configuration - - Test ensures that after ipa-restore on the master, the replica can be - re-synchronized and a new replica can be created. - - https://pagure.io/freeipa/issue/7455 - - Reviewed-By: Christian Heimes - Reviewed-By: Tibor Dudlak - -diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py -index 814141b83..90da8fa62 100644 ---- a/ipatests/pytest_ipa/integration/tasks.py -+++ b/ipatests/pytest_ipa/integration/tasks.py -@@ -1555,3 +1561,11 @@ def strip_cert_header(pem): - return s.group(1) - else: - return pem -+ -+ -+def user_add(host, login): -+ host.run_command([ -+ "ipa", "user-add", login, -+ "--first", "test", -+ "--last", "user" -+ ]) -END EXCERPT -From 5e6cb0ca034c711fe81fcfe7c651c5af3c65aa40 Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Dec 07 2018 15:06:13 +0000 -Subject: Resolve user/group names in idoverride*-find - - -ipa idoverrideuser-find and ...group-find have an --anchor argument. The -anchor argument used to support only anchor UUIDs like -':IPA:domain:UUID' or ':SID:S-sid'. The find commands now detect regular -user or group names and translate them to anchors. - -Fixes: https://pagure.io/freeipa/issue/6594 -Signed-off-by: Christian Heimes -Reviewed-By: Rob Crittenden - ---- - -diff --git a/ipaserver/plugins/idviews.py b/ipaserver/plugins/idviews.py -index 3252982..5213486 100644 ---- a/ipaserver/plugins/idviews.py -+++ b/ipaserver/plugins/idviews.py -@@ -766,6 +766,40 @@ class baseidoverride(LDAPObject): - error=_('Default Trust View cannot contain IPA users') - ) - -+ def filter_for_anchor(self, ldap, filter, options, obj_type): -+ """Modify filter to support user and group names -+ -+ Allow users to pass in an IPA user/group name and resolve it to an -+ anchor name. -+ -+ :param ldap: ldap connection -+ :param filter: pre_callback filter -+ :param options: option dict -+ :param obj_type: 'user' or 'group' -+ :return: modified or same filter -+ """ -+ anchor = options.get('ipaanchoruuid', None) -+ # return original filter if anchor is absent or correct -+ if anchor is None or ANCHOR_REGEX.match(anchor): -+ return filter -+ try: -+ resolved_anchor = resolve_object_to_anchor( -+ ldap, obj_type, anchor, -+ options.get('fallback_to_ldap', False) -+ ) -+ except (errors.NotFound, errors.ValidationError): -+ # anchor cannot be resolved, let it pass through -+ return filter -+ else: -+ return ldap.make_filter( -+ { -+ 'objectClass': self.object_class, -+ 'ipaanchoruuid': resolved_anchor, -+ }, -+ rules=ldap.MATCH_ALL -+ ) -+ -+ - class baseidoverride_add(LDAPCreate): - __doc__ = _('Add a new ID override.') - msg_summary = _('Added ID override "%(value)s"') -@@ -1128,6 +1162,15 @@ class idoverrideuser_find(baseidoverride_find): - msg_summary = ngettext('%(count)d User ID override matched', - '%(count)d User ID overrides matched', 0) - -+ def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, -+ **options): -+ result = super(idoverrideuser_find, self).pre_callback( -+ ldap, filter, attrs_list, base_dn, scope, *args, **options -+ ) -+ filter, base_dn, scope = result -+ filter = self.obj.filter_for_anchor(ldap, filter, options, 'user') -+ return filter, base_dn, scope -+ - def post_callback(self, ldap, entries, truncated, *args, **options): - truncated = super(idoverrideuser_find, self).post_callback( - ldap, entries, truncated, *args, **options) -@@ -1173,6 +1216,15 @@ class idoverridegroup_find(baseidoverride_find): - msg_summary = ngettext('%(count)d Group ID override matched', - '%(count)d Group ID overrides matched', 0) - -+ def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, -+ **options): -+ result = super(idoverridegroup_find, self).pre_callback( -+ ldap, filter, attrs_list, base_dn, scope, *args, **options -+ ) -+ filter, base_dn, scope = result -+ filter = self.obj.filter_for_anchor(ldap, filter, options, 'group') -+ return filter, base_dn, scope -+ - - @register() - class idoverridegroup_show(baseidoverride_show): - -From 11b06d24a94c5e92a0275df759bc81f0fc81d802 Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Dec 07 2018 15:06:13 +0000 -Subject: Add integration tests for idviews - - -Add several tests to verify new anchor override and general idview -override functionality. - -Fixes: https://pagure.io/freeipa/issue/6594 -Signed-off-by: Christian Heimes -Reviewed-By: Rob Crittenden - ---- - -diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py -index 36178e8..3548f2b 100644 ---- a/ipatests/pytest_ipa/integration/tasks.py -+++ b/ipatests/pytest_ipa/integration/tasks.py -@@ -1576,9 +1576,19 @@ def strip_cert_header(pem): - return pem - - --def user_add(host, login): -- host.run_command([ -+def user_add(host, login, first='test', last='user', extra_args=()): -+ cmd = [ - "ipa", "user-add", login, -- "--first", "test", -- "--last", "user" -- ]) -+ "--first", first, -+ "--last", last -+ ] -+ cmd.extend(extra_args) -+ return host.run_command(cmd) -+ -+ -+def group_add(host, groupname, extra_args=()): -+ cmd = [ -+ "ipa", "group-add", groupname, -+ ] -+ cmd.extend(extra_args) -+ return host.run_command(cmd) -diff --git a/ipatests/test_integration/test_idviews.py b/ipatests/test_integration/test_idviews.py -index 9a8f379..6ede4d0 100644 ---- a/ipatests/test_integration/test_idviews.py -+++ b/ipatests/test_integration/test_idviews.py -@@ -165,6 +165,7 @@ class TestRulesWithServicePrincipals(IntegrationTest): - - topology = 'star' - num_replicas = 0 -+ num_clients = 0 - service_certprofile = 'caIPAserviceCert' - caacl = 'test_caacl' - keytab = "replica.keytab" -@@ -238,3 +239,133 @@ EOF - raiseonerr=False) - assert(result.returncode == 0), ( - 'Failed to add a cert to custom certprofile') -+ -+ -+class TestIDViews(IntegrationTest): -+ topology = 'star' -+ num_replicas = 0 -+ num_clients = 1 -+ -+ user1 = 'testuser1' -+ user1_uid = 10001 -+ user1_gid = 10001 -+ user1_uid_override = 5001 -+ user1_gid_override = 6001 -+ -+ user2 = 'testuser2' -+ user2_uid = 10002 -+ user2_gid = 10002 -+ -+ group1 = 'testgroup1' -+ group1_gid = 11001 -+ group1_gid_override = 7001 -+ -+ idview = 'testview' -+ -+ @classmethod -+ def install(cls, mh): -+ super(TestIDViews, cls).install(mh) -+ master = cls.master -+ client = cls.clients[0] -+ tasks.kinit_admin(master) -+ -+ tasks.user_add( -+ master, cls.user1, first='Test1', -+ extra_args=[ -+ '--uid', str(cls.user1_uid), -+ '--gidnumber', str(cls.user1_gid), -+ ] -+ ) -+ tasks.user_add( -+ master, cls.user2, first='Test2', -+ extra_args=[ -+ '--uid', str(cls.user2_uid), -+ '--gidnumber', str(cls.user2_gid), -+ ] -+ ) -+ tasks.group_add( -+ master, cls.group1, extra_args=['--gid', str(cls.group1_gid)] -+ ) -+ -+ master.run_command(['ipa', 'idview-add', cls.idview]) -+ -+ # add overrides for user1 and its default user group -+ master.run_command([ -+ 'ipa', 'idoverrideuser-add', cls.idview, cls.user1, -+ '--uid', str(cls.user1_uid_override), -+ '--gid', str(cls.user1_gid_override), -+ '--homedir', '/special-home/{}'.format(cls.user1), -+ '--shell', '/bin/special' -+ ]) -+ master.run_command([ -+ 'ipa', 'idoverridegroup-add', cls.idview, cls.group1, -+ '--gid', str(cls.group1_gid_override), -+ ]) -+ -+ # ID view overrides don't work on IPA masters -+ master.run_command([ -+ 'ipa', 'idview-apply', cls.idview, -+ '--hosts', client.hostname -+ ]) -+ # finally restart SSSD to materialize idviews -+ client.run_command(['systemctl', 'restart', 'sssd.service']) -+ -+ def test_useroverride(self): -+ result = self.clients[0].run_command(['id', self.user1]) -+ assert 'uid={}'.format(self.user1_uid_override) in result.stdout_text -+ assert 'gid={}'.format(self.user1_gid_override) in result.stdout_text -+ -+ result = self.clients[0].run_command( -+ ['getent', 'passwd', str(self.user1_uid_override)] -+ ) -+ expected = '{}:*:{}:{}'.format( -+ self.user1, self.user1_uid_override, self.user1_gid_override -+ ) -+ assert expected in result.stdout_text -+ -+ result = self.master.run_command(['id', self.user1]) -+ assert 'uid={}'.format(self.user1_uid) in result.stdout_text -+ assert 'gid={}'.format(self.user1_gid) in result.stdout_text -+ -+ def test_useroverride_original_uid(self): -+ # It's still possible to request the user with its original UID. In -+ # this case the getent command returns the user with override uid. -+ result = self.clients[0].run_command( -+ ['getent', 'passwd', str(self.user1_uid)] -+ ) -+ expected = '{}:*:{}:{}'.format( -+ self.user1, self.user1_uid_override, self.user1_gid_override -+ ) -+ assert expected in result.stdout_text -+ -+ def test_anchor_username(self): -+ result = self.master.run_command([ -+ 'ipa', 'idoverrideuser-find', self.idview, '--anchor', self.user1 -+ ]) -+ expected = "Anchor to override: {}".format(self.user1) -+ assert expected in result.stdout_text -+ -+ def test_groupoverride(self): -+ result = self.clients[0].run_command(['getent', 'group', self.group1]) -+ assert ':{}:'.format(self.group1_gid_override) in result.stdout_text -+ -+ result = self.master.run_command(['getent', 'group', self.group1]) -+ assert ':{}:'.format(self.group1_gid) in result.stdout_text -+ -+ def test_groupoverride_system_objects(self): -+ # group override for user group should fail -+ result = self.master.run_command( -+ ['ipa', 'idoverridegroup-add', self.idview, self.user1, -+ '--gid', str(self.user1_gid_override)], -+ raiseonerr=False -+ ) -+ assert result.returncode == 1 -+ assert "cannot be overridden" in result.stderr_text -+ -+ def test_anchor_groupname(self): -+ result = self.master.run_command([ -+ 'ipa', 'idoverridegroup-find', self.idview, -+ '--anchor', self.group1 -+ ]) -+ expected = "Anchor to override: {}".format(self.group1) -+ assert expected in result.stdout_text - -ONLY APPLYING TO ipatests/prci_definitions/nightly_rawhide.yaml, other -files are not available or compatible - -From e86498ea2f8259118025e622cc5f1cf2c26f2757 Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Dec 07 2018 15:06:13 +0000 -Subject: Run idviews integration tests in nightly - - -See: https://pagure.io/freeipa/issue/6594 -Signed-off-by: Christian Heimes -Reviewed-By: Rob Crittenden - ---- - -#diff --git a/ipatests/prci_definitions/nightly_f28.yaml b/ipatests/prci_definitions/nightly_f28.yaml -#index 8462c14..ac792f1 100644 -#--- a/ipatests/prci_definitions/nightly_f28.yaml -#+++ b/ipatests/prci_definitions/nightly_f28.yaml -#@@ -195,6 +195,18 @@ jobs: -# timeout: 10800 -# topology: *master_1repl -# -#+ fedora-28/test_idviews: -#+ requires: [fedora-28/build] -#+ priority: 50 -#+ job: -#+ class: RunPytest -#+ args: -#+ build_url: '{fedora-28/build_url}' -#+ test_suite: test_integration/test_idviews.py::TestIDViews -#+ template: *ci-master-f28 -#+ timeout: 3600 -#+ topology: *master_1repl_1client -#+ -# fedora-28/test_caless_TestServerInstall: -# requires: [fedora-28/build] -# priority: 50 -#diff --git a/ipatests/prci_definitions/nightly_master.yaml b/ipatests/prci_definitions/nightly_master.yaml -#index 3f2b346..953a60e 100644 -#--- a/ipatests/prci_definitions/nightly_master.yaml -#+++ b/ipatests/prci_definitions/nightly_master.yaml -#@@ -195,6 +195,18 @@ jobs: -# timeout: 10800 -# topology: *master_1repl -# -#+ fedora-28/test_idviews: -#+ requires: [fedora-29/build] -#+ priority: 50 -#+ job: -#+ class: RunPytest -#+ args: -#+ build_url: '{fedora-29/build_url}' -#+ test_suite: test_integration/test_idviews.py::TestIDViews -#+ template: *ci-master-f29 -#+ timeout: 3600 -#+ topology: *master_1repl_1client -#+ -# fedora-29/test_caless_TestServerInstall: -# requires: [fedora-29/build] -# priority: 50 -diff --git a/ipatests/prci_definitions/nightly_rawhide.yaml b/ipatests/prci_definitions/nightly_rawhide.yaml -index bdc34d2..e74e5f6 100644 ---- a/ipatests/prci_definitions/nightly_rawhide.yaml -+++ b/ipatests/prci_definitions/nightly_rawhide.yaml -@@ -195,6 +195,18 @@ jobs: - timeout: 10800 - topology: *master_1repl - -+ fedora-28/test_idviews: -+ requires: [fedora-rawhide/build] -+ priority: 50 -+ job: -+ class: RunPytest -+ args: -+ build_url: '{fedora-rawhide/build_url}' -+ test_suite: test_integration/test_idviews.py::TestIDViews -+ template: *ci-master-frawhide -+ timeout: 3600 -+ topology: *master_1repl_1client -+ - fedora-rawhide/test_caless_TestServerInstall: - requires: [fedora-rawhide/build] - priority: 50 - diff --git a/SOURCES/0030-Fix-systemd-user-HBAC-rule.patch b/SOURCES/0030-Fix-systemd-user-HBAC-rule.patch deleted file mode 100644 index 50fe287..0000000 --- a/SOURCES/0030-Fix-systemd-user-HBAC-rule.patch +++ /dev/null @@ -1,58 +0,0 @@ -From b3f06994b7b44a0f9cd0c6bd0302c9db87dc2502 Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Tue, 15 Jan 2019 17:33:56 +0100 -Subject: [PATCH] Fix systemd-user HBAC rule - -2ef6e14c5a87724a3b37dd5f0817af48c4411e03 added an invalid HBAC rule that -encoded the service wrongly. - -See: https://bugzilla.redhat.com/show_bug.cgi?id=1643928 -Fixes: https://pagure.io/freeipa/issue/7831 -Signed-off-by: Christian Heimes ---- - install/share/default-hbac.ldif | 2 +- - ipatests/test_integration/test_commands.py | 12 +++++++++--- - 2 files changed, 10 insertions(+), 4 deletions(-) - -diff --git a/install/share/default-hbac.ldif b/install/share/default-hbac.ldif -index 8dd90685c..c89bd3eef 100644 ---- a/install/share/default-hbac.ldif -+++ b/install/share/default-hbac.ldif -@@ -21,7 +21,7 @@ cn: allow_systemd-user - accessruletype: allow - usercategory: all - hostcategory: all --servicecategory: systemd-user -+memberService: cn=systemd-user,cn=hbacservices,cn=hbac,$SUFFIX - ipaenabledflag: TRUE - description: Allow pam_systemd to run user@.service to create a system user session - ipauniqueid: autogenerate -diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py -index 1fb6450a2..8b2c84fc6 100644 ---- a/ipatests/test_integration/test_commands.py -+++ b/ipatests/test_integration/test_commands.py -@@ -500,12 +500,18 @@ class TestIPACommand(IntegrationTest): - # https://pagure.io/freeipa/issue/7831 - tasks.kinit_admin(self.master) - # check for presence -- self.master.run_command( -- ['ipa', 'hbacrule-show', 'allow_systemd-user'] -- ) - self.master.run_command( - ['ipa', 'hbacsvc-show', 'systemd-user'] - ) -+ result = self.master.run_command( -+ ['ipa', 'hbacrule-show', 'allow_systemd-user', '--all'] -+ ) -+ lines = set(l.strip() for l in result.stdout_text.split('\n')) -+ assert 'User category: all' in lines -+ assert 'Host category: all' in lines -+ assert 'Enabled: TRUE' in lines -+ assert 'Services: systemd-user' in lines -+ assert 'accessruletype: allow' in lines - - # delete both - self.master.run_command( --- -2.20.1 - diff --git a/SOURCES/0031-ipa-client-automount-handle-NFS-configuration-file-c.patch b/SOURCES/0031-ipa-client-automount-handle-NFS-configuration-file-c.patch deleted file mode 100644 index 04ddf59..0000000 --- a/SOURCES/0031-ipa-client-automount-handle-NFS-configuration-file-c.patch +++ /dev/null @@ -1,189 +0,0 @@ -From c69875c8afdd877baf7139c0cd5241f70105cbd4 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fran=C3=A7ois=20Cami?= -Date: Tue, 26 Feb 2019 13:59:06 +0100 -Subject: [PATCH] ipa-client-automount: handle NFS configuration file changes -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -nfs-utils in Fedora 30 and later switched its configuration -file from /etc/sysconfig/nfs to /etc/nfs.conf, providing a -conversion service (nfs-convert.service) for upgrades. -However, for new installs the original configuration file -is missing. This change: -* adds a tuple-based osinfo.version_number method to handle - more kinds of OS versioning schemes -* detects RHEL and Fedora versions with the the new nfs-utils - behavior -* avoids backing up the new NFS configuration file as we do - not have to modify it. - -See: https://bugzilla.redhat.com/show_bug.cgi?id=1676981 - -Fixes: https://pagure.io/freeipa/issue/7868 -Signed-off-by: François Cami -Reviewed-By: Alexander Bokovoy -Reviewed-By: Christian Heimes -Reviewed-By: Rob Crittenden ---- - client/ipa-client-automount.in | 18 ++++++++++-------- - ipaplatform/fedora/constants.py | 9 ++++++++- - ipaplatform/fedora/paths.py | 3 +++ - ipaplatform/fedora/services.py | 2 +- - ipaplatform/osinfo.py | 9 +++++++++ - ipaplatform/rhel/constants.py | 7 +++++++ - ipaplatform/rhel/paths.py | 4 +++- - 7 files changed, 41 insertions(+), 11 deletions(-) - -diff --git a/client/ipa-client-automount.in b/client/ipa-client-automount.in -index 15926bd..f9eda9c 100755 ---- a/client/ipa-client-automount.in -+++ b/client/ipa-client-automount.in -@@ -335,14 +335,16 @@ def configure_nfs(fstore, statestore): - """ - Configure secure NFS - """ -- replacevars = { -- constants.SECURE_NFS_VAR: 'yes', -- } -- ipautil.backup_config_and_replace_variables(fstore, -- paths.SYSCONFIG_NFS, replacevars=replacevars) -- tasks.restore_context(paths.SYSCONFIG_NFS) -- -- print("Configured %s" % paths.SYSCONFIG_NFS) -+ # Newer Fedora releases ship /etc/nfs.conf instead of /etc/sysconfig/nfs -+ # and do not require changes there. On these, SECURE_NFS_VAR == None -+ if constants.SECURE_NFS_VAR: -+ replacevars = { -+ constants.SECURE_NFS_VAR: 'yes', -+ } -+ ipautil.backup_config_and_replace_variables(fstore, -+ paths.SYSCONFIG_NFS, replacevars=replacevars) -+ tasks.restore_context(paths.SYSCONFIG_NFS) -+ print("Configured %s" % paths.SYSCONFIG_NFS) - - # Prepare the changes - # We need to use IPAChangeConf as simple regexp substitution -diff --git a/ipaplatform/fedora/constants.py b/ipaplatform/fedora/constants.py -index d48696e..744b30a 100644 ---- a/ipaplatform/fedora/constants.py -+++ b/ipaplatform/fedora/constants.py -@@ -10,6 +10,12 @@ This Fedora base platform module exports platform related constants. - from __future__ import absolute_import - - from ipaplatform.redhat.constants import RedHatConstantsNamespace -+from ipaplatform.osinfo import osinfo -+ -+# Fedora 28 and earlier use /etc/sysconfig/nfs -+# Fedora 30 and later use /etc/nfs.conf -+# Fedora 29 has both -+HAS_NFS_CONF = osinfo.version_number >= (30,) - - - class FedoraConstantsNamespace(RedHatConstantsNamespace): -@@ -22,6 +28,7 @@ class FedoraConstantsNamespace(RedHatConstantsNamespace): - # secure remote password, and DSA cert authentication. - # see https://fedoraproject.org/wiki/Changes/CryptoPolicy - TLS_HIGH_CIPHERS = "PROFILE=SYSTEM:!3DES:!PSK:!SRP:!aDSS" -- -+ if HAS_NFS_CONF: -+ SECURE_NFS_VAR = None - - constants = FedoraConstantsNamespace() -diff --git a/ipaplatform/fedora/paths.py b/ipaplatform/fedora/paths.py -index a9bdedf..4e993c0 100644 ---- a/ipaplatform/fedora/paths.py -+++ b/ipaplatform/fedora/paths.py -@@ -26,6 +26,7 @@ in Fedora-based systems. - from __future__ import absolute_import - - from ipaplatform.redhat.paths import RedHatPathNamespace -+from ipaplatform.fedora.constants import HAS_NFS_CONF - - - class FedoraPathNamespace(RedHatPathNamespace): -@@ -33,6 +34,8 @@ class FedoraPathNamespace(RedHatPathNamespace): - "/etc/httpd/conf.modules.d/02-ipa-wsgi.conf" - ) - NAMED_CRYPTO_POLICY_FILE = "/etc/crypto-policies/back-ends/bind.config" -+ if HAS_NFS_CONF: -+ SYSCONFIG_NFS = '/etc/nfs.conf' - - - paths = FedoraPathNamespace() -diff --git a/ipaplatform/fedora/services.py b/ipaplatform/fedora/services.py -index 5ff64f1..543cb1b 100644 ---- a/ipaplatform/fedora/services.py -+++ b/ipaplatform/fedora/services.py -@@ -34,7 +34,7 @@ fedora_system_units = redhat_services.redhat_system_units.copy() - # Fedora 28 and earlier have fedora-domainname.service. Starting from - # Fedora 29, the service is called nis-domainname.service as defined in - # ipaplatform.redhat.services. --HAS_FEDORA_DOMAINNAME_SERVICE = int(osinfo.version_id) <= 28 -+HAS_FEDORA_DOMAINNAME_SERVICE = osinfo.version_number <= (28,) - - if HAS_FEDORA_DOMAINNAME_SERVICE: - fedora_system_units['domainname'] = 'fedora-domainname.service' -diff --git a/ipaplatform/osinfo.py b/ipaplatform/osinfo.py -index a38165d..35b024e 100644 ---- a/ipaplatform/osinfo.py -+++ b/ipaplatform/osinfo.py -@@ -178,6 +178,15 @@ class OSInfo(Mapping): - return self._info.get('VERSION_ID') - - @property -+ def version_number(self): -+ """Version number tuple based on version_id -+ """ -+ version_id = self._info.get('VERSION_ID') -+ if not version_id: -+ return () -+ return tuple(int(p) for p in version_id.split('.')) -+ -+ @property - def platform_ids(self): - """Ordered tuple of detected platforms (including override) - """ -diff --git a/ipaplatform/rhel/constants.py b/ipaplatform/rhel/constants.py -index 72335ac..073e332 100644 ---- a/ipaplatform/rhel/constants.py -+++ b/ipaplatform/rhel/constants.py -@@ -10,10 +10,17 @@ This RHEL base platform module exports platform related constants. - from __future__ import absolute_import - - from ipaplatform.redhat.constants import RedHatConstantsNamespace -+from ipaplatform.osinfo import osinfo -+ -+# RHEL 7 and earlier use /etc/sysconfig/nfs -+# RHEL 8 uses /etc/nfs.conf -+HAS_NFS_CONF = osinfo.version_number >= (8,) - - - class RHELConstantsNamespace(RedHatConstantsNamespace): - IPA_ADTRUST_PACKAGE_NAME = "ipa-server-trust-ad" - IPA_DNS_PACKAGE_NAME = "ipa-server-dns" -+ if HAS_NFS_CONF: -+ SECURE_NFS_VAR = None - - constants = RHELConstantsNamespace() -diff --git a/ipaplatform/rhel/paths.py b/ipaplatform/rhel/paths.py -index d8b64ab..c081ada 100644 ---- a/ipaplatform/rhel/paths.py -+++ b/ipaplatform/rhel/paths.py -@@ -26,10 +26,12 @@ in RHEL-based systems. - from __future__ import absolute_import - - from ipaplatform.redhat.paths import RedHatPathNamespace -+from ipaplatform.rhel.constants import HAS_NFS_CONF - - - class RHELPathNamespace(RedHatPathNamespace): -- pass -+ if HAS_NFS_CONF: -+ SYSCONFIG_NFS = '/etc/nfs.conf' - - - paths = RHELPathNamespace() --- -2.9.3 - diff --git a/SOURCES/1002-4.8.0-Remove-csrgen.patch b/SOURCES/1002-4.8.0-Remove-csrgen.patch new file mode 100644 index 0000000..8b7e374 --- /dev/null +++ b/SOURCES/1002-4.8.0-Remove-csrgen.patch @@ -0,0 +1,2051 @@ +Addtional patches that need to be partly reverted that are touching csrgen +related files: + +7b8a2af2197381058ca532d1ae206defb16fac88 +ac6568dcf58ec8d06df5493d14a28aa41845d4ef +9c86d35a3f0af4a793fada7dfe726e9cc66782ea +9836511a2b6d7cf48b1a54cb3158e5eac674081a +b431e9b684df11c811892bd9d2a5711355f0076e + +This is a collection of an existing patch to remove csrgen for 4.7.1 and +additional patches that have been added for 4.7.90 pre1. + +Additional reverted csrgen patches: + +852618fd6529fbdd7b03077fae37c6fbbe45b51b +0ac1d3ea62efd9751fcc59cea46bcdafe1f11c37 +7633d62d858c14523a99143aa0ff36f76bb4ff68 +53f87ee5cd9d19f6fb91a9a1eafc8ea798095954 +395a68d20887d0ac010e480e68b225d6dfeff726 +03786ad9f3bd5edc351040847b8a49c9cd9288b2 +c9d710a446d10aad72795e15bf041b87102628c1 +2b90c8a20e45ade9bfd27731cccc94a34cf3f61e +61dde27f70b9f8dd1b57ad1fbc3744f3c380613a +806784dbd9e69a89c7a705c89bf42ba1fd4265c9 +79378c90512a1cdd5f3d5ec6482e434caea06e01 +bd5a5012d24820b54cdca2955f5405b84de1178c +26ab51ddf47f421f3404709052db89f08c05adaa +a53e17830c3d4fd59a62248d4447491675c6a80e +e7588ab2dc73e7f66ebc6cdcfb99470540e37731 +136c6c3e2a4f77a27f435efd4a1cd95c9e089314 +5420e9cfbe7803808b6e26d2dae64f2a6a50149a + +Original patch from 4.7.1: + +From 468bcf90cb985e2b1eb394bd752dc39aa4b75582 Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Thu, 19 Jul 2018 18:37:18 -0400 +Subject: [PATCH] Remove csrgen + +This reverts commits: +* 72de679eb445c975ec70cd265d37d4927823ce5b +* 177f07e163d6d591a1e609d35e0a6f6f5347551e +* 80be18162921268be9c8981495c9e8a4de0c85cd +* 83e2c2b65eeb5a3aa4a59c0535e9177aac5e4637 +* ada91c20588046bb147fc701718d3da4d2c080ca +* 4350dcdea22fd2284836315d0ae7d38733a7620e +* 39a5d9c5aae77687f67d9be02457733bdfb99ead +* a26cf0d7910dd4c0a4da08682b4be8d3d94ba520 +* afd7c05d11432304bfdf183832a21d419f363689 +* f1a1c6eca1b294f24174d7b0e1f78de46d9d5b05 +* fc58eff6a3d7fe805e612b8b002304d8b9cd4ba9 +* 10ef5947860f5098182b1f95c08c1158e2da15f9 + +https://bugzilla.redhat.com/show_bug.cgi?id=1432630 +--- + freeipa.spec.in | 14 - + ipaclient/csrgen.py | 488 --------------------- + ipaclient/csrgen/profiles/caIPAserviceCert.json | 15 - + ipaclient/csrgen/profiles/userCert.json | 15 - + ipaclient/csrgen/rules/dataDNS.json | 8 - + ipaclient/csrgen/rules/dataEmail.json | 8 - + ipaclient/csrgen/rules/dataHostCN.json | 8 - + ipaclient/csrgen/rules/dataSubjectBase.json | 8 - + ipaclient/csrgen/rules/dataUsernameCN.json | 8 - + ipaclient/csrgen/rules/syntaxSAN.json | 8 - + ipaclient/csrgen/rules/syntaxSubject.json | 9 - + ipaclient/csrgen/templates/openssl_base.tmpl | 17 - + ipaclient/csrgen/templates/openssl_macros.tmpl | 29 -- + ipaclient/csrgen_ffi.py | 331 -------------- + ipaclient/plugins/cert.py | 80 ---- + ipaclient/plugins/csrgen.py | 128 ------ + ipaclient/setup.py | 8 - + .../data/test_csrgen/configs/caIPAserviceCert.conf | 16 - + .../data/test_csrgen/configs/userCert.conf | 16 - + .../data/test_csrgen/profiles/profile.json | 8 - + .../data/test_csrgen/rules/basic.json | 5 - + .../data/test_csrgen/rules/options.json | 8 - + .../data/test_csrgen/templates/identity_base.tmpl | 1 - + ipatests/test_ipaclient/test_csrgen.py | 304 ------------- + 24 files changed, 1540 deletions(-) + delete mode 100644 ipaclient/csrgen.py + delete mode 100644 ipaclient/csrgen/profiles/caIPAserviceCert.json + delete mode 100644 ipaclient/csrgen/profiles/userCert.json + delete mode 100644 ipaclient/csrgen/rules/dataDNS.json + delete mode 100644 ipaclient/csrgen/rules/dataEmail.json + delete mode 100644 ipaclient/csrgen/rules/dataHostCN.json + delete mode 100644 ipaclient/csrgen/rules/dataSubjectBase.json + delete mode 100644 ipaclient/csrgen/rules/dataUsernameCN.json + delete mode 100644 ipaclient/csrgen/rules/syntaxSAN.json + delete mode 100644 ipaclient/csrgen/rules/syntaxSubject.json + delete mode 100644 ipaclient/csrgen/templates/openssl_base.tmpl + delete mode 100644 ipaclient/csrgen/templates/openssl_macros.tmpl + delete mode 100644 ipaclient/csrgen_ffi.py + delete mode 100644 ipaclient/plugins/csrgen.py + delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/configs/caIPAserviceCert.conf + delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/configs/userCert.conf + delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/profiles/profile.json + delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/rules/basic.json + delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/rules/options.json + delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/templates/identity_base.tmpl + delete mode 100644 ipatests/test_ipaclient/test_csrgen.py + +diff -urN freeipa-4.8.0/freeipa.spec.in freeipa-4.8.0.removed_csrgen/freeipa.spec.in +--- freeipa-4.8.0/freeipa.spec.in 2019-06-29 10:01:30.458735813 +0200 ++++ freeipa-4.8.0.removed_csrgen/freeipa.spec.in 2019-07-03 13:24:38.471222723 +0200 +@@ -1247,13 +1247,6 @@ + %dir %{python3_sitelib}/ipaclient/remote_plugins/2_* + %{python3_sitelib}/ipaclient/remote_plugins/2_*/*.py + %{python3_sitelib}/ipaclient/remote_plugins/2_*/__pycache__/*.py* +-%dir %{python3_sitelib}/ipaclient/csrgen +-%dir %{python3_sitelib}/ipaclient/csrgen/profiles +-%{python3_sitelib}/ipaclient/csrgen/profiles/*.json +-%dir %{python3_sitelib}/ipaclient/csrgen/rules +-%{python3_sitelib}/ipaclient/csrgen/rules/*.json +-%dir %{python3_sitelib}/ipaclient/csrgen/templates +-%{python3_sitelib}/ipaclient/csrgen/templates/*.tmpl + %{python3_sitelib}/ipaclient-*.egg-info + + +diff -urN freeipa-4.8.0/ipaclient/csrgen/profiles/caIPAserviceCert.json freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/profiles/caIPAserviceCert.json +--- freeipa-4.8.0/ipaclient/csrgen/profiles/caIPAserviceCert.json 2019-07-03 08:42:41.844539797 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/profiles/caIPAserviceCert.json 1970-01-01 01:00:00.000000000 +0100 +@@ -1,15 +0,0 @@ +-[ +- { +- "syntax": "syntaxSubject", +- "data": [ +- "dataHostCN", +- "dataSubjectBase" +- ] +- }, +- { +- "syntax": "syntaxSAN", +- "data": [ +- "dataDNS" +- ] +- } +-] +diff -urN freeipa-4.8.0/ipaclient/csrgen/profiles/userCert.json freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/profiles/userCert.json +--- freeipa-4.8.0/ipaclient/csrgen/profiles/userCert.json 2019-07-03 08:42:41.848539737 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/profiles/userCert.json 1970-01-01 01:00:00.000000000 +0100 +@@ -1,15 +0,0 @@ +-[ +- { +- "syntax": "syntaxSubject", +- "data": [ +- "dataUsernameCN", +- "dataSubjectBase" +- ] +- }, +- { +- "syntax": "syntaxSAN", +- "data": [ +- "dataEmail" +- ] +- } +-] +diff -urN freeipa-4.8.0/ipaclient/csrgen/rules/dataDNS.json freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/rules/dataDNS.json +--- freeipa-4.8.0/ipaclient/csrgen/rules/dataDNS.json 2019-07-03 08:42:41.853539663 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/rules/dataDNS.json 1970-01-01 01:00:00.000000000 +0100 +@@ -1,8 +0,0 @@ +-{ +- "rule": { +- "template": "DNS = {{subject.krbprincipalname.0.partition('/')[2].partition('@')[0]}}" +- }, +- "options": { +- "data_source": "subject.krbprincipalname.0.partition('/')[2].partition('@')[0]" +- } +-} +diff -urN freeipa-4.8.0/ipaclient/csrgen/rules/dataEmail.json freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/rules/dataEmail.json +--- freeipa-4.8.0/ipaclient/csrgen/rules/dataEmail.json 2019-07-03 08:42:41.857539603 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/rules/dataEmail.json 1970-01-01 01:00:00.000000000 +0100 +@@ -1,8 +0,0 @@ +-{ +- "rule": { +- "template": "email = {{subject.mail.0}}" +- }, +- "options": { +- "data_source": "subject.mail.0" +- } +-} +diff -urN freeipa-4.8.0/ipaclient/csrgen/rules/dataHostCN.json freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/rules/dataHostCN.json +--- freeipa-4.8.0/ipaclient/csrgen/rules/dataHostCN.json 2019-07-03 08:42:41.861539544 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/rules/dataHostCN.json 1970-01-01 01:00:00.000000000 +0100 +@@ -1,8 +0,0 @@ +-{ +- "rule": { +- "template": "CN={{subject.krbprincipalname.0.partition('/')[2].partition('@')[0]}}" +- }, +- "options": { +- "data_source": "subject.krbprincipalname.0.partition('/')[2].partition('@')[0]" +- } +-} +diff -urN freeipa-4.8.0/ipaclient/csrgen/rules/dataSubjectBase.json freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/rules/dataSubjectBase.json +--- freeipa-4.8.0/ipaclient/csrgen/rules/dataSubjectBase.json 2019-07-03 08:42:41.865539484 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/rules/dataSubjectBase.json 1970-01-01 01:00:00.000000000 +0100 +@@ -1,8 +0,0 @@ +-{ +- "rule": { +- "template": "{{config.ipacertificatesubjectbase.0}}" +- }, +- "options": { +- "data_source": "config.ipacertificatesubjectbase.0" +- } +-} +diff -urN freeipa-4.8.0/ipaclient/csrgen/rules/dataUsernameCN.json freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/rules/dataUsernameCN.json +--- freeipa-4.8.0/ipaclient/csrgen/rules/dataUsernameCN.json 2019-07-03 08:42:41.869539424 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/rules/dataUsernameCN.json 1970-01-01 01:00:00.000000000 +0100 +@@ -1,8 +0,0 @@ +-{ +- "rule": { +- "template": "CN={{subject.uid.0}}" +- }, +- "options": { +- "data_source": "subject.uid.0" +- } +-} +diff -urN freeipa-4.8.0/ipaclient/csrgen/rules/syntaxSAN.json freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/rules/syntaxSAN.json +--- freeipa-4.8.0/ipaclient/csrgen/rules/syntaxSAN.json 2019-07-03 08:42:41.874539350 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/rules/syntaxSAN.json 1970-01-01 01:00:00.000000000 +0100 +@@ -1,8 +0,0 @@ +-{ +- "rule": { +- "template": "subjectAltName = @{% call openssl.section() %}{{ datarules|join('\n') }}{% endcall %}" +- }, +- "options": { +- "extension": true +- } +-} +diff -urN freeipa-4.8.0/ipaclient/csrgen/rules/syntaxSubject.json freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/rules/syntaxSubject.json +--- freeipa-4.8.0/ipaclient/csrgen/rules/syntaxSubject.json 2019-07-03 08:42:41.878539290 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/rules/syntaxSubject.json 1970-01-01 01:00:00.000000000 +0100 +@@ -1,9 +0,0 @@ +-{ +- "rule": { +- "template": "distinguished_name = {% call openssl.section() %}{{ datarules|reverse|join('\n') }}{% endcall %}" +- }, +- "options": { +- "required": true, +- "data_source_combinator": "and" +- } +-} +diff -urN freeipa-4.8.0/ipaclient/csrgen/templates/openssl_base.tmpl freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/templates/openssl_base.tmpl +--- freeipa-4.8.0/ipaclient/csrgen/templates/openssl_base.tmpl 2019-07-03 08:42:41.882539231 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/templates/openssl_base.tmpl 1970-01-01 01:00:00.000000000 +0100 +@@ -1,17 +0,0 @@ +-{% raw -%} +-{% import "openssl_macros.tmpl" as openssl -%} +-{% endraw -%} +-[ req ] +-prompt = no +-encrypt_key = no +- +-{{ parameters|join('\n') }} +-{% raw %}{% set rendered_extensions -%}{% endraw %} +-{{ extensions|join('\n') }} +-{% raw -%} +-{%- endset -%} +-{% if rendered_extensions -%} +-req_extensions = {% call openssl.section() %}{{ rendered_extensions }}{% endcall %} +-{% endif %} +-{{ openssl.openssl_sections|join('\n\n') }} +-{%- endraw %} +diff -urN freeipa-4.8.0/ipaclient/csrgen/templates/openssl_macros.tmpl freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/templates/openssl_macros.tmpl +--- freeipa-4.8.0/ipaclient/csrgen/templates/openssl_macros.tmpl 2019-07-03 08:42:41.886539171 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/csrgen/templates/openssl_macros.tmpl 1970-01-01 01:00:00.000000000 +0100 +@@ -1,29 +0,0 @@ +-{# List containing rendered sections to be included at end #} +-{% set openssl_sections = [] %} +- +-{# +-List containing one entry for each section name allocated. Because of +-scoping rules, we need to use a list so that it can be a "per-render global" +-that gets updated in place. Real globals are shared by all templates with the +-same environment, and variables defined in the macro don't persist after the +-macro invocation ends. +-#} +-{% set openssl_section_num = [] %} +- +-{% macro section() -%} +-{% set name -%} +-sec{{ openssl_section_num|length -}} +-{% endset -%} +-{% do openssl_section_num.append('') -%} +-{% set contents %}{{ caller() }}{% endset -%} +-{% if contents -%} +-{% set sectiondata = formatsection(name, contents) -%} +-{% do openssl_sections.append(sectiondata) -%} +-{% endif -%} +-{{ name -}} +-{% endmacro %} +- +-{% macro formatsection(name, contents) -%} +-[ {{ name }} ] +-{{ contents -}} +-{% endmacro %} +diff -urN freeipa-4.8.0/ipaclient/csrgen_ffi.py freeipa-4.8.0.removed_csrgen/ipaclient/csrgen_ffi.py +--- freeipa-4.8.0/ipaclient/csrgen_ffi.py 2019-07-03 08:42:41.816540214 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/csrgen_ffi.py 1970-01-01 01:00:00.000000000 +0100 +@@ -1,387 +0,0 @@ +-from cffi import FFI +-import ctypes.util +- +-from ipalib import errors +- +-_ffi = FFI() +- +-_ffi.cdef(''' +-/* libcrypto/crypto.h */ +-unsigned long OpenSSL_version_num(void); +-unsigned long SSLeay(void); +-const char * OpenSSL_version(int t); +-const char * SSLeay_version(int t); +- +-#define OPENSSL_VERSION 0 +-''') +- +-_libcrypto = _ffi.dlopen(ctypes.util.find_library('crypto')) +- +-# SSLeay_version has been renamed with OpenSSL_version in OpenSSL 1.1.0 +-# LibreSSL has OpenSSL_version since 2.7.0 +-try: +- OpenSSL_version = _libcrypto.OpenSSL_version +-except AttributeError: +- OpenSSL_version = _libcrypto.SSLeay_version +- +-_version = OpenSSL_version(_libcrypto.OPENSSL_VERSION) +-_version = _ffi.string(_version).decode('utf-8') +-LIBRESSL = _version.startswith('LibreSSL') +-if not _version.startswith("OpenSSL") and not LIBRESSL: +- raise ImportError("Only LibreSSL and OpenSSL are supported") +- +-# SSLeay has been renamed with OpenSSL_version_num in OpenSSL 1.1.0 +-# LibreSSL has OpenSSL_version_num since 2.7.0 +-try: +- OpenSSL_version_num = _libcrypto.OpenSSL_version_num +-except AttributeError: +- OpenSSL_version_num = _libcrypto.SSLeay +- +-# OpenSSL_version_num()/SSLeay() returns the value of OPENSSL_VERSION_NUMBER +-# +-# OPENSSL_VERSION_NUMBER is a numeric release version identifier: +-# MNNFFPPS: major minor fix patch status +-# For example, +-# 0x000906000 == 0.9.6 dev +-# 0x000906023 == 0.9.6b beta 3 +-# 0x00090605f == 0.9.6e release +-_openssl_version = OpenSSL_version_num() +- +-_ffi.cdef(''' +-typedef ... CONF; +-typedef ... CONF_METHOD; +-typedef ... BIO; +-typedef ... ipa_STACK_OF_CONF_VALUE; +- +-/* openssl/conf.h */ +-typedef struct { +- char *section; +- char *name; +- char *value; +-} CONF_VALUE; +- +-CONF *NCONF_new(CONF_METHOD *meth); +-void NCONF_free(CONF *conf); +-int NCONF_load_bio(CONF *conf, BIO *bp, long *eline); +-ipa_STACK_OF_CONF_VALUE *NCONF_get_section(const CONF *conf, +- const char *section); +-char *NCONF_get_string(const CONF *conf, const char *group, const char *name); +- +-/* openssl/safestack.h */ +-// int sk_CONF_VALUE_num(ipa_STACK_OF_CONF_VALUE *); +-// CONF_VALUE *sk_CONF_VALUE_value(ipa_STACK_OF_CONF_VALUE *, int); +- +-/* openssl/stack.h */ +-typedef ... _STACK; +- +-int OPENSSL_sk_num(const _STACK *); +-void *OPENSSL_sk_value(const _STACK *, int); +- +-int sk_num(const _STACK *); +-void *sk_value(const _STACK *, int); +- +-/* openssl/bio.h */ +-BIO *BIO_new_mem_buf(const void *buf, int len); +-int BIO_free(BIO *a); +- +-/* openssl/asn1.h */ +-typedef struct ASN1_ENCODING_st { +- unsigned char *enc; /* DER encoding */ +- long len; /* Length of encoding */ +- int modified; /* set to 1 if 'enc' is invalid */ +-} ASN1_ENCODING; +- +-/* openssl/evp.h */ +-typedef ... EVP_PKEY; +- +-void EVP_PKEY_free(EVP_PKEY *pkey); +- +-/* openssl/x509.h */ +-typedef ... ASN1_INTEGER; +-typedef ... ASN1_BIT_STRING; +-typedef ... ASN1_OBJECT; +-typedef ... X509; +-typedef ... X509_CRL; +-typedef ... X509_NAME; +-typedef ... X509_PUBKEY; +-typedef ... ipa_STACK_OF_X509_ATTRIBUTE; +- +-typedef struct X509_req_info_st { +- ASN1_ENCODING enc; +- ASN1_INTEGER *version; +- X509_NAME *subject; +- X509_PUBKEY *pubkey; +- /* d=2 hl=2 l= 0 cons: cont: 00 */ +- ipa_STACK_OF_X509_ATTRIBUTE *attributes; /* [ 0 ] */ +-} X509_REQ_INFO; +-''') +- +-# since OpenSSL 1.1.0 req_info field is no longer pointer to X509_REQ_INFO +-if _openssl_version >= 0x10100000 and not LIBRESSL: +- _ffi.cdef(''' +- typedef struct X509_req_st { +- X509_REQ_INFO req_info; +- } X509_REQ; +- ''') +-else: +- _ffi.cdef(''' +- typedef struct X509_req_st { +- X509_REQ_INFO *req_info; +- } X509_REQ; +- ''') +- +-_ffi.cdef(''' +-X509_REQ *X509_REQ_new(void); +-void X509_REQ_free(X509_REQ *); +-EVP_PKEY *d2i_PUBKEY_bio(BIO *bp, EVP_PKEY **a); +-int X509_REQ_set_pubkey(X509_REQ *x, EVP_PKEY *pkey); +-int X509_NAME_add_entry_by_OBJ(X509_NAME *name, const ASN1_OBJECT *obj, int type, +- const unsigned char *bytes, int len, int loc, +- int set); +-int X509_NAME_entry_count(X509_NAME *name); +-int i2d_X509_REQ_INFO(X509_REQ_INFO *a, unsigned char **out); +- +-/* openssl/objects.h */ +-ASN1_OBJECT *OBJ_txt2obj(const char *s, int no_name); +- +-/* openssl/x509v3.h */ +-typedef ... X509V3_CONF_METHOD; +- +-typedef struct v3_ext_ctx { +- int flags; +- X509 *issuer_cert; +- X509 *subject_cert; +- X509_REQ *subject_req; +- X509_CRL *crl; +- X509V3_CONF_METHOD *db_meth; +- void *db; +-} X509V3_CTX; +- +-void X509V3_set_ctx(X509V3_CTX *ctx, X509 *issuer, X509 *subject, +- X509_REQ *req, X509_CRL *crl, int flags); +-void X509V3_set_nconf(X509V3_CTX *ctx, CONF *conf); +-int X509V3_EXT_REQ_add_nconf(CONF *conf, X509V3_CTX *ctx, char *section, +- X509_REQ *req); +- +-/* openssl/x509v3.h */ +-unsigned long ERR_get_error(void); +-char *ERR_error_string(unsigned long e, char *buf); +-''') # noqa: E501 +- +-NULL = _ffi.NULL +-# openssl/conf.h +-NCONF_new = _libcrypto.NCONF_new +-NCONF_free = _libcrypto.NCONF_free +-NCONF_load_bio = _libcrypto.NCONF_load_bio +-NCONF_get_section = _libcrypto.NCONF_get_section +-NCONF_get_string = _libcrypto.NCONF_get_string +- +-# openssl/stack.h +-try: +- sk_num = _libcrypto.OPENSSL_sk_num +- sk_value = _libcrypto.OPENSSL_sk_value +-except AttributeError: +- sk_num = _libcrypto.sk_num +- sk_value = _libcrypto.sk_value +- +- +-def sk_CONF_VALUE_num(sk): +- return sk_num(_ffi.cast("_STACK *", sk)) +- +- +-def sk_CONF_VALUE_value(sk, i): +- return _ffi.cast("CONF_VALUE *", sk_value(_ffi.cast("_STACK *", sk), i)) +- +- +-# openssl/bio.h +-BIO_new_mem_buf = _libcrypto.BIO_new_mem_buf +-BIO_free = _libcrypto.BIO_free +- +-# openssl/x509.h +-X509_REQ_new = _libcrypto.X509_REQ_new +-X509_REQ_free = _libcrypto.X509_REQ_free +-X509_REQ_set_pubkey = _libcrypto.X509_REQ_set_pubkey +-d2i_PUBKEY_bio = _libcrypto.d2i_PUBKEY_bio +-i2d_X509_REQ_INFO = _libcrypto.i2d_X509_REQ_INFO +-X509_NAME_add_entry_by_OBJ = _libcrypto.X509_NAME_add_entry_by_OBJ +-X509_NAME_entry_count = _libcrypto.X509_NAME_entry_count +- +- +-def X509_REQ_get_subject_name(req): +- return req.req_info.subject +- +- +-# openssl/objects.h +-OBJ_txt2obj = _libcrypto.OBJ_txt2obj +- +-# openssl/evp.h +-EVP_PKEY_free = _libcrypto.EVP_PKEY_free +- +-# openssl/asn1.h +-MBSTRING_UTF8 = 0x1000 +- +-# openssl/x509v3.h +-X509V3_set_ctx = _libcrypto.X509V3_set_ctx +-X509V3_set_nconf = _libcrypto.X509V3_set_nconf +-X509V3_EXT_REQ_add_nconf = _libcrypto.X509V3_EXT_REQ_add_nconf +- +-# openssl/err.h +-ERR_get_error = _libcrypto.ERR_get_error +-ERR_error_string = _libcrypto.ERR_error_string +- +- +-def _raise_openssl_errors(): +- msgs = [] +- +- code = ERR_get_error() +- while code != 0: +- msg = _ffi.string(ERR_error_string(code, NULL)) +- try: +- strmsg = msg.decode('utf-8') +- except UnicodeDecodeError: +- strmsg = repr(msg) +- msgs.append(strmsg) +- code = ERR_get_error() +- +- raise errors.CSRTemplateError(reason='\n'.join(msgs)) +- +- +-def _parse_dn_section(subj, dn_sk): +- for i in range(sk_CONF_VALUE_num(dn_sk)): +- v = sk_CONF_VALUE_value(dn_sk, i) +- rdn_type = _ffi.string(v.name) +- +- # Skip past any leading X. X: X, etc to allow for multiple instances +- for idx, c in enumerate(rdn_type): +- if c in b':,.': +- if idx+1 < len(rdn_type): +- rdn_type = rdn_type[idx+1:] +- break +- if rdn_type.startswith(b'+'): +- rdn_type = rdn_type[1:] +- mval = -1 +- else: +- mval = 0 +- +- # convert rdn_type to an OID +- # +- # OpenSSL is fussy about the case of the string. For example, +- # lower-case 'o' (for "organization name") is not recognised. +- # Therefore, try to convert the given string into an OID. If +- # that fails, convert it upper case and try again. +- # +- oid = OBJ_txt2obj(rdn_type, 0) +- if oid == NULL: +- oid = OBJ_txt2obj(rdn_type.upper(), 0) +- if oid == NULL: +- raise errors.CSRTemplateError( +- reason='unrecognised attribute type: {}' +- .format(rdn_type.decode('utf-8'))) +- +- if not X509_NAME_add_entry_by_OBJ( +- subj, oid, MBSTRING_UTF8, +- _ffi.cast("unsigned char *", v.value), -1, -1, mval): +- _raise_openssl_errors() +- +- if not X509_NAME_entry_count(subj): +- raise errors.CSRTemplateError( +- reason='error, subject in config file is empty') +- +- +-def build_requestinfo(config, public_key_info): +- ''' +- Return a cffi buffer containing a DER-encoded CertificationRequestInfo. +- +- The returned object implements the buffer protocol. +- +- ''' +- reqdata = NULL +- req = NULL +- nconf_bio = NULL +- pubkey_bio = NULL +- pubkey = NULL +- +- try: +- reqdata = NCONF_new(NULL) +- if reqdata == NULL: +- _raise_openssl_errors() +- +- nconf_bio = BIO_new_mem_buf(config, len(config)) +- errorline = _ffi.new('long[1]', [-1]) +- i = NCONF_load_bio(reqdata, nconf_bio, errorline) +- if i < 0: +- if errorline[0] < 0: +- raise errors.CSRTemplateError(reason="Can't load config file") +- else: +- raise errors.CSRTemplateError( +- reason='Error on line %d of config file' % errorline[0]) +- +- dn_sect = NCONF_get_string(reqdata, b'req', b'distinguished_name') +- if dn_sect == NULL: +- raise errors.CSRTemplateError( +- reason='Unable to find "distinguished_name" key in config') +- +- dn_sk = NCONF_get_section(reqdata, dn_sect) +- if dn_sk == NULL: +- raise errors.CSRTemplateError( +- reason='Unable to find "%s" section in config' % +- _ffi.string(dn_sect)) +- +- pubkey_bio = BIO_new_mem_buf(public_key_info, len(public_key_info)) +- pubkey = d2i_PUBKEY_bio(pubkey_bio, NULL) +- if pubkey == NULL: +- _raise_openssl_errors() +- +- req = X509_REQ_new() +- if req == NULL: +- _raise_openssl_errors() +- +- subject = X509_REQ_get_subject_name(req) +- +- _parse_dn_section(subject, dn_sk) +- +- if not X509_REQ_set_pubkey(req, pubkey): +- _raise_openssl_errors() +- +- ext_ctx = _ffi.new("X509V3_CTX[1]") +- X509V3_set_ctx(ext_ctx, NULL, NULL, req, NULL, 0) +- X509V3_set_nconf(ext_ctx, reqdata) +- +- extn_section = NCONF_get_string(reqdata, b"req", b"req_extensions") +- if extn_section != NULL: +- if not X509V3_EXT_REQ_add_nconf( +- reqdata, ext_ctx, extn_section, req): +- _raise_openssl_errors() +- +- if _openssl_version < 0x10100000 or LIBRESSL: +- der_len = i2d_X509_REQ_INFO(req.req_info, NULL) +- else: +- req_info = _ffi.new("X509_REQ_INFO *", req.req_info) +- der_len = i2d_X509_REQ_INFO(req_info, NULL) +- req.req_info = req_info[0] +- if der_len < 0: +- _raise_openssl_errors() +- +- der_buf = _ffi.new("unsigned char[%d]" % der_len) +- der_out = _ffi.new("unsigned char **", der_buf) +- if _openssl_version < 0x10100000 or LIBRESSL: +- der_len = i2d_X509_REQ_INFO(req.req_info, der_out) +- else: +- der_len = i2d_X509_REQ_INFO(req_info, der_out) +- req.req_info = req_info[0] +- if der_len < 0: +- _raise_openssl_errors() +- +- return _ffi.buffer(der_buf, der_len) +- +- finally: +- if reqdata != NULL: +- NCONF_free(reqdata) +- if req != NULL: +- X509_REQ_free(req) +- if nconf_bio != NULL: +- BIO_free(nconf_bio) +- if pubkey_bio != NULL: +- BIO_free(pubkey_bio) +- if pubkey != NULL: +- EVP_PKEY_free(pubkey) +diff -urN freeipa-4.8.0/ipaclient/csrgen.py freeipa-4.8.0.removed_csrgen/ipaclient/csrgen.py +--- freeipa-4.8.0/ipaclient/csrgen.py 2019-07-03 08:42:41.811540288 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/csrgen.py 1970-01-01 01:00:00.000000000 +0100 +@@ -1,488 +0,0 @@ +-# +-# Copyright (C) 2016 FreeIPA Contributors see COPYING for license +-# +- +-import base64 +-import collections +-import errno +-import json +-import logging +-import os +-import os.path +-import pipes +-import subprocess +-import traceback +-import codecs +- +-import pkg_resources +- +-from cryptography.hazmat.backends import default_backend +-from cryptography.hazmat.primitives.asymmetric import padding +-from cryptography.hazmat.primitives import hashes +-from cryptography.hazmat.primitives.serialization import ( +- load_pem_private_key, Encoding, PublicFormat) +-from cryptography.x509 import load_pem_x509_certificate +-import jinja2 +-import jinja2.ext +-import jinja2.sandbox +-from pyasn1.codec.der import decoder, encoder +-from pyasn1.type import univ +-from pyasn1_modules import rfc2314 +-import six +- +-from ipalib import api +-from ipalib import errors +-from ipalib.text import _ +- +-if six.PY3: +- unicode = str +- +-__doc__ = _(""" +-Routines for constructing certificate signing requests using IPA data and +-stored templates. +-""") +- +-logger = logging.getLogger(__name__) +- +- +-class IndexableUndefined(jinja2.Undefined): +- def __getitem__(self, key): +- return jinja2.Undefined( +- hint=self._undefined_hint, obj=self._undefined_obj, +- name=self._undefined_name, exc=self._undefined_exception) +- +- +-class IPAExtension(jinja2.ext.Extension): +- """Jinja2 extension providing useful features for CSR generation rules.""" +- +- def __init__(self, environment): +- super(IPAExtension, self).__init__(environment) +- +- environment.filters.update( +- quote=self.quote, +- required=self.required, +- ) +- +- def quote(self, data): +- return pipes.quote(data) +- +- def required(self, data, name): +- if not data: +- raise errors.CSRTemplateError( +- reason=_( +- 'Required CSR generation rule %(name)s is missing data') % +- {'name': name}) +- return data +- +- +-class Formatter: +- """ +- Class for processing a set of CSR generation rules into a template. +- +- The template can be rendered with user and database data to produce a +- config, which specifies how to build a CSR. +- +- Subclasses of Formatter should set the value of base_template_name to the +- filename of a base template with spaces for the processed rules. +- Additionally, they should override the _get_template_params method to +- produce the correct output for the base template. +- """ +- base_template_name = None +- +- def __init__(self, csr_data_dir=None): +- # chain loaders: +- # 1) csr_data_dir/templates +- # 2) /etc/ipa/csrgen/templates +- # 3) ipaclient/csrgen/templates +- loaders = [] +- if csr_data_dir is not None: +- loaders.append(jinja2.FileSystemLoader( +- os.path.join(csr_data_dir, 'templates')) +- ) +- loaders.append(jinja2.FileSystemLoader( +- os.path.join(api.env.confdir, 'csrgen/templates')) +- ) +- loaders.append(jinja2.PackageLoader('ipaclient', 'csrgen/templates')) +- +- self.jinja2 = jinja2.sandbox.SandboxedEnvironment( +- loader=jinja2.ChoiceLoader(loaders), +- extensions=[jinja2.ext.ExprStmtExtension, IPAExtension], +- keep_trailing_newline=True, undefined=IndexableUndefined) +- +- self.passthrough_globals = {} +- +- def _define_passthrough(self, call): +- """Some macros are meant to be interpreted during the final render, not +- when data rules are interpolated into syntax rules. This method allows +- those macros to be registered so that calls to them are passed through +- to the prepared rule rather than interpreted. +- """ +- +- def passthrough(caller): +- return u'{%% call %s() %%}%s{%% endcall %%}' % (call, caller()) +- +- parts = call.split('.') +- current_level = self.passthrough_globals +- for part in parts[:-1]: +- if part not in current_level: +- current_level[part] = {} +- current_level = current_level[part] +- current_level[parts[-1]] = passthrough +- +- def build_template(self, rules): +- """ +- Construct a template that can produce CSR generator strings. +- +- :param rules: list of FieldMapping to use to populate the template. +- +- :returns: jinja2.Template that can be rendered to produce the CSR data. +- """ +- syntax_rules = [] +- for field_mapping in rules: +- data_rules_prepared = [ +- self._prepare_data_rule(rule) +- for rule in field_mapping.data_rules] +- +- data_sources = [] +- for xrule in field_mapping.data_rules: +- data_source = xrule.options.get('data_source') +- if data_source: +- data_sources.append(data_source) +- +- syntax_rules.append(self._prepare_syntax_rule( +- field_mapping.syntax_rule, data_rules_prepared, +- field_mapping.description, data_sources)) +- +- template_params = self._get_template_params(syntax_rules) +- base_template = self.jinja2.get_template( +- self.base_template_name, globals=self.passthrough_globals) +- +- try: +- combined_template_source = base_template.render(**template_params) +- except jinja2.UndefinedError: +- logger.debug(traceback.format_exc()) +- raise errors.CSRTemplateError(reason=_( +- 'Template error when formatting certificate data')) +- +- logger.debug( +- 'Formatting with template: %s', combined_template_source) +- combined_template = self.jinja2.from_string(combined_template_source) +- +- return combined_template +- +- def _wrap_conditional(self, rule, condition): +- rule = '{%% if %s %%}%s{%% endif %%}' % (condition, rule) +- return rule +- +- def _wrap_required(self, rule, description): +- template = '{%% filter required("%s") %%}%s{%% endfilter %%}' % ( +- description, rule) +- +- return template +- +- def _prepare_data_rule(self, data_rule): +- template = data_rule.template +- +- data_source = data_rule.options.get('data_source') +- if data_source: +- template = self._wrap_conditional(template, data_source) +- +- return template +- +- def _prepare_syntax_rule( +- self, syntax_rule, data_rules, description, data_sources): +- logger.debug('Syntax rule template: %s', syntax_rule.template) +- template = self.jinja2.from_string( +- syntax_rule.template, globals=self.passthrough_globals) +- is_required = syntax_rule.options.get('required', False) +- try: +- prepared_template = template.render(datarules=data_rules) +- except jinja2.UndefinedError: +- logger.debug(traceback.format_exc()) +- raise errors.CSRTemplateError(reason=_( +- 'Template error when formatting certificate data')) +- +- if data_sources: +- combinator = ' %s ' % syntax_rule.options.get( +- 'data_source_combinator', 'or') +- condition = combinator.join(data_sources) +- prepared_template = self._wrap_conditional( +- prepared_template, condition) +- +- if is_required: +- prepared_template = self._wrap_required( +- prepared_template, description) +- +- return prepared_template +- +- def _get_template_params(self, syntax_rules): +- """ +- Package the syntax rules into fields expected by the base template. +- +- :param syntax_rules: list of prepared syntax rules to be included in +- the template. +- +- :returns: dict of values needed to render the base template. +- """ +- raise NotImplementedError('Formatter class must be subclassed') +- +- +-class OpenSSLFormatter(Formatter): +- """Formatter class generating the openssl config-file format.""" +- +- base_template_name = 'openssl_base.tmpl' +- +- # Syntax rules are wrapped in this data structure, to keep track of whether +- # each goes in the extension or the root section +- SyntaxRule = collections.namedtuple( +- 'SyntaxRule', ['template', 'is_extension']) +- +- def __init__(self, *args, **kwargs): +- super(OpenSSLFormatter, self).__init__(*args, **kwargs) +- self._define_passthrough('openssl.section') +- +- def _get_template_params(self, syntax_rules): +- parameters = [rule.template for rule in syntax_rules +- if not rule.is_extension] +- extensions = [rule.template for rule in syntax_rules +- if rule.is_extension] +- +- return {'parameters': parameters, 'extensions': extensions} +- +- def _prepare_syntax_rule( +- self, syntax_rule, data_rules, description, data_sources): +- """Overrides method to pull out whether rule is an extension or not.""" +- prepared_template = super(OpenSSLFormatter, self)._prepare_syntax_rule( +- syntax_rule, data_rules, description, data_sources) +- is_extension = syntax_rule.options.get('extension', False) +- return self.SyntaxRule(prepared_template, is_extension) +- +- +-class FieldMapping: +- """Representation of the rules needed to construct a complete cert field. +- +- Attributes: +- description: str, a name or description of this field, to be used in +- messages +- syntax_rule: Rule, the rule defining the syntax of this field +- data_rules: list of Rule, the rules that produce data to be stored in +- this field +- """ +- __slots__ = ['description', 'syntax_rule', 'data_rules'] +- +- def __init__(self, description, syntax_rule, data_rules): +- self.description = description +- self.syntax_rule = syntax_rule +- self.data_rules = data_rules +- +- +-class Rule: +- __slots__ = ['name', 'template', 'options'] +- +- def __init__(self, name, template, options): +- self.name = name +- self.template = template +- self.options = options +- +- +-class RuleProvider: +- def rules_for_profile(self, profile_id): +- """ +- Return the rules needed to build a CSR using the given profile. +- +- :param profile_id: str, name of the CSR generation profile to use +- +- :returns: list of FieldMapping, filled out with the appropriate rules +- """ +- raise NotImplementedError('RuleProvider class must be subclassed') +- +- +-class FileRuleProvider(RuleProvider): +- def __init__(self, csr_data_dir=None): +- self.rules = {} +- self._csrgen_data_dirs = [] +- if csr_data_dir is not None: +- self._csrgen_data_dirs.append(csr_data_dir) +- self._csrgen_data_dirs.append( +- os.path.join(api.env.confdir, 'csrgen') +- ) +- self._csrgen_data_dirs.append( +- pkg_resources.resource_filename('ipaclient', 'csrgen') +- ) +- +- def _open(self, subdir, filename): +- for data_dir in self._csrgen_data_dirs: +- path = os.path.join(data_dir, subdir, filename) +- try: +- return open(path) +- except IOError as e: +- if e.errno != errno.ENOENT: +- raise +- raise IOError( +- errno.ENOENT, +- "'{}' not found in {}".format( +- os.path.join(subdir, filename), +- ", ".join(self._csrgen_data_dirs) +- ) +- ) +- +- def _rule(self, rule_name): +- if rule_name not in self.rules: +- try: +- with self._open('rules', '%s.json' % rule_name) as f: +- ruleconf = json.load(f) +- except IOError: +- raise errors.NotFound( +- reason=_('No generation rule %(rulename)s found.') % +- {'rulename': rule_name}) +- +- try: +- rule = ruleconf['rule'] +- except KeyError: +- raise errors.EmptyResult( +- reason=_('Generation rule "%(rulename)s" is missing the' +- ' "rule" key') % {'rulename': rule_name}) +- +- options = ruleconf.get('options', {}) +- +- self.rules[rule_name] = Rule( +- rule_name, rule['template'], options) +- +- return self.rules[rule_name] +- +- def rules_for_profile(self, profile_id): +- try: +- with self._open('profiles', '%s.json' % profile_id) as f: +- profile = json.load(f) +- except IOError: +- raise errors.NotFound( +- reason=_('No CSR generation rules are defined for profile' +- ' %(profile_id)s') % {'profile_id': profile_id}) +- +- field_mappings = [] +- for field in profile: +- syntax_rule = self._rule(field['syntax']) +- data_rules = [self._rule(name) for name in field['data']] +- field_mappings.append(FieldMapping( +- syntax_rule.name, syntax_rule, data_rules)) +- return field_mappings +- +- +-class CSRGenerator: +- def __init__(self, rule_provider, formatter_class=OpenSSLFormatter): +- self.rule_provider = rule_provider +- self.formatter = formatter_class() +- +- def csr_config(self, principal, config, profile_id): +- render_data = {'subject': principal, 'config': config} +- +- rules = self.rule_provider.rules_for_profile(profile_id) +- template = self.formatter.build_template(rules) +- +- try: +- config = template.render(render_data) +- except jinja2.UndefinedError: +- logger.debug(traceback.format_exc()) +- raise errors.CSRTemplateError(reason=_( +- 'Template error when formatting certificate data')) +- +- return config +- +- +-class CSRLibraryAdaptor: +- def get_subject_public_key_info(self): +- raise NotImplementedError('Use a subclass of CSRLibraryAdaptor') +- +- def sign_csr(self, certification_request_info): +- """Sign a CertificationRequestInfo. +- +- :returns: bytes, a DER-encoded signed CSR. +- """ +- raise NotImplementedError('Use a subclass of CSRLibraryAdaptor') +- +- +-class OpenSSLAdaptor: +- def __init__(self, key=None, key_filename=None, password_filename=None): +- """ +- Must provide either ``key_filename`` or ``key``. +- +- """ +- if key_filename is not None: +- with open(key_filename, 'rb') as key_file: +- key_bytes = key_file.read() +- +- password = None +- if password_filename is not None: +- with open(password_filename, 'rb') as password_file: +- password = password_file.read().strip() +- +- self._key = load_pem_private_key( +- key_bytes, password, default_backend()) +- +- elif key is not None: +- self._key = key +- +- else: +- raise ValueError("Must provide 'key' or 'key_filename'") +- +- def key(self): +- return self._key +- +- def get_subject_public_key_info(self): +- pubkey_info = self.key().public_key().public_bytes( +- Encoding.DER, PublicFormat.SubjectPublicKeyInfo) +- return pubkey_info +- +- def sign_csr(self, certification_request_info): +- reqinfo = decoder.decode( +- certification_request_info, rfc2314.CertificationRequestInfo())[0] +- csr = rfc2314.CertificationRequest() +- csr.setComponentByName('certificationRequestInfo', reqinfo) +- +- algorithm = rfc2314.SignatureAlgorithmIdentifier() +- algorithm.setComponentByName( +- 'algorithm', univ.ObjectIdentifier( +- '1.2.840.113549.1.1.11')) # sha256WithRSAEncryption +- csr.setComponentByName('signatureAlgorithm', algorithm) +- +- signature = self.key().sign( +- certification_request_info, +- padding.PKCS1v15(), +- hashes.SHA256() +- ) +- asn1sig = univ.BitString("'{sig}'H".format( +- sig=codecs.encode(signature, 'hex') +- .decode('ascii')) +- ) +- csr.setComponentByName('signature', asn1sig) +- return encoder.encode(csr) +- +- +-class NSSAdaptor: +- def __init__(self, database, password_filename): +- self.database = database +- self.password_filename = password_filename +- self.nickname = base64.b32encode(os.urandom(40)) +- +- def get_subject_public_key_info(self): +- temp_cn = base64.b32encode(os.urandom(40)).decode('ascii') +- +- password_args = [] +- if self.password_filename is not None: +- password_args = ['-f', self.password_filename] +- +- subprocess.check_call( +- ['certutil', '-S', '-n', self.nickname, '-s', 'CN=%s' % temp_cn, +- '-x', '-t', ',,', '-d', self.database] + password_args) +- cert_pem = subprocess.check_output( +- ['certutil', '-L', '-n', self.nickname, '-a', +- '-d', self.database] + password_args) +- +- cert = load_pem_x509_certificate(cert_pem, default_backend()) +- pubkey_info = cert.public_key().public_bytes( +- Encoding.DER, PublicFormat.SubjectPublicKeyInfo) +- +- return pubkey_info +- +- def sign_csr(self, certification_request_info): +- raise NotImplementedError('NSS is not yet supported') +diff -urN freeipa-4.8.0/ipaclient/plugins/cert.py freeipa-4.8.0.removed_csrgen/ipaclient/plugins/cert.py +--- freeipa-4.8.0/ipaclient/plugins/cert.py 2019-07-03 08:42:41.978537802 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/plugins/cert.py 2019-07-03 13:24:38.477222594 +0200 +@@ -21,8 +21,6 @@ + + import base64 + +-import six +- + from ipaclient.frontend import MethodOverride + from ipalib import errors + from ipalib import x509 +@@ -31,9 +29,6 @@ + from ipalib.plugable import Registry + from ipalib.text import _ + +-if six.PY3: +- unicode = str +- + register = Registry() + + +@@ -73,87 +68,12 @@ + + @register(override=True, no_fail=True) + class cert_request(CertRetrieveOverride): +- takes_options = CertRetrieveOverride.takes_options + ( +- Str( +- 'database?', +- label=_('Path to NSS database'), +- doc=_('Path to NSS database to use for private key'), +- ), +- Str( +- 'private_key?', +- label=_('Path to private key file'), +- doc=_('Path to PEM file containing a private key'), +- ), +- Str( +- 'password_file?', +- label=_( +- 'File containing a password for the private key or database'), +- ), +- Str( +- 'csr_profile_id?', +- label=_('Name of CSR generation profile (if not the same as' +- ' profile_id)'), +- ), +- ) +- + def get_args(self): + for arg in super(cert_request, self).get_args(): + if arg.name == 'csr': + arg = arg.clone_retype(arg.name, File, required=False) + yield arg + +- def forward(self, csr=None, **options): +- database = options.pop('database', None) +- private_key = options.pop('private_key', None) +- csr_profile_id = options.pop('csr_profile_id', None) +- password_file = options.pop('password_file', None) +- +- if csr is None: +- # Deferred import, ipaclient.csrgen is expensive to load. +- # see https://pagure.io/freeipa/issue/7484 +- from ipaclient import csrgen +- +- if database: +- adaptor = csrgen.NSSAdaptor(database, password_file) +- elif private_key: +- adaptor = csrgen.OpenSSLAdaptor( +- key_filename=private_key, password_filename=password_file) +- else: +- raise errors.InvocationError( +- message=u"One of 'database' or 'private_key' is required") +- +- pubkey_info = adaptor.get_subject_public_key_info() +- pubkey_info_b64 = base64.b64encode(pubkey_info) +- +- # If csr_profile_id is passed, that takes precedence. +- # Otherwise, use profile_id. If neither are passed, the default +- # in cert_get_requestdata will be used. +- profile_id = csr_profile_id +- if profile_id is None: +- profile_id = options.get('profile_id') +- +- response = self.api.Command.cert_get_requestdata( +- profile_id=profile_id, +- principal=options.get('principal'), +- public_key_info=pubkey_info_b64) +- +- req_info_b64 = response['result']['request_info'] +- req_info = base64.b64decode(req_info_b64) +- +- csr = adaptor.sign_csr(req_info) +- +- if not csr: +- raise errors.CertificateOperationError( +- error=(_('Generated CSR was empty'))) +- +- else: +- if database is not None or private_key is not None: +- raise errors.MutuallyExclusiveError(reason=_( +- "Options 'database' and 'private_key' are not compatible" +- " with 'csr'")) +- +- return super(cert_request, self).forward(csr, **options) +- + + @register(override=True, no_fail=True) + class cert_show(CertRetrieveOverride): +diff -urN freeipa-4.8.0/ipaclient/plugins/cert.py.orig freeipa-4.8.0.removed_csrgen/ipaclient/plugins/cert.py.orig +--- freeipa-4.8.0/ipaclient/plugins/cert.py.orig 1970-01-01 01:00:00.000000000 +0100 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/plugins/cert.py.orig 2019-07-03 13:24:38.478222573 +0200 +@@ -0,0 +1,215 @@ ++# Authors: ++# Andrew Wnuk ++# Jason Gerard DeRose ++# John Dennis ++# ++# Copyright (C) 2009 Red Hat ++# see file 'COPYING' for use and warranty information ++# ++# This program is free software; you can redistribute it and/or modify ++# it under the terms of the GNU General Public License as published by ++# the Free Software Foundation, either version 3 of the License, or ++# (at your option) any later version. ++# ++# This program is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++# ++# You should have received a copy of the GNU General Public License ++# along with this program. If not, see . ++ ++import base64 ++ ++import six ++ ++from ipaclient.frontend import MethodOverride ++from ipalib import errors ++from ipalib import x509 ++from ipalib import util ++from ipalib.parameters import BinaryFile, File, Flag, Str ++from ipalib.plugable import Registry ++from ipalib.text import _ ++ ++if six.PY3: ++ unicode = str ++ ++register = Registry() ++ ++ ++class CertRetrieveOverride(MethodOverride): ++ takes_options = ( ++ Str( ++ 'certificate_out?', ++ doc=_('Write certificate (chain if --chain used) to file'), ++ include='cli', ++ cli_metavar='FILE', ++ ), ++ ) ++ ++ def forward(self, *args, **options): ++ if 'certificate_out' in options: ++ certificate_out = options.pop('certificate_out') ++ try: ++ util.check_writable_file(certificate_out) ++ except errors.FileError as e: ++ raise errors.ValidationError(name='certificate-out', ++ error=str(e)) ++ else: ++ certificate_out = None ++ ++ result = super(CertRetrieveOverride, self).forward(*args, **options) ++ ++ if certificate_out is not None: ++ if options.get('chain', False): ++ certs = result['result']['certificate_chain'] ++ else: ++ certs = [base64.b64decode(result['result']['certificate'])] ++ certs = (x509.load_der_x509_certificate(cert) for cert in certs) ++ x509.write_certificate_list(certs, certificate_out) ++ ++ return result ++ ++ ++@register(override=True, no_fail=True) ++class cert_request(CertRetrieveOverride): ++ takes_options = CertRetrieveOverride.takes_options + ( ++ Str( ++ 'database?', ++ label=_('Path to NSS database'), ++ doc=_('Path to NSS database to use for private key'), ++ ), ++ Str( ++ 'private_key?', ++ label=_('Path to private key file'), ++ doc=_('Path to PEM file containing a private key'), ++ ), ++ Str( ++ 'password_file?', ++ label=_( ++ 'File containing a password for the private key or database'), ++ ), ++ Str( ++ 'csr_profile_id?', ++ label=_('Name of CSR generation profile (if not the same as' ++ ' profile_id)'), ++ ), ++ ) ++ ++ def get_args(self): ++ for arg in super(cert_request, self).get_args(): ++ if arg.name == 'csr': ++ arg = arg.clone_retype(arg.name, File, required=False) ++ yield arg ++ ++ def forward(self, csr=None, **options): ++ database = options.pop('database', None) ++ private_key = options.pop('private_key', None) ++ csr_profile_id = options.pop('csr_profile_id', None) ++ password_file = options.pop('password_file', None) ++ ++ if csr is None: ++ # Deferred import, ipaclient.csrgen is expensive to load. ++ # see https://pagure.io/freeipa/issue/7484 ++ from ipaclient import csrgen ++ ++ if database: ++ adaptor = csrgen.NSSAdaptor(database, password_file) ++ elif private_key: ++ adaptor = csrgen.OpenSSLAdaptor( ++ key_filename=private_key, password_filename=password_file) ++ else: ++ raise errors.InvocationError( ++ message=u"One of 'database' or 'private_key' is required") ++ ++ pubkey_info = adaptor.get_subject_public_key_info() ++ pubkey_info_b64 = base64.b64encode(pubkey_info) ++ ++ # If csr_profile_id is passed, that takes precedence. ++ # Otherwise, use profile_id. If neither are passed, the default ++ # in cert_get_requestdata will be used. ++ profile_id = csr_profile_id ++ if profile_id is None: ++ profile_id = options.get('profile_id') ++ ++ response = self.api.Command.cert_get_requestdata( ++ profile_id=profile_id, ++ principal=options.get('principal'), ++ public_key_info=pubkey_info_b64) ++ ++ req_info_b64 = response['result']['request_info'] ++ req_info = base64.b64decode(req_info_b64) ++ ++ csr = adaptor.sign_csr(req_info) ++ ++ if not csr: ++ raise errors.CertificateOperationError( ++ error=(_('Generated CSR was empty'))) ++ ++ else: ++ if database is not None or private_key is not None: ++ raise errors.MutuallyExclusiveError(reason=_( ++ "Options 'database' and 'private_key' are not compatible" ++ " with 'csr'")) ++ ++ return super(cert_request, self).forward(csr, **options) ++ ++ ++@register(override=True, no_fail=True) ++class cert_show(CertRetrieveOverride): ++ def get_options(self): ++ for option in super(cert_show, self).get_options(): ++ if option.name == 'out': ++ # skip server-defined --out ++ continue ++ if option.name == 'certificate_out': ++ # add --out as a deprecated alias of --certificate-out ++ option = option.clone_rename( ++ 'out', ++ cli_name='certificate_out', ++ deprecated_cli_aliases={'out'}, ++ ) ++ yield option ++ ++ def forward(self, *args, **options): ++ try: ++ options['certificate_out'] = options.pop('out') ++ except KeyError: ++ pass ++ ++ return super(cert_show, self).forward(*args, **options) ++ ++ ++@register(override=True, no_fail=True) ++class cert_remove_hold(MethodOverride): ++ has_output_params = ( ++ Flag('unrevoked', ++ label=_('Unrevoked'), ++ ), ++ Str('error_string', ++ label=_('Error'), ++ ), ++ ) ++ ++ ++@register(override=True, no_fail=True) ++class cert_find(MethodOverride): ++ takes_options = ( ++ BinaryFile( ++ 'file?', ++ label=_("Input filename"), ++ doc=_('File to load the certificate from.'), ++ include='cli', ++ ), ++ ) ++ ++ def forward(self, *args, **options): ++ if self.api.env.context == 'cli': ++ if 'certificate' in options and 'file' in options: ++ raise errors.MutuallyExclusiveError( ++ reason=_("cannot specify both raw certificate and file")) ++ if 'certificate' not in options and 'file' in options: ++ options['certificate'] = x509.load_unknown_x509_certificate( ++ options.pop('file')) ++ ++ return super(cert_find, self).forward(*args, **options) +diff -urN freeipa-4.8.0/ipaclient/plugins/csrgen.py freeipa-4.8.0.removed_csrgen/ipaclient/plugins/csrgen.py +--- freeipa-4.8.0/ipaclient/plugins/csrgen.py 2019-07-03 08:42:41.990537623 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/plugins/csrgen.py 1970-01-01 01:00:00.000000000 +0100 +@@ -1,128 +0,0 @@ +-# +-# Copyright (C) 2016 FreeIPA Contributors see COPYING for license +-# +- +-import base64 +- +-import six +- +-from ipalib import api +-from ipalib import errors +-from ipalib import output +-from ipalib import util +-from ipalib.frontend import Local, Str +-from ipalib.parameters import Bytes, Principal +-from ipalib.plugable import Registry +-from ipalib.text import _ +-from ipapython import dogtag +- +- +-if six.PY3: +- unicode = str +- +-register = Registry() +- +-__doc__ = _(""" +-Commands to build certificate requests automatically +-""") +- +- +-@register() +-class cert_get_requestdata(Local): +- __doc__ = _('Gather data for a certificate signing request.') +- +- NO_CLI = True +- +- takes_options = ( +- Principal( +- 'principal', +- label=_('Principal'), +- doc=_('Principal for this certificate (e.g.' +- ' HTTP/test.example.com)'), +- ), +- Str( +- 'profile_id?', +- label=_('Profile ID'), +- doc=_('CSR Generation Profile to use'), +- ), +- Bytes( +- 'public_key_info', +- label=_('Subject Public Key Info'), +- doc=_('DER-encoded SubjectPublicKeyInfo structure'), +- ), +- Str( +- 'out?', +- doc=_('Write CertificationRequestInfo to file'), +- ), +- ) +- +- has_output = ( +- output.Output( +- 'result', +- type=dict, +- doc=_('Dictionary mapping variable name to value'), +- ), +- ) +- +- has_output_params = ( +- Str( +- 'request_info', +- label=_('CertificationRequestInfo structure'), +- ) +- ) +- +- def execute(self, *args, **options): +- # Deferred import, ipaclient.csrgen is expensive to load. +- # see https://pagure.io/freeipa/issue/7484 +- from ipaclient import csrgen +- from ipaclient import csrgen_ffi +- +- if 'out' in options: +- util.check_writable_file(options['out']) +- +- principal = options.get('principal') +- profile_id = options.get('profile_id') +- if profile_id is None: +- profile_id = dogtag.DEFAULT_PROFILE +- public_key_info = options.get('public_key_info') +- public_key_info = base64.b64decode(public_key_info) +- +- if self.api.env.in_server: +- backend = self.api.Backend.ldap2 +- else: +- backend = self.api.Backend.rpcclient +- if not backend.isconnected(): +- backend.connect() +- +- try: +- if principal.is_host: +- principal_obj = api.Command.host_show( +- principal.hostname, all=True) +- elif principal.is_service: +- principal_obj = api.Command.service_show( +- unicode(principal), all=True) +- elif principal.is_user: +- principal_obj = api.Command.user_show( +- principal.username, all=True) +- except errors.NotFound: +- raise errors.NotFound( +- reason=_("The principal for this request doesn't exist.")) +- principal_obj = principal_obj['result'] +- config = api.Command.config_show()['result'] +- +- generator = csrgen.CSRGenerator(csrgen.FileRuleProvider()) +- +- csr_config = generator.csr_config(principal_obj, config, profile_id) +- request_info = base64.b64encode(csrgen_ffi.build_requestinfo( +- csr_config.encode('utf8'), public_key_info)) +- +- result = {} +- if 'out' in options: +- with open(options['out'], 'wb') as f: +- f.write(request_info) +- else: +- result = dict(request_info=request_info) +- +- return dict( +- result=result +- ) +diff -urN freeipa-4.8.0/ipaclient/setup.py freeipa-4.8.0.removed_csrgen/ipaclient/setup.py +--- freeipa-4.8.0/ipaclient/setup.py 2019-07-03 08:42:41.836539916 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipaclient/setup.py 2019-07-03 13:24:38.479222551 +0200 +@@ -41,13 +41,6 @@ + "ipaclient.remote_plugins.2_156", + "ipaclient.remote_plugins.2_164", + ], +- package_data={ +- 'ipaclient': [ +- 'csrgen/profiles/*.json', +- 'csrgen/rules/*.json', +- 'csrgen/templates/*.tmpl', +- ], +- }, + install_requires=[ + "cryptography", + "ipalib", +@@ -63,7 +56,6 @@ + extras_require={ + "install": ["ipaplatform"], + "otptoken_yubikey": ["python-yubico", "pyusb"], +- "csrgen": ["cffi", "jinja2"], + "ldap": ["python-ldap"], # ipapython.ipaldap + }, + zip_safe=False, +diff -urN freeipa-4.8.0/ipatests/test_ipaclient/data/test_csrgen/configs/caIPAserviceCert.conf freeipa-4.8.0.removed_csrgen/ipatests/test_ipaclient/data/test_csrgen/configs/caIPAserviceCert.conf +--- freeipa-4.8.0/ipatests/test_ipaclient/data/test_csrgen/configs/caIPAserviceCert.conf 2019-07-03 08:42:45.972478335 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipatests/test_ipaclient/data/test_csrgen/configs/caIPAserviceCert.conf 1970-01-01 01:00:00.000000000 +0100 +@@ -1,16 +0,0 @@ +-[ req ] +-prompt = no +-encrypt_key = no +- +-distinguished_name = sec0 +-req_extensions = sec2 +- +-[ sec0 ] +-O=DOMAIN.EXAMPLE.COM +-CN=machine.example.com +- +-[ sec1 ] +-DNS = machine.example.com +- +-[ sec2 ] +-subjectAltName = @sec1 +diff -urN freeipa-4.8.0/ipatests/test_ipaclient/data/test_csrgen/configs/userCert.conf freeipa-4.8.0.removed_csrgen/ipatests/test_ipaclient/data/test_csrgen/configs/userCert.conf +--- freeipa-4.8.0/ipatests/test_ipaclient/data/test_csrgen/configs/userCert.conf 2019-07-03 08:42:45.976478276 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipatests/test_ipaclient/data/test_csrgen/configs/userCert.conf 1970-01-01 01:00:00.000000000 +0100 +@@ -1,16 +0,0 @@ +-[ req ] +-prompt = no +-encrypt_key = no +- +-distinguished_name = sec0 +-req_extensions = sec2 +- +-[ sec0 ] +-O=DOMAIN.EXAMPLE.COM +-CN=testuser +- +-[ sec1 ] +-email = testuser@example.com +- +-[ sec2 ] +-subjectAltName = @sec1 +diff -urN freeipa-4.8.0/ipatests/test_ipaclient/data/test_csrgen/profiles/profile.json freeipa-4.8.0.removed_csrgen/ipatests/test_ipaclient/data/test_csrgen/profiles/profile.json +--- freeipa-4.8.0/ipatests/test_ipaclient/data/test_csrgen/profiles/profile.json 2019-07-03 08:42:45.980478216 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipatests/test_ipaclient/data/test_csrgen/profiles/profile.json 1970-01-01 01:00:00.000000000 +0100 +@@ -1,8 +0,0 @@ +-[ +- { +- "syntax": "basic", +- "data": [ +- "options" +- ] +- } +-] +diff -urN freeipa-4.8.0/ipatests/test_ipaclient/data/test_csrgen/rules/basic.json freeipa-4.8.0.removed_csrgen/ipatests/test_ipaclient/data/test_csrgen/rules/basic.json +--- freeipa-4.8.0/ipatests/test_ipaclient/data/test_csrgen/rules/basic.json 2019-07-03 08:42:45.984478157 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipatests/test_ipaclient/data/test_csrgen/rules/basic.json 1970-01-01 01:00:00.000000000 +0100 +@@ -1,5 +0,0 @@ +-{ +- "rule": { +- "template": "openssl_rule" +- } +-} +diff -urN freeipa-4.8.0/ipatests/test_ipaclient/data/test_csrgen/rules/options.json freeipa-4.8.0.removed_csrgen/ipatests/test_ipaclient/data/test_csrgen/rules/options.json +--- freeipa-4.8.0/ipatests/test_ipaclient/data/test_csrgen/rules/options.json 2019-07-03 08:42:45.988478097 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipatests/test_ipaclient/data/test_csrgen/rules/options.json 1970-01-01 01:00:00.000000000 +0100 +@@ -1,8 +0,0 @@ +-{ +- "rule": { +- "template": "openssl_rule" +- }, +- "options": { +- "rule_option": true +- } +-} +diff -urN freeipa-4.8.0/ipatests/test_ipaclient/data/test_csrgen/templates/identity_base.tmpl freeipa-4.8.0.removed_csrgen/ipatests/test_ipaclient/data/test_csrgen/templates/identity_base.tmpl +--- freeipa-4.8.0/ipatests/test_ipaclient/data/test_csrgen/templates/identity_base.tmpl 2019-07-03 08:42:45.993478023 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipatests/test_ipaclient/data/test_csrgen/templates/identity_base.tmpl 1970-01-01 01:00:00.000000000 +0100 +@@ -1 +0,0 @@ +-{{ options|join(";") }} +diff -urN freeipa-4.8.0/ipatests/test_ipaclient/test_csrgen.py freeipa-4.8.0.removed_csrgen/ipatests/test_ipaclient/test_csrgen.py +--- freeipa-4.8.0/ipatests/test_ipaclient/test_csrgen.py 2019-07-03 08:42:45.963478469 +0200 ++++ freeipa-4.8.0.removed_csrgen/ipatests/test_ipaclient/test_csrgen.py 1970-01-01 01:00:00.000000000 +0100 +@@ -1,304 +0,0 @@ +-# +-# Copyright (C) 2016 FreeIPA Contributors see COPYING for license +-# +- +-import os +-import pytest +- +-from cryptography.hazmat.backends import default_backend +-from cryptography.hazmat.primitives.asymmetric import rsa +-from cryptography import x509 +- +-from ipaclient import csrgen, csrgen_ffi +-from ipalib import errors +- +-BASE_DIR = os.path.dirname(__file__) +-CSR_DATA_DIR = os.path.join(BASE_DIR, 'data', 'test_csrgen') +- +- +-@pytest.fixture +-def formatter(): +- return csrgen.Formatter(csr_data_dir=CSR_DATA_DIR) +- +- +-@pytest.fixture +-def rule_provider(): +- return csrgen.FileRuleProvider(csr_data_dir=CSR_DATA_DIR) +- +- +-@pytest.fixture +-def generator(): +- return csrgen.CSRGenerator(csrgen.FileRuleProvider()) +- +- +-class StubRuleProvider(csrgen.RuleProvider): +- def __init__(self): +- self.syntax_rule = csrgen.Rule( +- 'syntax', '{{datarules|join(",")}}', {}) +- self.data_rule = csrgen.Rule('data', 'data_template', {}) +- self.field_mapping = csrgen.FieldMapping( +- 'example', self.syntax_rule, [self.data_rule]) +- self.rules = [self.field_mapping] +- +- def rules_for_profile(self, profile_id): +- return self.rules +- +- +-class IdentityFormatter(csrgen.Formatter): +- base_template_name = 'identity_base.tmpl' +- +- def __init__(self): +- super(IdentityFormatter, self).__init__(csr_data_dir=CSR_DATA_DIR) +- +- def _get_template_params(self, syntax_rules): +- return {'options': syntax_rules} +- +- +-class test_Formatter: +- def test_prepare_data_rule_with_data_source(self, formatter): +- data_rule = csrgen.Rule('uid', '{{subject.uid.0}}', +- {'data_source': 'subject.uid.0'}) +- prepared = formatter._prepare_data_rule(data_rule) +- assert prepared == '{% if subject.uid.0 %}{{subject.uid.0}}{% endif %}' +- +- def test_prepare_data_rule_no_data_source(self, formatter): +- """Not a normal case, but we should handle it anyway""" +- data_rule = csrgen.Rule('uid', 'static_text', {}) +- prepared = formatter._prepare_data_rule(data_rule) +- assert prepared == 'static_text' +- +- def test_prepare_syntax_rule_with_data_sources(self, formatter): +- syntax_rule = csrgen.Rule( +- 'example', '{{datarules|join(",")}}', {}) +- data_rules = ['{{subject.field1}}', '{{subject.field2}}'] +- data_sources = ['subject.field1', 'subject.field2'] +- prepared = formatter._prepare_syntax_rule( +- syntax_rule, data_rules, 'example', data_sources) +- +- assert prepared == ( +- '{% if subject.field1 or subject.field2 %}{{subject.field1}},' +- '{{subject.field2}}{% endif %}') +- +- def test_prepare_syntax_rule_with_combinator(self, formatter): +- syntax_rule = csrgen.Rule('example', '{{datarules|join(",")}}', +- {'data_source_combinator': 'and'}) +- data_rules = ['{{subject.field1}}', '{{subject.field2}}'] +- data_sources = ['subject.field1', 'subject.field2'] +- prepared = formatter._prepare_syntax_rule( +- syntax_rule, data_rules, 'example', data_sources) +- +- assert prepared == ( +- '{% if subject.field1 and subject.field2 %}{{subject.field1}},' +- '{{subject.field2}}{% endif %}') +- +- def test_prepare_syntax_rule_required(self, formatter): +- syntax_rule = csrgen.Rule('example', '{{datarules|join(",")}}', +- {'required': True}) +- data_rules = ['{{subject.field1}}'] +- data_sources = ['subject.field1'] +- prepared = formatter._prepare_syntax_rule( +- syntax_rule, data_rules, 'example', data_sources) +- +- assert prepared == ( +- '{% filter required("example") %}{% if subject.field1 %}' +- '{{subject.field1}}{% endif %}{% endfilter %}') +- +- def test_prepare_syntax_rule_passthrough(self, formatter): +- """ +- Calls to macros defined as passthrough are still call tags in the final +- template. +- """ +- formatter._define_passthrough('example.macro') +- +- syntax_rule = csrgen.Rule( +- 'example', +- '{% call example.macro() %}{{datarules|join(",")}}{% endcall %}', +- {}) +- data_rules = ['{{subject.field1}}'] +- data_sources = ['subject.field1'] +- prepared = formatter._prepare_syntax_rule( +- syntax_rule, data_rules, 'example', data_sources) +- +- assert prepared == ( +- '{% if subject.field1 %}{% call example.macro() %}' +- '{{subject.field1}}{% endcall %}{% endif %}') +- +- def test_prepare_syntax_rule_no_data_sources(self, formatter): +- """Not a normal case, but we should handle it anyway""" +- syntax_rule = csrgen.Rule( +- 'example', '{{datarules|join(",")}}', {}) +- data_rules = ['rule1', 'rule2'] +- data_sources = [] +- prepared = formatter._prepare_syntax_rule( +- syntax_rule, data_rules, 'example', data_sources) +- +- assert prepared == 'rule1,rule2' +- +- +-class test_FileRuleProvider: +- def test_rule_basic(self, rule_provider): +- rule_name = 'basic' +- +- rule = rule_provider._rule(rule_name) +- +- assert rule.template == 'openssl_rule' +- +- def test_rule_global_options(self, rule_provider): +- rule_name = 'options' +- +- rule = rule_provider._rule(rule_name) +- +- assert rule.options['rule_option'] is True +- +- def test_rule_nosuchrule(self, rule_provider): +- with pytest.raises(errors.NotFound): +- rule_provider._rule('nosuchrule') +- +- def test_rules_for_profile_success(self, rule_provider): +- rules = rule_provider.rules_for_profile('profile') +- +- assert len(rules) == 1 +- field_mapping = rules[0] +- assert field_mapping.syntax_rule.name == 'basic' +- assert len(field_mapping.data_rules) == 1 +- assert field_mapping.data_rules[0].name == 'options' +- +- def test_rules_for_profile_nosuchprofile(self, rule_provider): +- with pytest.raises(errors.NotFound): +- rule_provider.rules_for_profile('nosuchprofile') +- +- +-class test_CSRGenerator: +- def test_userCert_OpenSSL(self, generator): +- principal = { +- 'uid': ['testuser'], +- 'mail': ['testuser@example.com'], +- } +- config = { +- 'ipacertificatesubjectbase': [ +- 'O=DOMAIN.EXAMPLE.COM' +- ], +- } +- +- script = generator.csr_config(principal, config, 'userCert') +- with open(os.path.join( +- CSR_DATA_DIR, 'configs', 'userCert.conf')) as f: +- expected_script = f.read() +- assert script == expected_script +- +- def test_caIPAserviceCert_OpenSSL(self, generator): +- principal = { +- 'krbprincipalname': [ +- 'HTTP/machine.example.com@DOMAIN.EXAMPLE.COM' +- ], +- } +- config = { +- 'ipacertificatesubjectbase': [ +- 'O=DOMAIN.EXAMPLE.COM' +- ], +- } +- +- script = generator.csr_config( +- principal, config, 'caIPAserviceCert') +- with open(os.path.join( +- CSR_DATA_DIR, 'configs', 'caIPAserviceCert.conf')) as f: +- expected_script = f.read() +- assert script == expected_script +- +- def test_works_with_lowercase_attr_type_shortname(self, generator): +- principal = { +- 'uid': ['testuser'], +- 'mail': ['testuser@example.com'], +- } +- template_env = { +- 'ipacertificatesubjectbase': [ +- 'o=DOMAIN.EXAMPLE.COM' # lower-case attr type shortname +- ], +- } +- config = generator.csr_config(principal, template_env, 'userCert') +- +- key = rsa.generate_private_key( +- public_exponent=65537, +- key_size=2048, +- backend=default_backend(), +- ) +- adaptor = csrgen.OpenSSLAdaptor(key=key) +- +- reqinfo = bytes(csrgen_ffi.build_requestinfo( +- config.encode('utf-8'), adaptor.get_subject_public_key_info())) +- csr_der = adaptor.sign_csr(reqinfo) +- csr = x509.load_der_x509_csr(csr_der, default_backend()) +- assert ( +- csr.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME) +- == [x509.NameAttribute(x509.NameOID.COMMON_NAME, u'testuser')] +- ) +- assert ( +- csr.subject.get_attributes_for_oid(x509.NameOID.ORGANIZATION_NAME) +- == [x509.NameAttribute( +- x509.NameOID.ORGANIZATION_NAME, u'DOMAIN.EXAMPLE.COM')] +- ) +- +- def test_unrecognised_attr_type_raises(self, generator): +- principal = { +- 'uid': ['testuser'], +- 'mail': ['testuser@example.com'], +- } +- template_env = { +- 'ipacertificatesubjectbase': [ +- 'X=DOMAIN.EXAMPLE.COM' # unrecognised attr type +- ], +- } +- config = generator.csr_config(principal, template_env, 'userCert') +- +- key = rsa.generate_private_key( +- public_exponent=65537, +- key_size=2048, +- backend=default_backend(), +- ) +- adaptor = csrgen.OpenSSLAdaptor(key=key) +- +- with pytest.raises( +- errors.CSRTemplateError, +- match=r'^unrecognised attribute type: X$'): +- csrgen_ffi.build_requestinfo( +- config.encode('utf-8'), adaptor.get_subject_public_key_info()) +- +- +-class test_rule_handling: +- def test_optionalAttributeMissing(self, generator): +- principal = {'uid': 'testuser'} +- rule_provider = StubRuleProvider() +- rule_provider.data_rule.template = '{{subject.mail}}' +- rule_provider.data_rule.options = {'data_source': 'subject.mail'} +- generator = csrgen.CSRGenerator( +- rule_provider, formatter_class=IdentityFormatter) +- +- script = generator.csr_config( +- principal, {}, 'example') +- assert script == '\n' +- +- def test_twoDataRulesOneMissing(self, generator): +- principal = {'uid': 'testuser'} +- rule_provider = StubRuleProvider() +- rule_provider.data_rule.template = '{{subject.mail}}' +- rule_provider.data_rule.options = {'data_source': 'subject.mail'} +- rule_provider.field_mapping.data_rules.append(csrgen.Rule( +- 'data2', '{{subject.uid}}', {'data_source': 'subject.uid'})) +- generator = csrgen.CSRGenerator( +- rule_provider, formatter_class=IdentityFormatter) +- +- script = generator.csr_config(principal, {}, 'example') +- assert script == ',testuser\n' +- +- def test_requiredAttributeMissing(self): +- principal = {'uid': 'testuser'} +- rule_provider = StubRuleProvider() +- rule_provider.data_rule.template = '{{subject.mail}}' +- rule_provider.data_rule.options = {'data_source': 'subject.mail'} +- rule_provider.syntax_rule.options = {'required': True} +- generator = csrgen.CSRGenerator( +- rule_provider, formatter_class=IdentityFormatter) +- +- with pytest.raises(errors.CSRTemplateError): +- _script = generator.csr_config( +- principal, {}, 'example') diff --git a/SOURCES/1002-Remove-csrgen.patch b/SOURCES/1002-Remove-csrgen.patch deleted file mode 100644 index 596f1ea..0000000 --- a/SOURCES/1002-Remove-csrgen.patch +++ /dev/null @@ -1,1804 +0,0 @@ -From 468bcf90cb985e2b1eb394bd752dc39aa4b75582 Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Thu, 19 Jul 2018 18:37:18 -0400 -Subject: [PATCH] Remove csrgen - -This reverts commits: -* 72de679eb445c975ec70cd265d37d4927823ce5b -* 177f07e163d6d591a1e609d35e0a6f6f5347551e -* 80be18162921268be9c8981495c9e8a4de0c85cd -* 83e2c2b65eeb5a3aa4a59c0535e9177aac5e4637 -* ada91c20588046bb147fc701718d3da4d2c080ca -* 4350dcdea22fd2284836315d0ae7d38733a7620e -* 39a5d9c5aae77687f67d9be02457733bdfb99ead -* a26cf0d7910dd4c0a4da08682b4be8d3d94ba520 -* afd7c05d11432304bfdf183832a21d419f363689 -* f1a1c6eca1b294f24174d7b0e1f78de46d9d5b05 -* fc58eff6a3d7fe805e612b8b002304d8b9cd4ba9 -* 10ef5947860f5098182b1f95c08c1158e2da15f9 - -https://bugzilla.redhat.com/show_bug.cgi?id=1432630 ---- - freeipa.spec.in | 14 - - ipaclient/csrgen.py | 488 --------------------- - ipaclient/csrgen/profiles/caIPAserviceCert.json | 15 - - ipaclient/csrgen/profiles/userCert.json | 15 - - ipaclient/csrgen/rules/dataDNS.json | 8 - - ipaclient/csrgen/rules/dataEmail.json | 8 - - ipaclient/csrgen/rules/dataHostCN.json | 8 - - ipaclient/csrgen/rules/dataSubjectBase.json | 8 - - ipaclient/csrgen/rules/dataUsernameCN.json | 8 - - ipaclient/csrgen/rules/syntaxSAN.json | 8 - - ipaclient/csrgen/rules/syntaxSubject.json | 9 - - ipaclient/csrgen/templates/openssl_base.tmpl | 17 - - ipaclient/csrgen/templates/openssl_macros.tmpl | 29 -- - ipaclient/csrgen_ffi.py | 331 -------------- - ipaclient/plugins/cert.py | 80 ---- - ipaclient/plugins/csrgen.py | 128 ------ - ipaclient/setup.py | 8 - - .../data/test_csrgen/configs/caIPAserviceCert.conf | 16 - - .../data/test_csrgen/configs/userCert.conf | 16 - - .../data/test_csrgen/profiles/profile.json | 8 - - .../data/test_csrgen/rules/basic.json | 5 - - .../data/test_csrgen/rules/options.json | 8 - - .../data/test_csrgen/templates/identity_base.tmpl | 1 - - ipatests/test_ipaclient/test_csrgen.py | 304 ------------- - 24 files changed, 1540 deletions(-) - delete mode 100644 ipaclient/csrgen.py - delete mode 100644 ipaclient/csrgen/profiles/caIPAserviceCert.json - delete mode 100644 ipaclient/csrgen/profiles/userCert.json - delete mode 100644 ipaclient/csrgen/rules/dataDNS.json - delete mode 100644 ipaclient/csrgen/rules/dataEmail.json - delete mode 100644 ipaclient/csrgen/rules/dataHostCN.json - delete mode 100644 ipaclient/csrgen/rules/dataSubjectBase.json - delete mode 100644 ipaclient/csrgen/rules/dataUsernameCN.json - delete mode 100644 ipaclient/csrgen/rules/syntaxSAN.json - delete mode 100644 ipaclient/csrgen/rules/syntaxSubject.json - delete mode 100644 ipaclient/csrgen/templates/openssl_base.tmpl - delete mode 100644 ipaclient/csrgen/templates/openssl_macros.tmpl - delete mode 100644 ipaclient/csrgen_ffi.py - delete mode 100644 ipaclient/plugins/csrgen.py - delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/configs/caIPAserviceCert.conf - delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/configs/userCert.conf - delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/profiles/profile.json - delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/rules/basic.json - delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/rules/options.json - delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/templates/identity_base.tmpl - delete mode 100644 ipatests/test_ipaclient/test_csrgen.py - -diff --git a/freeipa.spec.in b/freeipa.spec.in -index a01fae7..cd1773a 100644 ---- a/freeipa.spec.in -+++ b/freeipa.spec.in -@@ -1538,13 +1538,6 @@ fi - %{python2_sitelib}/ipaclient/remote_plugins/*.py* - %dir %{python2_sitelib}/ipaclient/remote_plugins/2_* - %{python2_sitelib}/ipaclient/remote_plugins/2_*/*.py* --%dir %{python2_sitelib}/ipaclient/csrgen --%dir %{python2_sitelib}/ipaclient/csrgen/profiles --%{python2_sitelib}/ipaclient/csrgen/profiles/*.json --%dir %{python2_sitelib}/ipaclient/csrgen/rules --%{python2_sitelib}/ipaclient/csrgen/rules/*.json --%dir %{python2_sitelib}/ipaclient/csrgen/templates --%{python2_sitelib}/ipaclient/csrgen/templates/*.tmpl - %{python2_sitelib}/ipaclient-*.egg-info - - %endif # with_python2 -@@ -1567,13 +1560,6 @@ fi - %dir %{python3_sitelib}/ipaclient/remote_plugins/2_* - %{python3_sitelib}/ipaclient/remote_plugins/2_*/*.py - %{python3_sitelib}/ipaclient/remote_plugins/2_*/__pycache__/*.py* --%dir %{python3_sitelib}/ipaclient/csrgen --%dir %{python3_sitelib}/ipaclient/csrgen/profiles --%{python3_sitelib}/ipaclient/csrgen/profiles/*.json --%dir %{python3_sitelib}/ipaclient/csrgen/rules --%{python3_sitelib}/ipaclient/csrgen/rules/*.json --%dir %{python3_sitelib}/ipaclient/csrgen/templates --%{python3_sitelib}/ipaclient/csrgen/templates/*.tmpl - %{python3_sitelib}/ipaclient-*.egg-info - - -diff --git a/ipaclient/csrgen.py b/ipaclient/csrgen.py -deleted file mode 100644 -index e7573be..0000000 ---- a/ipaclient/csrgen.py -+++ /dev/null -@@ -1,488 +0,0 @@ --# --# Copyright (C) 2016 FreeIPA Contributors see COPYING for license --# -- --import base64 --import collections --import errno --import json --import logging --import os --import os.path --import pipes --import subprocess --import traceback --import codecs -- --import pkg_resources -- --from cryptography.hazmat.backends import default_backend --from cryptography.hazmat.primitives.asymmetric import padding --from cryptography.hazmat.primitives import hashes --from cryptography.hazmat.primitives.serialization import ( -- load_pem_private_key, Encoding, PublicFormat) --from cryptography.x509 import load_pem_x509_certificate --import jinja2 --import jinja2.ext --import jinja2.sandbox --from pyasn1.codec.der import decoder, encoder --from pyasn1.type import univ --from pyasn1_modules import rfc2314 --import six -- --from ipalib import api --from ipalib import errors --from ipalib.text import _ -- --if six.PY3: -- unicode = str -- --__doc__ = _(""" --Routines for constructing certificate signing requests using IPA data and --stored templates. --""") -- --logger = logging.getLogger(__name__) -- -- --class IndexableUndefined(jinja2.Undefined): -- def __getitem__(self, key): -- return jinja2.Undefined( -- hint=self._undefined_hint, obj=self._undefined_obj, -- name=self._undefined_name, exc=self._undefined_exception) -- -- --class IPAExtension(jinja2.ext.Extension): -- """Jinja2 extension providing useful features for CSR generation rules.""" -- -- def __init__(self, environment): -- super(IPAExtension, self).__init__(environment) -- -- environment.filters.update( -- quote=self.quote, -- required=self.required, -- ) -- -- def quote(self, data): -- return pipes.quote(data) -- -- def required(self, data, name): -- if not data: -- raise errors.CSRTemplateError( -- reason=_( -- 'Required CSR generation rule %(name)s is missing data') % -- {'name': name}) -- return data -- -- --class Formatter(object): -- """ -- Class for processing a set of CSR generation rules into a template. -- -- The template can be rendered with user and database data to produce a -- config, which specifies how to build a CSR. -- -- Subclasses of Formatter should set the value of base_template_name to the -- filename of a base template with spaces for the processed rules. -- Additionally, they should override the _get_template_params method to -- produce the correct output for the base template. -- """ -- base_template_name = None -- -- def __init__(self, csr_data_dir=None): -- # chain loaders: -- # 1) csr_data_dir/templates -- # 2) /etc/ipa/csrgen/templates -- # 3) ipaclient/csrgen/templates -- loaders = [] -- if csr_data_dir is not None: -- loaders.append(jinja2.FileSystemLoader( -- os.path.join(csr_data_dir, 'templates')) -- ) -- loaders.append(jinja2.FileSystemLoader( -- os.path.join(api.env.confdir, 'csrgen/templates')) -- ) -- loaders.append(jinja2.PackageLoader('ipaclient', 'csrgen/templates')) -- -- self.jinja2 = jinja2.sandbox.SandboxedEnvironment( -- loader=jinja2.ChoiceLoader(loaders), -- extensions=[jinja2.ext.ExprStmtExtension, IPAExtension], -- keep_trailing_newline=True, undefined=IndexableUndefined) -- -- self.passthrough_globals = {} -- -- def _define_passthrough(self, call): -- """Some macros are meant to be interpreted during the final render, not -- when data rules are interpolated into syntax rules. This method allows -- those macros to be registered so that calls to them are passed through -- to the prepared rule rather than interpreted. -- """ -- -- def passthrough(caller): -- return u'{%% call %s() %%}%s{%% endcall %%}' % (call, caller()) -- -- parts = call.split('.') -- current_level = self.passthrough_globals -- for part in parts[:-1]: -- if part not in current_level: -- current_level[part] = {} -- current_level = current_level[part] -- current_level[parts[-1]] = passthrough -- -- def build_template(self, rules): -- """ -- Construct a template that can produce CSR generator strings. -- -- :param rules: list of FieldMapping to use to populate the template. -- -- :returns: jinja2.Template that can be rendered to produce the CSR data. -- """ -- syntax_rules = [] -- for field_mapping in rules: -- data_rules_prepared = [ -- self._prepare_data_rule(rule) -- for rule in field_mapping.data_rules] -- -- data_sources = [] -- for xrule in field_mapping.data_rules: -- data_source = xrule.options.get('data_source') -- if data_source: -- data_sources.append(data_source) -- -- syntax_rules.append(self._prepare_syntax_rule( -- field_mapping.syntax_rule, data_rules_prepared, -- field_mapping.description, data_sources)) -- -- template_params = self._get_template_params(syntax_rules) -- base_template = self.jinja2.get_template( -- self.base_template_name, globals=self.passthrough_globals) -- -- try: -- combined_template_source = base_template.render(**template_params) -- except jinja2.UndefinedError: -- logger.debug(traceback.format_exc()) -- raise errors.CSRTemplateError(reason=_( -- 'Template error when formatting certificate data')) -- -- logger.debug( -- 'Formatting with template: %s', combined_template_source) -- combined_template = self.jinja2.from_string(combined_template_source) -- -- return combined_template -- -- def _wrap_conditional(self, rule, condition): -- rule = '{%% if %s %%}%s{%% endif %%}' % (condition, rule) -- return rule -- -- def _wrap_required(self, rule, description): -- template = '{%% filter required("%s") %%}%s{%% endfilter %%}' % ( -- description, rule) -- -- return template -- -- def _prepare_data_rule(self, data_rule): -- template = data_rule.template -- -- data_source = data_rule.options.get('data_source') -- if data_source: -- template = self._wrap_conditional(template, data_source) -- -- return template -- -- def _prepare_syntax_rule( -- self, syntax_rule, data_rules, description, data_sources): -- logger.debug('Syntax rule template: %s', syntax_rule.template) -- template = self.jinja2.from_string( -- syntax_rule.template, globals=self.passthrough_globals) -- is_required = syntax_rule.options.get('required', False) -- try: -- prepared_template = template.render(datarules=data_rules) -- except jinja2.UndefinedError: -- logger.debug(traceback.format_exc()) -- raise errors.CSRTemplateError(reason=_( -- 'Template error when formatting certificate data')) -- -- if data_sources: -- combinator = ' %s ' % syntax_rule.options.get( -- 'data_source_combinator', 'or') -- condition = combinator.join(data_sources) -- prepared_template = self._wrap_conditional( -- prepared_template, condition) -- -- if is_required: -- prepared_template = self._wrap_required( -- prepared_template, description) -- -- return prepared_template -- -- def _get_template_params(self, syntax_rules): -- """ -- Package the syntax rules into fields expected by the base template. -- -- :param syntax_rules: list of prepared syntax rules to be included in -- the template. -- -- :returns: dict of values needed to render the base template. -- """ -- raise NotImplementedError('Formatter class must be subclassed') -- -- --class OpenSSLFormatter(Formatter): -- """Formatter class generating the openssl config-file format.""" -- -- base_template_name = 'openssl_base.tmpl' -- -- # Syntax rules are wrapped in this data structure, to keep track of whether -- # each goes in the extension or the root section -- SyntaxRule = collections.namedtuple( -- 'SyntaxRule', ['template', 'is_extension']) -- -- def __init__(self, *args, **kwargs): -- super(OpenSSLFormatter, self).__init__(*args, **kwargs) -- self._define_passthrough('openssl.section') -- -- def _get_template_params(self, syntax_rules): -- parameters = [rule.template for rule in syntax_rules -- if not rule.is_extension] -- extensions = [rule.template for rule in syntax_rules -- if rule.is_extension] -- -- return {'parameters': parameters, 'extensions': extensions} -- -- def _prepare_syntax_rule( -- self, syntax_rule, data_rules, description, data_sources): -- """Overrides method to pull out whether rule is an extension or not.""" -- prepared_template = super(OpenSSLFormatter, self)._prepare_syntax_rule( -- syntax_rule, data_rules, description, data_sources) -- is_extension = syntax_rule.options.get('extension', False) -- return self.SyntaxRule(prepared_template, is_extension) -- -- --class FieldMapping(object): -- """Representation of the rules needed to construct a complete cert field. -- -- Attributes: -- description: str, a name or description of this field, to be used in -- messages -- syntax_rule: Rule, the rule defining the syntax of this field -- data_rules: list of Rule, the rules that produce data to be stored in -- this field -- """ -- __slots__ = ['description', 'syntax_rule', 'data_rules'] -- -- def __init__(self, description, syntax_rule, data_rules): -- self.description = description -- self.syntax_rule = syntax_rule -- self.data_rules = data_rules -- -- --class Rule(object): -- __slots__ = ['name', 'template', 'options'] -- -- def __init__(self, name, template, options): -- self.name = name -- self.template = template -- self.options = options -- -- --class RuleProvider(object): -- def rules_for_profile(self, profile_id): -- """ -- Return the rules needed to build a CSR using the given profile. -- -- :param profile_id: str, name of the CSR generation profile to use -- -- :returns: list of FieldMapping, filled out with the appropriate rules -- """ -- raise NotImplementedError('RuleProvider class must be subclassed') -- -- --class FileRuleProvider(RuleProvider): -- def __init__(self, csr_data_dir=None): -- self.rules = {} -- self._csrgen_data_dirs = [] -- if csr_data_dir is not None: -- self._csrgen_data_dirs.append(csr_data_dir) -- self._csrgen_data_dirs.append( -- os.path.join(api.env.confdir, 'csrgen') -- ) -- self._csrgen_data_dirs.append( -- pkg_resources.resource_filename('ipaclient', 'csrgen') -- ) -- -- def _open(self, subdir, filename): -- for data_dir in self._csrgen_data_dirs: -- path = os.path.join(data_dir, subdir, filename) -- try: -- return open(path) -- except IOError as e: -- if e.errno != errno.ENOENT: -- raise -- raise IOError( -- errno.ENOENT, -- "'{}' not found in {}".format( -- os.path.join(subdir, filename), -- ", ".join(self._csrgen_data_dirs) -- ) -- ) -- -- def _rule(self, rule_name): -- if rule_name not in self.rules: -- try: -- with self._open('rules', '%s.json' % rule_name) as f: -- ruleconf = json.load(f) -- except IOError: -- raise errors.NotFound( -- reason=_('No generation rule %(rulename)s found.') % -- {'rulename': rule_name}) -- -- try: -- rule = ruleconf['rule'] -- except KeyError: -- raise errors.EmptyResult( -- reason=_('Generation rule "%(rulename)s" is missing the' -- ' "rule" key') % {'rulename': rule_name}) -- -- options = ruleconf.get('options', {}) -- -- self.rules[rule_name] = Rule( -- rule_name, rule['template'], options) -- -- return self.rules[rule_name] -- -- def rules_for_profile(self, profile_id): -- try: -- with self._open('profiles', '%s.json' % profile_id) as f: -- profile = json.load(f) -- except IOError: -- raise errors.NotFound( -- reason=_('No CSR generation rules are defined for profile' -- ' %(profile_id)s') % {'profile_id': profile_id}) -- -- field_mappings = [] -- for field in profile: -- syntax_rule = self._rule(field['syntax']) -- data_rules = [self._rule(name) for name in field['data']] -- field_mappings.append(FieldMapping( -- syntax_rule.name, syntax_rule, data_rules)) -- return field_mappings -- -- --class CSRGenerator(object): -- def __init__(self, rule_provider, formatter_class=OpenSSLFormatter): -- self.rule_provider = rule_provider -- self.formatter = formatter_class() -- -- def csr_config(self, principal, config, profile_id): -- render_data = {'subject': principal, 'config': config} -- -- rules = self.rule_provider.rules_for_profile(profile_id) -- template = self.formatter.build_template(rules) -- -- try: -- config = template.render(render_data) -- except jinja2.UndefinedError: -- logger.debug(traceback.format_exc()) -- raise errors.CSRTemplateError(reason=_( -- 'Template error when formatting certificate data')) -- -- return config -- -- --class CSRLibraryAdaptor(object): -- def get_subject_public_key_info(self): -- raise NotImplementedError('Use a subclass of CSRLibraryAdaptor') -- -- def sign_csr(self, certification_request_info): -- """Sign a CertificationRequestInfo. -- -- :returns: bytes, a DER-encoded signed CSR. -- """ -- raise NotImplementedError('Use a subclass of CSRLibraryAdaptor') -- -- --class OpenSSLAdaptor(object): -- def __init__(self, key=None, key_filename=None, password_filename=None): -- """ -- Must provide either ``key_filename`` or ``key``. -- -- """ -- if key_filename is not None: -- with open(key_filename, 'rb') as key_file: -- key_bytes = key_file.read() -- -- password = None -- if password_filename is not None: -- with open(password_filename, 'rb') as password_file: -- password = password_file.read().strip() -- -- self._key = load_pem_private_key( -- key_bytes, password, default_backend()) -- -- elif key is not None: -- self._key = key -- -- else: -- raise ValueError("Must provide 'key' or 'key_filename'") -- -- def key(self): -- return self._key -- -- def get_subject_public_key_info(self): -- pubkey_info = self.key().public_key().public_bytes( -- Encoding.DER, PublicFormat.SubjectPublicKeyInfo) -- return pubkey_info -- -- def sign_csr(self, certification_request_info): -- reqinfo = decoder.decode( -- certification_request_info, rfc2314.CertificationRequestInfo())[0] -- csr = rfc2314.CertificationRequest() -- csr.setComponentByName('certificationRequestInfo', reqinfo) -- -- algorithm = rfc2314.SignatureAlgorithmIdentifier() -- algorithm.setComponentByName( -- 'algorithm', univ.ObjectIdentifier( -- '1.2.840.113549.1.1.11')) # sha256WithRSAEncryption -- csr.setComponentByName('signatureAlgorithm', algorithm) -- -- signature = self.key().sign( -- certification_request_info, -- padding.PKCS1v15(), -- hashes.SHA256() -- ) -- asn1sig = univ.BitString("'{sig}'H".format( -- sig=codecs.encode(signature, 'hex') -- .decode('ascii')) -- ) -- csr.setComponentByName('signature', asn1sig) -- return encoder.encode(csr) -- -- --class NSSAdaptor(object): -- def __init__(self, database, password_filename): -- self.database = database -- self.password_filename = password_filename -- self.nickname = base64.b32encode(os.urandom(40)) -- -- def get_subject_public_key_info(self): -- temp_cn = base64.b32encode(os.urandom(40)).decode('ascii') -- -- password_args = [] -- if self.password_filename is not None: -- password_args = ['-f', self.password_filename] -- -- subprocess.check_call( -- ['certutil', '-S', '-n', self.nickname, '-s', 'CN=%s' % temp_cn, -- '-x', '-t', ',,', '-d', self.database] + password_args) -- cert_pem = subprocess.check_output( -- ['certutil', '-L', '-n', self.nickname, '-a', -- '-d', self.database] + password_args) -- -- cert = load_pem_x509_certificate(cert_pem, default_backend()) -- pubkey_info = cert.public_key().public_bytes( -- Encoding.DER, PublicFormat.SubjectPublicKeyInfo) -- -- return pubkey_info -- -- def sign_csr(self, certification_request_info): -- raise NotImplementedError('NSS is not yet supported') -diff --git a/ipaclient/csrgen/profiles/caIPAserviceCert.json b/ipaclient/csrgen/profiles/caIPAserviceCert.json -deleted file mode 100644 -index 114d2ff..0000000 ---- a/ipaclient/csrgen/profiles/caIPAserviceCert.json -+++ /dev/null -@@ -1,15 +0,0 @@ --[ -- { -- "syntax": "syntaxSubject", -- "data": [ -- "dataHostCN", -- "dataSubjectBase" -- ] -- }, -- { -- "syntax": "syntaxSAN", -- "data": [ -- "dataDNS" -- ] -- } --] -diff --git a/ipaclient/csrgen/profiles/userCert.json b/ipaclient/csrgen/profiles/userCert.json -deleted file mode 100644 -index d6cf5cf..0000000 ---- a/ipaclient/csrgen/profiles/userCert.json -+++ /dev/null -@@ -1,15 +0,0 @@ --[ -- { -- "syntax": "syntaxSubject", -- "data": [ -- "dataUsernameCN", -- "dataSubjectBase" -- ] -- }, -- { -- "syntax": "syntaxSAN", -- "data": [ -- "dataEmail" -- ] -- } --] -diff --git a/ipaclient/csrgen/rules/dataDNS.json b/ipaclient/csrgen/rules/dataDNS.json -deleted file mode 100644 -index a79a3d7..0000000 ---- a/ipaclient/csrgen/rules/dataDNS.json -+++ /dev/null -@@ -1,8 +0,0 @@ --{ -- "rule": { -- "template": "DNS = {{subject.krbprincipalname.0.partition('/')[2].partition('@')[0]}}" -- }, -- "options": { -- "data_source": "subject.krbprincipalname.0.partition('/')[2].partition('@')[0]" -- } --} -diff --git a/ipaclient/csrgen/rules/dataEmail.json b/ipaclient/csrgen/rules/dataEmail.json -deleted file mode 100644 -index 4be6cec..0000000 ---- a/ipaclient/csrgen/rules/dataEmail.json -+++ /dev/null -@@ -1,8 +0,0 @@ --{ -- "rule": { -- "template": "email = {{subject.mail.0}}" -- }, -- "options": { -- "data_source": "subject.mail.0" -- } --} -diff --git a/ipaclient/csrgen/rules/dataHostCN.json b/ipaclient/csrgen/rules/dataHostCN.json -deleted file mode 100644 -index f30c50f..0000000 ---- a/ipaclient/csrgen/rules/dataHostCN.json -+++ /dev/null -@@ -1,8 +0,0 @@ --{ -- "rule": { -- "template": "CN={{subject.krbprincipalname.0.partition('/')[2].partition('@')[0]}}" -- }, -- "options": { -- "data_source": "subject.krbprincipalname.0.partition('/')[2].partition('@')[0]" -- } --} -diff --git a/ipaclient/csrgen/rules/dataSubjectBase.json b/ipaclient/csrgen/rules/dataSubjectBase.json -deleted file mode 100644 -index 31a38b4..0000000 ---- a/ipaclient/csrgen/rules/dataSubjectBase.json -+++ /dev/null -@@ -1,8 +0,0 @@ --{ -- "rule": { -- "template": "{{config.ipacertificatesubjectbase.0}}" -- }, -- "options": { -- "data_source": "config.ipacertificatesubjectbase.0" -- } --} -diff --git a/ipaclient/csrgen/rules/dataUsernameCN.json b/ipaclient/csrgen/rules/dataUsernameCN.json -deleted file mode 100644 -index acbb524..0000000 ---- a/ipaclient/csrgen/rules/dataUsernameCN.json -+++ /dev/null -@@ -1,8 +0,0 @@ --{ -- "rule": { -- "template": "CN={{subject.uid.0}}" -- }, -- "options": { -- "data_source": "subject.uid.0" -- } --} -diff --git a/ipaclient/csrgen/rules/syntaxSAN.json b/ipaclient/csrgen/rules/syntaxSAN.json -deleted file mode 100644 -index c6943ed..0000000 ---- a/ipaclient/csrgen/rules/syntaxSAN.json -+++ /dev/null -@@ -1,8 +0,0 @@ --{ -- "rule": { -- "template": "subjectAltName = @{% call openssl.section() %}{{ datarules|join('\n') }}{% endcall %}" -- }, -- "options": { -- "extension": true -- } --} -diff --git a/ipaclient/csrgen/rules/syntaxSubject.json b/ipaclient/csrgen/rules/syntaxSubject.json -deleted file mode 100644 -index c609e01..0000000 ---- a/ipaclient/csrgen/rules/syntaxSubject.json -+++ /dev/null -@@ -1,9 +0,0 @@ --{ -- "rule": { -- "template": "distinguished_name = {% call openssl.section() %}{{ datarules|reverse|join('\n') }}{% endcall %}" -- }, -- "options": { -- "required": true, -- "data_source_combinator": "and" -- } --} -diff --git a/ipaclient/csrgen/templates/openssl_base.tmpl b/ipaclient/csrgen/templates/openssl_base.tmpl -deleted file mode 100644 -index 8d37994..0000000 ---- a/ipaclient/csrgen/templates/openssl_base.tmpl -+++ /dev/null -@@ -1,17 +0,0 @@ --{% raw -%} --{% import "openssl_macros.tmpl" as openssl -%} --{% endraw -%} --[ req ] --prompt = no --encrypt_key = no -- --{{ parameters|join('\n') }} --{% raw %}{% set rendered_extensions -%}{% endraw %} --{{ extensions|join('\n') }} --{% raw -%} --{%- endset -%} --{% if rendered_extensions -%} --req_extensions = {% call openssl.section() %}{{ rendered_extensions }}{% endcall %} --{% endif %} --{{ openssl.openssl_sections|join('\n\n') }} --{%- endraw %} -diff --git a/ipaclient/csrgen/templates/openssl_macros.tmpl b/ipaclient/csrgen/templates/openssl_macros.tmpl -deleted file mode 100644 -index d31b8fe..0000000 ---- a/ipaclient/csrgen/templates/openssl_macros.tmpl -+++ /dev/null -@@ -1,29 +0,0 @@ --{# List containing rendered sections to be included at end #} --{% set openssl_sections = [] %} -- --{# --List containing one entry for each section name allocated. Because of --scoping rules, we need to use a list so that it can be a "per-render global" --that gets updated in place. Real globals are shared by all templates with the --same environment, and variables defined in the macro don't persist after the --macro invocation ends. --#} --{% set openssl_section_num = [] %} -- --{% macro section() -%} --{% set name -%} --sec{{ openssl_section_num|length -}} --{% endset -%} --{% do openssl_section_num.append('') -%} --{% set contents %}{{ caller() }}{% endset -%} --{% if contents -%} --{% set sectiondata = formatsection(name, contents) -%} --{% do openssl_sections.append(sectiondata) -%} --{% endif -%} --{{ name -}} --{% endmacro %} -- --{% macro formatsection(name, contents) -%} --[ {{ name }} ] --{{ contents -}} --{% endmacro %} -diff --git a/ipaclient/csrgen_ffi.py b/ipaclient/csrgen_ffi.py -deleted file mode 100644 -index 10a20bd..0000000 ---- a/ipaclient/csrgen_ffi.py -+++ /dev/null -@@ -1,331 +0,0 @@ --from cffi import FFI --import ctypes.util -- --from ipalib import errors -- --_ffi = FFI() -- --_ffi.cdef(''' --typedef ... CONF; --typedef ... CONF_METHOD; --typedef ... BIO; --typedef ... ipa_STACK_OF_CONF_VALUE; -- --/* openssl/conf.h */ --typedef struct { -- char *section; -- char *name; -- char *value; --} CONF_VALUE; -- --CONF *NCONF_new(CONF_METHOD *meth); --void NCONF_free(CONF *conf); --int NCONF_load_bio(CONF *conf, BIO *bp, long *eline); --ipa_STACK_OF_CONF_VALUE *NCONF_get_section(const CONF *conf, -- const char *section); --char *NCONF_get_string(const CONF *conf, const char *group, const char *name); -- --/* openssl/safestack.h */ --// int sk_CONF_VALUE_num(ipa_STACK_OF_CONF_VALUE *); --// CONF_VALUE *sk_CONF_VALUE_value(ipa_STACK_OF_CONF_VALUE *, int); -- --/* openssl/stack.h */ --typedef ... _STACK; -- --int OPENSSL_sk_num(const _STACK *); --void *OPENSSL_sk_value(const _STACK *, int); -- --int sk_num(const _STACK *); --void *sk_value(const _STACK *, int); -- --/* openssl/bio.h */ --BIO *BIO_new_mem_buf(const void *buf, int len); --int BIO_free(BIO *a); -- --/* openssl/asn1.h */ --typedef struct ASN1_ENCODING_st { -- unsigned char *enc; /* DER encoding */ -- long len; /* Length of encoding */ -- int modified; /* set to 1 if 'enc' is invalid */ --} ASN1_ENCODING; -- --/* openssl/evp.h */ --typedef ... EVP_PKEY; -- --void EVP_PKEY_free(EVP_PKEY *pkey); -- --/* openssl/x509.h */ --typedef ... ASN1_INTEGER; --typedef ... ASN1_BIT_STRING; --typedef ... ASN1_OBJECT; --typedef ... X509; --typedef ... X509_ALGOR; --typedef ... X509_CRL; --typedef ... X509_NAME; --typedef ... X509_PUBKEY; --typedef ... ipa_STACK_OF_X509_ATTRIBUTE; -- --typedef struct X509_req_info_st { -- ASN1_ENCODING enc; -- ASN1_INTEGER *version; -- X509_NAME *subject; -- X509_PUBKEY *pubkey; -- /* d=2 hl=2 l= 0 cons: cont: 00 */ -- ipa_STACK_OF_X509_ATTRIBUTE *attributes; /* [ 0 ] */ --} X509_REQ_INFO; -- --typedef struct X509_req_st { -- X509_REQ_INFO *req_info; -- X509_ALGOR *sig_alg; -- ASN1_BIT_STRING *signature; -- int references; --} X509_REQ; -- --X509_REQ *X509_REQ_new(void); --void X509_REQ_free(X509_REQ *); --EVP_PKEY *d2i_PUBKEY_bio(BIO *bp, EVP_PKEY **a); --int X509_REQ_set_pubkey(X509_REQ *x, EVP_PKEY *pkey); --int X509_NAME_add_entry_by_OBJ(X509_NAME *name, const ASN1_OBJECT *obj, int type, -- const unsigned char *bytes, int len, int loc, -- int set); --int X509_NAME_entry_count(X509_NAME *name); --int i2d_X509_REQ_INFO(X509_REQ_INFO *a, unsigned char **out); -- --/* openssl/objects.h */ --ASN1_OBJECT *OBJ_txt2obj(const char *s, int no_name); -- --/* openssl/x509v3.h */ --typedef ... X509V3_CONF_METHOD; -- --typedef struct v3_ext_ctx { -- int flags; -- X509 *issuer_cert; -- X509 *subject_cert; -- X509_REQ *subject_req; -- X509_CRL *crl; -- X509V3_CONF_METHOD *db_meth; -- void *db; --} X509V3_CTX; -- --void X509V3_set_ctx(X509V3_CTX *ctx, X509 *issuer, X509 *subject, -- X509_REQ *req, X509_CRL *crl, int flags); --void X509V3_set_nconf(X509V3_CTX *ctx, CONF *conf); --int X509V3_EXT_REQ_add_nconf(CONF *conf, X509V3_CTX *ctx, char *section, -- X509_REQ *req); -- --/* openssl/x509v3.h */ --unsigned long ERR_get_error(void); --char *ERR_error_string(unsigned long e, char *buf); --''') # noqa: E501 -- --_libcrypto = _ffi.dlopen(ctypes.util.find_library('crypto')) -- --NULL = _ffi.NULL -- --# openssl/conf.h --NCONF_new = _libcrypto.NCONF_new --NCONF_free = _libcrypto.NCONF_free --NCONF_load_bio = _libcrypto.NCONF_load_bio --NCONF_get_section = _libcrypto.NCONF_get_section --NCONF_get_string = _libcrypto.NCONF_get_string -- --# openssl/stack.h --try: -- sk_num = _libcrypto.OPENSSL_sk_num -- sk_value = _libcrypto.OPENSSL_sk_value --except AttributeError: -- sk_num = _libcrypto.sk_num -- sk_value = _libcrypto.sk_value -- -- --def sk_CONF_VALUE_num(sk): -- return sk_num(_ffi.cast("_STACK *", sk)) -- -- --def sk_CONF_VALUE_value(sk, i): -- return _ffi.cast("CONF_VALUE *", sk_value(_ffi.cast("_STACK *", sk), i)) -- -- --# openssl/bio.h --BIO_new_mem_buf = _libcrypto.BIO_new_mem_buf --BIO_free = _libcrypto.BIO_free -- --# openssl/x509.h --X509_REQ_new = _libcrypto.X509_REQ_new --X509_REQ_free = _libcrypto.X509_REQ_free --X509_REQ_set_pubkey = _libcrypto.X509_REQ_set_pubkey --d2i_PUBKEY_bio = _libcrypto.d2i_PUBKEY_bio --i2d_X509_REQ_INFO = _libcrypto.i2d_X509_REQ_INFO --X509_NAME_add_entry_by_OBJ = _libcrypto.X509_NAME_add_entry_by_OBJ --X509_NAME_entry_count = _libcrypto.X509_NAME_entry_count -- -- --def X509_REQ_get_subject_name(req): -- return req.req_info.subject -- -- --# openssl/objects.h --OBJ_txt2obj = _libcrypto.OBJ_txt2obj -- --# openssl/evp.h --EVP_PKEY_free = _libcrypto.EVP_PKEY_free -- --# openssl/asn1.h --MBSTRING_UTF8 = 0x1000 -- --# openssl/x509v3.h --X509V3_set_ctx = _libcrypto.X509V3_set_ctx --X509V3_set_nconf = _libcrypto.X509V3_set_nconf --X509V3_EXT_REQ_add_nconf = _libcrypto.X509V3_EXT_REQ_add_nconf -- --# openssl/err.h --ERR_get_error = _libcrypto.ERR_get_error --ERR_error_string = _libcrypto.ERR_error_string -- -- --def _raise_openssl_errors(): -- msgs = [] -- -- code = ERR_get_error() -- while code != 0: -- msg = _ffi.string(ERR_error_string(code, NULL)) -- try: -- strmsg = msg.decode('utf-8') -- except UnicodeDecodeError: -- strmsg = repr(msg) -- msgs.append(strmsg) -- code = ERR_get_error() -- -- raise errors.CSRTemplateError(reason='\n'.join(msgs)) -- -- --def _parse_dn_section(subj, dn_sk): -- for i in range(sk_CONF_VALUE_num(dn_sk)): -- v = sk_CONF_VALUE_value(dn_sk, i) -- rdn_type = _ffi.string(v.name) -- -- # Skip past any leading X. X: X, etc to allow for multiple instances -- for idx, c in enumerate(rdn_type): -- if c in b':,.': -- if idx+1 < len(rdn_type): -- rdn_type = rdn_type[idx+1:] -- break -- if rdn_type.startswith(b'+'): -- rdn_type = rdn_type[1:] -- mval = -1 -- else: -- mval = 0 -- -- # convert rdn_type to an OID -- # -- # OpenSSL is fussy about the case of the string. For example, -- # lower-case 'o' (for "organization name") is not recognised. -- # Therefore, try to convert the given string into an OID. If -- # that fails, convert it upper case and try again. -- # -- oid = OBJ_txt2obj(rdn_type, 0) -- if oid == NULL: -- oid = OBJ_txt2obj(rdn_type.upper(), 0) -- if oid == NULL: -- raise errors.CSRTemplateError( -- reason='unrecognised attribute type: {}' -- .format(rdn_type.decode('utf-8'))) -- -- if not X509_NAME_add_entry_by_OBJ( -- subj, oid, MBSTRING_UTF8, -- _ffi.cast("unsigned char *", v.value), -1, -1, mval): -- _raise_openssl_errors() -- -- if not X509_NAME_entry_count(subj): -- raise errors.CSRTemplateError( -- reason='error, subject in config file is empty') -- -- --def build_requestinfo(config, public_key_info): -- ''' -- Return a cffi buffer containing a DER-encoded CertificationRequestInfo. -- -- The returned object implements the buffer protocol. -- -- ''' -- reqdata = NULL -- req = NULL -- nconf_bio = NULL -- pubkey_bio = NULL -- pubkey = NULL -- -- try: -- reqdata = NCONF_new(NULL) -- if reqdata == NULL: -- _raise_openssl_errors() -- -- nconf_bio = BIO_new_mem_buf(config, len(config)) -- errorline = _ffi.new('long[1]', [-1]) -- i = NCONF_load_bio(reqdata, nconf_bio, errorline) -- if i < 0: -- if errorline[0] < 0: -- raise errors.CSRTemplateError(reason="Can't load config file") -- else: -- raise errors.CSRTemplateError( -- reason='Error on line %d of config file' % errorline[0]) -- -- dn_sect = NCONF_get_string(reqdata, b'req', b'distinguished_name') -- if dn_sect == NULL: -- raise errors.CSRTemplateError( -- reason='Unable to find "distinguished_name" key in config') -- -- dn_sk = NCONF_get_section(reqdata, dn_sect) -- if dn_sk == NULL: -- raise errors.CSRTemplateError( -- reason='Unable to find "%s" section in config' % -- _ffi.string(dn_sect)) -- -- pubkey_bio = BIO_new_mem_buf(public_key_info, len(public_key_info)) -- pubkey = d2i_PUBKEY_bio(pubkey_bio, NULL) -- if pubkey == NULL: -- _raise_openssl_errors() -- -- req = X509_REQ_new() -- if req == NULL: -- _raise_openssl_errors() -- -- subject = X509_REQ_get_subject_name(req) -- -- _parse_dn_section(subject, dn_sk) -- -- if not X509_REQ_set_pubkey(req, pubkey): -- _raise_openssl_errors() -- -- ext_ctx = _ffi.new("X509V3_CTX[1]") -- X509V3_set_ctx(ext_ctx, NULL, NULL, req, NULL, 0) -- X509V3_set_nconf(ext_ctx, reqdata) -- -- extn_section = NCONF_get_string(reqdata, b"req", b"req_extensions") -- if extn_section != NULL: -- if not X509V3_EXT_REQ_add_nconf( -- reqdata, ext_ctx, extn_section, req): -- _raise_openssl_errors() -- -- der_len = i2d_X509_REQ_INFO(req.req_info, NULL) -- if der_len < 0: -- _raise_openssl_errors() -- -- der_buf = _ffi.new("unsigned char[%d]" % der_len) -- der_out = _ffi.new("unsigned char **", der_buf) -- der_len = i2d_X509_REQ_INFO(req.req_info, der_out) -- if der_len < 0: -- _raise_openssl_errors() -- -- return _ffi.buffer(der_buf, der_len) -- -- finally: -- if reqdata != NULL: -- NCONF_free(reqdata) -- if req != NULL: -- X509_REQ_free(req) -- if nconf_bio != NULL: -- BIO_free(nconf_bio) -- if pubkey_bio != NULL: -- BIO_free(pubkey_bio) -- if pubkey != NULL: -- EVP_PKEY_free(pubkey) -diff --git a/ipaclient/plugins/cert.py b/ipaclient/plugins/cert.py -index 6d453d0..8a4db81 100644 ---- a/ipaclient/plugins/cert.py -+++ b/ipaclient/plugins/cert.py -@@ -21,8 +21,6 @@ - - import base64 - --import six -- - from ipaclient.frontend import MethodOverride - from ipalib import errors - from ipalib import x509 -@@ -31,9 +29,6 @@ from ipalib.parameters import BinaryFile, File, Flag, Str - from ipalib.plugable import Registry - from ipalib.text import _ - --if six.PY3: -- unicode = str -- - register = Registry() - - -@@ -74,87 +69,12 @@ class CertRetrieveOverride(MethodOverride): - - @register(override=True, no_fail=True) - class cert_request(CertRetrieveOverride): -- takes_options = CertRetrieveOverride.takes_options + ( -- Str( -- 'database?', -- label=_('Path to NSS database'), -- doc=_('Path to NSS database to use for private key'), -- ), -- Str( -- 'private_key?', -- label=_('Path to private key file'), -- doc=_('Path to PEM file containing a private key'), -- ), -- Str( -- 'password_file?', -- label=_( -- 'File containing a password for the private key or database'), -- ), -- Str( -- 'csr_profile_id?', -- label=_('Name of CSR generation profile (if not the same as' -- ' profile_id)'), -- ), -- ) -- - def get_args(self): - for arg in super(cert_request, self).get_args(): - if arg.name == 'csr': - arg = arg.clone_retype(arg.name, File, required=False) - yield arg - -- def forward(self, csr=None, **options): -- database = options.pop('database', None) -- private_key = options.pop('private_key', None) -- csr_profile_id = options.pop('csr_profile_id', None) -- password_file = options.pop('password_file', None) -- -- if csr is None: -- # Deferred import, ipaclient.csrgen is expensive to load. -- # see https://pagure.io/freeipa/issue/7484 -- from ipaclient import csrgen -- -- if database: -- adaptor = csrgen.NSSAdaptor(database, password_file) -- elif private_key: -- adaptor = csrgen.OpenSSLAdaptor( -- key_filename=private_key, password_filename=password_file) -- else: -- raise errors.InvocationError( -- message=u"One of 'database' or 'private_key' is required") -- -- pubkey_info = adaptor.get_subject_public_key_info() -- pubkey_info_b64 = base64.b64encode(pubkey_info) -- -- # If csr_profile_id is passed, that takes precedence. -- # Otherwise, use profile_id. If neither are passed, the default -- # in cert_get_requestdata will be used. -- profile_id = csr_profile_id -- if profile_id is None: -- profile_id = options.get('profile_id') -- -- response = self.api.Command.cert_get_requestdata( -- profile_id=profile_id, -- principal=options.get('principal'), -- public_key_info=pubkey_info_b64) -- -- req_info_b64 = response['result']['request_info'] -- req_info = base64.b64decode(req_info_b64) -- -- csr = adaptor.sign_csr(req_info) -- -- if not csr: -- raise errors.CertificateOperationError( -- error=(_('Generated CSR was empty'))) -- -- else: -- if database is not None or private_key is not None: -- raise errors.MutuallyExclusiveError(reason=_( -- "Options 'database' and 'private_key' are not compatible" -- " with 'csr'")) -- -- return super(cert_request, self).forward(csr, **options) -- - - @register(override=True, no_fail=True) - class cert_show(CertRetrieveOverride): -diff --git a/ipaclient/plugins/csrgen.py b/ipaclient/plugins/csrgen.py -deleted file mode 100644 -index 5aad636..0000000 ---- a/ipaclient/plugins/csrgen.py -+++ /dev/null -@@ -1,128 +0,0 @@ --# --# Copyright (C) 2016 FreeIPA Contributors see COPYING for license --# -- --import base64 -- --import six -- --from ipalib import api --from ipalib import errors --from ipalib import output --from ipalib import util --from ipalib.frontend import Local, Str --from ipalib.parameters import Bytes, Principal --from ipalib.plugable import Registry --from ipalib.text import _ --from ipapython import dogtag -- -- --if six.PY3: -- unicode = str -- --register = Registry() -- --__doc__ = _(""" --Commands to build certificate requests automatically --""") -- -- --@register() --class cert_get_requestdata(Local): -- __doc__ = _('Gather data for a certificate signing request.') -- -- NO_CLI = True -- -- takes_options = ( -- Principal( -- 'principal', -- label=_('Principal'), -- doc=_('Principal for this certificate (e.g.' -- ' HTTP/test.example.com)'), -- ), -- Str( -- 'profile_id?', -- label=_('Profile ID'), -- doc=_('CSR Generation Profile to use'), -- ), -- Bytes( -- 'public_key_info', -- label=_('Subject Public Key Info'), -- doc=_('DER-encoded SubjectPublicKeyInfo structure'), -- ), -- Str( -- 'out?', -- doc=_('Write CertificationRequestInfo to file'), -- ), -- ) -- -- has_output = ( -- output.Output( -- 'result', -- type=dict, -- doc=_('Dictionary mapping variable name to value'), -- ), -- ) -- -- has_output_params = ( -- Str( -- 'request_info', -- label=_('CertificationRequestInfo structure'), -- ) -- ) -- -- def execute(self, *args, **options): -- # Deferred import, ipaclient.csrgen is expensive to load. -- # see https://pagure.io/freeipa/issue/7484 -- from ipaclient import csrgen -- from ipaclient import csrgen_ffi -- -- if 'out' in options: -- util.check_writable_file(options['out']) -- -- principal = options.get('principal') -- profile_id = options.get('profile_id') -- if profile_id is None: -- profile_id = dogtag.DEFAULT_PROFILE -- public_key_info = options.get('public_key_info') -- public_key_info = base64.b64decode(public_key_info) -- -- if self.api.env.in_server: -- backend = self.api.Backend.ldap2 -- else: -- backend = self.api.Backend.rpcclient -- if not backend.isconnected(): -- backend.connect() -- -- try: -- if principal.is_host: -- principal_obj = api.Command.host_show( -- principal.hostname, all=True) -- elif principal.is_service: -- principal_obj = api.Command.service_show( -- unicode(principal), all=True) -- elif principal.is_user: -- principal_obj = api.Command.user_show( -- principal.username, all=True) -- except errors.NotFound: -- raise errors.NotFound( -- reason=_("The principal for this request doesn't exist.")) -- principal_obj = principal_obj['result'] -- config = api.Command.config_show()['result'] -- -- generator = csrgen.CSRGenerator(csrgen.FileRuleProvider()) -- -- csr_config = generator.csr_config(principal_obj, config, profile_id) -- request_info = base64.b64encode(csrgen_ffi.build_requestinfo( -- csr_config.encode('utf8'), public_key_info)) -- -- result = {} -- if 'out' in options: -- with open(options['out'], 'wb') as f: -- f.write(request_info) -- else: -- result = dict(request_info=request_info) -- -- return dict( -- result=result -- ) -diff --git a/ipaclient/setup.py b/ipaclient/setup.py -index 8eb9aee..cd03685 100644 ---- a/ipaclient/setup.py -+++ b/ipaclient/setup.py -@@ -41,13 +41,6 @@ if __name__ == '__main__': - "ipaclient.remote_plugins.2_156", - "ipaclient.remote_plugins.2_164", - ], -- package_data={ -- 'ipaclient': [ -- 'csrgen/profiles/*.json', -- 'csrgen/rules/*.json', -- 'csrgen/templates/*.tmpl', -- ], -- }, - install_requires=[ - "cryptography", - "ipalib", -@@ -63,7 +56,6 @@ if __name__ == '__main__': - extras_require={ - "install": ["ipaplatform"], - "otptoken_yubikey": ["python-yubico", "pyusb"], -- "csrgen": ["cffi", "jinja2"], - }, - zip_safe=False, - ) -diff --git a/ipatests/test_ipaclient/data/test_csrgen/configs/caIPAserviceCert.conf b/ipatests/test_ipaclient/data/test_csrgen/configs/caIPAserviceCert.conf -deleted file mode 100644 -index 3724bdc..0000000 ---- a/ipatests/test_ipaclient/data/test_csrgen/configs/caIPAserviceCert.conf -+++ /dev/null -@@ -1,16 +0,0 @@ --[ req ] --prompt = no --encrypt_key = no -- --distinguished_name = sec0 --req_extensions = sec2 -- --[ sec0 ] --O=DOMAIN.EXAMPLE.COM --CN=machine.example.com -- --[ sec1 ] --DNS = machine.example.com -- --[ sec2 ] --subjectAltName = @sec1 -diff --git a/ipatests/test_ipaclient/data/test_csrgen/configs/userCert.conf b/ipatests/test_ipaclient/data/test_csrgen/configs/userCert.conf -deleted file mode 100644 -index 00d63de..0000000 ---- a/ipatests/test_ipaclient/data/test_csrgen/configs/userCert.conf -+++ /dev/null -@@ -1,16 +0,0 @@ --[ req ] --prompt = no --encrypt_key = no -- --distinguished_name = sec0 --req_extensions = sec2 -- --[ sec0 ] --O=DOMAIN.EXAMPLE.COM --CN=testuser -- --[ sec1 ] --email = testuser@example.com -- --[ sec2 ] --subjectAltName = @sec1 -diff --git a/ipatests/test_ipaclient/data/test_csrgen/profiles/profile.json b/ipatests/test_ipaclient/data/test_csrgen/profiles/profile.json -deleted file mode 100644 -index 676f91b..0000000 ---- a/ipatests/test_ipaclient/data/test_csrgen/profiles/profile.json -+++ /dev/null -@@ -1,8 +0,0 @@ --[ -- { -- "syntax": "basic", -- "data": [ -- "options" -- ] -- } --] -diff --git a/ipatests/test_ipaclient/data/test_csrgen/rules/basic.json b/ipatests/test_ipaclient/data/test_csrgen/rules/basic.json -deleted file mode 100644 -index 094ef71..0000000 ---- a/ipatests/test_ipaclient/data/test_csrgen/rules/basic.json -+++ /dev/null -@@ -1,5 +0,0 @@ --{ -- "rule": { -- "template": "openssl_rule" -- } --} -diff --git a/ipatests/test_ipaclient/data/test_csrgen/rules/options.json b/ipatests/test_ipaclient/data/test_csrgen/rules/options.json -deleted file mode 100644 -index 393ed8c..0000000 ---- a/ipatests/test_ipaclient/data/test_csrgen/rules/options.json -+++ /dev/null -@@ -1,8 +0,0 @@ --{ -- "rule": { -- "template": "openssl_rule" -- }, -- "options": { -- "rule_option": true -- } --} -diff --git a/ipatests/test_ipaclient/data/test_csrgen/templates/identity_base.tmpl b/ipatests/test_ipaclient/data/test_csrgen/templates/identity_base.tmpl -deleted file mode 100644 -index 79111ab..0000000 ---- a/ipatests/test_ipaclient/data/test_csrgen/templates/identity_base.tmpl -+++ /dev/null -@@ -1 +0,0 @@ --{{ options|join(";") }} -diff --git a/ipatests/test_ipaclient/test_csrgen.py b/ipatests/test_ipaclient/test_csrgen.py -deleted file mode 100644 -index b58596b..0000000 ---- a/ipatests/test_ipaclient/test_csrgen.py -+++ /dev/null -@@ -1,304 +0,0 @@ --# --# Copyright (C) 2016 FreeIPA Contributors see COPYING for license --# -- --import os --import pytest -- --from cryptography.hazmat.backends import default_backend --from cryptography.hazmat.primitives.asymmetric import rsa --from cryptography import x509 -- --from ipaclient import csrgen, csrgen_ffi --from ipalib import errors -- --BASE_DIR = os.path.dirname(__file__) --CSR_DATA_DIR = os.path.join(BASE_DIR, 'data', 'test_csrgen') -- -- --@pytest.fixture --def formatter(): -- return csrgen.Formatter(csr_data_dir=CSR_DATA_DIR) -- -- --@pytest.fixture --def rule_provider(): -- return csrgen.FileRuleProvider(csr_data_dir=CSR_DATA_DIR) -- -- --@pytest.fixture --def generator(): -- return csrgen.CSRGenerator(csrgen.FileRuleProvider()) -- -- --class StubRuleProvider(csrgen.RuleProvider): -- def __init__(self): -- self.syntax_rule = csrgen.Rule( -- 'syntax', '{{datarules|join(",")}}', {}) -- self.data_rule = csrgen.Rule('data', 'data_template', {}) -- self.field_mapping = csrgen.FieldMapping( -- 'example', self.syntax_rule, [self.data_rule]) -- self.rules = [self.field_mapping] -- -- def rules_for_profile(self, profile_id): -- return self.rules -- -- --class IdentityFormatter(csrgen.Formatter): -- base_template_name = 'identity_base.tmpl' -- -- def __init__(self): -- super(IdentityFormatter, self).__init__(csr_data_dir=CSR_DATA_DIR) -- -- def _get_template_params(self, syntax_rules): -- return {'options': syntax_rules} -- -- --class test_Formatter(object): -- def test_prepare_data_rule_with_data_source(self, formatter): -- data_rule = csrgen.Rule('uid', '{{subject.uid.0}}', -- {'data_source': 'subject.uid.0'}) -- prepared = formatter._prepare_data_rule(data_rule) -- assert prepared == '{% if subject.uid.0 %}{{subject.uid.0}}{% endif %}' -- -- def test_prepare_data_rule_no_data_source(self, formatter): -- """Not a normal case, but we should handle it anyway""" -- data_rule = csrgen.Rule('uid', 'static_text', {}) -- prepared = formatter._prepare_data_rule(data_rule) -- assert prepared == 'static_text' -- -- def test_prepare_syntax_rule_with_data_sources(self, formatter): -- syntax_rule = csrgen.Rule( -- 'example', '{{datarules|join(",")}}', {}) -- data_rules = ['{{subject.field1}}', '{{subject.field2}}'] -- data_sources = ['subject.field1', 'subject.field2'] -- prepared = formatter._prepare_syntax_rule( -- syntax_rule, data_rules, 'example', data_sources) -- -- assert prepared == ( -- '{% if subject.field1 or subject.field2 %}{{subject.field1}},' -- '{{subject.field2}}{% endif %}') -- -- def test_prepare_syntax_rule_with_combinator(self, formatter): -- syntax_rule = csrgen.Rule('example', '{{datarules|join(",")}}', -- {'data_source_combinator': 'and'}) -- data_rules = ['{{subject.field1}}', '{{subject.field2}}'] -- data_sources = ['subject.field1', 'subject.field2'] -- prepared = formatter._prepare_syntax_rule( -- syntax_rule, data_rules, 'example', data_sources) -- -- assert prepared == ( -- '{% if subject.field1 and subject.field2 %}{{subject.field1}},' -- '{{subject.field2}}{% endif %}') -- -- def test_prepare_syntax_rule_required(self, formatter): -- syntax_rule = csrgen.Rule('example', '{{datarules|join(",")}}', -- {'required': True}) -- data_rules = ['{{subject.field1}}'] -- data_sources = ['subject.field1'] -- prepared = formatter._prepare_syntax_rule( -- syntax_rule, data_rules, 'example', data_sources) -- -- assert prepared == ( -- '{% filter required("example") %}{% if subject.field1 %}' -- '{{subject.field1}}{% endif %}{% endfilter %}') -- -- def test_prepare_syntax_rule_passthrough(self, formatter): -- """ -- Calls to macros defined as passthrough are still call tags in the final -- template. -- """ -- formatter._define_passthrough('example.macro') -- -- syntax_rule = csrgen.Rule( -- 'example', -- '{% call example.macro() %}{{datarules|join(",")}}{% endcall %}', -- {}) -- data_rules = ['{{subject.field1}}'] -- data_sources = ['subject.field1'] -- prepared = formatter._prepare_syntax_rule( -- syntax_rule, data_rules, 'example', data_sources) -- -- assert prepared == ( -- '{% if subject.field1 %}{% call example.macro() %}' -- '{{subject.field1}}{% endcall %}{% endif %}') -- -- def test_prepare_syntax_rule_no_data_sources(self, formatter): -- """Not a normal case, but we should handle it anyway""" -- syntax_rule = csrgen.Rule( -- 'example', '{{datarules|join(",")}}', {}) -- data_rules = ['rule1', 'rule2'] -- data_sources = [] -- prepared = formatter._prepare_syntax_rule( -- syntax_rule, data_rules, 'example', data_sources) -- -- assert prepared == 'rule1,rule2' -- -- --class test_FileRuleProvider(object): -- def test_rule_basic(self, rule_provider): -- rule_name = 'basic' -- -- rule = rule_provider._rule(rule_name) -- -- assert rule.template == 'openssl_rule' -- -- def test_rule_global_options(self, rule_provider): -- rule_name = 'options' -- -- rule = rule_provider._rule(rule_name) -- -- assert rule.options['rule_option'] is True -- -- def test_rule_nosuchrule(self, rule_provider): -- with pytest.raises(errors.NotFound): -- rule_provider._rule('nosuchrule') -- -- def test_rules_for_profile_success(self, rule_provider): -- rules = rule_provider.rules_for_profile('profile') -- -- assert len(rules) == 1 -- field_mapping = rules[0] -- assert field_mapping.syntax_rule.name == 'basic' -- assert len(field_mapping.data_rules) == 1 -- assert field_mapping.data_rules[0].name == 'options' -- -- def test_rules_for_profile_nosuchprofile(self, rule_provider): -- with pytest.raises(errors.NotFound): -- rule_provider.rules_for_profile('nosuchprofile') -- -- --class test_CSRGenerator(object): -- def test_userCert_OpenSSL(self, generator): -- principal = { -- 'uid': ['testuser'], -- 'mail': ['testuser@example.com'], -- } -- config = { -- 'ipacertificatesubjectbase': [ -- 'O=DOMAIN.EXAMPLE.COM' -- ], -- } -- -- script = generator.csr_config(principal, config, 'userCert') -- with open(os.path.join( -- CSR_DATA_DIR, 'configs', 'userCert.conf')) as f: -- expected_script = f.read() -- assert script == expected_script -- -- def test_caIPAserviceCert_OpenSSL(self, generator): -- principal = { -- 'krbprincipalname': [ -- 'HTTP/machine.example.com@DOMAIN.EXAMPLE.COM' -- ], -- } -- config = { -- 'ipacertificatesubjectbase': [ -- 'O=DOMAIN.EXAMPLE.COM' -- ], -- } -- -- script = generator.csr_config( -- principal, config, 'caIPAserviceCert') -- with open(os.path.join( -- CSR_DATA_DIR, 'configs', 'caIPAserviceCert.conf')) as f: -- expected_script = f.read() -- assert script == expected_script -- -- def test_works_with_lowercase_attr_type_shortname(self, generator): -- principal = { -- 'uid': ['testuser'], -- 'mail': ['testuser@example.com'], -- } -- template_env = { -- 'ipacertificatesubjectbase': [ -- 'o=DOMAIN.EXAMPLE.COM' # lower-case attr type shortname -- ], -- } -- config = generator.csr_config(principal, template_env, 'userCert') -- -- key = rsa.generate_private_key( -- public_exponent=65537, -- key_size=2048, -- backend=default_backend(), -- ) -- adaptor = csrgen.OpenSSLAdaptor(key=key) -- -- reqinfo = bytes(csrgen_ffi.build_requestinfo( -- config.encode('utf-8'), adaptor.get_subject_public_key_info())) -- csr_der = adaptor.sign_csr(reqinfo) -- csr = x509.load_der_x509_csr(csr_der, default_backend()) -- assert ( -- csr.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME) -- == [x509.NameAttribute(x509.NameOID.COMMON_NAME, u'testuser')] -- ) -- assert ( -- csr.subject.get_attributes_for_oid(x509.NameOID.ORGANIZATION_NAME) -- == [x509.NameAttribute( -- x509.NameOID.ORGANIZATION_NAME, u'DOMAIN.EXAMPLE.COM')] -- ) -- -- def test_unrecognised_attr_type_raises(self, generator): -- principal = { -- 'uid': ['testuser'], -- 'mail': ['testuser@example.com'], -- } -- template_env = { -- 'ipacertificatesubjectbase': [ -- 'X=DOMAIN.EXAMPLE.COM' # unrecognised attr type -- ], -- } -- config = generator.csr_config(principal, template_env, 'userCert') -- -- key = rsa.generate_private_key( -- public_exponent=65537, -- key_size=2048, -- backend=default_backend(), -- ) -- adaptor = csrgen.OpenSSLAdaptor(key=key) -- -- with pytest.raises( -- errors.CSRTemplateError, -- message='unrecognised attribute type: X'): -- csrgen_ffi.build_requestinfo( -- config.encode('utf-8'), adaptor.get_subject_public_key_info()) -- -- --class test_rule_handling(object): -- def test_optionalAttributeMissing(self, generator): -- principal = {'uid': 'testuser'} -- rule_provider = StubRuleProvider() -- rule_provider.data_rule.template = '{{subject.mail}}' -- rule_provider.data_rule.options = {'data_source': 'subject.mail'} -- generator = csrgen.CSRGenerator( -- rule_provider, formatter_class=IdentityFormatter) -- -- script = generator.csr_config( -- principal, {}, 'example') -- assert script == '\n' -- -- def test_twoDataRulesOneMissing(self, generator): -- principal = {'uid': 'testuser'} -- rule_provider = StubRuleProvider() -- rule_provider.data_rule.template = '{{subject.mail}}' -- rule_provider.data_rule.options = {'data_source': 'subject.mail'} -- rule_provider.field_mapping.data_rules.append(csrgen.Rule( -- 'data2', '{{subject.uid}}', {'data_source': 'subject.uid'})) -- generator = csrgen.CSRGenerator( -- rule_provider, formatter_class=IdentityFormatter) -- -- script = generator.csr_config(principal, {}, 'example') -- assert script == ',testuser\n' -- -- def test_requiredAttributeMissing(self): -- principal = {'uid': 'testuser'} -- rule_provider = StubRuleProvider() -- rule_provider.data_rule.template = '{{subject.mail}}' -- rule_provider.data_rule.options = {'data_source': 'subject.mail'} -- rule_provider.syntax_rule.options = {'required': True} -- generator = csrgen.CSRGenerator( -- rule_provider, formatter_class=IdentityFormatter) -- -- with pytest.raises(errors.CSRTemplateError): -- _script = generator.csr_config( -- principal, {}, 'example') --- -2.13.6 - diff --git a/SOURCES/freeipa-4.7.1.tar.gz.asc b/SOURCES/freeipa-4.7.1.tar.gz.asc deleted file mode 100644 index 3f461b5..0000000 --- a/SOURCES/freeipa-4.7.1.tar.gz.asc +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN PGP SIGNATURE----- - -iQIzBAABCAAdFiEEL88tWdi+3Yi1YO6/QPd0nE8v3u0FAlu3sM0ACgkQQPd0nE8v -3u3frhAAgZMF8ifQwBFMs1bQV6AhgP7RCnPxh2qDmcVccbCx3ug655wvawzXZw5H -171ec7mDnoSD2VtJv2PZ4L3migO+JOlEQg7Yw50DihLnXKrH70hqByvSqOOacsPX -hdrXRmOMGrG6l/qXs9OF2PIvXVGtIgRKcsQFQ8jyBFvRKFlxY/NzWUG3RmT8IRZm -LWfqS66If6n99QvR8zCQTARvTUC5hsgS1W00eLEMcAlTs8SYWf3NiQlov1Eucj0L -wDsvDKoKChXYoQqF4+ymCwPJ/MWvBxtYGwXo9wKVa0itC/Y+HdrCR6QVeL5bQalf -9bLFIYMuBwt7jPWaz5iST/sl6Svh7pDrIGod7I/G0bhzFwqFxFvINVY5WUGRAske -ElfvN2RMqjRwrd63AZWXs9kfsTYSZ1oPb7OrMBuAGkoXcfbHQAQesXou9gIGi3yJ -QGX6neghtop8fKDpNH1iLamVzlM2DZl5po1kiA5/ltbjOiXL86UORRia8CXew6rz -B9QKEhNorWb6jakXlBUmbDVbew29ZQ9wfOAlVOwF2No7Wr51EC+kXUuLkliBgCfM -QWhdBJ9Ae8eFGQAgRpd2DPzhMU68N+DplIGyJQfBs+3qDK7Ods+eTmi/UAuXdiYG -qUE3Gzfa4LfXedlTX3Qcyo48CPVZPNCLh2qcGKtWXuN/pSEOkM0= -=esuk ------END PGP SIGNATURE----- diff --git a/SOURCES/freeipa-4.8.0.tar.gz.asc b/SOURCES/freeipa-4.8.0.tar.gz.asc new file mode 100644 index 0000000..06b7f8e --- /dev/null +++ b/SOURCES/freeipa-4.8.0.tar.gz.asc @@ -0,0 +1,16 @@ +-----BEGIN PGP SIGNATURE----- + +iQIzBAABCgAdFiEEhAodHH8+xLL+UwQ1RxniuKu/YhoFAl0cTrUACgkQRxniuKu/ +YhoehQ//YuCG+Rq2wbkSDiooP8/0K5HvO/atz4Ke8iaKsOKS8gdmaJTyMnsOOoTc +hlx925TIOc4/Xp1+qeIn7T8xZp3rYtGcMcxIyKlUrHCU1Jxc07zf+ZlSwCZTCjLU +YGAh9ReC9+//1oJqnr/C+Rp3veZbYn3DIG261GMqOlHCUfF91hF78XctzklcZNpV +D38a+gfXdWivejezA/GWyiY3foIcLI98zpBd2v0PXEzaKO2BqrVlOl2nDC7BGapS +PvpB4GPwuwo8qxASFbu8I6uxyp2oDZtrM/Tb1HM31cuslieH5p/XRwJ8zoewHvgo +jSKXfcBHmRvjMjSL07R3b7JjZ+1jmj/C5VNXQcPfp2qdhDhmywDArfC3uIBJ2otx +oxKbtAhAzeGIaoyfgjrxk0ZOubnIbmk/M8nan2F9ChJV/NoKVjDVAfUDDM2h6wXg +IRBg6uIOkJAKuOr7i0zaxBkBi/8NpUE214JvJnNfWa1gpoYu1S5tzuja6dSeteRM +JTPPzpkKD2sgK/laRmZQo2si1qFOGEYnLTO0dWJV4/ScZCCy9+rCQ0C6NwKYC7xy +8c1Juu/YqJF/14VbYAWQIABIK/0z0TiVI0r0v75rzSFpMiThrgC6wXo1zFEADiK0 +GSoYwkcygn0ne21jQxLizGAPRYvdQ5RkpiE2/J4nVSTyuSM/cM4= +=Q29c +-----END PGP SIGNATURE----- diff --git a/SPECS/ipa.spec b/SPECS/ipa.spec index d5238cb..290dced 100644 --- a/SPECS/ipa.spec +++ b/SPECS/ipa.spec @@ -1,9 +1,12 @@ # 389-ds-base 1.4 no longer supports i686 platform, build only client # packages, https://bugzilla.redhat.com/show_bug.cgi?id=1544386 -%ifarch %{ix86} -%{!?ONLY_CLIENT:%global ONLY_CLIENT 1} +%if 0%{?fedora} >= 28 || 0%{?rhel} > 7 + %ifarch %{ix86} + %{!?ONLY_CLIENT:%global ONLY_CLIENT 1} + %endif %endif + # Define ONLY_CLIENT to only make the ipa-client and ipa-python # subpackages %{!?ONLY_CLIENT:%global ONLY_CLIENT 0} @@ -13,9 +16,13 @@ %global enable_server_option --enable-server %endif -# Build with ipatests -# RHEL spec file only: Force ipatests off -%global with_ipatests 0 +# Build ipatests +%if 0%{?rhel} + %global with_ipatests 0 +%endif +%if ! %{ONLY_CLIENT} + %{!?with_ipatests:%global with_ipatests 1} +%endif %if 0%{?with_ipatests} %global with_ipatests_option --with-ipatests %else @@ -23,25 +30,19 @@ %endif # Python 2/3 packages and default Python interpreter -%if 0%{?rhel} -%global with_default_python 3 -%global with_python2 0 +%if 0%{?rhel} > 7 + %global with_default_python 3 %endif %if 0%{?fedora} >= 29 -# F29 only supports Python 3 as default Python -%global with_default_python 3 + # F29 only supports Python 3 as default Python + %global with_default_python 3 %endif %{!?with_default_python:%global with_default_python 3} -%{!?with_python2:%global with_python2 1} -%if %{with_default_python} == 3 +%global with_python3 1 %global python %{__python3} -%else -%global with_python2 1 -%global python %{__python2} -%endif # lint is not executed during rpmbuild # %%global with_lint 1 @@ -51,11 +52,10 @@ %global linter_options --disable-pylint --without-jslint %endif -# RHEL spec file only: alt name is freeipa - -%global alt_name freeipa %if 0%{?rhel} -%global krb5_version 1.16.1 +%global package_name ipa +%global alt_name freeipa +%global krb5_version 1.17-7 %global krb5_kdb_version 7.0 # 0.7.16: https://github.com/drkjam/netaddr/issues/71 %global python_netaddr_version 0.7.16 @@ -64,66 +64,85 @@ %global selinux_policy_version 3.14.1-14 %global slapi_nis_version 0.56.1-4 %global python_ldap_version 3.1.0-1 -%global ds_version 1.4.0.8-1 -%global hostname_version 3.20-6 +# python3-lib389 +# Fix for "Installation fails: Replica Busy" +# https://pagure.io/389-ds-base/issue/49818 +%global ds_version 1.4.0.16 + %else -%global krb5_version 1.16.1 +# Fedora +%global package_name freeipa +%global alt_name ipa +# Fix for CVE-2018-20217 +%global krb5_version 1.17-17 %global krb5_kdb_version 7.0 # 0.7.16: https://github.com/drkjam/netaddr/issues/71 %global python_netaddr_version 0.7.16 # Require 4.7.0 which brings Python 3 bindings -%global samba_version 2:4.7.0 +%global samba_version 2:4.9.0 # DNSSEC AVC violation, RHBZ#1537971 %global selinux_policy_version 3.13.1-283.24 %global slapi_nis_version 0.56.1 # fix for segfault in python3-ldap, https://pagure.io/freeipa/issue/7324 %global python_ldap_version 3.1.0-1 +# Fix for create suffix +# https://pagure.io/389-ds-base/issue/49984 +%global ds_version 1.4.1.1 -# Fix for "Crash when failing to read from SASL connection" -# https://pagure.io/389-ds-base/issue/49639 -%global ds_version 1.4.0.8-1 +# Don't use Fedora's Python dependency generator on Fedora 30/rawhide yet. +# Some packages don't provide new dist aliases. +# https://docs.fedoraproject.org/en-US/packaging-guidelines/Python/ +%{?python_disable_dependency_generator} -%global hostname_version 3.20-5 %endif # Fedora -# Require Dogtag PKI 10.6.6 to detect when fips is available, -# https://pagure.io/freeipa/issue/7608 -# Require Dogtag PKI 10.6.7 due to change of handling for the default token name -# https://pagure.io/dogtagpki/issue/3073 -# and lack of creating admin NSS db twice -# https://pagure.io/freeipa/issue/7742 -%global pki_version 10.6.7 - -# NSS release with fix for CKA_LABEL import bug in shared SQL database. -# https://bugzilla.redhat.com/show_bug.cgi?id=1568271 -%global nss_version 3.36.1-1.1 +# Require Dogtag PKI 10.6.8-3 (10.6.7 was never pushed to stable) +# 10.6.7 fixes UpdateNumberRange clone installation issue +# https://pagure.io/freeipa/issue/7654 and empty token issue +# and https://pagure.io/dogtagpki/issue/3073 +%global pki_version 10.7.1-2 + +# https://pagure.io/certmonger/issue/90 +%global certmonger_version 0.79.7-3 + +# NSS release with fix for p11-kit-proxy issue, affects F28 +# https://pagure.io/freeipa/issue/7810 +%if 0%{?fedora} == 28 +%global nss_version 3.41.0-3 +%else +%global nss_version 3.41.0-1 +%endif -# One-Way Trust authenticated by trust secret -# https://bugzilla.redhat.com/show_bug.cgi?id=1345975#c20 -%global sssd_version 2.0.0 +%global sssd_version 2.2.0-1 -%define krb5_base_version %(LC_ALL=C rpm -q --qf '%%{VERSION}' krb5-devel | grep -Eo '^[^.]+\.[^.]+') +%global kdcproxy_version 0.4-3 %global plugin_dir %{_libdir}/dirsrv/plugins %global etc_systemd_dir %{_sysconfdir}/systemd/system %global gettext_domain ipa -%global VERSION 4.7.1 - %define _hardened_build 1 -# RHEL spec file only: name is "ipa" -Name: ipa -Version: %{VERSION} +# Work-around fact that RPM SPEC parser does not accept +# "Version: @VERSION@" in freeipa.spec.in used for Autoconf string replacement +%define IPA_VERSION 4.8.0 +%define AT_SIGN @ +# redefine IPA_VERSION only if its value matches the Autoconf placeholder +%if "%{IPA_VERSION}" == "%{AT_SIGN}VERSION%{AT_SIGN}" + %define IPA_VERSION nonsense.to.please.RPM.SPEC.parser +%endif + +Name: %{package_name} +Version: %{IPA_VERSION} Release: 11%{?dist} Summary: The Identity, Policy and Audit system -Group: System Environment/Base License: GPLv3+ -URL: https://www.freeipa.org/ -Source0: https://releases.pagure.org/freeipa/freeipa-%{VERSION}.tar.gz -Source1: https://releases.pagure.org/freeipa/freeipa-%{VERSION}.tar.gz.asc +URL: http://www.freeipa.org/ +Source0: https://releases.pagure.org/freeipa/freeipa-%{version}.tar.gz +Source1: https://releases.pagure.org/freeipa/freeipa-%{version}.tar.gz.asc + # RHEL spec file only: START: Change branding to IPA and Identity Management # Moved branding logos and background to redhat-logos-ipa-80.4: # header-logo.png, login-screen-background.jpg, login-screen-logo.png, @@ -132,40 +151,19 @@ Source1: https://releases.pagure.org/freeipa/freeipa-%{VERSION}.tar.gz.as # RHEL spec file only: START Patch0001: 0001-No-need-to-call-rhel-specific-domainname-service.patch -Patch0002: 0002-freeipa-4.7.0-ipaclient-Remove-no-sssd-and-noac-options_rhbz#1614301.patch -Patch0003: 0003-adtrust-define-Guests-mapping.patch -Patch0004: 0004-freeipa-4.7.1-Find_orphan_automember_rules_rhbz#1638373.patch -Patch0005: 0005-net-groupmap-force-using-empty-config.patch -Patch0006: 0006-Keep-dogtags-client-db-in-external-ca-step-1.patch -Patch0007: 0007-Replace-hard-coded-interpreter-with-sys.executable.patch -Patch0008: 0008-Fix_misleading_errors_during_client_install_rollback_rhbz#1658283.patch -Patch0009: 0009-ipa-advise_update_url_of_cacerdir_rehash_tool_9cfd07e_rhbz#1658287.patch -Patch0010: 0010-Handle_NTP_configuration_in_replica_server_installation_f3e3da5_rhbz#1651679.patch -Patch0011: 0011-Fix_defects_found_by_static_analysis_rhbz#1658182.patch -Patch0012: 0012-ipa-replica-install_--setup-adtrust_check_for_package_ipa-server-trust-ad_rhbz#1658294.patch -Patch0013: 0013-ipaldap_invalid_modlist_when_attribute_encoding_can_vary_rhbz#1658302.patch -Patch0014: 0014-Allow_ipaapi_and_Apache_user_to_access_SSSD_IFP_rhbz#1639910.patch -Patch0015: 0015-Add_sysadm_r_to_default_SELinux_user_map_order_1853e2e_rhbz#1658303.patch -Patch0016: 0016-certdb_non-empty_Subject_Key_Identifier_and_validate_server_cert_sig_rhbz#1641988.patch -Patch0017: 0017-ipa-replica-install_password_and_admin-password_options_mutually_exclusive_rhbz#1658309.patch -Patch0018: 0018-ipa_upgrade_handle_double-encoded_certificates_rhbz#1658310.patch -Patch0019: 0019-PKINIT_fix_ipa-pkinit-manage_enable_disable_rhbz#1658313.patch -Patch0020: 0020-Enable_LDAP_debug_output_in_client_to_display_TLS_errors_in_join_rhbz#1658316.patch -Patch0021: 0021-rpc_always_read_response_rhbz#1639890.patch -Patch0022: 0022-ipa_vault-retrieve_fix_internal_error_rhbz#1658485.patch -Patch0023: 0023-Move_ipa_s_systemd_tmpfiles_from_var_run_to_run_rhbz#1658487.patch -Patch0024: 0024-Fix_authselect_invocations_to_work_with_1.0.2_rhbz#1654291.patch -Patch0025: 0025-ipa-client-automount_and_NFS_unit_name_changes_rhbz#1645501.patch -Patch0026: 0026-Fix_compile_issue_with_new_389-ds_rhbz#1659448.patch -Patch0027: 0027-ipaserver-dcerpc-fix-exclusion-entry-with-a-forest-t.patch -Patch0028: 0028-Create-systemd-user-HBAC-service-and-rule.patch -# Patch0029 contains small excerpt from upstream 8182ebc to have user_add in -# ipatests/pytest_ipa/integration/tasks.py to be able to have working test -Patch0029: 0029-Resolve_user_group_names_in_idoverride_-find_rhbz#1657745.patch -Patch0030: 0030-Fix-systemd-user-HBAC-rule.patch -Patch0031: 0031-ipa-client-automount-handle-NFS-configuration-file-c.patch +Patch0002: 0002-Fix-test_webui.test_selinuxusermap.patch +Patch0003: 0003-Remove-posixAccount-from-service_find-search-filter-2f9cbff_rhbz#1731437.patch +Patch0004: 0004-Repeated-uninstallation-of-ipa-client-samba-crashes_rhbz#1732529.patch +Patch0005: 0005-WebUI-Add-PKINIT-status-field-to-Configuration-page-a46383f_rhbz#1518153.patch +Patch0006: 0006-external-ca-profile-fix_rhbz#1731813.patch +Patch0007: 0007-Allow-insecure-binds-for-migration-8e207fd3_rhbz#1731963.patch +Patch0008: 0008-install-Add-missing-scripts-to-app_DATA_rhbz#1741170.patch +Patch0009: 0009-extdom-unify-error-code-handling-especially-LDAP_NO_SUCH_OBJECT_rhbz#1741530.patch +Patch0010: 0010-Fix-automount-behavior-with-authselect_rhbz#1740167.patch +Patch0011: 0011-adtrust-avoid-using-timestamp-in-klist-output_ed1c1626-rhbz#1750242.patch +Patch0012: 0012-add-default-access-control-configuration-to-trusted-domain-objects_rhbz#1751707.patch Patch1001: 1001-Change-branding-to-IPA-and-Identity-Management.patch -Patch1002: 1002-Remove-csrgen.patch +Patch1002: 1002-4.8.0-Remove-csrgen.patch # RHEL spec file only: END # For the timestamp trick in patch application @@ -188,10 +186,6 @@ BuildRequires: automake BuildRequires: libtool BuildRequires: gettext BuildRequires: gettext-devel -%if 0%{?with_python2} -BuildRequires: python2-devel -BuildRequires: python2-setuptools -%endif # with_python2 BuildRequires: python3-devel BuildRequires: python3-setuptools BuildRequires: systemd @@ -224,7 +218,6 @@ BuildRequires: python3-lesscpy >= 0.13.0-2 # # Build dependencies for makeapi/makeaci # -%if %{with_default_python} == 3 BuildRequires: python3-cffi BuildRequires: python3-dns BuildRequires: python3-ldap >= %{python_ldap_version} @@ -233,16 +226,6 @@ BuildRequires: python3-netaddr >= %{python_netaddr_version} BuildRequires: python3-pyasn1 BuildRequires: python3-pyasn1-modules BuildRequires: python3-six -%else -BuildRequires: python2-cffi -BuildRequires: python2-dns -BuildRequires: python2-ldap >= %{python_ldap_version} -BuildRequires: python2-libsss_nss_idmap -BuildRequires: python2-netaddr >= %{python_netaddr_version} -BuildRequires: python2-pyasn1 -BuildRequires: python2-pyasn1-modules -BuildRequires: python2-six -%endif # # Build dependencies for wheel packaging and PyPI upload @@ -251,7 +234,11 @@ BuildRequires: python2-six BuildRequires: dbus-glib-devel BuildRequires: libffi-devel BuildRequires: python3-tox +%if 0%{?fedora} <= 28 BuildRequires: python3-twine +%else +BuildRequires: twine +%endif BuildRequires: python3-wheel %endif # with_wheels @@ -260,49 +247,9 @@ BuildRequires: python3-wheel # %if 0%{?with_lint} BuildRequires: jsl +BuildRequires: rpmlint BuildRequires: softhsm -%if 0%{?with_python2} -BuildRequires: python2-augeas -BuildRequires: python2-cffi -BuildRequires: python2-cryptography >= 1.6 -BuildRequires: python2-custodia >= 0.3.1 -BuildRequires: python2-dateutil -BuildRequires: python2-dbus -BuildRequires: python2-dns -BuildRequires: python2-dns >= 1.15 -BuildRequires: python2-enum34 -BuildRequires: python2-gssapi >= 1.2.0-5 -BuildRequires: python2-jinja2 -BuildRequires: python2-jwcrypto >= 0.4.2 -BuildRequires: python2-ldap >= %{python_ldap_version} -BuildRequires: python2-libipa_hbac -BuildRequires: python2-libsss_nss_idmap -BuildRequires: python2-lxml -BuildRequires: python2-netaddr >= %{python_netaddr_version} -BuildRequires: python2-netifaces -BuildRequires: python2-paste -%if 0%{?fedora} < 29 -# Fedora 29 workaround: Dogtag no longer packages Python 2 -BuildRequires: python2-pki >= %{pki_version} -%endif -BuildRequires: python2-polib -BuildRequires: python2-pyasn1 -BuildRequires: python2-pyasn1-modules -BuildRequires: python2-pycodestyle -BuildRequires: python2-pylint -BuildRequires: python2-pytest-multihost -BuildRequires: python2-pytest-sourceorder -BuildRequires: python2-qrcode-core >= 5.0.0 -BuildRequires: python2-samba -BuildRequires: python2-six -BuildRequires: python2-sss -BuildRequires: python2-sss-murmur -BuildRequires: python2-sssdconfig >= %{sssd_version} -BuildRequires: python2-systemd -BuildRequires: python2-yubico -%endif # with_python2 - BuildRequires: python3-augeas BuildRequires: python3-cffi BuildRequires: python3-cryptography >= 1.6 @@ -315,6 +262,7 @@ BuildRequires: python3-jinja2 BuildRequires: python3-jwcrypto >= 0.4.2 BuildRequires: python3-ldap >= %{python_ldap_version} BuildRequires: python3-ldap >= %{python_ldap_version} +BuildRequires: python3-lib389 >= %{ds_version} BuildRequires: python3-libipa_hbac BuildRequires: python3-libsss_nss_idmap BuildRequires: python3-lxml @@ -326,7 +274,12 @@ BuildRequires: python3-polib BuildRequires: python3-pyasn1 BuildRequires: python3-pyasn1-modules BuildRequires: python3-pycodestyle +%if 0%{?fedora} >= 29 +# https://bugzilla.redhat.com/show_bug.cgi?id=1648299 +BuildRequires: python3-pylint >= 2.1.1-2 +%else BuildRequires: python3-pylint >= 1.7 +%endif BuildRequires: python3-pytest-multihost BuildRequires: python3-pytest-sourceorder BuildRequires: python3-qrcode-core >= 5.0.0 @@ -360,41 +313,26 @@ and integration with Active Directory based infrastructures (Trusts). %package server Summary: The IPA authentication server -Group: System Environment/Base Requires: %{name}-server-common = %{version}-%{release} Requires: %{name}-client = %{version}-%{release} Requires: %{name}-common = %{version}-%{release} -%if %{with_default_python} == 3 Requires: python3-ipaserver = %{version}-%{release} Requires: python3-ldap >= %{python_ldap_version} -%else -Requires: python2-ipaserver = %{version}-%{release} -Requires: python2-ldap >= %{python_ldap_version} -%endif Requires: 389-ds-base >= %{ds_version} -Requires: 389-ds-base-legacy-tools >= %{ds_version} Requires: openldap-clients > 2.4.35-4 Requires: nss >= %{nss_version} Requires: nss-tools >= %{nss_version} Requires(post): krb5-server >= %{krb5_version} -Requires(post): krb5-server >= %{krb5_base_version}, krb5-server < %{krb5_base_version}.100 +Requires(post): krb5-kdb-version = %{krb5_kdb_version} Requires: krb5-pkinit-openssl >= %{krb5_version} Requires: cyrus-sasl-gssapi%{?_isa} Requires: chrony Requires: httpd >= 2.4.6-31 -%if %{with_default_python} == 3 -%{?__python3:Requires(preun): %{__python3}} -%{?__python3:Requires(postun): %{__python3}} +Requires(preun): python3 +Requires(postun): python3 Requires: python3-gssapi >= 1.2.0-5 Requires: python3-systemd Requires: python3-mod_wsgi -%else -Requires(preun): python2 -Requires(postun): python2 -Requires: python2-gssapi >= 1.2.0-5 -Requires: python2-systemd -Requires: mod_wsgi -%endif Requires: mod_auth_gssapi >= 1.5.0 Requires: mod_ssl Requires: mod_session @@ -408,17 +346,13 @@ Requires(post): systemd-units Requires: selinux-policy >= %{selinux_policy_version} Requires(post): selinux-policy-base >= %{selinux_policy_version} Requires: slapi-nis >= %{slapi_nis_version} -# jss is an indirect dependency. 4.4.5 fixes sub CA replication bug, -# see https://pagure.io/freeipa/issue/7536 -# see https://pagure.io/freeipa/issue/7590 -Requires: jss >= 4.4.5-1 Requires: pki-ca >= %{pki_version} Requires: pki-kra >= %{pki_version} Requires(preun): systemd-units Requires(postun): systemd-units Requires: policycoreutils >= 2.1.12-5 Requires: tar -Requires(pre): certmonger >= 0.79.5-1 +Requires(pre): certmonger >= %{certmonger_version} Requires(pre): 389-ds-base >= %{ds_version} Requires: fontawesome-fonts Requires: open-sans-fonts @@ -430,7 +364,6 @@ Requires: gzip Requires: oddjob # 0.7.0-2: https://pagure.io/gssproxy/pull-request/172 Requires: gssproxy >= 0.7.0-2 -# 1.15.2: FindByNameAndCertificate (https://pagure.io/SSSD/sssd/issue/3050) Requires: sssd-dbus >= %{sssd_version} Provides: %{alt_name}-server = %{version} @@ -462,51 +395,12 @@ and integration with Active Directory based infrastructures (Trusts). If you are installing an IPA server, you need to install this package. -%if 0%{?with_python2} && 0%{?fedora} < 29 -# Fedora 29 workaround: don't build python2-ipaserver, python2-pki is n/a -%package -n python2-ipaserver -Summary: Python libraries used by IPA server -Group: System Environment/Libraries -BuildArch: noarch -%{?python_provide:%python_provide python2-ipaserver} -%{!?python_provide:Provides: python-ipaserver = %{version}-%{release}} -Requires: %{name}-server-common = %{version}-%{release} -Requires: %{name}-common = %{version}-%{release} -Requires: python2-augeas -Requires: python2-custodia >= 0.3.1 -Requires: python2-dbus -Requires: python2-dns >= 1.15 -Requires: python2-gssapi >= 1.2.0-5 -Requires: python2-ipaclient = %{version}-%{release} -Requires: python2-kdcproxy >= 0.3 -Requires: python2-ldap >= %{python_ldap_version} -Requires: python2-lxml -Requires: python2-pki >= %{pki_version} -Requires: python2-pyasn1 >= 0.3.2-2 -Requires: python2-sssdconfig >= %{sssd_version} -Requires: rpm-libs - -%description -n python2-ipaserver -IPA is an integrated solution to provide centrally managed Identity (users, -hosts, services), Authentication (SSO, 2FA), and Authorization -(host access control, SELinux user roles, services). The solution provides -features for further integration with Linux based clients (SUDO, automount) -and integration with Active Directory based infrastructures (Trusts). -If you are installing an IPA server, you need to install this package. - -%endif # with_python2 and Fedora < 29 - %package -n python3-ipaserver Summary: Python libraries used by IPA server -Group: System Environment/Libraries BuildArch: noarch %{?python_provide:%python_provide python3-ipaserver} Requires: %{name}-server-common = %{version}-%{release} Requires: %{name}-common = %{version}-%{release} -%if 0%{?fedora} >= 29 -Conflicts: python2-ipaserver -Obsoletes: python2-ipaserver < %{version} -%endif # we need pre-requires since earlier versions may break upgrade Requires(pre): python3-ldap >= %{python_ldap_version} Requires: python3-augeas @@ -515,7 +409,7 @@ Requires: python3-dbus Requires: python3-dns >= 1.15 Requires: python3-gssapi >= 1.2.0 Requires: python3-ipaclient = %{version}-%{release} -Requires: python3-kdcproxy >= 0.3 +Requires: python3-kdcproxy >= %{kdcproxy_version} Requires: python3-lxml Requires: python3-pki >= %{pki_version} Requires: python3-pyasn1 >= 0.3.2-2 @@ -533,13 +427,14 @@ If you are installing an IPA server, you need to install this package. %package server-common Summary: Common files used by IPA server -Group: System Environment/Base BuildArch: noarch Requires: %{name}-client-common = %{version}-%{release} Requires: httpd >= 2.4.6-31 Requires: systemd-units >= 38 Requires: custodia >= 0.3.1 +%if 0%{?rhel} > 7 Requires: redhat-logos-ipa >= 80.4 +%endif Provides: %{alt_name}-server-common = %{version} Conflicts: %{alt_name}-server-common @@ -556,7 +451,6 @@ If you are installing an IPA server, you need to install this package. %package server-dns Summary: IPA integrated DNS server with support for automatic DNSSEC signing -Group: System Environment/Base BuildArch: noarch Requires: %{name}-server = %{version}-%{release} Requires: bind-dyndb-ldap >= 11.0-2 @@ -581,7 +475,6 @@ Integrated DNS server is BIND 9. OpenDNSSEC provides key management. %package server-trust-ad Summary: Virtual package to install packages required for Active Directory trusts -Group: System Environment/Base Requires: %{name}-server = %{version}-%{release} Requires: %{name}-common = %{version}-%{release} @@ -589,17 +482,10 @@ Requires: samba >= %{samba_version} Requires: samba-winbind Requires: libsss_idmap -%if %{with_default_python} == 3 -%{?__python3:Requires(post): %{__python3}} +Requires(post): python3 Requires: python3-samba Requires: python3-libsss_nss_idmap Requires: python3-sss -%else -Requires(post): python2 -Requires: python2-samba -Requires: python2-libsss_nss_idmap -Requires: python2-sss -%endif # with_default_python # We use alternatives to divert winbind_krb5_locator.so plugin to libkrb5 # on the installes where server-trust-ad subpackage is installed because @@ -623,35 +509,23 @@ dependencies at once. %package client Summary: IPA authentication for use on clients -Group: System Environment/Base Requires: %{name}-client-common = %{version}-%{release} Requires: %{name}-common = %{version}-%{release} -%if %{with_default_python} == 3 Requires: python3-gssapi >= 1.2.0-5 Requires: python3-ipaclient = %{version}-%{release} Requires: python3-ldap >= %{python_ldap_version} Requires: python3-sssdconfig >= %{sssd_version} -%else -Requires: python2-gssapi >= 1.2.0-5 -Requires: python2-ipaclient = %{version}-%{release} -Requires: python2-ldap >= %{python_ldap_version} -Requires: python2-sssdconfig >= %{sssd_version} -%endif Requires: cyrus-sasl-gssapi%{?_isa} Requires: chrony Requires: krb5-workstation >= %{krb5_version} Requires: authselect >= 0.4-2 Requires: curl # NIS domain name config: /usr/lib/systemd/system/*-domainname.service -%if 0%{?fedora} >= 29 || 0%{?rhel} -Requires: hostname >= %{hostname_version} -%else -Requires: initscripts -%endif +Requires: hostname Requires: libcurl >= 7.21.7-2 Requires: xmlrpc-c >= 1.27.4 Requires: sssd-ipa >= %{sssd_version} -Requires: certmonger >= 0.79.5-1 +Requires: certmonger >= %{certmonger_version} Requires: nss-tools >= %{nss_version} Requires: bind-utils Requires: oddjob-mkhomedir @@ -683,35 +557,25 @@ If your network uses IPA for authentication, this package should be installed on every client machine. This package provides command-line tools for IPA administrators. -%if 0%{?with_python2} - -%package -n python2-ipaclient -Summary: Python libraries used by IPA client -Group: System Environment/Libraries -BuildArch: noarch -%{?python_provide:%python_provide python2-ipaclient} -%{!?python_provide:Provides: python-ipaclient = %{version}-%{release}} -Requires: %{name}-client-common = %{version}-%{release} -Requires: %{name}-common = %{version}-%{release} -Requires: python2-ipalib = %{version}-%{release} -Requires: python2-augeas -Requires: python2-dns >= 1.15 -Requires: python2-jinja2 - -%description -n python2-ipaclient -IPA is an integrated solution to provide centrally managed Identity (users, -hosts, services), Authentication (SSO, 2FA), and Authorization -(host access control, SELinux user roles, services). The solution provides -features for further integration with Linux based clients (SUDO, automount) -and integration with Active Directory based infrastructures (Trusts). -If your network uses IPA for authentication, this package should be -installed on every client machine. +%package client-samba +Summary: Tools to configure Samba on IPA client +Group: System Environment/Base +Requires: %{name}-client = %{version}-%{release} +Requires: python3-samba +Requires: samba-client +Requires: samba-winbind +Requires: samba-common-tools +Requires: samba +Requires: sssd-winbind-idmap +Requires: tdb-tools +Requires: cifs-utils -%endif # with_python2 +%description client-samba +This package provides command-line tools to deploy Samba domain member +on the machine enrolled into a FreeIPA environment %package -n python3-ipaclient Summary: Python libraries used by IPA client -Group: System Environment/Libraries BuildArch: noarch %{?python_provide:%python_provide python3-ipaclient} Requires: %{name}-client-common = %{version}-%{release} @@ -719,6 +583,7 @@ Requires: %{name}-common = %{version}-%{release} Requires: python3-ipalib = %{version}-%{release} Requires: python3-augeas Requires: python3-dns >= 1.15 +Requires: python3-jinja2 # RHEL spec file only: DELETED: Remove csrgen %description -n python3-ipaclient @@ -733,7 +598,6 @@ installed on every client machine. %package client-common Summary: Common files used by IPA client -Group: System Environment/Base BuildArch: noarch Provides: %{alt_name}-client-common = %{version} @@ -752,16 +616,11 @@ installed on every client machine. %package python-compat Summary: Compatiblity package for Python libraries used by IPA -Group: System Environment/Libraries BuildArch: noarch Obsoletes: %{name}-python < 4.2.91 Provides: %{name}-python = %{version}-%{release} Requires: %{name}-common = %{version}-%{release} -%if %{with_default_python} == 3 Requires: python3-ipalib = %{version}-%{release} -%else -Requires: python2-ipalib = %{version}-%{release} -%endif Provides: %{alt_name}-python-compat = %{version} Conflicts: %{alt_name}-python-compat @@ -777,66 +636,13 @@ hosts, services), Authentication (SSO, 2FA), and Authorization features for further integration with Linux based clients (SUDO, automount) and integration with Active Directory based infrastructures (Trusts). This is a compatibility package to accommodate %{name}-python split into -python2-ipalib and %{name}-common. Packages still depending on +python3-ipalib and %{name}-common. Packages still depending on %{name}-python should be fixed to depend on python2-ipaclient or %{name}-common instead. -%if 0%{?with_python2} - -%package -n python2-ipalib -Summary: Python libraries used by IPA -Group: System Environment/Libraries -BuildArch: noarch -Conflicts: %{name}-python < 4.2.91 -%{?python_provide:%python_provide python2-ipalib} -%{!?python_provide:Provides: python-ipalib = %{version}-%{release}} -Provides: python2-ipapython = %{version}-%{release} -%{?python_provide:%python_provide python2-ipapython} -%{!?python_provide:Provides: python-ipapython = %{version}-%{release}} -Provides: python2-ipaplatform = %{version}-%{release} -%{?python_provide:%python_provide python2-ipaplatform} -%{!?python_provide:Provides: python-ipaplatform = %{version}-%{release}} -Requires: %{name}-common = %{version}-%{release} -Requires: gnupg2 -Requires: keyutils -Requires: python2 >= 2.7.9 -Requires: python2-cffi -Requires: python2-cryptography >= 1.6 -Requires: python2-dateutil -Requires: python2-dbus -Requires: python2-dns >= 1.15 -Requires: python2-enum34 -Requires: python2-gssapi >= 1.2.0-5 -Requires: python2-jwcrypto >= 0.4.2 -Requires: python2-ldap >= %{python_ldap_version} -Requires: python2-libipa_hbac -Requires: python2-netaddr >= %{python_netaddr_version} -Requires: python2-netifaces >= 0.10.4 -Requires: python2-pyasn1 >= 0.3.2-2 -Requires: python2-pyasn1-modules >= 0.3.2-2 -Requires: python2-pyusb -Requires: python2-qrcode-core >= 5.0.0 -Requires: python2-requests -Requires: python2-setuptools -Requires: python2-six -Requires: python2-sss-murmur -Requires: python2-yubico >= 1.2.3 - -Conflicts: %{alt_name}-python < %{version} - -%description -n python2-ipalib -IPA is an integrated solution to provide centrally managed Identity (users, -hosts, services), Authentication (SSO, 2FA), and Authorization -(host access control, SELinux user roles, services). The solution provides -features for further integration with Linux based clients (SUDO, automount) -and integration with Active Directory based infrastructures (Trusts). -If you are using IPA, you need to install this package. - -%endif # with_python2 %package -n python3-ipalib Summary: Python3 libraries used by IPA -Group: System Environment/Libraries BuildArch: noarch %{?python_provide:%python_provide python3-ipalib} Provides: python3-ipapython = %{version}-%{release} @@ -883,7 +689,6 @@ If you are using IPA with Python 3, you need to install this package. %package common Summary: Common files used by IPA -Group: System Environment/Libraries BuildArch: noarch Conflicts: %{name}-python < 4.2.91 @@ -904,55 +709,12 @@ If you are using IPA, you need to install this package. %if 0%{?with_ipatests} -%if 0%{?with_python2} && 0%{?fedora} < 29 -# Fedora 29 workaround: don't build python2-ipaserver, depends on ipaserver -%package -n python2-ipatests -Summary: IPA tests and test tools -BuildArch: noarch -Obsoletes: %{name}-tests < 4.2.91 -Provides: %{name}-tests = %{version}-%{release} -%{?python_provide:%python_provide python2-ipatests} -%{!?python_provide:Provides: python-ipatests = %{version}-%{release}} -Requires: python2-ipaclient = %{version}-%{release} -Requires: python2-ipaserver = %{version}-%{release} -Requires: iptables -Requires: ldns-utils -Requires: python2-coverage -Requires: python2-cryptography >= 1.6 -Requires: python2-mock -Requires: python2-paste -Requires: python2-polib -Requires: python2-pytest >= 2.6 -Requires: python2-pytest-multihost >= 0.5 -Requires: python2-pytest-sourceorder -Requires: python2-sssdconfig >= %{sssd_version} -Requires: tar -Requires: xz - -Provides: %{alt_name}-tests = %{version} -Conflicts: %{alt_name}-tests -Obsoletes: %{alt_name}-tests < %{version} - -%description -n python2-ipatests -IPA is an integrated solution to provide centrally managed Identity (users, -hosts, services), Authentication (SSO, 2FA), and Authorization -(host access control, SELinux user roles, services). The solution provides -features for further integration with Linux based clients (SUDO, automount) -and integration with Active Directory based infrastructures (Trusts). -This package contains tests that verify IPA functionality. - -%endif # with_python2 and Fedora < 29 - %package -n python3-ipatests Summary: IPA tests and test tools BuildArch: noarch %{?python_provide:%python_provide python3-ipatests} Requires: python3-ipaclient = %{version}-%{release} Requires: python3-ipaserver = %{version}-%{release} -%if 0%{?fedora} >= 29 -Conflicts: python2-ipatests -Obsoletes: python2-ipatests < %{version} -%endif Requires: iptables Requires: ldns-utils Requires: python3-coverage @@ -993,7 +755,9 @@ UpdateTimestamps() { done } -%setup -n freeipa-%{VERSION} -q +%setup -n freeipa-%{version} -q + +# RHEL spec file only: START # To allow proper application patches to the stripped po files, strip originals pushd po @@ -1016,25 +780,6 @@ done # product-name.png # RHEL spec file only: END -# Workaround: We want to build Python things twice. To be sure we do not mess -# up something, do two separate builds in separate directories. -# freeipa-$VER-python3 for Python 3 build -# freeipa-$VER-python2 for Python 2 build -# freeipa-$VER is a symlink to default Python version - -%if %{with_default_python} == 3 -%if 0%{?with_python2} -cp -r %{_builddir}/freeipa-%{version} %{_builddir}/freeipa-%{version}-python2 -%endif -mv %{_builddir}/freeipa-%{version} %{_builddir}/freeipa-%{version}-python3 -ln -sr %{_builddir}/freeipa-%{version}-python3 %{_builddir}/freeipa-%{version} -%else -# Python 2 default -cp -r %{_builddir}/freeipa-%{version} %{_builddir}/freeipa-%{version}-python3 -mv %{_builddir}/freeipa-%{version} %{_builddir}/freeipa-%{version}-python2 -ln -sr %{_builddir}/freeipa-%{version}-python2 %{_builddir}/freeipa-%{version} -%endif - %build # RHEL spec file only: START autoreconf -i -f @@ -1043,35 +788,11 @@ autoreconf -i -f # PATH is workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1005235 export PATH=/usr/bin:/usr/sbin:$PATH - -%if 0%{?with_python2} -export PYTHON=%{__python2} -pushd %{_builddir}/freeipa-%{version}-python2 -# Workaround: make sure all shebangs are pointing to Python 2 -# This should be solved properly using setuptools -# and this hack should be removed. -find \ - ! -name '*.pyc' -a \ - ! -name '*.pyo' -a \ - -type f -exec grep -qsm1 '^#!.*\bpython' {} \; \ - -exec sed -i -e '1 s|^#!.*\bpython[^ ]*|#!%{__python2}|' {} \; - -%configure --with-vendor-suffix=-%{release} \ - %{enable_server_option} \ - %{with_ipatests_option} \ - %{linter_options} \ - --with-ipaplatform=rhel -popd -%endif # ! with_python2 - export PYTHON=%{__python3} -pushd %{_builddir}/freeipa-%{version}-python3 %configure --with-vendor-suffix=-%{release} \ %{enable_server_option} \ %{with_ipatests_option} \ - %{linter_options} \ - --with-ipaplatform=rhel -popd + %{linter_options} # run build in default dir # -Onone is workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1398405 @@ -1092,67 +813,17 @@ make %{?_smp_mflags} check VERBOSE=yes LIBDIR=%{_libdir} # (These are typically configuration files created by IPA installer.) # All other artifacts should be created by make install. # -# Exception to this rule are test programs which where want to install -# Python2/3 versions at the same time so we need to rename them. Yuck. - -# Python 3 packages and commands -pushd %{_builddir}/freeipa-%{version}-python3 -%{__make} python_install DESTDIR=%{?buildroot} INSTALL="%{__install} -p" -popd -%if 0%{?with_ipatests} -mv %{buildroot}%{_bindir}/ipa-run-tests %{buildroot}%{_bindir}/ipa-run-tests-%{python3_version} -mv %{buildroot}%{_bindir}/ipa-test-config %{buildroot}%{_bindir}/ipa-test-config-%{python3_version} -mv %{buildroot}%{_bindir}/ipa-test-task %{buildroot}%{_bindir}/ipa-test-task-%{python3_version} -ln -rs %{buildroot}%{_bindir}/ipa-run-tests-%{python3_version} %{buildroot}%{_bindir}/ipa-run-tests-3 -ln -rs %{buildroot}%{_bindir}/ipa-test-config-%{python3_version} %{buildroot}%{_bindir}/ipa-test-config-3 -ln -rs %{buildroot}%{_bindir}/ipa-test-task-%{python3_version} %{buildroot}%{_bindir}/ipa-test-task-3 -%endif # with_ipatests -# Python 2 packages and commands -%if 0%{?with_python2} -pushd %{_builddir}/freeipa-%{version}-python2 %{__make} python_install DESTDIR=%{?buildroot} INSTALL="%{__install} -p" -popd -%if 0%{?with_python2} && 0%{?fedora} < 29 -# Fedora 29 workaround: don't ship ipatests binaries -mv %{buildroot}%{_bindir}/ipa-run-tests %{buildroot}%{_bindir}/ipa-run-tests-%{python2_version} -mv %{buildroot}%{_bindir}/ipa-test-config %{buildroot}%{_bindir}/ipa-test-config-%{python2_version} -mv %{buildroot}%{_bindir}/ipa-test-task %{buildroot}%{_bindir}/ipa-test-task-%{python2_version} -ln -rs %{buildroot}%{_bindir}/ipa-run-tests-%{python2_version} %{buildroot}%{_bindir}/ipa-run-tests-2 -ln -rs %{buildroot}%{_bindir}/ipa-test-config-%{python2_version} %{buildroot}%{_bindir}/ipa-test-config-2 -ln -rs %{buildroot}%{_bindir}/ipa-test-task-%{python2_version} %{buildroot}%{_bindir}/ipa-test-task-2 -%endif # with_ipatests and Fedora < 29 -%endif # with_python2 # default installation # This installs all Python packages twice and overrides the ipa-test # commands. We'll fix the command links later with ln --force. %make_install -# Decide which Python (2 or 3) should be used as default for tests -%if 0%{?with_ipatests} -%if %{with_default_python} == 3 -ln -frs %{buildroot}%{_bindir}/ipa-run-tests-%{python3_version} %{buildroot}%{_bindir}/ipa-run-tests -ln -frs %{buildroot}%{_bindir}/ipa-test-config-%{python3_version} %{buildroot}%{_bindir}/ipa-test-config -ln -frs %{buildroot}%{_bindir}/ipa-test-task-%{python3_version} %{buildroot}%{_bindir}/ipa-test-task -%else -ln -frs %{buildroot}%{_bindir}/ipa-run-tests-%{python2_version} %{buildroot}%{_bindir}/ipa-run-tests -ln -frs %{buildroot}%{_bindir}/ipa-test-config-%{python2_version} %{buildroot}%{_bindir}/ipa-test-config -ln -frs %{buildroot}%{_bindir}/ipa-test-task-%{python2_version} %{buildroot}%{_bindir}/ipa-test-task -%endif # with_default_python -%endif # with_ipatests - # remove files which are useful only for make uninstall find %{buildroot} -wholename '*/site-packages/*/install_files.txt' -exec rm {} \; -%if 0%{?with_ipatests} && 0%{?with_python2} && 0%{?fedora} >= 29 -# Fedora 29 workaround: Remove Python 2 ipaserver and ipatests -rm -rf %{buildroot}%{python2_sitelib}/ipaserver -rm -rf %{buildroot}%{python2_sitelib}/ipaserver-*.egg-info -rm -rf %{buildroot}%{python2_sitelib}/ipatests -rm -rf %{buildroot}%{python2_sitelib}/ipatests-*.egg-info -%endif # with python2 ipatests and Fedora >= 29 - # RHEL spec file only: START # Moved branding logos and background to redhat-logos-ipa-80.4: # header-logo.png, login-screen-background.jpg, login-screen-logo.png, @@ -1211,10 +882,6 @@ mkdir -p %{buildroot}%{_sysconfdir}/cron.d %endif # ONLY_CLIENT -%clean -rm -rf %{buildroot} - - %if ! %{ONLY_CLIENT} %post server @@ -1419,14 +1086,21 @@ fi %{_sbindir}/ipa-cacert-manage %{_sbindir}/ipa-winsync-migrate %{_sbindir}/ipa-pkinit-manage +%{_sbindir}/ipa-crlgen-manage +%{_sbindir}/ipa-cert-fix %{_libexecdir}/certmonger/dogtag-ipa-ca-renew-agent-submit %{_libexecdir}/certmonger/ipa-server-guard +%{_libexecdir}/ipa/custodia/ipa-custodia-dmldap +%{_libexecdir}/ipa/custodia/ipa-custodia-pki-tomcat +%{_libexecdir}/ipa/custodia/ipa-custodia-pki-tomcat-wrapped +%{_libexecdir}/ipa/custodia/ipa-custodia-ra-agent %dir %{_libexecdir}/ipa %{_libexecdir}/ipa/ipa-custodia %{_libexecdir}/ipa/ipa-custodia-check %{_libexecdir}/ipa/ipa-httpd-kdcproxy %{_libexecdir}/ipa/ipa-httpd-pwdreader %{_libexecdir}/ipa/ipa-pki-retrieve-key +%{_libexecdir}/ipa/ipa-pki-wait-running %{_libexecdir}/ipa/ipa-otpd %dir %{_libexecdir}/ipa/oddjob %attr(0755,root,root) %{_libexecdir}/ipa/oddjob/org.freeipa.server.conncheck @@ -1477,16 +1151,9 @@ fi %{_mandir}/man1/ipa-cacert-manage.1* %{_mandir}/man1/ipa-winsync-migrate.1* %{_mandir}/man1/ipa-pkinit-manage.1* +%{_mandir}/man1/ipa-crlgen-manage.1* +%{_mandir}/man1/ipa-cert-fix.1* -%if 0%{?with_python2} && 0%{?fedora} < 29 -# Fedora 29 workaround: don't build python2-ipaserver, python2-pki is n/a -%files -n python2-ipaserver -%doc README.md Contributors.txt -%license COPYING -%{python2_sitelib}/ipaserver -%{python2_sitelib}/ipaserver-*.egg-info - -%endif # with_python2 and Fedora < 29 %files -n python3-ipaserver %doc README.md Contributors.txt @@ -1508,6 +1175,7 @@ fi # END %{_usr}/share/ipa/wsgi.py* %{_usr}/share/ipa/kdcproxy.wsgi +%{_usr}/share/ipa/ipaca*.ini %{_usr}/share/ipa/*.ldif %{_usr}/share/ipa/*.uldif %{_usr}/share/ipa/*.template @@ -1620,6 +1288,7 @@ fi %{_sbindir}/ipa-join %{_bindir}/ipa %config %{_sysconfdir}/bash_completion.d +%config %{_sysconfdir}/sysconfig/certmonger %{_mandir}/man1/ipa.1* %{_mandir}/man1/ipa-getkeytab.1* %{_mandir}/man1/ipa-rmkeytab.1* @@ -1628,25 +1297,11 @@ fi %{_mandir}/man1/ipa-certupdate.1* %{_mandir}/man1/ipa-join.1* -%if 0%{?with_python2} - -%files -n python2-ipaclient +%files client-samba %doc README.md Contributors.txt %license COPYING -%dir %{python_sitelib}/ipaclient -%{python_sitelib}/ipaclient/*.py* -%dir %{python_sitelib}/ipaclient/install -%{python_sitelib}/ipaclient/install/*.py* -%dir %{python_sitelib}/ipaclient/plugins -%{python_sitelib}/ipaclient/plugins/*.py* -%dir %{python_sitelib}/ipaclient/remote_plugins -%{python_sitelib}/ipaclient/remote_plugins/*.py* -%dir %{python_sitelib}/ipaclient/remote_plugins/2_* -%{python_sitelib}/ipaclient/remote_plugins/2_*/*.py* -# RHEL spec file only: DELETED: Remove csrgen -%{python_sitelib}/ipaclient-*.egg-info - -%endif # with_python2 +%{_sbindir}/ipa-client-samba +%{_mandir}/man1/ipa-client-samba.1* %files -n python3-ipaclient %doc README.md Contributors.txt @@ -1699,27 +1354,6 @@ fi %doc README.md Contributors.txt %license COPYING -%if 0%{?with_python2} - -%files -n python2-ipalib -%doc README.md Contributors.txt -%license COPYING -%dir %{python_sitelib}/ipapython -%{python_sitelib}/ipapython/*.py* -%dir %{python_sitelib}/ipapython/install -%{python_sitelib}/ipapython/install/*.py* -%dir %{python_sitelib}/ipalib -%{python_sitelib}/ipalib/*.py* -%dir %{python_sitelib}/ipalib/install -%{python_sitelib}/ipalib/install/*.py* -%dir %{python_sitelib}/ipaplatform -%{python_sitelib}/ipaplatform/* -%{python_sitelib}/ipapython-*.egg-info -%{python_sitelib}/ipalib-*.egg-info -%{python_sitelib}/ipaplatform-*.egg-info -%{python_sitelib}/ipaplatform-*-nspkg.pth - -%endif # with_python2 %files common -f %{gettext_domain}.lang %doc README.md Contributors.txt @@ -1742,56 +1376,101 @@ fi %if 0%{?with_ipatests} -%if 0%{?with_python2} && 0%{?fedora} < 29 -# Fedora 29 workaround: don't build python2-ipatests, depends on ipaserver -%files -n python2-ipatests -%doc README.md Contributors.txt -%license COPYING -%{python_sitelib}/ipatests -%{python_sitelib}/ipatests-*.egg-info -%{_bindir}/ipa-run-tests-2 -%{_bindir}/ipa-test-config-2 -%{_bindir}/ipa-test-task-2 -%{_bindir}/ipa-run-tests-%{python2_version} -%{_bindir}/ipa-test-config-%{python2_version} -%{_bindir}/ipa-test-task-%{python2_version} -%if %{with_default_python} != 3 -%{_bindir}/ipa-run-tests -%{_bindir}/ipa-test-config -%{_bindir}/ipa-test-task -%{_mandir}/man1/ipa-run-tests.1* -%{_mandir}/man1/ipa-test-config.1* -%{_mandir}/man1/ipa-test-task.1* -%endif - -%endif # with_python2 and Fedora < 29 %files -n python3-ipatests %doc README.md Contributors.txt %license COPYING %{python3_sitelib}/ipatests %{python3_sitelib}/ipatests-*.egg-info -%{_bindir}/ipa-run-tests-3 -%{_bindir}/ipa-test-config-3 -%{_bindir}/ipa-test-task-3 -%{_bindir}/ipa-run-tests-%{python3_version} -%{_bindir}/ipa-test-config-%{python3_version} -%{_bindir}/ipa-test-task-%{python3_version} -%if %{with_default_python} == 3 %{_bindir}/ipa-run-tests %{_bindir}/ipa-test-config %{_bindir}/ipa-test-task %{_mandir}/man1/ipa-run-tests.1* %{_mandir}/man1/ipa-test-config.1* %{_mandir}/man1/ipa-test-task.1* -%endif %endif # with_ipatests %changelog -* Tue May 07 2019 CentOS Sources - 4.7.1-11.el8.centos -- Apply debranding changes +* Mon Sep 23 2019 Thomas Woerner - 4.8.0-11 +- Adtrust: avoid using timestamp in klist output + Resolves: RHBZ#1750242 +- Add default access control configuration to trusted domain objects + Resolves: RHBZ#1751707 + +* Thu Aug 29 2019 Thomas Woerner - 4.8.0-10 +- Fix automount behavior with authselect + Resolves: RHBZ#1740167 + +* Mon Aug 19 2019 Thomas Woerner - 4.8.0-9 +- extdom: unify error code handling especially LDAP_NO_SUCH_OBJECT + Resolves: RHBZ#1741530 + +* Thu Aug 15 2019 Thomas Woerner - 4.8.0-8 +- FreeIPA 4.8.0 tarball lacks two update files that are in git + Resolves: RHBZ#1741170 + +* Tue Aug 13 2019 Thomas Woerner - 4.8.0-7 +- Allow insecure binds for migration + Resolves: RHBZ#1731963 + +* Fri Aug 2 2019 Thomas Woerner - 4.8.0-6 +- Fix --external-ca-profile not passed to CSR + Resolves: RHBZ#1731813 + +* Tue Jul 30 2019 Thomas Woerner - 4.8.0-5 +- Remove posixAccount from service_find search filter + Resolves: RHBZ#1731437 +- Fix repeated uninstallation of ipa-client-samba crashes + Resolves: RHBZ#1732529 +- WebUI: Add PKINIT status field to 'Configuration' page + Resolves: RHBZ#1518153 + +* Tue Jul 16 2019 Alexander Bokovoy - 4.8.0-4 +- Fix krb5-kdb-server -> krb5-kdb-version + Related: RHBZ#1700121 + +* Mon Jul 15 2019 Alexander Bokovoy - 4.8.0-3 +- Make sure ipa-server depends on krb5-kdb-version to pick up + right MIT Kerberos KDB ABI + Related: RHBZ#1700121 +- User field separator uses '$$' within ipaSELInuxUserMapOrder + Fixes: RHBZ#1729099 + +* Wed Jul 3 2019 Thomas Woerner - 4.8.0-2 +- Fixed kdcproxy_version to 0.4-3 +- Fixed krb5_version to 1.17-7 + Related: RHBZ#1684528 + +* Wed Jul 3 2019 Thomas Woerner - 4.8.0-1 +- New upstream release 4.8.0 + - New subpackage: freeipa-client-samba + - Added command ipa-cert-fix with man page + - New sysconfdir sysconfig/certmonger +- Updated pki_version, certmonger_version, sssd_version and kdcproxy_version + Related: RHBZ#1684528 + +* Tue May 21 2019 Alexander Bokovoy - 4.7.90-3 +- Fix upgrade issue with AD trust when no trust yet established + Fixes: RHBZ#1708874 + Related: RHBZ#1684528 + +* Thu May 9 2019 Alexander Bokovoy - 4.7.90-2 +- Require certmonger 0.79.7-1 + Related: RHBZ#1708095 + +* Mon May 6 2019 Thomas Woerner - 4.7.90-1 +- Update to 4.7.90-pre1 + Related: RHBZ#1684528 +- Removed patches 0002 to 0031 as these are upsteram and part of 4.7.90-pre1 +- Added new patches 0001-revert-minssf-defaults.patch and + 0001-Correct-default-fontawesome-path-broken-by-da2cf1c5.patch + +* Tue Apr 16 2019 Alexander Bokovoy - 4.7.1-12 +- Remove strict dependencies to krb5-server version in order to allow + update of krb5 to 1.17 and change dependency to KDB DAL version. + Resolves: RHBZ#1700121 * Wed Feb 27 2019 Rob Crittenden - 4.7.1-11 - Handle NFS configuration file changes. nfs-utils moved the