Added a Python version

This commit is contained in:
Myles Braithwaite 2022-11-06 00:13:33 -05:00 committed by John R. Dennison
parent 9f80743da6
commit f6c1a4666e
6 changed files with 537 additions and 213 deletions

66
python_app/README.md Normal file
View File

@ -0,0 +1,66 @@
# tls-dashboard
A dashboard written in JavaScript & HTML to check the remaining time before a TLS certificate expires. A combination of a Node module and an HTML/CSS/JS webpage to display the info.
**Version:** 1.1.0
## Node Setup
### `node_app/config.js`
Contains the configuration variables for the node script.
* `connection_timeout` - The time in milliseconds that node should leave a connection open without response after the socket has been assigned. Once the timeout expires, node emits a `timeout` event and aborts the connection request. Default is 5000ms.
* `output_file`
* `path` - The path to the directory that you want the output file written to. Can be relative or absolute, requires a trailing `/`, and defaults to the `../web_service/js/tls-dashboard/` directory. If you move the contents of the `./web_service` directory, make sure you update this path.
* `name` - The name of the output file. This typically doesn't need to be changed, but if you do change it, you'll also need to change the filename in `index.html` at line 14.
### `node_app/monitored_hosts.js`
Contains an array of all of the hostnames that you want to monitor.
### `node_app/get_cert_info.js`
This module performs the actual HTTPS connection and evaluation of the peer certificates, and outputs the results into a flat file. There are no configuration changes needed in this file. To get things going, you can either:
1. Run the script manually whenever you need to update your dashboard by calling `node get_cert_info.js`; or
2. Set up the script to run on a cronjob
It's entirely up to you how you want to handle it.
## Python Setup
### `python_app/config.json`
Contains the configuration variables for the python script.
* `connection_timeout` - The time in milliseconds that node should leave a connection open without response after the socket has been assigned. Once the timeout expires, node emits a `timeout` event and aborts the connection request. Default is 5000ms.
* `output_file`
* `path` - The path to the directory that you want the output file written to. Can be relative or absolute, requires a trailing `/`, and defaults to the `../web_service/js/tls-dashboard/` directory. If you move the contents of the `./web_service` directory, make sure you update this path.
* `name` - The name of the output file. This typically doesn't need to be changed, but if you do change it, you'll also need to change the filename in `index.html` at line 14.
### `python_app/monitored_hosts.json`
Contains an array of all of the hostnames that you want to monitor.
### `python_app/get_cert_info.py`
This module performs the actual HTTPS connection and evaluation of the peer certificates, and outputs the results into a flat file. There are no configuration changes needed in this file. To get things going, you can either:
1. Run teh script manually whenever you need to update your dashboard by calling `python3 get_cert_info.py`
2. Set up the script to run on a cronjob
It's entirely up to you how you want to handle it.
## Web Service Setup
To get the web service started, you'll need to either move the contents of the `web_service` directory to somewhere in your web site's path, or create a symlink from the web site path back to the directory. If you move the contents, please update the `output_file.path` config value. These are static files with relative links, so other than moving them/pointing the server to them, there's nothing else required for you to do.
## Example
Take a look at a live example page [here on GitLab][1]. Screenshots below.
![Example dashboard](https://raw.githubusercontent.com/cmrunton/tls-dashboard/master/screenshot.png)
![Example dashboard](https://raw.githubusercontent.com/cmrunton/tls-dashboard/master/screenshot_2.png)
## TODO
1. Database integration?
2. Slack integration?
## Dependencies
The node module has no dependencies external to the node core. The following dependencies are provided for the web service to render properly.
* jQuery v2.2.2
* Handlebars v4.0.5
* Bootstrap v4.0.0-alpha (CSS only)
[1]:https://craine.gitlab.io/tls-dashboard/

7
python_app/config.json Normal file
View File

@ -0,0 +1,7 @@
{
"connection_timeout": 5000,
"output_file": {
"path": "../web_service/js/tls-dashboard/",
"name": "certificates.js"
}
}

106
python_app/get_cert_info.py Normal file
View File

@ -0,0 +1,106 @@
#!/usr/bin/env python3
import re
import os
import ssl
import json
import socket
import datetime
OUTPUT_TEMPLATE = """var run_date = '{run_date}';
var cert_info = {cert_info}"""
def parse_date(date_string):
"""
Takes a date string and returns the nuumber of days between now and
the future date
"""
return datetime.datetime.strptime(date_string, "%b %d %X %Y %Z")
def camelcase_to_underscore(name):
"""
Takes the SSL camelcase stuff and converts it to underscore
Found on StackOverflow <http://stackoverflow.com/a/1176023/43363>
"""
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
def get_cert_parameters(hostname, timeout=5000, ssl_port=443):
"""
Creates a connection to the host, and then reads the resulting peer
certificate to extract the desired info
"""
context = ssl.create_default_context()
sock = socket.socket(socket.AF_INET)
sock.settimeout(timeout)
connection = context.wrap_socket(sock, server_hostname=hostname)
connection.connect((hostname, ssl_port))
certificate = connection.getpeercert()
connection.close()
cert_info = {
"server": hostname,
"subject": {},
"issuer": {},
"info": {}
}
for s in certificate.get('subject', None):
subject = s[0]
key = camelcase_to_underscore(subject[0])
cert_info['subject'][key] = subject[1]
for i in certificate.get('issuer', None):
issuer = i[0]
key = camelcase_to_underscore(issuer[0])
cert_info['issuer'][key] = issuer[1]
cert_info['info']['valid_from'] = parse_date(certificate['notAfter'])
cert_info['info']['valid_to'] = parse_date(certificate['notBefore'])
time_left = cert_info['info']['valid_from'] - datetime.datetime.now()
cert_info['info']['days_left'] = time_left.days
return cert_info
def json_default(obj):
if isinstance(obj, datetime.datetime):
return obj.isoformat()
def main():
ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(ROOT_PATH, 'config.json'), 'r') as f:
config = json.loads(f.read())
with open(os.path.join(ROOT_PATH, 'monitored_hosts.json'), 'r') as f:
monitored_hosts = json.loads(f.read())
cert_info = {}
count = 1
for host in monitored_hosts['hosts']:
cert_info[count] = get_cert_parameters(host)
count += 1
with open(os.path.join(config['output_file']['path'],
config['output_file']['name']), 'w') as f:
run_date = datetime.datetime.now().strftime('%a %b %d %Y')
json_cert_info = json.dumps(cert_info, indent=4, default=json_default)
f.write(OUTPUT_TEMPLATE.format(cert_info=json_cert_info,
run_date=run_date))
if __name__ == '__main__':
main()

View File

@ -0,0 +1,10 @@
{
"hosts": [
"www.google.com",
"www.twitter.com",
"www.github.com",
"www.bitbucket.com",
"news.ycombinator.com",
"barnacl.es"
]
}

View File

@ -1,219 +1,135 @@
var run_date = 'Fri Jun 17 2016';
var run_date = 'Wed May 25 2016';
var cert_info = {
"1": {
"server": {
"hostname":"http-only.runtondev.com"
"1": {
"server": "www.google.com",
"subject": {
"common_name": "www.google.com",
"country_name": "US",
"organization_name": "Google Inc",
"state_or_province_name": "California",
"locality_name": "Mountain View"
},
"issuer": {
"common_name": "Google Internet Authority G2",
"country_name": "US",
"organization_name": "Google Inc"
},
"info": {
"days_left": 76,
"valid_from": "2016-08-10T10:46:00",
"valid_to": "2016-05-18T10:59:02"
}
},
"subject": {
"org": "Unknown",
"common_name": "The connection was refused by the remote server",
"sans": "Unknown"
"2": {
"server": "www.twitter.com",
"subject": {
"common_name": "twitter.com",
"business_category": "Private Organization",
"jurisdiction_country_name": "US",
"state_or_province_name": "California",
"organizational_unit_name": "Twitter Security",
"organization_name": "Twitter, Inc.",
"street_address": "1355 Market St",
"serial_number": "4337446",
"jurisdiction_state_or_province_name": "Delaware",
"locality_name": "San Francisco",
"postal_code": "94103",
"country_name": "US"
},
"issuer": {
"common_name": "DigiCert SHA2 Extended Validation Server CA",
"country_name": "US",
"organization_name": "DigiCert Inc",
"organizational_unit_name": "www.digicert.com"
},
"info": {
"days_left": 657,
"valid_from": "2018-03-14T12:00:00",
"valid_to": "2016-03-09T00:00:00"
}
},
"issuer": {
"org": "Unknown",
"common_name": "ECONNREFUSED"
"3": {
"server": "www.github.com",
"subject": {
"postal_code": "94107",
"serial_number": "5157550",
"jurisdiction_state_or_province_name": "Delaware",
"business_category": "Private Organization",
"jurisdiction_country_name": "US",
"state_or_province_name": "California",
"locality_name": "San Francisco",
"street_address": "88 Colin P Kelly, Jr Street",
"country_name": "US",
"common_name": "github.com",
"organization_name": "GitHub, Inc."
},
"issuer": {
"common_name": "DigiCert SHA2 Extended Validation Server CA",
"country_name": "US",
"organization_name": "DigiCert Inc",
"organizational_unit_name": "www.digicert.com"
},
"info": {
"days_left": 721,
"valid_from": "2018-05-17T12:00:00",
"valid_to": "2016-03-10T00:00:00"
}
},
"info": {
"days_left": "--",
"sort_order": 100000,
"background_class": "info"
"4": {
"server": "www.bitbucket.com",
"subject": {
"common_name": "*.bitbucket.com",
"country_name": "US",
"organization_name": "Atlassian, Inc.",
"state_or_province_name": "CA",
"locality_name": "San Francisco"
},
"issuer": {
"common_name": "DigiCert SHA2 High Assurance Server CA",
"country_name": "US",
"organization_name": "DigiCert Inc",
"organizational_unit_name": "www.digicert.com"
},
"info": {
"days_left": 349,
"valid_from": "2017-05-10T12:00:00",
"valid_to": "2015-04-10T00:00:00"
}
},
"5": {
"server": "news.ycombinator.com",
"subject": {
"common_name": "*.ycombinator.com",
"organizational_unit_name": "PositiveSSL Wildcard"
},
"issuer": {
"common_name": "COMODO RSA Domain Validation Secure Server CA",
"country_name": "GB",
"organization_name": "COMODO CA Limited",
"state_or_province_name": "Greater Manchester",
"locality_name": "Salford"
},
"info": {
"days_left": 1183,
"valid_from": "2019-08-21T23:59:59",
"valid_to": "2014-08-22T00:00:00"
}
},
"6": {
"server": "barnacl.es",
"subject": {
"common_name": "www.barnacl.es"
},
"issuer": {
"common_name": "StartCom Class 1 DV Server CA",
"country_name": "IL",
"organization_name": "StartCom Ltd.",
"organizational_unit_name": "StartCom Certification Authority"
},
"info": {
"days_left": 323,
"valid_from": "2017-04-13T20:08:11",
"valid_to": "2016-04-13T20:08:11"
}
}
},
"2": {
"server": {
"hostname":"www.google.com"
},
"subject": {
"org": "Google Inc",
"common_name": "www.google.com",
"sans": "DNS:www.google.com"
},
"issuer": {
"org": "Google Inc",
"common_name": "Google Internet Authority G2"
},
"info": {
"valid_from": "2016-06-08T12:37:29.000Z",
"valid_to": "2016-08-31T12:30:00.000Z",
"days_left": 75,
"sort_order": 75,
"background_class": "success"
}
},
"3": {
"server": {
"hostname":"expired.badssl.com"
},
"subject": {
"org": "Unknown",
"common_name": "The certificate has expired",
"sans": "Unknown"
},
"issuer": {
"org": "Unknown",
"common_name": "CERT_HAS_EXPIRED"
},
"info": {
"days_left": "0",
"sort_order": 0,
"background_class": "danger"
}
},
"4": {
"server": {
"hostname":"incomplete-chain.badssl.com"
},
"subject": {
"org": "Unknown",
"common_name": "The server provided a self-signed certificate or the provided certificate chain was incomplete",
"sans": "Unknown"
},
"issuer": {
"org": "Unknown",
"common_name": "UNABLE_TO_VERIFY_LEAF_SIGNATURE"
},
"info": {
"days_left": "--",
"sort_order": 100000,
"background_class": "info"
}
},
"5": {
"server": {
"hostname":"wrong.host.badssl.com"
},
"subject": {
"org": "Unknown",
"common_name": "There was mismatch between the requested hostname and the certificate presented by the server",
"sans": "Unknown"
},
"issuer": {
"org": "Unknown",
"common_name": "HOSTNAME_MISMATCH"
},
"info": {
"days_left": "--",
"sort_order": 100000,
"background_class": "info"
}
},
"6": {
"server": {
"hostname":"self-signed.badssl.com"
},
"subject": {
"org": "Unknown",
"common_name": "The server provided a self-signed certificate or the provided certificate chain was incomplete",
"sans": "Unknown"
},
"issuer": {
"org": "Unknown",
"common_name": "UNABLE_TO_VERIFY_LEAF_SIGNATURE"
},
"info": {
"days_left": "--",
"sort_order": 100000,
"background_class": "info"
}
},
"7": {
"server": {
"hostname":"sha256.badssl.com"
},
"subject": {
"common_name": "*.badssl.com",
"sans": "DNS:*.badssl.com, DNS:badssl.com"
},
"issuer": {
"org": "COMODO CA Limited",
"common_name": "COMODO RSA Domain Validation Secure Server CA"
},
"info": {
"valid_from": "2015-04-09T00:00:00.000Z",
"valid_to": "2016-07-07T23:59:59.000Z",
"days_left": 20,
"sort_order": 20,
"background_class": "danger"
}
},
"8": {
"server": {
"hostname":"www.twitter.com"
},
"subject": {
"org": "Twitter, Inc.",
"common_name": "twitter.com",
"sans": "DNS:twitter.com, DNS:www.twitter.com"
},
"issuer": {
"org": "DigiCert Inc",
"common_name": "DigiCert SHA2 Extended Validation Server CA"
},
"info": {
"valid_from": "2016-03-09T00:00:00.000Z",
"valid_to": "2018-03-14T12:00:00.000Z",
"days_left": 635,
"sort_order": 635,
"background_class": "success"
}
},
"9": {
"server": {
"hostname":"nonexistent.runtondev.com"
},
"subject": {
"org": "Unknown",
"common_name": "The connection was reset by the server or timed out",
"sans": "Unknown"
},
"issuer": {
"org": "Unknown",
"common_name": "ECONNRESET"
},
"info": {
"days_left": "--",
"sort_order": 100000,
"background_class": "info"
}
},
"10": {
"server": {
"hostname":"warning.runtondev.com"
},
"subject": {
"common_name": "warning.runtondev.com",
"sans": ""
},
"issuer": {
"org": "Madeup CA",
"common_name": "CA that doesn't exist"
},
"info": {
"valid_from": "2015-04-09T00:00:00.000Z",
"valid_to": "2016-07-07T23:59:59.000Z",
"days_left": 45,
"sort_order": 45,
"background_class": "warning"
}
},
"11": {
"server": {
"hostname":"danger.runtondev.com"
},
"subject": {
"common_name": "danger.runtondev.com",
"sans": ""
},
"issuer": {
"org": "Madeup CA",
"common_name": "CA that doesn't exist"
},
"info": {
"valid_from": "2015-04-09T00:00:00.000Z",
"valid_to": "2016-07-07T23:59:59.000Z",
"days_left": 1,
"sort_order": 1,
"background_class": "danger"
}
},
}

View File

@ -0,0 +1,219 @@
var run_date = 'Fri Jun 17 2016';
var cert_info = {
"1": {
"server": {
"hostname":"http-only.runtondev.com"
},
"subject": {
"org": "Unknown",
"common_name": "The connection was refused by the remote server",
"sans": "Unknown"
},
"issuer": {
"org": "Unknown",
"common_name": "ECONNREFUSED"
},
"info": {
"days_left": "--",
"sort_order": 100000,
"background_class": "info"
}
},
"2": {
"server": {
"hostname":"www.google.com"
},
"subject": {
"org": "Google Inc",
"common_name": "www.google.com",
"sans": "DNS:www.google.com"
},
"issuer": {
"org": "Google Inc",
"common_name": "Google Internet Authority G2"
},
"info": {
"valid_from": "2016-06-08T12:37:29.000Z",
"valid_to": "2016-08-31T12:30:00.000Z",
"days_left": 75,
"sort_order": 75,
"background_class": "success"
}
},
"3": {
"server": {
"hostname":"expired.badssl.com"
},
"subject": {
"org": "Unknown",
"common_name": "The certificate has expired",
"sans": "Unknown"
},
"issuer": {
"org": "Unknown",
"common_name": "CERT_HAS_EXPIRED"
},
"info": {
"days_left": "0",
"sort_order": 0,
"background_class": "danger"
}
},
"4": {
"server": {
"hostname":"incomplete-chain.badssl.com"
},
"subject": {
"org": "Unknown",
"common_name": "The server provided a self-signed certificate or the provided certificate chain was incomplete",
"sans": "Unknown"
},
"issuer": {
"org": "Unknown",
"common_name": "UNABLE_TO_VERIFY_LEAF_SIGNATURE"
},
"info": {
"days_left": "--",
"sort_order": 100000,
"background_class": "info"
}
},
"5": {
"server": {
"hostname":"wrong.host.badssl.com"
},
"subject": {
"org": "Unknown",
"common_name": "There was mismatch between the requested hostname and the certificate presented by the server",
"sans": "Unknown"
},
"issuer": {
"org": "Unknown",
"common_name": "HOSTNAME_MISMATCH"
},
"info": {
"days_left": "--",
"sort_order": 100000,
"background_class": "info"
}
},
"6": {
"server": {
"hostname":"self-signed.badssl.com"
},
"subject": {
"org": "Unknown",
"common_name": "The server provided a self-signed certificate or the provided certificate chain was incomplete",
"sans": "Unknown"
},
"issuer": {
"org": "Unknown",
"common_name": "UNABLE_TO_VERIFY_LEAF_SIGNATURE"
},
"info": {
"days_left": "--",
"sort_order": 100000,
"background_class": "info"
}
},
"7": {
"server": {
"hostname":"sha256.badssl.com"
},
"subject": {
"common_name": "*.badssl.com",
"sans": "DNS:*.badssl.com, DNS:badssl.com"
},
"issuer": {
"org": "COMODO CA Limited",
"common_name": "COMODO RSA Domain Validation Secure Server CA"
},
"info": {
"valid_from": "2015-04-09T00:00:00.000Z",
"valid_to": "2016-07-07T23:59:59.000Z",
"days_left": 20,
"sort_order": 20,
"background_class": "danger"
}
},
"8": {
"server": {
"hostname":"www.twitter.com"
},
"subject": {
"org": "Twitter, Inc.",
"common_name": "twitter.com",
"sans": "DNS:twitter.com, DNS:www.twitter.com"
},
"issuer": {
"org": "DigiCert Inc",
"common_name": "DigiCert SHA2 Extended Validation Server CA"
},
"info": {
"valid_from": "2016-03-09T00:00:00.000Z",
"valid_to": "2018-03-14T12:00:00.000Z",
"days_left": 635,
"sort_order": 635,
"background_class": "success"
}
},
"9": {
"server": {
"hostname":"nonexistent.runtondev.com"
},
"subject": {
"org": "Unknown",
"common_name": "The connection was reset by the server or timed out",
"sans": "Unknown"
},
"issuer": {
"org": "Unknown",
"common_name": "ECONNRESET"
},
"info": {
"days_left": "--",
"sort_order": 100000,
"background_class": "info"
}
},
"10": {
"server": {
"hostname":"warning.runtondev.com"
},
"subject": {
"common_name": "warning.runtondev.com",
"sans": ""
},
"issuer": {
"org": "Madeup CA",
"common_name": "CA that doesn't exist"
},
"info": {
"valid_from": "2015-04-09T00:00:00.000Z",
"valid_to": "2016-07-07T23:59:59.000Z",
"days_left": 45,
"sort_order": 45,
"background_class": "warning"
}
},
"11": {
"server": {
"hostname":"danger.runtondev.com"
},
"subject": {
"common_name": "danger.runtondev.com",
"sans": ""
},
"issuer": {
"org": "Madeup CA",
"common_name": "CA that doesn't exist"
},
"info": {
"valid_from": "2015-04-09T00:00:00.000Z",
"valid_to": "2016-07-07T23:59:59.000Z",
"days_left": 1,
"sort_order": 1,
"background_class": "danger"
}
},
}