diff --git a/install/share/Makefile.am b/install/share/Makefile.am index 684da8dde..6c4c3f0ed 100644 --- a/install/share/Makefile.am +++ b/install/share/Makefile.am @@ -106,6 +106,7 @@ dist_app_DATA = \ 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-engine.conf.template b/install/share/pki-acme-engine.conf.template index cb9898c5f..e6a56593f 100644 --- a/install/share/pki-acme-engine.conf.template +++ b/install/share/pki-acme-engine.conf.template @@ -1,2 +1,11 @@ +# Parameters read by ACMEEngineConfigFileSource, i.e. these are +# expected to be in the file pointed to by the 'filename' directive +# above. +# +# IPA only sets the values it uses. +# +# Whether to enable the ACME service: enabled=false -wildcard=false + +# Whether to accept wildcard DNS identifiers: +policy.wildcard=false diff --git a/install/share/pki-acme-issuer.conf.template b/install/share/pki-acme-issuer.conf.template index de2a06fec..968d08f0a 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/pki-acme-realm.conf.template b/install/share/pki-acme-realm.conf.template new file mode 100644 index 000000000..2e9c1607b --- /dev/null +++ b/install/share/pki-acme-realm.conf.template @@ -0,0 +1,8 @@ +authType=BasicAuth +class=org.dogtagpki.acme.realm.DSRealm +groupsDN=ou=groups,o=ipaca +usersDN=ou=people,o=ipaca +url=ldaps://$FQDN:636 +configFile=/etc/pki/pki-tomcat/ca/CS.cfg +username=$USER +password=$PASSWORD diff --git a/install/share/profiles/Makefile.am b/install/share/profiles/Makefile.am index eb53703cc..a04902cce 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 e636e29ed..2487056e1 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 234f66a35..b46e661ad 100644 --- a/ipaplatform/base/paths.py +++ b/ipaplatform/base/paths.py @@ -127,6 +127,7 @@ class BasePathNamespace: 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 abd96bd45..251281a7e 100644 --- a/ipapython/dogtag.py +++ b/ipapython/dogtag.py @@ -56,8 +56,8 @@ INCLUDED_PROFILES = { 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 fca829de0..6c84e928b 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -71,7 +71,7 @@ ADMIN_GROUPS = [ 'Security Domain Administrators' ] -ACME_AGENT_GROUP = 'ACME Agents' +ACME_AGENT_GROUP = 'Enterprise ACME Administrators' PROFILES_DN = DN(('ou', 'certificateProfiles'), ('ou', 'ca'), ('o', 'ipaca')) @@ -768,6 +768,12 @@ class CAInstance(DogtagInstance): 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): try: return dogtag.get_ca_certchain(ca_host=self.fqdn) @@ -1479,6 +1485,8 @@ class CAInstance(DogtagInstance): 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 @@ -1510,6 +1518,7 @@ class CAInstance(DogtagInstance): ('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, @@ -1732,6 +1741,11 @@ def ensure_acme_containers(): DN(('ou', 'orders'), ou_acme), DN(('ou', 'authorizations'), ou_acme), DN(('ou', 'challenges'), ou_acme), + DN(('ou', 'certificates'), ou_acme), + ] + + extensible_rdns = [ + DN(('ou', 'config'), ou_acme), ] for rdn in rdns: @@ -1741,6 +1755,13 @@ def ensure_acme_containers(): ou=[rdn[0][0].value], ) + for rdn in extensible_rdns: + ensure_entry( + DN(rdn, ('o', 'ipaca')), + objectclass=['top', 'organizationalUnit', 'extensibleObject'], + ou=[rdn[0][0].value], + ) + def ensure_entry(dn, **attrs): """Ensure an entry exists. diff --git a/ipaserver/install/ipa_acme_manage.py b/ipaserver/install/ipa_acme_manage.py index ff56d3bf6..d3dcb135a 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 @@ from ipalib.facts import is_ipa_configured # 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 @@ class IPAACMEManage(AdminTool): 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') + api.bootstrap(in_server=True, confdir=paths.ETC_IPA) + api.finalize() + api.Backend.ldap2.connect() - 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. + 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 diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py index ef58fd24c..18891d53c 100644 --- a/ipaserver/install/server/upgrade.py +++ b/ipaserver/install/server/upgrade.py @@ -1112,7 +1112,9 @@ def ca_upgrade_schema(ca): return False # ACME schema file moved in pki-server-10.9.0-0.3 + # ACME database connections were abstrated in pki-acme-10.10.0 for path in [ + '/usr/share/pki/acme/conf/database/ds/schema.ldif', '/usr/share/pki/acme/conf/database/ldap/schema.ldif', '/usr/share/pki/acme/database/ldap/schema.ldif', ]: