Hi all,

an update for CA ACL tests!

I, with help from M. Babinsky, managed to find a way how to change the identity during acceptance cest run, which allows
to test CA ACLs (and perhaps other areas with some form of access controll).

This allowed me to write a test for CA ACLs and certificate profiles that checks if the ACL/profile is being used and enforced. The first several tests are based on Fraser's blogpost using SMIME profile [1].

The master and ipa-4-2 branches diverged a bit, so I had to change two commits when rebasing to ipa-4-2 branch.

Commits should be applied in the order (including rebased patches I sent in an earlier email):

master:
    * 12 - 17

ipa-4-2:
    * 18, 13 - 15, 19, 17

For convenience:
patches on top of master: https://github.com/apophys/freeipa/tree/acl-profile-functional
patches on top of ipa-4-2: https://github.com/apophys/freeipa/tree/acl-42


[1]: https://blog-ftweedal.rhcloud.com/2015/08/user-certificates-and-custom-profiles-with-freeipa-4-2/

Cheers,
Milan
From c25482bc366945591be40a3dbb14ec5022d59e21 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Fri, 17 Jul 2015 14:42:23 +0200
Subject: [PATCH 1/6] ipatests: add fuzzy instances for CA ACL DN and RDN

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/test_xmlrpc/xmlrpc_test.py | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/ipatests/test_xmlrpc/xmlrpc_test.py b/ipatests/test_xmlrpc/xmlrpc_test.py
index 56ddad9b8a0a1164c29f38970e0a97513d1a8d1f..c8be6160bdca0a95622ce5f8e4752e609f73dec5 100644
--- a/ipatests/test_xmlrpc/xmlrpc_test.py
+++ b/ipatests/test_xmlrpc/xmlrpc_test.py
@@ -77,6 +77,14 @@ fuzzy_sudocmddn = Fuzzy(
     '(?i)ipauniqueid=%s,cn=sudocmds,cn=sudo,%s' % (uuid_re, api.env.basedn)
 )
 
+# Matches caacl dn
+fuzzy_caacldn = Fuzzy(
+    '(?i)ipauniqueid=%s,cn=caacls,cn=ca,%s' % (uuid_re, api.env.basedn)
+)
+
+# Matches fuzzy ipaUniqueID DN group (RDN)
+fuzzy_ipauniqueid = Fuzzy('(?i)ipauniqueid=%s' % uuid_re)
+
 # Matches a hash signature, not enforcing length
 fuzzy_hash = Fuzzy('^([a-f0-9][a-f0-9]:)+[a-f0-9][a-f0-9]$', type=six.string_types)
 
-- 
2.5.3

From 80df2016c0e6f180ca0f8caebd434a0e15d4b03f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Tue, 30 Jun 2015 17:00:18 +0200
Subject: [PATCH 2/6] ipatests: Add initial CAACLTracker implementation

The patch implements the tracker for CA ACL feature.
The basic CRUD checkers has been implemented. The methods
for adding and removing the association of the resources
with the ACL do not have the check methods. These will be provided
as a separate test suite.

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/test_xmlrpc/objectclasses.py     |   5 +
 ipatests/test_xmlrpc/test_caacl_plugin.py | 318 ++++++++++++++++++++++++++++++
 2 files changed, 323 insertions(+)
 create mode 100644 ipatests/test_xmlrpc/test_caacl_plugin.py

diff --git a/ipatests/test_xmlrpc/objectclasses.py b/ipatests/test_xmlrpc/objectclasses.py
index 1cd77c7f885fe408d0d9d48fc6d8284900c91b7f..134a08803f3abca1124c4d26274d9e3fc981b941 100644
--- a/ipatests/test_xmlrpc/objectclasses.py
+++ b/ipatests/test_xmlrpc/objectclasses.py
@@ -217,3 +217,8 @@ certprofile = [
     u'top',
     u'ipacertprofile',
 ]
+
+caacl = [
+    u'ipaassociation',
+    u'ipacaacl'
+]
diff --git a/ipatests/test_xmlrpc/test_caacl_plugin.py b/ipatests/test_xmlrpc/test_caacl_plugin.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba3408813d5d47f7f6261f187129fbee645c5ef7
--- /dev/null
+++ b/ipatests/test_xmlrpc/test_caacl_plugin.py
@@ -0,0 +1,318 @@
+#
+# Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+#
+
+"""
+Test the `ipalib.plugins.caacl` module.
+"""
+
+import os
+
+import pytest
+
+from ipapython import ipautil
+from ipalib import errors, x509
+from ipapython.dn import DN
+from ipatests.test_xmlrpc.ldaptracker import Tracker
+from ipatests.test_xmlrpc.xmlrpc_test import (XMLRPC_test, fuzzy_caacldn,
+                                              fuzzy_uuid, fuzzy_ipauniqueid,
+                                              raises_exact)
+from ipatests.test_xmlrpc import objectclasses
+from ipatests.util import assert_deepequal
+
+
+class CAACLTracker(Tracker):
+    """Tracker class for CA ACL LDAP object."""
+    # TODO: more documentation for this class
+    # TODO: find a way how to restore the ACL
+    #       after delete. Simple `ensure_exists`
+    #       won't be enough here as the services,
+    #       hosts, etc. are added after the ACL
+    #       itself is created.
+
+    member_keys = {
+        u'memberuser_user', u'memberuser_group',
+        u'memberhost_host', u'memberhost_hostgroup',
+        u'memberservice_service',
+        u'ipamembercertprofile_certprofile'}
+    retrieve_keys = {
+        u'dn', u'cn', u'description', u'ipaenabledflag',
+        u'ipacacategory', u'ipamemberca',
+        u'ipacertprofilecategory', u'ipamembercertprofile',
+        u'usercategory', u'memberuser',
+        u'hostcategory', u'memberhost',
+        u'servicecategory', u'memberservice'} | member_keys
+    retrieve_all_keys = retrieve_keys | {u'objectclass', u'ipauniqueid'}
+    create_keys = {u'dn', u'cn', u'description', u'ipacertprofilecategory',
+                   u'usercategory', u'hostcategory', u'ipacacategory',
+                   u'ipaenabledflag', u'objectclass', u'ipauniqueid'}
+    update_keys = create_keys - {u'dn'}
+
+    def __init__(self, name, profile_category=None, user_category=None,
+                 service_category=None, host_category=None, description=None,
+                 default_version=None):
+        super(CAACLTracker, self).__init__(default_version=default_version)
+
+        self._name = name
+        self.description = description
+        self._categories = dict(
+            profilecategory=[profile_category],
+            usercategory=[user_category],
+            servicecategory=[service_category],
+            hostcategory=[host_category])
+
+        self.dn = fuzzy_caacldn
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def rdn(self):
+        return fuzzy_ipauniqueid
+
+    @property
+    def categories(self):
+        return self._categories
+
+    @property
+    def create_categories(self):
+        """Return the categories set on create."""
+        return dict(filter(lambda x: x[1][0], self.categories.items()))
+
+    def make_create_command(self, force=True):
+        return self.make_command(u'caacl_add', self.name,
+                                 description=self.description,
+                                 **self.create_categories)
+
+    def check_create(self, result):
+        assert_deepequal(dict(
+            value=self.name,
+            summary=u'Added CA ACL "{}"'.format(self.name),
+            result=dict(self.filter_attrs(self.create_keys))
+        ), result)
+
+    def track_create(self):
+        self.attrs = dict(
+            dn=self.dn,
+            ipauniqueid=[fuzzy_uuid],
+            cn=[self.name],
+            objectclass=objectclasses.caacl,
+            ipaenabledflag=[u'TRUE'])
+
+        self.attrs.update(self.create_categories)
+        if self.description:
+            self.attrs.update({u'description', [self.description]})
+
+        self.exists = True
+
+    def make_delete_command(self):
+        return self.make_command('caacl_del', self.name)
+
+    def check_delete(self, result):
+        assert_deepequal(dict(
+            value=[self.name],
+            summary=u'Deleted CA ACL "{}"'.format(self.name),
+            result=dict(failed=[])
+        ), result)
+
+    def make_retrieve_command(self, all=False, raw=False):
+        return self.make_command('caacl_show', self.name, all=all, raw=raw)
+
+    def check_retrieve(self, result, all=False, raw=False):
+        if all:
+            expected = self.filter_attrs(self.retrieve_all_keys)
+        else:
+            expected = self.filter_attrs(self.retrieve_keys)
+
+        assert_deepequal(dict(
+            value=self.name,
+            summary=None,
+            result=expected
+        ), result)
+
+    def make_find_command(self, *args, **kwargs):
+        return self.make_command('caacl_find', *args, **kwargs)
+
+    def check_find(self, result, all=False, raw=False):
+        if all:
+            expected = self.filter_attrs(self.retrieve_all_keys)
+        else:
+            expected = self.filter_attrs(self.retrieve_keys)
+
+        assert_deepequal(dict(
+            count=1,
+            truncated=False,
+            summary=u'1 CA ACL matched',
+            result=[expected]
+        ), result)
+
+    def make_update_command(self, updates):
+        return self.make_command('caacl_mod', self.name, **updates)
+
+    def check_update(self, result, extra_keys=()):
+        assert_deepequal(dict(
+            value=self.name,
+            summary=u'Modified CA ACL "{}"'.format(self.name),
+            result=self.filter_attrs(self.update_keys | set(extra_keys))
+        ), result)
+
+    # Helper methods for caacl subcommands. The check methods will be
+    # implemented in standalone test
+    #
+    # The methods implemented here will be:
+    # caacl_{add,remove}_{host, service, certprofile, user [, subca]}
+
+    # TODO: can API command take single value as unicode or
+    #       do we need a list with one element?
+    def _add_acl_component(self, command_name, keys):
+        """ Add a resource into ACL rule and track it.
+
+            command_name - the name in the API
+            keys = {
+                'tracker_attr': {
+                    'api_key': 'value'
+                }
+            }
+
+            e.g.
+
+            keys = {
+                'memberhost_host': {
+                    'host': 'hostname'
+                },
+                'memberhost_hostgroup': {
+                    'hostgroup': 'hostgroup_name'
+                }
+            }
+        """
+
+        if not self.exists:
+            raise errors.NotFound(reason="The tracked entry doesn't exist.")
+
+        command = self.make_command(command_name, self.name)
+        command_options = dict()
+
+        # track
+        for tracker_attr in keys:
+            api_options = keys[tracker_attr]
+            for option in api_options:
+                try:
+                    self.attrs[tracker_attr].append(api_options[option])
+                except KeyError:
+                    self.attrs[tracker_attr] = [api_options[option]]
+            # prepare options for the command call
+            command_options.update(api_options)
+
+        return command(**command_options)
+
+    def _remove_acl_component(self, command_name, keys):
+        """ Remove a resource from ACL rule and track it.
+
+            command_name - the name in the API
+            keys = {
+                'tracker_attr': {
+                    'api_key': 'value'
+                }
+            }
+
+            e.g.
+
+            keys = {
+                'memberhost_host': {
+                    'host': 'hostname'
+                },
+                'memberhost_hostgroup': {
+                    'hostgroup': 'hostgroup_name'
+                }
+            }
+        """
+        command = self.make_command(command_name, self.name)
+        command_options = dict()
+
+        for tracker_attr in keys:
+            api_options = keys[tracker_attr]
+            for option in api_options:
+                self.attrs[tracker_attr].remove(api_options[option])
+                if len(self.attrs[tracker_attr]) == 0:
+                    del self.attrs[tracker_attr]
+            command_options.update(api_options)
+
+        return command(**command_options)
+
+    def add_host(self, host=None, hostgroup=None):
+        """Associates an host or hostgroup entry with the ACL.
+
+           The command takes an unicode string with the name
+           of the entry (RDN).
+
+           It is the responsibility of a test writer to provide
+           the correct value, object type as the method does not
+           verify whether the entry exists.
+
+           The method can add only one entry of each type
+           in one call.
+        """
+
+        options = {
+            u'memberhost_host': {u'host': host},
+            u'memberhost_hostgroup': {u'hostgroup': hostgroup}}
+
+        return self._add_acl_component(u'caacl_add_host', options)
+
+    def remove_host(self, host=None, hostgroup=None):
+        options = {
+            u'memberhost_host': {u'host': host},
+            u'memberhost_hostgroup': {u'hostgroup': hostgroup}}
+
+        return self._remove_acl_component(u'caacl_remove_host', options)
+
+    def add_user(self, user=None, group=None):
+        options = {
+            u'memberuser_user': {u'user': user},
+            u'memberuser_group': {u'group': group}}
+
+        return self._add_acl_component(u'caacl_add_user', options)
+
+    def remove_user(self, user=None, group=None):
+        options = {
+            u'memberuser_user': {u'user': user},
+            u'memberuser_group': {u'group': group}}
+
+        return self._remove_acl_component(u'caacl_remove_user', options)
+
+    def add_service(self, service=None):
+        options = {
+            u'memberservice_service': {u'service': service}}
+
+        return self._add_acl_component(u'caacl_add_service', options)
+
+    def remove_service(self, service=None):
+        options = {
+            u'memberservice_service': {u'service': service}}
+
+        self._remove_acl_component(u'caacl_remove_service', options)
+
+    def add_profile(self, certprofile=None):
+        options = {
+            u'ipamembercertprofile_certprofile':
+                {u'certprofile': certprofile}}
+
+        self._add_acl_component(u'caacl_add_profile', options)
+
+    def remove_profile(self, certprofile=None):
+        options = {
+            u'ipamembercertprofile_certprofile':
+                {u'certprofile': certprofile}}
+
+        self._remove_acl_component(u'caacl_remove_profile', options)
+
+    def enable(self):
+        command = self.make_command(u'caacl_enable', self.name)
+        self.attrs.update({u'ipaenabledflag': u'TRUE'})
+        command()
+
+    def disable(self):
+        command = self.make_command(u'caacl_disable', self.name)
+        self.attrs.update({u'ipaenabledflag': u'FALSE'})
+        command()
-- 
2.5.3

From 2f956a9d43c05f847d1292e874eab32af34da568 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Fri, 7 Aug 2015 15:54:18 +0200
Subject: [PATCH 3/6] tests: add test to check the default ACL

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/test_xmlrpc/test_caacl_plugin.py | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/ipatests/test_xmlrpc/test_caacl_plugin.py b/ipatests/test_xmlrpc/test_caacl_plugin.py
index ba3408813d5d47f7f6261f187129fbee645c5ef7..0d5658f1b5398a93ccf96092ec1ea15a2bd68813 100644
--- a/ipatests/test_xmlrpc/test_caacl_plugin.py
+++ b/ipatests/test_xmlrpc/test_caacl_plugin.py
@@ -316,3 +316,18 @@ class CAACLTracker(Tracker):
         command = self.make_command(u'caacl_disable', self.name)
         self.attrs.update({u'ipaenabledflag': u'FALSE'})
         command()
+
+
+@pytest.fixture(scope='class')
+def default_acl(request):
+    name = u'hosts_services_caIPAserviceCert'
+    tracker = CAACLTracker(name, service_category=u'all', host_category=u'all')
+    tracker.track_create()
+    tracker.attrs.update(
+        {u'ipamembercertprofile_certprofile': [u'caIPAserviceCert']})
+    return tracker
+
+
+class TestDefaultACL(XMLRPC_test):
+    def test_default_acl_present(self, default_acl):
+        default_acl.retrieve()
-- 
2.5.3

From b52fdd5e2ab002cb89e92b46dacee408c961d81d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Sun, 23 Aug 2015 01:16:04 +0200
Subject: [PATCH 4/6] ipatests: CA ACL - added config templates

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/test_xmlrpc/data/alice-key.pem   |  27 ++++++++
 ipatests/test_xmlrpc/data/alice.conf.tmpl |  13 ++++
 ipatests/test_xmlrpc/data/smime.cfg.tmpl  | 108 ++++++++++++++++++++++++++++++
 3 files changed, 148 insertions(+)
 create mode 100644 ipatests/test_xmlrpc/data/alice-key.pem
 create mode 100644 ipatests/test_xmlrpc/data/alice.conf.tmpl
 create mode 100644 ipatests/test_xmlrpc/data/smime.cfg.tmpl

diff --git a/ipatests/test_xmlrpc/data/alice-key.pem b/ipatests/test_xmlrpc/data/alice-key.pem
new file mode 100644
index 0000000000000000000000000000000000000000..af5ea0b1829ba5401aa6a53a25b5768a37b6e8af
--- /dev/null
+++ b/ipatests/test_xmlrpc/data/alice-key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAsYQl2pLroHyuZWe6vSd6GsxPRxhGlAT0K87S1vMRr5cfU0Eu
+SZx0jsS4jXfAKqW/BhsVHsGA75iVFhbUY7CRGOZ9b9z1kUyajkd0TiJWxLvSDUma
+muwi/gsRDvCr+ro0zk/v38sddXXrhMKhJBj67P3PesEqobQK59GjFk+tAYn+DROP
+bo9w6bkJuhNqEaWmmYmTQz8EdBLIwGb/9FAD8iDuEFd70Mvdnn8DZ9zi5fJnpTUG
+fT6B/mTgTY/37i7scaH8/3+/dHFk5bTjQHIsj0On9z8/ehs8llgSMawMlIuOB1q6
+i7LuVkoD/zz3d7k8ytiYqOd8wfBIEEIdfULQaQIDAQABAoIBACrnpb6OhCTl/cDE
+sX3GbNzNRNwKIgTkrZ9o/cy2MzAddpTIzEc+aW2YXoLSzr+AEAuJwDEO0/sVBfOw
+0OTHaEp8axT+ctwLh8+btaCs7Avg2YQcpiGLsWl1g0n5IZgYKWs0JuYQUa5yMdqE
+sC3pW7ysG9mvln4+5ePh52kdGNOl/4MNaEbQ+OR0V8mmb3wPPPeUIHFKKzLJj7po
+n2nxdhebX8W/BGUkcV7zx89Bpufnypc8e0nn9cznbGsq/U1LUTCVJ2SzCJ2qDwUG
++0ZM1dprfp3/J3yFxKEBlZRMS//VQ5/DIFCcxFPzFizRonkgrzSF1kapNSsQvuzE
+Wax+rYECgYEA3SZthkGW2lvHjI1mMLQY94+7IB9ixdUB4lUvYmwuoUf7k/fJZ4Nq
+t4BycD9Ttj/Q19akZdXfuJgnVtDV1OgExTuJqV4gZ1jz5ewvP1ILfjcjeMLFfoez
+u0JkkeCqrBVWeCc0Ax5lWtm8JsVGV4Nd5gVLI/nmLaMh9InbClQRGC8CgYEAzX10
+NEqPFvvjEaKe2q7EIkHm4sL6TB30ajvC1Zp50jonj1i0L6Un5WqB3RCX0HAbkuz2
+umi9bxMFRfRVJsTcmhb2UrIDEolYkmIm/ji+JS0tvk7jKwl2fAv2waTBwpBA7K3g
+YUHoY6L2r7eCh/2dZvp7LiVyHrIatMsiL9msYucCgYB5MHfQnNzYKHeAFHStt+P+
+tisrfUeZdhMkPt5Kp1IeW94Hxj/+k8vFZ4RO8sUjGHGP9jX9AGkrNWZJcwPbOpJy
+qx/TSpujRuHRW87AemuF7R1pLgMgRak+szF9p4qf5smN6p3cH6oXUT6EWJMlnf20
+8a2tt2JmHAGdinYYgN0lTQKBgQCZJXWUfzjTTVj2zLcNjhCY43q658t5LR36ip1z
+apR+DF9tYxOvKqxoO4+bfQFYFCVIxBhB50u/W3KjpyxLH461vIVKLmdBymDbgBFF
+iG6V8GzWF58QdRX770KxISRS6AWrHw9KDL+wekTVwrOivG4x0F47jybVH7HtqjLJ
+bLYgYwKBgQCmLVTDie+GB2nUVe+IUOBsLfyQXfbQT1ksgX5l7M+W1tOKq1h5R/SS
+YDhasCpNIuK/yMTIXxKP1B7+Kd0I4Ib7w7Ri4kHdai+Wlf+sCjTytXt8YXUQo5mx
+fEadGM6UR/95ug9S4VbeqZdrPcjnOa4RcdzDUsxrqYRMUIlwWNCy8Q==
+-----END RSA PRIVATE KEY-----
diff --git a/ipatests/test_xmlrpc/data/alice.conf.tmpl b/ipatests/test_xmlrpc/data/alice.conf.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..f6795220730a263f6a62c486a7821073ad5e022c
--- /dev/null
+++ b/ipatests/test_xmlrpc/data/alice.conf.tmpl
@@ -0,0 +1,13 @@
+[ req ]
+prompt = no
+encrypt_key = no
+
+distinguished_name = dn
+req_extensions = exts
+
+[ dn ]
+commonName = "alice"
+{ipacertbase}
+
+[ exts ]
+subjectAltName=email:alice@{ipadomain}
diff --git a/ipatests/test_xmlrpc/data/smime.cfg.tmpl b/ipatests/test_xmlrpc/data/smime.cfg.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..3baf03f0bf1400026b45390e43ec079e42544235
--- /dev/null
+++ b/ipatests/test_xmlrpc/data/smime.cfg.tmpl
@@ -0,0 +1,108 @@
+auth.instance_id=raCertAuth
+classId=caEnrollImpl
+desc=Certificate for S-MIME extension
+enable=true
+enableBy=ipara
+input.i1.class_id=certReqInputImpl
+input.i2.class_id=submitterInfoInputImpl
+input.list=i1,i2
+name=SMIME certificate profile
+output.list=o1
+output.o1.class_id=certOutputImpl
+policyset.list=serverCertSet
+policyset.serverCertSet.1.constraint.class_id=subjectNameConstraintImpl
+policyset.serverCertSet.1.constraint.name=Subject Name Constraint
+policyset.serverCertSet.1.constraint.params.accept=true
+policyset.serverCertSet.1.constraint.params.pattern=CN=[^,]+,.+
+policyset.serverCertSet.1.default.class_id=subjectNameDefaultImpl
+policyset.serverCertSet.1.default.name=Subject Name Default
+policyset.serverCertSet.1.default.params.name=CN=$request.req_subject_name.cn$, O={iparealm}
+policyset.serverCertSet.10.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.10.constraint.name=No Constraint
+policyset.serverCertSet.10.default.class_id=subjectKeyIdentifierExtDefaultImpl
+policyset.serverCertSet.10.default.name=Subject Key Identifier Extension Default
+policyset.serverCertSet.10.default.params.critical=false
+policyset.serverCertSet.11.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.11.constraint.name=No Constraint
+policyset.serverCertSet.11.default.class_id=userExtensionDefaultImpl
+policyset.serverCertSet.11.default.name=User Supplied Extension Default
+policyset.serverCertSet.11.default.params.userExtOID=2.5.29.17
+policyset.serverCertSet.2.constraint.class_id=validityConstraintImpl
+policyset.serverCertSet.2.constraint.name=Validity Constraint
+policyset.serverCertSet.2.constraint.params.notAfterCheck=false
+policyset.serverCertSet.2.constraint.params.notBeforeCheck=false
+policyset.serverCertSet.2.constraint.params.range=740
+policyset.serverCertSet.2.default.class_id=validityDefaultImpl
+policyset.serverCertSet.2.default.name=Validity Default
+policyset.serverCertSet.2.default.params.range=731
+policyset.serverCertSet.2.default.params.startTime=0
+policyset.serverCertSet.3.constraint.class_id=keyConstraintImpl
+policyset.serverCertSet.3.constraint.name=Key Constraint
+policyset.serverCertSet.3.constraint.params.keyParameters=1024,2048,3072,4096
+policyset.serverCertSet.3.constraint.params.keyType=RSA
+policyset.serverCertSet.3.default.class_id=userKeyDefaultImpl
+policyset.serverCertSet.3.default.name=Key Default
+policyset.serverCertSet.4.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.4.constraint.name=No Constraint
+policyset.serverCertSet.4.default.class_id=authorityKeyIdentifierExtDefaultImpl
+policyset.serverCertSet.4.default.name=Authority Key Identifier Default
+policyset.serverCertSet.5.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.5.constraint.name=No Constraint
+policyset.serverCertSet.5.default.class_id=authInfoAccessExtDefaultImpl
+policyset.serverCertSet.5.default.name=AIA Extension Default
+policyset.serverCertSet.5.default.params.authInfoAccessADEnable_0=true
+policyset.serverCertSet.5.default.params.authInfoAccessADLocationType_0=URIName
+policyset.serverCertSet.5.default.params.authInfoAccessADLocation_0=http://ipa-ca.{ipadomain}/ca/ocsp
+policyset.serverCertSet.5.default.params.authInfoAccessADMethod_0=1.3.6.1.5.5.7.48.1
+policyset.serverCertSet.5.default.params.authInfoAccessCritical=false
+policyset.serverCertSet.5.default.params.authInfoAccessNumADs=1
+policyset.serverCertSet.6.constraint.class_id=keyUsageExtConstraintImpl
+policyset.serverCertSet.6.constraint.name=Key Usage Extension Constraint
+policyset.serverCertSet.6.constraint.params.keyUsageCritical=true
+policyset.serverCertSet.6.constraint.params.keyUsageCrlSign=false
+policyset.serverCertSet.6.constraint.params.keyUsageDataEncipherment=true
+policyset.serverCertSet.6.constraint.params.keyUsageDecipherOnly=false
+policyset.serverCertSet.6.constraint.params.keyUsageDigitalSignature=true
+policyset.serverCertSet.6.constraint.params.keyUsageEncipherOnly=false
+policyset.serverCertSet.6.constraint.params.keyUsageKeyAgreement=false
+policyset.serverCertSet.6.constraint.params.keyUsageKeyCertSign=false
+policyset.serverCertSet.6.constraint.params.keyUsageKeyEncipherment=true
+policyset.serverCertSet.6.constraint.params.keyUsageNonRepudiation=true
+policyset.serverCertSet.6.default.class_id=keyUsageExtDefaultImpl
+policyset.serverCertSet.6.default.name=Key Usage Default
+policyset.serverCertSet.6.default.params.keyUsageCritical=true
+policyset.serverCertSet.6.default.params.keyUsageCrlSign=false
+policyset.serverCertSet.6.default.params.keyUsageDataEncipherment=true
+policyset.serverCertSet.6.default.params.keyUsageDecipherOnly=false
+policyset.serverCertSet.6.default.params.keyUsageDigitalSignature=true
+policyset.serverCertSet.6.default.params.keyUsageEncipherOnly=false
+policyset.serverCertSet.6.default.params.keyUsageKeyAgreement=false
+policyset.serverCertSet.6.default.params.keyUsageKeyCertSign=false
+policyset.serverCertSet.6.default.params.keyUsageKeyEncipherment=true
+policyset.serverCertSet.6.default.params.keyUsageNonRepudiation=true
+policyset.serverCertSet.7.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.7.constraint.name=No Constraint
+policyset.serverCertSet.7.default.class_id=extendedKeyUsageExtDefaultImpl
+policyset.serverCertSet.7.default.name=Extended Key Usage Extension Default
+policyset.serverCertSet.7.default.params.exKeyUsageCritical=false
+policyset.serverCertSet.7.default.params.exKeyUsageOIDs=1.3.6.1.5.5.7.3.4
+policyset.serverCertSet.8.constraint.class_id=signingAlgConstraintImpl
+policyset.serverCertSet.8.constraint.name=No Constraint
+policyset.serverCertSet.8.constraint.params.signingAlgsAllowed=SHA1withRSA,SHA256withRSA,SHA512withRSA,MD5withRSA,MD2withRSA,SHA1withDSA,SHA1withEC,SHA256withEC,SHA384withEC,SHA512withEC
+policyset.serverCertSet.8.default.class_id=signingAlgDefaultImpl
+policyset.serverCertSet.8.default.name=Signing Alg
+policyset.serverCertSet.8.default.params.signingAlg=-
+policyset.serverCertSet.9.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.9.constraint.name=No Constraint
+policyset.serverCertSet.9.default.class_id=crlDistributionPointsExtDefaultImpl
+policyset.serverCertSet.9.default.name=CRL Distribution Points Extension Default
+policyset.serverCertSet.9.default.params.crlDistPointsCritical=false
+policyset.serverCertSet.9.default.params.crlDistPointsEnable_0=true
+policyset.serverCertSet.9.default.params.crlDistPointsIssuerName_0=CN=Certificate Authority,o=ipaca
+policyset.serverCertSet.9.default.params.crlDistPointsIssuerType_0=DirectoryName
+policyset.serverCertSet.9.default.params.crlDistPointsNum=1
+policyset.serverCertSet.9.default.params.crlDistPointsPointName_0=http://ipa-ca.{ipadomain}/ipa/crl/MasterCRL.bin
+policyset.serverCertSet.9.default.params.crlDistPointsPointType_0=URIName
+policyset.serverCertSet.9.default.params.crlDistPointsReasons_0=
+policyset.serverCertSet.list=1,2,3,4,5,6,7,8,9,10,11
+visible=false
-- 
2.5.3

