Skip to content

Commit

Permalink
Require an ipa-ca SAN on 3rd party certs if ACME is enabled
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
rcritten committed Nov 2, 2020
1 parent 9c4785f commit 2768b0d
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 2 deletions.
2 changes: 2 additions & 0 deletions install/tools/man/ipa-server-certinstall.1
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions ipaserver/install/cainstance.py
Expand Up @@ -31,6 +31,7 @@
import os
import re
import shutil
import ssl
import sys
import syslog
import time
Expand Down Expand Up @@ -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()
Expand Down
10 changes: 9 additions & 1 deletion ipaserver/install/ipa_acme_manage.py
Expand Up @@ -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
Expand Down Expand Up @@ -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.")
Expand All @@ -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()
Expand Down
14 changes: 13 additions & 1 deletion ipaserver/install/ipa_server_certinstall.py
Expand Up @@ -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):
Expand Down Expand Up @@ -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()

Expand Down

0 comments on commit 2768b0d

Please sign in to comment.