diff options
Diffstat (limited to 'roles/openshift_certificate_expiry/library/openshift_cert_expiry.py')
-rw-r--r-- | roles/openshift_certificate_expiry/library/openshift_cert_expiry.py | 839 |
1 files changed, 0 insertions, 839 deletions
diff --git a/roles/openshift_certificate_expiry/library/openshift_cert_expiry.py b/roles/openshift_certificate_expiry/library/openshift_cert_expiry.py deleted file mode 100644 index e355266b0..000000000 --- a/roles/openshift_certificate_expiry/library/openshift_cert_expiry.py +++ /dev/null @@ -1,839 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# pylint: disable=line-too-long,invalid-name - -"""For details on this module see DOCUMENTATION (below)""" - -import base64 -import datetime -import io -import os -import subprocess -import yaml - -# pylint import-error disabled because pylint cannot find the package -# when installed in a virtualenv -from ansible.module_utils.six.moves import configparser # pylint: disable=import-error -from ansible.module_utils.basic import AnsibleModule - -try: - # You can comment this import out and include a 'pass' in this - # block if you're manually testing this module on a NON-ATOMIC - # HOST (or any host that just doesn't have PyOpenSSL - # available). That will force the `load_and_handle_cert` function - # to use the Fake OpenSSL classes. - import OpenSSL.crypto - HAS_OPENSSL = True -except ImportError: - # Some platforms (such as RHEL Atomic) may not have the Python - # OpenSSL library installed. In this case we will use a manual - # work-around to parse each certificate. - # - # Check for 'OpenSSL.crypto' in `sys.modules` later. - HAS_OPENSSL = False - -DOCUMENTATION = ''' ---- -module: openshift_cert_expiry -short_description: Check OpenShift Container Platform (OCP) and Kube certificate expirations on a cluster -description: - - The M(openshift_cert_expiry) module has two basic functions: to flag certificates which will expire in a set window of time from now, and to notify you about certificates which have already expired. - - When the module finishes, a summary of the examination is returned. Each certificate in the summary has a C(health) key with a value of one of the following: - - C(ok) - not expired, and outside of the expiration C(warning_days) window. - - C(warning) - not expired, but will expire between now and the C(warning_days) window. - - C(expired) - an expired certificate. - - Certificate flagging follow this logic: - - If the expiration date is before now then the certificate is classified as C(expired). - - The certificates time to live (expiration date - now) is calculated, if that time window is less than C(warning_days) the certificate is classified as C(warning). - - All other conditions are classified as C(ok). - - The following keys are ALSO present in the certificate summary: - - C(cert_cn) - The common name of the certificate (additional CNs present in SAN extensions are omitted) - - C(days_remaining) - The number of days until the certificate expires. - - C(expiry) - The date the certificate expires on. - - C(path) - The full path to the certificate on the examined host. -version_added: "1.0" -options: - config_base: - description: - - Base path to OCP system settings. - required: false - default: /etc/origin - warning_days: - description: - - Flag certificates which will expire in C(warning_days) days from now. - required: false - default: 30 - show_all: - description: - - Enable this option to show analysis of ALL certificates examined by this module. - - By default only certificates which have expired, or will expire within the C(warning_days) window will be reported. - required: false - default: false - -author: "Tim Bielawa (@tbielawa) <tbielawa@redhat.com>" -''' - -EXAMPLES = ''' -# Default invocation, only notify about expired certificates or certificates which will expire within 30 days from now -- openshift_cert_expiry: - -# Expand the warning window to show certificates expiring within a year from now -- openshift_cert_expiry: warning_days=365 - -# Show expired, soon to expire (now + 30 days), and all other certificates examined -- openshift_cert_expiry: show_all=true -''' - - -class FakeOpenSSLCertificate(object): - """This provides a rough mock of what you get from -`OpenSSL.crypto.load_certificate()`. This is a work-around for -platforms missing the Python OpenSSL library. - """ - def __init__(self, cert_string): - """`cert_string` is a certificate in the form you get from running a -.crt through 'openssl x509 -in CERT.cert -text'""" - self.cert_string = cert_string - self.serial = None - self.subject = None - self.extensions = [] - self.not_after = None - self._parse_cert() - - def _parse_cert(self): - """Manually parse the certificate line by line""" - self.extensions = [] - - PARSING_ALT_NAMES = False - PARSING_HEX_SERIAL = False - for line in self.cert_string.split('\n'): - l = line.strip() - if PARSING_ALT_NAMES: - # We're parsing a 'Subject Alternative Name' line - self.extensions.append( - FakeOpenSSLCertificateSANExtension(l)) - - PARSING_ALT_NAMES = False - continue - - if PARSING_HEX_SERIAL: - # Hex serials arrive colon-delimited - serial_raw = l.replace(':', '') - # Convert to decimal - self.serial = int('0x' + serial_raw, base=16) - PARSING_HEX_SERIAL = False - continue - - # parse out the bits that we can - if l.startswith('Serial Number:'): - # Decimal format: - # Serial Number: 11 (0xb) - # => 11 - # Hex Format (large serials): - # Serial Number: - # 0a:de:eb:24:04:75:ab:56:39:14:e9:5a:22:e2:85:bf - # => 14449739080294792594019643629255165375 - if l.endswith(':'): - PARSING_HEX_SERIAL = True - continue - self.serial = int(l.split()[-2]) - - elif l.startswith('Not After :'): - # Not After : Feb 7 18:19:35 2019 GMT - # => strptime(str, '%b %d %H:%M:%S %Y %Z') - # => strftime('%Y%m%d%H%M%SZ') - # => 20190207181935Z - not_after_raw = l.partition(' : ')[-1] - # Last item: ('Not After', ' : ', 'Feb 7 18:19:35 2019 GMT') - not_after_parsed = datetime.datetime.strptime(not_after_raw, '%b %d %H:%M:%S %Y %Z') - self.not_after = not_after_parsed.strftime('%Y%m%d%H%M%SZ') - - elif l.startswith('X509v3 Subject Alternative Name:'): - PARSING_ALT_NAMES = True - continue - - elif l.startswith('Subject:'): - # O = system:nodes, CN = system:node:m01.example.com - self.subject = FakeOpenSSLCertificateSubjects(l.partition(': ')[-1]) - - def get_serial_number(self): - """Return the serial number of the cert""" - return self.serial - - def get_subject(self): - """Subjects must implement get_components() and return dicts or -tuples. An 'openssl x509 -in CERT.cert -text' with 'Subject': - - Subject: Subject: O=system:nodes, CN=system:node:m01.example.com - -might return: [('O=system', 'nodes'), ('CN=system', 'node:m01.example.com')] - """ - return self.subject - - def get_extension(self, i): - """Extensions must implement get_short_name() and return the string -'subjectAltName'""" - return self.extensions[i] - - def get_extension_count(self): - """ get_extension_count """ - return len(self.extensions) - - def get_notAfter(self): - """Returns a date stamp as a string in the form -'20180922170439Z'. strptime the result with format param: -'%Y%m%d%H%M%SZ'.""" - return self.not_after - - -class FakeOpenSSLCertificateSANExtension(object): # pylint: disable=too-few-public-methods - """Mocks what happens when `get_extension` is called on a certificate -object""" - - def __init__(self, san_string): - """With `san_string` as you get from: - - $ openssl x509 -in certificate.crt -text - """ - self.san_string = san_string - self.short_name = 'subjectAltName' - - def get_short_name(self): - """Return the 'type' of this extension. It's always the same though -because we only care about subjectAltName's""" - return self.short_name - - def __str__(self): - """Return this extension and the value as a simple string""" - return self.san_string - - -# pylint: disable=too-few-public-methods -class FakeOpenSSLCertificateSubjects(object): - """Mocks what happens when `get_subject` is called on a certificate -object""" - - def __init__(self, subject_string): - """With `subject_string` as you get from: - - $ openssl x509 -in certificate.crt -text - """ - self.subjects = [] - for s in subject_string.split(', '): - name, _, value = s.partition(' = ') - self.subjects.append((name, value)) - - def get_components(self): - """Returns a list of tuples""" - return self.subjects - - -###################################################################### -def filter_paths(path_list): - """`path_list` - A list of file paths to check. Only files which exist -will be returned - """ - return [p for p in path_list if os.path.exists(os.path.realpath(p))] - - -# pylint: disable=too-many-locals,too-many-branches -# -# TODO: Break this function down into smaller chunks -def load_and_handle_cert(cert_string, now, base64decode=False, ans_module=None): - """Load a certificate, split off the good parts, and return some -useful data - -Params: - -- `cert_string` (string) - a certificate loaded into a string object -- `now` (datetime) - a datetime object of the time to calculate the certificate 'time_remaining' against -- `base64decode` (bool) - run base64.b64decode() on the input -- `ans_module` (AnsibleModule) - The AnsibleModule object for this module (so we can raise errors) - -Returns: -A tuple of the form: - (cert_subject, cert_expiry_date, time_remaining, cert_serial_number) - """ - if base64decode: - _cert_string = base64.b64decode(cert_string).decode('utf-8') - else: - _cert_string = cert_string - - # Disable this. We 'redefine' the type because we are working - # around a missing library on the target host. - # - # pylint: disable=redefined-variable-type - if HAS_OPENSSL: - # No work-around required - cert_loaded = OpenSSL.crypto.load_certificate( - OpenSSL.crypto.FILETYPE_PEM, _cert_string) - else: - # Missing library, work-around required. Run the 'openssl' - # command on it to decode it - cmd = 'openssl x509 -text' - try: - openssl_proc = subprocess.Popen(cmd.split(), - stdout=subprocess.PIPE, - stdin=subprocess.PIPE) - except OSError: - ans_module.fail_json(msg="Error: The 'OpenSSL' python library and CLI command were not found on the target host. Unable to parse any certificates. This host will not be included in generated reports.") - else: - openssl_decoded = openssl_proc.communicate(_cert_string.encode('utf-8'))[0].decode('utf-8') - cert_loaded = FakeOpenSSLCertificate(openssl_decoded) - - ###################################################################### - # Read all possible names from the cert - cert_subjects = [] - for name, value in cert_loaded.get_subject().get_components(): - if isinstance(name, bytes) or isinstance(value, bytes): - name = name.decode('utf-8') - value = value.decode('utf-8') - cert_subjects.append('{}:{}'.format(name, value)) - - # To read SANs from a cert we must read the subjectAltName - # extension from the X509 Object. What makes this more difficult - # is that pyOpenSSL does not give extensions as an iterable - san = None - for i in range(cert_loaded.get_extension_count()): - ext = cert_loaded.get_extension(i) - if ext.get_short_name() == 'subjectAltName': - san = ext - - if san is not None: - # The X509Extension object for subjectAltName prints as a - # string with the alt names separated by a comma and a - # space. Split the string by ', ' and then add our new names - # to the list of existing names - cert_subjects.extend(str(san).split(', ')) - - cert_subject = ', '.join(cert_subjects) - ###################################################################### - - # Grab the expiration date - not_after = cert_loaded.get_notAfter() - # example get_notAfter() => 20180922170439Z - if isinstance(not_after, bytes): - not_after = not_after.decode('utf-8') - - cert_expiry_date = datetime.datetime.strptime( - not_after, - '%Y%m%d%H%M%SZ') - - time_remaining = cert_expiry_date - now - - return (cert_subject, cert_expiry_date, time_remaining, cert_loaded.get_serial_number()) - - -def classify_cert(cert_meta, now, time_remaining, expire_window, cert_list): - """Given metadata about a certificate under examination, classify it - into one of three categories, 'ok', 'warning', and 'expired'. - -Params: - -- `cert_meta` dict - A dict with certificate metadata. Required fields - include: 'cert_cn', 'path', 'expiry', 'days_remaining', 'health'. -- `now` (datetime) - a datetime object of the time to calculate the certificate 'time_remaining' against -- `time_remaining` (datetime.timedelta) - a timedelta for how long until the cert expires -- `expire_window` (datetime.timedelta) - a timedelta for how long the warning window is -- `cert_list` list - A list to shove the classified cert into - -Return: -- `cert_list` - The updated list of classified certificates - """ - expiry_str = str(cert_meta['expiry']) - # Categorization - if cert_meta['expiry'] < now: - # This already expired, must NOTIFY - cert_meta['health'] = 'expired' - elif time_remaining < expire_window: - # WARN about this upcoming expirations - cert_meta['health'] = 'warning' - else: - # Not expired or about to expire - cert_meta['health'] = 'ok' - - cert_meta['expiry'] = expiry_str - cert_meta['serial_hex'] = hex(int(cert_meta['serial'])) - cert_list.append(cert_meta) - return cert_list - - -def tabulate_summary(certificates, kubeconfigs, etcd_certs, router_certs, registry_certs): - """Calculate the summary text for when the module finishes -running. This includes counts of each classification and what have -you. - -Params: - -- `certificates` (list of dicts) - Processed `expire_check_result` - dicts with filled in `health` keys for system certificates. -- `kubeconfigs` - as above for kubeconfigs -- `etcd_certs` - as above for etcd certs - -Return: - -- `summary_results` (dict) - Counts of each cert type classification - and total items examined. - """ - items = certificates + kubeconfigs + etcd_certs + router_certs + registry_certs - - summary_results = { - 'system_certificates': len(certificates), - 'kubeconfig_certificates': len(kubeconfigs), - 'etcd_certificates': len(etcd_certs), - 'router_certs': len(router_certs), - 'registry_certs': len(registry_certs), - 'total': len(items), - 'ok': 0, - 'warning': 0, - 'expired': 0 - } - - summary_results['expired'] = len([c for c in items if c['health'] == 'expired']) - summary_results['warning'] = len([c for c in items if c['health'] == 'warning']) - summary_results['ok'] = len([c for c in items if c['health'] == 'ok']) - - return summary_results - - -###################################################################### -# This is our module MAIN function after all, so there's bound to be a -# lot of code bundled up into one block -# -# Reason: These checks are disabled because the issue was introduced -# during a period where the pylint checks weren't enabled for this file -# Status: temporarily disabled pending future refactoring -# pylint: disable=too-many-locals,too-many-statements,too-many-branches -def main(): - """This module examines certificates (in various forms) which compose -an OpenShift Container Platform cluster - """ - - module = AnsibleModule( - argument_spec=dict( - config_base=dict( - required=False, - default="/etc/origin", - type='str'), - warning_days=dict( - required=False, - default=30, - type='int'), - show_all=dict( - required=False, - default=False, - type='bool') - ), - supports_check_mode=True, - ) - - # Basic scaffolding for OpenShift specific certs - openshift_base_config_path = os.path.realpath(module.params['config_base']) - openshift_master_config_path = os.path.join(openshift_base_config_path, - "master", "master-config.yaml") - openshift_node_config_path = os.path.join(openshift_base_config_path, - "node", "node-config.yaml") - openshift_cert_check_paths = [ - openshift_master_config_path, - openshift_node_config_path, - ] - - # Paths for Kubeconfigs. Additional kubeconfigs are conditionally - # checked later in the code - master_kube_configs = ['admin', 'openshift-master', - 'openshift-node', 'openshift-router', - 'openshift-registry'] - - kubeconfig_paths = [] - for m_kube_config in master_kube_configs: - kubeconfig_paths.append( - os.path.join(openshift_base_config_path, "master", m_kube_config + ".kubeconfig") - ) - - # Validate some paths we have the ability to do ahead of time - openshift_cert_check_paths = filter_paths(openshift_cert_check_paths) - kubeconfig_paths = filter_paths(kubeconfig_paths) - - # etcd, where do you hide your certs? Used when parsing etcd.conf - etcd_cert_params = [ - "ETCD_CA_FILE", - "ETCD_CERT_FILE", - "ETCD_PEER_CA_FILE", - "ETCD_PEER_CERT_FILE", - ] - - # Expiry checking stuff - now = datetime.datetime.now() - # todo, catch exception for invalid input and return a fail_json - warning_days = int(module.params['warning_days']) - expire_window = datetime.timedelta(days=warning_days) - - # Module stuff - # - # The results of our cert checking to return from the task call - check_results = {} - check_results['meta'] = {} - check_results['meta']['warning_days'] = warning_days - check_results['meta']['checked_at_time'] = str(now) - check_results['meta']['warn_before_date'] = str(now + expire_window) - check_results['meta']['show_all'] = str(module.params['show_all']) - # All the analyzed certs accumulate here - ocp_certs = [] - - ###################################################################### - # Sure, why not? Let's enable check mode. - if module.check_mode: - check_results['ocp_certs'] = [] - module.exit_json( - check_results=check_results, - msg="Checked 0 total certificates. Expired/Warning/OK: 0/0/0. Warning window: %s days" % module.params['warning_days'], - rc=0, - changed=False - ) - - ###################################################################### - # Check for OpenShift Container Platform specific certs - ###################################################################### - for os_cert in filter_paths(openshift_cert_check_paths): - # Open up that config file and locate the cert and CA - with io.open(os_cert, 'r', encoding='utf-8') as fp: - cert_meta = {} - cfg = yaml.load(fp) - # cert files are specified in parsed `fp` as relative to the path - # of the original config file. 'master-config.yaml' with certFile - # = 'foo.crt' implies that 'foo.crt' is in the same - # directory. certFile = '../foo.crt' is in the parent directory. - cfg_path = os.path.dirname(fp.name) - cert_meta['certFile'] = os.path.join(cfg_path, cfg['servingInfo']['certFile']) - cert_meta['clientCA'] = os.path.join(cfg_path, cfg['servingInfo']['clientCA']) - - ###################################################################### - # Load the certificate and the CA, parse their expiration dates into - # datetime objects so we can manipulate them later - for v in cert_meta.values(): - with io.open(v, 'r', encoding='utf-8') as fp: - cert = fp.read() - (cert_subject, - cert_expiry_date, - time_remaining, - cert_serial) = load_and_handle_cert(cert, now, ans_module=module) - - expire_check_result = { - 'cert_cn': cert_subject, - 'path': fp.name, - 'expiry': cert_expiry_date, - 'days_remaining': time_remaining.days, - 'health': None, - 'serial': cert_serial - } - - classify_cert(expire_check_result, now, time_remaining, expire_window, ocp_certs) - - ###################################################################### - # /Check for OpenShift Container Platform specific certs - ###################################################################### - - ###################################################################### - # Check service Kubeconfigs - ###################################################################### - kubeconfigs = [] - - # There may be additional kubeconfigs to check, but their naming - # is less predictable than the ones we've already assembled. - - try: - # Try to read the standard 'node-config.yaml' file to check if - # this host is a node. - with io.open(openshift_node_config_path, 'r', encoding='utf-8') as fp: - cfg = yaml.load(fp) - - # OK, the config file exists, therefore this is a - # node. Nodes have their own kubeconfig files to - # communicate with the master API. Let's read the relative - # path to that file from the node config. - node_masterKubeConfig = cfg['masterKubeConfig'] - # As before, the path to the 'masterKubeConfig' file is - # relative to `fp` - cfg_path = os.path.dirname(fp.name) - node_kubeconfig = os.path.join(cfg_path, node_masterKubeConfig) - - with io.open(node_kubeconfig, 'r', encoding='utf8') as fp: - # Read in the nodes kubeconfig file and grab the good stuff - cfg = yaml.load(fp) - - c = cfg['users'][0]['user']['client-certificate-data'] - (cert_subject, - cert_expiry_date, - time_remaining, - cert_serial) = load_and_handle_cert(c, now, base64decode=True, ans_module=module) - - expire_check_result = { - 'cert_cn': cert_subject, - 'path': fp.name, - 'expiry': cert_expiry_date, - 'days_remaining': time_remaining.days, - 'health': None, - 'serial': cert_serial - } - - classify_cert(expire_check_result, now, time_remaining, expire_window, kubeconfigs) - except IOError: - # This is not a node - pass - - for kube in filter_paths(kubeconfig_paths): - with io.open(kube, 'r', encoding='utf-8') as fp: - # TODO: Maybe consider catching exceptions here? - cfg = yaml.load(fp) - - # Per conversation, "the kubeconfigs you care about: - # admin, router, registry should all be single - # value". Following that advice we only grab the data for - # the user at index 0 in the 'users' list. There should - # not be more than one user. - c = cfg['users'][0]['user']['client-certificate-data'] - (cert_subject, - cert_expiry_date, - time_remaining, - cert_serial) = load_and_handle_cert(c, now, base64decode=True, ans_module=module) - - expire_check_result = { - 'cert_cn': cert_subject, - 'path': fp.name, - 'expiry': cert_expiry_date, - 'days_remaining': time_remaining.days, - 'health': None, - 'serial': cert_serial - } - - classify_cert(expire_check_result, now, time_remaining, expire_window, kubeconfigs) - - ###################################################################### - # /Check service Kubeconfigs - ###################################################################### - - ###################################################################### - # Check etcd certs - # - # Two things to check: 'external' etcd, and embedded etcd. - ###################################################################### - # FIRST: The 'external' etcd - # - # Some values may be duplicated, make this a set for now so we - # unique them all - etcd_certs_to_check = set([]) - etcd_certs = [] - etcd_cert_params.append('dne') - try: - with io.open('/etc/etcd/etcd.conf', 'r', encoding='utf-8') as fp: - # Add dummy header section. - config = io.StringIO() - config.write(u'[ETCD]\n') - config.write(fp.read().replace('%', '%%')) - config.seek(0, os.SEEK_SET) - - etcd_config = configparser.ConfigParser() - etcd_config.readfp(config) - - for param in etcd_cert_params: - try: - etcd_certs_to_check.add(etcd_config.get('ETCD', param)) - except configparser.NoOptionError: - # That parameter does not exist, oh well... - pass - except IOError: - # No etcd to see here, move along - pass - - for etcd_cert in filter_paths(etcd_certs_to_check): - with io.open(etcd_cert, 'r', encoding='utf-8') as fp: - c = fp.read() - (cert_subject, - cert_expiry_date, - time_remaining, - cert_serial) = load_and_handle_cert(c, now, ans_module=module) - - expire_check_result = { - 'cert_cn': cert_subject, - 'path': fp.name, - 'expiry': cert_expiry_date, - 'days_remaining': time_remaining.days, - 'health': None, - 'serial': cert_serial - } - - classify_cert(expire_check_result, now, time_remaining, expire_window, etcd_certs) - - ###################################################################### - # Now the embedded etcd - ###################################################################### - try: - with io.open('/etc/origin/master/master-config.yaml', 'r', encoding='utf-8') as fp: - cfg = yaml.load(fp) - except IOError: - # Not present - pass - else: - if cfg.get('etcdConfig', {}).get('servingInfo', {}).get('certFile', None) is not None: - # This is embedded - etcd_crt_name = cfg['etcdConfig']['servingInfo']['certFile'] - else: - # Not embedded - etcd_crt_name = None - - if etcd_crt_name is not None: - # etcd_crt_name is relative to the location of the - # master-config.yaml file - cfg_path = os.path.dirname(fp.name) - etcd_cert = os.path.join(cfg_path, etcd_crt_name) - with open(etcd_cert, 'r') as etcd_fp: - (cert_subject, - cert_expiry_date, - time_remaining, - cert_serial) = load_and_handle_cert(etcd_fp.read(), now, ans_module=module) - - expire_check_result = { - 'cert_cn': cert_subject, - 'path': etcd_fp.name, - 'expiry': cert_expiry_date, - 'days_remaining': time_remaining.days, - 'health': None, - 'serial': cert_serial - } - - classify_cert(expire_check_result, now, time_remaining, expire_window, etcd_certs) - - ###################################################################### - # /Check etcd certs - ###################################################################### - - ###################################################################### - # Check router/registry certs - # - # These are saved as secrets in etcd. That means that we can not - # simply read a file to grab the data. Instead we're going to - # subprocess out to the 'oc get' command. On non-masters this - # command will fail, that is expected so we catch that exception. - ###################################################################### - router_certs = [] - registry_certs = [] - - ###################################################################### - # First the router certs - try: - router_secrets_raw = subprocess.Popen('oc get -n default secret router-certs -o yaml'.split(), - stdout=subprocess.PIPE) - router_ds = yaml.load(router_secrets_raw.communicate()[0]) - router_c = router_ds['data']['tls.crt'] - router_path = router_ds['metadata']['selfLink'] - except TypeError: - # YAML couldn't load the result, this is not a master - pass - except OSError: - # The OC command doesn't exist here. Move along. - pass - else: - (cert_subject, - cert_expiry_date, - time_remaining, - cert_serial) = load_and_handle_cert(router_c, now, base64decode=True, ans_module=module) - - expire_check_result = { - 'cert_cn': cert_subject, - 'path': router_path, - 'expiry': cert_expiry_date, - 'days_remaining': time_remaining.days, - 'health': None, - 'serial': cert_serial - } - - classify_cert(expire_check_result, now, time_remaining, expire_window, router_certs) - - ###################################################################### - # Now for registry - try: - registry_secrets_raw = subprocess.Popen('oc get -n default secret registry-certificates -o yaml'.split(), - stdout=subprocess.PIPE) - registry_ds = yaml.load(registry_secrets_raw.communicate()[0]) - registry_c = registry_ds['data']['registry.crt'] - registry_path = registry_ds['metadata']['selfLink'] - except TypeError: - # YAML couldn't load the result, this is not a master - pass - except OSError: - # The OC command doesn't exist here. Move along. - pass - else: - (cert_subject, - cert_expiry_date, - time_remaining, - cert_serial) = load_and_handle_cert(registry_c, now, base64decode=True, ans_module=module) - - expire_check_result = { - 'cert_cn': cert_subject, - 'path': registry_path, - 'expiry': cert_expiry_date, - 'days_remaining': time_remaining.days, - 'health': None, - 'serial': cert_serial - } - - classify_cert(expire_check_result, now, time_remaining, expire_window, registry_certs) - - ###################################################################### - # /Check router/registry certs - ###################################################################### - - res = tabulate_summary(ocp_certs, kubeconfigs, etcd_certs, router_certs, registry_certs) - - msg = "Checked {count} total certificates. Expired/Warning/OK: {exp}/{warn}/{ok}. Warning window: {window} days".format( - count=res['total'], - exp=res['expired'], - warn=res['warning'], - ok=res['ok'], - window=int(module.params['warning_days']), - ) - - # By default we only return detailed information about expired or - # warning certificates. If show_all is true then we will print all - # the certificates examined. - if not module.params['show_all']: - check_results['ocp_certs'] = [crt for crt in ocp_certs if crt['health'] in ['expired', 'warning']] - check_results['kubeconfigs'] = [crt for crt in kubeconfigs if crt['health'] in ['expired', 'warning']] - check_results['etcd'] = [crt for crt in etcd_certs if crt['health'] in ['expired', 'warning']] - check_results['registry'] = [crt for crt in registry_certs if crt['health'] in ['expired', 'warning']] - check_results['router'] = [crt for crt in router_certs if crt['health'] in ['expired', 'warning']] - else: - check_results['ocp_certs'] = ocp_certs - check_results['kubeconfigs'] = kubeconfigs - check_results['etcd'] = etcd_certs - check_results['registry'] = registry_certs - check_results['router'] = router_certs - - # Sort the final results to report in order of ascending safety - # time. That is to say, the certificates which will expire sooner - # will be at the front of the list and certificates which will - # expire later are at the end. Router and registry certs should be - # limited to just 1 result, so don't bother sorting those. - def cert_key(item): - ''' return the days_remaining key ''' - return item['days_remaining'] - - check_results['ocp_certs'] = sorted(check_results['ocp_certs'], key=cert_key) - check_results['kubeconfigs'] = sorted(check_results['kubeconfigs'], key=cert_key) - check_results['etcd'] = sorted(check_results['etcd'], key=cert_key) - - # This module will never change anything, but we might want to - # change the return code parameter if there is some catastrophic - # error we noticed earlier - module.exit_json( - check_results=check_results, - summary=res, - msg=msg, - rc=0, - changed=False - ) - - -if __name__ == '__main__': - main() |