From 288e304643a6a02e6d90ed5c1b4e7f6b349ad929 Mon Sep 17 00:00:00 2001
From: Tim Bielawa <tbielawa@redhat.com>
Date: Mon, 20 Feb 2017 15:58:06 -0800
Subject: Implement fake openssl cert classes

---
 .../library/openshift_cert_expiry.py               | 197 +++++++++++++++++++--
 .../test/master.server.crt                         |  42 +++++
 .../test/master.server.crt.txt                     |  82 +++++++++
 .../test/system-node-m01.example.com.crt           |  19 ++
 .../test/system-node-m01.example.com.crt.txt       |  75 ++++++++
 .../test/test_fakeopensslclasses.py                |  86 +++++++++
 6 files changed, 485 insertions(+), 16 deletions(-)
 create mode 100644 roles/openshift_certificate_expiry/test/master.server.crt
 create mode 100644 roles/openshift_certificate_expiry/test/master.server.crt.txt
 create mode 100644 roles/openshift_certificate_expiry/test/system-node-m01.example.com.crt
 create mode 100644 roles/openshift_certificate_expiry/test/system-node-m01.example.com.crt.txt
 create mode 100644 roles/openshift_certificate_expiry/test/test_fakeopensslclasses.py

(limited to 'roles/openshift_certificate_expiry')

diff --git a/roles/openshift_certificate_expiry/library/openshift_cert_expiry.py b/roles/openshift_certificate_expiry/library/openshift_cert_expiry.py
index 85671b164..33930c0c1 100644
--- a/roles/openshift_certificate_expiry/library/openshift_cert_expiry.py
+++ b/roles/openshift_certificate_expiry/library/openshift_cert_expiry.py
@@ -5,13 +5,32 @@
 """For details on this module see DOCUMENTATION (below)"""
 
 import datetime
+import io
 import os
 import subprocess
+import sys
+import tempfile
 
+# File pointers from io.open require unicode inputs when using their
+# `write` method
+import six
 from six.moves import configparser
 
 import yaml
-import OpenSSL.crypto
+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
+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.
+    pass
 
 DOCUMENTATION = '''
 ---
@@ -66,6 +85,128 @@ EXAMPLES = '''
 '''
 
 
+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
+        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
+
+            # parse out the bits that we can
+            if l.startswith('Serial Number:'):
+                # Serial Number: 11 (0xb)
+                # => 11
+                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_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
+
+
 # We only need this for one thing, we don't care if it doesn't have
 # that many public methods
 #
@@ -100,7 +241,8 @@ will be returned
     return [p for p in path_list if os.path.exists(os.path.realpath(p))]
 
 
-def load_and_handle_cert(cert_string, now, base64decode=False):
+# pylint: disable=too-many-locals,too-many-branches
+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
 
@@ -109,6 +251,7 @@ 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 .decode('base64') on the input?
+- `ans_module` (AnsibleModule) - The AnsibleModule object for this module (so we can raise errors)
 
 Returns:
 A 3-tuple of the form: (certificate_common_name, certificate_expiry_date, certificate_time_remaining)
@@ -119,10 +262,33 @@ A 3-tuple of the form: (certificate_common_name, certificate_expiry_date, certif
     else:
         _cert_string = cert_string
 
-    cert_loaded = OpenSSL.crypto.load_certificate(
-        OpenSSL.crypto.FILETYPE_PEM, _cert_string)
-
-    cert_serial = cert_loaded.get_serial_number()
+    # Disable this. We 'redefine' the type because we are working
+    # around a missing library on the target host.
+    #
+    # pylint: disable=redefined-variable-type
+    if 'OpenSSL.crypto' in sys.modules:
+        # No work-around required
+        cert_loaded = OpenSSL.crypto.load_certificate(
+            OpenSSL.crypto.FILETYPE_PEM, _cert_string)
+    else:
+        # Missing library, work-around required. We need to write the
+        # cert out to disk temporarily so we can run the 'openssl'
+        # command on it to decode it
+        _, path = tempfile.mkstemp()
+        with io.open(path, 'w') as fp:
+            fp.write(six.u(_cert_string))
+            fp.flush()
+
+        cmd = 'openssl x509 -in {} -text'.format(path)
+        try:
+            openssl_decoded = subprocess.Popen(cmd.split(),
+                                               stdout=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_decoded.communicate()[0]
+            os.remove(path)
+            cert_loaded = FakeOpenSSLCertificate(openssl_decoded)
 
     ######################################################################
     # Read all possible names from the cert
@@ -172,15 +338,14 @@ A 3-tuple of the form: (certificate_common_name, certificate_expiry_date, certif
     ######################################################################
 
     # Grab the expiration date
-    cert_expiry = cert_loaded.get_notAfter()
     cert_expiry_date = datetime.datetime.strptime(
-        cert_expiry,
+        cert_loaded.get_notAfter(),
         # example get_notAfter() => 20180922170439Z
         '%Y%m%d%H%M%SZ')
 
     time_remaining = cert_expiry_date - now
 
-    return (cert_subject, cert_expiry_date, time_remaining, cert_serial)
+    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):
@@ -379,7 +544,7 @@ an OpenShift Container Platform cluster
                 (cert_subject,
                  cert_expiry_date,
                  time_remaining,
-                 cert_serial) = load_and_handle_cert(cert, now)
+                 cert_serial) = load_and_handle_cert(cert, now, ans_module=module)
 
                 expire_check_result = {
                     'cert_cn': cert_subject,
@@ -428,7 +593,7 @@ an OpenShift Container Platform cluster
         (cert_subject,
          cert_expiry_date,
          time_remaining,
-         cert_serial) = load_and_handle_cert(c, now, base64decode=True)
+         cert_serial) = load_and_handle_cert(c, now, base64decode=True, ans_module=module)
 
         expire_check_result = {
             'cert_cn': cert_subject,
@@ -458,7 +623,7 @@ an OpenShift Container Platform cluster
         (cert_subject,
          cert_expiry_date,
          time_remaining,
-         cert_serial) = load_and_handle_cert(c, now, base64decode=True)
+         cert_serial) = load_and_handle_cert(c, now, base64decode=True, ans_module=module)
 
         expire_check_result = {
             'cert_cn': cert_subject,
@@ -512,7 +677,7 @@ an OpenShift Container Platform cluster
             (cert_subject,
              cert_expiry_date,
              time_remaining,
-             cert_serial) = load_and_handle_cert(c, now)
+             cert_serial) = load_and_handle_cert(c, now, ans_module=module)
 
             expire_check_result = {
                 'cert_cn': cert_subject,
@@ -551,7 +716,7 @@ an OpenShift Container Platform cluster
                 (cert_subject,
                  cert_expiry_date,
                  time_remaining,
-                 cert_serial) = load_and_handle_cert(etcd_fp.read(), now)
+                 cert_serial) = load_and_handle_cert(etcd_fp.read(), now, ans_module=module)
 
                 expire_check_result = {
                     'cert_cn': cert_subject,
@@ -597,7 +762,7 @@ an OpenShift Container Platform cluster
         (cert_subject,
          cert_expiry_date,
          time_remaining,
-         cert_serial) = load_and_handle_cert(router_c, now, base64decode=True)
+         cert_serial) = load_and_handle_cert(router_c, now, base64decode=True, ans_module=module)
 
         expire_check_result = {
             'cert_cn': cert_subject,
@@ -628,7 +793,7 @@ an OpenShift Container Platform cluster
         (cert_subject,
          cert_expiry_date,
          time_remaining,
-         cert_serial) = load_and_handle_cert(registry_c, now, base64decode=True)
+         cert_serial) = load_and_handle_cert(registry_c, now, base64decode=True, ans_module=module)
 
         expire_check_result = {
             'cert_cn': cert_subject,
diff --git a/roles/openshift_certificate_expiry/test/master.server.crt b/roles/openshift_certificate_expiry/test/master.server.crt
new file mode 100644
index 000000000..51aa85c8c
--- /dev/null
+++ b/roles/openshift_certificate_expiry/test/master.server.crt
@@ -0,0 +1,42 @@
+-----BEGIN CERTIFICATE-----
+MIID7zCCAtegAwIBAgIBBDANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBtvcGVu
+c2hpZnQtc2lnbmVyQDE0ODY0OTExNTgwHhcNMTcwMjA3MTgxMjM5WhcNMTkwMjA3
+MTgxMjQwWjAVMRMwEQYDVQQDEwoxNzIuMzAuMC4xMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEA44n6kVlnRXSwgnKhXX7JrRvxZm+nCqEE/vpKRfNtrMDP
+AuVtcLUWdEDdT0L7QdceLTCBFe7VugrfokPhVi0XavrC2xFpYJ6+wPpuo7HyBRhf
+z/8rOxftAnMeFU5JhFDaeLwSbDjiRgjE1ZYYz8Hcq9YlPujptD6j6YaW1Inae+Vs
+QKXc1uAobemhClLKazEzccVGu53CaSHe4kJoKUZwJ8Ujt/nRHUr+wekbkpx0NfmF
+UEGgNRXN46cq7ZwkLHsjtuR2pttC6JhF+KHgXTRyWM9ssfvL2McmhTFxrToAlhsq
+8MuHMn0y9DMzmAK6EntvlC5AscxTRljtwHZEicspFwIDAQABo4IBNzCCATMwDgYD
+VR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAw
+gf0GA1UdEQSB9TCB8oIKa3ViZXJuZXRlc4ISa3ViZXJuZXRlcy5kZWZhdWx0ghZr
+dWJlcm5ldGVzLmRlZmF1bHQuc3ZjgiRrdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNs
+dXN0ZXIubG9jYWyCD20wMS5leGFtcGxlLmNvbYIJb3BlbnNoaWZ0ghFvcGVuc2hp
+ZnQuZGVmYXVsdIIVb3BlbnNoaWZ0LmRlZmF1bHQuc3ZjgiNvcGVuc2hpZnQuZGVm
+YXVsdC5zdmMuY2x1c3Rlci5sb2NhbIIKMTcyLjMwLjAuMYIPMTkyLjE2OC4xMjIu
+MjQxhwSsHgABhwTAqHrxMA0GCSqGSIb3DQEBCwUAA4IBAQDSdKBpUVB5Sgn1JB//
+bk804+zrUf01koaT83/17WMI+zG8IOwCZ9Be5+zDe4ThXH+PQC6obbwUi9dn8SN6
+rlihvrhNvAJaknY1YRjW07L7aus2RFKXpzsLuWoWLVlLXBTpmfWhQ2w40bCo4Kri
+jQqvezBQ+u1otFzozWmF7nrI/JK+7o89hLvaobx+mDj5wCPQLO+cM/q11Jcz3htv
+VOTFsMh2VnuKOxZqLGJz2CXkr6YXvAhJiFQWaRCnJEaA2ogTYbDahV5ixFKwqpGZ
+o+yDEroPlCw54Bxs0P1ewUx4TRsqd+Qzhnr73xiFBQ0M7JjhKHF6EczHt87XPvsn
+HEL2
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC6jCCAdKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBtvcGVu
+c2hpZnQtc2lnbmVyQDE0ODY0OTExNTgwHhcNMTcwMjA3MTgxMjM3WhcNMjIwMjA2
+MTgxMjM4WjAmMSQwIgYDVQQDDBtvcGVuc2hpZnQtc2lnbmVyQDE0ODY0OTExNTgw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDdyU8AD7sTXHP5jk04i1HY
+cmUXuSiXdIByeIAtTZqiHU46Od0qnZib7tY1NSbo9FtGRl5YEvrfNL+1ig0hZjDh
+gKZK4fNbsanuKgj2SWx11bt4yJH0YSbm9+H45y0E15IY1h30jGHnHFNFZDdYwxtO
+8u+GShb4MOqZL9aUEfvfaoGJIIpfR+eW5NaBZQr6tiM89Z1wJYqoqzgzI/XIyUXR
+zWLOayP1M/eeSXPvBncwZfTPLzphZjB2rz3MdloPrdYMm2b5tfbEOjD7L2aYOJJU
+nVSkgjoFXAazL8KuXSIGcdrdDecyJ4ta8ijD4VIZRN9PnBlYiKaz0DsagkGjUVRd
+AgMBAAGjIzAhMA4GA1UdDwEB/wQEAwICpDAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
+SIb3DQEBCwUAA4IBAQAZ/Kerb5eJXbByQ29fq60V+MQgLIJ1iTo3+zfaXxGlmV9v
+fTp3S1xQhdGyhww7UC0Ze940eRq6BQ5/I6qPcgSGNpUB064pnjSf0CexCY4qoGqK
+4VSvHRrG9TP5V+YIlX9UR1zuPy//a+wuCwKaqiWedTMb4jpvj5jsEOGIrciSmurg
+/9nKvvJXRbgqRYQeJGLT5QW5clHywsyTrE7oYytYSEcAvEs3UZT37H74wj2RFxk6
+KcEzsxUB3W+iYst0QdOPByt64OCwAaUJ96VJstaOYMmyWSShAxGAKDSjcrr4JJnF
+KtqOC1K56x0ONuBsY4MB15TNGPp8SbOhVV6OfIWj
+-----END CERTIFICATE-----
diff --git a/roles/openshift_certificate_expiry/test/master.server.crt.txt b/roles/openshift_certificate_expiry/test/master.server.crt.txt
new file mode 100644
index 000000000..6b3c8fb03
--- /dev/null
+++ b/roles/openshift_certificate_expiry/test/master.server.crt.txt
@@ -0,0 +1,82 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 4 (0x4)
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=openshift-signer@1486491158
+        Validity
+            Not Before: Feb  7 18:12:39 2017 GMT
+            Not After : Feb  7 18:12:40 2019 GMT
+        Subject: CN=172.30.0.1
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:e3:89:fa:91:59:67:45:74:b0:82:72:a1:5d:7e:
+                    c9:ad:1b:f1:66:6f:a7:0a:a1:04:fe:fa:4a:45:f3:
+                    6d:ac:c0:cf:02:e5:6d:70:b5:16:74:40:dd:4f:42:
+                    fb:41:d7:1e:2d:30:81:15:ee:d5:ba:0a:df:a2:43:
+                    e1:56:2d:17:6a:fa:c2:db:11:69:60:9e:be:c0:fa:
+                    6e:a3:b1:f2:05:18:5f:cf:ff:2b:3b:17:ed:02:73:
+                    1e:15:4e:49:84:50:da:78:bc:12:6c:38:e2:46:08:
+                    c4:d5:96:18:cf:c1:dc:ab:d6:25:3e:e8:e9:b4:3e:
+                    a3:e9:86:96:d4:89:da:7b:e5:6c:40:a5:dc:d6:e0:
+                    28:6d:e9:a1:0a:52:ca:6b:31:33:71:c5:46:bb:9d:
+                    c2:69:21:de:e2:42:68:29:46:70:27:c5:23:b7:f9:
+                    d1:1d:4a:fe:c1:e9:1b:92:9c:74:35:f9:85:50:41:
+                    a0:35:15:cd:e3:a7:2a:ed:9c:24:2c:7b:23:b6:e4:
+                    76:a6:db:42:e8:98:45:f8:a1:e0:5d:34:72:58:cf:
+                    6c:b1:fb:cb:d8:c7:26:85:31:71:ad:3a:00:96:1b:
+                    2a:f0:cb:87:32:7d:32:f4:33:33:98:02:ba:12:7b:
+                    6f:94:2e:40:b1:cc:53:46:58:ed:c0:76:44:89:cb:
+                    29:17
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Digital Signature, Key Encipherment
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication
+            X509v3 Basic Constraints: critical
+                CA:FALSE
+            X509v3 Subject Alternative Name: 
+                DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, DNS:m01.example.com, DNS:openshift, DNS:openshift.default, DNS:openshift.default.svc, DNS:openshift.default.svc.cluster.local, DNS:172.30.0.1, DNS:192.168.122.241, IP Address:172.30.0.1, IP Address:192.168.122.241
+    Signature Algorithm: sha256WithRSAEncryption
+         d2:74:a0:69:51:50:79:4a:09:f5:24:1f:ff:6e:4f:34:e3:ec:
+         eb:51:fd:35:92:86:93:f3:7f:f5:ed:63:08:fb:31:bc:20:ec:
+         02:67:d0:5e:e7:ec:c3:7b:84:e1:5c:7f:8f:40:2e:a8:6d:bc:
+         14:8b:d7:67:f1:23:7a:ae:58:a1:be:b8:4d:bc:02:5a:92:76:
+         35:61:18:d6:d3:b2:fb:6a:eb:36:44:52:97:a7:3b:0b:b9:6a:
+         16:2d:59:4b:5c:14:e9:99:f5:a1:43:6c:38:d1:b0:a8:e0:aa:
+         e2:8d:0a:af:7b:30:50:fa:ed:68:b4:5c:e8:cd:69:85:ee:7a:
+         c8:fc:92:be:ee:8f:3d:84:bb:da:a1:bc:7e:98:38:f9:c0:23:
+         d0:2c:ef:9c:33:fa:b5:d4:97:33:de:1b:6f:54:e4:c5:b0:c8:
+         76:56:7b:8a:3b:16:6a:2c:62:73:d8:25:e4:af:a6:17:bc:08:
+         49:88:54:16:69:10:a7:24:46:80:da:88:13:61:b0:da:85:5e:
+         62:c4:52:b0:aa:91:99:a3:ec:83:12:ba:0f:94:2c:39:e0:1c:
+         6c:d0:fd:5e:c1:4c:78:4d:1b:2a:77:e4:33:86:7a:fb:df:18:
+         85:05:0d:0c:ec:98:e1:28:71:7a:11:cc:c7:b7:ce:d7:3e:fb:
+         27:1c:42:f6
+-----BEGIN CERTIFICATE-----
+MIID7zCCAtegAwIBAgIBBDANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBtvcGVu
+c2hpZnQtc2lnbmVyQDE0ODY0OTExNTgwHhcNMTcwMjA3MTgxMjM5WhcNMTkwMjA3
+MTgxMjQwWjAVMRMwEQYDVQQDEwoxNzIuMzAuMC4xMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEA44n6kVlnRXSwgnKhXX7JrRvxZm+nCqEE/vpKRfNtrMDP
+AuVtcLUWdEDdT0L7QdceLTCBFe7VugrfokPhVi0XavrC2xFpYJ6+wPpuo7HyBRhf
+z/8rOxftAnMeFU5JhFDaeLwSbDjiRgjE1ZYYz8Hcq9YlPujptD6j6YaW1Inae+Vs
+QKXc1uAobemhClLKazEzccVGu53CaSHe4kJoKUZwJ8Ujt/nRHUr+wekbkpx0NfmF
+UEGgNRXN46cq7ZwkLHsjtuR2pttC6JhF+KHgXTRyWM9ssfvL2McmhTFxrToAlhsq
+8MuHMn0y9DMzmAK6EntvlC5AscxTRljtwHZEicspFwIDAQABo4IBNzCCATMwDgYD
+VR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAw
+gf0GA1UdEQSB9TCB8oIKa3ViZXJuZXRlc4ISa3ViZXJuZXRlcy5kZWZhdWx0ghZr
+dWJlcm5ldGVzLmRlZmF1bHQuc3ZjgiRrdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNs
+dXN0ZXIubG9jYWyCD20wMS5leGFtcGxlLmNvbYIJb3BlbnNoaWZ0ghFvcGVuc2hp
+ZnQuZGVmYXVsdIIVb3BlbnNoaWZ0LmRlZmF1bHQuc3ZjgiNvcGVuc2hpZnQuZGVm
+YXVsdC5zdmMuY2x1c3Rlci5sb2NhbIIKMTcyLjMwLjAuMYIPMTkyLjE2OC4xMjIu
+MjQxhwSsHgABhwTAqHrxMA0GCSqGSIb3DQEBCwUAA4IBAQDSdKBpUVB5Sgn1JB//
+bk804+zrUf01koaT83/17WMI+zG8IOwCZ9Be5+zDe4ThXH+PQC6obbwUi9dn8SN6
+rlihvrhNvAJaknY1YRjW07L7aus2RFKXpzsLuWoWLVlLXBTpmfWhQ2w40bCo4Kri
+jQqvezBQ+u1otFzozWmF7nrI/JK+7o89hLvaobx+mDj5wCPQLO+cM/q11Jcz3htv
+VOTFsMh2VnuKOxZqLGJz2CXkr6YXvAhJiFQWaRCnJEaA2ogTYbDahV5ixFKwqpGZ
+o+yDEroPlCw54Bxs0P1ewUx4TRsqd+Qzhnr73xiFBQ0M7JjhKHF6EczHt87XPvsn
+HEL2
+-----END CERTIFICATE-----
diff --git a/roles/openshift_certificate_expiry/test/system-node-m01.example.com.crt b/roles/openshift_certificate_expiry/test/system-node-m01.example.com.crt
new file mode 100644
index 000000000..cd13ddc38
--- /dev/null
+++ b/roles/openshift_certificate_expiry/test/system-node-m01.example.com.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDEzCCAfugAwIBAgIBCzANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBtvcGVu
+c2hpZnQtc2lnbmVyQDE0ODY0OTExNTgwHhcNMTcwMjA3MTgxOTM0WhcNMTkwMjA3
+MTgxOTM1WjA9MRUwEwYDVQQKEwxzeXN0ZW06bm9kZXMxJDAiBgNVBAMTG3N5c3Rl
+bTpub2RlOm0wMS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAOcVdDaSmeXuSp+7VCHUjEDeTP3j9aH0nreBj3079sEzethlLoQmwAqf
+CZp23qXGYm0R89+CC55buaH1FN/ltQ8QDGUzi4tdow9Af/0OcD0EINO2ukmyG5/9
+N+X905mo+y923wppvrchAA6AcxxeDyA63zouGS4exI98iuZlcdS48zbsGERkRPGg
+hoGCo7HoiKtUNL5X8MYibnFYnA4EUngdHZsRKuKte4t8GY4PYq4cxIOYXsJsNmT5
+mkFy4ThGFfR9IGg/VfyeWIkRe2VWyaUgzL0gHytAhlRJ9l54ynx96YEWrjCtp/kh
+d3KeVj0IUcMzvoXX5hipYUPkoezcxI8CAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWg
+MBMGA1UdJQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEL
+BQADggEBAM1jexLtuOOTsOPfEal/ICzfP9aX3m0R/yGPjwQv43jOc81NcL5d+CeD
+MX36tKAxFIe+wvXo0kUQOzTK3D7ol4x2YTtB4uDzNE5tVh5dWi2LrKYSqZDIrhKO
+MOmJRWR3AFEopaaGQpxsD/FSfZ5Mg0OMMBPHABxMrsiserHO1nh4ax3+SI0i7Jen
+gVsB4B/Xxg9Lw9JDX3/XMcI+fyLVw5ctO62BaluljpT+HkdbRWnH8ar7TmcJjzTo
+/TyXOeOLkbozx0BQK16d/CbtLomJ+PO4cdwCNs2Z6HGSPTL7S9y0pct52N0kfJfx
+ZGXMsW+N62S2vVSXEekMR0GJgJnLNSo=
+-----END CERTIFICATE-----
diff --git a/roles/openshift_certificate_expiry/test/system-node-m01.example.com.crt.txt b/roles/openshift_certificate_expiry/test/system-node-m01.example.com.crt.txt
new file mode 100644
index 000000000..67a3eb81c
--- /dev/null
+++ b/roles/openshift_certificate_expiry/test/system-node-m01.example.com.crt.txt
@@ -0,0 +1,75 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 11 (0xb)
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=openshift-signer@1486491158
+        Validity
+            Not Before: Feb  7 18:19:34 2017 GMT
+            Not After : Feb  7 18:19:35 2019 GMT
+        Subject: O=system:nodes, CN=system:node:m01.example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:e7:15:74:36:92:99:e5:ee:4a:9f:bb:54:21:d4:
+                    8c:40:de:4c:fd:e3:f5:a1:f4:9e:b7:81:8f:7d:3b:
+                    f6:c1:33:7a:d8:65:2e:84:26:c0:0a:9f:09:9a:76:
+                    de:a5:c6:62:6d:11:f3:df:82:0b:9e:5b:b9:a1:f5:
+                    14:df:e5:b5:0f:10:0c:65:33:8b:8b:5d:a3:0f:40:
+                    7f:fd:0e:70:3d:04:20:d3:b6:ba:49:b2:1b:9f:fd:
+                    37:e5:fd:d3:99:a8:fb:2f:76:df:0a:69:be:b7:21:
+                    00:0e:80:73:1c:5e:0f:20:3a:df:3a:2e:19:2e:1e:
+                    c4:8f:7c:8a:e6:65:71:d4:b8:f3:36:ec:18:44:64:
+                    44:f1:a0:86:81:82:a3:b1:e8:88:ab:54:34:be:57:
+                    f0:c6:22:6e:71:58:9c:0e:04:52:78:1d:1d:9b:11:
+                    2a:e2:ad:7b:8b:7c:19:8e:0f:62:ae:1c:c4:83:98:
+                    5e:c2:6c:36:64:f9:9a:41:72:e1:38:46:15:f4:7d:
+                    20:68:3f:55:fc:9e:58:89:11:7b:65:56:c9:a5:20:
+                    cc:bd:20:1f:2b:40:86:54:49:f6:5e:78:ca:7c:7d:
+                    e9:81:16:ae:30:ad:a7:f9:21:77:72:9e:56:3d:08:
+                    51:c3:33:be:85:d7:e6:18:a9:61:43:e4:a1:ec:dc:
+                    c4:8f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Digital Signature, Key Encipherment
+            X509v3 Extended Key Usage: 
+                TLS Web Client Authentication
+            X509v3 Basic Constraints: critical
+                CA:FALSE
+    Signature Algorithm: sha256WithRSAEncryption
+         cd:63:7b:12:ed:b8:e3:93:b0:e3:df:11:a9:7f:20:2c:df:3f:
+         d6:97:de:6d:11:ff:21:8f:8f:04:2f:e3:78:ce:73:cd:4d:70:
+         be:5d:f8:27:83:31:7d:fa:b4:a0:31:14:87:be:c2:f5:e8:d2:
+         45:10:3b:34:ca:dc:3e:e8:97:8c:76:61:3b:41:e2:e0:f3:34:
+         4e:6d:56:1e:5d:5a:2d:8b:ac:a6:12:a9:90:c8:ae:12:8e:30:
+         e9:89:45:64:77:00:51:28:a5:a6:86:42:9c:6c:0f:f1:52:7d:
+         9e:4c:83:43:8c:30:13:c7:00:1c:4c:ae:c8:ac:7a:b1:ce:d6:
+         78:78:6b:1d:fe:48:8d:22:ec:97:a7:81:5b:01:e0:1f:d7:c6:
+         0f:4b:c3:d2:43:5f:7f:d7:31:c2:3e:7f:22:d5:c3:97:2d:3b:
+         ad:81:6a:5b:a5:8e:94:fe:1e:47:5b:45:69:c7:f1:aa:fb:4e:
+         67:09:8f:34:e8:fd:3c:97:39:e3:8b:91:ba:33:c7:40:50:2b:
+         5e:9d:fc:26:ed:2e:89:89:f8:f3:b8:71:dc:02:36:cd:99:e8:
+         71:92:3d:32:fb:4b:dc:b4:a5:cb:79:d8:dd:24:7c:97:f1:64:
+         65:cc:b1:6f:8d:eb:64:b6:bd:54:97:11:e9:0c:47:41:89:80:
+         99:cb:35:2a
+-----BEGIN CERTIFICATE-----
+MIIDEzCCAfugAwIBAgIBCzANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBtvcGVu
+c2hpZnQtc2lnbmVyQDE0ODY0OTExNTgwHhcNMTcwMjA3MTgxOTM0WhcNMTkwMjA3
+MTgxOTM1WjA9MRUwEwYDVQQKEwxzeXN0ZW06bm9kZXMxJDAiBgNVBAMTG3N5c3Rl
+bTpub2RlOm0wMS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAOcVdDaSmeXuSp+7VCHUjEDeTP3j9aH0nreBj3079sEzethlLoQmwAqf
+CZp23qXGYm0R89+CC55buaH1FN/ltQ8QDGUzi4tdow9Af/0OcD0EINO2ukmyG5/9
+N+X905mo+y923wppvrchAA6AcxxeDyA63zouGS4exI98iuZlcdS48zbsGERkRPGg
+hoGCo7HoiKtUNL5X8MYibnFYnA4EUngdHZsRKuKte4t8GY4PYq4cxIOYXsJsNmT5
+mkFy4ThGFfR9IGg/VfyeWIkRe2VWyaUgzL0gHytAhlRJ9l54ynx96YEWrjCtp/kh
+d3KeVj0IUcMzvoXX5hipYUPkoezcxI8CAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWg
+MBMGA1UdJQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEL
+BQADggEBAM1jexLtuOOTsOPfEal/ICzfP9aX3m0R/yGPjwQv43jOc81NcL5d+CeD
+MX36tKAxFIe+wvXo0kUQOzTK3D7ol4x2YTtB4uDzNE5tVh5dWi2LrKYSqZDIrhKO
+MOmJRWR3AFEopaaGQpxsD/FSfZ5Mg0OMMBPHABxMrsiserHO1nh4ax3+SI0i7Jen
+gVsB4B/Xxg9Lw9JDX3/XMcI+fyLVw5ctO62BaluljpT+HkdbRWnH8ar7TmcJjzTo
+/TyXOeOLkbozx0BQK16d/CbtLomJ+PO4cdwCNs2Z6HGSPTL7S9y0pct52N0kfJfx
+ZGXMsW+N62S2vVSXEekMR0GJgJnLNSo=
+-----END CERTIFICATE-----
diff --git a/roles/openshift_certificate_expiry/test/test_fakeopensslclasses.py b/roles/openshift_certificate_expiry/test/test_fakeopensslclasses.py
new file mode 100644
index 000000000..e98d6ac64
--- /dev/null
+++ b/roles/openshift_certificate_expiry/test/test_fakeopensslclasses.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+'''
+ Unit tests for the FakeOpenSSL classes
+'''
+
+import os
+import sys
+import unittest
+import pytest
+
+# Disable import-error b/c our libraries aren't loaded in jenkins
+# pylint: disable=import-error,wrong-import-position
+# place class in our python path
+module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-1]), 'library')
+sys.path.insert(0, module_path)
+openshift_cert_expiry = pytest.importorskip("openshift_cert_expiry")
+
+
+@pytest.mark.skip('Skipping all tests because of unresolved import errors')
+class TestFakeOpenSSLClasses(unittest.TestCase):
+    '''
+     Test class for FakeOpenSSL classes
+    '''
+
+    def setUp(self):
+        ''' setup method for other tests '''
+        with open('test/system-node-m01.example.com.crt.txt', 'r') as fp:
+            self.cert_string = fp.read()
+
+        self.fake_cert = openshift_cert_expiry.FakeOpenSSLCertificate(self.cert_string)
+
+        with open('test/master.server.crt.txt', 'r') as fp:
+            self.cert_san_string = fp.read()
+
+        self.fake_san_cert = openshift_cert_expiry.FakeOpenSSLCertificate(self.cert_san_string)
+
+    def test_FakeOpenSSLCertificate_get_serial_number(self):
+        """We can read the serial number from the cert"""
+        self.assertEqual(11, self.fake_cert.get_serial_number())
+
+    def test_FakeOpenSSLCertificate_get_notAfter(self):
+        """We can read the cert expiry date"""
+        expiry = self.fake_cert.get_notAfter()
+        self.assertEqual('20190207181935Z', expiry)
+
+    def test_FakeOpenSSLCertificate_get_sans(self):
+        """We can read Subject Alt Names from a cert"""
+        ext = self.fake_san_cert.get_extension(0)
+
+        if ext.get_short_name() == 'subjectAltName':
+            sans = str(ext)
+
+        self.assertEqual('DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, DNS:m01.example.com, DNS:openshift, DNS:openshift.default, DNS:openshift.default.svc, DNS:openshift.default.svc.cluster.local, DNS:172.30.0.1, DNS:192.168.122.241, IP Address:172.30.0.1, IP Address:192.168.122.241', sans)
+
+    def test_FakeOpenSSLCertificate_get_sans_no_sans(self):
+        """We can tell when there are no Subject Alt Names in a cert"""
+        with self.assertRaises(IndexError):
+            self.fake_cert.get_extension(0)
+
+    def test_FakeOpenSSLCertificate_get_subject(self):
+        """We can read the Subject from a cert"""
+        # Subject: O=system:nodes, CN=system:node:m01.example.com
+        subject = self.fake_cert.get_subject()
+        subjects = []
+        for name, value in subject.get_components():
+            subjects.append('{}={}'.format(name, value))
+
+        self.assertEqual('O=system:nodes, CN=system:node:m01.example.com', ', '.join(subjects))
+
+    def test_FakeOpenSSLCertificate_get_subject_san_cert(self):
+        """We can read the Subject from a cert with sans"""
+        # Subject: O=system:nodes, CN=system:node:m01.example.com
+        subject = self.fake_san_cert.get_subject()
+        subjects = []
+        for name, value in subject.get_components():
+            subjects.append('{}={}'.format(name, value))
+
+        self.assertEqual('CN=172.30.0.1', ', '.join(subjects))
+
+    def tearDown(self):
+        '''TearDown method'''
+        pass
+
+
+if __name__ == "__main__":
+    unittest.main()
-- 
cgit v1.2.3


From 003bc8d5b9233e61c7a2a5f0a27b66ac1babc1a0 Mon Sep 17 00:00:00 2001
From: Tim Bielawa <tbielawa@redhat.com>
Date: Tue, 21 Feb 2017 11:00:00 -0800
Subject: Address cert expiry parsing review comments

---
 .../library/openshift_cert_expiry.py                             | 9 ++++++---
 .../openshift_certificate_expiry/test/test_fakeopensslclasses.py | 6 +-----
 2 files changed, 7 insertions(+), 8 deletions(-)

(limited to 'roles/openshift_certificate_expiry')

diff --git a/roles/openshift_certificate_expiry/library/openshift_cert_expiry.py b/roles/openshift_certificate_expiry/library/openshift_cert_expiry.py
index 33930c0c1..b093d84fe 100644
--- a/roles/openshift_certificate_expiry/library/openshift_cert_expiry.py
+++ b/roles/openshift_certificate_expiry/library/openshift_cert_expiry.py
@@ -242,6 +242,8 @@ will be returned
 
 
 # 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
@@ -254,8 +256,8 @@ Params:
 - `ans_module` (AnsibleModule) - The AnsibleModule object for this module (so we can raise errors)
 
 Returns:
-A 3-tuple of the form: (certificate_common_name, certificate_expiry_date, certificate_time_remaining)
-
+A tuple of the form:
+    (cert_subject, cert_expiry_date, time_remaining, cert_serial_number)
     """
     if base64decode:
         _cert_string = cert_string.decode('base-64')
@@ -287,8 +289,9 @@ A 3-tuple of the form: (certificate_common_name, certificate_expiry_date, certif
             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_decoded.communicate()[0]
-            os.remove(path)
             cert_loaded = FakeOpenSSLCertificate(openssl_decoded)
+        finally:
+            os.remove(path)
 
     ######################################################################
     # Read all possible names from the cert
diff --git a/roles/openshift_certificate_expiry/test/test_fakeopensslclasses.py b/roles/openshift_certificate_expiry/test/test_fakeopensslclasses.py
index e98d6ac64..2e245191f 100644
--- a/roles/openshift_certificate_expiry/test/test_fakeopensslclasses.py
+++ b/roles/openshift_certificate_expiry/test/test_fakeopensslclasses.py
@@ -11,7 +11,7 @@ import pytest
 # Disable import-error b/c our libraries aren't loaded in jenkins
 # pylint: disable=import-error,wrong-import-position
 # place class in our python path
-module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-1]), 'library')
+module_path = os.path.join('/'.join(os.path.realpath(__file__).split(os.path.sep)[:-1]), 'library')
 sys.path.insert(0, module_path)
 openshift_cert_expiry = pytest.importorskip("openshift_cert_expiry")
 
@@ -77,10 +77,6 @@ class TestFakeOpenSSLClasses(unittest.TestCase):
 
         self.assertEqual('CN=172.30.0.1', ', '.join(subjects))
 
-    def tearDown(self):
-        '''TearDown method'''
-        pass
-
 
 if __name__ == "__main__":
     unittest.main()
-- 
cgit v1.2.3