ipa-certupdate: track lightweight CA certificates

Enhance the ipa-certupdate program to add Certmonger tracking
requests for lightweight CA certificates.

Also update the dogtag-ipa-ca-renew-agent-submit to not store or
retrieve lightweight CA certificates, becaues Dogtag clones observe
renewals and update their NSSDBs on their own, and allow the helper
to request non-self-signed certificates.

Part of: https://fedorahosted.org/freeipa/ticket/4559

Reviewed-By: Jan Cholasta <jcholast@redhat.com>
This commit is contained in:
Fraser Tweedale 2016-06-21 15:01:41 +10:00 committed by Jan Cholasta
parent b720aa94e9
commit 0078e7a919
2 changed files with 82 additions and 9 deletions

View File

@ -62,6 +62,24 @@ if six.PY3:
unicode = str unicode = str
IPA_CA_NICKNAME = 'caSigningCert cert-pki-ca'
def get_nickname():
csr = os.environ.get('CERTMONGER_CSR')
return pkcs10.get_friendlyname(csr) if csr else None
def is_lightweight_ca():
nickname = get_nickname() or ''
return nickname != IPA_CA_NICKNAME and nickname.startswith(IPA_CA_NICKNAME)
def is_renewable():
cert = os.environ.get('CERTMONGER_CERTIFICATE')
if not cert:
return False
else:
return x509.is_self_signed(cert) or is_lightweight_ca()
@contextlib.contextmanager @contextlib.contextmanager
def ldap_connect(): def ldap_connect():
conn = None conn = None
@ -210,6 +228,11 @@ def store_cert():
if not cert: if not cert:
return (REJECTED, "New certificate requests not supported") return (REJECTED, "New certificate requests not supported")
if is_lightweight_ca():
# Lightweight CAs are updated in Dogtag's NSSDB
# by Dogtag itself, so do not store it
return (ISSUED, cert)
dercert = x509.normalize_certificate(cert) dercert = x509.normalize_certificate(cert)
dn = DN(('cn', nickname), ('cn', 'ca_renewal'), dn = DN(('cn', nickname), ('cn', 'ca_renewal'),
@ -338,6 +361,12 @@ def retrieve_cert_continuous():
if old_cert: if old_cert:
old_cert = x509.normalize_certificate(old_cert) old_cert = x509.normalize_certificate(old_cert)
if is_lightweight_ca():
# Lightweight CAs are updated in Dogtag's NSSDB
# by Dogtag itself, so do not try to retrieve it.
# Everything is fine as is.
return (ISSUED, os.environ.get('CERTMONGER_CERTIFICATE'))
result = call_handler(retrieve_or_reuse_cert) result = call_handler(retrieve_or_reuse_cert)
if result[0] != ISSUED: if result[0] != ISSUED:
return result return result
@ -393,13 +422,12 @@ def renew_ca_cert():
cert = os.environ.get('CERTMONGER_CERTIFICATE') cert = os.environ.get('CERTMONGER_CERTIFICATE')
if not cert: if not cert:
return (REJECTED, "New certificate requests not supported") return (REJECTED, "New certificate requests not supported")
is_self_signed = x509.is_self_signed(cert)
operation = os.environ.get('CERTMONGER_OPERATION') operation = os.environ.get('CERTMONGER_OPERATION')
if operation == 'SUBMIT': if operation == 'SUBMIT':
state = 'retrieve' state = 'retrieve'
if is_self_signed: if is_renewable():
ca = cainstance.CAInstance(host_name=api.env.host, ldapi=False) ca = cainstance.CAInstance(host_name=api.env.host, ldapi=False)
if ca.is_renewal_master(): if ca.is_renewal_master():
state = 'request' state = 'request'
@ -419,10 +447,11 @@ def renew_ca_cert():
if state == 'retrieve': if state == 'retrieve':
result = call_handler(retrieve_cert) result = call_handler(retrieve_cert)
if result[0] == REJECTED and not is_self_signed: if result[0] == REJECTED and not is_renewable():
syslog.syslog(syslog.LOG_ALERT, syslog.syslog(syslog.LOG_ALERT,
"IPA CA certificate is about to expire, " "Certificate with subject '%s' is about to expire, "
"use ipa-cacert-manage to renew it") "use ipa-cacert-manage to renew it"
% (os.environ.get("CERTMONGER_REQ_SUBJECT"),))
elif state == 'request': elif state == 'request':
profile = os.environ['CERTMONGER_CA_PROFILE'] profile = os.environ['CERTMONGER_CA_PROFILE']
os.environ['CERTMONGER_CA_PROFILE'] = 'caCACert' os.environ['CERTMONGER_CA_PROFILE'] = 'caCACert'

View File

@ -29,7 +29,10 @@ from ipaplatform import services
from ipaplatform.paths import paths from ipaplatform.paths import paths
from ipaplatform.tasks import tasks from ipaplatform.tasks import tasks
from ipalib import api, errors, x509, certstore from ipalib import api, errors, x509, certstore
from ipalib.constants import IPA_CA_CN
IPA_CA_NICKNAME = 'caSigningCert cert-pki-ca'
RENEWAL_CA_NAME = 'dogtag-ipa-ca-renew-agent'
class CertUpdate(admintool.AdminTool): class CertUpdate(admintool.AdminTool):
command_name = 'ipa-certupdate' command_name = 'ipa-certupdate'
@ -76,18 +79,27 @@ class CertUpdate(admintool.AdminTool):
version=u'2.0', version=u'2.0',
) )
ca_enabled = result['result']['enable_ra'] ca_enabled = result['result']['enable_ra']
api.Backend.rpcclient.disconnect()
ldap.do_sasl_gssapi_bind() ldap.do_sasl_gssapi_bind()
certs = certstore.get_ca_certs(ldap, api.env.basedn, certs = certstore.get_ca_certs(ldap, api.env.basedn,
api.env.realm, ca_enabled) api.env.realm, ca_enabled)
# find lightweight CAs (on renewal master only)
lwcas = []
for ca_obj in api.Command.ca_find()['result']:
if IPA_CA_CN not in ca_obj['cn']:
lwcas.append(ca_obj)
api.Backend.rpcclient.disconnect()
finally: finally:
shutil.rmtree(tmpdir) shutil.rmtree(tmpdir)
server_fstore = sysrestore.FileStore(paths.SYSRESTORE) server_fstore = sysrestore.FileStore(paths.SYSRESTORE)
if server_fstore.has_files(): if server_fstore.has_files():
self.update_server(certs) self.update_server(certs)
for entry in lwcas:
self.server_track_lightweight_ca(entry)
self.update_client(certs) self.update_client(certs)
@ -122,11 +134,10 @@ class CertUpdate(admintool.AdminTool):
if services.knownservices.httpd.is_running(): if services.knownservices.httpd.is_running():
services.knownservices.httpd.restart() services.knownservices.httpd.restart()
nickname = 'caSigningCert cert-pki-ca'
criteria = { criteria = {
'cert-database': paths.PKI_TOMCAT_ALIAS_DIR, 'cert-database': paths.PKI_TOMCAT_ALIAS_DIR,
'cert-nickname': nickname, 'cert-nickname': IPA_CA_NICKNAME,
'ca-name': 'dogtag-ipa-ca-renew-agent', 'ca-name': RENEWAL_CA_NAME
} }
request_id = certmonger.get_request_id(criteria) request_id = certmonger.get_request_id(criteria)
if request_id is not None: if request_id is not None:
@ -152,6 +163,39 @@ class CertUpdate(admintool.AdminTool):
self.update_file(paths.CA_CRT, certs) self.update_file(paths.CA_CRT, certs)
def server_track_lightweight_ca(self, entry):
nickname = "{} {}".format(IPA_CA_NICKNAME, entry['ipacaid'][0])
criteria = {
'cert-database': paths.PKI_TOMCAT_ALIAS_DIR,
'cert-nickname': nickname,
'ca-name': RENEWAL_CA_NAME,
}
request_id = certmonger.get_request_id(criteria)
if request_id is None:
try:
certmonger.dogtag_start_tracking(
secdir=paths.PKI_TOMCAT_ALIAS_DIR,
pin=certmonger.get_pin('internal'),
pinfile=None,
nickname=nickname,
ca=RENEWAL_CA_NAME,
pre_command='stop_pkicad',
post_command='renew_ca_cert "%s"' % nickname,
)
request_id = certmonger.get_request_id(criteria)
certmonger.modify(request_id, profile='ipaCACertRenewal')
self.log.debug(
'Lightweight CA renewal: '
'added tracking request for "%s"', nickname)
except RuntimeError as e:
self.log.error(
'Lightweight CA renewal: Certmonger failed to '
'start tracking certificate: %s', e)
else:
self.log.debug(
'Lightweight CA renewal: '
'already tracking certificate "%s"', nickname)
def update_file(self, filename, certs, mode=0o444): def update_file(self, filename, certs, mode=0o444):
certs = (c[0] for c in certs if c[2] is not False) certs = (c[0] for c in certs if c[2] is not False)
try: try: