# This file is part of acme-updater, written by Helmut Pozimski 2016-2017. # # stov is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 2 of the License. # # stov is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with stov. If not, see . # -*- coding: utf8 -*- """ Contains the service module which manages certificates for generic services that don't need any special configuration. The configuration needs to at least contain the keys "certificate_path", "key_path", "tlsa"and "tlsa_ports". """ import logging import os import subprocess import shutil from amulib import helpers import OpenSSL LOGGER = logging.getLogger("acme-updater") def run(service_name, config, acme_dir="/var/lib/acme", named_key_path="/run/named/session.key", dns_server="localhost"): """ :param service_name: name of the service :type service_name: str :param config: configuration for the service :type config: dict :param acme_dir: path to the acme state dir :type acme_dir: str :param named_key_path: path to the named session.key :type named_key_path: str :param dns_server: DNS server to use to create TLSA records :type dns_server: str """ certificate_path = config["certificate_path"] key_path = config["key_path"] tlsa = config["tlsa"] tlsa_ports = config["tlsa_ports"] renewal_successful = False try: with open(certificate_path, "r") as cert_file: cert_text = cert_file.read() except IOError: LOGGER.error("Error while opening the %s certificate", service_name) else: current_cert = OpenSSL.crypto.load_certificate( OpenSSL.crypto.FILETYPE_PEM, cert_text ) cert_alt_names = helpers.get_subject_alt_name(current_cert) fqdn = cert_alt_names[0] acme_cert_path = os.path.join(acme_dir, "live", fqdn, "cert") acme_fullchain_path = os.path.join(acme_dir, "live", fqdn, "fullchain") if helpers.check_renewal(current_cert, acme_cert_path): try: with open(acme_cert_path, "r") as acme_cert_file: acme_cert_text = acme_cert_file.read() except IOError: LOGGER.error("Error while opening new %s " "certificate file", service_name) else: acme_cert = OpenSSL.crypto.load_certificate( OpenSSL.crypto.FILETYPE_PEM, acme_cert_text ) if tlsa: for name in cert_alt_names: for port in tlsa_ports: helpers.create_tlsa_records(name, port, acme_cert, named_key_path, dns_server) newkey_path = os.path.join(acme_dir, "live", fqdn, "privkey") if certificate_path == key_path: if helpers.create_backup_copy(certificate_path): try: with open(certificate_path, "wb") as target: with open(acme_fullchain_path, "rb") as chain: with open(newkey_path, "rb") as newkey: shutil.copyfileobj(newkey, target) shutil.copyfileobj(chain, target) except IOError: LOGGER.error("Renewal of cert for %s failed, " "please clean up manually and " "check the backup files!", service_name) else: renewal_successful = True else: LOGGER.error("Renewal of cert for %s failed, " "please clean up manually and " "check the backup files!", service_name) else: if helpers.copy_file(acme_fullchain_path, certificate_path): if helpers.copy_file(newkey_path, key_path): renewal_successful = True else: LOGGER.error("Renewal of cert for %s failed, " "please clean up manually and " "check the backup files!", service_name) else: LOGGER.error("Renewal of cert for %s failed, " "please clean up manually and " "check the backup files!", service_name) if renewal_successful: LOGGER.info("Certificate for %s successfully " "renewed, restarting service.", service_name) subprocess.call(["/etc/init.d/%s" % service_name, "restart"])