apache.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. # SPDX-FileCopyrightText: 2016-2023 Helmut Pozimski <helmut@pozimski.eu>
  2. #
  3. # SPDX-License-Identifier: GPL-2.0-only
  4. # -*- coding: utf8 -*-
  5. """
  6. Contains the apache module which takes care of maintaining the apache 2 SSL
  7. certificates.
  8. """
  9. import datetime
  10. import logging
  11. import os
  12. import subprocess
  13. import OpenSSL
  14. from amulib import helpers
  15. from amulib.cert_path_provider import CertPathProvider
  16. from amulib.helpers import restart_service
  17. LOGGER = logging.getLogger("acme-updater")
  18. def run(cert_path_provider: CertPathProvider, config=None,
  19. named_key_path="/run/named/session.key", dns_server="localhost"):
  20. """
  21. Main method of the apache module, actually replaces the certificates,
  22. manages the service and writes TLSA records if necessary.
  23. :param cert_path_provider: provider of the certificate path
  24. :type cert_path_provider: CertPathProvider
  25. :param config: configuration for the module.
  26. :type config: dict
  27. :param named_key_path: path to the named session.key
  28. :type named_key_path: str
  29. :param dns_server: DNS server to use to create TLSA records
  30. :type dns_server: str
  31. """
  32. cert_renewed = False
  33. parsed_vhosts = []
  34. if config:
  35. vhosts_dir = config["vhosts_dir"]
  36. tlsa = config["tlsa"]
  37. exclude_vhosts = config["exclude_vhosts"]
  38. tlsa_exclude = config["tlsa_exclude"]
  39. else:
  40. # Default parameters based on best guesses
  41. vhosts_dir = "/etc/apache2/sites-enabled"
  42. tlsa = False
  43. exclude_vhosts = []
  44. tlsa_exclude = []
  45. for vhost in os.listdir(vhosts_dir):
  46. if vhost.endswith(".conf") and vhost not in exclude_vhosts:
  47. vhost_absolute = os.path.join(vhosts_dir, vhost)
  48. with open(vhost_absolute, "r") as vhost_file:
  49. parsed_vhosts.extend(helpers.parse_apache_vhost(vhost_file))
  50. for vhost_entry in parsed_vhosts:
  51. main_domain = vhost_entry.get_main_domain()
  52. try:
  53. with open(vhost_entry.get_cert_path(), "r") as cert_file:
  54. cert_text = cert_file.read()
  55. except IOError:
  56. LOGGER.error("Error while opening cert file %s ", vhost_entry.get_cert_path())
  57. else:
  58. x509_current_cert = OpenSSL.crypto.load_certificate(
  59. OpenSSL.crypto.FILETYPE_PEM, cert_text)
  60. if "Let's Encrypt" in x509_current_cert.get_issuer().__str__():
  61. fullchain_path = cert_path_provider.provide_fullchain_path(main_domain)
  62. try:
  63. with open(fullchain_path, "r") as acme_cert_file:
  64. acme_cert_text = acme_cert_file.read()
  65. except IOError:
  66. LOGGER.error("Could not open certificate for %s in acme "
  67. "state directory", main_domain)
  68. else:
  69. x509_acme_cert = OpenSSL.crypto.load_certificate(
  70. OpenSSL.crypto.FILETYPE_PEM, acme_cert_text
  71. )
  72. expiry_date = \
  73. x509_acme_cert.get_notAfter().decode("utf-8")
  74. expiry_datetime = helpers.parse_asn1_time(expiry_date)
  75. if expiry_datetime < datetime.datetime.utcnow():
  76. LOGGER.warning(
  77. "Certificate for %s is expired and no newer "
  78. "one is available, bailing out!", main_domain)
  79. else:
  80. serial_current_cert = \
  81. x509_current_cert.get_serial_number()
  82. serial_acme_cert = x509_acme_cert.get_serial_number()
  83. if serial_current_cert == serial_acme_cert:
  84. LOGGER.debug("Cert for %s matches with the one "
  85. "installed, nothing to do.", main_domain)
  86. else:
  87. if tlsa:
  88. for domain in vhost_entry.get_domains():
  89. if domain not in tlsa_exclude:
  90. helpers.create_tlsa_records(
  91. domain, "443", x509_acme_cert,
  92. named_key_path, dns_server)
  93. if helpers.copy_file(fullchain_path, vhost_entry.get_cert_path()):
  94. acme_key_path = cert_path_provider.provide_key_path(main_domain)
  95. if helpers.copy_file(acme_key_path, vhost_entry.get_key_path()):
  96. LOGGER.info(
  97. "Successfully renewed cert for %s",
  98. main_domain)
  99. cert_renewed = True
  100. else:
  101. LOGGER.error(
  102. "Renewal of cert for %s failed, "
  103. "please clean up manually and "
  104. "check the backup files!",
  105. main_domain)
  106. else:
  107. LOGGER.error("Renewal of cert for %s failed, "
  108. "please clean up manually and "
  109. "check the backup files!",
  110. main_domain)
  111. if cert_renewed:
  112. LOGGER.debug("Checking apache configuration")
  113. try:
  114. subprocess.check_call(["/usr/sbin/apache2ctl", "-t"])
  115. except subprocess.CalledProcessError:
  116. LOGGER.error("Error in apache configuration, will not restart the "
  117. "web server")
  118. else:
  119. try:
  120. restart_service("apache2")
  121. except subprocess.CalledProcessError:
  122. LOGGER.error("Apache restart failed!")
  123. else:
  124. LOGGER.info("Apache restarted successfully")