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/6366

Reviewed-By: Martin Babinsky <mbabinsk@redhat.com>
This commit is contained in:
Milan Kubík 2016-09-12 14:54:40 +02:00 committed by Martin Babinsky
parent 7eb78aa8db
commit 10b4b155b6
2 changed files with 303 additions and 2 deletions

View File

@ -597,6 +597,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
@ -630,6 +631,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,

View File

@ -9,13 +9,22 @@ import tempfile
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 @@ SMIME_MOD_CONSTR_PROFILE_TEMPLATE = os.path.join(BASE_DIR, 'data/smime-mod.cfg.t
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,294 @@ class TestCertSignMIMEwithSubCA(XMLRPC_test):
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
* two service entries
* srv/santest-host-1
* srv/santest-host-2
* 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 managed 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 TestSignServiceCertWithoutSANServiceInACL(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.
"""
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
)
@pytest.mark.tier1
class TestManagedByACIOnCertRequest(CAACLEnforcementOnCertBase):
"""Test issuence of a certificate by external host
The test verifies that the managed by attribute of a service
is enforced on certificate signing.
The two test cases test the issuance of a service certificate
to a service by a second host.
In one of them the service is not managed by the principal
requesting the certificate, thus the issuance should fail.
The second one makes the service managed, thus the certificate
should be issued.
"""
def test_update_the_caacl(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_issuing_service_cert_by_unrelated_host(self,
santest_subca,
santest_host_1,
santest_host_2,
santest_service_host_1,
santest_csr):
with host_keytab(santest_host_2.name) as keytab_filename:
with change_principal(santest_host_2.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
)
def test_issuing_service_cert_by_related_host(self,
santest_subca,
santest_host_1,
santest_host_2,
santest_service_host_1,
santest_csr):
# The test case alters the previous state by making
# the service managed by the second host.
# Then it attempts to request the certificate again
api.Command['service_add_host'](
santest_service_host_1.name, host=[santest_host_2.fqdn]
)
with host_keytab(santest_host_2.name) as keytab_filename:
with change_principal(santest_host_2.attrs['krbcanonicalname'][0],
keytab=keytab_filename):
api.Command.cert_request(
santest_csr,
principal=santest_service_host_1.name,
cacn=santest_subca.name
)