From 4ca03aec8db9d83550c8b61c1b7e4dc41c8d7766 Mon Sep 17 00:00:00 2001 From: Giles Thomas Date: Tue, 5 Feb 2019 18:37:09 +0000 Subject: [PATCH 001/122] Don't verify existing certificate in HTTP01Response.simple_verify (certbot#6614) --- acme/acme/challenges.py | 2 +- acme/acme/challenges_test.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 501f74881..29b9bbb50 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -308,7 +308,7 @@ class HTTP01Response(KeyAuthorizationChallengeResponse): uri = chall.uri(domain) logger.debug("Verifying %s at %s...", chall.typ, uri) try: - http_response = requests.get(uri) + http_response = requests.get(uri, verify=False) except requests.exceptions.RequestException as error: logger.error("Unable to reach %s: %s", uri, error) return False diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index 81d39058e..be15e5b1a 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -186,7 +186,7 @@ class HTTP01ResponseTest(unittest.TestCase): mock_get.return_value = mock.MagicMock(text=validation) self.assertTrue(self.response.simple_verify( self.chall, "local", KEY.public_key())) - mock_get.assert_called_once_with(self.chall.uri("local")) + mock_get.assert_called_once_with(self.chall.uri("local"), verify=False) @mock.patch("acme.challenges.requests.get") def test_simple_verify_bad_validation(self, mock_get): @@ -202,7 +202,7 @@ class HTTP01ResponseTest(unittest.TestCase): HTTP01Response.WHITESPACE_CUTSET)) self.assertTrue(self.response.simple_verify( self.chall, "local", KEY.public_key())) - mock_get.assert_called_once_with(self.chall.uri("local")) + mock_get.assert_called_once_with(self.chall.uri("local"), verify=False) @mock.patch("acme.challenges.requests.get") def test_simple_verify_connection_error(self, mock_get): From b27e5804b9671e28f37a6da7e2f1f7fa9455d24a Mon Sep 17 00:00:00 2001 From: Giles Thomas Date: Tue, 5 Feb 2019 18:43:03 +0000 Subject: [PATCH 002/122] Added change description to CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cae93c5b7..ef91f1a8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). when a certificate is issued. * Support for initiating (but not solving end-to-end) TLS-ALPN-01 challenges with the `acme` module. +* Don't verify the existing certificate in HTTP01Response.simple_verify, for + compatibility with the real-world ACME challenge checks. ### Changed From 270754deffc4e8bd54ba1e2962e2332ee4980df5 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Tue, 13 Aug 2019 19:22:51 +0300 Subject: [PATCH 003/122] [Apache v2] New ParserNode interface abstraction (#7246) * New ParserNode interface abstraction * Add the test assertions, and fix interface * Update certbot-apache/certbot_apache/interfaces.py Co-Authored-By: Brad Warren * Update certbot-apache/certbot_apache/interfaces.py Co-Authored-By: Brad Warren * Update certbot-apache/certbot_apache/interfaces.py Co-Authored-By: Brad Warren * Add dummy tests and change arguments to properties * Add more context to comment property docstring * Add documentation to the main docstring * Streamline the parameter naming * Update certbot-apache/certbot_apache/interfaces.py Co-Authored-By: Brad Warren * Add explicit instructions how whitespaces are treated in set_parameters * Add the information about lookups being case insensitive. * Add context about whitespacing to add_ - methods * Update certbot-apache/certbot_apache/interfaces.py Co-Authored-By: Brad Warren * Update certbot-apache/certbot_apache/interfaces.py Co-Authored-By: Brad Warren --- certbot-apache/certbot_apache/interfaces.py | 476 ++++++++++++++++++ .../certbot_apache/tests/parsernode_test.py | 86 ++++ 2 files changed, 562 insertions(+) create mode 100644 certbot-apache/certbot_apache/interfaces.py create mode 100644 certbot-apache/certbot_apache/tests/parsernode_test.py diff --git a/certbot-apache/certbot_apache/interfaces.py b/certbot-apache/certbot_apache/interfaces.py new file mode 100644 index 000000000..a614f85c9 --- /dev/null +++ b/certbot-apache/certbot_apache/interfaces.py @@ -0,0 +1,476 @@ +"""ParserNode interface for interacting with configuration tree. + +General description +------------------- + +The ParserNode interfaces are designed to be able to contain all the parsing logic, +while allowing their users to interact with the configuration tree in a Pythonic +and well structured manner. + +The structure allows easy traversal of the tree of ParserNodes. Each ParserNode +stores a reference to its ancestor and immediate children, allowing the user to +traverse the tree using built in interface methods as well as accessing the interface +properties directly. + +ParserNode interface implementation should stand between the actual underlying +parser functionality and the business logic within Configurator code, interfacing +with both. The ParserNode tree is a result of configuration parsing action. + +ParserNode tree will be in charge of maintaining the parser state and hence the +abstract syntax tree (AST). Interactions between ParserNode tree and underlying +parser should involve only parsing the configuration files to this structure, and +writing it back to the filesystem - while preserving the format including whitespaces. + +For some implementations (Apache for example) it's important to keep track of and +to use state information while parsing conditional blocks and directives. This +allows the implementation to set a flag to parts of the parsed configuration +structure as not being in effect in a case of unmatched conditional block. It's +important to store these blocks in the tree as well in order to not to conduct +destructive actions (failing to write back parts of the configuration) while writing +the AST back to the filesystem. + +The ParserNode tree is in charge of maintaining the its own structure while every +child node fetched with find - methods or by iterating its list of children can be +changed in place. When making changes the affected nodes should be flagged as "dirty" +in order for the parser implementation to figure out the parts of the configuration +that need to be written back to disk during the save() operation. + + +Metadata +-------- + +The metadata holds all the implementation specific attributes of the ParserNodes - +things like the positional information related to the AST, file paths, whitespacing, +and any other information relevant to the underlying parser engine. + +Access to the metadata should be handled by implementation specific methods, allowing +the Configurator functionality to access the underlying information where needed. +A good example of this is file path of a node - something that is needed by the +reverter functionality within the Configurator. + + +Apache implementation +--------------------- + +The Apache implementation of ParserNode interface requires some implementation +specific functionalities that are not described by the interface itself. + +Conditional blocks + +Apache configuration can have conditional blocks, for example: , +resulting the directives and subblocks within it being either enabled or disabled. +While find_* interface methods allow including the disabled parts of the configuration +tree in searches a special care needs to be taken while parsing the structure in +order to reflect the active state of configuration. + +Whitespaces + +Each ParserNode object is responsible of storing its prepending whitespace characters +in order to be able to write the AST back to filesystem like it was, preserving the +format, this applies for parameters of BlockNode and DirectiveNode as well. +When parameters of ParserNode are changed, the pre-existing whitespaces in the +parameter sequence are discarded, as the general reason for storing them is to +maintain the ability to write the configuration back to filesystem exactly like +it was. This loses its meaning when we have to change the directives or blocks +parameters for other reasons. + +Searches and matching + +Apache configuration is largely case insensitive, so the Apache implementation of +ParserNode interface needs to provide the user means to match block and directive +names and parameters in case insensitive manner. This does not apply to everything +however, for example the parameters of a conditional statement may be case sensitive. +For this reason the internal representation of data should not ignore the case. +""" + +import abc +import six + + +@six.add_metaclass(abc.ABCMeta) +class ParserNode(object): + """ + ParserNode is the basic building block of the tree of such nodes, + representing the structure of the configuration. It is largely meant to keep + the structure information intact and idiomatically accessible. + + The root node as well as the child nodes of it should be instances of ParserNode. + Nodes keep track of their differences to on-disk representation of configuration + by marking modified ParserNodes as dirty to enable partial write-to-disk for + different files in the configuration structure. + + While for the most parts the usage and the child types are obvious, "include"- + and similar directives are an exception to this rule. This is because of the + nature of include directives - which unroll the contents of another file or + configuration block to their place. While we could unroll the included nodes + to the parent tree, it remains important to keep the context of include nodes + separate in order to write back the original configuration as it was. + + For parsers that require the implementation to keep track of the whitespacing, + it's responsibility of each ParserNode object itself to store its prepending + whitespaces in order to be able to reconstruct the complete configuration file + as it was when originally read from the disk. + """ + + @property + @abc.abstractmethod + def ancestor(self): # pragma: no cover + """ + This property contains a reference to ancestor node, or None if the node + is the root node of the configuration tree. + + :returns: The ancestor BlockNode object, or None for root node. + :rtype: ParserNode + """ + + raise NotImplementedError + + @property + @abc.abstractmethod + def dirty(self): # pragma: no cover + """ + This property contains a boolean value of the information if this node has + been modified since last save (or after the initial parse). + + :returns: True if this node has had changes that have not yet been written + to disk. + :rtype: bool + """ + + raise NotImplementedError + + @abc.abstractmethod + def save(self, msg): + """ + Save traverses the children, and attempts to write the AST to disk for + all the objects that are marked dirty. The actual operation of course + depends on the underlying implementation. save() shouldn't be called + from the Configurator outside of its designated save() method in order + to ensure that the Reverter checkpoints are created properly. + + Note: this approach of keeping internal structure of the configuration + within the ParserNode tree does not represent the file inclusion structure + of actual configuration files that reside in the filesystem. To handle + file writes properly, the file specific temporary trees should be extracted + from the full ParserNode tree where necessary when writing to disk. + + """ + + +@six.add_metaclass(abc.ABCMeta) +class CommentNode(ParserNode): + """ + CommentNode class is used for representation of comments within the parsed + configuration structure. Because of the nature of comments, it is not able + to have child nodes and hence it is always treated as a leaf node. + + CommentNode stores its contents in class variable 'comment' and does not + have a specific name. + + """ + + @property + @abc.abstractmethod + def comment(self): # pragma: no cover + """ + Comment property contains the contents of the comment without the comment + directives (typically # or /* ... */). + + :returns: A string containing the comment + :rtype: str + """ + + raise NotImplementedError + +@six.add_metaclass(abc.ABCMeta) +class DirectiveNode(ParserNode): + """ + DirectiveNode class represents a configuration directive within the configuration. + It can have zero or more parameters attached to it. Because of the nature of + single directives, it is not able to have child nodes and hence it is always + treated as a leaf node. + """ + + @property + @abc.abstractmethod + def enabled(self): # pragma: no cover + """ + Configuration blocks may have conditional statements enabling or disabling + their contents. This property returns the state of this DirectiveNode. + + :returns: True if the DirectiveNode is parsed and enabled in the configuration. + :rtype: bool + """ + + raise NotImplementedError + + @property + @abc.abstractmethod + def name(self): # pragma: no cover + """ + Name property contains the name of the directive. + + :returns: Name of this node + :rtype: str + """ + + raise NotImplementedError + + @property + @abc.abstractmethod + def parameters(self): # pragma: no cover + """ + This property contains a tuple of parameters of this ParserNode object + excluding whitespaces. + + :returns: A tuple of parameters for this node + :rtype: tuple + """ + + raise NotImplementedError + + @abc.abstractmethod + def set_parameters(self, parameters): + """ + Sets the sequence of parameters for this ParserNode object without + whitespaces. While the whitespaces for parameters are discarded when using + this method, the whitespacing preceeding the ParserNode itself should be + kept intact. + + :param list parameters: sequence of parameters + """ + + +@six.add_metaclass(abc.ABCMeta) +class BlockNode(ParserNode): + """ + BlockNode class represents a block of nested configuration directives, comments + and other blocks as its children. A BlockNode can have zero or more parameters + attached to it. + + Configuration blocks typically consist of one or more child nodes of all possible + types. Because of this, the BlockNode class has various discovery and structure + management methods. + + Lists of parameters used as an optional argument for some of the methods should + be lists of strings that are applicable parameters for each specific BlockNode + or DirectiveNode type. As an example, for a following configuration example: + + + ... + + + The node type would be BlockNode, name would be 'VirtualHost' and its parameters + would be: ['*:80']. + + While for the following example: + + LoadModule alias_module /usr/lib/apache2/modules/mod_alias.so + + The node type would be DirectiveNode, name would be 'LoadModule' and its + parameters would be: ['alias_module', '/usr/lib/apache2/modules/mod_alias.so'] + + The applicable parameters are dependent on the underlying configuration language + and its grammar. + """ + + @abc.abstractmethod + def add_child_block(self, name, parameters=None, position=None): + """ + Adds a new BlockNode child node with provided values and marks the callee + BlockNode dirty. This is used to add new children to the AST. The preceeding + whitespaces should not be added based on the ancestor or siblings for the + newly created object. This is to match the current behavior of the legacy + parser implementation. + + :param str name: The name of the child node to add + :param list parameters: list of parameters for the node + :param int position: Position in the list of children to add the new child + node to. Defaults to None, which appends the newly created node to the list. + If an integer is given, the child is inserted before that index in the + list similar to list().insert. + + :returns: BlockNode instance of the created child block + + """ + + @abc.abstractmethod + def add_child_directive(self, name, parameters=None, position=None): + """ + Adds a new DirectiveNode child node with provided values and marks the + callee BlockNode dirty. This is used to add new children to the AST. The + preceeding whitespaces should not be added based on the ancestor or siblings + for the newly created object. This is to match the current behavior of the + legacy parser implementation. + + + :param str name: The name of the child node to add + :param list parameters: list of parameters for the node + :param int position: Position in the list of children to add the new child + node to. Defaults to None, which appends the newly created node to the list. + If an integer is given, the child is inserted before that index in the + list similar to list().insert. + + :returns: DirectiveNode instance of the created child directive + + """ + + @abc.abstractmethod + def add_child_comment(self, comment="", position=None): + """ + Adds a new CommentNode child node with provided value and marks the + callee BlockNode dirty. This is used to add new children to the AST. The + preceeding whitespaces should not be added based on the ancestor or siblings + for the newly created object. This is to match the current behavior of the + legacy parser implementation. + + + :param str comment: Comment contents + :param int position: Position in the list of children to add the new child + node to. Defaults to None, which appends the newly created node to the list. + If an integer is given, the child is inserted before that index in the + list similar to list().insert. + + :returns: CommentNode instance of the created child comment + + """ + + @property + @abc.abstractmethod + def children(self): # pragma: no cover + """ + This property contains a list ParserNode objects that are the children + for this node. The order of children is the same as that of the parsed + configuration block. + + :returns: A tuple of this block's children + :rtype: tuple + """ + + raise NotImplementedError + + @property + @abc.abstractmethod + def enabled(self): + """ + Configuration blocks may have conditional statements enabling or disabling + their contents. This property returns the state of this configuration block. + In case of unmatched conditional statement in block, this block itself should + be set enabled while its children should be set disabled. + + :returns: True if the BlockNode is parsed and enabled in the configuration. + :rtype: bool + """ + + @abc.abstractmethod + def find_blocks(self, name, exclude=True): + """ + Find a configuration block by name. This method walks the child tree of + ParserNodes under the instance it was called from. This way it is possible + to search for the whole configuration tree, when starting from root node or + to do a partial search when starting from a specified branch. The lookup + should be case insensitive. + + :param str name: The name of the directive to search for + :param bool exclude: If the search results should exclude the contents of + ParserNode objects that reside within conditional blocks and because + of current state are not enabled. + + :returns: A list of found BlockNode objects. + """ + + @abc.abstractmethod + def find_directives(self, name, exclude=True): + """ + Find a directive by name. This method walks the child tree of ParserNodes + under the instance it was called from. This way it is possible to search + for the whole configuration tree, when starting from root node, or to do + a partial search when starting from a specified branch. The lookup should + be case insensitive. + + :param str name: The name of the directive to search for + :param bool exclude: If the search results should exclude the contents of + ParserNode objects that reside within conditional blocks and because + of current state are not enabled. + + :returns: A list of found DirectiveNode objects. + + """ + + @abc.abstractmethod + def find_comments(self, comment, exact=False): + """ + Find comments with value containing or being exactly the same as search term. + + This method walks the child tree of ParserNodes under the instance it was + called from. This way it is possible to search for the whole configuration + tree, when starting from root node, or to do a partial search when starting + from a specified branch. The lookup should be case sensitive. + + :param str comment: The content of comment to search for + :param bool exact: If the comment needs to exactly match the search term + + :returns: A list of found CommentNode objects. + + """ + + @abc.abstractmethod + def delete_child(self, child): + """ + Remove a specified child node from the list of children of the called + BlockNode object. + + :param ParserNode child: Child ParserNode object to remove from the list + of children of the callee. + """ + + @property + @abc.abstractmethod + def name(self): # pragma: no cover + """ + Name property contains the name of the block. As an example for config: + ... + the name would be "VirtualHost". + + :returns: Name of this node + :rtype: str + """ + + raise NotImplementedError + + @property + @abc.abstractmethod + def parameters(self): # pragma: no cover + """ + This property contains a tuple of parameters of this ParserNode object + excluding whitespaces. + + :returns: A tuple of parameters for this node + :rtype: tuple + """ + + raise NotImplementedError + + @abc.abstractmethod + def set_parameters(self, parameters): + """ + Sets the sequence of parameters for this ParserNode object without + whitespaces. While the whitespaces for parameters are discarded when using + this method, the whitespacing preceeding the ParserNode itself should be + kept intact. + + :param list parameters: sequence of parameters + """ + + @abc.abstractmethod + def unsaved_files(self): + """ + Returns a list of file paths that have been changed since the last save + (or the initial configuration parse). The intended use for this method + is to tell the Reverter which files need to be included in a checkpoint. + + This is typically called for the root of the ParserNode tree. + + :returns: list of file paths of files that have been changed but not yet + saved to disk. + """ diff --git a/certbot-apache/certbot_apache/tests/parsernode_test.py b/certbot-apache/certbot_apache/tests/parsernode_test.py new file mode 100644 index 000000000..6b45a4d6d --- /dev/null +++ b/certbot-apache/certbot_apache/tests/parsernode_test.py @@ -0,0 +1,86 @@ +""" Tests for ParserNode interface """ + +import unittest + +from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module + +from certbot_apache import interfaces + + + +class DummyCommentNode(interfaces.CommentNode): + """ A dummy class implementing CommentNode interface """ + ancestor = None + comment = "" + dirty = False + + def save(self, msg): # pragma: no cover + pass + + +class DummyDirectiveNode(interfaces.DirectiveNode): + """ A dummy class implementing DirectiveNode interface """ + ancestor = None + parameters = tuple() # type: Tuple[str, ...] + dirty = False + enabled = True + name = "" + + def save(self, msg): # pragma: no cover + pass + + def set_parameters(self, parameters): # pragma: no cover + pass + + +class DummyBlockNode(interfaces.BlockNode): + """ A dummy class implementing BlockNode interface """ + ancestor = None + parameters = tuple() # type: Tuple[str, ...] + children = tuple() # type: Tuple[interfaces.ParserNode, ...] + dirty = False + enabled = True + name = "" + + def save(self, msg): # pragma: no cover + pass + + def add_child_block(self, name, parameters=None, position=None): # pragma: no cover + pass + + def add_child_directive(self, name, parameters=None, position=None): # pragma: no cover + pass + + def add_child_comment(self, comment="", position=None): # pragma: no cover + pass + + def find_blocks(self, name, exclude=True): # pragma: no cover + pass + + def find_directives(self, name, exclude=True): # pragma: no cover + pass + + def find_comments(self, comment, exact=False): # pragma: no cover + pass + + def delete_child(self, child): # pragma: no cover + pass + + def set_parameters(self, parameters): # pragma: no cover + pass + + def unsaved_files(self): # pragma: no cover + pass + + +class ParserNodeTest(unittest.TestCase): + """Dummy placeholder test case for ParserNode interfaces""" + + def test_dummy(self): + dummyblock = DummyBlockNode() + dummydirective = DummyDirectiveNode() + dummycomment = DummyCommentNode() + + +if __name__ == "__main__": + unittest.main() # pragma: no cover From af1c66b28f5cad7c33c7da06feb2cc3c2bd8dc30 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Fri, 30 Aug 2019 23:42:18 +0300 Subject: [PATCH 004/122] [Apache v2] Modifications to ParserNode interfaces (#7330) This PR contains the changes requested in initial pre-review comments of #7308 Move properties to class pydocs in interfaces.py Prefer class ABC register() functionality instead of class inheritance for interface classes Add apache implementation specific functions to interfaces * Move class argument definitions to class pydoc * Add apache specific functionality to the interface * Bring inheritance back * Define initialization for different ParserNode classes * Add parsernode utils to check keyword arguments and document the defaults in pydoc * Fix pydocs and make BlockNode a child of DirectiveNode * Refine docs, and remove unused __init__ from BlockNode * Split parsernode util tests to their own respective file * Skip cover for dummy calls to super * Add types to method documentation * Add documentation for children --- certbot-apache/certbot_apache/interfaces.py | 248 +++++++++--------- .../certbot_apache/parsernode_util.py | 85 ++++++ .../certbot_apache/tests/parsernode_test.py | 101 ++++--- .../tests/parsernode_util_test.py | 87 ++++++ 4 files changed, 360 insertions(+), 161 deletions(-) create mode 100644 certbot-apache/certbot_apache/parsernode_util.py create mode 100644 certbot-apache/certbot_apache/tests/parsernode_util_test.py diff --git a/certbot-apache/certbot_apache/interfaces.py b/certbot-apache/certbot_apache/interfaces.py index a614f85c9..919aa7e93 100644 --- a/certbot-apache/certbot_apache/interfaces.py +++ b/certbot-apache/certbot_apache/interfaces.py @@ -86,6 +86,8 @@ For this reason the internal representation of data should not ignore the case. import abc import six +from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module + @six.add_metaclass(abc.ABCMeta) class ParserNode(object): @@ -110,35 +112,42 @@ class ParserNode(object): it's responsibility of each ParserNode object itself to store its prepending whitespaces in order to be able to reconstruct the complete configuration file as it was when originally read from the disk. + + ParserNode objects should have the following attributes: + + # Reference to ancestor node, or None if the node is the root node of the + # configuration tree. + ancestor: Optional[ParserNode] + + # True if this node has been modified since last save. + dirty: bool + + # Filepath of the file where the configuration element for this ParserNode + # object resides. For root node, the value for filepath is the httpd root + # configuration file. Filepath can be None if a configuration directive is + # defined in for example the httpd command line. + filepath: Optional[str] """ - @property @abc.abstractmethod - def ancestor(self): # pragma: no cover + def __init__(self, **kwargs): """ - This property contains a reference to ancestor node, or None if the node - is the root node of the configuration tree. + Initializes the ParserNode instance, and sets the ParserNode specific + instance variables. This is not meant to be used directly, but through + specific classes implementing ParserNode interface. - :returns: The ancestor BlockNode object, or None for root node. - :rtype: ParserNode + :param ancestor: BlockNode ancestor for this CommentNode. Required. + :type ancestor: BlockNode or None + + :param filepath: Filesystem path for the file where this CommentNode + does or should exist in the filesystem. Required. + :type filepath: str or None + + :param dirty: Boolean flag for denoting if this CommentNode has been + created or changed after the last save. Default: False. + :type dirty: bool """ - raise NotImplementedError - - @property - @abc.abstractmethod - def dirty(self): # pragma: no cover - """ - This property contains a boolean value of the information if this node has - been modified since last save (or after the initial parse). - - :returns: True if this node has had changes that have not yet been written - to disk. - :rtype: bool - """ - - raise NotImplementedError - @abc.abstractmethod def save(self, msg): """ @@ -154,10 +163,13 @@ class ParserNode(object): file writes properly, the file specific temporary trees should be extracted from the full ParserNode tree where necessary when writing to disk. + :param str msg: Message describing the reason for the save. + """ -@six.add_metaclass(abc.ABCMeta) +# Linter rule exclusion done because of https://github.com/PyCQA/pylint/issues/179 +@six.add_metaclass(abc.ABCMeta) # pylint: disable=abstract-method class CommentNode(ParserNode): """ CommentNode class is used for representation of comments within the parsed @@ -167,20 +179,38 @@ class CommentNode(ParserNode): CommentNode stores its contents in class variable 'comment' and does not have a specific name. + CommentNode objects should have the following attributes in addition to + the ones described in ParserNode: + + # Contains the contents of the comment without the directive notation + # (typically # or /* ... */). + comment: str + """ - @property @abc.abstractmethod - def comment(self): # pragma: no cover + def __init__(self, **kwargs): """ - Comment property contains the contents of the comment without the comment - directives (typically # or /* ... */). + Initializes the CommentNode instance and sets its instance variables. - :returns: A string containing the comment - :rtype: str + :param comment: Contents of the comment. Required. + :type comment: str + + :param ancestor: BlockNode ancestor for this CommentNode. Required. + :type ancestor: BlockNode or None + + :param filepath: Filesystem path for the file where this CommentNode + does or should exist in the filesystem. Required. + :type filepath: str or None + + :param dirty: Boolean flag for denoting if this CommentNode has been + created or changed after the last save. Default: False. + :type dirty: bool """ + super(CommentNode, self).__init__(ancestor=kwargs['ancestor'], + dirty=kwargs.get('dirty', False), + filepath=kwargs['filepath']) # pragma: no cover - raise NotImplementedError @six.add_metaclass(abc.ABCMeta) class DirectiveNode(ParserNode): @@ -189,45 +219,61 @@ class DirectiveNode(ParserNode): It can have zero or more parameters attached to it. Because of the nature of single directives, it is not able to have child nodes and hence it is always treated as a leaf node. + + If a this directive was defined on the httpd command line, the ancestor instance + variable for this DirectiveNode should be None, and it should be inserted to the + beginning of root BlockNode children sequence. + + DirectiveNode objects should have the following attributes in addition to + the ones described in ParserNode: + + # True if this DirectiveNode is enabled and False if it is inside of an + # inactive conditional block. + enabled: bool + + # Name, or key of the configuration directive. If BlockNode subclass of + # DirectiveNode is the root configuration node, the name should be None. + name: Optional[str] + + # Tuple of parameters of this ParserNode object, excluding whitespaces. + parameters: Tuple[str, ...] + """ - @property @abc.abstractmethod - def enabled(self): # pragma: no cover + def __init__(self, **kwargs): """ - Configuration blocks may have conditional statements enabling or disabling - their contents. This property returns the state of this DirectiveNode. + Initializes the DirectiveNode instance and sets its instance variables. + + :param name: Name or key of the DirectiveNode object. Required. + :type name: str or None + + :param tuple parameters: Tuple of str parameters for this DirectiveNode. + Default: (). + :type parameters: tuple + + :param ancestor: BlockNode ancestor for this DirectiveNode, or None for + root configuration node. Required. + :type ancestor: BlockNode or None + + :param filepath: Filesystem path for the file where this DirectiveNode + does or should exist in the filesystem, or None for directives introduced + in the httpd command line. Required. + :type filepath: str or None + + :param dirty: Boolean flag for denoting if this DirectiveNode has been + created or changed after the last save. Default: False. + :type dirty: bool + + :param enabled: True if this DirectiveNode object is parsed in the active + configuration of the httpd. False if the DirectiveNode exists within a + unmatched conditional configuration block. Default: True. + :type enabled: bool - :returns: True if the DirectiveNode is parsed and enabled in the configuration. - :rtype: bool """ - - raise NotImplementedError - - @property - @abc.abstractmethod - def name(self): # pragma: no cover - """ - Name property contains the name of the directive. - - :returns: Name of this node - :rtype: str - """ - - raise NotImplementedError - - @property - @abc.abstractmethod - def parameters(self): # pragma: no cover - """ - This property contains a tuple of parameters of this ParserNode object - excluding whitespaces. - - :returns: A tuple of parameters for this node - :rtype: tuple - """ - - raise NotImplementedError + super(DirectiveNode, self).__init__(ancestor=kwargs['ancestor'], + dirty=kwargs.get('dirty', False), + filepath=kwargs['filepath']) # pragma: no cover @abc.abstractmethod def set_parameters(self, parameters): @@ -242,7 +288,7 @@ class DirectiveNode(ParserNode): @six.add_metaclass(abc.ABCMeta) -class BlockNode(ParserNode): +class BlockNode(DirectiveNode): """ BlockNode class represents a block of nested configuration directives, comments and other blocks as its children. A BlockNode can have zero or more parameters @@ -272,6 +318,15 @@ class BlockNode(ParserNode): The applicable parameters are dependent on the underlying configuration language and its grammar. + + BlockNode objects should have the following attributes in addition to + the ones described in DirectiveNode: + + # Tuple of direct children of this BlockNode object. The order of children + # in this tuple retain the order of elements in the parsed configuration + # block. + children: Tuple[ParserNode, ...] + """ @abc.abstractmethod @@ -335,33 +390,6 @@ class BlockNode(ParserNode): """ - @property - @abc.abstractmethod - def children(self): # pragma: no cover - """ - This property contains a list ParserNode objects that are the children - for this node. The order of children is the same as that of the parsed - configuration block. - - :returns: A tuple of this block's children - :rtype: tuple - """ - - raise NotImplementedError - - @property - @abc.abstractmethod - def enabled(self): - """ - Configuration blocks may have conditional statements enabling or disabling - their contents. This property returns the state of this configuration block. - In case of unmatched conditional statement in block, this block itself should - be set enabled while its children should be set disabled. - - :returns: True if the BlockNode is parsed and enabled in the configuration. - :rtype: bool - """ - @abc.abstractmethod def find_blocks(self, name, exclude=True): """ @@ -424,44 +452,6 @@ class BlockNode(ParserNode): of children of the callee. """ - @property - @abc.abstractmethod - def name(self): # pragma: no cover - """ - Name property contains the name of the block. As an example for config: - ... - the name would be "VirtualHost". - - :returns: Name of this node - :rtype: str - """ - - raise NotImplementedError - - @property - @abc.abstractmethod - def parameters(self): # pragma: no cover - """ - This property contains a tuple of parameters of this ParserNode object - excluding whitespaces. - - :returns: A tuple of parameters for this node - :rtype: tuple - """ - - raise NotImplementedError - - @abc.abstractmethod - def set_parameters(self, parameters): - """ - Sets the sequence of parameters for this ParserNode object without - whitespaces. While the whitespaces for parameters are discarded when using - this method, the whitespacing preceeding the ParserNode itself should be - kept intact. - - :param list parameters: sequence of parameters - """ - @abc.abstractmethod def unsaved_files(self): """ diff --git a/certbot-apache/certbot_apache/parsernode_util.py b/certbot-apache/certbot_apache/parsernode_util.py new file mode 100644 index 000000000..2450a1c15 --- /dev/null +++ b/certbot-apache/certbot_apache/parsernode_util.py @@ -0,0 +1,85 @@ +"""ParserNode utils""" + + +def validate_kwargs(kwargs, required_names): + """ + Ensures that the kwargs dict has all the expected values. This function modifies + the kwargs dictionary, and hence the returned dictionary should be used instead + in the caller function instead of the original kwargs. + + :param dict kwargs: Dictionary of keyword arguments to validate. + :param list required_names: List of required parameter names. + """ + + validated_kwargs = dict() + for name in required_names: + try: + validated_kwargs[name] = kwargs.pop(name) + except KeyError: + raise TypeError("Required keyword argument: {} undefined.".format(name)) + + # Raise exception if unknown key word arguments are found. + if kwargs: + unknown = ", ".join(kwargs.keys()) + raise TypeError("Unknown keyword argument(s): {}".format(unknown)) + return validated_kwargs + + +def parsernode_kwargs(kwargs): + """ + Validates keyword arguments for ParserNode. This function modifies the kwargs + dictionary, and hence the returned dictionary should be used instead in the + caller function instead of the original kwargs. + + + :param dict kwargs: Keyword argument dictionary to validate. + + :returns: Tuple of validated and prepared arguments. + """ + kwargs.setdefault("dirty", False) + kwargs = validate_kwargs(kwargs, ["ancestor", "dirty", "filepath"]) + return kwargs["ancestor"], kwargs["dirty"], kwargs["filepath"] + + +def commentnode_kwargs(kwargs): + """ + Validates keyword arguments for CommentNode and sets the default values for + optional kwargs. This function modifies the kwargs dictionary, and hence the + returned dictionary should be used instead in the caller function instead of + the original kwargs. + + + :param dict kwargs: Keyword argument dictionary to validate. + + :returns: Tuple of validated and prepared arguments and ParserNode kwargs. + """ + kwargs.setdefault("dirty", False) + kwargs = validate_kwargs(kwargs, ["ancestor", "dirty", "filepath", "comment"]) + + comment = kwargs.pop("comment") + return comment, kwargs + + +def directivenode_kwargs(kwargs): + """ + Validates keyword arguments for DirectiveNode and BlockNode and sets the + default values for optional kwargs. This function modifies the kwargs + dictionary, and hence the returned dictionary should be used instead in the + caller function instead of the original kwargs. + + :param dict kwargs: Keyword argument dictionary to validate. + + :returns: Tuple of validated and prepared arguments and ParserNode kwargs. + """ + + kwargs.setdefault("dirty", False) + kwargs.setdefault("enabled", True) + kwargs.setdefault("parameters", ()) + + kwargs = validate_kwargs(kwargs, ["ancestor", "dirty", "filepath", "name", + "parameters", "enabled"]) + + name = kwargs.pop("name") + parameters = kwargs.pop("parameters") + enabled = kwargs.pop("enabled") + return name, parameters, enabled, kwargs diff --git a/certbot-apache/certbot_apache/tests/parsernode_test.py b/certbot-apache/certbot_apache/tests/parsernode_test.py index 6b45a4d6d..0fba32b98 100644 --- a/certbot-apache/certbot_apache/tests/parsernode_test.py +++ b/certbot-apache/certbot_apache/tests/parsernode_test.py @@ -2,84 +2,121 @@ import unittest -from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module - from certbot_apache import interfaces +from certbot_apache import parsernode_util as util +class DummyParserNode(interfaces.ParserNode): + """ A dummy class implementing ParserNode interface """ -class DummyCommentNode(interfaces.CommentNode): + def __init__(self, **kwargs): + """ + Initializes the ParserNode instance. + """ + ancestor, dirty, filepath = util.parsernode_kwargs(kwargs) + self.ancestor = ancestor + self.dirty = dirty + self.filepath = filepath + super(DummyParserNode, self).__init__(**kwargs) + + def save(self, msg): # pragma: no cover + """Save""" + pass + + +class DummyCommentNode(DummyParserNode): """ A dummy class implementing CommentNode interface """ - ancestor = None - comment = "" - dirty = False - def save(self, msg): # pragma: no cover - pass + def __init__(self, **kwargs): + """ + Initializes the CommentNode instance and sets its instance variables. + """ + comment, kwargs = util.commentnode_kwargs(kwargs) + self.comment = comment + super(DummyCommentNode, self).__init__(**kwargs) -class DummyDirectiveNode(interfaces.DirectiveNode): +class DummyDirectiveNode(DummyParserNode): """ A dummy class implementing DirectiveNode interface """ - ancestor = None - parameters = tuple() # type: Tuple[str, ...] - dirty = False - enabled = True - name = "" - def save(self, msg): # pragma: no cover - pass + # pylint: disable=too-many-arguments + def __init__(self, **kwargs): + """ + Initializes the DirectiveNode instance and sets its instance variables. + """ + name, parameters, enabled, kwargs = util.directivenode_kwargs(kwargs) + self.name = name + self.parameters = parameters + self.enabled = enabled + + super(DummyDirectiveNode, self).__init__(**kwargs) def set_parameters(self, parameters): # pragma: no cover + """Set parameters""" pass -class DummyBlockNode(interfaces.BlockNode): +class DummyBlockNode(DummyDirectiveNode): """ A dummy class implementing BlockNode interface """ - ancestor = None - parameters = tuple() # type: Tuple[str, ...] - children = tuple() # type: Tuple[interfaces.ParserNode, ...] - dirty = False - enabled = True - name = "" - - def save(self, msg): # pragma: no cover - pass def add_child_block(self, name, parameters=None, position=None): # pragma: no cover + """Add child block""" pass def add_child_directive(self, name, parameters=None, position=None): # pragma: no cover + """Add child directive""" pass def add_child_comment(self, comment="", position=None): # pragma: no cover + """Add child comment""" pass def find_blocks(self, name, exclude=True): # pragma: no cover + """Find blocks""" pass def find_directives(self, name, exclude=True): # pragma: no cover + """Find directives""" pass def find_comments(self, comment, exact=False): # pragma: no cover + """Find comments""" pass def delete_child(self, child): # pragma: no cover - pass - - def set_parameters(self, parameters): # pragma: no cover + """Delete child""" pass def unsaved_files(self): # pragma: no cover + """Unsaved files""" pass +interfaces.CommentNode.register(DummyCommentNode) +interfaces.DirectiveNode.register(DummyDirectiveNode) +interfaces.BlockNode.register(DummyBlockNode) + class ParserNodeTest(unittest.TestCase): """Dummy placeholder test case for ParserNode interfaces""" def test_dummy(self): - dummyblock = DummyBlockNode() - dummydirective = DummyDirectiveNode() - dummycomment = DummyCommentNode() + dummyblock = DummyBlockNode( + name="None", + parameters=(), + ancestor=None, + dirty=False, + filepath="/some/random/path" + ) + dummydirective = DummyDirectiveNode( + name="Name", + ancestor=None, + filepath="/another/path" + ) + dummycomment = DummyCommentNode( + comment="Comment", + ancestor=dummyblock, + filepath="/some/file" + ) if __name__ == "__main__": diff --git a/certbot-apache/certbot_apache/tests/parsernode_util_test.py b/certbot-apache/certbot_apache/tests/parsernode_util_test.py new file mode 100644 index 000000000..acb7a92db --- /dev/null +++ b/certbot-apache/certbot_apache/tests/parsernode_util_test.py @@ -0,0 +1,87 @@ +""" Tests for ParserNode utils """ +import unittest + +from certbot_apache import parsernode_util as util + + +class ParserNodeUtilTest(unittest.TestCase): + """Tests for ParserNode utils""" + + def _setup_parsernode(self): + """ Sets up kwargs dict for ParserNode """ + return { + "ancestor": None, + "dirty": False, + "filepath": "/tmp", + } + + def _setup_commentnode(self): + """ Sets up kwargs dict for CommentNode """ + + pn = self._setup_parsernode() + pn["comment"] = "x" + return pn + + def _setup_directivenode(self): + """ Sets up kwargs dict for DirectiveNode """ + + pn = self._setup_parsernode() + pn["name"] = "Name" + pn["parameters"] = ("first",) + pn["enabled"] = True + return pn + + def test_unknown_parameter(self): + params = self._setup_parsernode() + params["unknown"] = "unknown" + self.assertRaises(TypeError, util.parsernode_kwargs, params) + + params = self._setup_commentnode() + params["unknown"] = "unknown" + self.assertRaises(TypeError, util.commentnode_kwargs, params) + + params = self._setup_directivenode() + params["unknown"] = "unknown" + self.assertRaises(TypeError, util.directivenode_kwargs, params) + + def test_parsernode(self): + params = self._setup_parsernode() + ctrl = self._setup_parsernode() + + ancestor, dirty, filepath = util.parsernode_kwargs(params) + self.assertEqual(ancestor, ctrl["ancestor"]) + self.assertEqual(dirty, ctrl["dirty"]) + self.assertEqual(filepath, ctrl["filepath"]) + + def test_commentnode(self): + params = self._setup_commentnode() + ctrl = self._setup_commentnode() + + comment, _ = util.commentnode_kwargs(params) + self.assertEqual(comment, ctrl["comment"]) + + def test_directivenode(self): + params = self._setup_directivenode() + ctrl = self._setup_directivenode() + + name, parameters, enabled, _ = util.directivenode_kwargs(params) + self.assertEqual(name, ctrl["name"]) + self.assertEqual(parameters, ctrl["parameters"]) + self.assertEqual(enabled, ctrl["enabled"]) + + def test_missing_required(self): + c_params = self._setup_commentnode() + c_params.pop("comment") + self.assertRaises(TypeError, util.commentnode_kwargs, c_params) + + d_params = self._setup_directivenode() + d_params.pop("ancestor") + self.assertRaises(TypeError, util.directivenode_kwargs, d_params) + + p_params = self._setup_parsernode() + p_params.pop("filepath") + self.assertRaises(TypeError, util.parsernode_kwargs, p_params) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover From 9620cc75d4125066e4e676e299d3eecce09bbf84 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Mon, 9 Sep 2019 22:09:09 +0300 Subject: [PATCH 005/122] [Apache v2] Allow initialization of ParserNode instances using metadata dictionary instead of required arguments (#7366) Add metadata keyword argument to the ParserNode interface, allowing the initialization of the object from contents of the metadata - if the implementation allows it. As an example, Augeas implementation needs nothing more than the Augeas DOM path of a configuration directive to be able to populate the ParserNode instance with all data relevant to the DirectiveNode. The checks also allow skipping the otherwise required keyword arguments if metadata is provided. * Allow creating ParserNode instances using information from metadata dictionary * Update certbot-apache/certbot_apache/interfaces.py Co-Authored-By: Brad Warren * Update certbot-apache/certbot_apache/interfaces.py Co-Authored-By: Brad Warren * Address review comments * Fix filepath comment * Update certbot-apache/certbot_apache/interfaces.py Co-Authored-By: Brad Warren --- certbot-apache/certbot_apache/interfaces.py | 37 +++++++++++-- .../certbot_apache/parsernode_util.py | 52 +++++++++++++++++-- .../certbot_apache/tests/parsernode_test.py | 3 +- .../tests/parsernode_util_test.py | 30 ++++++++++- 4 files changed, 111 insertions(+), 11 deletions(-) diff --git a/certbot-apache/certbot_apache/interfaces.py b/certbot-apache/certbot_apache/interfaces.py index 919aa7e93..cdfdfac91 100644 --- a/certbot-apache/certbot_apache/interfaces.py +++ b/certbot-apache/certbot_apache/interfaces.py @@ -45,8 +45,10 @@ and any other information relevant to the underlying parser engine. Access to the metadata should be handled by implementation specific methods, allowing the Configurator functionality to access the underlying information where needed. -A good example of this is file path of a node - something that is needed by the -reverter functionality within the Configurator. + +For some implementations the node can be initialized using the information carried +in metadata alone. This is useful especially when populating the ParserNode tree +while parsing the configuration. Apache implementation @@ -55,6 +57,20 @@ Apache implementation The Apache implementation of ParserNode interface requires some implementation specific functionalities that are not described by the interface itself. +Initialization + +When the user of a ParserNode class is creating these objects, they must specify +the parameters as described in the documentation for the __init__ methods below. +When these objects are created internally, however, some parameters may not be +needed because (possibly more detailed) information is included in the metadata +parameter. In this case, implementations can deviate from the required parameters +from __init__, however, they should still behave the same when metadata is not +provided. + +For consistency internally, if an argument is provided directly in the ParserNode +initialization parameters as well as within metadata it's recommended to establish +clear behavior around this scenario within the implementation. + Conditional blocks Apache configuration can have conditional blocks, for example: , @@ -86,7 +102,7 @@ For this reason the internal representation of data should not ignore the case. import abc import six -from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any, Dict, Optional, Tuple # pylint: disable=unused-import, no-name-in-module @six.add_metaclass(abc.ABCMeta) @@ -127,6 +143,10 @@ class ParserNode(object): # configuration file. Filepath can be None if a configuration directive is # defined in for example the httpd command line. filepath: Optional[str] + + # Metadata dictionary holds all the implementation specific key-value pairs + # for the ParserNode instance. + metadata: Dict[str, Any] """ @abc.abstractmethod @@ -146,6 +166,11 @@ class ParserNode(object): :param dirty: Boolean flag for denoting if this CommentNode has been created or changed after the last save. Default: False. :type dirty: bool + + :param metadata: Dictionary of metadata values for this ParserNode object. + Metadata information should be used only internally in the implementation. + Default: {} + :type metadata: dict """ @abc.abstractmethod @@ -209,7 +234,8 @@ class CommentNode(ParserNode): """ super(CommentNode, self).__init__(ancestor=kwargs['ancestor'], dirty=kwargs.get('dirty', False), - filepath=kwargs['filepath']) # pragma: no cover + filepath=kwargs['filepath'], + metadata=kwargs.get('metadata', {})) # pragma: no cover @six.add_metaclass(abc.ABCMeta) @@ -273,7 +299,8 @@ class DirectiveNode(ParserNode): """ super(DirectiveNode, self).__init__(ancestor=kwargs['ancestor'], dirty=kwargs.get('dirty', False), - filepath=kwargs['filepath']) # pragma: no cover + filepath=kwargs['filepath'], + metadata=kwargs.get('metadata', {})) # pragma: no cover @abc.abstractmethod def set_parameters(self, parameters): diff --git a/certbot-apache/certbot_apache/parsernode_util.py b/certbot-apache/certbot_apache/parsernode_util.py index 2450a1c15..d9646862a 100644 --- a/certbot-apache/certbot_apache/parsernode_util.py +++ b/certbot-apache/certbot_apache/parsernode_util.py @@ -31,14 +31,28 @@ def parsernode_kwargs(kwargs): dictionary, and hence the returned dictionary should be used instead in the caller function instead of the original kwargs. + If metadata is provided, the otherwise required argument "filepath" may be + omitted if the implementation is able to extract its value from the metadata. + This usecase is handled within this function. Filepath defaults to None. :param dict kwargs: Keyword argument dictionary to validate. :returns: Tuple of validated and prepared arguments. """ + + # As many values of ParserNode instances can be derived from the metadata, + # (ancestor being a common exception here) make sure we permit it here as well. + if "metadata" in kwargs: + # Filepath can be derived from the metadata in Augeas implementation. + # Default is None, as in this case the responsibility of populating this + # variable lies on the implementation. + kwargs.setdefault("filepath", None) + kwargs.setdefault("dirty", False) - kwargs = validate_kwargs(kwargs, ["ancestor", "dirty", "filepath"]) - return kwargs["ancestor"], kwargs["dirty"], kwargs["filepath"] + kwargs.setdefault("metadata", {}) + + kwargs = validate_kwargs(kwargs, ["ancestor", "dirty", "filepath", "metadata"]) + return kwargs["ancestor"], kwargs["dirty"], kwargs["filepath"], kwargs["metadata"] def commentnode_kwargs(kwargs): @@ -48,13 +62,29 @@ def commentnode_kwargs(kwargs): returned dictionary should be used instead in the caller function instead of the original kwargs. + If metadata is provided, the otherwise required argument "comment" may be + omitted if the implementation is able to extract its value from the metadata. + This usecase is handled within this function. :param dict kwargs: Keyword argument dictionary to validate. :returns: Tuple of validated and prepared arguments and ParserNode kwargs. """ + + # As many values of ParserNode instances can be derived from the metadata, + # (ancestor being a common exception here) make sure we permit it here as well. + if "metadata" in kwargs: + kwargs.setdefault("comment", None) + # Filepath can be derived from the metadata in Augeas implementation. + # Default is None, as in this case the responsibility of populating this + # variable lies on the implementation. + kwargs.setdefault("filepath", None) + kwargs.setdefault("dirty", False) - kwargs = validate_kwargs(kwargs, ["ancestor", "dirty", "filepath", "comment"]) + kwargs.setdefault("metadata", {}) + + kwargs = validate_kwargs(kwargs, ["ancestor", "dirty", "filepath", "comment", + "metadata"]) comment = kwargs.pop("comment") return comment, kwargs @@ -67,17 +97,31 @@ def directivenode_kwargs(kwargs): dictionary, and hence the returned dictionary should be used instead in the caller function instead of the original kwargs. + If metadata is provided, the otherwise required argument "name" may be + omitted if the implementation is able to extract its value from the metadata. + This usecase is handled within this function. + :param dict kwargs: Keyword argument dictionary to validate. :returns: Tuple of validated and prepared arguments and ParserNode kwargs. """ + # As many values of ParserNode instances can be derived from the metadata, + # (ancestor being a common exception here) make sure we permit it here as well. + if "metadata" in kwargs: + kwargs.setdefault("name", None) + # Filepath can be derived from the metadata in Augeas implementation. + # Default is None, as in this case the responsibility of populating this + # variable lies on the implementation. + kwargs.setdefault("filepath", None) + kwargs.setdefault("dirty", False) kwargs.setdefault("enabled", True) kwargs.setdefault("parameters", ()) + kwargs.setdefault("metadata", {}) kwargs = validate_kwargs(kwargs, ["ancestor", "dirty", "filepath", "name", - "parameters", "enabled"]) + "parameters", "enabled", "metadata"]) name = kwargs.pop("name") parameters = kwargs.pop("parameters") diff --git a/certbot-apache/certbot_apache/tests/parsernode_test.py b/certbot-apache/certbot_apache/tests/parsernode_test.py index 0fba32b98..1a2288c82 100644 --- a/certbot-apache/certbot_apache/tests/parsernode_test.py +++ b/certbot-apache/certbot_apache/tests/parsernode_test.py @@ -13,10 +13,11 @@ class DummyParserNode(interfaces.ParserNode): """ Initializes the ParserNode instance. """ - ancestor, dirty, filepath = util.parsernode_kwargs(kwargs) + ancestor, dirty, filepath, metadata = util.parsernode_kwargs(kwargs) self.ancestor = ancestor self.dirty = dirty self.filepath = filepath + self.metadata = metadata super(DummyParserNode, self).__init__(**kwargs) def save(self, msg): # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/parsernode_util_test.py b/certbot-apache/certbot_apache/tests/parsernode_util_test.py index acb7a92db..a079759ee 100644 --- a/certbot-apache/certbot_apache/tests/parsernode_util_test.py +++ b/certbot-apache/certbot_apache/tests/parsernode_util_test.py @@ -48,10 +48,21 @@ class ParserNodeUtilTest(unittest.TestCase): params = self._setup_parsernode() ctrl = self._setup_parsernode() - ancestor, dirty, filepath = util.parsernode_kwargs(params) + ancestor, dirty, filepath, metadata = util.parsernode_kwargs(params) self.assertEqual(ancestor, ctrl["ancestor"]) self.assertEqual(dirty, ctrl["dirty"]) self.assertEqual(filepath, ctrl["filepath"]) + self.assertEqual(metadata, {}) + + def test_parsernode_from_metadata(self): + params = self._setup_parsernode() + params.pop("filepath") + md = {"some": "value"} + params["metadata"] = md + + # Just testing that error from missing required parameters is not raised + _, _, _, metadata = util.parsernode_kwargs(params) + self.assertEqual(metadata, md) def test_commentnode(self): params = self._setup_commentnode() @@ -60,6 +71,14 @@ class ParserNodeUtilTest(unittest.TestCase): comment, _ = util.commentnode_kwargs(params) self.assertEqual(comment, ctrl["comment"]) + def test_commentnode_from_metadata(self): + params = self._setup_commentnode() + params.pop("comment") + params["metadata"] = {} + + # Just testing that error from missing required parameters is not raised + util.commentnode_kwargs(params) + def test_directivenode(self): params = self._setup_directivenode() ctrl = self._setup_directivenode() @@ -69,6 +88,15 @@ class ParserNodeUtilTest(unittest.TestCase): self.assertEqual(parameters, ctrl["parameters"]) self.assertEqual(enabled, ctrl["enabled"]) + def test_directivenode_from_metadata(self): + params = self._setup_directivenode() + params.pop("filepath") + params.pop("name") + params["metadata"] = {"irrelevant": "value"} + + # Just testing that error from missing required parameters is not raised + util.directivenode_kwargs(params) + def test_missing_required(self): c_params = self._setup_commentnode() c_params.pop("comment") From 23fb6d28773a117e6c9456ae64d8aaba4a6fbe56 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Wed, 18 Sep 2019 23:31:44 +0300 Subject: [PATCH 006/122] [Apache v2] DualParserNode implementation 1/3 (#7374) * DualParserNode, DualCommentNode and DualDirectiveNode implementations * Address review comments * Address review comments * Simplify isPass --- certbot-apache/certbot_apache/assertions.py | 61 ++++++++++++ certbot-apache/certbot_apache/augeasparser.py | 69 ++++++++++++++ certbot-apache/certbot_apache/dualparser.py | 95 +++++++++++++++++++ .../certbot_apache/tests/dualnode_test.py | 88 +++++++++++++++++ 4 files changed, 313 insertions(+) create mode 100644 certbot-apache/certbot_apache/assertions.py create mode 100644 certbot-apache/certbot_apache/augeasparser.py create mode 100644 certbot-apache/certbot_apache/dualparser.py create mode 100644 certbot-apache/certbot_apache/tests/dualnode_test.py diff --git a/certbot-apache/certbot_apache/assertions.py b/certbot-apache/certbot_apache/assertions.py new file mode 100644 index 000000000..659efee28 --- /dev/null +++ b/certbot-apache/certbot_apache/assertions.py @@ -0,0 +1,61 @@ +"""Dual parser node assertions""" +from certbot_apache import interfaces + + +PASS = "CERTBOT_PASS_ASSERT" + + +def assertEqual(first, second): + """ Equality assertion """ + + if isinstance(first, interfaces.CommentNode): + assertEqualComment(first, second) + elif isinstance(first, interfaces.DirectiveNode): + assertEqualDirective(first, second) + + # Skip tests if filepath includes the pass value. This is done + # because filepath is variable of the base ParserNode interface, and + # unless the implementation is actually done, we cannot assume getting + # correct results from boolean assertion for dirty + if not isPass(first.filepath) and not isPass(second.filepath): + assert first.dirty == second.dirty + # We might want to disable this later if testing with two separate + # (but identical) directory structures. + assert first.filepath == second.filepath + +def assertEqualComment(first, second): # pragma: no cover + """ Equality assertion for CommentNode """ + + assert isinstance(first, interfaces.CommentNode) + assert isinstance(second, interfaces.CommentNode) + + if not isPass(first.comment) and not isPass(second.comment): + assert first.comment == second.comment + +def _assertEqualDirectiveComponents(first, second): # pragma: no cover + """ Handles assertion for instance variables for DirectiveNode and BlockNode""" + + # Enabled value cannot be asserted, because Augeas implementation + # is unable to figure that out. + # assert first.enabled == second.enabled + if not isPass(first.name) and not isPass(second.name): + assert first.name == second.name + + if not isPass(first.parameters) and not isPass(second.parameters): + assert first.parameters == second.parameters + +def assertEqualDirective(first, second): + """ Equality assertion for DirectiveNode """ + + assert isinstance(first, interfaces.DirectiveNode) + assert isinstance(second, interfaces.DirectiveNode) + _assertEqualDirectiveComponents(first, second) + +def isPass(value): # pragma: no cover + """Checks if the value is set to PASS""" + return PASS in value + +def assertEqualSimple(first, second): + """ Simple assertion """ + if not isPass(first) and not isPass(second): + assert first == second diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py new file mode 100644 index 000000000..b7ac5ec3d --- /dev/null +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -0,0 +1,69 @@ +""" Augeas implementation of the ParserNode interface """ +from certbot_apache import assertions +from certbot_apache import interfaces +from certbot_apache import parsernode_util as util + + +class AugeasParserNode(interfaces.ParserNode): + """ Augeas implementation of ParserNode interface """ + + def __init__(self, **kwargs): + ancestor, dirty, filepath, metadata = util.parsernode_kwargs(kwargs) # pylint: disable=unused-variable + super(AugeasParserNode, self).__init__(**kwargs) + self.ancestor = ancestor + # self.filepath = filepath + self.filepath = assertions.PASS + self.dirty = dirty + self.metadata = metadata + + def save(self, msg): # pragma: no cover + pass + + +class AugeasCommentNode(AugeasParserNode): + """ Augeas implementation of CommentNode interface """ + + def __init__(self, **kwargs): + comment, kwargs = util.commentnode_kwargs(kwargs) # pylint: disable=unused-variable + super(AugeasCommentNode, self).__init__(**kwargs) + # self.comment = comment + self.comment = assertions.PASS + + def __eq__(self, other): # pragma: no cover + if isinstance(other, self.__class__): + return (self.comment == other.comment and + self.filepath == other.filepath and + self.dirty == other.dirty and + self.ancestor == other.ancestor and + self.metadata == other.metadata) + return False + + +class AugeasDirectiveNode(AugeasParserNode): + """ Augeas implementation of DirectiveNode interface """ + + def __init__(self, **kwargs): + name, parameters, enabled, kwargs = util.directivenode_kwargs(kwargs) + super(AugeasDirectiveNode, self).__init__(**kwargs) + self.name = name + self.parameters = parameters + self.enabled = enabled + + def __eq__(self, other): # pragma: no cover + if isinstance(other, self.__class__): + return (self.name == other.name and + self.filepath == other.filepath and + self.parameters == other.parameters and + self.enabled == other.enabled and + self.dirty == other.dirty and + self.ancestor == other.ancestor and + self.metadata == other.metadata) + return False + + def set_parameters(self, parameters): + """Sets the parameters for DirectiveNode""" + self.parameters = parameters + + +interfaces.CommentNode.register(AugeasCommentNode) +interfaces.DirectiveNode.register(AugeasDirectiveNode) diff --git a/certbot-apache/certbot_apache/dualparser.py b/certbot-apache/certbot_apache/dualparser.py new file mode 100644 index 000000000..4bb4b3497 --- /dev/null +++ b/certbot-apache/certbot_apache/dualparser.py @@ -0,0 +1,95 @@ +""" Dual ParserNode implementation """ +from certbot_apache import assertions +from certbot_apache import augeasparser + + +class DualNodeBase(object): + """ Dual parser interface for in development testing. This is used as the + base class for dual parser interface classes. This class handles runtime + attribute value assertions.""" + + def save(self, msg): # pragma: no cover + """ Call save for both parsers """ + self.primary.save(msg) + self.secondary.save(msg) + + def __getattr__(self, aname): + """ Attribute value assertion """ + firstval = getattr(self.primary, aname) + secondval = getattr(self.secondary, aname) + assertions.assertEqualSimple(firstval, secondval) + return firstval + + +class DualCommentNode(DualNodeBase): + """ Dual parser implementation of CommentNode interface """ + + def __init__(self, **kwargs): + """ This initialization implementation allows ordinary initialization + of CommentNode objects as well as creating a DualCommentNode object + using precreated or fetched CommentNode objects if provided as optional + arguments primary and secondary. + + Parameters other than the following are from interfaces.CommentNode: + + :param CommentNode primary: Primary pre-created CommentNode, mainly + used when creating new DualParser nodes using add_* methods. + :param CommentNode secondary: Secondary pre-created CommentNode + """ + + kwargs.setdefault("primary", None) + kwargs.setdefault("secondary", None) + primary = kwargs.pop("primary") + secondary = kwargs.pop("secondary") + + if primary or secondary: + assert primary and secondary + self.primary = primary + self.secondary = secondary + else: + self.primary = augeasparser.AugeasCommentNode(**kwargs) + self.secondary = augeasparser.AugeasCommentNode(**kwargs) + + assertions.assertEqual(self.primary, self.secondary) + + +class DualDirectiveNode(DualNodeBase): + """ Dual parser implementation of DirectiveNode interface """ + + def __init__(self, **kwargs): + """ This initialization implementation allows ordinary initialization + of DirectiveNode objects as well as creating a DualDirectiveNode object + using precreated or fetched DirectiveNode objects if provided as optional + arguments primary and secondary. + + Parameters other than the following are from interfaces.DirectiveNode: + + :param DirectiveNode primary: Primary pre-created DirectiveNode, mainly + used when creating new DualParser nodes using add_* methods. + :param DirectiveNode secondary: Secondary pre-created DirectiveNode + + + """ + + kwargs.setdefault("primary", None) + kwargs.setdefault("secondary", None) + primary = kwargs.pop("primary") + secondary = kwargs.pop("secondary") + + if primary or secondary: + assert primary and secondary + self.primary = primary + self.secondary = secondary + else: + self.primary = augeasparser.AugeasDirectiveNode(**kwargs) + self.secondary = augeasparser.AugeasDirectiveNode(**kwargs) + + assertions.assertEqual(self.primary, self.secondary) + + def set_parameters(self, parameters): + """ Sets parameters and asserts that both implementation successfully + set the parameter sequence """ + + self.primary.set_parameters(parameters) + self.secondary.set_parameters(parameters) + assertions.assertEqual(self.primary, self.secondary) diff --git a/certbot-apache/certbot_apache/tests/dualnode_test.py b/certbot-apache/certbot_apache/tests/dualnode_test.py new file mode 100644 index 000000000..f8a012280 --- /dev/null +++ b/certbot-apache/certbot_apache/tests/dualnode_test.py @@ -0,0 +1,88 @@ +"""Tests for DualParserNode implementation""" +import unittest + +import mock + +from certbot_apache import assertions +from certbot_apache import dualparser + + +class DualParserNodeTest(unittest.TestCase): + """DualParserNode tests""" + + def setUp(self): # pylint: disable=arguments-differ + self.directive = dualparser.DualDirectiveNode(name="directive", + ancestor=None, + filepath="/tmp/something") + self.comment = dualparser.DualCommentNode(comment="comment", + ancestor=None, + filepath="/tmp/something") + + def test_create_with_precreated(self): + cnode = dualparser.DualCommentNode(comment="comment", + ancestor=None, + filepath="/tmp/something", + primary=self.comment.secondary, + secondary=self.comment.primary) + dnode = dualparser.DualDirectiveNode(name="directive", + ancestor=None, + filepath="/tmp/something", + primary=self.directive.secondary, + secondary=self.directive.primary) + # Switched around + self.assertTrue(cnode.primary is self.comment.secondary) + self.assertTrue(cnode.secondary is self.comment.primary) + self.assertTrue(dnode.primary is self.directive.secondary) + self.assertTrue(dnode.secondary is self.directive.primary) + + def test_set_params(self): + params = ("first", "second") + self.directive.set_parameters(params) + self.assertEqual(self.directive.primary.parameters, params) + self.assertEqual(self.directive.secondary.parameters, params) + + def test_set_parameters(self): + pparams = mock.MagicMock() + sparams = mock.MagicMock() + pparams.parameters = ("a", "b") + sparams.parameters = ("a", "b") + self.directive.primary.set_parameters = pparams + self.directive.secondary.set_parameters = sparams + self.directive.set_parameters(("param", "seq")) + self.assertTrue(pparams.called) + self.assertTrue(sparams.called) + + def test_getattr_equality(self): + self.directive.primary.variableexception = "value" + self.directive.secondary.variableexception = "not_value" + with self.assertRaises(AssertionError): + _ = self.directive.variableexception + + self.directive.primary.variable = "value" + self.directive.secondary.variable = "value" + try: + self.directive.variable + except AssertionError: # pragma: no cover + self.fail("getattr check raised an AssertionError where it shouldn't have") + + def test_parsernode_dirty_assert(self): + # disable assertion pass + self.comment.primary.comment = "value" + self.comment.secondary.comment = "value" + self.comment.primary.filepath = "x" + self.comment.secondary.filepath = "x" + + self.comment.primary.dirty = False + self.comment.secondary.dirty = True + with self.assertRaises(AssertionError): + assertions.assertEqual(self.comment.primary, self.comment.secondary) + + def test_parsernode_filepath_assert(self): + # disable assertion pass + self.comment.primary.comment = "value" + self.comment.secondary.comment = "value" + + self.comment.primary.filepath = "first" + self.comment.secondary.filepath = "second" + with self.assertRaises(AssertionError): + assertions.assertEqual(self.comment.primary, self.comment.secondary) From c22434033033d7268c8e57f9f37998d3e01ce298 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Fri, 20 Sep 2019 00:44:50 +0300 Subject: [PATCH 007/122] [Apache v2] DualParserNode implementation 2/3 (#7375) * DualParserNode, DualCommentNode and DualDirectiveNode implementations * Add DualBlockNode * Address review comments * Address review comments * Call the right assertion after name change * Simplify isPass * Add explanation to _create_matching_list pydoc * Break when match was found --- certbot-apache/certbot_apache/augeasparser.py | 67 ++++++++- certbot-apache/certbot_apache/dualparser.py | 138 ++++++++++++++++++ .../certbot_apache/tests/dualnode_test.py | 75 +++++++++- 3 files changed, 275 insertions(+), 5 deletions(-) diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py index b7ac5ec3d..f396ab76a 100644 --- a/certbot-apache/certbot_apache/augeasparser.py +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -1,4 +1,4 @@ -""" Augeas implementation of the ParserNode interface """ +""" Augeas implementation of the ParserNode interfaces """ from certbot_apache import assertions from certbot_apache import interfaces from certbot_apache import parsernode_util as util @@ -65,5 +65,70 @@ class AugeasDirectiveNode(AugeasParserNode): self.parameters = parameters +class AugeasBlockNode(AugeasDirectiveNode): + """ Augeas implementation of BlockNode interface """ + + def __init__(self, **kwargs): + super(AugeasBlockNode, self).__init__(**kwargs) + self.children = () + + def __eq__(self, other): # pragma: no cover + if isinstance(other, self.__class__): + return (self.name == other.name and + self.filepath == other.filepath and + self.parameters == other.parameters and + self.children == other.children and + self.enabled == other.enabled and + self.dirty == other.dirty and + self.ancestor == other.ancestor and + self.metadata == other.metadata) + return False + + def add_child_block(self, name, parameters=None, position=None): # pylint: disable=unused-argument + """Adds a new BlockNode to the sequence of children""" + new_block = AugeasBlockNode(name=assertions.PASS, + ancestor=self, + filepath=assertions.PASS) + self.children += (new_block,) + return new_block + + def add_child_directive(self, name, parameters=None, position=None): # pylint: disable=unused-argument + """Adds a new DirectiveNode to the sequence of children""" + new_dir = AugeasDirectiveNode(name=assertions.PASS, + ancestor=self, + filepath=assertions.PASS) + self.children += (new_dir,) + return new_dir + + def add_child_comment(self, comment="", position=None): # pylint: disable=unused-argument + """Adds a new CommentNode to the sequence of children""" + new_comment = AugeasCommentNode(comment=assertions.PASS, + ancestor=self, + filepath=assertions.PASS) + self.children += (new_comment,) + return new_comment + + def find_blocks(self, name, exclude=True): # pragma: no cover + """Recursive search of BlockNodes from the sequence of children""" + pass + + def find_directives(self, name, exclude=True): # pragma: no cover + """Recursive search of DirectiveNodes from the sequence of children""" + pass + + def find_comments(self, comment, exact=False): # pragma: no cover + """Recursive search of DirectiveNodes from the sequence of children""" + pass + + def delete_child(self, child): # pragma: no cover + """Deletes a ParserNode from the sequence of children""" + pass + + def unsaved_files(self): # pragma: no cover + """Returns a list of unsaved filepaths""" + return [assertions.PASS] + + interfaces.CommentNode.register(AugeasCommentNode) interfaces.DirectiveNode.register(AugeasDirectiveNode) +interfaces.BlockNode.register(AugeasBlockNode) diff --git a/certbot-apache/certbot_apache/dualparser.py b/certbot-apache/certbot_apache/dualparser.py index 4bb4b3497..5f00d59f9 100644 --- a/certbot-apache/certbot_apache/dualparser.py +++ b/certbot-apache/certbot_apache/dualparser.py @@ -93,3 +93,141 @@ class DualDirectiveNode(DualNodeBase): self.primary.set_parameters(parameters) self.secondary.set_parameters(parameters) assertions.assertEqual(self.primary, self.secondary) + +class DualBlockNode(DualNodeBase): + """ Dual parser implementation of BlockNode interface """ + + def __init__(self, **kwargs): + """ This initialization implementation allows ordinary initialization + of BlockNode objects as well as creating a DualBlockNode object + using precreated or fetched BlockNode objects if provided as optional + arguments primary and secondary. + + Parameters other than the following are from interfaces.BlockNode: + + :param BlockNode primary: Primary pre-created BlockNode, mainly + used when creating new DualParser nodes using add_* methods. + :param BlockNode secondary: Secondary pre-created BlockNode + """ + + kwargs.setdefault("primary", None) + kwargs.setdefault("secondary", None) + primary = kwargs.pop("primary") + secondary = kwargs.pop("secondary") + + if primary or secondary: + assert primary and secondary + self.primary = primary + self.secondary = secondary + else: + self.primary = augeasparser.AugeasBlockNode(**kwargs) + self.secondary = augeasparser.AugeasBlockNode(**kwargs) + + assertions.assertEqual(self.primary, self.secondary) + + def add_child_block(self, name, parameters=None, position=None): + """ Creates a new child BlockNode, asserts that both implementations + did it in a similar way, and returns a newly created DualBlockNode object + encapsulating both of the newly created objects """ + + primary_new = self.primary.add_child_block(name, parameters, position) + secondary_new = self.secondary.add_child_block(name, parameters, position) + assertions.assertEqual(primary_new, secondary_new) + new_block = DualBlockNode(primary=primary_new, secondary=secondary_new) + return new_block + + def add_child_directive(self, name, parameters=None, position=None): + """ Creates a new child DirectiveNode, asserts that both implementations + did it in a similar way, and returns a newly created DualDirectiveNode + object encapsulating both of the newly created objects """ + + primary_new = self.primary.add_child_directive(name, parameters, position) + secondary_new = self.secondary.add_child_directive(name, parameters, position) + assertions.assertEqual(primary_new, secondary_new) + new_dir = DualDirectiveNode(primary=primary_new, secondary=secondary_new) + return new_dir + + def add_child_comment(self, comment="", position=None): + """ Creates a new child CommentNode, asserts that both implementations + did it in a similar way, and returns a newly created DualCommentNode + object encapsulating both of the newly created objects """ + + primary_new = self.primary.add_child_comment(comment, position) + secondary_new = self.secondary.add_child_comment(comment, position) + assertions.assertEqual(primary_new, secondary_new) + new_comment = DualCommentNode(primary=primary_new, secondary=secondary_new) + return new_comment + + def _create_matching_list(self, primary_list, secondary_list): # pragma: no cover + """ Matches the list of primary_list to a list of secondary_list and + returns a list of tuples. This is used to create results for find_ + methods. + + This helper function exists, because we cannot ensure that the list of + search results returned by primary.find_* and secondary.find_* are ordered + in a same way. The function pairs the same search results from both + implementations to a list of tuples. + """ + + matched = list() + for p in primary_list: + match = None + for s in secondary_list: + try: + assertions.assertEqual(p, s) + match = s + break + except AssertionError: + continue + if match: + matched.append((p, match)) + else: + raise AssertionError("Could not find a matching node.") + return matched + + def find_blocks(self, name, exclude=True): # pragma: no cover + """ + Performs a search for BlockNodes using both implementations and does simple + checks for results. This is built upon the assumption that unimplemented + find_* methods return a list with a single assertion passing object. + After the assertion, it creates a list of newly created DualBlockNode + instances that encapsulate the pairs of returned BlockNode objects. + """ + pass + + def find_directives(self, name, exclude=True): # pragma: no cover + """ + Performs a search for DirectiveNodes using both implementations and + checks the results. This is built upon the assumption that unimplemented + find_* methods return a list with a single assertion passing object. + After the assertion, it creates a list of newly created DualDirectiveNode + instances that encapsulate the pairs of returned DirectiveNode objects. + """ + pass + + def find_comments(self, comment, exact=False): # pragma: no cover + """ + Performs a search for CommentNodes using both implementations and + checks the results. This is built upon the assumption that unimplemented + find_* methods return a list with a single assertion passing object. + After the assertion, it creates a list of newly created DualCommentNode + instances that encapsulate the pairs of returned CommentNode objects. + """ + pass + + def delete_child(self, child): # pragma: no cover + """Deletes a child from the ParserNode implementations. The actual + ParserNode implementations are used here directly in order to be able + to match a child to the list of children.""" + + self.primary.delete_child(child.primary) + self.secondary.delete_child(child.secondary) + + def unsaved_files(self): + """ Fetches the list of unsaved file paths and asserts that the lists + match """ + primary_files = self.primary.unsaved_files() + secondary_files = self.secondary.unsaved_files() + assertions.assertEqualSimple(primary_files, secondary_files) + + return primary_files diff --git a/certbot-apache/certbot_apache/tests/dualnode_test.py b/certbot-apache/certbot_apache/tests/dualnode_test.py index f8a012280..97f58d6c9 100644 --- a/certbot-apache/certbot_apache/tests/dualnode_test.py +++ b/certbot-apache/certbot_apache/tests/dualnode_test.py @@ -5,35 +5,49 @@ import mock from certbot_apache import assertions from certbot_apache import dualparser +from certbot_apache import interfaces class DualParserNodeTest(unittest.TestCase): """DualParserNode tests""" def setUp(self): # pylint: disable=arguments-differ + self.block = dualparser.DualBlockNode(name="block", + ancestor=None, + filepath="/tmp/something") + self.block_two = dualparser.DualBlockNode(name="block", + ancestor=self.block, + filepath="/tmp/something") self.directive = dualparser.DualDirectiveNode(name="directive", - ancestor=None, + ancestor=self.block, filepath="/tmp/something") self.comment = dualparser.DualCommentNode(comment="comment", - ancestor=None, + ancestor=self.block, filepath="/tmp/something") def test_create_with_precreated(self): cnode = dualparser.DualCommentNode(comment="comment", - ancestor=None, + ancestor=self.block, filepath="/tmp/something", primary=self.comment.secondary, secondary=self.comment.primary) dnode = dualparser.DualDirectiveNode(name="directive", - ancestor=None, + ancestor=self.block, filepath="/tmp/something", primary=self.directive.secondary, secondary=self.directive.primary) + bnode = dualparser.DualBlockNode(name="block", + ancestor=self.block, + filepath="/tmp/something", + primary=self.block.secondary, + secondary=self.block.primary) # Switched around self.assertTrue(cnode.primary is self.comment.secondary) self.assertTrue(cnode.secondary is self.comment.primary) self.assertTrue(dnode.primary is self.directive.secondary) self.assertTrue(dnode.secondary is self.directive.primary) + self.assertTrue(bnode.primary is self.block.secondary) + self.assertTrue(bnode.secondary is self.block.primary) def test_set_params(self): params = ("first", "second") @@ -52,6 +66,26 @@ class DualParserNodeTest(unittest.TestCase): self.assertTrue(pparams.called) self.assertTrue(sparams.called) + def test_delete_child(self): + pdel = mock.MagicMock() + sdel = mock.MagicMock() + self.block.primary.delete_child = pdel + self.block.secondary.delete_child = sdel + self.block.delete_child(self.comment) + self.assertTrue(pdel.called) + self.assertTrue(sdel.called) + + def test_unsaved_files(self): + puns = mock.MagicMock() + suns = mock.MagicMock() + puns.return_value = assertions.PASS + suns.return_value = assertions.PASS + self.block.primary.unsaved_files = puns + self.block.secondary.unsaved_files = suns + self.block.unsaved_files() + self.assertTrue(puns.called) + self.assertTrue(suns.called) + def test_getattr_equality(self): self.directive.primary.variableexception = "value" self.directive.secondary.variableexception = "not_value" @@ -86,3 +120,36 @@ class DualParserNodeTest(unittest.TestCase): self.comment.secondary.filepath = "second" with self.assertRaises(AssertionError): assertions.assertEqual(self.comment.primary, self.comment.secondary) + + def test_add_child_block(self): + self.assertEqual(len(self.block.primary.children), 0) + self.assertEqual(len(self.block.secondary.children), 0) + self.block.add_child_block("Block") + self.assertEqual(len(self.block.primary.children), 1) + self.assertEqual(len(self.block.secondary.children), 1) + self.assertTrue(isinstance(self.block.primary.children[0], + interfaces.BlockNode)) + self.assertEqual(self.block.primary.children[0].ancestor, + self.block.primary) + + def test_add_child_directive(self): + self.assertEqual(len(self.block.primary.children), 0) + self.assertEqual(len(self.block.secondary.children), 0) + self.block.add_child_directive("Directive") + self.assertEqual(len(self.block.primary.children), 1) + self.assertEqual(len(self.block.secondary.children), 1) + self.assertTrue(isinstance(self.block.primary.children[0], + interfaces.DirectiveNode)) + self.assertEqual(self.block.primary.children[0].ancestor, + self.block.primary) + + def test_add_child_comment(self): + self.assertEqual(len(self.block.primary.children), 0) + self.assertEqual(len(self.block.secondary.children), 0) + self.block.add_child_comment("Comment") + self.assertEqual(len(self.block.primary.children), 1) + self.assertEqual(len(self.block.secondary.children), 1) + self.assertTrue(isinstance(self.block.primary.children[0], + interfaces.CommentNode)) + self.assertEqual(self.block.primary.children[0].ancestor, + self.block.primary) From feacbe9671d0514afd87fe098d9a4de4241ef497 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Mon, 23 Sep 2019 23:27:48 +0300 Subject: [PATCH 008/122] [Apache v2] DualParserNode implementation 3/3 (#7376) * DualParserNode, DualCommentNode and DualDirectiveNode implementations * Add DualBlockNode * DualBlockNode find_ methods * Address review comments * Address review comments * Simplify isPass * Add explanation to _create_matching_list pydoc * Remove unnecessary conditional block * Address review comments --- certbot-apache/certbot_apache/assertions.py | 43 +++ certbot-apache/certbot_apache/augeasparser.py | 29 +- certbot-apache/certbot_apache/dualparser.py | 62 ++++- .../certbot_apache/tests/dualnode_test.py | 248 +++++++++++++++++- 4 files changed, 361 insertions(+), 21 deletions(-) diff --git a/certbot-apache/certbot_apache/assertions.py b/certbot-apache/certbot_apache/assertions.py index 659efee28..fc2b35e14 100644 --- a/certbot-apache/certbot_apache/assertions.py +++ b/certbot-apache/certbot_apache/assertions.py @@ -13,6 +13,11 @@ def assertEqual(first, second): elif isinstance(first, interfaces.DirectiveNode): assertEqualDirective(first, second) + # Do an extra interface implementation assertion, as the contents were + # already checked for BlockNode in the assertEqualDirective + if isinstance(first, interfaces.BlockNode): + assert isinstance(second, interfaces.BlockNode) + # Skip tests if filepath includes the pass value. This is done # because filepath is variable of the base ParserNode interface, and # unless the implementation is actually done, we cannot assume getting @@ -55,6 +60,44 @@ def isPass(value): # pragma: no cover """Checks if the value is set to PASS""" return PASS in value +def isPassDirective(block): + """ Checks if BlockNode or DirectiveNode should pass the assertion """ + + if isPass(block.name): + return True + if isPass(block.parameters): # pragma: no cover + return True + if isPass(block.filepath): # pragma: no cover + return True + return False + +def isPassComment(comment): + """ Checks if CommentNode should pass the assertion """ + + if isPass(comment.comment): + return True + if isPass(comment.filepath): # pragma: no cover + return True + return False + +def isPassNodeList(nodelist): # pragma: no cover + """ Checks if a ParserNode in the nodelist should pass the assertion, + this function is used for results of find_* methods. Unimplemented find_* + methods should return a sequence containing a single ParserNode instance + with assertion pass string.""" + + try: + node = nodelist[0] + except IndexError: + node = None + + if not node: # pragma: no cover + return False + + if isinstance(node, interfaces.DirectiveNode): + return isPassDirective(node) + return isPassComment(node) + def assertEqualSimple(first, second): """ Simple assertion """ if not isPass(first) and not isPass(second): diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py index f396ab76a..9325de5de 100644 --- a/certbot-apache/certbot_apache/augeasparser.py +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -11,8 +11,7 @@ class AugeasParserNode(interfaces.ParserNode): ancestor, dirty, filepath, metadata = util.parsernode_kwargs(kwargs) # pylint: disable=unused-variable super(AugeasParserNode, self).__init__(**kwargs) self.ancestor = ancestor - # self.filepath = filepath - self.filepath = assertions.PASS + self.filepath = filepath self.dirty = dirty self.metadata = metadata @@ -27,9 +26,9 @@ class AugeasCommentNode(AugeasParserNode): comment, kwargs = util.commentnode_kwargs(kwargs) # pylint: disable=unused-variable super(AugeasCommentNode, self).__init__(**kwargs) # self.comment = comment - self.comment = assertions.PASS + self.comment = comment - def __eq__(self, other): # pragma: no cover + def __eq__(self, other): if isinstance(other, self.__class__): return (self.comment == other.comment and self.filepath == other.filepath and @@ -49,7 +48,7 @@ class AugeasDirectiveNode(AugeasParserNode): self.parameters = parameters self.enabled = enabled - def __eq__(self, other): # pragma: no cover + def __eq__(self, other): if isinstance(other, self.__class__): return (self.name == other.name and self.filepath == other.filepath and @@ -72,7 +71,7 @@ class AugeasBlockNode(AugeasDirectiveNode): super(AugeasBlockNode, self).__init__(**kwargs) self.children = () - def __eq__(self, other): # pragma: no cover + def __eq__(self, other): if isinstance(other, self.__class__): return (self.name == other.name and self.filepath == other.filepath and @@ -108,17 +107,23 @@ class AugeasBlockNode(AugeasDirectiveNode): self.children += (new_comment,) return new_comment - def find_blocks(self, name, exclude=True): # pragma: no cover + def find_blocks(self, name, exclude=True): # pylint: disable=unused-argument """Recursive search of BlockNodes from the sequence of children""" - pass + return [AugeasBlockNode(name=assertions.PASS, + ancestor=self, + filepath=assertions.PASS)] - def find_directives(self, name, exclude=True): # pragma: no cover + def find_directives(self, name, exclude=True): # pylint: disable=unused-argument """Recursive search of DirectiveNodes from the sequence of children""" - pass + return [AugeasDirectiveNode(name=assertions.PASS, + ancestor=self, + filepath=assertions.PASS)] - def find_comments(self, comment, exact=False): # pragma: no cover + def find_comments(self, comment, exact=False): # pylint: disable=unused-argument """Recursive search of DirectiveNodes from the sequence of children""" - pass + return [AugeasCommentNode(comment=assertions.PASS, + ancestor=self, + filepath=assertions.PASS)] def delete_child(self, child): # pragma: no cover """Deletes a ParserNode from the sequence of children""" diff --git a/certbot-apache/certbot_apache/dualparser.py b/certbot-apache/certbot_apache/dualparser.py index 5f00d59f9..5fa337a45 100644 --- a/certbot-apache/certbot_apache/dualparser.py +++ b/certbot-apache/certbot_apache/dualparser.py @@ -94,6 +94,7 @@ class DualDirectiveNode(DualNodeBase): self.secondary.set_parameters(parameters) assertions.assertEqual(self.primary, self.secondary) + class DualBlockNode(DualNodeBase): """ Dual parser implementation of BlockNode interface """ @@ -158,7 +159,7 @@ class DualBlockNode(DualNodeBase): new_comment = DualCommentNode(primary=primary_new, secondary=secondary_new) return new_comment - def _create_matching_list(self, primary_list, secondary_list): # pragma: no cover + def _create_matching_list(self, primary_list, secondary_list): """ Matches the list of primary_list to a list of secondary_list and returns a list of tuples. This is used to create results for find_ methods. @@ -185,7 +186,7 @@ class DualBlockNode(DualNodeBase): raise AssertionError("Could not find a matching node.") return matched - def find_blocks(self, name, exclude=True): # pragma: no cover + def find_blocks(self, name, exclude=True): """ Performs a search for BlockNodes using both implementations and does simple checks for results. This is built upon the assumption that unimplemented @@ -193,9 +194,11 @@ class DualBlockNode(DualNodeBase): After the assertion, it creates a list of newly created DualBlockNode instances that encapsulate the pairs of returned BlockNode objects. """ - pass - def find_directives(self, name, exclude=True): # pragma: no cover + return self._find_helper(DualBlockNode, "find_blocks", name, + exclude=exclude) + + def find_directives(self, name, exclude=True): """ Performs a search for DirectiveNodes using both implementations and checks the results. This is built upon the assumption that unimplemented @@ -203,9 +206,11 @@ class DualBlockNode(DualNodeBase): After the assertion, it creates a list of newly created DualDirectiveNode instances that encapsulate the pairs of returned DirectiveNode objects. """ - pass - def find_comments(self, comment, exact=False): # pragma: no cover + return self._find_helper(DualDirectiveNode, "find_directives", name, + exclude=exclude) + + def find_comments(self, comment, exact=False): """ Performs a search for CommentNodes using both implementations and checks the results. This is built upon the assumption that unimplemented @@ -213,9 +218,50 @@ class DualBlockNode(DualNodeBase): After the assertion, it creates a list of newly created DualCommentNode instances that encapsulate the pairs of returned CommentNode objects. """ - pass - def delete_child(self, child): # pragma: no cover + return self._find_helper(DualCommentNode, "find_comments", comment, + exact=exact) + + def _find_helper(self, nodeclass, findfunc, search, **kwargs): + """A helper for find_* functions. The function specific attributes should + be passed as keyword arguments. + + :param interfaces.ParserNode nodeclass: The node class for results. + :param str findfunc: Name of the find function to call + :param str search: The search term + """ + + primary_res = getattr(self.primary, findfunc)(search, **kwargs) + secondary_res = getattr(self.secondary, findfunc)(search, **kwargs) + + # The order of search results for Augeas implementation cannot be + # assured. + + pass_primary = assertions.isPassNodeList(primary_res) + pass_secondary = assertions.isPassNodeList(secondary_res) + new_nodes = list() + + if pass_primary and pass_secondary: + # Both unimplemented + new_nodes.append(nodeclass(primary=primary_res[0], + secondary=secondary_res[0])) + elif pass_primary: + for c in secondary_res: + new_nodes.append(nodeclass(primary=primary_res[0], + secondary=c)) + elif pass_secondary: + for c in primary_res: + new_nodes.append(nodeclass(primary=c, + secondary=secondary_res[0])) + else: + assert len(primary_res) == len(secondary_res) + matches = self._create_matching_list(primary_res, secondary_res) + for p, s in matches: + new_nodes.append(nodeclass(primary=p, secondary=s)) + + return new_nodes + + def delete_child(self, child): """Deletes a child from the ParserNode implementations. The actual ParserNode implementations are used here directly in order to be able to match a child to the list of children.""" diff --git a/certbot-apache/certbot_apache/tests/dualnode_test.py b/certbot-apache/certbot_apache/tests/dualnode_test.py index 97f58d6c9..eeea7d151 100644 --- a/certbot-apache/certbot_apache/tests/dualnode_test.py +++ b/certbot-apache/certbot_apache/tests/dualnode_test.py @@ -4,11 +4,12 @@ import unittest import mock from certbot_apache import assertions +from certbot_apache import augeasparser from certbot_apache import dualparser from certbot_apache import interfaces -class DualParserNodeTest(unittest.TestCase): +class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-methods """DualParserNode tests""" def setUp(self): # pylint: disable=arguments-differ @@ -153,3 +154,248 @@ class DualParserNodeTest(unittest.TestCase): interfaces.CommentNode)) self.assertEqual(self.block.primary.children[0].ancestor, self.block.primary) + + def test_find_blocks(self): + dblks = self.block.find_blocks("block") + p_dblks = [d.primary for d in dblks] + s_dblks = [d.secondary for d in dblks] + p_blks = self.block.primary.find_blocks("block") + s_blks = self.block.secondary.find_blocks("block") + # Check that every block response is represented in the list of + # DualParserNode instances. + for p in p_dblks: + self.assertTrue(p in p_blks) + for s in s_dblks: + self.assertTrue(s in s_blks) + + def test_find_directives(self): + ddirs = self.block.find_directives("directive") + p_ddirs = [d.primary for d in ddirs] + s_ddirs = [d.secondary for d in ddirs] + p_dirs = self.block.primary.find_directives("directive") + s_dirs = self.block.secondary.find_directives("directive") + # Check that every directive response is represented in the list of + # DualParserNode instances. + for p in p_ddirs: + self.assertTrue(p in p_dirs) + for s in s_ddirs: + self.assertTrue(s in s_dirs) + + def test_find_comments(self): + dcoms = self.block.find_comments("comment") + p_dcoms = [d.primary for d in dcoms] + s_dcoms = [d.secondary for d in dcoms] + p_coms = self.block.primary.find_comments("comment") + s_coms = self.block.secondary.find_comments("comment") + # Check that every comment response is represented in the list of + # DualParserNode instances. + for p in p_dcoms: + self.assertTrue(p in p_coms) + for s in s_dcoms: + self.assertTrue(s in s_coms) + + def test_find_blocks_first_passing(self): + youshallnotpass = [augeasparser.AugeasBlockNode(name="notpassing", + ancestor=self.block, + filepath="/path/to/whatever")] + youshallpass = [augeasparser.AugeasBlockNode(name=assertions.PASS, + ancestor=self.block, + filepath=assertions.PASS)] + find_blocks_primary = mock.MagicMock(return_value=youshallpass) + find_blocks_secondary = mock.MagicMock(return_value=youshallnotpass) + self.block.primary.find_blocks = find_blocks_primary + self.block.secondary.find_blocks = find_blocks_secondary + + blocks = self.block.find_blocks("something") + for block in blocks: + try: + assertions.assertEqual(block.primary, block.secondary) + except AssertionError: # pragma: no cover + self.fail("Assertion should have passed") + self.assertTrue(assertions.isPassDirective(block.primary)) + self.assertFalse(assertions.isPassDirective(block.secondary)) + + def test_find_blocks_second_passing(self): + youshallnotpass = [augeasparser.AugeasBlockNode(name="notpassing", + ancestor=self.block, + filepath="/path/to/whatever")] + youshallpass = [augeasparser.AugeasBlockNode(name=assertions.PASS, + ancestor=self.block, + filepath=assertions.PASS)] + find_blocks_primary = mock.MagicMock(return_value=youshallnotpass) + find_blocks_secondary = mock.MagicMock(return_value=youshallpass) + self.block.primary.find_blocks = find_blocks_primary + self.block.secondary.find_blocks = find_blocks_secondary + + blocks = self.block.find_blocks("something") + for block in blocks: + try: + assertions.assertEqual(block.primary, block.secondary) + except AssertionError: # pragma: no cover + self.fail("Assertion should have passed") + self.assertFalse(assertions.isPassDirective(block.primary)) + self.assertTrue(assertions.isPassDirective(block.secondary)) + + def test_find_dirs_first_passing(self): + notpassing = [augeasparser.AugeasDirectiveNode(name="notpassing", + ancestor=self.block, + filepath="/path/to/whatever")] + passing = [augeasparser.AugeasDirectiveNode(name=assertions.PASS, + ancestor=self.block, + filepath=assertions.PASS)] + find_dirs_primary = mock.MagicMock(return_value=passing) + find_dirs_secondary = mock.MagicMock(return_value=notpassing) + self.block.primary.find_directives = find_dirs_primary + self.block.secondary.find_directives = find_dirs_secondary + + directives = self.block.find_directives("something") + for directive in directives: + try: + assertions.assertEqual(directive.primary, directive.secondary) + except AssertionError: # pragma: no cover + self.fail("Assertion should have passed") + self.assertTrue(assertions.isPassDirective(directive.primary)) + self.assertFalse(assertions.isPassDirective(directive.secondary)) + + def test_find_dirs_second_passing(self): + notpassing = [augeasparser.AugeasDirectiveNode(name="notpassing", + ancestor=self.block, + filepath="/path/to/whatever")] + passing = [augeasparser.AugeasDirectiveNode(name=assertions.PASS, + ancestor=self.block, + filepath=assertions.PASS)] + find_dirs_primary = mock.MagicMock(return_value=notpassing) + find_dirs_secondary = mock.MagicMock(return_value=passing) + self.block.primary.find_directives = find_dirs_primary + self.block.secondary.find_directives = find_dirs_secondary + + directives = self.block.find_directives("something") + for directive in directives: + try: + assertions.assertEqual(directive.primary, directive.secondary) + except AssertionError: # pragma: no cover + self.fail("Assertion should have passed") + self.assertFalse(assertions.isPassDirective(directive.primary)) + self.assertTrue(assertions.isPassDirective(directive.secondary)) + + def test_find_coms_first_passing(self): + notpassing = [augeasparser.AugeasCommentNode(comment="notpassing", + ancestor=self.block, + filepath="/path/to/whatever")] + passing = [augeasparser.AugeasCommentNode(comment=assertions.PASS, + ancestor=self.block, + filepath=assertions.PASS)] + find_coms_primary = mock.MagicMock(return_value=passing) + find_coms_secondary = mock.MagicMock(return_value=notpassing) + self.block.primary.find_comments = find_coms_primary + self.block.secondary.find_comments = find_coms_secondary + + comments = self.block.find_comments("something") + for comment in comments: + try: + assertions.assertEqual(comment.primary, comment.secondary) + except AssertionError: # pragma: no cover + self.fail("Assertion should have passed") + self.assertTrue(assertions.isPassComment(comment.primary)) + self.assertFalse(assertions.isPassComment(comment.secondary)) + + def test_find_coms_second_passing(self): + notpassing = [augeasparser.AugeasCommentNode(comment="notpassing", + ancestor=self.block, + filepath="/path/to/whatever")] + passing = [augeasparser.AugeasCommentNode(comment=assertions.PASS, + ancestor=self.block, + filepath=assertions.PASS)] + find_coms_primary = mock.MagicMock(return_value=notpassing) + find_coms_secondary = mock.MagicMock(return_value=passing) + self.block.primary.find_comments = find_coms_primary + self.block.secondary.find_comments = find_coms_secondary + + comments = self.block.find_comments("something") + for comment in comments: + try: + assertions.assertEqual(comment.primary, comment.secondary) + except AssertionError: # pragma: no cover + self.fail("Assertion should have passed") + self.assertFalse(assertions.isPassComment(comment.primary)) + self.assertTrue(assertions.isPassComment(comment.secondary)) + + def test_find_blocks_no_pass_equal(self): + notpassing1 = [augeasparser.AugeasBlockNode(name="notpassing", + ancestor=self.block, + filepath="/path/to/whatever")] + notpassing2 = [augeasparser.AugeasBlockNode(name="notpassing", + ancestor=self.block, + filepath="/path/to/whatever")] + find_blocks_primary = mock.MagicMock(return_value=notpassing1) + find_blocks_secondary = mock.MagicMock(return_value=notpassing2) + self.block.primary.find_blocks = find_blocks_primary + self.block.secondary.find_blocks = find_blocks_secondary + + blocks = self.block.find_blocks("anything") + for block in blocks: + self.assertEqual(block.primary, block.secondary) + self.assertTrue(block.primary is not block.secondary) + + def test_find_dirs_no_pass_equal(self): + notpassing1 = [augeasparser.AugeasDirectiveNode(name="notpassing", + ancestor=self.block, + filepath="/path/to/whatever")] + notpassing2 = [augeasparser.AugeasDirectiveNode(name="notpassing", + ancestor=self.block, + filepath="/path/to/whatever")] + find_dirs_primary = mock.MagicMock(return_value=notpassing1) + find_dirs_secondary = mock.MagicMock(return_value=notpassing2) + self.block.primary.find_directives = find_dirs_primary + self.block.secondary.find_directives = find_dirs_secondary + + directives = self.block.find_directives("anything") + for directive in directives: + self.assertEqual(directive.primary, directive.secondary) + self.assertTrue(directive.primary is not directive.secondary) + + def test_find_comments_no_pass_equal(self): + notpassing1 = [augeasparser.AugeasCommentNode(comment="notpassing", + ancestor=self.block, + filepath="/path/to/whatever")] + notpassing2 = [augeasparser.AugeasCommentNode(comment="notpassing", + ancestor=self.block, + filepath="/path/to/whatever")] + find_coms_primary = mock.MagicMock(return_value=notpassing1) + find_coms_secondary = mock.MagicMock(return_value=notpassing2) + self.block.primary.find_comments = find_coms_primary + self.block.secondary.find_comments = find_coms_secondary + + comments = self.block.find_comments("anything") + for comment in comments: + self.assertEqual(comment.primary, comment.secondary) + self.assertTrue(comment.primary is not comment.secondary) + + def test_find_blocks_no_pass_notequal(self): + notpassing1 = [augeasparser.AugeasBlockNode(name="notpassing", + ancestor=self.block, + filepath="/path/to/whatever")] + notpassing2 = [augeasparser.AugeasBlockNode(name="different", + ancestor=self.block, + filepath="/path/to/whatever")] + find_blocks_primary = mock.MagicMock(return_value=notpassing1) + find_blocks_secondary = mock.MagicMock(return_value=notpassing2) + self.block.primary.find_blocks = find_blocks_primary + self.block.secondary.find_blocks = find_blocks_secondary + + with self.assertRaises(AssertionError): + _ = self.block.find_blocks("anything") + + def test_parsernode_notequal(self): + ne_block = augeasparser.AugeasBlockNode(name="different", + ancestor=self.block, + filepath="/path/to/whatever") + ne_directive = augeasparser.AugeasDirectiveNode(name="different", + ancestor=self.block, + filepath="/path/to/whatever") + ne_comment = augeasparser.AugeasCommentNode(comment="different", + ancestor=self.block, + filepath="/path/to/whatever") + self.assertFalse(self.block == ne_block) + self.assertFalse(self.directive == ne_directive) + self.assertFalse(self.comment == ne_comment) From 3f36298716e87131e622d82d8e15e4f896357a96 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Mon, 21 Oct 2019 22:52:00 +0300 Subject: [PATCH 009/122] [Apache v2] find_blocks and find_directives implementation (#7443) * Implement AugeasDirectiveNode and AugeasBlockNode find and create functions * Add tests for _aug_get_block_name --- certbot-apache/certbot_apache/augeasparser.py | 119 ++++++++++++++++-- certbot-apache/certbot_apache/configurator.py | 16 +++ certbot-apache/certbot_apache/dualparser.py | 3 +- certbot-apache/certbot_apache/parser.py | 4 +- .../certbot_apache/tests/augeasnode_test.py | 65 ++++++++++ .../certbot_apache/tests/dualnode_test.py | 39 ++---- 6 files changed, 203 insertions(+), 43 deletions(-) create mode 100644 certbot-apache/certbot_apache/tests/augeasnode_test.py diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py index 9325de5de..1bbf1ed77 100644 --- a/certbot-apache/certbot_apache/augeasparser.py +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -1,8 +1,14 @@ """ Augeas implementation of the ParserNode interfaces """ + +from certbot_apache import apache_util from certbot_apache import assertions from certbot_apache import interfaces +from certbot_apache import parser from certbot_apache import parsernode_util as util +from certbot.compat import os +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module + class AugeasParserNode(interfaces.ParserNode): """ Augeas implementation of ParserNode interface """ @@ -14,6 +20,7 @@ class AugeasParserNode(interfaces.ParserNode): self.filepath = filepath self.dirty = dirty self.metadata = metadata + self.parser = self.metadata.get("augeasparser") def save(self, msg): # pragma: no cover pass @@ -85,45 +92,72 @@ class AugeasBlockNode(AugeasDirectiveNode): def add_child_block(self, name, parameters=None, position=None): # pylint: disable=unused-argument """Adds a new BlockNode to the sequence of children""" + new_metadata = {"augeasparser": self.parser} new_block = AugeasBlockNode(name=assertions.PASS, ancestor=self, - filepath=assertions.PASS) + filepath=assertions.PASS, + metadata=new_metadata) self.children += (new_block,) return new_block def add_child_directive(self, name, parameters=None, position=None): # pylint: disable=unused-argument """Adds a new DirectiveNode to the sequence of children""" + new_metadata = {"augeasparser": self.parser} new_dir = AugeasDirectiveNode(name=assertions.PASS, ancestor=self, - filepath=assertions.PASS) + filepath=assertions.PASS, + metadata=new_metadata) self.children += (new_dir,) return new_dir def add_child_comment(self, comment="", position=None): # pylint: disable=unused-argument """Adds a new CommentNode to the sequence of children""" + new_metadata = {"augeasparser": self.parser} new_comment = AugeasCommentNode(comment=assertions.PASS, ancestor=self, - filepath=assertions.PASS) + filepath=assertions.PASS, + metadata=new_metadata) self.children += (new_comment,) return new_comment def find_blocks(self, name, exclude=True): # pylint: disable=unused-argument """Recursive search of BlockNodes from the sequence of children""" - return [AugeasBlockNode(name=assertions.PASS, - ancestor=self, - filepath=assertions.PASS)] + + nodes = list() + paths = self._aug_find_blocks(name) + if exclude: + paths = self.parser.exclude_dirs(paths) + for path in paths: + nodes.append(self._create_blocknode(path)) + + return nodes def find_directives(self, name, exclude=True): # pylint: disable=unused-argument """Recursive search of DirectiveNodes from the sequence of children""" - return [AugeasDirectiveNode(name=assertions.PASS, - ancestor=self, - filepath=assertions.PASS)] + + nodes = list() + ownpath = self.metadata.get("augeaspath") + + directives = self.parser.find_dir(name, start=ownpath, exclude=exclude) + already_parsed = set() # type: Set[str] + for directive in directives: + # Remove the /arg part from the Augeas path + directive = directive.partition("/arg")[0] + # find_dir returns an object for each _parameter_ of a directive + # so we need to filter out duplicates. + if directive not in already_parsed: + nodes.append(self._create_directivenode(directive)) + already_parsed.add(directive) + + return nodes def find_comments(self, comment, exact=False): # pylint: disable=unused-argument """Recursive search of DirectiveNodes from the sequence of children""" + new_metadata = {"augeasparser": self.parser} return [AugeasCommentNode(comment=assertions.PASS, ancestor=self, - filepath=assertions.PASS)] + filepath=assertions.PASS, + metadata=new_metadata)] def delete_child(self, child): # pragma: no cover """Deletes a ParserNode from the sequence of children""" @@ -133,6 +167,71 @@ class AugeasBlockNode(AugeasDirectiveNode): """Returns a list of unsaved filepaths""" return [assertions.PASS] + def _create_directivenode(self, path): + """Helper function to create a DirectiveNode from Augeas path""" + + name = self.parser.get_arg(path) + params = tuple(self._aug_get_params(path)) + metadata = {"augeasparser": self.parser, "augeaspath": path} + + # Because of the dynamic nature, and the fact that we're not populating + # the complete ParserNode tree, we use the search parent as ancestor + return AugeasDirectiveNode(name=name, + parameters=params, + ancestor=self, + filepath=apache_util.get_file_path(path), + metadata=metadata) + + def _create_blocknode(self, path): + """Helper function to create a BlockNode from Augeas path""" + + name = self._aug_get_block_name(path) + params = tuple(self._aug_get_params(path)) + metadata = {"augeasparser": self.parser, "augeaspath": path} + + # Because of the dynamic nature, and the fact that we're not populating + # the complete ParserNode tree, we use the search parent as ancestor + return AugeasBlockNode(name=name, + parameters=params, + ancestor=self, + filepath=apache_util.get_file_path(path), + metadata=metadata) + + def _aug_find_blocks(self, name): + """Helper function to perform a search to Augeas DOM tree to search + configuration blocks with a given name""" + + # The code here is modified from configurator.get_virtual_hosts() + blk_paths = set() + for vhost_path in list(self.parser.parser_paths): + paths = self.parser.aug.match( + ("/files%s//*[label()=~regexp('%s')]" % + (vhost_path, parser.case_i(name)))) + blk_paths.update([path for path in paths if + name.lower() in os.path.basename(path).lower()]) + return blk_paths + + def _aug_get_params(self, path): + """Helper function to get parameters for BlockNodes""" + + arg_paths = self.parser.aug.match(path + "/arg") + return [self.parser.get_arg(apath) for apath in arg_paths] + + def _aug_get_block_name(self, path): + """Helper function to get name of a configuration block from path.""" + + # Remove the ending slash if any + if path[-1] == "/": # pragma: no cover + path = path[:-1] + + # Get the block name + name = path.split("/")[-1] + + # remove [...], it's not allowed in Apache configuration and is used + # for indexing within Augeas + name = name.split("[")[0] + return name + interfaces.CommentNode.register(AugeasCommentNode) interfaces.DirectiveNode.register(AugeasDirectiveNode) diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index f7c27bf76..d01b0f5ce 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -30,8 +30,10 @@ from certbot.plugins.util import path_surgery from certbot.plugins.enhancements import AutoHSTSEnhancement from certbot_apache import apache_util +from certbot_apache import assertions from certbot_apache import constants from certbot_apache import display_ops +from certbot_apache import dualparser from certbot_apache import http_01 from certbot_apache import obj from certbot_apache import parser @@ -204,6 +206,7 @@ class ApacheConfigurator(common.Installer): # These will be set in the prepare function self._prepared = False self.parser = None + self.parser_root = None self.version = version self.vhosts = None self.options = copy.deepcopy(self.OS_DEFAULTS) @@ -253,6 +256,10 @@ class ApacheConfigurator(common.Installer): # Perform the actual Augeas initialization to be able to react self.parser = self.get_parser() + # Set up ParserNode root + pn_meta = {"augeasparser": self.parser} + self.parser_root = self.get_parsernode_root(pn_meta) + # Check for errors in parsing files with Augeas self.parser.check_parsing_errors("httpd.aug") @@ -348,6 +355,15 @@ class ApacheConfigurator(common.Installer): self.option("server_root"), self.conf("vhost-root"), self.version, configurator=self) + def get_parsernode_root(self, metadata): + """Initializes the ParserNode parser root instance.""" + return dualparser.DualBlockNode( + name=assertions.PASS, + ancestor=None, + filepath=assertions.PASS, + metadata=metadata + ) + def _wildcard_domain(self, domain): """ Checks if domain is a wildcard domain diff --git a/certbot-apache/certbot_apache/dualparser.py b/certbot-apache/certbot_apache/dualparser.py index 5fa337a45..d56e07de2 100644 --- a/certbot-apache/certbot_apache/dualparser.py +++ b/certbot-apache/certbot_apache/dualparser.py @@ -17,7 +17,8 @@ class DualNodeBase(object): """ Attribute value assertion """ firstval = getattr(self.primary, aname) secondval = getattr(self.secondary, aname) - assertions.assertEqualSimple(firstval, secondval) + if not callable(firstval): + assertions.assertEqualSimple(firstval, secondval) return firstval diff --git a/certbot-apache/certbot_apache/parser.py b/certbot-apache/certbot_apache/parser.py index b5f0cd81a..7ae9ac386 100644 --- a/certbot-apache/certbot_apache/parser.py +++ b/certbot-apache/certbot_apache/parser.py @@ -613,7 +613,7 @@ class ApacheParser(object): "%s//*[self::directive=~regexp('%s')]" % (start, regex)) if exclude: - matches = self._exclude_dirs(matches) + matches = self.exclude_dirs(matches) if arg is None: arg_suffix = "/arg" @@ -680,7 +680,7 @@ class ApacheParser(object): return value - def _exclude_dirs(self, matches): + def exclude_dirs(self, matches): """Exclude directives that are not loaded into the configuration.""" filters = [("ifmodule", self.modules), ("ifdefine", self.variables)] diff --git a/certbot-apache/certbot_apache/tests/augeasnode_test.py b/certbot-apache/certbot_apache/tests/augeasnode_test.py new file mode 100644 index 000000000..c4631c57c --- /dev/null +++ b/certbot-apache/certbot_apache/tests/augeasnode_test.py @@ -0,0 +1,65 @@ +"""Tests for AugeasParserNode classes""" +import mock + +from certbot_apache import assertions + +from certbot_apache.tests import util + + +class AugeasParserNodeTest(util.ApacheTest): + """Test AugeasParserNode using available test configurations""" + + def setUp(self): # pylint: disable=arguments-differ + super(AugeasParserNodeTest, self).setUp() + + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir) + self.vh_truth = util.get_vh_truth( + self.temp_dir, "debian_apache_2_4/multiple_vhosts") + + def test_get_block_node_name(self): + from certbot_apache.augeasparser import AugeasBlockNode + block = AugeasBlockNode( + name=assertions.PASS, + ancestor=None, + filepath=assertions.PASS, + metadata={"augeasparser": mock.Mock()} + ) + testcases = { + "/some/path/FirstNode/SecondNode": "SecondNode", + "/some/path/FirstNode/SecondNode/": "SecondNode", + "OnlyPathItem": "OnlyPathItem", + "/files/etc/apache2/apache2.conf/VirtualHost": "VirtualHost", + "/Anything": "Anything", + } + for test in testcases: + self.assertEqual(block._aug_get_block_name(test), testcases[test]) # pylint: disable=protected-access + + def test_find_blocks(self): + blocks = self.config.parser_root.find_blocks("VirtualHost", exclude=False) + self.assertEqual(len(blocks), 12) + + def test_find_blocks_case_insensitive(self): + vhs = self.config.parser_root.find_blocks("VirtualHost") + vhs2 = self.config.parser_root.find_blocks("viRtuAlHoST") + self.assertEqual(len(vhs), len(vhs2)) + + def test_find_directive_found(self): + directives = self.config.parser_root.find_directives("Listen") + self.assertEqual(len(directives), 1) + self.assertTrue(directives[0].filepath.endswith("/apache2/ports.conf")) + self.assertEqual(directives[0].parameters, (u'80',)) + + def test_find_directive_notfound(self): + directives = self.config.parser_root.find_directives("Nonexistent") + self.assertEqual(len(directives), 0) + + def test_find_directive_from_block(self): + blocks = self.config.parser_root.find_blocks("virtualhost") + found = False + for vh in blocks: + if vh.filepath.endswith("sites-enabled/certbot.conf"): + servername = vh.find_directives("servername") + self.assertEqual(servername[0].parameters[0], "certbot.demo") + found = True + self.assertTrue(found) diff --git a/certbot-apache/certbot_apache/tests/dualnode_test.py b/certbot-apache/certbot_apache/tests/dualnode_test.py index eeea7d151..b37a7fdf9 100644 --- a/certbot-apache/certbot_apache/tests/dualnode_test.py +++ b/certbot-apache/certbot_apache/tests/dualnode_test.py @@ -13,18 +13,23 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- """DualParserNode tests""" def setUp(self): # pylint: disable=arguments-differ + metadata = {"augeasparser": mock.Mock()} self.block = dualparser.DualBlockNode(name="block", ancestor=None, - filepath="/tmp/something") + filepath="/tmp/something", + metadata=metadata) self.block_two = dualparser.DualBlockNode(name="block", ancestor=self.block, - filepath="/tmp/something") + filepath="/tmp/something", + metadata=metadata) self.directive = dualparser.DualDirectiveNode(name="directive", ancestor=self.block, - filepath="/tmp/something") + filepath="/tmp/something", + metadata=metadata) self.comment = dualparser.DualCommentNode(comment="comment", ancestor=self.block, - filepath="/tmp/something") + filepath="/tmp/something", + metadata=metadata) def test_create_with_precreated(self): cnode = dualparser.DualCommentNode(comment="comment", @@ -155,32 +160,6 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- self.assertEqual(self.block.primary.children[0].ancestor, self.block.primary) - def test_find_blocks(self): - dblks = self.block.find_blocks("block") - p_dblks = [d.primary for d in dblks] - s_dblks = [d.secondary for d in dblks] - p_blks = self.block.primary.find_blocks("block") - s_blks = self.block.secondary.find_blocks("block") - # Check that every block response is represented in the list of - # DualParserNode instances. - for p in p_dblks: - self.assertTrue(p in p_blks) - for s in s_dblks: - self.assertTrue(s in s_blks) - - def test_find_directives(self): - ddirs = self.block.find_directives("directive") - p_ddirs = [d.primary for d in ddirs] - s_ddirs = [d.secondary for d in ddirs] - p_dirs = self.block.primary.find_directives("directive") - s_dirs = self.block.secondary.find_directives("directive") - # Check that every directive response is represented in the list of - # DualParserNode instances. - for p in p_ddirs: - self.assertTrue(p in p_dirs) - for s in s_ddirs: - self.assertTrue(s in s_dirs) - def test_find_comments(self): dcoms = self.block.find_comments("comment") p_dcoms = [d.primary for d in dcoms] From 9b2322a573876841064251c1f1155b96e28da962 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Sat, 26 Oct 2019 00:54:28 +0300 Subject: [PATCH 010/122] Use dummy values for ancestor (#7462) --- certbot-apache/certbot_apache/augeasparser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py index 1bbf1ed77..ebbf4120f 100644 --- a/certbot-apache/certbot_apache/augeasparser.py +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -178,7 +178,7 @@ class AugeasBlockNode(AugeasDirectiveNode): # the complete ParserNode tree, we use the search parent as ancestor return AugeasDirectiveNode(name=name, parameters=params, - ancestor=self, + ancestor=assertions.PASS, filepath=apache_util.get_file_path(path), metadata=metadata) @@ -193,7 +193,7 @@ class AugeasBlockNode(AugeasDirectiveNode): # the complete ParserNode tree, we use the search parent as ancestor return AugeasBlockNode(name=name, parameters=params, - ancestor=self, + ancestor=assertions.PASS, filepath=apache_util.get_file_path(path), metadata=metadata) From d645574839c5791a131d24d90df2b86d2643d9d5 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Wed, 30 Oct 2019 20:03:23 +0200 Subject: [PATCH 011/122] [Apache v2] AugeasBlockNode find_comments() implementation (#7457) * find_comments implementation and AugeasCommentNode creation * Use dummy value for ancestor * Add NotImplementedError when calling find_comments with exact parameter * Remove parameter 'exact' from find_comments interface * Fix comment --- certbot-apache/certbot_apache/augeasparser.py | 35 +++++++++++++++---- certbot-apache/certbot_apache/dualparser.py | 7 ++-- certbot-apache/certbot_apache/interfaces.py | 5 ++- .../certbot_apache/tests/augeasnode_test.py | 9 +++++ .../certbot_apache/tests/dualnode_test.py | 11 ++++++ 5 files changed, 53 insertions(+), 14 deletions(-) diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py index ebbf4120f..04286ef4d 100644 --- a/certbot-apache/certbot_apache/augeasparser.py +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -151,13 +151,21 @@ class AugeasBlockNode(AugeasDirectiveNode): return nodes - def find_comments(self, comment, exact=False): # pylint: disable=unused-argument - """Recursive search of DirectiveNodes from the sequence of children""" - new_metadata = {"augeasparser": self.parser} - return [AugeasCommentNode(comment=assertions.PASS, - ancestor=self, - filepath=assertions.PASS, - metadata=new_metadata)] + def find_comments(self, comment): + """ + Recursive search of DirectiveNodes from the sequence of children. + + :param str comment: Comment content to search for. + """ + + nodes = list() + ownpath = self.metadata.get("augeaspath") + + comments = self.parser.find_comments(comment, start=ownpath) + for com in comments: + nodes.append(self._create_commentnode(com)) + + return nodes def delete_child(self, child): # pragma: no cover """Deletes a ParserNode from the sequence of children""" @@ -167,6 +175,19 @@ class AugeasBlockNode(AugeasDirectiveNode): """Returns a list of unsaved filepaths""" return [assertions.PASS] + def _create_commentnode(self, path): + """Helper function to create a CommentNode from Augeas path""" + + comment = self.parser.aug.get(path) + metadata = {"augeasparser": self.parser, "augeaspath": path} + + # Because of the dynamic nature of AugeasParser and the fact that we're + # not populating the complete node tree, the ancestor has a dummy value + return AugeasCommentNode(comment=comment, + ancestor=assertions.PASS, + filepath=apache_util.get_file_path(path), + metadata=metadata) + def _create_directivenode(self, path): """Helper function to create a DirectiveNode from Augeas path""" diff --git a/certbot-apache/certbot_apache/dualparser.py b/certbot-apache/certbot_apache/dualparser.py index d56e07de2..8897449b6 100644 --- a/certbot-apache/certbot_apache/dualparser.py +++ b/certbot-apache/certbot_apache/dualparser.py @@ -211,7 +211,7 @@ class DualBlockNode(DualNodeBase): return self._find_helper(DualDirectiveNode, "find_directives", name, exclude=exclude) - def find_comments(self, comment, exact=False): + def find_comments(self, comment): """ Performs a search for CommentNodes using both implementations and checks the results. This is built upon the assumption that unimplemented @@ -220,8 +220,7 @@ class DualBlockNode(DualNodeBase): instances that encapsulate the pairs of returned CommentNode objects. """ - return self._find_helper(DualCommentNode, "find_comments", comment, - exact=exact) + return self._find_helper(DualCommentNode, "find_comments", comment) def _find_helper(self, nodeclass, findfunc, search, **kwargs): """A helper for find_* functions. The function specific attributes should @@ -245,7 +244,7 @@ class DualBlockNode(DualNodeBase): if pass_primary and pass_secondary: # Both unimplemented new_nodes.append(nodeclass(primary=primary_res[0], - secondary=secondary_res[0])) + secondary=secondary_res[0])) # pragma: no cover elif pass_primary: for c in secondary_res: new_nodes.append(nodeclass(primary=primary_res[0], diff --git a/certbot-apache/certbot_apache/interfaces.py b/certbot-apache/certbot_apache/interfaces.py index cdfdfac91..ecad2d4eb 100644 --- a/certbot-apache/certbot_apache/interfaces.py +++ b/certbot-apache/certbot_apache/interfaces.py @@ -453,9 +453,9 @@ class BlockNode(DirectiveNode): """ @abc.abstractmethod - def find_comments(self, comment, exact=False): + def find_comments(self, comment): """ - Find comments with value containing or being exactly the same as search term. + Find comments with value containing the search term. This method walks the child tree of ParserNodes under the instance it was called from. This way it is possible to search for the whole configuration @@ -463,7 +463,6 @@ class BlockNode(DirectiveNode): from a specified branch. The lookup should be case sensitive. :param str comment: The content of comment to search for - :param bool exact: If the comment needs to exactly match the search term :returns: A list of found CommentNode objects. diff --git a/certbot-apache/certbot_apache/tests/augeasnode_test.py b/certbot-apache/certbot_apache/tests/augeasnode_test.py index c4631c57c..acb2d5329 100644 --- a/certbot-apache/certbot_apache/tests/augeasnode_test.py +++ b/certbot-apache/certbot_apache/tests/augeasnode_test.py @@ -63,3 +63,12 @@ class AugeasParserNodeTest(util.ApacheTest): self.assertEqual(servername[0].parameters[0], "certbot.demo") found = True self.assertTrue(found) + + def test_find_comments(self): + rootcomment = self.config.parser_root.find_comments( + "This is the main Apache server configuration file. " + ) + self.assertEqual(len(rootcomment), 1) + self.assertTrue(rootcomment[0].filepath.endswith( + "debian_apache_2_4/multiple_vhosts/apache2/apache2.conf" + )) diff --git a/certbot-apache/certbot_apache/tests/dualnode_test.py b/certbot-apache/certbot_apache/tests/dualnode_test.py index b37a7fdf9..3101a9c6f 100644 --- a/certbot-apache/certbot_apache/tests/dualnode_test.py +++ b/certbot-apache/certbot_apache/tests/dualnode_test.py @@ -161,6 +161,17 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- self.block.primary) def test_find_comments(self): + pri_comments = [augeasparser.AugeasCommentNode(comment="some comment", + ancestor=self.block, + filepath="/path/to/whatever")] + sec_comments = [augeasparser.AugeasCommentNode(comment=assertions.PASS, + ancestor=self.block, + filepath=assertions.PASS)] + find_coms_primary = mock.MagicMock(return_value=pri_comments) + find_coms_secondary = mock.MagicMock(return_value=sec_comments) + self.block.primary.find_comments = find_coms_primary + self.block.secondary.find_comments = find_coms_secondary + dcoms = self.block.find_comments("comment") p_dcoms = [d.primary for d in dcoms] s_dcoms = [d.secondary for d in dcoms] From 19de05c72fdb3f2825f2f2b7074f5d5d1010c536 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Wed, 6 Nov 2019 20:33:24 +0200 Subject: [PATCH 012/122] [Apache v2] Implement set_parameters() (#7461) * find_comments implementation and AugeasCommentNode creation * set_parameters implementation * Change parameters to a property * Remove parameters property setter * More pythonic iteration handling --- certbot-apache/certbot_apache/augeasparser.py | 60 +++++++---- certbot-apache/certbot_apache/configurator.py | 5 +- certbot-apache/certbot_apache/parser.py | 6 ++ .../certbot_apache/tests/augeasnode_test.py | 58 ++++++++++ .../certbot_apache/tests/dualnode_test.py | 101 +++++++++++------- 5 files changed, 169 insertions(+), 61 deletions(-) diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py index 04286ef4d..5f719f9a3 100644 --- a/certbot-apache/certbot_apache/augeasparser.py +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -52,8 +52,9 @@ class AugeasDirectiveNode(AugeasParserNode): name, parameters, enabled, kwargs = util.directivenode_kwargs(kwargs) super(AugeasDirectiveNode, self).__init__(**kwargs) self.name = name - self.parameters = parameters self.enabled = enabled + if parameters: + self.set_parameters(parameters) def __eq__(self, other): if isinstance(other, self.__class__): @@ -67,8 +68,39 @@ class AugeasDirectiveNode(AugeasParserNode): return False def set_parameters(self, parameters): - """Sets the parameters for DirectiveNode""" - self.parameters = parameters + """ + Sets parameters of a DirectiveNode or BlockNode object. + + :param list parameters: List of all parameters for the node to set. + """ + orig_params = self._aug_get_params(self.metadata["augeaspath"]) + + # Clear out old parameters + for _ in orig_params: + # When the first parameter is removed, the indices get updated + param_path = "{}/arg[1]".format(self.metadata["augeaspath"]) + self.parser.aug.remove(param_path) + # Insert new ones + for pi, param in enumerate(parameters): + param_path = "{}/arg[{}]".format(self.metadata["augeaspath"], pi+1) + self.parser.aug.set(param_path, param) + + @property + def parameters(self): + """ + Fetches the parameters from Augeas tree, ensuring that the sequence always + represents the current state + + :returns: Tuple of parameters for this DirectiveNode + :rtype: tuple: + """ + return tuple(self._aug_get_params(self.metadata["augeaspath"])) + + def _aug_get_params(self, path): + """Helper function to get parameters for DirectiveNodes and BlockNodes""" + + arg_paths = self.parser.aug.match(path + "/arg") + return [self.parser.get_arg(apath) for apath in arg_paths] class AugeasBlockNode(AugeasDirectiveNode): @@ -90,9 +122,10 @@ class AugeasBlockNode(AugeasDirectiveNode): self.metadata == other.metadata) return False - def add_child_block(self, name, parameters=None, position=None): # pylint: disable=unused-argument + # pylint: disable=unused-argument + def add_child_block(self, name, parameters=None, position=None): # pragma: no cover """Adds a new BlockNode to the sequence of children""" - new_metadata = {"augeasparser": self.parser} + new_metadata = {"augeasparser": self.parser, "augeaspath": assertions.PASS} new_block = AugeasBlockNode(name=assertions.PASS, ancestor=self, filepath=assertions.PASS, @@ -100,9 +133,10 @@ class AugeasBlockNode(AugeasDirectiveNode): self.children += (new_block,) return new_block - def add_child_directive(self, name, parameters=None, position=None): # pylint: disable=unused-argument + # pylint: disable=unused-argument + def add_child_directive(self, name, parameters=None, position=None): # pragma: no cover """Adds a new DirectiveNode to the sequence of children""" - new_metadata = {"augeasparser": self.parser} + new_metadata = {"augeasparser": self.parser, "augeaspath": assertions.PASS} new_dir = AugeasDirectiveNode(name=assertions.PASS, ancestor=self, filepath=assertions.PASS, @@ -112,7 +146,7 @@ class AugeasBlockNode(AugeasDirectiveNode): def add_child_comment(self, comment="", position=None): # pylint: disable=unused-argument """Adds a new CommentNode to the sequence of children""" - new_metadata = {"augeasparser": self.parser} + new_metadata = {"augeasparser": self.parser, "augeaspath": assertions.PASS} new_comment = AugeasCommentNode(comment=assertions.PASS, ancestor=self, filepath=assertions.PASS, @@ -192,13 +226,11 @@ class AugeasBlockNode(AugeasDirectiveNode): """Helper function to create a DirectiveNode from Augeas path""" name = self.parser.get_arg(path) - params = tuple(self._aug_get_params(path)) metadata = {"augeasparser": self.parser, "augeaspath": path} # Because of the dynamic nature, and the fact that we're not populating # the complete ParserNode tree, we use the search parent as ancestor return AugeasDirectiveNode(name=name, - parameters=params, ancestor=assertions.PASS, filepath=apache_util.get_file_path(path), metadata=metadata) @@ -207,13 +239,11 @@ class AugeasBlockNode(AugeasDirectiveNode): """Helper function to create a BlockNode from Augeas path""" name = self._aug_get_block_name(path) - params = tuple(self._aug_get_params(path)) metadata = {"augeasparser": self.parser, "augeaspath": path} # Because of the dynamic nature, and the fact that we're not populating # the complete ParserNode tree, we use the search parent as ancestor return AugeasBlockNode(name=name, - parameters=params, ancestor=assertions.PASS, filepath=apache_util.get_file_path(path), metadata=metadata) @@ -232,12 +262,6 @@ class AugeasBlockNode(AugeasDirectiveNode): name.lower() in os.path.basename(path).lower()]) return blk_paths - def _aug_get_params(self, path): - """Helper function to get parameters for BlockNodes""" - - arg_paths = self.parser.aug.match(path + "/arg") - return [self.parser.get_arg(apath) for apath in arg_paths] - def _aug_get_block_name(self, path): """Helper function to get name of a configuration block from path.""" diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index d01b0f5ce..808748006 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -257,7 +257,8 @@ class ApacheConfigurator(common.Installer): self.parser = self.get_parser() # Set up ParserNode root - pn_meta = {"augeasparser": self.parser} + pn_meta = {"augeasparser": self.parser, + "augeaspath": self.parser.get_root_augpath()} self.parser_root = self.get_parsernode_root(pn_meta) # Check for errors in parsing files with Augeas @@ -360,7 +361,7 @@ class ApacheConfigurator(common.Installer): return dualparser.DualBlockNode( name=assertions.PASS, ancestor=None, - filepath=assertions.PASS, + filepath=self.parser.loc["root"], metadata=metadata ) diff --git a/certbot-apache/certbot_apache/parser.py b/certbot-apache/certbot_apache/parser.py index 7ae9ac386..906b97e72 100644 --- a/certbot-apache/certbot_apache/parser.py +++ b/certbot-apache/certbot_apache/parser.py @@ -680,6 +680,12 @@ class ApacheParser(object): return value + def get_root_augpath(self): + """ + Returns the Augeas path of root configuration. + """ + return get_aug_path(self.loc["root"]) + def exclude_dirs(self, matches): """Exclude directives that are not loaded into the configuration.""" filters = [("ifmodule", self.modules), ("ifdefine", self.variables)] diff --git a/certbot-apache/certbot_apache/tests/augeasnode_test.py b/certbot-apache/certbot_apache/tests/augeasnode_test.py index acb2d5329..bbf9093d3 100644 --- a/certbot-apache/certbot_apache/tests/augeasnode_test.py +++ b/certbot-apache/certbot_apache/tests/augeasnode_test.py @@ -1,6 +1,8 @@ """Tests for AugeasParserNode classes""" import mock +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + from certbot_apache import assertions from certbot_apache.tests import util @@ -72,3 +74,59 @@ class AugeasParserNodeTest(util.ApacheTest): self.assertTrue(rootcomment[0].filepath.endswith( "debian_apache_2_4/multiple_vhosts/apache2/apache2.conf" )) + + def test_set_parameters(self): + servernames = self.config.parser_root.find_directives("servername") + names = [] # type: List[str] + for servername in servernames: + names += servername.parameters + self.assertFalse("going_to_set_this" in names) + servernames[0].set_parameters(["something", "going_to_set_this"]) + servernames = self.config.parser_root.find_directives("servername") + names = [] + for servername in servernames: + names += servername.parameters + self.assertTrue("going_to_set_this" in names) + + def test_set_parameters_atinit(self): + from certbot_apache.augeasparser import AugeasDirectiveNode + servernames = self.config.parser_root.find_directives("servername") + setparam = "certbot_apache.augeasparser.AugeasDirectiveNode.set_parameters" + with mock.patch(setparam) as mock_set: + AugeasDirectiveNode( + name=servernames[0].name, + parameters=["test", "setting", "these"], + ancestor=assertions.PASS, + metadata=servernames[0].metadata + ) + self.assertTrue(mock_set.called) + self.assertEqual( + mock_set.call_args_list[0][0][0], + ["test", "setting", "these"] + ) + + def test_set_parameters_delete(self): + # Set params + servername = self.config.parser_root.find_directives("servername")[0] + servername.set_parameters(["thisshouldnotexistpreviously", "another", + "third"]) + + # Delete params + servernames = self.config.parser_root.find_directives("servername") + found = False + for servername in servernames: + if "thisshouldnotexistpreviously" in servername.parameters: + self.assertEqual(len(servername.parameters), 3) + servername.set_parameters(["thisshouldnotexistpreviously"]) + found = True + self.assertTrue(found) + + # Verify params + servernames = self.config.parser_root.find_directives("servername") + found = False + for servername in servernames: + if "thisshouldnotexistpreviously" in servername.parameters: + self.assertEqual(len(servername.parameters), 1) + servername.set_parameters(["thisshouldnotexistpreviously"]) + found = True + self.assertTrue(found) diff --git a/certbot-apache/certbot_apache/tests/dualnode_test.py b/certbot-apache/certbot_apache/tests/dualnode_test.py index 3101a9c6f..5d6f827e7 100644 --- a/certbot-apache/certbot_apache/tests/dualnode_test.py +++ b/certbot-apache/certbot_apache/tests/dualnode_test.py @@ -13,23 +13,26 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- """DualParserNode tests""" def setUp(self): # pylint: disable=arguments-differ - metadata = {"augeasparser": mock.Mock()} + parser_mock = mock.MagicMock() + parser_mock.aug.match.return_value = [] + parser_mock.get_arg.return_value = [] + self.metadata = {"augeasparser": parser_mock, "augeaspath": "/invalid"} self.block = dualparser.DualBlockNode(name="block", ancestor=None, filepath="/tmp/something", - metadata=metadata) + metadata=self.metadata) self.block_two = dualparser.DualBlockNode(name="block", ancestor=self.block, filepath="/tmp/something", - metadata=metadata) + metadata=self.metadata) self.directive = dualparser.DualDirectiveNode(name="directive", ancestor=self.block, filepath="/tmp/something", - metadata=metadata) + metadata=self.metadata) self.comment = dualparser.DualCommentNode(comment="comment", ancestor=self.block, filepath="/tmp/something", - metadata=metadata) + metadata=self.metadata) def test_create_with_precreated(self): cnode = dualparser.DualCommentNode(comment="comment", @@ -57,9 +60,11 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- def test_set_params(self): params = ("first", "second") + self.directive.primary.set_parameters = mock.Mock() + self.directive.secondary.set_parameters = mock.Mock() self.directive.set_parameters(params) - self.assertEqual(self.directive.primary.parameters, params) - self.assertEqual(self.directive.secondary.parameters, params) + self.assertTrue(self.directive.primary.set_parameters.called) + self.assertTrue(self.directive.secondary.set_parameters.called) def test_set_parameters(self): pparams = mock.MagicMock() @@ -128,26 +133,22 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- assertions.assertEqual(self.comment.primary, self.comment.secondary) def test_add_child_block(self): - self.assertEqual(len(self.block.primary.children), 0) - self.assertEqual(len(self.block.secondary.children), 0) + mock_first = mock.MagicMock(return_value=self.block.primary) + mock_second = mock.MagicMock(return_value=self.block.secondary) + self.block.primary.add_child_block = mock_first + self.block.secondary.add_child_block = mock_second self.block.add_child_block("Block") - self.assertEqual(len(self.block.primary.children), 1) - self.assertEqual(len(self.block.secondary.children), 1) - self.assertTrue(isinstance(self.block.primary.children[0], - interfaces.BlockNode)) - self.assertEqual(self.block.primary.children[0].ancestor, - self.block.primary) + self.assertTrue(mock_first.called) + self.assertTrue(mock_second.called) def test_add_child_directive(self): - self.assertEqual(len(self.block.primary.children), 0) - self.assertEqual(len(self.block.secondary.children), 0) + mock_first = mock.MagicMock(return_value=self.directive.primary) + mock_second = mock.MagicMock(return_value=self.directive.secondary) + self.block.primary.add_child_directive = mock_first + self.block.secondary.add_child_directive = mock_second self.block.add_child_directive("Directive") - self.assertEqual(len(self.block.primary.children), 1) - self.assertEqual(len(self.block.secondary.children), 1) - self.assertTrue(isinstance(self.block.primary.children[0], - interfaces.DirectiveNode)) - self.assertEqual(self.block.primary.children[0].ancestor, - self.block.primary) + self.assertTrue(mock_first.called) + self.assertTrue(mock_second.called) def test_add_child_comment(self): self.assertEqual(len(self.block.primary.children), 0) @@ -187,10 +188,12 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- def test_find_blocks_first_passing(self): youshallnotpass = [augeasparser.AugeasBlockNode(name="notpassing", ancestor=self.block, - filepath="/path/to/whatever")] + filepath="/path/to/whatever", + metadata=self.metadata)] youshallpass = [augeasparser.AugeasBlockNode(name=assertions.PASS, ancestor=self.block, - filepath=assertions.PASS)] + filepath=assertions.PASS, + metadata=self.metadata)] find_blocks_primary = mock.MagicMock(return_value=youshallpass) find_blocks_secondary = mock.MagicMock(return_value=youshallnotpass) self.block.primary.find_blocks = find_blocks_primary @@ -208,10 +211,12 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- def test_find_blocks_second_passing(self): youshallnotpass = [augeasparser.AugeasBlockNode(name="notpassing", ancestor=self.block, - filepath="/path/to/whatever")] + filepath="/path/to/whatever", + metadata=self.metadata)] youshallpass = [augeasparser.AugeasBlockNode(name=assertions.PASS, ancestor=self.block, - filepath=assertions.PASS)] + filepath=assertions.PASS, + metadata=self.metadata)] find_blocks_primary = mock.MagicMock(return_value=youshallnotpass) find_blocks_secondary = mock.MagicMock(return_value=youshallpass) self.block.primary.find_blocks = find_blocks_primary @@ -229,10 +234,12 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- def test_find_dirs_first_passing(self): notpassing = [augeasparser.AugeasDirectiveNode(name="notpassing", ancestor=self.block, - filepath="/path/to/whatever")] + filepath="/path/to/whatever", + metadata=self.metadata)] passing = [augeasparser.AugeasDirectiveNode(name=assertions.PASS, ancestor=self.block, - filepath=assertions.PASS)] + filepath=assertions.PASS, + metadata=self.metadata)] find_dirs_primary = mock.MagicMock(return_value=passing) find_dirs_secondary = mock.MagicMock(return_value=notpassing) self.block.primary.find_directives = find_dirs_primary @@ -250,10 +257,12 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- def test_find_dirs_second_passing(self): notpassing = [augeasparser.AugeasDirectiveNode(name="notpassing", ancestor=self.block, - filepath="/path/to/whatever")] + filepath="/path/to/whatever", + metadata=self.metadata)] passing = [augeasparser.AugeasDirectiveNode(name=assertions.PASS, ancestor=self.block, - filepath=assertions.PASS)] + filepath=assertions.PASS, + metadata=self.metadata)] find_dirs_primary = mock.MagicMock(return_value=notpassing) find_dirs_secondary = mock.MagicMock(return_value=passing) self.block.primary.find_directives = find_dirs_primary @@ -271,10 +280,12 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- def test_find_coms_first_passing(self): notpassing = [augeasparser.AugeasCommentNode(comment="notpassing", ancestor=self.block, - filepath="/path/to/whatever")] + filepath="/path/to/whatever", + metadata=self.metadata)] passing = [augeasparser.AugeasCommentNode(comment=assertions.PASS, ancestor=self.block, - filepath=assertions.PASS)] + filepath=assertions.PASS, + metadata=self.metadata)] find_coms_primary = mock.MagicMock(return_value=passing) find_coms_secondary = mock.MagicMock(return_value=notpassing) self.block.primary.find_comments = find_coms_primary @@ -313,10 +324,12 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- def test_find_blocks_no_pass_equal(self): notpassing1 = [augeasparser.AugeasBlockNode(name="notpassing", ancestor=self.block, - filepath="/path/to/whatever")] + filepath="/path/to/whatever", + metadata=self.metadata)] notpassing2 = [augeasparser.AugeasBlockNode(name="notpassing", ancestor=self.block, - filepath="/path/to/whatever")] + filepath="/path/to/whatever", + metadata=self.metadata)] find_blocks_primary = mock.MagicMock(return_value=notpassing1) find_blocks_secondary = mock.MagicMock(return_value=notpassing2) self.block.primary.find_blocks = find_blocks_primary @@ -330,10 +343,12 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- def test_find_dirs_no_pass_equal(self): notpassing1 = [augeasparser.AugeasDirectiveNode(name="notpassing", ancestor=self.block, - filepath="/path/to/whatever")] + filepath="/path/to/whatever", + metadata=self.metadata)] notpassing2 = [augeasparser.AugeasDirectiveNode(name="notpassing", ancestor=self.block, - filepath="/path/to/whatever")] + filepath="/path/to/whatever", + metadata=self.metadata)] find_dirs_primary = mock.MagicMock(return_value=notpassing1) find_dirs_secondary = mock.MagicMock(return_value=notpassing2) self.block.primary.find_directives = find_dirs_primary @@ -347,10 +362,12 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- def test_find_comments_no_pass_equal(self): notpassing1 = [augeasparser.AugeasCommentNode(comment="notpassing", ancestor=self.block, - filepath="/path/to/whatever")] + filepath="/path/to/whatever", + metadata=self.metadata)] notpassing2 = [augeasparser.AugeasCommentNode(comment="notpassing", ancestor=self.block, - filepath="/path/to/whatever")] + filepath="/path/to/whatever", + metadata=self.metadata)] find_coms_primary = mock.MagicMock(return_value=notpassing1) find_coms_secondary = mock.MagicMock(return_value=notpassing2) self.block.primary.find_comments = find_coms_primary @@ -364,10 +381,12 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- def test_find_blocks_no_pass_notequal(self): notpassing1 = [augeasparser.AugeasBlockNode(name="notpassing", ancestor=self.block, - filepath="/path/to/whatever")] + filepath="/path/to/whatever", + metadata=self.metadata)] notpassing2 = [augeasparser.AugeasBlockNode(name="different", ancestor=self.block, - filepath="/path/to/whatever")] + filepath="/path/to/whatever", + metadata=self.metadata)] find_blocks_primary = mock.MagicMock(return_value=notpassing1) find_blocks_secondary = mock.MagicMock(return_value=notpassing2) self.block.primary.find_blocks = find_blocks_primary From 578ca1c6af600a53f68910667b9a6135b4b14e0c Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Mon, 11 Nov 2019 21:33:14 +0200 Subject: [PATCH 013/122] [Apache v2] Adding nodes 1/3 : add_child_block() (#7497) * Implement add_child_block() * Add comments and example * Check augas path inconsistencies in initialization --- certbot-apache/certbot_apache/augeasparser.py | 178 ++++++++++++++++-- .../certbot_apache/tests/augeasnode_test.py | 105 ++++++++++- .../certbot_apache/tests/configurator_test.py | 3 +- .../certbot_apache/tests/dualnode_test.py | 21 ++- 4 files changed, 286 insertions(+), 21 deletions(-) diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py index 5f719f9a3..e340519ce 100644 --- a/certbot-apache/certbot_apache/augeasparser.py +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -1,4 +1,72 @@ -""" Augeas implementation of the ParserNode interfaces """ +""" +Augeas implementation of the ParserNode interfaces. + +Augeas works internally by using XPATH notation. The following is a short example +of how this all works internally, to better understand what's going on under the +hood. + +A configuration file /etc/apache2/apache2.conf with the following content: + + # First comment line + # Second comment line + WhateverDirective whatevervalue + + DirectiveInABlock dirvalue + + SomeDirective somedirectivevalue + + AnotherDirectiveInABlock dirvalue + + # Yet another comment + + +Translates over to Augeas path notation (of immediate children), when calling +for example: aug.match("/files/etc/apache2/apache2.conf/*") + +[ + "/files/etc/apache2/apache2.conf/#comment[1]", + "/files/etc/apache2/apache2.conf/#comment[2]", + "/files/etc/apache2/apache2.conf/directive[1]", + "/files/etc/apache2/apache2.conf/ABlock[1]", + "/files/etc/apache2/apache2.conf/directive[2]", + "/files/etc/apache2/apache2.conf/ABlock[2]", + "/files/etc/apache2/apache2.conf/#comment[3]" +] + +Regardless of directives name, its key in the Augeas tree is always "directive", +with index where needed of course. Comments work similarly, while blocks +have their own key in the Augeas XPATH notation. + +It's important to note that all of the unique keys have their own indices. + +Augeas paths are case sensitive, while Apache configuration is case insensitive. +It looks like this: + + + directive value + + + Directive Value + + + directive value + + + DiReCtiVe VaLuE + + +Translates over to: + +[ + "/files/etc/apache2/apache2.conf/block[1]", + "/files/etc/apache2/apache2.conf/Block[1]", + "/files/etc/apache2/apache2.conf/block[2]", + "/files/etc/apache2/apache2.conf/bLoCk[1]", +] +""" +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from certbot import errors +from certbot.compat import os from certbot_apache import apache_util from certbot_apache import assertions @@ -6,8 +74,6 @@ from certbot_apache import interfaces from certbot_apache import parser from certbot_apache import parsernode_util as util -from certbot.compat import os -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module class AugeasParserNode(interfaces.ParserNode): @@ -21,6 +87,15 @@ class AugeasParserNode(interfaces.ParserNode): self.dirty = dirty self.metadata = metadata self.parser = self.metadata.get("augeasparser") + try: + if self.metadata["augeaspath"].endswith("/"): + raise errors.PluginError( + "Augeas path: {} has a trailing slash".format( + self.metadata["augeaspath"] + ) + ) + except KeyError: + raise errors.PluginError("Augeas path is required") def save(self, msg): # pragma: no cover pass @@ -125,12 +200,22 @@ class AugeasBlockNode(AugeasDirectiveNode): # pylint: disable=unused-argument def add_child_block(self, name, parameters=None, position=None): # pragma: no cover """Adds a new BlockNode to the sequence of children""" - new_metadata = {"augeasparser": self.parser, "augeaspath": assertions.PASS} - new_block = AugeasBlockNode(name=assertions.PASS, - ancestor=self, - filepath=assertions.PASS, + + insertpath, realpath, before = self._aug_resolve_child_position( + name, + position + ) + new_metadata = {"augeasparser": self.parser, "augeaspath": realpath} + + # Create the new block + self.parser.aug.insert(insertpath, name, before) + + # Parameters will be set at the initialization of the new object + new_block = AugeasBlockNode(name=name, + parameters=parameters, + ancestor=assertions.PASS, + filepath=apache_util.get_file_path(realpath), metadata=new_metadata) - self.children += (new_block,) return new_block # pylint: disable=unused-argument @@ -238,7 +323,7 @@ class AugeasBlockNode(AugeasDirectiveNode): def _create_blocknode(self, path): """Helper function to create a BlockNode from Augeas path""" - name = self._aug_get_block_name(path) + name = self._aug_get_name(path) metadata = {"augeasparser": self.parser, "augeaspath": path} # Because of the dynamic nature, and the fact that we're not populating @@ -262,8 +347,10 @@ class AugeasBlockNode(AugeasDirectiveNode): name.lower() in os.path.basename(path).lower()]) return blk_paths - def _aug_get_block_name(self, path): - """Helper function to get name of a configuration block from path.""" + def _aug_get_name(self, path): + """ + Helper function to get name of a configuration block or variable from path. + """ # Remove the ending slash if any if path[-1] == "/": # pragma: no cover @@ -277,6 +364,75 @@ class AugeasBlockNode(AugeasDirectiveNode): name = name.split("[")[0] return name + def _aug_resolve_child_position(self, name, position): + """ + Helper function that iterates through the immediate children and figures + out the insertion path for a new AugeasParserNode. + + Augeas also generalizes indices for directives and comments, simply by + using "directive" or "comment" respectively as their names. + + This function iterates over the existing children of the AugeasBlockNode, + returning their insertion path, resulting Augeas path and if the new node + should be inserted before or after the returned insertion path. + + Note: while Apache is case insensitive, Augeas is not, and blocks like + Nameofablock and NameOfABlock have different indices. + + :param str name: Name of the AugeasBlockNode to insert, "directive" for + AugeasDirectiveNode or "comment" for AugeasCommentNode + :param int position: The position to insert the child AugeasParserNode to + + :returns: Tuple of insert path, resulting path and a boolean if the new + node should be inserted before it. + :rtype: tuple of str, str, bool + """ + + # Default to appending + before = False + + all_children = self.parser.aug.match("{}/*".format( + self.metadata["augeaspath"]) + ) + + # Calculate resulting_path + # Augeas indices start at 1. We use counter to calculate the index to + # be used in resulting_path. + counter = 1 + for i, child in enumerate(all_children): + if position is not None and i >= position: + # We're not going to insert the new node to an index after this + break + childname = self._aug_get_name(child) + if name == childname: + counter += 1 + + resulting_path = "{}/{}[{}]".format( + self.metadata["augeaspath"], + name, + counter + ) + + # Form the correct insert_path + # Inserting the only child and appending as the last child work + # similarly in Augeas. + append = not all_children or position is None or position >= len(all_children) + if append: + insert_path = "{}/*[last()]".format( + self.metadata["augeaspath"] + ) + elif position == 0: + # Insert as the first child, before the current first one. + insert_path = all_children[0] + before = True + else: + insert_path = "{}/*[{}]".format( + self.metadata["augeaspath"], + position + ) + + return (insert_path, resulting_path, before) + interfaces.CommentNode.register(AugeasCommentNode) interfaces.DirectiveNode.register(AugeasDirectiveNode) diff --git a/certbot-apache/certbot_apache/tests/augeasnode_test.py b/certbot-apache/certbot_apache/tests/augeasnode_test.py index bbf9093d3..8846c3c07 100644 --- a/certbot-apache/certbot_apache/tests/augeasnode_test.py +++ b/certbot-apache/certbot_apache/tests/augeasnode_test.py @@ -2,6 +2,7 @@ import mock from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from certbot import errors from certbot_apache import assertions @@ -25,7 +26,7 @@ class AugeasParserNodeTest(util.ApacheTest): name=assertions.PASS, ancestor=None, filepath=assertions.PASS, - metadata={"augeasparser": mock.Mock()} + metadata={"augeasparser": mock.Mock(), "augeaspath": "/files/anything"} ) testcases = { "/some/path/FirstNode/SecondNode": "SecondNode", @@ -35,7 +36,7 @@ class AugeasParserNodeTest(util.ApacheTest): "/Anything": "Anything", } for test in testcases: - self.assertEqual(block._aug_get_block_name(test), testcases[test]) # pylint: disable=protected-access + self.assertEqual(block._aug_get_name(test), testcases[test]) # pylint: disable=protected-access def test_find_blocks(self): blocks = self.config.parser_root.find_blocks("VirtualHost", exclude=False) @@ -130,3 +131,103 @@ class AugeasParserNodeTest(util.ApacheTest): servername.set_parameters(["thisshouldnotexistpreviously"]) found = True self.assertTrue(found) + + def test_add_child_block(self): + nb = self.config.parser_root.primary.add_child_block( + "NewBlock", + ["first", "second"] + ) + rpath, _, directive = nb.metadata["augeaspath"].rpartition("/") + self.assertEqual( + rpath, + self.config.parser_root.primary.metadata["augeaspath"] + ) + self.assertTrue(directive.startswith("NewBlock")) + + def test_add_child_block_beginning(self): + self.config.parser_root.primary.add_child_block( + "Beginning", + position=0 + ) + parser = self.config.parser_root.primary.parser + root_path = self.config.parser_root.primary.metadata["augeaspath"] + # Get first child + first = parser.aug.match("{}/*[1]".format(root_path)) + self.assertTrue(first[0].endswith("Beginning")) + + def test_add_child_block_append(self): + self.config.parser_root.primary.add_child_block( + "VeryLast", + ) + parser = self.config.parser_root.primary.parser + root_path = self.config.parser_root.primary.metadata["augeaspath"] + # Get last child + last = parser.aug.match("{}/*[last()]".format(root_path)) + self.assertTrue(last[0].endswith("VeryLast")) + + def test_add_child_block_append_alt(self): + self.config.parser_root.primary.add_child_block( + "VeryLastAlt", + position=99999 + ) + parser = self.config.parser_root.primary.parser + root_path = self.config.parser_root.primary.metadata["augeaspath"] + # Get last child + last = parser.aug.match("{}/*[last()]".format(root_path)) + self.assertTrue(last[0].endswith("VeryLastAlt")) + + def test_add_child_block_middle(self): + self.config.parser_root.primary.add_child_block( + "Middle", + position=5 + ) + parser = self.config.parser_root.primary.parser + root_path = self.config.parser_root.primary.metadata["augeaspath"] + # Augeas indices start at 1 :( + middle = parser.aug.match("{}/*[6]".format(root_path)) + self.assertTrue(middle[0].endswith("Middle")) + + def test_add_child_block_existing_name(self): + parser = self.config.parser_root.primary.parser + root_path = self.config.parser_root.primary.metadata["augeaspath"] + # There already exists a single VirtualHost in the base config + new_block = parser.aug.match("{}/VirtualHost[2]".format(root_path)) + self.assertEqual(len(new_block), 0) + vh = self.config.parser_root.primary.add_child_block( + "VirtualHost", + ) + new_block = parser.aug.match("{}/VirtualHost[2]".format(root_path)) + self.assertEqual(len(new_block), 1) + self.assertTrue(vh.metadata["augeaspath"].endswith("VirtualHost[2]")) + + def test_node_init_error_bad_augeaspath(self): + from certbot_apache.augeasparser import AugeasBlockNode + parameters = { + "name": assertions.PASS, + "ancestor": None, + "filepath": assertions.PASS, + "metadata": { + "augeasparser": mock.Mock(), + "augeaspath": "/files/path/endswith/slash/" + } + } + self.assertRaises( + errors.PluginError, + AugeasBlockNode, + **parameters + ) + def test_node_init_error_missing_augeaspath(self): + from certbot_apache.augeasparser import AugeasBlockNode + parameters = { + "name": assertions.PASS, + "ancestor": None, + "filepath": assertions.PASS, + "metadata": { + "augeasparser": mock.Mock(), + } + } + self.assertRaises( + errors.PluginError, + AugeasBlockNode, + **parameters + ) diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index 1eafae982..68d9c90fa 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -78,7 +78,8 @@ class MultipleVhostsTest(util.ApacheTest): @mock.patch("certbot_apache.parser.ApacheParser") @mock.patch("certbot_apache.configurator.util.exe_exists") - def _test_prepare_locked(self, unused_parser, unused_exe_exists): + @mock.patch("certbot_apache.configurator.ApacheConfigurator.get_parsernode_root") + def _test_prepare_locked(self, _node, _exists, _parser): try: self.config.prepare() except errors.PluginError as err: diff --git a/certbot-apache/certbot_apache/tests/dualnode_test.py b/certbot-apache/certbot_apache/tests/dualnode_test.py index 5d6f827e7..dbce18431 100644 --- a/certbot-apache/certbot_apache/tests/dualnode_test.py +++ b/certbot-apache/certbot_apache/tests/dualnode_test.py @@ -164,10 +164,12 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- def test_find_comments(self): pri_comments = [augeasparser.AugeasCommentNode(comment="some comment", ancestor=self.block, - filepath="/path/to/whatever")] + filepath="/path/to/whatever", + metadata=self.metadata)] sec_comments = [augeasparser.AugeasCommentNode(comment=assertions.PASS, ancestor=self.block, - filepath=assertions.PASS)] + filepath=assertions.PASS, + metadata=self.metadata)] find_coms_primary = mock.MagicMock(return_value=pri_comments) find_coms_secondary = mock.MagicMock(return_value=sec_comments) self.block.primary.find_comments = find_coms_primary @@ -303,10 +305,12 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- def test_find_coms_second_passing(self): notpassing = [augeasparser.AugeasCommentNode(comment="notpassing", ancestor=self.block, - filepath="/path/to/whatever")] + filepath="/path/to/whatever", + metadata=self.metadata)] passing = [augeasparser.AugeasCommentNode(comment=assertions.PASS, ancestor=self.block, - filepath=assertions.PASS)] + filepath=assertions.PASS, + metadata=self.metadata)] find_coms_primary = mock.MagicMock(return_value=notpassing) find_coms_secondary = mock.MagicMock(return_value=passing) self.block.primary.find_comments = find_coms_primary @@ -398,13 +402,16 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- def test_parsernode_notequal(self): ne_block = augeasparser.AugeasBlockNode(name="different", ancestor=self.block, - filepath="/path/to/whatever") + filepath="/path/to/whatever", + metadata=self.metadata) ne_directive = augeasparser.AugeasDirectiveNode(name="different", ancestor=self.block, - filepath="/path/to/whatever") + filepath="/path/to/whatever", + metadata=self.metadata) ne_comment = augeasparser.AugeasCommentNode(comment="different", ancestor=self.block, - filepath="/path/to/whatever") + filepath="/path/to/whatever", + metadata=self.metadata) self.assertFalse(self.block == ne_block) self.assertFalse(self.directive == ne_directive) self.assertFalse(self.comment == ne_comment) From 2bc64183a8fe963959e91bfd87fb8f0e64b17650 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 11 Nov 2019 17:11:47 -0800 Subject: [PATCH 014/122] fix docstring --- certbot/plugins/common_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot/plugins/common_test.py b/certbot/plugins/common_test.py index 977500f86..5d529e993 100644 --- a/certbot/plugins/common_test.py +++ b/certbot/plugins/common_test.py @@ -178,7 +178,7 @@ class InstallerTest(test_util.ConfigTestCase): class AddrTest(unittest.TestCase): - """Tests for certbot._internal.client.plugins.common.Addr.""" + """Tests for certbot._internal.plugins.common.Addr.""" def setUp(self): from certbot.plugins.common import Addr From bdf24d2bed1976ae5f8938d46709b1cdef424b4f Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Wed, 13 Nov 2019 00:19:02 +0200 Subject: [PATCH 015/122] Implement add_child_directive (#7517) --- certbot-apache/certbot_apache/augeasparser.py | 24 +++++++++++++++---- .../certbot_apache/tests/augeasnode_test.py | 22 ++++++++++++++++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py index e340519ce..5fd5247c1 100644 --- a/certbot-apache/certbot_apache/augeasparser.py +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -221,12 +221,26 @@ class AugeasBlockNode(AugeasDirectiveNode): # pylint: disable=unused-argument def add_child_directive(self, name, parameters=None, position=None): # pragma: no cover """Adds a new DirectiveNode to the sequence of children""" - new_metadata = {"augeasparser": self.parser, "augeaspath": assertions.PASS} - new_dir = AugeasDirectiveNode(name=assertions.PASS, - ancestor=self, - filepath=assertions.PASS, + + if not parameters: + raise errors.PluginError("Directive requires parameters and none were set.") + + insertpath, realpath, before = self._aug_resolve_child_position( + "directive", + position + ) + new_metadata = {"augeasparser": self.parser, "augeaspath": realpath} + + # Create the new directive + self.parser.aug.insert(insertpath, "directive", before) + # Set the directive key + self.parser.aug.set(realpath, name) + + new_dir = AugeasDirectiveNode(name=name, + parameters=parameters, + ancestor=assertions.PASS, + filepath=apache_util.get_file_path(realpath), metadata=new_metadata) - self.children += (new_dir,) return new_dir def add_child_comment(self, comment="", position=None): # pylint: disable=unused-argument diff --git a/certbot-apache/certbot_apache/tests/augeasnode_test.py b/certbot-apache/certbot_apache/tests/augeasnode_test.py index 8846c3c07..76f46c14d 100644 --- a/certbot-apache/certbot_apache/tests/augeasnode_test.py +++ b/certbot-apache/certbot_apache/tests/augeasnode_test.py @@ -9,7 +9,7 @@ from certbot_apache import assertions from certbot_apache.tests import util -class AugeasParserNodeTest(util.ApacheTest): +class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-methods """Test AugeasParserNode using available test configurations""" def setUp(self): # pylint: disable=arguments-differ @@ -216,6 +216,7 @@ class AugeasParserNodeTest(util.ApacheTest): AugeasBlockNode, **parameters ) + def test_node_init_error_missing_augeaspath(self): from certbot_apache.augeasparser import AugeasBlockNode parameters = { @@ -231,3 +232,22 @@ class AugeasParserNodeTest(util.ApacheTest): AugeasBlockNode, **parameters ) + + def test_add_child_directive(self): + self.config.parser_root.primary.add_child_directive( + "ThisWasAdded", + ["with", "parameters"], + position=0 + ) + dirs = self.config.parser_root.find_directives("ThisWasAdded") + self.assertEqual(len(dirs), 1) + self.assertEqual(dirs[0].parameters, ("with", "parameters")) + # The new directive was added to the very first line of the config + self.assertTrue(dirs[0].metadata["augeaspath"].endswith("[1]")) + + def test_add_child_directive_exception(self): + self.assertRaises( + errors.PluginError, + self.config.parser_root.primary.add_child_directive, + "ThisRaisesErrorBecauseMissingParameters" + ) From d14eec9ecf89da33dacb4fbac618a226e54526b0 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Wed, 13 Nov 2019 00:19:21 +0200 Subject: [PATCH 016/122] [Apache v2] Implement save() and unsaved_files() (#7520) * Implement save() and unsaved_files() * Linter fix --- certbot-apache/certbot_apache/augeasparser.py | 8 ++++---- .../certbot_apache/tests/augeasnode_test.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py index 5fd5247c1..a1d72fe52 100644 --- a/certbot-apache/certbot_apache/augeasparser.py +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -97,8 +97,8 @@ class AugeasParserNode(interfaces.ParserNode): except KeyError: raise errors.PluginError("Augeas path is required") - def save(self, msg): # pragma: no cover - pass + def save(self, msg): + self.parser.save(msg) class AugeasCommentNode(AugeasParserNode): @@ -304,9 +304,9 @@ class AugeasBlockNode(AugeasDirectiveNode): """Deletes a ParserNode from the sequence of children""" pass - def unsaved_files(self): # pragma: no cover + def unsaved_files(self): """Returns a list of unsaved filepaths""" - return [assertions.PASS] + return self.parser.unsaved_files() def _create_commentnode(self, path): """Helper function to create a CommentNode from Augeas path""" diff --git a/certbot-apache/certbot_apache/tests/augeasnode_test.py b/certbot-apache/certbot_apache/tests/augeasnode_test.py index 76f46c14d..39736ee17 100644 --- a/certbot-apache/certbot_apache/tests/augeasnode_test.py +++ b/certbot-apache/certbot_apache/tests/augeasnode_test.py @@ -20,6 +20,18 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- self.vh_truth = util.get_vh_truth( self.temp_dir, "debian_apache_2_4/multiple_vhosts") + def test_save(self): + with mock.patch('certbot_apache.parser.ApacheParser.save') as mock_save: + self.config.parser_root.save("A save message") + self.assertTrue(mock_save.called) + self.assertEqual(mock_save.call_args[0][0], "A save message") + + def test_unsaved_files(self): + with mock.patch('certbot_apache.parser.ApacheParser.unsaved_files') as mock_uf: + mock_uf.return_value = ["first", "second"] + files = self.config.parser_root.unsaved_files() + self.assertEqual(files, ["first", "second"]) + def test_get_block_node_name(self): from certbot_apache.augeasparser import AugeasBlockNode block = AugeasBlockNode( From 517ff5cb1930e21b1de0d8c5343cb99c622e321f Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Wed, 13 Nov 2019 00:19:35 +0200 Subject: [PATCH 017/122] [Apache v2] Implement delete_child() (#7521) * Implement delete_child * Fix linter --- certbot-apache/certbot_apache/augeasparser.py | 15 ++++++++++++--- .../certbot_apache/tests/augeasnode_test.py | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py index a1d72fe52..47b8602ab 100644 --- a/certbot-apache/certbot_apache/augeasparser.py +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -300,9 +300,18 @@ class AugeasBlockNode(AugeasDirectiveNode): return nodes - def delete_child(self, child): # pragma: no cover - """Deletes a ParserNode from the sequence of children""" - pass + def delete_child(self, child): + """ + Deletes a ParserNode from the sequence of children, and raises an + exception if it's unable to do so. + :param AugeasParserNode: child: A node to delete. + """ + if not self.parser.aug.remove(child.metadata["augeaspath"]): + + raise errors.PluginError( + ("Could not delete child node, the Augeas path: {} doesn't " + + "seem to exist.").format(child.metadata["augeaspath"]) + ) def unsaved_files(self): """Returns a list of unsaved filepaths""" diff --git a/certbot-apache/certbot_apache/tests/augeasnode_test.py b/certbot-apache/certbot_apache/tests/augeasnode_test.py index 39736ee17..a86b618b2 100644 --- a/certbot-apache/certbot_apache/tests/augeasnode_test.py +++ b/certbot-apache/certbot_apache/tests/augeasnode_test.py @@ -144,6 +144,24 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- found = True self.assertTrue(found) + def test_delete_child(self): + listens = self.config.parser_root.find_directives("Listen") + self.assertEqual(len(listens), 1) + self.config.parser_root.primary.delete_child(listens[0]) + + listens = self.config.parser_root.find_directives("Listen") + self.assertEqual(len(listens), 0) + + def test_delete_child_not_found(self): + listen = self.config.parser_root.find_directives("Listen")[0] + listen.primary.metadata["augeaspath"] = "/files/something/nonexistent" + + self.assertRaises( + errors.PluginError, + self.config.parser_root.delete_child, + listen + ) + def test_add_child_block(self): nb = self.config.parser_root.primary.add_child_block( "NewBlock", From b70f9c474481efd8850fc83622ccb6f4312f3021 Mon Sep 17 00:00:00 2001 From: sydneyli Date: Fri, 15 Nov 2019 02:22:18 -0800 Subject: [PATCH 018/122] [Apache v2] Initial ApacheParser skeleton (#7559) * Fix metadata & primary references in Augeas tests. When performing actions only on one of the trees in DualNodeParser, the two trees get out-of-sync. Similarly, we can't expect that the metadata between the two trees will remain the same. Did a pass over the tests to re-wire metadata and primary usage. * Add ApacheParser skeleton. Fix plumbing in configurator & dualparser to initialize ApacheParser alongside AugeasParser. * Silence coverage reports for now --- certbot-apache/certbot_apache/apacheparser.py | 157 ++++++++++++++++++ certbot-apache/certbot_apache/configurator.py | 3 +- certbot-apache/certbot_apache/dualparser.py | 7 +- .../certbot_apache/tests/augeasnode_test.py | 28 ++-- .../certbot_apache/tests/dualnode_test.py | 2 +- 5 files changed, 178 insertions(+), 19 deletions(-) create mode 100644 certbot-apache/certbot_apache/apacheparser.py diff --git a/certbot-apache/certbot_apache/apacheparser.py b/certbot-apache/certbot_apache/apacheparser.py new file mode 100644 index 000000000..6625735b4 --- /dev/null +++ b/certbot-apache/certbot_apache/apacheparser.py @@ -0,0 +1,157 @@ +""" apacheconfig implementation of the ParserNode interfaces """ + +from certbot_apache import assertions +from certbot_apache import interfaces +from certbot_apache import parsernode_util as util + + +class ApacheParserNode(interfaces.ParserNode): + """ apacheconfig implementation of ParserNode interface. + + Expects metadata `ac_ast` to be passed in, where `ac_ast` is the AST provided + by parsing the equivalent configuration text using the apacheconfig library. + """ + + def __init__(self, **kwargs): + ancestor, dirty, filepath, metadata = util.parsernode_kwargs(kwargs) # pylint: disable=unused-variable + super(ApacheParserNode, self).__init__(**kwargs) + self.ancestor = ancestor + self.filepath = filepath + self.dirty = dirty + self.metadata = metadata + self._raw = self.metadata["ac_ast"] + + def save(self, msg): # pragma: no cover + pass + + +class ApacheCommentNode(ApacheParserNode): + """ apacheconfig implementation of CommentNode interface """ + + def __init__(self, **kwargs): + comment, kwargs = util.commentnode_kwargs(kwargs) # pylint: disable=unused-variable + super(ApacheCommentNode, self).__init__(**kwargs) + self.comment = comment + + def __eq__(self, other): # pragma: no cover + if isinstance(other, self.__class__): + return (self.comment == other.comment and + self.dirty == other.dirty and + self.ancestor == other.ancestor and + self.metadata == other.metadata and + self.filepath == other.filepath) + return False + + +class ApacheDirectiveNode(ApacheParserNode): + """ apacheconfig implementation of DirectiveNode interface """ + + def __init__(self, **kwargs): + name, parameters, enabled, kwargs = util.directivenode_kwargs(kwargs) + super(ApacheDirectiveNode, self).__init__(**kwargs) + self.name = name + self.parameters = parameters + self.enabled = enabled + self.include = None + + def __eq__(self, other): # pragma: no cover + if isinstance(other, self.__class__): + return (self.name == other.name and + self.filepath == other.filepath and + self.parameters == other.parameters and + self.enabled == other.enabled and + self.dirty == other.dirty and + self.ancestor == other.ancestor and + self.metadata == other.metadata) + return False + + def set_parameters(self, parameters): + """Sets the parameters for DirectiveNode""" + pass + + +class ApacheBlockNode(ApacheDirectiveNode): + """ apacheconfig implementation of BlockNode interface """ + + def __init__(self, **kwargs): + super(ApacheBlockNode, self).__init__(**kwargs) + self.children = () + + def __eq__(self, other): # pragma: no cover + if isinstance(other, self.__class__): + return (self.name == other.name and + self.filepath == other.filepath and + self.parameters == other.parameters and + self.children == other.children and + self.enabled == other.enabled and + self.dirty == other.dirty and + self.ancestor == other.ancestor and + self.metadata == other.metadata) + return False + + def add_child_block(self, name, parameters=None, position=None): # pylint: disable=unused-argument + """Adds a new BlockNode to the sequence of children""" + new_block = ApacheBlockNode(name=assertions.PASS, + parameters=assertions.PASS, + ancestor=self, + filepath=assertions.PASS, + metadata=self.metadata) + self.children += (new_block,) + return new_block + + def add_child_directive(self, name, parameters=None, position=None): # pylint: disable=unused-argument + """Adds a new DirectiveNode to the sequence of children""" + new_dir = ApacheDirectiveNode(name=assertions.PASS, + parameters=assertions.PASS, + ancestor=self, + filepath=assertions.PASS, + metadata=self.metadata) + self.children += (new_dir,) + return new_dir + + # pylint: disable=unused-argument + def add_child_comment(self, comment="", position=None): # pragma: no cover + + """Adds a new CommentNode to the sequence of children""" + new_comment = ApacheCommentNode(comment=assertions.PASS, + ancestor=self, + filepath=assertions.PASS, + metadata=self.metadata) + self.children += (new_comment,) + return new_comment + + def find_blocks(self, name, exclude=True): # pylint: disable=unused-argument + """Recursive search of BlockNodes from the sequence of children""" + return [ApacheBlockNode(name=assertions.PASS, + parameters=assertions.PASS, + ancestor=self, + filepath=assertions.PASS, + metadata=self.metadata)] + + def find_directives(self, name, exclude=True): # pylint: disable=unused-argument + """Recursive search of DirectiveNodes from the sequence of children""" + return [ApacheDirectiveNode(name=assertions.PASS, + parameters=assertions.PASS, + ancestor=self, + filepath=assertions.PASS, + metadata=self.metadata)] + + def find_comments(self, comment, exact=False): # pylint: disable=unused-argument + """Recursive search of DirectiveNodes from the sequence of children""" + return [ApacheCommentNode(comment=assertions.PASS, + ancestor=self, + filepath=assertions.PASS, + metadata=self.metadata)] + + def delete_child(self, child): # pragma: no cover + """Deletes a ParserNode from the sequence of children""" + pass + + def unsaved_files(self): # pragma: no cover + """Returns a list of unsaved filepaths""" + return [assertions.PASS] + + +interfaces.CommentNode.register(ApacheCommentNode) +interfaces.DirectiveNode.register(ApacheDirectiveNode) +interfaces.BlockNode.register(ApacheBlockNode) diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index 808748006..2b20ebb7c 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -258,7 +258,8 @@ class ApacheConfigurator(common.Installer): # Set up ParserNode root pn_meta = {"augeasparser": self.parser, - "augeaspath": self.parser.get_root_augpath()} + "augeaspath": self.parser.get_root_augpath(), + "ac_ast": None} self.parser_root = self.get_parsernode_root(pn_meta) # Check for errors in parsing files with Augeas diff --git a/certbot-apache/certbot_apache/dualparser.py b/certbot-apache/certbot_apache/dualparser.py index 8897449b6..ef8e86196 100644 --- a/certbot-apache/certbot_apache/dualparser.py +++ b/certbot-apache/certbot_apache/dualparser.py @@ -1,6 +1,7 @@ """ Dual ParserNode implementation """ from certbot_apache import assertions from certbot_apache import augeasparser +from certbot_apache import apacheparser class DualNodeBase(object): @@ -49,7 +50,7 @@ class DualCommentNode(DualNodeBase): self.secondary = secondary else: self.primary = augeasparser.AugeasCommentNode(**kwargs) - self.secondary = augeasparser.AugeasCommentNode(**kwargs) + self.secondary = apacheparser.ApacheCommentNode(**kwargs) assertions.assertEqual(self.primary, self.secondary) @@ -83,7 +84,7 @@ class DualDirectiveNode(DualNodeBase): self.secondary = secondary else: self.primary = augeasparser.AugeasDirectiveNode(**kwargs) - self.secondary = augeasparser.AugeasDirectiveNode(**kwargs) + self.secondary = apacheparser.ApacheDirectiveNode(**kwargs) assertions.assertEqual(self.primary, self.secondary) @@ -123,7 +124,7 @@ class DualBlockNode(DualNodeBase): self.secondary = secondary else: self.primary = augeasparser.AugeasBlockNode(**kwargs) - self.secondary = augeasparser.AugeasBlockNode(**kwargs) + self.secondary = apacheparser.ApacheBlockNode(**kwargs) assertions.assertEqual(self.primary, self.secondary) diff --git a/certbot-apache/certbot_apache/tests/augeasnode_test.py b/certbot-apache/certbot_apache/tests/augeasnode_test.py index a86b618b2..2bf21408f 100644 --- a/certbot-apache/certbot_apache/tests/augeasnode_test.py +++ b/certbot-apache/certbot_apache/tests/augeasnode_test.py @@ -110,7 +110,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- name=servernames[0].name, parameters=["test", "setting", "these"], ancestor=assertions.PASS, - metadata=servernames[0].metadata + metadata=servernames[0].primary.metadata ) self.assertTrue(mock_set.called) self.assertEqual( @@ -145,11 +145,11 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- self.assertTrue(found) def test_delete_child(self): - listens = self.config.parser_root.find_directives("Listen") + listens = self.config.parser_root.primary.find_directives("Listen") self.assertEqual(len(listens), 1) self.config.parser_root.primary.delete_child(listens[0]) - listens = self.config.parser_root.find_directives("Listen") + listens = self.config.parser_root.primary.find_directives("Listen") self.assertEqual(len(listens), 0) def test_delete_child_not_found(self): @@ -163,11 +163,11 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- ) def test_add_child_block(self): - nb = self.config.parser_root.primary.add_child_block( + nb = self.config.parser_root.add_child_block( "NewBlock", ["first", "second"] ) - rpath, _, directive = nb.metadata["augeaspath"].rpartition("/") + rpath, _, directive = nb.primary.metadata["augeaspath"].rpartition("/") self.assertEqual( rpath, self.config.parser_root.primary.metadata["augeaspath"] @@ -175,7 +175,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- self.assertTrue(directive.startswith("NewBlock")) def test_add_child_block_beginning(self): - self.config.parser_root.primary.add_child_block( + self.config.parser_root.add_child_block( "Beginning", position=0 ) @@ -186,7 +186,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- self.assertTrue(first[0].endswith("Beginning")) def test_add_child_block_append(self): - self.config.parser_root.primary.add_child_block( + self.config.parser_root.add_child_block( "VeryLast", ) parser = self.config.parser_root.primary.parser @@ -196,7 +196,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- self.assertTrue(last[0].endswith("VeryLast")) def test_add_child_block_append_alt(self): - self.config.parser_root.primary.add_child_block( + self.config.parser_root.add_child_block( "VeryLastAlt", position=99999 ) @@ -207,7 +207,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- self.assertTrue(last[0].endswith("VeryLastAlt")) def test_add_child_block_middle(self): - self.config.parser_root.primary.add_child_block( + self.config.parser_root.add_child_block( "Middle", position=5 ) @@ -223,12 +223,12 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- # There already exists a single VirtualHost in the base config new_block = parser.aug.match("{}/VirtualHost[2]".format(root_path)) self.assertEqual(len(new_block), 0) - vh = self.config.parser_root.primary.add_child_block( + vh = self.config.parser_root.add_child_block( "VirtualHost", ) new_block = parser.aug.match("{}/VirtualHost[2]".format(root_path)) self.assertEqual(len(new_block), 1) - self.assertTrue(vh.metadata["augeaspath"].endswith("VirtualHost[2]")) + self.assertTrue(vh.primary.metadata["augeaspath"].endswith("VirtualHost[2]")) def test_node_init_error_bad_augeaspath(self): from certbot_apache.augeasparser import AugeasBlockNode @@ -264,7 +264,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- ) def test_add_child_directive(self): - self.config.parser_root.primary.add_child_directive( + self.config.parser_root.add_child_directive( "ThisWasAdded", ["with", "parameters"], position=0 @@ -273,11 +273,11 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- self.assertEqual(len(dirs), 1) self.assertEqual(dirs[0].parameters, ("with", "parameters")) # The new directive was added to the very first line of the config - self.assertTrue(dirs[0].metadata["augeaspath"].endswith("[1]")) + self.assertTrue(dirs[0].primary.metadata["augeaspath"].endswith("[1]")) def test_add_child_directive_exception(self): self.assertRaises( errors.PluginError, - self.config.parser_root.primary.add_child_directive, + self.config.parser_root.add_child_directive, "ThisRaisesErrorBecauseMissingParameters" ) diff --git a/certbot-apache/certbot_apache/tests/dualnode_test.py b/certbot-apache/certbot_apache/tests/dualnode_test.py index dbce18431..2878a693e 100644 --- a/certbot-apache/certbot_apache/tests/dualnode_test.py +++ b/certbot-apache/certbot_apache/tests/dualnode_test.py @@ -16,7 +16,7 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- parser_mock = mock.MagicMock() parser_mock.aug.match.return_value = [] parser_mock.get_arg.return_value = [] - self.metadata = {"augeasparser": parser_mock, "augeaspath": "/invalid"} + self.metadata = {"augeasparser": parser_mock, "augeaspath": "/invalid", "ac_ast": None} self.block = dualparser.DualBlockNode(name="block", ancestor=None, filepath="/tmp/something", From ac1a60ff0b98251930d9f3a8338127c8a0513ffa Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Mon, 18 Nov 2019 21:21:51 +0200 Subject: [PATCH 019/122] Implement add_child_comment (#7518) --- certbot-apache/certbot_apache/augeasparser.py | 22 ++++++++++++++----- .../certbot_apache/tests/augeasnode_test.py | 10 +++++++++ .../certbot_apache/tests/dualnode_test.py | 15 +++++-------- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py index 47b8602ab..3956b1d16 100644 --- a/certbot-apache/certbot_apache/augeasparser.py +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -243,14 +243,24 @@ class AugeasBlockNode(AugeasDirectiveNode): metadata=new_metadata) return new_dir - def add_child_comment(self, comment="", position=None): # pylint: disable=unused-argument + def add_child_comment(self, comment="", position=None): """Adds a new CommentNode to the sequence of children""" - new_metadata = {"augeasparser": self.parser, "augeaspath": assertions.PASS} - new_comment = AugeasCommentNode(comment=assertions.PASS, - ancestor=self, - filepath=assertions.PASS, + + insertpath, realpath, before = self._aug_resolve_child_position( + "#comment", + position + ) + new_metadata = {"augeasparser": self.parser, "augeaspath": realpath} + + # Create the new comment + self.parser.aug.insert(insertpath, "#comment", before) + # Set the comment content + self.parser.aug.set(realpath, comment) + + new_comment = AugeasCommentNode(comment=comment, + ancestor=assertions.PASS, + filepath=apache_util.get_file_path(realpath), metadata=new_metadata) - self.children += (new_comment,) return new_comment def find_blocks(self, name, exclude=True): # pylint: disable=unused-argument diff --git a/certbot-apache/certbot_apache/tests/augeasnode_test.py b/certbot-apache/certbot_apache/tests/augeasnode_test.py index 2bf21408f..bf01f9fac 100644 --- a/certbot-apache/certbot_apache/tests/augeasnode_test.py +++ b/certbot-apache/certbot_apache/tests/augeasnode_test.py @@ -144,6 +144,16 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- found = True self.assertTrue(found) + def test_add_child_comment(self): + newc = self.config.parser_root.primary.add_child_comment("The content") + comments = self.config.parser_root.find_comments("The content") + self.assertEqual(len(comments), 1) + self.assertEqual( + newc.metadata["augeaspath"], + comments[0].primary.metadata["augeaspath"] + ) + self.assertEqual(newc.comment, comments[0].comment) + def test_delete_child(self): listens = self.config.parser_root.primary.find_directives("Listen") self.assertEqual(len(listens), 1) diff --git a/certbot-apache/certbot_apache/tests/dualnode_test.py b/certbot-apache/certbot_apache/tests/dualnode_test.py index 2878a693e..bdfab4fc7 100644 --- a/certbot-apache/certbot_apache/tests/dualnode_test.py +++ b/certbot-apache/certbot_apache/tests/dualnode_test.py @@ -6,7 +6,6 @@ import mock from certbot_apache import assertions from certbot_apache import augeasparser from certbot_apache import dualparser -from certbot_apache import interfaces class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-methods @@ -151,15 +150,13 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- self.assertTrue(mock_second.called) def test_add_child_comment(self): - self.assertEqual(len(self.block.primary.children), 0) - self.assertEqual(len(self.block.secondary.children), 0) + mock_first = mock.MagicMock(return_value=self.comment.primary) + mock_second = mock.MagicMock(return_value=self.comment.secondary) + self.block.primary.add_child_comment = mock_first + self.block.secondary.add_child_comment = mock_second self.block.add_child_comment("Comment") - self.assertEqual(len(self.block.primary.children), 1) - self.assertEqual(len(self.block.secondary.children), 1) - self.assertTrue(isinstance(self.block.primary.children[0], - interfaces.CommentNode)) - self.assertEqual(self.block.primary.children[0].ancestor, - self.block.primary) + self.assertTrue(mock_first.called) + self.assertTrue(mock_second.called) def test_find_comments(self): pri_comments = [augeasparser.AugeasCommentNode(comment="some comment", From 06fdbf2a55eca3097b489ae9794c48366e5a93ea Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Mon, 2 Dec 2019 16:25:39 +0200 Subject: [PATCH 020/122] [Apache v2] Implement find_ancestors (#7561) * Implement find_ancestors * Create the node properly and add assertions * Update certbot-apache/certbot_apache/augeasparser.py Co-Authored-By: ohemorange * Remove comment --- certbot-apache/certbot_apache/apacheparser.py | 8 ++ certbot-apache/certbot_apache/augeasparser.py | 92 ++++++++++++------- certbot-apache/certbot_apache/dualparser.py | 89 ++++++++++-------- certbot-apache/certbot_apache/interfaces.py | 12 +++ .../certbot_apache/tests/augeasnode_test.py | 21 +++++ .../certbot_apache/tests/dualnode_test.py | 9 ++ .../certbot_apache/tests/parsernode_test.py | 4 + 7 files changed, 163 insertions(+), 72 deletions(-) diff --git a/certbot-apache/certbot_apache/apacheparser.py b/certbot-apache/certbot_apache/apacheparser.py index 6625735b4..d9f33f095 100644 --- a/certbot-apache/certbot_apache/apacheparser.py +++ b/certbot-apache/certbot_apache/apacheparser.py @@ -24,6 +24,14 @@ class ApacheParserNode(interfaces.ParserNode): def save(self, msg): # pragma: no cover pass + def find_ancestors(self, name): # pylint: disable=unused-variable + """Find ancestor BlockNodes with a given name""" + return [ApacheBlockNode(name=assertions.PASS, + parameters=assertions.PASS, + ancestor=self, + filepath=assertions.PASS, + metadata=self.metadata)] + class ApacheCommentNode(ApacheParserNode): """ apacheconfig implementation of CommentNode interface """ diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py index 3956b1d16..8a3a37083 100644 --- a/certbot-apache/certbot_apache/augeasparser.py +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -75,7 +75,6 @@ from certbot_apache import parser from certbot_apache import parsernode_util as util - class AugeasParserNode(interfaces.ParserNode): """ Augeas implementation of ParserNode interface """ @@ -100,6 +99,63 @@ class AugeasParserNode(interfaces.ParserNode): def save(self, msg): self.parser.save(msg) + def find_ancestors(self, name): + """ + Searches for ancestor BlockNodes with a given name. + + :param str name: Name of the BlockNode parent to search for + + :returns: List of matching ancestor nodes. + :rtype: list of AugeasBlockNode + """ + + ancestors = [] + + parent = self.metadata["augeaspath"] + while True: + # Get the path of ancestor node + parent = parent.rpartition("/")[0] + if not parent: + break + anc = self._create_blocknode(parent) + if anc.name.lower() == name.lower(): + ancestors.append(anc) + + return ancestors + + def _create_blocknode(self, path): + """ + Helper function to create a BlockNode from Augeas path. This is used by + AugeasParserNode.find_ancestors and AugeasBlockNode. + and AugeasBlockNode.find_blocks + + """ + + name = self._aug_get_name(path) + metadata = {"augeasparser": self.parser, "augeaspath": path} + + return AugeasBlockNode(name=name, + ancestor=assertions.PASS, + filepath=apache_util.get_file_path(path), + metadata=metadata) + + def _aug_get_name(self, path): + """ + Helper function to get name of a configuration block or variable from path. + """ + + # Remove the ending slash if any + if path[-1] == "/": # pragma: no cover + path = path[:-1] + + # Get the block name + name = path.split("/")[-1] + + # remove [...], it's not allowed in Apache configuration and is used + # for indexing within Augeas + name = name.split("[")[0] + return name + class AugeasCommentNode(AugeasParserNode): """ Augeas implementation of CommentNode interface """ @@ -263,7 +319,7 @@ class AugeasBlockNode(AugeasDirectiveNode): metadata=new_metadata) return new_comment - def find_blocks(self, name, exclude=True): # pylint: disable=unused-argument + def find_blocks(self, name, exclude=True): """Recursive search of BlockNodes from the sequence of children""" nodes = list() @@ -275,7 +331,7 @@ class AugeasBlockNode(AugeasDirectiveNode): return nodes - def find_directives(self, name, exclude=True): # pylint: disable=unused-argument + def find_directives(self, name, exclude=True): """Recursive search of DirectiveNodes from the sequence of children""" nodes = list() @@ -353,19 +409,6 @@ class AugeasBlockNode(AugeasDirectiveNode): filepath=apache_util.get_file_path(path), metadata=metadata) - def _create_blocknode(self, path): - """Helper function to create a BlockNode from Augeas path""" - - name = self._aug_get_name(path) - metadata = {"augeasparser": self.parser, "augeaspath": path} - - # Because of the dynamic nature, and the fact that we're not populating - # the complete ParserNode tree, we use the search parent as ancestor - return AugeasBlockNode(name=name, - ancestor=assertions.PASS, - filepath=apache_util.get_file_path(path), - metadata=metadata) - def _aug_find_blocks(self, name): """Helper function to perform a search to Augeas DOM tree to search configuration blocks with a given name""" @@ -380,23 +423,6 @@ class AugeasBlockNode(AugeasDirectiveNode): name.lower() in os.path.basename(path).lower()]) return blk_paths - def _aug_get_name(self, path): - """ - Helper function to get name of a configuration block or variable from path. - """ - - # Remove the ending slash if any - if path[-1] == "/": # pragma: no cover - path = path[:-1] - - # Get the block name - name = path.split("/")[-1] - - # remove [...], it's not allowed in Apache configuration and is used - # for indexing within Augeas - name = name.split("[")[0] - return name - def _aug_resolve_child_position(self, name, position): """ Helper function that iterates through the immediate children and figures diff --git a/certbot-apache/certbot_apache/dualparser.py b/certbot-apache/certbot_apache/dualparser.py index ef8e86196..667462d34 100644 --- a/certbot-apache/certbot_apache/dualparser.py +++ b/certbot-apache/certbot_apache/dualparser.py @@ -18,10 +18,59 @@ class DualNodeBase(object): """ Attribute value assertion """ firstval = getattr(self.primary, aname) secondval = getattr(self.secondary, aname) - if not callable(firstval): + exclusions = [ + # Metadata will inherently be different, as ApacheParserNode does + # not have Augeas paths and so on. + aname == "metadata", + callable(firstval) + ] + if not any(exclusions): assertions.assertEqualSimple(firstval, secondval) return firstval + def find_ancestors(self, name): + """ Traverses the ancestor tree and returns ancestors matching name """ + return self._find_helper(DualBlockNode, "find_ancestors", name) + + def _find_helper(self, nodeclass, findfunc, search, **kwargs): + """A helper for find_* functions. The function specific attributes should + be passed as keyword arguments. + + :param interfaces.ParserNode nodeclass: The node class for results. + :param str findfunc: Name of the find function to call + :param str search: The search term + """ + + primary_res = getattr(self.primary, findfunc)(search, **kwargs) + secondary_res = getattr(self.secondary, findfunc)(search, **kwargs) + + # The order of search results for Augeas implementation cannot be + # assured. + + pass_primary = assertions.isPassNodeList(primary_res) + pass_secondary = assertions.isPassNodeList(secondary_res) + new_nodes = list() + + if pass_primary and pass_secondary: + # Both unimplemented + new_nodes.append(nodeclass(primary=primary_res[0], + secondary=secondary_res[0])) # pragma: no cover + elif pass_primary: + for c in secondary_res: + new_nodes.append(nodeclass(primary=primary_res[0], + secondary=c)) + elif pass_secondary: + for c in primary_res: + new_nodes.append(nodeclass(primary=c, + secondary=secondary_res[0])) + else: + assert len(primary_res) == len(secondary_res) + matches = self._create_matching_list(primary_res, secondary_res) + for p, s in matches: + new_nodes.append(nodeclass(primary=p, secondary=s)) + + return new_nodes + class DualCommentNode(DualNodeBase): """ Dual parser implementation of CommentNode interface """ @@ -223,44 +272,6 @@ class DualBlockNode(DualNodeBase): return self._find_helper(DualCommentNode, "find_comments", comment) - def _find_helper(self, nodeclass, findfunc, search, **kwargs): - """A helper for find_* functions. The function specific attributes should - be passed as keyword arguments. - - :param interfaces.ParserNode nodeclass: The node class for results. - :param str findfunc: Name of the find function to call - :param str search: The search term - """ - - primary_res = getattr(self.primary, findfunc)(search, **kwargs) - secondary_res = getattr(self.secondary, findfunc)(search, **kwargs) - - # The order of search results for Augeas implementation cannot be - # assured. - - pass_primary = assertions.isPassNodeList(primary_res) - pass_secondary = assertions.isPassNodeList(secondary_res) - new_nodes = list() - - if pass_primary and pass_secondary: - # Both unimplemented - new_nodes.append(nodeclass(primary=primary_res[0], - secondary=secondary_res[0])) # pragma: no cover - elif pass_primary: - for c in secondary_res: - new_nodes.append(nodeclass(primary=primary_res[0], - secondary=c)) - elif pass_secondary: - for c in primary_res: - new_nodes.append(nodeclass(primary=c, - secondary=secondary_res[0])) - else: - assert len(primary_res) == len(secondary_res) - matches = self._create_matching_list(primary_res, secondary_res) - for p, s in matches: - new_nodes.append(nodeclass(primary=p, secondary=s)) - - return new_nodes def delete_child(self, child): """Deletes a child from the ParserNode implementations. The actual diff --git a/certbot-apache/certbot_apache/interfaces.py b/certbot-apache/certbot_apache/interfaces.py index ecad2d4eb..2d25fad0f 100644 --- a/certbot-apache/certbot_apache/interfaces.py +++ b/certbot-apache/certbot_apache/interfaces.py @@ -192,6 +192,18 @@ class ParserNode(object): """ + @abc.abstractmethod + def find_ancestors(self, name): + """ + Traverses the ancestor tree up, searching for BlockNodes with a specific + name. + + :param str name: Name of the ancestor BlockNode to search for + + :returns: A list of ancestor BlockNodes that match the name + :rtype: list of BlockNode + """ + # Linter rule exclusion done because of https://github.com/PyCQA/pylint/issues/179 @six.add_metaclass(abc.ABCMeta) # pylint: disable=abstract-method diff --git a/certbot-apache/certbot_apache/tests/augeasnode_test.py b/certbot-apache/certbot_apache/tests/augeasnode_test.py index bf01f9fac..043f5d248 100644 --- a/certbot-apache/certbot_apache/tests/augeasnode_test.py +++ b/certbot-apache/certbot_apache/tests/augeasnode_test.py @@ -291,3 +291,24 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- self.config.parser_root.add_child_directive, "ThisRaisesErrorBecauseMissingParameters" ) + + def test_find_ancestors(self): + vhsblocks = self.config.parser_root.find_blocks("VirtualHost") + macro_test = False + nonmacro_test = False + for vh in vhsblocks: + if "/macro/" in vh.metadata["augeaspath"].lower(): + ancs = vh.find_ancestors("Macro") + self.assertEqual(len(ancs), 1) + macro_test = True + else: + ancs = vh.find_ancestors("Macro") + self.assertEqual(len(ancs), 0) + nonmacro_test = True + self.assertTrue(macro_test) + self.assertTrue(nonmacro_test) + + def test_find_ancestors_bad_path(self): + self.config.parser_root.primary.metadata["augeaspath"] = "" + ancs = self.config.parser_root.primary.find_ancestors("Anything") + self.assertEqual(len(ancs), 0) diff --git a/certbot-apache/certbot_apache/tests/dualnode_test.py b/certbot-apache/certbot_apache/tests/dualnode_test.py index bdfab4fc7..9e5a5e9aa 100644 --- a/certbot-apache/certbot_apache/tests/dualnode_test.py +++ b/certbot-apache/certbot_apache/tests/dualnode_test.py @@ -412,3 +412,12 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- self.assertFalse(self.block == ne_block) self.assertFalse(self.directive == ne_directive) self.assertFalse(self.comment == ne_comment) + + def test_find_ancestors(self): + primarymock = mock.MagicMock(return_value=[]) + secondarymock = mock.MagicMock(return_value=[]) + self.block.primary.find_ancestors = primarymock + self.block.secondary.find_ancestors = secondarymock + self.block.find_ancestors("anything") + self.assertTrue(primarymock.called) + self.assertTrue(secondarymock.called) diff --git a/certbot-apache/certbot_apache/tests/parsernode_test.py b/certbot-apache/certbot_apache/tests/parsernode_test.py index 1a2288c82..a6caf4814 100644 --- a/certbot-apache/certbot_apache/tests/parsernode_test.py +++ b/certbot-apache/certbot_apache/tests/parsernode_test.py @@ -24,6 +24,10 @@ class DummyParserNode(interfaces.ParserNode): """Save""" pass + def find_ancestors(self, name): # pragma: no cover + """ Find ancestors """ + return [] + class DummyCommentNode(DummyParserNode): """ A dummy class implementing CommentNode interface """ From 6148e5c35599cc3b39a5be70e4c520414d1e5eb7 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Tue, 3 Dec 2019 01:00:53 +0200 Subject: [PATCH 021/122] [Apache v2] Move the apachectl parsing to apache_util (#7569) * Move the Apache CLI parsing to apache_util * Fix test mocks * Address review comments * Fix the parsernode metadata dictionary --- certbot-apache/certbot_apache/apache_util.py | 119 ++++++++++++++++++ certbot-apache/certbot_apache/configurator.py | 7 ++ .../certbot_apache/override_gentoo.py | 2 +- certbot-apache/certbot_apache/parser.py | 77 +----------- .../certbot_apache/tests/centos_test.py | 4 +- .../certbot_apache/tests/configurator_test.py | 4 +- .../certbot_apache/tests/debian_test.py | 2 +- .../certbot_apache/tests/fedora_test.py | 4 +- .../certbot_apache/tests/gentoo_test.py | 4 +- .../certbot_apache/tests/parser_test.py | 12 +- certbot-apache/certbot_apache/tests/util.py | 28 +++-- 11 files changed, 163 insertions(+), 100 deletions(-) diff --git a/certbot-apache/certbot_apache/apache_util.py b/certbot-apache/certbot_apache/apache_util.py index 7a2ecf49b..70febc949 100644 --- a/certbot-apache/certbot_apache/apache_util.py +++ b/certbot-apache/certbot_apache/apache_util.py @@ -1,9 +1,16 @@ """ Utility functions for certbot-apache plugin """ import binascii +import logging +import re +import subprocess +from certbot import errors from certbot import util + from certbot.compat import os +logger = logging.getLogger(__name__) + def get_mod_deps(mod_name): """Get known module dependencies. @@ -105,3 +112,115 @@ def parse_define_file(filepath, varname): def unique_id(): """ Returns an unique id to be used as a VirtualHost identifier""" return binascii.hexlify(os.urandom(16)).decode("utf-8") + + +def parse_defines(apachectl): + """ + Gets Defines from httpd process and returns a dictionary of + the defined variables. + + :param str apachectl: Path to apachectl executable + + :returns: dictionary of defined variables + :rtype: dict + """ + + variables = dict() + define_cmd = [apachectl, "-t", "-D", + "DUMP_RUN_CFG"] + matches = parse_from_subprocess(define_cmd, r"Define: ([^ \n]*)") + try: + matches.remove("DUMP_RUN_CFG") + except ValueError: + return {} + + for match in matches: + if match.count("=") > 1: + logger.error("Unexpected number of equal signs in " + "runtime config dump.") + raise errors.PluginError( + "Error parsing Apache runtime variables") + parts = match.partition("=") + variables[parts[0]] = parts[2] + + return variables + + +def parse_includes(apachectl): + """ + Gets Include directives from httpd process and returns a list of + their values. + + :param str apachectl: Path to apachectl executable + + :returns: list of found Include directive values + :rtype: list of str + """ + + inc_cmd = [apachectl, "-t", "-D", + "DUMP_INCLUDES"] + return parse_from_subprocess(inc_cmd, r"\(.*\) (.*)") + + +def parse_modules(apachectl): + """ + Get loaded modules from httpd process, and return the list + of loaded module names. + + :param str apachectl: Path to apachectl executable + + :returns: list of found LoadModule module names + :rtype: list of str + """ + + mod_cmd = [apachectl, "-t", "-D", + "DUMP_MODULES"] + return parse_from_subprocess(mod_cmd, r"(.*)_module") + + +def parse_from_subprocess(command, regexp): + """Get values from stdout of subprocess command + + :param list command: Command to run + :param str regexp: Regexp for parsing + + :returns: list parsed from command output + :rtype: list + + """ + stdout = _get_runtime_cfg(command) + return re.compile(regexp).findall(stdout) + + +def _get_runtime_cfg(command): + """ + Get runtime configuration info. + + :param command: Command to run + + :returns: stdout from command + + """ + try: + proc = subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True) + stdout, stderr = proc.communicate() + + except (OSError, ValueError): + logger.error( + "Error running command %s for runtime parameters!%s", + command, os.linesep) + raise errors.MisconfigurationError( + "Error accessing loaded Apache parameters: {0}".format( + command)) + # Small errors that do not impede + if proc.returncode != 0: + logger.warning("Error in checking parameter list: %s", stderr) + raise errors.MisconfigurationError( + "Apache is unable to check whether or not the module is " + "loaded because Apache is misconfigured.") + + return stdout diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index 2b20ebb7c..9a9dec7a8 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -359,6 +359,13 @@ class ApacheConfigurator(common.Installer): def get_parsernode_root(self, metadata): """Initializes the ParserNode parser root instance.""" + + apache_vars = dict() + apache_vars["defines"] = apache_util.parse_defines(self.option("ctl")) + apache_vars["includes"] = apache_util.parse_includes(self.option("ctl")) + apache_vars["modules"] = apache_util.parse_modules(self.option("ctl")) + metadata["apache_vars"] = apache_vars + return dualparser.DualBlockNode( name=assertions.PASS, ancestor=None, diff --git a/certbot-apache/certbot_apache/override_gentoo.py b/certbot-apache/certbot_apache/override_gentoo.py index c358a10fa..fdb1a8d8a 100644 --- a/certbot-apache/certbot_apache/override_gentoo.py +++ b/certbot-apache/certbot_apache/override_gentoo.py @@ -70,6 +70,6 @@ class GentooParser(parser.ApacheParser): def update_modules(self): """Get loaded modules from httpd process, and add them to DOM""" mod_cmd = [self.configurator.option("ctl"), "modules"] - matches = self.parse_from_subprocess(mod_cmd, r"(.*)_module") + matches = apache_util.parse_from_subprocess(mod_cmd, r"(.*)_module") for mod in matches: self.add_mod(mod.strip()) diff --git a/certbot-apache/certbot_apache/parser.py b/certbot-apache/certbot_apache/parser.py index 906b97e72..b9bdaca37 100644 --- a/certbot-apache/certbot_apache/parser.py +++ b/certbot-apache/certbot_apache/parser.py @@ -3,7 +3,6 @@ import copy import fnmatch import logging import re -import subprocess import sys import six @@ -13,6 +12,7 @@ from acme.magic_typing import Dict, List, Set # pylint: disable=unused-import, from certbot import errors from certbot.compat import os +from certbot_apache import apache_util from certbot_apache import constants logger = logging.getLogger(__name__) @@ -291,32 +291,15 @@ class ApacheParser(object): def update_runtime_variables(self): """Update Includes, Defines and Includes from httpd config dump data""" + self.update_defines() self.update_includes() self.update_modules() def update_defines(self): - """Get Defines from httpd process""" + """Updates the dictionary of known variables in the configuration""" - variables = dict() - define_cmd = [self.configurator.option("ctl"), "-t", "-D", - "DUMP_RUN_CFG"] - matches = self.parse_from_subprocess(define_cmd, r"Define: ([^ \n]*)") - try: - matches.remove("DUMP_RUN_CFG") - except ValueError: - return - - for match in matches: - if match.count("=") > 1: - logger.error("Unexpected number of equal signs in " - "runtime config dump.") - raise errors.PluginError( - "Error parsing Apache runtime variables") - parts = match.partition("=") - variables[parts[0]] = parts[2] - - self.variables = variables + self.variables = apache_util.parse_defines(self.configurator.option("ctl")) def update_includes(self): """Get includes from httpd process, and add them to DOM if needed""" @@ -326,9 +309,7 @@ class ApacheParser(object): # configuration files _ = self.find_dir("Include") - inc_cmd = [self.configurator.option("ctl"), "-t", "-D", - "DUMP_INCLUDES"] - matches = self.parse_from_subprocess(inc_cmd, r"\(.*\) (.*)") + matches = apache_util.parse_includes(self.configurator.option("ctl")) if matches: for i in matches: if not self.parsed_in_current(i): @@ -337,56 +318,10 @@ class ApacheParser(object): def update_modules(self): """Get loaded modules from httpd process, and add them to DOM""" - mod_cmd = [self.configurator.option("ctl"), "-t", "-D", - "DUMP_MODULES"] - matches = self.parse_from_subprocess(mod_cmd, r"(.*)_module") + matches = apache_util.parse_modules(self.configurator.option("ctl")) for mod in matches: self.add_mod(mod.strip()) - def parse_from_subprocess(self, command, regexp): - """Get values from stdout of subprocess command - - :param list command: Command to run - :param str regexp: Regexp for parsing - - :returns: list parsed from command output - :rtype: list - - """ - stdout = self._get_runtime_cfg(command) - return re.compile(regexp).findall(stdout) - - def _get_runtime_cfg(self, command): # pylint: disable=no-self-use - """Get runtime configuration info. - :param command: Command to run - - :returns: stdout from command - - """ - try: - proc = subprocess.Popen( - command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True) - stdout, stderr = proc.communicate() - - except (OSError, ValueError): - logger.error( - "Error running command %s for runtime parameters!%s", - command, os.linesep) - raise errors.MisconfigurationError( - "Error accessing loaded Apache parameters: {0}".format( - command)) - # Small errors that do not impede - if proc.returncode != 0: - logger.warning("Error in checking parameter list: %s", stderr) - raise errors.MisconfigurationError( - "Apache is unable to check whether or not the module is " - "loaded because Apache is misconfigured.") - - return stdout - def filter_args_num(self, matches, args): # pylint: disable=no-self-use """Filter out directives with specific number of arguments. diff --git a/certbot-apache/certbot_apache/tests/centos_test.py b/certbot-apache/certbot_apache/tests/centos_test.py index dddbf489e..bb97ec144 100644 --- a/certbot-apache/certbot_apache/tests/centos_test.py +++ b/certbot-apache/certbot_apache/tests/centos_test.py @@ -107,7 +107,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest): def test_get_parser(self): self.assertIsInstance(self.config.parser, override_centos.CentOSParser) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache.apache_util._get_runtime_cfg") def test_opportunistic_httpd_runtime_parsing(self, mock_get): define_val = ( 'Define: TEST1\n' @@ -156,7 +156,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest): raise Exception("Missed: %s" % vhost) # pragma: no cover self.assertEqual(found, 2) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache.apache_util._get_runtime_cfg") def test_get_sysconfig_vars(self, mock_cfg): """Make sure we read the sysconfig OPTIONS variable correctly""" # Return nothing for the process calls diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index 68d9c90fa..350112f6f 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -807,7 +807,7 @@ class MultipleVhostsTest(util.ApacheTest): self.assertEqual(mock_restart.call_count, 1) @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache.apache_util._get_runtime_cfg") def test_cleanup(self, mock_cfg, mock_restart): mock_cfg.return_value = "" _, achalls = self.get_key_and_achalls() @@ -823,7 +823,7 @@ class MultipleVhostsTest(util.ApacheTest): self.assertFalse(mock_restart.called) @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache.apache_util._get_runtime_cfg") def test_cleanup_no_errors(self, mock_cfg, mock_restart): mock_cfg.return_value = "" _, achalls = self.get_key_and_achalls() diff --git a/certbot-apache/certbot_apache/tests/debian_test.py b/certbot-apache/certbot_apache/tests/debian_test.py index 54ced2d0b..72f806589 100644 --- a/certbot-apache/certbot_apache/tests/debian_test.py +++ b/certbot-apache/certbot_apache/tests/debian_test.py @@ -47,7 +47,7 @@ class MultipleVhostsTestDebian(util.ApacheTest): @mock.patch("certbot.util.run_script") @mock.patch("certbot.util.exe_exists") - @mock.patch("certbot_apache.parser.subprocess.Popen") + @mock.patch("certbot_apache.apache_util.subprocess.Popen") def test_enable_mod(self, mock_popen, mock_exe_exists, mock_run_script): mock_popen().communicate.return_value = ("Define: DUMP_RUN_CFG", "") mock_popen().returncode = 0 diff --git a/certbot-apache/certbot_apache/tests/fedora_test.py b/certbot-apache/certbot_apache/tests/fedora_test.py index 4d3f3a313..799c24c20 100644 --- a/certbot-apache/certbot_apache/tests/fedora_test.py +++ b/certbot-apache/certbot_apache/tests/fedora_test.py @@ -101,7 +101,7 @@ class MultipleVhostsTestFedora(util.ApacheTest): def test_get_parser(self): self.assertIsInstance(self.config.parser, override_fedora.FedoraParser) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache.apache_util._get_runtime_cfg") def test_opportunistic_httpd_runtime_parsing(self, mock_get): define_val = ( 'Define: TEST1\n' @@ -156,7 +156,7 @@ class MultipleVhostsTestFedora(util.ApacheTest): raise Exception("Missed: %s" % vhost) # pragma: no cover self.assertEqual(found, 2) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache.apache_util._get_runtime_cfg") def test_get_sysconfig_vars(self, mock_cfg): """Make sure we read the sysconfig OPTIONS variable correctly""" # Return nothing for the process calls diff --git a/certbot-apache/certbot_apache/tests/gentoo_test.py b/certbot-apache/certbot_apache/tests/gentoo_test.py index d0d3ba0dd..353a66564 100644 --- a/certbot-apache/certbot_apache/tests/gentoo_test.py +++ b/certbot-apache/certbot_apache/tests/gentoo_test.py @@ -90,7 +90,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest): for define in defines: self.assertTrue(define in self.config.parser.variables.keys()) - @mock.patch("certbot_apache.parser.ApacheParser.parse_from_subprocess") + @mock.patch("certbot_apache.apache_util.parse_from_subprocess") def test_no_binary_configdump(self, mock_subprocess): """Make sure we don't call binary dumps other than modules from Apache as this is not supported in Gentoo currently""" @@ -104,7 +104,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest): self.config.parser.reset_modules() self.assertTrue(mock_subprocess.called) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache.apache_util._get_runtime_cfg") def test_opportunistic_httpd_runtime_parsing(self, mock_get): mod_val = ( 'Loaded Modules:\n' diff --git a/certbot-apache/certbot_apache/tests/parser_test.py b/certbot-apache/certbot_apache/tests/parser_test.py index 27d66f680..303ac09e3 100644 --- a/certbot-apache/certbot_apache/tests/parser_test.py +++ b/certbot-apache/certbot_apache/tests/parser_test.py @@ -167,7 +167,7 @@ class BasicParserTest(util.ParserTest): self.assertTrue(mock_logger.debug.called) @mock.patch("certbot_apache.parser.ApacheParser.find_dir") - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache.apache_util._get_runtime_cfg") def test_update_runtime_variables(self, mock_cfg, _): define_val = ( 'ServerRoot: "/etc/apache2"\n' @@ -273,7 +273,7 @@ class BasicParserTest(util.ParserTest): self.assertEqual(mock_parse.call_count, 25) @mock.patch("certbot_apache.parser.ApacheParser.find_dir") - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache.apache_util._get_runtime_cfg") def test_update_runtime_variables_alt_values(self, mock_cfg, _): inc_val = ( 'Included configuration files:\n' @@ -295,7 +295,7 @@ class BasicParserTest(util.ParserTest): # path derived from root configuration Include statements self.assertEqual(mock_parse.call_count, 1) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache.apache_util._get_runtime_cfg") def test_update_runtime_vars_bad_output(self, mock_cfg): mock_cfg.return_value = "Define: TLS=443=24" self.parser.update_runtime_variables() @@ -305,7 +305,7 @@ class BasicParserTest(util.ParserTest): errors.PluginError, self.parser.update_runtime_variables) @mock.patch("certbot_apache.configurator.ApacheConfigurator.option") - @mock.patch("certbot_apache.parser.subprocess.Popen") + @mock.patch("certbot_apache.apache_util.subprocess.Popen") def test_update_runtime_vars_bad_ctl(self, mock_popen, mock_opt): mock_popen.side_effect = OSError mock_opt.return_value = "nonexistent" @@ -313,7 +313,7 @@ class BasicParserTest(util.ParserTest): errors.MisconfigurationError, self.parser.update_runtime_variables) - @mock.patch("certbot_apache.parser.subprocess.Popen") + @mock.patch("certbot_apache.apache_util.subprocess.Popen") def test_update_runtime_vars_bad_exit(self, mock_popen): mock_popen().communicate.return_value = ("", "") mock_popen.returncode = -1 @@ -357,7 +357,7 @@ class ParserInitTest(util.ApacheTest): ApacheParser, os.path.relpath(self.config_path), "/dummy/vhostpath", version=(2, 4, 22), configurator=self.config) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache.apache_util._get_runtime_cfg") def test_unparseable(self, mock_cfg): from certbot_apache.parser import ApacheParser mock_cfg.return_value = ('Define: TEST') diff --git a/certbot-apache/certbot_apache/tests/util.py b/certbot-apache/certbot_apache/tests/util.py index 8e3de04be..4155c93c0 100644 --- a/certbot-apache/certbot_apache/tests/util.py +++ b/certbot-apache/certbot_apache/tests/util.py @@ -111,19 +111,21 @@ def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-loc mock_exe_exists.return_value = True with mock.patch("certbot_apache.parser.ApacheParser." "update_runtime_variables"): - try: - config_class = entrypoint.OVERRIDE_CLASSES[os_info] - except KeyError: - config_class = configurator.ApacheConfigurator - config = config_class(config=mock_le_config, name="apache", - version=version) - if not conf_vhost_path: - config_class.OS_DEFAULTS["vhost_root"] = vhost_path - else: - # Custom virtualhost path was requested - config.config.apache_vhost_root = conf_vhost_path - config.config.apache_ctl = config_class.OS_DEFAULTS["ctl"] - config.prepare() + with mock.patch("certbot_apache.apache_util.parse_from_subprocess") as mock_sp: + mock_sp.return_value = [] + try: + config_class = entrypoint.OVERRIDE_CLASSES[os_info] + except KeyError: + config_class = configurator.ApacheConfigurator + config = config_class(config=mock_le_config, name="apache", + version=version) + if not conf_vhost_path: + config_class.OS_DEFAULTS["vhost_root"] = vhost_path + else: + # Custom virtualhost path was requested + config.config.apache_vhost_root = conf_vhost_path + config.config.apache_ctl = config_class.OS_DEFAULTS["ctl"] + config.prepare() return config From 5c588a6f8ddc7bc5f13b79e90138c6d88c68b0e0 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Tue, 10 Dec 2019 20:20:00 +0200 Subject: [PATCH 022/122] [Apache v2] Implement parsed_files (#7562) * Implement parsed_files * Add parsed_files stub to ApacheParserNodes and fix assertions * Update certbot-apache/certbot_apache/interfaces.py Co-Authored-By: ohemorange * Add more descriptive comments * Update certbot-apache/certbot_apache/augeasparser.py Co-Authored-By: ohemorange * Update certbot-apache/certbot_apache/dualparser.py Co-Authored-By: ohemorange * Update certbot-apache/certbot_apache/interfaces.py Co-Authored-By: ohemorange --- certbot-apache/certbot_apache/apacheparser.py | 4 ++++ certbot-apache/certbot_apache/assertions.py | 16 ++++++++++++++++ certbot-apache/certbot_apache/augeasparser.py | 14 ++++++++++++++ certbot-apache/certbot_apache/dualparser.py | 19 +++++++++++++++++-- certbot-apache/certbot_apache/interfaces.py | 12 ++++++++++++ .../certbot_apache/tests/augeasnode_test.py | 4 ++++ .../certbot_apache/tests/dualnode_test.py | 19 +++++++++++++++++++ 7 files changed, 86 insertions(+), 2 deletions(-) diff --git a/certbot-apache/certbot_apache/apacheparser.py b/certbot-apache/certbot_apache/apacheparser.py index d9f33f095..969a4ab69 100644 --- a/certbot-apache/certbot_apache/apacheparser.py +++ b/certbot-apache/certbot_apache/apacheparser.py @@ -159,6 +159,10 @@ class ApacheBlockNode(ApacheDirectiveNode): """Returns a list of unsaved filepaths""" return [assertions.PASS] + def parsed_paths(self): # pragma: no cover + """Returns a list of parsed configuration file paths""" + return [assertions.PASS] + interfaces.CommentNode.register(ApacheCommentNode) interfaces.DirectiveNode.register(ApacheDirectiveNode) diff --git a/certbot-apache/certbot_apache/assertions.py b/certbot-apache/certbot_apache/assertions.py index fc2b35e14..c7a61f446 100644 --- a/certbot-apache/certbot_apache/assertions.py +++ b/certbot-apache/certbot_apache/assertions.py @@ -1,4 +1,6 @@ """Dual parser node assertions""" +import fnmatch + from certbot_apache import interfaces @@ -102,3 +104,17 @@ def assertEqualSimple(first, second): """ Simple assertion """ if not isPass(first) and not isPass(second): assert first == second + +def assertEqualPathsList(first, second): # pragma: no cover + """ + Checks that the two lists of file paths match. This assertion allows for wildcard + paths. + """ + if any([isPass(path) for path in first]): + return + if any([isPass(path) for path in second]): + return + for fpath in first: + assert any([fnmatch.fnmatch(fpath, spath) for spath in second]) + for spath in second: + assert any([fnmatch.fnmatch(fpath, spath) for fpath in first]) diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py index 8a3a37083..d2771c9d2 100644 --- a/certbot-apache/certbot_apache/augeasparser.py +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -383,6 +383,20 @@ class AugeasBlockNode(AugeasDirectiveNode): """Returns a list of unsaved filepaths""" return self.parser.unsaved_files() + def parsed_paths(self): + """ + Returns a list of file paths that have currently been parsed into the parser + tree. The returned list may include paths with wildcard characters, for + example: ['/etc/apache2/conf.d/*.load'] + + This is typically called on the root node of the ParserNode tree. + + :returns: list of file paths of files that have been parsed + """ + + parsed_paths = self.parser.aug.match("/augeas/load/Httpd/incl") + return [self.parser.aug.get(path) for path in parsed_paths] + def _create_commentnode(self, path): """Helper function to create a CommentNode from Augeas path""" diff --git a/certbot-apache/certbot_apache/dualparser.py b/certbot-apache/certbot_apache/dualparser.py index 667462d34..ef0800782 100644 --- a/certbot-apache/certbot_apache/dualparser.py +++ b/certbot-apache/certbot_apache/dualparser.py @@ -54,7 +54,7 @@ class DualNodeBase(object): if pass_primary and pass_secondary: # Both unimplemented new_nodes.append(nodeclass(primary=primary_res[0], - secondary=secondary_res[0])) # pragma: no cover + secondary=secondary_res[0])) # pragma: no cover elif pass_primary: for c in secondary_res: new_nodes.append(nodeclass(primary=primary_res[0], @@ -272,7 +272,6 @@ class DualBlockNode(DualNodeBase): return self._find_helper(DualCommentNode, "find_comments", comment) - def delete_child(self, child): """Deletes a child from the ParserNode implementations. The actual ParserNode implementations are used here directly in order to be able @@ -289,3 +288,19 @@ class DualBlockNode(DualNodeBase): assertions.assertEqualSimple(primary_files, secondary_files) return primary_files + + def parsed_paths(self): + """ + Returns a list of file paths that have currently been parsed into the parser + tree. The returned list may include paths with wildcard characters, for + example: ['/etc/apache2/conf.d/*.load'] + + This is typically called on the root node of the ParserNode tree. + + :returns: list of file paths of files that have been parsed + """ + + primary_paths = self.primary.parsed_paths() + secondary_paths = self.secondary.parsed_paths() + assertions.assertEqualPathsList(primary_paths, secondary_paths) + return primary_paths diff --git a/certbot-apache/certbot_apache/interfaces.py b/certbot-apache/certbot_apache/interfaces.py index 2d25fad0f..1b67be5c8 100644 --- a/certbot-apache/certbot_apache/interfaces.py +++ b/certbot-apache/certbot_apache/interfaces.py @@ -502,3 +502,15 @@ class BlockNode(DirectiveNode): :returns: list of file paths of files that have been changed but not yet saved to disk. """ + + @abc.abstractmethod + def parsed_paths(self): + """ + Returns a list of file paths that have currently been parsed into the parser + tree. The returned list may include paths with wildcard characters, for + example: ['/etc/apache2/conf.d/*.load'] + + This is typically called on the root node of the ParserNode tree. + + :returns: list of file paths of files that have been parsed + """ diff --git a/certbot-apache/certbot_apache/tests/augeasnode_test.py b/certbot-apache/certbot_apache/tests/augeasnode_test.py index 043f5d248..849a468c9 100644 --- a/certbot-apache/certbot_apache/tests/augeasnode_test.py +++ b/certbot-apache/certbot_apache/tests/augeasnode_test.py @@ -292,6 +292,10 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- "ThisRaisesErrorBecauseMissingParameters" ) + def test_parsed_paths(self): + paths = self.config.parser_root.parsed_paths() + self.assertEqual(len(paths), 6) + def test_find_ancestors(self): vhsblocks = self.config.parser_root.find_blocks("VirtualHost") macro_test = False diff --git a/certbot-apache/certbot_apache/tests/dualnode_test.py b/certbot-apache/certbot_apache/tests/dualnode_test.py index 9e5a5e9aa..b5ab588f0 100644 --- a/certbot-apache/certbot_apache/tests/dualnode_test.py +++ b/certbot-apache/certbot_apache/tests/dualnode_test.py @@ -413,6 +413,25 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- self.assertFalse(self.directive == ne_directive) self.assertFalse(self.comment == ne_comment) + def test_parsed_paths(self): + mock_p = mock.MagicMock(return_value=['/path/file.conf', + '/another/path', + '/path/other.conf']) + mock_s = mock.MagicMock(return_value=['/path/*.conf', '/another/path']) + self.block.primary.parsed_paths = mock_p + self.block.secondary.parsed_paths = mock_s + self.block.parsed_paths() + self.assertTrue(mock_p.called) + self.assertTrue(mock_s.called) + + def test_parsed_paths_error(self): + mock_p = mock.MagicMock(return_value=['/path/file.conf']) + mock_s = mock.MagicMock(return_value=['/path/*.conf', '/another/path']) + self.block.primary.parsed_paths = mock_p + self.block.secondary.parsed_paths = mock_s + with self.assertRaises(AssertionError): + self.block.parsed_paths() + def test_find_ancestors(self): primarymock = mock.MagicMock(return_value=[]) secondarymock = mock.MagicMock(return_value=[]) From 4401eacaace73583bfeb591f079e1fb2fb68bab2 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Thu, 19 Dec 2019 10:51:41 +0200 Subject: [PATCH 023/122] Implement get_virtual_hosts() for ParserNode interfaces (#7564) --- certbot-apache/certbot_apache/apache_util.py | 17 +++ certbot-apache/certbot_apache/assertions.py | 22 ++++ certbot-apache/certbot_apache/augeasparser.py | 36 +++++- certbot-apache/certbot_apache/configurator.py | 103 +++++++++++++++++- certbot-apache/certbot_apache/obj.py | 3 +- .../tests/parsernode_configurator_test.py | 36 ++++++ 6 files changed, 210 insertions(+), 7 deletions(-) create mode 100644 certbot-apache/certbot_apache/tests/parsernode_configurator_test.py diff --git a/certbot-apache/certbot_apache/apache_util.py b/certbot-apache/certbot_apache/apache_util.py index 70febc949..085ccddc8 100644 --- a/certbot-apache/certbot_apache/apache_util.py +++ b/certbot-apache/certbot_apache/apache_util.py @@ -1,5 +1,6 @@ """ Utility functions for certbot-apache plugin """ import binascii +import fnmatch import logging import re import subprocess @@ -114,6 +115,22 @@ def unique_id(): return binascii.hexlify(os.urandom(16)).decode("utf-8") +def included_in_paths(filepath, paths): + """ + Returns true if the filepath is included in the list of paths + that may contain full paths or wildcard paths that need to be + expanded. + + :param str filepath: Filepath to check + :params list paths: List of paths to check against + + :returns: True if included + :rtype: bool + """ + + return any([fnmatch.fnmatch(filepath, path) for path in paths]) + + def parse_defines(apachectl): """ Gets Defines from httpd process and returns a dictionary of diff --git a/certbot-apache/certbot_apache/assertions.py b/certbot-apache/certbot_apache/assertions.py index c7a61f446..1a5ce2096 100644 --- a/certbot-apache/certbot_apache/assertions.py +++ b/certbot-apache/certbot_apache/assertions.py @@ -60,6 +60,8 @@ def assertEqualDirective(first, second): def isPass(value): # pragma: no cover """Checks if the value is set to PASS""" + if isinstance(value, bool): + return True return PASS in value def isPassDirective(block): @@ -105,6 +107,26 @@ def assertEqualSimple(first, second): if not isPass(first) and not isPass(second): assert first == second +def isEqualVirtualHost(first, second): + """ + Checks that two VirtualHost objects are similar. There are some built + in differences with the implementations: VirtualHost created by ParserNode + implementation doesn't have "path" defined, as it was used for Augeas path + and that cannot obviously be used in the future. Similarly the legacy + version lacks "node" variable, that has a reference to the BlockNode for the + VirtualHost. + """ + return ( + first.name == second.name and + first.aliases == second.aliases and + first.filep == second.filep and + first.addrs == second.addrs and + first.ssl == second.ssl and + first.enabled == second.enabled and + first.modmacro == second.modmacro and + first.ancestor == second.ancestor + ) + def assertEqualPathsList(first, second): # pragma: no cover """ Checks that the two lists of file paths match. This assertion allows for wildcard diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py index d2771c9d2..1c6ce6675 100644 --- a/certbot-apache/certbot_apache/augeasparser.py +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -115,7 +115,8 @@ class AugeasParserNode(interfaces.ParserNode): while True: # Get the path of ancestor node parent = parent.rpartition("/")[0] - if not parent: + # Root of the tree + if not parent or parent == "/files": break anc = self._create_blocknode(parent) if anc.name.lower() == name.lower(): @@ -134,7 +135,13 @@ class AugeasParserNode(interfaces.ParserNode): name = self._aug_get_name(path) metadata = {"augeasparser": self.parser, "augeaspath": path} + # Check if the file was included from the root config or initial state + enabled = self.parser.parsed_in_original( + apache_util.get_file_path(path) + ) + return AugeasBlockNode(name=name, + enabled=enabled, ancestor=assertions.PASS, filepath=apache_util.get_file_path(path), metadata=metadata) @@ -265,10 +272,15 @@ class AugeasBlockNode(AugeasDirectiveNode): # Create the new block self.parser.aug.insert(insertpath, name, before) + # Check if the file was included from the root config or initial state + enabled = self.parser.parsed_in_original( + apache_util.get_file_path(realpath) + ) # Parameters will be set at the initialization of the new object new_block = AugeasBlockNode(name=name, parameters=parameters, + enabled=enabled, ancestor=assertions.PASS, filepath=apache_util.get_file_path(realpath), metadata=new_metadata) @@ -291,9 +303,14 @@ class AugeasBlockNode(AugeasDirectiveNode): self.parser.aug.insert(insertpath, "directive", before) # Set the directive key self.parser.aug.set(realpath, name) + # Check if the file was included from the root config or initial state + enabled = self.parser.parsed_in_original( + apache_util.get_file_path(realpath) + ) new_dir = AugeasDirectiveNode(name=name, parameters=parameters, + enabled=enabled, ancestor=assertions.PASS, filepath=apache_util.get_file_path(realpath), metadata=new_metadata) @@ -394,8 +411,14 @@ class AugeasBlockNode(AugeasDirectiveNode): :returns: list of file paths of files that have been parsed """ - parsed_paths = self.parser.aug.match("/augeas/load/Httpd/incl") - return [self.parser.aug.get(path) for path in parsed_paths] + res_paths = [] + + paths = self.parser.existing_paths + for directory in paths: + for filename in paths[directory]: + res_paths.append(os.path.join(directory, filename)) + + return res_paths def _create_commentnode(self, path): """Helper function to create a CommentNode from Augeas path""" @@ -416,10 +439,13 @@ class AugeasBlockNode(AugeasDirectiveNode): name = self.parser.get_arg(path) metadata = {"augeasparser": self.parser, "augeaspath": path} - # Because of the dynamic nature, and the fact that we're not populating - # the complete ParserNode tree, we use the search parent as ancestor + # Check if the file was included from the root config or initial state + enabled = self.parser.parsed_in_original( + apache_util.get_file_path(path) + ) return AugeasDirectiveNode(name=name, ancestor=assertions.PASS, + enabled=enabled, filepath=apache_util.get_file_path(path), metadata=metadata) diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index 9a9dec7a8..d4466cc53 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -202,7 +202,11 @@ class ApacheConfigurator(common.Installer): self._autohsts = {} # type: Dict[str, Dict[str, Union[int, float]]] # Reverter save notes self.save_notes = "" - + # Should we use ParserNode implementation instead of the old behavior + self.USE_PARSERNODE = False + # Saves the list of file paths that were parsed initially, and + # not added to parser tree by self.conf("vhost-root") for example. + self.parsed_paths = [] # type: List[str] # These will be set in the prepare function self._prepared = False self.parser = None @@ -261,6 +265,7 @@ class ApacheConfigurator(common.Installer): "augeaspath": self.parser.get_root_augpath(), "ac_ast": None} self.parser_root = self.get_parsernode_root(pn_meta) + self.parsed_paths = self.parser_root.parsed_paths() # Check for errors in parsing files with Augeas self.parser.check_parsing_errors("httpd.aug") @@ -897,6 +902,29 @@ class ApacheConfigurator(common.Installer): return vhost def get_virtual_hosts(self): + """ + Temporary wrapper for legacy and ParserNode version for + get_virtual_hosts. This should be replaced with the ParserNode + implementation when ready. + """ + + v1_vhosts = self.get_virtual_hosts_v1() + v2_vhosts = self.get_virtual_hosts_v2() + + for v1_vh in v1_vhosts: + found = False + for v2_vh in v2_vhosts: + if assertions.isEqualVirtualHost(v1_vh, v2_vh): + found = True + break + if not found: + raise AssertionError("Equivalent for {} was not found".format(v1_vh.path)) + + if self.USE_PARSERNODE: + return v2_vhosts + return v1_vhosts + + def get_virtual_hosts_v1(self): """Returns list of virtual hosts found in the Apache configuration. :returns: List of :class:`~certbot_apache.obj.VirtualHost` @@ -949,6 +977,79 @@ class ApacheConfigurator(common.Installer): vhs.append(new_vhost) return vhs + def get_virtual_hosts_v2(self): + """Returns list of virtual hosts found in the Apache configuration using + ParserNode interface. + :returns: List of :class:`~certbot_apache.obj.VirtualHost` + objects found in configuration + :rtype: list + """ + + vhs = [] + vhosts = self.parser_root.find_blocks("VirtualHost", exclude=False) + for vhblock in vhosts: + vhs.append(self._create_vhost_v2(vhblock)) + return vhs + + def _create_vhost_v2(self, node): + """Used by get_virtual_hosts_v2 to create vhost objects using ParserNode + interfaces. + :param interfaces.BlockNode node: The BlockNode object of VirtualHost block + :returns: newly created vhost + :rtype: :class:`~certbot_apache.obj.VirtualHost` + """ + addrs = set() + for param in node.parameters: + addrs.add(obj.Addr.fromstring(param)) + + is_ssl = False + sslengine = node.find_directives("SSLEngine") + if sslengine: + for directive in sslengine: + if directive.parameters[0].lower() == "on": + is_ssl = True + break + + # "SSLEngine on" might be set outside of + # Treat vhosts with port 443 as ssl vhosts + for addr in addrs: + if addr.get_port() == "443": + is_ssl = True + + enabled = apache_util.included_in_paths(node.filepath, self.parsed_paths) + + macro = False + # Check if the VirtualHost is contained in a mod_macro block + if node.find_ancestors("Macro"): + macro = True + vhost = obj.VirtualHost( + node.filepath, None, addrs, is_ssl, enabled, modmacro=macro, node=node + ) + self._populate_vhost_names_v2(vhost) + return vhost + + def _populate_vhost_names_v2(self, vhost): + """Helper function that populates the VirtualHost names. + :param host: In progress vhost whose names will be added + :type host: :class:`~certbot_apache.obj.VirtualHost` + """ + + servername_match = vhost.node.find_directives("ServerName", + exclude=False) + serveralias_match = vhost.node.find_directives("ServerAlias", + exclude=False) + + servername = None + if servername_match: + servername = servername_match[-1].parameters[-1] + + if not vhost.modmacro: + for alias in serveralias_match: + for serveralias in alias.parameters: + vhost.aliases.add(serveralias) + vhost.name = servername + + def is_name_vhost(self, target_addr): """Returns if vhost is a name based vhost diff --git a/certbot-apache/certbot_apache/obj.py b/certbot-apache/certbot_apache/obj.py index 22abc85cd..939251802 100644 --- a/certbot-apache/certbot_apache/obj.py +++ b/certbot-apache/certbot_apache/obj.py @@ -124,7 +124,7 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods strip_name = re.compile(r"^(?:.+://)?([^ :$]*)") def __init__(self, filep, path, addrs, ssl, enabled, name=None, - aliases=None, modmacro=False, ancestor=None): + aliases=None, modmacro=False, ancestor=None, node=None): # pylint: disable=too-many-arguments """Initialize a VH.""" @@ -137,6 +137,7 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods self.enabled = enabled self.modmacro = modmacro self.ancestor = ancestor + self.node = node def get_names(self): """Return a set of all names.""" diff --git a/certbot-apache/certbot_apache/tests/parsernode_configurator_test.py b/certbot-apache/certbot_apache/tests/parsernode_configurator_test.py new file mode 100644 index 000000000..97f07d3d2 --- /dev/null +++ b/certbot-apache/certbot_apache/tests/parsernode_configurator_test.py @@ -0,0 +1,36 @@ +"""Tests for ApacheConfigurator for AugeasParserNode classes""" +import unittest + +import mock + +from certbot_apache.tests import util + + +class ConfiguratorParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-methods + """Test AugeasParserNode using available test configurations""" + + def setUp(self): # pylint: disable=arguments-differ + super(ConfiguratorParserNodeTest, self).setUp() + + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir) + self.vh_truth = util.get_vh_truth( + self.temp_dir, "debian_apache_2_4/multiple_vhosts") + + def test_parsernode_get_vhosts(self): + self.config.USE_PARSERNODE = True + vhosts = self.config.get_virtual_hosts() + # Legacy get_virtual_hosts() do not set the node + self.assertTrue(vhosts[0].node is not None) + + def test_parsernode_get_vhosts_mismatch(self): + vhosts = self.config.get_virtual_hosts_v2() + # One of the returned VirtualHost objects differs + vhosts[0].name = "IdidntExpectThat" + self.config.get_virtual_hosts_v2 = mock.MagicMock(return_value=vhosts) + with self.assertRaises(AssertionError): + _ = self.config.get_virtual_hosts() + + +if __name__ == "__main__": + unittest.main() # pragma: no cover From 887d72fd5d9b77d3cea914ffed23b9d62e93c5e7 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 2 Jan 2020 21:48:55 +0100 Subject: [PATCH 024/122] Remove POST-as-GET fallback to GET (#6994) --- acme/acme/client.py | 22 +++------------------- acme/tests/client_test.py | 13 ------------- 2 files changed, 3 insertions(+), 32 deletions(-) diff --git a/acme/acme/client.py b/acme/acme/client.py index aabcbe312..527430120 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -776,29 +776,13 @@ class ClientV2(ClientBase): def _post_as_get(self, *args, **kwargs): """ - Send GET request using the POST-as-GET protocol if needed. - The request will be first issued using POST-as-GET for ACME v2. If the ACME CA servers do - not support this yet and return an error, request will be retried using GET. - For ACME v1, only GET request will be tried, as POST-as-GET is not supported. + Send GET request using the POST-as-GET protocol. :param args: :param kwargs: :return: """ - if self.acme_version >= 2: - # We add an empty payload for POST-as-GET requests - new_args = args[:1] + (None,) + args[1:] - try: - return self._post(*new_args, **kwargs) - except messages.Error as error: - if error.code == 'malformed': - logger.debug('Error during a POST-as-GET request, ' - 'your ACME CA server may not support it:\n%s', error) - logger.debug('Retrying request with GET.') - else: # pragma: no cover - raise - - # If POST-as-GET is not supported yet, we use a GET instead. - return self.net.get(*args, **kwargs) + new_args = args[:1] + (None,) + args[1:] + return self._post(*new_args, **kwargs) class BackwardsCompatibleClientV2(object): diff --git a/acme/tests/client_test.py b/acme/tests/client_test.py index 192cd2949..a38fedbd6 100644 --- a/acme/tests/client_test.py +++ b/acme/tests/client_test.py @@ -885,19 +885,6 @@ class ClientV2Test(ClientTestBase): new_nonce_url='https://www.letsencrypt-demo.org/acme/new-nonce') self.client.net.get.assert_not_called() - class FakeError(messages.Error): - """Fake error to reproduce a malformed request ACME error""" - def __init__(self): # pylint: disable=super-init-not-called - pass - @property - def code(self): - return 'malformed' - self.client.net.post.side_effect = FakeError() - - self.client.poll(self.authzr2) # pylint: disable=protected-access - - self.client.net.get.assert_called_once_with(self.authzr2.uri) - class MockJSONDeSerializable(jose.JSONDeSerializable): # pylint: disable=missing-docstring From fda655370a564a42ce3b6fc85e91d263a794aaa4 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 2 Jan 2020 23:44:16 +0100 Subject: [PATCH 025/122] Update CHANGELOG.md (#7659) --- certbot/CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 0e8c20a50..f2671a7b4 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -10,7 +10,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed -* +* Removed the fallback introduced with 0.34.0 in `acme` to retry a POST-as-GET + request as a GET request when the targeted ACME CA server seems to not support + POST-as-GET requests. ### Fixed From 70be256c663be8d4163b4abb5587da79dbd2da2a Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Fri, 3 Jan 2020 01:44:11 +0200 Subject: [PATCH 026/122] Fix gating to ensure that no parsernode functionality is run unless explicitly requested (#7654) --- certbot-apache/certbot_apache/configurator.py | 33 ++++++++++--------- .../certbot_apache/tests/augeasnode_test.py | 2 +- .../tests/parsernode_configurator_test.py | 3 +- certbot-apache/certbot_apache/tests/util.py | 5 +-- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index d4466cc53..c189552d5 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -187,6 +187,7 @@ class ApacheConfigurator(common.Installer): """ version = kwargs.pop("version", None) + use_parsernode = kwargs.pop("use_parsernode", False) super(ApacheConfigurator, self).__init__(*args, **kwargs) # Add name_server association dict @@ -203,7 +204,7 @@ class ApacheConfigurator(common.Installer): # Reverter save notes self.save_notes = "" # Should we use ParserNode implementation instead of the old behavior - self.USE_PARSERNODE = False + self.USE_PARSERNODE = use_parsernode # Saves the list of file paths that were parsed initially, and # not added to parser tree by self.conf("vhost-root") for example. self.parsed_paths = [] # type: List[str] @@ -264,8 +265,9 @@ class ApacheConfigurator(common.Installer): pn_meta = {"augeasparser": self.parser, "augeaspath": self.parser.get_root_augpath(), "ac_ast": None} - self.parser_root = self.get_parsernode_root(pn_meta) - self.parsed_paths = self.parser_root.parsed_paths() + if self.USE_PARSERNODE: + self.parser_root = self.get_parsernode_root(pn_meta) + self.parsed_paths = self.parser_root.parsed_paths() # Check for errors in parsing files with Augeas self.parser.check_parsing_errors("httpd.aug") @@ -909,18 +911,18 @@ class ApacheConfigurator(common.Installer): """ v1_vhosts = self.get_virtual_hosts_v1() - v2_vhosts = self.get_virtual_hosts_v2() - - for v1_vh in v1_vhosts: - found = False - for v2_vh in v2_vhosts: - if assertions.isEqualVirtualHost(v1_vh, v2_vh): - found = True - break - if not found: - raise AssertionError("Equivalent for {} was not found".format(v1_vh.path)) - if self.USE_PARSERNODE: + v2_vhosts = self.get_virtual_hosts_v2() + + for v1_vh in v1_vhosts: + found = False + for v2_vh in v2_vhosts: + if assertions.isEqualVirtualHost(v1_vh, v2_vh): + found = True + break + if not found: + raise AssertionError("Equivalent for {} was not found".format(v1_vh.path)) + return v2_vhosts return v1_vhosts @@ -1003,7 +1005,8 @@ class ApacheConfigurator(common.Installer): addrs.add(obj.Addr.fromstring(param)) is_ssl = False - sslengine = node.find_directives("SSLEngine") + # Exclusion to match the behavior in get_virtual_hosts_v2 + sslengine = node.find_directives("SSLEngine", exclude=False) if sslengine: for directive in sslengine: if directive.parameters[0].lower() == "on": diff --git a/certbot-apache/certbot_apache/tests/augeasnode_test.py b/certbot-apache/certbot_apache/tests/augeasnode_test.py index 849a468c9..d090c4aa5 100644 --- a/certbot-apache/certbot_apache/tests/augeasnode_test.py +++ b/certbot-apache/certbot_apache/tests/augeasnode_test.py @@ -16,7 +16,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- super(AugeasParserNodeTest, self).setUp() self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, self.work_dir) + self.config_path, self.vhost_path, self.config_dir, self.work_dir, use_parsernode=True) self.vh_truth = util.get_vh_truth( self.temp_dir, "debian_apache_2_4/multiple_vhosts") diff --git a/certbot-apache/certbot_apache/tests/parsernode_configurator_test.py b/certbot-apache/certbot_apache/tests/parsernode_configurator_test.py index 97f07d3d2..96bd63fdd 100644 --- a/certbot-apache/certbot_apache/tests/parsernode_configurator_test.py +++ b/certbot-apache/certbot_apache/tests/parsernode_configurator_test.py @@ -13,7 +13,8 @@ class ConfiguratorParserNodeTest(util.ApacheTest): # pylint: disable=too-many-p super(ConfiguratorParserNodeTest, self).setUp() self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, self.work_dir) + self.config_path, self.vhost_path, self.config_dir, + self.work_dir, use_parsernode=True) self.vh_truth = util.get_vh_truth( self.temp_dir, "debian_apache_2_4/multiple_vhosts") diff --git a/certbot-apache/certbot_apache/tests/util.py b/certbot-apache/certbot_apache/tests/util.py index 4155c93c0..ee941f507 100644 --- a/certbot-apache/certbot_apache/tests/util.py +++ b/certbot-apache/certbot_apache/tests/util.py @@ -85,7 +85,8 @@ def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-loc config_path, vhost_path, config_dir, work_dir, version=(2, 4, 7), os_info="generic", - conf_vhost_path=None): + conf_vhost_path=None, + use_parsernode=False): """Create an Apache Configurator with the specified options. :param conf: Function that returns binary paths. self.conf in Configurator @@ -118,7 +119,7 @@ def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-loc except KeyError: config_class = configurator.ApacheConfigurator config = config_class(config=mock_le_config, name="apache", - version=version) + version=version, use_parsernode=use_parsernode) if not conf_vhost_path: config_class.OS_DEFAULTS["vhost_root"] = vhost_path else: From 3b065238b39a133eb8f450ca3bc7aba2aa04ea33 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Mon, 6 Jan 2020 17:19:33 +0200 Subject: [PATCH 027/122] Modifications needed for merging to master --- .../certbot_apache/_internal/apacheparser.py | 12 ++++++------ .../certbot_apache/_internal/assertions.py | 6 +++--- .../certbot_apache/_internal/augeasparser.py | 10 +++++----- .../certbot_apache/_internal/dualparser.py | 6 +++--- certbot-apache/tests/augeasnode_test.py | 19 ++++++++++--------- certbot-apache/tests/centos_test.py | 4 ++-- certbot-apache/tests/configurator_test.py | 6 +++--- certbot-apache/tests/debian_test.py | 2 +- certbot-apache/tests/dualnode_test.py | 6 +++--- certbot-apache/tests/fedora_test.py | 4 ++-- certbot-apache/tests/gentoo_test.py | 4 ++-- certbot-apache/tests/parser_test.py | 12 ++++++------ .../tests/parsernode_configurator_test.py | 2 +- certbot-apache/tests/parsernode_test.py | 4 ++-- certbot-apache/tests/parsernode_util_test.py | 2 +- certbot-apache/tests/util.py | 2 +- 16 files changed, 51 insertions(+), 50 deletions(-) diff --git a/certbot-apache/certbot_apache/_internal/apacheparser.py b/certbot-apache/certbot_apache/_internal/apacheparser.py index 969a4ab69..77f4517fe 100644 --- a/certbot-apache/certbot_apache/_internal/apacheparser.py +++ b/certbot-apache/certbot_apache/_internal/apacheparser.py @@ -1,8 +1,8 @@ """ apacheconfig implementation of the ParserNode interfaces """ -from certbot_apache import assertions -from certbot_apache import interfaces -from certbot_apache import parsernode_util as util +from certbot_apache._internal import assertions +from certbot_apache._internal import interfaces +from certbot_apache._internal import parsernode_util as util class ApacheParserNode(interfaces.ParserNode): @@ -73,9 +73,9 @@ class ApacheDirectiveNode(ApacheParserNode): self.metadata == other.metadata) return False - def set_parameters(self, parameters): + def set_parameters(self, _parameters): """Sets the parameters for DirectiveNode""" - pass + return class ApacheBlockNode(ApacheDirectiveNode): @@ -153,7 +153,7 @@ class ApacheBlockNode(ApacheDirectiveNode): def delete_child(self, child): # pragma: no cover """Deletes a ParserNode from the sequence of children""" - pass + return def unsaved_files(self): # pragma: no cover """Returns a list of unsaved filepaths""" diff --git a/certbot-apache/certbot_apache/_internal/assertions.py b/certbot-apache/certbot_apache/_internal/assertions.py index 1a5ce2096..e1b4cdcc8 100644 --- a/certbot-apache/certbot_apache/_internal/assertions.py +++ b/certbot-apache/certbot_apache/_internal/assertions.py @@ -1,7 +1,7 @@ """Dual parser node assertions""" import fnmatch -from certbot_apache import interfaces +from certbot_apache._internal import interfaces PASS = "CERTBOT_PASS_ASSERT" @@ -36,8 +36,8 @@ def assertEqualComment(first, second): # pragma: no cover assert isinstance(first, interfaces.CommentNode) assert isinstance(second, interfaces.CommentNode) - if not isPass(first.comment) and not isPass(second.comment): - assert first.comment == second.comment + if not isPass(first.comment) and not isPass(second.comment): # type: ignore + assert first.comment == second.comment # type: ignore def _assertEqualDirectiveComponents(first, second): # pragma: no cover """ Handles assertion for instance variables for DirectiveNode and BlockNode""" diff --git a/certbot-apache/certbot_apache/_internal/augeasparser.py b/certbot-apache/certbot_apache/_internal/augeasparser.py index 1c6ce6675..e1d7c941d 100644 --- a/certbot-apache/certbot_apache/_internal/augeasparser.py +++ b/certbot-apache/certbot_apache/_internal/augeasparser.py @@ -68,11 +68,11 @@ from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-m from certbot import errors from certbot.compat import os -from certbot_apache import apache_util -from certbot_apache import assertions -from certbot_apache import interfaces -from certbot_apache import parser -from certbot_apache import parsernode_util as util +from certbot_apache._internal import apache_util +from certbot_apache._internal import assertions +from certbot_apache._internal import interfaces +from certbot_apache._internal import parser +from certbot_apache._internal import parsernode_util as util class AugeasParserNode(interfaces.ParserNode): diff --git a/certbot-apache/certbot_apache/_internal/dualparser.py b/certbot-apache/certbot_apache/_internal/dualparser.py index ef0800782..aa66cf84c 100644 --- a/certbot-apache/certbot_apache/_internal/dualparser.py +++ b/certbot-apache/certbot_apache/_internal/dualparser.py @@ -1,7 +1,7 @@ """ Dual ParserNode implementation """ -from certbot_apache import assertions -from certbot_apache import augeasparser -from certbot_apache import apacheparser +from certbot_apache._internal import assertions +from certbot_apache._internal import augeasparser +from certbot_apache._internal import apacheparser class DualNodeBase(object): diff --git a/certbot-apache/tests/augeasnode_test.py b/certbot-apache/tests/augeasnode_test.py index d090c4aa5..9d663a05f 100644 --- a/certbot-apache/tests/augeasnode_test.py +++ b/certbot-apache/tests/augeasnode_test.py @@ -1,12 +1,13 @@ """Tests for AugeasParserNode classes""" import mock +import util + from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors -from certbot_apache import assertions +from certbot_apache._internal import assertions -from certbot_apache.tests import util class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-methods @@ -21,19 +22,19 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- self.temp_dir, "debian_apache_2_4/multiple_vhosts") def test_save(self): - with mock.patch('certbot_apache.parser.ApacheParser.save') as mock_save: + with mock.patch('certbot_apache._internal.parser.ApacheParser.save') as mock_save: self.config.parser_root.save("A save message") self.assertTrue(mock_save.called) self.assertEqual(mock_save.call_args[0][0], "A save message") def test_unsaved_files(self): - with mock.patch('certbot_apache.parser.ApacheParser.unsaved_files') as mock_uf: + with mock.patch('certbot_apache._internal.parser.ApacheParser.unsaved_files') as mock_uf: mock_uf.return_value = ["first", "second"] files = self.config.parser_root.unsaved_files() self.assertEqual(files, ["first", "second"]) def test_get_block_node_name(self): - from certbot_apache.augeasparser import AugeasBlockNode + from certbot_apache._internal.augeasparser import AugeasBlockNode block = AugeasBlockNode( name=assertions.PASS, ancestor=None, @@ -102,9 +103,9 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- self.assertTrue("going_to_set_this" in names) def test_set_parameters_atinit(self): - from certbot_apache.augeasparser import AugeasDirectiveNode + from certbot_apache._internal.augeasparser import AugeasDirectiveNode servernames = self.config.parser_root.find_directives("servername") - setparam = "certbot_apache.augeasparser.AugeasDirectiveNode.set_parameters" + setparam = "certbot_apache._internal.augeasparser.AugeasDirectiveNode.set_parameters" with mock.patch(setparam) as mock_set: AugeasDirectiveNode( name=servernames[0].name, @@ -241,7 +242,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- self.assertTrue(vh.primary.metadata["augeaspath"].endswith("VirtualHost[2]")) def test_node_init_error_bad_augeaspath(self): - from certbot_apache.augeasparser import AugeasBlockNode + from certbot_apache._internal.augeasparser import AugeasBlockNode parameters = { "name": assertions.PASS, "ancestor": None, @@ -258,7 +259,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- ) def test_node_init_error_missing_augeaspath(self): - from certbot_apache.augeasparser import AugeasBlockNode + from certbot_apache._internal.augeasparser import AugeasBlockNode parameters = { "name": assertions.PASS, "ancestor": None, diff --git a/certbot-apache/tests/centos_test.py b/certbot-apache/tests/centos_test.py index 8959d73b8..55fee3faa 100644 --- a/certbot-apache/tests/centos_test.py +++ b/certbot-apache/tests/centos_test.py @@ -106,7 +106,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest): def test_get_parser(self): self.assertIsInstance(self.config.parser, override_centos.CentOSParser) - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") def test_opportunistic_httpd_runtime_parsing(self, mock_get): define_val = ( 'Define: TEST1\n' @@ -155,7 +155,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest): raise Exception("Missed: %s" % vhost) # pragma: no cover self.assertEqual(found, 2) - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") def test_get_sysconfig_vars(self, mock_cfg): """Make sure we read the sysconfig OPTIONS variable correctly""" # Return nothing for the process calls diff --git a/certbot-apache/tests/configurator_test.py b/certbot-apache/tests/configurator_test.py index fc4559e17..cbb052155 100644 --- a/certbot-apache/tests/configurator_test.py +++ b/certbot-apache/tests/configurator_test.py @@ -76,7 +76,7 @@ class MultipleVhostsTest(util.ApacheTest): @mock.patch("certbot_apache._internal.parser.ApacheParser") @mock.patch("certbot_apache._internal.configurator.util.exe_exists") @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.get_parsernode_root") - def _test_prepare_locked(self, unused_parser, unused_exe_exists): + def _test_prepare_locked(self, _node, _exists, _parser): try: self.config.prepare() except errors.PluginError as err: @@ -800,7 +800,7 @@ class MultipleVhostsTest(util.ApacheTest): self.assertEqual(mock_restart.call_count, 1) @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") def test_cleanup(self, mock_cfg, mock_restart): mock_cfg.return_value = "" _, achalls = self.get_key_and_achalls() @@ -816,7 +816,7 @@ class MultipleVhostsTest(util.ApacheTest): self.assertFalse(mock_restart.called) @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") def test_cleanup_no_errors(self, mock_cfg, mock_restart): mock_cfg.return_value = "" _, achalls = self.get_key_and_achalls() diff --git a/certbot-apache/tests/debian_test.py b/certbot-apache/tests/debian_test.py index 6e63a9bd3..400e503fb 100644 --- a/certbot-apache/tests/debian_test.py +++ b/certbot-apache/tests/debian_test.py @@ -46,7 +46,7 @@ class MultipleVhostsTestDebian(util.ApacheTest): @mock.patch("certbot.util.run_script") @mock.patch("certbot.util.exe_exists") - @mock.patch("certbot_apache._internal.parser.subprocess.Popen") + @mock.patch("certbot_apache._internal.apache_util.subprocess.Popen") def test_enable_mod(self, mock_popen, mock_exe_exists, mock_run_script): mock_popen().communicate.return_value = ("Define: DUMP_RUN_CFG", "") mock_popen().returncode = 0 diff --git a/certbot-apache/tests/dualnode_test.py b/certbot-apache/tests/dualnode_test.py index b5ab588f0..0871bac78 100644 --- a/certbot-apache/tests/dualnode_test.py +++ b/certbot-apache/tests/dualnode_test.py @@ -3,9 +3,9 @@ import unittest import mock -from certbot_apache import assertions -from certbot_apache import augeasparser -from certbot_apache import dualparser +from certbot_apache._internal import assertions +from certbot_apache._internal import augeasparser +from certbot_apache._internal import dualparser class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-methods diff --git a/certbot-apache/tests/fedora_test.py b/certbot-apache/tests/fedora_test.py index 2bfd6babb..cb1614278 100644 --- a/certbot-apache/tests/fedora_test.py +++ b/certbot-apache/tests/fedora_test.py @@ -100,7 +100,7 @@ class MultipleVhostsTestFedora(util.ApacheTest): def test_get_parser(self): self.assertIsInstance(self.config.parser, override_fedora.FedoraParser) - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") def test_opportunistic_httpd_runtime_parsing(self, mock_get): define_val = ( 'Define: TEST1\n' @@ -155,7 +155,7 @@ class MultipleVhostsTestFedora(util.ApacheTest): raise Exception("Missed: %s" % vhost) # pragma: no cover self.assertEqual(found, 2) - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") def test_get_sysconfig_vars(self, mock_cfg): """Make sure we read the sysconfig OPTIONS variable correctly""" # Return nothing for the process calls diff --git a/certbot-apache/tests/gentoo_test.py b/certbot-apache/tests/gentoo_test.py index 90a163fd3..fb5d192d0 100644 --- a/certbot-apache/tests/gentoo_test.py +++ b/certbot-apache/tests/gentoo_test.py @@ -90,7 +90,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest): for define in defines: self.assertTrue(define in self.config.parser.variables.keys()) - @mock.patch("certbot_apache._internal.parser.ApacheParser.parse_from_subprocess") + @mock.patch("certbot_apache._internal.apache_util.parse_from_subprocess") def test_no_binary_configdump(self, mock_subprocess): """Make sure we don't call binary dumps other than modules from Apache as this is not supported in Gentoo currently""" @@ -104,7 +104,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest): self.config.parser.reset_modules() self.assertTrue(mock_subprocess.called) - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") def test_opportunistic_httpd_runtime_parsing(self, mock_get): mod_val = ( 'Loaded Modules:\n' diff --git a/certbot-apache/tests/parser_test.py b/certbot-apache/tests/parser_test.py index b334ce52e..f5a0a3d11 100644 --- a/certbot-apache/tests/parser_test.py +++ b/certbot-apache/tests/parser_test.py @@ -165,7 +165,7 @@ class BasicParserTest(util.ParserTest): self.assertTrue(mock_logger.debug.called) @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") def test_update_runtime_variables(self, mock_cfg, _): define_val = ( 'ServerRoot: "/etc/apache2"\n' @@ -271,7 +271,7 @@ class BasicParserTest(util.ParserTest): self.assertEqual(mock_parse.call_count, 25) @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") def test_update_runtime_variables_alt_values(self, mock_cfg, _): inc_val = ( 'Included configuration files:\n' @@ -293,7 +293,7 @@ class BasicParserTest(util.ParserTest): # path derived from root configuration Include statements self.assertEqual(mock_parse.call_count, 1) - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") def test_update_runtime_vars_bad_output(self, mock_cfg): mock_cfg.return_value = "Define: TLS=443=24" self.parser.update_runtime_variables() @@ -303,7 +303,7 @@ class BasicParserTest(util.ParserTest): errors.PluginError, self.parser.update_runtime_variables) @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.option") - @mock.patch("certbot_apache._internal.parser.subprocess.Popen") + @mock.patch("certbot_apache._internal.apache_util.subprocess.Popen") def test_update_runtime_vars_bad_ctl(self, mock_popen, mock_opt): mock_popen.side_effect = OSError mock_opt.return_value = "nonexistent" @@ -311,7 +311,7 @@ class BasicParserTest(util.ParserTest): errors.MisconfigurationError, self.parser.update_runtime_variables) - @mock.patch("certbot_apache._internal.parser.subprocess.Popen") + @mock.patch("certbot_apache._internal.apache_util.subprocess.Popen") def test_update_runtime_vars_bad_exit(self, mock_popen): mock_popen().communicate.return_value = ("", "") mock_popen.returncode = -1 @@ -355,7 +355,7 @@ class ParserInitTest(util.ApacheTest): ApacheParser, os.path.relpath(self.config_path), "/dummy/vhostpath", version=(2, 4, 22), configurator=self.config) - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") def test_unparseable(self, mock_cfg): from certbot_apache._internal.parser import ApacheParser mock_cfg.return_value = ('Define: TEST') diff --git a/certbot-apache/tests/parsernode_configurator_test.py b/certbot-apache/tests/parsernode_configurator_test.py index 96bd63fdd..67d65995a 100644 --- a/certbot-apache/tests/parsernode_configurator_test.py +++ b/certbot-apache/tests/parsernode_configurator_test.py @@ -3,7 +3,7 @@ import unittest import mock -from certbot_apache.tests import util +import util class ConfiguratorParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-methods diff --git a/certbot-apache/tests/parsernode_test.py b/certbot-apache/tests/parsernode_test.py index a6caf4814..a86952f53 100644 --- a/certbot-apache/tests/parsernode_test.py +++ b/certbot-apache/tests/parsernode_test.py @@ -2,8 +2,8 @@ import unittest -from certbot_apache import interfaces -from certbot_apache import parsernode_util as util +from certbot_apache._internal import interfaces +from certbot_apache._internal import parsernode_util as util class DummyParserNode(interfaces.ParserNode): diff --git a/certbot-apache/tests/parsernode_util_test.py b/certbot-apache/tests/parsernode_util_test.py index a079759ee..715388da5 100644 --- a/certbot-apache/tests/parsernode_util_test.py +++ b/certbot-apache/tests/parsernode_util_test.py @@ -1,7 +1,7 @@ """ Tests for ParserNode utils """ import unittest -from certbot_apache import parsernode_util as util +from certbot_apache._internal import parsernode_util as util class ParserNodeUtilTest(unittest.TestCase): diff --git a/certbot-apache/tests/util.py b/certbot-apache/tests/util.py index 60704ec66..ccd0b274d 100644 --- a/certbot-apache/tests/util.py +++ b/certbot-apache/tests/util.py @@ -111,7 +111,7 @@ def get_apache_configurator( mock_exe_exists.return_value = True with mock.patch("certbot_apache._internal.parser.ApacheParser." "update_runtime_variables"): - with mock.patch("certbot_apache.apache_util.parse_from_subprocess") as mock_sp: + with mock.patch("certbot_apache._internal.apache_util.parse_from_subprocess") as mock_sp: mock_sp.return_value = [] try: config_class = entrypoint.OVERRIDE_CLASSES[os_info] From 0e78436b059e99a5fd07ea7329b911bcb3f0e147 Mon Sep 17 00:00:00 2001 From: sydneyli Date: Tue, 7 Jan 2020 09:57:43 -0800 Subject: [PATCH 028/122] [Apache v2] Add apacheconfig as a dependency (#7643) * Add apacheconfig as a dependency. * Change apacheconfig to a dev dependency * Bump apacheconfig dep to 0.3.1 --- .travis.yml | 2 +- certbot-apache/setup.py | 6 ++++++ tools/dev_constraints.txt | 2 ++ tools/oldest_constraints.txt | 1 + tox.ini | 6 ++++++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 63129c9b1..9f18b04ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,7 +57,7 @@ matrix: # cryptography we support cannot be compiled against the version of # OpenSSL in Xenial or newer. dist: trusty - env: TOXENV='py27-{acme,apache,certbot,dns,nginx}-oldest' + env: TOXENV='py27-{acme,apache,apache-v2,certbot,dns,nginx}-oldest' <<: *not-on-master - python: "3.4" env: TOXENV=py34 diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 204d01620..c48b8a336 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -18,6 +18,9 @@ install_requires = [ 'zope.interface', ] +dev_extras = [ + 'apacheconfig>=0.3.1', +] class PyTest(TestCommand): user_options = [] @@ -69,6 +72,9 @@ setup( packages=find_packages(), include_package_data=True, install_requires=install_requires, + extras_require={ + 'dev': dev_extras, + }, entry_points={ 'certbot.plugins': [ 'apache = certbot_apache._internal.entrypoint:ENTRYPOINT', diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index d5d78c96a..94a59a6dd 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -3,6 +3,7 @@ # Some dev package versions specified here may be overridden by higher level constraints # files during tests (eg. letsencrypt-auto-source/pieces/dependency-requirements.txt). alabaster==0.7.10 +apacheconfig==0.3.1 apipkg==1.4 appnope==0.1.0 asn1crypto==0.22.0 @@ -64,6 +65,7 @@ pexpect==4.7.0 pickleshare==0.7.4 pkginfo==1.4.2 pluggy==0.13.0 +ply==3.4 prompt-toolkit==1.0.15 ptyprocess==0.6.0 py==1.8.0 diff --git a/tools/oldest_constraints.txt b/tools/oldest_constraints.txt index c5a5c5aa0..6154b497a 100644 --- a/tools/oldest_constraints.txt +++ b/tools/oldest_constraints.txt @@ -40,6 +40,7 @@ pytz==2012rc0 google-api-python-client==1.5.5 # Our setup.py constraints +apacheconfig==0.3.1 cloudflare==1.5.1 cryptography==1.2.3 parsedatetime==1.3 diff --git a/tox.ini b/tox.ini index 5f1a9a426..31a8a8578 100644 --- a/tox.ini +++ b/tox.ini @@ -90,6 +90,12 @@ commands = setenv = {[testenv:py27-oldest]setenv} +[testenv:py27-apache-v2-oldest] +commands = + {[base]install_and_test} certbot-apache[dev] +setenv = + {[testenv:py27-oldest]setenv} + [testenv:py27-certbot-oldest] commands = {[base]install_and_test} certbot[dev] From 84c1b912d9ce82c40f868c24200ed2e4c7d28cfc Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 8 Jan 2020 16:36:34 +0100 Subject: [PATCH 029/122] Implement a sunset mechanism in certbot-auto for systems not supported anymore (#7587) * Sunset mechanism * Simplify code * Update letsencrypt-auto-source/letsencrypt-auto.template Co-Authored-By: Brad Warren * Update template * Deprecate for all RHEL/CentOS 6 32bits flavors * Add a wrapper to uname to do tests on fake 32 bits versions * Replace all occurences * Add some tests about sunset mechanism * Update letsencrypt-auto-source/tests/centos6_tests.sh Co-Authored-By: Brad Warren * Update letsencrypt-auto-source/tests/centos6_tests.sh Co-Authored-By: Brad Warren * Various corrections * Recreate script * Update comment position * Test also install only * Fix docker * Update letsencrypt-auto-source/tests/centos6_tests.sh Co-Authored-By: Brad Warren * What error command is doing here ? * Fix permissions * Rebuild script * Add changelog * Update letsencrypt-auto-source/letsencrypt-auto.template Co-Authored-By: Brad Warren * Update changelog * Trigger CI * Handle old venv path * Modify test * Fix test error detection from subpaths * Edit echo * Use set -e * Update letsencrypt-auto-source/letsencrypt-auto.template Co-Authored-By: Brad Warren * Corrections Co-authored-by: Brad Warren --- certbot/CHANGELOG.md | 4 + letsencrypt-auto-source/Dockerfile.centos6 | 6 ++ letsencrypt-auto-source/letsencrypt-auto | 86 +++++++++++++----- .../letsencrypt-auto.template | 86 +++++++++++++----- .../tests/centos6_tests.sh | 88 +++++++++++++------ .../tests/uname_wrapper.sh | 10 +++ 6 files changed, 211 insertions(+), 69 deletions(-) create mode 100644 letsencrypt-auto-source/tests/uname_wrapper.sh diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index f2671a7b4..5209de607 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -13,6 +13,10 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Removed the fallback introduced with 0.34.0 in `acme` to retry a POST-as-GET request as a GET request when the targeted ACME CA server seems to not support POST-as-GET requests. +* certbot-auto no longer supports architectures other than x86_64 on RHEL 6 + based systems. Existing certbot-auto installations affected by this will + continue to work, but they will no longer receive updates. To install a + newer version of Certbot on these systems, you should update your OS. ### Fixed diff --git a/letsencrypt-auto-source/Dockerfile.centos6 b/letsencrypt-auto-source/Dockerfile.centos6 index 09aa52dcd..f152e1bac 100644 --- a/letsencrypt-auto-source/Dockerfile.centos6 +++ b/letsencrypt-auto-source/Dockerfile.centos6 @@ -30,6 +30,12 @@ RUN update-ca-trust # Copy code: COPY . /home/lea/certbot/letsencrypt-auto-source +# Tweak uname binary for tests on fake 32bits +COPY tests/uname_wrapper.sh /bin +RUN mv /bin/uname /bin/uname_orig \ + && mv /bin/uname_wrapper.sh /bin/uname \ + && chmod +x /bin/uname + USER lea WORKDIR /home/lea diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 2f48751f2..2e658f642 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -758,6 +758,11 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` + if [ "$PYVER" -eq 26 -a $(uname -m) != 'x86_64' ]; then + # 32 bits CentOS 6 and affiliates are not supported anymore by certbot-auto. + DEPRECATED_OS=1 + fi + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an # error, RPM_DIST_VERSION is set to "unknown". @@ -870,6 +875,13 @@ if [ "$NO_BOOTSTRAP" = 1 ]; then unset BOOTSTRAP_VERSION fi +if [ "$DEPRECATED_OS" = 1 ]; then + Bootstrap() { + error "Skipping bootstrap because certbot-auto is deprecated on this system." + } + unset BOOTSTRAP_VERSION +fi + # Sets PREV_BOOTSTRAP_VERSION to the identifier for the bootstrap script used # to install OS dependencies on this system. PREV_BOOTSTRAP_VERSION isn't set # if it is unknown how OS dependencies were installed on this system. @@ -1067,6 +1079,28 @@ if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. shift 1 # the --le-auto-phase2 arg + + if [ "$DEPRECATED_OS" = 1 ]; then + # Phase 2 damage control mode for deprecated OSes. + # In this situation, we bypass any bootstrap or certbot venv setup. + error "Your system is not supported by certbot-auto anymore." + + if [ ! -d "$VENV_PATH" ] && OldVenvExists; then + VENV_BIN="$OLD_VENV_PATH/bin" + fi + + if [ -f "$VENV_BIN/letsencrypt" -a "$INSTALL_ONLY" != 1 ]; then + error "Certbot will no longer receive updates." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + "$VENV_BIN/letsencrypt" "$@" + exit 0 + else + error "Certbot cannot be installed." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + exit 1 + fi + fi + SetPrevBootstrapVersion if [ -z "$PHASE_1_VERSION" -a "$USE_PYTHON_3" = 1 ]; then @@ -1617,6 +1651,9 @@ UNLIKELY_EOF say "Installation succeeded." fi + # If you're modifying any of the code after this point in this current `if` block, you + # may need to update the "$DEPRECATED_OS" = 1 case at the beginning of phase 2 as well. + if [ "$INSTALL_ONLY" = 1 ]; then say "Certbot is installed." exit 0 @@ -1828,30 +1865,35 @@ UNLIKELY_EOF error "WARNING: unable to check for updates." fi - LE_VERSION_STATE=`CompareVersions "$LE_PYTHON" "$LE_AUTO_VERSION" "$REMOTE_VERSION"` - if [ "$LE_VERSION_STATE" = "UNOFFICIAL" ]; then - say "Unofficial certbot-auto version detected, self-upgrade is disabled: $LE_AUTO_VERSION" - elif [ "$LE_VERSION_STATE" = "OUTDATED" ]; then - say "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..." + # If for any reason REMOTE_VERSION is not set, let's assume certbot-auto is up-to-date, + # and do not go into the self-upgrading process. + if [ -n "$REMOTE_VERSION" ]; then + LE_VERSION_STATE=`CompareVersions "$LE_PYTHON" "$LE_AUTO_VERSION" "$REMOTE_VERSION"` - # Now we drop into Python so we don't have to install even more - # dependencies (curl, etc.), for better flow control, and for the option of - # future Windows compatibility. - "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" + if [ "$LE_VERSION_STATE" = "UNOFFICIAL" ]; then + say "Unofficial certbot-auto version detected, self-upgrade is disabled: $LE_AUTO_VERSION" + elif [ "$LE_VERSION_STATE" = "OUTDATED" ]; then + say "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..." - # Install new copy of certbot-auto. - # TODO: Deal with quotes in pathnames. - say "Replacing certbot-auto..." - # Clone permissions with cp. chmod and chown don't have a --reference - # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: - cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" - cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" - # Using mv rather than cp leaves the old file descriptor pointing to the - # original copy so the shell can continue to read it unmolested. mv across - # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the - # cp is unlikely to fail if the rm doesn't. - mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" - fi # A newer version is available. + # Now we drop into Python so we don't have to install even more + # dependencies (curl, etc.), for better flow control, and for the option of + # future Windows compatibility. + "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" + + # Install new copy of certbot-auto. + # TODO: Deal with quotes in pathnames. + say "Replacing certbot-auto..." + # Clone permissions with cp. chmod and chown don't have a --reference + # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: + cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" + cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" + # Using mv rather than cp leaves the old file descriptor pointing to the + # original copy so the shell can continue to read it unmolested. mv across + # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the + # cp is unlikely to fail if the rm doesn't. + mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" + fi # A newer version is available. + fi fi # Self-upgrading is allowed. RerunWithArgs --le-auto-phase2 "$@" diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index 31c5bb134..e481fd6f3 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -333,6 +333,11 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` + if [ "$PYVER" -eq 26 -a $(uname -m) != 'x86_64' ]; then + # 32 bits CentOS 6 and affiliates are not supported anymore by certbot-auto. + DEPRECATED_OS=1 + fi + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an # error, RPM_DIST_VERSION is set to "unknown". @@ -445,6 +450,13 @@ if [ "$NO_BOOTSTRAP" = 1 ]; then unset BOOTSTRAP_VERSION fi +if [ "$DEPRECATED_OS" = 1 ]; then + Bootstrap() { + error "Skipping bootstrap because certbot-auto is deprecated on this system." + } + unset BOOTSTRAP_VERSION +fi + # Sets PREV_BOOTSTRAP_VERSION to the identifier for the bootstrap script used # to install OS dependencies on this system. PREV_BOOTSTRAP_VERSION isn't set # if it is unknown how OS dependencies were installed on this system. @@ -534,6 +546,28 @@ if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. shift 1 # the --le-auto-phase2 arg + + if [ "$DEPRECATED_OS" = 1 ]; then + # Phase 2 damage control mode for deprecated OSes. + # In this situation, we bypass any bootstrap or certbot venv setup. + error "Your system is not supported by certbot-auto anymore." + + if [ ! -d "$VENV_PATH" ] && OldVenvExists; then + VENV_BIN="$OLD_VENV_PATH/bin" + fi + + if [ -f "$VENV_BIN/letsencrypt" -a "$INSTALL_ONLY" != 1 ]; then + error "Certbot will no longer receive updates." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + "$VENV_BIN/letsencrypt" "$@" + exit 0 + else + error "Certbot cannot be installed." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + exit 1 + fi + fi + SetPrevBootstrapVersion if [ -z "$PHASE_1_VERSION" -a "$USE_PYTHON_3" = 1 ]; then @@ -657,6 +691,9 @@ UNLIKELY_EOF say "Installation succeeded." fi + # If you're modifying any of the code after this point in this current `if` block, you + # may need to update the "$DEPRECATED_OS" = 1 case at the beginning of phase 2 as well. + if [ "$INSTALL_ONLY" = 1 ]; then say "Certbot is installed." exit 0 @@ -720,30 +757,35 @@ UNLIKELY_EOF error "WARNING: unable to check for updates." fi - LE_VERSION_STATE=`CompareVersions "$LE_PYTHON" "$LE_AUTO_VERSION" "$REMOTE_VERSION"` - if [ "$LE_VERSION_STATE" = "UNOFFICIAL" ]; then - say "Unofficial certbot-auto version detected, self-upgrade is disabled: $LE_AUTO_VERSION" - elif [ "$LE_VERSION_STATE" = "OUTDATED" ]; then - say "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..." + # If for any reason REMOTE_VERSION is not set, let's assume certbot-auto is up-to-date, + # and do not go into the self-upgrading process. + if [ -n "$REMOTE_VERSION" ]; then + LE_VERSION_STATE=`CompareVersions "$LE_PYTHON" "$LE_AUTO_VERSION" "$REMOTE_VERSION"` - # Now we drop into Python so we don't have to install even more - # dependencies (curl, etc.), for better flow control, and for the option of - # future Windows compatibility. - "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" + if [ "$LE_VERSION_STATE" = "UNOFFICIAL" ]; then + say "Unofficial certbot-auto version detected, self-upgrade is disabled: $LE_AUTO_VERSION" + elif [ "$LE_VERSION_STATE" = "OUTDATED" ]; then + say "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..." - # Install new copy of certbot-auto. - # TODO: Deal with quotes in pathnames. - say "Replacing certbot-auto..." - # Clone permissions with cp. chmod and chown don't have a --reference - # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: - cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" - cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" - # Using mv rather than cp leaves the old file descriptor pointing to the - # original copy so the shell can continue to read it unmolested. mv across - # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the - # cp is unlikely to fail if the rm doesn't. - mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" - fi # A newer version is available. + # Now we drop into Python so we don't have to install even more + # dependencies (curl, etc.), for better flow control, and for the option of + # future Windows compatibility. + "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" + + # Install new copy of certbot-auto. + # TODO: Deal with quotes in pathnames. + say "Replacing certbot-auto..." + # Clone permissions with cp. chmod and chown don't have a --reference + # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: + cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" + cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" + # Using mv rather than cp leaves the old file descriptor pointing to the + # original copy so the shell can continue to read it unmolested. mv across + # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the + # cp is unlikely to fail if the rm doesn't. + mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" + fi # A newer version is available. + fi fi # Self-upgrading is allowed. RerunWithArgs --le-auto-phase2 "$@" diff --git a/letsencrypt-auto-source/tests/centos6_tests.sh b/letsencrypt-auto-source/tests/centos6_tests.sh index 2c6dcf734..713e83b16 100644 --- a/letsencrypt-auto-source/tests/centos6_tests.sh +++ b/letsencrypt-auto-source/tests/centos6_tests.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e # Start by making sure your system is up-to-date: yum update -y > /dev/null yum install -y centos-release-scl > /dev/null @@ -6,46 +7,51 @@ yum install -y python27 > /dev/null 2> /dev/null LE_AUTO="certbot/letsencrypt-auto-source/letsencrypt-auto" +echo "" + +# we're going to modify env variables, so do this in a subshell +( +# ensure CentOS6 32bits is not supported anymore, and so certbot is not installed +export UNAME_FAKE_32BITS=true +if ! "$LE_AUTO" 2>&1 | grep -q "Certbot cannot be installed."; then + echo "On CentOS 32 bits, certbot-auto installed certbot." + exit 1 +fi +) + +echo "PASSED: On CentOS 6 32 bits, certbot-auto refused to install certbot." + # we're going to modify env variables, so do this in a subshell ( source /opt/rh/python27/enable # ensure python 3 isn't installed -python3 --version 2> /dev/null -RESULT=$? -if [ $RESULT -eq 0 ]; then - error "Python3 is already installed." +if python3 --version 2> /dev/null; then + echo "Python3 is already installed." exit 1 fi # ensure python2.7 is available -python2.7 --version 2> /dev/null -RESULT=$? -if [ $RESULT -ne 0 ]; then - error "Python3 is not available." +if ! python2.7 --version 2> /dev/null; then + echo "Python2.7 is not available." exit 1 fi # bootstrap, but don't install python 3. -"$LE_AUTO" --no-self-upgrade -n > /dev/null 2> /dev/null +"$LE_AUTO" --no-self-upgrade -n --version > /dev/null 2> /dev/null # ensure python 3 isn't installed -python3 --version 2> /dev/null -RESULT=$? -if [ $RESULT -eq 0 ]; then - error "letsencrypt-auto installed Python3 even though Python2.7 is present." +if python3 --version 2> /dev/null; then + echo "letsencrypt-auto installed Python3 even though Python2.7 is present." exit 1 fi - -echo "" -echo "PASSED: Did not upgrade to Python3 when Python2.7 is present." ) +echo "PASSED: Did not upgrade to Python3 when Python2.7 is present." + # ensure python2.7 isn't available -python2.7 --version 2> /dev/null -RESULT=$? -if [ $RESULT -eq 0 ]; then - error "Python2.7 is still available." +if python2.7 --version 2> /dev/null; then + echo "Python2.7 is still available." exit 1 fi @@ -56,13 +62,11 @@ if ! "$LE_AUTO" 2>&1 | grep -q "WARNING: couldn't find Python"; then fi # bootstrap, this time installing python3 -"$LE_AUTO" --no-self-upgrade -n > /dev/null 2> /dev/null +"$LE_AUTO" --no-self-upgrade -n --version > /dev/null 2> /dev/null # ensure python 3 is installed -python3 --version > /dev/null -RESULT=$? -if [ $RESULT -ne 0 ]; then - error "letsencrypt-auto failed to install Python3 when only Python2.6 is present." +if ! python3 --version > /dev/null; then + echo "letsencrypt-auto failed to install Python3 when only Python2.6 is present." exit 1 fi @@ -77,5 +81,39 @@ if [ "$($VENV_PATH/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1)" != 3 ]; th fi unset VENV_PATH +# we're going to modify env variables, so do this in a subshell +( +# ensure CentOS6 32bits is not supported anymore, and so certbot +# is not upgraded nor reinstalled. +export UNAME_FAKE_32BITS=true +set -o pipefail +if ! "$LE_AUTO" --version 2>&1 | grep -q "Certbot will no longer receive updates."; then + echo "On CentOS 6 32 bits, certbot-auto failed or upgraded installed certbot instance." + exit 1 +fi +set +o pipefail +if ! "$LE_AUTO" --install-only 2>&1 | grep -q "Certbot cannot be installed."; then + echo "On CentOS 6 32 bits, certbot-auto installed certbot again." + exit 1 +fi +) + +# we're going to modify env variables, so do this in a subshell +( +# Prepare a certbot installation in the old venv path +rm -rf /opt/eff.org +VENV_PATH=~/.local/share/letsencrypt "$LE_AUTO" --install-only > /dev/null 2> /dev/null +# fake 32 bits mode +export UNAME_FAKE_32BITS=true +set -o pipefail +if ! "$LE_AUTO" --version 2>&1 | grep -q "Certbot will no longer receive updates."; then + echo "On CentOS 6 32 bits, certbot-auto failed or upgraded installed certbot in the old venv path." + exit 1 +fi +set +o pipefail +) + +echo "PASSED: On CentOS 6 32 bits, certbot-auto refused to install/upgrade certbot." + # test using python3 pytest -v -s certbot/letsencrypt-auto-source/tests diff --git a/letsencrypt-auto-source/tests/uname_wrapper.sh b/letsencrypt-auto-source/tests/uname_wrapper.sh new file mode 100644 index 000000000..df1f568c6 --- /dev/null +++ b/letsencrypt-auto-source/tests/uname_wrapper.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +uname_output=$(/bin/uname_orig "$@") + +if [ "$UNAME_FAKE_32BITS" = true ]; then + uname_output="${uname_output//x86_64/i686}" +fi + +echo "$uname_output" From 456122e342db13cda50b9ae59a1be9521b6547d8 Mon Sep 17 00:00:00 2001 From: Vladimir Varlamov Date: Thu, 9 Jan 2020 22:34:04 +0300 Subject: [PATCH 030/122] improve help about supply selecting in delete command (#7673) for #6625 --- certbot/certbot/_internal/cli.py | 4 ++-- certbot/docs/cli-help.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/certbot/certbot/_internal/cli.py b/certbot/certbot/_internal/cli.py index fb3010a4e..9853d9b53 100644 --- a/certbot/certbot/_internal/cli.py +++ b/certbot/certbot/_internal/cli.py @@ -92,8 +92,8 @@ obtain, install, and renew certificates: manage certificates: certificates Display information about certificates you have from Certbot - revoke Revoke a certificate (supply --cert-path or --cert-name) - delete Delete a certificate + revoke Revoke a certificate (supply --cert-name or --cert-path) + delete Delete a certificate (supply --cert-name) manage your account: register Create an ACME account diff --git a/certbot/docs/cli-help.txt b/certbot/docs/cli-help.txt index b46206b87..de12cefda 100644 --- a/certbot/docs/cli-help.txt +++ b/certbot/docs/cli-help.txt @@ -24,8 +24,8 @@ obtain, install, and renew certificates: manage certificates: certificates Display information about certificates you have from Certbot - revoke Revoke a certificate (supply --cert-path or --cert-name) - delete Delete a certificate + revoke Revoke a certificate (supply --cert-name or --cert-path) + delete Delete a certificate (supply --cert-name) manage your account: register Create an ACME account From ceea41c1e2c03ea3d8c9976a6a3ff87f248cedd7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 10 Jan 2020 16:48:01 -0800 Subject: [PATCH 031/122] Do not document private members (#7675) It looks like we're currently documenting functions that are marked private (prefixed with an underscore) such as https://certbot.eff.org/docs/api/certbot.crypto_util.html#certbot.crypto_util._load_cert_or_req. I do not think we should do this because the functionality is private, should not be used, and including it in our docs just adds visual noise. This PR stops us from documenting private code and fixes up `tools/sphinx-quickstart.sh` so we don't document it in future modules. * Do not document private code. * Don't document private members in the future. --- acme/docs/conf.py | 2 +- certbot-dns-cloudflare/docs/conf.py | 2 +- certbot-dns-cloudxns/docs/conf.py | 2 +- certbot-dns-digitalocean/docs/conf.py | 2 +- certbot-dns-dnsimple/docs/conf.py | 2 +- certbot-dns-dnsmadeeasy/docs/conf.py | 2 +- certbot-dns-gehirn/docs/conf.py | 2 +- certbot-dns-google/docs/conf.py | 2 +- certbot-dns-linode/docs/conf.py | 2 +- certbot-dns-luadns/docs/conf.py | 2 +- certbot-dns-nsone/docs/conf.py | 2 +- certbot-dns-ovh/docs/conf.py | 2 +- certbot-dns-rfc2136/docs/conf.py | 2 +- certbot-dns-route53/docs/conf.py | 2 +- certbot-dns-sakuracloud/docs/conf.py | 2 +- certbot/docs/conf.py | 2 +- letshelp-certbot/docs/conf.py | 2 +- tools/sphinx-quickstart.sh | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/acme/docs/conf.py b/acme/docs/conf.py index 01029a81f..8c1689128 100644 --- a/acme/docs/conf.py +++ b/acme/docs/conf.py @@ -41,7 +41,7 @@ extensions = [ ] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-cloudflare/docs/conf.py b/certbot-dns-cloudflare/docs/conf.py index 488268577..97e54421e 100644 --- a/certbot-dns-cloudflare/docs/conf.py +++ b/certbot-dns-cloudflare/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-cloudxns/docs/conf.py b/certbot-dns-cloudxns/docs/conf.py index 16ccd1d62..1fc05c94c 100644 --- a/certbot-dns-cloudxns/docs/conf.py +++ b/certbot-dns-cloudxns/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-digitalocean/docs/conf.py b/certbot-dns-digitalocean/docs/conf.py index 9c493a220..0741e4cea 100644 --- a/certbot-dns-digitalocean/docs/conf.py +++ b/certbot-dns-digitalocean/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-dnsimple/docs/conf.py b/certbot-dns-dnsimple/docs/conf.py index b5cb24e2f..99cc93135 100644 --- a/certbot-dns-dnsimple/docs/conf.py +++ b/certbot-dns-dnsimple/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-dnsmadeeasy/docs/conf.py b/certbot-dns-dnsmadeeasy/docs/conf.py index 60e0163bd..1f0c57812 100644 --- a/certbot-dns-dnsmadeeasy/docs/conf.py +++ b/certbot-dns-dnsmadeeasy/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-gehirn/docs/conf.py b/certbot-dns-gehirn/docs/conf.py index 67aafa3b4..527bc3d55 100644 --- a/certbot-dns-gehirn/docs/conf.py +++ b/certbot-dns-gehirn/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-google/docs/conf.py b/certbot-dns-google/docs/conf.py index 8f045cf3f..b2ddcfb34 100644 --- a/certbot-dns-google/docs/conf.py +++ b/certbot-dns-google/docs/conf.py @@ -39,7 +39,7 @@ extensions = ['sphinx.ext.autodoc', 'jsonlexer'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-linode/docs/conf.py b/certbot-dns-linode/docs/conf.py index f23d65023..c6d564b7a 100644 --- a/certbot-dns-linode/docs/conf.py +++ b/certbot-dns-linode/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-luadns/docs/conf.py b/certbot-dns-luadns/docs/conf.py index 899480f66..8e9d49988 100644 --- a/certbot-dns-luadns/docs/conf.py +++ b/certbot-dns-luadns/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-nsone/docs/conf.py b/certbot-dns-nsone/docs/conf.py index aec0771a2..5531959ed 100644 --- a/certbot-dns-nsone/docs/conf.py +++ b/certbot-dns-nsone/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-ovh/docs/conf.py b/certbot-dns-ovh/docs/conf.py index a4985edee..56e24a920 100644 --- a/certbot-dns-ovh/docs/conf.py +++ b/certbot-dns-ovh/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-rfc2136/docs/conf.py b/certbot-dns-rfc2136/docs/conf.py index e4df84594..c0d55078e 100644 --- a/certbot-dns-rfc2136/docs/conf.py +++ b/certbot-dns-rfc2136/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-route53/docs/conf.py b/certbot-dns-route53/docs/conf.py index cb8aae0b6..c2eb880ac 100644 --- a/certbot-dns-route53/docs/conf.py +++ b/certbot-dns-route53/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-sakuracloud/docs/conf.py b/certbot-dns-sakuracloud/docs/conf.py index f973779ab..70a4d7434 100644 --- a/certbot-dns-sakuracloud/docs/conf.py +++ b/certbot-dns-sakuracloud/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot/docs/conf.py b/certbot/docs/conf.py index 6b7c1c2c0..1e57bc224 100644 --- a/certbot/docs/conf.py +++ b/certbot/docs/conf.py @@ -52,7 +52,7 @@ if sphinx.version_info >= (1, 6): extensions.append('sphinx.ext.imgconverter') autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/letshelp-certbot/docs/conf.py b/letshelp-certbot/docs/conf.py index fcff25d55..fc482a348 100644 --- a/letshelp-certbot/docs/conf.py +++ b/letshelp-certbot/docs/conf.py @@ -40,7 +40,7 @@ extensions = [ ] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/tools/sphinx-quickstart.sh b/tools/sphinx-quickstart.sh index 72dc9e200..35a7f7fad 100755 --- a/tools/sphinx-quickstart.sh +++ b/tools/sphinx-quickstart.sh @@ -14,7 +14,7 @@ sed -i -e "s|\# import os|import os|" conf.py sed -i -e "s|\# needs_sphinx = '1.0'|needs_sphinx = '1.0'|" conf.py sed -i -e "s|intersphinx_mapping = {'https://docs.python.org/': None}|intersphinx_mapping = {\n 'python': ('https://docs.python.org/', None),\n 'acme': ('https://acme-python.readthedocs.org/en/latest/', None),\n 'certbot': ('https://certbot.eff.org/docs/', None),\n}|" conf.py sed -i -e "s|html_theme = 'alabaster'|\n# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs\n# on_rtd is whether we are on readthedocs.org\non_rtd = os.environ.get('READTHEDOCS', None) == 'True'\nif not on_rtd: # only import and set the theme if we're building docs locally\n import sphinx_rtd_theme\n html_theme = 'sphinx_rtd_theme'\n html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]\n# otherwise, readthedocs.org uses their theme by default, so no need to specify it|" conf.py -sed -i -e "s|# Add any paths that contain templates here, relative to this directory.|autodoc_member_order = 'bysource'\nautodoc_default_flags = ['show-inheritance', 'private-members']\n\n# Add any paths that contain templates here, relative to this directory.|" conf.py +sed -i -e "s|# Add any paths that contain templates here, relative to this directory.|autodoc_member_order = 'bysource'\nautodoc_default_flags = ['show-inheritance']\n\n# Add any paths that contain templates here, relative to this directory.|" conf.py sed -i -e "s|# The name of the Pygments (syntax highlighting) style to use.|default_role = 'py:obj'\n\n# The name of the Pygments (syntax highlighting) style to use.|" conf.py echo "/_build/" >> .gitignore echo "================= From e84ed49c56d7f0b90a8161cea8bff82efa8f4257 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 13 Jan 2020 09:24:41 +0100 Subject: [PATCH 032/122] Fix certbot-auto regarding python 3.4 -> python 3.6 migration for CentOS 6 users (#7519) * Revert "Add back Python 3.4 support (#7510)" This reverts commit 9b848b1d65783000a13ef3f94ac5fe0e8c3879e7. * Fix certbot-auto * Use a more consistent way to enable rh-python36 * Avoid to call CompareVersions unecessarily * Control rh-python36 exit code * Fix travis config * Remove vscode config * Ignore vscode * Fix merge conflicts regarding #7587 (#70) * Add changelog entry * Finish sentence * Update certbot/CHANGELOG.md Co-Authored-By: Joona Hoikkala * Update letsencrypt-auto-source/tests/centos6_tests.sh Co-Authored-By: Joona Hoikkala * Update letsencrypt-auto-source/tests/centos6_tests.sh Co-Authored-By: Joona Hoikkala * Update letsencrypt-auto-source/tests/centos6_tests.sh Co-Authored-By: Joona Hoikkala * Update letsencrypt-auto-source/tests/centos6_tests.sh Co-Authored-By: Joona Hoikkala * Update letsencrypt-auto-source/tests/centos6_tests.sh Co-Authored-By: Joona Hoikkala * Update comments * Improve warning message * Update changelog Co-authored-by: Joona Hoikkala --- .gitignore | 1 + .travis.yml | 4 + certbot/CHANGELOG.md | 5 + certbot/certbot/_internal/main.py | 4 + ...{Dockerfile.centos6 => Dockerfile.redhat6} | 21 +- letsencrypt-auto-source/letsencrypt-auto | 215 +++++++++++++----- .../letsencrypt-auto.template | 106 ++++++--- .../pieces/bootstrappers/rpm_common_base.sh | 24 +- .../pieces/bootstrappers/rpm_python3.sh | 7 - .../bootstrappers/rpm_python3_legacy.sh | 78 +++++++ .../tests/centos6_tests.sh | 184 +++++++++------ .../tests/oraclelinux6_tests.sh | 85 +++++++ .../letstest/scripts/test_leauto_upgrades.sh | 2 + tests/letstest/scripts/test_sdists.sh | 13 ++ tox.ini | 12 +- 15 files changed, 572 insertions(+), 189 deletions(-) rename letsencrypt-auto-source/{Dockerfile.centos6 => Dockerfile.redhat6} (55%) create mode 100644 letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh create mode 100644 letsencrypt-auto-source/tests/oraclelinux6_tests.sh diff --git a/.gitignore b/.gitignore index 68762da6b..6dd422187 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ tags \#*# .idea .ropeproject +.vscode # auth --cert-path --chain-path /*.pem diff --git a/.travis.yml b/.travis.yml index 63129c9b1..fdb692ac1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -232,6 +232,10 @@ matrix: env: TOXENV=le_auto_centos6 services: docker <<: *extended-test-suite + - sudo: required + env: TOXENV=le_auto_oraclelinux6 + services: docker + <<: *extended-test-suite - sudo: required env: TOXENV=docker_dev services: docker diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 5209de607..b3b8ec9d9 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -17,6 +17,11 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). based systems. Existing certbot-auto installations affected by this will continue to work, but they will no longer receive updates. To install a newer version of Certbot on these systems, you should update your OS. +* Support for Python 3.4 in Certbot and its ACME library is deprecated and will be + removed in the next release of Certbot. certbot-auto users on x86_64 systems running + RHEL 6 or derivatives will be asked to enable Software Collections (SCL) repository + so Python 3.6 can be installed. certbot-auto can enable the SCL repo for you on CentOS 6 + while users on other RHEL 6 based systems will be asked to do this manually. ### Fixed diff --git a/certbot/certbot/_internal/main.py b/certbot/certbot/_internal/main.py index 72fcfca71..509b5b981 100644 --- a/certbot/certbot/_internal/main.py +++ b/certbot/certbot/_internal/main.py @@ -1337,6 +1337,10 @@ def main(cli_args=None): if config.func != plugins_cmd: # pylint: disable=comparison-with-callable raise + if sys.version_info[:2] == (3, 4): + logger.warning("Python 3.4 support will be dropped in the next release " + "of Certbot - please upgrade your Python version to 3.5+.") + set_displayer(config) # Reporter diff --git a/letsencrypt-auto-source/Dockerfile.centos6 b/letsencrypt-auto-source/Dockerfile.redhat6 similarity index 55% rename from letsencrypt-auto-source/Dockerfile.centos6 rename to letsencrypt-auto-source/Dockerfile.redhat6 index f152e1bac..66f21bc14 100644 --- a/letsencrypt-auto-source/Dockerfile.centos6 +++ b/letsencrypt-auto-source/Dockerfile.redhat6 @@ -1,9 +1,13 @@ # For running tests, build a docker image with a passwordless sudo and a trust # store we can manipulate. -FROM centos:6 +ARG REDHAT_DIST_FLAVOR +FROM ${REDHAT_DIST_FLAVOR}:6 -RUN yum install -y epel-release +ARG REDHAT_DIST_FLAVOR + +RUN curl -O https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm \ + && rpm -ivh epel-release-latest-6.noarch.rpm # Install pip and sudo: RUN yum install -y python-pip sudo @@ -27,7 +31,7 @@ RUN mkdir -p /home/lea/certbot COPY ./tests/certs/ca/my-root-ca.crt.pem /usr/local/share/ca-certificates/ RUN update-ca-trust -# Copy code: +# Copy current letsencrypt-auto: COPY . /home/lea/certbot/letsencrypt-auto-source # Tweak uname binary for tests on fake 32bits @@ -36,8 +40,15 @@ RUN mv /bin/uname /bin/uname_orig \ && mv /bin/uname_wrapper.sh /bin/uname \ && chmod +x /bin/uname +# Fetch previous letsencrypt-auto that was installing python 3.4 +RUN curl https://raw.githubusercontent.com/certbot/certbot/v0.38.0/letsencrypt-auto-source/letsencrypt-auto \ + -o /home/lea/certbot/letsencrypt-auto-source/letsencrypt-auto_py_34 \ + && chmod +x /home/lea/certbot/letsencrypt-auto-source/letsencrypt-auto_py_34 + +RUN cp /home/lea/certbot/letsencrypt-auto-source/tests/${REDHAT_DIST_FLAVOR}6_tests.sh /home/lea/certbot/letsencrypt-auto-source/tests/redhat6_tests.sh \ + && chmod +x /home/lea/certbot/letsencrypt-auto-source/tests/redhat6_tests.sh + USER lea WORKDIR /home/lea -RUN sudo chmod +x certbot/letsencrypt-auto-source/tests/centos6_tests.sh -CMD sudo certbot/letsencrypt-auto-source/tests/centos6_tests.sh +CMD ["sudo", "certbot/letsencrypt-auto-source/tests/redhat6_tests.sh"] diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 2e658f642..78e449444 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -256,20 +256,28 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_VERSION="2.7" -MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') +MIN_PYTHON_2_VERSION="2.7" +MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') +MIN_PYTHON_3_VERSION="3.5" +MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version +# digits of the python version. +# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their +# values depend on if we try to use Python 3 or Python 2. DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then + MIN_PYVER=$MIN_PYVER3 + MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else + MIN_PYVER=$MIN_PYVER2 + MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -285,7 +293,7 @@ DeterminePythonVersion() { fi fi - PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` + PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -368,7 +376,9 @@ BootstrapDebCommon() { # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Enables EPEL if applicable and possible. +# Note: this function is called both while selecting the bootstrap scripts and +# during the actual bootstrap. Some things like prompting to user can be done in the latter +# case, but not in the former one. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -388,26 +398,6 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi - - if ! $TOOL list *virtualenv >/dev/null 2>&1; then - echo "To use Certbot, packages from the EPEL repository need to be installed." - if ! $TOOL list epel-release >/dev/null 2>&1; then - error "Enable the EPEL repository and try running Certbot again." - exit 1 - fi - if [ "$ASSUME_YES" = 1 ]; then - /bin/echo -n "Enabling the EPEL repository in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." - sleep 1s - fi - if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then - error "Could not enable EPEL. Aborting bootstrap!" - exit 1 - fi - fi } BootstrapRpmCommonBase() { @@ -488,13 +478,91 @@ BootstrapRpmCommon() { BootstrapRpmCommonBase "$python_pkgs" } +# If new packages are installed by BootstrapRpmPython3 below, this version +# number must be increased. +BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 + +# Checks if rh-python36 can be installed. +Python36SclIsAvailable() { + InitializeRPMCommonBase >/dev/null 2>&1; + + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + return 0 + fi + if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + return 0 + fi + return 1 +} + +# Try to enable rh-python36 from SCL if it is necessary and possible. +EnablePython36SCL() { + if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then + return 0 + fi + if [ ! -f /opt/rh/rh-python36/enable ]; then + return 0 + fi + set +e + if ! . /opt/rh/rh-python36/enable; then + error 'Unable to enable rh-python36!' + exit 1 + fi + set -e +} + +# This bootstrap concerns old RedHat-based distributions that do not ship by default +# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing +# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. +BootstrapRpmPython3Legacy() { + # Tested with: + # - CentOS 6 + + InitializeRPMCommonBase + + if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then + echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." + if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + error "Enable the SCL repository and try running Certbot again." + exit 1 + fi + if [ "${ASSUME_YES}" = 1 ]; then + /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" + sleep 1s + fi + if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then + error "Could not enable SCL. Aborting bootstrap!" + exit 1 + fi + fi + + # CentOS 6 must use rh-python36 from SCL + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + python_pkgs="rh-python36-python + rh-python36-python-virtualenv + rh-python36-python-devel + " + else + error "No supported Python package available to install. Aborting bootstrap!" + exit 1 + fi + + BootstrapRpmCommonBase "${python_pkgs}" + + # Enable SCL rh-python36 after bootstrapping. + EnablePython36SCL +} + # If new packages are installed by BootstrapRpmPython3 below, this version # number must be increased. BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: - # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -505,12 +573,6 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " - # EPEL uses python34 - elif $TOOL list python34 >/dev/null 2>&1; then - python_pkgs="python34 - python34-devel - python34-tools - " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 @@ -774,31 +836,50 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi + # Handle legacy RPM distributions + if [ "$PYVER" -eq 26 ]; then + # Check if an automated bootstrap can be achieved on this system. + if ! Python36SclIsAvailable; then + INTERACTIVE_BOOTSTRAP=1 + fi - if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 + BootstrapMessage "Legacy RedHat-based OSes that will use Python3" + BootstrapRpmPython3Legacy } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" + + # Try now to enable SCL rh-python36 for systems already bootstrapped + # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto + EnablePython36SCL else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then + Bootstrap() { + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 + } + USE_PYTHON_3=1 + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + else + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + fi fi LE_PYTHON="$prev_le_python" @@ -1112,8 +1193,15 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # if non-interactive mode or stdin and stdout are connected to a terminal - if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # Check if we can rebootstrap without manual user intervention: this requires that + # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to + # require a manual user intervention. + if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then + CAN_REBOOTSTRAP=1 + fi + # Check if rebootstrap can be done non-interactively and current shell is non-interactive + # (true if stdin and stdout are not attached to a terminal). + if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -1124,12 +1212,21 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" + # Otherwise bootstrap needs to be done manually by the user. else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." + # If it is because bootstrapping is interactive, --non-interactive will be of no use. + if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then + error "Skipping upgrade because new OS dependencies may need to be installed." + error "This requires manual user intervention: please run this script again manually." + # If this is because of the environment (eg. non interactive shell without + # --non-interactive flag set), help the user in that direction. + else + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." + fi # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index e481fd6f3..53e57a498 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -256,20 +256,28 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_VERSION="2.7" -MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') +MIN_PYTHON_2_VERSION="2.7" +MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') +MIN_PYTHON_3_VERSION="3.5" +MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version +# digits of the python version. +# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their +# values depend on if we try to use Python 3 or Python 2. DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then + MIN_PYVER=$MIN_PYVER3 + MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else + MIN_PYVER=$MIN_PYVER2 + MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -285,7 +293,7 @@ DeterminePythonVersion() { fi fi - PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` + PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -298,6 +306,7 @@ DeterminePythonVersion() { {{ bootstrappers/deb_common.sh }} {{ bootstrappers/rpm_common_base.sh }} {{ bootstrappers/rpm_common.sh }} +{{ bootstrappers/rpm_python3_legacy.sh }} {{ bootstrappers/rpm_python3.sh }} {{ bootstrappers/suse_common.sh }} {{ bootstrappers/arch_common.sh }} @@ -349,31 +358,50 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi + # Handle legacy RPM distributions + if [ "$PYVER" -eq 26 ]; then + # Check if an automated bootstrap can be achieved on this system. + if ! Python36SclIsAvailable; then + INTERACTIVE_BOOTSTRAP=1 + fi - if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 + BootstrapMessage "Legacy RedHat-based OSes that will use Python3" + BootstrapRpmPython3Legacy } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" + + # Try now to enable SCL rh-python36 for systems already bootstrapped + # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto + EnablePython36SCL else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then + Bootstrap() { + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 + } + USE_PYTHON_3=1 + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + else + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + fi fi LE_PYTHON="$prev_le_python" @@ -579,8 +607,15 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # if non-interactive mode or stdin and stdout are connected to a terminal - if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # Check if we can rebootstrap without manual user intervention: this requires that + # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to + # require a manual user intervention. + if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then + CAN_REBOOTSTRAP=1 + fi + # Check if rebootstrap can be done non-interactively and current shell is non-interactive + # (true if stdin and stdout are not attached to a terminal). + if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -591,12 +626,21 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" + # Otherwise bootstrap needs to be done manually by the user. else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." + # If it is because bootstrapping is interactive, --non-interactive will be of no use. + if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then + error "Skipping upgrade because new OS dependencies may need to be installed." + error "This requires manual user intervention: please run this script again manually." + # If this is because of the environment (eg. non interactive shell without + # --non-interactive flag set), help the user in that direction. + else + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." + fi # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist diff --git a/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh b/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh index 326ad8b3f..2b00b199b 100644 --- a/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh +++ b/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh @@ -3,7 +3,9 @@ # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Enables EPEL if applicable and possible. +# Note: this function is called both while selecting the bootstrap scripts and +# during the actual bootstrap. Some things like prompting to user can be done in the latter +# case, but not in the former one. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -23,26 +25,6 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi - - if ! $TOOL list *virtualenv >/dev/null 2>&1; then - echo "To use Certbot, packages from the EPEL repository need to be installed." - if ! $TOOL list epel-release >/dev/null 2>&1; then - error "Enable the EPEL repository and try running Certbot again." - exit 1 - fi - if [ "$ASSUME_YES" = 1 ]; then - /bin/echo -n "Enabling the EPEL repository in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." - sleep 1s - fi - if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then - error "Could not enable EPEL. Aborting bootstrap!" - exit 1 - fi - fi } BootstrapRpmCommonBase() { diff --git a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh index f33b07ca9..ac0553db5 100644 --- a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh +++ b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh @@ -4,7 +4,6 @@ BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: - # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -15,12 +14,6 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " - # EPEL uses python34 - elif $TOOL list python34 >/dev/null 2>&1; then - python_pkgs="python34 - python34-devel - python34-tools - " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 diff --git a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh new file mode 100644 index 000000000..febfc7a83 --- /dev/null +++ b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh @@ -0,0 +1,78 @@ +# If new packages are installed by BootstrapRpmPython3 below, this version +# number must be increased. +BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 + +# Checks if rh-python36 can be installed. +Python36SclIsAvailable() { + InitializeRPMCommonBase >/dev/null 2>&1; + + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + return 0 + fi + if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + return 0 + fi + return 1 +} + +# Try to enable rh-python36 from SCL if it is necessary and possible. +EnablePython36SCL() { + if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then + return 0 + fi + if [ ! -f /opt/rh/rh-python36/enable ]; then + return 0 + fi + set +e + if ! . /opt/rh/rh-python36/enable; then + error 'Unable to enable rh-python36!' + exit 1 + fi + set -e +} + +# This bootstrap concerns old RedHat-based distributions that do not ship by default +# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing +# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. +BootstrapRpmPython3Legacy() { + # Tested with: + # - CentOS 6 + + InitializeRPMCommonBase + + if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then + echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." + if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + error "Enable the SCL repository and try running Certbot again." + exit 1 + fi + if [ "${ASSUME_YES}" = 1 ]; then + /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" + sleep 1s + fi + if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then + error "Could not enable SCL. Aborting bootstrap!" + exit 1 + fi + fi + + # CentOS 6 must use rh-python36 from SCL + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + python_pkgs="rh-python36-python + rh-python36-python-virtualenv + rh-python36-python-devel + " + else + error "No supported Python package available to install. Aborting bootstrap!" + exit 1 + fi + + BootstrapRpmCommonBase "${python_pkgs}" + + # Enable SCL rh-python36 after bootstrapping. + EnablePython36SCL +} diff --git a/letsencrypt-auto-source/tests/centos6_tests.sh b/letsencrypt-auto-source/tests/centos6_tests.sh index 713e83b16..86b307ad2 100644 --- a/letsencrypt-auto-source/tests/centos6_tests.sh +++ b/letsencrypt-auto-source/tests/centos6_tests.sh @@ -1,20 +1,22 @@ #!/bin/bash set -e # Start by making sure your system is up-to-date: -yum update -y > /dev/null -yum install -y centos-release-scl > /dev/null -yum install -y python27 > /dev/null 2> /dev/null +yum update -y >/dev/null +yum install -y centos-release-scl >/dev/null +yum install -y python27 >/dev/null 2>/dev/null +LE_AUTO_PY_34="certbot/letsencrypt-auto-source/letsencrypt-auto_py_34" LE_AUTO="certbot/letsencrypt-auto-source/letsencrypt-auto" -echo "" +# Last version of certbot-auto that was bootstraping Python 3.4 for CentOS 6 users +INITIAL_CERTBOT_VERSION_PY34="certbot 0.38.0" # we're going to modify env variables, so do this in a subshell ( # ensure CentOS6 32bits is not supported anymore, and so certbot is not installed export UNAME_FAKE_32BITS=true if ! "$LE_AUTO" 2>&1 | grep -q "Certbot cannot be installed."; then - echo "On CentOS 32 bits, certbot-auto installed certbot." + echo "ERROR: certbot-auto installed certbot on 32-bit CentOS." exit 1 fi ) @@ -23,97 +25,149 @@ echo "PASSED: On CentOS 6 32 bits, certbot-auto refused to install certbot." # we're going to modify env variables, so do this in a subshell ( -source /opt/rh/python27/enable + . /opt/rh/python27/enable -# ensure python 3 isn't installed -if python3 --version 2> /dev/null; then - echo "Python3 is already installed." - exit 1 -fi + # ensure python 3 isn't installed + if python3 --version 2> /dev/null; then + echo "ERROR: Python3 is already installed." + exit 1 + fi -# ensure python2.7 is available -if ! python2.7 --version 2> /dev/null; then - echo "Python2.7 is not available." - exit 1 -fi + # ensure python2.7 is available + if ! python2.7 --version 2> /dev/null; then + echo "ERROR: Python2.7 is not available." + exit 1 + fi -# bootstrap, but don't install python 3. -"$LE_AUTO" --no-self-upgrade -n --version > /dev/null 2> /dev/null + # bootstrap, but don't install python 3. + "$LE_AUTO" --no-self-upgrade -n --version > /dev/null 2> /dev/null -# ensure python 3 isn't installed -if python3 --version 2> /dev/null; then - echo "letsencrypt-auto installed Python3 even though Python2.7 is present." - exit 1 -fi + # ensure python 3 isn't installed + if python3 --version 2> /dev/null; then + echo "ERROR: letsencrypt-auto installed Python3 even though Python2.7 is present." + exit 1 + fi + + echo "PASSED: Did not upgrade to Python3 when Python2.7 is present." ) -echo "PASSED: Did not upgrade to Python3 when Python2.7 is present." - # ensure python2.7 isn't available if python2.7 --version 2> /dev/null; then - echo "Python2.7 is still available." + echo "ERROR: Python2.7 is still available." exit 1 fi # Skip self upgrade due to Python 3 not being available. if ! "$LE_AUTO" 2>&1 | grep -q "WARNING: couldn't find Python"; then - echo "Python upgrade failure warning not printed!" + echo "ERROR: Python upgrade failure warning not printed!" exit 1 fi -# bootstrap, this time installing python3 -"$LE_AUTO" --no-self-upgrade -n --version > /dev/null 2> /dev/null +# bootstrap from the old letsencrypt-auto, this time installing python3.4 +"$LE_AUTO_PY_34" --no-self-upgrade -n --version >/dev/null 2>/dev/null -# ensure python 3 is installed -if ! python3 --version > /dev/null; then - echo "letsencrypt-auto failed to install Python3 when only Python2.6 is present." +# ensure python 3.4 is installed +if ! python3.4 --version >/dev/null 2>/dev/null; then + echo "ERROR: letsencrypt-auto failed to install Python3.4 using letsencrypt-auto < 0.37.0 when only Python2.6 is present." exit 1 fi -echo "PASSED: Successfully upgraded to Python3 when only Python2.6 is present." -echo "" +echo "PASSED: Successfully upgraded to Python3.4 using letsencrypt-auto < 0.37.0 when only Python2.6 is present." -export VENV_PATH=$(mktemp -d) -"$LE_AUTO" -n --no-bootstrap --no-self-upgrade --version >/dev/null 2>&1 -if [ "$($VENV_PATH/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1)" != 3 ]; then - echo "Python 3 wasn't used with --no-bootstrap!" +# As "certbot-auto" (so without implicit --non-interactive flag set), check that the script +# refuses to install SCL Python 3.6 when run in a non interactive shell (simulated here +# using | tee /dev/null) if --non-interactive flag is not provided. +cp "$LE_AUTO" /tmp/certbot-auto +# NB: Readline has an issue on all Python versions for CentOS 6, making `certbot --version` +# output an unprintable ASCII character on a new line at the end. +# So we take the second last line of the output. +version=$(/tmp/certbot-auto --version 2>/dev/null | tee /dev/null | tail -2 | head -1) + +if [ "$version" != "$INITIAL_CERTBOT_VERSION_PY34" ]; then + echo "ERROR: certbot-auto upgraded certbot in a non-interactive shell with --non-interactive flag not set." exit 1 fi -unset VENV_PATH -# we're going to modify env variables, so do this in a subshell +echo "PASSED: certbot-auto did not upgrade certbot in a non-interactive shell with --non-interactive flag not set." + +if [ -f /opt/rh/rh-python36/enable ]; then + echo "ERROR: certbot-auto installed Python3.6 in a non-interactive shell with --non-interactive flag not set." + exit 1 +fi + +echo "PASSED: certbot-auto did not install Python3.6 in a non-interactive shell with --non-interactive flag not set." + +# now bootstrap from current letsencrypt-auto, that will install python3.6 from SCL +"$LE_AUTO" --no-self-upgrade -n --version >/dev/null 2>/dev/null + +# Following test is exectued in a subshell, to not leak any environment variable ( -# ensure CentOS6 32bits is not supported anymore, and so certbot -# is not upgraded nor reinstalled. -export UNAME_FAKE_32BITS=true -set -o pipefail -if ! "$LE_AUTO" --version 2>&1 | grep -q "Certbot will no longer receive updates."; then - echo "On CentOS 6 32 bits, certbot-auto failed or upgraded installed certbot instance." - exit 1 -fi -set +o pipefail -if ! "$LE_AUTO" --install-only 2>&1 | grep -q "Certbot cannot be installed."; then - echo "On CentOS 6 32 bits, certbot-auto installed certbot again." - exit 1 -fi + # enable SCL rh-python36 + . /opt/rh/rh-python36/enable + + # ensure python 3.6 is installed + if ! python3.6 --version >/dev/null 2>/dev/null; then + echo "ERROR: letsencrypt-auto failed to install Python3.6 using current letsencrypt-auto when only Python2.6/Python3.4 are present." + exit 1 + fi + + echo "PASSED: Successfully upgraded to Python3.6 using current letsencrypt-auto when only Python2.6/Python3.4 are present." +) + +# Following test is executed in a subshell, to not leak any environment variable +( + export VENV_PATH=$(mktemp -d) + "$LE_AUTO" -n --no-bootstrap --no-self-upgrade --version >/dev/null 2>&1 + if [ "$($VENV_PATH/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1-2)" != "3.6" ]; then + echo "ERROR: Python 3.6 wasn't used with --no-bootstrap!" + exit 1 + fi +) + +# Following test is exectued in a subshell, to not leak any environment variable +( + # enable SCL rh-python36 + . /opt/rh/rh-python36/enable + + # ensure everything works fine with certbot-auto bootstrap when python 3.6 is already enabled + export VENV_PATH=$(mktemp -d) + if ! "$LE_AUTO" --no-self-upgrade -n --version >/dev/null 2>/dev/null; then + echo "ERROR: Certbot-auto broke when Python 3.6 SCL is already enabled." + exit 1 + fi ) # we're going to modify env variables, so do this in a subshell ( -# Prepare a certbot installation in the old venv path -rm -rf /opt/eff.org -VENV_PATH=~/.local/share/letsencrypt "$LE_AUTO" --install-only > /dev/null 2> /dev/null -# fake 32 bits mode -export UNAME_FAKE_32BITS=true -set -o pipefail -if ! "$LE_AUTO" --version 2>&1 | grep -q "Certbot will no longer receive updates."; then - echo "On CentOS 6 32 bits, certbot-auto failed or upgraded installed certbot in the old venv path." - exit 1 -fi -set +o pipefail + # ensure CentOS6 32bits is not supported anymore, and so certbot + # is not upgraded nor reinstalled. + export UNAME_FAKE_32BITS=true + OUTPUT=$("$LE_AUTO" --version 2>&1) + if ! echo "$OUTPUT" | grep -q "Certbot will no longer receive updates."; then + echo "ERROR: certbot-auto failed to run or upgraded pre-existing Certbot instance on 32-bit CentOS 6." + exit 1 + fi + if ! "$LE_AUTO" --install-only 2>&1 | grep -q "Certbot cannot be installed."; then + echo "ERROR: certbot-auto reinstalled Certbot on 32-bit CentOS 6." + exit 1 + fi ) -echo "PASSED: On CentOS 6 32 bits, certbot-auto refused to install/upgrade certbot." +# we're going to modify env variables, so do this in a subshell +( + # Prepare a certbot installation in the old venv path + rm -rf /opt/eff.org + VENV_PATH=~/.local/share/letsencrypt "$LE_AUTO" --install-only > /dev/null 2> /dev/null + # fake 32 bits mode + export UNAME_FAKE_32BITS=true + OUTPUT=$("$LE_AUTO" --version 2>&1) + if ! echo "$OUTPUT" | grep -q "Certbot will no longer receive updates."; then + echo "ERROR: certbot-auto failed to run or upgraded pre-existing Certbot instance in the old venv path on 32-bit CentOS 6." + exit 1 + fi +) + +echo "PASSED: certbot-auto refused to install/upgrade certbot on 32-bit CentOS 6." # test using python3 pytest -v -s certbot/letsencrypt-auto-source/tests diff --git a/letsencrypt-auto-source/tests/oraclelinux6_tests.sh b/letsencrypt-auto-source/tests/oraclelinux6_tests.sh new file mode 100644 index 000000000..f3fd952f3 --- /dev/null +++ b/letsencrypt-auto-source/tests/oraclelinux6_tests.sh @@ -0,0 +1,85 @@ +#!/bin/bash +set -eo pipefail +# Start by making sure your system is up-to-date: +yum update -y >/dev/null + +LE_AUTO_PY_34="certbot/letsencrypt-auto-source/letsencrypt-auto_py_34" +LE_AUTO="certbot/letsencrypt-auto-source/letsencrypt-auto" + +# Apply installation instructions from official documentation: +# https://certbot.eff.org/lets-encrypt/centosrhel6-other +cp "$LE_AUTO" /usr/local/bin/certbot-auto +chown root /usr/local/bin/certbot-auto +chmod 0755 /usr/local/bin/certbot-auto +LE_AUTO=/usr/local/bin/certbot-auto + +# Last version of certbot-auto that was bootstraping Python 3.4 for CentOS 6 users +INITIAL_CERTBOT_VERSION_PY34="certbot 0.38.0" + +# Check bootstrap from current certbot-auto will fail, because SCL is not enabled. +set +o pipefail +if ! "$LE_AUTO" -n 2>&1 | grep -q "Enable the SCL repository and try running Certbot again."; then + echo "ERROR: Bootstrap was not aborted although SCL was not installed!" + exit 1 +fi +set -o pipefail + +echo "PASSED: Bootstrap was aborted since SCL was not installed." + +# Bootstrap from the old letsencrypt-auto, Python 3.4 will be installed from EPEL. +"$LE_AUTO_PY_34" --no-self-upgrade -n --install-only >/dev/null 2>/dev/null + +# Ensure Python 3.4 is installed +if ! command -v python3.4 &>/dev/null; then + echo "ERROR: old letsencrypt-auto failed to install Python3.4 using letsencrypt-auto < 0.37.0 when only Python2.6 is present." + exit 1 +fi + +echo "PASSED: Bootstrap from old letsencrypt-auto succeeded and installed Python 3.4" + +# Expect certbot-auto to skip rebootstrapping with a warning since SCL is not installed. +if ! "$LE_AUTO" --non-interactive --version 2>&1 | grep -q "This requires manual user intervention"; then + echo "FAILED: Script certbot-auto did not print a warning about needing manual intervention!" + exit 1 +fi + +echo "PASSED: Script certbot-auto did not rebootstrap." + +# NB: Readline has an issue on all Python versions for OL 6, making `certbot --version` +# output an unprintable ASCII character on a new line at the end. +# So we take the second last line of the output. +version=$($LE_AUTO --version 2>/dev/null | tail -2 | head -1) + +if [ "$version" != "$INITIAL_CERTBOT_VERSION_PY34" ]; then + echo "ERROR: Script certbot-auto upgraded certbot in a non-interactive shell while SCL was not enabled." + exit 1 +fi + +echo "PASSED: Script certbot-auto did not upgrade certbot but started it successfully while SCL was not enabled." + +# Enable SCL +yum install -y oracle-softwarecollection-release-el6 >/dev/null + +# Expect certbot-auto to bootstrap successfully since SCL is available. +"$LE_AUTO" -n --version &>/dev/null + +if [ "$(/opt/eff.org/certbot/venv/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1-2)" != "3.6" ]; then + echo "ERROR: Script certbot-auto failed to bootstrap and install Python 3.6 while SCL is available." + exit 1 +fi + +if ! /opt/eff.org/certbot/venv/bin/certbot --version > /dev/null 2> /dev/null; then + echo "ERROR: Script certbot-auto did not install certbot correctly while SCL is enabled." + exit 1 +fi + +echo "PASSED: Script certbot-auto correctly bootstraped Certbot using rh-python36 when SCL is available." + +# Expect certbot-auto will be totally silent now that everything has been correctly boostraped. +OUTPUT_LEN=$("$LE_AUTO" --install-only --no-self-upgrade --quiet 2>&1 | wc -c) +if [ "$OUTPUT_LEN" != 0 ]; then + echo certbot-auto produced unexpected output! + exit 1 +fi + +echo "PASSED: Script certbot-auto did not print anything in quiet mode." diff --git a/tests/letstest/scripts/test_leauto_upgrades.sh b/tests/letstest/scripts/test_leauto_upgrades.sh index 541f54f6b..fc7632793 100755 --- a/tests/letstest/scripts/test_leauto_upgrades.sh +++ b/tests/letstest/scripts/test_leauto_upgrades.sh @@ -117,6 +117,8 @@ if ! diff letsencrypt-auto letsencrypt-auto-source/letsencrypt-auto ; then fi if [ "$RUN_RHEL6_TESTS" = 1 ]; then + # Add the SCL python release to PATH in order to resolve python3 command + PATH="/opt/rh/rh-python36/root/usr/bin:$PATH" if ! command -v python3; then echo "Python3 wasn't properly installed" exit 1 diff --git a/tests/letstest/scripts/test_sdists.sh b/tests/letstest/scripts/test_sdists.sh index dc024c567..204f55d55 100755 --- a/tests/letstest/scripts/test_sdists.sh +++ b/tests/letstest/scripts/test_sdists.sh @@ -1,8 +1,21 @@ #!/bin/sh -xe cd letsencrypt + +# If we're on a RHEL 6 based system, we can be confident Python is already +# installed because the package manager is written in Python. +if command -v python && [ $(python -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') -eq 26 ]; then + # RHEL/CentOS 6 will need a special treatment, so we need to detect that environment + RUN_RHEL6_TESTS=1 +fi + letsencrypt-auto-source/letsencrypt-auto --install-only -n --debug +if [ "$RUN_RHEL6_TESTS" = 1 ]; then + # Enable the SCL Python 3.6 installed by letsencrypt-auto bootstrap + PATH="/opt/rh/rh-python36/root/usr/bin:$PATH" +fi + PLUGINS="certbot-apache certbot-nginx" PYTHON_MAJOR_VERSION=$(/opt/eff.org/certbot/venv/bin/python --version 2>&1 | cut -d" " -f 2 | cut -d. -f1) TEMP_DIR=$(mktemp -d) diff --git a/tox.ini b/tox.ini index 5f1a9a426..3a31558d8 100644 --- a/tox.ini +++ b/tox.ini @@ -207,7 +207,17 @@ passenv = DOCKER_* # At the moment, this tests under Python 2.6 only, as only that version is # readily available on the CentOS 6 Docker image. commands = - docker build -f letsencrypt-auto-source/Dockerfile.centos6 -t lea letsencrypt-auto-source + docker build -f letsencrypt-auto-source/Dockerfile.redhat6 --build-arg REDHAT_DIST_FLAVOR=centos -t lea letsencrypt-auto-source + docker run --rm -t -i lea +whitelist_externals = + docker +passenv = DOCKER_* + +[testenv:le_auto_oraclelinux6] +# At the moment, this tests under Python 2.6 only, as only that version is +# readily available on the Oracle Linux 6 Docker image. +commands = + docker build -f letsencrypt-auto-source/Dockerfile.redhat6 --build-arg REDHAT_DIST_FLAVOR=oraclelinux -t lea letsencrypt-auto-source docker run --rm -t -i lea whitelist_externals = docker From 9800e5d8fc2b03d3fc9124428abfb22922613ce9 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 14 Jan 2020 10:41:32 -0800 Subject: [PATCH 033/122] Update changelog for 1.1.0 release --- certbot/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index b3b8ec9d9..8cd893c04 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 1.1.0 - master +## 1.1.0 - 2020-01-14 ### Added From f512b5eaa2ae08dabdcf04e260b34bc59ea594d1 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 14 Jan 2020 10:52:03 -0800 Subject: [PATCH 034/122] Release 1.1.0 --- acme/setup.py | 2 +- certbot-apache/local-oldest-requirements.txt | 2 +- certbot-apache/setup.py | 4 +- certbot-auto | 327 +++++++++++++----- certbot-compatibility-test/setup.py | 2 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-cloudflare/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-cloudxns/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-digitalocean/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-dnsimple/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-dnsmadeeasy/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-gehirn/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-google/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-linode/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-luadns/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-nsone/setup.py | 4 +- certbot-dns-ovh/local-oldest-requirements.txt | 2 +- certbot-dns-ovh/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-rfc2136/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-route53/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-sakuracloud/setup.py | 4 +- certbot-nginx/local-oldest-requirements.txt | 2 +- certbot-nginx/setup.py | 4 +- certbot/certbot/__init__.py | 2 +- certbot/docs/cli-help.txt | 2 +- letsencrypt-auto | 327 +++++++++++++----- letsencrypt-auto-source/certbot-auto.asc | 16 +- letsencrypt-auto-source/letsencrypt-auto | 26 +- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +- 42 files changed, 551 insertions(+), 273 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 6da5fe519..17c321903 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/local-oldest-requirements.txt b/certbot-apache/local-oldest-requirements.txt index 3fce6f83b..cf61c15a5 100644 --- a/certbot-apache/local-oldest-requirements.txt +++ b/certbot-apache/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 204d01620..b27c5e50d 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'mock', 'python-augeas', 'setuptools', diff --git a/certbot-auto b/certbot-auto index 24c007e03..2d3f4cfef 100755 --- a/certbot-auto +++ b/certbot-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="1.0.0" +LE_AUTO_VERSION="1.1.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -256,20 +256,28 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_VERSION="2.7" -MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') +MIN_PYTHON_2_VERSION="2.7" +MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') +MIN_PYTHON_3_VERSION="3.5" +MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version +# digits of the python version. +# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their +# values depend on if we try to use Python 3 or Python 2. DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then + MIN_PYVER=$MIN_PYVER3 + MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else + MIN_PYVER=$MIN_PYVER2 + MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -285,7 +293,7 @@ DeterminePythonVersion() { fi fi - PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` + PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -368,7 +376,9 @@ BootstrapDebCommon() { # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Enables EPEL if applicable and possible. +# Note: this function is called both while selecting the bootstrap scripts and +# during the actual bootstrap. Some things like prompting to user can be done in the latter +# case, but not in the former one. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -388,26 +398,6 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi - - if ! $TOOL list *virtualenv >/dev/null 2>&1; then - echo "To use Certbot, packages from the EPEL repository need to be installed." - if ! $TOOL list epel-release >/dev/null 2>&1; then - error "Enable the EPEL repository and try running Certbot again." - exit 1 - fi - if [ "$ASSUME_YES" = 1 ]; then - /bin/echo -n "Enabling the EPEL repository in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." - sleep 1s - fi - if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then - error "Could not enable EPEL. Aborting bootstrap!" - exit 1 - fi - fi } BootstrapRpmCommonBase() { @@ -488,13 +478,91 @@ BootstrapRpmCommon() { BootstrapRpmCommonBase "$python_pkgs" } +# If new packages are installed by BootstrapRpmPython3 below, this version +# number must be increased. +BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 + +# Checks if rh-python36 can be installed. +Python36SclIsAvailable() { + InitializeRPMCommonBase >/dev/null 2>&1; + + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + return 0 + fi + if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + return 0 + fi + return 1 +} + +# Try to enable rh-python36 from SCL if it is necessary and possible. +EnablePython36SCL() { + if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then + return 0 + fi + if [ ! -f /opt/rh/rh-python36/enable ]; then + return 0 + fi + set +e + if ! . /opt/rh/rh-python36/enable; then + error 'Unable to enable rh-python36!' + exit 1 + fi + set -e +} + +# This bootstrap concerns old RedHat-based distributions that do not ship by default +# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing +# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. +BootstrapRpmPython3Legacy() { + # Tested with: + # - CentOS 6 + + InitializeRPMCommonBase + + if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then + echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." + if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + error "Enable the SCL repository and try running Certbot again." + exit 1 + fi + if [ "${ASSUME_YES}" = 1 ]; then + /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" + sleep 1s + fi + if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then + error "Could not enable SCL. Aborting bootstrap!" + exit 1 + fi + fi + + # CentOS 6 must use rh-python36 from SCL + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + python_pkgs="rh-python36-python + rh-python36-python-virtualenv + rh-python36-python-devel + " + else + error "No supported Python package available to install. Aborting bootstrap!" + exit 1 + fi + + BootstrapRpmCommonBase "${python_pkgs}" + + # Enable SCL rh-python36 after bootstrapping. + EnablePython36SCL +} + # If new packages are installed by BootstrapRpmPython3 below, this version # number must be increased. BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: - # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -505,12 +573,6 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " - # EPEL uses python34 - elif $TOOL list python34 >/dev/null 2>&1; then - python_pkgs="python34 - python34-devel - python34-tools - " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 @@ -758,6 +820,11 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` + if [ "$PYVER" -eq 26 -a $(uname -m) != 'x86_64' ]; then + # 32 bits CentOS 6 and affiliates are not supported anymore by certbot-auto. + DEPRECATED_OS=1 + fi + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an # error, RPM_DIST_VERSION is set to "unknown". @@ -769,31 +836,50 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi + # Handle legacy RPM distributions + if [ "$PYVER" -eq 26 ]; then + # Check if an automated bootstrap can be achieved on this system. + if ! Python36SclIsAvailable; then + INTERACTIVE_BOOTSTRAP=1 + fi - if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 + BootstrapMessage "Legacy RedHat-based OSes that will use Python3" + BootstrapRpmPython3Legacy } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" + + # Try now to enable SCL rh-python36 for systems already bootstrapped + # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto + EnablePython36SCL else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then + Bootstrap() { + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 + } + USE_PYTHON_3=1 + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + else + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + fi fi LE_PYTHON="$prev_le_python" @@ -870,6 +956,13 @@ if [ "$NO_BOOTSTRAP" = 1 ]; then unset BOOTSTRAP_VERSION fi +if [ "$DEPRECATED_OS" = 1 ]; then + Bootstrap() { + error "Skipping bootstrap because certbot-auto is deprecated on this system." + } + unset BOOTSTRAP_VERSION +fi + # Sets PREV_BOOTSTRAP_VERSION to the identifier for the bootstrap script used # to install OS dependencies on this system. PREV_BOOTSTRAP_VERSION isn't set # if it is unknown how OS dependencies were installed on this system. @@ -1067,6 +1160,28 @@ if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. shift 1 # the --le-auto-phase2 arg + + if [ "$DEPRECATED_OS" = 1 ]; then + # Phase 2 damage control mode for deprecated OSes. + # In this situation, we bypass any bootstrap or certbot venv setup. + error "Your system is not supported by certbot-auto anymore." + + if [ ! -d "$VENV_PATH" ] && OldVenvExists; then + VENV_BIN="$OLD_VENV_PATH/bin" + fi + + if [ -f "$VENV_BIN/letsencrypt" -a "$INSTALL_ONLY" != 1 ]; then + error "Certbot will no longer receive updates." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + "$VENV_BIN/letsencrypt" "$@" + exit 0 + else + error "Certbot cannot be installed." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + exit 1 + fi + fi + SetPrevBootstrapVersion if [ -z "$PHASE_1_VERSION" -a "$USE_PYTHON_3" = 1 ]; then @@ -1078,8 +1193,15 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # if non-interactive mode or stdin and stdout are connected to a terminal - if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # Check if we can rebootstrap without manual user intervention: this requires that + # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to + # require a manual user intervention. + if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then + CAN_REBOOTSTRAP=1 + fi + # Check if rebootstrap can be done non-interactively and current shell is non-interactive + # (true if stdin and stdout are not attached to a terminal). + if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -1090,12 +1212,21 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" + # Otherwise bootstrap needs to be done manually by the user. else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." + # If it is because bootstrapping is interactive, --non-interactive will be of no use. + if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then + error "Skipping upgrade because new OS dependencies may need to be installed." + error "This requires manual user intervention: please run this script again manually." + # If this is because of the environment (eg. non interactive shell without + # --non-interactive flag set), help the user in that direction. + else + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." + fi # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist @@ -1372,18 +1503,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.0.0 \ - --hash=sha256:8d074cff89dee002dec1c47cb0da04ea8e0ede8d68838b6d54aa41580d9262df \ - --hash=sha256:86b82d31db19fffffb0d6b218951e2121ef514e3ff659aa042deaf92a33e302a -acme==1.0.0 \ - --hash=sha256:f6972e436e76f7f1e395e81e149f8713ca8462d465b14993bddc53fb18a40644 \ - --hash=sha256:6a08f12f848ce563b50bca421ba9db653df9f82cfefeaf8aba517f046d1386c2 -certbot-apache==1.0.0 \ - --hash=sha256:e591d0cf773ad33ee978f7adb1b69288eac2c8847c643b06e70260e707626f8e \ - --hash=sha256:7335ab5687a0a47d9041d9e13f3a2d67d0e8372da97ab639edb31c14b787cd68 -certbot-nginx==1.0.0 \ - --hash=sha256:ce8a2e51165da7c15bfdc059cd6572d0f368c078f1e1a77633a2773310b2f231 \ - --hash=sha256:63b4ae09d4f1c9ef0a1a2a49c3f651d8a7cb30303ec6f954239e987c5da45dc4 +certbot==1.1.0 \ + --hash=sha256:66a5cab9267349941604c2c98082bfef85877653c023fc324b1c3869fb16add6 \ + --hash=sha256:46e93661a0db53f416c0f5476d8d2e62bc7259b7660dd983453b85df9ef6e8b8 +acme==1.1.0 \ + --hash=sha256:11b9beba706fb8f652c8910d46dd1939d670cac8169f3c66c18c080ed3353e71 \ + --hash=sha256:c305a20eeb9cb02240347703d497891c13d43a47c794fa100d4dbb479a5370d9 +certbot-apache==1.1.0 \ + --hash=sha256:9c847ff223c2e465e241c78d22f97cee77d5e551df608bed06c55f8627f4cbd2 \ + --hash=sha256:05e84dfe96b72582cde97c490977d8e2d33d440c927a320debb4cf287f6fadcc +certbot-nginx==1.1.0 \ + --hash=sha256:bf06fa2f5059f0fdb7d352c8739e1ed0830db4f0d89e812dab4f081bda6ec7d6 \ + --hash=sha256:0a80ecbd2a30f3757c7652cabfff854ca07873b1cf02ebbe1892786c3b3a5874 UNLIKELY_EOF # ------------------------------------------------------------------------- @@ -1617,6 +1748,9 @@ UNLIKELY_EOF say "Installation succeeded." fi + # If you're modifying any of the code after this point in this current `if` block, you + # may need to update the "$DEPRECATED_OS" = 1 case at the beginning of phase 2 as well. + if [ "$INSTALL_ONLY" = 1 ]; then say "Certbot is installed." exit 0 @@ -1828,30 +1962,35 @@ UNLIKELY_EOF error "WARNING: unable to check for updates." fi - LE_VERSION_STATE=`CompareVersions "$LE_PYTHON" "$LE_AUTO_VERSION" "$REMOTE_VERSION"` - if [ "$LE_VERSION_STATE" = "UNOFFICIAL" ]; then - say "Unofficial certbot-auto version detected, self-upgrade is disabled: $LE_AUTO_VERSION" - elif [ "$LE_VERSION_STATE" = "OUTDATED" ]; then - say "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..." + # If for any reason REMOTE_VERSION is not set, let's assume certbot-auto is up-to-date, + # and do not go into the self-upgrading process. + if [ -n "$REMOTE_VERSION" ]; then + LE_VERSION_STATE=`CompareVersions "$LE_PYTHON" "$LE_AUTO_VERSION" "$REMOTE_VERSION"` - # Now we drop into Python so we don't have to install even more - # dependencies (curl, etc.), for better flow control, and for the option of - # future Windows compatibility. - "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" + if [ "$LE_VERSION_STATE" = "UNOFFICIAL" ]; then + say "Unofficial certbot-auto version detected, self-upgrade is disabled: $LE_AUTO_VERSION" + elif [ "$LE_VERSION_STATE" = "OUTDATED" ]; then + say "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..." - # Install new copy of certbot-auto. - # TODO: Deal with quotes in pathnames. - say "Replacing certbot-auto..." - # Clone permissions with cp. chmod and chown don't have a --reference - # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: - cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" - cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" - # Using mv rather than cp leaves the old file descriptor pointing to the - # original copy so the shell can continue to read it unmolested. mv across - # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the - # cp is unlikely to fail if the rm doesn't. - mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" - fi # A newer version is available. + # Now we drop into Python so we don't have to install even more + # dependencies (curl, etc.), for better flow control, and for the option of + # future Windows compatibility. + "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" + + # Install new copy of certbot-auto. + # TODO: Deal with quotes in pathnames. + say "Replacing certbot-auto..." + # Clone permissions with cp. chmod and chown don't have a --reference + # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: + cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" + cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" + # Using mv rather than cp leaves the old file descriptor pointing to the + # original copy so the shell can continue to read it unmolested. mv across + # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the + # cp is unlikely to fail if the rm doesn't. + mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" + fi # A newer version is available. + fi fi # Self-upgrading is allowed. RerunWithArgs --le-auto-phase2 "$@" diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index f26fb0706..8b488129a 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -3,7 +3,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.1.0.dev0' +version = '1.1.0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/local-oldest-requirements.txt b/certbot-dns-cloudflare/local-oldest-requirements.txt index 3fce6f83b..cf61c15a5 100644 --- a/certbot-dns-cloudflare/local-oldest-requirements.txt +++ b/certbot-dns-cloudflare/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index b3fd81223..7a8f4ae7b 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'cloudflare>=1.5.1', 'mock', 'setuptools', diff --git a/certbot-dns-cloudxns/local-oldest-requirements.txt b/certbot-dns-cloudxns/local-oldest-requirements.txt index 67d4cc53b..1307698d4 100644 --- a/certbot-dns-cloudxns/local-oldest-requirements.txt +++ b/certbot-dns-cloudxns/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 288a6d115..00780ead2 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-digitalocean/local-oldest-requirements.txt b/certbot-dns-digitalocean/local-oldest-requirements.txt index 3fce6f83b..cf61c15a5 100644 --- a/certbot-dns-digitalocean/local-oldest-requirements.txt +++ b/certbot-dns-digitalocean/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index ba3190567..ce8c8f19f 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'mock', 'python-digitalocean>=1.11', 'setuptools', diff --git a/certbot-dns-dnsimple/local-oldest-requirements.txt b/certbot-dns-dnsimple/local-oldest-requirements.txt index 67d4cc53b..1307698d4 100644 --- a/certbot-dns-dnsimple/local-oldest-requirements.txt +++ b/certbot-dns-dnsimple/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 5729bd789..7fa0ed095 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -5,13 +5,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'mock', 'setuptools', 'zope.interface', diff --git a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt index 67d4cc53b..1307698d4 100644 --- a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt +++ b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 6fc756389..3b5f42632 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-gehirn/local-oldest-requirements.txt b/certbot-dns-gehirn/local-oldest-requirements.txt index 67d4cc53b..1307698d4 100644 --- a/certbot-dns-gehirn/local-oldest-requirements.txt +++ b/certbot-dns-gehirn/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 7c4da556d..151fa6531 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -4,12 +4,12 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dns-lexicon>=2.1.22', 'mock', 'setuptools', diff --git a/certbot-dns-google/local-oldest-requirements.txt b/certbot-dns-google/local-oldest-requirements.txt index 3fce6f83b..cf61c15a5 100644 --- a/certbot-dns-google/local-oldest-requirements.txt +++ b/certbot-dns-google/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index a0dc1c386..cb0d664be 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'google-api-python-client>=1.5.5', 'mock', 'oauth2client>=4.0', diff --git a/certbot-dns-linode/local-oldest-requirements.txt b/certbot-dns-linode/local-oldest-requirements.txt index 1829f7eb2..a8bd7449a 100644 --- a/certbot-dns-linode/local-oldest-requirements.txt +++ b/certbot-dns-linode/local-oldest-requirements.txt @@ -1,4 +1,4 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 dns-lexicon==2.2.3 diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index f772dc26a..5f12c51ae 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -4,12 +4,12 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dns-lexicon>=2.2.3', 'mock', 'setuptools', diff --git a/certbot-dns-luadns/local-oldest-requirements.txt b/certbot-dns-luadns/local-oldest-requirements.txt index 67d4cc53b..1307698d4 100644 --- a/certbot-dns-luadns/local-oldest-requirements.txt +++ b/certbot-dns-luadns/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 18ba8cacc..f041b59c7 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-nsone/local-oldest-requirements.txt b/certbot-dns-nsone/local-oldest-requirements.txt index 67d4cc53b..1307698d4 100644 --- a/certbot-dns-nsone/local-oldest-requirements.txt +++ b/certbot-dns-nsone/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 3894f01cd..adef7567a 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-ovh/local-oldest-requirements.txt b/certbot-dns-ovh/local-oldest-requirements.txt index 2e11550d6..c55e0d570 100644 --- a/certbot-dns-ovh/local-oldest-requirements.txt +++ b/certbot-dns-ovh/local-oldest-requirements.txt @@ -1,4 +1,4 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 dns-lexicon==2.7.14 diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 2fccf17c2..aeec4e88f 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dns-lexicon>=2.7.14', # Correct proxy use on OVH provider 'mock', 'setuptools', diff --git a/certbot-dns-rfc2136/local-oldest-requirements.txt b/certbot-dns-rfc2136/local-oldest-requirements.txt index 3fce6f83b..cf61c15a5 100644 --- a/certbot-dns-rfc2136/local-oldest-requirements.txt +++ b/certbot-dns-rfc2136/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 47167fa2b..df01f3868 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dnspython', 'mock', 'setuptools', diff --git a/certbot-dns-route53/local-oldest-requirements.txt b/certbot-dns-route53/local-oldest-requirements.txt index 3fce6f83b..cf61c15a5 100644 --- a/certbot-dns-route53/local-oldest-requirements.txt +++ b/certbot-dns-route53/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index b4dcc58c1..40a62b8c5 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'boto3', 'mock', 'setuptools', diff --git a/certbot-dns-sakuracloud/local-oldest-requirements.txt b/certbot-dns-sakuracloud/local-oldest-requirements.txt index 67d4cc53b..1307698d4 100644 --- a/certbot-dns-sakuracloud/local-oldest-requirements.txt +++ b/certbot-dns-sakuracloud/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 56c209a90..d13641024 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -4,12 +4,12 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dns-lexicon>=2.1.23', 'mock', 'setuptools', diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index 37532aabf..cee142934 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==1.0.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 96bf32d3e..1122a56bd 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=1.0.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'mock', 'PyOpenSSL', 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? diff --git a/certbot/certbot/__init__.py b/certbot/certbot/__init__.py index 71c7e4e87..1e78d4345 100644 --- a/certbot/certbot/__init__.py +++ b/certbot/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '1.1.0.dev0' +__version__ = '1.1.0' diff --git a/certbot/docs/cli-help.txt b/certbot/docs/cli-help.txt index de12cefda..9b463820a 100644 --- a/certbot/docs/cli-help.txt +++ b/certbot/docs/cli-help.txt @@ -113,7 +113,7 @@ optional arguments: case, and to know when to deprecate support for past Python versions and flags. If you wish to hide this information from the Let's Encrypt server, set this to - "". (default: CertbotACMEClient/1.0.0 (certbot(-auto); + "". (default: CertbotACMEClient/1.1.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the user agent are: --duplicate, diff --git a/letsencrypt-auto b/letsencrypt-auto index 24c007e03..2d3f4cfef 100755 --- a/letsencrypt-auto +++ b/letsencrypt-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="1.0.0" +LE_AUTO_VERSION="1.1.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -256,20 +256,28 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_VERSION="2.7" -MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') +MIN_PYTHON_2_VERSION="2.7" +MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') +MIN_PYTHON_3_VERSION="3.5" +MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version +# digits of the python version. +# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their +# values depend on if we try to use Python 3 or Python 2. DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then + MIN_PYVER=$MIN_PYVER3 + MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else + MIN_PYVER=$MIN_PYVER2 + MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -285,7 +293,7 @@ DeterminePythonVersion() { fi fi - PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` + PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -368,7 +376,9 @@ BootstrapDebCommon() { # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Enables EPEL if applicable and possible. +# Note: this function is called both while selecting the bootstrap scripts and +# during the actual bootstrap. Some things like prompting to user can be done in the latter +# case, but not in the former one. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -388,26 +398,6 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi - - if ! $TOOL list *virtualenv >/dev/null 2>&1; then - echo "To use Certbot, packages from the EPEL repository need to be installed." - if ! $TOOL list epel-release >/dev/null 2>&1; then - error "Enable the EPEL repository and try running Certbot again." - exit 1 - fi - if [ "$ASSUME_YES" = 1 ]; then - /bin/echo -n "Enabling the EPEL repository in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." - sleep 1s - fi - if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then - error "Could not enable EPEL. Aborting bootstrap!" - exit 1 - fi - fi } BootstrapRpmCommonBase() { @@ -488,13 +478,91 @@ BootstrapRpmCommon() { BootstrapRpmCommonBase "$python_pkgs" } +# If new packages are installed by BootstrapRpmPython3 below, this version +# number must be increased. +BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 + +# Checks if rh-python36 can be installed. +Python36SclIsAvailable() { + InitializeRPMCommonBase >/dev/null 2>&1; + + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + return 0 + fi + if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + return 0 + fi + return 1 +} + +# Try to enable rh-python36 from SCL if it is necessary and possible. +EnablePython36SCL() { + if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then + return 0 + fi + if [ ! -f /opt/rh/rh-python36/enable ]; then + return 0 + fi + set +e + if ! . /opt/rh/rh-python36/enable; then + error 'Unable to enable rh-python36!' + exit 1 + fi + set -e +} + +# This bootstrap concerns old RedHat-based distributions that do not ship by default +# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing +# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. +BootstrapRpmPython3Legacy() { + # Tested with: + # - CentOS 6 + + InitializeRPMCommonBase + + if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then + echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." + if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + error "Enable the SCL repository and try running Certbot again." + exit 1 + fi + if [ "${ASSUME_YES}" = 1 ]; then + /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" + sleep 1s + fi + if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then + error "Could not enable SCL. Aborting bootstrap!" + exit 1 + fi + fi + + # CentOS 6 must use rh-python36 from SCL + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + python_pkgs="rh-python36-python + rh-python36-python-virtualenv + rh-python36-python-devel + " + else + error "No supported Python package available to install. Aborting bootstrap!" + exit 1 + fi + + BootstrapRpmCommonBase "${python_pkgs}" + + # Enable SCL rh-python36 after bootstrapping. + EnablePython36SCL +} + # If new packages are installed by BootstrapRpmPython3 below, this version # number must be increased. BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: - # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -505,12 +573,6 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " - # EPEL uses python34 - elif $TOOL list python34 >/dev/null 2>&1; then - python_pkgs="python34 - python34-devel - python34-tools - " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 @@ -758,6 +820,11 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` + if [ "$PYVER" -eq 26 -a $(uname -m) != 'x86_64' ]; then + # 32 bits CentOS 6 and affiliates are not supported anymore by certbot-auto. + DEPRECATED_OS=1 + fi + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an # error, RPM_DIST_VERSION is set to "unknown". @@ -769,31 +836,50 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi + # Handle legacy RPM distributions + if [ "$PYVER" -eq 26 ]; then + # Check if an automated bootstrap can be achieved on this system. + if ! Python36SclIsAvailable; then + INTERACTIVE_BOOTSTRAP=1 + fi - if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 + BootstrapMessage "Legacy RedHat-based OSes that will use Python3" + BootstrapRpmPython3Legacy } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" + + # Try now to enable SCL rh-python36 for systems already bootstrapped + # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto + EnablePython36SCL else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then + Bootstrap() { + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 + } + USE_PYTHON_3=1 + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + else + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + fi fi LE_PYTHON="$prev_le_python" @@ -870,6 +956,13 @@ if [ "$NO_BOOTSTRAP" = 1 ]; then unset BOOTSTRAP_VERSION fi +if [ "$DEPRECATED_OS" = 1 ]; then + Bootstrap() { + error "Skipping bootstrap because certbot-auto is deprecated on this system." + } + unset BOOTSTRAP_VERSION +fi + # Sets PREV_BOOTSTRAP_VERSION to the identifier for the bootstrap script used # to install OS dependencies on this system. PREV_BOOTSTRAP_VERSION isn't set # if it is unknown how OS dependencies were installed on this system. @@ -1067,6 +1160,28 @@ if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. shift 1 # the --le-auto-phase2 arg + + if [ "$DEPRECATED_OS" = 1 ]; then + # Phase 2 damage control mode for deprecated OSes. + # In this situation, we bypass any bootstrap or certbot venv setup. + error "Your system is not supported by certbot-auto anymore." + + if [ ! -d "$VENV_PATH" ] && OldVenvExists; then + VENV_BIN="$OLD_VENV_PATH/bin" + fi + + if [ -f "$VENV_BIN/letsencrypt" -a "$INSTALL_ONLY" != 1 ]; then + error "Certbot will no longer receive updates." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + "$VENV_BIN/letsencrypt" "$@" + exit 0 + else + error "Certbot cannot be installed." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + exit 1 + fi + fi + SetPrevBootstrapVersion if [ -z "$PHASE_1_VERSION" -a "$USE_PYTHON_3" = 1 ]; then @@ -1078,8 +1193,15 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # if non-interactive mode or stdin and stdout are connected to a terminal - if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # Check if we can rebootstrap without manual user intervention: this requires that + # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to + # require a manual user intervention. + if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then + CAN_REBOOTSTRAP=1 + fi + # Check if rebootstrap can be done non-interactively and current shell is non-interactive + # (true if stdin and stdout are not attached to a terminal). + if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -1090,12 +1212,21 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" + # Otherwise bootstrap needs to be done manually by the user. else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." + # If it is because bootstrapping is interactive, --non-interactive will be of no use. + if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then + error "Skipping upgrade because new OS dependencies may need to be installed." + error "This requires manual user intervention: please run this script again manually." + # If this is because of the environment (eg. non interactive shell without + # --non-interactive flag set), help the user in that direction. + else + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." + fi # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist @@ -1372,18 +1503,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.0.0 \ - --hash=sha256:8d074cff89dee002dec1c47cb0da04ea8e0ede8d68838b6d54aa41580d9262df \ - --hash=sha256:86b82d31db19fffffb0d6b218951e2121ef514e3ff659aa042deaf92a33e302a -acme==1.0.0 \ - --hash=sha256:f6972e436e76f7f1e395e81e149f8713ca8462d465b14993bddc53fb18a40644 \ - --hash=sha256:6a08f12f848ce563b50bca421ba9db653df9f82cfefeaf8aba517f046d1386c2 -certbot-apache==1.0.0 \ - --hash=sha256:e591d0cf773ad33ee978f7adb1b69288eac2c8847c643b06e70260e707626f8e \ - --hash=sha256:7335ab5687a0a47d9041d9e13f3a2d67d0e8372da97ab639edb31c14b787cd68 -certbot-nginx==1.0.0 \ - --hash=sha256:ce8a2e51165da7c15bfdc059cd6572d0f368c078f1e1a77633a2773310b2f231 \ - --hash=sha256:63b4ae09d4f1c9ef0a1a2a49c3f651d8a7cb30303ec6f954239e987c5da45dc4 +certbot==1.1.0 \ + --hash=sha256:66a5cab9267349941604c2c98082bfef85877653c023fc324b1c3869fb16add6 \ + --hash=sha256:46e93661a0db53f416c0f5476d8d2e62bc7259b7660dd983453b85df9ef6e8b8 +acme==1.1.0 \ + --hash=sha256:11b9beba706fb8f652c8910d46dd1939d670cac8169f3c66c18c080ed3353e71 \ + --hash=sha256:c305a20eeb9cb02240347703d497891c13d43a47c794fa100d4dbb479a5370d9 +certbot-apache==1.1.0 \ + --hash=sha256:9c847ff223c2e465e241c78d22f97cee77d5e551df608bed06c55f8627f4cbd2 \ + --hash=sha256:05e84dfe96b72582cde97c490977d8e2d33d440c927a320debb4cf287f6fadcc +certbot-nginx==1.1.0 \ + --hash=sha256:bf06fa2f5059f0fdb7d352c8739e1ed0830db4f0d89e812dab4f081bda6ec7d6 \ + --hash=sha256:0a80ecbd2a30f3757c7652cabfff854ca07873b1cf02ebbe1892786c3b3a5874 UNLIKELY_EOF # ------------------------------------------------------------------------- @@ -1617,6 +1748,9 @@ UNLIKELY_EOF say "Installation succeeded." fi + # If you're modifying any of the code after this point in this current `if` block, you + # may need to update the "$DEPRECATED_OS" = 1 case at the beginning of phase 2 as well. + if [ "$INSTALL_ONLY" = 1 ]; then say "Certbot is installed." exit 0 @@ -1828,30 +1962,35 @@ UNLIKELY_EOF error "WARNING: unable to check for updates." fi - LE_VERSION_STATE=`CompareVersions "$LE_PYTHON" "$LE_AUTO_VERSION" "$REMOTE_VERSION"` - if [ "$LE_VERSION_STATE" = "UNOFFICIAL" ]; then - say "Unofficial certbot-auto version detected, self-upgrade is disabled: $LE_AUTO_VERSION" - elif [ "$LE_VERSION_STATE" = "OUTDATED" ]; then - say "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..." + # If for any reason REMOTE_VERSION is not set, let's assume certbot-auto is up-to-date, + # and do not go into the self-upgrading process. + if [ -n "$REMOTE_VERSION" ]; then + LE_VERSION_STATE=`CompareVersions "$LE_PYTHON" "$LE_AUTO_VERSION" "$REMOTE_VERSION"` - # Now we drop into Python so we don't have to install even more - # dependencies (curl, etc.), for better flow control, and for the option of - # future Windows compatibility. - "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" + if [ "$LE_VERSION_STATE" = "UNOFFICIAL" ]; then + say "Unofficial certbot-auto version detected, self-upgrade is disabled: $LE_AUTO_VERSION" + elif [ "$LE_VERSION_STATE" = "OUTDATED" ]; then + say "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..." - # Install new copy of certbot-auto. - # TODO: Deal with quotes in pathnames. - say "Replacing certbot-auto..." - # Clone permissions with cp. chmod and chown don't have a --reference - # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: - cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" - cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" - # Using mv rather than cp leaves the old file descriptor pointing to the - # original copy so the shell can continue to read it unmolested. mv across - # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the - # cp is unlikely to fail if the rm doesn't. - mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" - fi # A newer version is available. + # Now we drop into Python so we don't have to install even more + # dependencies (curl, etc.), for better flow control, and for the option of + # future Windows compatibility. + "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" + + # Install new copy of certbot-auto. + # TODO: Deal with quotes in pathnames. + say "Replacing certbot-auto..." + # Clone permissions with cp. chmod and chown don't have a --reference + # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: + cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" + cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" + # Using mv rather than cp leaves the old file descriptor pointing to the + # original copy so the shell can continue to read it unmolested. mv across + # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the + # cp is unlikely to fail if the rm doesn't. + mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" + fi # A newer version is available. + fi fi # Self-upgrading is allowed. RerunWithArgs --le-auto-phase2 "$@" diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index aea28117c..1a030eb47 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl3mmvMACgkQTRfJlc2X -dfKUbQf/aW8ZWRH36WhTHmZjJmBumSUYclFdDAR4c6Ym+MBTeYT0iQq/dqfqTklB -7jPHTcxWbyMJCjOqtMEDRt+aVF0A91OA1bSRt1MJCm7o8Oa1h4XVVPL2UZYCPNlu -46UEBGDOkd6DlrRvD0X2BrQ4EsktLe1d+EoDbDPebwfip9OYnEYMD7EQB9O3N8eo -aYRkaSJMc2HalI5u0oLEhnZGucNw6K7uvuW0LkwmRWpN8Lc8e9ELZ3FOCE6qD9yh -giAkvZNklwhAxkk9spFkEilvEOPVtKgiSS6jZIL5G1NlAhp8n6+vhatY5Aotw8nO -QrqmPvzBd+2Gy2nrrGuSMC146m0x/g== -=3A0n +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl4eDcYACgkQTRfJlc2X +dfIAiQgAufTpgNvnHKoLQLwWf3GbjLQYWc3w1zRbGUMjghS/rS1yuf7RE/IPItET +ocIuIE36ogjvgnRuI0OOu3yJ+jxe41u3ToPb0ehNhINd+3rXsDhzwJDPjFdOiq98 +NoW9wQE9AHSfKEEVprckuZe2XmNLsYbBfa9THFULYIlnqAewtercXXx0eKaMG9+d +aRaD+LZXANx7IV6XnI9jfdKRuldHDvYp1TdvrRWBAVHid8j44c3P0pSvzf0YKGbx +xIty/w0zQFIWCfqPdK7/R2EHbEyR0SdI00a1Va1x7P8JGf7kDyLXl+Y9Yth7/uHA +osivJCpSrtAEbvMXojnL7u7kq3b37Q== +=Une9 -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 78e449444..2d3f4cfef 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="1.1.0.dev0" +LE_AUTO_VERSION="1.1.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1503,18 +1503,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.0.0 \ - --hash=sha256:8d074cff89dee002dec1c47cb0da04ea8e0ede8d68838b6d54aa41580d9262df \ - --hash=sha256:86b82d31db19fffffb0d6b218951e2121ef514e3ff659aa042deaf92a33e302a -acme==1.0.0 \ - --hash=sha256:f6972e436e76f7f1e395e81e149f8713ca8462d465b14993bddc53fb18a40644 \ - --hash=sha256:6a08f12f848ce563b50bca421ba9db653df9f82cfefeaf8aba517f046d1386c2 -certbot-apache==1.0.0 \ - --hash=sha256:e591d0cf773ad33ee978f7adb1b69288eac2c8847c643b06e70260e707626f8e \ - --hash=sha256:7335ab5687a0a47d9041d9e13f3a2d67d0e8372da97ab639edb31c14b787cd68 -certbot-nginx==1.0.0 \ - --hash=sha256:ce8a2e51165da7c15bfdc059cd6572d0f368c078f1e1a77633a2773310b2f231 \ - --hash=sha256:63b4ae09d4f1c9ef0a1a2a49c3f651d8a7cb30303ec6f954239e987c5da45dc4 +certbot==1.1.0 \ + --hash=sha256:66a5cab9267349941604c2c98082bfef85877653c023fc324b1c3869fb16add6 \ + --hash=sha256:46e93661a0db53f416c0f5476d8d2e62bc7259b7660dd983453b85df9ef6e8b8 +acme==1.1.0 \ + --hash=sha256:11b9beba706fb8f652c8910d46dd1939d670cac8169f3c66c18c080ed3353e71 \ + --hash=sha256:c305a20eeb9cb02240347703d497891c13d43a47c794fa100d4dbb479a5370d9 +certbot-apache==1.1.0 \ + --hash=sha256:9c847ff223c2e465e241c78d22f97cee77d5e551df608bed06c55f8627f4cbd2 \ + --hash=sha256:05e84dfe96b72582cde97c490977d8e2d33d440c927a320debb4cf287f6fadcc +certbot-nginx==1.1.0 \ + --hash=sha256:bf06fa2f5059f0fdb7d352c8739e1ed0830db4f0d89e812dab4f081bda6ec7d6 \ + --hash=sha256:0a80ecbd2a30f3757c7652cabfff854ca07873b1cf02ebbe1892786c3b3a5874 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index 705f30e3fce3c45db179e553348d7b7e845347b6..bae77d59b03aeb894b36fb45c0e245e7d60390d3 100644 GIT binary patch literal 256 zcmV+b0ssEAyS$rfNEUV!Wh;2cS{MwhW*KFW9UAqrQ{{0e?w7n+C59VM_X+?SAt^yd z0iNLyAY~e39kT5**?!n!d=x%;*;HG@=uy&|A2TSoXAEblY3xW6Ply`4f(38*T G)t{UAv38mO literal 256 zcmV+b0ssDARL+ERwN3$7z?P9q)sh!%6T!2Xdb4NHkA|X} z!5EAZ3$%Zoj@f6~8lo6Fr9fbm?QfGMq$MJP)sycQ3JK#Sd?NxyvycV=F@=*=j^m@| zolC!4%-5%P<|T}Z_>%giBBnzGqgzg#KjW}!j+yRiWZsn8AIUe+6=Z%)IcqA<=VYJW zd(fb&JyVM;j9TXF7PVz}Pp$}g0=GwN`VdKnknST?`u=#imX!JHePJ*y++m^GRj+n| z?FL3J5E+Os67n9PO?f!Lot<5vo_x9tQ;F$ZjFUlRdicE?eKd95cqEQrGmU^@T5zBt GWES(DZG2e( diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index d4bdfd49e..67a33390b 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==1.0.0 \ - --hash=sha256:8d074cff89dee002dec1c47cb0da04ea8e0ede8d68838b6d54aa41580d9262df \ - --hash=sha256:86b82d31db19fffffb0d6b218951e2121ef514e3ff659aa042deaf92a33e302a -acme==1.0.0 \ - --hash=sha256:f6972e436e76f7f1e395e81e149f8713ca8462d465b14993bddc53fb18a40644 \ - --hash=sha256:6a08f12f848ce563b50bca421ba9db653df9f82cfefeaf8aba517f046d1386c2 -certbot-apache==1.0.0 \ - --hash=sha256:e591d0cf773ad33ee978f7adb1b69288eac2c8847c643b06e70260e707626f8e \ - --hash=sha256:7335ab5687a0a47d9041d9e13f3a2d67d0e8372da97ab639edb31c14b787cd68 -certbot-nginx==1.0.0 \ - --hash=sha256:ce8a2e51165da7c15bfdc059cd6572d0f368c078f1e1a77633a2773310b2f231 \ - --hash=sha256:63b4ae09d4f1c9ef0a1a2a49c3f651d8a7cb30303ec6f954239e987c5da45dc4 +certbot==1.1.0 \ + --hash=sha256:66a5cab9267349941604c2c98082bfef85877653c023fc324b1c3869fb16add6 \ + --hash=sha256:46e93661a0db53f416c0f5476d8d2e62bc7259b7660dd983453b85df9ef6e8b8 +acme==1.1.0 \ + --hash=sha256:11b9beba706fb8f652c8910d46dd1939d670cac8169f3c66c18c080ed3353e71 \ + --hash=sha256:c305a20eeb9cb02240347703d497891c13d43a47c794fa100d4dbb479a5370d9 +certbot-apache==1.1.0 \ + --hash=sha256:9c847ff223c2e465e241c78d22f97cee77d5e551df608bed06c55f8627f4cbd2 \ + --hash=sha256:05e84dfe96b72582cde97c490977d8e2d33d440c927a320debb4cf287f6fadcc +certbot-nginx==1.1.0 \ + --hash=sha256:bf06fa2f5059f0fdb7d352c8739e1ed0830db4f0d89e812dab4f081bda6ec7d6 \ + --hash=sha256:0a80ecbd2a30f3757c7652cabfff854ca07873b1cf02ebbe1892786c3b3a5874 From 60cd920bcb4490e630febff1a934316c5b6de1ca Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 14 Jan 2020 10:52:05 -0800 Subject: [PATCH 035/122] Add contents to certbot/CHANGELOG.md for next version --- certbot/CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 8cd893c04..8884aeb5d 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -2,6 +2,22 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 1.2.0 - master + +### Added + +* + +### Changed + +* + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + ## 1.1.0 - 2020-01-14 ### Added From 619b17753e9907df79b6ebf019600a5b46677f1c Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 14 Jan 2020 10:52:05 -0800 Subject: [PATCH 036/122] Bump version to 1.2.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 17c321903..2ab4db34e 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index b27c5e50d..b676330c6 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 8b488129a..177fd9d31 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -3,7 +3,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.1.0' +version = '1.2.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 7a8f4ae7b..0f9dd70dd 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 00780ead2..d4d40e8b3 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index ce8c8f19f..62fc85603 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 7fa0ed095..69e20c06b 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 3b5f42632..c42573554 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 151fa6531..61ba30c0e 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index cb0d664be..d53ed9723 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 5f12c51ae..1e698c229 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index f041b59c7..0df82b08f 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index adef7567a..b98ec96c5 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index aeec4e88f..1b2e0cad2 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index df01f3868..1af30681a 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 40a62b8c5..70e19048b 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index d13641024..2c762bc4a 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 1122a56bd..eb6961729 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/certbot/__init__.py b/certbot/certbot/__init__.py index 1e78d4345..caae1a041 100644 --- a/certbot/certbot/__init__.py +++ b/certbot/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '1.1.0' +__version__ = '1.2.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 2d3f4cfef..17badacc7 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="1.1.0" +LE_AUTO_VERSION="1.2.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates From 6e07e8b5c0f3c6da2ae03a2a63adc7ce8e15a202 Mon Sep 17 00:00:00 2001 From: osirisinferi Date: Thu, 16 Jan 2020 20:31:22 +0100 Subject: [PATCH 037/122] Add missing directory field (#7687) Fixes #7683. * Add missing directory field to error message * Added change to CHANGELOG.md --- acme/acme/messages.py | 2 +- certbot/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/acme/acme/messages.py b/acme/acme/messages.py index 96a1ed7c0..c824c43cf 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -251,7 +251,7 @@ class Directory(jose.JSONDeSerializable): try: return self._jobj[self._canon_key(name)] except KeyError: - raise KeyError('Directory field not found') + raise KeyError('Directory field "' + self._canon_key(name) + '" not found') def to_partial_json(self): return self._jobj diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 8884aeb5d..dd70000cd 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -10,7 +10,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed -* +* Add directory field to error message when field is missing. ### Fixed From 91ce42ce9c47395f979c7fd72a6fa2b6da1f3ac7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 16 Jan 2020 13:44:08 -0800 Subject: [PATCH 038/122] Do not list the name twice. (#7689) --- acme/acme/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme/acme/messages.py b/acme/acme/messages.py index c824c43cf..1d907e5fc 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -245,7 +245,7 @@ class Directory(jose.JSONDeSerializable): try: return self[name.replace('_', '-')] except KeyError as error: - raise AttributeError(str(error) + ': ' + name) + raise AttributeError(str(error)) def __getitem__(self, name): try: From 9a3186a67ee9ed28299a190aaee70179fb8b0cba Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 16 Jan 2020 13:47:23 -0800 Subject: [PATCH 039/122] Cleanup disabled warnings list in pytest.ini. (#7690) --- pytest.ini | 7 +------ tools/dev_constraints.txt | 18 +++++++++--------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/pytest.ini b/pytest.ini index 6c2404056..31a294ca6 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,15 +4,10 @@ [pytest] # In general, all warnings are treated as errors. Here are the exceptions: # 1- decodestring: https://github.com/rthalley/dnspython/issues/338 -# 2- ignore our own TLS-SNI-01 warning -# 3- ignore warn for importing abstract classes from collections instead of collections.abc, +# 2- ignore warn for importing abstract classes from collections instead of collections.abc, # too much third party dependencies are still relying on this behavior, # but it should be corrected to allow Certbot compatiblity with Python >= 3.8 -# 4- ipdb uses deprecated functionality of IPython. See -# https://github.com/gotcha/ipdb/issues/144. filterwarnings = error ignore:decodestring:DeprecationWarning ignore:.*collections\.abc:DeprecationWarning - ignore:The `color_scheme` argument is deprecated:DeprecationWarning:IPython.* - ignore:.*get_systemd_os_info:DeprecationWarning diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index d5d78c96a..a16a9d680 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -21,7 +21,7 @@ codecov==2.0.15 configparser==3.7.4 contextlib2==0.6.0.post1 coverage==4.5.4 -decorator==4.1.2 +decorator==4.4.1 dns-lexicon==3.2.1 dnspython==1.15.0 docker==3.7.2 @@ -39,8 +39,8 @@ google-api-python-client==1.5.5 httplib2==0.10.3 imagesize==0.7.1 importlib-metadata==0.23 -ipdb==0.10.2 -ipython==5.5.0 +ipdb==0.12.3 +ipython==5.8.0 ipython-genutils==0.2.0 isort==4.3.21 Jinja2==2.9.6 @@ -59,12 +59,12 @@ ndg-httpsclient==0.3.2 oauth2client==4.0.0 packaging==19.2 paramiko==2.4.2 -pathlib2==2.3.0 +pathlib2==2.3.5 pexpect==4.7.0 -pickleshare==0.7.4 +pickleshare==0.7.5 pkginfo==1.4.2 pluggy==0.13.0 -prompt-toolkit==1.0.15 +prompt-toolkit==1.0.18 ptyprocess==0.6.0 py==1.8.0 pyasn1==0.1.9 @@ -90,7 +90,7 @@ requests-file==1.4.2 requests-toolbelt==0.8.0 rsa==3.4.2 s3transfer==0.1.11 -scandir==1.6 +scandir==1.10.0 simplegeneric==0.8.1 singledispatch==3.4.0.3 snowballstemmer==1.2.1 @@ -102,13 +102,13 @@ tldextract==2.2.0 toml==0.10.0 tox==3.14.0 tqdm==4.19.4 -traitlets==4.3.2 +traitlets==4.3.3 twine==1.11.0 typed-ast==1.4.0 typing==3.6.4 uritemplate==3.0.0 virtualenv==16.6.2 -wcwidth==0.1.7 +wcwidth==0.1.8 websocket-client==0.56.0 wrapt==1.11.2 zipp==0.6.0 From 5f0703cbf161f216c59f6b50888644df741b008c Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 16 Jan 2020 13:54:25 -0800 Subject: [PATCH 040/122] Fix minimum certbot version in plugins (#7684) Fixes the problem found at https://github.com/certbot/certbot/pull/7682#discussion_r367140415. --- certbot-apache/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index b676330c6..7c74d5869 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'mock', 'python-augeas', 'setuptools', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 0f9dd70dd..7f4f5137a 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'cloudflare>=1.5.1', 'mock', 'setuptools', diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index d4d40e8b3..4e04ae820 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 62fc85603..1f146b678 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'mock', 'python-digitalocean>=1.11', 'setuptools', diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 69e20c06b..c486c37df 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -11,7 +11,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'mock', 'setuptools', 'zope.interface', diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index c42573554..7eb4473aa 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 61ba30c0e..19036b52b 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -9,7 +9,7 @@ version = '1.2.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dns-lexicon>=2.1.22', 'mock', 'setuptools', diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index d53ed9723..08d9755a1 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'google-api-python-client>=1.5.5', 'mock', 'oauth2client>=4.0', diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 1e698c229..27165a09f 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -9,7 +9,7 @@ version = '1.2.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dns-lexicon>=2.2.3', 'mock', 'setuptools', diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 0df82b08f..ea669dc65 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index b98ec96c5..f90d73e0e 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 1b2e0cad2..6a9281498 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dns-lexicon>=2.7.14', # Correct proxy use on OVH provider 'mock', 'setuptools', diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 1af30681a..df391fc65 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dnspython', 'mock', 'setuptools', diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 70e19048b..01f9c9ee2 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'boto3', 'mock', 'setuptools', diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 2c762bc4a..e81be051d 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -9,7 +9,7 @@ version = '1.2.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dns-lexicon>=2.1.23', 'mock', 'setuptools', diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index eb6961729..4a5e5eb05 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=1.0.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'mock', 'PyOpenSSL', 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? From 702ad99090f1901460c272d4ee481e4a9b4e3fac Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 16 Jan 2020 14:08:38 -0800 Subject: [PATCH 041/122] Don't run some tests multiple times. (#7685) --- .travis.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index fdb692ac1..33c50b9f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,9 +62,6 @@ matrix: - python: "3.4" env: TOXENV=py34 <<: *not-on-master - - python: "3.7" - env: TOXENV=py37 - <<: *not-on-master - python: "3.8" env: TOXENV=py38 <<: *not-on-master @@ -163,9 +160,6 @@ matrix: sudo: required services: docker <<: *extended-test-suite - - python: "3.4" - env: TOXENV=py34 - <<: *extended-test-suite - python: "3.5" env: TOXENV=py35 <<: *extended-test-suite @@ -175,9 +169,6 @@ matrix: - python: "3.7" env: TOXENV=py37 <<: *extended-test-suite - - python: "3.8" - env: TOXENV=py38 - <<: *extended-test-suite - python: "3.4" env: ACME_SERVER=boulder-v1 TOXENV=integration sudo: required From fcdeaf48f23f8971692dcb0d1daa84b03e3e8c67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 17 Jan 2020 16:42:10 +0200 Subject: [PATCH 042/122] Include added/deleted TXT record name in RFC 2136 debug log (#7696) --- .../certbot_dns_rfc2136/_internal/dns_rfc2136.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py index ee71c9681..cb4d5addb 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py @@ -129,7 +129,7 @@ class _RFC2136Client(object): rcode = response.rcode() if rcode == dns.rcode.NOERROR: - logger.debug('Successfully added TXT record') + logger.debug('Successfully added TXT record %s', record_name) else: raise errors.PluginError('Received response from server: {0}' .format(dns.rcode.to_text(rcode))) @@ -164,7 +164,7 @@ class _RFC2136Client(object): rcode = response.rcode() if rcode == dns.rcode.NOERROR: - logger.debug('Successfully deleted TXT record') + logger.debug('Successfully deleted TXT record %s', record_name) else: raise errors.PluginError('Received response from server: {0}' .format(dns.rcode.to_text(rcode))) From 1702cb90fdc84e0f251df4d023b721a63f553011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 17 Jan 2020 19:55:51 +0200 Subject: [PATCH 043/122] Spelling and grammar fixes (#7695) --- .azure-pipelines/INSTALL.md | 4 ++-- acme/acme/client.py | 2 +- acme/acme/messages.py | 2 +- .../certbot_apache/_internal/configurator.py | 2 +- .../certbot_apache/_internal/parser.py | 2 +- .../apache/httpd/conf.d/ssl.conf | 2 +- .../apache/httpd/conf/httpd.conf | 2 +- .../apache/httpd/conf.d/ssl.conf | 2 +- .../apache2/mods-available/ssl.conf | 2 +- .../apache2/mods-available/ssl.conf | 2 +- .../apache2/mods-available/ssl.conf | 2 +- .../apache2/modules.d/00_languages.conf | 2 +- .../apache/apache2/modules.d/40_mod_ssl.conf | 2 +- .../certbot_integration_tests/conftest.py | 2 +- .../utils/acme_server.py | 2 +- .../validator_test.py | 2 +- certbot-dns-gehirn/README.rst | 2 +- .../certbot_dns_gehirn/__init__.py | 12 +++++------ .../_internal/dns_gehirn.py | 20 +++++++++---------- certbot-dns-gehirn/setup.py | 2 +- certbot/CHANGELOG.md | 6 +++--- certbot/certbot/_internal/cli.py | 2 +- certbot/certbot/_internal/ocsp.py | 2 +- certbot/certbot/_internal/renewal.py | 8 ++++---- certbot/certbot/compat/filesystem.py | 2 +- certbot/certbot/display/ops.py | 4 ++-- certbot/docs/cli-help.txt | 6 +++--- certbot/tests/util_test.py | 2 +- .../rebuild_dependencies.py | 2 +- .../tests/centos6_tests.sh | 4 ++-- linter_plugin.py | 2 +- pytest.ini | 2 +- 32 files changed, 56 insertions(+), 56 deletions(-) diff --git a/.azure-pipelines/INSTALL.md b/.azure-pipelines/INSTALL.md index 9c1e4bff7..1a50bcb0c 100644 --- a/.azure-pipelines/INSTALL.md +++ b/.azure-pipelines/INSTALL.md @@ -69,12 +69,12 @@ Access can be defined for all or only selected repositories, which is nice. ``` - Redirected to Azure DevOps, select the account created in _Having an Azure DevOps account_ section. -- Select the organization, and click "Create a new project" (let's name it the same than the targetted github repo) +- Select the organization, and click "Create a new project" (let's name it the same than the targeted github repo) - The Visibility is public, to profit from 10 parallel jobs ``` !!! ACCESS !!! -Azure Pipelines needs access to the GitHub account (in term of beeing able to check it is valid), and the Resources shared between the GitHub account and Azure Pipelines. +Azure Pipelines needs access to the GitHub account (in term of being able to check it is valid), and the Resources shared between the GitHub account and Azure Pipelines. ``` _Done. We can move to pipelines configuration._ diff --git a/acme/acme/client.py b/acme/acme/client.py index 527430120..f48ff40b2 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -942,7 +942,7 @@ class ClientNetwork(object): :param messages.RegistrationResource account: Account object. Required if you are planning to use .post() with acme_version=2 for anything other than creating a new account; may be set later after registering. - :param josepy.JWASignature alg: Algoritm to use in signing JWS. + :param josepy.JWASignature alg: Algorithm to use in signing JWS. :param bool verify_ssl: Whether to verify certificates on SSL connections. :param str user_agent: String to send as User-Agent header. :param float timeout: Timeout for requests. diff --git a/acme/acme/messages.py b/acme/acme/messages.py index 1d907e5fc..e82d12890 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -36,7 +36,7 @@ ERROR_CODES = { ' domain'), 'dns': 'There was a problem with a DNS query during identifier validation', 'dnssec': 'The server could not validate a DNSSEC signed domain', - 'incorrectResponse': 'Response recieved didn\'t match the challenge\'s requirements', + 'incorrectResponse': 'Response received didn\'t match the challenge\'s requirements', # deprecate invalidEmail 'invalidEmail': 'The provided email for a registration was invalid', 'invalidContact': 'The provided contact URI was invalid', diff --git a/certbot-apache/certbot_apache/_internal/configurator.py b/certbot-apache/certbot_apache/_internal/configurator.py index 20c225e04..84b59d2c7 100644 --- a/certbot-apache/certbot_apache/_internal/configurator.py +++ b/certbot-apache/certbot_apache/_internal/configurator.py @@ -1817,7 +1817,7 @@ class ApacheConfigurator(common.Installer): ssl_vhost.filep) def _verify_no_matching_http_header(self, ssl_vhost, header_substring): - """Checks to see if an there is an existing Header directive that + """Checks to see if there is an existing Header directive that contains the string header_substring. :param ssl_vhost: vhost to check diff --git a/certbot-apache/certbot_apache/_internal/parser.py b/certbot-apache/certbot_apache/_internal/parser.py index e4073ee16..0703b8fb5 100644 --- a/certbot-apache/certbot_apache/_internal/parser.py +++ b/certbot-apache/certbot_apache/_internal/parser.py @@ -764,7 +764,7 @@ class ApacheParser(object): split_arg = arg.split("/") for idx, split in enumerate(split_arg): if any(char in ApacheParser.fnmatch_chars for char in split): - # Turn it into a augeas regex + # Turn it into an augeas regex # TODO: Can this instead be an augeas glob instead of regex split_arg[idx] = ("* [label()=~regexp('%s')]" % self.fnmatch_to_re(split)) diff --git a/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf index fb2174af1..abe07dd0c 100644 --- a/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf +++ b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf @@ -26,7 +26,7 @@ Listen 443 # Pass Phrase Dialog: # Configure the pass phrase gathering process. -# The filtering dialog program (`builtin' is a internal +# The filtering dialog program (`builtin' is an internal # terminal dialog) has to provide the pass phrase on stdout. SSLPassPhraseDialog builtin diff --git a/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf index 579d194ce..eac6143da 100644 --- a/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf +++ b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf @@ -702,7 +702,7 @@ IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t # English (en) - Esperanto (eo) - Estonian (et) - French (fr) - German (de) # Greek-Modern (el) - Hebrew (he) - Italian (it) - Japanese (ja) # Korean (ko) - Luxembourgeois* (ltz) - Norwegian Nynorsk (nn) -# Norwegian (no) - Polish (pl) - Portugese (pt) +# Norwegian (no) - Polish (pl) - Portuguese (pt) # Brazilian Portuguese (pt-BR) - Russian (ru) - Swedish (sv) # Simplified Chinese (zh-CN) - Spanish (es) - Traditional Chinese (zh-TW) # diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf index 6e2502e9a..c90fc780f 100644 --- a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf @@ -13,7 +13,7 @@ Listen 443 https # Pass Phrase Dialog: # Configure the pass phrase gathering process. -# The filtering dialog program (`builtin' is a internal +# The filtering dialog program (`builtin' is an internal # terminal dialog) has to provide the pass phrase on stdout. SSLPassPhraseDialog exec:/usr/libexec/httpd-ssl-pass-dialog diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf index e9fcf4f9b..65baec874 100644 --- a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf @@ -31,7 +31,7 @@ # Pass Phrase Dialog: # Configure the pass phrase gathering process. - # The filtering dialog program (`builtin' is a internal + # The filtering dialog program (`builtin' is an internal # terminal dialog) has to provide the pass phrase on stdout. SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf index e9fcf4f9b..65baec874 100644 --- a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf @@ -31,7 +31,7 @@ # Pass Phrase Dialog: # Configure the pass phrase gathering process. - # The filtering dialog program (`builtin' is a internal + # The filtering dialog program (`builtin' is an internal # terminal dialog) has to provide the pass phrase on stdout. SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf index e9fcf4f9b..65baec874 100644 --- a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf @@ -31,7 +31,7 @@ # Pass Phrase Dialog: # Configure the pass phrase gathering process. - # The filtering dialog program (`builtin' is a internal + # The filtering dialog program (`builtin' is an internal # terminal dialog) has to provide the pass phrase on stdout. SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf index c429bf94c..10cf3fb54 100644 --- a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf @@ -33,7 +33,7 @@ # English (en) - Esperanto (eo) - Estonian (et) - French (fr) - German (de) # Greek-Modern (el) - Hebrew (he) - Italian (it) - Japanese (ja) # Korean (ko) - Luxembourgeois* (ltz) - Norwegian Nynorsk (nn) -# Norwegian (no) - Polish (pl) - Portugese (pt) +# Norwegian (no) - Polish (pl) - Portuguese (pt) # Brazilian Portuguese (pt-BR) - Russian (ru) - Swedish (sv) # Simplified Chinese (zh-CN) - Spanish (es) - Traditional Chinese (zh-TW) AddLanguage ca .ca diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf index f51de4641..7f3cef423 100644 --- a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf @@ -43,7 +43,7 @@ SSLRandomSeed connect builtin ## Pass Phrase Dialog: # Configure the pass phrase gathering process. The filtering dialog program -# (`builtin' is a internal terminal dialog) has to provide the pass phrase on +# (`builtin' is an internal terminal dialog) has to provide the pass phrase on # stdout. SSLPassPhraseDialog builtin diff --git a/certbot-ci/certbot_integration_tests/conftest.py b/certbot-ci/certbot_integration_tests/conftest.py index 6eb9ee865..bb1d76e57 100644 --- a/certbot-ci/certbot_integration_tests/conftest.py +++ b/certbot-ci/certbot_integration_tests/conftest.py @@ -62,7 +62,7 @@ def _setup_primary_node(config): """ Setup the environment for integration tests. Will: - - check runtime compatiblity (Docker, docker-compose, Nginx) + - check runtime compatibility (Docker, docker-compose, Nginx) - create a temporary workspace and the persistent GIT repositories space - configure and start paralleled ACME CA servers using Docker - transfer ACME CA servers configurations to pytest nodes using env variables diff --git a/certbot-ci/certbot_integration_tests/utils/acme_server.py b/certbot-ci/certbot_integration_tests/utils/acme_server.py index fbf97fef1..5483251e6 100755 --- a/certbot-ci/certbot_integration_tests/utils/acme_server.py +++ b/certbot-ci/certbot_integration_tests/utils/acme_server.py @@ -189,7 +189,7 @@ class ACMEServer(object): print('=> Finished configuring the HTTP proxy.') def _launch_process(self, command, cwd=os.getcwd(), env=None): - """Launch silently an subprocess OS command""" + """Launch silently a subprocess OS command""" if not env: env = os.environ process = subprocess.Popen(command, stdout=self._stdout, stderr=subprocess.STDOUT, cwd=cwd, env=env) diff --git a/certbot-compatibility-test/certbot_compatibility_test/validator_test.py b/certbot-compatibility-test/certbot_compatibility_test/validator_test.py index 86edbdb55..235ce0e3c 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/validator_test.py +++ b/certbot-compatibility-test/certbot_compatibility_test/validator_test.py @@ -39,7 +39,7 @@ class ValidatorTest(unittest.TestCase): cert, "test.com", "127.0.0.1")) @mock.patch("certbot_compatibility_test.validator.requests.get") - def test_succesful_redirect(self, mock_get_request): + def test_successful_redirect(self, mock_get_request): mock_get_request.return_value = create_response( 301, {"location": "https://test.com"}) self.assertTrue(self.validator.redirect("test.com")) diff --git a/certbot-dns-gehirn/README.rst b/certbot-dns-gehirn/README.rst index 16058eff8..7a825bd7e 100644 --- a/certbot-dns-gehirn/README.rst +++ b/certbot-dns-gehirn/README.rst @@ -1 +1 @@ -Gehirn Infrastracture Service DNS Authenticator plugin for Certbot +Gehirn Infrastructure Service DNS Authenticator plugin for Certbot diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/__init__.py b/certbot-dns-gehirn/certbot_dns_gehirn/__init__.py index db54154ac..fdcb8cd48 100644 --- a/certbot-dns-gehirn/certbot_dns_gehirn/__init__.py +++ b/certbot-dns-gehirn/certbot_dns_gehirn/__init__.py @@ -1,14 +1,14 @@ """ The `~certbot_dns_gehirn.dns_gehirn` plugin automates the process of completing a ``dns-01`` challenge (`~acme.challenges.DNS01`) by creating, and subsequently -removing, TXT records using the Gehirn Infrastracture Service DNS API. +removing, TXT records using the Gehirn Infrastructure Service DNS API. Named Arguments --------------- ======================================== ===================================== -``--dns-gehirn-credentials`` Gehirn Infrastracture Service +``--dns-gehirn-credentials`` Gehirn Infrastructure Service credentials_ INI file. (Required) ``--dns-gehirn-propagation-seconds`` The number of seconds to wait for DNS @@ -22,15 +22,15 @@ Credentials ----------- Use of this plugin requires a configuration file containing -Gehirn Infrastracture Service DNS API credentials, -obtained from your Gehirn Infrastracture Service +Gehirn Infrastructure Service DNS API credentials, +obtained from your Gehirn Infrastructure Service `dashboard `_. .. code-block:: ini :name: credentials.ini :caption: Example credentials file: - # Gehirn Infrastracture Service API credentials used by Certbot + # Gehirn Infrastructure Service API credentials used by Certbot dns_gehirn_api_token = 00000000-0000-0000-0000-000000000000 dns_gehirn_api_secret = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw @@ -40,7 +40,7 @@ to this file for use during renewal, but does not store the file's contents. .. caution:: You should protect these API credentials as you would the password to your - Gehirn Infrastracture Service account. Users who can read this file can use + Gehirn Infrastructure Service account. Users who can read this file can use these credentials to issue arbitrary API calls on your behalf. Users who can cause Certbot to run using these credentials can complete a ``dns-01`` challenge to acquire new certificates or revoke existing certificates for diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py index 18090c95a..76c0ed584 100644 --- a/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py +++ b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py @@ -1,4 +1,4 @@ -"""DNS Authenticator for Gehirn Infrastracture Service DNS.""" +"""DNS Authenticator for Gehirn Infrastructure Service DNS.""" import logging from lexicon.providers import gehirn @@ -15,14 +15,14 @@ DASHBOARD_URL = "https://gis.gehirn.jp/" @zope.interface.implementer(interfaces.IAuthenticator) @zope.interface.provider(interfaces.IPluginFactory) class Authenticator(dns_common.DNSAuthenticator): - """DNS Authenticator for Gehirn Infrastracture Service DNS + """DNS Authenticator for Gehirn Infrastructure Service DNS - This Authenticator uses the Gehirn Infrastracture Service API to fulfill + This Authenticator uses the Gehirn Infrastructure Service API to fulfill a dns-01 challenge. """ description = 'Obtain certificates using a DNS TXT record ' + \ - '(if you are using Gehirn Infrastracture Service for DNS).' + '(if you are using Gehirn Infrastructure Service for DNS).' ttl = 60 def __init__(self, *args, **kwargs): @@ -32,20 +32,20 @@ class Authenticator(dns_common.DNSAuthenticator): @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) - add('credentials', help='Gehirn Infrastracture Service credentials file.') + add('credentials', help='Gehirn Infrastructure Service credentials file.') def more_info(self): # pylint: disable=missing-docstring,no-self-use return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ - 'the Gehirn Infrastracture Service API.' + 'the Gehirn Infrastructure Service API.' def _setup_credentials(self): self.credentials = self._configure_credentials( 'credentials', - 'Gehirn Infrastracture Service credentials file', + 'Gehirn Infrastructure Service credentials file', { - 'api-token': 'API token for Gehirn Infrastracture Service ' + \ + 'api-token': 'API token for Gehirn Infrastructure Service ' + \ 'API obtained from {0}'.format(DASHBOARD_URL), - 'api-secret': 'API secret for Gehirn Infrastracture Service ' + \ + 'api-secret': 'API secret for Gehirn Infrastructure Service ' + \ 'API obtained from {0}'.format(DASHBOARD_URL), } ) @@ -66,7 +66,7 @@ class Authenticator(dns_common.DNSAuthenticator): class _GehirnLexiconClient(dns_common_lexicon.LexiconClient): """ - Encapsulates all communication with the Gehirn Infrastracture Service via Lexicon. + Encapsulates all communication with the Gehirn Infrastructure Service via Lexicon. """ def __init__(self, api_token, api_secret, ttl): diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 19036b52b..0ba0228d1 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -38,7 +38,7 @@ class PyTest(TestCommand): setup( name='certbot-dns-gehirn', version=version, - description="Gehirn Infrastracture Service DNS Authenticator plugin for Certbot", + description="Gehirn Infrastructure Service DNS Authenticator plugin for Certbot", url='https://github.com/certbot/certbot', author="Certbot Project", author_email='client-dev@letsencrypt.org', diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index dd70000cd..84de0bfe5 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -248,7 +248,7 @@ More details about these changes can be found on our GitHub repo. ### Added -* dns_rfc2136 plugin now supports explicitly specifing an authorative +* dns_rfc2136 plugin now supports explicitly specifying an authoritative base domain for cases when the automatic method does not work (e.g. Split horizon DNS) @@ -632,7 +632,7 @@ https://github.com/certbot/certbot/milestone/62?closed=1 * Log warning about TLS-SNI deprecation in Certbot * Stop preferring TLS-SNI in the Apache, Nginx, and standalone plugins * OVH DNS plugin now relies on Lexicon>=2.7.14 to support HTTP proxies -* Default time the Linode plugin waits for DNS changes to propogate is now 1200 seconds. +* Default time the Linode plugin waits for DNS changes to propagate is now 1200 seconds. ### Fixed @@ -751,7 +751,7 @@ https://github.com/certbot/certbot/milestone/58?closed=1 increased over time. The max-age value is not increased to a large value until you've successfully managed to renew your certificate. This enhancement can be requested with the --auto-hsts flag. -* New official DNS plugins have been created for Gehirn Infrastracture Service, +* New official DNS plugins have been created for Gehirn Infrastructure Service, Linode, OVH, and Sakura Cloud. These plugins can be found on our Docker Hub page at https://hub.docker.com/u/certbot and on PyPI. * The ability to reuse ACME accounts from Let's Encrypt's ACMEv1 endpoint on diff --git a/certbot/certbot/_internal/cli.py b/certbot/certbot/_internal/cli.py index 9853d9b53..d5d498b4d 100644 --- a/certbot/certbot/_internal/cli.py +++ b/certbot/certbot/_internal/cli.py @@ -1414,7 +1414,7 @@ def _plugins_parsing(helpful, plugins): helpful.add(["plugins", "certonly"], "--dns-gehirn", action="store_true", default=flag_default("dns_gehirn"), help=("Obtain certificates using a DNS TXT record " - "(if you are using Gehirn Infrastracture Service for DNS).")) + "(if you are using Gehirn Infrastructure Service for DNS).")) helpful.add(["plugins", "certonly"], "--dns-google", action="store_true", default=flag_default("dns_google"), help=("Obtain certificates using a DNS TXT record (if you are " diff --git a/certbot/certbot/_internal/ocsp.py b/certbot/certbot/_internal/ocsp.py index 65a6d5c17..2f6543e5d 100644 --- a/certbot/certbot/_internal/ocsp.py +++ b/certbot/certbot/_internal/ocsp.py @@ -192,7 +192,7 @@ def _check_ocsp_cryptography(cert_path, chain_path, url): def _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert, cert_path): - """Verify that the OCSP is valid for serveral criterias""" + """Verify that the OCSP is valid for serveral criteria""" # Assert OCSP response corresponds to the certificate we are talking about if response_ocsp.serial_number != request_ocsp.serial_number: raise AssertionError('the certificate in response does not correspond ' diff --git a/certbot/certbot/_internal/renewal.py b/certbot/certbot/_internal/renewal.py index 0426b2e2d..930f6c1a9 100644 --- a/certbot/certbot/_internal/renewal.py +++ b/certbot/certbot/_internal/renewal.py @@ -192,7 +192,7 @@ def _restore_pref_challs(unused_name, value): :returns: converted option value to be stored in the runtime config :rtype: `list` of `str` - :raises errors.Error: if value can't be converted to an bool + :raises errors.Error: if value can't be converted to a bool """ # If pref_challs has only one element, configobj saves the value @@ -203,7 +203,7 @@ def _restore_pref_challs(unused_name, value): def _restore_bool(name, value): - """Restores an boolean key-value pair from a renewal config file. + """Restores a boolean key-value pair from a renewal config file. :param str name: option name :param str value: option value @@ -211,7 +211,7 @@ def _restore_bool(name, value): :returns: converted option value to be stored in the runtime config :rtype: bool - :raises errors.Error: if value can't be converted to an bool + :raises errors.Error: if value can't be converted to a bool """ lowercase_value = value.lower() @@ -244,7 +244,7 @@ def _restore_int(name, value): def _restore_str(unused_name, value): - """Restores an string key-value pair from a renewal config file. + """Restores a string key-value pair from a renewal config file. :param str unused_name: option name :param str value: option value diff --git a/certbot/certbot/compat/filesystem.py b/certbot/certbot/compat/filesystem.py index ba4a155e8..b49824f8d 100644 --- a/certbot/certbot/compat/filesystem.py +++ b/certbot/certbot/compat/filesystem.py @@ -541,7 +541,7 @@ def _generate_windows_flags(rights_desc): # write access on Linux: for Windows, FILE_GENERIC_WRITE does not include delete, move or # rename. This is something that requires ntsecuritycon.FILE_ALL_ACCESS. # So to reproduce the write right as POSIX, we will apply ntsecuritycon.FILE_ALL_ACCESS - # substracted of the rights corresponding to POSIX read and POSIX execute. + # subtracted of the rights corresponding to POSIX read and POSIX execute. # # Finally, having read + write + execute gives a ntsecuritycon.FILE_ALL_ACCESS, # so a "Full Control" on the file. diff --git a/certbot/certbot/display/ops.py b/certbot/certbot/display/ops.py index 92b09d6a1..eab9d251d 100644 --- a/certbot/certbot/display/ops.py +++ b/certbot/certbot/display/ops.py @@ -340,7 +340,7 @@ def validated_input(validator, *args, **kwargs): """Like `~certbot.interfaces.IDisplay.input`, but with validation. :param callable validator: A method which will be called on the - supplied input. If the method raises a `errors.Error`, its + supplied input. If the method raises an `errors.Error`, its text will be displayed and the user will be re-prompted. :param list `*args`: Arguments to be passed to `~certbot.interfaces.IDisplay.input`. :param dict `**kwargs`: Arguments to be passed to `~certbot.interfaces.IDisplay.input`. @@ -355,7 +355,7 @@ def validated_directory(validator, *args, **kwargs): """Like `~certbot.interfaces.IDisplay.directory_select`, but with validation. :param callable validator: A method which will be called on the - supplied input. If the method raises a `errors.Error`, its + supplied input. If the method raises an `errors.Error`, its text will be displayed and the user will be re-prompted. :param list `*args`: Arguments to be passed to `~certbot.interfaces.IDisplay.directory_select`. :param dict `**kwargs`: Arguments to be passed to diff --git a/certbot/docs/cli-help.txt b/certbot/docs/cli-help.txt index 9b463820a..51967eb76 100644 --- a/certbot/docs/cli-help.txt +++ b/certbot/docs/cli-help.txt @@ -451,7 +451,7 @@ plugins: --dns-dnsmadeeasy Obtain certificates using a DNS TXT record (if you are using DNS Made Easy for DNS). (default: False) --dns-gehirn Obtain certificates using a DNS TXT record (if you are - using Gehirn Infrastracture Service for DNS). + using Gehirn Infrastructure Service for DNS). (default: False) --dns-google Obtain certificates using a DNS TXT record (if you are using Google Cloud DNS). (default: False) @@ -560,14 +560,14 @@ dns-dnsmadeeasy: dns-gehirn: Obtain certificates using a DNS TXT record (if you are using Gehirn - Infrastracture Service for DNS). + Infrastructure Service for DNS). --dns-gehirn-propagation-seconds DNS_GEHIRN_PROPAGATION_SECONDS The number of seconds to wait for DNS to propagate before asking the ACME server to verify the DNS record. (default: 30) --dns-gehirn-credentials DNS_GEHIRN_CREDENTIALS - Gehirn Infrastracture Service credentials file. + Gehirn Infrastructure Service credentials file. (default: None) dns-google: diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index ae061de65..3ff09a83f 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -579,7 +579,7 @@ class AtexitRegisterTest(unittest.TestCase): with mock.patch('certbot.util.atexit') as mock_atexit: self._call(self.func, *self.args, **self.kwargs) - # _INITAL_PID must be mocked when calling atexit_func + # _INITIAL_PID must be mocked when calling atexit_func self.assertTrue(mock_atexit.register.called) args, kwargs = mock_atexit.register.call_args atexit_func = args[0] diff --git a/letsencrypt-auto-source/rebuild_dependencies.py b/letsencrypt-auto-source/rebuild_dependencies.py index a79bdd8aa..eedc604e0 100755 --- a/letsencrypt-auto-source/rebuild_dependencies.py +++ b/letsencrypt-auto-source/rebuild_dependencies.py @@ -63,7 +63,7 @@ CERTBOT_REPO_PATH = dirname(dirname(abspath(__file__))) # - then this venv is used to consistently construct an empty new venv # - once pipstraped, this new venv pip-installs certbot runtime (including apache/nginx), # without pinned dependencies, and respecting input authoritative requirements -# - `certbot plugins` is called to check we have an healthy environment +# - `certbot plugins` is called to check we have a healthy environment # - finally current set of dependencies is extracted out of the docker using pip freeze SCRIPT = r"""#!/bin/sh set -e diff --git a/letsencrypt-auto-source/tests/centos6_tests.sh b/letsencrypt-auto-source/tests/centos6_tests.sh index 86b307ad2..8bdffec87 100644 --- a/letsencrypt-auto-source/tests/centos6_tests.sh +++ b/letsencrypt-auto-source/tests/centos6_tests.sh @@ -100,7 +100,7 @@ echo "PASSED: certbot-auto did not install Python3.6 in a non-interactive shell # now bootstrap from current letsencrypt-auto, that will install python3.6 from SCL "$LE_AUTO" --no-self-upgrade -n --version >/dev/null 2>/dev/null -# Following test is exectued in a subshell, to not leak any environment variable +# Following test is executed in a subshell, to not leak any environment variable ( # enable SCL rh-python36 . /opt/rh/rh-python36/enable @@ -124,7 +124,7 @@ echo "PASSED: certbot-auto did not install Python3.6 in a non-interactive shell fi ) -# Following test is exectued in a subshell, to not leak any environment variable +# Following test is executed in a subshell, to not leak any environment variable ( # enable SCL rh-python36 . /opt/rh/rh-python36/enable diff --git a/linter_plugin.py b/linter_plugin.py index 6be8c2414..1754b1a2a 100644 --- a/linter_plugin.py +++ b/linter_plugin.py @@ -16,7 +16,7 @@ WHITELIST_PACKAGES = ['acme', 'certbot_compatibility_test', 'letshelp_certbot', class ForbidStandardOsModule(BaseChecker): """ This checker ensures that standard os module (and submodules) is not imported by certbot - modules. Otherwise a 'os-module-forbidden' error will be registered for the faulty lines. + modules. Otherwise an 'os-module-forbidden' error will be registered for the faulty lines. """ __implements__ = IAstroidChecker diff --git a/pytest.ini b/pytest.ini index 31a294ca6..019676292 100644 --- a/pytest.ini +++ b/pytest.ini @@ -6,7 +6,7 @@ # 1- decodestring: https://github.com/rthalley/dnspython/issues/338 # 2- ignore warn for importing abstract classes from collections instead of collections.abc, # too much third party dependencies are still relying on this behavior, -# but it should be corrected to allow Certbot compatiblity with Python >= 3.8 +# but it should be corrected to allow Certbot compatibility with Python >= 3.8 filterwarnings = error ignore:decodestring:DeprecationWarning From 07dc2400eb5622e88bf11c9a150afb89c947c937 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 21 Jan 2020 14:53:19 -0800 Subject: [PATCH 044/122] Downgrade NSIS and upgrade Python (#7702) * Add --allow-downgrade to chocolatey command. * Upgrade tests to use Python 3.8.1. --- .azure-pipelines/templates/installer-tests.yml | 2 +- windows-installer/construct.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines/templates/installer-tests.yml b/.azure-pipelines/templates/installer-tests.yml index e3a005334..f1ccd92ed 100644 --- a/.azure-pipelines/templates/installer-tests.yml +++ b/.azure-pipelines/templates/installer-tests.yml @@ -40,7 +40,7 @@ jobs: displayName: Retrieve Windows installer - script: $(Build.SourcesDirectory)\bin\certbot-beta-installer-win32.exe /S displayName: Install Certbot - - powershell: Invoke-WebRequest https://www.python.org/ftp/python/3.8.0/python-3.8.0-amd64-webinstall.exe -OutFile C:\py3-setup.exe + - powershell: Invoke-WebRequest https://www.python.org/ftp/python/3.8.1/python-3.8.1-amd64-webinstall.exe -OutFile C:\py3-setup.exe displayName: Get Python - script: C:\py3-setup.exe /quiet PrependPath=1 InstallAllUsers=1 Include_launcher=1 InstallLauncherAllUsers=1 Include_test=0 Include_doc=0 Include_dev=1 Include_debug=0 Include_tcltk=0 TargetDir=C:\py3 displayName: Install Python diff --git a/windows-installer/construct.py b/windows-installer/construct.py index 77ca67e65..f0724f5f4 100644 --- a/windows-installer/construct.py +++ b/windows-installer/construct.py @@ -56,7 +56,7 @@ def _prepare_build_tools(venv_path, venv_python, repo_path): subprocess.check_call([sys.executable, '-m', 'venv', venv_path]) subprocess.check_call([venv_python, os.path.join(repo_path, 'letsencrypt-auto-source', 'pieces', 'pipstrap.py')]) subprocess.check_call([venv_python, os.path.join(repo_path, 'tools', 'pip_install.py'), 'pynsist']) - subprocess.check_call(['choco', 'upgrade', '-y', 'nsis', '--version', NSIS_VERSION]) + subprocess.check_call(['choco', 'upgrade', '--allow-downgrade', '-y', 'nsis', '--version', NSIS_VERSION]) @contextlib.contextmanager From 7234d8922d61bbbd7eedfb8fb36c0763311eaacd Mon Sep 17 00:00:00 2001 From: Amjad Mashaal Date: Wed, 22 Jan 2020 01:34:34 +0200 Subject: [PATCH 045/122] Drop Travis tests for Python 3.4 (#7394) --- .travis.yml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 33c50b9f0..a521da703 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,8 +59,8 @@ matrix: dist: trusty env: TOXENV='py27-{acme,apache,certbot,dns,nginx}-oldest' <<: *not-on-master - - python: "3.4" - env: TOXENV=py34 + - python: "3.5" + env: TOXENV=py35 <<: *not-on-master - python: "3.8" env: TOXENV=py38 @@ -169,16 +169,6 @@ matrix: - python: "3.7" env: TOXENV=py37 <<: *extended-test-suite - - python: "3.4" - env: ACME_SERVER=boulder-v1 TOXENV=integration - sudo: required - services: docker - <<: *extended-test-suite - - python: "3.4" - env: ACME_SERVER=boulder-v2 TOXENV=integration - sudo: required - services: docker - <<: *extended-test-suite - python: "3.5" env: ACME_SERVER=boulder-v1 TOXENV=integration sudo: required From a6772043d6631341b525c4d69b47d6ef2d8b5d02 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 21 Jan 2020 15:53:31 -0800 Subject: [PATCH 046/122] Minor release script improvements (#7697) * Do not use git diff. * Add a warning on exit. --- tools/_release.sh | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/tools/_release.sh b/tools/_release.sh index 89f2a3737..1819adad2 100755 --- a/tools/_release.sh +++ b/tools/_release.sh @@ -7,6 +7,24 @@ if [ "$RELEASE_DIR" = "" ]; then exit 1 fi +ExitWarning() { + exit_status="$?" + if [ "$exit_status" != 0 ]; then + # Don't print each command before executing it because it will disrupt + # the desired output. + set +x + echo '******************************' + echo '* *' + echo '* THE RELEASE SCRIPT FAILED! *' + echo '* *' + echo '******************************' + set -x + fi + exit "$exit_status" +} + +trap ExitWarning EXIT + version="$1" echo Releasing production version "$version"... nextversion="$2" @@ -67,7 +85,6 @@ git checkout "$RELEASE_BRANCH" # Update changelog sed -i "s/master/$(date +'%Y-%m-%d')/" certbot/CHANGELOG.md git add certbot/CHANGELOG.md -git diff --cached git commit -m "Update changelog for $version release" for pkg_dir in $SUBPKGS certbot-compatibility-test @@ -230,7 +247,6 @@ cp -p letsencrypt-auto-source/letsencrypt-auto certbot-auto cp -p letsencrypt-auto-source/letsencrypt-auto letsencrypt-auto git add certbot-auto letsencrypt-auto letsencrypt-auto-source certbot/docs/cli-help.txt -git diff --cached while ! git commit --gpg-sign="$RELEASE_GPG_KEY" -m "Release $version"; do echo "Unable to sign the release commit using git." echo "You may have to configure git to use gpg2 by running:" @@ -258,7 +274,6 @@ $body $footer" > certbot/CHANGELOG.md git add certbot/CHANGELOG.md -git diff --cached git commit -m "Add contents to certbot/CHANGELOG.md for next version" echo "New root: $root" @@ -273,6 +288,5 @@ if [ "$RELEASE_BRANCH" = candidate-"$version" ] ; then SetVersion "$nextversion".dev0 letsencrypt-auto-source/build.py git add letsencrypt-auto-source/letsencrypt-auto - git diff git commit -m "Bump version to $nextversion" fi From 4473fd25cbd3a56906e25c0f0d20a7d953a77299 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 21 Jan 2020 23:18:21 -0800 Subject: [PATCH 047/122] Don't run Python 3.5 tests twice. (#7704) --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index a521da703..733470214 100644 --- a/.travis.yml +++ b/.travis.yml @@ -160,9 +160,6 @@ matrix: sudo: required services: docker <<: *extended-test-suite - - python: "3.5" - env: TOXENV=py35 - <<: *extended-test-suite - python: "3.6" env: TOXENV=py36 <<: *extended-test-suite From 90fd1afc38c7d467da4a70de63313c5263aebe79 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 21 Jan 2020 23:20:52 -0800 Subject: [PATCH 048/122] unpin macos (#7705) --- .travis.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 733470214..70038c150 100644 --- a/.travis.yml +++ b/.travis.yml @@ -225,9 +225,6 @@ matrix: - language: generic env: TOXENV=py27 os: osx - # Using this osx_image is a workaround for - # https://travis-ci.community/t/xcode-8-3-homebrew-outdated-error/3798. - osx_image: xcode10.2 addons: homebrew: packages: @@ -237,9 +234,6 @@ matrix: - language: generic env: TOXENV=py3 os: osx - # Using this osx_image is a workaround for - # https://travis-ci.community/t/xcode-8-3-homebrew-outdated-error/3798. - osx_image: xcode10.2 addons: homebrew: packages: From a342eb55469f8b92bc9550e4474ba04a2fd3dd58 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Thu, 23 Jan 2020 13:58:36 -0500 Subject: [PATCH 049/122] fixes #1948 -- MD5 on FIPS systems (#7708) * use MD5 in non-security mode to get around FIPS issue * update CHANGELOG * add myself to AUTHORS * ignore hashlib params --- AUTHORS.md | 1 + certbot/CHANGELOG.md | 1 + certbot/certbot/_internal/account.py | 17 ++++++++++++----- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index d24c5be1d..e89cd9d57 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -124,6 +124,7 @@ Authors * [Jonathan Herlin](https://github.com/Jonher937) * [Jon Walsh](https://github.com/code-tree) * [Joona Hoikkala](https://github.com/joohoi) +* [Josh McCullough](https://github.com/JoshMcCullough) * [Josh Soref](https://github.com/jsoref) * [Joubin Jabbari](https://github.com/joubin) * [Juho Juopperi](https://github.com/jkjuopperi) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 84de0bfe5..7d824d714 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -11,6 +11,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed * Add directory field to error message when field is missing. +* If MD5 hasher is not available, try it in non-security mode (fix for FIPS systems) -- [#1948](https://github.com/certbot/certbot/issues/1948) ### Fixed diff --git a/certbot/certbot/_internal/account.py b/certbot/certbot/_internal/account.py index c4ea6ef35..61f63bda6 100644 --- a/certbot/certbot/_internal/account.py +++ b/certbot/certbot/_internal/account.py @@ -56,11 +56,18 @@ class Account(object): tz=pytz.UTC).replace(microsecond=0), creation_host=socket.getfqdn()) if meta is None else meta - self.id = hashlib.md5( - self.key.key.public_key().public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo) - ).hexdigest() + # try MD5, else use MD5 in non-security mode (e.g. for FIPS systems / RHEL) + try: + hasher = hashlib.md5() + except ValueError: + hasher = hashlib.new('md5', usedforsecurity=False) # type: ignore + + hasher.update(self.key.key.public_key().public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo) + ) + + self.id = hasher.hexdigest() # Implementation note: Email? Multiple accounts can have the # same email address. Registration URI? Assigned by the # server, not guaranteed to be stable over time, nor From 5f315b46e9a4c2a79d15753969c4fbdf89f7f34b Mon Sep 17 00:00:00 2001 From: Amjad Mashaal Date: Fri, 24 Jan 2020 02:35:39 +0200 Subject: [PATCH 050/122] Update documentation files to remove claiming support for Python 3.4 (#7395) --- certbot/certbot/compat/filesystem.py | 2 +- certbot/docs/contributing.rst | 2 +- certbot/docs/install.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/certbot/certbot/compat/filesystem.py b/certbot/certbot/compat/filesystem.py index b49824f8d..65bb53f38 100644 --- a/certbot/certbot/compat/filesystem.py +++ b/certbot/certbot/compat/filesystem.py @@ -263,7 +263,7 @@ def replace(src, dst): :param str dst: The new file path. """ if hasattr(os, 'replace'): - # Use replace if possible. On Windows, only Python >= 3.4 is supported + # Use replace if possible. On Windows, only Python >= 3.5 is supported # so we can assume that os.replace() is always available for this platform. getattr(os, 'replace')(src, dst) else: diff --git a/certbot/docs/contributing.rst b/certbot/docs/contributing.rst index e1289c849..4ac266ed8 100644 --- a/certbot/docs/contributing.rst +++ b/certbot/docs/contributing.rst @@ -583,7 +583,7 @@ OS-level dependencies can be installed like so: In general... * ``sudo`` is required as a suggested way of running privileged process -* `Python`_ 2.7 or 3.4+ is required +* `Python`_ 2.7 or 3.5+ is required * `Augeas`_ is required for the Python bindings * ``virtualenv`` is used for managing other Python library dependencies diff --git a/certbot/docs/install.rst b/certbot/docs/install.rst index d21242367..11994776c 100644 --- a/certbot/docs/install.rst +++ b/certbot/docs/install.rst @@ -28,7 +28,7 @@ your system. System Requirements =================== -Certbot currently requires Python 2.7 or 3.4+ running on a UNIX-like operating +Certbot currently requires Python 2.7 or 3.5+ running on a UNIX-like operating system. By default, it requires root access in order to write to ``/etc/letsencrypt``, ``/var/log/letsencrypt``, ``/var/lib/letsencrypt``; to bind to port 80 (if you use the ``standalone`` plugin) and to read and From 2f24726d4cd8fa9b6c58cf8e80876082c960ab73 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 24 Jan 2020 15:13:58 +0200 Subject: [PATCH 051/122] Fix collections.abc imports for Python 3.9 (#7707) * Fix collections.abc imports for Python 3.9 * Update AUTHORS.md * No longer ignore collections.abc deprecation warning * Update changelog * Remove outdated comment * Disabling no-name-in-module not needed as linting is on Python 3 --- AUTHORS.md | 1 + certbot/CHANGELOG.md | 2 +- certbot/certbot/_internal/plugins/disco.py | 8 +++++++- pytest.ini | 4 ---- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index e89cd9d57..d2d5fc2a7 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -100,6 +100,7 @@ Authors * [Harlan Lieberman-Berg](https://github.com/hlieberman) * [Henri Salo](https://github.com/fgeek) * [Henry Chen](https://github.com/henrychen95) +* [Hugo van Kemenade](https://github.com/hugovk) * [Ingolf Becker](https://github.com/watercrossing) * [Jaap Eldering](https://github.com/eldering) * [Jacob Hoffman-Andrews](https://github.com/jsha) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 7d824d714..590bff38a 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -15,7 +15,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* +* Fix collections.abc imports for Python 3.9. More details about these changes can be found on our GitHub repo. diff --git a/certbot/certbot/_internal/plugins/disco.py b/certbot/certbot/_internal/plugins/disco.py index 360597474..d7d6390f7 100644 --- a/certbot/certbot/_internal/plugins/disco.py +++ b/certbot/certbot/_internal/plugins/disco.py @@ -13,6 +13,12 @@ from certbot import errors from certbot import interfaces from certbot._internal import constants +try: + # Python 3.3+ + from collections.abc import Mapping +except ImportError: # pragma: no cover + from collections import Mapping + logger = logging.getLogger(__name__) @@ -178,7 +184,7 @@ class PluginEntryPoint(object): return "\n".join(lines) -class PluginsRegistry(collections.Mapping): +class PluginsRegistry(Mapping): """Plugins registry.""" def __init__(self, plugins): diff --git a/pytest.ini b/pytest.ini index 019676292..e09813e52 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,10 +4,6 @@ [pytest] # In general, all warnings are treated as errors. Here are the exceptions: # 1- decodestring: https://github.com/rthalley/dnspython/issues/338 -# 2- ignore warn for importing abstract classes from collections instead of collections.abc, -# too much third party dependencies are still relying on this behavior, -# but it should be corrected to allow Certbot compatibility with Python >= 3.8 filterwarnings = error ignore:decodestring:DeprecationWarning - ignore:.*collections\.abc:DeprecationWarning From 896c1e0b66817eff447b41f619772a6441b416df Mon Sep 17 00:00:00 2001 From: ohemorange Date: Fri, 24 Jan 2020 10:09:28 -0800 Subject: [PATCH 052/122] Remove ECDHE-RSA-AES128-SHA from NGINX ciphers list (#7719) As mentioned in https://github.com/certbot/certbot/pull/7712#discussion_r370419867, it's time to remove this ciphersuite now that Windows 2008 R2 and Windows 7 are EOLed. * Remove ECDHE-RSA-AES128-SHA from NGINX ciphers list to celebrate Windows 2008 R2 deprecation * Update changelog --- .../_internal/tls_configs/options-ssl-nginx-old.conf | 2 +- .../_internal/tls_configs/options-ssl-nginx-tls12-only.conf | 2 +- .../tls_configs/options-ssl-nginx-tls13-session-tix-on.conf | 2 +- .../certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf | 2 +- certbot/CHANGELOG.md | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-old.conf b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-old.conf index 731e38919..a678b0507 100644 --- a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-old.conf +++ b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-old.conf @@ -10,4 +10,4 @@ ssl_session_timeout 1440m; ssl_protocols TLSv1.2; ssl_prefer_server_ciphers off; -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls12-only.conf b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls12-only.conf index 33771a189..1933cbc4f 100644 --- a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls12-only.conf +++ b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls12-only.conf @@ -11,4 +11,4 @@ ssl_session_tickets off; ssl_protocols TLSv1.2; ssl_prefer_server_ciphers off; -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf index 91197d2c8..52fdfde24 100644 --- a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf +++ b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf @@ -10,4 +10,4 @@ ssl_session_timeout 1440m; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf index 98b1c4ab9..978e6e8ab 100644 --- a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf +++ b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf @@ -11,4 +11,4 @@ ssl_session_tickets off; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 590bff38a..5ddfab472 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -12,6 +12,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Add directory field to error message when field is missing. * If MD5 hasher is not available, try it in non-security mode (fix for FIPS systems) -- [#1948](https://github.com/certbot/certbot/issues/1948) +* Remove ECDHE-RSA-AES128-SHA from NGINX ciphers list now that Windows 2008 R2 and Windows 7 are EOLed ### Fixed From 1e2f70b17a67658ed5b7a8ab5efdfef8f55623db Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 24 Jan 2020 12:32:07 -0800 Subject: [PATCH 053/122] Drop Python 3.4 support (#7721) Fixes #7393. * Remove Python 3.4 classifiers * Remove unneeded typing dependency * Exclude Python 3.4 in python_requires * Remove Python 3.4 deprecation warning * update changelog --- acme/setup.py | 3 +-- certbot-apache/setup.py | 3 +-- certbot-ci/setup.py | 3 +-- certbot-compatibility-test/setup.py | 3 +-- certbot-dns-cloudflare/setup.py | 3 +-- certbot-dns-cloudxns/setup.py | 3 +-- certbot-dns-digitalocean/setup.py | 3 +-- certbot-dns-dnsimple/setup.py | 3 +-- certbot-dns-dnsmadeeasy/setup.py | 3 +-- certbot-dns-gehirn/setup.py | 3 +-- certbot-dns-google/setup.py | 3 +-- certbot-dns-linode/setup.py | 3 +-- certbot-dns-luadns/setup.py | 3 +-- certbot-dns-nsone/setup.py | 3 +-- certbot-dns-ovh/setup.py | 3 +-- certbot-dns-rfc2136/setup.py | 3 +-- certbot-dns-route53/setup.py | 3 +-- certbot-dns-sakuracloud/setup.py | 3 +-- certbot-nginx/setup.py | 3 +-- certbot/CHANGELOG.md | 1 + certbot/certbot/_internal/main.py | 4 ---- certbot/setup.py | 4 +--- letshelp-certbot/setup.py | 3 +-- 23 files changed, 22 insertions(+), 47 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 2ab4db34e..458ca083d 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -61,7 +61,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', @@ -70,7 +70,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 7c74d5869..599925929 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -42,7 +42,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -53,7 +53,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-ci/setup.py b/certbot-ci/setup.py index fb82b6ca5..75d2cc96a 100644 --- a/certbot-ci/setup.py +++ b/certbot-ci/setup.py @@ -40,7 +40,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', @@ -49,7 +49,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 177fd9d31..c3443e35e 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -28,7 +28,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', @@ -37,7 +37,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 7f4f5137a..a3e64f07d 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -44,7 +44,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -55,7 +55,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 4e04ae820..a7a0072c4 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -44,7 +44,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -55,7 +55,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 1f146b678..fe5243bc5 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -45,7 +45,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -56,7 +56,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index c486c37df..e31bc5949 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -56,7 +56,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -67,7 +67,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 7eb4473aa..fceb0b518 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -44,7 +44,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -55,7 +55,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 0ba0228d1..974b17d44 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -43,7 +43,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -54,7 +54,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 08d9755a1..7b5583307 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -47,7 +47,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -58,7 +58,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 27165a09f..37b5a4921 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -43,7 +43,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -54,7 +54,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index ea669dc65..4d60ca520 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -44,7 +44,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -55,7 +55,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index f90d73e0e..81324dbc2 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -44,7 +44,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -55,7 +55,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 6a9281498..ee7d9fc69 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -44,7 +44,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -55,7 +55,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index df391fc65..aa2509727 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -44,7 +44,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -55,7 +55,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 01f9c9ee2..df43d90a9 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -39,7 +39,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -50,7 +50,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index e81be051d..4d35dc7ac 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -43,7 +43,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -54,7 +54,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 4a5e5eb05..aad736da5 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -42,7 +42,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -53,7 +53,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 5ddfab472..b025866e7 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -13,6 +13,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Add directory field to error message when field is missing. * If MD5 hasher is not available, try it in non-security mode (fix for FIPS systems) -- [#1948](https://github.com/certbot/certbot/issues/1948) * Remove ECDHE-RSA-AES128-SHA from NGINX ciphers list now that Windows 2008 R2 and Windows 7 are EOLed +* Support for Python 3.4 has been removed. ### Fixed diff --git a/certbot/certbot/_internal/main.py b/certbot/certbot/_internal/main.py index 509b5b981..72fcfca71 100644 --- a/certbot/certbot/_internal/main.py +++ b/certbot/certbot/_internal/main.py @@ -1337,10 +1337,6 @@ def main(cli_args=None): if config.func != plugins_cmd: # pylint: disable=comparison-with-callable raise - if sys.version_info[:2] == (3, 4): - logger.warning("Python 3.4 support will be dropped in the next release " - "of Certbot - please upgrade your Python version to 3.5+.") - set_displayer(config) # Reporter diff --git a/certbot/setup.py b/certbot/setup.py index 0026ef8e9..d19327e5e 100644 --- a/certbot/setup.py +++ b/certbot/setup.py @@ -88,7 +88,6 @@ dev3_extras = [ 'astroid', 'mypy', 'pylint', - 'typing', # for python3.4 ] docs_extras = [ @@ -124,7 +123,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', @@ -136,7 +135,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/letshelp-certbot/setup.py b/letshelp-certbot/setup.py index af992de16..448c145ce 100644 --- a/letshelp-certbot/setup.py +++ b/letshelp-certbot/setup.py @@ -21,7 +21,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 3 - Alpha', 'Intended Audience :: System Administrators', @@ -31,7 +31,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', From b1a8e7175b1d7ee9d7bd16d5ba9b3ddd055c80db Mon Sep 17 00:00:00 2001 From: ohemorange Date: Fri, 24 Jan 2020 13:37:42 -0800 Subject: [PATCH 054/122] Disable old SSL versions and ciphersuites to follow Mozilla recommendations in Apache (#7712) Part of #7204. Makes the smaller changes described at https://github.com/certbot/certbot/issues/7204#issuecomment-571838185 to disable many old ciphersuites and TLS versions < 1.2. Does not add checks for OpenSSL version or modify session tickets. Since Apache uses TLS protocol blacklisting instead of whitelisting (as in NGINX), we additionally may not need to determine if the server supports TLS1.3 and turn it on or off based on Apache version. * Update SSL versions and ciphersuites based on Mozilla intermediate recommendations for apache * Update constants with hashes of new config files * Update changelog --- .../_internal/centos-options-ssl-apache.conf | 13 +++---------- .../certbot_apache/_internal/constants.py | 2 ++ .../_internal/options-ssl-apache.conf | 13 +++---------- certbot/CHANGELOG.md | 1 + 4 files changed, 9 insertions(+), 20 deletions(-) diff --git a/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf b/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf index 56c946a4e..1a3799628 100644 --- a/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf +++ b/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf @@ -7,19 +7,12 @@ SSLEngine on # Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS -SSLHonorCipherOrder on +SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 +SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 +SSLHonorCipherOrder off SSLOptions +StrictRequire # Add vhost name to log entries: LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common - -#CustomLog /var/log/apache2/access.log vhost_combined -#LogLevel warn -#ErrorLog /var/log/apache2/error.log - -# Always ensure Cookies have "Secure" set (JAH 2012/1) -#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/certbot-apache/certbot_apache/_internal/constants.py b/certbot-apache/certbot_apache/_internal/constants.py index 47e3be856..a37bebac5 100644 --- a/certbot-apache/certbot_apache/_internal/constants.py +++ b/certbot-apache/certbot_apache/_internal/constants.py @@ -24,6 +24,8 @@ ALL_SSL_OPTIONS_HASHES = [ '0fcdc81280cd179a07ec4d29d3595068b9326b455c488de4b09f585d5dafc137', '86cc09ad5415cd6d5f09a947fe2501a9344328b1e8a8b458107ea903e80baa6c', '06675349e457eae856120cdebb564efe546f0b87399f2264baeb41e442c724c7', + '5cc003edd93fb9cd03d40c7686495f8f058f485f75b5e764b789245a386e6daf', + '007cd497a56a3bb8b6a2c1aeb4997789e7e38992f74e44cc5d13a625a738ac73', ] """SHA256 hashes of the contents of previous versions of all versions of MOD_SSL_CONF_SRC""" diff --git a/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf b/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf index 8113ee81e..60095faa0 100644 --- a/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf +++ b/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf @@ -7,9 +7,9 @@ SSLEngine on # Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS -SSLHonorCipherOrder on +SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 +SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 +SSLHonorCipherOrder off SSLCompression off SSLOptions +StrictRequire @@ -17,10 +17,3 @@ SSLOptions +StrictRequire # Add vhost name to log entries: LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common - -#CustomLog /var/log/apache2/access.log vhost_combined -#LogLevel warn -#ErrorLog /var/log/apache2/error.log - -# Always ensure Cookies have "Secure" set (JAH 2012/1) -#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index b025866e7..1cd4d3f1c 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -12,6 +12,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Add directory field to error message when field is missing. * If MD5 hasher is not available, try it in non-security mode (fix for FIPS systems) -- [#1948](https://github.com/certbot/certbot/issues/1948) +* Disable old SSL versions and ciphersuites to follow Mozilla recommendations in Apache. * Remove ECDHE-RSA-AES128-SHA from NGINX ciphers list now that Windows 2008 R2 and Windows 7 are EOLed * Support for Python 3.4 has been removed. From 2072599bd716d9b6afe4b7b66d0ef12a01f36cf7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 24 Jan 2020 14:02:54 -0800 Subject: [PATCH 055/122] Unpin Python 3.4 dependencies (#7709) * Unpin dependencies pinned back for py3.4 support. * update pinned packages * run build.py * Update boto3 and deps to work with requests --- letsencrypt-auto-source/letsencrypt-auto | 175 +++++++++++------- .../pieces/dependency-requirements.txt | 175 +++++++++++------- .../rebuild_dependencies.py | 6 - tools/dev_constraints.txt | 14 +- 4 files changed, 219 insertions(+), 151 deletions(-) diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 17badacc7..9d2013cd1 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -1274,11 +1274,11 @@ if [ "$1" = "--le-auto-phase2" ]; then # pip install hashin # hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` -ConfigArgParse==0.14.0 \ - --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 -certifi==2019.9.11 \ - --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ - --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef +ConfigArgParse==1.0 \ + --hash=sha256:bf378245bc9cdc403a527e5b7406b991680c2a530e7e81af747880b54eb57133 +certifi==2019.11.28 \ + --hash=sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3 \ + --hash=sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f cffi==1.13.2 \ --hash=sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42 \ --hash=sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04 \ @@ -1351,8 +1351,6 @@ enum34==1.1.6 \ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -future==0.18.2 \ - --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c @@ -1365,40 +1363,40 @@ josepy==1.2.0 \ mock==1.3.0 \ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb -parsedatetime==2.4 \ - --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ - --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.4.3 \ - --hash=sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8 \ - --hash=sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9 -pyOpenSSL==19.0.0 \ - --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ - --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 +parsedatetime==2.5 \ + --hash=sha256:3b835fc54e472c17ef447be37458b400e3fefdf14bb1ffdedb5d2c853acf4ba1 \ + --hash=sha256:d2e9ddb1e463de871d32088a3f3cea3dc8282b1b2800e081bd0ef86900451667 +pbr==5.4.4 \ + --hash=sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b \ + --hash=sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488 +pyOpenSSL==19.1.0 \ + --hash=sha256:621880965a720b8ece2f1b2f54ea2071966ab00e2970ad2ce11d596102063504 \ + --hash=sha256:9a24494b2602aaf402be5c9e30a0b82d4a5c67528fe8fb475e3f3bc00dd69507 pyRFC3339==1.1 \ --hash=sha256:67196cb83b470709c580bb4738b83165e67c6cc60e1f2e4f286cfcb402a926f4 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.4.5 \ - --hash=sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f \ - --hash=sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a +pyparsing==2.4.6 \ + --hash=sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f \ + --hash=sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 pytz==2019.3 \ --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be -requests==2.21.0 \ - --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ - --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b +requests==2.22.0 \ + --hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 \ + --hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31 requests-toolbelt==0.9.1 \ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 -six==1.13.0 \ - --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ - --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 -urllib3==1.24.3 \ - --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ - --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb +six==1.14.0 \ + --hash=sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a \ + --hash=sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c +urllib3==1.25.8 \ + --hash=sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc \ + --hash=sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc zope.component==4.6 \ --hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6 zope.deferredimport==4.3.1 \ @@ -1410,47 +1408,86 @@ zope.deprecation==4.4.0 \ zope.event==4.4 \ --hash=sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf \ --hash=sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7 -zope.hookable==4.2.0 \ - --hash=sha256:22886e421234e7e8cedc21202e1d0ab59960e40a47dd7240e9659a2d82c51370 \ - --hash=sha256:39912f446e45b4e1f1951b5ffa2d5c8b074d25727ec51855ae9eab5408f105ab \ - --hash=sha256:3adb7ea0871dbc56b78f62c4f5c024851fc74299f4f2a95f913025b076cde220 \ - --hash=sha256:3d7c4b96341c02553d8b8d71065a9366ef67e6c6feca714f269894646bb8268b \ - --hash=sha256:4e826a11a529ed0464ffcecf34b0b7bd1b4928dd5848c5c61bedd7833e8f4801 \ - --hash=sha256:700d68cc30728de1c4c62088a981c6daeaefdf20a0d81995d2c0b7f442c5f88c \ - --hash=sha256:77c82a430cedfbf508d1aa406b2f437363c24fa90c73f577ead0fb5295749b83 \ - --hash=sha256:c1df3929a3666fc5a0c80d60a0c1e6f6ef97c7f6ed2f1b7cf49f3e6f3d4dde15 \ - --hash=sha256:dba8b2dd2cd41cb5f37bfa3f3d82721b8ae10e492944e48ddd90a439227f2893 \ - --hash=sha256:f492540305b15b5591bd7195d61f28946bb071de071cee5d68b6b8414da90fd2 -zope.interface==4.6.0 \ - --hash=sha256:086707e0f413ff8800d9c4bc26e174f7ee4c9c8b0302fbad68d083071822316c \ - --hash=sha256:1157b1ec2a1f5bf45668421e3955c60c610e31913cc695b407a574efdbae1f7b \ - --hash=sha256:11ebddf765bff3bbe8dbce10c86884d87f90ed66ee410a7e6c392086e2c63d02 \ - --hash=sha256:14b242d53f6f35c2d07aa2c0e13ccb710392bcd203e1b82a1828d216f6f6b11f \ - --hash=sha256:1b3d0dcabc7c90b470e59e38a9acaa361be43b3a6ea644c0063951964717f0e5 \ - --hash=sha256:20a12ab46a7e72b89ce0671e7d7a6c3c1ca2c2766ac98112f78c5bddaa6e4375 \ - --hash=sha256:298f82c0ab1b182bd1f34f347ea97dde0fffb9ecf850ecf7f8904b8442a07487 \ - --hash=sha256:2f6175722da6f23dbfc76c26c241b67b020e1e83ec7fe93c9e5d3dd18667ada2 \ - --hash=sha256:3b877de633a0f6d81b600624ff9137312d8b1d0f517064dfc39999352ab659f0 \ - --hash=sha256:4265681e77f5ac5bac0905812b828c9fe1ce80c6f3e3f8574acfb5643aeabc5b \ - --hash=sha256:550695c4e7313555549aa1cdb978dc9413d61307531f123558e438871a883d63 \ - --hash=sha256:5f4d42baed3a14c290a078e2696c5f565501abde1b2f3f1a1c0a94fbf6fbcc39 \ - --hash=sha256:62dd71dbed8cc6a18379700701d959307823b3b2451bdc018594c48956ace745 \ - --hash=sha256:7040547e5b882349c0a2cc9b50674b1745db551f330746af434aad4f09fba2cc \ - --hash=sha256:7e099fde2cce8b29434684f82977db4e24f0efa8b0508179fce1602d103296a2 \ - --hash=sha256:7e5c9a5012b2b33e87980cee7d1c82412b2ebabcb5862d53413ba1a2cfde23aa \ - --hash=sha256:81295629128f929e73be4ccfdd943a0906e5fe3cdb0d43ff1e5144d16fbb52b1 \ - --hash=sha256:95cc574b0b83b85be9917d37cd2fad0ce5a0d21b024e1a5804d044aabea636fc \ - --hash=sha256:968d5c5702da15c5bf8e4a6e4b67a4d92164e334e9c0b6acf080106678230b98 \ - --hash=sha256:9e998ba87df77a85c7bed53240a7257afe51a07ee6bc3445a0bf841886da0b97 \ - --hash=sha256:a0c39e2535a7e9c195af956610dba5a1073071d2d85e9d2e5d789463f63e52ab \ - --hash=sha256:a15e75d284178afe529a536b0e8b28b7e107ef39626a7809b4ee64ff3abc9127 \ - --hash=sha256:a6a6ff82f5f9b9702478035d8f6fb6903885653bff7ec3a1e011edc9b1a7168d \ - --hash=sha256:b639f72b95389620c1f881d94739c614d385406ab1d6926a9ffe1c8abbea23fe \ - --hash=sha256:bad44274b151d46619a7567010f7cde23a908c6faa84b97598fd2f474a0c6891 \ - --hash=sha256:bbcef00d09a30948756c5968863316c949d9cedbc7aabac5e8f0ffbdb632e5f1 \ - --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ - --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ - --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 +zope.hookable==5.0.0 \ + --hash=sha256:0992a0dd692003c09fb958e1480cebd1a28f2ef32faa4857d864f3ca8e9d6952 \ + --hash=sha256:0f325838dbac827a1e2ed5d482c1f2656b6844dc96aa098f7727e76395fcd694 \ + --hash=sha256:22a317ba00f61bac99eac1a5e330be7cb8c316275a21269ec58aa396b602af0c \ + --hash=sha256:25531cb5e7b35e8a6d1d6eddef624b9a22ce5dcf8f4448ef0f165acfa8c3fc21 \ + --hash=sha256:30890892652766fc80d11f078aca9a5b8150bef6b88aba23799581a53515c404 \ + --hash=sha256:342d682d93937e5b8c232baffb32a87d5eee605d44f74566657c64a239b7f342 \ + --hash=sha256:46b2fddf1f5aeb526e02b91f7e62afbb9fff4ffd7aafc97cdb00a0d717641567 \ + --hash=sha256:523318ff96df9b8d378d997c00c5d4cbfbff68dc48ff5ee5addabdb697d27528 \ + --hash=sha256:53aa02eb8921d4e667c69d76adeed8fe426e43870c101cb08dcd2f3468aff742 \ + --hash=sha256:62e79e8fdde087cb20822d7874758f5acbedbffaf3c0fbe06309eb8a41ee4e06 \ + --hash=sha256:74bf2f757f7385b56dc3548adae508d8b3ef952d600b4b12b88f7d1706b05dcc \ + --hash=sha256:751ee9d89eb96e00c1d7048da9725ce392a708ed43406416dc5ed61e4d199764 \ + --hash=sha256:7b83bc341e682771fe810b360cd5d9c886a948976aea4b979ff214e10b8b523b \ + --hash=sha256:81eeeb27dbb0ddaed8070daee529f0d1bfe4f74c7351cce2aaca3ea287c4cc32 \ + --hash=sha256:856509191e16930335af4d773c0fc31a17bae8991eb6f167a09d5eddf25b56cc \ + --hash=sha256:8853e81fd07b18fa9193b19e070dc0557848d9945b1d2dac3b7782543458c87d \ + --hash=sha256:94506a732da2832029aecdfe6ea07eb1b70ee06d802fff34e1b3618fe7cdf026 \ + --hash=sha256:95ad874a8cc94e786969215d660143817f745225579bfe318c4676e218d3147c \ + --hash=sha256:9758ec9174966ffe5c499b6c3d149f80aa0a9238020006a2b87c6af5963fcf48 \ + --hash=sha256:a169823e331da939aa7178fc152e65699aeb78957e46c6f80ccb50ee4c3616c2 \ + --hash=sha256:a67878a798f6ca292729a28c2226592b3d000dc6ee7825d31887b553686c7ac7 \ + --hash=sha256:a9a6d9eb2319a09905670810e2de971d6c49013843700b4975e2fc0afe96c8db \ + --hash=sha256:b3e118b58a3d2301960e6f5f25736d92f6b9f861728d3b8c26d69f54d8a157d2 \ + --hash=sha256:ca6705c2a1fb5059a4efbe9f5426be4cdf71b3c9564816916fc7aa7902f19ede \ + --hash=sha256:cf711527c9d4ae72085f137caffb4be74fc007ffb17cd103628c7d5ba17e205f \ + --hash=sha256:d087602a6845ebe9d5a1c5a949fedde2c45f372d77fbce4f7fe44b68b28a1d03 \ + --hash=sha256:d1080e1074ddf75ad6662a9b34626650759c19a9093e1a32a503d37e48da135b \ + --hash=sha256:db9c60368aff2b7e6c47115f3ad9bd6e96aa298b12ed5f8cb13f5673b30be565 \ + --hash=sha256:dbeb127a04473f5a989169eb400b67beb921c749599b77650941c21fe39cb8d9 \ + --hash=sha256:dca336ca3682d869d291d7cd18284f6ff6876e4244eb1821430323056b000e2c \ + --hash=sha256:dd69a9be95346d10c853b6233fcafe3c0315b89424b378f2ad45170d8e161568 \ + --hash=sha256:dd79f8fae5894f1ee0a0042214685f2d039341250c994b825c10a4cd075d80f6 \ + --hash=sha256:e647d850aa1286d98910133cee12bd87c354f7b7bb3f3cd816a62ba7fa2f7007 \ + --hash=sha256:f37a210b5c04b2d4e4bac494ab15b70196f219a1e1649ddca78560757d4278fb \ + --hash=sha256:f67820b6d33a705dc3c1c457156e51686f7b350ff57f2112e1a9a4dad38ec268 \ + --hash=sha256:f68969978ccf0e6123902f7365aae5b7a9e99169d4b9105c47cf28e788116894 \ + --hash=sha256:f717a0b34460ae1ac0064e91b267c0588ac2c098ffd695992e72cd5462d97a67 \ + --hash=sha256:f9d58ccec8684ca276d5a4e7b0dfacca028336300a8f715d616d9f0ce9ae8096 \ + --hash=sha256:fcc3513a54e656067cbf7b98bab0d6b9534b9eabc666d1f78aad6acdf0962736 +zope.interface==4.7.1 \ + --hash=sha256:048b16ac882a05bc7ef534e8b9f15c9d7a6c190e24e8938a19b7617af4ed854a \ + --hash=sha256:05816cf8e7407cf62f2ec95c0a5d69ec4fa5741d9ccd10db9f21691916a9a098 \ + --hash=sha256:065d6a1ac89d35445168813bed45048ed4e67a4cdfc5a68fdb626a770378869f \ + --hash=sha256:14157421f4121a57625002cc4f48ac7521ea238d697c4a4459a884b62132b977 \ + --hash=sha256:18dc895945694f397a0be86be760ff664b790f95d8e7752d5bab80284ff9105d \ + --hash=sha256:1962c9f838bd6ae4075d0014f72697510daefc7e1c7e48b2607df0b6e157989c \ + --hash=sha256:1a67408cacd198c7e6274a19920bb4568d56459e659e23c4915528686ac1763a \ + --hash=sha256:21bf781076dd616bd07cf0223f79d61ab4f45176076f90bc2890e18c48195da4 \ + --hash=sha256:21c0a5d98650aebb84efa16ce2c8df1a46bdc4fe8a9e33237d0ca0b23f416ead \ + --hash=sha256:23cfeea25d1e42ff3bf4f9a0c31e9d5950aa9e7c4b12f0c4bd086f378f7b7a71 \ + --hash=sha256:24b6fce1fb71abf9f4093e3259084efcc0ef479f89356757780685bd2b06ef37 \ + --hash=sha256:24f84ce24eb6b5fcdcb38ad9761524f1ae96f7126abb5e597f8a3973d9921409 \ + --hash=sha256:25e0ef4a824017809d6d8b0ce4ab3288594ba283e4d4f94d8cfb81d73ed65114 \ + --hash=sha256:2e8fdd625e9aba31228e7ddbc36bad5c38dc3ee99a86aa420f89a290bd987ce9 \ + --hash=sha256:2f3bc2f49b67b1bea82b942d25bc958d4f4ea6709b411cb2b6b9718adf7914ce \ + --hash=sha256:35d24be9d04d50da3a6f4d61de028c1dd087045385a0ff374d93ef85af61b584 \ + --hash=sha256:35dbe4e8c73003dff40dfaeb15902910a4360699375e7b47d3c909a83ff27cd0 \ + --hash=sha256:3dfce831b824ab5cf446ed0c350b793ac6fa5fe33b984305cb4c966a86a8fb79 \ + --hash=sha256:3f7866365df5a36a7b8de8056cd1c605648f56f9a226d918ed84c85d25e8d55f \ + --hash=sha256:455cc8c01de3bac6f9c223967cea41f4449f58b4c2e724ec8177382ddd183ab4 \ + --hash=sha256:4bb937e998be9d5e345f486693e477ba79e4344674484001a0b646be1d530487 \ + --hash=sha256:52303a20902ca0888dfb83230ca3ee6fbe63c0ad1dd60aa0bba7958ccff454d8 \ + --hash=sha256:6e0a897d4e09859cc80c6a16a29697406ead752292ace17f1805126a4f63c838 \ + --hash=sha256:6e1816e7c10966330d77af45f77501f9a68818c065dec0ad11d22b50a0e212e7 \ + --hash=sha256:73b5921c5c6ce3358c836461b5470bf675601c96d5e5d8f2a446951470614f67 \ + --hash=sha256:8093cd45cdb5f6c8591cfd1af03d32b32965b0f79b94684cd0c9afdf841982bb \ + --hash=sha256:864b4a94b60db301899cf373579fd9ef92edddbf0fb2cd5ae99f53ef423ccc56 \ + --hash=sha256:8a27b4d3ea9c6d086ce8e7cdb3e8d319b6752e2a03238a388ccc83ccbe165f50 \ + --hash=sha256:91b847969d4784abd855165a2d163f72ac1e58e6dce09a5e46c20e58f19cc96d \ + --hash=sha256:b47b1028be4758c3167e474884ccc079b94835f058984b15c145966c4df64d27 \ + --hash=sha256:b68814a322835d8ad671b7acc23a3b2acecba527bb14f4b53fc925f8a27e44d8 \ + --hash=sha256:bcb50a032c3b6ec7fb281b3a83d2b31ab5246c5b119588725b1350d3a1d9f6a3 \ + --hash=sha256:c56db7d10b25ce8918b6aec6b08ac401842b47e6c136773bfb3b590753f7fb67 \ + --hash=sha256:c94b77a13d4f47883e4f97f9fa00f5feadd38af3e6b3c7be45cfdb0a14c7149b \ + --hash=sha256:db381f6fdaef483ad435f778086ccc4890120aff8df2ba5cfeeac24d280b3145 \ + --hash=sha256:e6487d01c8b7ed86af30ea141fcc4f93f8a7dde26f94177c1ad637c353bd5c07 \ + --hash=sha256:e86923fa728dfba39c5bb6046a450bd4eec8ad949ac404eca728cfce320d1732 \ + --hash=sha256:f6ca36dc1e9eeb46d779869c60001b3065fb670b5775c51421c099ea2a77c3c9 \ + --hash=sha256:fb62f2cbe790a50d95593fb40e8cca261c31a2f5637455ea39440d6457c2ba25 zope.proxy==4.3.3 \ --hash=sha256:04646ac04ffa9c8e32fb2b5c3cd42995b2548ea14251f3c21ca704afae88e42c \ --hash=sha256:07b6bceea232559d24358832f1cd2ed344bbf05ca83855a5b9698b5f23c5ed60 \ diff --git a/letsencrypt-auto-source/pieces/dependency-requirements.txt b/letsencrypt-auto-source/pieces/dependency-requirements.txt index 034fae46d..eec5a9946 100644 --- a/letsencrypt-auto-source/pieces/dependency-requirements.txt +++ b/letsencrypt-auto-source/pieces/dependency-requirements.txt @@ -9,11 +9,11 @@ # pip install hashin # hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` -ConfigArgParse==0.14.0 \ - --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 -certifi==2019.9.11 \ - --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ - --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef +ConfigArgParse==1.0 \ + --hash=sha256:bf378245bc9cdc403a527e5b7406b991680c2a530e7e81af747880b54eb57133 +certifi==2019.11.28 \ + --hash=sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3 \ + --hash=sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f cffi==1.13.2 \ --hash=sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42 \ --hash=sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04 \ @@ -86,8 +86,6 @@ enum34==1.1.6 \ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -future==0.18.2 \ - --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c @@ -100,40 +98,40 @@ josepy==1.2.0 \ mock==1.3.0 \ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb -parsedatetime==2.4 \ - --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ - --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.4.3 \ - --hash=sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8 \ - --hash=sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9 -pyOpenSSL==19.0.0 \ - --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ - --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 +parsedatetime==2.5 \ + --hash=sha256:3b835fc54e472c17ef447be37458b400e3fefdf14bb1ffdedb5d2c853acf4ba1 \ + --hash=sha256:d2e9ddb1e463de871d32088a3f3cea3dc8282b1b2800e081bd0ef86900451667 +pbr==5.4.4 \ + --hash=sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b \ + --hash=sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488 +pyOpenSSL==19.1.0 \ + --hash=sha256:621880965a720b8ece2f1b2f54ea2071966ab00e2970ad2ce11d596102063504 \ + --hash=sha256:9a24494b2602aaf402be5c9e30a0b82d4a5c67528fe8fb475e3f3bc00dd69507 pyRFC3339==1.1 \ --hash=sha256:67196cb83b470709c580bb4738b83165e67c6cc60e1f2e4f286cfcb402a926f4 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.4.5 \ - --hash=sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f \ - --hash=sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a +pyparsing==2.4.6 \ + --hash=sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f \ + --hash=sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 pytz==2019.3 \ --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be -requests==2.21.0 \ - --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ - --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b +requests==2.22.0 \ + --hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 \ + --hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31 requests-toolbelt==0.9.1 \ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 -six==1.13.0 \ - --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ - --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 -urllib3==1.24.3 \ - --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ - --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb +six==1.14.0 \ + --hash=sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a \ + --hash=sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c +urllib3==1.25.8 \ + --hash=sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc \ + --hash=sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc zope.component==4.6 \ --hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6 zope.deferredimport==4.3.1 \ @@ -145,47 +143,86 @@ zope.deprecation==4.4.0 \ zope.event==4.4 \ --hash=sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf \ --hash=sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7 -zope.hookable==4.2.0 \ - --hash=sha256:22886e421234e7e8cedc21202e1d0ab59960e40a47dd7240e9659a2d82c51370 \ - --hash=sha256:39912f446e45b4e1f1951b5ffa2d5c8b074d25727ec51855ae9eab5408f105ab \ - --hash=sha256:3adb7ea0871dbc56b78f62c4f5c024851fc74299f4f2a95f913025b076cde220 \ - --hash=sha256:3d7c4b96341c02553d8b8d71065a9366ef67e6c6feca714f269894646bb8268b \ - --hash=sha256:4e826a11a529ed0464ffcecf34b0b7bd1b4928dd5848c5c61bedd7833e8f4801 \ - --hash=sha256:700d68cc30728de1c4c62088a981c6daeaefdf20a0d81995d2c0b7f442c5f88c \ - --hash=sha256:77c82a430cedfbf508d1aa406b2f437363c24fa90c73f577ead0fb5295749b83 \ - --hash=sha256:c1df3929a3666fc5a0c80d60a0c1e6f6ef97c7f6ed2f1b7cf49f3e6f3d4dde15 \ - --hash=sha256:dba8b2dd2cd41cb5f37bfa3f3d82721b8ae10e492944e48ddd90a439227f2893 \ - --hash=sha256:f492540305b15b5591bd7195d61f28946bb071de071cee5d68b6b8414da90fd2 -zope.interface==4.6.0 \ - --hash=sha256:086707e0f413ff8800d9c4bc26e174f7ee4c9c8b0302fbad68d083071822316c \ - --hash=sha256:1157b1ec2a1f5bf45668421e3955c60c610e31913cc695b407a574efdbae1f7b \ - --hash=sha256:11ebddf765bff3bbe8dbce10c86884d87f90ed66ee410a7e6c392086e2c63d02 \ - --hash=sha256:14b242d53f6f35c2d07aa2c0e13ccb710392bcd203e1b82a1828d216f6f6b11f \ - --hash=sha256:1b3d0dcabc7c90b470e59e38a9acaa361be43b3a6ea644c0063951964717f0e5 \ - --hash=sha256:20a12ab46a7e72b89ce0671e7d7a6c3c1ca2c2766ac98112f78c5bddaa6e4375 \ - --hash=sha256:298f82c0ab1b182bd1f34f347ea97dde0fffb9ecf850ecf7f8904b8442a07487 \ - --hash=sha256:2f6175722da6f23dbfc76c26c241b67b020e1e83ec7fe93c9e5d3dd18667ada2 \ - --hash=sha256:3b877de633a0f6d81b600624ff9137312d8b1d0f517064dfc39999352ab659f0 \ - --hash=sha256:4265681e77f5ac5bac0905812b828c9fe1ce80c6f3e3f8574acfb5643aeabc5b \ - --hash=sha256:550695c4e7313555549aa1cdb978dc9413d61307531f123558e438871a883d63 \ - --hash=sha256:5f4d42baed3a14c290a078e2696c5f565501abde1b2f3f1a1c0a94fbf6fbcc39 \ - --hash=sha256:62dd71dbed8cc6a18379700701d959307823b3b2451bdc018594c48956ace745 \ - --hash=sha256:7040547e5b882349c0a2cc9b50674b1745db551f330746af434aad4f09fba2cc \ - --hash=sha256:7e099fde2cce8b29434684f82977db4e24f0efa8b0508179fce1602d103296a2 \ - --hash=sha256:7e5c9a5012b2b33e87980cee7d1c82412b2ebabcb5862d53413ba1a2cfde23aa \ - --hash=sha256:81295629128f929e73be4ccfdd943a0906e5fe3cdb0d43ff1e5144d16fbb52b1 \ - --hash=sha256:95cc574b0b83b85be9917d37cd2fad0ce5a0d21b024e1a5804d044aabea636fc \ - --hash=sha256:968d5c5702da15c5bf8e4a6e4b67a4d92164e334e9c0b6acf080106678230b98 \ - --hash=sha256:9e998ba87df77a85c7bed53240a7257afe51a07ee6bc3445a0bf841886da0b97 \ - --hash=sha256:a0c39e2535a7e9c195af956610dba5a1073071d2d85e9d2e5d789463f63e52ab \ - --hash=sha256:a15e75d284178afe529a536b0e8b28b7e107ef39626a7809b4ee64ff3abc9127 \ - --hash=sha256:a6a6ff82f5f9b9702478035d8f6fb6903885653bff7ec3a1e011edc9b1a7168d \ - --hash=sha256:b639f72b95389620c1f881d94739c614d385406ab1d6926a9ffe1c8abbea23fe \ - --hash=sha256:bad44274b151d46619a7567010f7cde23a908c6faa84b97598fd2f474a0c6891 \ - --hash=sha256:bbcef00d09a30948756c5968863316c949d9cedbc7aabac5e8f0ffbdb632e5f1 \ - --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ - --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ - --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 +zope.hookable==5.0.0 \ + --hash=sha256:0992a0dd692003c09fb958e1480cebd1a28f2ef32faa4857d864f3ca8e9d6952 \ + --hash=sha256:0f325838dbac827a1e2ed5d482c1f2656b6844dc96aa098f7727e76395fcd694 \ + --hash=sha256:22a317ba00f61bac99eac1a5e330be7cb8c316275a21269ec58aa396b602af0c \ + --hash=sha256:25531cb5e7b35e8a6d1d6eddef624b9a22ce5dcf8f4448ef0f165acfa8c3fc21 \ + --hash=sha256:30890892652766fc80d11f078aca9a5b8150bef6b88aba23799581a53515c404 \ + --hash=sha256:342d682d93937e5b8c232baffb32a87d5eee605d44f74566657c64a239b7f342 \ + --hash=sha256:46b2fddf1f5aeb526e02b91f7e62afbb9fff4ffd7aafc97cdb00a0d717641567 \ + --hash=sha256:523318ff96df9b8d378d997c00c5d4cbfbff68dc48ff5ee5addabdb697d27528 \ + --hash=sha256:53aa02eb8921d4e667c69d76adeed8fe426e43870c101cb08dcd2f3468aff742 \ + --hash=sha256:62e79e8fdde087cb20822d7874758f5acbedbffaf3c0fbe06309eb8a41ee4e06 \ + --hash=sha256:74bf2f757f7385b56dc3548adae508d8b3ef952d600b4b12b88f7d1706b05dcc \ + --hash=sha256:751ee9d89eb96e00c1d7048da9725ce392a708ed43406416dc5ed61e4d199764 \ + --hash=sha256:7b83bc341e682771fe810b360cd5d9c886a948976aea4b979ff214e10b8b523b \ + --hash=sha256:81eeeb27dbb0ddaed8070daee529f0d1bfe4f74c7351cce2aaca3ea287c4cc32 \ + --hash=sha256:856509191e16930335af4d773c0fc31a17bae8991eb6f167a09d5eddf25b56cc \ + --hash=sha256:8853e81fd07b18fa9193b19e070dc0557848d9945b1d2dac3b7782543458c87d \ + --hash=sha256:94506a732da2832029aecdfe6ea07eb1b70ee06d802fff34e1b3618fe7cdf026 \ + --hash=sha256:95ad874a8cc94e786969215d660143817f745225579bfe318c4676e218d3147c \ + --hash=sha256:9758ec9174966ffe5c499b6c3d149f80aa0a9238020006a2b87c6af5963fcf48 \ + --hash=sha256:a169823e331da939aa7178fc152e65699aeb78957e46c6f80ccb50ee4c3616c2 \ + --hash=sha256:a67878a798f6ca292729a28c2226592b3d000dc6ee7825d31887b553686c7ac7 \ + --hash=sha256:a9a6d9eb2319a09905670810e2de971d6c49013843700b4975e2fc0afe96c8db \ + --hash=sha256:b3e118b58a3d2301960e6f5f25736d92f6b9f861728d3b8c26d69f54d8a157d2 \ + --hash=sha256:ca6705c2a1fb5059a4efbe9f5426be4cdf71b3c9564816916fc7aa7902f19ede \ + --hash=sha256:cf711527c9d4ae72085f137caffb4be74fc007ffb17cd103628c7d5ba17e205f \ + --hash=sha256:d087602a6845ebe9d5a1c5a949fedde2c45f372d77fbce4f7fe44b68b28a1d03 \ + --hash=sha256:d1080e1074ddf75ad6662a9b34626650759c19a9093e1a32a503d37e48da135b \ + --hash=sha256:db9c60368aff2b7e6c47115f3ad9bd6e96aa298b12ed5f8cb13f5673b30be565 \ + --hash=sha256:dbeb127a04473f5a989169eb400b67beb921c749599b77650941c21fe39cb8d9 \ + --hash=sha256:dca336ca3682d869d291d7cd18284f6ff6876e4244eb1821430323056b000e2c \ + --hash=sha256:dd69a9be95346d10c853b6233fcafe3c0315b89424b378f2ad45170d8e161568 \ + --hash=sha256:dd79f8fae5894f1ee0a0042214685f2d039341250c994b825c10a4cd075d80f6 \ + --hash=sha256:e647d850aa1286d98910133cee12bd87c354f7b7bb3f3cd816a62ba7fa2f7007 \ + --hash=sha256:f37a210b5c04b2d4e4bac494ab15b70196f219a1e1649ddca78560757d4278fb \ + --hash=sha256:f67820b6d33a705dc3c1c457156e51686f7b350ff57f2112e1a9a4dad38ec268 \ + --hash=sha256:f68969978ccf0e6123902f7365aae5b7a9e99169d4b9105c47cf28e788116894 \ + --hash=sha256:f717a0b34460ae1ac0064e91b267c0588ac2c098ffd695992e72cd5462d97a67 \ + --hash=sha256:f9d58ccec8684ca276d5a4e7b0dfacca028336300a8f715d616d9f0ce9ae8096 \ + --hash=sha256:fcc3513a54e656067cbf7b98bab0d6b9534b9eabc666d1f78aad6acdf0962736 +zope.interface==4.7.1 \ + --hash=sha256:048b16ac882a05bc7ef534e8b9f15c9d7a6c190e24e8938a19b7617af4ed854a \ + --hash=sha256:05816cf8e7407cf62f2ec95c0a5d69ec4fa5741d9ccd10db9f21691916a9a098 \ + --hash=sha256:065d6a1ac89d35445168813bed45048ed4e67a4cdfc5a68fdb626a770378869f \ + --hash=sha256:14157421f4121a57625002cc4f48ac7521ea238d697c4a4459a884b62132b977 \ + --hash=sha256:18dc895945694f397a0be86be760ff664b790f95d8e7752d5bab80284ff9105d \ + --hash=sha256:1962c9f838bd6ae4075d0014f72697510daefc7e1c7e48b2607df0b6e157989c \ + --hash=sha256:1a67408cacd198c7e6274a19920bb4568d56459e659e23c4915528686ac1763a \ + --hash=sha256:21bf781076dd616bd07cf0223f79d61ab4f45176076f90bc2890e18c48195da4 \ + --hash=sha256:21c0a5d98650aebb84efa16ce2c8df1a46bdc4fe8a9e33237d0ca0b23f416ead \ + --hash=sha256:23cfeea25d1e42ff3bf4f9a0c31e9d5950aa9e7c4b12f0c4bd086f378f7b7a71 \ + --hash=sha256:24b6fce1fb71abf9f4093e3259084efcc0ef479f89356757780685bd2b06ef37 \ + --hash=sha256:24f84ce24eb6b5fcdcb38ad9761524f1ae96f7126abb5e597f8a3973d9921409 \ + --hash=sha256:25e0ef4a824017809d6d8b0ce4ab3288594ba283e4d4f94d8cfb81d73ed65114 \ + --hash=sha256:2e8fdd625e9aba31228e7ddbc36bad5c38dc3ee99a86aa420f89a290bd987ce9 \ + --hash=sha256:2f3bc2f49b67b1bea82b942d25bc958d4f4ea6709b411cb2b6b9718adf7914ce \ + --hash=sha256:35d24be9d04d50da3a6f4d61de028c1dd087045385a0ff374d93ef85af61b584 \ + --hash=sha256:35dbe4e8c73003dff40dfaeb15902910a4360699375e7b47d3c909a83ff27cd0 \ + --hash=sha256:3dfce831b824ab5cf446ed0c350b793ac6fa5fe33b984305cb4c966a86a8fb79 \ + --hash=sha256:3f7866365df5a36a7b8de8056cd1c605648f56f9a226d918ed84c85d25e8d55f \ + --hash=sha256:455cc8c01de3bac6f9c223967cea41f4449f58b4c2e724ec8177382ddd183ab4 \ + --hash=sha256:4bb937e998be9d5e345f486693e477ba79e4344674484001a0b646be1d530487 \ + --hash=sha256:52303a20902ca0888dfb83230ca3ee6fbe63c0ad1dd60aa0bba7958ccff454d8 \ + --hash=sha256:6e0a897d4e09859cc80c6a16a29697406ead752292ace17f1805126a4f63c838 \ + --hash=sha256:6e1816e7c10966330d77af45f77501f9a68818c065dec0ad11d22b50a0e212e7 \ + --hash=sha256:73b5921c5c6ce3358c836461b5470bf675601c96d5e5d8f2a446951470614f67 \ + --hash=sha256:8093cd45cdb5f6c8591cfd1af03d32b32965b0f79b94684cd0c9afdf841982bb \ + --hash=sha256:864b4a94b60db301899cf373579fd9ef92edddbf0fb2cd5ae99f53ef423ccc56 \ + --hash=sha256:8a27b4d3ea9c6d086ce8e7cdb3e8d319b6752e2a03238a388ccc83ccbe165f50 \ + --hash=sha256:91b847969d4784abd855165a2d163f72ac1e58e6dce09a5e46c20e58f19cc96d \ + --hash=sha256:b47b1028be4758c3167e474884ccc079b94835f058984b15c145966c4df64d27 \ + --hash=sha256:b68814a322835d8ad671b7acc23a3b2acecba527bb14f4b53fc925f8a27e44d8 \ + --hash=sha256:bcb50a032c3b6ec7fb281b3a83d2b31ab5246c5b119588725b1350d3a1d9f6a3 \ + --hash=sha256:c56db7d10b25ce8918b6aec6b08ac401842b47e6c136773bfb3b590753f7fb67 \ + --hash=sha256:c94b77a13d4f47883e4f97f9fa00f5feadd38af3e6b3c7be45cfdb0a14c7149b \ + --hash=sha256:db381f6fdaef483ad435f778086ccc4890120aff8df2ba5cfeeac24d280b3145 \ + --hash=sha256:e6487d01c8b7ed86af30ea141fcc4f93f8a7dde26f94177c1ad637c353bd5c07 \ + --hash=sha256:e86923fa728dfba39c5bb6046a450bd4eec8ad949ac404eca728cfce320d1732 \ + --hash=sha256:f6ca36dc1e9eeb46d779869c60001b3065fb670b5775c51421c099ea2a77c3c9 \ + --hash=sha256:fb62f2cbe790a50d95593fb40e8cca261c31a2f5637455ea39440d6457c2ba25 zope.proxy==4.3.3 \ --hash=sha256:04646ac04ffa9c8e32fb2b5c3cd42995b2548ea14251f3c21ca704afae88e42c \ --hash=sha256:07b6bceea232559d24358832f1cd2ed344bbf05ca83855a5b9698b5f23c5ed60 \ diff --git a/letsencrypt-auto-source/rebuild_dependencies.py b/letsencrypt-auto-source/rebuild_dependencies.py index eedc604e0..6d1ec15ff 100755 --- a/letsencrypt-auto-source/rebuild_dependencies.py +++ b/letsencrypt-auto-source/rebuild_dependencies.py @@ -46,12 +46,6 @@ AUTHORITATIVE_CONSTRAINTS = { # certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. # TODO: hashin seems to overwrite environment markers in dependencies. This needs to be fixed. 'enum34': '1.1.6 ; python_version < \'3.4\'', - # Newer versions of the packages below dropped support for python 3.4. Once - # Certbot does as well, we should unpin these dependencies. - 'requests': '2.21.0', - 'ConfigArgParse': '0.14.0', - 'zope.hookable': '4.2.0', - 'zope.interface': '4.6.0', } diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index a16a9d680..1204cbf5f 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -13,8 +13,8 @@ backports.functools-lru-cache==1.5 backports.shutil-get-terminal-size==1.0.0 backports.ssl-match-hostname==3.7.0.1 bcrypt==3.1.6 -boto3==1.9.36 -botocore==1.12.36 +boto3==1.11.7 +botocore==1.14.7 cached-property==1.5.1 cloudflare==1.5.1 codecov==2.0.15 @@ -29,11 +29,11 @@ docker-compose==1.25.0 docker-pycreds==0.4.0 dockerpty==0.4.1 docopt==0.6.2 -docutils==0.12 +docutils==0.15.2 execnet==1.5.0 functools32==3.2.3.post2 future==0.16.0 -futures==3.1.1 +futures==3.3.0 filelock==3.0.12 google-api-python-client==1.5.5 httplib2==0.10.3 @@ -44,7 +44,7 @@ ipython==5.8.0 ipython-genutils==0.2.0 isort==4.3.21 Jinja2==2.9.6 -jmespath==0.9.3 +jmespath==0.9.4 josepy==1.1.0 jsonschema==2.6.0 lazy-object-proxy==1.4.3 @@ -81,7 +81,7 @@ pytest-forked==0.2 pytest-xdist==1.22.5 pytest-sugar==0.9.2 pytest-rerunfailures==4.2 -python-dateutil==2.6.1 +python-dateutil==2.8.1 python-digitalocean==1.11 pywin32==227 PyYAML==3.13 @@ -89,7 +89,7 @@ repoze.sphinx.autointerface==0.8 requests-file==1.4.2 requests-toolbelt==0.8.0 rsa==3.4.2 -s3transfer==0.1.11 +s3transfer==0.3.1 scandir==1.10.0 simplegeneric==0.8.1 singledispatch==3.4.0.3 From b8a9dd75eb8c8be7990d3053536de59716c3c585 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 24 Jan 2020 15:02:57 -0800 Subject: [PATCH 056/122] Update dns-lexicon version. (#7723) --- tools/dev_constraints.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index 1204cbf5f..d40c6e19a 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -22,7 +22,7 @@ configparser==3.7.4 contextlib2==0.6.0.post1 coverage==4.5.4 decorator==4.4.1 -dns-lexicon==3.2.1 +dns-lexicon==3.3.17 dnspython==1.15.0 docker==3.7.2 docker-compose==1.25.0 From e3c996de102b876aaedfc77517898e42094242da Mon Sep 17 00:00:00 2001 From: Cameron Steel Date: Sat, 25 Jan 2020 10:25:03 +1100 Subject: [PATCH 057/122] dns-cloudflare: Implement limited-scope API Tokens (#7583) A while ago Cloudflare added support for limited-scope API Tokens in place of using a global API key, but support for them in cloudflare/python-cloudflare took a while to get through. In summary, this PR: - Implements token functionality through the INI file parameter `dns_cloudflare_api_token` (in addition to the traditional `dns_cloudflare_email` and `dns_cloudflare_api_key`). This needed a more advanced parameter validator than the built in `required_variables` mechanism. - Updates the docs to reflect the new option, needed token permissions, and version details of the `cloudflare` module * Update python-cloudflare version * Add Cloudflare API Token support to certbot-dns-cloudflare * Add token-specific errors to certbot-dns-cloudflare * Tidy up certbot-dns-cloudflare * Implement Cloudflare API Tokens in testing for certbot-dns-cloudflare(needs work) * Further tidying of certbot-dns-cloudflare * Update CHANGELOG with Cloudflare API Tokens implementation * Improve testing of certbot-dns-cloudflare * Improve certbot-dns-cloudflare test formatting * Further improve testing for certbot-dns-cloudflare * Change needed permissions for token * Add documentation regarding python-cloudflare version * Fix changelog, references to python-cloudflare and docs * Fix behaviour when domain does not match cloudflare root domain. Improve error handling. * Improve testing * Improve hints and error handling --- AUTHORS.md | 1 + .../certbot_dns_cloudflare/__init__.py | 35 +++++++-- .../_internal/dns_cloudflare.py | 73 +++++++++++++++---- .../tests/dns_cloudflare_test.py | 68 ++++++++++++++++- certbot/CHANGELOG.md | 2 +- tools/dev_constraints.txt | 2 +- 6 files changed, 159 insertions(+), 22 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index d2d5fc2a7..80a24d3be 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -36,6 +36,7 @@ Authors * [Brad Warren](https://github.com/bmw) * [Brandon Kraft](https://github.com/kraftbj) * [Brandon Kreisel](https://github.com/kraftbj) +* [Cameron Steel](https://github.com/Tugzrida) * [Ceesjan Luiten](https://github.com/quinox) * [Chad Whitacre](https://github.com/whit537) * [Chhatoi Pritam Baral](https://github.com/pritambaral) diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py index b08bc0968..11886ea54 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py @@ -22,17 +22,40 @@ Credentials Use of this plugin requires a configuration file containing Cloudflare API credentials, obtained from your Cloudflare -`account page `_. This plugin -does not currently support Cloudflare's "API Tokens", so please ensure you use -the "Global API Key" for authentication. +`account page `_. + +Previously, Cloudflare's "Global API Key" was used for authentication, however +this key can access the entire Cloudflare API for all domains in your account, +meaning it could cause a lot of damage if leaked. + +Cloudflare's newer API Tokens can be restricted to specific domains and +operations, and are therefore now the recommended authentication option. + +However, due to some shortcomings in Cloudflare's implementation of Tokens, +Tokens created for Certbot currently require ``Zone:Zone:Read`` and ``Zone:DNS:Edit`` +permissions for **all** zones in your account. While this is not ideal, your Token +will still have fewer permission than the Global key, so it's still worth doing. +Hopefully Cloudflare will improve this in the future. + +Using Cloudflare Tokens also requires at least version 2.3.1 of the ``cloudflare`` +python module. If the version that automatically installed with this plugin is +older than that, and you can't upgrade it on your system, you'll have to stick to +the Global key. .. code-block:: ini - :name: credentials.ini - :caption: Example credentials file: + :name: certbot_cloudflare_token.ini + :caption: Example credentials file using restricted API Token (recommended): + + # Cloudflare API token used by Certbot + dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567 + +.. code-block:: ini + :name: certbot_cloudflare_key.ini + :caption: Example credentials file using Global API Key (not recommended): # Cloudflare API credentials used by Certbot dns_cloudflare_email = cloudflare@example.com - dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234567 + dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234 The path to this file can be provided interactively or using the ``--dns-cloudflare-credentials`` command-line argument. Certbot records the path diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py index 0bbdf703a..22124ac04 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py @@ -4,6 +4,10 @@ import logging import CloudFlare import zope.interface +from acme.magic_typing import Any +from acme.magic_typing import Dict +from acme.magic_typing import List + from certbot import errors from certbot import interfaces from certbot.plugins import dns_common @@ -38,14 +42,35 @@ class Authenticator(dns_common.DNSAuthenticator): return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the Cloudflare API.' + def _validate_credentials(self, credentials): + token = credentials.conf('api-token') + email = credentials.conf('email') + key = credentials.conf('api-key') + if token: + if email or key: + raise errors.PluginError('{}: dns_cloudflare_email and dns_cloudflare_api_key are ' + 'not needed when using an API Token' + .format(credentials.confobj.filename)) + elif email or key: + if not email: + raise errors.PluginError('{}: dns_cloudflare_email is required when using a Global ' + 'API Key. (should be email address associated with ' + 'Cloudflare account)'.format(credentials.confobj.filename)) + if not key: + raise errors.PluginError('{}: dns_cloudflare_api_key is required when using a ' + 'Global API Key. (see {})' + .format(credentials.confobj.filename, ACCOUNT_URL)) + else: + raise errors.PluginError('{}: Either dns_cloudflare_api_token (recommended), or ' + 'dns_cloudflare_email and dns_cloudflare_api_key are required.' + ' (see {})'.format(credentials.confobj.filename, ACCOUNT_URL)) + def _setup_credentials(self): self.credentials = self._configure_credentials( 'credentials', 'Cloudflare credentials INI file', - { - 'email': 'email address associated with Cloudflare account', - 'api-key': 'API key for Cloudflare account, obtained from {0}'.format(ACCOUNT_URL) - } + None, + self._validate_credentials ) def _perform(self, domain, validation_name, validation): @@ -55,6 +80,8 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_cloudflare_client().del_txt_record(domain, validation_name, validation) def _get_cloudflare_client(self): + if self.credentials.conf('api-token'): + return _CloudflareClient(None, self.credentials.conf('api-token')) return _CloudflareClient(self.credentials.conf('email'), self.credentials.conf('api-key')) @@ -88,8 +115,15 @@ class _CloudflareClient(object): logger.debug('Attempting to add record to zone %s: %s', zone_id, data) self.cf.zones.dns_records.post(zone_id, data=data) # zones | pylint: disable=no-member except CloudFlare.exceptions.CloudFlareAPIError as e: + code = int(e) + hint = None + + if code == 9109: + hint = 'Does your API token have "Zone:DNS:Edit" permissions?' + logger.error('Encountered CloudFlareAPIError adding TXT record: %d %s', e, e) - raise errors.PluginError('Error communicating with the Cloudflare API: {0}'.format(e)) + raise errors.PluginError('Error communicating with the Cloudflare API: {0}{1}' + .format(e, ' ({0})'.format(hint) if hint else '')) record_id = self._find_txt_record_id(zone_id, record_name, record_content) logger.debug('Successfully added TXT record with record_id: %s', record_id) @@ -139,6 +173,8 @@ class _CloudflareClient(object): """ zone_name_guesses = dns_common.base_domain_name_guesses(domain) + zones = [] # type: List[Dict[str, Any]] + code = msg = None for zone_name in zone_name_guesses: params = {'name': zone_name, @@ -148,16 +184,26 @@ class _CloudflareClient(object): zones = self.cf.zones.get(params=params) # zones | pylint: disable=no-member except CloudFlare.exceptions.CloudFlareAPIError as e: code = int(e) + msg = str(e) hint = None if code == 6003: - hint = 'Did you copy your entire API key?' + hint = ('Did you copy your entire API token/key? To use Cloudflare tokens, ' + 'you\'ll need the python package cloudflare>=2.3.1.{}' + .format(' This certbot is running cloudflare ' + str(CloudFlare.__version__) + if hasattr(CloudFlare, '__version__') else '')) elif code == 9103: - hint = 'Did you enter the correct email address?' + hint = 'Did you enter the correct email address and Global key?' + elif code == 9109: + hint = 'Did you enter a valid Cloudflare Token?' - raise errors.PluginError('Error determining zone_id: {0} {1}. Please confirm that ' - 'you have supplied valid Cloudflare API credentials.{2}' - .format(code, e, ' ({0})'.format(hint) if hint else '')) + if hint: + raise errors.PluginError('Error determining zone_id: {0} {1}. Please confirm ' + 'that you have supplied valid Cloudflare API credentials. ({2})' + .format(code, msg, hint)) + else: + logger.debug('Unrecognised CloudFlareAPIError while finding zone_id: %d %s. ' + 'Continuing with next zone guess...', e, e) if zones: zone_id = zones[0]['id'] @@ -165,9 +211,10 @@ class _CloudflareClient(object): return zone_id raise errors.PluginError('Unable to determine zone_id for {0} using zone names: {1}. ' - 'Please confirm that the domain name has been entered correctly ' - 'and is already associated with the supplied Cloudflare account.' - .format(domain, zone_name_guesses)) + 'Please confirm that the domain name has been entered correctly ' + 'and is already associated with the supplied Cloudflare account.{2}' + .format(domain, zone_name_guesses, ' The error from Cloudflare was:' + ' {0} {1}'.format(code, msg) if code is not None else '')) def _find_txt_record_id(self, zone_id, record_name, record_content): """ diff --git a/certbot-dns-cloudflare/tests/dns_cloudflare_test.py b/certbot-dns-cloudflare/tests/dns_cloudflare_test.py index b24628b0d..d38330191 100644 --- a/certbot-dns-cloudflare/tests/dns_cloudflare_test.py +++ b/certbot-dns-cloudflare/tests/dns_cloudflare_test.py @@ -12,6 +12,9 @@ from certbot.plugins.dns_test_common import DOMAIN from certbot.tests import util as test_util API_ERROR = CloudFlare.exceptions.CloudFlareAPIError(1000, '', '') + +API_TOKEN = 'an-api-token' + API_KEY = 'an-api-key' EMAIL = 'example@example.com' @@ -49,6 +52,50 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)] self.assertEqual(expected, self.mock_client.mock_calls) + def test_api_token(self): + dns_test_common.write({"cloudflare_api_token": API_TOKEN}, + self.config.cloudflare_credentials) + self.auth.perform([self.achall]) + + expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] + self.assertEqual(expected, self.mock_client.mock_calls) + + def test_no_creds(self): + dns_test_common.write({}, self.config.cloudflare_credentials) + self.assertRaises(errors.PluginError, + self.auth.perform, + [self.achall]) + + def test_missing_email_or_key(self): + dns_test_common.write({"cloudflare_api_key": API_KEY}, self.config.cloudflare_credentials) + self.assertRaises(errors.PluginError, + self.auth.perform, + [self.achall]) + + dns_test_common.write({"cloudflare_email": EMAIL}, self.config.cloudflare_credentials) + self.assertRaises(errors.PluginError, + self.auth.perform, + [self.achall]) + + def test_email_or_key_with_token(self): + dns_test_common.write({"cloudflare_api_token": API_TOKEN, "cloudflare_email": EMAIL}, + self.config.cloudflare_credentials) + self.assertRaises(errors.PluginError, + self.auth.perform, + [self.achall]) + + dns_test_common.write({"cloudflare_api_token": API_TOKEN, "cloudflare_api_key": API_KEY}, + self.config.cloudflare_credentials) + self.assertRaises(errors.PluginError, + self.auth.perform, + [self.achall]) + + dns_test_common.write({"cloudflare_api_token": API_TOKEN, "cloudflare_email": EMAIL, + "cloudflare_api_key": API_KEY}, self.config.cloudflare_credentials) + self.assertRaises(errors.PluginError, + self.auth.perform, + [self.achall]) + class CloudflareClientTest(unittest.TestCase): record_name = "foo" @@ -83,7 +130,7 @@ class CloudflareClientTest(unittest.TestCase): def test_add_txt_record_error(self): self.cf.zones.get.return_value = [{'id': self.zone_id}] - self.cf.zones.dns_records.post.side_effect = API_ERROR + self.cf.zones.dns_records.post.side_effect = CloudFlare.exceptions.CloudFlareAPIError(9109, '', '') self.assertRaises( errors.PluginError, @@ -106,6 +153,25 @@ class CloudflareClientTest(unittest.TestCase): self.cloudflare_client.add_txt_record, DOMAIN, self.record_name, self.record_content, self.record_ttl) + def test_add_txt_record_bad_creds(self): + self.cf.zones.get.side_effect = CloudFlare.exceptions.CloudFlareAPIError(6003, '', '') + self.assertRaises( + errors.PluginError, + self.cloudflare_client.add_txt_record, + DOMAIN, self.record_name, self.record_content, self.record_ttl) + + self.cf.zones.get.side_effect = CloudFlare.exceptions.CloudFlareAPIError(9103, '', '') + self.assertRaises( + errors.PluginError, + self.cloudflare_client.add_txt_record, + DOMAIN, self.record_name, self.record_content, self.record_ttl) + + self.cf.zones.get.side_effect = CloudFlare.exceptions.CloudFlareAPIError(9109, '', '') + self.assertRaises( + errors.PluginError, + self.cloudflare_client.add_txt_record, + DOMAIN, self.record_name, self.record_content, self.record_ttl) + def test_del_txt_record(self): self.cf.zones.get.return_value = [{'id': self.zone_id}] self.cf.zones.dns_records.get.return_value = [{'id': self.record_id}] diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 1cd4d3f1c..48a2a554b 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -6,7 +6,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added -* +* Added support for Cloudflare's limited-scope API Tokens ### Changed diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index d40c6e19a..265d967d8 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -16,7 +16,7 @@ bcrypt==3.1.6 boto3==1.11.7 botocore==1.14.7 cached-property==1.5.1 -cloudflare==1.5.1 +cloudflare==2.3.1 codecov==2.0.15 configparser==3.7.4 contextlib2==0.6.0.post1 From 2338ab36fd70e9d65eeafb8e22cd05b3ac879e3f Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 27 Jan 2020 13:13:38 -0800 Subject: [PATCH 058/122] Add backwards compatibility docs (#7611) Fixes #7463. * Add backwards compatibility docs. * Exclude certbot-auto --- certbot/docs/compatibility.rst | 39 ++++++++++++++++++++++++++++++++++ certbot/docs/index.rst | 1 + 2 files changed, 40 insertions(+) create mode 100644 certbot/docs/compatibility.rst diff --git a/certbot/docs/compatibility.rst b/certbot/docs/compatibility.rst new file mode 100644 index 000000000..a511f36a2 --- /dev/null +++ b/certbot/docs/compatibility.rst @@ -0,0 +1,39 @@ +======================= +Backwards Compatibility +======================= + +All Certbot components including `acme `_, +Certbot, and :ref:`non-third party plugins ` follow `Semantic +Versioning `_ both for its Python :doc:`API ` and for the +application itself. This means that we will not change behavior in a backwards +incompatible way except in a new major version of the project. + +.. note:: None of this applies to the behavior of Certbot distribution + mechanisms such as :ref:`certbot-auto ` or OS packages whose + behavior may change at any time. Semantic versioning only applies to the + common Certbot components that are installed by various distribution + methods. + +For Certbot as an application, the command line interface and non-interactive +behavior can be considered stable with two exceptions. The first is that no +aspects of Certbot's console or log output should be considered stable and it +may change at any time. The second is that Certbot's behavior should only be +considered stable with certain files but not all. Files with which users should +expect Certbot to maintain its current behavior with are: + +* ``/etc/letsencrypt/live//{cert,chain,fullchain,privkey}.pem`` where + ```` is the name given to ``--cert-name``. If ``--cert-name`` is not + set by the user, it is the first domain given to ``--domains``. +* :ref:`CLI configuration files ` +* Hook directories in ``/etc/letsencrypt/renewal-hooks`` + +Certbot's behavior with other files may change at any point. + +Another area where Certbot should not be considered stable is its behavior when +not run in non-interactive mode which also may change at any point. + +In general, if we're making a change that we expect will break some users, we +will bump the major version and will have warned about it in a prior release +when possible. For our Python API, we will issue warnings using Python's +warning module. For application level changes, we will print and log warning +messages. diff --git a/certbot/docs/index.rst b/certbot/docs/index.rst index 17cde1adf..a7fc75c5b 100644 --- a/certbot/docs/index.rst +++ b/certbot/docs/index.rst @@ -10,6 +10,7 @@ Welcome to the Certbot documentation! using contributing packaging + compatibility resources .. toctree:: From 11e402893fc0bff32fb6fb5ab2a307c3dc62a633 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Wed, 29 Jan 2020 15:21:17 -0800 Subject: [PATCH 059/122] Remove SSLCompression off line from all config options (#7726) Based on discussion at https://github.com/certbot/certbot/pull/7712#discussion_r371451761. * Remove SSLCompression off line from all config options * Update changelog --- certbot-apache/MANIFEST.in | 1 - .../_internal/centos-options-ssl-apache.conf | 18 ------------------ .../_internal/options-ssl-apache.conf | 1 - .../_internal/override_centos.py | 2 +- .../_internal/override_fedora.py | 2 +- certbot/CHANGELOG.md | 2 +- 6 files changed, 3 insertions(+), 23 deletions(-) delete mode 100644 certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf diff --git a/certbot-apache/MANIFEST.in b/certbot-apache/MANIFEST.in index fa15504e7..2316983bb 100644 --- a/certbot-apache/MANIFEST.in +++ b/certbot-apache/MANIFEST.in @@ -1,7 +1,6 @@ include LICENSE.txt include README.rst recursive-include tests * -include certbot_apache/_internal/centos-options-ssl-apache.conf include certbot_apache/_internal/options-ssl-apache.conf recursive-include certbot_apache/_internal/augeas_lens *.aug global-exclude __pycache__ diff --git a/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf b/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf deleted file mode 100644 index 1a3799628..000000000 --- a/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf +++ /dev/null @@ -1,18 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -SSLEngine on - -# Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 -SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 -SSLHonorCipherOrder off - -SSLOptions +StrictRequire - -# Add vhost name to log entries: -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined -LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common diff --git a/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf b/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf index 60095faa0..1a3799628 100644 --- a/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf +++ b/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf @@ -10,7 +10,6 @@ SSLEngine on SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 SSLHonorCipherOrder off -SSLCompression off SSLOptions +StrictRequire diff --git a/certbot-apache/certbot_apache/_internal/override_centos.py b/certbot-apache/certbot_apache/_internal/override_centos.py index b3576e083..a3ef2d760 100644 --- a/certbot-apache/certbot_apache/_internal/override_centos.py +++ b/certbot-apache/certbot_apache/_internal/override_centos.py @@ -38,7 +38,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator): handle_sites=False, challenge_location="/etc/httpd/conf.d", MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", os.path.join("_internal", "centos-options-ssl-apache.conf")) + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) ) def config_test(self): diff --git a/certbot-apache/certbot_apache/_internal/override_fedora.py b/certbot-apache/certbot_apache/_internal/override_fedora.py index a9607a60f..8197b0dcd 100644 --- a/certbot-apache/certbot_apache/_internal/override_fedora.py +++ b/certbot-apache/certbot_apache/_internal/override_fedora.py @@ -33,7 +33,7 @@ class FedoraConfigurator(configurator.ApacheConfigurator): challenge_location="/etc/httpd/conf.d", MOD_SSL_CONF_SRC=pkg_resources.resource_filename( # TODO: eventually newest version of Fedora will need their own config - "certbot_apache", os.path.join("_internal", "centos-options-ssl-apache.conf")) + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) ) def config_test(self): diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 48a2a554b..86d27143c 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -12,7 +12,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Add directory field to error message when field is missing. * If MD5 hasher is not available, try it in non-security mode (fix for FIPS systems) -- [#1948](https://github.com/certbot/certbot/issues/1948) -* Disable old SSL versions and ciphersuites to follow Mozilla recommendations in Apache. +* Disable old SSL versions and ciphersuites and remove `SSLCompression off` setting to follow Mozilla recommendations in Apache. * Remove ECDHE-RSA-AES128-SHA from NGINX ciphers list now that Windows 2008 R2 and Windows 7 are EOLed * Support for Python 3.4 has been removed. From 35fa4c0457827f08134f7ea96e8ebf17cc120c3d Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 29 Jan 2020 15:30:51 -0800 Subject: [PATCH 060/122] Add space between words. --- certbot-nginx/certbot_nginx/_internal/configurator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py index 70d9d87f8..fdead036a 100644 --- a/certbot-nginx/certbot_nginx/_internal/configurator.py +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -1008,7 +1008,7 @@ class NginxConfigurator(common.Installer): matches = re.findall(r"built with OpenSSL ([^ ]+) ", text) if not matches: logger.warning("NGINX configured with OpenSSL alternatives is not officially" - "supported by Certbot.") + " supported by Certbot.") return "" return matches[0] From 8d9943cb08be2f12bb167d991069a1c128f5b162 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 30 Jan 2020 11:47:48 -0800 Subject: [PATCH 061/122] Update instructions about how to build docs (#7605) --- certbot/docs/contributing.rst | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/certbot/docs/contributing.rst b/certbot/docs/contributing.rst index 4ac266ed8..c13005a9d 100644 --- a/certbot/docs/contributing.rst +++ b/certbot/docs/contributing.rst @@ -524,19 +524,22 @@ during the next release. Updating the documentation ========================== -In order to generate the Sphinx documentation, run the following -commands: +Many of the packages in the Certbot repository have documentation in a +``docs/`` directory. This directory is located under the top level directory +for the package. For instance, Certbot's documentation is under +``certbot/docs``. + +To build the documentation of a package, make sure you have followed the +instructions to set up a `local copy`_ of Certbot including activating the +virtual environment. After that, ``cd`` to the docs directory you want to build +and run the command: .. code-block:: shell - make -C docs clean html man - -This should generate documentation in the ``docs/_build/html`` -directory. - -.. note:: If you skipped the "Getting Started" instructions above, - run ``pip install -e "certbot[docs]"`` to install Certbot's docs extras modules. + make clean html +This would generate the HTML documentation in ``_build/html`` in your current +``docs/`` directory. .. _docker-dev: From 174fa0e05ca3f8e6c8549abc7f3bc4bc8c40123a Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 30 Jan 2020 13:26:39 -0800 Subject: [PATCH 062/122] Turn off Travis notifications in test branches. (#7733) When I want to manually run the full test suite to test something, I've been manually deleting our notification setup from `.travis.yml` to avoid spamming IRC with my personal test failures. This PR sets this behavior up to happen automatically by turning off IRC notifications in test branches. You can see this working by noticing the IRC notification section in the bottom of the config for this PR at https://travis-ci.com/certbot/certbot/builds/146827907/config and the fact that it is absent from a `test-` branch based on this one at https://travis-ci.com/certbot/certbot/jobs/282059094/config. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 70038c150..0ed6b47af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -274,6 +274,7 @@ after_success: '[ "$TOXENV" == "py27-cover" ] && codecov -F linux' notifications: email: false irc: + if: NOT branch =~ ^test-.*$ channels: # This is set to a secure variable to prevent forks from sending # notifications. This value was created by installing From 7d0651c315846e1c48b707ab3e5eb8d059bf3f56 Mon Sep 17 00:00:00 2001 From: OsirisInferi Date: Sun, 2 Feb 2020 21:56:09 +0100 Subject: [PATCH 063/122] Parse `$hostname` in `server_name` --- certbot-nginx/certbot_nginx/_internal/configurator.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py index 70d9d87f8..4e5bfda4c 100644 --- a/certbot-nginx/certbot_nginx/_internal/configurator.py +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -598,6 +598,13 @@ class NginxConfigurator(common.Installer): all_names = set() # type: Set[str] for vhost in self.parser.get_vhosts(): + try: + vhost.names.remove("$hostname") + vhost.names.add(socket.gethostname()) + breakpoint() + except KeyError: + pass + all_names.update(vhost.names) for addr in vhost.addrs: From 05e35ff2e0d46973a5eb1770c18e4e6be1a0ee82 Mon Sep 17 00:00:00 2001 From: OsirisInferi Date: Sun, 2 Feb 2020 21:59:03 +0100 Subject: [PATCH 064/122] Add CHANGELOG entry --- certbot/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 86d27143c..886932738 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -7,6 +7,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added * Added support for Cloudflare's limited-scope API Tokens +* Added support for `$hostname` in nginx `server_name` directive ### Changed From 9b35dbf2be7193e189cc3b74596a4045c5feb399 Mon Sep 17 00:00:00 2001 From: OsirisInferi Date: Sun, 2 Feb 2020 22:31:05 +0100 Subject: [PATCH 065/122] Forgot to remove a `breakpoint()` statement --- certbot-nginx/certbot_nginx/_internal/configurator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py index 4e5bfda4c..66fbf5c61 100644 --- a/certbot-nginx/certbot_nginx/_internal/configurator.py +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -601,7 +601,6 @@ class NginxConfigurator(common.Installer): try: vhost.names.remove("$hostname") vhost.names.add(socket.gethostname()) - breakpoint() except KeyError: pass From 86926dff9293ab24d4f025ccf70c47619750d897 Mon Sep 17 00:00:00 2001 From: OsirisInferi Date: Tue, 4 Feb 2020 19:27:27 +0100 Subject: [PATCH 066/122] Use unrestrictive umask for challenge directory --- certbot-apache/certbot_apache/_internal/http_01.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/certbot-apache/certbot_apache/_internal/http_01.py b/certbot-apache/certbot_apache/_internal/http_01.py index c34abc2b4..53ccd2bc7 100644 --- a/certbot-apache/certbot_apache/_internal/http_01.py +++ b/certbot-apache/certbot_apache/_internal/http_01.py @@ -168,7 +168,9 @@ class ApacheHttp01(common.ChallengePerformer): def _set_up_challenges(self): if not os.path.isdir(self.challenge_dir): + old_umask = os.umask(0o022) filesystem.makedirs(self.challenge_dir, 0o755) + os.umask(old_umask) responses = [] for achall in self.achalls: From 601a114d1ba6030f3f765ff86bb39658172e0a75 Mon Sep 17 00:00:00 2001 From: OsirisInferi Date: Tue, 4 Feb 2020 19:47:27 +0100 Subject: [PATCH 067/122] Update changelog --- certbot/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 86d27143c..01cd3d402 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -19,6 +19,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed * Fix collections.abc imports for Python 3.9. +* Fix Apache plugin to use less restrictive umask for making the challenge directory when a restrictive umask was set when certbot was started. More details about these changes can be found on our GitHub repo. From 6c5959d892339d8921aa302dffddd70aa4d3caad Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 4 Feb 2020 13:46:57 -0800 Subject: [PATCH 068/122] Update changelog for 1.2.0 release --- certbot/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 86d27143c..a14cea76c 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 1.2.0 - master +## 1.2.0 - 2020-02-04 ### Added From 3907b53b4b94e3519c6b3a83ca3396087ec77f33 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 4 Feb 2020 14:01:02 -0800 Subject: [PATCH 069/122] Release 1.2.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 201 +++++++++++------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/certbot/__init__.py | 2 +- certbot/docs/cli-help.txt | 2 +- letsencrypt-auto | 201 +++++++++++------- letsencrypt-auto-source/certbot-auto.asc | 16 +- letsencrypt-auto-source/letsencrypt-auto | 26 +-- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +-- 26 files changed, 291 insertions(+), 217 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 458ca083d..58d2c12ce 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 599925929..8a887b444 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index 2d3f4cfef..cea58e2cb 100755 --- a/certbot-auto +++ b/certbot-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="1.1.0" +LE_AUTO_VERSION="1.2.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1274,11 +1274,11 @@ if [ "$1" = "--le-auto-phase2" ]; then # pip install hashin # hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` -ConfigArgParse==0.14.0 \ - --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 -certifi==2019.9.11 \ - --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ - --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef +ConfigArgParse==1.0 \ + --hash=sha256:bf378245bc9cdc403a527e5b7406b991680c2a530e7e81af747880b54eb57133 +certifi==2019.11.28 \ + --hash=sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3 \ + --hash=sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f cffi==1.13.2 \ --hash=sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42 \ --hash=sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04 \ @@ -1351,8 +1351,6 @@ enum34==1.1.6 \ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -future==0.18.2 \ - --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c @@ -1365,40 +1363,40 @@ josepy==1.2.0 \ mock==1.3.0 \ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb -parsedatetime==2.4 \ - --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ - --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.4.3 \ - --hash=sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8 \ - --hash=sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9 -pyOpenSSL==19.0.0 \ - --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ - --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 +parsedatetime==2.5 \ + --hash=sha256:3b835fc54e472c17ef447be37458b400e3fefdf14bb1ffdedb5d2c853acf4ba1 \ + --hash=sha256:d2e9ddb1e463de871d32088a3f3cea3dc8282b1b2800e081bd0ef86900451667 +pbr==5.4.4 \ + --hash=sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b \ + --hash=sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488 +pyOpenSSL==19.1.0 \ + --hash=sha256:621880965a720b8ece2f1b2f54ea2071966ab00e2970ad2ce11d596102063504 \ + --hash=sha256:9a24494b2602aaf402be5c9e30a0b82d4a5c67528fe8fb475e3f3bc00dd69507 pyRFC3339==1.1 \ --hash=sha256:67196cb83b470709c580bb4738b83165e67c6cc60e1f2e4f286cfcb402a926f4 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.4.5 \ - --hash=sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f \ - --hash=sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a +pyparsing==2.4.6 \ + --hash=sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f \ + --hash=sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 pytz==2019.3 \ --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be -requests==2.21.0 \ - --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ - --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b +requests==2.22.0 \ + --hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 \ + --hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31 requests-toolbelt==0.9.1 \ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 -six==1.13.0 \ - --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ - --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 -urllib3==1.24.3 \ - --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ - --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb +six==1.14.0 \ + --hash=sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a \ + --hash=sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c +urllib3==1.25.8 \ + --hash=sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc \ + --hash=sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc zope.component==4.6 \ --hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6 zope.deferredimport==4.3.1 \ @@ -1410,47 +1408,86 @@ zope.deprecation==4.4.0 \ zope.event==4.4 \ --hash=sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf \ --hash=sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7 -zope.hookable==4.2.0 \ - --hash=sha256:22886e421234e7e8cedc21202e1d0ab59960e40a47dd7240e9659a2d82c51370 \ - --hash=sha256:39912f446e45b4e1f1951b5ffa2d5c8b074d25727ec51855ae9eab5408f105ab \ - --hash=sha256:3adb7ea0871dbc56b78f62c4f5c024851fc74299f4f2a95f913025b076cde220 \ - --hash=sha256:3d7c4b96341c02553d8b8d71065a9366ef67e6c6feca714f269894646bb8268b \ - --hash=sha256:4e826a11a529ed0464ffcecf34b0b7bd1b4928dd5848c5c61bedd7833e8f4801 \ - --hash=sha256:700d68cc30728de1c4c62088a981c6daeaefdf20a0d81995d2c0b7f442c5f88c \ - --hash=sha256:77c82a430cedfbf508d1aa406b2f437363c24fa90c73f577ead0fb5295749b83 \ - --hash=sha256:c1df3929a3666fc5a0c80d60a0c1e6f6ef97c7f6ed2f1b7cf49f3e6f3d4dde15 \ - --hash=sha256:dba8b2dd2cd41cb5f37bfa3f3d82721b8ae10e492944e48ddd90a439227f2893 \ - --hash=sha256:f492540305b15b5591bd7195d61f28946bb071de071cee5d68b6b8414da90fd2 -zope.interface==4.6.0 \ - --hash=sha256:086707e0f413ff8800d9c4bc26e174f7ee4c9c8b0302fbad68d083071822316c \ - --hash=sha256:1157b1ec2a1f5bf45668421e3955c60c610e31913cc695b407a574efdbae1f7b \ - --hash=sha256:11ebddf765bff3bbe8dbce10c86884d87f90ed66ee410a7e6c392086e2c63d02 \ - --hash=sha256:14b242d53f6f35c2d07aa2c0e13ccb710392bcd203e1b82a1828d216f6f6b11f \ - --hash=sha256:1b3d0dcabc7c90b470e59e38a9acaa361be43b3a6ea644c0063951964717f0e5 \ - --hash=sha256:20a12ab46a7e72b89ce0671e7d7a6c3c1ca2c2766ac98112f78c5bddaa6e4375 \ - --hash=sha256:298f82c0ab1b182bd1f34f347ea97dde0fffb9ecf850ecf7f8904b8442a07487 \ - --hash=sha256:2f6175722da6f23dbfc76c26c241b67b020e1e83ec7fe93c9e5d3dd18667ada2 \ - --hash=sha256:3b877de633a0f6d81b600624ff9137312d8b1d0f517064dfc39999352ab659f0 \ - --hash=sha256:4265681e77f5ac5bac0905812b828c9fe1ce80c6f3e3f8574acfb5643aeabc5b \ - --hash=sha256:550695c4e7313555549aa1cdb978dc9413d61307531f123558e438871a883d63 \ - --hash=sha256:5f4d42baed3a14c290a078e2696c5f565501abde1b2f3f1a1c0a94fbf6fbcc39 \ - --hash=sha256:62dd71dbed8cc6a18379700701d959307823b3b2451bdc018594c48956ace745 \ - --hash=sha256:7040547e5b882349c0a2cc9b50674b1745db551f330746af434aad4f09fba2cc \ - --hash=sha256:7e099fde2cce8b29434684f82977db4e24f0efa8b0508179fce1602d103296a2 \ - --hash=sha256:7e5c9a5012b2b33e87980cee7d1c82412b2ebabcb5862d53413ba1a2cfde23aa \ - --hash=sha256:81295629128f929e73be4ccfdd943a0906e5fe3cdb0d43ff1e5144d16fbb52b1 \ - --hash=sha256:95cc574b0b83b85be9917d37cd2fad0ce5a0d21b024e1a5804d044aabea636fc \ - --hash=sha256:968d5c5702da15c5bf8e4a6e4b67a4d92164e334e9c0b6acf080106678230b98 \ - --hash=sha256:9e998ba87df77a85c7bed53240a7257afe51a07ee6bc3445a0bf841886da0b97 \ - --hash=sha256:a0c39e2535a7e9c195af956610dba5a1073071d2d85e9d2e5d789463f63e52ab \ - --hash=sha256:a15e75d284178afe529a536b0e8b28b7e107ef39626a7809b4ee64ff3abc9127 \ - --hash=sha256:a6a6ff82f5f9b9702478035d8f6fb6903885653bff7ec3a1e011edc9b1a7168d \ - --hash=sha256:b639f72b95389620c1f881d94739c614d385406ab1d6926a9ffe1c8abbea23fe \ - --hash=sha256:bad44274b151d46619a7567010f7cde23a908c6faa84b97598fd2f474a0c6891 \ - --hash=sha256:bbcef00d09a30948756c5968863316c949d9cedbc7aabac5e8f0ffbdb632e5f1 \ - --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ - --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ - --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 +zope.hookable==5.0.0 \ + --hash=sha256:0992a0dd692003c09fb958e1480cebd1a28f2ef32faa4857d864f3ca8e9d6952 \ + --hash=sha256:0f325838dbac827a1e2ed5d482c1f2656b6844dc96aa098f7727e76395fcd694 \ + --hash=sha256:22a317ba00f61bac99eac1a5e330be7cb8c316275a21269ec58aa396b602af0c \ + --hash=sha256:25531cb5e7b35e8a6d1d6eddef624b9a22ce5dcf8f4448ef0f165acfa8c3fc21 \ + --hash=sha256:30890892652766fc80d11f078aca9a5b8150bef6b88aba23799581a53515c404 \ + --hash=sha256:342d682d93937e5b8c232baffb32a87d5eee605d44f74566657c64a239b7f342 \ + --hash=sha256:46b2fddf1f5aeb526e02b91f7e62afbb9fff4ffd7aafc97cdb00a0d717641567 \ + --hash=sha256:523318ff96df9b8d378d997c00c5d4cbfbff68dc48ff5ee5addabdb697d27528 \ + --hash=sha256:53aa02eb8921d4e667c69d76adeed8fe426e43870c101cb08dcd2f3468aff742 \ + --hash=sha256:62e79e8fdde087cb20822d7874758f5acbedbffaf3c0fbe06309eb8a41ee4e06 \ + --hash=sha256:74bf2f757f7385b56dc3548adae508d8b3ef952d600b4b12b88f7d1706b05dcc \ + --hash=sha256:751ee9d89eb96e00c1d7048da9725ce392a708ed43406416dc5ed61e4d199764 \ + --hash=sha256:7b83bc341e682771fe810b360cd5d9c886a948976aea4b979ff214e10b8b523b \ + --hash=sha256:81eeeb27dbb0ddaed8070daee529f0d1bfe4f74c7351cce2aaca3ea287c4cc32 \ + --hash=sha256:856509191e16930335af4d773c0fc31a17bae8991eb6f167a09d5eddf25b56cc \ + --hash=sha256:8853e81fd07b18fa9193b19e070dc0557848d9945b1d2dac3b7782543458c87d \ + --hash=sha256:94506a732da2832029aecdfe6ea07eb1b70ee06d802fff34e1b3618fe7cdf026 \ + --hash=sha256:95ad874a8cc94e786969215d660143817f745225579bfe318c4676e218d3147c \ + --hash=sha256:9758ec9174966ffe5c499b6c3d149f80aa0a9238020006a2b87c6af5963fcf48 \ + --hash=sha256:a169823e331da939aa7178fc152e65699aeb78957e46c6f80ccb50ee4c3616c2 \ + --hash=sha256:a67878a798f6ca292729a28c2226592b3d000dc6ee7825d31887b553686c7ac7 \ + --hash=sha256:a9a6d9eb2319a09905670810e2de971d6c49013843700b4975e2fc0afe96c8db \ + --hash=sha256:b3e118b58a3d2301960e6f5f25736d92f6b9f861728d3b8c26d69f54d8a157d2 \ + --hash=sha256:ca6705c2a1fb5059a4efbe9f5426be4cdf71b3c9564816916fc7aa7902f19ede \ + --hash=sha256:cf711527c9d4ae72085f137caffb4be74fc007ffb17cd103628c7d5ba17e205f \ + --hash=sha256:d087602a6845ebe9d5a1c5a949fedde2c45f372d77fbce4f7fe44b68b28a1d03 \ + --hash=sha256:d1080e1074ddf75ad6662a9b34626650759c19a9093e1a32a503d37e48da135b \ + --hash=sha256:db9c60368aff2b7e6c47115f3ad9bd6e96aa298b12ed5f8cb13f5673b30be565 \ + --hash=sha256:dbeb127a04473f5a989169eb400b67beb921c749599b77650941c21fe39cb8d9 \ + --hash=sha256:dca336ca3682d869d291d7cd18284f6ff6876e4244eb1821430323056b000e2c \ + --hash=sha256:dd69a9be95346d10c853b6233fcafe3c0315b89424b378f2ad45170d8e161568 \ + --hash=sha256:dd79f8fae5894f1ee0a0042214685f2d039341250c994b825c10a4cd075d80f6 \ + --hash=sha256:e647d850aa1286d98910133cee12bd87c354f7b7bb3f3cd816a62ba7fa2f7007 \ + --hash=sha256:f37a210b5c04b2d4e4bac494ab15b70196f219a1e1649ddca78560757d4278fb \ + --hash=sha256:f67820b6d33a705dc3c1c457156e51686f7b350ff57f2112e1a9a4dad38ec268 \ + --hash=sha256:f68969978ccf0e6123902f7365aae5b7a9e99169d4b9105c47cf28e788116894 \ + --hash=sha256:f717a0b34460ae1ac0064e91b267c0588ac2c098ffd695992e72cd5462d97a67 \ + --hash=sha256:f9d58ccec8684ca276d5a4e7b0dfacca028336300a8f715d616d9f0ce9ae8096 \ + --hash=sha256:fcc3513a54e656067cbf7b98bab0d6b9534b9eabc666d1f78aad6acdf0962736 +zope.interface==4.7.1 \ + --hash=sha256:048b16ac882a05bc7ef534e8b9f15c9d7a6c190e24e8938a19b7617af4ed854a \ + --hash=sha256:05816cf8e7407cf62f2ec95c0a5d69ec4fa5741d9ccd10db9f21691916a9a098 \ + --hash=sha256:065d6a1ac89d35445168813bed45048ed4e67a4cdfc5a68fdb626a770378869f \ + --hash=sha256:14157421f4121a57625002cc4f48ac7521ea238d697c4a4459a884b62132b977 \ + --hash=sha256:18dc895945694f397a0be86be760ff664b790f95d8e7752d5bab80284ff9105d \ + --hash=sha256:1962c9f838bd6ae4075d0014f72697510daefc7e1c7e48b2607df0b6e157989c \ + --hash=sha256:1a67408cacd198c7e6274a19920bb4568d56459e659e23c4915528686ac1763a \ + --hash=sha256:21bf781076dd616bd07cf0223f79d61ab4f45176076f90bc2890e18c48195da4 \ + --hash=sha256:21c0a5d98650aebb84efa16ce2c8df1a46bdc4fe8a9e33237d0ca0b23f416ead \ + --hash=sha256:23cfeea25d1e42ff3bf4f9a0c31e9d5950aa9e7c4b12f0c4bd086f378f7b7a71 \ + --hash=sha256:24b6fce1fb71abf9f4093e3259084efcc0ef479f89356757780685bd2b06ef37 \ + --hash=sha256:24f84ce24eb6b5fcdcb38ad9761524f1ae96f7126abb5e597f8a3973d9921409 \ + --hash=sha256:25e0ef4a824017809d6d8b0ce4ab3288594ba283e4d4f94d8cfb81d73ed65114 \ + --hash=sha256:2e8fdd625e9aba31228e7ddbc36bad5c38dc3ee99a86aa420f89a290bd987ce9 \ + --hash=sha256:2f3bc2f49b67b1bea82b942d25bc958d4f4ea6709b411cb2b6b9718adf7914ce \ + --hash=sha256:35d24be9d04d50da3a6f4d61de028c1dd087045385a0ff374d93ef85af61b584 \ + --hash=sha256:35dbe4e8c73003dff40dfaeb15902910a4360699375e7b47d3c909a83ff27cd0 \ + --hash=sha256:3dfce831b824ab5cf446ed0c350b793ac6fa5fe33b984305cb4c966a86a8fb79 \ + --hash=sha256:3f7866365df5a36a7b8de8056cd1c605648f56f9a226d918ed84c85d25e8d55f \ + --hash=sha256:455cc8c01de3bac6f9c223967cea41f4449f58b4c2e724ec8177382ddd183ab4 \ + --hash=sha256:4bb937e998be9d5e345f486693e477ba79e4344674484001a0b646be1d530487 \ + --hash=sha256:52303a20902ca0888dfb83230ca3ee6fbe63c0ad1dd60aa0bba7958ccff454d8 \ + --hash=sha256:6e0a897d4e09859cc80c6a16a29697406ead752292ace17f1805126a4f63c838 \ + --hash=sha256:6e1816e7c10966330d77af45f77501f9a68818c065dec0ad11d22b50a0e212e7 \ + --hash=sha256:73b5921c5c6ce3358c836461b5470bf675601c96d5e5d8f2a446951470614f67 \ + --hash=sha256:8093cd45cdb5f6c8591cfd1af03d32b32965b0f79b94684cd0c9afdf841982bb \ + --hash=sha256:864b4a94b60db301899cf373579fd9ef92edddbf0fb2cd5ae99f53ef423ccc56 \ + --hash=sha256:8a27b4d3ea9c6d086ce8e7cdb3e8d319b6752e2a03238a388ccc83ccbe165f50 \ + --hash=sha256:91b847969d4784abd855165a2d163f72ac1e58e6dce09a5e46c20e58f19cc96d \ + --hash=sha256:b47b1028be4758c3167e474884ccc079b94835f058984b15c145966c4df64d27 \ + --hash=sha256:b68814a322835d8ad671b7acc23a3b2acecba527bb14f4b53fc925f8a27e44d8 \ + --hash=sha256:bcb50a032c3b6ec7fb281b3a83d2b31ab5246c5b119588725b1350d3a1d9f6a3 \ + --hash=sha256:c56db7d10b25ce8918b6aec6b08ac401842b47e6c136773bfb3b590753f7fb67 \ + --hash=sha256:c94b77a13d4f47883e4f97f9fa00f5feadd38af3e6b3c7be45cfdb0a14c7149b \ + --hash=sha256:db381f6fdaef483ad435f778086ccc4890120aff8df2ba5cfeeac24d280b3145 \ + --hash=sha256:e6487d01c8b7ed86af30ea141fcc4f93f8a7dde26f94177c1ad637c353bd5c07 \ + --hash=sha256:e86923fa728dfba39c5bb6046a450bd4eec8ad949ac404eca728cfce320d1732 \ + --hash=sha256:f6ca36dc1e9eeb46d779869c60001b3065fb670b5775c51421c099ea2a77c3c9 \ + --hash=sha256:fb62f2cbe790a50d95593fb40e8cca261c31a2f5637455ea39440d6457c2ba25 zope.proxy==4.3.3 \ --hash=sha256:04646ac04ffa9c8e32fb2b5c3cd42995b2548ea14251f3c21ca704afae88e42c \ --hash=sha256:07b6bceea232559d24358832f1cd2ed344bbf05ca83855a5b9698b5f23c5ed60 \ @@ -1503,18 +1540,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.1.0 \ - --hash=sha256:66a5cab9267349941604c2c98082bfef85877653c023fc324b1c3869fb16add6 \ - --hash=sha256:46e93661a0db53f416c0f5476d8d2e62bc7259b7660dd983453b85df9ef6e8b8 -acme==1.1.0 \ - --hash=sha256:11b9beba706fb8f652c8910d46dd1939d670cac8169f3c66c18c080ed3353e71 \ - --hash=sha256:c305a20eeb9cb02240347703d497891c13d43a47c794fa100d4dbb479a5370d9 -certbot-apache==1.1.0 \ - --hash=sha256:9c847ff223c2e465e241c78d22f97cee77d5e551df608bed06c55f8627f4cbd2 \ - --hash=sha256:05e84dfe96b72582cde97c490977d8e2d33d440c927a320debb4cf287f6fadcc -certbot-nginx==1.1.0 \ - --hash=sha256:bf06fa2f5059f0fdb7d352c8739e1ed0830db4f0d89e812dab4f081bda6ec7d6 \ - --hash=sha256:0a80ecbd2a30f3757c7652cabfff854ca07873b1cf02ebbe1892786c3b3a5874 +certbot==1.2.0 \ + --hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \ + --hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c +acme==1.2.0 \ + --hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \ + --hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab +certbot-apache==1.2.0 \ + --hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \ + --hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3 +certbot-nginx==1.2.0 \ + --hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \ + --hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index c3443e35e..891bd8e1c 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -3,7 +3,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.2.0.dev0' +version = '1.2.0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index a3e64f07d..706113d45 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index a7a0072c4..c290c5599 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index fe5243bc5..fac989ae6 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index e31bc5949..fe29a7137 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index fceb0b518..a36c74f9d 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 974b17d44..1ef15c85f 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 7b5583307..f2b99ba15 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 37b5a4921..eead76cc8 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 4d60ca520..f8484e9f5 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 81324dbc2..7a4e21a8e 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index ee7d9fc69..dbecc3f7f 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index aa2509727..8f2f09f27 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index df43d90a9..1d30e5b40 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 4d35dc7ac..d0403ac87 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index aad736da5..dc50f5cb3 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/certbot/__init__.py b/certbot/certbot/__init__.py index caae1a041..ab972d9e3 100644 --- a/certbot/certbot/__init__.py +++ b/certbot/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '1.2.0.dev0' +__version__ = '1.2.0' diff --git a/certbot/docs/cli-help.txt b/certbot/docs/cli-help.txt index 51967eb76..ff49609c4 100644 --- a/certbot/docs/cli-help.txt +++ b/certbot/docs/cli-help.txt @@ -113,7 +113,7 @@ optional arguments: case, and to know when to deprecate support for past Python versions and flags. If you wish to hide this information from the Let's Encrypt server, set this to - "". (default: CertbotACMEClient/1.1.0 (certbot(-auto); + "". (default: CertbotACMEClient/1.2.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the user agent are: --duplicate, diff --git a/letsencrypt-auto b/letsencrypt-auto index 2d3f4cfef..cea58e2cb 100755 --- a/letsencrypt-auto +++ b/letsencrypt-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="1.1.0" +LE_AUTO_VERSION="1.2.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1274,11 +1274,11 @@ if [ "$1" = "--le-auto-phase2" ]; then # pip install hashin # hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` -ConfigArgParse==0.14.0 \ - --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 -certifi==2019.9.11 \ - --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ - --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef +ConfigArgParse==1.0 \ + --hash=sha256:bf378245bc9cdc403a527e5b7406b991680c2a530e7e81af747880b54eb57133 +certifi==2019.11.28 \ + --hash=sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3 \ + --hash=sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f cffi==1.13.2 \ --hash=sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42 \ --hash=sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04 \ @@ -1351,8 +1351,6 @@ enum34==1.1.6 \ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -future==0.18.2 \ - --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c @@ -1365,40 +1363,40 @@ josepy==1.2.0 \ mock==1.3.0 \ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb -parsedatetime==2.4 \ - --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ - --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.4.3 \ - --hash=sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8 \ - --hash=sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9 -pyOpenSSL==19.0.0 \ - --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ - --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 +parsedatetime==2.5 \ + --hash=sha256:3b835fc54e472c17ef447be37458b400e3fefdf14bb1ffdedb5d2c853acf4ba1 \ + --hash=sha256:d2e9ddb1e463de871d32088a3f3cea3dc8282b1b2800e081bd0ef86900451667 +pbr==5.4.4 \ + --hash=sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b \ + --hash=sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488 +pyOpenSSL==19.1.0 \ + --hash=sha256:621880965a720b8ece2f1b2f54ea2071966ab00e2970ad2ce11d596102063504 \ + --hash=sha256:9a24494b2602aaf402be5c9e30a0b82d4a5c67528fe8fb475e3f3bc00dd69507 pyRFC3339==1.1 \ --hash=sha256:67196cb83b470709c580bb4738b83165e67c6cc60e1f2e4f286cfcb402a926f4 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.4.5 \ - --hash=sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f \ - --hash=sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a +pyparsing==2.4.6 \ + --hash=sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f \ + --hash=sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 pytz==2019.3 \ --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be -requests==2.21.0 \ - --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ - --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b +requests==2.22.0 \ + --hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 \ + --hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31 requests-toolbelt==0.9.1 \ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 -six==1.13.0 \ - --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ - --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 -urllib3==1.24.3 \ - --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ - --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb +six==1.14.0 \ + --hash=sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a \ + --hash=sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c +urllib3==1.25.8 \ + --hash=sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc \ + --hash=sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc zope.component==4.6 \ --hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6 zope.deferredimport==4.3.1 \ @@ -1410,47 +1408,86 @@ zope.deprecation==4.4.0 \ zope.event==4.4 \ --hash=sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf \ --hash=sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7 -zope.hookable==4.2.0 \ - --hash=sha256:22886e421234e7e8cedc21202e1d0ab59960e40a47dd7240e9659a2d82c51370 \ - --hash=sha256:39912f446e45b4e1f1951b5ffa2d5c8b074d25727ec51855ae9eab5408f105ab \ - --hash=sha256:3adb7ea0871dbc56b78f62c4f5c024851fc74299f4f2a95f913025b076cde220 \ - --hash=sha256:3d7c4b96341c02553d8b8d71065a9366ef67e6c6feca714f269894646bb8268b \ - --hash=sha256:4e826a11a529ed0464ffcecf34b0b7bd1b4928dd5848c5c61bedd7833e8f4801 \ - --hash=sha256:700d68cc30728de1c4c62088a981c6daeaefdf20a0d81995d2c0b7f442c5f88c \ - --hash=sha256:77c82a430cedfbf508d1aa406b2f437363c24fa90c73f577ead0fb5295749b83 \ - --hash=sha256:c1df3929a3666fc5a0c80d60a0c1e6f6ef97c7f6ed2f1b7cf49f3e6f3d4dde15 \ - --hash=sha256:dba8b2dd2cd41cb5f37bfa3f3d82721b8ae10e492944e48ddd90a439227f2893 \ - --hash=sha256:f492540305b15b5591bd7195d61f28946bb071de071cee5d68b6b8414da90fd2 -zope.interface==4.6.0 \ - --hash=sha256:086707e0f413ff8800d9c4bc26e174f7ee4c9c8b0302fbad68d083071822316c \ - --hash=sha256:1157b1ec2a1f5bf45668421e3955c60c610e31913cc695b407a574efdbae1f7b \ - --hash=sha256:11ebddf765bff3bbe8dbce10c86884d87f90ed66ee410a7e6c392086e2c63d02 \ - --hash=sha256:14b242d53f6f35c2d07aa2c0e13ccb710392bcd203e1b82a1828d216f6f6b11f \ - --hash=sha256:1b3d0dcabc7c90b470e59e38a9acaa361be43b3a6ea644c0063951964717f0e5 \ - --hash=sha256:20a12ab46a7e72b89ce0671e7d7a6c3c1ca2c2766ac98112f78c5bddaa6e4375 \ - --hash=sha256:298f82c0ab1b182bd1f34f347ea97dde0fffb9ecf850ecf7f8904b8442a07487 \ - --hash=sha256:2f6175722da6f23dbfc76c26c241b67b020e1e83ec7fe93c9e5d3dd18667ada2 \ - --hash=sha256:3b877de633a0f6d81b600624ff9137312d8b1d0f517064dfc39999352ab659f0 \ - --hash=sha256:4265681e77f5ac5bac0905812b828c9fe1ce80c6f3e3f8574acfb5643aeabc5b \ - --hash=sha256:550695c4e7313555549aa1cdb978dc9413d61307531f123558e438871a883d63 \ - --hash=sha256:5f4d42baed3a14c290a078e2696c5f565501abde1b2f3f1a1c0a94fbf6fbcc39 \ - --hash=sha256:62dd71dbed8cc6a18379700701d959307823b3b2451bdc018594c48956ace745 \ - --hash=sha256:7040547e5b882349c0a2cc9b50674b1745db551f330746af434aad4f09fba2cc \ - --hash=sha256:7e099fde2cce8b29434684f82977db4e24f0efa8b0508179fce1602d103296a2 \ - --hash=sha256:7e5c9a5012b2b33e87980cee7d1c82412b2ebabcb5862d53413ba1a2cfde23aa \ - --hash=sha256:81295629128f929e73be4ccfdd943a0906e5fe3cdb0d43ff1e5144d16fbb52b1 \ - --hash=sha256:95cc574b0b83b85be9917d37cd2fad0ce5a0d21b024e1a5804d044aabea636fc \ - --hash=sha256:968d5c5702da15c5bf8e4a6e4b67a4d92164e334e9c0b6acf080106678230b98 \ - --hash=sha256:9e998ba87df77a85c7bed53240a7257afe51a07ee6bc3445a0bf841886da0b97 \ - --hash=sha256:a0c39e2535a7e9c195af956610dba5a1073071d2d85e9d2e5d789463f63e52ab \ - --hash=sha256:a15e75d284178afe529a536b0e8b28b7e107ef39626a7809b4ee64ff3abc9127 \ - --hash=sha256:a6a6ff82f5f9b9702478035d8f6fb6903885653bff7ec3a1e011edc9b1a7168d \ - --hash=sha256:b639f72b95389620c1f881d94739c614d385406ab1d6926a9ffe1c8abbea23fe \ - --hash=sha256:bad44274b151d46619a7567010f7cde23a908c6faa84b97598fd2f474a0c6891 \ - --hash=sha256:bbcef00d09a30948756c5968863316c949d9cedbc7aabac5e8f0ffbdb632e5f1 \ - --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ - --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ - --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 +zope.hookable==5.0.0 \ + --hash=sha256:0992a0dd692003c09fb958e1480cebd1a28f2ef32faa4857d864f3ca8e9d6952 \ + --hash=sha256:0f325838dbac827a1e2ed5d482c1f2656b6844dc96aa098f7727e76395fcd694 \ + --hash=sha256:22a317ba00f61bac99eac1a5e330be7cb8c316275a21269ec58aa396b602af0c \ + --hash=sha256:25531cb5e7b35e8a6d1d6eddef624b9a22ce5dcf8f4448ef0f165acfa8c3fc21 \ + --hash=sha256:30890892652766fc80d11f078aca9a5b8150bef6b88aba23799581a53515c404 \ + --hash=sha256:342d682d93937e5b8c232baffb32a87d5eee605d44f74566657c64a239b7f342 \ + --hash=sha256:46b2fddf1f5aeb526e02b91f7e62afbb9fff4ffd7aafc97cdb00a0d717641567 \ + --hash=sha256:523318ff96df9b8d378d997c00c5d4cbfbff68dc48ff5ee5addabdb697d27528 \ + --hash=sha256:53aa02eb8921d4e667c69d76adeed8fe426e43870c101cb08dcd2f3468aff742 \ + --hash=sha256:62e79e8fdde087cb20822d7874758f5acbedbffaf3c0fbe06309eb8a41ee4e06 \ + --hash=sha256:74bf2f757f7385b56dc3548adae508d8b3ef952d600b4b12b88f7d1706b05dcc \ + --hash=sha256:751ee9d89eb96e00c1d7048da9725ce392a708ed43406416dc5ed61e4d199764 \ + --hash=sha256:7b83bc341e682771fe810b360cd5d9c886a948976aea4b979ff214e10b8b523b \ + --hash=sha256:81eeeb27dbb0ddaed8070daee529f0d1bfe4f74c7351cce2aaca3ea287c4cc32 \ + --hash=sha256:856509191e16930335af4d773c0fc31a17bae8991eb6f167a09d5eddf25b56cc \ + --hash=sha256:8853e81fd07b18fa9193b19e070dc0557848d9945b1d2dac3b7782543458c87d \ + --hash=sha256:94506a732da2832029aecdfe6ea07eb1b70ee06d802fff34e1b3618fe7cdf026 \ + --hash=sha256:95ad874a8cc94e786969215d660143817f745225579bfe318c4676e218d3147c \ + --hash=sha256:9758ec9174966ffe5c499b6c3d149f80aa0a9238020006a2b87c6af5963fcf48 \ + --hash=sha256:a169823e331da939aa7178fc152e65699aeb78957e46c6f80ccb50ee4c3616c2 \ + --hash=sha256:a67878a798f6ca292729a28c2226592b3d000dc6ee7825d31887b553686c7ac7 \ + --hash=sha256:a9a6d9eb2319a09905670810e2de971d6c49013843700b4975e2fc0afe96c8db \ + --hash=sha256:b3e118b58a3d2301960e6f5f25736d92f6b9f861728d3b8c26d69f54d8a157d2 \ + --hash=sha256:ca6705c2a1fb5059a4efbe9f5426be4cdf71b3c9564816916fc7aa7902f19ede \ + --hash=sha256:cf711527c9d4ae72085f137caffb4be74fc007ffb17cd103628c7d5ba17e205f \ + --hash=sha256:d087602a6845ebe9d5a1c5a949fedde2c45f372d77fbce4f7fe44b68b28a1d03 \ + --hash=sha256:d1080e1074ddf75ad6662a9b34626650759c19a9093e1a32a503d37e48da135b \ + --hash=sha256:db9c60368aff2b7e6c47115f3ad9bd6e96aa298b12ed5f8cb13f5673b30be565 \ + --hash=sha256:dbeb127a04473f5a989169eb400b67beb921c749599b77650941c21fe39cb8d9 \ + --hash=sha256:dca336ca3682d869d291d7cd18284f6ff6876e4244eb1821430323056b000e2c \ + --hash=sha256:dd69a9be95346d10c853b6233fcafe3c0315b89424b378f2ad45170d8e161568 \ + --hash=sha256:dd79f8fae5894f1ee0a0042214685f2d039341250c994b825c10a4cd075d80f6 \ + --hash=sha256:e647d850aa1286d98910133cee12bd87c354f7b7bb3f3cd816a62ba7fa2f7007 \ + --hash=sha256:f37a210b5c04b2d4e4bac494ab15b70196f219a1e1649ddca78560757d4278fb \ + --hash=sha256:f67820b6d33a705dc3c1c457156e51686f7b350ff57f2112e1a9a4dad38ec268 \ + --hash=sha256:f68969978ccf0e6123902f7365aae5b7a9e99169d4b9105c47cf28e788116894 \ + --hash=sha256:f717a0b34460ae1ac0064e91b267c0588ac2c098ffd695992e72cd5462d97a67 \ + --hash=sha256:f9d58ccec8684ca276d5a4e7b0dfacca028336300a8f715d616d9f0ce9ae8096 \ + --hash=sha256:fcc3513a54e656067cbf7b98bab0d6b9534b9eabc666d1f78aad6acdf0962736 +zope.interface==4.7.1 \ + --hash=sha256:048b16ac882a05bc7ef534e8b9f15c9d7a6c190e24e8938a19b7617af4ed854a \ + --hash=sha256:05816cf8e7407cf62f2ec95c0a5d69ec4fa5741d9ccd10db9f21691916a9a098 \ + --hash=sha256:065d6a1ac89d35445168813bed45048ed4e67a4cdfc5a68fdb626a770378869f \ + --hash=sha256:14157421f4121a57625002cc4f48ac7521ea238d697c4a4459a884b62132b977 \ + --hash=sha256:18dc895945694f397a0be86be760ff664b790f95d8e7752d5bab80284ff9105d \ + --hash=sha256:1962c9f838bd6ae4075d0014f72697510daefc7e1c7e48b2607df0b6e157989c \ + --hash=sha256:1a67408cacd198c7e6274a19920bb4568d56459e659e23c4915528686ac1763a \ + --hash=sha256:21bf781076dd616bd07cf0223f79d61ab4f45176076f90bc2890e18c48195da4 \ + --hash=sha256:21c0a5d98650aebb84efa16ce2c8df1a46bdc4fe8a9e33237d0ca0b23f416ead \ + --hash=sha256:23cfeea25d1e42ff3bf4f9a0c31e9d5950aa9e7c4b12f0c4bd086f378f7b7a71 \ + --hash=sha256:24b6fce1fb71abf9f4093e3259084efcc0ef479f89356757780685bd2b06ef37 \ + --hash=sha256:24f84ce24eb6b5fcdcb38ad9761524f1ae96f7126abb5e597f8a3973d9921409 \ + --hash=sha256:25e0ef4a824017809d6d8b0ce4ab3288594ba283e4d4f94d8cfb81d73ed65114 \ + --hash=sha256:2e8fdd625e9aba31228e7ddbc36bad5c38dc3ee99a86aa420f89a290bd987ce9 \ + --hash=sha256:2f3bc2f49b67b1bea82b942d25bc958d4f4ea6709b411cb2b6b9718adf7914ce \ + --hash=sha256:35d24be9d04d50da3a6f4d61de028c1dd087045385a0ff374d93ef85af61b584 \ + --hash=sha256:35dbe4e8c73003dff40dfaeb15902910a4360699375e7b47d3c909a83ff27cd0 \ + --hash=sha256:3dfce831b824ab5cf446ed0c350b793ac6fa5fe33b984305cb4c966a86a8fb79 \ + --hash=sha256:3f7866365df5a36a7b8de8056cd1c605648f56f9a226d918ed84c85d25e8d55f \ + --hash=sha256:455cc8c01de3bac6f9c223967cea41f4449f58b4c2e724ec8177382ddd183ab4 \ + --hash=sha256:4bb937e998be9d5e345f486693e477ba79e4344674484001a0b646be1d530487 \ + --hash=sha256:52303a20902ca0888dfb83230ca3ee6fbe63c0ad1dd60aa0bba7958ccff454d8 \ + --hash=sha256:6e0a897d4e09859cc80c6a16a29697406ead752292ace17f1805126a4f63c838 \ + --hash=sha256:6e1816e7c10966330d77af45f77501f9a68818c065dec0ad11d22b50a0e212e7 \ + --hash=sha256:73b5921c5c6ce3358c836461b5470bf675601c96d5e5d8f2a446951470614f67 \ + --hash=sha256:8093cd45cdb5f6c8591cfd1af03d32b32965b0f79b94684cd0c9afdf841982bb \ + --hash=sha256:864b4a94b60db301899cf373579fd9ef92edddbf0fb2cd5ae99f53ef423ccc56 \ + --hash=sha256:8a27b4d3ea9c6d086ce8e7cdb3e8d319b6752e2a03238a388ccc83ccbe165f50 \ + --hash=sha256:91b847969d4784abd855165a2d163f72ac1e58e6dce09a5e46c20e58f19cc96d \ + --hash=sha256:b47b1028be4758c3167e474884ccc079b94835f058984b15c145966c4df64d27 \ + --hash=sha256:b68814a322835d8ad671b7acc23a3b2acecba527bb14f4b53fc925f8a27e44d8 \ + --hash=sha256:bcb50a032c3b6ec7fb281b3a83d2b31ab5246c5b119588725b1350d3a1d9f6a3 \ + --hash=sha256:c56db7d10b25ce8918b6aec6b08ac401842b47e6c136773bfb3b590753f7fb67 \ + --hash=sha256:c94b77a13d4f47883e4f97f9fa00f5feadd38af3e6b3c7be45cfdb0a14c7149b \ + --hash=sha256:db381f6fdaef483ad435f778086ccc4890120aff8df2ba5cfeeac24d280b3145 \ + --hash=sha256:e6487d01c8b7ed86af30ea141fcc4f93f8a7dde26f94177c1ad637c353bd5c07 \ + --hash=sha256:e86923fa728dfba39c5bb6046a450bd4eec8ad949ac404eca728cfce320d1732 \ + --hash=sha256:f6ca36dc1e9eeb46d779869c60001b3065fb670b5775c51421c099ea2a77c3c9 \ + --hash=sha256:fb62f2cbe790a50d95593fb40e8cca261c31a2f5637455ea39440d6457c2ba25 zope.proxy==4.3.3 \ --hash=sha256:04646ac04ffa9c8e32fb2b5c3cd42995b2548ea14251f3c21ca704afae88e42c \ --hash=sha256:07b6bceea232559d24358832f1cd2ed344bbf05ca83855a5b9698b5f23c5ed60 \ @@ -1503,18 +1540,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.1.0 \ - --hash=sha256:66a5cab9267349941604c2c98082bfef85877653c023fc324b1c3869fb16add6 \ - --hash=sha256:46e93661a0db53f416c0f5476d8d2e62bc7259b7660dd983453b85df9ef6e8b8 -acme==1.1.0 \ - --hash=sha256:11b9beba706fb8f652c8910d46dd1939d670cac8169f3c66c18c080ed3353e71 \ - --hash=sha256:c305a20eeb9cb02240347703d497891c13d43a47c794fa100d4dbb479a5370d9 -certbot-apache==1.1.0 \ - --hash=sha256:9c847ff223c2e465e241c78d22f97cee77d5e551df608bed06c55f8627f4cbd2 \ - --hash=sha256:05e84dfe96b72582cde97c490977d8e2d33d440c927a320debb4cf287f6fadcc -certbot-nginx==1.1.0 \ - --hash=sha256:bf06fa2f5059f0fdb7d352c8739e1ed0830db4f0d89e812dab4f081bda6ec7d6 \ - --hash=sha256:0a80ecbd2a30f3757c7652cabfff854ca07873b1cf02ebbe1892786c3b3a5874 +certbot==1.2.0 \ + --hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \ + --hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c +acme==1.2.0 \ + --hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \ + --hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab +certbot-apache==1.2.0 \ + --hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \ + --hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3 +certbot-nginx==1.2.0 \ + --hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \ + --hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index 1a030eb47..488d0bf2e 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl4eDcYACgkQTRfJlc2X -dfIAiQgAufTpgNvnHKoLQLwWf3GbjLQYWc3w1zRbGUMjghS/rS1yuf7RE/IPItET -ocIuIE36ogjvgnRuI0OOu3yJ+jxe41u3ToPb0ehNhINd+3rXsDhzwJDPjFdOiq98 -NoW9wQE9AHSfKEEVprckuZe2XmNLsYbBfa9THFULYIlnqAewtercXXx0eKaMG9+d -aRaD+LZXANx7IV6XnI9jfdKRuldHDvYp1TdvrRWBAVHid8j44c3P0pSvzf0YKGbx -xIty/w0zQFIWCfqPdK7/R2EHbEyR0SdI00a1Va1x7P8JGf7kDyLXl+Y9Yth7/uHA -osivJCpSrtAEbvMXojnL7u7kq3b37Q== -=Une9 +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl456ZoACgkQTRfJlc2X +dfJx8wf/addMw4kUlwu6poHqLvsifZzHAESgvq+qybgFvl5yTh2U+99PGBgxRYx+ +bENIWBi6+XB+CiVuLzIXWw/VkXh+za99orRkkVK9PI33Xr7jBMZo5Oa3JviYjl3X +PcfjioRQCD+a9Tf9RO25LXQmxn87Ql9x3nxJuk//YeSpuImFmYjIBPE4n/LPEf7z +8WHU4oxxa/bgqGCPgv6O7ZBw7ipd3g+VHcDZcNQMP4tWYb6m7x/nN61yirid7q3M +uqQ1lbitN48ISyru6xPyE6WGTvfl1SIQd21FNRETpcoesx+MTv3ApWT4dqXjZvaX +FeM55IS65e7ci6yLV9qdAbqGKzhX0Q== +=uLcV -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 9d2013cd1..cea58e2cb 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="1.2.0.dev0" +LE_AUTO_VERSION="1.2.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1540,18 +1540,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.1.0 \ - --hash=sha256:66a5cab9267349941604c2c98082bfef85877653c023fc324b1c3869fb16add6 \ - --hash=sha256:46e93661a0db53f416c0f5476d8d2e62bc7259b7660dd983453b85df9ef6e8b8 -acme==1.1.0 \ - --hash=sha256:11b9beba706fb8f652c8910d46dd1939d670cac8169f3c66c18c080ed3353e71 \ - --hash=sha256:c305a20eeb9cb02240347703d497891c13d43a47c794fa100d4dbb479a5370d9 -certbot-apache==1.1.0 \ - --hash=sha256:9c847ff223c2e465e241c78d22f97cee77d5e551df608bed06c55f8627f4cbd2 \ - --hash=sha256:05e84dfe96b72582cde97c490977d8e2d33d440c927a320debb4cf287f6fadcc -certbot-nginx==1.1.0 \ - --hash=sha256:bf06fa2f5059f0fdb7d352c8739e1ed0830db4f0d89e812dab4f081bda6ec7d6 \ - --hash=sha256:0a80ecbd2a30f3757c7652cabfff854ca07873b1cf02ebbe1892786c3b3a5874 +certbot==1.2.0 \ + --hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \ + --hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c +acme==1.2.0 \ + --hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \ + --hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab +certbot-apache==1.2.0 \ + --hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \ + --hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3 +certbot-nginx==1.2.0 \ + --hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \ + --hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index bae77d59b03aeb894b36fb45c0e245e7d60390d3..fefc81b37796cdecd066b9bb212d8e285fffd4d0 100644 GIT binary patch literal 256 zcmV+b0ssCyy6+%Byu~4aOZx+sGM?uWzM?nz3Jfz_Cb^!H(J$u3EbYx+Wmt@nUdJdC z5^ed_c;i%mwYmiD>ud)5-Lk6YWkh7j-?{0%L?KJZd%TEUG+|;|-*#FI(%jlkvk;lO zO~Yh(rw(~6mZiF)`O}!O(DYn@gPBFqESJ@M(%s)|7%F?plRgOU1Sm{BW(R9Bfx|N( zKD3y8K_?ev`Hu@xHbPDe3A34WZ3?o_F0i8u)P>>`q{3hr9CRb?E6FEIK^btkZ@Qg6 zt+z9SQGsl`GlM4_;VnLX96Xg&)Kv+HS#^V0%aEYWG9N+LKk6WSdEBew*pUM_Rk_eZ GIvjh!?|NoXAEblY3xW6Ply`4f(38*T G)t{UAv38mO diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 67a33390b..eb9027edb 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==1.1.0 \ - --hash=sha256:66a5cab9267349941604c2c98082bfef85877653c023fc324b1c3869fb16add6 \ - --hash=sha256:46e93661a0db53f416c0f5476d8d2e62bc7259b7660dd983453b85df9ef6e8b8 -acme==1.1.0 \ - --hash=sha256:11b9beba706fb8f652c8910d46dd1939d670cac8169f3c66c18c080ed3353e71 \ - --hash=sha256:c305a20eeb9cb02240347703d497891c13d43a47c794fa100d4dbb479a5370d9 -certbot-apache==1.1.0 \ - --hash=sha256:9c847ff223c2e465e241c78d22f97cee77d5e551df608bed06c55f8627f4cbd2 \ - --hash=sha256:05e84dfe96b72582cde97c490977d8e2d33d440c927a320debb4cf287f6fadcc -certbot-nginx==1.1.0 \ - --hash=sha256:bf06fa2f5059f0fdb7d352c8739e1ed0830db4f0d89e812dab4f081bda6ec7d6 \ - --hash=sha256:0a80ecbd2a30f3757c7652cabfff854ca07873b1cf02ebbe1892786c3b3a5874 +certbot==1.2.0 \ + --hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \ + --hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c +acme==1.2.0 \ + --hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \ + --hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab +certbot-apache==1.2.0 \ + --hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \ + --hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3 +certbot-nginx==1.2.0 \ + --hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \ + --hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db From 97ae63efa6dd24b239e641d3b73a80f7de9b8cf2 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 4 Feb 2020 14:01:03 -0800 Subject: [PATCH 070/122] Add contents to certbot/CHANGELOG.md for next version --- certbot/CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index a14cea76c..3e3fda49f 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -2,6 +2,22 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 1.3.0 - master + +### Added + +* + +### Changed + +* + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + ## 1.2.0 - 2020-02-04 ### Added From 6a4b610269be13dd4227e03ff91c5d00188eb78e Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 4 Feb 2020 14:01:04 -0800 Subject: [PATCH 071/122] Bump version to 1.3.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 58d2c12ce..0e11779ba 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 8a887b444..f9b85008b 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 891bd8e1c..1dbcefa75 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -3,7 +3,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.2.0' +version = '1.3.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 706113d45..9376bc1c4 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index c290c5599..4e99ff5ff 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index fac989ae6..9c9d1717c 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index fe29a7137..9cde6214c 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index a36c74f9d..adaba6851 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 1ef15c85f..a849cef45 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index f2b99ba15..51d5b8a3f 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index eead76cc8..e7e91b929 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index f8484e9f5..ea64f79a2 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 7a4e21a8e..d6bedca1c 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index dbecc3f7f..8f5b052a2 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 8f2f09f27..fa51c2108 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 1d30e5b40..f25e348ff 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index d0403ac87..8df2320ba 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index dc50f5cb3..3b75a3424 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/certbot/__init__.py b/certbot/certbot/__init__.py index ab972d9e3..84ade6b08 100644 --- a/certbot/certbot/__init__.py +++ b/certbot/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '1.2.0' +__version__ = '1.3.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index cea58e2cb..e2813853b 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="1.2.0" +LE_AUTO_VERSION="1.3.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates From f3ed13374456f3b53fc87dc0fa1ed71b1efa37e7 Mon Sep 17 00:00:00 2001 From: OsirisInferi Date: Wed, 5 Feb 2020 22:17:29 +0100 Subject: [PATCH 072/122] Wrap makedirs() within exception handelrs --- certbot-apache/certbot_apache/_internal/http_01.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/certbot-apache/certbot_apache/_internal/http_01.py b/certbot-apache/certbot_apache/_internal/http_01.py index 53ccd2bc7..ad62a77bb 100644 --- a/certbot-apache/certbot_apache/_internal/http_01.py +++ b/certbot-apache/certbot_apache/_internal/http_01.py @@ -169,8 +169,14 @@ class ApacheHttp01(common.ChallengePerformer): def _set_up_challenges(self): if not os.path.isdir(self.challenge_dir): old_umask = os.umask(0o022) - filesystem.makedirs(self.challenge_dir, 0o755) - os.umask(old_umask) + try: + filesystem.makedirs(self.challenge_dir, 0o755) + except OSError as exception: + if exception.errno not in (errno.EEXIST, errno.EISDIR): + raise errors.PluginError( + "Couldn't create root for http-01 challenge") + finally: + os.umask(old_umask) responses = [] for achall in self.achalls: From d3a4b8fd8c068624b40179f567e191b6979bf6cf Mon Sep 17 00:00:00 2001 From: OsirisInferi Date: Wed, 5 Feb 2020 22:27:12 +0100 Subject: [PATCH 073/122] Missing import --- certbot-apache/certbot_apache/_internal/http_01.py | 1 + 1 file changed, 1 insertion(+) diff --git a/certbot-apache/certbot_apache/_internal/http_01.py b/certbot-apache/certbot_apache/_internal/http_01.py index ad62a77bb..6c822cc38 100644 --- a/certbot-apache/certbot_apache/_internal/http_01.py +++ b/certbot-apache/certbot_apache/_internal/http_01.py @@ -1,5 +1,6 @@ """A class that performs HTTP-01 challenges for Apache""" import logging +import errno from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module From 7b35abbcb42845a5c3a889884f20345edf55f640 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 5 Feb 2020 23:12:29 +0100 Subject: [PATCH 074/122] Windows installer integration tests (#7724) As discussed in #7539, we need proper tests of the Windows installer itself in order to variety that all the logic contained in a production-grade runtime of Certbot on Windows is correctly setup by each version of the installer, and so for a variety of Windows OSes. This PR handles this requirement. The new `windows_installer_integration_tests` module in `certbot-ci` will: * run the given Windows installer * check that Certbot is properly installed and working * check that the scheduled renew task is set up * check that the scheduled task actually launch the Certbot renew logic The Windows nightly tests are updated accordingly, in order to have the tests run on Windows Server 2012R2, 2016 and 2019. These tests will evolve as we add more logic on the installer. * Configure an integration test testing the windows installer * Write the test module * Configurable installer path, prepare azure pipelines * Fix option * Update test_main.py * Add confirmation for this destructive test * Use regex to validate certbot --version output * Explicit dependency on a log output * Use an exception to ask confirmation * Use --allow-persistent-changes --- .../templates/installer-tests.yml | 16 ++--- .../__init__.py | 0 .../conftest.py | 38 ++++++++++++ .../test_main.py | 61 +++++++++++++++++++ certbot/certbot/_internal/renewal.py | 3 + 5 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 certbot-ci/windows_installer_integration_tests/__init__.py create mode 100644 certbot-ci/windows_installer_integration_tests/conftest.py create mode 100644 certbot-ci/windows_installer_integration_tests/test_main.py diff --git a/.azure-pipelines/templates/installer-tests.yml b/.azure-pipelines/templates/installer-tests.yml index f1ccd92ed..6d5672339 100644 --- a/.azure-pipelines/templates/installer-tests.yml +++ b/.azure-pipelines/templates/installer-tests.yml @@ -33,22 +33,24 @@ jobs: pool: vmImage: $(imageName) steps: + - powershell: Invoke-WebRequest https://www.python.org/ftp/python/3.8.1/python-3.8.1-amd64-webinstall.exe -OutFile C:\py3-setup.exe + displayName: Get Python + - script: C:\py3-setup.exe /quiet PrependPath=1 InstallAllUsers=1 Include_launcher=1 InstallLauncherAllUsers=1 Include_test=0 Include_doc=0 Include_dev=1 Include_debug=0 Include_tcltk=0 TargetDir=C:\py3 + displayName: Install Python - task: DownloadPipelineArtifact@2 inputs: artifact: windows-installer path: $(Build.SourcesDirectory)/bin displayName: Retrieve Windows installer - - script: $(Build.SourcesDirectory)\bin\certbot-beta-installer-win32.exe /S - displayName: Install Certbot - - powershell: Invoke-WebRequest https://www.python.org/ftp/python/3.8.1/python-3.8.1-amd64-webinstall.exe -OutFile C:\py3-setup.exe - displayName: Get Python - - script: C:\py3-setup.exe /quiet PrependPath=1 InstallAllUsers=1 Include_launcher=1 InstallLauncherAllUsers=1 Include_test=0 Include_doc=0 Include_dev=1 Include_debug=0 Include_tcltk=0 TargetDir=C:\py3 - displayName: Install Python - script: | py -3 -m venv venv venv\Scripts\python tools\pip_install.py -e certbot-ci displayName: Prepare Certbot-CI + - script: | + set PATH=%ProgramFiles(x86)%\Certbot\bin;%PATH% + venv\Scripts\python -m pytest certbot-ci\windows_installer_integration_tests --allow-persistent-changes --installer-path $(Build.SourcesDirectory)\bin\certbot-beta-installer-win32.exe + displayName: Run windows installer integration tests - script: | set PATH=%ProgramFiles(x86)%\Certbot\bin;%PATH% venv\Scripts\python -m pytest certbot-ci\certbot_integration_tests\certbot_tests -n 4 - displayName: Run integration tests + displayName: Run certbot integration tests diff --git a/certbot-ci/windows_installer_integration_tests/__init__.py b/certbot-ci/windows_installer_integration_tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/certbot-ci/windows_installer_integration_tests/conftest.py b/certbot-ci/windows_installer_integration_tests/conftest.py new file mode 100644 index 000000000..e36654f90 --- /dev/null +++ b/certbot-ci/windows_installer_integration_tests/conftest.py @@ -0,0 +1,38 @@ +""" +General conftest for pytest execution of all integration tests lying +in the window_installer_integration tests package. +As stated by pytest documentation, conftest module is used to set on +for a directory a specific configuration using built-in pytest hooks. + +See https://docs.pytest.org/en/latest/reference.html#hook-reference +""" +from __future__ import print_function +import os + +import pytest + +ROOT_PATH = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + + +def pytest_addoption(parser): + """ + Standard pytest hook to add options to the pytest parser. + :param parser: current pytest parser that will be used on the CLI + """ + parser.addoption('--installer-path', + default=os.path.join(ROOT_PATH, 'windows-installer', 'build', + 'nsis', 'certbot-beta-installer-win32.exe'), + help='set the path of the windows installer to use, default to ' + 'CERTBOT_ROOT_PATH\\windows-installer\\build\\nsis\\certbot-beta-installer-win32.exe') + parser.addoption('--allow-persistent-changes', action='store_true', + help='needs to be set, and confirm that the test will make persistent changes on this machine') + + +def pytest_configure(config): + """ + Standard pytest hook used to add a configuration logic for each node of a pytest run. + :param config: the current pytest configuration + """ + if not config.option.allow_persistent_changes: + raise RuntimeError('This integration test would install Certbot on your machine. ' + 'Please run it again with the `--allow-persistent-changes` flag set to acknowledge.') diff --git a/certbot-ci/windows_installer_integration_tests/test_main.py b/certbot-ci/windows_installer_integration_tests/test_main.py new file mode 100644 index 000000000..c8c347aa8 --- /dev/null +++ b/certbot-ci/windows_installer_integration_tests/test_main.py @@ -0,0 +1,61 @@ +import os +import time +import unittest +import subprocess +import re + + +@unittest.skipIf(os.name != 'nt', reason='Windows installer tests must be run on Windows.') +def test_it(request): + try: + subprocess.check_call(['certbot', '--version']) + except (subprocess.CalledProcessError, OSError): + pass + else: + raise AssertionError('Expect certbot to not be available in the PATH.') + + try: + # Install certbot + subprocess.check_call([request.config.option.installer_path, '/S']) + + # Assert certbot is installed and runnable + output = subprocess.check_output(['certbot', '--version'], universal_newlines=True) + assert re.match(r'^certbot \d+\.\d+\.\d+.*$', output), 'Flag --version does not output a version.' + + # Assert renew task is installed and ready + output = _ps('(Get-ScheduledTask -TaskName "Certbot Renew Task").State', capture_stdout=True) + assert output.strip() == 'Ready' + + # Assert renew task is working + now = time.time() + _ps('Start-ScheduledTask -TaskName "Certbot Renew Task"') + + status = 'Running' + while status != 'Ready': + status = _ps('(Get-ScheduledTask -TaskName "Certbot Renew Task").State', capture_stdout=True).strip() + time.sleep(1) + + log_path = os.path.join('C:\\', 'Certbot', 'log', 'letsencrypt.log') + + modification_time = os.path.getmtime(log_path) + assert now < modification_time, 'Certbot log file has not been modified by the renew task.' + + with open(log_path) as file_h: + data = file_h.read() + assert 'no renewal failures' in data, 'Renew task did not execute properly.' + + finally: + # Sadly this command cannot work in non interactive mode: uninstaller will ask explicitly permission in an UAC prompt + # print('Uninstalling Certbot ...') + # uninstall_path = _ps('(gci "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"' + # ' | foreach { gp $_.PSPath }' + # ' | ? { $_ -match "Certbot" }' + # ' | select UninstallString)' + # '.UninstallString', capture_stdout=True) + # subprocess.check_call([uninstall_path, '/S']) + pass + + +def _ps(powershell_str, capture_stdout=False): + fn = subprocess.check_output if capture_stdout else subprocess.check_call + return fn(['powershell.exe', '-c', powershell_str], universal_newlines=True) diff --git a/certbot/certbot/_internal/renewal.py b/certbot/certbot/_internal/renewal.py index 930f6c1a9..bf30404f5 100644 --- a/certbot/certbot/_internal/renewal.py +++ b/certbot/certbot/_internal/renewal.py @@ -471,4 +471,7 @@ def handle_renewal_request(config): if renew_failures or parse_failures: raise errors.Error("{0} renew failure(s), {1} parse failure(s)".format( len(renew_failures), len(parse_failures))) + + # Windows installer integration tests rely on handle_renewal_request behavior here. + # If the text below changes, these tests will need to be updated accordingly. logger.debug("no renewal failures") From cc764b65c1bb3371389ba2ef7c20176d10b58538 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 5 Feb 2020 14:37:39 -0800 Subject: [PATCH 075/122] Set recreate = true in tox.ini. (#7746) Fixes #7745. --- tox.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tox.ini b/tox.ini index 3a31558d8..6d9814192 100644 --- a/tox.ini +++ b/tox.ini @@ -67,6 +67,9 @@ passenv = commands = {[base]install_and_test} {[base]all_packages} python tests/lock_test.py +# We always recreate the virtual environment to avoid problems like +# https://github.com/certbot/certbot/issues/7745. +recreate = true setenv = PYTEST_ADDOPTS = {env:PYTEST_ADDOPTS:--numprocesses auto} PYTHONHASHSEED = 0 From 7da5196206b33d5593bd15cd1dcce4d790db7e6d Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 5 Feb 2020 14:49:01 -0800 Subject: [PATCH 076/122] Add triggers for only a single CI system (#7748) * Configure travis-test to only run on Travis. * Configure azure-test to only run on Azure. * Add docs and comments to keep it up-to-date. --- .azure-pipelines/advanced.yml | 3 +++ .travis.yml | 12 +++++++----- certbot/docs/contributing.rst | 10 ++++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.azure-pipelines/advanced.yml b/.azure-pipelines/advanced.yml index 44cdf5d54..dda7f9bfd 100644 --- a/.azure-pipelines/advanced.yml +++ b/.azure-pipelines/advanced.yml @@ -1,5 +1,8 @@ # Advanced pipeline for isolated checks and release purpose trigger: + # When changing these triggers, please ensure the documentation under + # "Running tests in CI" is still correct. + - azure-test-* - test-* - '*.x' pr: diff --git a/.travis.yml b/.travis.yml index 0ed6b47af..6c5147603 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,17 +14,19 @@ before_script: - export TOX_TESTENV_PASSENV=TRAVIS # Only build pushes to the master branch, PRs, and branches beginning with -# `test-` or of the form `digit(s).digit(s).x`. This reduces the number of -# simultaneous Travis runs, which speeds turnaround time on review since there -# is a cap of on the number of simultaneous runs. +# `test-`, `travis-test-`, or of the form `digit(s).digit(s).x`. This reduces +# the number of simultaneous Travis runs, which speeds turnaround time on +# review since there is a cap of on the number of simultaneous runs. branches: + # When changing these branches, please ensure the documentation under + # "Running tests in CI" is still correct. only: # apache-parser-v2 is a temporary branch for doing work related to # rewriting the parser in the Apache plugin. - apache-parser-v2 - master - /^\d+\.\d+\.x$/ - - /^test-.*$/ + - /^(travis-)?test-.*$/ # Jobs for the main test suite are always executed (including on PRs) except for pushes on master. not-on-master: ¬-on-master @@ -274,7 +276,7 @@ after_success: '[ "$TOXENV" == "py27-cover" ] && codecov -F linux' notifications: email: false irc: - if: NOT branch =~ ^test-.*$ + if: NOT branch =~ ^(travis-)?test-.*$ channels: # This is set to a secure variable to prevent forks from sending # notifications. This value was created by installing diff --git a/certbot/docs/contributing.rst b/certbot/docs/contributing.rst index c13005a9d..25d832761 100644 --- a/certbot/docs/contributing.rst +++ b/certbot/docs/contributing.rst @@ -201,6 +201,16 @@ using an HTTP-01 challenge on a machine with Python 3: certbot_test certonly --standalone -d test.example.com # To stop Pebble, launch `fg` to get back the background job, then press CTRL+C +Running tests in CI +~~~~~~~~~~~~~~~~~~~ + +Certbot uses both Azure Pipelines and Travis to run continuous integration +tests. If you are using our Azure and Travis setup, a branch whose name starts +with `test-` will run all Azure and Travis tests on that branch. If the branch +name starts with `azure-test-`, it will run all of our Azure tests and none of +our Travis tests. If the branch stats with `travis-test-`, only our Travis +tests will be run. + Code components and layout ========================== From c98183c9986c47984695d467fa5a9ccf5b937c37 Mon Sep 17 00:00:00 2001 From: Filip Lajszczak Date: Thu, 6 Feb 2020 15:27:20 +0000 Subject: [PATCH 077/122] restore CHANGELOG in root directory --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) create mode 120000 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 120000 index 000000000..ba7396f24 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +certbot/CHANGELOG.md \ No newline at end of file From 5035a510a2e4f3e1d44223e59036f74c0f7c58b1 Mon Sep 17 00:00:00 2001 From: OsirisInferi Date: Thu, 6 Feb 2020 21:10:41 +0100 Subject: [PATCH 078/122] Add test for $hostname parsing --- certbot-nginx/tests/configurator_test.py | 6 ++++-- .../tests/testdata/etc_nginx/sites-enabled/example.net | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 certbot-nginx/tests/testdata/etc_nginx/sites-enabled/example.net diff --git a/certbot-nginx/tests/configurator_test.py b/certbot-nginx/tests/configurator_test.py index ef5593395..57097859a 100644 --- a/certbot-nginx/tests/configurator_test.py +++ b/certbot-nginx/tests/configurator_test.py @@ -76,15 +76,17 @@ class NginxConfiguratorTest(util.NginxTest): else: # pragma: no cover self.fail("Exception wasn't raised!") + @mock.patch("certbot_nginx._internal.configurator.socket.gethostname") @mock.patch("certbot_nginx._internal.configurator.socket.gethostbyaddr") - def test_get_all_names(self, mock_gethostbyaddr): + def test_get_all_names(self, mock_gethostbyaddr, mock_gethostname): mock_gethostbyaddr.return_value = ('155.225.50.69.nephoscale.net', [], []) + mock_gethostname.return_value = ('example.net') names = self.config.get_all_names() self.assertEqual(names, { "155.225.50.69.nephoscale.net", "www.example.org", "another.alias", "migration.com", "summer.com", "geese.com", "sslon.com", "globalssl.com", "globalsslsetssl.com", "ipv6.com", "ipv6ssl.com", - "headers.com"}) + "headers.com", "example.net"}) def test_supported_enhancements(self): self.assertEqual(['redirect', 'ensure-http-header', 'staple-ocsp'], diff --git a/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/example.net b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/example.net new file mode 100644 index 000000000..67d566fe6 --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/example.net @@ -0,0 +1,5 @@ +server { + listen 80; + listen [::]:80; + server_name $hostname; +} From 0e03f827339dd3f6261b985f92b4b5ec6c6164d8 Mon Sep 17 00:00:00 2001 From: OsirisInferi Date: Thu, 6 Feb 2020 21:12:17 +0100 Subject: [PATCH 079/122] Remove todo:: --- certbot-nginx/certbot_nginx/_internal/configurator.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py index 66fbf5c61..90a8e6de6 100644 --- a/certbot-nginx/certbot_nginx/_internal/configurator.py +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -313,9 +313,6 @@ class NginxConfigurator(common.Installer): .. todo:: This should maybe return list if no obvious answer is presented. - .. todo:: The special name "$hostname" corresponds to the machine's - hostname. Currently we just ignore this. - :param str target_name: domain name :param bool create_if_no_match: If we should create a new vhost from default when there is no match found. If we can't choose a default, raise a From 4f80f8b910b0446eda6c4224c5855e0653dec274 Mon Sep 17 00:00:00 2001 From: OsirisInferi Date: Thu, 6 Feb 2020 21:24:25 +0100 Subject: [PATCH 080/122] Fixing existing tests --- certbot-nginx/tests/configurator_test.py | 4 ++-- certbot-nginx/tests/parser_test.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/certbot-nginx/tests/configurator_test.py b/certbot-nginx/tests/configurator_test.py index 57097859a..0d9d6d356 100644 --- a/certbot-nginx/tests/configurator_test.py +++ b/certbot-nginx/tests/configurator_test.py @@ -36,7 +36,7 @@ class NginxConfiguratorTest(util.NginxTest): def test_prepare(self): self.assertEqual((1, 6, 2), self.config.version) - self.assertEqual(11, len(self.config.parser.parsed)) + self.assertEqual(12, len(self.config.parser.parsed)) @mock.patch("certbot_nginx._internal.configurator.util.exe_exists") @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") @@ -926,7 +926,7 @@ class NginxConfiguratorTest(util.NginxTest): prefer_ssl=False, no_ssl_filter_port='80') # Check that the dialog was called with only port 80 vhosts - self.assertEqual(len(mock_select_vhs.call_args[0][0]), 5) + self.assertEqual(len(mock_select_vhs.call_args[0][0]), 6) class InstallSslOptionsConfTest(util.NginxTest): diff --git a/certbot-nginx/tests/parser_test.py b/certbot-nginx/tests/parser_test.py index 2f3b260ca..f3a5665c5 100644 --- a/certbot-nginx/tests/parser_test.py +++ b/certbot-nginx/tests/parser_test.py @@ -58,7 +58,8 @@ class NginxParserTest(util.NginxTest): 'sites-enabled/sslon.com', 'sites-enabled/globalssl.com', 'sites-enabled/ipv6.com', - 'sites-enabled/ipv6ssl.com']}, + 'sites-enabled/ipv6ssl.com', + 'sites-enabled/example.net']}, set(nparser.parsed.keys())) self.assertEqual([['server_name', 'somename', 'alias', 'another.alias']], nparser.parsed[nparser.abs_path('server.conf')]) @@ -88,7 +89,7 @@ class NginxParserTest(util.NginxTest): parsed = nparser._parse_files(nparser.abs_path( 'sites-enabled/example.com.test')) self.assertEqual(3, len(glob.glob(nparser.abs_path('*.test')))) - self.assertEqual(8, len( + self.assertEqual(9, len( glob.glob(nparser.abs_path('sites-enabled/*.test')))) self.assertEqual([[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], @@ -171,7 +172,7 @@ class NginxParserTest(util.NginxTest): '*.www.example.com']), [], [2, 1, 0]) - self.assertEqual(13, len(vhosts)) + self.assertEqual(14, len(vhosts)) example_com = [x for x in vhosts if 'example.com' in x.filep][0] self.assertEqual(vhost3, example_com) default = [x for x in vhosts if 'default' in x.filep][0] From 1859fb059d598021703a79a50f60393ed8887759 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 6 Feb 2020 15:39:47 -0800 Subject: [PATCH 081/122] Don't display todo comments in docs (#7753) Currently if you go to https://certbot.eff.org/docs/api/certbot.crypto_util.html, there is a todo comment displayed at the top of the page. These todos were written for developers, not users, so I do not think they should be shown from our documentation. This PR makes the quick and easy fix of configuring Sphinx not to show these todo items. I created #7752 to track removing all of these todos from our docstrings and disabling the Sphinx todo extension. * Set todo_include_todos=False in sphinx-quickstart * Remove todos from existing docs. --- acme/docs/conf.py | 2 +- certbot-dns-cloudflare/docs/conf.py | 2 +- certbot-dns-cloudxns/docs/conf.py | 2 +- certbot-dns-digitalocean/docs/conf.py | 2 +- certbot-dns-dnsimple/docs/conf.py | 2 +- certbot-dns-dnsmadeeasy/docs/conf.py | 2 +- certbot-dns-gehirn/docs/conf.py | 2 +- certbot-dns-google/docs/conf.py | 2 +- certbot-dns-linode/docs/conf.py | 2 +- certbot-dns-luadns/docs/conf.py | 2 +- certbot-dns-nsone/docs/conf.py | 2 +- certbot-dns-ovh/docs/conf.py | 2 +- certbot-dns-rfc2136/docs/conf.py | 2 +- certbot-dns-route53/docs/conf.py | 2 +- certbot-dns-sakuracloud/docs/conf.py | 2 +- certbot/docs/conf.py | 2 +- letshelp-certbot/docs/conf.py | 2 +- tools/sphinx-quickstart.sh | 2 ++ 18 files changed, 19 insertions(+), 17 deletions(-) diff --git a/acme/docs/conf.py b/acme/docs/conf.py index 8c1689128..a9c69d538 100644 --- a/acme/docs/conf.py +++ b/acme/docs/conf.py @@ -113,7 +113,7 @@ pygments_style = 'sphinx' #keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False # -- Options for HTML output ---------------------------------------------- diff --git a/certbot-dns-cloudflare/docs/conf.py b/certbot-dns-cloudflare/docs/conf.py index 97e54421e..e280a14a6 100644 --- a/certbot-dns-cloudflare/docs/conf.py +++ b/certbot-dns-cloudflare/docs/conf.py @@ -84,7 +84,7 @@ default_role = 'py:obj' pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False # -- Options for HTML output ---------------------------------------------- diff --git a/certbot-dns-cloudxns/docs/conf.py b/certbot-dns-cloudxns/docs/conf.py index 1fc05c94c..03c4204ee 100644 --- a/certbot-dns-cloudxns/docs/conf.py +++ b/certbot-dns-cloudxns/docs/conf.py @@ -84,7 +84,7 @@ default_role = 'py:obj' pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False # -- Options for HTML output ---------------------------------------------- diff --git a/certbot-dns-digitalocean/docs/conf.py b/certbot-dns-digitalocean/docs/conf.py index 0741e4cea..73bceabcc 100644 --- a/certbot-dns-digitalocean/docs/conf.py +++ b/certbot-dns-digitalocean/docs/conf.py @@ -84,7 +84,7 @@ default_role = 'py:obj' pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False # -- Options for HTML output ---------------------------------------------- diff --git a/certbot-dns-dnsimple/docs/conf.py b/certbot-dns-dnsimple/docs/conf.py index 99cc93135..c739ff6ee 100644 --- a/certbot-dns-dnsimple/docs/conf.py +++ b/certbot-dns-dnsimple/docs/conf.py @@ -84,7 +84,7 @@ default_role = 'py:obj' pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False # -- Options for HTML output ---------------------------------------------- diff --git a/certbot-dns-dnsmadeeasy/docs/conf.py b/certbot-dns-dnsmadeeasy/docs/conf.py index 1f0c57812..bdb5faf11 100644 --- a/certbot-dns-dnsmadeeasy/docs/conf.py +++ b/certbot-dns-dnsmadeeasy/docs/conf.py @@ -84,7 +84,7 @@ default_role = 'py:obj' pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False # -- Options for HTML output ---------------------------------------------- diff --git a/certbot-dns-gehirn/docs/conf.py b/certbot-dns-gehirn/docs/conf.py index 527bc3d55..8ec35d152 100644 --- a/certbot-dns-gehirn/docs/conf.py +++ b/certbot-dns-gehirn/docs/conf.py @@ -84,7 +84,7 @@ default_role = 'py:obj' pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False # -- Options for HTML output ---------------------------------------------- diff --git a/certbot-dns-google/docs/conf.py b/certbot-dns-google/docs/conf.py index b2ddcfb34..7db48b837 100644 --- a/certbot-dns-google/docs/conf.py +++ b/certbot-dns-google/docs/conf.py @@ -85,7 +85,7 @@ default_role = 'py:obj' pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False # -- Options for HTML output ---------------------------------------------- diff --git a/certbot-dns-linode/docs/conf.py b/certbot-dns-linode/docs/conf.py index c6d564b7a..1c566571e 100644 --- a/certbot-dns-linode/docs/conf.py +++ b/certbot-dns-linode/docs/conf.py @@ -84,7 +84,7 @@ default_role = 'py:obj' pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False # -- Options for HTML output ---------------------------------------------- diff --git a/certbot-dns-luadns/docs/conf.py b/certbot-dns-luadns/docs/conf.py index 8e9d49988..ed318619d 100644 --- a/certbot-dns-luadns/docs/conf.py +++ b/certbot-dns-luadns/docs/conf.py @@ -84,7 +84,7 @@ default_role = 'py:obj' pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False # -- Options for HTML output ---------------------------------------------- diff --git a/certbot-dns-nsone/docs/conf.py b/certbot-dns-nsone/docs/conf.py index 5531959ed..2b9cf2d39 100644 --- a/certbot-dns-nsone/docs/conf.py +++ b/certbot-dns-nsone/docs/conf.py @@ -84,7 +84,7 @@ default_role = 'py:obj' pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False # -- Options for HTML output ---------------------------------------------- diff --git a/certbot-dns-ovh/docs/conf.py b/certbot-dns-ovh/docs/conf.py index 56e24a920..6015a700e 100644 --- a/certbot-dns-ovh/docs/conf.py +++ b/certbot-dns-ovh/docs/conf.py @@ -84,7 +84,7 @@ default_role = 'py:obj' pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False # -- Options for HTML output ---------------------------------------------- diff --git a/certbot-dns-rfc2136/docs/conf.py b/certbot-dns-rfc2136/docs/conf.py index c0d55078e..731b9cb1d 100644 --- a/certbot-dns-rfc2136/docs/conf.py +++ b/certbot-dns-rfc2136/docs/conf.py @@ -84,7 +84,7 @@ default_role = 'py:obj' pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False # -- Options for HTML output ---------------------------------------------- diff --git a/certbot-dns-route53/docs/conf.py b/certbot-dns-route53/docs/conf.py index c2eb880ac..c9bdfd15d 100644 --- a/certbot-dns-route53/docs/conf.py +++ b/certbot-dns-route53/docs/conf.py @@ -84,7 +84,7 @@ default_role = 'py:obj' pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False # -- Options for HTML output ---------------------------------------------- diff --git a/certbot-dns-sakuracloud/docs/conf.py b/certbot-dns-sakuracloud/docs/conf.py index 70a4d7434..5bc85f44e 100644 --- a/certbot-dns-sakuracloud/docs/conf.py +++ b/certbot-dns-sakuracloud/docs/conf.py @@ -84,7 +84,7 @@ default_role = 'py:obj' pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False # -- Options for HTML output ---------------------------------------------- diff --git a/certbot/docs/conf.py b/certbot/docs/conf.py index 1e57bc224..53ddbeff7 100644 --- a/certbot/docs/conf.py +++ b/certbot/docs/conf.py @@ -122,7 +122,7 @@ pygments_style = 'sphinx' #keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False suppress_warnings = ['image.nonlocal_uri'] diff --git a/letshelp-certbot/docs/conf.py b/letshelp-certbot/docs/conf.py index fc482a348..b4289a345 100644 --- a/letshelp-certbot/docs/conf.py +++ b/letshelp-certbot/docs/conf.py @@ -112,7 +112,7 @@ pygments_style = 'sphinx' #keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +todo_include_todos = False # -- Options for HTML output ---------------------------------------------- diff --git a/tools/sphinx-quickstart.sh b/tools/sphinx-quickstart.sh index 35a7f7fad..f8b806b1c 100755 --- a/tools/sphinx-quickstart.sh +++ b/tools/sphinx-quickstart.sh @@ -16,6 +16,8 @@ sed -i -e "s|intersphinx_mapping = {'https://docs.python.org/': None}|intersphin sed -i -e "s|html_theme = 'alabaster'|\n# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs\n# on_rtd is whether we are on readthedocs.org\non_rtd = os.environ.get('READTHEDOCS', None) == 'True'\nif not on_rtd: # only import and set the theme if we're building docs locally\n import sphinx_rtd_theme\n html_theme = 'sphinx_rtd_theme'\n html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]\n# otherwise, readthedocs.org uses their theme by default, so no need to specify it|" conf.py sed -i -e "s|# Add any paths that contain templates here, relative to this directory.|autodoc_member_order = 'bysource'\nautodoc_default_flags = ['show-inheritance']\n\n# Add any paths that contain templates here, relative to this directory.|" conf.py sed -i -e "s|# The name of the Pygments (syntax highlighting) style to use.|default_role = 'py:obj'\n\n# The name of the Pygments (syntax highlighting) style to use.|" conf.py +# If the --ext-todo flag is removed from sphinx-quickstart, the line below can be removed. +sed -i -e "s|todo_include_todos = True|todo_include_todos = False|" conf.py echo "/_build/" >> .gitignore echo "================= API Documentation From 86a6cc53cfc537949214dfea2bd87f227de19215 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 7 Feb 2020 00:08:41 -0800 Subject: [PATCH 082/122] Remove text that certbot.tests.utils isn't public (#7754) --- certbot/certbot/tests/util.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/certbot/certbot/tests/util.py b/certbot/certbot/tests/util.py index 02abe0a31..a9870b0fd 100644 --- a/certbot/certbot/tests/util.py +++ b/certbot/certbot/tests/util.py @@ -1,8 +1,4 @@ -"""Test utilities. - -.. warning:: This module is not part of the public API. - -""" +"""Test utilities.""" import logging from multiprocessing import Event from multiprocessing import Process From 7cc6cf2604bae3b6117b25d582cbad96dc53e4f0 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 7 Feb 2020 11:04:07 -0800 Subject: [PATCH 083/122] Remove link to letsencrypt readthedocs (#7757) After a brief discussion in Mattermost, I shut down letsencrypt.readthedocs.io. Turns out we were linking to it in our README here so let's remove the broken link. I didn't update the link to point to one of the readthedocs projects we still have because are main Certbot docs are self-hosted rather than being on readthedocs. --- certbot/README.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/certbot/README.rst b/certbot/README.rst index 2c934ce59..6ea22c63a 100644 --- a/certbot/README.rst +++ b/certbot/README.rst @@ -81,10 +81,6 @@ ACME working area in github: https://github.com/ietf-wg-acme/acme :target: https://codecov.io/gh/certbot/certbot :alt: Coverage status -.. |docs| image:: https://readthedocs.org/projects/letsencrypt/badge/ - :target: https://readthedocs.org/projects/letsencrypt/ - :alt: Documentation status - .. |container| image:: https://quay.io/repository/letsencrypt/letsencrypt/status :target: https://quay.io/repository/letsencrypt/letsencrypt :alt: Docker Repository on Quay.io From 5607025e9b05900b904ed340ed42737359aa1064 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 7 Feb 2020 12:58:15 -0800 Subject: [PATCH 084/122] Really remove old docs link from README (#7758) --- certbot/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot/README.rst b/certbot/README.rst index 6ea22c63a..d1b1e4fe2 100644 --- a/certbot/README.rst +++ b/certbot/README.rst @@ -71,7 +71,7 @@ ACME spec: http://ietf-wg-acme.github.io/acme/ ACME working area in github: https://github.com/ietf-wg-acme/acme -|build-status| |coverage| |docs| |container| +|build-status| |coverage| |container| .. |build-status| image:: https://travis-ci.com/certbot/certbot.svg?branch=master :target: https://travis-ci.com/certbot/certbot From e6f050dbe9fa8faf247f0489c25987c742f72ff3 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Mon, 10 Feb 2020 19:52:42 +0200 Subject: [PATCH 085/122] Move ocsp.py to public api (#7744) We should move ocsp.py to public API, as an upcoming OCSP prefetching functionality in Apache plugin relies on it, and as the plugins are note released in lockstep with the Certbot core, we need to be careful when changing those APIs. * Move ocsp.py to public api * Fix type annotations, move to pointing to an interface and fix linting * Add certbot.ocsp to documentation table of contents * Modify tests to reflect the changes in ocsp.py * Add changelog entry * Fix notAfter mock for tests --- certbot/CHANGELOG.md | 2 + certbot/certbot/_internal/cert_manager.py | 2 +- certbot/certbot/{_internal => }/ocsp.py | 11 +++-- certbot/docs/api/certbot.ocsp.rst | 7 +++ certbot/docs/api/certbot.rst | 1 + certbot/tests/ocsp_test.py | 55 ++++++++++++----------- 6 files changed, 46 insertions(+), 32 deletions(-) rename certbot/certbot/{_internal => }/ocsp.py (97%) create mode 100644 certbot/docs/api/certbot.ocsp.rst diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 50a6bc7f5..ff3061e01 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -6,6 +6,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added +* Added certbot.ocsp Certbot's API. The certbot.ocsp module can be used to + determine the OCSP status of certificates. * Don't verify the existing certificate in HTTP01Response.simple_verify, for compatibility with the real-world ACME challenge checks. diff --git a/certbot/certbot/_internal/cert_manager.py b/certbot/certbot/_internal/cert_manager.py index 1def76a3d..298e7d269 100644 --- a/certbot/certbot/_internal/cert_manager.py +++ b/certbot/certbot/_internal/cert_manager.py @@ -11,8 +11,8 @@ from acme.magic_typing import List # pylint: disable=unused-import, no-name-in- from certbot import crypto_util from certbot import errors from certbot import interfaces +from certbot import ocsp from certbot import util -from certbot._internal import ocsp from certbot._internal import storage from certbot.compat import os from certbot.display import util as display_util diff --git a/certbot/certbot/_internal/ocsp.py b/certbot/certbot/ocsp.py similarity index 97% rename from certbot/certbot/_internal/ocsp.py rename to certbot/certbot/ocsp.py index 2f6543e5d..5c3cfdd59 100644 --- a/certbot/certbot/_internal/ocsp.py +++ b/certbot/certbot/ocsp.py @@ -21,7 +21,7 @@ from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in from certbot import crypto_util from certbot import errors from certbot import util -from certbot._internal.storage import RenewableCert # pylint: disable=unused-import +from certbot.interfaces import RenewableCert # pylint: disable=unused-import try: # Only cryptography>=2.5 has ocsp module @@ -32,7 +32,6 @@ except (ImportError, AttributeError): # pragma: no cover ocsp = None # type: ignore - logger = logging.getLogger(__name__) @@ -64,12 +63,12 @@ class RevocationChecker(object): .. todo:: Make this a non-blocking call - :param `.storage.RenewableCert` cert: Certificate object + :param `.interfaces.RenewableCert` cert: Certificate object :returns: True if revoked; False if valid or the check failed or cert is expired. :rtype: bool """ - cert_path, chain_path = cert.cert, cert.chain + cert_path, chain_path = cert.cert_path, cert.chain_path if self.broken: return False @@ -78,7 +77,7 @@ class RevocationChecker(object): # so don't check OCSP if the cert is expired. # https://github.com/certbot/certbot/issues/7152 now = pytz.UTC.fromutc(datetime.utcnow()) - if cert.target_expiry <= now: + if crypto_util.notAfter(cert_path) <= now: return False url, host = _determine_ocsp_server(cert_path) @@ -296,5 +295,5 @@ def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): return True else: logger.warning("Unable to properly parse OCSP output: %s\nstderr:%s", - ocsp_output, ocsp_errors) + ocsp_output, ocsp_errors) return False diff --git a/certbot/docs/api/certbot.ocsp.rst b/certbot/docs/api/certbot.ocsp.rst new file mode 100644 index 000000000..1266c328a --- /dev/null +++ b/certbot/docs/api/certbot.ocsp.rst @@ -0,0 +1,7 @@ +certbot.ocsp package +====================== + +.. automodule:: certbot.ocsp + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.rst b/certbot/docs/api/certbot.rst index 6f5b4b403..e4245f80f 100644 --- a/certbot/docs/api/certbot.rst +++ b/certbot/docs/api/certbot.rst @@ -26,6 +26,7 @@ Submodules certbot.errors certbot.interfaces certbot.main + certbot.ocsp certbot.reverter certbot.util diff --git a/certbot/tests/ocsp_test.py b/certbot/tests/ocsp_test.py index 6e4ab52b8..6b05a8d3c 100644 --- a/certbot/tests/ocsp_test.py +++ b/certbot/tests/ocsp_test.py @@ -32,12 +32,12 @@ ocsp: Use -help for summary. class OCSPTestOpenSSL(unittest.TestCase): """ - OCSP revokation tests using OpenSSL binary. + OCSP revocation tests using OpenSSL binary. """ def setUp(self): - from certbot._internal import ocsp - with mock.patch('certbot._internal.ocsp.Popen') as mock_popen: + from certbot import ocsp + with mock.patch('certbot.ocsp.Popen') as mock_popen: with mock.patch('certbot.util.exe_exists') as mock_exists: mock_communicate = mock.MagicMock() mock_communicate.communicate.return_value = (None, out) @@ -48,8 +48,8 @@ class OCSPTestOpenSSL(unittest.TestCase): def tearDown(self): pass - @mock.patch('certbot._internal.ocsp.logger.info') - @mock.patch('certbot._internal.ocsp.Popen') + @mock.patch('certbot.ocsp.logger.info') + @mock.patch('certbot.ocsp.Popen') @mock.patch('certbot.util.exe_exists') def test_init(self, mock_exists, mock_popen, mock_log): mock_communicate = mock.MagicMock() @@ -57,7 +57,7 @@ class OCSPTestOpenSSL(unittest.TestCase): mock_popen.return_value = mock_communicate mock_exists.return_value = True - from certbot._internal import ocsp + from certbot import ocsp checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) self.assertEqual(mock_popen.call_count, 1) self.assertEqual(checker.host_args("x"), ["Host=x"]) @@ -74,14 +74,15 @@ class OCSPTestOpenSSL(unittest.TestCase): self.assertEqual(mock_log.call_count, 1) self.assertEqual(checker.broken, True) - @mock.patch('certbot._internal.ocsp._determine_ocsp_server') + @mock.patch('certbot.ocsp._determine_ocsp_server') + @mock.patch('certbot.ocsp.crypto_util.notAfter') @mock.patch('certbot.util.run_script') - def test_ocsp_revoked(self, mock_run, mock_determine): + def test_ocsp_revoked(self, mock_run, mock_na, mock_determine): now = pytz.UTC.fromutc(datetime.utcnow()) cert_obj = mock.MagicMock() - cert_obj.cert = "x" - cert_obj.chain = "y" - cert_obj.target_expiry = now + timedelta(hours=2) + cert_obj.cert_path = "x" + cert_obj.chain_path = "y" + mock_na.return_value = now + timedelta(hours=2) self.checker.broken = True mock_determine.return_value = ("", "") @@ -99,7 +100,7 @@ class OCSPTestOpenSSL(unittest.TestCase): self.assertEqual(mock_run.call_count, 2) # cert expired - cert_obj.target_expiry = now + mock_na.return_value = now mock_determine.return_value = ("", "") count_before = mock_determine.call_count self.assertEqual(self.checker.ocsp_revoked(cert_obj), False) @@ -108,16 +109,16 @@ class OCSPTestOpenSSL(unittest.TestCase): def test_determine_ocsp_server(self): cert_path = test_util.vector_path('ocsp_certificate.pem') - from certbot._internal import ocsp + from certbot import ocsp result = ocsp._determine_ocsp_server(cert_path) self.assertEqual(('http://ocsp.test4.buypass.com', 'ocsp.test4.buypass.com'), result) - @mock.patch('certbot._internal.ocsp.logger') + @mock.patch('certbot.ocsp.logger') @mock.patch('certbot.util.run_script') def test_translate_ocsp(self, mock_run, mock_log): # pylint: disable=protected-access mock_run.return_value = openssl_confused - from certbot._internal import ocsp + from certbot import ocsp self.assertEqual(ocsp._translate_ocsp_query(*openssl_happy), False) self.assertEqual(ocsp._translate_ocsp_query(*openssl_confused), False) self.assertEqual(mock_log.debug.call_count, 1) @@ -145,18 +146,22 @@ class OSCPTestCryptography(unittest.TestCase): """ def setUp(self): - from certbot._internal import ocsp + from certbot import ocsp self.checker = ocsp.RevocationChecker() self.cert_path = test_util.vector_path('ocsp_certificate.pem') self.chain_path = test_util.vector_path('ocsp_issuer_certificate.pem') self.cert_obj = mock.MagicMock() - self.cert_obj.cert = self.cert_path - self.cert_obj.chain = self.chain_path + self.cert_obj.cert_path = self.cert_path + self.cert_obj.chain_path = self.chain_path now = pytz.UTC.fromutc(datetime.utcnow()) - self.cert_obj.target_expiry = now + timedelta(hours=2) + self.mock_notAfter = mock.patch('certbot.ocsp.crypto_util.notAfter', + return_value=now + timedelta(hours=2)) + self.mock_notAfter.start() + # Ensure the mock.patch is stopped even if test raises an exception + self.addCleanup(self.mock_notAfter.stop) - @mock.patch('certbot._internal.ocsp._determine_ocsp_server') - @mock.patch('certbot._internal.ocsp._check_ocsp_cryptography') + @mock.patch('certbot.ocsp._determine_ocsp_server') + @mock.patch('certbot.ocsp._check_ocsp_cryptography') def test_ensure_cryptography_toggled(self, mock_revoke, mock_determine): mock_determine.return_value = ('http://example.com', 'example.com') self.checker.ocsp_revoked(self.cert_obj) @@ -263,7 +268,7 @@ class OSCPTestCryptography(unittest.TestCase): with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL): # This mock is necessary to avoid the first call contained in _determine_ocsp_server # of the method cryptography.x509.Extensions.get_extension_for_class. - with mock.patch('certbot._internal.ocsp._determine_ocsp_server') as mock_server: + with mock.patch('certbot.ocsp._determine_ocsp_server') as mock_server: mock_server.return_value = ('https://example.com', 'example.com') with mock.patch('cryptography.x509.Extensions.get_extension_for_class', side_effect=x509.ExtensionNotFound( @@ -275,12 +280,12 @@ class OSCPTestCryptography(unittest.TestCase): @contextlib.contextmanager def _ocsp_mock(certificate_status, response_status, http_status_code=200, check_signature_side_effect=None): - with mock.patch('certbot._internal.ocsp.ocsp.load_der_ocsp_response') as mock_response: + with mock.patch('certbot.ocsp.ocsp.load_der_ocsp_response') as mock_response: mock_response.return_value = _construct_mock_ocsp_response( certificate_status, response_status) - with mock.patch('certbot._internal.ocsp.requests.post') as mock_post: + with mock.patch('certbot.ocsp.requests.post') as mock_post: mock_post.return_value = mock.Mock(status_code=http_status_code) - with mock.patch('certbot._internal.ocsp.crypto_util.verify_signed_payload') \ + with mock.patch('certbot.ocsp.crypto_util.verify_signed_payload') \ as mock_check: if check_signature_side_effect: mock_check.side_effect = check_signature_side_effect From 02bf7d7dfc0864b742f0efe9cc7074f525a872ab Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 10 Feb 2020 11:01:17 -0800 Subject: [PATCH 086/122] Print script output in case of a failure. (#7759) These tests failed at https://travis-ci.com/certbot/certbot/jobs/285202481 but do not include any output from the script about what went wrong because the string created from `subprocess.CalledProcessError` does not include value of output. This PR fixes that by printing these values which `pytest` will include in the output if the test fails. --- letsencrypt-auto-source/tests/auto_test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/letsencrypt-auto-source/tests/auto_test.py b/letsencrypt-auto-source/tests/auto_test.py index 9c823fb55..805bb21af 100644 --- a/letsencrypt-auto-source/tests/auto_test.py +++ b/letsencrypt-auto-source/tests/auto_test.py @@ -167,6 +167,10 @@ def out_and_err(command, input=None, shell=False, env=None): if status: error = CalledProcessError(status, command) error.output = out + print('stdout output was:') + print(out) + print('stderr output was:') + print(err) raise error return out, err From b8856ac810ba89deff0bee012db1fd515dc75efa Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 11 Feb 2020 12:05:29 -0800 Subject: [PATCH 087/122] Fix unpinned tests (#7760) Our nightly tests failed last night due to a new release of `virtualenv` and `pip`'s lack of dependency resolution: https://travis-ci.com/certbot/certbot/jobs/285797857#L280. It looks like we were not the only ones affected by this problem: https://github.com/pypa/virtualenv/issues/1551 This fixes the problem by using `-I` to skip the logic where `pip` decides a dependency is already satisfied and has it reinstall/update the packages passed to `pip` and all of their dependencies. You can see our nightly tests passing with this change at https://github.com/certbot/certbot/runs/439231061. --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 23d957a21..1eae66333 100644 --- a/.travis.yml +++ b/.travis.yml @@ -263,8 +263,10 @@ addons: # except in tests where the environment variable CERTBOT_NO_PIN is set. # virtualenv is listed here explicitly to make sure it is upgraded when # CERTBOT_NO_PIN is set to work around failures we've seen when using an older -# version of virtualenv. -install: 'tools/pip_install.py -U codecov tox virtualenv' +# version of virtualenv. The option "-I" is set so when CERTBOT_NO_PIN is also +# set, pip updates dependencies it thinks are already satisfied to avoid some +# problems with its lack of real dependency resolution. +install: 'tools/pip_install.py -I codecov tox virtualenv' # Most of the time TRAVIS_RETRY is an empty string, and has no effect on the # script command. It is set only to `travis_retry` during farm tests, in # order to trigger the Travis retry feature, and compensate the inherent From 605ef40656110e4b1e9f5d35c47b102ea2aa2b4b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 11 Feb 2020 14:20:29 -0800 Subject: [PATCH 088/122] Remove duplicate pyparsing pin --- tools/oldest_constraints.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/oldest_constraints.txt b/tools/oldest_constraints.txt index 6154b497a..85d058796 100644 --- a/tools/oldest_constraints.txt +++ b/tools/oldest_constraints.txt @@ -13,7 +13,6 @@ ply==3.4 pyasn1==0.1.9 pycparser==2.14 pyOpenSSL==0.13.1 -pyparsing==1.5.6 pyRFC3339==1.0 python-augeas==0.5.0 oauth2client==4.0.0 From 7d540fc33ad39ec6664807c1a6445426c5534097 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 11 Feb 2020 14:44:37 -0800 Subject: [PATCH 089/122] update pyparsing comment --- certbot-nginx/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 3b75a3424..b180fe06a 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -13,7 +13,7 @@ install_requires = [ 'certbot>=1.1.0', 'mock', 'PyOpenSSL', - 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? + 'pyparsing>=1.5.5', # Python3 support 'setuptools', 'zope.interface', ] From df584a3b90f9c18efaac07a5d7fb0bf9be5a81a9 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 12 Feb 2020 13:12:03 -0800 Subject: [PATCH 090/122] Remove _internal from docstring. --- certbot/tests/plugins/common_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot/tests/plugins/common_test.py b/certbot/tests/plugins/common_test.py index fc05bf894..7543f28f3 100644 --- a/certbot/tests/plugins/common_test.py +++ b/certbot/tests/plugins/common_test.py @@ -177,7 +177,7 @@ class InstallerTest(test_util.ConfigTestCase): class AddrTest(unittest.TestCase): - """Tests for certbot._internal.plugins.common.Addr.""" + """Tests for certbot.plugins.common.Addr.""" def setUp(self): from certbot.plugins.common import Addr From fc7e5e8e6060d9e0df2e704a20103d5c0f456925 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 13 Feb 2020 22:56:16 +0100 Subject: [PATCH 091/122] Remove useless pylint error suppression directives (#7657) As pylint is evolving, it improves its accuracy, and several pylint error suppression (`# pylint: disable=ERROR) added in certbot codebase months or years ago are not needed anymore to make it happy. There is a (disabled by default) pylint error to detect the useless suppressions (pylint-ception: `useless-suppression`). It is not working perfectly (it has also false-positives ...) but it is a good start to clean the codebase. This PR removes several of these useless suppressions as detected by the current pylint version we use. * Remove useless suppress * Remove useless lines --- acme/acme/challenges.py | 2 +- acme/acme/client.py | 14 ++++++------ acme/acme/crypto_util.py | 12 +++++----- acme/acme/jws.py | 4 ++-- acme/acme/magic_typing.py | 1 - acme/acme/messages.py | 7 +++--- acme/acme/standalone.py | 16 +++++--------- .../certbot_apache/_internal/configurator.py | 20 ++++++++--------- .../certbot_apache/_internal/entrypoint.py | 2 +- .../certbot_apache/_internal/http_01.py | 4 ++-- .../certbot_apache/_internal/obj.py | 2 +- .../_internal/override_centos.py | 2 +- .../certbot_apache/_internal/parser.py | 10 ++++----- .../configurators/nginx/common.py | 2 +- .../certbot_compatibility_test/test_driver.py | 4 ++-- .../certbot_compatibility_test/validator.py | 3 +-- .../_internal/dns_cloudflare.py | 2 +- .../_internal/dns_cloudxns.py | 2 +- .../_internal/dns_digitalocean.py | 2 +- .../_internal/dns_dnsimple.py | 2 +- .../_internal/dns_dnsmadeeasy.py | 2 +- .../_internal/dns_gehirn.py | 2 +- .../_internal/dns_google.py | 8 +++---- .../_internal/dns_linode.py | 2 +- .../_internal/dns_luadns.py | 2 +- .../certbot_dns_nsone/_internal/dns_nsone.py | 2 +- .../certbot_dns_ovh/_internal/dns_ovh.py | 2 +- .../_internal/dns_rfc2136.py | 2 +- .../_internal/dns_route53.py | 10 ++++----- .../_internal/dns_sakuracloud.py | 2 +- .../certbot_nginx/_internal/configurator.py | 14 ++++++------ .../certbot_nginx/_internal/http_01.py | 2 +- .../certbot_nginx/_internal/nginxparser.py | 1 - .../certbot_nginx/_internal/parser.py | 11 +++++----- .../certbot_nginx/_internal/parser_obj.py | 2 +- certbot/certbot/_internal/auth_handler.py | 6 ++--- certbot/certbot/_internal/cert_manager.py | 2 +- certbot/certbot/_internal/cli.py | 8 +++---- certbot/certbot/_internal/client.py | 6 ++--- certbot/certbot/_internal/configuration.py | 18 +++++++-------- certbot/certbot/_internal/error_handler.py | 10 ++++----- certbot/certbot/_internal/hooks.py | 4 ++-- certbot/certbot/_internal/lock.py | 10 ++++----- certbot/certbot/_internal/main.py | 2 +- certbot/certbot/_internal/plugins/disco.py | 5 +---- certbot/certbot/_internal/plugins/manual.py | 12 +++++----- certbot/certbot/_internal/plugins/null.py | 2 +- .../certbot/_internal/plugins/standalone.py | 22 +++++++++---------- certbot/certbot/_internal/plugins/webroot.py | 20 ++++++++--------- certbot/certbot/_internal/renewal.py | 2 +- certbot/certbot/_internal/reporter.py | 2 +- certbot/certbot/_internal/storage.py | 2 +- certbot/certbot/compat/_path.py | 2 +- certbot/certbot/compat/filesystem.py | 7 +++--- certbot/certbot/compat/misc.py | 2 +- certbot/certbot/crypto_util.py | 2 +- certbot/certbot/display/util.py | 1 - certbot/certbot/interfaces.py | 2 +- certbot/certbot/ocsp.py | 6 ++--- certbot/certbot/plugins/common.py | 3 +-- certbot/certbot/plugins/dns_common.py | 8 +++---- certbot/certbot/plugins/dns_common_lexicon.py | 6 ++--- certbot/certbot/plugins/enhancements.py | 6 ++--- certbot/certbot/plugins/storage.py | 4 ++-- certbot/certbot/reverter.py | 6 ++--- certbot/certbot/tests/acme_util.py | 3 +-- certbot/certbot/tests/util.py | 2 +- certbot/certbot/util.py | 8 +++---- letshelp-certbot/letshelp_certbot/apache.py | 2 +- .../letshelp_certbot/magic_typing.py | 1 - .../letshelp_certbot/magic_typing_test.py | 4 ++-- 71 files changed, 183 insertions(+), 202 deletions(-) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 39c8d6269..f3fb19b42 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -54,7 +54,7 @@ class UnrecognizedChallenge(Challenge): object.__setattr__(self, "jobj", jobj) def to_partial_json(self): - return self.jobj # pylint: disable=no-member + return self.jobj @classmethod def from_json(cls, jobj): diff --git a/acme/acme/client.py b/acme/acme/client.py index f48ff40b2..3e03748b5 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -15,16 +15,16 @@ import requests from requests.adapters import HTTPAdapter from requests_toolbelt.adapters.source import SourceAddressAdapter import six -from six.moves import http_client # pylint: disable=import-error +from six.moves import http_client from acme import crypto_util from acme import errors from acme import jws from acme import messages -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Text # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict +from acme.magic_typing import List +from acme.magic_typing import Set +from acme.magic_typing import Text logger = logging.getLogger(__name__) @@ -36,7 +36,7 @@ if sys.version_info < (2, 7, 9): # pragma: no cover try: requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3() # type: ignore except AttributeError: - import urllib3.contrib.pyopenssl # pylint: disable=import-error + import urllib3.contrib.pyopenssl urllib3.contrib.pyopenssl.inject_into_urllib3() DEFAULT_NETWORK_TIMEOUT = 45 @@ -666,7 +666,7 @@ class ClientV2(ClientBase): response = self._post(self.directory['newOrder'], order) body = messages.Order.from_json(response.json()) authorizations = [] - for url in body.authorizations: # pylint: disable=not-an-iterable + for url in body.authorizations: authorizations.append(self._authzr_from_response(self._post_as_get(url), uri=url)) return messages.OrderResource( body=body, diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index 66dfc738c..dc8fedad0 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -11,10 +11,9 @@ from OpenSSL import crypto from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052 from acme import errors -from acme.magic_typing import Callable # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Callable +from acme.magic_typing import Tuple +from acme.magic_typing import Union logger = logging.getLogger(__name__) @@ -74,7 +73,7 @@ class SSLSocket(object): class FakeConnection(object): """Fake OpenSSL.SSL.Connection.""" - # pylint: disable=missing-docstring + # pylint: disable=missing-function-docstring def __init__(self, connection): self._wrapped = connection @@ -86,7 +85,7 @@ class SSLSocket(object): # OpenSSL.SSL.Connection.shutdown doesn't accept any args return self._wrapped.shutdown() - def accept(self): # pylint: disable=missing-docstring + def accept(self): # pylint: disable=missing-function-docstring sock, addr = self.sock.accept() context = SSL.Context(self.method) @@ -298,7 +297,6 @@ def dump_pyopenssl_chain(chain, filetype=crypto.FILETYPE_PEM): def _dump_cert(cert): if isinstance(cert, jose.ComparableX509): - # pylint: disable=protected-access cert = cert.wrapped return crypto.dump_certificate(filetype, cert) diff --git a/acme/acme/jws.py b/acme/acme/jws.py index 894e69f3d..9128f56b3 100644 --- a/acme/acme/jws.py +++ b/acme/acme/jws.py @@ -15,7 +15,7 @@ class Header(jose.Header): url = jose.Field('url', omitempty=True) @nonce.decoder - def nonce(value): # pylint: disable=missing-docstring,no-self-argument + def nonce(value): # pylint: disable=no-self-argument,missing-function-docstring try: return jose.decode_b64jose(value) except jose.DeserializationError as error: @@ -25,7 +25,7 @@ class Header(jose.Header): class Signature(jose.Signature): """ACME-specific Signature. Uses ACME-specific Header for customer fields.""" - __slots__ = jose.Signature._orig_slots # pylint: disable=no-member + __slots__ = jose.Signature._orig_slots # TODO: decoder/encoder should accept cls? Otherwise, subclassing # JSONObjectWithFields is tricky... diff --git a/acme/acme/magic_typing.py b/acme/acme/magic_typing.py index 5a6358c69..d6b1ff056 100644 --- a/acme/acme/magic_typing.py +++ b/acme/acme/magic_typing.py @@ -10,7 +10,6 @@ class TypingClass(object): try: # mypy doesn't respect modifying sys.modules from typing import * # pylint: disable=wildcard-import, unused-wildcard-import - # pylint: disable=unused-import from typing import Collection, IO # type: ignore # pylint: enable=unused-import except ImportError: diff --git a/acme/acme/messages.py b/acme/acme/messages.py index e82d12890..f8f4bfbe7 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -11,7 +11,7 @@ from acme import jws from acme import util try: - from collections.abc import Hashable # pylint: disable=no-name-in-module + from collections.abc import Hashable except ImportError: # pragma: no cover from collections import Hashable @@ -460,7 +460,6 @@ class ChallengeResource(Resource): @property def uri(self): """The URL of the challenge body.""" - # pylint: disable=function-redefined,no-member return self.body.uri @@ -488,7 +487,7 @@ class Authorization(ResourceBody): wildcard = jose.Field('wildcard', omitempty=True) @challenges.decoder - def challenges(value): # pylint: disable=missing-docstring,no-self-argument + def challenges(value): # pylint: disable=no-self-argument,missing-function-docstring return tuple(ChallengeBody.from_json(chall) for chall in value) @property @@ -585,7 +584,7 @@ class Order(ResourceBody): error = jose.Field('error', omitempty=True, decoder=Error.from_json) @identifiers.decoder - def identifiers(value): # pylint: disable=missing-docstring,no-self-argument + def identifiers(value): # pylint: disable=no-self-argument,missing-function-docstring return tuple(Identifier.from_json(identifier) for identifier in value) class OrderResource(ResourceWithURI): diff --git a/acme/acme/standalone.py b/acme/acme/standalone.py index cf0da4e86..236f2c234 100644 --- a/acme/acme/standalone.py +++ b/acme/acme/standalone.py @@ -5,19 +5,16 @@ import logging import socket import threading -from six.moves import BaseHTTPServer # type: ignore # pylint: disable=import-error -from six.moves import http_client # pylint: disable=import-error -from six.moves import socketserver # type: ignore # pylint: disable=import-error +from six.moves import BaseHTTPServer # type: ignore +from six.moves import http_client +from six.moves import socketserver # type: ignore from acme import challenges from acme import crypto_util -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List logger = logging.getLogger(__name__) -# six.moves.* | pylint: disable=no-member,attribute-defined-outside-init -# pylint: disable=no-init - class TLSServer(socketserver.TCPServer): """Generic TLS Server.""" @@ -30,7 +27,6 @@ class TLSServer(socketserver.TCPServer): self.address_family = socket.AF_INET self.certs = kwargs.pop("certs", {}) self.method = kwargs.pop( - # pylint: disable=protected-access "method", crypto_util._DEFAULT_SSL_METHOD) self.allow_reuse_address = kwargs.pop("allow_reuse_address", True) socketserver.TCPServer.__init__(self, *args, **kwargs) @@ -39,7 +35,7 @@ class TLSServer(socketserver.TCPServer): self.socket = crypto_util.SSLSocket( self.socket, certs=self.certs, method=self.method) - def server_bind(self): # pylint: disable=missing-docstring + def server_bind(self): self._wrap_sock() return socketserver.TCPServer.server_bind(self) @@ -178,7 +174,7 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): self.log_message("Incoming request") BaseHTTPServer.BaseHTTPRequestHandler.handle(self) - def do_GET(self): # pylint: disable=invalid-name,missing-docstring + def do_GET(self): # pylint: disable=invalid-name,missing-function-docstring if self.path == "/": self.handle_index() elif self.path.startswith("/" + challenges.HTTP01.URI_ROOT_PATH): diff --git a/certbot-apache/certbot_apache/_internal/configurator.py b/certbot-apache/certbot_apache/_internal/configurator.py index e9ed1f8ab..465237590 100644 --- a/certbot-apache/certbot_apache/_internal/configurator.py +++ b/certbot-apache/certbot_apache/_internal/configurator.py @@ -14,11 +14,11 @@ import zope.component import zope.interface from acme import challenges -from acme.magic_typing import DefaultDict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import DefaultDict +from acme.magic_typing import Dict +from acme.magic_typing import List +from acme.magic_typing import Set +from acme.magic_typing import Union from certbot import errors from certbot import interfaces from certbot import util @@ -792,7 +792,7 @@ class ApacheConfigurator(common.Installer): return util.get_filtered_names(all_names) - def get_name_from_ip(self, addr): # pylint: disable=no-self-use + def get_name_from_ip(self, addr): """Returns a reverse dns name if available. :param addr: IP Address @@ -1726,7 +1726,7 @@ class ApacheConfigurator(common.Installer): ###################################################################### # Enhancements ###################################################################### - def supported_enhancements(self): # pylint: disable=no-self-use + def supported_enhancements(self): """Returns currently supported enhancements.""" return ["redirect", "ensure-http-header", "staple-ocsp"] @@ -2292,7 +2292,7 @@ class ApacheConfigurator(common.Installer): vhost.enabled = True return - def enable_mod(self, mod_name, temp=False): # pylint: disable=unused-argument + def enable_mod(self, mod_name, temp=False): """Enables module in Apache. Both enables and reloads Apache so module is active. @@ -2350,7 +2350,7 @@ class ApacheConfigurator(common.Installer): error = str(err) raise errors.MisconfigurationError(error) - def config_test(self): # pylint: disable=no-self-use + def config_test(self): """Check the configuration of Apache for errors. :raises .errors.MisconfigurationError: If config_test fails @@ -2400,7 +2400,7 @@ class ApacheConfigurator(common.Installer): ########################################################################### # Challenges Section ########################################################################### - def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use + def get_chall_pref(self, unused_domain): """Return list of challenge preferences.""" return [challenges.HTTP01] diff --git a/certbot-apache/certbot_apache/_internal/entrypoint.py b/certbot-apache/certbot_apache/_internal/entrypoint.py index d43094976..e31e1f4eb 100644 --- a/certbot-apache/certbot_apache/_internal/entrypoint.py +++ b/certbot-apache/certbot_apache/_internal/entrypoint.py @@ -1,7 +1,7 @@ """ Entry point for Apache Plugin """ # Pylint does not like disutils.version when running inside a venv. # See: https://github.com/PyCQA/pylint/issues/73 -from distutils.version import LooseVersion # pylint: disable=no-name-in-module,import-error +from distutils.version import LooseVersion from certbot import util from certbot_apache._internal import configurator diff --git a/certbot-apache/certbot_apache/_internal/http_01.py b/certbot-apache/certbot_apache/_internal/http_01.py index c34abc2b4..5ea0ce8ec 100644 --- a/certbot-apache/certbot_apache/_internal/http_01.py +++ b/certbot-apache/certbot_apache/_internal/http_01.py @@ -1,8 +1,8 @@ """A class that performs HTTP-01 challenges for Apache""" import logging -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List +from acme.magic_typing import Set from certbot import errors from certbot.compat import filesystem from certbot.compat import os diff --git a/certbot-apache/certbot_apache/_internal/obj.py b/certbot-apache/certbot_apache/_internal/obj.py index 940bb6144..498766744 100644 --- a/certbot-apache/certbot_apache/_internal/obj.py +++ b/certbot-apache/certbot_apache/_internal/obj.py @@ -1,7 +1,7 @@ """Module contains classes used by the Apache Configurator.""" import re -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set from certbot.plugins import common diff --git a/certbot-apache/certbot_apache/_internal/override_centos.py b/certbot-apache/certbot_apache/_internal/override_centos.py index a3ef2d760..2ab160c2f 100644 --- a/certbot-apache/certbot_apache/_internal/override_centos.py +++ b/certbot-apache/certbot_apache/_internal/override_centos.py @@ -4,7 +4,7 @@ import logging import pkg_resources import zope.interface -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List from certbot import errors from certbot import interfaces from certbot import util diff --git a/certbot-apache/certbot_apache/_internal/parser.py b/certbot-apache/certbot_apache/_internal/parser.py index aae3dc6e4..992672913 100644 --- a/certbot-apache/certbot_apache/_internal/parser.py +++ b/certbot-apache/certbot_apache/_internal/parser.py @@ -7,9 +7,9 @@ import sys import six -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict +from acme.magic_typing import List +from acme.magic_typing import Set from certbot import errors from certbot.compat import os from certbot_apache._internal import apache_util @@ -321,7 +321,7 @@ class ApacheParser(object): for mod in matches: self.add_mod(mod.strip()) - def filter_args_num(self, matches, args): # pylint: disable=no-self-use + def filter_args_num(self, matches, args): """Filter out directives with specific number of arguments. This function makes the assumption that all related arguments are given @@ -715,7 +715,7 @@ class ApacheParser(object): return get_aug_path(arg) - def fnmatch_to_re(self, clean_fn_match): # pylint: disable=no-self-use + def fnmatch_to_re(self, clean_fn_match): """Method converts Apache's basic fnmatch to regular expression. Assumption - Configs are assumed to be well-formed and only writable by diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py index 3011b9823..7cb4e9722 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py @@ -5,7 +5,7 @@ import subprocess import zope.interface -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set from certbot._internal import configuration from certbot_compatibility_test import errors from certbot_compatibility_test import interfaces diff --git a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py index 2c3f880e0..d719f583b 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py +++ b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py @@ -15,8 +15,8 @@ from urllib3.util import connection from acme import challenges from acme import crypto_util from acme import messages -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List +from acme.magic_typing import Tuple from certbot import achallenges from certbot import errors as le_errors from certbot.tests import acme_util diff --git a/certbot-compatibility-test/certbot_compatibility_test/validator.py b/certbot-compatibility-test/certbot_compatibility_test/validator.py index 796ebbe9d..b527ce16b 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/validator.py +++ b/certbot-compatibility-test/certbot_compatibility_test/validator.py @@ -4,7 +4,7 @@ import socket import requests import six -from six.moves import xrange # pylint: disable=import-error, redefined-builtin +from six.moves import xrange from acme import crypto_util from acme import errors as acme_errors @@ -13,7 +13,6 @@ logger = logging.getLogger(__name__) class Validator(object): - # pylint: disable=no-self-use """Collection of functions to test a live webserver's configuration""" def certificate(self, cert, name, alt_host=None, port=443): diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py index 22124ac04..22dbcfa1f 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py @@ -38,7 +38,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add) add('credentials', help='Cloudflare credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the Cloudflare API.' diff --git a/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py b/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py index 2a0f12ea7..654c04c70 100644 --- a/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py +++ b/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py @@ -34,7 +34,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='CloudXNS credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the CloudXNS API.' diff --git a/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/dns_digitalocean.py b/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/dns_digitalocean.py index 7f3abbe31..75e25a848 100644 --- a/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/dns_digitalocean.py +++ b/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/dns_digitalocean.py @@ -30,7 +30,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add) add('credentials', help='DigitalOcean credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the DigitalOcean API.' diff --git a/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py b/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py index 8c48d31e7..9f7f100d7 100644 --- a/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py +++ b/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py @@ -34,7 +34,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='DNSimple credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the DNSimple API.' diff --git a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py index ed3146dce..4a1fcffc3 100644 --- a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py +++ b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py @@ -35,7 +35,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60) add('credentials', help='DNS Made Easy credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the DNS Made Easy API.' diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py index 76c0ed584..39deddae5 100644 --- a/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py +++ b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py @@ -34,7 +34,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='Gehirn Infrastructure Service credentials file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the Gehirn Infrastructure Service API.' diff --git a/certbot-dns-google/certbot_dns_google/_internal/dns_google.py b/certbot-dns-google/certbot_dns_google/_internal/dns_google.py index 3aa910b52..4eaed1783 100644 --- a/certbot-dns-google/certbot_dns_google/_internal/dns_google.py +++ b/certbot-dns-google/certbot_dns_google/_internal/dns_google.py @@ -45,7 +45,7 @@ class Authenticator(dns_common.DNSAuthenticator): 'required permissions.)').format(ACCT_URL, PERMISSIONS_URL), default=None) - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the Google Cloud DNS API.' @@ -148,7 +148,7 @@ class _GoogleClient(object): }, ] - changes = self.dns.changes() # changes | pylint: disable=no-member + changes = self.dns.changes() try: request = changes.create(project=self.project_id, managedZone=zone_id, body=data) @@ -213,7 +213,7 @@ class _GoogleClient(object): }, ] - changes = self.dns.changes() # changes | pylint: disable=no-member + changes = self.dns.changes() try: request = changes.create(project=self.project_id, managedZone=zone_id, body=data) @@ -264,7 +264,7 @@ class _GoogleClient(object): zone_dns_name_guesses = dns_common.base_domain_name_guesses(domain) - mz = self.dns.managedZones() # managedZones | pylint: disable=no-member + mz = self.dns.managedZones() for zone_name in zone_dns_name_guesses: try: request = mz.list(project=self.project_id, dnsName=zone_name + '.') diff --git a/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py b/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py index ea6046849..f7b3ec3d4 100644 --- a/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py +++ b/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py @@ -35,7 +35,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=1200) add('credentials', help='Linode credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the Linode API.' diff --git a/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py b/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py index 7c18c7131..d5b499c72 100644 --- a/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py +++ b/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py @@ -34,7 +34,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='LuaDNS credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the LuaDNS API.' diff --git a/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py b/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py index f5af37389..d328d80ce 100644 --- a/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py +++ b/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py @@ -34,7 +34,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='NS1 credentials file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the NS1 API.' diff --git a/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py b/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py index a495983f2..11ae6b8f0 100644 --- a/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py +++ b/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py @@ -34,7 +34,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) add('credentials', help='OVH credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the OVH API.' diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py index cb4d5addb..3bb4f444b 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py @@ -50,7 +50,7 @@ class Authenticator(dns_common.DNSAuthenticator): super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60) add('credentials', help='RFC 2136 credentials INI file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'RFC 2136 Dynamic Updates.' diff --git a/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py b/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py index 637558304..6250d2274 100644 --- a/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py +++ b/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py @@ -8,9 +8,9 @@ from botocore.exceptions import ClientError from botocore.exceptions import NoCredentialsError import zope.interface -from acme.magic_typing import DefaultDict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import DefaultDict +from acme.magic_typing import Dict +from acme.magic_typing import List from certbot import errors from certbot import interfaces from certbot.plugins import dns_common @@ -41,13 +41,13 @@ class Authenticator(dns_common.DNSAuthenticator): self.r53 = boto3.client("route53") self._resource_records = collections.defaultdict(list) # type: DefaultDict[str, List[Dict[str, str]]] - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return "Solve a DNS01 challenge using AWS Route53" def _setup_credentials(self): pass - def _perform(self, domain, validation_name, validation): # pylint: disable=missing-docstring + def _perform(self, domain, validation_name, validation): pass def perform(self, achalls): diff --git a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py index 25042bfc6..67cfb2e97 100644 --- a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py +++ b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py @@ -35,7 +35,7 @@ class Authenticator(dns_common.DNSAuthenticator): add, default_propagation_seconds=90) add('credentials', help='Sakura Cloud credentials file.') - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the Sakura Cloud API.' diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py index 52d8a08bc..459950aa1 100644 --- a/certbot-nginx/certbot_nginx/_internal/configurator.py +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -1,6 +1,6 @@ """Nginx Configuration""" # https://github.com/PyCQA/pylint/issues/73 -from distutils.version import LooseVersion # pylint: disable=no-name-in-module, import-error +from distutils.version import LooseVersion import logging import re import socket @@ -14,9 +14,9 @@ import zope.interface from acme import challenges from acme import crypto_util as acme_crypto_util -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict +from acme.magic_typing import List +from acme.magic_typing import Set from certbot import crypto_util from certbot import errors from certbot import interfaces @@ -696,7 +696,7 @@ class NginxConfigurator(common.Installer): ################################## # enhancement methods (IInstaller) ################################## - def supported_enhancements(self): # pylint: disable=no-self-use + def supported_enhancements(self): """Returns currently supported enhancements.""" return ['redirect', 'ensure-http-header', 'staple-ocsp'] @@ -915,7 +915,7 @@ class NginxConfigurator(common.Installer): """ nginx_restart(self.conf('ctl'), self.nginx_conf) - def config_test(self): # pylint: disable=no-self-use + def config_test(self): """Check the configuration of Nginx for errors. :raises .errors.MisconfigurationError: If config_test fails @@ -1090,7 +1090,7 @@ class NginxConfigurator(common.Installer): ########################################################################### # Challenges Section for IAuthenticator ########################################################################### - def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use + def get_chall_pref(self, unused_domain): """Return list of challenge preferences.""" return [challenges.HTTP01] diff --git a/certbot-nginx/certbot_nginx/_internal/http_01.py b/certbot-nginx/certbot_nginx/_internal/http_01.py index 97b111576..2f6458f87 100644 --- a/certbot-nginx/certbot_nginx/_internal/http_01.py +++ b/certbot-nginx/certbot_nginx/_internal/http_01.py @@ -3,7 +3,7 @@ import logging from acme import challenges -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List from certbot import errors from certbot.compat import os from certbot.plugins import common diff --git a/certbot-nginx/certbot_nginx/_internal/nginxparser.py b/certbot-nginx/certbot_nginx/_internal/nginxparser.py index 4fa1362a0..a8ac90427 100644 --- a/certbot-nginx/certbot_nginx/_internal/nginxparser.py +++ b/certbot-nginx/certbot_nginx/_internal/nginxparser.py @@ -20,7 +20,6 @@ import six logger = logging.getLogger(__name__) class RawNginxParser(object): - # pylint: disable=expression-not-assigned # pylint: disable=pointless-statement """A class that parses nginx configuration with pyparsing.""" diff --git a/certbot-nginx/certbot_nginx/_internal/parser.py b/certbot-nginx/certbot_nginx/_internal/parser.py index edb77a1c1..f71d7c018 100644 --- a/certbot-nginx/certbot_nginx/_internal/parser.py +++ b/certbot-nginx/certbot_nginx/_internal/parser.py @@ -8,11 +8,11 @@ import re import pyparsing import six -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict +from acme.magic_typing import List +from acme.magic_typing import Set +from acme.magic_typing import Tuple +from acme.magic_typing import Union from certbot import errors from certbot.compat import os from certbot_nginx._internal import nginxparser @@ -127,7 +127,6 @@ class NginxParser(object): return servers def get_vhosts(self): - # pylint: disable=cell-var-from-loop """Gets list of all 'virtual hosts' found in Nginx configuration. Technically this is a misnomer because Nginx does not have virtual hosts, it has 'server blocks'. diff --git a/certbot-nginx/certbot_nginx/_internal/parser_obj.py b/certbot-nginx/certbot_nginx/_internal/parser_obj.py index e03913887..61b31b2d5 100644 --- a/certbot-nginx/certbot_nginx/_internal/parser_obj.py +++ b/certbot-nginx/certbot_nginx/_internal/parser_obj.py @@ -6,7 +6,7 @@ import logging import six -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List from certbot import errors logger = logging.getLogger(__name__) diff --git a/certbot/certbot/_internal/auth_handler.py b/certbot/certbot/_internal/auth_handler.py index e4ad91247..7ea2a1de8 100644 --- a/certbot/certbot/_internal/auth_handler.py +++ b/certbot/certbot/_internal/auth_handler.py @@ -8,9 +8,9 @@ import zope.component from acme import challenges from acme import errors as acme_errors from acme import messages -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict +from acme.magic_typing import List +from acme.magic_typing import Tuple from certbot import achallenges from certbot import errors from certbot import interfaces diff --git a/certbot/certbot/_internal/cert_manager.py b/certbot/certbot/_internal/cert_manager.py index 298e7d269..e6cbd5c2c 100644 --- a/certbot/certbot/_internal/cert_manager.py +++ b/certbot/certbot/_internal/cert_manager.py @@ -7,7 +7,7 @@ import traceback import pytz import zope.component -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List from certbot import crypto_util from certbot import errors from certbot import interfaces diff --git a/certbot/certbot/_internal/cli.py b/certbot/certbot/_internal/cli.py index d5d498b4d..c0dbf7424 100644 --- a/certbot/certbot/_internal/cli.py +++ b/certbot/certbot/_internal/cli.py @@ -15,9 +15,9 @@ import zope.interface from zope.interface import interfaces as zope_interfaces from acme import challenges -from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any +from acme.magic_typing import Dict +from acme.magic_typing import Optional import certbot from certbot import crypto_util from certbot import errors @@ -505,7 +505,7 @@ class HelpfulArgumentParser(object): " and ".join(flag_default("config_files")))) # This is the only way to turn off overly verbose config flag documentation - self.parser._add_config_file_help = False # pylint: disable=protected-access + self.parser._add_config_file_help = False # Help that are synonyms for --help subcommands COMMANDS_TOPICS = ["command", "commands", "subcommand", "subcommands", "verbs"] diff --git a/certbot/certbot/_internal/client.py b/certbot/certbot/_internal/client.py index 9ce741e38..526f4200f 100644 --- a/certbot/certbot/_internal/client.py +++ b/certbot/certbot/_internal/client.py @@ -14,8 +14,8 @@ from acme import client as acme_client from acme import crypto_util as acme_crypto_util from acme import errors as acme_errors from acme import messages -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List +from acme.magic_typing import Optional import certbot from certbot import crypto_util from certbot import errors @@ -343,7 +343,7 @@ class Client(object): orderr = self._get_order_and_authorizations(csr.data, self.config.allow_subset_of_names) authzr = orderr.authorizations - auth_domains = set(a.body.identifier.value for a in authzr) # pylint: disable=not-an-iterable + auth_domains = set(a.body.identifier.value for a in authzr) successful_domains = [d for d in domains if d in auth_domains] # allow_subset_of_names is currently disabled for wildcard diff --git a/certbot/certbot/_internal/configuration.py b/certbot/certbot/_internal/configuration.py index f3db207db..f1e85f9fe 100644 --- a/certbot/certbot/_internal/configuration.py +++ b/certbot/certbot/_internal/configuration.py @@ -65,7 +65,7 @@ class NamespaceConfig(object): return (parsed.netloc + parsed.path).replace('/', os.path.sep) @property - def accounts_dir(self): # pylint: disable=missing-docstring + def accounts_dir(self): # pylint: disable=missing-function-docstring return self.accounts_dir_for_server_path(self.server_path) def accounts_dir_for_server_path(self, server_path): @@ -75,23 +75,23 @@ class NamespaceConfig(object): self.namespace.config_dir, constants.ACCOUNTS_DIR, server_path) @property - def backup_dir(self): # pylint: disable=missing-docstring + def backup_dir(self): # pylint: disable=missing-function-docstring return os.path.join(self.namespace.work_dir, constants.BACKUP_DIR) @property - def csr_dir(self): # pylint: disable=missing-docstring + def csr_dir(self): # pylint: disable=missing-function-docstring return os.path.join(self.namespace.config_dir, constants.CSR_DIR) @property - def in_progress_dir(self): # pylint: disable=missing-docstring + def in_progress_dir(self): # pylint: disable=missing-function-docstring return os.path.join(self.namespace.work_dir, constants.IN_PROGRESS_DIR) @property - def key_dir(self): # pylint: disable=missing-docstring + def key_dir(self): # pylint: disable=missing-function-docstring return os.path.join(self.namespace.config_dir, constants.KEY_DIR) @property - def temp_checkpoint_dir(self): # pylint: disable=missing-docstring + def temp_checkpoint_dir(self): # pylint: disable=missing-function-docstring return os.path.join( self.namespace.work_dir, constants.TEMP_CHECKPOINT_DIR) @@ -102,15 +102,15 @@ class NamespaceConfig(object): return type(self)(new_ns) @property - def default_archive_dir(self): # pylint: disable=missing-docstring + def default_archive_dir(self): # pylint: disable=missing-function-docstring return os.path.join(self.namespace.config_dir, constants.ARCHIVE_DIR) @property - def live_dir(self): # pylint: disable=missing-docstring + def live_dir(self): # pylint: disable=missing-function-docstring return os.path.join(self.namespace.config_dir, constants.LIVE_DIR) @property - def renewal_configs_dir(self): # pylint: disable=missing-docstring + def renewal_configs_dir(self): # pylint: disable=missing-function-docstring return os.path.join( self.namespace.config_dir, constants.RENEWAL_CONFIGS_DIR) diff --git a/certbot/certbot/_internal/error_handler.py b/certbot/certbot/_internal/error_handler.py index 5ca3cc57e..41c12eafa 100644 --- a/certbot/certbot/_internal/error_handler.py +++ b/certbot/certbot/_internal/error_handler.py @@ -4,11 +4,11 @@ import logging import signal import traceback -from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Callable # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any +from acme.magic_typing import Callable +from acme.magic_typing import Dict +from acme.magic_typing import List +from acme.magic_typing import Union from certbot import errors from certbot.compat import os diff --git a/certbot/certbot/_internal/hooks.py b/certbot/certbot/_internal/hooks.py index 25addd915..589c59e89 100644 --- a/certbot/certbot/_internal/hooks.py +++ b/certbot/certbot/_internal/hooks.py @@ -5,8 +5,8 @@ import logging from subprocess import PIPE from subprocess import Popen -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List +from acme.magic_typing import Set from certbot import errors from certbot import util from certbot.compat import filesystem diff --git a/certbot/certbot/_internal/lock.py b/certbot/certbot/_internal/lock.py index 7823eaac3..8f3c28006 100644 --- a/certbot/certbot/_internal/lock.py +++ b/certbot/certbot/_internal/lock.py @@ -2,15 +2,15 @@ import errno import logging -from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Optional from certbot import errors from certbot.compat import filesystem from certbot.compat import os try: - import fcntl # pylint: disable=import-error + import fcntl except ImportError: - import msvcrt # pylint: disable=import-error + import msvcrt POSIX_MODE = False else: POSIX_MODE = True @@ -115,10 +115,10 @@ class _BaseLockMechanism(object): """ return self._fd is not None - def acquire(self): # pylint: disable=missing-docstring + def acquire(self): # pylint: disable=missing-function-docstring pass # pragma: no cover - def release(self): # pylint: disable=missing-docstring + def release(self): # pylint: disable=missing-function-docstring pass # pragma: no cover diff --git a/certbot/certbot/_internal/main.py b/certbot/certbot/_internal/main.py index 72fcfca71..8674cd151 100644 --- a/certbot/certbot/_internal/main.py +++ b/certbot/certbot/_internal/main.py @@ -11,7 +11,7 @@ import josepy as jose import zope.component from acme import errors as acme_errors -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union import certbot from certbot import crypto_util from certbot import errors diff --git a/certbot/certbot/_internal/plugins/disco.py b/certbot/certbot/_internal/plugins/disco.py index d7d6390f7..d98a4cb0c 100644 --- a/certbot/certbot/_internal/plugins/disco.py +++ b/certbot/certbot/_internal/plugins/disco.py @@ -8,7 +8,7 @@ import six import zope.interface import zope.interface.verify -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict from certbot import errors from certbot import interfaces from certbot._internal import constants @@ -195,14 +195,12 @@ class PluginsRegistry(Mapping): # Pylint checks for super init, but also claims the super # has no __init__member - # pylint: disable=super-init-not-called self._plugins = collections.OrderedDict(sorted(six.iteritems(plugins))) @classmethod def find_all(cls): """Find plugins using setuptools entry points.""" plugins = {} # type: Dict[str, PluginEntryPoint] - # pylint: disable=not-callable entry_points = itertools.chain( pkg_resources.iter_entry_points( constants.SETUPTOOLS_PLUGINS_ENTRY_POINT), @@ -212,7 +210,6 @@ class PluginsRegistry(Mapping): plugin_ep = PluginEntryPoint(entry_point) assert plugin_ep.name not in plugins, ( "PREFIX_FREE_DISTRIBUTIONS messed up") - # providedBy | pylint: disable=no-member if interfaces.IPluginFactory.providedBy(plugin_ep.plugin_cls): plugins[plugin_ep.name] = plugin_ep else: # pragma: no cover diff --git a/certbot/certbot/_internal/plugins/manual.py b/certbot/certbot/_internal/plugins/manual.py index be6abaad4..3204fe1da 100644 --- a/certbot/certbot/_internal/plugins/manual.py +++ b/certbot/certbot/_internal/plugins/manual.py @@ -3,7 +3,7 @@ import zope.component import zope.interface from acme import challenges -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict from certbot import achallenges # pylint: disable=unused-import from certbot import errors from certbot import interfaces @@ -81,7 +81,7 @@ permitted by DNS standards.) add('public-ip-logging-ok', action='store_true', help='Automatically allows public IP logging (default: Ask)') - def prepare(self): # pylint: disable=missing-docstring + def prepare(self): # pylint: disable=missing-function-docstring if self.config.noninteractive_mode and not self.conf('auth-hook'): raise errors.PluginError( 'An authentication script must be provided with --{0} when ' @@ -97,17 +97,17 @@ permitted by DNS standards.) hook_prefix = self.option_name(name)[:-len('-hook')] hooks.validate_hook(hook, hook_prefix) - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return ( 'This plugin allows the user to customize setup for domain ' 'validation challenges either through shell scripts provided by ' 'the user or by performing the setup manually.') def get_chall_pref(self, domain): - # pylint: disable=missing-docstring,no-self-use,unused-argument + # pylint: disable=unused-argument,missing-function-docstring return [challenges.HTTP01, challenges.DNS01] - def perform(self, achalls): # pylint: disable=missing-docstring + def perform(self, achalls): # pylint: disable=missing-function-docstring self._verify_ip_logging_ok() if self.conf('auth-hook'): perform_achall = self._perform_achall_with_script @@ -170,7 +170,7 @@ permitted by DNS standards.) display.notification(msg, wrap=False, force_interactive=True) self.subsequent_any_challenge = True - def cleanup(self, achalls): # pylint: disable=missing-docstring + def cleanup(self, achalls): # pylint: disable=missing-function-docstring if self.conf('cleanup-hook'): for achall in achalls: env = self.env.pop(achall) diff --git a/certbot/certbot/_internal/plugins/null.py b/certbot/certbot/_internal/plugins/null.py index bf4615497..cf7c05a2b 100644 --- a/certbot/certbot/_internal/plugins/null.py +++ b/certbot/certbot/_internal/plugins/null.py @@ -18,7 +18,7 @@ class Installer(common.Plugin): description = "Null Installer" hidden = True - # pylint: disable=missing-docstring,no-self-use + # pylint: disable=missing-function-docstring def prepare(self): pass # pragma: no cover diff --git a/certbot/certbot/_internal/plugins/standalone.py b/certbot/certbot/_internal/plugins/standalone.py index 80421299e..bbb56178c 100644 --- a/certbot/certbot/_internal/plugins/standalone.py +++ b/certbot/certbot/_internal/plugins/standalone.py @@ -11,12 +11,12 @@ import zope.interface from acme import challenges from acme import standalone as acme_standalone -from acme.magic_typing import DefaultDict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import TYPE_CHECKING # pylint: disable=unused-import, no-name-in-module -from certbot import achallenges # pylint: disable=unused-import +from acme.magic_typing import DefaultDict +from acme.magic_typing import Dict +from acme.magic_typing import Set +from acme.magic_typing import Tuple +from acme.magic_typing import TYPE_CHECKING +from certbot import achallenges from certbot import errors from certbot import interfaces from certbot.plugins import common @@ -139,20 +139,20 @@ class Authenticator(common.Plugin): def add_parser_arguments(cls, add): pass # No additional argument for the standalone plugin parser - def more_info(self): # pylint: disable=missing-docstring + def more_info(self): # pylint: disable=missing-function-docstring return("This authenticator creates its own ephemeral TCP listener " "on the necessary port in order to respond to incoming " "http-01 challenges from the certificate authority. Therefore, " "it does not rely on any existing server program.") - def prepare(self): # pylint: disable=missing-docstring + def prepare(self): # pylint: disable=missing-function-docstring pass def get_chall_pref(self, domain): - # pylint: disable=unused-argument,missing-docstring + # pylint: disable=unused-argument,missing-function-docstring return [challenges.HTTP01] - def perform(self, achalls): # pylint: disable=missing-docstring + def perform(self, achalls): # pylint: disable=missing-function-docstring return [self._try_perform_single(achall) for achall in achalls] def _try_perform_single(self, achall): @@ -177,7 +177,7 @@ class Authenticator(common.Plugin): self.http_01_resources.add(resource) return servers, response - def cleanup(self, achalls): # pylint: disable=missing-docstring + def cleanup(self, achalls): # pylint: disable=missing-function-docstring # reduce self.served and close servers if no challenges are served for unused_servers, server_achalls in self.served.items(): for achall in achalls: diff --git a/certbot/certbot/_internal/plugins/webroot.py b/certbot/certbot/_internal/plugins/webroot.py index c7737e0d1..042b60656 100644 --- a/certbot/certbot/_internal/plugins/webroot.py +++ b/certbot/certbot/_internal/plugins/webroot.py @@ -9,11 +9,11 @@ import six import zope.component import zope.interface -from acme import challenges # pylint: disable=unused-import -from acme.magic_typing import DefaultDict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme import challenges +from acme.magic_typing import DefaultDict +from acme.magic_typing import Dict +from acme.magic_typing import List +from acme.magic_typing import Set from certbot import achallenges # pylint: disable=unused-import from certbot import errors from certbot import interfaces @@ -42,7 +42,7 @@ necessary validation resources to appropriate paths on the file system. It expects that there is some other HTTP server configured to serve all files under specified web root ({0}).""" - def more_info(self): # pylint: disable=missing-docstring,no-self-use + def more_info(self): # pylint: disable=missing-function-docstring return self.MORE_INFO.format(self.conf("path")) @classmethod @@ -64,7 +64,7 @@ to serve all files under specified web root ({0}).""" '{"example.com":"/var/www"}.') def get_chall_pref(self, domain): # pragma: no cover - # pylint: disable=missing-docstring,no-self-use,unused-argument + # pylint: disable=unused-argument,missing-function-docstring return [challenges.HTTP01] def __init__(self, *args, **kwargs): @@ -75,10 +75,10 @@ to serve all files under specified web root ({0}).""" # stack of dirs successfully created by this authenticator self._created_dirs = [] # type: List[str] - def prepare(self): # pylint: disable=missing-docstring + def prepare(self): # pylint: disable=missing-function-docstring pass - def perform(self, achalls): # pylint: disable=missing-docstring + def perform(self, achalls): # pylint: disable=missing-function-docstring self._set_webroots(achalls) self._create_challenge_dirs() @@ -213,7 +213,7 @@ to serve all files under specified web root ({0}).""" self.performed[root_path].add(achall) return response - def cleanup(self, achalls): # pylint: disable=missing-docstring + def cleanup(self, achalls): # pylint: disable=missing-function-docstring for achall in achalls: root_path = self.full_roots.get(achall.domain, None) if root_path is not None: diff --git a/certbot/certbot/_internal/renewal.py b/certbot/certbot/_internal/renewal.py index bf30404f5..fd23b0d18 100644 --- a/certbot/certbot/_internal/renewal.py +++ b/certbot/certbot/_internal/renewal.py @@ -13,7 +13,7 @@ import OpenSSL import six import zope.component -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List from certbot import crypto_util from certbot import errors from certbot import interfaces diff --git a/certbot/certbot/_internal/reporter.py b/certbot/certbot/_internal/reporter.py index 947f343d4..3b959f4e1 100644 --- a/certbot/certbot/_internal/reporter.py +++ b/certbot/certbot/_internal/reporter.py @@ -6,7 +6,7 @@ import logging import sys import textwrap -from six.moves import queue # type: ignore # pylint: disable=import-error +from six.moves import queue # type: ignore import zope.interface from certbot import interfaces diff --git a/certbot/certbot/_internal/storage.py b/certbot/certbot/_internal/storage.py index 964515eee..6a34355a8 100644 --- a/certbot/certbot/_internal/storage.py +++ b/certbot/certbot/_internal/storage.py @@ -883,7 +883,7 @@ class RenewableCert(interfaces.RenewableCert): return crypto_util.get_names_from_cert(f.read()) def ocsp_revoked(self, version=None): - # pylint: disable=no-self-use,unused-argument + # pylint: disable=unused-argument """Is the specified cert version revoked according to OCSP? Also returns True if the cert version is declared as intended diff --git a/certbot/certbot/compat/_path.py b/certbot/certbot/compat/_path.py index 5c5fe460e..44c5e300f 100644 --- a/certbot/certbot/compat/_path.py +++ b/certbot/certbot/compat/_path.py @@ -9,7 +9,7 @@ from __future__ import absolute_import # First round of wrapping: we import statically all public attributes exposed by the os.path # module. This allows in particular to have pylint, mypy, IDEs be aware that most of os.path # members are available in certbot.compat.path. -from os.path import * # type: ignore # pylint: disable=wildcard-import,unused-wildcard-import,redefined-builtin,os-module-forbidden +from os.path import * # type: ignore # pylint: disable=wildcard-import,unused-wildcard-import,os-module-forbidden # Second round of wrapping: we import dynamically all attributes from the os.path module that have # not yet been imported by the first round (static star import). diff --git a/certbot/certbot/compat/filesystem.py b/certbot/certbot/compat/filesystem.py index 65bb53f38..88c2916fa 100644 --- a/certbot/certbot/compat/filesystem.py +++ b/certbot/certbot/compat/filesystem.py @@ -5,12 +5,11 @@ import errno import os # pylint: disable=os-module-forbidden import stat -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List +from acme.magic_typing import Tuple # pylint: disable=unused-import +from acme.magic_typing import Union # pylint: disable=unused-import try: - # pylint: disable=import-error import ntsecuritycon import win32security import win32con diff --git a/certbot/certbot/compat/misc.py b/certbot/certbot/compat/misc.py index ffe611edb..956c56370 100644 --- a/certbot/certbot/compat/misc.py +++ b/certbot/certbot/compat/misc.py @@ -11,7 +11,7 @@ from certbot import errors from certbot.compat import os try: - from win32com.shell import shell as shellwin32 # pylint: disable=import-error + from win32com.shell import shell as shellwin32 POSIX_MODE = False except ImportError: # pragma: no cover POSIX_MODE = True diff --git a/certbot/certbot/crypto_util.py b/certbot/certbot/crypto_util.py index 9aae75991..9136445bc 100644 --- a/certbot/certbot/crypto_util.py +++ b/certbot/certbot/crypto_util.py @@ -23,7 +23,7 @@ import six import zope.component from acme import crypto_util as acme_crypto_util -from acme.magic_typing import IO # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import IO # pylint: disable=unused-import from certbot import errors from certbot import interfaces from certbot import util diff --git a/certbot/certbot/display/util.py b/certbot/certbot/display/util.py index ba2dd4ecf..05330b1a9 100644 --- a/certbot/certbot/display/util.py +++ b/certbot/certbot/display/util.py @@ -334,7 +334,6 @@ class FileDisplay(object): return self.input(message, default, cli_flag, force_interactive) def _scrub_checklist_input(self, indices, tags): - # pylint: disable=no-self-use """Validate input and transform indices to appropriate tags. :param list indices: input diff --git a/certbot/certbot/interfaces.py b/certbot/certbot/interfaces.py index e96712d23..81b41d1ec 100644 --- a/certbot/certbot/interfaces.py +++ b/certbot/certbot/interfaces.py @@ -4,7 +4,7 @@ import abc import six import zope.interface -# pylint: disable=no-self-argument,no-method-argument,no-init,inherit-non-class +# pylint: disable=no-self-argument,no-method-argument,inherit-non-class @six.add_metaclass(abc.ABCMeta) diff --git a/certbot/certbot/ocsp.py b/certbot/certbot/ocsp.py index 5c3cfdd59..6a95f26fa 100644 --- a/certbot/certbot/ocsp.py +++ b/certbot/certbot/ocsp.py @@ -16,8 +16,8 @@ from cryptography.hazmat.primitives import serialization import pytz import requests -from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Optional +from acme.magic_typing import Tuple from certbot import crypto_util from certbot import errors from certbot import util @@ -26,7 +26,7 @@ from certbot.interfaces import RenewableCert # pylint: disable=unused-import try: # Only cryptography>=2.5 has ocsp module # and signature_hash_algorithm attribute in OCSPResponse class - from cryptography.x509 import ocsp # pylint: disable=import-error, ungrouped-imports + from cryptography.x509 import ocsp # pylint: disable=ungrouped-imports getattr(ocsp.OCSPResponse, 'signature_hash_algorithm') except (ImportError, AttributeError): # pragma: no cover ocsp = None # type: ignore diff --git a/certbot/certbot/plugins/common.py b/certbot/certbot/plugins/common.py index 6fa1e76f8..7d21e6d0a 100644 --- a/certbot/certbot/plugins/common.py +++ b/certbot/certbot/plugins/common.py @@ -10,7 +10,7 @@ from josepy import util as jose_util import pkg_resources import zope.interface -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List from certbot import achallenges # pylint: disable=unused-import from certbot import crypto_util from certbot import errors @@ -74,7 +74,6 @@ class Plugin(object): """ # dummy function, doesn't check if dest.startswith(self.dest_namespace) def add(arg_name_no_prefix, *args, **kwargs): - # pylint: disable=missing-docstring return parser.add_argument( "--{0}{1}".format(option_namespace(name), arg_name_no_prefix), *args, **kwargs) diff --git a/certbot/certbot/plugins/dns_common.py b/certbot/certbot/plugins/dns_common.py index d31266434..245b7dc05 100644 --- a/certbot/certbot/plugins/dns_common.py +++ b/certbot/certbot/plugins/dns_common.py @@ -37,13 +37,13 @@ class DNSAuthenticator(common.Plugin): help='The number of seconds to wait for DNS to propagate before asking the ACME server ' 'to verify the DNS record.') - def get_chall_pref(self, unused_domain): # pylint: disable=missing-docstring,no-self-use + def get_chall_pref(self, unused_domain): # pylint: disable=missing-function-docstring return [challenges.DNS01] - def prepare(self): # pylint: disable=missing-docstring + def prepare(self): # pylint: disable=missing-function-docstring pass - def perform(self, achalls): # pylint: disable=missing-docstring + def perform(self, achalls): # pylint: disable=missing-function-docstring self._setup_credentials() self._attempt_cleanup = True @@ -66,7 +66,7 @@ class DNSAuthenticator(common.Plugin): return responses - def cleanup(self, achalls): # pylint: disable=missing-docstring + def cleanup(self, achalls): # pylint: disable=missing-function-docstring if self._attempt_cleanup: for achall in achalls: domain = achall.domain diff --git a/certbot/certbot/plugins/dns_common_lexicon.py b/certbot/certbot/plugins/dns_common_lexicon.py index 3e28a291b..c3d80ca29 100644 --- a/certbot/certbot/plugins/dns_common_lexicon.py +++ b/certbot/certbot/plugins/dns_common_lexicon.py @@ -4,9 +4,9 @@ import logging from requests.exceptions import HTTPError from requests.exceptions import RequestException -from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any +from acme.magic_typing import Dict +from acme.magic_typing import Union from certbot import errors from certbot.plugins import dns_common diff --git a/certbot/certbot/plugins/enhancements.py b/certbot/certbot/plugins/enhancements.py index f8d9db7dc..be9b7933d 100644 --- a/certbot/certbot/plugins/enhancements.py +++ b/certbot/certbot/plugins/enhancements.py @@ -3,9 +3,9 @@ import abc import six -from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any +from acme.magic_typing import Dict +from acme.magic_typing import List from certbot._internal import constants ENHANCEMENTS = ["redirect", "ensure-http-header", "ocsp-stapling"] diff --git a/certbot/certbot/plugins/storage.py b/certbot/certbot/plugins/storage.py index 7956295d2..fe3a97dd5 100644 --- a/certbot/certbot/plugins/storage.py +++ b/certbot/certbot/plugins/storage.py @@ -2,8 +2,8 @@ import json import logging -from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any +from acme.magic_typing import Dict from certbot import errors from certbot.compat import filesystem from certbot.compat import os diff --git a/certbot/certbot/reverter.py b/certbot/certbot/reverter.py index 47a77c80a..80ba0f599 100644 --- a/certbot/certbot/reverter.py +++ b/certbot/certbot/reverter.py @@ -195,7 +195,7 @@ class Reverter(object): with open(os.path.join(cp_dir, "CHANGES_SINCE"), "a") as notes_fd: notes_fd.write(save_notes) - def _read_and_append(self, filepath): # pylint: disable=no-self-use + def _read_and_append(self, filepath): """Reads the file lines and returns a file obj. Read the file returning the lines, and a pointer to the end of the file. @@ -250,7 +250,7 @@ class Reverter(object): raise errors.ReverterError( "Unable to remove directory: %s" % cp_dir) - def _run_undo_commands(self, filepath): # pylint: disable=no-self-use + def _run_undo_commands(self, filepath): """Run all commands in a file.""" # NOTE: csv module uses native strings. That is, bytes on Python 2 and # unicode on Python 3 @@ -413,7 +413,7 @@ class Reverter(object): "Incomplete or failed recovery for IN_PROGRESS checkpoint " "- %s" % self.config.in_progress_dir) - def _remove_contained_files(self, file_list): # pylint: disable=no-self-use + def _remove_contained_files(self, file_list): """Erase all files contained within file_list. :param str file_list: file containing list of file paths to be deleted diff --git a/certbot/certbot/tests/acme_util.py b/certbot/certbot/tests/acme_util.py index 3d560dcbc..f4a20ea86 100644 --- a/certbot/certbot/tests/acme_util.py +++ b/certbot/certbot/tests/acme_util.py @@ -27,7 +27,7 @@ def gen_combos(challbs): return tuple((i,) for i, _ in enumerate(challbs)) -def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name +def chall_to_challb(chall, status): """Return ChallengeBody from Challenge.""" kwargs = { "chall": chall, @@ -67,7 +67,6 @@ def gen_authzr(authz_status, domain, challs, statuses, combos=True): :param bool combos: Whether or not to add combinations """ - # pylint: disable=redefined-outer-name challbs = tuple( chall_to_challb(chall, status) for chall, status in six.moves.zip(challs, statuses) diff --git a/certbot/certbot/tests/util.py b/certbot/certbot/tests/util.py index a9870b0fd..8b28b1080 100644 --- a/certbot/certbot/tests/util.py +++ b/certbot/certbot/tests/util.py @@ -14,7 +14,7 @@ import mock import OpenSSL import pkg_resources import six -from six.moves import reload_module # pylint: disable=import-error +from six.moves import reload_module from certbot import interfaces from certbot import util diff --git a/certbot/certbot/util.py b/certbot/certbot/util.py index 0a47cd87a..aff2952f7 100644 --- a/certbot/certbot/util.py +++ b/certbot/certbot/util.py @@ -5,7 +5,7 @@ import argparse import atexit import collections from collections import OrderedDict -import distutils.version # pylint: disable=import-error,no-name-in-module +import distutils.version import errno import logging import platform @@ -17,8 +17,8 @@ import sys import configargparse import six -from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple +from acme.magic_typing import Union from certbot import errors from certbot._internal import constants from certbot._internal import lock @@ -26,7 +26,7 @@ from certbot.compat import filesystem from certbot.compat import os if sys.platform.startswith('linux'): - import distro # pylint: disable=import-error + import distro _USE_DISTRO = True else: _USE_DISTRO = False diff --git a/letshelp-certbot/letshelp_certbot/apache.py b/letshelp-certbot/letshelp_certbot/apache.py index ebe4e3671..a5947399a 100755 --- a/letshelp-certbot/letshelp_certbot/apache.py +++ b/letshelp-certbot/letshelp_certbot/apache.py @@ -16,7 +16,7 @@ import textwrap import six -from letshelp_certbot.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from letshelp_certbot.magic_typing import List _DESCRIPTION = """ Let's Help is a simple script you can run to help out the Certbot diff --git a/letshelp-certbot/letshelp_certbot/magic_typing.py b/letshelp-certbot/letshelp_certbot/magic_typing.py index 5a6358c69..d6b1ff056 100644 --- a/letshelp-certbot/letshelp_certbot/magic_typing.py +++ b/letshelp-certbot/letshelp_certbot/magic_typing.py @@ -10,7 +10,6 @@ class TypingClass(object): try: # mypy doesn't respect modifying sys.modules from typing import * # pylint: disable=wildcard-import, unused-wildcard-import - # pylint: disable=unused-import from typing import Collection, IO # type: ignore # pylint: enable=unused-import except ImportError: diff --git a/letshelp-certbot/letshelp_certbot/magic_typing_test.py b/letshelp-certbot/letshelp_certbot/magic_typing_test.py index 200ca03b8..41bc245fa 100644 --- a/letshelp-certbot/letshelp_certbot/magic_typing_test.py +++ b/letshelp-certbot/letshelp_certbot/magic_typing_test.py @@ -18,7 +18,7 @@ class MagicTypingTest(unittest.TestCase): sys.modules['typing'] = typing_class_mock if 'letshelp_certbot.magic_typing' in sys.modules: del sys.modules['letshelp_certbot.magic_typing'] # pragma: no cover - from letshelp_certbot.magic_typing import Text # pylint: disable=no-name-in-module + from letshelp_certbot.magic_typing import Text self.assertEqual(Text, text_mock) del sys.modules['letshelp_certbot.magic_typing'] sys.modules['typing'] = temp_typing @@ -31,7 +31,7 @@ class MagicTypingTest(unittest.TestCase): sys.modules['typing'] = None if 'letshelp_certbot.magic_typing' in sys.modules: del sys.modules['letshelp_certbot.magic_typing'] # pragma: no cover - from letshelp_certbot.magic_typing import Text # pylint: disable=no-name-in-module + from letshelp_certbot.magic_typing import Text self.assertTrue(Text is None) del sys.modules['letshelp_certbot.magic_typing'] sys.modules['typing'] = temp_typing From 3f52695ec27a4b5fdfe675982e959f7fffbccb18 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 14 Feb 2020 17:18:53 -0800 Subject: [PATCH 092/122] more robustly stop patches (#7763) --- certbot/tests/cert_manager_test.py | 8 +++--- certbot/tests/main_test.py | 40 +++++++++++++----------------- 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index 81134f02f..eb8005b2b 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -564,14 +564,12 @@ class GetCertnameTest(unittest.TestCase): """Tests for certbot._internal.cert_manager.""" def setUp(self): - self.get_utility_patch = test_util.patch_get_utility() - self.mock_get_utility = self.get_utility_patch.start() + get_utility_patch = test_util.patch_get_utility() + self.mock_get_utility = get_utility_patch.start() + self.addCleanup(get_utility_patch.stop) self.config = mock.MagicMock() self.config.certname = None - def tearDown(self): - self.get_utility_patch.stop() - @mock.patch('certbot._internal.storage.renewal_conf_files') @mock.patch('certbot._internal.storage.lineagename_for_filename') def test_get_certnames(self, mock_name, mock_files): diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index 7b22c81d6..7ebe5e66a 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -62,7 +62,7 @@ class RunTest(test_util.ConfigTestCase): def setUp(self): super(RunTest, self).setUp() self.domain = 'example.org' - self.patches = [ + patches = [ mock.patch('certbot._internal.main._get_and_save_cert'), mock.patch('certbot._internal.main.display_ops.success_installation'), mock.patch('certbot._internal.main.display_ops.success_renewal'), @@ -71,17 +71,15 @@ class RunTest(test_util.ConfigTestCase): mock.patch('certbot._internal.main._report_new_cert'), mock.patch('certbot._internal.main._find_cert')] - self.mock_auth = self.patches[0].start() - self.mock_success_installation = self.patches[1].start() - self.mock_success_renewal = self.patches[2].start() - self.mock_init = self.patches[3].start() - self.mock_suggest_donation = self.patches[4].start() - self.mock_report_cert = self.patches[5].start() - self.mock_find_cert = self.patches[6].start() - - def tearDown(self): - for patch in self.patches: - patch.stop() + self.mock_auth = patches[0].start() + self.mock_success_installation = patches[1].start() + self.mock_success_renewal = patches[2].start() + self.mock_init = patches[3].start() + self.mock_suggest_donation = patches[4].start() + self.mock_report_cert = patches[5].start() + self.mock_find_cert = patches[6].start() + for patch in patches: + self.addCleanup(patch.stop) def _call(self): args = '-a webroot -i null -d {0}'.format(self.domain).split() @@ -243,16 +241,18 @@ class RevokeTest(test_util.TempDirTestCase): with open(self.tmp_cert_path, 'r') as f: self.tmp_cert = (self.tmp_cert_path, f.read()) - self.patches = [ + patches = [ mock.patch('acme.client.BackwardsCompatibleClientV2'), mock.patch('certbot._internal.client.Client'), mock.patch('certbot._internal.main._determine_account'), mock.patch('certbot._internal.main.display_ops.success_revocation') ] - self.mock_acme_client = self.patches[0].start() - self.patches[1].start() - self.mock_determine_account = self.patches[2].start() - self.mock_success_revoke = self.patches[3].start() + self.mock_acme_client = patches[0].start() + patches[1].start() + self.mock_determine_account = patches[2].start() + self.mock_success_revoke = patches[3].start() + for patch in patches: + self.addCleanup(patch.stop) from certbot._internal.account import Account @@ -265,12 +265,6 @@ class RevokeTest(test_util.TempDirTestCase): self.mock_determine_account.return_value = (self.acc, None) - def tearDown(self): - super(RevokeTest, self).tearDown() - - for patch in self.patches: - patch.stop() - def _call(self, args=None): if not args: args = 'revoke --cert-path={0} ' From fd64c8c33b2176e6569d64d30776bd5fc9fd3820 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 14 Feb 2020 17:19:19 -0800 Subject: [PATCH 093/122] Remove letshelp-certbot (#7761) * remove references to letshelp * remove letshelp files * Remove line continuation Co-authored-by: ohemorange --- certbot/docs/packaging.rst | 2 +- letshelp-certbot/LICENSE.txt | 190 ----------- letshelp-certbot/MANIFEST.in | 4 - letshelp-certbot/README.rst | 1 - letshelp-certbot/docs/.gitignore | 1 - letshelp-certbot/docs/Makefile | 192 ----------- letshelp-certbot/docs/_static/.gitignore | 0 letshelp-certbot/docs/_templates/.gitignore | 0 letshelp-certbot/docs/api.rst | 8 - letshelp-certbot/docs/api/index.rst | 11 - letshelp-certbot/docs/conf.py | 310 ----------------- letshelp-certbot/docs/index.rst | 27 -- letshelp-certbot/docs/make.bat | 263 --------------- letshelp-certbot/letshelp_certbot/__init__.py | 1 - letshelp-certbot/letshelp_certbot/apache.py | 314 ------------------ .../letshelp_certbot/apache_test.py | 241 -------------- .../letshelp_certbot/magic_typing.py | 16 - .../letshelp_certbot/magic_typing_test.py | 41 --- .../testdata/mods-available/ssl.load | 2 - .../testdata/mods-enabled/ssl.load | 1 - .../testdata/super_secret_file.txt | 1 - .../testdata/uncommonly_named_k3y | 6 - .../testdata/uncommonly_named_p4sswd | 1 - .../readthedocs.org.requirements.txt | 10 - letshelp-certbot/setup.cfg | 2 - letshelp-certbot/setup.py | 58 ---- linter_plugin.py | 2 +- mypy.ini | 3 - tools/_venv_common.py | 1 - tools/install_and_test.py | 2 +- tox.cover.py | 5 +- tox.ini | 4 +- 32 files changed, 6 insertions(+), 1714 deletions(-) delete mode 100644 letshelp-certbot/LICENSE.txt delete mode 100644 letshelp-certbot/MANIFEST.in delete mode 100644 letshelp-certbot/README.rst delete mode 100644 letshelp-certbot/docs/.gitignore delete mode 100644 letshelp-certbot/docs/Makefile delete mode 100644 letshelp-certbot/docs/_static/.gitignore delete mode 100644 letshelp-certbot/docs/_templates/.gitignore delete mode 100644 letshelp-certbot/docs/api.rst delete mode 100644 letshelp-certbot/docs/api/index.rst delete mode 100644 letshelp-certbot/docs/conf.py delete mode 100644 letshelp-certbot/docs/index.rst delete mode 100644 letshelp-certbot/docs/make.bat delete mode 100644 letshelp-certbot/letshelp_certbot/__init__.py delete mode 100755 letshelp-certbot/letshelp_certbot/apache.py delete mode 100644 letshelp-certbot/letshelp_certbot/apache_test.py delete mode 100644 letshelp-certbot/letshelp_certbot/magic_typing.py delete mode 100644 letshelp-certbot/letshelp_certbot/magic_typing_test.py delete mode 100644 letshelp-certbot/letshelp_certbot/testdata/mods-available/ssl.load delete mode 120000 letshelp-certbot/letshelp_certbot/testdata/mods-enabled/ssl.load delete mode 100644 letshelp-certbot/letshelp_certbot/testdata/super_secret_file.txt delete mode 100644 letshelp-certbot/letshelp_certbot/testdata/uncommonly_named_k3y delete mode 100644 letshelp-certbot/letshelp_certbot/testdata/uncommonly_named_p4sswd delete mode 100644 letshelp-certbot/readthedocs.org.requirements.txt delete mode 100644 letshelp-certbot/setup.cfg delete mode 100644 letshelp-certbot/setup.py diff --git a/certbot/docs/packaging.rst b/certbot/docs/packaging.rst index 7b0b1d41a..d2a94dbe7 100644 --- a/certbot/docs/packaging.rst +++ b/certbot/docs/packaging.rst @@ -38,7 +38,7 @@ Notes for package maintainers 0. Please use our tagged releases, not ``master``! -1. Do not package ``certbot-compatibility-test`` or ``letshelp-certbot`` - it's only used internally. +1. Do not package ``certbot-compatibility-test`` as it's only used internally. 2. To run tests on our packages, you should use ``python setup.py test``. Doing things like running ``pytest`` directly on our package files may not work because Certbot relies on setuptools to register and find its plugins. diff --git a/letshelp-certbot/LICENSE.txt b/letshelp-certbot/LICENSE.txt deleted file mode 100644 index 981c46c9f..000000000 --- a/letshelp-certbot/LICENSE.txt +++ /dev/null @@ -1,190 +0,0 @@ - Copyright 2015 Electronic Frontier Foundation and others - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/letshelp-certbot/MANIFEST.in b/letshelp-certbot/MANIFEST.in deleted file mode 100644 index 623392f28..000000000 --- a/letshelp-certbot/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -include LICENSE.txt -include README.rst -recursive-include docs * -recursive-include letshelp_certbot/testdata * diff --git a/letshelp-certbot/README.rst b/letshelp-certbot/README.rst deleted file mode 100644 index bbe2f2570..000000000 --- a/letshelp-certbot/README.rst +++ /dev/null @@ -1 +0,0 @@ -Let's help Certbot client diff --git a/letshelp-certbot/docs/.gitignore b/letshelp-certbot/docs/.gitignore deleted file mode 100644 index ba65b13af..000000000 --- a/letshelp-certbot/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/_build/ diff --git a/letshelp-certbot/docs/Makefile b/letshelp-certbot/docs/Makefile deleted file mode 100644 index 4b392ab8d..000000000 --- a/letshelp-certbot/docs/Makefile +++ /dev/null @@ -1,192 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/letshelp-certbot.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/letshelp-certbot.qhc" - -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/letshelp-certbot" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/letshelp-certbot" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/letshelp-certbot/docs/_static/.gitignore b/letshelp-certbot/docs/_static/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/letshelp-certbot/docs/_templates/.gitignore b/letshelp-certbot/docs/_templates/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/letshelp-certbot/docs/api.rst b/letshelp-certbot/docs/api.rst deleted file mode 100644 index 8668ec5d8..000000000 --- a/letshelp-certbot/docs/api.rst +++ /dev/null @@ -1,8 +0,0 @@ -================= -API Documentation -================= - -.. toctree:: - :glob: - - api/** diff --git a/letshelp-certbot/docs/api/index.rst b/letshelp-certbot/docs/api/index.rst deleted file mode 100644 index 5ced5f501..000000000 --- a/letshelp-certbot/docs/api/index.rst +++ /dev/null @@ -1,11 +0,0 @@ -:mod:`letshelp_certbot` ---------------------------- - -.. automodule:: letshelp_certbot - :members: - -:mod:`letshelp_certbot.apache` -================================== - -.. automodule:: letshelp_certbot.apache - :members: diff --git a/letshelp-certbot/docs/conf.py b/letshelp-certbot/docs/conf.py deleted file mode 100644 index b4289a345..000000000 --- a/letshelp-certbot/docs/conf.py +++ /dev/null @@ -1,310 +0,0 @@ -# -*- coding: utf-8 -*- -# -# letshelp-certbot documentation build configuration file, created by -# sphinx-quickstart on Sun Oct 18 13:40:19 2015. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import os -import shlex -import sys - -here = os.path.abspath(os.path.dirname(__file__)) - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath(os.path.join(here, '..'))) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.viewcode', -] - -autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'letshelp-certbot' -copyright = u'2014-2015, Let\'s Encrypt Project' -author = u'Certbot Project' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0' -# The full version, including alpha/beta/rc tags. -release = '0' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = 'en' - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -default_role = 'py:obj' - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. - -# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs -# on_rtd is whether we are on readthedocs.org -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' -if not on_rtd: # only import and set the theme if we're building docs locally - import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] -# otherwise, readthedocs.org uses their theme by default, so no need to specify it - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'letshelp-certbotdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - #'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - #'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - #'preamble': '', - - # Latex figure (float) alignment - #'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'letshelp-certbot.tex', u'letshelp-certbot Documentation', - u'Certbot Project', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'letshelp-certbot', u'letshelp-certbot Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'letshelp-certbot', u'letshelp-certbot Documentation', - author, 'letshelp-certbot', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False - - -intersphinx_mapping = { - 'python': ('https://docs.python.org/', None), - 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), - 'certbot': ('https://certbot.eff.org/docs/', None), -} diff --git a/letshelp-certbot/docs/index.rst b/letshelp-certbot/docs/index.rst deleted file mode 100644 index 678d9be2e..000000000 --- a/letshelp-certbot/docs/index.rst +++ /dev/null @@ -1,27 +0,0 @@ -.. letshelp-certbot documentation master file, created by - sphinx-quickstart on Sun Oct 18 13:40:19 2015. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to letshelp-certbot's documentation! -================================================ - -Contents: - -.. toctree:: - :maxdepth: 2 - - -.. toctree:: - :maxdepth: 1 - - api - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/letshelp-certbot/docs/make.bat b/letshelp-certbot/docs/make.bat deleted file mode 100644 index 0229b4f69..000000000 --- a/letshelp-certbot/docs/make.bat +++ /dev/null @@ -1,263 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 2> nul -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\letshelp-certbot.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\letshelp-certbot.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/letshelp-certbot/letshelp_certbot/__init__.py b/letshelp-certbot/letshelp_certbot/__init__.py deleted file mode 100644 index 6882a19d4..000000000 --- a/letshelp-certbot/letshelp_certbot/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tools for submitting server configurations""" diff --git a/letshelp-certbot/letshelp_certbot/apache.py b/letshelp-certbot/letshelp_certbot/apache.py deleted file mode 100755 index a5947399a..000000000 --- a/letshelp-certbot/letshelp_certbot/apache.py +++ /dev/null @@ -1,314 +0,0 @@ -#!/usr/bin/env python -"""Certbot Apache configuration submission script""" - -from __future__ import print_function - -import argparse -import atexit -import os -import re -import shutil -import subprocess -import sys -import tarfile -import tempfile -import textwrap - -import six - -from letshelp_certbot.magic_typing import List - -_DESCRIPTION = """ -Let's Help is a simple script you can run to help out the Certbot -project. Since Certbot will support automatically configuring HTTPS on -many servers, we want to test this functionality on as many configurations as -possible. This script will create a sanitized copy of your Apache -configuration, notifying you of the files that have been selected. If (and only -if) you approve this selection, these files will be sent to the Certbot -developers. - -""" - - -_NO_APACHECTL = """ -Unable to find `apachectl` which is required for this script to work. If it is -installed, please run this script again with the --apache-ctl command line -argument and the path to the binary. - -""" - - -# Keywords likely to be found in filenames of sensitive files -_SENSITIVE_FILENAME_REGEX = re.compile(r"^(?!.*proxy_fdpass).*pass.*$|private|" - r"secret|^(?!.*certbot).*cert.*$|crt|" - r"key|rsa|dsa|pw|\.pem|\.der|\.p12|" - r"\.pfx|\.p7b") - - -def make_and_verify_selection(server_root, temp_dir): - """Copies server_root to temp_dir and verifies selection with the user - - :param str server_root: Path to the Apache server root - :param str temp_dir: Path to the temporary directory to copy files to - - """ - copied_files, copied_dirs = copy_config(server_root, temp_dir) - - print(textwrap.fill("A secure copy of the files that have been selected " - "for submission has been created under {0}. All " - "comments have been removed and the files are only " - "accessible by the current user. A list of the files " - "that have been included is shown below. Please make " - "sure that this selection does not contain private " - "keys, passwords, or any other sensitive " - "information.".format(temp_dir))) - print("\nFiles:") - for copied_file in copied_files: - print(copied_file) - print("Directories (including all contained files):") - for copied_dir in copied_dirs: - print(copied_dir) - - sys.stdout.write("\nIs it safe to submit these files? ") - while True: - ans = six.moves.input("(Y)es/(N)o: ").lower() - if ans.startswith("y"): - return - if ans.startswith("n"): - sys.exit("Your files were not submitted") - - -def copy_config(server_root, temp_dir): - """Safely copies server_root to temp_dir and returns copied files - - :param str server_root: Absolute path to the Apache server root - :param str temp_dir: Path to the temporary directory to copy files to - - :returns: List of copied files and a list of leaf directories where - all contained files were copied - :rtype: `tuple` of `list` of `str` - - """ - copied_files = [] # type: List[str] - copied_dirs = [] # type: List[str] - dir_len = len(os.path.dirname(server_root)) - - for config_path, config_dirs, config_files in os.walk(server_root): - temp_path = os.path.join(temp_dir, config_path[dir_len + 1:]) - os.mkdir(temp_path) - - copied_all = True - copied_files_in_current_dir = [] - for config_file in config_files: - config_file_path = os.path.join(config_path, config_file) - temp_file_path = os.path.join(temp_path, config_file) - if os.path.islink(config_file_path): - os.symlink(os.readlink(config_file_path), temp_file_path) - elif safe_config_file(config_file_path): - copy_file_without_comments(config_file_path, temp_file_path) - copied_files_in_current_dir.append(config_file_path) - else: - copied_all = False - - # If copied all files in leaf directory - if copied_all and not config_dirs: - copied_dirs.append(config_path) - else: - copied_files += copied_files_in_current_dir - - return copied_files, copied_dirs - - -def copy_file_without_comments(source, destination): - """Copies source to destination, removing comments - - :param str source: Path to the file to be copied - :param str destination: Path where source should be copied to - - """ - with open(source, "r") as infile: - with open(destination, "w") as outfile: - for line in infile: - if not (line.isspace() or line.lstrip().startswith("#")): - outfile.write(line) - - -def safe_config_file(config_file): - """Returns True if config_file can be safely copied - - :param str config_file: Path to an Apache configuration file - - :returns: True if config_file can be safely copied - :rtype: bool - - """ - config_file_lower = config_file.lower() - if _SENSITIVE_FILENAME_REGEX.search(config_file_lower): - return False - - proc = subprocess.Popen(["file", config_file], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) - file_output, _ = proc.communicate() - - if "ASCII" in file_output: - possible_password_file = empty_or_all_comments = True - with open(config_file) as config_fd: - for line in config_fd: - if not (line.isspace() or line.lstrip().startswith("#")): - empty_or_all_comments = False - if line.startswith("-----BEGIN"): - return False - if ":" not in line: - possible_password_file = False - # If file isn't empty or commented out and could be a password file, - # don't include it in selection. It is safe to include the file if - # it consists solely of comments because comments are removed before - # submission. - return empty_or_all_comments or not possible_password_file - - return False - - -def setup_tempdir(args): - """Creates a temporary directory and necessary files for config - - :param argparse.Namespace args: Parsed command line arguments - - :returns: Path to temporary directory - :rtype: str - - """ - tempdir = tempfile.mkdtemp() - - with open(os.path.join(tempdir, "config_file"), "w") as config_fd: - config_fd.write(args.config_file + "\n") - - proc = subprocess.Popen([args.apache_ctl, "-v"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) - with open(os.path.join(tempdir, "version"), "w") as version_fd: - version_fd.write(proc.communicate()[0]) - - proc = subprocess.Popen([args.apache_ctl, "-d", args.server_root, "-f", - args.config_file, "-M"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) - with open(os.path.join(tempdir, "modules"), "w") as modules_fd: - modules_fd.write(proc.communicate()[0]) - - proc = subprocess.Popen([args.apache_ctl, "-d", args.server_root, "-f", - args.config_file, "-t", "-D", "DUMP_VHOSTS"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) - with open(os.path.join(tempdir, "vhosts"), "w") as vhosts_fd: - vhosts_fd.write(proc.communicate()[0]) - - return tempdir - - -def verify_config(args): - """Verifies server_root and config_file specify a valid config - - :param argparse.Namespace args: Parsed command line arguments - - """ - with open(os.devnull, "w") as devnull: - try: - subprocess.check_call([args.apache_ctl, "-d", args.server_root, - "-f", args.config_file, "-t"], - stdout=devnull, stderr=subprocess.STDOUT) - except OSError: - sys.exit(_NO_APACHECTL) - except subprocess.CalledProcessError: - sys.exit("Syntax check from apachectl failed") - - -def locate_config(apache_ctl): - """Uses the apachectl binary to find configuration files - - :param str apache_ctl: Path to `apachectl` binary - - - :returns: Path to Apache server root and main configuration file - :rtype: `tuple` of `str` - - """ - try: - proc = subprocess.Popen([apache_ctl, "-V"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) - output, _ = proc.communicate() - except OSError: - sys.exit(_NO_APACHECTL) - - server_root = config_file = "" - for line in output.splitlines(): - # Relevant output lines are of the form: -D DIRECTIVE="VALUE" - if "HTTPD_ROOT" in line: - server_root = line[line.find('"') + 1:-1] - elif "SERVER_CONFIG_FILE" in line: - config_file = line[line.find('"') + 1:-1] - - if not (server_root and config_file): - sys.exit("Unable to locate Apache configuration. Please run this " - "script again and specify --server-root and --config-file") - - return server_root, config_file - - -def get_args(): - """Parses command line arguments - - :returns: Parsed command line options - :rtype: argparse.Namespace - - """ - parser = argparse.ArgumentParser(description=_DESCRIPTION) - parser.add_argument("-c", "--apache-ctl", default="apachectl", - help="path to the `apachectl` binary") - parser.add_argument("-d", "--server-root", - help=("location of the root directory of your Apache " - "configuration")) - parser.add_argument("-f", "--config-file", - help=("location of your main Apache configuration " - "file relative to the server root")) - args = parser.parse_args() - - # args.server_root XOR args.config_file - if bool(args.server_root) != bool(args.config_file): - sys.exit("If either --server-root and --config-file are specified, " - "they both must be included") - elif args.server_root and args.config_file: - args.server_root = os.path.abspath(args.server_root) - args.config_file = os.path.abspath(args.config_file) - - if args.config_file.startswith(args.server_root): - args.config_file = args.config_file[len(args.server_root) + 1:] - else: - sys.exit("This script expects the Apache configuration file to be " - "inside the server root") - - return args - - -def main(): - """Main script execution""" - args = get_args() - if args.server_root is None: - args.server_root, args.config_file = locate_config(args.apache_ctl) - - verify_config(args) - tempdir = setup_tempdir(args) - atexit.register(lambda: shutil.rmtree(tempdir)) - make_and_verify_selection(args.server_root, tempdir) - - tarpath = os.path.join(tempdir, "config.tar.gz") - with tarfile.open(tarpath, mode="w:gz") as tar: - tar.add(tempdir, arcname=".") - - # TODO: Submit tarpath - - -if __name__ == "__main__": - main() # pragma: no cover diff --git a/letshelp-certbot/letshelp_certbot/apache_test.py b/letshelp-certbot/letshelp_certbot/apache_test.py deleted file mode 100644 index 0853046b4..000000000 --- a/letshelp-certbot/letshelp_certbot/apache_test.py +++ /dev/null @@ -1,241 +0,0 @@ -"""Tests for letshelp.letshelp_certbot_apache.py""" -import argparse -import functools -import os -import subprocess -import tarfile -import tempfile -import unittest - -# six is used in mock.patch() -import mock -import pkg_resources -import six # pylint: disable=unused-import - -import letshelp_certbot.apache as letshelp_le_apache - -_PARTIAL_CONF_PATH = os.path.join("mods-available", "ssl.load") -_PARTIAL_LINK_PATH = os.path.join("mods-enabled", "ssl.load") -_CONFIG_FILE = pkg_resources.resource_filename( - __name__, os.path.join("testdata", _PARTIAL_CONF_PATH)) -_PASSWD_FILE = pkg_resources.resource_filename( - __name__, os.path.join("testdata", "uncommonly_named_p4sswd")) -_KEY_FILE = pkg_resources.resource_filename( - __name__, os.path.join("testdata", "uncommonly_named_k3y")) -_SECRET_FILE = pkg_resources.resource_filename( - __name__, os.path.join("testdata", "super_secret_file.txt")) - - -_MODULE_NAME = "letshelp_certbot.apache" - - -_COMPILE_SETTINGS = """Server version: Apache/2.4.10 (Debian) -Server built: Mar 15 2015 09:51:43 -Server's Module Magic Number: 20120211:37 -Server loaded: APR 1.5.1, APR-UTIL 1.5.4 -Compiled using: APR 1.5.1, APR-UTIL 1.5.4 -Architecture: 64-bit -Server MPM: event - threaded: yes (fixed thread count) - forked: yes (variable process count) -Server compiled with.... - -D APR_HAS_SENDFILE - -D APR_HAS_MMAP - -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled) - -D APR_USE_SYSVSEM_SERIALIZE - -D APR_USE_PTHREAD_SERIALIZE - -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT - -D APR_HAS_OTHER_CHILD - -D AP_HAVE_RELIABLE_PIPED_LOGS - -D DYNAMIC_MODULE_LIMIT=256 - -D HTTPD_ROOT="/etc/apache2" - -D SUEXEC_BIN="/usr/lib/apache2/suexec" - -D DEFAULT_PIDLOG="/var/run/apache2.pid" - -D DEFAULT_SCOREBOARD="logs/apache_runtime_status" - -D DEFAULT_ERRORLOG="logs/error_log" - -D AP_TYPES_CONFIG_FILE="mime.types" - -D SERVER_CONFIG_FILE="apache2.conf" - -""" - - -class LetsHelpApacheTest(unittest.TestCase): - @mock.patch(_MODULE_NAME + ".copy_config") - def test_make_and_verify_selection(self, mock_copy_config): - mock_copy_config.return_value = (["apache2.conf"], ["apache2"]) - - with mock.patch("six.moves.input") as mock_input: - with mock.patch(_MODULE_NAME + ".sys.stdout"): - mock_input.side_effect = ["Yes", "No"] - letshelp_le_apache.make_and_verify_selection("root", "temp") - self.assertRaises( - SystemExit, letshelp_le_apache.make_and_verify_selection, - "server_root", "temp_dir") - - def test_copy_config(self): - tempdir = tempfile.mkdtemp() - server_root = pkg_resources.resource_filename(__name__, "testdata") - letshelp_le_apache.copy_config(server_root, tempdir) - - temp_testdata = os.path.join(tempdir, "testdata") - self.assertFalse(os.path.exists(os.path.join( - temp_testdata, os.path.basename(_PASSWD_FILE)))) - self.assertFalse(os.path.exists(os.path.join( - temp_testdata, os.path.basename(_KEY_FILE)))) - self.assertFalse(os.path.exists(os.path.join( - temp_testdata, os.path.basename(_SECRET_FILE)))) - self.assertTrue(os.path.exists(os.path.join( - temp_testdata, _PARTIAL_CONF_PATH))) - self.assertTrue(os.path.exists(os.path.join( - temp_testdata, _PARTIAL_LINK_PATH))) - - def test_copy_file_without_comments(self): - dest = tempfile.mkstemp()[1] - letshelp_le_apache.copy_file_without_comments(_PASSWD_FILE, dest) - - with open(_PASSWD_FILE) as original: - with open(dest) as copy: - for original_line, copied_line in zip(original, copy): - self.assertEqual(original_line, copied_line) - - @mock.patch(_MODULE_NAME + ".subprocess.Popen") - def test_safe_config_file(self, mock_popen): - mock_popen().communicate.return_value = ("PEM RSA private key", None) - self.assertFalse(letshelp_le_apache.safe_config_file("filename")) - - mock_popen().communicate.return_value = ("ASCII text", None) - self.assertFalse(letshelp_le_apache.safe_config_file(_PASSWD_FILE)) - self.assertFalse(letshelp_le_apache.safe_config_file(_KEY_FILE)) - self.assertFalse(letshelp_le_apache.safe_config_file(_SECRET_FILE)) - self.assertTrue(letshelp_le_apache.safe_config_file(_CONFIG_FILE)) - - @mock.patch(_MODULE_NAME + ".subprocess.Popen") - def test_tempdir(self, mock_popen): - mock_popen().communicate.side_effect = [ - ("version", None), ("modules", None), ("vhosts", None)] - args = _get_args() - - tempdir = letshelp_le_apache.setup_tempdir(args) - - with open(os.path.join(tempdir, "config_file")) as config_fd: - self.assertEqual(config_fd.read(), args.config_file + "\n") - - with open(os.path.join(tempdir, "version")) as version_fd: - self.assertEqual(version_fd.read(), "version") - - with open(os.path.join(tempdir, "modules")) as modules_fd: - self.assertEqual(modules_fd.read(), "modules") - - with open(os.path.join(tempdir, "vhosts")) as vhosts_fd: - self.assertEqual(vhosts_fd.read(), "vhosts") - - @mock.patch(_MODULE_NAME + ".subprocess.check_call") - def test_verify_config(self, mock_check_call): - args = _get_args() - mock_check_call.side_effect = [ - None, OSError, subprocess.CalledProcessError(1, "apachectl")] - - letshelp_le_apache.verify_config(args) - self.assertRaises(SystemExit, letshelp_le_apache.verify_config, args) - self.assertRaises(SystemExit, letshelp_le_apache.verify_config, args) - - @mock.patch(_MODULE_NAME + ".subprocess.Popen") - def test_locate_config(self, mock_popen): - mock_popen().communicate.side_effect = [ - OSError, ("bad_output", None), (_COMPILE_SETTINGS, None)] - - self.assertRaises( - SystemExit, letshelp_le_apache.locate_config, "ctl") - self.assertRaises( - SystemExit, letshelp_le_apache.locate_config, "ctl") - server_root, config_file = letshelp_le_apache.locate_config("ctl") - self.assertEqual(server_root, "/etc/apache2") - self.assertEqual(config_file, "apache2.conf") - - @mock.patch(_MODULE_NAME + ".argparse") - def test_get_args(self, mock_argparse): - argv = ["-d", "/etc/apache2"] - mock_argparse.ArgumentParser.return_value = _create_mock_parser(argv) - self.assertRaises(SystemExit, letshelp_le_apache.get_args) - - server_root = "/etc/apache2" - config_file = server_root + "/apache2.conf" - argv = ["-d", server_root, "-f", config_file] - mock_argparse.ArgumentParser.return_value = _create_mock_parser(argv) - args = letshelp_le_apache.get_args() - self.assertEqual(args.apache_ctl, "apachectl") - self.assertEqual(args.server_root, server_root) - self.assertEqual(args.config_file, os.path.basename(config_file)) - - server_root = "/etc/apache2" - config_file = "/etc/httpd/httpd.conf" - argv = ["-d", server_root, "-f", config_file] - mock_argparse.ArgumentParser.return_value = _create_mock_parser(argv) - self.assertRaises(SystemExit, letshelp_le_apache.get_args) - - def test_main_with_args(self): - with mock.patch(_MODULE_NAME + ".get_args"): - self._test_main_common() - - def test_main_without_args(self): - with mock.patch(_MODULE_NAME + ".get_args") as get_args: - args = _get_args() - server_root, config_file = args.server_root, args.config_file - args.server_root = args.config_file = None - get_args.return_value = args - with mock.patch(_MODULE_NAME + ".locate_config") as locate: - locate.return_value = (server_root, config_file) - self._test_main_common() - - def _test_main_common(self): - with mock.patch(_MODULE_NAME + ".verify_config"): - with mock.patch(_MODULE_NAME + ".setup_tempdir") as mock_setup: - tempdir_path = tempfile.mkdtemp() - mock_setup.return_value = tempdir_path - with mock.patch(_MODULE_NAME + ".make_and_verify_selection"): - testdir_basename = "test" - os.mkdir(os.path.join(tempdir_path, testdir_basename)) - - letshelp_le_apache.main() - - tar = tarfile.open(os.path.join( - tempdir_path, "config.tar.gz")) - - tempdir = tar.next() - if tempdir is None: - self.fail("Invalid tarball!") # pragma: no cover - else: - self.assertTrue(tempdir.isdir()) - self.assertEqual(tempdir.name, ".") - - testdir = tar.next() - if testdir is None: - self.fail("Invalid tarball!") # pragma: no cover - else: - self.assertTrue(testdir.isdir()) - self.assertEqual(os.path.basename(testdir.name), - testdir_basename) - - self.assertEqual(tar.next(), None) - - -def _create_mock_parser(argv): - parser = argparse.ArgumentParser() - mock_parser = mock.MagicMock() - mock_parser.add_argument = parser.add_argument - mock_parser.parse_args = functools.partial(parser.parse_args, argv) - - return mock_parser - - -def _get_args(): - args = argparse.Namespace() - args.apache_ctl = "apache_ctl" - args.config_file = "config_file" - args.server_root = "server_root" - - return args - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/letshelp-certbot/letshelp_certbot/magic_typing.py b/letshelp-certbot/letshelp_certbot/magic_typing.py deleted file mode 100644 index d6b1ff056..000000000 --- a/letshelp-certbot/letshelp_certbot/magic_typing.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Shim class to not have to depend on typing module in prod.""" -import sys - - -class TypingClass(object): - """Ignore import errors by getting anything""" - def __getattr__(self, name): - return None - -try: - # mypy doesn't respect modifying sys.modules - from typing import * # pylint: disable=wildcard-import, unused-wildcard-import - from typing import Collection, IO # type: ignore - # pylint: enable=unused-import -except ImportError: - sys.modules[__name__] = TypingClass() diff --git a/letshelp-certbot/letshelp_certbot/magic_typing_test.py b/letshelp-certbot/letshelp_certbot/magic_typing_test.py deleted file mode 100644 index 41bc245fa..000000000 --- a/letshelp-certbot/letshelp_certbot/magic_typing_test.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Tests for letshelp_certbot.magic_typing.""" -import sys -import unittest - -import mock - - -class MagicTypingTest(unittest.TestCase): - """Tests for letshelp_certbot.magic_typing.""" - def test_import_success(self): - try: - import typing as temp_typing - except ImportError: # pragma: no cover - temp_typing = None # pragma: no cover - typing_class_mock = mock.MagicMock() - text_mock = mock.MagicMock() - typing_class_mock.Text = text_mock - sys.modules['typing'] = typing_class_mock - if 'letshelp_certbot.magic_typing' in sys.modules: - del sys.modules['letshelp_certbot.magic_typing'] # pragma: no cover - from letshelp_certbot.magic_typing import Text - self.assertEqual(Text, text_mock) - del sys.modules['letshelp_certbot.magic_typing'] - sys.modules['typing'] = temp_typing - - def test_import_failure(self): - try: - import typing as temp_typing - except ImportError: # pragma: no cover - temp_typing = None # pragma: no cover - sys.modules['typing'] = None - if 'letshelp_certbot.magic_typing' in sys.modules: - del sys.modules['letshelp_certbot.magic_typing'] # pragma: no cover - from letshelp_certbot.magic_typing import Text - self.assertTrue(Text is None) - del sys.modules['letshelp_certbot.magic_typing'] - sys.modules['typing'] = temp_typing - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/letshelp-certbot/letshelp_certbot/testdata/mods-available/ssl.load b/letshelp-certbot/letshelp_certbot/testdata/mods-available/ssl.load deleted file mode 100644 index 3d2336ae0..000000000 --- a/letshelp-certbot/letshelp_certbot/testdata/mods-available/ssl.load +++ /dev/null @@ -1,2 +0,0 @@ -# Depends: setenvif mime socache_shmcb -LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so diff --git a/letshelp-certbot/letshelp_certbot/testdata/mods-enabled/ssl.load b/letshelp-certbot/letshelp_certbot/testdata/mods-enabled/ssl.load deleted file mode 120000 index 9d7972384..000000000 --- a/letshelp-certbot/letshelp_certbot/testdata/mods-enabled/ssl.load +++ /dev/null @@ -1 +0,0 @@ -../mods-available/ssl.load \ No newline at end of file diff --git a/letshelp-certbot/letshelp_certbot/testdata/super_secret_file.txt b/letshelp-certbot/letshelp_certbot/testdata/super_secret_file.txt deleted file mode 100644 index 9f592eb7d..000000000 --- a/letshelp-certbot/letshelp_certbot/testdata/super_secret_file.txt +++ /dev/null @@ -1 +0,0 @@ -hunter2 diff --git a/letshelp-certbot/letshelp_certbot/testdata/uncommonly_named_k3y b/letshelp-certbot/letshelp_certbot/testdata/uncommonly_named_k3y deleted file mode 100644 index 659274d1d..000000000 --- a/letshelp-certbot/letshelp_certbot/testdata/uncommonly_named_k3y +++ /dev/null @@ -1,6 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIGrAgEAAiEAm2Fylv+Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEkCAwEAAQIh -AJT0BA/xD01dFCAXzSNyj9nfSZa3NpqzJZZn/eOm7vghAhEAzUVNZn4lLLBD1R6N -E8TKNQIRAMHHyn3O5JeY36lwKwkUlEUCEAliRauN0L0+QZuYjfJ9aJECEGx4dru3 -rTPCyighdqWNlHUCEQCiLjlwSRtWgmMBudCkVjzt ------END RSA PRIVATE KEY----- diff --git a/letshelp-certbot/letshelp_certbot/testdata/uncommonly_named_p4sswd b/letshelp-certbot/letshelp_certbot/testdata/uncommonly_named_p4sswd deleted file mode 100644 index 3559c1d1f..000000000 --- a/letshelp-certbot/letshelp_certbot/testdata/uncommonly_named_p4sswd +++ /dev/null @@ -1 +0,0 @@ -johntheripper:$apr1$fIGE9.JL$jTCwNWZy9Ak/yvOLuOyzQ1 diff --git a/letshelp-certbot/readthedocs.org.requirements.txt b/letshelp-certbot/readthedocs.org.requirements.txt deleted file mode 100644 index b24681caa..000000000 --- a/letshelp-certbot/readthedocs.org.requirements.txt +++ /dev/null @@ -1,10 +0,0 @@ -# readthedocs.org gives no way to change the install command to "pip -# install -e certbot[docs]" (that would in turn install documentation -# dependencies), but it allows to specify a requirements.txt file at -# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) - -# Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e certbot[docs]" must be used instead - --e letshelp-certbot[docs] diff --git a/letshelp-certbot/setup.cfg b/letshelp-certbot/setup.cfg deleted file mode 100644 index 2a9acf13d..000000000 --- a/letshelp-certbot/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal = 1 diff --git a/letshelp-certbot/setup.py b/letshelp-certbot/setup.py deleted file mode 100644 index 448c145ce..000000000 --- a/letshelp-certbot/setup.py +++ /dev/null @@ -1,58 +0,0 @@ -from setuptools import find_packages -from setuptools import setup - -version = '0.7.0.dev0' - -install_requires = [ - 'mock', - 'setuptools', # pkg_resources -] - -docs_extras = [ - 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags - 'sphinx_rtd_theme', -] - -setup( - name='letshelp-certbot', - version=version, - description="Let's help Certbot client", - url='https://github.com/letsencrypt/letsencrypt', - author="Certbot Project", - author_email='client-dev@letsencrypt.org', - license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Topic :: Internet :: WWW/HTTP', - 'Topic :: Security', - 'Topic :: System :: Installation/Setup', - 'Topic :: System :: Networking', - 'Topic :: System :: Systems Administration', - 'Topic :: Utilities', - ], - - packages=find_packages(), - include_package_data=True, - install_requires=install_requires, - extras_require={ - 'docs': docs_extras, - }, - entry_points={ - 'console_scripts': [ - 'letshelp-certbot-apache = letshelp_certbot.apache:main', - ], - }, - test_suite='letshelp_certbot', -) diff --git a/linter_plugin.py b/linter_plugin.py index 1754b1a2a..b6388e2c7 100644 --- a/linter_plugin.py +++ b/linter_plugin.py @@ -10,7 +10,7 @@ from pylint.checkers import BaseChecker from pylint.interfaces import IAstroidChecker # Modules in theses packages can import the os module. -WHITELIST_PACKAGES = ['acme', 'certbot_compatibility_test', 'letshelp_certbot', 'lock_test'] +WHITELIST_PACKAGES = ['acme', 'certbot_compatibility_test', 'lock_test'] class ForbidStandardOsModule(BaseChecker): diff --git a/mypy.ini b/mypy.ini index 188ed031f..a19fa2a5f 100644 --- a/mypy.ini +++ b/mypy.ini @@ -5,6 +5,3 @@ python_version = 2.7 [mypy-acme.magic_typing_test] ignore_errors = True - -[mypy-letshelp_certbot.magic_typing_test] -ignore_errors = True diff --git a/tools/_venv_common.py b/tools/_venv_common.py index c61385054..75d0d5d33 100644 --- a/tools/_venv_common.py +++ b/tools/_venv_common.py @@ -39,7 +39,6 @@ REQUIREMENTS = [ '-e certbot-dns-route53', '-e certbot-dns-sakuracloud', '-e certbot-nginx', - '-e letshelp-certbot', '-e certbot-compatibility-test', '-e certbot-ci', ] diff --git a/tools/install_and_test.py b/tools/install_and_test.py index 192708957..0b47fa5f8 100755 --- a/tools/install_and_test.py +++ b/tools/install_and_test.py @@ -12,7 +12,7 @@ import re import subprocess import sys -SKIP_PROJECTS_ON_WINDOWS = ['certbot-apache', 'letshelp-certbot'] +SKIP_PROJECTS_ON_WINDOWS = ['certbot-apache'] def call_with_print(command): diff --git a/tox.cover.py b/tox.cover.py index 0ef5c0d07..3e69a14d6 100755 --- a/tox.cover.py +++ b/tox.cover.py @@ -9,7 +9,7 @@ DEFAULT_PACKAGES = [ 'certbot_dns_digitalocean', 'certbot_dns_dnsimple', 'certbot_dns_dnsmadeeasy', 'certbot_dns_gehirn', 'certbot_dns_google', 'certbot_dns_linode', 'certbot_dns_luadns', 'certbot_dns_nsone', 'certbot_dns_ovh', 'certbot_dns_rfc2136', 'certbot_dns_route53', - 'certbot_dns_sakuracloud', 'certbot_nginx', 'letshelp_certbot'] + 'certbot_dns_sakuracloud', 'certbot_nginx'] COVER_THRESHOLDS = { 'certbot': {'linux': 96, 'windows': 96}, @@ -30,10 +30,9 @@ COVER_THRESHOLDS = { 'certbot_dns_route53': {'linux': 92, 'windows': 92}, 'certbot_dns_sakuracloud': {'linux': 97, 'windows': 97}, 'certbot_nginx': {'linux': 97, 'windows': 97}, - 'letshelp_certbot': {'linux': 100, 'windows': 100} } -SKIP_PROJECTS_ON_WINDOWS = ['certbot-apache', 'letshelp-certbot'] +SKIP_PROJECTS_ON_WINDOWS = ['certbot-apache'] def cover(package): diff --git a/tox.ini b/tox.ini index b2710ce35..3903cdf45 100644 --- a/tox.ini +++ b/tox.ini @@ -34,8 +34,7 @@ all_packages = certbot[dev] \ certbot-apache \ {[base]dns_packages} \ - certbot-nginx \ - letshelp-certbot + certbot-nginx install_packages = python {toxinidir}/tools/pip_install_editable.py {[base]all_packages} source_paths = @@ -58,7 +57,6 @@ source_paths = certbot-dns-route53/certbot_dns_route53 certbot-dns-sakuracloud/certbot_dns_sakuracloud certbot-nginx/certbot_nginx - letshelp-certbot/letshelp_certbot tests/lock_test.py [testenv] From 99b1538d0a70986c7e925e8c091b2f36bded9a4b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 18 Feb 2020 11:55:48 -0800 Subject: [PATCH 094/122] Fix spurious pylint errors. (#7780) This fixes (part of) the problem identified in https://github.com/certbot/certbot/pull/7657#issuecomment-586506340. When I tested our pylint setup on Python 3.5.9, 3.6.9, or 3.6.10, tests failed with: ``` ************* Module acme.challenges acme/acme/challenges.py:57:15: E1101: Instance of 'UnrecognizedChallenge' has no 'jobj' member (no-member) ************* Module acme.jws acme/acme/jws.py:28:16: E1101: Class 'Signature' has no '_orig_slots' member (no-member) ``` These errors did not occur for me on Python 3.6.7 or Python 3.7+. You also cannot run our lint setup on Python 2.7 because our pinned version of pylint's dependency `asteroid` does not support Python 2. Because of this, `pylint` is not installed in the virtual environment created by `tools/venv.py` and our [`lint` environment in tox specifies that Python 3 should be used](https://github.com/certbot/certbot/blob/fd64c8c33b2176e6569d64d30776bd5fc9fd3820/tox.ini#L132). I tried updating pylint and its dependencies to fix the problem, but they still occur so I think adding back these disable checks on these lines again is the best fix for now. --- acme/acme/challenges.py | 2 +- acme/acme/jws.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index f3fb19b42..39c8d6269 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -54,7 +54,7 @@ class UnrecognizedChallenge(Challenge): object.__setattr__(self, "jobj", jobj) def to_partial_json(self): - return self.jobj + return self.jobj # pylint: disable=no-member @classmethod def from_json(cls, jobj): diff --git a/acme/acme/jws.py b/acme/acme/jws.py index 9128f56b3..2188c3727 100644 --- a/acme/acme/jws.py +++ b/acme/acme/jws.py @@ -25,7 +25,7 @@ class Header(jose.Header): class Signature(jose.Signature): """ACME-specific Signature. Uses ACME-specific Header for customer fields.""" - __slots__ = jose.Signature._orig_slots + __slots__ = jose.Signature._orig_slots # pylint: disable=no-member # TODO: decoder/encoder should accept cls? Otherwise, subclassing # JSONObjectWithFields is tricky... From 42dda355c598d9e1938f6ab4eb30f4fc05b5964f Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 18 Feb 2020 14:54:07 -0800 Subject: [PATCH 095/122] Correct AutoHSTS docs (#7767) domains is a list of strings, not a single string. * Correct AutoHSTS docs. * Fix Apache enable_autohsts docs. --- certbot-apache/certbot_apache/_internal/configurator.py | 2 +- certbot/certbot/plugins/enhancements.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/certbot-apache/certbot_apache/_internal/configurator.py b/certbot-apache/certbot_apache/_internal/configurator.py index 465237590..8daa28173 100644 --- a/certbot-apache/certbot_apache/_internal/configurator.py +++ b/certbot-apache/certbot_apache/_internal/configurator.py @@ -2471,7 +2471,7 @@ class ApacheConfigurator(common.Installer): :type _unused_lineage: certbot._internal.storage.RenewableCert :param domains: List of domains in certificate to enhance - :type domains: str + :type domains: `list` of `str` """ self._autohsts_fetch_state() diff --git a/certbot/certbot/plugins/enhancements.py b/certbot/certbot/plugins/enhancements.py index be9b7933d..4abce2d2f 100644 --- a/certbot/certbot/plugins/enhancements.py +++ b/certbot/certbot/plugins/enhancements.py @@ -153,7 +153,7 @@ class AutoHSTSEnhancement(object): :type lineage: certbot.interfaces.RenewableCert :param domains: List of domains in certificate to enhance - :type domains: str + :type domains: `list` of `str` """ # This is used to configure internal new style enhancements in Certbot. These From c883efde0f649a0a7f5da1c35cbb1ae54d752109 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 20 Feb 2020 14:35:47 -0800 Subject: [PATCH 096/122] add pgp key docs (#7765) Fixes #7613. --- certbot/docs/packaging.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/certbot/docs/packaging.rst b/certbot/docs/packaging.rst index d2a94dbe7..06ae7bbff 100644 --- a/certbot/docs/packaging.rst +++ b/certbot/docs/packaging.rst @@ -33,6 +33,10 @@ example: `v0.11.1`. .. _`Semantic Versioning`: http://semver.org/ +Our packages are cryptographically signed and their signature can be verified +using the PGP key ``A2CFB51FA275A7286234E7B24D17C995CD9775F2``. This key can be +found on major key servers and at https://dl.eff.org/certbot.pub. + Notes for package maintainers ============================= From 7d79c91e9b52ef75d7ac86232dbbedc84f066587 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 21 Feb 2020 11:18:53 -0800 Subject: [PATCH 097/122] Move our macOS tests to Azure Pipelines (#7793) [Our macOS tests are failing](https://travis-ci.com/certbot/certbot/builds/149965318) again this time due to the problem described at https://travis-ci.community/t/macos-build-fails-because-of-homebrew-bundle-unknown-command/7296/14. I tried adding `update: true` to the Homebrew config as described in that thread, but [it didn't work](https://travis-ci.com/certbot/certbot/builds/150070374). I also tried updating the macOS image we use which [didn't work](https://travis-ci.com/certbot/certbot/builds/150072389). Since we continue to have problems with macOS on Travis, let try moving the tests to Azure Pipelines. * test macos * Remove Travis macOS setup * add displayName --- .azure-pipelines/templates/tests-suite.yml | 24 +++++++++++++++++----- .travis.yml | 19 ----------------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/.azure-pipelines/templates/tests-suite.yml b/.azure-pipelines/templates/tests-suite.yml index 119f755a6..069ea94d6 100644 --- a/.azure-pipelines/templates/tests-suite.yml +++ b/.azure-pipelines/templates/tests-suite.yml @@ -1,22 +1,36 @@ jobs: - job: test - pool: - vmImage: vs2017-win2016 strategy: matrix: - py35: + macos-py27: + IMAGE_NAME: macOS-10.14 + PYTHON_VERSION: 2.7 + TOXENV: py27 + macos-py38: + IMAGE_NAME: macOS-10.14 + PYTHON_VERSION: 3.8 + TOXENV: py38 + windows-py35: + IMAGE_NAME: vs2017-win2016 PYTHON_VERSION: 3.5 TOXENV: py35 - py37-cover: + windows-py37-cover: + IMAGE_NAME: vs2017-win2016 PYTHON_VERSION: 3.7 TOXENV: py37-cover - integration-certbot: + windows-integration-certbot: + IMAGE_NAME: vs2017-win2016 PYTHON_VERSION: 3.7 TOXENV: integration-certbot PYTEST_ADDOPTS: --numprocesses 4 + pool: + vmImage: $(IMAGE_NAME) variables: - group: certbot-common steps: + - bash: brew install augeas + condition: startswith(variables['IMAGE_NAME'], 'macOS') + displayName: Install Augeas - task: UsePythonVersion@0 inputs: versionSpec: $(PYTHON_VERSION) diff --git a/.travis.yml b/.travis.yml index 1eae66333..e5354898d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ cache: - $HOME/.cache/pip before_script: - - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ulimit -n 1024 ; fi' # On Travis, the fastest parallelization for integration tests has proved to be 4. - 'if [[ "$TOXENV" == *"integration"* ]]; then export PYTEST_ADDOPTS="--numprocesses 4"; fi' # Use Travis retry feature for farm tests since they are flaky @@ -224,24 +223,6 @@ matrix: packages: # don't install nginx and apache - libaugeas0 <<: *extended-test-suite - - language: generic - env: TOXENV=py27 - os: osx - addons: - homebrew: - packages: - - augeas - - python2 - <<: *extended-test-suite - - language: generic - env: TOXENV=py3 - os: osx - addons: - homebrew: - packages: - - augeas - - python3 - <<: *extended-test-suite # container-based infrastructure sudo: false From 84b57fac9341453b12135cdf26d9ede092e2c3aa Mon Sep 17 00:00:00 2001 From: Raklyon <46962656+Raklyon@users.noreply.github.com> Date: Fri, 21 Feb 2020 21:30:58 +0100 Subject: [PATCH 098/122] Refactor cli.py, splitting in it smaller submodules (#6803) * Refactor cli.py into a package with submodules * Added unit tests for helpful module in cli. * Fixed linter errors * Fixed pylint issues * Updated changelog.md * Fixed test failing and mypy error. Appeared a new pylint error (seems to be in conflict with mypy) mypy require zope.interface to be imported but when imported it is not used and pylint throws an error. * Fixed pylint errors * Apply changes to cli since last merge from master (efc8d49806b14a31d88cfc0f1b6daca1dd373d8d) * Fix lint * Remaining lint errors Co-authored-by: Adrien Ferrand --- certbot/CHANGELOG.md | 2 +- certbot/certbot/_internal/cli.py | 1581 ----------------- certbot/certbot/_internal/cli/__init__.py | 526 ++++++ .../certbot/_internal/cli/cli_constants.py | 107 ++ certbot/certbot/_internal/cli/cli_utils.py | 239 +++ certbot/certbot/_internal/cli/group_adder.py | 19 + certbot/certbot/_internal/cli/helpful.py | 468 +++++ certbot/certbot/_internal/cli/paths_parser.py | 50 + .../certbot/_internal/cli/plugins_parsing.py | 97 + .../cli/report_config_interaction.py | 27 + certbot/certbot/_internal/cli/subparsers.py | 72 + certbot/certbot/_internal/cli/verb_help.py | 106 ++ certbot/tests/cli_test.py | 2 +- certbot/tests/helpful_test.py | 193 ++ 14 files changed, 1906 insertions(+), 1583 deletions(-) delete mode 100644 certbot/certbot/_internal/cli.py create mode 100644 certbot/certbot/_internal/cli/__init__.py create mode 100644 certbot/certbot/_internal/cli/cli_constants.py create mode 100644 certbot/certbot/_internal/cli/cli_utils.py create mode 100644 certbot/certbot/_internal/cli/group_adder.py create mode 100644 certbot/certbot/_internal/cli/helpful.py create mode 100644 certbot/certbot/_internal/cli/paths_parser.py create mode 100644 certbot/certbot/_internal/cli/plugins_parsing.py create mode 100644 certbot/certbot/_internal/cli/report_config_interaction.py create mode 100644 certbot/certbot/_internal/cli/subparsers.py create mode 100644 certbot/certbot/_internal/cli/verb_help.py create mode 100644 certbot/tests/helpful_test.py diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index ff3061e01..126b07eec 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -13,7 +13,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed -* +* certbot._internal.cli is now a package split in submodules instead of a whole module. ### Fixed diff --git a/certbot/certbot/_internal/cli.py b/certbot/certbot/_internal/cli.py deleted file mode 100644 index c0dbf7424..000000000 --- a/certbot/certbot/_internal/cli.py +++ /dev/null @@ -1,1581 +0,0 @@ -"""Certbot command line argument & config processing.""" -# pylint: disable=too-many-lines -from __future__ import print_function - -import argparse -import copy -import glob -import logging.handlers -import sys - -import configargparse -import six -import zope.component -import zope.interface -from zope.interface import interfaces as zope_interfaces - -from acme import challenges -from acme.magic_typing import Any -from acme.magic_typing import Dict -from acme.magic_typing import Optional -import certbot -from certbot import crypto_util -from certbot import errors -from certbot import interfaces -from certbot import util -from certbot._internal import constants -from certbot._internal import hooks -from certbot._internal.plugins import disco as plugins_disco -import certbot._internal.plugins.selection as plugin_selection -from certbot.compat import os -from certbot.display import util as display_util -import certbot.plugins.enhancements as enhancements - -logger = logging.getLogger(__name__) - -# Global, to save us from a lot of argument passing within the scope of this module -helpful_parser = None # type: Optional[HelpfulArgumentParser] - -# For help strings, figure out how the user ran us. -# When invoked from letsencrypt-auto, sys.argv[0] is something like: -# "/home/user/.local/share/certbot/bin/certbot" -# Note that this won't work if the user set VENV_PATH or XDG_DATA_HOME before -# running letsencrypt-auto (and sudo stops us from seeing if they did), so it -# should only be used for purposes where inability to detect letsencrypt-auto -# fails safely - -LEAUTO = "letsencrypt-auto" -if "CERTBOT_AUTO" in os.environ: - # if we're here, this is probably going to be certbot-auto, unless the - # user saved the script under a different name - LEAUTO = os.path.basename(os.environ["CERTBOT_AUTO"]) - -old_path_fragment = os.path.join(".local", "share", "letsencrypt") -new_path_prefix = os.path.abspath(os.path.join(os.sep, "opt", - "eff.org", "certbot", "venv")) -if old_path_fragment in sys.argv[0] or sys.argv[0].startswith(new_path_prefix): - cli_command = LEAUTO -else: - cli_command = "certbot" - -# Argparse's help formatting has a lot of unhelpful peculiarities, so we want -# to replace as much of it as we can... - -# This is the stub to include in help generated by argparse -SHORT_USAGE = """ - {0} [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ... - -Certbot can obtain and install HTTPS/TLS/SSL certificates. By default, -it will attempt to use a webserver both for obtaining and installing the -certificate. """.format(cli_command) - -# This section is used for --help and --help all ; it needs information -# about installed plugins to be fully formatted -COMMAND_OVERVIEW = """The most common SUBCOMMANDS and flags are: - -obtain, install, and renew certificates: - (default) run Obtain & install a certificate in your current webserver - certonly Obtain or renew a certificate, but do not install it - renew Renew all previously obtained certificates that are near expiry - enhance Add security enhancements to your existing configuration - -d DOMAINS Comma-separated list of domains to obtain a certificate for - - %s - --standalone Run a standalone webserver for authentication - %s - --webroot Place files in a server's webroot folder for authentication - --manual Obtain certificates interactively, or using shell script hooks - - -n Run non-interactively - --test-cert Obtain a test certificate from a staging server - --dry-run Test "renew" or "certonly" without saving any certificates to disk - -manage certificates: - certificates Display information about certificates you have from Certbot - revoke Revoke a certificate (supply --cert-name or --cert-path) - delete Delete a certificate (supply --cert-name) - -manage your account: - register Create an ACME account - unregister Deactivate an ACME account - update_account Update an ACME account - --agree-tos Agree to the ACME server's Subscriber Agreement - -m EMAIL Email address for important account notifications -""" - -# This is the short help for certbot --help, where we disable argparse -# altogether -HELP_AND_VERSION_USAGE = """ -More detailed help: - - -h, --help [TOPIC] print this message, or detailed help on a topic; - the available TOPICS are: - - all, automation, commands, paths, security, testing, or any of the - subcommands or plugins (certonly, renew, install, register, nginx, - apache, standalone, webroot, etc.) - -h all print a detailed help page including all topics - --version print the version number -""" - - -# These argparse parameters should be removed when detecting defaults. -ARGPARSE_PARAMS_TO_REMOVE = ("const", "nargs", "type",) - - -# These sets are used when to help detect options set by the user. -EXIT_ACTIONS = set(("help", "version",)) - - -ZERO_ARG_ACTIONS = set(("store_const", "store_true", - "store_false", "append_const", "count",)) - - -# Maps a config option to a set of config options that may have modified it. -# This dictionary is used recursively, so if A modifies B and B modifies C, -# it is determined that C was modified by the user if A was modified. -VAR_MODIFIERS = {"account": set(("server",)), - "renew_hook": set(("deploy_hook",)), - "server": set(("dry_run", "staging",)), - "webroot_map": set(("webroot_path",))} - - -def report_config_interaction(modified, modifiers): - """Registers config option interaction to be checked by set_by_cli. - - This function can be called by during the __init__ or - add_parser_arguments methods of plugins to register interactions - between config options. - - :param modified: config options that can be modified by modifiers - :type modified: iterable or str (string_types) - :param modifiers: config options that modify modified - :type modifiers: iterable or str (string_types) - - """ - if isinstance(modified, six.string_types): - modified = (modified,) - if isinstance(modifiers, six.string_types): - modifiers = (modifiers,) - - for var in modified: - VAR_MODIFIERS.setdefault(var, set()).update(modifiers) - - -class _Default(object): - """A class to use as a default to detect if a value is set by a user""" - - def __bool__(self): - return False - - def __eq__(self, other): - return isinstance(other, _Default) - - def __hash__(self): - return id(_Default) - - def __nonzero__(self): - return self.__bool__() - - -def set_by_cli(var): - """ - Return True if a particular config variable has been set by the user - (CLI or config file) including if the user explicitly set it to the - default. Returns False if the variable was assigned a default value. - """ - detector = set_by_cli.detector # type: ignore - if detector is None and helpful_parser is not None: - # Setup on first run: `detector` is a weird version of config in which - # the default value of every attribute is wrangled to be boolean-false - plugins = plugins_disco.PluginsRegistry.find_all() - # reconstructed_args == sys.argv[1:], or whatever was passed to main() - reconstructed_args = helpful_parser.args + [helpful_parser.verb] - detector = set_by_cli.detector = prepare_and_parse_args( # type: ignore - plugins, reconstructed_args, detect_defaults=True) - # propagate plugin requests: eg --standalone modifies config.authenticator - detector.authenticator, detector.installer = ( # type: ignore - plugin_selection.cli_plugin_requests(detector)) - - if not isinstance(getattr(detector, var), _Default): - logger.debug("Var %s=%s (set by user).", var, getattr(detector, var)) - return True - - for modifier in VAR_MODIFIERS.get(var, []): - if set_by_cli(modifier): - logger.debug("Var %s=%s (set by user).", - var, VAR_MODIFIERS.get(var, [])) - return True - - return False - -# static housekeeping var -# functions attributed are not supported by mypy -# https://github.com/python/mypy/issues/2087 -set_by_cli.detector = None # type: ignore - - -def has_default_value(option, value): - """Does option have the default value? - - If the default value of option is not known, False is returned. - - :param str option: configuration variable being considered - :param value: value of the configuration variable named option - - :returns: True if option has the default value, otherwise, False - :rtype: bool - - """ - if helpful_parser is not None: - return (option in helpful_parser.defaults and - helpful_parser.defaults[option] == value) - return False - - -def option_was_set(option, value): - """Was option set by the user or does it differ from the default? - - :param str option: configuration variable being considered - :param value: value of the configuration variable named option - - :returns: True if the option was set, otherwise, False - :rtype: bool - - """ - return set_by_cli(option) or not has_default_value(option, value) - - -def argparse_type(variable): - """Return our argparse type function for a config variable (default: str)""" - # pylint: disable=protected-access - if helpful_parser is not None: - for action in helpful_parser.parser._actions: - if action.type is not None and action.dest == variable: - return action.type - return str - -def read_file(filename, mode="rb"): - """Returns the given file's contents. - - :param str filename: path to file - :param str mode: open mode (see `open`) - - :returns: absolute path of filename and its contents - :rtype: tuple - - :raises argparse.ArgumentTypeError: File does not exist or is not readable. - - """ - try: - filename = os.path.abspath(filename) - with open(filename, mode) as the_file: - contents = the_file.read() - return filename, contents - except IOError as exc: - raise argparse.ArgumentTypeError(exc.strerror) - - -def flag_default(name): - """Default value for CLI flag.""" - # XXX: this is an internal housekeeping notion of defaults before - # argparse has been set up; it is not accurate for all flags. Call it - # with caution. Plugin defaults are missing, and some things are using - # defaults defined in this file, not in constants.py :( - return copy.deepcopy(constants.CLI_DEFAULTS[name]) - - -def config_help(name, hidden=False): - """Extract the help message for an `.IConfig` attribute.""" - if hidden: - return argparse.SUPPRESS - field = interfaces.IConfig.__getitem__(name) # type: zope.interface.interface.Attribute - return field.__doc__ - - -class HelpfulArgumentGroup(object): - """Emulates an argparse group for use with HelpfulArgumentParser. - - This class is used in the add_group method of HelpfulArgumentParser. - Command line arguments can be added to the group, but help - suppression and default detection is applied by - HelpfulArgumentParser when necessary. - - """ - def __init__(self, helpful_arg_parser, topic): - self._parser = helpful_arg_parser - self._topic = topic - - def add_argument(self, *args, **kwargs): - """Add a new command line argument to the argument group.""" - self._parser.add(self._topic, *args, **kwargs) - -class CustomHelpFormatter(argparse.HelpFormatter): - """This is a clone of ArgumentDefaultsHelpFormatter, with bugfixes. - - In particular we fix https://bugs.python.org/issue28742 - """ - - def _get_help_string(self, action): - helpstr = action.help - if '%(default)' not in action.help and '(default:' not in action.help: - if action.default != argparse.SUPPRESS: - defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE] - if action.option_strings or action.nargs in defaulting_nargs: - helpstr += ' (default: %(default)s)' - return helpstr - -# The attributes here are: -# short: a string that will be displayed by "certbot -h commands" -# opts: a string that heads the section of flags with which this command is documented, -# both for "certbot -h SUBCOMMAND" and "certbot -h all" -# usage: an optional string that overrides the header of "certbot -h SUBCOMMAND" -VERB_HELP = [ - ("run (default)", { - "short": "Obtain/renew a certificate, and install it", - "opts": "Options for obtaining & installing certificates", - "usage": SHORT_USAGE.replace("[SUBCOMMAND]", ""), - "realname": "run" - }), - ("certonly", { - "short": "Obtain or renew a certificate, but do not install it", - "opts": "Options for modifying how a certificate is obtained", - "usage": ("\n\n certbot certonly [options] [-d DOMAIN] [-d DOMAIN] ...\n\n" - "This command obtains a TLS/SSL certificate without installing it anywhere.") - }), - ("renew", { - "short": "Renew all certificates (or one specified with --cert-name)", - "opts": ("The 'renew' subcommand will attempt to renew all" - " certificates (or more precisely, certificate lineages) you have" - " previously obtained if they are close to expiry, and print a" - " summary of the results. By default, 'renew' will reuse the options" - " used to create obtain or most recently successfully renew each" - " certificate lineage. You can try it with `--dry-run` first. For" - " more fine-grained control, you can renew individual lineages with" - " the `certonly` subcommand. Hooks are available to run commands" - " before and after renewal; see" - " https://certbot.eff.org/docs/using.html#renewal for more" - " information on these."), - "usage": "\n\n certbot renew [--cert-name CERTNAME] [options]\n\n" - }), - ("certificates", { - "short": "List certificates managed by Certbot", - "opts": "List certificates managed by Certbot", - "usage": ("\n\n certbot certificates [options] ...\n\n" - "Print information about the status of certificates managed by Certbot.") - }), - ("delete", { - "short": "Clean up all files related to a certificate", - "opts": "Options for deleting a certificate", - "usage": "\n\n certbot delete --cert-name CERTNAME\n\n" - }), - ("revoke", { - "short": "Revoke a certificate specified with --cert-path or --cert-name", - "opts": "Options for revocation of certificates", - "usage": "\n\n certbot revoke [--cert-path /path/to/fullchain.pem | " - "--cert-name example.com] [options]\n\n" - }), - ("register", { - "short": "Register for account with Let's Encrypt / other ACME server", - "opts": "Options for account registration", - "usage": "\n\n certbot register --email user@example.com [options]\n\n" - }), - ("update_account", { - "short": "Update existing account with Let's Encrypt / other ACME server", - "opts": "Options for account modification", - "usage": "\n\n certbot update_account --email updated_email@example.com [options]\n\n" - }), - ("unregister", { - "short": "Irrevocably deactivate your account", - "opts": "Options for account deactivation.", - "usage": "\n\n certbot unregister [options]\n\n" - }), - ("install", { - "short": "Install an arbitrary certificate in a server", - "opts": "Options for modifying how a certificate is deployed", - "usage": "\n\n certbot install --cert-path /path/to/fullchain.pem " - " --key-path /path/to/private-key [options]\n\n" - }), - ("rollback", { - "short": "Roll back server conf changes made during certificate installation", - "opts": "Options for rolling back server configuration changes", - "usage": "\n\n certbot rollback --checkpoints 3 [options]\n\n" - }), - ("plugins", { - "short": "List plugins that are installed and available on your system", - "opts": 'Options for the "plugins" subcommand', - "usage": "\n\n certbot plugins [options]\n\n" - }), - ("update_symlinks", { - "short": "Recreate symlinks in your /etc/letsencrypt/live/ directory", - "opts": ("Recreates certificate and key symlinks in {0}, if you changed them by hand " - "or edited a renewal configuration file".format( - os.path.join(flag_default("config_dir"), "live"))), - "usage": "\n\n certbot update_symlinks [options]\n\n" - }), - ("enhance", { - "short": "Add security enhancements to your existing configuration", - "opts": ("Helps to harden the TLS configuration by adding security enhancements " - "to already existing configuration."), - "usage": "\n\n certbot enhance [options]\n\n" - }), - -] -# VERB_HELP is a list in order to preserve order, but a dict is sometimes useful -VERB_HELP_MAP = dict(VERB_HELP) - - -class HelpfulArgumentParser(object): - """Argparse Wrapper. - - This class wraps argparse, adding the ability to make --help less - verbose, and request help on specific subcategories at a time, eg - 'certbot --help security' for security options. - - """ - - - def __init__(self, args, plugins, detect_defaults=False): - from certbot._internal import main - self.VERBS = { - "auth": main.certonly, - "certonly": main.certonly, - "run": main.run, - "install": main.install, - "plugins": main.plugins_cmd, - "register": main.register, - "update_account": main.update_account, - "unregister": main.unregister, - "renew": main.renew, - "revoke": main.revoke, - "rollback": main.rollback, - "everything": main.run, - "update_symlinks": main.update_symlinks, - "certificates": main.certificates, - "delete": main.delete, - "enhance": main.enhance, - } - - # Get notification function for printing - try: - self.notify = zope.component.getUtility( - interfaces.IDisplay).notification - except zope_interfaces.ComponentLookupError: - self.notify = display_util.NoninteractiveDisplay( - sys.stdout).notification - - - # List of topics for which additional help can be provided - HELP_TOPICS = ["all", "security", "paths", "automation", "testing"] - HELP_TOPICS += list(self.VERBS) + self.COMMANDS_TOPICS + ["manage"] - - plugin_names = list(plugins) - self.help_topics = HELP_TOPICS + plugin_names + [None] # type: ignore - - self.detect_defaults = detect_defaults - self.args = args - - if self.args and self.args[0] == 'help': - self.args[0] = '--help' - - self.determine_verb() - help1 = self.prescan_for_flag("-h", self.help_topics) - help2 = self.prescan_for_flag("--help", self.help_topics) - if isinstance(help1, bool) and isinstance(help2, bool): - self.help_arg = help1 or help2 - else: - self.help_arg = help1 if isinstance(help1, six.string_types) else help2 - - short_usage = self._usage_string(plugins, self.help_arg) - - self.visible_topics = self.determine_help_topics(self.help_arg) - - # elements are added by .add_group() - self.groups = {} # type: Dict[str, argparse._ArgumentGroup] - # elements are added by .parse_args() - self.defaults = {} # type: Dict[str, Any] - - self.parser = configargparse.ArgParser( - prog="certbot", - usage=short_usage, - formatter_class=CustomHelpFormatter, - args_for_setting_config_path=["-c", "--config"], - default_config_files=flag_default("config_files"), - config_arg_help_message="path to config file (default: {0})".format( - " and ".join(flag_default("config_files")))) - - # This is the only way to turn off overly verbose config flag documentation - self.parser._add_config_file_help = False - - # Help that are synonyms for --help subcommands - COMMANDS_TOPICS = ["command", "commands", "subcommand", "subcommands", "verbs"] - def _list_subcommands(self): - longest = max(len(v) for v in VERB_HELP_MAP) - - text = "The full list of available SUBCOMMANDS is:\n\n" - for verb, props in sorted(VERB_HELP): - doc = props.get("short", "") - text += '{0:<{length}} {1}\n'.format(verb, doc, length=longest) - - text += "\nYou can get more help on a specific subcommand with --help SUBCOMMAND\n" - return text - - def _usage_string(self, plugins, help_arg): - """Make usage strings late so that plugins can be initialised late - - :param plugins: all discovered plugins - :param help_arg: False for none; True for --help; "TOPIC" for --help TOPIC - :rtype: str - :returns: a short usage string for the top of --help TOPIC) - """ - if "nginx" in plugins: - nginx_doc = "--nginx Use the Nginx plugin for authentication & installation" - else: - nginx_doc = "(the certbot nginx plugin is not installed)" - if "apache" in plugins: - apache_doc = "--apache Use the Apache plugin for authentication & installation" - else: - apache_doc = "(the certbot apache plugin is not installed)" - - usage = SHORT_USAGE - if help_arg is True: - self.notify(usage + COMMAND_OVERVIEW % (apache_doc, nginx_doc) + HELP_AND_VERSION_USAGE) - sys.exit(0) - elif help_arg in self.COMMANDS_TOPICS: - self.notify(usage + self._list_subcommands()) - sys.exit(0) - elif help_arg == "all": - # if we're doing --help all, the OVERVIEW is part of the SHORT_USAGE at - # the top; if we're doing --help someothertopic, it's OT so it's not - usage += COMMAND_OVERVIEW % (apache_doc, nginx_doc) - else: - custom = VERB_HELP_MAP.get(help_arg, {}).get("usage", None) - usage = custom if custom else usage - - return usage - - def remove_config_file_domains_for_renewal(self, parsed_args): - """Make "certbot renew" safe if domains are set in cli.ini.""" - # Works around https://github.com/certbot/certbot/issues/4096 - if self.verb == "renew": - for source, flags in self.parser._source_to_settings.items(): # pylint: disable=protected-access - if source.startswith("config_file") and "domains" in flags: - parsed_args.domains = _Default() if self.detect_defaults else [] - - def parse_args(self): - """Parses command line arguments and returns the result. - - :returns: parsed command line arguments - :rtype: argparse.Namespace - - """ - parsed_args = self.parser.parse_args(self.args) - parsed_args.func = self.VERBS[self.verb] - parsed_args.verb = self.verb - - self.remove_config_file_domains_for_renewal(parsed_args) - - if self.detect_defaults: - return parsed_args - - self.defaults = dict((key, copy.deepcopy(self.parser.get_default(key))) - for key in vars(parsed_args)) - - # Do any post-parsing homework here - - if self.verb == "renew": - if parsed_args.force_interactive: - raise errors.Error( - "{0} cannot be used with renew".format( - constants.FORCE_INTERACTIVE_FLAG)) - parsed_args.noninteractive_mode = True - - if parsed_args.force_interactive and parsed_args.noninteractive_mode: - raise errors.Error( - "Flag for non-interactive mode and {0} conflict".format( - constants.FORCE_INTERACTIVE_FLAG)) - - if parsed_args.staging or parsed_args.dry_run: - self.set_test_server(parsed_args) - - if parsed_args.csr: - self.handle_csr(parsed_args) - - if parsed_args.must_staple: - parsed_args.staple = True - - if parsed_args.validate_hooks: - hooks.validate_hooks(parsed_args) - - if parsed_args.allow_subset_of_names: - if any(util.is_wildcard_domain(d) for d in parsed_args.domains): - raise errors.Error("Using --allow-subset-of-names with a" - " wildcard domain is not supported.") - - if parsed_args.hsts and parsed_args.auto_hsts: - raise errors.Error( - "Parameters --hsts and --auto-hsts cannot be used simultaneously.") - - return parsed_args - - def set_test_server(self, parsed_args): - """We have --staging/--dry-run; perform sanity check and set config.server""" - - # Flag combinations should produce these results: - # | --staging | --dry-run | - # ------------------------------------------------------------ - # | --server acme-v02 | Use staging | Use staging | - # | --server acme-staging-v02 | Use staging | Use staging | - # | --server | Conflict error | Use | - - default_servers = (flag_default("server"), constants.STAGING_URI) - - if parsed_args.staging and parsed_args.server not in default_servers: - raise errors.Error("--server value conflicts with --staging") - - if parsed_args.server in default_servers: - parsed_args.server = constants.STAGING_URI - - if parsed_args.dry_run: - if self.verb not in ["certonly", "renew"]: - raise errors.Error("--dry-run currently only works with the " - "'certonly' or 'renew' subcommands (%r)" % self.verb) - parsed_args.break_my_certs = parsed_args.staging = True - if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, "*")): - # The user has a prod account, but might not have a staging - # one; we don't want to start trying to perform interactive registration - parsed_args.tos = True - parsed_args.register_unsafely_without_email = True - - def handle_csr(self, parsed_args): - """Process a --csr flag.""" - if parsed_args.verb != "certonly": - raise errors.Error("Currently, a CSR file may only be specified " - "when obtaining a new or replacement " - "via the certonly command. Please try the " - "certonly command instead.") - if parsed_args.allow_subset_of_names: - raise errors.Error("--allow-subset-of-names cannot be used with --csr") - - csrfile, contents = parsed_args.csr[0:2] - typ, csr, domains = crypto_util.import_csr_file(csrfile, contents) - - # This is not necessary for webroot to work, however, - # obtain_certificate_from_csr requires parsed_args.domains to be set - for domain in domains: - add_domains(parsed_args, domain) - - if not domains: - # TODO: add CN to domains instead: - raise errors.Error( - "Unfortunately, your CSR %s needs to have a SubjectAltName for every domain" - % parsed_args.csr[0]) - - parsed_args.actual_csr = (csr, typ) - - csr_domains = {d.lower() for d in domains} - config_domains = set(parsed_args.domains) - if csr_domains != config_domains: - raise errors.ConfigurationError( - "Inconsistent domain requests:\nFrom the CSR: {0}\nFrom command line/config: {1}" - .format(", ".join(csr_domains), ", ".join(config_domains))) - - - def determine_verb(self): - """Determines the verb/subcommand provided by the user. - - This function works around some of the limitations of argparse. - - """ - if "-h" in self.args or "--help" in self.args: - # all verbs double as help arguments; don't get them confused - self.verb = "help" - return - - for i, token in enumerate(self.args): - if token in self.VERBS: - verb = token - if verb == "auth": - verb = "certonly" - if verb == "everything": - verb = "run" - self.verb = verb - self.args.pop(i) - return - - self.verb = "run" - - def prescan_for_flag(self, flag, possible_arguments): - """Checks cli input for flags. - - Check for a flag, which accepts a fixed set of possible arguments, in - the command line; we will use this information to configure argparse's - help correctly. Return the flag's argument, if it has one that matches - the sequence @possible_arguments; otherwise return whether the flag is - present. - - """ - if flag not in self.args: - return False - pos = self.args.index(flag) - try: - nxt = self.args[pos + 1] - if nxt in possible_arguments: - return nxt - except IndexError: - pass - return True - - def add(self, topics, *args, **kwargs): - """Add a new command line argument. - - :param topics: str or [str] help topic(s) this should be listed under, - or None for options that don't fit under a specific - topic which will only be shown in "--help all" output. - The first entry determines where the flag lives in the - "--help all" output (None -> "optional arguments"). - :param list *args: the names of this argument flag - :param dict **kwargs: various argparse settings for this argument - - """ - - if isinstance(topics, list): - # if this flag can be listed in multiple sections, try to pick the one - # that the user has asked for help about - topic = self.help_arg if self.help_arg in topics else topics[0] - else: - topic = topics # there's only one - - if self.detect_defaults: - kwargs = self.modify_kwargs_for_default_detection(**kwargs) - - if self.visible_topics[topic]: - if topic in self.groups: - group = self.groups[topic] - group.add_argument(*args, **kwargs) - else: - self.parser.add_argument(*args, **kwargs) - else: - kwargs["help"] = argparse.SUPPRESS - self.parser.add_argument(*args, **kwargs) - - def modify_kwargs_for_default_detection(self, **kwargs): - """Modify an arg so we can check if it was set by the user. - - Changes the parameters given to argparse when adding an argument - so we can properly detect if the value was set by the user. - - :param dict kwargs: various argparse settings for this argument - - :returns: a modified versions of kwargs - :rtype: dict - - """ - action = kwargs.get("action", None) - if action not in EXIT_ACTIONS: - kwargs["action"] = ("store_true" if action in ZERO_ARG_ACTIONS else - "store") - kwargs["default"] = _Default() - for param in ARGPARSE_PARAMS_TO_REMOVE: - kwargs.pop(param, None) - - return kwargs - - def add_deprecated_argument(self, argument_name, num_args): - """Adds a deprecated argument with the name argument_name. - - Deprecated arguments are not shown in the help. If they are used - on the command line, a warning is shown stating that the - argument is deprecated and no other action is taken. - - :param str argument_name: Name of deprecated argument. - :param int nargs: Number of arguments the option takes. - - """ - util.add_deprecated_argument( - self.parser.add_argument, argument_name, num_args) - - def add_group(self, topic, verbs=(), **kwargs): - """Create a new argument group. - - This method must be called once for every topic, however, calls - to this function are left next to the argument definitions for - clarity. - - :param str topic: Name of the new argument group. - :param str verbs: List of subcommands that should be documented as part of - this help group / topic - - :returns: The new argument group. - :rtype: `HelpfulArgumentGroup` - - """ - if self.visible_topics[topic]: - self.groups[topic] = self.parser.add_argument_group(topic, **kwargs) - if self.help_arg: - for v in verbs: - self.groups[topic].add_argument(v, help=VERB_HELP_MAP[v]["short"]) - return HelpfulArgumentGroup(self, topic) - - def add_plugin_args(self, plugins): - """ - - Let each of the plugins add its own command line arguments, which - may or may not be displayed as help topics. - - """ - for name, plugin_ep in six.iteritems(plugins): - parser_or_group = self.add_group(name, - description=plugin_ep.long_description) - plugin_ep.plugin_cls.inject_parser_options(parser_or_group, name) - - def determine_help_topics(self, chosen_topic): - """ - - The user may have requested help on a topic, return a dict of which - topics to display. @chosen_topic has prescan_for_flag's return type - - :returns: dict - - """ - # topics maps each topic to whether it should be documented by - # argparse on the command line - if chosen_topic == "auth": - chosen_topic = "certonly" - if chosen_topic == "everything": - chosen_topic = "run" - if chosen_topic == "all": - # Addition of condition closes #6209 (removal of duplicate route53 option). - return {t: t != 'certbot-route53:auth' for t in self.help_topics} - elif not chosen_topic: - return {t: False for t in self.help_topics} - return {t: t == chosen_topic for t in self.help_topics} - - -def _add_all_groups(helpful): - helpful.add_group("automation", description="Flags for automating execution & other tweaks") - helpful.add_group("security", description="Security parameters & server settings") - helpful.add_group("testing", - description="The following flags are meant for testing and integration purposes only.") - helpful.add_group("paths", description="Flags for changing execution paths & servers") - helpful.add_group("manage", - description="Various subcommands and flags are available for managing your certificates:", - verbs=["certificates", "delete", "renew", "revoke", "update_symlinks"]) - - # VERBS - for verb, docs in VERB_HELP: - name = docs.get("realname", verb) - helpful.add_group(name, description=docs["opts"]) - - -def prepare_and_parse_args(plugins, args, detect_defaults=False): - """Returns parsed command line arguments. - - :param .PluginsRegistry plugins: available plugins - :param list args: command line arguments with the program name removed - - :returns: parsed command line arguments - :rtype: argparse.Namespace - - """ - - - helpful = HelpfulArgumentParser(args, plugins, detect_defaults) - _add_all_groups(helpful) - - # --help is automatically provided by argparse - helpful.add( - None, "-v", "--verbose", dest="verbose_count", action="count", - default=flag_default("verbose_count"), help="This flag can be used " - "multiple times to incrementally increase the verbosity of output, " - "e.g. -vvv.") - helpful.add( - None, "-t", "--text", dest="text_mode", action="store_true", - default=flag_default("text_mode"), help=argparse.SUPPRESS) - helpful.add( - None, "--max-log-backups", type=nonnegative_int, - default=flag_default("max_log_backups"), - help="Specifies the maximum number of backup logs that should " - "be kept by Certbot's built in log rotation. Setting this " - "flag to 0 disables log rotation entirely, causing " - "Certbot to always append to the same log file.") - helpful.add( - [None, "automation", "run", "certonly", "enhance"], - "-n", "--non-interactive", "--noninteractive", - dest="noninteractive_mode", action="store_true", - default=flag_default("noninteractive_mode"), - help="Run without ever asking for user input. This may require " - "additional command line flags; the client will try to explain " - "which ones are required if it finds one missing") - helpful.add( - [None, "register", "run", "certonly", "enhance"], - constants.FORCE_INTERACTIVE_FLAG, action="store_true", - default=flag_default("force_interactive"), - help="Force Certbot to be interactive even if it detects it's not " - "being run in a terminal. This flag cannot be used with the " - "renew subcommand.") - helpful.add( - [None, "run", "certonly", "certificates", "enhance"], - "-d", "--domains", "--domain", dest="domains", - metavar="DOMAIN", action=_DomainsAction, - default=flag_default("domains"), - help="Domain names to apply. For multiple domains you can use " - "multiple -d flags or enter a comma separated list of domains " - "as a parameter. The first domain provided will be the " - "subject CN of the certificate, and all domains will be " - "Subject Alternative Names on the certificate. " - "The first domain will also be used in " - "some software user interfaces and as the file paths for the " - "certificate and related material unless otherwise " - "specified or you already have a certificate with the same " - "name. In the case of a name collision it will append a number " - "like 0001 to the file path name. (default: Ask)") - helpful.add( - [None, "run", "certonly", "register"], - "--eab-kid", dest="eab_kid", - metavar="EAB_KID", - help="Key Identifier for External Account Binding" - ) - helpful.add( - [None, "run", "certonly", "register"], - "--eab-hmac-key", dest="eab_hmac_key", - metavar="EAB_HMAC_KEY", - help="HMAC key for External Account Binding" - ) - helpful.add( - [None, "run", "certonly", "manage", "delete", "certificates", - "renew", "enhance"], "--cert-name", dest="certname", - metavar="CERTNAME", default=flag_default("certname"), - help="Certificate name to apply. This name is used by Certbot for housekeeping " - "and in file paths; it doesn't affect the content of the certificate itself. " - "To see certificate names, run 'certbot certificates'. " - "When creating a new certificate, specifies the new certificate's name. " - "(default: the first provided domain or the name of an existing " - "certificate on your system for the same domains)") - helpful.add( - [None, "testing", "renew", "certonly"], - "--dry-run", action="store_true", dest="dry_run", - default=flag_default("dry_run"), - help="Perform a test run of the client, obtaining test (invalid) certificates" - " but not saving them to disk. This can currently only be used" - " with the 'certonly' and 'renew' subcommands. \nNote: Although --dry-run" - " tries to avoid making any persistent changes on a system, it " - " is not completely side-effect free: if used with webserver authenticator plugins" - " like apache and nginx, it makes and then reverts temporary config changes" - " in order to obtain test certificates, and reloads webservers to deploy and then" - " roll back those changes. It also calls --pre-hook and --post-hook commands" - " if they are defined because they may be necessary to accurately simulate" - " renewal. --deploy-hook commands are not called.") - helpful.add( - ["register", "automation"], "--register-unsafely-without-email", action="store_true", - default=flag_default("register_unsafely_without_email"), - help="Specifying this flag enables registering an account with no " - "email address. This is strongly discouraged, because in the " - "event of key loss or account compromise you will irrevocably " - "lose access to your account. You will also be unable to receive " - "notice about impending expiration or revocation of your " - "certificates. Updates to the Subscriber Agreement will still " - "affect you, and will be effective 14 days after posting an " - "update to the web site.") - helpful.add( - ["register", "update_account", "unregister", "automation"], "-m", "--email", - default=flag_default("email"), - help=config_help("email")) - helpful.add(["register", "update_account", "automation"], "--eff-email", action="store_true", - default=flag_default("eff_email"), dest="eff_email", - help="Share your e-mail address with EFF") - helpful.add(["register", "update_account", "automation"], "--no-eff-email", - action="store_false", default=flag_default("eff_email"), dest="eff_email", - help="Don't share your e-mail address with EFF") - helpful.add( - ["automation", "certonly", "run"], - "--keep-until-expiring", "--keep", "--reinstall", - dest="reinstall", action="store_true", default=flag_default("reinstall"), - help="If the requested certificate matches an existing certificate, always keep the " - "existing one until it is due for renewal (for the " - "'run' subcommand this means reinstall the existing certificate). (default: Ask)") - helpful.add( - "automation", "--expand", action="store_true", default=flag_default("expand"), - help="If an existing certificate is a strict subset of the requested names, " - "always expand and replace it with the additional names. (default: Ask)") - helpful.add( - "automation", "--version", action="version", - version="%(prog)s {0}".format(certbot.__version__), - help="show program's version number and exit") - helpful.add( - ["automation", "renew"], - "--force-renewal", "--renew-by-default", dest="renew_by_default", - action="store_true", default=flag_default("renew_by_default"), - help="If a certificate " - "already exists for the requested domains, renew it now, " - "regardless of whether it is near expiry. (Often " - "--keep-until-expiring is more appropriate). Also implies " - "--expand.") - helpful.add( - "automation", "--renew-with-new-domains", dest="renew_with_new_domains", - action="store_true", default=flag_default("renew_with_new_domains"), - help="If a " - "certificate already exists for the requested certificate name " - "but does not match the requested domains, renew it now, " - "regardless of whether it is near expiry.") - helpful.add( - "automation", "--reuse-key", dest="reuse_key", - action="store_true", default=flag_default("reuse_key"), - help="When renewing, use the same private key as the existing " - "certificate.") - - helpful.add( - ["automation", "renew", "certonly"], - "--allow-subset-of-names", action="store_true", - default=flag_default("allow_subset_of_names"), - help="When performing domain validation, do not consider it a failure " - "if authorizations can not be obtained for a strict subset of " - "the requested domains. This may be useful for allowing renewals for " - "multiple domains to succeed even if some domains no longer point " - "at this system. This option cannot be used with --csr.") - helpful.add( - "automation", "--agree-tos", dest="tos", action="store_true", - default=flag_default("tos"), - help="Agree to the ACME Subscriber Agreement (default: Ask)") - helpful.add( - ["unregister", "automation"], "--account", metavar="ACCOUNT_ID", - default=flag_default("account"), - help="Account ID to use") - helpful.add( - "automation", "--duplicate", dest="duplicate", action="store_true", - default=flag_default("duplicate"), - help="Allow making a certificate lineage that duplicates an existing one " - "(both can be renewed in parallel)") - helpful.add( - "automation", "--os-packages-only", action="store_true", - default=flag_default("os_packages_only"), - help="(certbot-auto only) install OS package dependencies and then stop") - helpful.add( - "automation", "--no-self-upgrade", action="store_true", - default=flag_default("no_self_upgrade"), - help="(certbot-auto only) prevent the certbot-auto script from" - " upgrading itself to newer released versions (default: Upgrade" - " automatically)") - helpful.add( - "automation", "--no-bootstrap", action="store_true", - default=flag_default("no_bootstrap"), - help="(certbot-auto only) prevent the certbot-auto script from" - " installing OS-level dependencies (default: Prompt to install " - " OS-wide dependencies, but exit if the user says 'No')") - helpful.add( - "automation", "--no-permissions-check", action="store_true", - default=flag_default("no_permissions_check"), - help="(certbot-auto only) skip the check on the file system" - " permissions of the certbot-auto script") - helpful.add( - ["automation", "renew", "certonly", "run"], - "-q", "--quiet", dest="quiet", action="store_true", - default=flag_default("quiet"), - help="Silence all output except errors. Useful for automation via cron." - " Implies --non-interactive.") - # overwrites server, handled in HelpfulArgumentParser.parse_args() - helpful.add(["testing", "revoke", "run"], "--test-cert", "--staging", - dest="staging", action="store_true", default=flag_default("staging"), - help="Use the staging server to obtain or revoke test (invalid) certificates; equivalent" - " to --server " + constants.STAGING_URI) - helpful.add( - "testing", "--debug", action="store_true", default=flag_default("debug"), - help="Show tracebacks in case of errors, and allow certbot-auto " - "execution on experimental platforms") - helpful.add( - [None, "certonly", "run"], "--debug-challenges", action="store_true", - default=flag_default("debug_challenges"), - help="After setting up challenges, wait for user input before " - "submitting to CA") - helpful.add( - "testing", "--no-verify-ssl", action="store_true", - help=config_help("no_verify_ssl"), - default=flag_default("no_verify_ssl")) - helpful.add( - ["testing", "standalone", "manual"], "--http-01-port", type=int, - dest="http01_port", - default=flag_default("http01_port"), help=config_help("http01_port")) - helpful.add( - ["testing", "standalone"], "--http-01-address", - dest="http01_address", - default=flag_default("http01_address"), help=config_help("http01_address")) - helpful.add( - ["testing", "nginx"], "--https-port", type=int, - default=flag_default("https_port"), - help=config_help("https_port")) - helpful.add( - "testing", "--break-my-certs", action="store_true", - default=flag_default("break_my_certs"), - help="Be willing to replace or renew valid certificates with invalid " - "(testing/staging) certificates") - helpful.add( - "security", "--rsa-key-size", type=int, metavar="N", - default=flag_default("rsa_key_size"), help=config_help("rsa_key_size")) - helpful.add( - "security", "--must-staple", action="store_true", - dest="must_staple", default=flag_default("must_staple"), - help=config_help("must_staple")) - helpful.add( - ["security", "enhance"], - "--redirect", action="store_true", dest="redirect", - default=flag_default("redirect"), - help="Automatically redirect all HTTP traffic to HTTPS for the newly " - "authenticated vhost. (default: Ask)") - helpful.add( - "security", "--no-redirect", action="store_false", dest="redirect", - default=flag_default("redirect"), - help="Do not automatically redirect all HTTP traffic to HTTPS for the newly " - "authenticated vhost. (default: Ask)") - helpful.add( - ["security", "enhance"], - "--hsts", action="store_true", dest="hsts", default=flag_default("hsts"), - help="Add the Strict-Transport-Security header to every HTTP response." - " Forcing browser to always use SSL for the domain." - " Defends against SSL Stripping.") - helpful.add( - "security", "--no-hsts", action="store_false", dest="hsts", - default=flag_default("hsts"), help=argparse.SUPPRESS) - helpful.add( - ["security", "enhance"], - "--uir", action="store_true", dest="uir", default=flag_default("uir"), - help='Add the "Content-Security-Policy: upgrade-insecure-requests"' - ' header to every HTTP response. Forcing the browser to use' - ' https:// for every http:// resource.') - helpful.add( - "security", "--no-uir", action="store_false", dest="uir", default=flag_default("uir"), - help=argparse.SUPPRESS) - helpful.add( - "security", "--staple-ocsp", action="store_true", dest="staple", - default=flag_default("staple"), - help="Enables OCSP Stapling. A valid OCSP response is stapled to" - " the certificate that the server offers during TLS.") - helpful.add( - "security", "--no-staple-ocsp", action="store_false", dest="staple", - default=flag_default("staple"), help=argparse.SUPPRESS) - helpful.add( - "security", "--strict-permissions", action="store_true", - default=flag_default("strict_permissions"), - help="Require that all configuration files are owned by the current " - "user; only needed if your config is somewhere unsafe like /tmp/") - helpful.add( - ["manual", "standalone", "certonly", "renew"], - "--preferred-challenges", dest="pref_challs", - action=_PrefChallAction, default=flag_default("pref_challs"), - help='A sorted, comma delimited list of the preferred challenge to ' - 'use during authorization with the most preferred challenge ' - 'listed first (Eg, "dns" or "http,dns"). ' - 'Not all plugins support all challenges. See ' - 'https://certbot.eff.org/docs/using.html#plugins for details. ' - 'ACME Challenges are versioned, but if you pick "http" rather ' - 'than "http-01", Certbot will select the latest version ' - 'automatically.') - helpful.add( - "renew", "--pre-hook", - help="Command to be run in a shell before obtaining any certificates." - " Intended primarily for renewal, where it can be used to temporarily" - " shut down a webserver that might conflict with the standalone" - " plugin. This will only be called if a certificate is actually to be" - " obtained/renewed. When renewing several certificates that have" - " identical pre-hooks, only the first will be executed.") - helpful.add( - "renew", "--post-hook", - help="Command to be run in a shell after attempting to obtain/renew" - " certificates. Can be used to deploy renewed certificates, or to" - " restart any servers that were stopped by --pre-hook. This is only" - " run if an attempt was made to obtain/renew a certificate. If" - " multiple renewed certificates have identical post-hooks, only" - " one will be run.") - helpful.add("renew", "--renew-hook", - action=_RenewHookAction, help=argparse.SUPPRESS) - helpful.add( - "renew", "--no-random-sleep-on-renew", action="store_false", - default=flag_default("random_sleep_on_renew"), dest="random_sleep_on_renew", - help=argparse.SUPPRESS) - helpful.add( - "renew", "--deploy-hook", action=_DeployHookAction, - help='Command to be run in a shell once for each successfully' - ' issued certificate. For this command, the shell variable' - ' $RENEWED_LINEAGE will point to the config live subdirectory' - ' (for example, "/etc/letsencrypt/live/example.com") containing' - ' the new certificates and keys; the shell variable' - ' $RENEWED_DOMAINS will contain a space-delimited list of' - ' renewed certificate domains (for example, "example.com' - ' www.example.com"') - helpful.add( - "renew", "--disable-hook-validation", - action="store_false", dest="validate_hooks", - default=flag_default("validate_hooks"), - help="Ordinarily the commands specified for" - " --pre-hook/--post-hook/--deploy-hook will be checked for" - " validity, to see if the programs being run are in the $PATH," - " so that mistakes can be caught early, even when the hooks" - " aren't being run just yet. The validation is rather" - " simplistic and fails if you use more advanced shell" - " constructs, so you can use this switch to disable it." - " (default: False)") - helpful.add( - "renew", "--no-directory-hooks", action="store_false", - default=flag_default("directory_hooks"), dest="directory_hooks", - help="Disable running executables found in Certbot's hook directories" - " during renewal. (default: False)") - helpful.add( - "renew", "--disable-renew-updates", action="store_true", - default=flag_default("disable_renew_updates"), dest="disable_renew_updates", - help="Disable automatic updates to your server configuration that" - " would otherwise be done by the selected installer plugin, and triggered" - " when the user executes \"certbot renew\", regardless of if the certificate" - " is renewed. This setting does not apply to important TLS configuration" - " updates.") - helpful.add( - "renew", "--no-autorenew", action="store_false", - default=flag_default("autorenew"), dest="autorenew", - help="Disable auto renewal of certificates.") - - # Populate the command line parameters for new style enhancements - enhancements.populate_cli(helpful.add) - - _create_subparsers(helpful) - _paths_parser(helpful) - # _plugins_parsing should be the last thing to act upon the main - # parser (--help should display plugin-specific options last) - _plugins_parsing(helpful, plugins) - - if not detect_defaults: - global helpful_parser # pylint: disable=global-statement - helpful_parser = helpful - return helpful.parse_args() - - -def _create_subparsers(helpful): - from certbot._internal.client import sample_user_agent # avoid import loops - helpful.add( - None, "--user-agent", default=flag_default("user_agent"), - help='Set a custom user agent string for the client. User agent strings allow ' - 'the CA to collect high level statistics about success rates by OS, ' - 'plugin and use case, and to know when to deprecate support for past Python ' - "versions and flags. If you wish to hide this information from the Let's " - 'Encrypt server, set this to "". ' - '(default: {0}). The flags encoded in the user agent are: ' - '--duplicate, --force-renew, --allow-subset-of-names, -n, and ' - 'whether any hooks are set.'.format(sample_user_agent())) - helpful.add( - None, "--user-agent-comment", default=flag_default("user_agent_comment"), - type=_user_agent_comment_type, - help="Add a comment to the default user agent string. May be used when repackaging Certbot " - "or calling it from another tool to allow additional statistical data to be collected." - " Ignored if --user-agent is set. (Example: Foo-Wrapper/1.0)") - helpful.add("certonly", - "--csr", default=flag_default("csr"), type=read_file, - help="Path to a Certificate Signing Request (CSR) in DER or PEM format." - " Currently --csr only works with the 'certonly' subcommand.") - helpful.add("revoke", - "--reason", dest="reason", - choices=CaseInsensitiveList(sorted(constants.REVOCATION_REASONS, - key=constants.REVOCATION_REASONS.get)), - action=_EncodeReasonAction, default=flag_default("reason"), - help="Specify reason for revoking certificate. (default: unspecified)") - helpful.add("revoke", - "--delete-after-revoke", action="store_true", - default=flag_default("delete_after_revoke"), - help="Delete certificates after revoking them, along with all previous and later " - "versions of those certificates.") - helpful.add("revoke", - "--no-delete-after-revoke", action="store_false", - dest="delete_after_revoke", - default=flag_default("delete_after_revoke"), - help="Do not delete certificates after revoking them. This " - "option should be used with caution because the 'renew' " - "subcommand will attempt to renew undeleted revoked " - "certificates.") - helpful.add("rollback", - "--checkpoints", type=int, metavar="N", - default=flag_default("rollback_checkpoints"), - help="Revert configuration N number of checkpoints.") - helpful.add("plugins", - "--init", action="store_true", default=flag_default("init"), - help="Initialize plugins.") - helpful.add("plugins", - "--prepare", action="store_true", default=flag_default("prepare"), - help="Initialize and prepare plugins.") - helpful.add("plugins", - "--authenticators", action="append_const", dest="ifaces", - default=flag_default("ifaces"), - const=interfaces.IAuthenticator, help="Limit to authenticator plugins only.") - helpful.add("plugins", - "--installers", action="append_const", dest="ifaces", - default=flag_default("ifaces"), - const=interfaces.IInstaller, help="Limit to installer plugins only.") - - -class CaseInsensitiveList(list): - """A list that will ignore case when searching. - - This class is passed to the `choices` argument of `argparse.add_arguments` - through the `helpful` wrapper. It is necessary due to special handling of - command line arguments by `set_by_cli` in which the `type_func` is not applied.""" - def __contains__(self, element): - return super(CaseInsensitiveList, self).__contains__(element.lower()) - - -def _paths_parser(helpful): - add = helpful.add - verb = helpful.verb - if verb == "help": - verb = helpful.help_arg - - cph = "Path to where certificate is saved (with auth --csr), installed from, or revoked." - sections = ["paths", "install", "revoke", "certonly", "manage"] - if verb == "certonly": - add(sections, "--cert-path", type=os.path.abspath, - default=flag_default("auth_cert_path"), help=cph) - elif verb == "revoke": - add(sections, "--cert-path", type=read_file, required=False, help=cph) - else: - add(sections, "--cert-path", type=os.path.abspath, help=cph) - - section = "paths" - if verb in ("install", "revoke"): - section = verb - # revoke --key-path reads a file, install --key-path takes a string - add(section, "--key-path", - type=((verb == "revoke" and read_file) or os.path.abspath), - help="Path to private key for certificate installation " - "or revocation (if account key is missing)") - - default_cp = None - if verb == "certonly": - default_cp = flag_default("auth_chain_path") - add(["paths", "install"], "--fullchain-path", default=default_cp, type=os.path.abspath, - help="Accompanying path to a full certificate chain (certificate plus chain).") - add("paths", "--chain-path", default=default_cp, type=os.path.abspath, - help="Accompanying path to a certificate chain.") - add("paths", "--config-dir", default=flag_default("config_dir"), - help=config_help("config_dir")) - add("paths", "--work-dir", default=flag_default("work_dir"), - help=config_help("work_dir")) - add("paths", "--logs-dir", default=flag_default("logs_dir"), - help="Logs directory.") - add("paths", "--server", default=flag_default("server"), - help=config_help("server")) - - -def _plugins_parsing(helpful, plugins): - # It's nuts, but there are two "plugins" topics. Somehow this works - helpful.add_group( - "plugins", description="Plugin Selection: Certbot client supports an " - "extensible plugins architecture. See '%(prog)s plugins' for a " - "list of all installed plugins and their names. You can force " - "a particular plugin by setting options provided below. Running " - "--help will list flags specific to that plugin.") - - helpful.add("plugins", "--configurator", default=flag_default("configurator"), - help="Name of the plugin that is both an authenticator and an installer." - " Should not be used together with --authenticator or --installer. " - "(default: Ask)") - helpful.add("plugins", "-a", "--authenticator", default=flag_default("authenticator"), - help="Authenticator plugin name.") - helpful.add("plugins", "-i", "--installer", default=flag_default("installer"), - help="Installer plugin name (also used to find domains).") - helpful.add(["plugins", "certonly", "run", "install"], - "--apache", action="store_true", default=flag_default("apache"), - help="Obtain and install certificates using Apache") - helpful.add(["plugins", "certonly", "run", "install"], - "--nginx", action="store_true", default=flag_default("nginx"), - help="Obtain and install certificates using Nginx") - helpful.add(["plugins", "certonly"], "--standalone", action="store_true", - default=flag_default("standalone"), - help='Obtain certificates using a "standalone" webserver.') - helpful.add(["plugins", "certonly"], "--manual", action="store_true", - default=flag_default("manual"), - help="Provide laborious manual instructions for obtaining a certificate") - helpful.add(["plugins", "certonly"], "--webroot", action="store_true", - default=flag_default("webroot"), - help="Obtain certificates by placing files in a webroot directory.") - helpful.add(["plugins", "certonly"], "--dns-cloudflare", action="store_true", - default=flag_default("dns_cloudflare"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using Cloudflare for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-cloudxns", action="store_true", - default=flag_default("dns_cloudxns"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using CloudXNS for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-digitalocean", action="store_true", - default=flag_default("dns_digitalocean"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using DigitalOcean for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-dnsimple", action="store_true", - default=flag_default("dns_dnsimple"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using DNSimple for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-dnsmadeeasy", action="store_true", - default=flag_default("dns_dnsmadeeasy"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using DNS Made Easy for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-gehirn", action="store_true", - default=flag_default("dns_gehirn"), - help=("Obtain certificates using a DNS TXT record " - "(if you are using Gehirn Infrastructure Service for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-google", action="store_true", - default=flag_default("dns_google"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using Google Cloud DNS).")) - helpful.add(["plugins", "certonly"], "--dns-linode", action="store_true", - default=flag_default("dns_linode"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using Linode for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-luadns", action="store_true", - default=flag_default("dns_luadns"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using LuaDNS for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-nsone", action="store_true", - default=flag_default("dns_nsone"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using NS1 for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-ovh", action="store_true", - default=flag_default("dns_ovh"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using OVH for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-rfc2136", action="store_true", - default=flag_default("dns_rfc2136"), - help="Obtain certificates using a DNS TXT record (if you are using BIND for DNS).") - helpful.add(["plugins", "certonly"], "--dns-route53", action="store_true", - default=flag_default("dns_route53"), - help=("Obtain certificates using a DNS TXT record (if you are using Route53 for " - "DNS).")) - helpful.add(["plugins", "certonly"], "--dns-sakuracloud", action="store_true", - default=flag_default("dns_sakuracloud"), - help=("Obtain certificates using a DNS TXT record " - "(if you are using Sakura Cloud for DNS).")) - - # things should not be reorder past/pre this comment: - # plugins_group should be displayed in --help before plugin - # specific groups (so that plugins_group.description makes sense) - - helpful.add_plugin_args(plugins) - - -class _EncodeReasonAction(argparse.Action): - """Action class for parsing revocation reason.""" - - def __call__(self, parser, namespace, reason, option_string=None): - """Encodes the reason for certificate revocation.""" - code = constants.REVOCATION_REASONS[reason.lower()] - setattr(namespace, self.dest, code) - - -class _DomainsAction(argparse.Action): - """Action class for parsing domains.""" - - def __call__(self, parser, namespace, domain, option_string=None): - """Just wrap add_domains in argparseese.""" - add_domains(namespace, domain) - -def add_domains(args_or_config, domains): - """Registers new domains to be used during the current client run. - - Domains are not added to the list of requested domains if they have - already been registered. - - :param args_or_config: parsed command line arguments - :type args_or_config: argparse.Namespace or - configuration.NamespaceConfig - :param str domain: one or more comma separated domains - - :returns: domains after they have been normalized and validated - :rtype: `list` of `str` - - """ - validated_domains = [] - for domain in domains.split(","): - domain = util.enforce_domain_sanity(domain.strip()) - validated_domains.append(domain) - if domain not in args_or_config.domains: - args_or_config.domains.append(domain) - - return validated_domains - -class _PrefChallAction(argparse.Action): - """Action class for parsing preferred challenges.""" - - def __call__(self, parser, namespace, pref_challs, option_string=None): - try: - challs = parse_preferred_challenges(pref_challs.split(",")) - except errors.Error as error: - raise argparse.ArgumentError(self, str(error)) - namespace.pref_challs.extend(challs) - - -def parse_preferred_challenges(pref_challs): - """Translate and validate preferred challenges. - - :param pref_challs: list of preferred challenge types - :type pref_challs: `list` of `str` - - :returns: validated list of preferred challenge types - :rtype: `list` of `str` - - :raises errors.Error: if pref_challs is invalid - - """ - aliases = {"dns": "dns-01", "http": "http-01"} - challs = [c.strip() for c in pref_challs] - challs = [aliases.get(c, c) for c in challs] - - unrecognized = ", ".join(name for name in challs - if name not in challenges.Challenge.TYPES) - if unrecognized: - raise errors.Error( - "Unrecognized challenges: {0}".format(unrecognized)) - return challs - - -def _user_agent_comment_type(value): - if "(" in value or ")" in value: - raise argparse.ArgumentTypeError("may not contain parentheses") - return value - - -class _DeployHookAction(argparse.Action): - """Action class for parsing deploy hooks.""" - - def __call__(self, parser, namespace, values, option_string=None): - renew_hook_set = namespace.deploy_hook != namespace.renew_hook - if renew_hook_set and namespace.renew_hook != values: - raise argparse.ArgumentError( - self, "conflicts with --renew-hook value") - namespace.deploy_hook = namespace.renew_hook = values - - -class _RenewHookAction(argparse.Action): - """Action class for parsing renew hooks.""" - - def __call__(self, parser, namespace, values, option_string=None): - deploy_hook_set = namespace.deploy_hook is not None - if deploy_hook_set and namespace.deploy_hook != values: - raise argparse.ArgumentError( - self, "conflicts with --deploy-hook value") - namespace.renew_hook = values - - -def nonnegative_int(value): - """Converts value to an int and checks that it is not negative. - - This function should used as the type parameter for argparse - arguments. - - :param str value: value provided on the command line - - :returns: integer representation of value - :rtype: int - - :raises argparse.ArgumentTypeError: if value isn't a non-negative integer - - """ - try: - int_value = int(value) - except ValueError: - raise argparse.ArgumentTypeError("value must be an integer") - - if int_value < 0: - raise argparse.ArgumentTypeError("value must be non-negative") - return int_value diff --git a/certbot/certbot/_internal/cli/__init__.py b/certbot/certbot/_internal/cli/__init__.py new file mode 100644 index 000000000..96dfb163e --- /dev/null +++ b/certbot/certbot/_internal/cli/__init__.py @@ -0,0 +1,526 @@ +"""Certbot command line argument & config processing.""" +# pylint: disable=too-many-lines +from __future__ import print_function +import logging +import logging.handlers +import argparse +import sys +import certbot._internal.plugins.selection as plugin_selection +from certbot._internal.plugins import disco as plugins_disco + +from acme.magic_typing import Optional + +# pylint: disable=ungrouped-imports +import certbot +from certbot._internal import constants + +import certbot.plugins.enhancements as enhancements + + +from certbot._internal.cli.cli_constants import ( + LEAUTO, + old_path_fragment, + new_path_prefix, + cli_command, + SHORT_USAGE, + COMMAND_OVERVIEW, + HELP_AND_VERSION_USAGE, + ARGPARSE_PARAMS_TO_REMOVE, + EXIT_ACTIONS, + ZERO_ARG_ACTIONS, + VAR_MODIFIERS +) + +from certbot._internal.cli.cli_utils import ( + _Default, + read_file, + flag_default, + config_help, + HelpfulArgumentGroup, + CustomHelpFormatter, + _DomainsAction, + add_domains, + CaseInsensitiveList, + _user_agent_comment_type, + _EncodeReasonAction, + parse_preferred_challenges, + _PrefChallAction, + _DeployHookAction, + _RenewHookAction, + nonnegative_int +) + +# These imports depend on cli_constants and cli_utils. +from certbot._internal.cli.report_config_interaction import report_config_interaction +from certbot._internal.cli.verb_help import VERB_HELP, VERB_HELP_MAP +from certbot._internal.cli.group_adder import _add_all_groups +from certbot._internal.cli.subparsers import _create_subparsers +from certbot._internal.cli.paths_parser import _paths_parser +from certbot._internal.cli.plugins_parsing import _plugins_parsing + +# These imports depend on some or all of the submodules for cli. +from certbot._internal.cli.helpful import HelpfulArgumentParser +# pylint: enable=ungrouped-imports + + +logger = logging.getLogger(__name__) + + +# Global, to save us from a lot of argument passing within the scope of this module +helpful_parser = None # type: Optional[HelpfulArgumentParser] + + +def prepare_and_parse_args(plugins, args, detect_defaults=False): + """Returns parsed command line arguments. + + :param .PluginsRegistry plugins: available plugins + :param list args: command line arguments with the program name removed + + :returns: parsed command line arguments + :rtype: argparse.Namespace + + """ + + helpful = HelpfulArgumentParser(args, plugins, detect_defaults) + _add_all_groups(helpful) + + # --help is automatically provided by argparse + helpful.add( + None, "-v", "--verbose", dest="verbose_count", action="count", + default=flag_default("verbose_count"), help="This flag can be used " + "multiple times to incrementally increase the verbosity of output, " + "e.g. -vvv.") + helpful.add( + None, "-t", "--text", dest="text_mode", action="store_true", + default=flag_default("text_mode"), help=argparse.SUPPRESS) + helpful.add( + None, "--max-log-backups", type=nonnegative_int, + default=flag_default("max_log_backups"), + help="Specifies the maximum number of backup logs that should " + "be kept by Certbot's built in log rotation. Setting this " + "flag to 0 disables log rotation entirely, causing " + "Certbot to always append to the same log file.") + helpful.add( + [None, "automation", "run", "certonly", "enhance"], + "-n", "--non-interactive", "--noninteractive", + dest="noninteractive_mode", action="store_true", + default=flag_default("noninteractive_mode"), + help="Run without ever asking for user input. This may require " + "additional command line flags; the client will try to explain " + "which ones are required if it finds one missing") + helpful.add( + [None, "register", "run", "certonly", "enhance"], + constants.FORCE_INTERACTIVE_FLAG, action="store_true", + default=flag_default("force_interactive"), + help="Force Certbot to be interactive even if it detects it's not " + "being run in a terminal. This flag cannot be used with the " + "renew subcommand.") + helpful.add( + [None, "run", "certonly", "certificates", "enhance"], + "-d", "--domains", "--domain", dest="domains", + metavar="DOMAIN", action=_DomainsAction, + default=flag_default("domains"), + help="Domain names to apply. For multiple domains you can use " + "multiple -d flags or enter a comma separated list of domains " + "as a parameter. The first domain provided will be the " + "subject CN of the certificate, and all domains will be " + "Subject Alternative Names on the certificate. " + "The first domain will also be used in " + "some software user interfaces and as the file paths for the " + "certificate and related material unless otherwise " + "specified or you already have a certificate with the same " + "name. In the case of a name collision it will append a number " + "like 0001 to the file path name. (default: Ask)") + helpful.add( + [None, "run", "certonly", "register"], + "--eab-kid", dest="eab_kid", + metavar="EAB_KID", + help="Key Identifier for External Account Binding" + ) + helpful.add( + [None, "run", "certonly", "register"], + "--eab-hmac-key", dest="eab_hmac_key", + metavar="EAB_HMAC_KEY", + help="HMAC key for External Account Binding" + ) + helpful.add( + [None, "run", "certonly", "manage", "delete", "certificates", + "renew", "enhance"], "--cert-name", dest="certname", + metavar="CERTNAME", default=flag_default("certname"), + help="Certificate name to apply. This name is used by Certbot for housekeeping " + "and in file paths; it doesn't affect the content of the certificate itself. " + "To see certificate names, run 'certbot certificates'. " + "When creating a new certificate, specifies the new certificate's name. " + "(default: the first provided domain or the name of an existing " + "certificate on your system for the same domains)") + helpful.add( + [None, "testing", "renew", "certonly"], + "--dry-run", action="store_true", dest="dry_run", + default=flag_default("dry_run"), + help="Perform a test run of the client, obtaining test (invalid) certificates" + " but not saving them to disk. This can currently only be used" + " with the 'certonly' and 'renew' subcommands. \nNote: Although --dry-run" + " tries to avoid making any persistent changes on a system, it " + " is not completely side-effect free: if used with webserver authenticator plugins" + " like apache and nginx, it makes and then reverts temporary config changes" + " in order to obtain test certificates, and reloads webservers to deploy and then" + " roll back those changes. It also calls --pre-hook and --post-hook commands" + " if they are defined because they may be necessary to accurately simulate" + " renewal. --deploy-hook commands are not called.") + helpful.add( + ["register", "automation"], "--register-unsafely-without-email", action="store_true", + default=flag_default("register_unsafely_without_email"), + help="Specifying this flag enables registering an account with no " + "email address. This is strongly discouraged, because in the " + "event of key loss or account compromise you will irrevocably " + "lose access to your account. You will also be unable to receive " + "notice about impending expiration or revocation of your " + "certificates. Updates to the Subscriber Agreement will still " + "affect you, and will be effective 14 days after posting an " + "update to the web site.") + helpful.add( + ["register", "update_account", "unregister", "automation"], "-m", "--email", + default=flag_default("email"), + help=config_help("email")) + helpful.add(["register", "update_account", "automation"], "--eff-email", action="store_true", + default=flag_default("eff_email"), dest="eff_email", + help="Share your e-mail address with EFF") + helpful.add(["register", "update_account", "automation"], "--no-eff-email", + action="store_false", default=flag_default("eff_email"), dest="eff_email", + help="Don't share your e-mail address with EFF") + helpful.add( + ["automation", "certonly", "run"], + "--keep-until-expiring", "--keep", "--reinstall", + dest="reinstall", action="store_true", default=flag_default("reinstall"), + help="If the requested certificate matches an existing certificate, always keep the " + "existing one until it is due for renewal (for the " + "'run' subcommand this means reinstall the existing certificate). (default: Ask)") + helpful.add( + "automation", "--expand", action="store_true", default=flag_default("expand"), + help="If an existing certificate is a strict subset of the requested names, " + "always expand and replace it with the additional names. (default: Ask)") + helpful.add( + "automation", "--version", action="version", + version="%(prog)s {0}".format(certbot.__version__), + help="show program's version number and exit") + helpful.add( + ["automation", "renew"], + "--force-renewal", "--renew-by-default", dest="renew_by_default", + action="store_true", default=flag_default("renew_by_default"), + help="If a certificate " + "already exists for the requested domains, renew it now, " + "regardless of whether it is near expiry. (Often " + "--keep-until-expiring is more appropriate). Also implies " + "--expand.") + helpful.add( + "automation", "--renew-with-new-domains", dest="renew_with_new_domains", + action="store_true", default=flag_default("renew_with_new_domains"), + help="If a " + "certificate already exists for the requested certificate name " + "but does not match the requested domains, renew it now, " + "regardless of whether it is near expiry.") + helpful.add( + "automation", "--reuse-key", dest="reuse_key", + action="store_true", default=flag_default("reuse_key"), + help="When renewing, use the same private key as the existing " + "certificate.") + + helpful.add( + ["automation", "renew", "certonly"], + "--allow-subset-of-names", action="store_true", + default=flag_default("allow_subset_of_names"), + help="When performing domain validation, do not consider it a failure " + "if authorizations can not be obtained for a strict subset of " + "the requested domains. This may be useful for allowing renewals for " + "multiple domains to succeed even if some domains no longer point " + "at this system. This option cannot be used with --csr.") + helpful.add( + "automation", "--agree-tos", dest="tos", action="store_true", + default=flag_default("tos"), + help="Agree to the ACME Subscriber Agreement (default: Ask)") + helpful.add( + ["unregister", "automation"], "--account", metavar="ACCOUNT_ID", + default=flag_default("account"), + help="Account ID to use") + helpful.add( + "automation", "--duplicate", dest="duplicate", action="store_true", + default=flag_default("duplicate"), + help="Allow making a certificate lineage that duplicates an existing one " + "(both can be renewed in parallel)") + helpful.add( + "automation", "--os-packages-only", action="store_true", + default=flag_default("os_packages_only"), + help="(certbot-auto only) install OS package dependencies and then stop") + helpful.add( + "automation", "--no-self-upgrade", action="store_true", + default=flag_default("no_self_upgrade"), + help="(certbot-auto only) prevent the certbot-auto script from" + " upgrading itself to newer released versions (default: Upgrade" + " automatically)") + helpful.add( + "automation", "--no-bootstrap", action="store_true", + default=flag_default("no_bootstrap"), + help="(certbot-auto only) prevent the certbot-auto script from" + " installing OS-level dependencies (default: Prompt to install " + " OS-wide dependencies, but exit if the user says 'No')") + helpful.add( + "automation", "--no-permissions-check", action="store_true", + default=flag_default("no_permissions_check"), + help="(certbot-auto only) skip the check on the file system" + " permissions of the certbot-auto script") + helpful.add( + ["automation", "renew", "certonly", "run"], + "-q", "--quiet", dest="quiet", action="store_true", + default=flag_default("quiet"), + help="Silence all output except errors. Useful for automation via cron." + " Implies --non-interactive.") + # overwrites server, handled in HelpfulArgumentParser.parse_args() + helpful.add(["testing", "revoke", "run"], "--test-cert", "--staging", + dest="staging", action="store_true", default=flag_default("staging"), + help="Use the staging server to obtain or revoke test (invalid) certificates; equivalent" + " to --server " + constants.STAGING_URI) + helpful.add( + "testing", "--debug", action="store_true", default=flag_default("debug"), + help="Show tracebacks in case of errors, and allow certbot-auto " + "execution on experimental platforms") + helpful.add( + [None, "certonly", "run"], "--debug-challenges", action="store_true", + default=flag_default("debug_challenges"), + help="After setting up challenges, wait for user input before " + "submitting to CA") + helpful.add( + "testing", "--no-verify-ssl", action="store_true", + help=config_help("no_verify_ssl"), + default=flag_default("no_verify_ssl")) + helpful.add( + ["testing", "standalone", "manual"], "--http-01-port", type=int, + dest="http01_port", + default=flag_default("http01_port"), help=config_help("http01_port")) + helpful.add( + ["testing", "standalone"], "--http-01-address", + dest="http01_address", + default=flag_default("http01_address"), help=config_help("http01_address")) + helpful.add( + ["testing", "nginx"], "--https-port", type=int, + default=flag_default("https_port"), + help=config_help("https_port")) + helpful.add( + "testing", "--break-my-certs", action="store_true", + default=flag_default("break_my_certs"), + help="Be willing to replace or renew valid certificates with invalid " + "(testing/staging) certificates") + helpful.add( + "security", "--rsa-key-size", type=int, metavar="N", + default=flag_default("rsa_key_size"), help=config_help("rsa_key_size")) + helpful.add( + "security", "--must-staple", action="store_true", + dest="must_staple", default=flag_default("must_staple"), + help=config_help("must_staple")) + helpful.add( + ["security", "enhance"], + "--redirect", action="store_true", dest="redirect", + default=flag_default("redirect"), + help="Automatically redirect all HTTP traffic to HTTPS for the newly " + "authenticated vhost. (default: Ask)") + helpful.add( + "security", "--no-redirect", action="store_false", dest="redirect", + default=flag_default("redirect"), + help="Do not automatically redirect all HTTP traffic to HTTPS for the newly " + "authenticated vhost. (default: Ask)") + helpful.add( + ["security", "enhance"], + "--hsts", action="store_true", dest="hsts", default=flag_default("hsts"), + help="Add the Strict-Transport-Security header to every HTTP response." + " Forcing browser to always use SSL for the domain." + " Defends against SSL Stripping.") + helpful.add( + "security", "--no-hsts", action="store_false", dest="hsts", + default=flag_default("hsts"), help=argparse.SUPPRESS) + helpful.add( + ["security", "enhance"], + "--uir", action="store_true", dest="uir", default=flag_default("uir"), + help='Add the "Content-Security-Policy: upgrade-insecure-requests"' + ' header to every HTTP response. Forcing the browser to use' + ' https:// for every http:// resource.') + helpful.add( + "security", "--no-uir", action="store_false", dest="uir", default=flag_default("uir"), + help=argparse.SUPPRESS) + helpful.add( + "security", "--staple-ocsp", action="store_true", dest="staple", + default=flag_default("staple"), + help="Enables OCSP Stapling. A valid OCSP response is stapled to" + " the certificate that the server offers during TLS.") + helpful.add( + "security", "--no-staple-ocsp", action="store_false", dest="staple", + default=flag_default("staple"), help=argparse.SUPPRESS) + helpful.add( + "security", "--strict-permissions", action="store_true", + default=flag_default("strict_permissions"), + help="Require that all configuration files are owned by the current " + "user; only needed if your config is somewhere unsafe like /tmp/") + helpful.add( + ["manual", "standalone", "certonly", "renew"], + "--preferred-challenges", dest="pref_challs", + action=_PrefChallAction, default=flag_default("pref_challs"), + help='A sorted, comma delimited list of the preferred challenge to ' + 'use during authorization with the most preferred challenge ' + 'listed first (Eg, "dns" or "http,dns"). ' + 'Not all plugins support all challenges. See ' + 'https://certbot.eff.org/docs/using.html#plugins for details. ' + 'ACME Challenges are versioned, but if you pick "http" rather ' + 'than "http-01", Certbot will select the latest version ' + 'automatically.') + helpful.add( + "renew", "--pre-hook", + help="Command to be run in a shell before obtaining any certificates." + " Intended primarily for renewal, where it can be used to temporarily" + " shut down a webserver that might conflict with the standalone" + " plugin. This will only be called if a certificate is actually to be" + " obtained/renewed. When renewing several certificates that have" + " identical pre-hooks, only the first will be executed.") + helpful.add( + "renew", "--post-hook", + help="Command to be run in a shell after attempting to obtain/renew" + " certificates. Can be used to deploy renewed certificates, or to" + " restart any servers that were stopped by --pre-hook. This is only" + " run if an attempt was made to obtain/renew a certificate. If" + " multiple renewed certificates have identical post-hooks, only" + " one will be run.") + helpful.add("renew", "--renew-hook", + action=_RenewHookAction, help=argparse.SUPPRESS) + helpful.add( + "renew", "--no-random-sleep-on-renew", action="store_false", + default=flag_default("random_sleep_on_renew"), dest="random_sleep_on_renew", + help=argparse.SUPPRESS) + helpful.add( + "renew", "--deploy-hook", action=_DeployHookAction, + help='Command to be run in a shell once for each successfully' + ' issued certificate. For this command, the shell variable' + ' $RENEWED_LINEAGE will point to the config live subdirectory' + ' (for example, "/etc/letsencrypt/live/example.com") containing' + ' the new certificates and keys; the shell variable' + ' $RENEWED_DOMAINS will contain a space-delimited list of' + ' renewed certificate domains (for example, "example.com' + ' www.example.com"') + helpful.add( + "renew", "--disable-hook-validation", + action="store_false", dest="validate_hooks", + default=flag_default("validate_hooks"), + help="Ordinarily the commands specified for" + " --pre-hook/--post-hook/--deploy-hook will be checked for" + " validity, to see if the programs being run are in the $PATH," + " so that mistakes can be caught early, even when the hooks" + " aren't being run just yet. The validation is rather" + " simplistic and fails if you use more advanced shell" + " constructs, so you can use this switch to disable it." + " (default: False)") + helpful.add( + "renew", "--no-directory-hooks", action="store_false", + default=flag_default("directory_hooks"), dest="directory_hooks", + help="Disable running executables found in Certbot's hook directories" + " during renewal. (default: False)") + helpful.add( + "renew", "--disable-renew-updates", action="store_true", + default=flag_default("disable_renew_updates"), dest="disable_renew_updates", + help="Disable automatic updates to your server configuration that" + " would otherwise be done by the selected installer plugin, and triggered" + " when the user executes \"certbot renew\", regardless of if the certificate" + " is renewed. This setting does not apply to important TLS configuration" + " updates.") + helpful.add( + "renew", "--no-autorenew", action="store_false", + default=flag_default("autorenew"), dest="autorenew", + help="Disable auto renewal of certificates.") + + # Populate the command line parameters for new style enhancements + enhancements.populate_cli(helpful.add) + + _create_subparsers(helpful) + _paths_parser(helpful) + # _plugins_parsing should be the last thing to act upon the main + # parser (--help should display plugin-specific options last) + _plugins_parsing(helpful, plugins) + + if not detect_defaults: + global helpful_parser # pylint: disable=global-statement + helpful_parser = helpful + return helpful.parse_args() + + +def set_by_cli(var): + """ + Return True if a particular config variable has been set by the user + (CLI or config file) including if the user explicitly set it to the + default. Returns False if the variable was assigned a default value. + """ + detector = set_by_cli.detector # type: ignore + if detector is None and helpful_parser is not None: + # Setup on first run: `detector` is a weird version of config in which + # the default value of every attribute is wrangled to be boolean-false + plugins = plugins_disco.PluginsRegistry.find_all() + # reconstructed_args == sys.argv[1:], or whatever was passed to main() + reconstructed_args = helpful_parser.args + [helpful_parser.verb] + detector = set_by_cli.detector = prepare_and_parse_args( # type: ignore + plugins, reconstructed_args, detect_defaults=True) + # propagate plugin requests: eg --standalone modifies config.authenticator + detector.authenticator, detector.installer = ( # type: ignore + plugin_selection.cli_plugin_requests(detector)) + + if not isinstance(getattr(detector, var), _Default): + logger.debug("Var %s=%s (set by user).", var, getattr(detector, var)) + return True + + for modifier in VAR_MODIFIERS.get(var, []): + if set_by_cli(modifier): + logger.debug("Var %s=%s (set by user).", + var, VAR_MODIFIERS.get(var, [])) + return True + + return False + + +# static housekeeping var +# functions attributed are not supported by mypy +# https://github.com/python/mypy/issues/2087 +set_by_cli.detector = None # type: ignore + + +def has_default_value(option, value): + """Does option have the default value? + + If the default value of option is not known, False is returned. + + :param str option: configuration variable being considered + :param value: value of the configuration variable named option + + :returns: True if option has the default value, otherwise, False + :rtype: bool + + """ + if helpful_parser is not None: + return (option in helpful_parser.defaults and + helpful_parser.defaults[option] == value) + return False + + +def option_was_set(option, value): + """Was option set by the user or does it differ from the default? + + :param str option: configuration variable being considered + :param value: value of the configuration variable named option + + :returns: True if the option was set, otherwise, False + :rtype: bool + + """ + return set_by_cli(option) or not has_default_value(option, value) + + +def argparse_type(variable): + """Return our argparse type function for a config variable (default: str)""" + # pylint: disable=protected-access + if helpful_parser is not None: + for action in helpful_parser.parser._actions: + if action.type is not None and action.dest == variable: + return action.type + return str diff --git a/certbot/certbot/_internal/cli/cli_constants.py b/certbot/certbot/_internal/cli/cli_constants.py new file mode 100644 index 000000000..748ae0d94 --- /dev/null +++ b/certbot/certbot/_internal/cli/cli_constants.py @@ -0,0 +1,107 @@ +"""Certbot command line constants""" +import sys + +from certbot.compat import os + +# For help strings, figure out how the user ran us. +# When invoked from letsencrypt-auto, sys.argv[0] is something like: +# "/home/user/.local/share/certbot/bin/certbot" +# Note that this won't work if the user set VENV_PATH or XDG_DATA_HOME before +# running letsencrypt-auto (and sudo stops us from seeing if they did), so it +# should only be used for purposes where inability to detect letsencrypt-auto +# fails safely + +LEAUTO = "letsencrypt-auto" +if "CERTBOT_AUTO" in os.environ: + # if we're here, this is probably going to be certbot-auto, unless the + # user saved the script under a different name + LEAUTO = os.path.basename(os.environ["CERTBOT_AUTO"]) + +old_path_fragment = os.path.join(".local", "share", "letsencrypt") +new_path_prefix = os.path.abspath(os.path.join(os.sep, "opt", + "eff.org", "certbot", "venv")) +if old_path_fragment in sys.argv[0] or sys.argv[0].startswith(new_path_prefix): + cli_command = LEAUTO +else: + cli_command = "certbot" + + +# Argparse's help formatting has a lot of unhelpful peculiarities, so we want +# to replace as much of it as we can... + +# This is the stub to include in help generated by argparse +SHORT_USAGE = """ + {0} [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ... + +Certbot can obtain and install HTTPS/TLS/SSL certificates. By default, +it will attempt to use a webserver both for obtaining and installing the +certificate. """.format(cli_command) + +# This section is used for --help and --help all ; it needs information +# about installed plugins to be fully formatted +COMMAND_OVERVIEW = """The most common SUBCOMMANDS and flags are: + +obtain, install, and renew certificates: + (default) run Obtain & install a certificate in your current webserver + certonly Obtain or renew a certificate, but do not install it + renew Renew all previously obtained certificates that are near expiry + enhance Add security enhancements to your existing configuration + -d DOMAINS Comma-separated list of domains to obtain a certificate for + + %s + --standalone Run a standalone webserver for authentication + %s + --webroot Place files in a server's webroot folder for authentication + --manual Obtain certificates interactively, or using shell script hooks + + -n Run non-interactively + --test-cert Obtain a test certificate from a staging server + --dry-run Test "renew" or "certonly" without saving any certificates to disk + +manage certificates: + certificates Display information about certificates you have from Certbot + revoke Revoke a certificate (supply --cert-name or --cert-path) + delete Delete a certificate (supply --cert-name) + +manage your account: + register Create an ACME account + unregister Deactivate an ACME account + update_account Update an ACME account + --agree-tos Agree to the ACME server's Subscriber Agreement + -m EMAIL Email address for important account notifications +""" + +# This is the short help for certbot --help, where we disable argparse +# altogether +HELP_AND_VERSION_USAGE = """ +More detailed help: + + -h, --help [TOPIC] print this message, or detailed help on a topic; + the available TOPICS are: + + all, automation, commands, paths, security, testing, or any of the + subcommands or plugins (certonly, renew, install, register, nginx, + apache, standalone, webroot, etc.) + -h all print a detailed help page including all topics + --version print the version number +""" + +# These argparse parameters should be removed when detecting defaults. +ARGPARSE_PARAMS_TO_REMOVE = ("const", "nargs", "type",) + + +# These sets are used when to help detect options set by the user. +EXIT_ACTIONS = set(("help", "version",)) + + +ZERO_ARG_ACTIONS = set(("store_const", "store_true", + "store_false", "append_const", "count",)) + + +# Maps a config option to a set of config options that may have modified it. +# This dictionary is used recursively, so if A modifies B and B modifies C, +# it is determined that C was modified by the user if A was modified. +VAR_MODIFIERS = {"account": set(("server",)), + "renew_hook": set(("deploy_hook",)), + "server": set(("dry_run", "staging",)), + "webroot_map": set(("webroot_path",))} diff --git a/certbot/certbot/_internal/cli/cli_utils.py b/certbot/certbot/_internal/cli/cli_utils.py new file mode 100644 index 000000000..a0ddce38f --- /dev/null +++ b/certbot/certbot/_internal/cli/cli_utils.py @@ -0,0 +1,239 @@ +"""Certbot command line util function""" +import argparse +import copy + +import zope.interface.interface # pylint: disable=unused-import + +from acme import challenges +from certbot import interfaces +from certbot import util +from certbot import errors +from certbot.compat import os +from certbot._internal import constants + + +class _Default(object): + """A class to use as a default to detect if a value is set by a user""" + + def __bool__(self): + return False + + def __eq__(self, other): + return isinstance(other, _Default) + + def __hash__(self): + return id(_Default) + + def __nonzero__(self): + return self.__bool__() + + +def read_file(filename, mode="rb"): + """Returns the given file's contents. + + :param str filename: path to file + :param str mode: open mode (see `open`) + + :returns: absolute path of filename and its contents + :rtype: tuple + + :raises argparse.ArgumentTypeError: File does not exist or is not readable. + + """ + try: + filename = os.path.abspath(filename) + with open(filename, mode) as the_file: + contents = the_file.read() + return filename, contents + except IOError as exc: + raise argparse.ArgumentTypeError(exc.strerror) + + +def flag_default(name): + """Default value for CLI flag.""" + # XXX: this is an internal housekeeping notion of defaults before + # argparse has been set up; it is not accurate for all flags. Call it + # with caution. Plugin defaults are missing, and some things are using + # defaults defined in this file, not in constants.py :( + return copy.deepcopy(constants.CLI_DEFAULTS[name]) + + +def config_help(name, hidden=False): + """Extract the help message for an `.IConfig` attribute.""" + if hidden: + return argparse.SUPPRESS + field = interfaces.IConfig.__getitem__(name) # type: zope.interface.interface.Attribute + return field.__doc__ + + +class HelpfulArgumentGroup(object): + """Emulates an argparse group for use with HelpfulArgumentParser. + + This class is used in the add_group method of HelpfulArgumentParser. + Command line arguments can be added to the group, but help + suppression and default detection is applied by + HelpfulArgumentParser when necessary. + + """ + def __init__(self, helpful_arg_parser, topic): + self._parser = helpful_arg_parser + self._topic = topic + + def add_argument(self, *args, **kwargs): + """Add a new command line argument to the argument group.""" + self._parser.add(self._topic, *args, **kwargs) + + +class CustomHelpFormatter(argparse.HelpFormatter): + """This is a clone of ArgumentDefaultsHelpFormatter, with bugfixes. + + In particular we fix https://bugs.python.org/issue28742 + """ + + def _get_help_string(self, action): + helpstr = action.help + if '%(default)' not in action.help and '(default:' not in action.help: + if action.default != argparse.SUPPRESS: + defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE] + if action.option_strings or action.nargs in defaulting_nargs: + helpstr += ' (default: %(default)s)' + return helpstr + + +class _DomainsAction(argparse.Action): + """Action class for parsing domains.""" + + def __call__(self, parser, namespace, domain, option_string=None): + """Just wrap add_domains in argparseese.""" + add_domains(namespace, domain) + + +def add_domains(args_or_config, domains): + """Registers new domains to be used during the current client run. + + Domains are not added to the list of requested domains if they have + already been registered. + + :param args_or_config: parsed command line arguments + :type args_or_config: argparse.Namespace or + configuration.NamespaceConfig + :param str domain: one or more comma separated domains + + :returns: domains after they have been normalized and validated + :rtype: `list` of `str` + + """ + validated_domains = [] + for domain in domains.split(","): + domain = util.enforce_domain_sanity(domain.strip()) + validated_domains.append(domain) + if domain not in args_or_config.domains: + args_or_config.domains.append(domain) + + return validated_domains + + +class CaseInsensitiveList(list): + """A list that will ignore case when searching. + + This class is passed to the `choices` argument of `argparse.add_arguments` + through the `helpful` wrapper. It is necessary due to special handling of + command line arguments by `set_by_cli` in which the `type_func` is not applied.""" + def __contains__(self, element): + return super(CaseInsensitiveList, self).__contains__(element.lower()) + + +def _user_agent_comment_type(value): + if "(" in value or ")" in value: + raise argparse.ArgumentTypeError("may not contain parentheses") + return value + + +class _EncodeReasonAction(argparse.Action): + """Action class for parsing revocation reason.""" + + def __call__(self, parser, namespace, reason, option_string=None): + """Encodes the reason for certificate revocation.""" + code = constants.REVOCATION_REASONS[reason.lower()] + setattr(namespace, self.dest, code) + + +def parse_preferred_challenges(pref_challs): + """Translate and validate preferred challenges. + + :param pref_challs: list of preferred challenge types + :type pref_challs: `list` of `str` + + :returns: validated list of preferred challenge types + :rtype: `list` of `str` + + :raises errors.Error: if pref_challs is invalid + + """ + aliases = {"dns": "dns-01", "http": "http-01"} + challs = [c.strip() for c in pref_challs] + challs = [aliases.get(c, c) for c in challs] + + unrecognized = ", ".join(name for name in challs + if name not in challenges.Challenge.TYPES) + if unrecognized: + raise errors.Error( + "Unrecognized challenges: {0}".format(unrecognized)) + return challs + + +class _PrefChallAction(argparse.Action): + """Action class for parsing preferred challenges.""" + + def __call__(self, parser, namespace, pref_challs, option_string=None): + try: + challs = parse_preferred_challenges(pref_challs.split(",")) + except errors.Error as error: + raise argparse.ArgumentError(self, str(error)) + namespace.pref_challs.extend(challs) + + +class _DeployHookAction(argparse.Action): + """Action class for parsing deploy hooks.""" + + def __call__(self, parser, namespace, values, option_string=None): + renew_hook_set = namespace.deploy_hook != namespace.renew_hook + if renew_hook_set and namespace.renew_hook != values: + raise argparse.ArgumentError( + self, "conflicts with --renew-hook value") + namespace.deploy_hook = namespace.renew_hook = values + + +class _RenewHookAction(argparse.Action): + """Action class for parsing renew hooks.""" + + def __call__(self, parser, namespace, values, option_string=None): + deploy_hook_set = namespace.deploy_hook is not None + if deploy_hook_set and namespace.deploy_hook != values: + raise argparse.ArgumentError( + self, "conflicts with --deploy-hook value") + namespace.renew_hook = values + + +def nonnegative_int(value): + """Converts value to an int and checks that it is not negative. + + This function should used as the type parameter for argparse + arguments. + + :param str value: value provided on the command line + + :returns: integer representation of value + :rtype: int + + :raises argparse.ArgumentTypeError: if value isn't a non-negative integer + + """ + try: + int_value = int(value) + except ValueError: + raise argparse.ArgumentTypeError("value must be an integer") + + if int_value < 0: + raise argparse.ArgumentTypeError("value must be non-negative") + return int_value diff --git a/certbot/certbot/_internal/cli/group_adder.py b/certbot/certbot/_internal/cli/group_adder.py new file mode 100644 index 000000000..f22fbc496 --- /dev/null +++ b/certbot/certbot/_internal/cli/group_adder.py @@ -0,0 +1,19 @@ +"""This module contains a function to add the groups of arguments for the help +display""" +from certbot._internal.cli import VERB_HELP + + +def _add_all_groups(helpful): + helpful.add_group("automation", description="Flags for automating execution & other tweaks") + helpful.add_group("security", description="Security parameters & server settings") + helpful.add_group("testing", + description="The following flags are meant for testing and integration purposes only.") + helpful.add_group("paths", description="Flags for changing execution paths & servers") + helpful.add_group("manage", + description="Various subcommands and flags are available for managing your certificates:", + verbs=["certificates", "delete", "renew", "revoke", "update_symlinks"]) + + # VERBS + for verb, docs in VERB_HELP: + name = docs.get("realname", verb) + helpful.add_group(name, description=docs["opts"]) diff --git a/certbot/certbot/_internal/cli/helpful.py b/certbot/certbot/_internal/cli/helpful.py new file mode 100644 index 000000000..e63ab4b87 --- /dev/null +++ b/certbot/certbot/_internal/cli/helpful.py @@ -0,0 +1,468 @@ +"""Certbot command line argument parser""" +from __future__ import print_function +import argparse +import copy +import glob +import sys +import configargparse +import six +import zope.component +import zope.interface + +from zope.interface import interfaces as zope_interfaces + +# pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any, Dict, Optional +# pylint: enable=unused-import, no-name-in-module + +from certbot import crypto_util +from certbot import errors +from certbot import interfaces +from certbot import util +from certbot.compat import os +from certbot._internal import constants +from certbot._internal import hooks + +from certbot.display import util as display_util + +from certbot._internal.cli import ( + SHORT_USAGE, + CustomHelpFormatter, + flag_default, + VERB_HELP, + VERB_HELP_MAP, + COMMAND_OVERVIEW, + HELP_AND_VERSION_USAGE, + _Default, + add_domains, + EXIT_ACTIONS, + ZERO_ARG_ACTIONS, + ARGPARSE_PARAMS_TO_REMOVE, + HelpfulArgumentGroup +) + + +class HelpfulArgumentParser(object): + """Argparse Wrapper. + + This class wraps argparse, adding the ability to make --help less + verbose, and request help on specific subcategories at a time, eg + 'certbot --help security' for security options. + + """ + def __init__(self, args, plugins, detect_defaults=False): + from certbot._internal import main + self.VERBS = { + "auth": main.certonly, + "certonly": main.certonly, + "run": main.run, + "install": main.install, + "plugins": main.plugins_cmd, + "register": main.register, + "update_account": main.update_account, + "unregister": main.unregister, + "renew": main.renew, + "revoke": main.revoke, + "rollback": main.rollback, + "everything": main.run, + "update_symlinks": main.update_symlinks, + "certificates": main.certificates, + "delete": main.delete, + "enhance": main.enhance, + } + + # Get notification function for printing + try: + self.notify = zope.component.getUtility( + interfaces.IDisplay).notification + except zope_interfaces.ComponentLookupError: + self.notify = display_util.NoninteractiveDisplay( + sys.stdout).notification + + + # List of topics for which additional help can be provided + HELP_TOPICS = ["all", "security", "paths", "automation", "testing"] + HELP_TOPICS += list(self.VERBS) + self.COMMANDS_TOPICS + ["manage"] + + plugin_names = list(plugins) + self.help_topics = HELP_TOPICS + plugin_names + [None] # type: ignore + + self.detect_defaults = detect_defaults + self.args = args + + if self.args and self.args[0] == 'help': + self.args[0] = '--help' + + self.determine_verb() + help1 = self.prescan_for_flag("-h", self.help_topics) + help2 = self.prescan_for_flag("--help", self.help_topics) + if isinstance(help1, bool) and isinstance(help2, bool): + self.help_arg = help1 or help2 + else: + self.help_arg = help1 if isinstance(help1, six.string_types) else help2 + + short_usage = self._usage_string(plugins, self.help_arg) + + self.visible_topics = self.determine_help_topics(self.help_arg) + + # elements are added by .add_group() + self.groups = {} # type: Dict[str, argparse._ArgumentGroup] + # elements are added by .parse_args() + self.defaults = {} # type: Dict[str, Any] + + self.parser = configargparse.ArgParser( + prog="certbot", + usage=short_usage, + formatter_class=CustomHelpFormatter, + args_for_setting_config_path=["-c", "--config"], + default_config_files=flag_default("config_files"), + config_arg_help_message="path to config file (default: {0})".format( + " and ".join(flag_default("config_files")))) + + # This is the only way to turn off overly verbose config flag documentation + self.parser._add_config_file_help = False + + # Help that are synonyms for --help subcommands + COMMANDS_TOPICS = ["command", "commands", "subcommand", "subcommands", "verbs"] + + def _list_subcommands(self): + longest = max(len(v) for v in VERB_HELP_MAP) + + text = "The full list of available SUBCOMMANDS is:\n\n" + for verb, props in sorted(VERB_HELP): + doc = props.get("short", "") + text += '{0:<{length}} {1}\n'.format(verb, doc, length=longest) + + text += "\nYou can get more help on a specific subcommand with --help SUBCOMMAND\n" + return text + + def _usage_string(self, plugins, help_arg): + """Make usage strings late so that plugins can be initialised late + + :param plugins: all discovered plugins + :param help_arg: False for none; True for --help; "TOPIC" for --help TOPIC + :rtype: str + :returns: a short usage string for the top of --help TOPIC) + """ + if "nginx" in plugins: + nginx_doc = "--nginx Use the Nginx plugin for authentication & installation" + else: + nginx_doc = "(the certbot nginx plugin is not installed)" + if "apache" in plugins: + apache_doc = "--apache Use the Apache plugin for authentication & installation" + else: + apache_doc = "(the certbot apache plugin is not installed)" + + usage = SHORT_USAGE + if help_arg is True: + self.notify(usage + COMMAND_OVERVIEW % (apache_doc, nginx_doc) + HELP_AND_VERSION_USAGE) + sys.exit(0) + elif help_arg in self.COMMANDS_TOPICS: + self.notify(usage + self._list_subcommands()) + sys.exit(0) + elif help_arg == "all": + # if we're doing --help all, the OVERVIEW is part of the SHORT_USAGE at + # the top; if we're doing --help someothertopic, it's OT so it's not + usage += COMMAND_OVERVIEW % (apache_doc, nginx_doc) + else: + custom = VERB_HELP_MAP.get(help_arg, {}).get("usage", None) + usage = custom if custom else usage + + return usage + + def remove_config_file_domains_for_renewal(self, parsed_args): + """Make "certbot renew" safe if domains are set in cli.ini.""" + # Works around https://github.com/certbot/certbot/issues/4096 + if self.verb == "renew": + for source, flags in self.parser._source_to_settings.items(): # pylint: disable=protected-access + if source.startswith("config_file") and "domains" in flags: + parsed_args.domains = _Default() if self.detect_defaults else [] + + def parse_args(self): + """Parses command line arguments and returns the result. + + :returns: parsed command line arguments + :rtype: argparse.Namespace + + """ + parsed_args = self.parser.parse_args(self.args) + parsed_args.func = self.VERBS[self.verb] + parsed_args.verb = self.verb + + self.remove_config_file_domains_for_renewal(parsed_args) + + if self.detect_defaults: + return parsed_args + + self.defaults = dict((key, copy.deepcopy(self.parser.get_default(key))) + for key in vars(parsed_args)) + + # Do any post-parsing homework here + + if self.verb == "renew": + if parsed_args.force_interactive: + raise errors.Error( + "{0} cannot be used with renew".format( + constants.FORCE_INTERACTIVE_FLAG)) + parsed_args.noninteractive_mode = True + + if parsed_args.force_interactive and parsed_args.noninteractive_mode: + raise errors.Error( + "Flag for non-interactive mode and {0} conflict".format( + constants.FORCE_INTERACTIVE_FLAG)) + + if parsed_args.staging or parsed_args.dry_run: + self.set_test_server(parsed_args) + + if parsed_args.csr: + self.handle_csr(parsed_args) + + if parsed_args.must_staple: + parsed_args.staple = True + + if parsed_args.validate_hooks: + hooks.validate_hooks(parsed_args) + + if parsed_args.allow_subset_of_names: + if any(util.is_wildcard_domain(d) for d in parsed_args.domains): + raise errors.Error("Using --allow-subset-of-names with a" + " wildcard domain is not supported.") + + if parsed_args.hsts and parsed_args.auto_hsts: + raise errors.Error( + "Parameters --hsts and --auto-hsts cannot be used simultaneously.") + + return parsed_args + + def set_test_server(self, parsed_args): + """We have --staging/--dry-run; perform sanity check and set config.server""" + + # Flag combinations should produce these results: + # | --staging | --dry-run | + # ------------------------------------------------------------ + # | --server acme-v02 | Use staging | Use staging | + # | --server acme-staging-v02 | Use staging | Use staging | + # | --server | Conflict error | Use | + + default_servers = (flag_default("server"), constants.STAGING_URI) + + if parsed_args.staging and parsed_args.server not in default_servers: + raise errors.Error("--server value conflicts with --staging") + + if parsed_args.server in default_servers: + parsed_args.server = constants.STAGING_URI + + if parsed_args.dry_run: + if self.verb not in ["certonly", "renew"]: + raise errors.Error("--dry-run currently only works with the " + "'certonly' or 'renew' subcommands (%r)" % self.verb) + parsed_args.break_my_certs = parsed_args.staging = True + if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, "*")): + # The user has a prod account, but might not have a staging + # one; we don't want to start trying to perform interactive registration + parsed_args.tos = True + parsed_args.register_unsafely_without_email = True + + def handle_csr(self, parsed_args): + """Process a --csr flag.""" + if parsed_args.verb != "certonly": + raise errors.Error("Currently, a CSR file may only be specified " + "when obtaining a new or replacement " + "via the certonly command. Please try the " + "certonly command instead.") + if parsed_args.allow_subset_of_names: + raise errors.Error("--allow-subset-of-names cannot be used with --csr") + + csrfile, contents = parsed_args.csr[0:2] + typ, csr, domains = crypto_util.import_csr_file(csrfile, contents) + + # This is not necessary for webroot to work, however, + # obtain_certificate_from_csr requires parsed_args.domains to be set + for domain in domains: + add_domains(parsed_args, domain) + + if not domains: + # TODO: add CN to domains instead: + raise errors.Error( + "Unfortunately, your CSR %s needs to have a SubjectAltName for every domain" + % parsed_args.csr[0]) + + parsed_args.actual_csr = (csr, typ) + + csr_domains = {d.lower() for d in domains} + config_domains = set(parsed_args.domains) + if csr_domains != config_domains: + raise errors.ConfigurationError( + "Inconsistent domain requests:\nFrom the CSR: {0}\nFrom command line/config: {1}" + .format(", ".join(csr_domains), ", ".join(config_domains))) + + + def determine_verb(self): + """Determines the verb/subcommand provided by the user. + + This function works around some of the limitations of argparse. + + """ + if "-h" in self.args or "--help" in self.args: + # all verbs double as help arguments; don't get them confused + self.verb = "help" + return + + for i, token in enumerate(self.args): + if token in self.VERBS: + verb = token + if verb == "auth": + verb = "certonly" + if verb == "everything": + verb = "run" + self.verb = verb + self.args.pop(i) + return + + self.verb = "run" + + def prescan_for_flag(self, flag, possible_arguments): + """Checks cli input for flags. + + Check for a flag, which accepts a fixed set of possible arguments, in + the command line; we will use this information to configure argparse's + help correctly. Return the flag's argument, if it has one that matches + the sequence @possible_arguments; otherwise return whether the flag is + present. + + """ + if flag not in self.args: + return False + pos = self.args.index(flag) + try: + nxt = self.args[pos + 1] + if nxt in possible_arguments: + return nxt + except IndexError: + pass + return True + + def add(self, topics, *args, **kwargs): + """Add a new command line argument. + + :param topics: str or [str] help topic(s) this should be listed under, + or None for options that don't fit under a specific + topic which will only be shown in "--help all" output. + The first entry determines where the flag lives in the + "--help all" output (None -> "optional arguments"). + :param list *args: the names of this argument flag + :param dict **kwargs: various argparse settings for this argument + + """ + + if isinstance(topics, list): + # if this flag can be listed in multiple sections, try to pick the one + # that the user has asked for help about + topic = self.help_arg if self.help_arg in topics else topics[0] + else: + topic = topics # there's only one + + if self.detect_defaults: + kwargs = self.modify_kwargs_for_default_detection(**kwargs) + + if self.visible_topics[topic]: + if topic in self.groups: + group = self.groups[topic] + group.add_argument(*args, **kwargs) + else: + self.parser.add_argument(*args, **kwargs) + else: + kwargs["help"] = argparse.SUPPRESS + self.parser.add_argument(*args, **kwargs) + + def modify_kwargs_for_default_detection(self, **kwargs): + """Modify an arg so we can check if it was set by the user. + + Changes the parameters given to argparse when adding an argument + so we can properly detect if the value was set by the user. + + :param dict kwargs: various argparse settings for this argument + + :returns: a modified versions of kwargs + :rtype: dict + + """ + action = kwargs.get("action", None) + if action not in EXIT_ACTIONS: + kwargs["action"] = ("store_true" if action in ZERO_ARG_ACTIONS else + "store") + kwargs["default"] = _Default() + for param in ARGPARSE_PARAMS_TO_REMOVE: + kwargs.pop(param, None) + + return kwargs + + def add_deprecated_argument(self, argument_name, num_args): + """Adds a deprecated argument with the name argument_name. + + Deprecated arguments are not shown in the help. If they are used + on the command line, a warning is shown stating that the + argument is deprecated and no other action is taken. + + :param str argument_name: Name of deprecated argument. + :param int nargs: Number of arguments the option takes. + + """ + util.add_deprecated_argument( + self.parser.add_argument, argument_name, num_args) + + def add_group(self, topic, verbs=(), **kwargs): + """Create a new argument group. + + This method must be called once for every topic, however, calls + to this function are left next to the argument definitions for + clarity. + + :param str topic: Name of the new argument group. + :param str verbs: List of subcommands that should be documented as part of + this help group / topic + + :returns: The new argument group. + :rtype: `HelpfulArgumentGroup` + + """ + if self.visible_topics[topic]: + self.groups[topic] = self.parser.add_argument_group(topic, **kwargs) + if self.help_arg: + for v in verbs: + self.groups[topic].add_argument(v, help=VERB_HELP_MAP[v]["short"]) + return HelpfulArgumentGroup(self, topic) + + def add_plugin_args(self, plugins): + """ + + Let each of the plugins add its own command line arguments, which + may or may not be displayed as help topics. + + """ + for name, plugin_ep in six.iteritems(plugins): + parser_or_group = self.add_group(name, + description=plugin_ep.long_description) + plugin_ep.plugin_cls.inject_parser_options(parser_or_group, name) + + def determine_help_topics(self, chosen_topic): + """ + + The user may have requested help on a topic, return a dict of which + topics to display. @chosen_topic has prescan_for_flag's return type + + :returns: dict + + """ + # topics maps each topic to whether it should be documented by + # argparse on the command line + if chosen_topic == "auth": + chosen_topic = "certonly" + if chosen_topic == "everything": + chosen_topic = "run" + if chosen_topic == "all": + # Addition of condition closes #6209 (removal of duplicate route53 option). + return {t: t != 'certbot-route53:auth' for t in self.help_topics} + elif not chosen_topic: + return {t: False for t in self.help_topics} + return {t: t == chosen_topic for t in self.help_topics} diff --git a/certbot/certbot/_internal/cli/paths_parser.py b/certbot/certbot/_internal/cli/paths_parser.py new file mode 100644 index 000000000..4378435d7 --- /dev/null +++ b/certbot/certbot/_internal/cli/paths_parser.py @@ -0,0 +1,50 @@ +"""This is a module that adds configuration to the argument parser regarding +paths for certificates""" +from certbot.compat import os +from certbot._internal.cli import ( + read_file, + flag_default, + config_help +) + + +def _paths_parser(helpful): + add = helpful.add + verb = helpful.verb + if verb == "help": + verb = helpful.help_arg + + cph = "Path to where certificate is saved (with auth --csr), installed from, or revoked." + sections = ["paths", "install", "revoke", "certonly", "manage"] + if verb == "certonly": + add(sections, "--cert-path", type=os.path.abspath, + default=flag_default("auth_cert_path"), help=cph) + elif verb == "revoke": + add(sections, "--cert-path", type=read_file, required=False, help=cph) + else: + add(sections, "--cert-path", type=os.path.abspath, help=cph) + + section = "paths" + if verb in ("install", "revoke"): + section = verb + # revoke --key-path reads a file, install --key-path takes a string + add(section, "--key-path", + type=((verb == "revoke" and read_file) or os.path.abspath), + help="Path to private key for certificate installation " + "or revocation (if account key is missing)") + + default_cp = None + if verb == "certonly": + default_cp = flag_default("auth_chain_path") + add(["paths", "install"], "--fullchain-path", default=default_cp, type=os.path.abspath, + help="Accompanying path to a full certificate chain (certificate plus chain).") + add("paths", "--chain-path", default=default_cp, type=os.path.abspath, + help="Accompanying path to a certificate chain.") + add("paths", "--config-dir", default=flag_default("config_dir"), + help=config_help("config_dir")) + add("paths", "--work-dir", default=flag_default("work_dir"), + help=config_help("work_dir")) + add("paths", "--logs-dir", default=flag_default("logs_dir"), + help="Logs directory.") + add("paths", "--server", default=flag_default("server"), + help=config_help("server")) diff --git a/certbot/certbot/_internal/cli/plugins_parsing.py b/certbot/certbot/_internal/cli/plugins_parsing.py new file mode 100644 index 000000000..9e11ad3ab --- /dev/null +++ b/certbot/certbot/_internal/cli/plugins_parsing.py @@ -0,0 +1,97 @@ +"""This is a module that handles parsing of plugins for the argument parser""" +from certbot._internal.cli import flag_default + + +def _plugins_parsing(helpful, plugins): + # It's nuts, but there are two "plugins" topics. Somehow this works + helpful.add_group( + "plugins", description="Plugin Selection: Certbot client supports an " + "extensible plugins architecture. See '%(prog)s plugins' for a " + "list of all installed plugins and their names. You can force " + "a particular plugin by setting options provided below. Running " + "--help will list flags specific to that plugin.") + + helpful.add("plugins", "--configurator", default=flag_default("configurator"), + help="Name of the plugin that is both an authenticator and an installer." + " Should not be used together with --authenticator or --installer. " + "(default: Ask)") + helpful.add("plugins", "-a", "--authenticator", default=flag_default("authenticator"), + help="Authenticator plugin name.") + helpful.add("plugins", "-i", "--installer", default=flag_default("installer"), + help="Installer plugin name (also used to find domains).") + helpful.add(["plugins", "certonly", "run", "install"], + "--apache", action="store_true", default=flag_default("apache"), + help="Obtain and install certificates using Apache") + helpful.add(["plugins", "certonly", "run", "install"], + "--nginx", action="store_true", default=flag_default("nginx"), + help="Obtain and install certificates using Nginx") + helpful.add(["plugins", "certonly"], "--standalone", action="store_true", + default=flag_default("standalone"), + help='Obtain certificates using a "standalone" webserver.') + helpful.add(["plugins", "certonly"], "--manual", action="store_true", + default=flag_default("manual"), + help="Provide laborious manual instructions for obtaining a certificate") + helpful.add(["plugins", "certonly"], "--webroot", action="store_true", + default=flag_default("webroot"), + help="Obtain certificates by placing files in a webroot directory.") + helpful.add(["plugins", "certonly"], "--dns-cloudflare", action="store_true", + default=flag_default("dns_cloudflare"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using Cloudflare for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-cloudxns", action="store_true", + default=flag_default("dns_cloudxns"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using CloudXNS for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-digitalocean", action="store_true", + default=flag_default("dns_digitalocean"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using DigitalOcean for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-dnsimple", action="store_true", + default=flag_default("dns_dnsimple"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using DNSimple for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-dnsmadeeasy", action="store_true", + default=flag_default("dns_dnsmadeeasy"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using DNS Made Easy for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-gehirn", action="store_true", + default=flag_default("dns_gehirn"), + help=("Obtain certificates using a DNS TXT record " + "(if you are using Gehirn Infrastructure Service for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-google", action="store_true", + default=flag_default("dns_google"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using Google Cloud DNS).")) + helpful.add(["plugins", "certonly"], "--dns-linode", action="store_true", + default=flag_default("dns_linode"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using Linode for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-luadns", action="store_true", + default=flag_default("dns_luadns"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using LuaDNS for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-nsone", action="store_true", + default=flag_default("dns_nsone"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using NS1 for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-ovh", action="store_true", + default=flag_default("dns_ovh"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using OVH for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-rfc2136", action="store_true", + default=flag_default("dns_rfc2136"), + help="Obtain certificates using a DNS TXT record (if you are using BIND for DNS).") + helpful.add(["plugins", "certonly"], "--dns-route53", action="store_true", + default=flag_default("dns_route53"), + help=("Obtain certificates using a DNS TXT record (if you are using Route53 for " + "DNS).")) + helpful.add(["plugins", "certonly"], "--dns-sakuracloud", action="store_true", + default=flag_default("dns_sakuracloud"), + help=("Obtain certificates using a DNS TXT record " + "(if you are using Sakura Cloud for DNS).")) + + # things should not be reorder past/pre this comment: + # plugins_group should be displayed in --help before plugin + # specific groups (so that plugins_group.description makes sense) + + helpful.add_plugin_args(plugins) diff --git a/certbot/certbot/_internal/cli/report_config_interaction.py b/certbot/certbot/_internal/cli/report_config_interaction.py new file mode 100644 index 000000000..39266e776 --- /dev/null +++ b/certbot/certbot/_internal/cli/report_config_interaction.py @@ -0,0 +1,27 @@ +"""This is a module that reports config option interaction that should be +checked by set_by_cli""" +import six + +from certbot._internal.cli import VAR_MODIFIERS + + +def report_config_interaction(modified, modifiers): + """Registers config option interaction to be checked by set_by_cli. + + This function can be called by during the __init__ or + add_parser_arguments methods of plugins to register interactions + between config options. + + :param modified: config options that can be modified by modifiers + :type modified: iterable or str (string_types) + :param modifiers: config options that modify modified + :type modifiers: iterable or str (string_types) + + """ + if isinstance(modified, six.string_types): + modified = (modified,) + if isinstance(modifiers, six.string_types): + modifiers = (modifiers,) + + for var in modified: + VAR_MODIFIERS.setdefault(var, set()).update(modifiers) diff --git a/certbot/certbot/_internal/cli/subparsers.py b/certbot/certbot/_internal/cli/subparsers.py new file mode 100644 index 000000000..13f8705ce --- /dev/null +++ b/certbot/certbot/_internal/cli/subparsers.py @@ -0,0 +1,72 @@ +"""This module creates subparsers for the argument parser""" +from certbot import interfaces +from certbot._internal import constants + +from certbot._internal.cli import ( + flag_default, + read_file, + CaseInsensitiveList, + _user_agent_comment_type, + _EncodeReasonAction +) + + +def _create_subparsers(helpful): + from certbot._internal.client import sample_user_agent # avoid import loops + helpful.add( + None, "--user-agent", default=flag_default("user_agent"), + help='Set a custom user agent string for the client. User agent strings allow ' + 'the CA to collect high level statistics about success rates by OS, ' + 'plugin and use case, and to know when to deprecate support for past Python ' + "versions and flags. If you wish to hide this information from the Let's " + 'Encrypt server, set this to "". ' + '(default: {0}). The flags encoded in the user agent are: ' + '--duplicate, --force-renew, --allow-subset-of-names, -n, and ' + 'whether any hooks are set.'.format(sample_user_agent())) + helpful.add( + None, "--user-agent-comment", default=flag_default("user_agent_comment"), + type=_user_agent_comment_type, + help="Add a comment to the default user agent string. May be used when repackaging Certbot " + "or calling it from another tool to allow additional statistical data to be collected." + " Ignored if --user-agent is set. (Example: Foo-Wrapper/1.0)") + helpful.add("certonly", + "--csr", default=flag_default("csr"), type=read_file, + help="Path to a Certificate Signing Request (CSR) in DER or PEM format." + " Currently --csr only works with the 'certonly' subcommand.") + helpful.add("revoke", + "--reason", dest="reason", + choices=CaseInsensitiveList(sorted(constants.REVOCATION_REASONS, + key=constants.REVOCATION_REASONS.get)), + action=_EncodeReasonAction, default=flag_default("reason"), + help="Specify reason for revoking certificate. (default: unspecified)") + helpful.add("revoke", + "--delete-after-revoke", action="store_true", + default=flag_default("delete_after_revoke"), + help="Delete certificates after revoking them, along with all previous and later " + "versions of those certificates.") + helpful.add("revoke", + "--no-delete-after-revoke", action="store_false", + dest="delete_after_revoke", + default=flag_default("delete_after_revoke"), + help="Do not delete certificates after revoking them. This " + "option should be used with caution because the 'renew' " + "subcommand will attempt to renew undeleted revoked " + "certificates.") + helpful.add("rollback", + "--checkpoints", type=int, metavar="N", + default=flag_default("rollback_checkpoints"), + help="Revert configuration N number of checkpoints.") + helpful.add("plugins", + "--init", action="store_true", default=flag_default("init"), + help="Initialize plugins.") + helpful.add("plugins", + "--prepare", action="store_true", default=flag_default("prepare"), + help="Initialize and prepare plugins.") + helpful.add("plugins", + "--authenticators", action="append_const", dest="ifaces", + default=flag_default("ifaces"), + const=interfaces.IAuthenticator, help="Limit to authenticator plugins only.") + helpful.add("plugins", + "--installers", action="append_const", dest="ifaces", + default=flag_default("ifaces"), + const=interfaces.IInstaller, help="Limit to installer plugins only.") diff --git a/certbot/certbot/_internal/cli/verb_help.py b/certbot/certbot/_internal/cli/verb_help.py new file mode 100644 index 000000000..131cfec96 --- /dev/null +++ b/certbot/certbot/_internal/cli/verb_help.py @@ -0,0 +1,106 @@ +"""This module contain help information for verbs supported by certbot""" +from certbot.compat import os +from certbot._internal.cli import ( + SHORT_USAGE, + flag_default +) + +# The attributes here are: +# short: a string that will be displayed by "certbot -h commands" +# opts: a string that heads the section of flags with which this command is documented, +# both for "certbot -h SUBCOMMAND" and "certbot -h all" +# usage: an optional string that overrides the header of "certbot -h SUBCOMMAND" +VERB_HELP = [ + ("run (default)", { + "short": "Obtain/renew a certificate, and install it", + "opts": "Options for obtaining & installing certificates", + "usage": SHORT_USAGE.replace("[SUBCOMMAND]", ""), + "realname": "run" + }), + ("certonly", { + "short": "Obtain or renew a certificate, but do not install it", + "opts": "Options for modifying how a certificate is obtained", + "usage": ("\n\n certbot certonly [options] [-d DOMAIN] [-d DOMAIN] ...\n\n" + "This command obtains a TLS/SSL certificate without installing it anywhere.") + }), + ("renew", { + "short": "Renew all certificates (or one specified with --cert-name)", + "opts": ("The 'renew' subcommand will attempt to renew all" + " certificates (or more precisely, certificate lineages) you have" + " previously obtained if they are close to expiry, and print a" + " summary of the results. By default, 'renew' will reuse the options" + " used to create obtain or most recently successfully renew each" + " certificate lineage. You can try it with `--dry-run` first. For" + " more fine-grained control, you can renew individual lineages with" + " the `certonly` subcommand. Hooks are available to run commands" + " before and after renewal; see" + " https://certbot.eff.org/docs/using.html#renewal for more" + " information on these."), + "usage": "\n\n certbot renew [--cert-name CERTNAME] [options]\n\n" + }), + ("certificates", { + "short": "List certificates managed by Certbot", + "opts": "List certificates managed by Certbot", + "usage": ("\n\n certbot certificates [options] ...\n\n" + "Print information about the status of certificates managed by Certbot.") + }), + ("delete", { + "short": "Clean up all files related to a certificate", + "opts": "Options for deleting a certificate", + "usage": "\n\n certbot delete --cert-name CERTNAME\n\n" + }), + ("revoke", { + "short": "Revoke a certificate specified with --cert-path or --cert-name", + "opts": "Options for revocation of certificates", + "usage": "\n\n certbot revoke [--cert-path /path/to/fullchain.pem | " + "--cert-name example.com] [options]\n\n" + }), + ("register", { + "short": "Register for account with Let's Encrypt / other ACME server", + "opts": "Options for account registration", + "usage": "\n\n certbot register --email user@example.com [options]\n\n" + }), + ("update_account", { + "short": "Update existing account with Let's Encrypt / other ACME server", + "opts": "Options for account modification", + "usage": "\n\n certbot update_account --email updated_email@example.com [options]\n\n" + }), + ("unregister", { + "short": "Irrevocably deactivate your account", + "opts": "Options for account deactivation.", + "usage": "\n\n certbot unregister [options]\n\n" + }), + ("install", { + "short": "Install an arbitrary certificate in a server", + "opts": "Options for modifying how a certificate is deployed", + "usage": "\n\n certbot install --cert-path /path/to/fullchain.pem " + " --key-path /path/to/private-key [options]\n\n" + }), + ("rollback", { + "short": "Roll back server conf changes made during certificate installation", + "opts": "Options for rolling back server configuration changes", + "usage": "\n\n certbot rollback --checkpoints 3 [options]\n\n" + }), + ("plugins", { + "short": "List plugins that are installed and available on your system", + "opts": 'Options for the "plugins" subcommand', + "usage": "\n\n certbot plugins [options]\n\n" + }), + ("update_symlinks", { + "short": "Recreate symlinks in your /etc/letsencrypt/live/ directory", + "opts": ("Recreates certificate and key symlinks in {0}, if you changed them by hand " + "or edited a renewal configuration file".format( + os.path.join(flag_default("config_dir"), "live"))), + "usage": "\n\n certbot update_symlinks [options]\n\n" + }), + ("enhance", { + "short": "Add security enhancements to your existing configuration", + "opts": ("Helps to harden the TLS configuration by adding security enhancements " + "to already existing configuration."), + "usage": "\n\n certbot enhance [options]\n\n" + }), +] + + +# VERB_HELP is a list in order to preserve order, but a dict is sometimes useful +VERB_HELP_MAP = dict(VERB_HELP) diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index 05da1da4e..3a7fb57f8 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -93,7 +93,7 @@ class ParseTest(unittest.TestCase): return output.getvalue() - @mock.patch("certbot._internal.cli.flag_default") + @mock.patch("certbot._internal.cli.helpful.flag_default") def test_cli_ini_domains(self, mock_flag_default): with tempfile.NamedTemporaryFile() as tmp_config: tmp_config.close() # close now because of compatibility issues on Windows diff --git a/certbot/tests/helpful_test.py b/certbot/tests/helpful_test.py new file mode 100644 index 000000000..292e55304 --- /dev/null +++ b/certbot/tests/helpful_test.py @@ -0,0 +1,193 @@ +"""Tests for certbot.helpful_parser""" +import unittest + +from certbot import errors +from certbot._internal.cli import HelpfulArgumentParser +from certbot._internal.cli import _DomainsAction +from certbot._internal import constants + + +class TestScanningFlags(unittest.TestCase): + '''Test the prescan_for_flag method of HelpfulArgumentParser''' + def test_prescan_no_help_flag(self): + arg_parser = HelpfulArgumentParser(['run'], {}) + detected_flag = arg_parser.prescan_for_flag('--help', + ['all', 'certonly']) + self.assertFalse(detected_flag) + detected_flag = arg_parser.prescan_for_flag('-h', + ['all, certonly']) + self.assertFalse(detected_flag) + + def test_prescan_unvalid_topic(self): + arg_parser = HelpfulArgumentParser(['--help', 'all'], {}) + detected_flag = arg_parser.prescan_for_flag('--help', + ['potato']) + self.assertIs(detected_flag, True) + detected_flag = arg_parser.prescan_for_flag('-h', + arg_parser.help_topics) + self.assertFalse(detected_flag) + + def test_prescan_valid_topic(self): + arg_parser = HelpfulArgumentParser(['-h', 'all'], {}) + detected_flag = arg_parser.prescan_for_flag('-h', + arg_parser.help_topics) + self.assertEqual(detected_flag, 'all') + detected_flag = arg_parser.prescan_for_flag('--help', + arg_parser.help_topics) + self.assertFalse(detected_flag) + +class TestDetermineVerbs(unittest.TestCase): + '''Tests for determine_verb methods of HelpfulArgumentParser''' + def test_determine_verb_wrong_verb(self): + arg_parser = HelpfulArgumentParser(['potato'], {}) + self.assertEqual(arg_parser.verb, "run") + self.assertEqual(arg_parser.args, ["potato"]) + + def test_determine_verb_help(self): + arg_parser = HelpfulArgumentParser(['--help', 'everything'], {}) + self.assertEqual(arg_parser.verb, "help") + self.assertEqual(arg_parser.args, ["--help", "everything"]) + arg_parser = HelpfulArgumentParser(['-d', 'some_domain', '--help', + 'all'], {}) + self.assertEqual(arg_parser.verb, "help") + self.assertEqual(arg_parser.args, ['-d', 'some_domain', '--help', + 'all']) + + def test_determine_verb(self): + arg_parser = HelpfulArgumentParser(['certonly'], {}) + self.assertEqual(arg_parser.verb, 'certonly') + self.assertEqual(arg_parser.args, []) + + arg_parser = HelpfulArgumentParser(['auth'], {}) + self.assertEqual(arg_parser.verb, 'certonly') + self.assertEqual(arg_parser.args, []) + + arg_parser = HelpfulArgumentParser(['everything'], {}) + self.assertEqual(arg_parser.verb, 'run') + self.assertEqual(arg_parser.args, []) + + +class TestAdd(unittest.TestCase): + '''Tests for add method in HelpfulArgumentParser''' + def test_add_trivial_argument(self): + arg_parser = HelpfulArgumentParser(['run'], {}) + arg_parser.add(None, "--hello-world") + parsed_args = arg_parser.parser.parse_args(['--hello-world', + 'Hello World!']) + self.assertIs(parsed_args.hello_world, 'Hello World!') + self.assertFalse(hasattr(parsed_args, 'potato')) + + def test_add_expected_argument(self): + arg_parser = HelpfulArgumentParser(['--help', 'run'], {}) + arg_parser.add( + [None, "run", "certonly", "register"], + "--eab-kid", dest="eab_kid", action="store", + metavar="EAB_KID", + help="Key Identifier for External Account Binding") + parsed_args = arg_parser.parser.parse_args(["--eab-kid", None]) + self.assertIs(parsed_args.eab_kid, None) + self.assertTrue(hasattr(parsed_args, 'eab_kid')) + + +class TestAddGroup(unittest.TestCase): + '''Test add_group method of HelpfulArgumentParser''' + def test_add_group_no_input(self): + arg_parser = HelpfulArgumentParser(['run'], {}) + self.assertRaises(TypeError, arg_parser.add_group) + + def test_add_group_topic_not_visible(self): + # The user request help on run. A topic that given somewhere in the + # args won't be added to the groups in the parser. + arg_parser = HelpfulArgumentParser(['--help', 'run'], {}) + arg_parser.add_group("auth", + description="description of auth") + self.assertEqual(arg_parser.groups, {}) + + def test_add_group_topic_requested_help(self): + arg_parser = HelpfulArgumentParser(['--help', 'run'], {}) + arg_parser.add_group("run", + description="description of run") + self.assertTrue(arg_parser.groups["run"]) + arg_parser.add_group("certonly", description="description of certonly") + with self.assertRaises(KeyError): + self.assertFalse(arg_parser.groups["certonly"]) + + +class TestParseArgsErrors(unittest.TestCase): + '''Tests for errors that should be met for some cases in parse_args method + in HelpfulArgumentParser''' + def test_parse_args_renew_force_interactive(self): + arg_parser = HelpfulArgumentParser(['renew', '--force-interactive'], + {}) + arg_parser.add( + None, constants.FORCE_INTERACTIVE_FLAG, action="store_true") + + with self.assertRaises(errors.Error): + arg_parser.parse_args() + + def test_parse_args_non_interactive_and_force_interactive(self): + arg_parser = HelpfulArgumentParser(['--force-interactive', + '--non-interactive'], {}) + arg_parser.add( + None, constants.FORCE_INTERACTIVE_FLAG, action="store_true") + arg_parser.add( + None, "--non-interactive", dest="noninteractive_mode", + action="store_true" + ) + + with self.assertRaises(errors.Error): + arg_parser.parse_args() + + def test_parse_args_subset_names_wildcard_domain(self): + arg_parser = HelpfulArgumentParser(['--domain', + '*.example.com,potato.example.com', + '--allow-subset-of-names'], {}) + # The following arguments are added because they have to be defined + # in order for arg_parser to run completely. They are not used for the + # test. + arg_parser.add( + None, constants.FORCE_INTERACTIVE_FLAG, action="store_true") + arg_parser.add( + None, "--non-interactive", dest="noninteractive_mode", + action="store_true") + arg_parser.add( + None, "--staging" + ) + arg_parser.add(None, "--dry-run") + arg_parser.add(None, "--csr") + arg_parser.add(None, "--must-staple") + arg_parser.add(None, "--validate-hooks") + + arg_parser.add(None, "-d", "--domain", dest="domains", + metavar="DOMAIN", action=_DomainsAction) + arg_parser.add(None, "--allow-subset-of-names") + # with self.assertRaises(errors.Error): + # arg_parser.parse_args() + + def test_parse_args_hosts_and_auto_hosts(self): + arg_parser = HelpfulArgumentParser(['--hsts', '--auto-hsts'], {}) + + arg_parser.add( + None, "--hsts", action="store_true", dest="hsts") + arg_parser.add( + None, "--auto-hsts", action="store_true", dest="auto_hsts") + # The following arguments are added because they have to be defined + # in order for arg_parser to run completely. They are not used for the + # test. + arg_parser.add( + None, constants.FORCE_INTERACTIVE_FLAG, action="store_true") + arg_parser.add( + None, "--non-interactive", dest="noninteractive_mode", + action="store_true") + arg_parser.add(None, "--staging") + arg_parser.add(None, "--dry-run") + arg_parser.add(None, "--csr") + arg_parser.add(None, "--must-staple") + arg_parser.add(None, "--validate-hooks") + arg_parser.add(None, "--allow-subset-of-names") + with self.assertRaises(errors.Error): + arg_parser.parse_args() + + +if __name__ == '__main__': + unittest.main() # pragma: no cover From 9819443440382695b74b77379d76e4886c0bdf70 Mon Sep 17 00:00:00 2001 From: osirisinferi Date: Sat, 22 Feb 2020 15:22:27 +0100 Subject: [PATCH 099/122] Add test --- certbot-apache/tests/http_01_test.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/certbot-apache/tests/http_01_test.py b/certbot-apache/tests/http_01_test.py index 643a6bdd5..422a76443 100644 --- a/certbot-apache/tests/http_01_test.py +++ b/certbot-apache/tests/http_01_test.py @@ -1,5 +1,6 @@ """Test for certbot_apache._internal.http_01.""" import unittest +import errno import mock @@ -197,6 +198,12 @@ class ApacheHttp01Test(util.ApacheTest): self.assertTrue(os.path.exists(challenge_dir)) + @mock.patch("certbot_apache._internal.http_01.filesystem.makedirs") + def test_failed_makedirs(self, mock_makedirs): + mock_makedirs.side_effect = OSError(errno.EACCES, "msg") + self.http.add_chall(self.achalls[0]) + self.assertRaises(errors.PluginError, self.http.perform) + def _test_challenge_conf(self): with open(self.http.challenge_conf_pre) as f: pre_conf_contents = f.read() From 2633c3ffb6a4f66933daef238b6a140ffc059818 Mon Sep 17 00:00:00 2001 From: alexzorin Date: Mon, 24 Feb 2020 07:49:42 +1100 Subject: [PATCH 100/122] acme: ignore params in content-type check (#7342) * acme: ignore params in content-type check Fixes the warning in #7339 * Suppress coverage complaint in test * Update CHANGELOG * Repair symlink Co-authored-by: Adrien Ferrand --- acme/acme/client.py | 3 +++ acme/tests/client_test.py | 29 +++++++++++++++++++++++++++++ certbot/CHANGELOG.md | 1 + 3 files changed, 33 insertions(+) diff --git a/acme/acme/client.py b/acme/acme/client.py index 3e03748b5..cecb727c7 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -1022,6 +1022,9 @@ class ClientNetwork(object): """ response_ct = response.headers.get('Content-Type') + # Strip parameters from the media-type (rfc2616#section-3.7) + if response_ct: + response_ct = response_ct.split(';')[0].strip() try: # TODO: response.json() is called twice, once here, and # once in _get and _post clients diff --git a/acme/tests/client_test.py b/acme/tests/client_test.py index a38fedbd6..a4966140f 100644 --- a/acme/tests/client_test.py +++ b/acme/tests/client_test.py @@ -980,6 +980,35 @@ class ClientNetworkTest(unittest.TestCase): self.assertEqual( self.response, self.net._check_response(self.response)) + @mock.patch('acme.client.logger') + def test_check_response_ok_ct_with_charset(self, mock_logger): + self.response.json.return_value = {} + self.response.headers['Content-Type'] = 'application/json; charset=utf-8' + # pylint: disable=protected-access + self.assertEqual(self.response, self.net._check_response( + self.response, content_type='application/json')) + try: + mock_logger.debug.assert_called_with( + 'Ignoring wrong Content-Type (%r) for JSON decodable response', + 'application/json; charset=utf-8' + ) + except AssertionError: + return + raise AssertionError('Expected Content-Type warning ' #pragma: no cover + 'to not have been logged') + + @mock.patch('acme.client.logger') + def test_check_response_ok_bad_ct(self, mock_logger): + self.response.json.return_value = {} + self.response.headers['Content-Type'] = 'text/plain' + # pylint: disable=protected-access + self.assertEqual(self.response, self.net._check_response( + self.response, content_type='application/json')) + mock_logger.debug.assert_called_with( + 'Ignoring wrong Content-Type (%r) for JSON decodable response', + 'text/plain' + ) + def test_check_response_conflict(self): self.response.ok = False self.response.status_code = 409 diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 126b07eec..bc5ad90d6 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -14,6 +14,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed * certbot._internal.cli is now a package split in submodules instead of a whole module. +* Fix acme module warnings when response Content-Type includes params (e.g. charset). ### Fixed From 4fd04366aad02e2fa51057d4912346bde9e39d02 Mon Sep 17 00:00:00 2001 From: martin-c Date: Sun, 23 Feb 2020 22:14:51 +0100 Subject: [PATCH 101/122] Fix issue #7165 in _create_challenge_dirs(), attempt to fix pylint errors (#7568) * fix issue #7165 by checking if directory exists before trying to create it, fix possible pylint issues in webroot.py * fix get_chall_pref definition * Update CHANGELOG.md * Update CHANGELOG.md Co-authored-by: Adrien Ferrand --- certbot/CHANGELOG.md | 2 ++ certbot/certbot/_internal/plugins/webroot.py | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index bc5ad90d6..30479f25b 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -15,6 +15,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * certbot._internal.cli is now a package split in submodules instead of a whole module. * Fix acme module warnings when response Content-Type includes params (e.g. charset). +* Fixed issue where webroot plugin would incorrectly raise `Read-only file system` + error when creating challenge directories (issue #7165). ### Fixed diff --git a/certbot/certbot/_internal/plugins/webroot.py b/certbot/certbot/_internal/plugins/webroot.py index 042b60656..9383ce66d 100644 --- a/certbot/certbot/_internal/plugins/webroot.py +++ b/certbot/certbot/_internal/plugins/webroot.py @@ -1,7 +1,6 @@ """Webroot plugin.""" import argparse import collections -import errno import json import logging @@ -71,7 +70,7 @@ to serve all files under specified web root ({0}).""" super(Authenticator, self).__init__(*args, **kwargs) self.full_roots = {} # type: Dict[str, str] self.performed = collections.defaultdict(set) \ - # type: DefaultDict[str, Set[achallenges.KeyAuthorizationAnnotatedChallenge]] + # type: DefaultDict[str, Set[achallenges.KeyAuthorizationAnnotatedChallenge]] # stack of dirs successfully created by this authenticator self._created_dirs = [] # type: List[str] @@ -137,7 +136,7 @@ to serve all files under specified web root ({0}).""" "webroot when using the webroot plugin.") return None if index == 0 else known_webroots[index - 1] # code == display_util.OK - def _prompt_for_new_webroot(self, domain, allowraise=False): + def _prompt_for_new_webroot(self, domain, allowraise=False): # pylint: no-self-use code, webroot = ops.validated_directory( _validate_webroot, "Input the webroot for {0}:".format(domain), @@ -170,6 +169,10 @@ to serve all files under specified web root ({0}).""" # We ignore the last prefix in the next iteration, # as it does not correspond to a folder path ('/' or 'C:') for prefix in sorted(util.get_prefixes(self.full_roots[name])[:-1], key=len): + if os.path.isdir(prefix): + # Don't try to create directory if it already exists, as some filesystems + # won't reliably raise EEXIST or EISDIR if directory exists. + continue try: # Set owner as parent directory if possible, apply mode for Linux/Windows. # For Linux, this is coupled with the "umask" call above because @@ -184,14 +187,13 @@ to serve all files under specified web root ({0}).""" logger.info("Unable to change owner and uid of webroot directory") logger.debug("Error was: %s", exception) except OSError as exception: - if exception.errno not in (errno.EEXIST, errno.EISDIR): - raise errors.PluginError( - "Couldn't create root for {0} http-01 " - "challenge responses: {1}".format(name, exception)) + raise errors.PluginError( + "Couldn't create root for {0} http-01 " + "challenge responses: {1}".format(name, exception)) finally: os.umask(old_umask) - def _get_validation_path(self, root_path, achall): + def _get_validation_path(self, root_path, achall): # pylint: no-self-use return os.path.join(root_path, achall.chall.encode("token")) def _perform_single(self, achall): From 4ea98d830bcc3d1b980a4055243c6a6a25d8dc54 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 24 Feb 2020 12:31:16 -0800 Subject: [PATCH 102/122] remove _internal docs (#7801) --- certbot/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 30479f25b..57fbc820b 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -13,7 +13,6 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed -* certbot._internal.cli is now a package split in submodules instead of a whole module. * Fix acme module warnings when response Content-Type includes params (e.g. charset). * Fixed issue where webroot plugin would incorrectly raise `Read-only file system` error when creating challenge directories (issue #7165). From f4c0a9fd63c9be4cd4e745dd5f701040bcd14682 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 27 Feb 2020 10:43:41 -0800 Subject: [PATCH 103/122] Split advanced pipeline (#7813) I want to do what I did in https://github.com/certbot/certbot/pull/7733 to our Azure Pipelines setup, but unfortunately this isn't currently possible. The only filters available for service hooks for the "build completed" trigger are the pipeline and build status. See ![Screen Shot 2020-02-26 at 3 04 56 PM](https://user-images.githubusercontent.com/6504915/75396464-64ad0780-58a9-11ea-97a1-3454a9754675.png) To accomplish this, I propose splitting the "advanced" pipeline into two cases. One is for builds on protected branches where we want to be notified if they fail while the other is just used to manually run tests on certain branches. --- .azure-pipelines/advanced-test.yml | 12 ++++++++++++ .azure-pipelines/advanced.yml | 10 ++-------- .azure-pipelines/release.yml | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 .azure-pipelines/advanced-test.yml diff --git a/.azure-pipelines/advanced-test.yml b/.azure-pipelines/advanced-test.yml new file mode 100644 index 000000000..b9ac9c38a --- /dev/null +++ b/.azure-pipelines/advanced-test.yml @@ -0,0 +1,12 @@ +# Advanced pipeline for running our full test suite on demand. +trigger: + # When changing these triggers, please ensure the documentation under + # "Running tests in CI" is still correct. + - azure-test-* + - test-* + +jobs: + # Any addition here should be reflected in the advanced and release pipelines. + # It is advised to declare all jobs here as templates to improve maintainability. + - template: templates/tests-suite.yml + - template: templates/installer-tests.yml diff --git a/.azure-pipelines/advanced.yml b/.azure-pipelines/advanced.yml index dda7f9bfd..7f0f5de50 100644 --- a/.azure-pipelines/advanced.yml +++ b/.azure-pipelines/advanced.yml @@ -1,12 +1,6 @@ -# Advanced pipeline for isolated checks and release purpose +# Advanced pipeline for running our full test suite on protected branches. trigger: - # When changing these triggers, please ensure the documentation under - # "Running tests in CI" is still correct. - - azure-test-* - - test-* - '*.x' -pr: - - test-* # This pipeline is also nightly run on master schedules: - cron: "0 4 * * *" @@ -17,7 +11,7 @@ schedules: always: true jobs: - # Any addition here should be reflected in the release pipeline. + # Any addition here should be reflected in the advanced-test and release pipelines. # It is advised to declare all jobs here as templates to improve maintainability. - template: templates/tests-suite.yml - template: templates/installer-tests.yml diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index aeb5ee327..e9acbc69a 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -6,7 +6,7 @@ trigger: pr: none jobs: - # Any addition here should be reflected in the advanced pipeline. + # Any addition here should be reflected in the advanced and advanced-test pipelines. # It is advised to declare all jobs here as templates to improve maintainability. - template: templates/tests-suite.yml - template: templates/installer-tests.yml From 24aa1e9127802f9c6ac459bbf91e6ff9b4595483 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 27 Feb 2020 10:47:43 -0800 Subject: [PATCH 104/122] update letstest reqs (#7809) I don't fully understand why, but since I updated my macbook to macOS Catalina, the test script currently fails to run for me with the versions of our dependencies we have pinned. Updating the dependencies solves the problem though and you can see Travis also successfully running tests with these new dependencies at https://travis-ci.com/certbot/certbot/builds/150573696. --- tests/letstest/requirements.txt | 38 ++++++++++++++------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/tests/letstest/requirements.txt b/tests/letstest/requirements.txt index 64e1f6a0c..24bd77331 100644 --- a/tests/letstest/requirements.txt +++ b/tests/letstest/requirements.txt @@ -1,25 +1,19 @@ -asn1crypto==0.24.0 -awscli==1.16.157 -bcrypt==3.1.6 -boto3==1.9.146 -botocore==1.12.147 -cffi==1.12.3 -colorama==0.3.9 -cryptography==2.4.2 -docutils==0.14 -enum34==1.1.6 +bcrypt==3.1.7 +boto3==1.12.7 +botocore==1.15.7 +cffi==1.14.0 +cryptography==2.8 +docutils==0.15.2 +enum34==1.1.9 Fabric==1.14.1 -futures==3.2.0 -idna==2.8 -ipaddress==1.0.22 -jmespath==0.9.4 -paramiko==2.4.2 -pyasn1==0.4.5 +futures==3.3.0 +ipaddress==1.0.23 +jmespath==0.9.5 +paramiko==2.7.1 pycparser==2.19 PyNaCl==1.3.0 -python-dateutil==2.8.0 -PyYAML==3.10 -rsa==3.4.2 -s3transfer==0.2.0 -six==1.12.0 -urllib3==1.24.3 +python-dateutil==2.8.1 +PyYAML==5.3 +s3transfer==0.3.3 +six==1.14.0 +urllib3==1.25.8 From 8c75a9de9fe28ca0e4baf8686620a9c6b5733515 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 27 Feb 2020 10:47:56 -0800 Subject: [PATCH 105/122] Remove unused notify code. (#7805) This code is unused and hasn't been modified since 2015 except for various times our files have been renamed. Let's remove it. --- certbot/certbot/_internal/notify.py | 34 ------------------- certbot/tests/notify_test.py | 52 ----------------------------- 2 files changed, 86 deletions(-) delete mode 100644 certbot/certbot/_internal/notify.py delete mode 100644 certbot/tests/notify_test.py diff --git a/certbot/certbot/_internal/notify.py b/certbot/certbot/_internal/notify.py deleted file mode 100644 index dda0a85af..000000000 --- a/certbot/certbot/_internal/notify.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Send e-mail notification to system administrators.""" - -import email -import smtplib -import socket -import subprocess - - -def notify(subject, whom, what): - """Send email notification. - - Try to notify the addressee (``whom``) by e-mail, with Subject: - defined by ``subject`` and message body by ``what``. - - """ - msg = email.message_from_string(what) - msg.add_header("From", "Certbot renewal agent ") - msg.add_header("To", whom) - msg.add_header("Subject", subject) - msg = msg.as_string() - try: - lmtp = smtplib.LMTP() - lmtp.connect() - lmtp.sendmail("root", [whom], msg) - except (smtplib.SMTPHeloError, smtplib.SMTPRecipientsRefused, - smtplib.SMTPSenderRefused, smtplib.SMTPDataError, socket.error): - # We should try using /usr/sbin/sendmail in this case - try: - proc = subprocess.Popen(["/usr/sbin/sendmail", "-t"], - stdin=subprocess.PIPE) - proc.communicate(msg) - except OSError: - return False - return True diff --git a/certbot/tests/notify_test.py b/certbot/tests/notify_test.py deleted file mode 100644 index d6f7d2239..000000000 --- a/certbot/tests/notify_test.py +++ /dev/null @@ -1,52 +0,0 @@ -"""Tests for certbot._internal.notify.""" -import socket -import unittest - -import mock - - -class NotifyTests(unittest.TestCase): - """Tests for the notifier.""" - - @mock.patch("certbot._internal.notify.smtplib.LMTP") - def test_smtp_success(self, mock_lmtp): - from certbot._internal.notify import notify - lmtp_obj = mock.MagicMock() - mock_lmtp.return_value = lmtp_obj - self.assertTrue(notify("Goose", "auntrhody@example.com", - "The old grey goose is dead.")) - self.assertEqual(lmtp_obj.connect.call_count, 1) - self.assertEqual(lmtp_obj.sendmail.call_count, 1) - - @mock.patch("certbot._internal.notify.smtplib.LMTP") - @mock.patch("certbot._internal.notify.subprocess.Popen") - def test_smtp_failure(self, mock_popen, mock_lmtp): - from certbot._internal.notify import notify - lmtp_obj = mock.MagicMock() - mock_lmtp.return_value = lmtp_obj - lmtp_obj.sendmail.side_effect = socket.error(17) - proc = mock.MagicMock() - mock_popen.return_value = proc - self.assertTrue(notify("Goose", "auntrhody@example.com", - "The old grey goose is dead.")) - self.assertEqual(lmtp_obj.sendmail.call_count, 1) - self.assertEqual(proc.communicate.call_count, 1) - - @mock.patch("certbot._internal.notify.smtplib.LMTP") - @mock.patch("certbot._internal.notify.subprocess.Popen") - def test_everything_fails(self, mock_popen, mock_lmtp): - from certbot._internal.notify import notify - lmtp_obj = mock.MagicMock() - mock_lmtp.return_value = lmtp_obj - lmtp_obj.sendmail.side_effect = socket.error(17) - proc = mock.MagicMock() - mock_popen.return_value = proc - proc.communicate.side_effect = OSError("What we have here is a " - "failure to communicate.") - self.assertFalse(notify("Goose", "auntrhody@example.com", - "The old grey goose is dead.")) - self.assertEqual(lmtp_obj.sendmail.call_count, 1) - self.assertEqual(proc.communicate.call_count, 1) - -if __name__ == "__main__": - unittest.main() # pragma: no cover From 2f737ee292680e2f8043e0dfe3affcccc03914e8 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 27 Feb 2020 10:49:50 -0800 Subject: [PATCH 106/122] Change how _USE_DISTRO is set for mypy (#7804) If you run `mypy --platform darwin certbot/certbot/util.py` you'll get: ``` certbot/certbot/util.py:303: error: Name 'distro' is not defined certbot/certbot/util.py:319: error: Name 'distro' is not defined certbot/certbot/util.py:369: error: Name 'distro' is not defined ``` This is because mypy's logic for handling platform specific code is pretty simple and can't figure out what we're doing with `_USE_DISTRO` here. See https://mypy.readthedocs.io/en/stable/common_issues.html#python-version-and-system-platform-checks for more info. Setting `_USE_DISTRO` to the result of `sys.platform.startswith('linux')` solves the problem without changing the overall behavior of our code here though. This fixes part of https://github.com/certbot/certbot/issues/7803, but there's more work to be done on Windows. --- certbot/certbot/util.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/certbot/certbot/util.py b/certbot/certbot/util.py index aff2952f7..e69b11543 100644 --- a/certbot/certbot/util.py +++ b/certbot/certbot/util.py @@ -25,11 +25,9 @@ from certbot._internal import lock from certbot.compat import filesystem from certbot.compat import os -if sys.platform.startswith('linux'): +_USE_DISTRO = sys.platform.startswith('linux') +if _USE_DISTRO: import distro - _USE_DISTRO = True -else: - _USE_DISTRO = False logger = logging.getLogger(__name__) From a2be8e1956c79662fd28d8b8af4802ea89cf29bf Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 27 Feb 2020 10:50:20 -0800 Subject: [PATCH 107/122] Fix tests on macOS Catalina (#7794) This PR fixes the failures that can be seen at https://dev.azure.com/certbot/certbot/_build/results?buildId=1184&view=results. You can see this code running on macOS Catalina at https://dev.azure.com/certbot/certbot/_build/results?buildId=1192&view=results. --- certbot/tests/cli_test.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index 3a7fb57f8..be2c8f29e 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -30,15 +30,23 @@ class TestReadFile(TempDirTestCase): # However a relative path between two different drives is invalid. So we move to # self.tempdir to ensure that we stay on the same drive. os.chdir(self.tempdir) - rel_test_path = os.path.relpath(os.path.join(self.tempdir, 'foo')) + # The read-only filesystem introduced with macOS Catalina can break + # code using relative paths below. See + # https://bugs.python.org/issue38295 for another example of this. + # Eliminating any possible symlinks in self.tempdir before passing + # it to os.path.relpath solves the problem. This is done by calling + # filesystem.realpath which removes any symlinks in the path on + # POSIX systems. + real_path = filesystem.realpath(os.path.join(self.tempdir, 'foo')) + relative_path = os.path.relpath(real_path) self.assertRaises( - argparse.ArgumentTypeError, cli.read_file, rel_test_path) + argparse.ArgumentTypeError, cli.read_file, relative_path) test_contents = b'bar\n' - with open(rel_test_path, 'wb') as f: + with open(relative_path, 'wb') as f: f.write(test_contents) - path, contents = cli.read_file(rel_test_path) + path, contents = cli.read_file(relative_path) self.assertEqual(path, os.path.abspath(path)) self.assertEqual(contents, test_contents) finally: From 6309ded92f03104f2baa9b881db9827f5fe11e4c Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 27 Feb 2020 14:43:28 -0800 Subject: [PATCH 108/122] Remove references to deprecated flags in Certbot. (#7509) Related to https://github.com/certbot/certbot/pull/7482, this removes some references to deprecated options in Certbot. The only references I didn't remove were: * In `certbot/tests/testdata/sample-renewal*` which contains a lot of old values and I think there's even some value in keeping them so we know if we make a change that suddenly causes old renewal configuration files to error. * In the Apache and Nginx plugins and I created https://github.com/certbot/certbot/issues/7508 to resolve that issue. --- certbot/certbot/_internal/main.py | 2 +- certbot/certbot/display/ops.py | 2 +- certbot/tests/cli_test.py | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/certbot/certbot/_internal/main.py b/certbot/certbot/_internal/main.py index 8674cd151..4a57dd78d 100644 --- a/certbot/certbot/_internal/main.py +++ b/certbot/certbot/_internal/main.py @@ -394,7 +394,7 @@ def _find_domains_or_certname(config, installer, question=None): :param installer: Installer object :type installer: interfaces.IInstaller - :param `str` question: Overriding dialog question to ask the user if asked + :param `str` question: Overriding default question to ask the user if asked to choose from domain names. :returns: Two-part tuple of domains and certname diff --git a/certbot/certbot/display/ops.py b/certbot/certbot/display/ops.py index eab9d251d..f24f6ed99 100644 --- a/certbot/certbot/display/ops.py +++ b/certbot/certbot/display/ops.py @@ -107,7 +107,7 @@ def choose_names(installer, question=None): :param installer: An installer object :type installer: :class:`certbot.interfaces.IInstaller` - :param `str` question: Overriding dialog question to ask the user if asked + :param `str` question: Overriding default question to ask the user if asked to choose from domain names. :returns: List of selected names diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index be2c8f29e..7d21f8bb8 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -150,7 +150,6 @@ class ParseTest(unittest.TestCase): self.assertTrue("how a certificate is deployed" in out) self.assertTrue("--webroot-path" in out) self.assertTrue("--text" not in out) - self.assertTrue("--dialog" not in out) self.assertTrue("%s" not in out) self.assertTrue("{0}" not in out) self.assertTrue("--renew-hook" not in out) @@ -211,7 +210,6 @@ class ParseTest(unittest.TestCase): self.assertTrue("how a certificate is deployed" in out) self.assertTrue("--webroot-path" in out) self.assertTrue("--text" not in out) - self.assertTrue("--dialog" not in out) self.assertTrue("%s" not in out) self.assertTrue("{0}" not in out) From fa67b7ba0fb03453fc8d03e3631d6782a54a233b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 27 Feb 2020 14:44:39 -0800 Subject: [PATCH 109/122] Remove codecov (#7811) After getting a +1 from everyone on the team, this PR removes the use of `codecov` from the Certbot repo because we keep having problems with it. Two noteworthy things about this PR are: 1. I left the text at https://github.com/certbot/certbot/blob/4ea98d830bcc3d1b980a4055243c6a6a25d8dc54/.azure-pipelines/INSTALL.md#add-a-secret-variable-to-a-pipeline-like-codecov_token because I think it's useful to document how to set up a secret variable in general. 2. I'm not sure what the text "Option -e makes sure we fail fast and don't submit to codecov." in `tox.cover.py` refers to but it seems incorrect since `-e` isn't accepted or used by the script so I just deleted the line. As part of this, I said I'd open an issue to track setting up coveralls (which seems to be the only real alternative to codecov) which is at https://github.com/certbot/certbot/issues/7810. With my change, failure output looks something like: ``` $ tox -e py27-cover ... Name Stmts Miss Cover Missing ------------------------------------------------------------------------------------------ certbot/certbot/__init__.py 1 0 100% certbot/certbot/_internal/__init__.py 0 0 100% certbot/certbot/_internal/account.py 191 4 98% 62-63, 206, 337 ... certbot/tests/storage_test.py 530 0 100% certbot/tests/util_test.py 374 29 92% 211-213, 480-484, 489-499, 504-511, 545-547, 552-554 ------------------------------------------------------------------------------------------ TOTAL 14451 647 96% Command '['/path/to/certbot/dir/.tox/py27-cover/bin/python', '-m', 'coverage', 'report', '--fail-under', '100', '--include', 'certbot/*', '--show-missing']' returned non-zero exit status 2 Test coverage on certbot did not meet threshold of 100%. ERROR: InvocationError for command /Users/bmw/Development/certbot/certbot/.tox/py27-cover/bin/python tox.cover.py (exited with code 1) _________________________________________________________________________________________________________________________________________________________ summary _________________________________________________________________________________________________________________________________________________________ ERROR: py27-cover: commands failed ``` I printed the exception just so we're not throwing away information. I think it's also possible we fail for a reason other than the threshold not meeting the percentage, but I've personally never seen this, `coverage report` output is not being captured so hopefully that would inform devs if something else is going on, and saying something like "Test coverage probably did not..." seems like overkill to me personally. * remove codecov * remove unused variable group * remove codecov.yml * Improve tox.cover.py failure output. --- .azure-pipelines/templates/tests-suite.yml | 13 ------------- .codecov.yml | 18 ------------------ .travis.yml | 4 +--- certbot/README.rst | 6 +----- tools/dev_constraints.txt | 1 - tox.cover.py | 19 +++++++++++++------ 6 files changed, 15 insertions(+), 46 deletions(-) delete mode 100644 .codecov.yml diff --git a/.azure-pipelines/templates/tests-suite.yml b/.azure-pipelines/templates/tests-suite.yml index 069ea94d6..d330b7954 100644 --- a/.azure-pipelines/templates/tests-suite.yml +++ b/.azure-pipelines/templates/tests-suite.yml @@ -25,8 +25,6 @@ jobs: PYTEST_ADDOPTS: --numprocesses 4 pool: vmImage: $(IMAGE_NAME) - variables: - - group: certbot-common steps: - bash: brew install augeas condition: startswith(variables['IMAGE_NAME'], 'macOS') @@ -39,14 +37,3 @@ jobs: displayName: Install dependencies - script: python -m tox displayName: Run tox - # We do not require codecov report upload to succeed. So to avoid to break the pipeline if - # something goes wrong, each command is suffixed with a command that hides any non zero exit - # codes and echoes an informative message instead. - - bash: | - curl -s https://codecov.io/bash -o codecov-bash || echo "Failed to download codecov-bash" - chmod +x codecov-bash || echo "Failed to apply execute permissions on codecov-bash" - ./codecov-bash -F windows || echo "Codecov did not collect coverage reports" - condition: in(variables['TOXENV'], 'py37-cover', 'integration-certbot') - env: - CODECOV_TOKEN: $(codecov_token) - displayName: Publish coverage diff --git a/.codecov.yml b/.codecov.yml deleted file mode 100644 index 0a97fffe3..000000000 --- a/.codecov.yml +++ /dev/null @@ -1,18 +0,0 @@ -coverage: - status: - project: - default: off - linux: - flags: linux - # Fixed target instead of auto set by #7173, can - # be removed when flags in Codecov are added back. - target: 97.4 - threshold: 0.1 - base: auto - windows: - flags: windows - # Fixed target instead of auto set by #7173, can - # be removed when flags in Codecov are added back. - target: 97.4 - threshold: 0.1 - base: auto diff --git a/.travis.yml b/.travis.yml index e5354898d..d498d0305 100644 --- a/.travis.yml +++ b/.travis.yml @@ -247,15 +247,13 @@ addons: # version of virtualenv. The option "-I" is set so when CERTBOT_NO_PIN is also # set, pip updates dependencies it thinks are already satisfied to avoid some # problems with its lack of real dependency resolution. -install: 'tools/pip_install.py -I codecov tox virtualenv' +install: 'tools/pip_install.py -I tox virtualenv' # Most of the time TRAVIS_RETRY is an empty string, and has no effect on the # script command. It is set only to `travis_retry` during farm tests, in # order to trigger the Travis retry feature, and compensate the inherent # flakiness of these specific tests. script: '$TRAVIS_RETRY tox' -after_success: '[ "$TOXENV" == "py27-cover" ] && codecov -F linux' - notifications: email: false irc: diff --git a/certbot/README.rst b/certbot/README.rst index d1b1e4fe2..5ed74f247 100644 --- a/certbot/README.rst +++ b/certbot/README.rst @@ -71,16 +71,12 @@ ACME spec: http://ietf-wg-acme.github.io/acme/ ACME working area in github: https://github.com/ietf-wg-acme/acme -|build-status| |coverage| |container| +|build-status| |container| .. |build-status| image:: https://travis-ci.com/certbot/certbot.svg?branch=master :target: https://travis-ci.com/certbot/certbot :alt: Travis CI status -.. |coverage| image:: https://codecov.io/gh/certbot/certbot/branch/master/graph/badge.svg - :target: https://codecov.io/gh/certbot/certbot - :alt: Coverage status - .. |container| image:: https://quay.io/repository/letsencrypt/letsencrypt/status :target: https://quay.io/repository/letsencrypt/letsencrypt :alt: Docker Repository on Quay.io diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index 7d2013c7a..cfa036435 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -18,7 +18,6 @@ boto3==1.11.7 botocore==1.14.7 cached-property==1.5.1 cloudflare==2.3.1 -codecov==2.0.15 configparser==3.7.4 contextlib2==0.6.0.post1 coverage==4.5.4 diff --git a/tox.cover.py b/tox.cover.py index 3e69a14d6..4848b2740 100755 --- a/tox.cover.py +++ b/tox.cover.py @@ -1,4 +1,6 @@ #!/usr/bin/env python +from __future__ import print_function + import argparse import os import subprocess @@ -48,18 +50,23 @@ def cover(package): subprocess.check_call([sys.executable, '-m', 'pytest', '--cov', pkg_dir, '--cov-append', '--cov-report=', pkg_dir]) - subprocess.check_call([ - sys.executable, '-m', 'coverage', 'report', '--fail-under', str(threshold), '--include', - '{0}/*'.format(pkg_dir), '--show-missing']) + try: + subprocess.check_call([ + sys.executable, '-m', 'coverage', 'report', '--fail-under', + str(threshold), '--include', '{0}/*'.format(pkg_dir), + '--show-missing']) + except subprocess.CalledProcessError as err: + print(err) + print('Test coverage on', pkg_dir, + 'did not meet threshold of {0}%.'.format(threshold)) + sys.exit(1) def main(): description = """ This script is used by tox.ini (and thus by Travis CI and Azure Pipelines) in order to generate separate stats for each package. It should be removed once -those packages are moved to a separate repo. - -Option -e makes sure we fail fast and don't submit to codecov.""" +those packages are moved to a separate repo.""" parser = argparse.ArgumentParser(description=description) parser.add_argument('--packages', nargs='+') From 50ea6085537dfec3bceaa4f9f4e4065de84d1407 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 27 Feb 2020 15:07:33 -0800 Subject: [PATCH 110/122] Don't run advanced tests on PRs. (#7820) When I wrote https://github.com/certbot/certbot/pull/7813, I didn't understand the default behavior for pull requests if you don't specify `pr` in the yaml file. According to https://docs.microsoft.com/en-us/azure/devops/pipelines/build/triggers?view=azure-devops&tabs=yaml#pr-triggers: > If no pr triggers appear in your YAML file, pull request builds are automatically enabled for all branches... This is not the behavior we want. This PR fixes the problem by disabling builds on PRs. You should be able to see this working because the advanced tests should not run on this PR but they did run on https://github.com/certbot/certbot/pull/7811. --- .azure-pipelines/advanced-test.yml | 1 + .azure-pipelines/advanced.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.azure-pipelines/advanced-test.yml b/.azure-pipelines/advanced-test.yml index b9ac9c38a..5be29ba79 100644 --- a/.azure-pipelines/advanced-test.yml +++ b/.azure-pipelines/advanced-test.yml @@ -4,6 +4,7 @@ trigger: # "Running tests in CI" is still correct. - azure-test-* - test-* +pr: none jobs: # Any addition here should be reflected in the advanced and release pipelines. diff --git a/.azure-pipelines/advanced.yml b/.azure-pipelines/advanced.yml index 7f0f5de50..d950e6524 100644 --- a/.azure-pipelines/advanced.yml +++ b/.azure-pipelines/advanced.yml @@ -1,6 +1,7 @@ # Advanced pipeline for running our full test suite on protected branches. trigger: - '*.x' +pr: none # This pipeline is also nightly run on master schedules: - cron: "0 4 * * *" From 9f8e4507ad0cb3dbedb726dda4c46affb1eb7ad3 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 28 Feb 2020 00:44:23 +0000 Subject: [PATCH 111/122] Document safe and simple usage by services without root privileges (#7821) Certificates are public information by design: they are provided by web servers without any prior authentication required. In a public key cryptographic system, only the private key is secret information. The private key file is already created as accessible only to the root user with mode 0600, and these file permissions are set before any key content is written to the file. There is no window within which an attacker with access to the containing directory would be able to read the private key content. Older versions of Certbot (prior to 0.29.0) would create private key files with mode 0644 and rely solely on the containing directory permissions to restrict access. We therefore cannot (yet) set the relevant default directory permissions to 0755, since it is possible that a user could install Certbot, obtain a certificate, then downgrade to a pre-0.29.0 version of Certbot, then obtain another certificate. This chain of events would leave the second certificate's private key file exposed. As a compromise solution, document the fact that it is safe for the common case of non-downgrading users to change the permissions of /etc/letsencrypt/{live,archive} to 0755, and explain how to use chgrp and chmod to make the private key file readable by a non-root service user. This provides guidance on the simplest way to solve the common problem of making keys and certificates usable by services that run without root privileges, with no requirement to create a custom (and hence error-prone) executable hook. Remove the existing custom executable hook example, so that the documentation contains only the simplest and safest way to solve this very common problem. Signed-off-by: Michael Brown --- certbot/docs/using.rst | 48 ++++++++++-------------------------------- 1 file changed, 11 insertions(+), 37 deletions(-) diff --git a/certbot/docs/using.rst b/certbot/docs/using.rst index 27ae826bd..8ec172c24 100644 --- a/certbot/docs/using.rst +++ b/certbot/docs/using.rst @@ -485,43 +485,6 @@ If you want your hook to run only after a successful renewal, use ``certbot renew --deploy-hook /path/to/deploy-hook-script`` -For example, if you have a daemon that does not read its certificates as the -root user, a deploy hook like this can copy them to the correct location and -apply appropriate file permissions. - -/path/to/deploy-hook-script - -.. code-block:: none - - #!/bin/sh - - set -e - - for domain in $RENEWED_DOMAINS; do - case $domain in - example.com) - daemon_cert_root=/etc/some-daemon/certs - - # Make sure the certificate and private key files are - # never world readable, even just for an instant while - # we're copying them into daemon_cert_root. - umask 077 - - cp "$RENEWED_LINEAGE/fullchain.pem" "$daemon_cert_root/$domain.cert" - cp "$RENEWED_LINEAGE/privkey.pem" "$daemon_cert_root/$domain.key" - - # Apply the proper file ownership and permissions for - # the daemon to read its certificate and key. - chown some-daemon "$daemon_cert_root/$domain.cert" \ - "$daemon_cert_root/$domain.key" - chmod 400 "$daemon_cert_root/$domain.cert" \ - "$daemon_cert_root/$domain.key" - - service some-daemon restart >/dev/null - ;; - esac - done - You can also specify hooks by placing files in subdirectories of Certbot's configuration directory. Assuming your configuration directory is ``/etc/letsencrypt``, any executable files found in @@ -686,6 +649,17 @@ your (web) server configuration directly to those files (or create symlinks). During the renewal_, ``/etc/letsencrypt/live`` is updated with the latest necessary files. +For historical reasons, the containing directories are created with +permissions of ``0700`` meaning that certificates are accessible only +to servers that run as the root user. **If you will never downgrade +to an older version of Certbot**, then you can safely fix this using +``chmod 0755 /etc/letsencrypt/{live,archive}``. + +For servers that drop root privileges before attempting to read the +private key file, you will also need to use ``chgrp`` and ``chmod +0640`` to allow the server to read +``/etc/letsencrypt/live/$domain/privkey.pem``. + .. note:: ``/etc/letsencrypt/archive`` and ``/etc/letsencrypt/keys`` contain all previous keys and certificates, while ``/etc/letsencrypt/live`` symlinks to the latest versions. From 31470262110ab8e9a388d738461b08a4f489d430 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Mar 2020 11:07:15 -0800 Subject: [PATCH 112/122] Check OCSP as part of determining if the certificate is due for renewal (#7829) Fixes #1028. Doing this now because of https://community.letsencrypt.org/t/revoking-certain-certificates-on-march-4/. The new `ocsp_revoked_by_paths` function is taken from https://github.com/certbot/certbot/pull/7649 with the optional argument removed for now because it is unused. This function was added in this PR because `storage.py` uses `self.latest_common_version()` to determine which certificate should be looked at for determining renewal status at https://github.com/certbot/certbot/blob/9f8e4507ad0cb3dbedb726dda4c46affb1eb7ad3/certbot/certbot/_internal/storage.py#L939-L947 I think this is unnecessary and you can just look at the currently linked certificate, but I don't think we should be changing the logic that code has always had now. * Check OCSP status as part of determining to renew * add integration tests * add ocsp_revoked_by_paths --- .../certbot_tests/test_main.py | 17 ++++++++++ certbot/CHANGELOG.md | 2 ++ certbot/certbot/_internal/storage.py | 33 +++++++++++-------- certbot/certbot/ocsp.py | 13 +++++++- certbot/tests/storage_test.py | 33 ++++++++++++++++--- 5 files changed, 80 insertions(+), 18 deletions(-) diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index 94e76cf79..f0c5edd3f 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -595,6 +595,23 @@ def test_ocsp_status_live(context): assert output.count('REVOKED') == 1, 'Expected {0} to be REVOKED'.format(cert) +def test_ocsp_renew(context): + """Test that revoked certificates are renewed.""" + # Obtain a certificate + certname = context.get_domain('ocsp-renew') + context.certbot(['--domains', certname]) + + # Test that "certbot renew" does not renew the certificate + assert_cert_count_for_lineage(context.config_dir, certname, 1) + context.certbot(['renew'], force_renew=False) + assert_cert_count_for_lineage(context.config_dir, certname, 1) + + # Revoke the certificate and test that it does renew the certificate + context.certbot(['revoke', '--cert-name', certname, '--no-delete-after-revoke']) + context.certbot(['renew'], force_renew=False) + assert_cert_count_for_lineage(context.config_dir, certname, 2) + + def test_dry_run_deactivate_authzs(context): """Test that Certbot deactivates authorizations when performing a dry run""" diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 6c1b112d7..2a934ee5b 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -13,6 +13,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed +* Certbot will now renew certificates early if they have been revoked according + to OCSP. * Fix acme module warnings when response Content-Type includes params (e.g. charset). * Fixed issue where webroot plugin would incorrectly raise `Read-only file system` error when creating challenge directories (issue #7165). diff --git a/certbot/certbot/_internal/storage.py b/certbot/certbot/_internal/storage.py index 6a34355a8..2dac163e2 100644 --- a/certbot/certbot/_internal/storage.py +++ b/certbot/certbot/_internal/storage.py @@ -15,6 +15,7 @@ import certbot from certbot import crypto_util from certbot import errors from certbot import interfaces +from certbot import ocsp from certbot import util from certbot._internal import cli from certbot._internal import constants @@ -882,27 +883,33 @@ class RenewableCert(interfaces.RenewableCert): with open(target) as f: return crypto_util.get_names_from_cert(f.read()) - def ocsp_revoked(self, version=None): - # pylint: disable=unused-argument + def ocsp_revoked(self, version): """Is the specified cert version revoked according to OCSP? - Also returns True if the cert version is declared as intended - to be revoked according to Let's Encrypt OCSP extensions. - (If no version is specified, uses the current version.) - - This method is not yet implemented and currently always returns - False. + Also returns True if the cert version is declared as revoked + according to OCSP. If OCSP status could not be determined, False + is returned. :param int version: the desired version number - :returns: whether the certificate is or will be revoked + :returns: True if the certificate is revoked, otherwise, False :rtype: bool """ - # XXX: This query and its associated network service aren't - # implemented yet, so we currently return False (indicating that the - # certificate is not revoked). - return False + cert_path = self.version("cert", version) + chain_path = self.version("chain", version) + # While the RevocationChecker should return False if it failed to + # determine the OCSP status, let's ensure we don't crash Certbot by + # catching all exceptions here. + try: + return ocsp.RevocationChecker().ocsp_revoked_by_paths(cert_path, + chain_path) + except Exception as e: # pylint: disable=broad-except + logger.warning( + "An error occurred determining the OCSP status of %s.", + cert_path) + logger.debug(str(e)) + return False def autorenewal_is_enabled(self): """Is automatic renewal enabled for this cert? diff --git a/certbot/certbot/ocsp.py b/certbot/certbot/ocsp.py index 6a95f26fa..9799c675c 100644 --- a/certbot/certbot/ocsp.py +++ b/certbot/certbot/ocsp.py @@ -68,8 +68,19 @@ class RevocationChecker(object): :rtype: bool """ - cert_path, chain_path = cert.cert_path, cert.chain_path + return self.ocsp_revoked_by_paths(cert.cert_path, cert.chain_path) + def ocsp_revoked_by_paths(self, cert_path, chain_path): + # type: (str, str) -> bool + """Performs the OCSP revocation check + + :param str cert_path: Certificate filepath + :param str chain_path: Certificate chain filepath + + :returns: True if revoked; False if valid or the check failed or cert is expired. + :rtype: bool + + """ if self.broken: return False diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index 6208974ec..0f7620b78 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -672,10 +672,35 @@ class RenewableCertTests(BaseRenewableCertTest): errors.CertStorageError, self.test_rc._update_link_to, "elephant", 17) - def test_ocsp_revoked(self): - # XXX: This is currently hardcoded to False due to a lack of an - # OCSP server to test against. - self.assertFalse(self.test_rc.ocsp_revoked()) + @mock.patch("certbot.ocsp.RevocationChecker.ocsp_revoked_by_paths") + def test_ocsp_revoked(self, mock_checker): + # Write out test files + for kind in ALL_FOUR: + self._write_out_kind(kind, 1) + version = self.test_rc.latest_common_version() + expected_cert_path = self.test_rc.version("cert", version) + expected_chain_path = self.test_rc.version("chain", version) + + # Test with cert revoked + mock_checker.return_value = True + self.assertTrue(self.test_rc.ocsp_revoked(version)) + self.assertEqual(mock_checker.call_args[0][0], expected_cert_path) + self.assertEqual(mock_checker.call_args[0][1], expected_chain_path) + + # Test with cert not revoked + mock_checker.return_value = False + self.assertFalse(self.test_rc.ocsp_revoked(version)) + self.assertEqual(mock_checker.call_args[0][0], expected_cert_path) + self.assertEqual(mock_checker.call_args[0][1], expected_chain_path) + + # Test with error + mock_checker.side_effect = ValueError + with mock.patch("certbot._internal.storage.logger.warning") as logger: + self.assertFalse(self.test_rc.ocsp_revoked(version)) + self.assertEqual(mock_checker.call_args[0][0], expected_cert_path) + self.assertEqual(mock_checker.call_args[0][1], expected_chain_path) + log_msg = logger.call_args[0][0] + self.assertIn("An error occurred determining the OCSP status", log_msg) def test_add_time_interval(self): from certbot._internal import storage From b1fb3296e949c5ce5175321328a89a41b7dd3d12 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Mar 2020 12:36:36 -0800 Subject: [PATCH 113/122] Update changelog for 1.3.0 release --- certbot/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 2a934ee5b..493e4d5c1 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 1.3.0 - master +## 1.3.0 - 2020-03-03 ### Added From 6edb4e1a3924821316b9344adb9c533937426fa7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Mar 2020 12:43:02 -0800 Subject: [PATCH 114/122] Release 1.3.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 26 +++++++++--------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/certbot/__init__.py | 2 +- certbot/docs/cli-help.txt | 2 +- letsencrypt-auto | 26 +++++++++--------- letsencrypt-auto-source/certbot-auto.asc | 16 +++++------ letsencrypt-auto-source/letsencrypt-auto | 26 +++++++++--------- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 ++++++++-------- 26 files changed, 79 insertions(+), 79 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 0e11779ba..922cae26c 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index b37ee3972..6483313b8 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index cea58e2cb..0ea3275c3 100755 --- a/certbot-auto +++ b/certbot-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="1.2.0" +LE_AUTO_VERSION="1.3.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1540,18 +1540,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.2.0 \ - --hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \ - --hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c -acme==1.2.0 \ - --hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \ - --hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab -certbot-apache==1.2.0 \ - --hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \ - --hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3 -certbot-nginx==1.2.0 \ - --hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \ - --hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db +certbot==1.3.0 \ + --hash=sha256:979793b36151be26c159f1946d065a0cbbcaed3e9ac452c19a142b0d2d2b42e3 \ + --hash=sha256:bc2091cbbc2f432872ed69309046e79771d9c81cd441bde3e6a6553ecd04b1d8 +acme==1.3.0 \ + --hash=sha256:b888757c750e393407a3cdf0eb5c2d06036951e10c41db4c83537617568561b6 \ + --hash=sha256:c0de9e1fbcb4a28509825a4d19ab5455910862b23fa338acebc7bbe7c0abd20d +certbot-apache==1.3.0 \ + --hash=sha256:1050cd262bcc598957c45a6fa1febdf5e41e87176c0aebad3a1ab7268b0d82d9 \ + --hash=sha256:4a6bb818a7a70803127590a54bb25c1e79810761c9d4c92cf9f16a56b518bd52 +certbot-nginx==1.3.0 \ + --hash=sha256:46106b96429d1aaf3765635056352d2372941027a3bc26bbf964e4329202adc7 \ + --hash=sha256:9aa0869c1250b7ea0a1eb1df6bdb5d0d6190d6ca0400da1033a8decc0df6f65b UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 1dbcefa75..f85311999 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -3,7 +3,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.3.0.dev0' +version = '1.3.0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 9376bc1c4..d5da3ed95 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 4e99ff5ff..491096d70 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 9c9d1717c..23c2903f4 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 9cde6214c..197873733 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index adaba6851..b0a016441 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index a849cef45..2c76869bc 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 51d5b8a3f..61528b3a0 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index e7e91b929..1e7829588 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index ea64f79a2..418c635b9 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index d6bedca1c..99bc1022e 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 8f5b052a2..9f5974bcb 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index fa51c2108..42c6f11bd 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index f25e348ff..64e307b52 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 8df2320ba..b18da9d97 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index b180fe06a..fe707fb09 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0.dev0' +version = '1.3.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/certbot/__init__.py b/certbot/certbot/__init__.py index 84ade6b08..39171bad2 100644 --- a/certbot/certbot/__init__.py +++ b/certbot/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '1.3.0.dev0' +__version__ = '1.3.0' diff --git a/certbot/docs/cli-help.txt b/certbot/docs/cli-help.txt index ff49609c4..3c2289030 100644 --- a/certbot/docs/cli-help.txt +++ b/certbot/docs/cli-help.txt @@ -113,7 +113,7 @@ optional arguments: case, and to know when to deprecate support for past Python versions and flags. If you wish to hide this information from the Let's Encrypt server, set this to - "". (default: CertbotACMEClient/1.2.0 (certbot(-auto); + "". (default: CertbotACMEClient/1.3.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the user agent are: --duplicate, diff --git a/letsencrypt-auto b/letsencrypt-auto index cea58e2cb..0ea3275c3 100755 --- a/letsencrypt-auto +++ b/letsencrypt-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="1.2.0" +LE_AUTO_VERSION="1.3.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1540,18 +1540,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.2.0 \ - --hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \ - --hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c -acme==1.2.0 \ - --hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \ - --hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab -certbot-apache==1.2.0 \ - --hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \ - --hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3 -certbot-nginx==1.2.0 \ - --hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \ - --hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db +certbot==1.3.0 \ + --hash=sha256:979793b36151be26c159f1946d065a0cbbcaed3e9ac452c19a142b0d2d2b42e3 \ + --hash=sha256:bc2091cbbc2f432872ed69309046e79771d9c81cd441bde3e6a6553ecd04b1d8 +acme==1.3.0 \ + --hash=sha256:b888757c750e393407a3cdf0eb5c2d06036951e10c41db4c83537617568561b6 \ + --hash=sha256:c0de9e1fbcb4a28509825a4d19ab5455910862b23fa338acebc7bbe7c0abd20d +certbot-apache==1.3.0 \ + --hash=sha256:1050cd262bcc598957c45a6fa1febdf5e41e87176c0aebad3a1ab7268b0d82d9 \ + --hash=sha256:4a6bb818a7a70803127590a54bb25c1e79810761c9d4c92cf9f16a56b518bd52 +certbot-nginx==1.3.0 \ + --hash=sha256:46106b96429d1aaf3765635056352d2372941027a3bc26bbf964e4329202adc7 \ + --hash=sha256:9aa0869c1250b7ea0a1eb1df6bdb5d0d6190d6ca0400da1033a8decc0df6f65b UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index 488d0bf2e..84473dc30 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl456ZoACgkQTRfJlc2X -dfJx8wf/addMw4kUlwu6poHqLvsifZzHAESgvq+qybgFvl5yTh2U+99PGBgxRYx+ -bENIWBi6+XB+CiVuLzIXWw/VkXh+za99orRkkVK9PI33Xr7jBMZo5Oa3JviYjl3X -PcfjioRQCD+a9Tf9RO25LXQmxn87Ql9x3nxJuk//YeSpuImFmYjIBPE4n/LPEf7z -8WHU4oxxa/bgqGCPgv6O7ZBw7ipd3g+VHcDZcNQMP4tWYb6m7x/nN61yirid7q3M -uqQ1lbitN48ISyru6xPyE6WGTvfl1SIQd21FNRETpcoesx+MTv3ApWT4dqXjZvaX -FeM55IS65e7ci6yLV9qdAbqGKzhX0Q== -=uLcV +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl5ewVUACgkQTRfJlc2X +dfJnZAf+KmxYl1YoP/FlTG5Npb64qaDdxm59SeEVJez6fZh15xq71tRPYR+4xszE +XTeyGt7uAxjYqeiBJU5xBvGC1Veprhj5AbflVOTP+5yiBr9iNWC35zmgaE63UlZ/ +V94sfL0pkax7wLngil7a0OuzUjikzK3gXOqrY8LoUdr4mAA9AhSjajWHmyY3tpDR +84GKrVhybIt0sjy/172VuPPbXZKno/clztkKMZHXNrDeL5jgJ15Va4Ts5FK0j9VT +HQvuazbGkYVCuvlp8Np5ESDje69LCJfPZxl34htoa8WNJoVIOsQWZpoXp5B5huSP +vGrh4LabZ5UDsl+k11ikHBRUpO7E5w== +=IgRH -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index e2813853b..0ea3275c3 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="1.3.0.dev0" +LE_AUTO_VERSION="1.3.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1540,18 +1540,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.2.0 \ - --hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \ - --hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c -acme==1.2.0 \ - --hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \ - --hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab -certbot-apache==1.2.0 \ - --hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \ - --hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3 -certbot-nginx==1.2.0 \ - --hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \ - --hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db +certbot==1.3.0 \ + --hash=sha256:979793b36151be26c159f1946d065a0cbbcaed3e9ac452c19a142b0d2d2b42e3 \ + --hash=sha256:bc2091cbbc2f432872ed69309046e79771d9c81cd441bde3e6a6553ecd04b1d8 +acme==1.3.0 \ + --hash=sha256:b888757c750e393407a3cdf0eb5c2d06036951e10c41db4c83537617568561b6 \ + --hash=sha256:c0de9e1fbcb4a28509825a4d19ab5455910862b23fa338acebc7bbe7c0abd20d +certbot-apache==1.3.0 \ + --hash=sha256:1050cd262bcc598957c45a6fa1febdf5e41e87176c0aebad3a1ab7268b0d82d9 \ + --hash=sha256:4a6bb818a7a70803127590a54bb25c1e79810761c9d4c92cf9f16a56b518bd52 +certbot-nginx==1.3.0 \ + --hash=sha256:46106b96429d1aaf3765635056352d2372941027a3bc26bbf964e4329202adc7 \ + --hash=sha256:9aa0869c1250b7ea0a1eb1df6bdb5d0d6190d6ca0400da1033a8decc0df6f65b UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index fefc81b37796cdecd066b9bb212d8e285fffd4d0..8c4f52d6e2992d76012ed4c5e8b65c366e53cf8c 100644 GIT binary patch literal 256 zcmV+b0ssEUOnRo|t9S`PT@Q6pdEx5)TLaw|tXK(Czj^_>8PhYd%g<>HQ?bGidlv-5 zE}9A&SzM6uK*eeB!}iF{EuInepHwl!!N=ypH>Giu0~gNuz3H_G-xK$*4GGCrIi8a} zc|vYH^EA>Vt1;uU?ETR7;K}DFE1nDBiU-xb=ODbJ#yW{#gBQmTy?oXA)L zay!@Hc;qXdB0+{NAC2*m>{YND9$B?_{$Q;DGoOCVmMhh`)kRLNXuPfjru6g4?PpB5 zE=0kjQwl0Y1m+t}(iNS8e1OH8)e$OZ#|vb^J4)NEd(wxnV%*phX+ZJIy2kBPS7%B+ G&t7>eJc8N) literal 256 zcmV+b0ssCyy6+%Byu~4aOZx+sGM?uWzM?nz3Jfz_Cb^!H(J$u3EbYx+Wmt@nUdJdC z5^ed_c;i%mwYmiD>ud)5-Lk6YWkh7j-?{0%L?KJZd%TEUG+|;|-*#FI(%jlkvk;lO zO~Yh(rw(~6mZiF)`O}!O(DYn@gPBFqESJ@M(%s)|7%F?plRgOU1Sm{BW(R9Bfx|N( zKD3y8K_?ev`Hu@xHbPDe3A34WZ3?o_F0i8u)P>>`q{3hr9CRb?E6FEIK^btkZ@Qg6 zt+z9SQGsl`GlM4_;VnLX96Xg&)Kv+HS#^V0%aEYWG9N+LKk6WSdEBew*pUM_Rk_eZ GIvjh!?|N Date: Tue, 3 Mar 2020 12:43:03 -0800 Subject: [PATCH 115/122] Add contents to certbot/CHANGELOG.md for next version --- certbot/CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 493e4d5c1..cb1f2968c 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -2,6 +2,22 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 1.4.0 - master + +### Added + +* + +### Changed + +* + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + ## 1.3.0 - 2020-03-03 ### Added From 144d4f2b446a5659e713133868eb921885ce105b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Mar 2020 12:43:04 -0800 Subject: [PATCH 116/122] Bump version to 1.4.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 922cae26c..0527b3fb5 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 6483313b8..4ec1d0a9c 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index f85311999..d6760576a 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -3,7 +3,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.3.0' +version = '1.4.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index d5da3ed95..67aac3231 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 491096d70..6c653967d 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 23c2903f4..c45fc8d03 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 197873733..9124f0552 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index b0a016441..2a4fd92b0 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 2c76869bc..2198fdd3e 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 61528b3a0..087766edd 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 1e7829588..1e6b96b71 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 418c635b9..4db50b56c 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 99bc1022e..49e5e3bcf 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 9f5974bcb..6c66b39dc 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 42c6f11bd..5e0900e4d 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 64e307b52..98455c362 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index b18da9d97..16990a4d3 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index fe707fb09..34785f963 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.3.0' +version = '1.4.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/certbot/__init__.py b/certbot/certbot/__init__.py index 39171bad2..0ce7ff6b7 100644 --- a/certbot/certbot/__init__.py +++ b/certbot/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '1.3.0' +__version__ = '1.4.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 0ea3275c3..ca0bda2d5 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="1.3.0" +LE_AUTO_VERSION="1.4.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates From d72a1a71d22792cecf66a8d636705b9319e8fca3 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 5 Mar 2020 11:50:52 -0800 Subject: [PATCH 117/122] Fix issues with Azure Pipelines (#7838) This PR fixes two issues. First, it fixes #7814 by removing our tests on Windows Server 2012. I also added the sentence "Certbot supports Windows Server 2016 and Windows Server 2019." to https://community.letsencrypt.org/t/beta-phase-of-certbot-for-windows/105822. Second, it fixes the test failures which can be seen at https://dev.azure.com/certbot/certbot/_build/results?buildId=1309&view=results by no longer manually installing our own version of Python and instead using the one provided by Azure. These small changes are in the same PR because I wanted to fix test failures ASAP and `UsePythonVersion` is not available on Windows 2012. See https://github.com/certbot/certbot/pull/7641#discussion_r358510854. You can see tests passing with this change at https://dev.azure.com/certbot/certbot/_build/results?buildId=1311&view=results. * stop testing on win2012 * switch to UsePythonVersion --- .azure-pipelines/templates/installer-tests.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.azure-pipelines/templates/installer-tests.yml b/.azure-pipelines/templates/installer-tests.yml index 6d5672339..ea2101792 100644 --- a/.azure-pipelines/templates/installer-tests.yml +++ b/.azure-pipelines/templates/installer-tests.yml @@ -28,15 +28,13 @@ jobs: imageName: windows-2019 win2016: imageName: vs2017-win2016 - win2012r2: - imageName: vs2015-win2012r2 pool: vmImage: $(imageName) steps: - - powershell: Invoke-WebRequest https://www.python.org/ftp/python/3.8.1/python-3.8.1-amd64-webinstall.exe -OutFile C:\py3-setup.exe - displayName: Get Python - - script: C:\py3-setup.exe /quiet PrependPath=1 InstallAllUsers=1 Include_launcher=1 InstallLauncherAllUsers=1 Include_test=0 Include_doc=0 Include_dev=1 Include_debug=0 Include_tcltk=0 TargetDir=C:\py3 - displayName: Install Python + - task: UsePythonVersion@0 + inputs: + versionSpec: 3.8 + addToPath: true - task: DownloadPipelineArtifact@2 inputs: artifact: windows-installer From 7f63141e410ae7ce200c7c7408d05c87eae0c5f5 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 6 Mar 2020 09:46:30 -0800 Subject: [PATCH 118/122] Add changes to the correct changelog entry (#7833) https://github.com/certbot/certbot/pull/7742 and https://github.com/certbot/certbot/pull/7738 landed after our 1.2.0 release, but the 1.2.0 changelog entry was modified instead of the one for master/1.3.0. This PR moves the changelog entries to the 1.3.0 section. --- certbot/CHANGELOG.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index cb1f2968c..868f3c8be 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -26,6 +26,7 @@ More details about these changes can be found on our GitHub repo. determine the OCSP status of certificates. * Don't verify the existing certificate in HTTP01Response.simple_verify, for compatibility with the real-world ACME challenge checks. +* Added support for `$hostname` in nginx `server_name` directive ### Changed @@ -37,7 +38,7 @@ More details about these changes can be found on our GitHub repo. ### Fixed -* +* Fix Apache plugin to use less restrictive umask for making the challenge directory when a restrictive umask was set when certbot was started. More details about these changes can be found on our GitHub repo. @@ -46,7 +47,6 @@ More details about these changes can be found on our GitHub repo. ### Added * Added support for Cloudflare's limited-scope API Tokens -* Added support for `$hostname` in nginx `server_name` directive ### Changed @@ -59,7 +59,6 @@ More details about these changes can be found on our GitHub repo. ### Fixed * Fix collections.abc imports for Python 3.9. -* Fix Apache plugin to use less restrictive umask for making the challenge directory when a restrictive umask was set when certbot was started. More details about these changes can be found on our GitHub repo. From 69aec55ead42c9ef9231602d002825e838e9dafb Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 9 Mar 2020 13:05:35 -0700 Subject: [PATCH 119/122] Remove --no-site-packages outside of certbot-auto. (#7832) --- certbot-compatibility-test/Dockerfile | 2 +- tools/_release.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/certbot-compatibility-test/Dockerfile b/certbot-compatibility-test/Dockerfile index a9996f779..a6a0c93db 100644 --- a/certbot-compatibility-test/Dockerfile +++ b/certbot-compatibility-test/Dockerfile @@ -31,7 +31,7 @@ COPY certbot-nginx /opt/certbot/src/certbot-nginx/ COPY certbot-compatibility-test /opt/certbot/src/certbot-compatibility-test/ COPY tools /opt/certbot/src/tools -RUN VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages -p python2 /opt/certbot/venv && \ +RUN VIRTUALENV_NO_DOWNLOAD=1 virtualenv -p python2 /opt/certbot/venv && \ /opt/certbot/venv/bin/pip install -U setuptools && \ /opt/certbot/venv/bin/pip install -U pip ENV PATH /opt/certbot/venv/bin:$PATH diff --git a/tools/_release.sh b/tools/_release.sh index 1819adad2..97d5f5eb8 100755 --- a/tools/_release.sh +++ b/tools/_release.sh @@ -59,7 +59,7 @@ mv "dist.$version" "dist.$version.$(date +%s).bak" || true git tag --delete "$tag" || true tmpvenv=$(mktemp -d) -VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages -p python2 $tmpvenv +VIRTUALENV_NO_DOWNLOAD=1 virtualenv -p python2 $tmpvenv . $tmpvenv/bin/activate # update setuptools/pip just like in other places in the repo pip install -U setuptools @@ -160,7 +160,7 @@ cd "dist.$version" python -m SimpleHTTPServer $PORT & # cd .. is NOT done on purpose: we make sure that all subpackages are # installed from local PyPI rather than current directory (repo root) -VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages ../venv +VIRTUALENV_NO_DOWNLOAD=1 virtualenv ../venv . ../venv/bin/activate pip install -U setuptools pip install -U pip From 78168a5248cf119289053ffcf048c4be9b2af9d6 Mon Sep 17 00:00:00 2001 From: radek-sprta Date: Wed, 11 Mar 2020 21:27:19 +0100 Subject: [PATCH 120/122] Add CloudDNS to third-party plugins (#7840) --- certbot/docs/using.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/certbot/docs/using.rst b/certbot/docs/using.rst index 8ec172c24..3c3ef6fad 100644 --- a/certbot/docs/using.rst +++ b/certbot/docs/using.rst @@ -280,6 +280,7 @@ pritunl_ N Y Install certificates in pritunl distributed OpenVPN proxmox_ N Y Install certificates in Proxmox Virtualization servers dns-standalone_ Y N Obtain certificates via an integrated DNS server dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server +dns-clouddns_ Y N DNS Authentication using CloudDNS API ================== ==== ==== =============================================================== .. _haproxy: https://github.com/greenhost/certbot-haproxy @@ -291,6 +292,7 @@ dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server .. _external-auth: https://github.com/EnigmaBridge/certbot-external-auth .. _dns-standalone: https://github.com/siilike/certbot-dns-standalone .. _dns-ispconfig: https://github.com/m42e/certbot-dns-ispconfig +.. _dns-clouddns: https://github.com/vshosting/certbot-dns-clouddns If you're interested, you can also :ref:`write your own plugin `. From 44b97df4e91a3d228bf933ee169e964070f96dd3 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 12 Mar 2020 17:29:03 +0100 Subject: [PATCH 121/122] Exposes environment variable to let hooks scripts know when the last challenge is handled (#7837) Fixes #5484 This PRs makes Certbot expose two new environment variables in the auth and cleanup hooks of the `manual` plugin: * `CERTBOT_REMAINING_CHALLENGES` contains the number of challenges that remain after the current one (so it equals to 0 when the script is called for the last challenge) * `CERTBOT_ALL_DOMAINS` contains a comma-separated list of all domains concerned by a challenge for the current certificate With these variables, an hook script can know when it is run for the last time, and then trigger appropriate finalizers for all challenges that have been executed. This will be particularly useful for certificates with a lot of domains validated with DNS-01 challenges: instead of waiting on each hook execution to check that the relevant DNS TXT entry has been inserted, these waits can be avoided thanks to the latest hook verifying all domains in one run. * Inject environment variables in manual scripts about remaining challenges * Adapt tests * Less variables and less lines * Update manual.py * Update manual_test.py * Add documentation * Add changelog --- certbot/CHANGELOG.md | 5 ++++- certbot/certbot/_internal/plugins/manual.py | 21 +++++++++++++-------- certbot/docs/using.rst | 4 +++- certbot/tests/plugins/manual_test.py | 19 +++++++++++++------ 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 868f3c8be..903af0610 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -6,7 +6,10 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added -* +* Expose two new environment variables in the authenticator and cleanup scripts used by + the `manual` plugin: `CERTBOT_REMAINING_CHALLENGES` is equal to the number of challenges + remaining after the current challenge, `CERTBOT_ALL_DOMAINS` is a comma-separated list + of all domains challenged for the current certificate. ### Changed diff --git a/certbot/certbot/_internal/plugins/manual.py b/certbot/certbot/_internal/plugins/manual.py index 3204fe1da..87ccdbd7e 100644 --- a/certbot/certbot/_internal/plugins/manual.py +++ b/certbot/certbot/_internal/plugins/manual.py @@ -35,7 +35,11 @@ class Authenticator(common.Plugin): 'is the validation string, and $CERTBOT_TOKEN is the filename of the ' 'resource requested when performing an HTTP-01 challenge. An additional ' 'cleanup script can also be provided and can use the additional variable ' - '$CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth script.') + '$CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth script.' + 'For both authenticator and cleanup script, on HTTP-01 and DNS-01 challenges,' + '$CERTBOT_REMAINING_CHALLENGES will be equal to the number of challenges that ' + 'remain after the current one, and $CERTBOT_ALL_DOMAINS contains a comma-separated ' + 'list of all domains that are challenged for the current certificate.') _DNS_INSTRUCTIONS = """\ Please deploy a DNS TXT record under the name {domain} with the following value: @@ -109,14 +113,13 @@ permitted by DNS standards.) def perform(self, achalls): # pylint: disable=missing-function-docstring self._verify_ip_logging_ok() - if self.conf('auth-hook'): - perform_achall = self._perform_achall_with_script - else: - perform_achall = self._perform_achall_manually responses = [] for achall in achalls: - perform_achall(achall) + if self.conf('auth-hook'): + self._perform_achall_with_script(achall, achalls) + else: + self._perform_achall_manually(achall) responses.append(achall.response(achall.account_key)) return responses @@ -134,9 +137,11 @@ permitted by DNS standards.) else: raise errors.PluginError('Must agree to IP logging to proceed') - def _perform_achall_with_script(self, achall): + def _perform_achall_with_script(self, achall, achalls): env = dict(CERTBOT_DOMAIN=achall.domain, - CERTBOT_VALIDATION=achall.validation(achall.account_key)) + CERTBOT_VALIDATION=achall.validation(achall.account_key), + CERTBOT_ALL_DOMAINS=','.join(one_achall.domain for one_achall in achalls), + CERTBOT_REMAINING_CHALLENGES=str(len(achalls) - achalls.index(achall) - 1)) if isinstance(achall.chall, challenges.HTTP01): env['CERTBOT_TOKEN'] = achall.chall.encode('token') else: diff --git a/certbot/docs/using.rst b/certbot/docs/using.rst index 3c3ef6fad..d3c2d1582 100644 --- a/certbot/docs/using.rst +++ b/certbot/docs/using.rst @@ -738,8 +738,10 @@ the ``cleanup.sh`` script. Additionally certbot will pass relevant environment variables to these scripts: - ``CERTBOT_DOMAIN``: The domain being authenticated -- ``CERTBOT_VALIDATION``: The validation string (HTTP-01 and DNS-01 only) +- ``CERTBOT_VALIDATION``: The validation string - ``CERTBOT_TOKEN``: Resource name part of the HTTP-01 challenge (HTTP-01 only) +- ``CERTBOT_REMAINING_CHALLENGES``: Number of challenges remaining after the current challenge +- ``CERTBOT_ALL_DOMAINS``: A comma-separated list of all domains challenged for the current certificate Additionally for cleanup: diff --git a/certbot/tests/plugins/manual_test.py b/certbot/tests/plugins/manual_test.py index bd11a9538..6cdef148a 100644 --- a/certbot/tests/plugins/manual_test.py +++ b/certbot/tests/plugins/manual_test.py @@ -72,16 +72,23 @@ class AuthenticatorTest(test_util.TempDirTestCase): self.config.manual_public_ip_logging_ok = True self.config.manual_auth_hook = ( '{0} -c "from __future__ import print_function;' - 'from certbot.compat import os; print(os.environ.get(\'CERTBOT_DOMAIN\'));' + 'from certbot.compat import os;' + 'print(os.environ.get(\'CERTBOT_DOMAIN\'));' 'print(os.environ.get(\'CERTBOT_TOKEN\', \'notoken\'));' - 'print(os.environ.get(\'CERTBOT_VALIDATION\', \'novalidation\'));"' + 'print(os.environ.get(\'CERTBOT_VALIDATION\', \'novalidation\'));' + 'print(os.environ.get(\'CERTBOT_ALL_DOMAINS\'));' + 'print(os.environ.get(\'CERTBOT_REMAINING_CHALLENGES\'));"' .format(sys.executable)) - dns_expected = '{0}\n{1}\n{2}'.format( + dns_expected = '{0}\n{1}\n{2}\n{3}\n{4}'.format( self.dns_achall.domain, 'notoken', - self.dns_achall.validation(self.dns_achall.account_key)) - http_expected = '{0}\n{1}\n{2}'.format( + self.dns_achall.validation(self.dns_achall.account_key), + ','.join(achall.domain for achall in self.achalls), + len(self.achalls) - self.achalls.index(self.dns_achall) - 1) + http_expected = '{0}\n{1}\n{2}\n{3}\n{4}'.format( self.http_achall.domain, self.http_achall.chall.encode('token'), - self.http_achall.validation(self.http_achall.account_key)) + self.http_achall.validation(self.http_achall.account_key), + ','.join(achall.domain for achall in self.achalls), + len(self.achalls) - self.achalls.index(self.http_achall) - 1) self.assertEqual( self.auth.perform(self.achalls), From 2fd85a4f36c37cd7dfa96f129338c2b6d95dd0d8 Mon Sep 17 00:00:00 2001 From: osirisinferi Date: Thu, 12 Mar 2020 17:37:49 +0100 Subject: [PATCH 122/122] Add serial number to certificates output (#7842) Fixes #7835 I had to mock out `get_serial_from_cert` to keep a test from failing, because `cert_path` was mocked itself in `test_report_human_readable`. Also, I kept the same style for the serial number as the recent Let's Encrypt e-mail: lowercase hexadecimal without a `0x` prefix and without colons every 2 chars. Shouldn't be a problem to change the format if required. --- certbot/CHANGELOG.md | 1 + certbot/certbot/_internal/cert_manager.py | 11 +++++++---- certbot/certbot/crypto_util.py | 14 ++++++++++++++ certbot/tests/cert_manager_test.py | 4 +++- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 903af0610..2f22b5204 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -6,6 +6,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added +* Added serial number of certificate to the output of `certbot certificates` * Expose two new environment variables in the authenticator and cleanup scripts used by the `manual` plugin: `CERTBOT_REMAINING_CHALLENGES` is equal to the number of challenges remaining after the current challenge, `CERTBOT_ALL_DOMAINS` is a comma-separated list diff --git a/certbot/certbot/_internal/cert_manager.py b/certbot/certbot/_internal/cert_manager.py index e6cbd5c2c..2652b3d2c 100644 --- a/certbot/certbot/_internal/cert_manager.py +++ b/certbot/certbot/_internal/cert_manager.py @@ -276,12 +276,15 @@ def human_readable_cert_info(config, cert, skip_filter_checks=False): status = "VALID: {0} days".format(diff.days) valid_string = "{0} ({1})".format(cert.target_expiry, status) + serial = format(crypto_util.get_serial_from_cert(cert.cert_path), 'x') certinfo.append(" Certificate Name: {0}\n" - " Domains: {1}\n" - " Expiry Date: {2}\n" - " Certificate Path: {3}\n" - " Private Key Path: {4}".format( + " Serial Number: {1}\n" + " Domains: {2}\n" + " Expiry Date: {3}\n" + " Certificate Path: {4}\n" + " Private Key Path: {5}".format( cert.lineagename, + serial, " ".join(cert.names()), valid_string, cert.fullchain, diff --git a/certbot/certbot/crypto_util.py b/certbot/certbot/crypto_util.py index 9136445bc..adb972f24 100644 --- a/certbot/certbot/crypto_util.py +++ b/certbot/certbot/crypto_util.py @@ -491,3 +491,17 @@ def cert_and_chain_from_fullchain(fullchain_pem): crypto.load_certificate(crypto.FILETYPE_PEM, fullchain_pem)).decode() chain = fullchain_pem[len(cert):].lstrip() return (cert, chain) + +def get_serial_from_cert(cert_path): + """Retrieve the serial number of a certificate from certificate path + + :param str cert_path: path to a cert in PEM format + + :returns: serial number of the certificate + :rtype: int + """ + # pylint: disable=redefined-outer-name + with open(cert_path) as f: + x509 = crypto.load_certificate(crypto.FILETYPE_PEM, + f.read()) + return x509.get_serial_number() diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index eb8005b2b..bea64f09c 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -200,9 +200,11 @@ class CertificatesTest(BaseCertManagerTest): self.assertTrue(mock_utility.called) shutil.rmtree(empty_tempdir) + @mock.patch('certbot.crypto_util.get_serial_from_cert') @mock.patch('certbot._internal.cert_manager.ocsp.RevocationChecker.ocsp_revoked') - def test_report_human_readable(self, mock_revoked): + def test_report_human_readable(self, mock_revoked, mock_serial): mock_revoked.return_value = None + mock_serial.return_value = 1234567890 from certbot._internal import cert_manager import datetime import pytz