From 965027ce528358cd838cffd6f3a8228630643637 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Wed, 17 Feb 2016 11:36:38 -0800 Subject: [PATCH 1/7] Start metamorphising to use LE's IInstaller interface --- MTAConfigGenerator.py | 137 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 130 insertions(+), 7 deletions(-) diff --git a/MTAConfigGenerator.py b/MTAConfigGenerator.py index bb3d5e5db..ae74892eb 100755 --- a/MTAConfigGenerator.py +++ b/MTAConfigGenerator.py @@ -28,13 +28,6 @@ class PostfixConfigGenerator(MTAConfigGenerator): def __init__(self, policy_config, postfix_dir, fixup=False): self.fixup = fixup self.postfix_dir = postfix_dir - self.policy_file = os.path.join(postfix_dir, "starttls_everywhere_policy") - MTAConfigGenerator.__init__(self, policy_config) - self.postfix_cf_file = self.find_postfix_cf() - self.wrangle_existing_config() - self.set_domainwise_tls_policies() - #TODO make this optional for testing, etc. - os.system("sudo service postfix reload") def ensure_cf_var(self, var, ideal, also_acceptable): """ @@ -145,6 +138,133 @@ class PostfixConfigGenerator(MTAConfigGenerator): f.write("\n".join(self.policy_lines) + "\n") f.close() + ### Let's Encrypt client IPlugin ### + + def prepare(): + """Prepare the plugin. + Finish up any additional initialization. + :raises .PluginError: + when full initialization cannot be completed. + :raises .MisconfigurationError: + when full initialization cannot be completed. Plugin will + be displayed on a list of available plugins. + :raises .NoInstallationError: + when the necessary programs/files cannot be located. Plugin + will NOT be displayed on a list of available plugins. + :raises .NotSupportedError: + when the installation is recognized, but the version is not + currently supported. + """ + # XXX ensure we raise the right kinds of exceptions + self.policy_file = os.path.join(postfix_dir, "starttls_everywhere_policy") + MTAConfigGenerator.__init__(self, policy_config) + self.postfix_cf_file = self.find_postfix_cf() + + + def more_info(): + """Human-readable string to help the user. + Should describe the steps taken and any relevant info to help the user + decide which plugin to use. + :rtype str: + """ + + + ### Let's Encrypt client IInstaller ### + + def get_all_names(): + """Returns all names that may be authenticated. + :rtype: `list` of `str` + """ + + def deploy_cert(domain, cert_path, key_path, chain_path, fullchain_path): + """Deploy certificate. + :param str domain: domain to deploy certificate file + :param str cert_path: absolute path to the certificate file + :param str key_path: absolute path to the private key file + :param str chain_path: absolute path to the certificate chain file + :param str fullchain_path: absolute path to the certificate fullchain + file (cert plus chain) + :raises .PluginError: when cert cannot be deployed + """ + + self.wrangle_existing_config() + self.set_domainwise_tls_policies() + + def enhance(domain, enhancement, options=None): + """Perform a configuration enhancement. + :param str domain: domain for which to provide enhancement + :param str enhancement: An enhancement as defined in + :const:`~letsencrypt.constants.ENHANCEMENTS` + :param options: Flexible options parameter for enhancement. + Check documentation of + :const:`~letsencrypt.constants.ENHANCEMENTS` + for expected options for each enhancement. + :raises .PluginError: If Enhancement is not supported, or if + an error occurs during the enhancement. + """ + + def supported_enhancements(): + """Returns a list of supported enhancements. + :returns: supported enhancements which should be a subset of + :const:`~letsencrypt.constants.ENHANCEMENTS` + :rtype: :class:`list` of :class:`str` + """ + + def get_all_certs_keys(): + """Retrieve all certs and keys set in configuration. + :returns: tuples with form `[(cert, key, path)]`, where: + - `cert` - str path to certificate file + - `key` - str path to associated key file + - `path` - file path to configuration file + :rtype: list + """ + + def save(title=None, temporary=False): + """Saves all changes to the configuration files. + Both title and temporary are needed because a save may be + intended to be permanent, but the save is not ready to be a full + checkpoint. If an exception is raised, it is assumed a new + checkpoint was not created. + :param str title: The title of the save. If a title is given, the + configuration will be saved as a new checkpoint and put in a + timestamped directory. `title` has no effect if temporary is true. + :param bool temporary: Indicates whether the changes made will + be quickly reversed in the future (challenges) + :raises .PluginError: when save is unsuccessful + """ + + def rollback_checkpoints(rollback=1): + """Revert `rollback` number of configuration checkpoints. + :raises .PluginError: when configuration cannot be fully reverted + """ + + def recovery_routine(): + """Revert configuration to most recent finalized checkpoint. + Remove all changes (temporary and permanent) that have not been + finalized. This is useful to protect against crashes and other + execution interruptions. + :raises .errors.PluginError: If unable to recover the configuration + """ + + def view_config_changes(): + """Display all of the LE config changes. + :raises .PluginError: when config changes cannot be parsed + """ + + def config_test(): + """Make sure the configuration is valid. + :raises .MisconfigurationError: when the config is not in a usable state + """ + + def restart(): + """Restart or refresh the server content. + :raises .PluginError: when server cannot be restarted + """ + if os.geteuid() != 0: + os.system("sudo service postfix reload") + else: + os.system("service postfix reload") + if __name__ == "__main__": import Config as config @@ -155,3 +275,6 @@ if __name__ == "__main__": c.load_from_json_file(sys.argv[1]) postfix_dir = sys.argv[2] pcgen = PostfixConfigGenerator(c, postfix_dir, fixup=True) + pcgen.prepare() + pcgen.deploy_cert() # XXX add cert args! + pcgen.restart() From fedf97028427203ff8ea9e3b2e63e24826982c71 Mon Sep 17 00:00:00 2001 From: dmwilcox Date: Wed, 17 Feb 2016 11:37:28 -0800 Subject: [PATCH 2/7] Fix bad import to be import *as*... as it should be. --- Config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Config.py b/Config.py index 402fb4953..ac5202d0c 100644 --- a/Config.py +++ b/Config.py @@ -1,5 +1,5 @@ from datetime import datetime -from dateutil import dateutil_parser +from dateutil import parser as dateutil_parser import collections import json import logging From 47a5b7e3ba6fd6708e2a776f5730602029f12c00 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Wed, 17 Feb 2016 11:56:41 -0800 Subject: [PATCH 3/7] Start implementing cert installation --- MTAConfigGenerator.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/MTAConfigGenerator.py b/MTAConfigGenerator.py index ae74892eb..74a53a345 100755 --- a/MTAConfigGenerator.py +++ b/MTAConfigGenerator.py @@ -81,7 +81,6 @@ class PostfixConfigGenerator(MTAConfigGenerator): self.ensure_cf_var("smtp_tls_policy_maps", policy_cf_entry, []) - self.maybe_add_config_lines() def maybe_add_config_lines(self): if not self.additions: @@ -186,8 +185,9 @@ class PostfixConfigGenerator(MTAConfigGenerator): file (cert plus chain) :raises .PluginError: when cert cannot be deployed """ - self.wrangle_existing_config() + self.ensure_cf_var("smtpd_tls_cert_file", fullchain_path, []) + self.ensure_cf_var("smtpd_tls_key_file", key_path, []) self.set_domainwise_tls_policies() def enhance(domain, enhancement, options=None): @@ -233,6 +233,8 @@ class PostfixConfigGenerator(MTAConfigGenerator): :raises .PluginError: when save is unsuccessful """ + self.maybe_add_config_lines() + def rollback_checkpoints(rollback=1): """Revert `rollback` number of configuration checkpoints. :raises .PluginError: when configuration cannot be fully reverted @@ -277,4 +279,5 @@ if __name__ == "__main__": pcgen = PostfixConfigGenerator(c, postfix_dir, fixup=True) pcgen.prepare() pcgen.deploy_cert() # XXX add cert args! + pcgen.save() pcgen.restart() From 8f5b8558d28f817077d663854d89b5128d1b8cbe Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Wed, 17 Feb 2016 12:09:25 -0800 Subject: [PATCH 4/7] Actually deploy a cert? - Also add missing selves to interface methods --- MTAConfigGenerator.py | 48 ++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/MTAConfigGenerator.py b/MTAConfigGenerator.py index 74a53a345..3a2a653d6 100755 --- a/MTAConfigGenerator.py +++ b/MTAConfigGenerator.py @@ -28,6 +28,8 @@ class PostfixConfigGenerator(MTAConfigGenerator): def __init__(self, policy_config, postfix_dir, fixup=False): self.fixup = fixup self.postfix_dir = postfix_dir + self.policy_file = os.path.join(postfix_dir, "starttls_everywhere_policy") + MTAConfigGenerator.__init__(self, policy_config) def ensure_cf_var(self, var, ideal, also_acceptable): """ @@ -139,7 +141,7 @@ class PostfixConfigGenerator(MTAConfigGenerator): ### Let's Encrypt client IPlugin ### - def prepare(): + def prepare(self): """Prepare the plugin. Finish up any additional initialization. :raises .PluginError: @@ -155,12 +157,10 @@ class PostfixConfigGenerator(MTAConfigGenerator): currently supported. """ # XXX ensure we raise the right kinds of exceptions - self.policy_file = os.path.join(postfix_dir, "starttls_everywhere_policy") - MTAConfigGenerator.__init__(self, policy_config) self.postfix_cf_file = self.find_postfix_cf() - def more_info(): + def more_info(self): """Human-readable string to help the user. Should describe the steps taken and any relevant info to help the user decide which plugin to use. @@ -170,12 +170,12 @@ class PostfixConfigGenerator(MTAConfigGenerator): ### Let's Encrypt client IInstaller ### - def get_all_names(): + def get_all_names(self): """Returns all names that may be authenticated. :rtype: `list` of `str` """ - def deploy_cert(domain, cert_path, key_path, chain_path, fullchain_path): + def deploy_cert(self, domain, _cert_path, key_path, _chain_path, fullchain_path): """Deploy certificate. :param str domain: domain to deploy certificate file :param str cert_path: absolute path to the certificate file @@ -190,7 +190,7 @@ class PostfixConfigGenerator(MTAConfigGenerator): self.ensure_cf_var("smtpd_tls_key_file", key_path, []) self.set_domainwise_tls_policies() - def enhance(domain, enhancement, options=None): + def enhance(self, domain, enhancement, options=None): """Perform a configuration enhancement. :param str domain: domain for which to provide enhancement :param str enhancement: An enhancement as defined in @@ -203,14 +203,14 @@ class PostfixConfigGenerator(MTAConfigGenerator): an error occurs during the enhancement. """ - def supported_enhancements(): + def supported_enhancements(self): """Returns a list of supported enhancements. :returns: supported enhancements which should be a subset of :const:`~letsencrypt.constants.ENHANCEMENTS` :rtype: :class:`list` of :class:`str` """ - def get_all_certs_keys(): + def get_all_certs_keys(self): """Retrieve all certs and keys set in configuration. :returns: tuples with form `[(cert, key, path)]`, where: - `cert` - str path to certificate file @@ -219,7 +219,7 @@ class PostfixConfigGenerator(MTAConfigGenerator): :rtype: list """ - def save(title=None, temporary=False): + def save(self, title=None, temporary=False): """Saves all changes to the configuration files. Both title and temporary are needed because a save may be intended to be permanent, but the save is not ready to be a full @@ -235,12 +235,12 @@ class PostfixConfigGenerator(MTAConfigGenerator): self.maybe_add_config_lines() - def rollback_checkpoints(rollback=1): + def rollback_checkpoints(self, rollback=1): """Revert `rollback` number of configuration checkpoints. :raises .PluginError: when configuration cannot be fully reverted """ - def recovery_routine(): + def recovery_routine(self): """Revert configuration to most recent finalized checkpoint. Remove all changes (temporary and permanent) that have not been finalized. This is useful to protect against crashes and other @@ -248,17 +248,17 @@ class PostfixConfigGenerator(MTAConfigGenerator): :raises .errors.PluginError: If unable to recover the configuration """ - def view_config_changes(): + def view_config_changes(self): """Display all of the LE config changes. :raises .PluginError: when config changes cannot be parsed """ - def config_test(): + def config_test(self): """Make sure the configuration is valid. :raises .MisconfigurationError: when the config is not in a usable state """ - def restart(): + def restart(self): """Restart or refresh the server content. :raises .PluginError: when server cannot be restarted """ @@ -268,16 +268,26 @@ class PostfixConfigGenerator(MTAConfigGenerator): os.system("service postfix reload") +def usage(): + print "Usage: MTAConfigGenerator starttls-everywhere.json /etc/postfix /etc/letsencrypt/live/example.com/" + sys.exit(1) + if __name__ == "__main__": import Config as config - if len(sys.argv) != 3: - print "Usage: MTAConfigGenerator starttls-everywhere.json /etc/postfix" - sys.exit(1) + if len(sys.argv) != 4: + usage() c = config.Config() c.load_from_json_file(sys.argv[1]) postfix_dir = sys.argv[2] + le_lineage = sys.argv[3] + pieces = [os.path.join(le_lineage, f) for f in ("cert.pem", "privkey.pem", "chain.pem", "fullchain.pem")] + if not os.isdir(le_lineage) or not all(os.isfile(p) for p in pieces) : + print "Let's Encrypt directory", le_lineage, "does not appear to contain a valid lineage" + print + usage() + cert, key, chain, fullchain = pieces pcgen = PostfixConfigGenerator(c, postfix_dir, fixup=True) pcgen.prepare() - pcgen.deploy_cert() # XXX add cert args! + pcgen.deploy_cert(cert, key, chain, fullchain) pcgen.save() pcgen.restart() From 3aeb62cf7e3d0eacedaafeca386afddf4a85a39f Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Wed, 17 Feb 2016 12:13:28 -0800 Subject: [PATCH 5/7] bugfixes, cleanups --- MTAConfigGenerator.py | 8 +++++--- requirements.txt | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/MTAConfigGenerator.py b/MTAConfigGenerator.py index 3a2a653d6..3c676700e 100755 --- a/MTAConfigGenerator.py +++ b/MTAConfigGenerator.py @@ -269,7 +269,8 @@ class PostfixConfigGenerator(MTAConfigGenerator): def usage(): - print "Usage: MTAConfigGenerator starttls-everywhere.json /etc/postfix /etc/letsencrypt/live/example.com/" + print ("Usage: %s starttls-everywhere.json /etc/postfix /etc/letsencrypt/live/example.com/" % + sys.argv[0]) sys.exit(1) if __name__ == "__main__": @@ -280,8 +281,9 @@ if __name__ == "__main__": c.load_from_json_file(sys.argv[1]) postfix_dir = sys.argv[2] le_lineage = sys.argv[3] - pieces = [os.path.join(le_lineage, f) for f in ("cert.pem", "privkey.pem", "chain.pem", "fullchain.pem")] - if not os.isdir(le_lineage) or not all(os.isfile(p) for p in pieces) : + pieces = [os.path.join(le_lineage, f) for f in ( + "cert.pem", "privkey.pem", "chain.pem", "fullchain.pem")] + if not os.path.isdir(le_lineage) or not all(os.path.isfile(p) for p in pieces) : print "Let's Encrypt directory", le_lineage, "does not appear to contain a valid lineage" print usage() diff --git a/requirements.txt b/requirements.txt index 891e5809d..5334ba03a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ dnspython publicsuffix m2crypto +dateutils From 074fef773bcf3097a6c6532740b12535e3f3334d Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Wed, 17 Feb 2016 12:14:24 -0800 Subject: [PATCH 6/7] Make up a domain --- MTAConfigGenerator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MTAConfigGenerator.py b/MTAConfigGenerator.py index 3c676700e..9de48ca26 100755 --- a/MTAConfigGenerator.py +++ b/MTAConfigGenerator.py @@ -290,6 +290,6 @@ if __name__ == "__main__": cert, key, chain, fullchain = pieces pcgen = PostfixConfigGenerator(c, postfix_dir, fixup=True) pcgen.prepare() - pcgen.deploy_cert(cert, key, chain, fullchain) + pcgen.deploy_cert("example.com", cert, key, chain, fullchain) pcgen.save() pcgen.restart() From 28bb0eb6acf7c63c763189963803a93f0b2691fd Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Wed, 17 Feb 2016 12:20:26 -0800 Subject: [PATCH 7/7] Obtain acceptable_mxs the right way --- MTAConfigGenerator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MTAConfigGenerator.py b/MTAConfigGenerator.py index 9de48ca26..43dad5f37 100755 --- a/MTAConfigGenerator.py +++ b/MTAConfigGenerator.py @@ -115,7 +115,7 @@ class PostfixConfigGenerator(MTAConfigGenerator): def set_domainwise_tls_policies(self): self.policy_lines = [] - all_acceptable_mxs = self.policy_config.get_acceptable_mxs_dict() + all_acceptable_mxs = self.policy_config.acceptable_mxs for address_domain, properties in all_acceptable_mxs.items(): mx_list = properties.accept_mx_domains if len(mx_list) > 1: