apophys's pull request #73: "Tests for certificates with SAN" was opened

PR body:
"""
Commits include several new test cases for CA ACLs and cert request for CSRs 
containing subject alternative name extension.

Also included minor fixes in used tracker and couple of new context managers 
used in the test cases.
"""

See the full pull-request at https://github.com/freeipa/freeipa/pull/73
... or pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/73/head:pr73
git checkout pr73
From c76d81a83e723634558bc1d8d3b0c8923414ff7a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Mon, 12 Sep 2016 14:52:05 +0200
Subject: [PATCH 1/3] ipatests: provide context manager for keytab usage in RPC
 tests

https://fedorahosted.org/freeipa/ticket/6291
---
 ipatests/util.py | 52 +++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 47 insertions(+), 5 deletions(-)

diff --git a/ipatests/util.py b/ipatests/util.py
index 8878993..4c1a77a 100644
--- a/ipatests/util.py
+++ b/ipatests/util.py
@@ -40,7 +40,9 @@
 from ipalib.plugable import Plugin
 from ipalib.request import context
 from ipapython.dn import DN
-from ipapython.ipautil import private_ccache, kinit_password, run
+from ipapython.ipautil import (
+    private_ccache, kinit_password, kinit_keytab, run
+)
 from ipaplatform.paths import paths
 
 if six.PY3:
@@ -693,8 +695,8 @@ def unlock_principal_password(user, oldpw, newpw):
 
 
 @contextmanager
-def change_principal(user, password, client=None, path=None,
-                     canonicalize=False, enterprise=False):
+def change_principal(principal, password=None, client=None, path=None,
+                     canonicalize=False, enterprise=False, keytab=None):
 
     if path:
         ccache_name = path
@@ -709,8 +711,12 @@ def change_principal(user, password, client=None, path=None,
 
     try:
         with private_ccache(ccache_name):
-            kinit_password(user, password, ccache_name,
-                           canonicalize=canonicalize, enterprise=enterprise)
+            if keytab:
+                kinit_keytab(principal, keytab, ccache_name)
+            else:
+                kinit_password(principal, password, ccache_name,
+                               canonicalize=canonicalize,
+                               enterprise=enterprise)
             client.Backend.rpcclient.connect()
 
             try:
@@ -720,6 +726,42 @@ def change_principal(user, password, client=None, path=None,
     finally:
         client.Backend.rpcclient.connect()
 
+
+@contextmanager
+def get_entity_keytab(principal, options=None):
+    """Requests a keytab for an entity
+
+    The keytab will generate new keys if not specified
+    otherwise in the options.
+    To retrieve existing keytab, use the -r option
+    """
+    keytab_filename = os.path.join('/tmp', str(uuid.uuid4()))
+
+    try:
+        cmd = [paths.IPA_GETKEYTAB, '-p', principal, '-k', keytab_filename]
+
+        if options:
+            cmd.extend(options)
+        run(cmd)
+
+        yield keytab_filename
+    finally:
+        os.remove(keytab_filename)
+
+
+@contextmanager
+def host_keytab(hostname, options=None):
+    """Retrieves keytab for a particular host
+
+    After leaving the context manager, the keytab file is
+    deleted.
+    """
+    principal = u'host/{}'.format(hostname)
+
+    with get_entity_keytab(principal, options) as keytab:
+        yield keytab
+
+
 def get_group_dn(cn):
     return DN(('cn', cn), api.env.container_group, api.env.basedn)
 

From 98c89a239b4b16a1be67aac72ac1b556900f46c0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Mon, 12 Sep 2016 14:53:48 +0200
Subject: [PATCH 2/3] ipatests: Fix name property on a service tracker

https://fedorahosted.org/freeipa/ticket/6291
---
 ipatests/test_xmlrpc/tracker/service_plugin.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ipatests/test_xmlrpc/tracker/service_plugin.py b/ipatests/test_xmlrpc/tracker/service_plugin.py
index fe34390..8a52446 100644
--- a/ipatests/test_xmlrpc/tracker/service_plugin.py
+++ b/ipatests/test_xmlrpc/tracker/service_plugin.py
@@ -52,7 +52,7 @@ class ServiceTracker(KerberosAliasMixin, Tracker):
 
     def __init__(self, name, host_fqdn, options=None):
         super(ServiceTracker, self).__init__(default_version=None)
-        self._name = "{0}/{1}@{2}".format(name, host_fqdn, api.env.realm)
+        self._name = u"{0}/{1}@{2}".format(name, host_fqdn, api.env.realm)
         self.dn = DN(
             ('krbprincipalname', self.name), api.env.container_service,
             api.env.basedn)

From 89be52d2cf3db8b978429607d0d730a32898f047 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Mon, 12 Sep 2016 14:54:40 +0200
Subject: [PATCH 3/3] ipatests: Implement tests with CSRs requesting SAN

The patch implements several test cases testing the enforcement
of CA ACLs on certificate requests with subject alternative names.

https://fedorahosted.org/freeipa/ticket/6291
---
 freeipa.spec.in                                    |   2 +
 .../test_xmlrpc/test_caacl_profile_enforcement.py  | 243 ++++++++++++++++++++-
 2 files changed, 243 insertions(+), 2 deletions(-)

diff --git a/freeipa.spec.in b/freeipa.spec.in
index 589060b..9ba3fac 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -598,6 +598,7 @@ Requires: python-pytest-multihost >= 0.5
 Requires: python-pytest-sourceorder
 Requires: ldns-utils
 Requires: python-sssdconfig
+Requires: python2-cryptography
 
 Provides: %{alt_name}-tests = %{version}
 Conflicts: %{alt_name}-tests
@@ -631,6 +632,7 @@ Requires: python3-pytest-multihost >= 0.5
 Requires: python3-pytest-sourceorder
 Requires: ldns-utils
 Requires: python3-sssdconfig
+Requires: python3-cryptography
 
 %description -n python3-ipatests
 IPA is an integrated solution to provide centrally managed Identity (users,
diff --git a/ipatests/test_xmlrpc/test_caacl_profile_enforcement.py b/ipatests/test_xmlrpc/test_caacl_profile_enforcement.py
index a73e845..9e342af 100644
--- a/ipatests/test_xmlrpc/test_caacl_profile_enforcement.py
+++ b/ipatests/test_xmlrpc/test_caacl_profile_enforcement.py
@@ -9,13 +9,22 @@
 
 import six
 
+from cryptography import x509
+from cryptography.x509.oid import NameOID
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import hashes, serialization
+from cryptography.hazmat.primitives.asymmetric import rsa
+
 from ipalib import api, errors
 from ipatests.util import (
-    prepare_config, unlock_principal_password, change_principal)
+    prepare_config, unlock_principal_password, change_principal,
+    host_keytab)
 from ipatests.test_xmlrpc.xmlrpc_test import XMLRPC_test
 from ipatests.test_xmlrpc.tracker.certprofile_plugin import CertprofileTracker
 from ipatests.test_xmlrpc.tracker.caacl_plugin import CAACLTracker
 from ipatests.test_xmlrpc.tracker.ca_plugin import CATracker
+from ipatests.test_xmlrpc.tracker.host_plugin import HostTracker
+from ipatests.test_xmlrpc.tracker.service_plugin import ServiceTracker
 
 from ipapython.ipautil import run
 
@@ -29,7 +38,6 @@
 CERT_OPENSSL_CONFIG_TEMPLATE = os.path.join(BASE_DIR, 'data/usercert.conf.tmpl')
 CERT_RSA_PRIVATE_KEY_PATH = os.path.join(BASE_DIR, 'data/usercert-priv-key.pem')
 
-
 SMIME_USER_INIT_PW = u'Change123'
 SMIME_USER_PW = u'Secret123'
 
@@ -354,3 +362,234 @@ def test_sign_smime_csr_fallback_to_default_cert_profile(
             with change_principal(smime_user, SMIME_USER_PW):
                 api.Command.cert_request(csr, principal=smime_user_principal,
                                          cacn=smime_signing_ca.name)
+
+
+@pytest.fixture(scope='class')
+def santest_subca(request):
+    name = u'default-profile-subca'
+    subject = u'CN={},O=test'.format(name)
+    tr = CATracker(name, subject)
+    return tr.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def santest_subca_acl(request):
+    tr = CAACLTracker(u'default_profile_subca')
+    return tr.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def santest_host_1(request):
+    tr = HostTracker(u'santest-host-1')
+    return tr.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def santest_host_2(request):
+    tr = HostTracker(u'santest-host-2')
+    return tr.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def santest_service_host_1(request, santest_host_1):
+    tr = ServiceTracker(u'srv', santest_host_1.name)
+    return tr.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def santest_service_host_2(request, santest_host_2):
+    tr = ServiceTracker(u'srv', santest_host_2.name)
+    return tr.make_fixture(request)
+
+
+@pytest.fixture
+def santest_csr(request, santest_host_1, santest_host_2):
+    backend = default_backend()
+    pkey = rsa.generate_private_key(
+        public_exponent=65537,
+        key_size=2048,
+        backend=backend
+    )
+
+    csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
+        x509.NameAttribute(NameOID.COMMON_NAME, santest_host_1.fqdn),
+        x509.NameAttribute(NameOID.ORGANIZATION_NAME, api.env.realm)
+    ])).add_extension(x509.SubjectAlternativeName([
+        x509.DNSName(santest_host_1.name),
+        x509.DNSName(santest_host_2.name)
+    ]), False
+    ).add_extension(
+        x509.BasicConstraints(ca=False, path_length=None),
+        True
+    ).add_extension(
+        x509.KeyUsage(
+            digital_signature=True, content_commitment=True,
+            key_encipherment=True, data_encipherment=False,
+            key_agreement=False, key_cert_sign=False,
+            crl_sign=False, encipher_only=False,
+            decipher_only=False
+        ),
+        False
+    ).sign(
+        pkey, hashes.SHA256(), backend
+    ).public_bytes(serialization.Encoding.PEM)
+
+    return unicode(csr)
+
+
+class CAACLEnforcementOnCertBase(XMLRPC_test):
+    """Base setup class for tests with SAN in CSR
+
+    The class prepares an environment for test cases based
+    on evaluation of ACLs and fields requested in a CSR.
+
+    The class creates following entries:
+
+        * two host entries
+            * santest-host-1
+            * santest-host-2
+        * one service entry
+            * srv/santest-host-1
+        * Sub CA
+            * default-profile-subca
+
+            This one is created in order not to need
+            to re-import caIPAServiceCert profile
+        * CA ACL
+            * default_profile_subca
+
+        After executing the methods the CA ACL should contain:
+
+        CA ACL:
+            * santest-host-1        -- host
+            * srv/santest-host-1    -- service
+            * default-profile-subca -- CA
+            * caIPAServiceCert      -- profile
+    """
+
+    def test_prepare_caacl_hosts(self, santest_subca_acl,
+                                 santest_host_1, santest_host_2):
+        santest_subca_acl.ensure_exists()
+        santest_host_1.ensure_exists()
+        santest_host_2.ensure_exists()
+        santest_subca_acl.add_host(santest_host_1.name)
+
+    def test_prepare_caacl_CA(self, santest_subca_acl, santest_subca):
+        santest_subca.ensure_exists()
+        santest_subca_acl.add_ca(santest_subca.name)
+
+    def test_prepare_caacl_profile(self, santest_subca_acl):
+        santest_subca_acl.add_profile(u'caIPAserviceCert')
+
+    def test_prepare_caacl_services(self, santest_subca_acl,
+                                    santest_service_host_1,
+                                    santest_service_host_2):
+        santest_service_host_1.ensure_exists()
+        santest_service_host_2.ensure_exists()
+
+        santest_subca_acl.add_service(santest_service_host_1.name)
+
+
+@pytest.mark.tier1
+class TestSignCertificateWithInvalidSAN(CAACLEnforcementOnCertBase):
+    """Sign certificate request witn an invalid SAN entry
+
+    Using the environment prepared by the base class, ask to sign
+    a certificate request for a service managet by one host only.
+    The CSR contains another domain name in SAN extension that should
+    be refused as the host does not have rights to manage the service.
+    """
+    def test_request_cert_with_not_allowed_SAN(
+            self, santest_subca, santest_host_1, santest_host_2,
+            santest_service_host_1, santest_csr):
+
+        with host_keytab(santest_host_1.name) as keytab_filename:
+            with change_principal(santest_host_1.attrs['krbcanonicalname'][0],
+                                  keytab=keytab_filename):
+                with pytest.raises(errors.ACIError):
+                    api.Command.cert_request(
+                        santest_csr,
+                        principal=santest_service_host_1.name,
+                        cacn=santest_subca.name
+                    )
+
+
+@pytest.mark.tier1
+class TestSignServiceCertManagedByMultipleHosts(CAACLEnforcementOnCertBase):
+    """ Sign certificate request with multiple subject alternative names
+
+    Using the environment of the base class, modify the service to be managed
+    by the second host. Then request a certificate for the service with SAN
+    of the second host in CSR. The certificate should be issued.
+    """
+    def test_make_service_managed_by_each_host(self,
+                                               santest_host_1,
+                                               santest_service_host_1,
+                                               santest_host_2,
+                                               santest_service_host_2):
+        api.Command['service_add_host'](
+            santest_service_host_1.name, host=[santest_host_2.fqdn]
+        )
+        api.Command['service_add_host'](
+            santest_service_host_2.name, host=[santest_host_1.fqdn]
+        )
+
+    def test_extend_the_ca_acl(self, santest_subca_acl, santest_host_2,
+                               santest_service_host_2):
+        santest_subca_acl.add_host(santest_host_2.name)
+        santest_subca_acl.add_service(santest_service_host_2.name)
+
+    def test_request_cert_with_additional_host(
+            self, santest_subca, santest_host_1, santest_host_2,
+            santest_service_host_1, santest_csr):
+
+        with host_keytab(santest_host_1.name) as keytab_filename:
+            with change_principal(santest_host_1.attrs['krbcanonicalname'][0],
+                                  keytab=keytab_filename):
+                api.Command.cert_request(
+                    santest_csr,
+                    principal=santest_service_host_1.name,
+                    cacn=santest_subca.name
+                )
+
+
+@pytest.mark.tier1
+class TestSignServiceCertWithoudSANServiceInACL(CAACLEnforcementOnCertBase):
+    """ Sign certificate request with multiple subject alternative names
+
+    This test case doesn't have the service hosted on a host in SAN
+    in the CA ACL. The assumption is that the issuance will fail.
+
+    Using the environment of the base class, modify the service to be managed
+    by the second host. Then request a certificate for the service with SAN
+    of the second host in CSR. The certificate should be issued.
+    """
+    def test_make_service_managed_by_each_host(self,
+                                               santest_host_1,
+                                               santest_service_host_1,
+                                               santest_host_2,
+                                               santest_service_host_2):
+        api.Command['service_add_host'](
+            santest_service_host_1.name, host=[santest_host_2.fqdn]
+        )
+        api.Command['service_add_host'](
+            santest_service_host_2.name, host=[santest_host_1.fqdn]
+        )
+
+    def test_extend_the_ca_acl(self, santest_subca_acl, santest_host_2,
+                               santest_service_host_2):
+        santest_subca_acl.add_host(santest_host_2.name)
+
+    def test_request_cert_with_additional_host(
+            self, santest_subca, santest_host_1, santest_host_2,
+            santest_service_host_1, santest_csr):
+
+        with host_keytab(santest_host_1.name) as keytab_filename:
+            with change_principal(santest_host_1.attrs['krbcanonicalname'][0],
+                                  keytab=keytab_filename):
+                with pytest.raises(errors.ACIError):
+                    api.Command.cert_request(
+                        santest_csr,
+                        principal=santest_service_host_1.name,
+                        cacn=santest_subca.name
+                    )
-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to