summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Bielawa <tbielawa@redhat.com>2016-10-06 10:01:48 -0700
committerTim Bielawa <tbielawa@redhat.com>2016-10-20 07:49:40 -0700
commit4273b21105dd11f52de354b4777d33e4296ba7e0 (patch)
treebc8cdda1e6147e2b0020feb3544217ab5422a464
parent5f7f6a6023c470337f0d879f55eb619fd63e2dbe (diff)
downloadopenshift-4273b21105dd11f52de354b4777d33e4296ba7e0.tar.gz
openshift-4273b21105dd11f52de354b4777d33e4296ba7e0.tar.bz2
openshift-4273b21105dd11f52de354b4777d33e4296ba7e0.tar.xz
openshift-4273b21105dd11f52de354b4777d33e4296ba7e0.zip
Get router/registry certs. Collect common names and subjectAltNames
-rw-r--r--library/openshift_cert_expiry.py167
-rw-r--r--playbooks/common/openshift-cluster/templates/cert-expiry-table.html.j266
2 files changed, 186 insertions, 47 deletions
diff --git a/library/openshift_cert_expiry.py b/library/openshift_cert_expiry.py
index 4e66de755..f18ab75d0 100644
--- a/library/openshift_cert_expiry.py
+++ b/library/openshift_cert_expiry.py
@@ -4,6 +4,8 @@
"""For details on this module see DOCUMENTATION (below)"""
+# router/registry cert grabbing
+import subprocess
# etcd config file
import ConfigParser
# Expiration parsing
@@ -15,7 +17,6 @@ import yaml
# Certificate loading
import OpenSSL.crypto
-
DOCUMENTATION = '''
---
module: openshift_cert_expiry
@@ -126,8 +127,59 @@ A 3-tuple of the form: (certificate_common_name, certificate_expiry_date, certif
cert_loaded = OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM, _cert_string)
+ ######################################################################
+ # Read just the first name from the cert - DISABLED while testing
+ # out the 'get all possible names' function (below)
+ #
# Strip the subject down to just the value of the first name
- cert_subject = cert_loaded.get_subject().get_components()[0][1]
+ # cert_subject = cert_loaded.get_subject().get_components()[0][1]
+
+ ######################################################################
+ # Read all possible names from the cert
+ cert_subjects = []
+ for name, value in cert_loaded.get_subject().get_components():
+ 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 a list, nor does
+ # it provide a count of all loaded extensions.
+ #
+ # Rather, extensions are REQUESTED by index. We must iterate over
+ # all extensions until we find the one called 'subjectAltName'. If
+ # we don't find that extension we'll eventually request an
+ # extension at an index where no extension exists (IndexError is
+ # raised). When that happens we know that the cert has no SANs so
+ # we break out of the loop.
+ i = 0
+ checked_all_extensions = False
+ while not checked_all_extensions:
+ try:
+ # Read the extension at index 'i'
+ ext = cert_loaded.get_extension(i)
+ except IndexError:
+ # We tried to read an extension but it isn't there, that
+ # means we ran out of extensions to check. Abort
+ san = None
+ checked_all_extensions = True
+ else:
+ # We were able to load the extension at index 'i'
+ if ext.get_short_name() == 'subjectAltName':
+ san = ext
+ checked_all_extensions = True
+ else:
+ # Try reading the next extension
+ i += 1
+
+ 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
cert_expiry = cert_loaded.get_notAfter()
@@ -174,7 +226,7 @@ Return:
return cert_list
-def tabulate_summary(certificates, kubeconfigs, etcd_certs):
+def tabulate_summary(certificates, kubeconfigs, etcd_certs, router_certs, registry_certs):
"""Calculate the summary text for when the module finishes
running. This includes counds of each classification and what have
you.
@@ -190,12 +242,14 @@ Return:
- `summary_results` (dict) - Counts of each cert type classification
and total items examined.
"""
- items = certificates + kubeconfigs + etcd_certs
+ 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,
@@ -213,7 +267,7 @@ Return:
# This is our module MAIN function after all, so there's bound to be a
# lot of code bundled up into one block
#
-# pylint: disable=too-many-locals,too-many-locals,too-many-statements
+# pylint: disable=too-many-locals,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
@@ -250,21 +304,19 @@ an OpenShift Container Platform cluster
openshift_node_config_path,
]
- # Paths for Kubeconfigs. Additional kubeconfigs are conditionally checked later in the code
- kubeconfig_paths = [
- os.path.normpath(
- os.path.join(openshift_base_config_path, "master/admin.kubeconfig")
- ),
- os.path.normpath(
- os.path.join(openshift_base_config_path, "master/openshift-master.kubeconfig")
- ),
- os.path.normpath(
- os.path.join(openshift_base_config_path, "master/openshift-node.kubeconfig")
- ),
- os.path.normpath(
- os.path.join(openshift_base_config_path, "master/openshift-router.kubeconfig")
- ),
- ]
+ # 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.normpath(
+ os.path.join(openshift_base_config_path, "master/%s.kubeconfig" % m_kube_config)
+ )
+ )
# etcd, where do you hide your certs? Used when parsing etcd.conf
etcd_cert_params = [
@@ -460,7 +512,80 @@ an OpenShift Container Platform cluster
# /Check etcd certs
######################################################################
- res = tabulate_summary(ocp_certs, kubeconfigs, 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 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
+ else:
+ (cert_subject,
+ cert_expiry_date,
+ time_remaining) = load_and_handle_cert(router_c, now, base64decode=True)
+
+ expire_check_result = {
+ 'cert_cn': cert_subject,
+ 'path': router_path,
+ 'expiry': cert_expiry_date,
+ 'days_remaining': time_remaining.days,
+ 'health': None,
+ }
+
+ classify_cert(expire_check_result, now, time_remaining, expire_window, router_certs)
+
+ check_results['router'] = router_certs
+
+ ######################################################################
+ # Now for registry
+ # registry_secrets = subprocess.call('oc get secret registry-certificates -o yaml'.split())
+ # out = subprocess.PIPE
+ try:
+ registry_secrets_raw = subprocess.Popen('oc get 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
+ else:
+ (cert_subject,
+ cert_expiry_date,
+ time_remaining) = load_and_handle_cert(registry_c, now, base64decode=True)
+
+ expire_check_result = {
+ 'cert_cn': cert_subject,
+ 'path': registry_path,
+ 'expiry': cert_expiry_date,
+ 'days_remaining': time_remaining.days,
+ 'health': None,
+ }
+
+ classify_cert(expire_check_result, now, time_remaining, expire_window, registry_certs)
+
+ check_results['registry'] = 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'],
diff --git a/playbooks/common/openshift-cluster/templates/cert-expiry-table.html.j2 b/playbooks/common/openshift-cluster/templates/cert-expiry-table.html.j2
index da7844c37..f74d7f1ce 100644
--- a/playbooks/common/openshift-cluster/templates/cert-expiry-table.html.j2
+++ b/playbooks/common/openshift-cluster/templates/cert-expiry-table.html.j2
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<title>OCP Certificate Expiry Report</title>
- {# For fancy icons #}
+ {# For fancy icons and a pleasing font #}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700" rel="stylesheet" />
<style type="text/css">
@@ -12,6 +12,7 @@
margin-left: 50px;
margin-right: 50px;
margin-bottom: 20px;
+ padding-top: 70px;
}
table {
border-collapse: collapse;
@@ -37,62 +38,75 @@
</style>
</head>
<body>
- <center><h1>OCP Certificate Expiry Report</h1></center>
-
- <hr />
+ <nav class="navbar navbar-default navbar-fixed-top">
+ <div class="container-fluid">
+ <div class="navbar-header">
+ <a class="navbar-brand" href="#">OCP Certificate Expiry Report</a>
+ </div>
+ <div class="collapse navbar-collapse">
+ <p class="navbar-text navbar-right">
+ <a href="https://docs.openshift.com/container-platform/latest/install_config/redeploying_certificates.html"
+ target="_blank"
+ class="navbar-link">
+ <i class="glyphicon glyphicon-book"></i> Redeploying Certificates
+ </a>
+ </p>
+ </div>
+ </div>
+ </nav>
{# Each host has a header and table to itself #}
{% for host in play_hosts %}
<h1>{{ host }}</h1>
<p>
- {{ hostvars[host].check_results.msg }}
+ {{ hostvars[host].check_results.msg }}
</p>
<ul>
- <li><b>Expirations checked at:</b> {{ hostvars[host].check_results.check_results.meta.checked_at_time }}</li>
- <li><b>Warn after date:</b> {{ hostvars[host].check_results.check_results.meta.warn_after_date }}</li>
+ <li><b>Expirations checked at:</b> {{ hostvars[host].check_results.check_results.meta.checked_at_time }}</li>
+ <li><b>Warn after date:</b> {{ hostvars[host].check_results.check_results.meta.warn_after_date }}</li>
</ul>
<table border="1" width="100%">
{# These are hard-coded right now, but should be grabbed dynamically from the registered results #}
- {%- for kind in ['ocp_certs', 'etcd', 'kubeconfigs'] -%}
+ {%- for kind in ['ocp_certs', 'etcd', 'kubeconfigs', 'router', 'registry'] -%}
<tr>
<th colspan="6" style="text-align:center"><h2 class="cert-kind">{{ kind }}</h2></th>
</tr>
<tr>
- <th>&nbsp;</th>
- <th>Certificate Common Name</th>
+ <th>&nbsp;</th>
+ <th style="width:33%">Certificate Common/Alt Name(s)</th>
<th>Health</th>
<th>Days Remaining</th>
<th>Expiration Date</th>
<th>Path</th>
</tr>
- {# A row for each certificate examined #}
+ {# A row for each certificate examined #}
{%- for v in hostvars[host].check_results.check_results[kind] -%}
- {# Let's add some flair and show status visually with fancy icons #}
- {% if v.health == 'ok' %}
- {% set health_icon = 'glyphicon glyphicon-ok' %}
- {% elif v.health == 'warning' %}
- {% set health_icon = 'glyphicon glyphicon-alert' %}
- {% else %}
- {% set health_icon = 'glyphicon glyphicon-remove' %}
- {% endif %}
+ {# Let's add some flair and show status visually with fancy icons #}
+ {% if v.health == 'ok' %}
+ {% set health_icon = 'glyphicon glyphicon-ok' %}
+ {% elif v.health == 'warning' %}
+ {% set health_icon = 'glyphicon glyphicon-alert' %}
+ {% else %}
+ {% set health_icon = 'glyphicon glyphicon-remove' %}
+ {% endif %}
- <tr class="{{ loop.cycle('odd', 'even') }}">
- <td style="text-align:center"><i class="{{ health_icon }}"></i></td>
- <td>{{ v.cert_cn }}</td>
+ <tr class="{{ loop.cycle('odd', 'even') }}">
+ <td style="text-align:center"><i class="{{ health_icon }}"></i></td>
+ <td style="width:33%">{{ v.cert_cn }}</td>
<td>{{ v.health }}</td>
<td>{{ v.days_remaining }}</td>
<td>{{ v.expiry }}</td>
<td>{{ v.path }}</td>
</tr>
{% endfor %}
- {# end row generation per cert of this type #}
+ {# end row generation per cert of this type #}
{% endfor %}
- {# end generation for each kind of cert block #}
+ {# end generation for each kind of cert block #}
</table>
<hr />
{% endfor %}
@@ -100,10 +114,10 @@
<footer>
<p>
- Expiration report generated by <a href="https://github.com/openshift/openshift-ansible" target="_blank">openshift-ansible</a>
+ Expiration report generated by <a href="https://github.com/openshift/openshift-ansible" target="_blank">openshift-ansible</a>
</p>
<p>
- Status icons from bootstrap/glyphicon
+ Status icons from bootstrap/glyphicon
</p>
</footer>
</body>