From 2768b0dbaf0e07bc632a4867b13ef4c5ac875372 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Mon, 26 Oct 2020 12:48:18 -0400 Subject: [PATCH] Require an ipa-ca SAN on 3rd party certs if ACME is enabled ACME requires an ipa-ca SAN to have a fixed URL to connect to. If the Apache certificate is replaced by a 3rd party cert then it must provide this SAN otherwise it will break ACME. https://pagure.io/freeipa/issue/8498 Signed-off-by: Rob Crittenden Reviewed-By: Fraser Tweedale Reviewed-By: Mohammad Rizwan --- install/tools/man/ipa-server-certinstall.1 | 2 ++ ipaserver/install/cainstance.py | 25 +++++++++++++++++++++ ipaserver/install/ipa_acme_manage.py | 10 ++++++++- ipaserver/install/ipa_server_certinstall.py | 14 +++++++++++- 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/install/tools/man/ipa-server-certinstall.1 b/install/tools/man/ipa-server-certinstall.1 index 79bd7c885d1..8798819d7fa 100644 --- a/install/tools/man/ipa-server-certinstall.1 +++ b/install/tools/man/ipa-server-certinstall.1 @@ -30,6 +30,8 @@ They may be generated and managed using the NSS pk12util command or the OpenSSL The service(s) are not automatically restarted. In order to use the newly installed certificate(s) you will need to manually restart the Directory, Apache and/or Krb5kdc servers. +If the ACME service is enabled then the web certificate must have a Subject Alternative Name (SAN) for ipa-ca.$DOMAIN. + .SH "OPTIONS" .TP \fB\-d\fR, \fB\-\-dirsrv\fR diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index 84f12d70cf6..535407cde2f 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -31,6 +31,7 @@ import os import re import shutil +import ssl import sys import syslog import time @@ -2271,6 +2272,30 @@ def import_ra_key(custodia): CAInstance.configure_agent_renewal() +def check_ipa_ca_san(cert): + """ + Test whether the certificate has an ipa-ca SAN + + :param cert: x509.IPACertificate + + This SAN is necessary for ACME. + + The caller is responsible for initializing the api. + + On success returns None, on failure raises ValidationError + """ + expect = f'{ipalib.constants.IPA_CA_RECORD}.' \ + f'{ipautil.format_netloc(api.env.domain)}' + + try: + cert.match_hostname(expect) + except ssl.CertificateError: + raise errors.ValidationError( + name='certificate', + error='Does not have a \'{}\' SAN'.format(expect) + ) + + if __name__ == "__main__": standard_logging_setup("install.log") ds = dsinstance.DsInstance() diff --git a/ipaserver/install/ipa_acme_manage.py b/ipaserver/install/ipa_acme_manage.py index c61d7f6e9db..a1b969d607b 100644 --- a/ipaserver/install/ipa_acme_manage.py +++ b/ipaserver/install/ipa_acme_manage.py @@ -4,7 +4,7 @@ import enum -from ipalib import api, errors +from ipalib import api, errors, x509 from ipalib import _ from ipalib.facts import is_ipa_configured from ipaplatform.paths import paths @@ -90,6 +90,13 @@ def validate_options(self): except ValueError: self.option_parser.error(f'unknown command "{self.args[0]}"') + def check_san_status(self): + """ + Require the Apache cert to have ipa-ca.$DOMAIN SAN + """ + cert = x509.load_certificate_from_file(paths.HTTPD_CERT_FILE) + cainstance.check_ipa_ca_san(cert) + def run(self): if not is_ipa_configured(): print("IPA is not configured.") @@ -106,6 +113,7 @@ def run(self): state = acme_state(api) with state as ca_api: if self.command == Command.ENABLE: + self.check_san_status() ca_api.enable() elif self.command == Command.DISABLE: ca_api.disable() diff --git a/ipaserver/install/ipa_server_certinstall.py b/ipaserver/install/ipa_server_certinstall.py index 74f7ff1f3e3..e29f00ec377 100644 --- a/ipaserver/install/ipa_server_certinstall.py +++ b/ipaserver/install/ipa_server_certinstall.py @@ -27,12 +27,13 @@ from ipalib import x509 from ipalib.install import certmonger from ipaplatform.paths import paths -from ipapython import admintool +from ipapython import admintool, dogtag from ipapython.certdb import NSSDatabase, get_ca_nickname from ipapython.dn import DN from ipapython import ipaldap from ipalib import api, errors from ipaserver.install import certs, dsinstance, installutils, krbinstance +from ipaserver.install import cainstance class ServerCertInstall(admintool.AdminTool): @@ -105,11 +106,22 @@ def ask_for_options(self): raise admintool.ScriptError( "Private key unlock password required") + def validate_http_cert(self): + if dogtag.acme_status(): + cert, unused, _unused = self.load_pkcs12( + ca_chain_fname=paths.IPA_CA_CRT, + host_name=api.env.host + ) + cainstance.check_ipa_ca_san(cert) + def run(self): api.bootstrap(in_server=True, confdir=paths.ETC_IPA) api.finalize() api.Backend.ldap2.connect(bind_pw=self.options.dirman_password) + if self.options.http: + self.validate_http_cert() + if self.options.dirsrv: self.install_dirsrv_cert()