SHARE
TWEET

Untitled

a guest Aug 13th, 2019 49 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/env python3
  2.  
  3. # vim: autoindent tabstop=4 shiftwidth=4 expandtab softtabstop=4 filetype=python
  4.  
  5. # This file is part of Supermicro IPMI certificate updater.
  6. # Supermicro IPMI certificate updater is free software: you can
  7. # redistribute it and/or modify it under the terms of the GNU General Public
  8. # License as published by the Free Software Foundation, version 2.
  9. #
  10. # This program is distributed in the hope that it will be useful, but WITHOUT
  11. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  12. # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
  13. # details.
  14. #
  15. # You should have received a copy of the GNU General Public License along with
  16. # this program; if not, write to the Free Software Foundation, Inc., 51
  17. # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18. #
  19. # Copyright (c) Jari Turkia
  20.  
  21.  
  22. import os
  23. import argparse
  24. import requests
  25. import logging
  26. from datetime import datetime
  27. from lxml import etree
  28. from urllib.parse import urlparse
  29.  
  30. REQUEST_TIMEOUT = 5.0
  31.  
  32. LOGIN_URL = '%s/cgi/login.cgi'
  33. IPMI_CERT_INFO_URL = '%s/cgi/ipmi.cgi'
  34. UPLOAD_CERT_URL = '%s/cgi/upload_ssl.cgi'
  35. REBOOT_IPMI_URL = '%s/cgi/BMCReset.cgi'
  36. CONFIG_CERT_URL = '%s/cgi/url_redirect.cgi?url_name=config_ssl'
  37.  
  38.  
  39. def login(session, url, username, password):
  40.     """
  41.     Log into IPMI interface
  42.     :param session: Current session object
  43.     :type session requests.session
  44.     :param url: base-URL to IPMI
  45.     :param username: username to use for logging in
  46.     :param password: password to use for logging in
  47.     :return: bool
  48.     """
  49.     login_data = {
  50.         'name': username,
  51.         'pwd': password
  52.     }
  53.  
  54.     login_url = LOGIN_URL % url
  55.     try:
  56.         result = session.post(login_url, login_data, timeout=REQUEST_TIMEOUT, verify=False)
  57.     except ConnectionError:
  58.         return False
  59.     if not result.ok:
  60.         return False
  61.     if '/cgi/url_redirect.cgi?url_name=mainmenu' not in result.text:
  62.         return False
  63.  
  64.     return True
  65.  
  66.  
  67. def get_ipmi_cert_info(session, url):
  68.     """
  69.     Verify existing certificate information
  70.     :param session: Current session object
  71.     :type session requests.session
  72.     :param url: base-URL to IPMI
  73.     :return: dict
  74.     """
  75.     timestamp = datetime.utcnow().strftime('%a %d %b %Y %H:%M:%S GMT')
  76.  
  77.     cert_info_data = {
  78.         'SSL_STATUS.XML': '(0,0)',
  79.         'time_stamp': timestamp  # 'Thu Jul 12 2018 19:52:48 GMT+0300 (FLE Daylight Time)'
  80.     }
  81.  
  82.     #for cookie in session.cookies:
  83.     #    print(cookie)
  84.     ipmi_info_url = IPMI_CERT_INFO_URL % url
  85.     try:
  86.         result = session.post(ipmi_info_url, cert_info_data, timeout=REQUEST_TIMEOUT, verify=False)
  87.     except ConnectionError:
  88.         return False
  89.     if not result.ok:
  90.         return False
  91.  
  92.     root = etree.fromstring(result.text)
  93.     # <?xml> <IPMI> <SSL_INFO> <STATUS>
  94.     status = root.xpath('//IPMI/SSL_INFO/STATUS')
  95.     if not status:
  96.         return False
  97.     # Since xpath will return a list, just pick the first one from it.
  98.     status = status[0]
  99.     has_cert = int(status.get('CERT_EXIST'))
  100.     has_cert = bool(has_cert)
  101.     if has_cert:
  102.         valid_from = status.get('VALID_FROM')
  103.         valid_until = status.get('VALID_UNTIL')
  104.  
  105.     return {
  106.         'has_cert': has_cert,
  107.         'valid_from': valid_from,
  108.         'valid_until': valid_until
  109.     }
  110.  
  111. def get_ipmi_cert_valid(session, url):
  112.     """
  113.     Verify existing certificate information
  114.     :param session: Current session object
  115.     :type session requests.session
  116.     :param url: base-URL to IPMI
  117.     :return: bool
  118.     """
  119.     timestamp = datetime.utcnow().strftime('%a %d %b %Y %H:%M:%S GMT')
  120.  
  121.     cert_info_data = {
  122.         'SSL_VALIDATE.XML': '(0,0)',
  123.         'time_stamp': timestamp  # 'Thu Jul 12 2018 19:52:48 GMT+0300 (FLE Daylight Time)'
  124.     }
  125.  
  126.     #for cookie in session.cookies:
  127.     #    print(cookie)
  128.     ipmi_info_url = IPMI_CERT_INFO_URL % url
  129.     try:
  130.         result = session.post(ipmi_info_url, cert_info_data, timeout=REQUEST_TIMEOUT, verify=False)
  131.     except ConnectionError:
  132.         return False
  133.     if not result.ok:
  134.         return False
  135.  
  136.     root = etree.fromstring(result.text)
  137.     # <?xml> <IPMI> <SSL_INFO>
  138.     status = root.xpath('//IPMI/SSL_INFO')
  139.     if not status:
  140.         return False
  141.     # Since xpath will return a list, just pick the first one from it.
  142.     status = status[0]
  143.     valid_cert = int(status.get('VALIDATE'))
  144.     return bool(valid_cert)
  145.  
  146. def upload_cert(session, url, key_file, cert_file):
  147.     """
  148.     Send X.509 certificate and private key to server
  149.     :param session: Current session object
  150.     :type session requests.session
  151.     :param url: base-URL to IPMI
  152.     :param key_file: filename to X.509 certificate private key
  153.     :param cert_file: filename to X.509 certificate PEM
  154.     :return:
  155.     """
  156.     with open(key_file, 'rb') as filehandle:
  157.         key_data = filehandle.read()
  158.     with open(cert_file, 'rb') as filehandle:
  159.         cert_data = filehandle.read()
  160.     files_to_upload = [
  161.         ('/tmp/cert.pem', ('cert.pem', cert_data, 'application/octet-stream')),
  162.         ('/tmp/key.pem', ('key.pem', key_data, 'application/octet-stream'))
  163.     ]
  164.  
  165.     upload_cert_url = UPLOAD_CERT_URL % url
  166.     try:
  167.         result = session.post(upload_cert_url, files=files_to_upload, timeout=REQUEST_TIMEOUT, verify=False)
  168.     except ConnectionError:
  169.         return False
  170.     if not result.ok:
  171.         return False
  172.  
  173.     if 'Content-Type' not in result.headers.keys() or result.headers['Content-Type'] != 'text/html':
  174.         # On failure, Content-Type will be 'text/plain' and 'Transfer-Encoding' is 'chunked'
  175.         return False
  176.     if 'CONFPAGE_RESET' not in result.text:
  177.         return False
  178.     return True
  179.  
  180.  
  181. def reboot_ipmi(session, url):
  182.     timestamp = datetime.utcnow().strftime('%a %d %b %Y %H:%M:%S GMT')
  183.  
  184.     reboot_data = {
  185.         'time_stamp': timestamp  # 'Thu Jul 12 2018 19:52:48 GMT+0300 (FLE Daylight Time)'
  186.     }
  187.  
  188.     upload_cert_url = REBOOT_IPMI_URL % url
  189.     try:
  190.         result = session.post(upload_cert_url, reboot_data, timeout=REQUEST_TIMEOUT, verify=False)
  191.     except ConnectionError:
  192.         return False
  193.     if not result.ok:
  194.         return False
  195.  
  196.     #print("Url: %s" % upload_cert_url)
  197.     #print(result.headers)
  198.     #print(result.text)
  199.     if '<STATE CODE="OK"/>' not in result.text:
  200.         return False
  201.  
  202.     return True
  203.  
  204.  
  205. def main():
  206.     parser = argparse.ArgumentParser(description='Update Supermicro IPMI SSL certificate')
  207.     parser.add_argument('--ipmi-url', required=True,
  208.                         help='Supermicro IPMI 2.0 URL')
  209.     parser.add_argument('--key-file', required=True,
  210.                         help='X.509 Private key filename')
  211.     parser.add_argument('--cert-file', required=True,
  212.                         help='X.509 Certificate filename')
  213.     parser.add_argument('--username', required=True,
  214.                         help='IPMI username with admin access')
  215.     parser.add_argument('--password', required=True,
  216.                         help='IPMI user password')
  217.     parser.add_argument('--no-reboot', action='store_true',
  218.                         help='The default is to reboot the IPMI after upload for the change to take effect.')
  219.     parser.add_argument('--quiet', action='store_true',
  220.                         help='Do not output anything if successful')
  221.     args = parser.parse_args()
  222.  
  223.     # Confirm args
  224.     if not os.path.isfile(args.key_file):
  225.         print("--key-file '%s' doesn't exist!" % args.key_file)
  226.         exit(2)
  227.     if not os.path.isfile(args.cert_file):
  228.         print("--cert-file '%s' doesn't exist!" % args.cert_file)
  229.         exit(2)
  230.     if args.ipmi_url[-1] == '/':
  231.         args.ipmi_url = args.ipmi_url[0:-1]
  232.  
  233.     if not args.quiet:
  234.         # Enable reuest logging
  235.         logging.basicConfig()
  236.         logging.getLogger().setLevel(logging.DEBUG)
  237.         requests_log = logging.getLogger("requests.packages.urllib3")
  238.         requests_log.setLevel(logging.DEBUG)
  239.         requests_log.propagate = True
  240.  
  241.     # Start the operation
  242.     requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
  243.     session = requests.session()
  244.     if not login(session, args.ipmi_url, args.username, args.password):
  245.         print("Login failed. Cannot continue!")
  246.         exit(2)
  247.  
  248.  
  249.     # Set mandatory cookies:
  250.     url_parts = urlparse(args.ipmi_url)
  251.     # Cookie: langSetFlag=0; language=English; SID=<dynamic session ID here!>; mainpage=configuration; subpage=config_ssl
  252.     mandatory_cookies = {
  253.         'langSetFlag': '0',
  254.         'language': 'English',
  255.         'mainpage': 'configuration',
  256.         'subpage': 'config_ssl'
  257.     }
  258.     for cookie_name, cookie_value in mandatory_cookies.items():
  259.         session.cookies.set(cookie_name, cookie_value, domain=url_parts.hostname)
  260.  
  261.     cert_info = get_ipmi_cert_info(session, args.ipmi_url)
  262.     if not cert_info:
  263.         print("Failed to extract certificate information from IPMI!")
  264.         exit(2)
  265.     if not args.quiet and cert_info['has_cert']:
  266.         print("There exists a certificate, which is valid until: %s" % cert_info['valid_until'])
  267.  
  268.     # Go upload!
  269.     if not upload_cert(session, args.ipmi_url, args.key_file, args.cert_file):
  270.         print("Failed to upload X.509 files to IPMI!")
  271.         exit(2)
  272.  
  273.     cert_valid = get_ipmi_cert_valid(session, args.ipmi_url)
  274.     if not cert_valid:
  275.         print("Uploads failed validation")
  276.         exit(2)
  277.  
  278.     if not args.quiet:
  279.         print("Uploaded files ok.")
  280.  
  281.     cert_info = get_ipmi_cert_info(session, args.ipmi_url)
  282.     if not cert_info:
  283.         print("Failed to extract certificate information from IPMI!")
  284.         exit(2)
  285.     if not args.quiet and cert_info['has_cert']:
  286.         print("After upload, there exists a certificate, which is valid until: %s" % cert_info['valid_until'])
  287.  
  288.     if not args.no_reboot:
  289.         if not args.quiet:
  290.             print("Rebooting IPMI to apply changes.")
  291.         if not reboot_ipmi(session, args.ipmi_url):
  292.             print("Rebooting failed! Go reboot it manually?")
  293.  
  294.     if not args.quiet:
  295.         print("All done!")
  296.  
  297.  
  298. if __name__ == "__main__":
  299.     main()
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top