def get_csr_from_certmonger(nickname): """ Get the csr for the provided nickname by asking certmonger. Returns the csr in ASCII format without the header/footer in a single line or None if not found. """ criteria = { 'cert-database': paths.PKI_TOMCAT_ALIAS_DIR, 'cert-nickname': nickname, } id = certmonger.get_request_id(criteria) if id: csr = certmonger.get_request_value(id, "csr") if csr: try: # Make sure the value can be parsed as valid CSR csr_obj = crypto_x509.load_pem_x509_csr( csr.encode('ascii'), default_backend()) val = base64.b64encode(csr_obj.public_bytes(x509.Encoding.DER)) return val.decode('ascii') except Exception as e: # Fallthrough and return None logger.debug("Unable to get CSR from certmonger: %s", e) return None
def renew(self): ca = cainstance.CAInstance(api.env.realm) if not ca.is_configured(): raise admintool.ScriptError("CA is not configured on this system") criteria = { 'cert-database': paths.PKI_TOMCAT_ALIAS_DIR, 'cert-nickname': self.cert_nickname, 'ca-name': 'dogtag-ipa-ca-renew-agent', } self.request_id = certmonger.get_request_id(criteria) if self.request_id is None: raise admintool.ScriptError( "CA certificate is not tracked by certmonger") self.log.debug( "Found certmonger request id %r", self.request_id) db = certs.CertDB(api.env.realm, nssdir=paths.PKI_TOMCAT_ALIAS_DIR) cert = db.get_cert_from_db(self.cert_nickname, pem=False) options = self.options if options.external_cert_files: return self.renew_external_step_2(ca, cert) if options.self_signed is not None: self_signed = options.self_signed else: self_signed = x509.is_self_signed(cert, x509.DER) if self_signed: return self.renew_self_signed(ca) else: return self.renew_external_step_1(ca)
def check_dates(self): """Check validity dates""" # TODO: make this configurable threshold = 7 # days requests = self.get_requests() now = datetime.datetime.utcnow() for request in requests: request_id = certmonger.get_request_id(request) if request_id is None: # The missing tracking is reported in check_tracking() continue nickname = request.get('cert-nickname') rawcert = certmonger.get_request_value(request_id, 'cert') cert = load_pem_certificate(str(rawcert)) diff = cert.not_valid_after - now if diff.days < 0: # TODO: this is false-positive generator self.failure("Certificate %s is expired" % nickname) elif diff.days < threshold: self.failure("Certificate %s is expiring soon" % nickname) elif cert.not_valid_before > now: self.failure("Certificate %s is not valid yet" % nickname)
def check_tracking(self): """Compare expected vs actual tracking configuration""" requests = self.get_requests() cm = certmonger._certmonger() ids = [] all_requests = cm.obj_if.get_requests() for req in all_requests: request = certmonger._cm_dbus_object(cm.bus, cm, req, certmonger.DBUS_CM_REQUEST_IF, certmonger.DBUS_CM_IF, True) id = request.prop_if.Get(certmonger.DBUS_CM_REQUEST_IF, 'nickname') ids.append(str(id)) for request in requests: request_id = certmonger.get_request_id(request) try: if request_id is not None: ids.remove(request_id) except ValueError as e: self.failure('Failure trying to remove % from ' 'list: %s' % (request_id, e)) if request_id is None: self.failure('Missing tracking for %s' % request) if ids: self.warning('Unknown certmonger ids: %s' % ','.join(ids))
def _get_ca_request_id(self, ca_name): """Lookup tracking request for IPA CA, using given ca-name.""" criteria = { 'cert-database': paths.PKI_TOMCAT_ALIAS_DIR, 'cert-nickname': self.cert_nickname, 'ca-name': ca_name, } return certmonger.get_request_id(criteria)
def compare_requests(self): """ Compare cert serial numbers to their request The CA subsystem certificates are renewed using the certmonger CA dogtag-ipa-ca-renew-agent. This renews by serial number, sending CS a request like: GET /ca/ee/ca/profileSubmit?profileId=caServerCert&serial_num=5& renewal=true&xml=true&requestor_name=IPA CS uses the existing cert to generate and return a new one. Double-check that the cert in that request entry, dn: cn=<serial#>,ou=ca,ou=requests,o=ipaca """ if not self.ca.is_configured(): self.failure('Skipping request compare because CA not installed') return requests = self.get_requests() for request in requests: if request.get('ca-name') != 'dogtag-ipa-ca-renew-agent': continue request_id = certmonger.get_request_id(request) serial = int(certmonger.get_request_value(request_id, 'serial'), 16) template_subject = DN( certmonger.get_request_value(request_id, 'template-subject')) dn = DN(('cn', serial), ('ou', 'ca'), ('ou', 'requests'), ('o', 'ipaca')) try: entries = self.conn.get_entries(dn, self.conn.SCOPE_SUBTREE) except errors.NotFound: self.failure('Unable to find request for serial %s' % serial) except Exception as e: self.failure('Failed to load request for serial %s' % serial) else: s = entries[0].get('extdata-req--005fsubject--005fname') if s is None: continue subject_der = base64.b64decode(s[0]) subject = DN(der_to_subject(subject_der)) subject = DN(der_to_subject(subject_der)) logger.debug('CS template %s, CM subject %s, serial %s', subject, template_subject, serial) if ((subject != template_subject) and (subject != template_subject.x500_text())): self.failure('Subject %s and template subject %s ' 'do not match for serial %s' % (subject, template_subject, serial))
def get_pkinit_request_ca(): """ Return the certmonger CA name which is serving the PKINIT certificate request. If the certificate is not tracked by Certmonger, return None """ pkinit_request_id = certmonger.get_request_id( {'cert-file': paths.KDC_CERT}) if pkinit_request_id is None: return return certmonger.get_request_value(pkinit_request_id, 'ca-name')
def check(self): requests = get_expected_requests(self.ca, self.ds, self.serverid) cm = certmonger._certmonger() ids = [] all_requests = cm.obj_if.get_requests() for req in all_requests: request = certmonger._cm_dbus_object(cm.bus, cm, req, certmonger.DBUS_CM_REQUEST_IF, certmonger.DBUS_CM_IF, True) id = request.prop_if.Get(certmonger.DBUS_CM_REQUEST_IF, 'nickname') ids.append(str(id)) for request in requests: request_id = certmonger.get_request_id(request) try: if request_id is not None: # Tracking found, move onto the next ids.remove(request_id) yield Result(self, constants.SUCCESS, key=request_id) continue except ValueError as e: # A request was found but the id isn't in the # list from certmonger!? yield Result(self, constants.ERROR, key=request_id, error=str(e), msg='Found request id {key} but it is not tracked' 'by certmonger!?: {error}') continue # The criteria was not met if request_id is None: flatten = ', '.join("{!s}={!s}".format(key, val) for (key, val) in request.items()) yield Result(self, constants.ERROR, key=flatten, msg='Expected certmonger tracking is missing for ' '{key}. Automated renewal will not happen ' 'for this certificate') continue # Report any unknown certmonger requests as warnings if ids: for id in ids: yield Result(self, constants.WARNING, key=id, msg='certmonger tracking request {key} found and ' 'is not expected on an IPA master.')
def update_server(certs): instance = '-'.join(api.env.realm.split('.')) update_db(paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % instance, certs) if services.knownservices.dirsrv.is_running(): services.knownservices.dirsrv.restart(instance) if services.knownservices.httpd.is_running(): services.knownservices.httpd.restart() criteria = { 'cert-database': paths.PKI_TOMCAT_ALIAS_DIR, 'cert-nickname': IPA_CA_NICKNAME, 'ca-name': RENEWAL_CA_NAME, } request_id = certmonger.get_request_id(criteria) if request_id is not None: timeout = api.env.startup_timeout + 60 # The dogtag-ipa-ca-renew-agent-reuse Certmonger CA never # actually renews the certificate; it only pulls it from the # ca_renewal LDAP cert store. # # Why is this needed? If the CA cert gets renewed long # before its notAfter (expiry) date (e.g. to switch from # self-signed to external, or to switch to new external CA), # then the other (i.e. not caRenewalMaster) CA replicas will # not promptly pick up the new CA cert. So we make # ipa-certupdate always check for an updated CA cert. # logger.debug("resubmitting certmonger request '%s'", request_id) certmonger.resubmit_request( request_id, ca='dogtag-ipa-ca-renew-agent-reuse', profile='') try: state = certmonger.wait_for_request(request_id, timeout) except RuntimeError: raise admintool.ScriptError( "Resubmitting certmonger request '%s' timed out, " "please check the request manually" % request_id) ca_error = certmonger.get_request_value(request_id, 'ca-error') if state != 'MONITORING' or ca_error: raise admintool.ScriptError( "Error resubmitting certmonger request '%s', " "please check the request manually" % request_id) logger.debug("modifying certmonger request '%s'", request_id) certmonger.modify(request_id, ca='dogtag-ipa-ca-renew-agent') update_file(paths.CA_CRT, certs) update_file(paths.CACERT_PEM, certs)
def check(self): requests = get_expected_requests(self.ca, self.ds, self.serverid) cm = certmonger._certmonger() ids = [] all_requests = cm.obj_if.get_requests() for req in all_requests: request = certmonger._cm_dbus_object(cm.bus, cm, req, certmonger.DBUS_CM_REQUEST_IF, certmonger.DBUS_CM_IF, True) id = request.prop_if.Get(certmonger.DBUS_CM_REQUEST_IF, 'nickname') ids.append(str(id)) for request in requests: request_id = certmonger.get_request_id(request) try: if request_id is not None: # Tracking found, move onto the next ids.remove(request_id) yield Result(self, constants.SUCCESS, key=request_id) continue except ValueError as e: # A request was found but the id isn't in the # list from certmonger!? yield Result(self, constants.ERROR, key=request_id, msg='Request id %s is not tracked: %s' % (request_id, e)) continue # The criteria was not met if request_id is None: flatten = ', '.join("{!s}={!s}".format(key, val) for (key, val) in request.items()) yield Result(self, constants.ERROR, key=flatten, msg='Missing tracking for %s' % flatten) continue # Report any unknown certmonger requests as warnings if ids: for id in ids: yield Result(self, constants.WARNING, key=id, msg='Unknown certmonger id %s' % id)
def update_server(self, certs): instance = '-'.join(api.env.realm.split('.')) self.update_db(paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % instance, certs) if services.knownservices.dirsrv.is_running(): services.knownservices.dirsrv.restart(instance) self.update_db(paths.HTTPD_ALIAS_DIR, certs) if services.knownservices.httpd.is_running(): services.knownservices.httpd.restart() criteria = { 'cert-database': paths.PKI_TOMCAT_ALIAS_DIR, 'cert-nickname': IPA_CA_NICKNAME, 'ca-name': RENEWAL_CA_NAME } request_id = certmonger.get_request_id(criteria) if request_id is not None: timeout = api.env.startup_timeout + 60 logger.debug("resubmitting certmonger request '%s'", request_id) certmonger.resubmit_request(request_id, ca='dogtag-ipa-ca-renew-agent-reuse', profile='') try: state = certmonger.wait_for_request(request_id, timeout) except RuntimeError: raise admintool.ScriptError( "Resubmitting certmonger request '%s' timed out, " "please check the request manually" % request_id) ca_error = certmonger.get_request_value(request_id, 'ca-error') if state != 'MONITORING' or ca_error: raise admintool.ScriptError( "Error resubmitting certmonger request '%s', " "please check the request manually" % request_id) logger.debug("modifying certmonger request '%s'", request_id) certmonger.modify(request_id, ca='dogtag-ipa-ca-renew-agent') self.update_file(paths.CA_CRT, certs) self.update_file(paths.CACERT_PEM, certs)
def update_server(self, certs): instance = '-'.join(api.env.realm.split('.')) self.update_db( paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % instance, certs) if services.knownservices.dirsrv.is_running(): services.knownservices.dirsrv.restart(instance) self.update_db(paths.HTTPD_ALIAS_DIR, certs) if services.knownservices.httpd.is_running(): services.knownservices.httpd.restart() criteria = { 'cert-database': paths.PKI_TOMCAT_ALIAS_DIR, 'cert-nickname': IPA_CA_NICKNAME, 'ca-name': RENEWAL_CA_NAME } request_id = certmonger.get_request_id(criteria) if request_id is not None: timeout = api.env.startup_timeout + 60 logger.debug("resubmitting certmonger request '%s'", request_id) certmonger.resubmit_request( request_id, ca='dogtag-ipa-ca-renew-agent-reuse', profile='') try: state = certmonger.wait_for_request(request_id, timeout) except RuntimeError: raise admintool.ScriptError( "Resubmitting certmonger request '%s' timed out, " "please check the request manually" % request_id) ca_error = certmonger.get_request_value(request_id, 'ca-error') if state != 'MONITORING' or ca_error: raise admintool.ScriptError( "Error resubmitting certmonger request '%s', " "please check the request manually" % request_id) logger.debug("modifying certmonger request '%s'", request_id) certmonger.modify(request_id, ca='dogtag-ipa-ca-renew-agent') self.update_file(paths.CA_CRT, certs) self.update_file(paths.CACERT_PEM, certs)
def check(self): # For simplicity use the expected certmonger tracking for the # list of certificates to check because it already filters out # based on whether the CA system is configure and whether the # certificates were issued by IPA. if not self.ca.is_configured(): logger.debug('CA is not configured, skipping revocation check') return requests = get_expected_requests(self.ca, self.ds, self.serverid) for request in requests: id = certmonger.get_request_id(request) if request.get('cert-file') is not None: certfile = request.get('cert-file') try: cert = x509.load_certificate_from_file(certfile) except Exception as e: yield Result(self, constants.ERROR, key=id, certfile=certfile, error=str(e), msg='Unable to open cert file {certfile}: ' '{error}') continue elif request.get('cert-database') is not None: nickname = request.get('cert-nickname') dbdir = request.get('cert-database') try: db = certdb.NSSDatabase(dbdir) except Exception as e: yield Result(self, constants.ERROR, key=id, dbdir=dbdir, error=str(e), msg='Unable to open NSS database {dbdir}: ' '{error}') continue try: cert = db.get_cert(nickname) except Exception as e: yield Result(self, constants.ERROR, key=id, dbdir=dbdir, nickname=nickname, error=str(e), msg='Unable to retrieve certificate ' '\'{nickname}\' from {dbdir}: {error}') continue else: yield Result(self, constants.ERROR, key=id, msg='Unable to to identify certificate storage ' 'type for request {key}') continue issued = is_ipa_issued_cert(api, cert) if issued is False: logger.debug('\'%s\' was not issued by IPA, skipping', DN(cert.subject)) continue if issued is None: logger.debug('LDAP is down, skipping \'%s\'', DN(cert.subject)) continue # Now we have the cert either way, check the recovation try: result = api.Command.cert_show(cert.serial_number, all=True) except Exception as e: yield Result(self, constants.ERROR, key=id, serial=cert.serial_number, error=str(e), msg='Request for certificate serial number ' '{serial} in request {key} failed: {error}') continue try: if result['result']['revoked']: reason = result['result']['revocation_reason'] reason_txt = self.revocation_reason[reason] yield Result(self, constants.ERROR, revocation_reason=reason_txt, key=id, msg='Certificate tracked by {key} is revoked ' '{revocation_reason}') else: yield Result(self, constants.SUCCESS, key=id) except Exception as e: yield Result(self, constants.ERROR, key=id, error=str(e), msg='Unable to determine revocation ' 'status for {key}: {error}')
def check(self): fqdn = socket.getfqdn() requests = get_expected_requests(self.ca, self.ds, self.serverid) for request in requests: request_id = certmonger.get_request_id(request) if request_id is None: yield Result(self, constants.ERROR, key=request_id, msg='Found request id {key} but it is not tracked' 'by certmonger!?') continue ca_name = certmonger.get_request_value(request_id, 'ca-name') if ca_name != 'IPA': logger.debug('Skipping request %s with CA %s', request_id, ca_name) continue profile = certmonger.get_request_value(request_id, 'template_profile') if profile != 'caIPAserviceCert': logger.debug('Skipping request %s with profile %s', request_id, profile) continue certfile = None if request.get('cert-file') is not None: certfile = request.get('cert-file') try: cert = x509.load_certificate_from_file(certfile) except Exception as e: yield Result(self, constants.ERROR, key=request_id, certfile=certfile, error=str(e), msg='Unable to open cert file {certfile}: ' '{error}') continue elif request.get('cert-database') is not None: nickname = request.get('cert-nickname') dbdir = request.get('cert-database') try: db = certdb.NSSDatabase(dbdir) except Exception as e: yield Result(self, constants.ERROR, key=request_id, dbdir=dbdir, error=str(e), msg='Unable to open NSS database {dbdir}: ' '{error}') continue try: cert = db.get_cert(nickname) except Exception as e: yield Result(self, constants.ERROR, key=id, dbdir=dbdir, nickname=nickname, error=str(e), msg='Unable to retrieve certificate ' '\'{nickname}\' from {dbdir}: {error}') continue hostlist = [fqdn] if self.ca.is_configured() and certfile == paths.HTTPD_CERT_FILE: hostlist.append(f'{IPA_CA_RECORD}.{api.env.domain}') error = False for host in hostlist: if host not in cert.san_a_label_dns_names: error = True yield Result(self, constants.ERROR, key=request_id, hostname=host, san=cert.san_a_label_dns_names, ca=ca_name, profile=profile, msg='Certificate request id {key} with ' 'profile {profile} for CA {ca} does not ' 'have a DNS SAN {san} matching name ' '{hostname}') if not error: yield Result(self, constants.SUCCESS, key=request_id, hostname=hostlist, san=cert.san_a_label_dns_names, ca=ca_name, profile=profile)
def execute(self, **options): ca = cainstance.CAInstance(self.api.env.realm, certs.NSS_DIR) if not ca.is_configured(): self.debug("CA is not configured on this host") return False, [] ldap = self.api.Backend.ldap2 base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), self.api.env.basedn) dn = DN(('cn', 'CA'), ('cn', self.api.env.host), base_dn) filter = '(&(cn=CA)(ipaConfigString=caRenewalMaster))' try: entries = ldap.get_entries(base_dn=base_dn, filter=filter, attrs_list=[]) except errors.NotFound: pass else: self.debug("found CA renewal master %s", entries[0].dn[1].value) master = False updates = [] for entry in entries: if entry.dn == dn: master = True continue updates.append({ 'dn': entry.dn, 'updates': [ dict(action='remove', attr='ipaConfigString', value='caRenewalMaster') ], }) if master: return False, updates else: return False, [] criteria = { 'cert-database': paths.HTTPD_ALIAS_DIR, 'cert-nickname': 'ipaCert', } request_id = certmonger.get_request_id(criteria) if request_id is not None: self.debug("found certmonger request for ipaCert") ca_name = certmonger.get_request_value(request_id, 'ca-name') if ca_name is None: self.warning( "certmonger request for ipaCert is missing ca_name, " "assuming local CA is renewal slave") return False, [] ca_name = ca_name.strip() if ca_name == 'dogtag-ipa-renew-agent': pass elif ca_name == 'dogtag-ipa-retrieve-agent-submit': return False, [] elif ca_name == 'dogtag-ipa-ca-renew-agent': return False, [] else: self.warning( "certmonger request for ipaCert has unknown ca_name '%s', " "assuming local CA is renewal slave", ca_name) return False, [] else: self.debug("certmonger request for ipaCert not found") config = installutils.get_directive(paths.CA_CS_CFG_PATH, 'subsystem.select', '=') if config == 'New': pass elif config == 'Clone': return False, [] else: self.warning( "CS.cfg has unknown subsystem.select value '%s', " "assuming local CA is renewal slave", config) return (False, False, []) update = { 'dn': dn, 'updates': [ dict(action='add', attr='ipaConfigString', value='caRenewalMaster') ], } return False, [update]
def execute(self, **options): ca = cainstance.CAInstance(self.api.env.realm) if not ca.is_configured(): logger.debug("CA is not configured on this host") return False, [] ldap = self.api.Backend.ldap2 base_dn = DN(self.api.env.container_masters, self.api.env.basedn) dn = DN(('cn', 'CA'), ('cn', self.api.env.host), base_dn) filter = '(&(cn=CA)(ipaConfigString=caRenewalMaster))' try: entries = ldap.get_entries(base_dn=base_dn, filter=filter, attrs_list=[]) except errors.NotFound: pass else: logger.debug("found CA renewal master %s", entries[0].dn[1].value) master = False updates = [] for entry in entries: if entry.dn == dn: master = True continue updates.append({ 'dn': entry.dn, 'updates': [ dict(action='remove', attr='ipaConfigString', value='caRenewalMaster') ], }) if master: return False, updates else: return False, [] criteria = { 'cert-file': paths.RA_AGENT_PEM, } request_id = certmonger.get_request_id(criteria) if request_id is not None: logger.debug("found certmonger request for RA cert") ca_name = certmonger.get_request_value(request_id, 'ca-name') if ca_name is None: logger.warning( "certmonger request for RA cert is missing ca_name, " "assuming local CA is renewal slave") return False, [] ca_name = ca_name.strip() if ca_name == 'dogtag-ipa-renew-agent': pass elif ca_name == 'dogtag-ipa-retrieve-agent-submit': return False, [] elif ca_name == 'dogtag-ipa-ca-renew-agent': return False, [] else: logger.warning( "certmonger request for RA cert has unknown ca_name '%s', " "assuming local CA is renewal slave", ca_name) return False, [] else: logger.debug("certmonger request for RA cert not found") config = directivesetter.get_directive( paths.CA_CS_CFG_PATH, 'subsystem.select', '=') if config == 'New': pass elif config == 'Clone': return False, [] else: logger.warning( "CS.cfg has unknown subsystem.select value '%s', " "assuming local CA is renewal slave", config) return (False, False, []) update = { 'dn': dn, 'updates': [ dict(action='add', attr='ipaConfigString', value='caRenewalMaster') ], } return False, [update]