apache.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. # This file is part of acme-updater, written by Helmut Pozimski 2016-2017.
  2. #
  3. # stov is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, version 2 of the License.
  6. #
  7. # stov is distributed in the hope that it will be useful,
  8. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. # GNU General Public License for more details.
  11. #
  12. # You should have received a copy of the GNU General Public License
  13. # along with stov. If not, see <http://www.gnu.org/licenses/>.
  14. # -*- coding: utf8 -*-
  15. """
  16. Contains the apache module which takes care of maintaining the apache 2 SSL
  17. certificates.
  18. """
  19. import logging
  20. import os
  21. import datetime
  22. import subprocess
  23. from amulib import helpers
  24. import OpenSSL
  25. LOGGER = logging.getLogger("acme-updater")
  26. def run(config=None, acme_dir="/var/lib/acme",
  27. named_key_path="/run/named/session.key", dns_server="localhost"):
  28. """
  29. Main method of the apache module, actually replaces the certificates,
  30. manages the service and writes TLSA records if necessary.
  31. :param config: configuration for the module.
  32. :type config: dict
  33. :param acme_dir: path to the acme state dir
  34. :type acme_dir: str
  35. :param named_key_path: path to the named session.key
  36. :type named_key_path: str
  37. :param dns_server: DNS server to use to create TLSA records
  38. :type dns_server: str
  39. """
  40. cert_renewed = False
  41. parsed_vhosts = []
  42. if config:
  43. vhosts_dir = config["vhosts_dir"]
  44. tlsa = config["tlsa"]
  45. exclude_vhosts = config["exclude_vhosts"]
  46. tlsa_exclude = config["tlsa_exclude"]
  47. else:
  48. # Default parameters based on best guesses
  49. vhosts_dir = "/etc/apache2/sites-enabled"
  50. tlsa = False
  51. exclude_vhosts = []
  52. tlsa_exclude = []
  53. for vhost in os.listdir(vhosts_dir):
  54. if vhost.endswith(".conf") and vhost not in exclude_vhosts:
  55. vhost_absolute = os.path.join(vhosts_dir, vhost)
  56. with open(vhost_absolute, "r") as vhost_file:
  57. parsed_vhosts.extend(helpers.parse_apache_vhost(vhost_file))
  58. for entry in parsed_vhosts:
  59. try:
  60. with open(entry[1], "r") as cert_file:
  61. cert_text = cert_file.read()
  62. except IOError:
  63. LOGGER.error("Error while opening cert file %s ", entry[1])
  64. else:
  65. x509_current_cert = OpenSSL.crypto.load_certificate(
  66. OpenSSL.crypto.FILETYPE_PEM, cert_text)
  67. if "Let's Encrypt" in x509_current_cert.get_issuer().__str__():
  68. acme_cert_path = os.path.join(acme_dir, "live", entry[0],
  69. "cert")
  70. try:
  71. with open(acme_cert_path, "r") as acme_cert_file:
  72. acme_cert_text = acme_cert_file.read()
  73. except IOError:
  74. LOGGER.error("Could not open certificate for %s in acme "
  75. "state directory", entry[0])
  76. else:
  77. x509_acme_cert = OpenSSL.crypto.load_certificate(
  78. OpenSSL.crypto.FILETYPE_PEM, acme_cert_text
  79. )
  80. expiry_date = \
  81. x509_acme_cert.get_notAfter().decode("utf-8")
  82. expiry_datetime = helpers.parse_asn1_time(expiry_date)
  83. if expiry_datetime < datetime.datetime.utcnow():
  84. LOGGER.warning(
  85. "Certificate for %s is expired and no newer "
  86. "one is available, bailing out!", entry[0])
  87. else:
  88. serial_current_cert = \
  89. x509_current_cert.get_serial_number()
  90. serial_acme_cert = x509_acme_cert.get_serial_number()
  91. if serial_current_cert == serial_acme_cert:
  92. LOGGER.debug("Cert for %s matches with the one "
  93. "installed, nothing to do.", entry[1])
  94. else:
  95. if tlsa:
  96. for domain in entry[3]:
  97. if domain not in tlsa_exclude:
  98. helpers.create_tlsa_records(
  99. domain, "443", x509_acme_cert,
  100. named_key_path, dns_server)
  101. if helpers.copy_file(acme_cert_path, entry[1]):
  102. acme_key_path = os.path.join(acme_dir,
  103. "live", entry[0],
  104. "privkey")
  105. if helpers.copy_file(acme_key_path, entry[2]):
  106. LOGGER.info(
  107. "Successfully renewed cert for %s",
  108. entry[0])
  109. cert_renewed = True
  110. else:
  111. LOGGER.error(
  112. "Renewal of cert for %s failed, "
  113. "please clean up manually and "
  114. "check the backup files!",
  115. entry[0])
  116. else:
  117. LOGGER.error("Renewal of cert for %s failed, "
  118. "please clean up manually and "
  119. "check the backup files!",
  120. entry[0])
  121. if cert_renewed:
  122. LOGGER.debug("Checking apache configuration")
  123. try:
  124. subprocess.check_call(["/usr/sbin/apache2ctl", "-t"])
  125. except subprocess.CalledProcessError:
  126. LOGGER.error("Error in apache configuration, will not restart the "
  127. "web server")
  128. else:
  129. try:
  130. subprocess.check_call(["/etc/init.d/apache2", "restart"])
  131. except subprocess.CalledProcessError:
  132. LOGGER.error("Apache restart failed!")
  133. else:
  134. LOGGER.info("Apache restarted successfully")