apache.py 5.8 KB

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