Guest User

Untitled

a guest
Jul 12th, 2018
87
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.88 KB | None | 0 0
  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. from datetime import datetime
  26. from lxml import etree
  27.  
  28. REQUEST_TIMEOUT = 5.0
  29.  
  30. LOGIN_URL = '%s/cgi/login.cgi'
  31. IPMI_CERT_INFO_URL = '%s/cgi/ipmi.cgi'
  32. UPLOAD_CERT_URL = '%s/cgi/upload_ssl.cgi'
  33. REBOOT_IPMI_URL = '%s/cgi/url_redirect.cgi?url_name=config_ssl_fw_reset'
  34.  
  35.  
  36. def login(session, url, username, password):
  37. """
  38. Log into IPMI interface
  39. :param session: Current session object
  40. :type session requests.session
  41. :param url: base-URL to IPMI
  42. :param username: username to use for logging in
  43. :param password: password to use for logging in
  44. :return: bool
  45. """
  46. login_data = {
  47. 'name': username,
  48. 'pwd': password
  49. }
  50.  
  51. login_url = LOGIN_URL % url
  52. try:
  53. result = session.post(login_url, login_data, timeout=REQUEST_TIMEOUT, verify=False)
  54. except ConnectionError:
  55. return False
  56. if not result.ok:
  57. return False
  58. if '/cgi/url_redirect.cgi?url_name=mainmenu' not in result.text:
  59. return False
  60.  
  61. return True
  62.  
  63.  
  64. def get_ipmi_cert_info(session, url):
  65. """
  66. Verify existing certificate information
  67. :param session: Current session object
  68. :type session requests.session
  69. :param url: base-URL to IPMI
  70. :return: dict
  71. """
  72. timestamp = datetime.utcnow().strftime('%a %d %b %Y %H:%M:%S GMT')
  73.  
  74. cert_info_data = {
  75. 'SSL_STATUS.XML': '(0,0)',
  76. 'time_stamp': timestamp # 'Thu Jul 12 2018 19:52:48 GMT+0300 (FLE Daylight Time)'
  77. }
  78.  
  79. ipmi_info_url = IPMI_CERT_INFO_URL % url
  80. try:
  81. result = session.post(ipmi_info_url, cert_info_data, timeout=REQUEST_TIMEOUT, verify=False)
  82. except ConnectionError:
  83. return False
  84. if not result.ok:
  85. return False
  86.  
  87. root = etree.fromstring(result.text)
  88. # <?xml> <IPMI> <SSL_INFO> <STATUS>
  89. status = root.xpath('//IPMI/SSL_INFO/STATUS')
  90. if not status:
  91. return False
  92. # Since xpath will return a list, just pick the first one from it.
  93. status = status[0]
  94. has_cert = int(status.get('CERT_EXIST'))
  95. has_cert = bool(has_cert)
  96. if has_cert:
  97. valid_from = status.get('VALID_FROM')
  98. valid_until = status.get('VALID_UNTIL')
  99.  
  100. return {
  101. 'has_cert': has_cert,
  102. 'valid_from': valid_from,
  103. 'valid_until': valid_until
  104. }
  105.  
  106.  
  107. def upload_cert(session, url, key_file, cert_file):
  108. """
  109. Send X.509 certificate and private key to server
  110. :param session: Current session object
  111. :type session requests.session
  112. :param url: base-URL to IPMI
  113. :param key_file: filename to X.509 certificate private key
  114. :param cert_file: filename to X.509 certificate PEM
  115. :return:
  116. """
  117. with open(key_file, 'rb') as filehandle:
  118. key_data = filehandle.read()
  119. with open(cert_file, 'rb') as filehandle:
  120. cert_data = filehandle.read()
  121. files_to_upload = [
  122. ('/tmp/key.pem', ('/tmp/key.pem', key_data, 'application/octet-stream')),
  123. ('/tmp/cert.pem', ('/tmp/cert.pem', cert_data, 'application/x-x509-ca-cert'))
  124. ]
  125.  
  126. upload_cert_url = UPLOAD_CERT_URL % url
  127. try:
  128. result = session.post(upload_cert_url, files=files_to_upload, timeout=REQUEST_TIMEOUT, verify=False)
  129. except ConnectionError:
  130. return False
  131. if not result.ok:
  132. return False
  133.  
  134. if 'Content-Type' not in result.headers.keys() or result.headers['Content-Type'] != 'text/html':
  135. # On failure, Content-Type will be 'text/plain' and 'Transfer-Encoding' is 'chunked'
  136. return False
  137. if 'CONFPAGE_RESET' not in result.text:
  138. return False
  139.  
  140. return True
  141.  
  142.  
  143. def reboot_ipmi(session, url):
  144. upload_cert_url = REBOOT_IPMI_URL % url
  145. try:
  146. result = session.get(upload_cert_url, timeout=REQUEST_TIMEOUT, verify=False)
  147. except ConnectionError:
  148. return False
  149. if not result.ok:
  150. return False
  151.  
  152. if 'LANG_FW_RESET_DESC1' not in result.text:
  153. return False
  154.  
  155. return True
  156.  
  157.  
  158. def main():
  159. parser = argparse.ArgumentParser(description='Update Supermicro IPMI SSL certificate')
  160. parser.add_argument('--ipmi-url', required=True,
  161. help='Supermicro IPMI 2.0 URL')
  162. parser.add_argument('--key-file', required=True,
  163. help='X.509 Private key filename')
  164. parser.add_argument('--cert-file', required=True,
  165. help='X.509 Certificate filename')
  166. parser.add_argument('--username', required=True,
  167. help='IPMI username with admin access')
  168. parser.add_argument('--password', required=True,
  169. help='IPMI user password')
  170. parser.add_argument('--no-reboot',
  171. help='The default is to reboot the IPMI after upload for the change to take effect.')
  172. args = parser.parse_args()
  173.  
  174. # Confirm args
  175. if not os.path.isfile(args.key_file):
  176. print("--key-file '%s' doesn't exist!" % args.key_file)
  177. exit(2)
  178. if not os.path.isfile(args.cert_file):
  179. print("--cert-file '%s' doesn't exist!" % args.cert_file)
  180. exit(2)
  181. if args.ipmi_url[-1] == '/':
  182. args.ipmi_url = args.ipmi_url[0:-1]
  183.  
  184. # Start the operation
  185. requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
  186. session = requests.session()
  187. if not login(session, args.ipmi_url, args.username, args.password):
  188. print("Login failed. Cannot continue!")
  189. exit(2)
  190. cert_info = get_ipmi_cert_info(session, args.ipmi_url)
  191. if not cert_info:
  192. print("Failed to extract certificate information from IPMI!")
  193. exit(2)
  194. if cert_info['has_cert']:
  195. print("There exists a certificate, which is valid until: %s" % cert_info['valid_until'])
  196.  
  197. # Go upload!
  198. if not upload_cert(session, args.ipmi_url, args.key_file, args.cert_file):
  199. print("Failed to upload X.509 files to IPMI!")
  200. exit(2)
  201.  
  202. print("Uploaded files ok.")
  203. if not args.no_reboot:
  204. print("Rebooting IPMI to apply changes.")
  205. if not reboot_ipmi(session, args.ipmi_url):
  206. print("Rebooting failed! Go reboot it manually?")
  207.  
  208. print("All done!")
  209.  
  210.  
  211. if __name__ == "__main__":
  212. main()
Add Comment
Please, Sign In to add comment