Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/python2.7
- """
- ' Name: ssl_tool
- ' Version: 0.9
- ' Description: SSL order, installation, and checking tool using Comodo's API
- ' Riley L. for contributions,suggestions,or found bugs email rileymace57@gmail.com
- ' Contributions by: Jadon N., Nick D., and Kevin A.
- """
- import argparse
- import getpass
- import sys
- import os
- import re
- import urllib
- import time
- import base64
- import hashlib
- import requests
- import tldextract
- from rads.cpanel_api import whm_api
- from rads.cpanel_api import cpanel_api
- from rads.common import strcolor
- from platform import node as hostname
- from glob import glob
- """
- ' Ideas:
- ' -wildcard status searches based on domain
- ' -allow output of statuses for multiple orders by date, domains, order
- numbers, etc.
- ' -provide options for sumarized status messages, or detailed
- ' -provide options for pulling status, and SSL cert data on more than
- just order number
- ' -allow for multiple order numbers, domains, etc.
- ' -check local server as well to see if the SSL is installed properly(
- in case a cx wants to pre-install an SSL before pointing their DNS )
- ' -add country searching, and auto-change for full CC names via
- https://pypi.python.org/pypi/pycountry
- ' -make help output more pretty.
- Example: http://stackoverflow.com/q/20094215/49853
- """
- def parse_arguments():
- """
- ' Parse the command line arguments, and compare to available options
- ' @param none
- ' @return void
- """
- main_parser = argparse.ArgumentParser(
- prog="\n\nssl_tool",
- description="Perform common SSL tasks.",
- epilog="Feel free to send ideas to CaseyB@InMotionHosting.com")
- subparsers = main_parser.add_subparsers(dest='cmd')
- csr_help_command = strcolor("yellow", "ssl_tool gen-csr -h")
- csr_help = "Generate CSR -- use '" + csr_help_command + "' for more info"
- csr_epilog = strcolor(
- "yellow",
- "Please keep in mind that every option except for the " +
- "ones for 'company', and 'division' in gen-csr are required. If " +
- "'company' or 'division' are omitted ssl_tool will automagically " +
- "use the value from 'domain'.")
- csr_subparser = subparsers.add_parser(
- "gen-csr",
- help=csr_help,
- epilog=csr_epilog)
- csr_subparser.add_argument(
- "-d",
- "--domain",
- metavar="DOMAIN",
- type=str.lower,
- required=True,
- nargs=1)
- csr_subparser.add_argument(
- "-e",
- "--email",
- metavar="EMAIL",
- required=True,
- nargs=1)
- csr_subparser.add_argument(
- "-c",
- "--city",
- metavar="CITY",
- required=True,
- nargs="+")
- csr_subparser.add_argument(
- "-s",
- "--state",
- metavar="STATE",
- required=True,
- nargs="+")
- csr_subparser.add_argument(
- "-p",
- "--country",
- metavar="COUNTRY",
- required=True,
- nargs="+")
- csr_subparser.add_argument(
- "-co",
- "--company",
- metavar="COMPANY",
- nargs="*")
- csr_subparser.add_argument(
- "-div",
- "--division",
- metavar="DIVISION",
- nargs="*")
- # Python 2.7 won't allow me to make a sub-parser optional and the following
- # is the best compromise I could come up with.
- main_subparser_epilog = strcolor(
- "yellow",
- "Note that only one option from 'main' is used at a time.")
- main_help_command = strcolor(
- "yellow",
- "ssl_tool main -h")
- main_subparser_help = (
- "Everything else -- use '" +
- main_help_command +
- "' for more info")
- main_subparser = subparsers.add_parser(
- "main",
- help=main_subparser_help,
- epilog=main_subparser_epilog)
- default_group = main_subparser.add_mutually_exclusive_group()
- order_ssl_help = "Order an SSL for a given domain name using the " + \
- "most recent CSR from this server, and setup DCV"
- default_group.add_argument(
- "-o",
- "--order-ssl",
- metavar="DOMAIN",
- type=str.lower,
- help=order_ssl_help,
- nargs=1)
- setup_dcv_help = "Setup DCV file for a domain " + \
- "based on it's most recent CSR in "
- setup_dcv_help += "/var/cpanel/ssl/system/csrs" + \
- "[ automatically called by -o ]"
- default_group.add_argument(
- "--setup-dcv",
- metavar="DOMAIN",
- type=str.lower,
- help=setup_dcv_help,
- nargs=1)
- order_status_help = "Check the status of an SSL order by the order number"
- default_group.add_argument(
- "-s",
- "--order-status",
- metavar="ORDER_NUMBER",
- help=order_status_help,
- nargs=1)
- install_ssl_help = "Install an SSL that has been confirmed ready " + \
- "by Comodo by the Comodo order number"
- default_group.add_argument(
- "-i",
- "--install-ssl",
- metavar="ORDER_NUMBER",
- help=install_ssl_help,
- nargs=1)
- check_install_help = "Check externally that an SSL is installed " + \
- "properly by given domain name"
- default_group.add_argument(
- "-c",
- "--check-install",
- metavar="DOMAIN",
- type=str.lower,
- help=check_install_help,
- nargs=1)
- default_group.add_argument(
- "-v",
- "--version",
- action='version',
- version='%(prog)s 1.0')
- if len(sys.argv) > 1:
- args = vars(main_parser.parse_args())
- for k_arg, v_arg in args.iteritems():
- if type(args[k_arg]) is list:
- args[k_arg] = ' '.join(v_arg)
- else:
- monolithic_help = ""
- # retrieve subparsers from parser
- subparsers_actions = [
- action for action in main_parser._actions
- if isinstance(action, argparse._SubParsersAction)]
- # print sub-help for each parser
- for subparsers_action in subparsers_actions:
- # get all subparsers and print help
- for choice, subparser in subparsers_action.choices.items():
- monolithic_help += "\n\n"
- monolithic_help += strcolor(
- "red",
- "Subparser '{}'".format(choice))
- monolithic_help += "\n"
- monolithic_help += subparser.format_help()
- print monolithic_help
- quit()
- return args
- def check_input(inputs, input_type='domain'):
- """
- Do a basic sanity check on input args
- @params inputString as 'arguments'
- @params optional input_type to check
- @return
- """
- domain_regex = re.compile(
- r"^(([a-zA-Z]{1})|" +
- r"([a-zA-Z0-9][a-zA-Z0-9-_]{0,61}[a-zA-Z0-9]))(\.(([a-zA-Z]{1})|" +
- r"([a-zA-Z0-9][a-zA-Z0-9-_]{0,61}[a-zA-Z0-9]))){0,6}$")
- if input_type is 'domain':
- if not domain_regex.match(inputs):
- print strcolor(
- "red",
- "Domain does not seem to be in a valid format.")
- sys.exit()
- if not is_valid_tld(inputs):
- print strcolor("red", "Invalid TLD used in domain name.")
- sys.exit()
- if len(inputs) > 253:
- print strcolor("red", "Domain entered is too long.")
- sys.exit()
- if 'inmotionhosting.com' in inputs or 'webhostinghub.com' in inputs:
- print strcolor("red", "Domain provided contains brand.")
- sys.exit()
- email_regex = re.compile(
- r"[^@]+@[^@]+\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$")
- if input_type is 'email':
- if not is_valid_tld(inputs):
- print strcolor("red", "Invalid TLD in email address.")
- sys.exit()
- if not email_regex.match(inputs):
- print strcolor(
- "red",
- "Email does not seem to be in a valid format.")
- sys.exit()
- if input_type is 'order_number' and not inputs.isdigit():
- print strcolor("red", "Order number must be only numerals.")
- sys.exit()
- def is_valid_tld(string):
- """
- Check string for valid TLD
- @params String to check as 'string'
- @return Bool of status
- """
- if len(tldextract.extract(string)[2]) != 0:
- return True
- return False
- def generate_csr(csr_data):
- """
- Generate and pring a CSR, and self-signed SSL based on a given string
- @params cPanel required CSR parameters as raw_csr_data
- @return Bool for success or failure
- """
- if check_input(csr_data['domain']):
- return False
- if len(csr_data['state']) == 2:
- print strcolor("red", "State must not be abbreviated.")
- return False
- if len(csr_data['country']) != 2 or not csr_data['country'].isalpha():
- print strcolor("red", "Country must be 2 letter ISO code.")
- return False
- if not csr_data['company']:
- csr_data['company'] = csr_data['domain']
- if not csr_data['division']:
- csr_data['division'] = csr_data['domain']
- if check_input(csr_data['email'], 'email'):
- return False
- userdata_request = {
- "function": "domainuserdata",
- "version": "1",
- "domain": csr_data['domain']
- }
- userdata_response = whm_api(**userdata_request)
- try:
- userdata_response['data']
- except (KeyError, TypeError):
- print strcolor("red", userdata_response['metadata']['reason'])
- return False
- csr_request = {
- "function": "generatessl",
- "version": "1",
- "domains": csr_data['domain'],
- "localityName": csr_data['city'],
- "stateOrProvinceName": csr_data['state'],
- "countryName": csr_data['country'],
- "organizationName": csr_data['company'],
- "organizationalUnitName": csr_data['division'],
- "emailAddress": csr_data['email'],
- "keysize": '2048',
- "signatureHash": "PREFER_SHA2"
- }
- csr_response = whm_api(**csr_request)
- try:
- print csr_response['data']['csr']
- except (KeyError, TypeError):
- print strcolor("red", csr_response['metadata']['reason'])
- return False
- print strcolor(
- "yellow",
- "Please note that if you are proceeding to install that " +
- "you do not need to copy the CSR data, as ssl_tool will " +
- "pull the CSR from the server for you during the install step.")
- return True
- def call_comodo(
- request_type,
- request_parameters,
- split_char='\n',
- password=''):
- """
- Call Comodo's API for given parameters
- @params type of request as a string, and request parameters as dict
- @return URL encoded raw results of API call
- """
- brand_domain = "inmotionhosting.com"
- server_hostname = hostname()
- if "webhostinghub.com" in server_hostname:
- brand_domain = "webhostinghub.com"
- base_request_url = "https://secure.comodo.net/products/"
- comodo_login_name = 'certs@' + brand_domain
- print
- if password is '':
- comodo_pass = getpass.getpass(
- "Current " +
- comodo_login_name +
- " password, please: ")
- else:
- comodo_pass = password
- comodo_request = {
- "loginName": comodo_login_name,
- "loginPassword": comodo_pass
- }
- comodo_request.update(request_parameters)
- print "Calling Comodo..."
- try:
- comodo_response = requests.post(
- base_request_url +
- request_type,
- data=comodo_request)
- except requests.exceptions.ConnectionError:
- print "Unable to reach Comodo at " + base_request_url + request_type \
- + " using " + comodo_login_name
- return False
- comodo_response = comodo_response.text.split(split_char)
- if split_char == '&':
- comodo_response = [pair.split('=') for pair in comodo_response]
- return {
- "comodo_response": comodo_response,
- "comodo_pass": comodo_pass
- }
- def get_csr(domain):
- """
- Get most recent CSR for given domain
- @params domain name as string to search for
- @return most recent CSR as string
- """
- check_input(domain)
- search_term = re.sub(r'[\.-]+', '_', domain)
- system_csr_path = "/var/cpanel/ssl/system/csrs/"
- max_mtime = 0
- most_recent_file_path = ""
- if not glob('{}*'.format(system_csr_path)):
- print strcolor("red", "No CSRs found!")
- print strcolor(
- "red",
- "Have you already generated the CSRs on this server?")
- return False
- for file_found in glob('{}{}*'.format(system_csr_path, search_term)):
- if os.path.getmtime(file_found) > max_mtime:
- max_mtime = os.path.getmtime(file_found)
- most_recent_file_path = file_found
- try:
- with open(most_recent_file_path, 'r') as csr_file:
- csr = csr_file.read()
- return csr
- except IOError:
- print strcolor("red", "CSR for given domain not found!")
- return False
- def setup_dcv(domain):
- """
- Setup DCV for the SSL order process
- @params domain name as string that the DCV needs to be setup for
- @return bool based on success / fail
- """
- if check_input(domain):
- return False
- csr = str(get_csr(domain))
- if csr == 'False':
- return False
- dcv_data = {'MD5': '', 'SHA1': ''}
- first_line = csr.find('\n')
- last_line = csr.rfind('\n')
- decoded_csr = base64.b64decode(csr[first_line+1:last_line])
- dcv_data['MD5'] = hashlib.md5(decoded_csr).hexdigest().upper()
- dcv_data['SHA1'] = hashlib.sha1(decoded_csr).hexdigest().upper()
- extracted_domain = tldextract.extract(domain)
- username_request = {
- 'function': 'domainuserdata',
- 'version': '1',
- 'domain': extracted_domain.domain + "." + extracted_domain.suffix
- }
- username_response = whm_api(**username_request)
- try:
- domain_owner = username_response['data']['userdata']['user']
- except (KeyError, TypeError):
- print strcolor("red", username_response['metadata']['reason'])
- print strcolor("yellow", "DCV MD5 is: " + dcv_data['MD5'])
- print strcolor("yellow", "DCV SHA1 is: " + dcv_data['SHA1'])
- print strcolor("red", "DCV not placed.")
- quit()
- # cpapi2
- # --user=fromcr6 DomainLookup getdocroot domain=www.fromcrimsontowool.com
- docroot_request = {
- 'module': 'DomainLookup',
- 'version': 2,
- 'function': 'getdocroot',
- 'user': domain_owner,
- 'domain': domain
- }
- docroot_response = cpanel_api(**docroot_request)
- try:
- domain_docroot = docroot_response['cpanelresult']['data'][0]['docroot']
- except (KeyError, TypeError):
- print strcolor("yellow", "DCV MD5 is: " + dcv_data['MD5'])
- print strcolor("yellow", "DCV SHA1 is: " + dcv_data['SHA1'])
- print strcolor("red", "DCV not placed.")
- quit()
- htaccess_rule = '<IfModule mod_rewrite.c>\nRewriteRule ' + \
- dcv_data['MD5'] + '.txt - [L]\n</IfModule>\n\n'
- target_htaccess_file = "/home/" + domain_owner + "/.htaccess"
- with open(target_htaccess_file, 'a+') as htaccess_file:
- original_contents = htaccess_file.read()
- if dcv_data['MD5'] in original_contents:
- print strcolor(
- "yellow",
- target_htaccess_file +
- " file already has MD5 hash inside.")
- return
- htaccess_file.seek(0, 0)
- htaccess_file.write(htaccess_rule + original_contents)
- print strcolor(
- "green",
- target_htaccess_file +
- " file updated with DCV hash.")
- if os.path.isdir(domain_docroot):
- with open(domain_docroot+'/'+dcv_data['MD5']+".txt", 'w+') as dcv_file:
- dcv_file.write(dcv_data['SHA1'] + "\n" + "comodoca.com")
- print strcolor(
- "green",
- "DCV file placed at: " +
- domain_docroot +
- "/" +
- dcv_data['MD5'] +
- ".txt")
- check_dcv(domain + "/" + dcv_data['MD5'] + ".txt")
- print strcolor("yellow", "DCV setup complete.")
- return
- def check_dcv(dcv_url):
- """
- Check the Domain Control Validation (DCV)
- @param URL to check the DCV
- @return bool
- """
- # Check for https and do proper DCV check
- site = requests.get(dcv_url)
- if site.url[:8] == r'https://':
- # Check HTTPS DCV
- print 'checking HTTPS URL'
- elif site.url[:7] == r'http://':
- # Check HTTP DCV
- print 'checking HTTP URL'
- return
- def order_ssl(domain):
- """
- Order an SSL using most recent CSR on the local server for a domain
- @param domain to generate for the SSL
- @return void
- """
- check_input(domain)
- csr = get_csr(domain)
- if not csr:
- return False
- setup_dcv(domain)
- # The 'test' option this will need to be removed,
- # or at least changed before release
- order_request = {
- "product": '488',
- "years": '1',
- "serverSoftware": '31',
- "csr": csr,
- "dcvMethod": 'HTTP_CSR_HASH',
- "isCustomerValidated": 'n',
- "test": 'Y'
- }
- order_response = call_comodo('!AutoApplySSL', order_request)[0]
- order_return_code = order_response[0]
- if order_return_code != '0' and order_return_code != '1':
- print strcolor("red", order_response[1])
- return False
- elif order_return_code == '1':
- print strcolor(
- "yellow",
- "Order was successful, but payment is still required.")
- elif int(order_return_code) >= 0:
- # The folowing text is courtesy of
- # https://secure.comodo.net/api/pdf/webhostreseller/sslcertificates/AutoApplySSL%20v1.44.pdf
- expected_return_texts = {
- 1: "This order has been automatically validated and the " +
- "certificate will be issued as soon as possible (i.e. " +
- "almost definitely within the next hour).",
- 24: "This order, although marked as validated by the " +
- "Web Host, is awaiting final approval by a Comodo " +
- "account manager.",
- 48: "This order was not marked as validated by the Web Host, " +
- "and could not be automatically validated by Comodo: " +
- "the 48 hours starts when " +
- "Comodo has received various documents from the end-user."
- }
- try:
- expected_delivery = int(order_response[3])
- except ValueError:
- print strcolor(
- "red",
- "Expected delivery time parameter is not a number!")
- return False
- if expected_delivery == 1:
- print strcolor(
- "green",
- expected_return_texts.get(expected_delivery))
- else:
- print strcolor(
- "yellow",
- expected_return_texts.get(expected_delivery))
- print strcolor("green", "Order Successful!")
- print "Order Number is " + strcolor("yellow", order_response[1])
- return
- def convert(name):
- """
- Convert a given string to replace _ with spaces, and apply title()
- @params string to convert
- @return converted string
- """
- string_1 = re.sub('(.)([A-Z][a-z]+)', r'\1 \2', name)
- return re.sub('([a-z0-9])([A-Z])', r'\1 \2', string_1).title()
- def order_status(order_number):
- """
- ' Check the order status of an already purchased SSL by order number
- ' @param Comodo order number
- ' @return void
- """
- check_input(order_number, 'order_number')
- comodo_parameters = {'orderNumber': order_number}
- status_result = call_comodo('!WebHostReport', comodo_parameters, '&')
- print_keys = [
- "orderNumber",
- "domain",
- "_status",
- "_dateTime",
- "_lastStatusChange",
- "_dcvStatus"
- ]
- print "\n"
- for key, value in status_result['comodo_response']:
- if not (any(name in key for name in print_keys) or
- key == 'errorMessage' and value != '0' or
- key == 'noOfResults' and value != '1'):
- continue
- if "status" in key:
- value = urllib.unquote(value).decode("utf8").replace("+", " ")
- if "lastStatusChange" in key or "dateTime" in key:
- if "dateTime" in key:
- key = "dateOrderSubmitted"
- date_time_str = "%d%^b%y %H%M %Z"
- value = time.strftime(date_time_str, time.localtime(int(value)))
- key_name = key.split('_')[-1]
- print convert(key_name) + ': ' + value
- print "\n"
- call_comodo(
- '!WebHostReport',
- comodo_parameters,
- '&',
- status_result['comodo_pass'])
- return
- def install_ssl(order_number):
- """
- Install an SSL after pulling it from Comodo
- @params Comodo order number as a string
- @return void
- """
- check_input(order_number, 'order_number')
- comodo_parameters = {
- 'orderNumber': order_number,
- 'queryType': '1',
- 'showFQDN': 'Y'
- }
- collect_ssl_response = call_comodo(
- 'download/CollectSSL',
- comodo_parameters)
- collect_ssl_response_code = collect_ssl_response[0]
- if collect_ssl_response_code != '2':
- print strcolor("red", collect_ssl_response[1])
- return False
- domain = collect_ssl_response[1].lower()
- begin_domain_ssl = collect_ssl_response.index(
- "-----BEGIN CERTIFICATE-----")
- end_domain_ssl = collect_ssl_response.index(
- "-----END CERTIFICATE-----")
- domain_ssl = collect_ssl_response[begin_domain_ssl:end_domain_ssl+1]
- domain_ssl = "\n".join(domain_ssl)
- ca_ssls = collect_ssl_response[end_domain_ssl+1:]
- ca_ssls = "\n".join(ca_ssls)
- print "Domain from given order number is: " + strcolor("green", domain)
- extracted_domain = tldextract.extract(domain)
- user_name_parameters = {
- 'function': 'domainuserdata',
- 'version': '1',
- 'domain': extracted_domain.domain + "." + extracted_domain.suffix
- }
- user_name_response = whm_api(**user_name_parameters)
- try:
- domain_ip = user_name_response['data']['userdata']['ip']
- except (KeyError, TypeError):
- print strcolor("red", user_name_response['metadata']['reason'])
- return False
- crt_key_parameters = {
- 'function': 'fetchsslinfo',
- 'version': '1',
- 'domain': domain
- }
- crt_response = whm_api(**crt_key_parameters)
- try:
- crt_key = crt_response['data']['key']
- except (KeyError, TypeError):
- print strcolor("red", crt_response['metadata']['reason'])
- return False
- install_ssl_request = {
- 'function': 'installssl',
- 'version': '1',
- 'domain': domain,
- 'crt': domain_ssl,
- 'key': crt_key,
- 'cab': ca_ssls,
- 'ip': domain_ip
- }
- install_response = whm_api(**install_ssl_request)
- try:
- install_response['data']['statusmsg']
- except (KeyError, TypeError):
- print strcolor(
- "red",
- "cPanel was unable to install the SSL from Comodo.")
- print strcolor("red", install_response['metadata']['reason'])
- return False
- check_install(domain)
- return
- def check_install(domain):
- """
- Check the installation of an SSL for a domain, and print the status
- @params domain to check as a string
- @return void
- """
- check_input(domain)
- one_month = 2592000
- one_year = 28930000
- time_now = int(time.time())
- ssl_age = 'fair'
- check_install_url = 'https://secure.comodo.com/sslchecker'
- check_install_request = {
- 'url': domain
- }
- fields_to_print = [
- 'server_ip',
- 'cert_notAfter',
- 'cert_issuer_DN',
- 'cert_subject_CN'
- ]
- field_labels = {
- 'server_ip': 'Server/Domain IP',
- 'cert_notAfter': 'Cert. not valid after',
- 'cert_issuer_DN': 'Cert. issuer',
- 'cert_subject_CN': 'SSL \'Common Name\''
- }
- check_install_results = requests.post(
- check_install_url, data=check_install_request).text.split('&')
- check_install_results = [check_install_result.split('=') for
- check_install_result in check_install_results]
- error_message = check_install_results[-2][1].replace("+", " ")
- if error_message != "OK":
- print strcolor("red", "Comodo returned: " + error_message)
- return False
- for key, value in check_install_results:
- if key == "server_domain" and value != domain:
- print strcolor(
- "yellow",
- "SSL domain does not match given domain: " + value)
- if key in fields_to_print:
- if domain not in value and key == 'cert_subject_CN':
- print strcolor(
- "red",
- "Domain given does not match certificate")
- value = strcolor("red", value)
- if "DN" in key:
- value = urllib.unquote(value.replace("%0D%0A", "="))
- value = value.decode("utf8")
- value = ', '.join(value.split('=')[1::2])
- value = value.replace("+", " ")
- if "notAfter" in key:
- if value > time_now + one_month:
- ssl_age = 'bad'
- if value > time_now + one_year - one_month:
- ssl_age = 'good'
- date_time = "%d%^b%y %H%M %Z"
- value = time.strftime(date_time, time.localtime(int(value)))
- if ssl_age == 'bad':
- value = strcolor("red", value)
- elif ssl_age == 'good':
- value = strcolor("green", value)
- else:
- value = strcolor("yellow", value)
- print field_labels[key] + ": " + value
- return
- def main():
- """
- Main function for getting things started
- @params none
- @return void
- """
- args = parse_arguments()
- if args['cmd'] == "gen-csr":
- generate_csr(args)
- elif args['order_ssl']:
- order_ssl(args['order_ssl'])
- elif args['setup_dcv']:
- setup_dcv(args['setup_dcv'])
- elif args['order_status']:
- order_status(args['order_status'])
- elif args['install_ssl']:
- install_ssl(args['install_ssl'])
- elif args['check_install']:
- check_install(args['check_install'])
- if __name__ == "__main__":
- try:
- main()
- except KeyboardInterrupt:
- print "\nCtrl + C received..."
- sys.exit("\nQuitting.")
Add Comment
Please, Sign In to add comment