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 <rcritten@redhat.com>
Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
Reviewed-By: Mohammad Rizwan <myusuf@redhat.com>
This commit is contained in:
Rob Crittenden
2020-10-26 12:48:18 -04:00
parent 9c4785f042
commit 2768b0dbaf
4 changed files with 49 additions and 2 deletions

View File

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

View File

@@ -31,6 +31,7 @@ import ldap
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()

View File

@@ -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 @@ class IPAACMEManage(AdminTool):
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 @@ class IPAACMEManage(AdminTool):
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()

View File

@@ -27,12 +27,13 @@ import optparse # pylint: disable=deprecated-module
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 @@ class ServerCertInstall(admintool.AdminTool):
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()