選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

sitegen.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. #! /usr/bin/env python3
  2. import json
  3. import argparse
  4. import os
  5. import subprocess
  6. from os import path
  7. class SiteGenException(Exception):
  8. error = None
  9. code = None
  10. def __init__(self, error, code):
  11. self.error = error
  12. self.code = code
  13. class SiteGen:
  14. siteConfDir = ""
  15. siteDir = ""
  16. confDir = ""
  17. hooksEnabledDir = ""
  18. hooksAvailableDir = ""
  19. templatesDir = ""
  20. certRenewTime = ""
  21. letsencryptCommand = ""
  22. letsencryptDir = ""
  23. certDir = ""
  24. def __init__(self, config):
  25. self.siteConfDir = config["siteConfDir"]
  26. self.siteDir = config["siteDir"]
  27. self.confDir = config["confDir"]
  28. self.hooksEnabledDir = path.join(self.confDir, "hooks-enabled")
  29. self.hooksAvailableDir = path.join(self.confDir, "hooks-available")
  30. self.templatesDir = path.join(self.confDir, "templates")
  31. self.certRenewTime = config["certRenewTime"]
  32. self.letsencryptCommand = config["letsencryptCommand"]
  33. self.letsencryptDir = config["letsencryptDir"]
  34. self.certDir = config["certDir"]
  35. def make_dirs(self):
  36. if not path.isdir(self.certDir):
  37. os.makedirs(self.certDir)
  38. def get_hook_dir(self, hook_type, is_enabled):
  39. return path.join(self.hooksEnabledDir if is_enabled else self.hooksAvailableDir, hook_type)
  40. def get_hook_file(self, hook_type, hook_name, is_enabled):
  41. return path.join(self.get_hook_dir(hook_type, is_enabled), hook_name)
  42. def get_hook_files(self, hook_type, is_enabled):
  43. hook_dir = self.get_hook_dir(hook_type, is_enabled)
  44. files = os.listdir(hook_dir)
  45. files.sort()
  46. return files
  47. def is_hook_present(self, hook_type, hook_name, is_enabled):
  48. return path.isfile(self.get_hook_file(hook_type, hook_name, is_enabled))
  49. def is_hook_enabled(self, hook_type, hook_name):
  50. return self.is_hook_present(hook_type, hook_name, True)
  51. def get_letsencrypt_dir(self, domain):
  52. return path.join(self.letsencryptDir, domain)
  53. def symlink_letsencrypt_file(self, domain, file, outfile):
  54. letsencrypt_cert_file = path.abspath(self.get_letsencrypt_dir(domain))
  55. my_cert_file = path.join(self.certDir, outfile)
  56. if path.lexists(my_cert_file):
  57. os.remove(my_cert_file)
  58. os.symlink(path.join(letsencrypt_cert_file, file), my_cert_file)
  59. def get_cert_files(self, domain):
  60. return [path.abspath(path.join(self.certDir, domain + ".crt")),
  61. path.abspath(path.join(self.certDir, domain + ".key")),
  62. path.abspath(path.join(self.certDir, domain + "-chain.crt"))]
  63. def execute(self, exe, args, get_output):
  64. args.insert(0, exe)
  65. proc = subprocess.Popen(args, stdout=(subprocess.PIPE if get_output else None))
  66. out = proc.communicate()
  67. return proc.returncode, out[0]
  68. def execute_hooks(self, hook_type, hook_event, args):
  69. args.insert(0, hook_event)
  70. for hook_name in self.get_hook_files(hook_type, True):
  71. self.execute(self.get_hook_file(hook_type, hook_name, True), args, False)
  72. def is_cert_present(self, domain):
  73. cert_files = self.get_cert_files(domain)
  74. for cert_file in cert_files:
  75. if not path.isfile(cert_file):
  76. return False
  77. return True
  78. def get_cert_end_date(self, domain):
  79. cert_files = self.get_cert_files(domain)
  80. res, out = self.execute("openssl", ["x509", "-noout", "-in", cert_files[0], "-enddate"], True)
  81. if res == 0:
  82. return out.decode("UTF-8").split("=")[1][:-1]
  83. return None
  84. def is_cert_gonna_expire(self, domain, checkend):
  85. cert_files = self.get_cert_files(domain)
  86. res, out = self.execute("openssl", ["x509", "-noout", "-in", cert_files[0], "-checkend", str(checkend)], True)
  87. return res == 1
  88. def get_all_domains(self):
  89. files = os.listdir(self.certDir)
  90. files.sort()
  91. domains = []
  92. for file in files:
  93. if file.endswith(".crt") and self.is_cert_present(file[:-4]):
  94. domains.append(file[:-4])
  95. return domains
  96. def cert_request(self, domain, logger):
  97. cert_files = self.get_cert_files(domain)
  98. cert_files.insert(0, domain)
  99. self.execute_hooks("cert", "pre", cert_files)
  100. res, out = self.execute(self.letsencryptCommand, [domain], False)
  101. if res != 0:
  102. raise SiteGenException("Certificate request failed with code %i" % res, res)
  103. self.symlink_letsencrypt_file(domain, "cert.pem", domain + ".crt")
  104. self.symlink_letsencrypt_file(domain, "privkey.pem", domain + ".key")
  105. self.symlink_letsencrypt_file(domain, "chain.pem", domain + "-chain.crt")
  106. self.execute_hooks("cert", "post", cert_files)
  107. def certs_request(self, domains, logger):
  108. for domain in domains:
  109. self.cert_request(domain, logger)
  110. def cert_check(self, domain, logger):
  111. if not self.is_cert_present(domain):
  112. raise SiteGenException("Certificate not present: %s" % domain, 1)
  113. if self.is_cert_gonna_expire(domain, self.certRenewTime):
  114. logger("%s: %s" % (domain, self.get_cert_end_date(domain)))
  115. return True
  116. return False
  117. def certs_check(self, domains, logger):
  118. for domain in domains:
  119. self.cert_check(domain, logger)
  120. def cert_enddate(self, domain, logger):
  121. if not self.is_cert_present(domain):
  122. raise SiteGenException("Certificate not present: %s" % domain, 1)
  123. logger("%s: %s" % (domain, self.get_cert_end_date(domain)))
  124. def certs_enddate(self, domains, logger):
  125. for domain in domains:
  126. self.cert_enddate(domain, logger)
  127. def cert_renew(self, domain, logger):
  128. if self.cert_check(domain, logger):
  129. self.cert_request(domain, logger)
  130. def certs_renew(self, domains, logger):
  131. for domain in domains:
  132. self.cert_renew(domain, logger)
  133. def site_create(self, domain, logger):
  134. pass
  135. def site_remove(self, domain, logger):
  136. pass
  137. def hook_enable(self, hook_type, hook_name, logger):
  138. if not self.is_hook_present(hook_type, hook_name, False):
  139. raise SiteGenException("Hook is not present", 1)
  140. if self.is_hook_enabled(hook_type, hook_name):
  141. raise SiteGenException("Hook is already enabled", 0)
  142. logger("Enabling %s %s" % (hook_type, hook_name))
  143. hook_dir = self.get_hook_dir(hook_type, hook_name)
  144. if not path.isdir(hook_dir):
  145. os.makedirs(hook_dir)
  146. hook_file_available = self.get_hook_file(hook_type, hook_name, False)
  147. hook_file_enabled = self.get_hook_file(hook_type, hook_name, True)
  148. hook_relative_file = path.relpath(hook_file_available, self.get_hook_dir(hook_type, True))
  149. os.symlink(hook_relative_file, hook_file_enabled)
  150. def hook_disable(self, hook_type, hook_name, logger):
  151. if not self.is_hook_present(hook_type, hook_name, False):
  152. raise SiteGenException("Hook is not present", 1)
  153. if not self.is_hook_enabled(hook_type, hook_name):
  154. raise SiteGenException("Hook is not enabled", 0)
  155. logger("Disabling %s %s" % (hook_type, hook_name))
  156. os.remove(self.get_hook_file(hook_type, hook_name, True))
  157. def main():
  158. parser = argparse.ArgumentParser(description='Manage apache websites and SSL certificates')
  159. parser.add_argument('--config', dest='config', default='/etc/sitegen/sitegen.json', help='Configuration file path')
  160. parser.add_argument('--cert-request', metavar='cert_request', const='', nargs='?',
  161. help='Request/renew a certificate. Request/renew all certificates if no domain is specified')
  162. parser.add_argument('--cert-check', metavar='cert_check', const='', nargs='?',
  163. help='Check if certificate needs to be renewed. Check all if no domain is specified')
  164. parser.add_argument('--cert-renew', metavar='cert_renew', const='', nargs='?',
  165. help='Renew certificate if it needs to be. Renew all that needs to be if no domain is specified')
  166. parser.add_argument('--cert-enddate', metavar='cert_enddate', const='', nargs='?',
  167. help='Print certificate enddate. Print all certificates enddate if no domain is specified')
  168. parser.add_argument('--site-create', help='Create a site configuration', metavar='site_create')
  169. parser.add_argument('--site-remove', help='Remove a site configuration', metavar='site_remove')
  170. parser.add_argument('--hook-site-enable', help='Enable a site hook', dest='site_hook_enable', metavar='hook')
  171. parser.add_argument('--hook-site-disable', help='Disable a site hook', dest='site_hook_disable', metavar='hook')
  172. parser.add_argument('--hook-cert-enable', help='Enable a certificate hook', dest='hook_cert_enable', metavar='hook')
  173. parser.add_argument('--hook-cert-disable', help='Disable a certificate hook', dest='hook_cert_disable', metavar='hook')
  174. args = parser.parse_args()
  175. with open(args.config, "r") as f:
  176. config = json.load(f)
  177. site_gen = SiteGen(config)
  178. site_gen.make_dirs()
  179. logger = print
  180. try:
  181. if args.cert_request is not None:
  182. if args.cert_request == "":
  183. site_gen.certs_request(site_gen.get_all_domains(), logger)
  184. else:
  185. site_gen.cert_request(args.cert_request, logger)
  186. elif args.cert_check is not None:
  187. if args.cert_check == "":
  188. site_gen.certs_check(site_gen.get_all_domains(), logger)
  189. else:
  190. site_gen.cert_check(args.cert_check, logger)
  191. elif args.cert_renew is not None:
  192. if args.cert_renew == "":
  193. site_gen.certs_renew(site_gen.get_all_domains(), logger)
  194. else:
  195. site_gen.cert_renew(args.cert_renew, logger)
  196. elif args.cert_enddate is not None:
  197. if args.cert_enddate == "":
  198. site_gen.certs_enddate(site_gen.get_all_domains(), logger)
  199. else:
  200. site_gen.cert_enddate(args.cert_enddate, logger)
  201. elif args.site_create is not None:
  202. site_gen.site_create(args.site_create, logger)
  203. elif args.site_remove is not None:
  204. site_gen.site_remove(args.site_remove, logger)
  205. elif args.site_hook_enable is not None:
  206. site_gen.hook_enable("site", args.site_hook_enable, logger)
  207. elif args.site_hook_disable is not None:
  208. site_gen.hook_disable("site", args.site_hook_disable, logger)
  209. elif args.hook_cert_enable is not None:
  210. site_gen.hook_enable("cert", args.hook_cert_enable, logger)
  211. elif args.hook_cert_disable is not None:
  212. site_gen.hook_disable("cert", args.hook_cert_disable, logger)
  213. else:
  214. parser.print_help()
  215. except SiteGenException as e:
  216. print(e.error)
  217. exit(e.code)
  218. if __name__ == "__main__":
  219. main()