From a6705f094b644f622d0b2c7ca1d046bebb946fde Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Tue, 22 Sep 2015 15:21:33 +0200
Subject: [PATCH 5/6] ipatests: added unlock_principal_password and
 change_principal

The unlock_principal_password unlocks the (new) user by running
ldappasswd as the user.

change_principal is an context manager that changes identity
for the supplied api object by disconnecting and reconnecting
the rpcclient in and outside of requested kerberos context.
This context manager allows to run tests that cannot be
executed as an admin user which can for example override
an CA ACL.

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/util.py | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/ipatests/util.py b/ipatests/util.py
index d180c91b77b0bafd6bff2f01b9dfd7740519c1bd..16c2c1fa7d59541733eec2dfa4a08ce981488f56 100644
--- a/ipatests/util.py
+++ b/ipatests/util.py
@@ -27,6 +27,8 @@ from os import path
 import tempfile
 import shutil
 import re
+import uuid
+from contextlib import contextmanager
 
 import six
 import ldap
@@ -34,9 +36,12 @@ import ldap.sasl
 import ldap.modlist
 
 import ipalib
+from ipalib import api
 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 ipaplatform.paths import paths
 
 if six.PY3:
     unicode = str
@@ -663,3 +668,38 @@ def prepare_config(template, values):
         config.write(template.format(**values))
 
     return config.name
+
+
+def unlock_principal_password(user, oldpw, newpw):
+    userdn = "uid={},{},{}".format(
+        user, api.env.container_user, api.env.basedn)
+
+    args = [paths.LDAPPASSWD, '-D', userdn, '-w', oldpw, '-a', oldpw,
+            '-s', newpw, '-x']
+    return run(args)
+
+
+@contextmanager
+def change_principal(user, password, client=None, path=None):
+
+    if path:
+        ccache_name = path
+    else:
+        ccache_name = os.path.join('/tmp', str(uuid.uuid4()))
+
+    if client is None:
+        client = api
+
+
+    client.Backend.rpcclient.disconnect()
+
+    with private_ccache(ccache_name):
+        kinit_password(user, password, ccache_name)
+        client.Backend.rpcclient.connect()
+
+        try:
+            yield
+        finally:
+            client.Backend.rpcclient.disconnect()
+
+    client.Backend.rpcclient.connect()
-- 
2.5.3

From ee1521b64087e3ff9dd66432f84cdf5db3a0af8b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Tue, 22 Sep 2015 15:22:25 +0200
Subject: [PATCH 6/6] ipatests: CA ACL and cert profile functional test

https://fedorahosted.org/freeipa/ticket/57
---
 .../test_xmlrpc/test_caacl_profile_enforcement.py  | 206 +++++++++++++++++++++
 1 file changed, 206 insertions(+)
 create mode 100644 ipatests/test_xmlrpc/test_caacl_profile_enforcement.py

