From 4ca03aec8db9d83550c8b61c1b7e4dc41c8d7766 Mon Sep 17 00:00:00 2001 From: Giles Thomas Date: Tue, 5 Feb 2019 18:37:09 +0000 Subject: [PATCH 01/60] 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 02/60] 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 03/60] [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 04/60] [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 05/60] [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 06/60] [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 07/60] [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 08/60] [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 09/60] [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 10/60] 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 11/60] [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 12/60] [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 13/60] [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 bdf24d2bed1976ae5f8938d46709b1cdef424b4f Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Wed, 13 Nov 2019 00:19:02 +0200 Subject: [PATCH 14/60] 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 15/60] [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 16/60] [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 17/60] [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 18/60] 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 19/60] [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 20/60] [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 21/60] [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 22/60] 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 70be256c663be8d4163b4abb5587da79dbd2da2a Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Fri, 3 Jan 2020 01:44:11 +0200 Subject: [PATCH 23/60] 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 24/60] 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 25/60] [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 a342eb55469f8b92bc9550e4474ba04a2fd3dd58 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Thu, 23 Jan 2020 13:58:36 -0500 Subject: [PATCH 26/60] 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 27/60] 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 28/60] 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 29/60] 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 30/60] 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 31/60] 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 32/60] 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 33/60] 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 34/60] 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 35/60] 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 36/60] 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 37/60] 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 38/60] 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 39/60] 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 40/60] 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 41/60] 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 42/60] 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 6c5959d892339d8921aa302dffddd70aa4d3caad Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 4 Feb 2020 13:46:57 -0800 Subject: [PATCH 43/60] 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 44/60] 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 45/60] 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 46/60] 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 7b35abbcb42845a5c3a889884f20345edf55f640 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 5 Feb 2020 23:12:29 +0100 Subject: [PATCH 47/60] 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 48/60] 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 49/60] 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 50/60] 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 51/60] 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 52/60] 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 53/60] 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 54/60] 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 55/60] 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 56/60] 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 57/60] 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 58/60] 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 59/60] 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 60/60] 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