URL: https://github.com/freeipa/freeipa/pull/5170
Author: rcritten
 Title: #5170: Centralize enable/disable of the ACME service
Action: opened

PR body:
"""
Centralize enable/disable of the ACME service

The initial implementation of ACME in dogtag and IPA required
that ACME be manually enabled on each CA.

dogtag added a REST API that can be access directly or through
the `pki acme` CLI tool to enable or disable the service.

It also abstracted the database connection and introduced the
concept of a realm which defines the DIT for ACME users and
groups, the URL and the identity. This is configured in realm.conf.

A new group was created, Enterprise ACME Administrators, that
controls the users allowed to modify ACME configuration.

The IPA RA is added to this group for the ipa-acme-manage tool
to authenticate to the API to enable/disable ACME.

Two ACME configuration templates were removed so that the dogtag
defaults would be used, configsources.conf and engine.conf.

Related dogtag installation documentation:
https://github.com/dogtagpki/pki/blob/master/docs/installation/acme/Configuring_ACME_Database.md
https://github.com/dogtagpki/pki/blob/master/docs/installation/acme/Configuring_ACME_Realm.md
https://github.com/dogtagpki/pki/blob/master/docs/installation/acme/Installing_PKI_ACME_Responder.md

ACME REST API:
https://github.com/dogtagpki/pki/wiki/PKI-ACME-Enable-REST-API

https://pagure.io/freeipa/issue/8524

"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/5170/head:pr5170
git checkout pr5170
From 34e256807c5424e32db10faab563a183c9710b7e Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcrit...@redhat.com>
Date: Fri, 2 Oct 2020 15:06:58 -0400
Subject: [PATCH 1/4] Enable importing LDIF files not shipped by IPA

This is to be able to import ACME schema provided by dogtag.

https://pagure.io/freeipa/issue/8524

Signed-off-by: Rob Crittenden <rcrit...@redhat.com>
---
 .../profiles/{acmeServerCert.cfg => acmeIPAServerCert.cfg}   | 0
 ipaserver/install/service.py                                 | 5 ++++-
 2 files changed, 4 insertions(+), 1 deletion(-)
 rename install/share/profiles/{acmeServerCert.cfg => acmeIPAServerCert.cfg} (100%)

diff --git a/install/share/profiles/acmeServerCert.cfg b/install/share/profiles/acmeIPAServerCert.cfg
similarity index 100%
rename from install/share/profiles/acmeServerCert.cfg
rename to install/share/profiles/acmeIPAServerCert.cfg
diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py
index 896fb19ca9..df26a0d683 100644
--- a/ipaserver/install/service.py
+++ b/ipaserver/install/service.py
@@ -347,7 +347,10 @@ def _ldap_mod(self, ldif, sub_dict=None, raise_on_err=True,
                   ldap_uri=None, dm_password=None):
         pw_name = None
         fd = None
-        path = os.path.join(paths.USR_SHARE_IPA_DIR, ldif)
+        if not os.path.isabs(ldif):
+            path = os.path.join(paths.USR_SHARE_IPA_DIR, ldif)
+        else:
+            path = ldif
         nologlist = []
 
         if sub_dict is not None:

From 2160c1e0ba1bb4f1a47ab32034f4adf99c5d7f51 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcrit...@redhat.com>
Date: Fri, 2 Oct 2020 15:08:10 -0400
Subject: [PATCH 2/4] Let dogtag.py be imported if the api is not initialized

This allows non-plugin components to import the RestClient
classes.

https://pagure.io/freeipa/issue/8524

Signed-off-by: Rob Crittenden <rcrit...@redhat.com>
---
 ipaserver/plugins/dogtag.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py
index 594f32fab0..805ca0a449 100644
--- a/ipaserver/plugins/dogtag.py
+++ b/ipaserver/plugins/dogtag.py
@@ -256,7 +256,7 @@
 from ipapython import dogtag, ipautil, certdb
 from ipaserver.masters import find_providing_server
 
-if api.env.in_server:
+if api.isdone('finalize') and api.env.in_server:
     import pki
     from pki.client import PKIConnection
     import pki.crypto as cryptoutil
@@ -1101,7 +1101,7 @@ def parse_updateCRL_xml(doc):
 #-------------------------------------------------------------------------------
 
 from ipalib import Registry, errors, SkipPluginModule
-if api.env.ra_plugin != 'dogtag':
+if api.isdone('finalize') and api.env.ra_plugin != 'dogtag':
     # In this case, abort loading this plugin module...
     raise SkipPluginModule(reason='dogtag not selected as RA plugin')
 import os

From d2f019ca4b1eb75b599f078dede7cc0bd3a2ca82 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcrit...@redhat.com>
Date: Fri, 2 Oct 2020 15:10:29 -0400
Subject: [PATCH 3/4] Centralize enable/disable of the ACME service

The initial implementation of ACME in dogtag and IPA required
that ACME be manually enabled on each CA.

dogtag added a REST API that can be access directly or through
the `pki acme` CLI tool to enable or disable the service.

It also abstracted the database connection and introduced the
concept of a realm which defines the DIT for ACME users and
groups, the URL and the identity. This is configured in realm.conf.

A new group was created, Enterprise ACME Administrators, that
controls the users allowed to modify ACME configuration.

The IPA RA is added to this group for the ipa-acme-manage tool
to authenticate to the API to enable/disable ACME.

Two ACME configuration templates were removed so that the dogtag
defaults would be used, configsources.conf and engine.conf.

Related dogtag installation documentation:
https://github.com/dogtagpki/pki/blob/master/docs/installation/acme/Configuring_ACME_Database.md
https://github.com/dogtagpki/pki/blob/master/docs/installation/acme/Configuring_ACME_Realm.md
https://github.com/dogtagpki/pki/blob/master/docs/installation/acme/Installing_PKI_ACME_Responder.md

ACME REST API:
https://github.com/dogtagpki/pki/wiki/PKI-ACME-Enable-REST-API

https://pagure.io/freeipa/issue/8524

Signed-off-by: Rob Crittenden <rcrit...@redhat.com>
---
 install/share/Makefile.am                     |  3 +-
 .../pki-acme-configsources.conf.template      |  2 -
 install/share/pki-acme-engine.conf.template   |  2 -
 install/share/pki-acme-issuer.conf.template   |  2 +-
 install/share/profiles/Makefile.am            |  2 +-
 install/share/profiles/acmeIPAServerCert.cfg  |  2 +-
 ipaplatform/base/paths.py                     |  3 +-
 ipapython/dogtag.py                           |  4 +-
 ipaserver/install/cainstance.py               | 13 ++-
 ipaserver/install/ipa_acme_manage.py          | 87 +++++++++++++------
 10 files changed, 77 insertions(+), 43 deletions(-)
 delete mode 100644 install/share/pki-acme-configsources.conf.template
 delete mode 100644 install/share/pki-acme-engine.conf.template

diff --git a/install/share/Makefile.am b/install/share/Makefile.am
index 1c1cd25db2..f16c7031c8 100644
--- a/install/share/Makefile.am
+++ b/install/share/Makefile.am
@@ -103,10 +103,9 @@ dist_app_DATA =				\
 	ipaca_default.ini		\
 	ipaca_customize.ini		\
 	ipaca_softhsm2.ini		\
-	pki-acme-configsources.conf.template	\
 	pki-acme-database.conf.template	\
-	pki-acme-engine.conf.template	\
 	pki-acme-issuer.conf.template	\
+	pki-acme-realm.conf.template	\
 	ldbm-tuning.ldif		\
 	$(NULL)
 
diff --git a/install/share/pki-acme-configsources.conf.template b/install/share/pki-acme-configsources.conf.template
deleted file mode 100644
index f82ac2157c..0000000000
--- a/install/share/pki-acme-configsources.conf.template
+++ /dev/null
@@ -1,2 +0,0 @@
-engine.class=org.dogtagpki.acme.server.ACMEEngineConfigFileSource
-engine.filename=/etc/pki/pki-tomcat/acme/engine.conf
diff --git a/install/share/pki-acme-engine.conf.template b/install/share/pki-acme-engine.conf.template
deleted file mode 100644
index cb9898c5f2..0000000000
--- a/install/share/pki-acme-engine.conf.template
+++ /dev/null
@@ -1,2 +0,0 @@
-enabled=false
-wildcard=false
diff --git a/install/share/pki-acme-issuer.conf.template b/install/share/pki-acme-issuer.conf.template
index de2a06fec8..968d08f0aa 100644
--- a/install/share/pki-acme-issuer.conf.template
+++ b/install/share/pki-acme-issuer.conf.template
@@ -1,5 +1,5 @@
 class=org.dogtagpki.acme.issuer.PKIIssuer
 url=https://$FQDN:8443
-profile=acmeServerCert
+profile=acmeIPAServerCert
 username=$USER
 password=$PASSWORD
diff --git a/install/share/profiles/Makefile.am b/install/share/profiles/Makefile.am
index eb53703cc5..a04902ccef 100644
--- a/install/share/profiles/Makefile.am
+++ b/install/share/profiles/Makefile.am
@@ -7,7 +7,7 @@ app_DATA =				\
 	caIPAserviceCert.UPGRADE.cfg	\
 	IECUserRoles.cfg		\
 	KDCs_PKINIT_Certs.cfg		\
-	acmeServerCert.cfg		\
+	acmeIPAServerCert.cfg		\
 	$(NULL)
 
 EXTRA_DIST =				\
diff --git a/install/share/profiles/acmeIPAServerCert.cfg b/install/share/profiles/acmeIPAServerCert.cfg
index e636e29edf..2487056e14 100644
--- a/install/share/profiles/acmeIPAServerCert.cfg
+++ b/install/share/profiles/acmeIPAServerCert.cfg
@@ -1,4 +1,4 @@
-profileId=acmeServerCert
+profileId=acmeIPAServerCert
 classId=caEnrollImpl
 desc=ACME profile for use in IPA deployments
 visible=true
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index ef894dd77a..8365e6c035 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -123,10 +123,9 @@ class BasePathNamespace:
     PKI_TOMCAT_ALIAS_PWDFILE_TXT = "/etc/pki/pki-tomcat/alias/pwdfile.txt"
     PKI_TOMCAT_PASSWORD_CONF = "/etc/pki/pki-tomcat/password.conf"
     PKI_TOMCAT_SERVER_XML = "/etc/pki/pki-tomcat/server.xml"
-    PKI_ACME_CONFIGSOURCES_CONF = "/etc/pki/pki-tomcat/acme/configsources.conf"
     PKI_ACME_DATABASE_CONF = "/etc/pki/pki-tomcat/acme/database.conf"
-    PKI_ACME_ENGINE_CONF = "/etc/pki/pki-tomcat/acme/engine.conf"
     PKI_ACME_ISSUER_CONF = "/etc/pki/pki-tomcat/acme/issuer.conf"
+    PKI_ACME_REALM_CONF = "/etc/pki/pki-tomcat/acme/realm.conf"
     ETC_REDHAT_RELEASE = "/etc/redhat-release"
     RESOLV_CONF = "/etc/resolv.conf"
     SAMBA_KEYTAB = "/etc/samba/samba.keytab"
diff --git a/ipapython/dogtag.py b/ipapython/dogtag.py
index abd96bd456..251281a7eb 100644
--- a/ipapython/dogtag.py
+++ b/ipapython/dogtag.py
@@ -56,8 +56,8 @@
     Profile(u'KDCs_PKINIT_Certs',
             u'Profile for PKINIT support by KDCs',
             False),
-    Profile(u'acmeServerCert',
-            u'ACME service certificate profile',
+    Profile(u'acmeIPAServerCert',
+            u'ACME IPA service certificate profile',
             False),
     }
 
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index 4d96a099c1..0e6d9e48c2 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -71,7 +71,7 @@
     'Security Domain Administrators'
 ]
 
-ACME_AGENT_GROUP = 'ACME Agents'
+ACME_AGENT_GROUP = 'Enterprise ACME Administrators'
 
 
 def check_ports():
@@ -770,6 +770,10 @@ def __create_ca_agent(self):
             self.basedn)
         conn.add_entry_to_group(user_dn, group_dn, 'uniqueMember')
 
+        group_dn = DN(('cn', ACME_AGENT_GROUP), ('ou', 'groups'),
+                      self.basedn)
+        conn.add_entry_to_group(user_dn, group_dn, 'uniqueMember')
+
         conn.disconnect()
 
     def __get_ca_chain(self):
@@ -1518,6 +1522,8 @@ def setup_acme(self) -> bool:
             logger.debug('ACME service is already deployed')
             return False
 
+        self._ldap_mod('/usr/share/pki/acme/database/ds/schema.ldif')
+
         configure_acme_acls()
 
         # create ACME agent group (if not exist already) and user
@@ -1544,11 +1550,9 @@ def setup_acme(self) -> bool:
 
         # write configuration files
         files = [
-            ('pki-acme-configsources.conf.template',
-                paths.PKI_ACME_CONFIGSOURCES_CONF),
             ('pki-acme-database.conf.template', paths.PKI_ACME_DATABASE_CONF),
-            ('pki-acme-engine.conf.template', paths.PKI_ACME_ENGINE_CONF),
             ('pki-acme-issuer.conf.template', paths.PKI_ACME_ISSUER_CONF),
+            ('pki-acme-realm.conf.template', paths.PKI_ACME_REALM_CONF),
         ]
         sub_dict = dict(
             FQDN=self.fqdn,
@@ -1776,6 +1780,7 @@ def ensure_acme_containers():
         DN(('ou', 'orders'), ou_acme),
         DN(('ou', 'authorizations'), ou_acme),
         DN(('ou', 'challenges'), ou_acme),
+        DN(('ou', 'certificates'), ou_acme),
     ]
 
     for rdn in rdns:
diff --git a/ipaserver/install/ipa_acme_manage.py b/ipaserver/install/ipa_acme_manage.py
index ff56d3bf64..d3dcb135af 100644
--- a/ipaserver/install/ipa_acme_manage.py
+++ b/ipaserver/install/ipa_acme_manage.py
@@ -3,13 +3,16 @@
 #
 
 import enum
-import pathlib
 
+from ipalib import api, errors
+from ipalib import _
+from ipalib.facts import is_ipa_configured
 from ipaplatform.paths import paths
 from ipapython.admintool import AdminTool
-from ipapython.directivesetter import DirectiveSetter
+from ipapython import cookie, dogtag
 from ipaserver.install import cainstance
-from ipalib.facts import is_ipa_configured
+
+from ipaserver.plugins.dogtag import RestClient
 
 # Manages the FreeIPA ACME service on a per-server basis.
 #
@@ -20,6 +23,49 @@
 # remove this program, or make it a wrapper for the API commands.
 
 
+class acme_state(RestClient):
+
+    def _request(self, url):
+        return dogtag.https_request(
+            self.ca_host, 8443,
+            url=url,
+            cafile=self.ca_cert,
+            client_certfile=paths.RA_AGENT_PEM,
+            client_keyfile=paths.RA_AGENT_KEY,
+            method='POST'
+        )
+
+    def __enter__(self):
+        status, resp_headers, _unused = self._request('/acme/login')
+        cookies = cookie.Cookie.parse(resp_headers.get('set-cookie', ''))
+        if status != 200 or len(cookies) == 0:
+            raise errors.RemoteRetrieveError(
+                reason=_('Failed to authenticate to CA REST API')
+            )
+        object.__setattr__(self, 'cookie', str(cookies[0]))
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        """Log out of the REST API"""
+        headers = dict(Cookie=self.cookie)
+        status, unused, _unused = self._request('/acme/logout')
+        object.__setattr__(self, 'cookie', None)
+        if status != 204:
+            raise RuntimeError('Failed to logout')
+
+    def enable(self):
+        headers = dict(Cookie=self.cookie)
+        status, unused, _unused = self._request('/acme/enable')
+        if status != 200:
+            raise RuntimeError('Failed to enable ACME')
+
+    def disable(self):
+        headers = dict(Cookie=self.cookie)
+        status, unused, _unused = self._request('/acme/disable')
+        if status != 200:
+            raise RuntimeError('Failed to disble ACME')
+
+
 class Command(enum.Enum):
     ENABLE = 'enable'
     DISABLE = 'disable'
@@ -52,28 +98,17 @@ def run(self):
             print("CA is not installed on this server.")
             return 1
 
-        if self.command == Command.ENABLE:
-            directive = 'enabled'
-            value = 'true'
-        elif self.command == Command.DISABLE:
-            directive = 'enabled'
-            value = 'false'
-        else:
-            raise RuntimeError('programmer error: unhandled enum case')
-
-        with DirectiveSetter(
-            paths.PKI_ACME_ENGINE_CONF,
-            separator='=',
-            quotes=False,
-        ) as ds:
-            ds.set(directive, value)
-
-        # Work around a limitation in PKI ACME service file watching
-        # where renames (what DirectiveSetter does) are not detected.
-        # It will be fixed, but keeping the workaround will do no harm.
-        pathlib.Path(paths.PKI_ACME_ENGINE_CONF).touch()
-
-        # Nothing else to do; the Dogtag ACME service monitors engine.conf
-        # for updates and reconfigures itself as required.
+        api.bootstrap(in_server=True, confdir=paths.ETC_IPA)
+        api.finalize()
+        api.Backend.ldap2.connect()
+
+        state = acme_state(api)
+        with state as ca_api:
+            if self.command == Command.ENABLE:
+                ca_api.enable()
+            elif self.command == Command.DISABLE:
+                ca_api.disable()
+            else:
+                raise RuntimeError('programmer error: unhandled enum case')
 
         return 0

From 3fe69947eb2b6318dd846859faa84e26c04c1f37 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcrit...@redhat.com>
Date: Fri, 2 Oct 2020 17:12:23 -0400
Subject: [PATCH 4/4] Temp commit using pki-fedora

---
 .freeipa-pr-ci.yaml                        |  2 +-
 ipatests/prci_definitions/temp_commit.yaml | 21 +++++++++++----------
 2 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/.freeipa-pr-ci.yaml b/.freeipa-pr-ci.yaml
index abcf8c5b63..8065669008 120000
--- a/.freeipa-pr-ci.yaml
+++ b/.freeipa-pr-ci.yaml
@@ -1 +1 @@
-ipatests/prci_definitions/gating.yaml
\ No newline at end of file
+ipatests/prci_definitions/temp_commit.yaml
\ No newline at end of file
diff --git a/ipatests/prci_definitions/temp_commit.yaml b/ipatests/prci_definitions/temp_commit.yaml
index ef2e4bfa90..d8e2a44ac7 100644
--- a/ipatests/prci_definitions/temp_commit.yaml
+++ b/ipatests/prci_definitions/temp_commit.yaml
@@ -47,7 +47,7 @@ topologies:
     memory: 14500
 
 jobs:
-  fedora-latest/build:
+  pki-fedora/build:
     requires: []
     priority: 100
     job:
@@ -55,20 +55,21 @@ jobs:
       args:
         git_repo: '{git_repo}'
         git_refspec: '{git_refspec}'
-        template: &ci-master-latest
-          name: freeipa/ci-master-f32
-          version: 0.0.11
+        template: &pki-master-latest
+          name: freeipa/pki-master-f32
+          version: 0.0.3
         timeout: 1800
         topology: *build
 
-  fedora-latest/temp_commit:
-    requires: [fedora-latest/build]
+  pki-fedora/temp_commit:
+    requires: [pki-fedora/build]
     priority: 50
     job:
       class: RunPytest
       args:
-        build_url: '{fedora-latest/build_url}'
-        test_suite: test_integration/test_REPLACEME.py
-        template: *ci-master-latest
-        timeout: 3600
+        build_url: '{pki-fedora/build_url}'
+        update_packages: True
+        test_suite: test_integration/test_acme.py
+        template: *pki-master-latest
+        timeout: 7200
         topology: *master_1repl_1client
_______________________________________________
FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org
To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org
Fedora Code of Conduct: 
https://docs.fedoraproject.org/en-US/project/code-of-conduct/
List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines
List Archives: 
https://lists.fedorahosted.org/archives/list/freeipa-devel@lists.fedorahosted.org

Reply via email to