diff --git a/ipatests/test_xmlrpc/test_caacl_profile_enforcement.py b/ipatests/test_xmlrpc/test_caacl_profile_enforcement.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc5534bafc9caae7b05f757fe99a1adb1a5d0bbc
--- /dev/null
+++ b/ipatests/test_xmlrpc/test_caacl_profile_enforcement.py
@@ -0,0 +1,206 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+#
+
+import os
+import pytest
+import tempfile
+
+from ipalib import api, errors
+from ipatests.util import (
+    prepare_config, unlock_principal_password, change_principal)
+from ipatests.test_xmlrpc.xmlrpc_test import XMLRPC_test
+from ipatests.test_xmlrpc.test_certprofile_plugin import CertprofileTracker
+from ipatests.test_xmlrpc.test_caacl_plugin import CAACLTracker
+
+from ipapython.ipautil import run
+
+BASE_DIR = os.path.dirname(__file__)
+
+SMIME_PROFILE_TEMPLATE = os.path.join(BASE_DIR, 'data/smime.cfg.tmpl')
+ALICE_OPENSSL_CONFIG_TEMPLATE = os.path.join(BASE_DIR, 'data/alice.conf.tmpl')
+ALICE_RSA_PRIVATE_KEY_PATH = os.path.join(BASE_DIR, 'data/alice-key.pem')
+
+CERT_SUBJECT_BASE = (
+    api.Command.config_show()
+    ['result']['ipacertificatesubjectbase'][0]
+)
+
+
+# environment preparation
+
+def _prepare_csr(key, config_template, template_values):
+    csr_file = tempfile.NamedTemporaryFile(mode='w', delete=False)
+    csr_file.close()
+
+    run(['openssl', 'req', '-new', '-key', key, '-out', csr_file.name,
+         '-config', prepare_config(config_template, template_values)], )
+
+    return csr_file
+
+
+@pytest.fixture(scope='class')
+def correct_csr(request):
+    csr_path = _prepare_csr(
+        ALICE_RSA_PRIVATE_KEY_PATH,
+        ALICE_OPENSSL_CONFIG_TEMPLATE,
+        dict(
+            ipacertbase=CERT_SUBJECT_BASE,
+            ipadomain=api.env.domain))
+
+    def fin():
+        csr_path.unlink(csr_path.name)
+    request.addfinalizer(fin)
+
+    with open(csr_path.name) as f:
+        csr = f.read()
+
+    return unicode(csr)
+
+
+@pytest.fixture(scope='class')
+def smime_profile(request):
+    profile_path = prepare_config(
+            SMIME_PROFILE_TEMPLATE,
+            dict(ipadomain=api.env.domain, iparealm=api.env.realm))
+
+    tracker = CertprofileTracker(u'smime', store=True,
+                                 desc=u"S/MIME certificate profile",
+                                 profile=profile_path)
+
+    # return tracker
+    return tracker.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def smime_acl(request):
+    tracker = CAACLTracker(u'smime_acl')
+
+    # return tracker
+    return tracker.make_fixture(request)
+
+
+# TODO: rewrite these into Tracker instances
+@pytest.fixture(scope='class')
+def smime_user(request):
+    api.Command.user_add(uid=u'alice', givenname=u'Alice', sn=u'SMIME',
+                         userpassword=u'Change123')
+
+    unlock_principal_password('alice', 'Change123', 'Secret123')
+
+    def fin():
+        api.Command.user_del(u'alice')
+    request.addfinalizer(fin)
+
+    return u'alice'
+
+
+@pytest.fixture(scope='class')
+def smime_group(request):
+    api.Command.group_add(u'smime_users')
+
+    def fin():
+        api.Command.group_del(u'smime_users')
+    request.addfinalizer(fin)
+
+    return u'smime_users'
+
+
+class TestCertSignMIME(XMLRPC_test):
+
+    def test_cert_import(self, smime_profile):
+        smime_profile.ensure_exists()
+
+    def test_create_acl(self, smime_acl):
+        smime_acl.ensure_exists()
+
+    def test_add_profile_to_acl(self, smime_acl, smime_profile):
+        smime_acl.add_profile(certprofile=smime_profile.name)
+
+    # rewrite to trackers, prepare elsewhere
+    def test_add_user_to_group(self, smime_group, smime_user):
+        api.Command.group_add_member(smime_group, user=smime_user)
+
+    def test_add_group_to_acl(self, smime_group, smime_acl):
+        smime_acl.add_user(group=smime_group)
+
+    def test_sign_smime_csr(self, smime_profile, correct_csr, smime_user):
+        with change_principal(smime_user, "Secret123"):
+            api.Command.cert_request(correct_csr, principal=smime_user,
+                                     profile_id=smime_profile.name)
+
+
+class TestSignWithDisabledACL(XMLRPC_test):
+
+    def test_import_profile_and_acl(self, smime_profile, smime_acl):
+        smime_profile.ensure_exists()
+        smime_acl.ensure_missing()
+        smime_acl.ensure_exists()
+
+    def test_add_profile_to_acl(self, smime_acl, smime_profile):
+        smime_acl.add_profile(certprofile=smime_profile.name)
+
+    # rewrite to trackers, prepare elsewhere
+    def test_add_user_to_group(self, smime_group, smime_user):
+        api.Command.group_add_member(smime_group, user=smime_user)
+
+    def test_add_group_to_acl(self, smime_group, smime_acl):
+        smime_acl.add_user(group=smime_group)
+
+    def test_disable_acl(self, smime_acl):
+        smime_acl.disable()
+
+    def test_signing_with_disabled_acl(self, correct_csr, smime_acl,
+                                       smime_profile, smime_user):
+
+        with change_principal(smime_user, "Secret123"):
+            with pytest.raises(errors.ACIError):
+                api.Command.cert_request(
+                    correct_csr, profile_id=smime_profile.name,
+                    principal=smime_user)
+
+    def test_admin_overrides_disabled_acl(self, correct_csr, smime_acl,
+                                          smime_profile, smime_user):
+        api.Command.cert_request(
+            correct_csr, profile_id=smime_profile.name,
+            principal=smime_user)
+
+
+class TestSignWithoutGroupMembership(XMLRPC_test):
+
+    def test_import_profile_and_acl(self, smime_profile, smime_acl):
+        smime_profile.ensure_exists()
+        smime_acl.ensure_missing()
+        smime_acl.ensure_exists()
+
+    def test_add_profile_to_acl(self, smime_acl, smime_profile):
+        smime_acl.add_profile(certprofile=smime_profile.name)
+
+    def test_add_group_to_acl(self, smime_group, smime_acl, smime_user):
+        # smime user should not be a member of this group
+        #
+        # adding smime_user fixture to ensure it exists
+        # TODO: change user to tracker and call ensure_exists
+        smime_acl.add_user(group=smime_group)
+
+    def test_signing_with_non_member_principal(self, correct_csr, smime_acl,
+                                               smime_profile, smime_user):
+
+        with change_principal(smime_user, "Secret123"):
+            with pytest.raises(errors.ACIError):
+                api.Command.cert_request(
+                    correct_csr,
+                    profile_id=smime_profile.name,
+                    principal=smime_user)
+
+    def test_admin_overrides_group_membership(self, correct_csr, smime_acl,
+                                              smime_profile, smime_user):
+        api.Command.cert_request(
+            correct_csr, profile_id=smime_profile.name,
+            principal=smime_user)
+
+
+class TestSignWithChangedProfile(XMLRPC_test):
+    """ Test to verify that the updated profile is used."""
+    pass  # import invalid profile, try to sign, expect fail
-- 
2.5.3

From 0b9b1605179f1ffe5ea5c6d5c9190b6b9f77dfdf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Fri, 17 Jul 2015 14:42:23 +0200
Subject: [PATCH 1/6] ipatests: add fuzzy instances for CA ACL DN and RDN

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/test_xmlrpc/xmlrpc_test.py | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/ipatests/test_xmlrpc/xmlrpc_test.py b/ipatests/test_xmlrpc/xmlrpc_test.py
index 808abae1944f87ac19d419c716658b07942556f0..7019d3b1f1fb92e27297526b63d090eb01124bd4 100644
--- a/ipatests/test_xmlrpc/xmlrpc_test.py
+++ b/ipatests/test_xmlrpc/xmlrpc_test.py
@@ -75,6 +75,14 @@ fuzzy_sudocmddn = Fuzzy(
     '(?i)ipauniqueid=%s,cn=sudocmds,cn=sudo,%s' % (uuid_re, api.env.basedn)
 )
 
+# Matches caacl dn
+fuzzy_caacldn = Fuzzy(
+    '(?i)ipauniqueid=%s,cn=caacls,cn=ca,%s' % (uuid_re, api.env.basedn)
+)
+
+# Matches fuzzy ipaUniqueID DN group (RDN)
+fuzzy_ipauniqueid = Fuzzy('(?i)ipauniqueid=%s' % uuid_re)
+
 # Matches a hash signature, not enforcing length
 fuzzy_hash = Fuzzy('^([a-f0-9][a-f0-9]:)+[a-f0-9][a-f0-9]$', type=basestring)
 
-- 
2.5.3

From 761e84af5975952f3737811e58d11df4e9279d10 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Tue, 22 Sep 2015 15:21:33 +0200
Subject: [PATCH 5/6] ipatests: added unlock_principal_password and
 change_principal

The unlock_principal_password unlocks the (new) user by running
ldappasswd as the user.

change_principal is an context manager that changes identity
for the supplied api object by disconnecting and reconnecting
the rpcclient in and outside of requested kerberos context.
This context manager allows to run tests that cannot be
executed as an admin user which can for example override
an CA ACL.

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/util.py | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/ipatests/util.py b/ipatests/util.py
index 8a7dccaa7baf15d931e929d179a3863d3b9e1e54..4d99ff6e0a505cd3f75053f97caca9edbc802bcf 100644
--- a/ipatests/util.py
+++ b/ipatests/util.py
@@ -30,10 +30,15 @@ import ldap.modlist
 import tempfile
 import shutil
 import re
+import uuid
+from contextlib import contextmanager
 import ipalib
+from ipalib import api
 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 ipaplatform.paths import paths
 
 
 class TempDir(object):
@@ -657,3 +662,38 @@ def prepare_config(template, values):
         config.write(template.format(**values))
 
     return config.name
+
+
+def unlock_principal_password(user, oldpw, newpw):
+    userdn = "uid={},{},{}".format(
+        user, api.env.container_user, api.env.basedn)
+
+    args = [paths.LDAPPASSWD, '-D', userdn, '-w', oldpw, '-a', oldpw,
+            '-s', newpw, '-x']
+    return run(args)
+
+
+@contextmanager
+def change_principal(user, password, client=None, path=None):
+
+    if path:
+        ccache_name = path
+    else:
+        ccache_name = os.path.join('/tmp', str(uuid.uuid4()))
+
+    if client is None:
+        client = api
+
+
+    client.Backend.rpcclient.disconnect()
+
+    with private_ccache(ccache_name):
+        kinit_password(user, password, ccache_name)
+        client.Backend.rpcclient.connect()
+
+        try:
+            yield
+        finally:
+            client.Backend.rpcclient.disconnect()
+
+    client.Backend.rpcclient.connect()
-- 
2.5.3

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