New upstream version 0.22.0
Andrew Starr-Bochicchio
5 years ago
0 | Copyright 2015 Electronic Frontier Foundation and others | |
1 | ||
2 | Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | you may not use this file except in compliance with the License. | |
4 | You may obtain a copy of the License at | |
5 | ||
6 | http://www.apache.org/licenses/LICENSE-2.0 | |
7 | ||
8 | Unless required by applicable law or agreed to in writing, software | |
9 | distributed under the License is distributed on an "AS IS" BASIS, | |
10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | See the License for the specific language governing permissions and | |
12 | limitations under the License. | |
13 | ||
14 | Apache License | |
15 | Version 2.0, January 2004 | |
16 | http://www.apache.org/licenses/ | |
17 | ||
18 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |
19 | ||
20 | 1. Definitions. | |
21 | ||
22 | "License" shall mean the terms and conditions for use, reproduction, | |
23 | and distribution as defined by Sections 1 through 9 of this document. | |
24 | ||
25 | "Licensor" shall mean the copyright owner or entity authorized by | |
26 | the copyright owner that is granting the License. | |
27 | ||
28 | "Legal Entity" shall mean the union of the acting entity and all | |
29 | other entities that control, are controlled by, or are under common | |
30 | control with that entity. For the purposes of this definition, | |
31 | "control" means (i) the power, direct or indirect, to cause the | |
32 | direction or management of such entity, whether by contract or | |
33 | otherwise, or (ii) ownership of fifty percent (50%) or more of the | |
34 | outstanding shares, or (iii) beneficial ownership of such entity. | |
35 | ||
36 | "You" (or "Your") shall mean an individual or Legal Entity | |
37 | exercising permissions granted by this License. | |
38 | ||
39 | "Source" form shall mean the preferred form for making modifications, | |
40 | including but not limited to software source code, documentation | |
41 | source, and configuration files. | |
42 | ||
43 | "Object" form shall mean any form resulting from mechanical | |
44 | transformation or translation of a Source form, including but | |
45 | not limited to compiled object code, generated documentation, | |
46 | and conversions to other media types. | |
47 | ||
48 | "Work" shall mean the work of authorship, whether in Source or | |
49 | Object form, made available under the License, as indicated by a | |
50 | copyright notice that is included in or attached to the work | |
51 | (an example is provided in the Appendix below). | |
52 | ||
53 | "Derivative Works" shall mean any work, whether in Source or Object | |
54 | form, that is based on (or derived from) the Work and for which the | |
55 | editorial revisions, annotations, elaborations, or other modifications | |
56 | represent, as a whole, an original work of authorship. For the purposes | |
57 | of this License, Derivative Works shall not include works that remain | |
58 | separable from, or merely link (or bind by name) to the interfaces of, | |
59 | the Work and Derivative Works thereof. | |
60 | ||
61 | "Contribution" shall mean any work of authorship, including | |
62 | the original version of the Work and any modifications or additions | |
63 | to that Work or Derivative Works thereof, that is intentionally | |
64 | submitted to Licensor for inclusion in the Work by the copyright owner | |
65 | or by an individual or Legal Entity authorized to submit on behalf of | |
66 | the copyright owner. For the purposes of this definition, "submitted" | |
67 | means any form of electronic, verbal, or written communication sent | |
68 | to the Licensor or its representatives, including but not limited to | |
69 | communication on electronic mailing lists, source code control systems, | |
70 | and issue tracking systems that are managed by, or on behalf of, the | |
71 | Licensor for the purpose of discussing and improving the Work, but | |
72 | excluding communication that is conspicuously marked or otherwise | |
73 | designated in writing by the copyright owner as "Not a Contribution." | |
74 | ||
75 | "Contributor" shall mean Licensor and any individual or Legal Entity | |
76 | on behalf of whom a Contribution has been received by Licensor and | |
77 | subsequently incorporated within the Work. | |
78 | ||
79 | 2. Grant of Copyright License. Subject to the terms and conditions of | |
80 | this License, each Contributor hereby grants to You a perpetual, | |
81 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
82 | copyright license to reproduce, prepare Derivative Works of, | |
83 | publicly display, publicly perform, sublicense, and distribute the | |
84 | Work and such Derivative Works in Source or Object form. | |
85 | ||
86 | 3. Grant of Patent License. Subject to the terms and conditions of | |
87 | this License, each Contributor hereby grants to You a perpetual, | |
88 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
89 | (except as stated in this section) patent license to make, have made, | |
90 | use, offer to sell, sell, import, and otherwise transfer the Work, | |
91 | where such license applies only to those patent claims licensable | |
92 | by such Contributor that are necessarily infringed by their | |
93 | Contribution(s) alone or by combination of their Contribution(s) | |
94 | with the Work to which such Contribution(s) was submitted. If You | |
95 | institute patent litigation against any entity (including a | |
96 | cross-claim or counterclaim in a lawsuit) alleging that the Work | |
97 | or a Contribution incorporated within the Work constitutes direct | |
98 | or contributory patent infringement, then any patent licenses | |
99 | granted to You under this License for that Work shall terminate | |
100 | as of the date such litigation is filed. | |
101 | ||
102 | 4. Redistribution. You may reproduce and distribute copies of the | |
103 | Work or Derivative Works thereof in any medium, with or without | |
104 | modifications, and in Source or Object form, provided that You | |
105 | meet the following conditions: | |
106 | ||
107 | (a) You must give any other recipients of the Work or | |
108 | Derivative Works a copy of this License; and | |
109 | ||
110 | (b) You must cause any modified files to carry prominent notices | |
111 | stating that You changed the files; and | |
112 | ||
113 | (c) You must retain, in the Source form of any Derivative Works | |
114 | that You distribute, all copyright, patent, trademark, and | |
115 | attribution notices from the Source form of the Work, | |
116 | excluding those notices that do not pertain to any part of | |
117 | the Derivative Works; and | |
118 | ||
119 | (d) If the Work includes a "NOTICE" text file as part of its | |
120 | distribution, then any Derivative Works that You distribute must | |
121 | include a readable copy of the attribution notices contained | |
122 | within such NOTICE file, excluding those notices that do not | |
123 | pertain to any part of the Derivative Works, in at least one | |
124 | of the following places: within a NOTICE text file distributed | |
125 | as part of the Derivative Works; within the Source form or | |
126 | documentation, if provided along with the Derivative Works; or, | |
127 | within a display generated by the Derivative Works, if and | |
128 | wherever such third-party notices normally appear. The contents | |
129 | of the NOTICE file are for informational purposes only and | |
130 | do not modify the License. You may add Your own attribution | |
131 | notices within Derivative Works that You distribute, alongside | |
132 | or as an addendum to the NOTICE text from the Work, provided | |
133 | that such additional attribution notices cannot be construed | |
134 | as modifying the License. | |
135 | ||
136 | You may add Your own copyright statement to Your modifications and | |
137 | may provide additional or different license terms and conditions | |
138 | for use, reproduction, or distribution of Your modifications, or | |
139 | for any such Derivative Works as a whole, provided Your use, | |
140 | reproduction, and distribution of the Work otherwise complies with | |
141 | the conditions stated in this License. | |
142 | ||
143 | 5. Submission of Contributions. Unless You explicitly state otherwise, | |
144 | any Contribution intentionally submitted for inclusion in the Work | |
145 | by You to the Licensor shall be under the terms and conditions of | |
146 | this License, without any additional terms or conditions. | |
147 | Notwithstanding the above, nothing herein shall supersede or modify | |
148 | the terms of any separate license agreement you may have executed | |
149 | with Licensor regarding such Contributions. | |
150 | ||
151 | 6. Trademarks. This License does not grant permission to use the trade | |
152 | names, trademarks, service marks, or product names of the Licensor, | |
153 | except as required for reasonable and customary use in describing the | |
154 | origin of the Work and reproducing the content of the NOTICE file. | |
155 | ||
156 | 7. Disclaimer of Warranty. Unless required by applicable law or | |
157 | agreed to in writing, Licensor provides the Work (and each | |
158 | Contributor provides its Contributions) on an "AS IS" BASIS, | |
159 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |
160 | implied, including, without limitation, any warranties or conditions | |
161 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |
162 | PARTICULAR PURPOSE. You are solely responsible for determining the | |
163 | appropriateness of using or redistributing the Work and assume any | |
164 | risks associated with Your exercise of permissions under this License. | |
165 | ||
166 | 8. Limitation of Liability. In no event and under no legal theory, | |
167 | whether in tort (including negligence), contract, or otherwise, | |
168 | unless required by applicable law (such as deliberate and grossly | |
169 | negligent acts) or agreed to in writing, shall any Contributor be | |
170 | liable to You for damages, including any direct, indirect, special, | |
171 | incidental, or consequential damages of any character arising as a | |
172 | result of this License or out of the use or inability to use the | |
173 | Work (including but not limited to damages for loss of goodwill, | |
174 | work stoppage, computer failure or malfunction, or any and all | |
175 | other commercial damages or losses), even if such Contributor | |
176 | has been advised of the possibility of such damages. | |
177 | ||
178 | 9. Accepting Warranty or Additional Liability. While redistributing | |
179 | the Work or Derivative Works thereof, You may choose to offer, | |
180 | and charge a fee for, acceptance of support, warranty, indemnity, | |
181 | or other liability obligations and/or rights consistent with this | |
182 | License. However, in accepting such obligations, You may act only | |
183 | on Your own behalf and on Your sole responsibility, not on behalf | |
184 | of any other Contributor, and only if You agree to indemnify, | |
185 | defend, and hold each Contributor harmless for any liability | |
186 | incurred by, or claims asserted against, such Contributor by reason | |
187 | of your accepting any such warranty or additional liability. | |
188 | ||
189 | END OF TERMS AND CONDITIONS |
0 | Metadata-Version: 1.2 | |
1 | Name: certbot-dns-rfc2136 | |
2 | Version: 0.22.0 | |
3 | Summary: RFC 2136 DNS Authenticator plugin for Certbot | |
4 | Home-page: https://github.com/certbot/certbot | |
5 | Author: Certbot Project | |
6 | Author-email: client-dev@letsencrypt.org | |
7 | License: Apache License 2.0 | |
8 | Description-Content-Type: UNKNOWN | |
9 | Description: UNKNOWN | |
10 | Platform: UNKNOWN | |
11 | Classifier: Development Status :: 3 - Alpha | |
12 | Classifier: Environment :: Plugins | |
13 | Classifier: Intended Audience :: System Administrators | |
14 | Classifier: License :: OSI Approved :: Apache Software License | |
15 | Classifier: Operating System :: POSIX :: Linux | |
16 | Classifier: Programming Language :: Python | |
17 | Classifier: Programming Language :: Python :: 2 | |
18 | Classifier: Programming Language :: Python :: 2.7 | |
19 | Classifier: Programming Language :: Python :: 3 | |
20 | Classifier: Programming Language :: Python :: 3.4 | |
21 | Classifier: Programming Language :: Python :: 3.5 | |
22 | Classifier: Programming Language :: Python :: 3.6 | |
23 | Classifier: Topic :: Internet :: WWW/HTTP | |
24 | Classifier: Topic :: Security | |
25 | Classifier: Topic :: System :: Installation/Setup | |
26 | Classifier: Topic :: System :: Networking | |
27 | Classifier: Topic :: System :: Systems Administration | |
28 | Classifier: Topic :: Utilities | |
29 | Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* |
0 | RFC 2136 DNS Authenticator plugin for Certbot |
0 | """ | |
1 | The `~certbot_dns_rfc2136.dns_rfc2136` plugin automates the process of | |
2 | completing a ``dns-01`` challenge (`~acme.challenges.DNS01`) by creating, and | |
3 | subsequently removing, TXT records using RFC 2136 Dynamic Updates. | |
4 | ||
5 | ||
6 | Named Arguments | |
7 | --------------- | |
8 | ||
9 | ===================================== ===================================== | |
10 | ``--dns-rfc2136-credentials`` RFC 2136 credentials_ INI file. | |
11 | (Required) | |
12 | ``--dns-rfc2136-propagation-seconds`` The number of seconds to wait for DNS | |
13 | to propagate before asking the ACME | |
14 | server to verify the DNS record. | |
15 | (Default: 60) | |
16 | ===================================== ===================================== | |
17 | ||
18 | ||
19 | Credentials | |
20 | ----------- | |
21 | ||
22 | Use of this plugin requires a configuration file containing the target DNS | |
23 | server that supports RFC 2136 Dynamic Updates, the name of the TSIG key, the | |
24 | TSIG key secret itself and the algorithm used if it's different to HMAC-MD5. | |
25 | ||
26 | .. code-block:: ini | |
27 | :name: credentials.ini | |
28 | :caption: Example credentials file: | |
29 | ||
30 | # Target DNS server | |
31 | dns_rfc2136_server = 192.0.2.1 | |
32 | # TSIG key name | |
33 | dns_rfc2136_name = keyname. | |
34 | # TSIG key secret | |
35 | dns_rfc2136_secret = 4q4wM/2I180UXoMyN4INVhJNi8V9BCV+jMw2mXgZw/CSuxUT8C7NKKFs \ | |
36 | AmKd7ak51vWKgSl12ib86oQRPkpDjg== | |
37 | # TSIG key algorithm | |
38 | dns_rfc2136_algorithm = HMAC-SHA512 | |
39 | ||
40 | The path to this file can be provided interactively or using the | |
41 | ``--dns-rfc2136-credentials`` command-line argument. Certbot records the | |
42 | path to this file for use during renewal, but does not store the file's contents. | |
43 | ||
44 | .. caution:: | |
45 | You should protect this TSIG key material as it can be used to potentially | |
46 | add, update, or delete any record in the target DNS server. Users who can | |
47 | read this file can use these credentials to issue arbitrary API calls on | |
48 | your behalf. Users who can cause Certbot to run using these credentials can | |
49 | complete a ``dns-01`` challenge to acquire new certificates or revoke | |
50 | existing certificates for associated domains, even if those domains aren't | |
51 | being managed by this server. | |
52 | ||
53 | Certbot will emit a warning if it detects that the credentials file can be | |
54 | accessed by other users on your system. The warning reads "Unsafe permissions | |
55 | on credentials configuration file", followed by the path to the credentials | |
56 | file. This warning will be emitted each time Certbot uses the credentials file, | |
57 | including for renewal, and cannot be silenced except by addressing the issue | |
58 | (e.g., by using a command like ``chmod 600`` to restrict access to the file). | |
59 | ||
60 | Sample BIND configuration | |
61 | ''''''''''''''''''''''''' | |
62 | ||
63 | Here's a sample BIND configuration for Certbot to use. You will need to | |
64 | generate a new TSIG key, include it in the BIND configuration and grant it | |
65 | permission to issue updates on the target DNS zone. | |
66 | ||
67 | .. code-block:: bash | |
68 | :caption: Generate a new SHA512 TSIG key | |
69 | ||
70 | dnssec-keygen -a HMAC-SHA512 -b 512 -n HOST keyname. | |
71 | ||
72 | .. note:: | |
73 | There are a few tools shipped with BIND that can all generate TSIG keys; | |
74 | ``dnssec-keygen``, ``rndc-confgen``, and ``ddns-confgen``. Try and use the | |
75 | most secure algorithm supported by your DNS server. | |
76 | ||
77 | .. code-block:: none | |
78 | :caption: Sample BIND configuration | |
79 | ||
80 | key "keyname." { | |
81 | algorithm hmac-sha512; | |
82 | secret "4q4wM/2I180UXoMyN4INVhJNi8V9BCV+jMw2mXgZw/CSuxUT8C7NKKFs \ | |
83 | AmKd7ak51vWKgSl12ib86oQRPkpDjg=="; | |
84 | }; | |
85 | ||
86 | zone "example.com." IN { | |
87 | type master; | |
88 | file "named.example.com"; | |
89 | update-policy { | |
90 | grant keyname. name _acme-challenge.example.com. txt; | |
91 | }; | |
92 | }; | |
93 | ||
94 | .. note:: | |
95 | This configuration limits the scope of the TSIG key to just be able to | |
96 | add and remove TXT records for one specific host for the purpose of | |
97 | completing the ``dns-01`` challenge. If your version of BIND doesn't | |
98 | support the | |
99 | `update-policy <http://www.zytrax.com/books/dns/ch7/xfer.html#update-policy>`_ | |
100 | directive then you can use the less-secure | |
101 | `allow-update <http://www.zytrax.com/books/dns/ch7/xfer.html#allow-update>`_ | |
102 | directive instead. | |
103 | ||
104 | Examples | |
105 | -------- | |
106 | ||
107 | .. code-block:: bash | |
108 | :caption: To acquire a certificate for ``example.com`` | |
109 | ||
110 | certbot certonly \\ | |
111 | --dns-rfc2136 \\ | |
112 | --dns-rfc2136-credentials ~/.secrets/certbot/rfc2136.ini \\ | |
113 | -d example.com | |
114 | ||
115 | .. code-block:: bash | |
116 | :caption: To acquire a single certificate for both ``example.com`` and | |
117 | ``www.example.com`` | |
118 | ||
119 | certbot certonly \\ | |
120 | --dns-rfc2136 \\ | |
121 | --dns-rfc2136-credentials ~/.secrets/certbot/rfc2136.ini \\ | |
122 | -d example.com \\ | |
123 | -d www.example.com | |
124 | ||
125 | .. code-block:: bash | |
126 | :caption: To acquire a certificate for ``example.com``, waiting 30 seconds | |
127 | for DNS propagation | |
128 | ||
129 | certbot certonly \\ | |
130 | --dns-rfc2136 \\ | |
131 | --dns-rfc2136-credentials ~/.secrets/certbot/rfc2136.ini \\ | |
132 | --dns-rfc2136-propagation-seconds 30 \\ | |
133 | -d example.com | |
134 | ||
135 | """ |
0 | """DNS Authenticator using RFC 2136 Dynamic Updates.""" | |
1 | import logging | |
2 | ||
3 | import dns.flags | |
4 | import dns.message | |
5 | import dns.name | |
6 | import dns.query | |
7 | import dns.rdataclass | |
8 | import dns.rdatatype | |
9 | import dns.tsig | |
10 | import dns.tsigkeyring | |
11 | import dns.update | |
12 | import zope.interface | |
13 | ||
14 | from certbot import errors | |
15 | from certbot import interfaces | |
16 | from certbot.plugins import dns_common | |
17 | ||
18 | logger = logging.getLogger(__name__) | |
19 | ||
20 | ||
21 | @zope.interface.implementer(interfaces.IAuthenticator) | |
22 | @zope.interface.provider(interfaces.IPluginFactory) | |
23 | class Authenticator(dns_common.DNSAuthenticator): | |
24 | """DNS Authenticator using RFC 2136 Dynamic Updates | |
25 | ||
26 | This Authenticator uses RFC 2136 Dynamic Updates to fulfull a dns-01 challenge. | |
27 | """ | |
28 | ||
29 | ALGORITHMS = { | |
30 | 'HMAC-MD5': dns.tsig.HMAC_MD5, | |
31 | 'HMAC-SHA1': dns.tsig.HMAC_SHA1, | |
32 | 'HMAC-SHA224': dns.tsig.HMAC_SHA224, | |
33 | 'HMAC-SHA256': dns.tsig.HMAC_SHA256, | |
34 | 'HMAC-SHA384': dns.tsig.HMAC_SHA384, | |
35 | 'HMAC-SHA512': dns.tsig.HMAC_SHA512 | |
36 | } | |
37 | ||
38 | description = 'Obtain certificates using a DNS TXT record (if you are using BIND for DNS).' | |
39 | ttl = 120 | |
40 | ||
41 | def __init__(self, *args, **kwargs): | |
42 | super(Authenticator, self).__init__(*args, **kwargs) | |
43 | self.credentials = None | |
44 | ||
45 | @classmethod | |
46 | def add_parser_arguments(cls, add): # pylint: disable=arguments-differ | |
47 | super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60) | |
48 | add('credentials', help='RFC 2136 credentials INI file.') | |
49 | ||
50 | def more_info(self): # pylint: disable=missing-docstring,no-self-use | |
51 | return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ | |
52 | 'RFC 2136 Dynamic Updates.' | |
53 | ||
54 | def _validate_algorithm(self, credentials): | |
55 | algorithm = credentials.conf('algorithm') | |
56 | if algorithm: | |
57 | if not self.ALGORITHMS.get(algorithm): | |
58 | raise errors.PluginError("Unknown algorithm: {0}.".format(algorithm)) | |
59 | ||
60 | def _setup_credentials(self): | |
61 | self.credentials = self._configure_credentials( | |
62 | 'credentials', | |
63 | 'RFC 2136 credentials INI file', | |
64 | { | |
65 | 'name': 'TSIG key name', | |
66 | 'secret': 'TSIG key secret', | |
67 | 'server': 'The target DNS server' | |
68 | }, | |
69 | self._validate_algorithm | |
70 | ) | |
71 | ||
72 | def _perform(self, domain, validation_name, validation): | |
73 | self._get_rfc2136_client().add_txt_record(domain, validation_name, validation, self.ttl) | |
74 | ||
75 | def _cleanup(self, domain, validation_name, validation): | |
76 | self._get_rfc2136_client().del_txt_record(domain, validation_name, validation) | |
77 | ||
78 | def _get_rfc2136_client(self): | |
79 | return _RFC2136Client(self.credentials.conf('server'), | |
80 | self.credentials.conf('name'), | |
81 | self.credentials.conf('secret'), | |
82 | self.ALGORITHMS.get(self.credentials.conf('algorithm'), | |
83 | dns.tsig.HMAC_MD5)) | |
84 | ||
85 | ||
86 | class _RFC2136Client(object): | |
87 | """ | |
88 | Encapsulates all communication with the target DNS server. | |
89 | """ | |
90 | def __init__(self, server, key_name, key_secret, key_algorithm): | |
91 | self.server = server | |
92 | self.keyring = dns.tsigkeyring.from_text({ | |
93 | key_name: key_secret | |
94 | }) | |
95 | self.algorithm = key_algorithm | |
96 | ||
97 | def add_txt_record(self, domain_name, record_name, record_content, record_ttl): | |
98 | """ | |
99 | Add a TXT record using the supplied information. | |
100 | ||
101 | :param str domain: The domain to use to find the closest SOA. | |
102 | :param str record_name: The record name (typically beginning with '_acme-challenge.'). | |
103 | :param str record_content: The record content (typically the challenge validation). | |
104 | :param int record_ttl: The record TTL (number of seconds that the record may be cached). | |
105 | :raises certbot.errors.PluginError: if an error occurs communicating with the DNS server | |
106 | """ | |
107 | ||
108 | domain = self._find_domain(domain_name) | |
109 | ||
110 | n = dns.name.from_text(record_name) | |
111 | o = dns.name.from_text(domain) | |
112 | rel = n.relativize(o) | |
113 | ||
114 | update = dns.update.Update( | |
115 | domain, | |
116 | keyring=self.keyring, | |
117 | keyalgorithm=self.algorithm) | |
118 | update.add(rel, record_ttl, dns.rdatatype.TXT, record_content) | |
119 | ||
120 | try: | |
121 | response = dns.query.tcp(update, self.server) | |
122 | except Exception as e: | |
123 | raise errors.PluginError('Encountered error adding TXT record: {0}' | |
124 | .format(e)) | |
125 | rcode = response.rcode() | |
126 | ||
127 | if rcode == dns.rcode.NOERROR: | |
128 | logger.debug('Successfully added TXT record') | |
129 | else: | |
130 | raise errors.PluginError('Received response from server: {0}' | |
131 | .format(dns.rcode.to_text(rcode))) | |
132 | ||
133 | def del_txt_record(self, domain_name, record_name, record_content): | |
134 | """ | |
135 | Delete a TXT record using the supplied information. | |
136 | ||
137 | :param str domain: The domain to use to find the closest SOA. | |
138 | :param str record_name: The record name (typically beginning with '_acme-challenge.'). | |
139 | :param str record_content: The record content (typically the challenge validation). | |
140 | :param int record_ttl: The record TTL (number of seconds that the record may be cached). | |
141 | :raises certbot.errors.PluginError: if an error occurs communicating with the DNS server | |
142 | """ | |
143 | ||
144 | domain = self._find_domain(domain_name) | |
145 | ||
146 | n = dns.name.from_text(record_name) | |
147 | o = dns.name.from_text(domain) | |
148 | rel = n.relativize(o) | |
149 | ||
150 | update = dns.update.Update( | |
151 | domain, | |
152 | keyring=self.keyring, | |
153 | keyalgorithm=self.algorithm) | |
154 | update.delete(rel, dns.rdatatype.TXT, record_content) | |
155 | ||
156 | try: | |
157 | response = dns.query.tcp(update, self.server) | |
158 | except Exception as e: | |
159 | raise errors.PluginError('Encountered error deleting TXT record: {0}' | |
160 | .format(e)) | |
161 | rcode = response.rcode() | |
162 | ||
163 | if rcode == dns.rcode.NOERROR: | |
164 | logger.debug('Successfully deleted TXT record') | |
165 | else: | |
166 | raise errors.PluginError('Received response from server: {0}' | |
167 | .format(dns.rcode.to_text(rcode))) | |
168 | ||
169 | def _find_domain(self, domain_name): | |
170 | """ | |
171 | Find the closest domain with an SOA record for a given domain name. | |
172 | ||
173 | :param str domain_name: The domain name for which to find the closest SOA record. | |
174 | :returns: The domain, if found. | |
175 | :rtype: str | |
176 | :raises certbot.errors.PluginError: if no SOA record can be found. | |
177 | """ | |
178 | ||
179 | domain_name_guesses = dns_common.base_domain_name_guesses(domain_name) | |
180 | ||
181 | # Loop through until we find an authoritative SOA record | |
182 | for guess in domain_name_guesses: | |
183 | if self._query_soa(guess): | |
184 | return guess | |
185 | ||
186 | raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.' | |
187 | .format(domain_name, domain_name_guesses)) | |
188 | ||
189 | def _query_soa(self, domain_name): | |
190 | """ | |
191 | Query a domain name for an authoritative SOA record. | |
192 | ||
193 | :param str domain_name: The domain name to query for an SOA record. | |
194 | :returns: True if found, False otherwise. | |
195 | :rtype: bool | |
196 | :raises certbot.errors.PluginError: if no response is received. | |
197 | """ | |
198 | ||
199 | domain = dns.name.from_text(domain_name) | |
200 | ||
201 | request = dns.message.make_query(domain, dns.rdatatype.SOA, dns.rdataclass.IN) | |
202 | # Turn off Recursion Desired bit in query | |
203 | request.flags ^= dns.flags.RD | |
204 | ||
205 | try: | |
206 | response = dns.query.udp(request, self.server) | |
207 | rcode = response.rcode() | |
208 | ||
209 | # Authoritative Answer bit should be set | |
210 | if (rcode == dns.rcode.NOERROR and response.get_rrset(response.answer, | |
211 | domain, dns.rdataclass.IN, dns.rdatatype.SOA) and response.flags & dns.flags.AA): | |
212 | logger.debug('Received authoritative SOA response for %s', domain_name) | |
213 | return True | |
214 | ||
215 | logger.debug('No authoritative SOA record found for %s', domain_name) | |
216 | return False | |
217 | except Exception as e: | |
218 | raise errors.PluginError('Encountered error when making query: {0}' | |
219 | .format(e)) | |
220 |
0 | """Tests for certbot_dns_rfc2136.dns_rfc2136.""" | |
1 | ||
2 | import os | |
3 | import unittest | |
4 | ||
5 | import dns.flags | |
6 | import dns.rcode | |
7 | import dns.tsig | |
8 | import mock | |
9 | ||
10 | from certbot import errors | |
11 | from certbot.plugins import dns_test_common | |
12 | from certbot.plugins.dns_test_common import DOMAIN | |
13 | from certbot.tests import util as test_util | |
14 | ||
15 | SERVER = '192.0.2.1' | |
16 | NAME = 'a-tsig-key.' | |
17 | SECRET = 'SSB3b25kZXIgd2hvIHdpbGwgYm90aGVyIHRvIGRlY29kZSB0aGlzIHRleHQK' | |
18 | VALID_CONFIG = {"rfc2136_server": SERVER, "rfc2136_name": NAME, "rfc2136_secret": SECRET} | |
19 | ||
20 | ||
21 | class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): | |
22 | ||
23 | def setUp(self): | |
24 | from certbot_dns_rfc2136.dns_rfc2136 import Authenticator | |
25 | ||
26 | super(AuthenticatorTest, self).setUp() | |
27 | ||
28 | path = os.path.join(self.tempdir, 'file.ini') | |
29 | dns_test_common.write(VALID_CONFIG, path) | |
30 | ||
31 | self.config = mock.MagicMock(rfc2136_credentials=path, | |
32 | rfc2136_propagation_seconds=0) # don't wait during tests | |
33 | ||
34 | self.auth = Authenticator(self.config, "rfc2136") | |
35 | ||
36 | self.mock_client = mock.MagicMock() | |
37 | # _get_rfc2136_client | pylint: disable=protected-access | |
38 | self.auth._get_rfc2136_client = mock.MagicMock(return_value=self.mock_client) | |
39 | ||
40 | def test_perform(self): | |
41 | self.auth.perform([self.achall]) | |
42 | ||
43 | expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] | |
44 | self.assertEqual(expected, self.mock_client.mock_calls) | |
45 | ||
46 | def test_cleanup(self): | |
47 | # _attempt_cleanup | pylint: disable=protected-access | |
48 | self.auth._attempt_cleanup = True | |
49 | self.auth.cleanup([self.achall]) | |
50 | ||
51 | expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)] | |
52 | self.assertEqual(expected, self.mock_client.mock_calls) | |
53 | ||
54 | def test_invalid_algorithm_raises(self): | |
55 | config = VALID_CONFIG.copy() | |
56 | config["rfc2136_algorithm"] = "INVALID" | |
57 | dns_test_common.write(config, self.config.rfc2136_credentials) | |
58 | ||
59 | self.assertRaises(errors.PluginError, | |
60 | self.auth.perform, | |
61 | [self.achall]) | |
62 | ||
63 | def test_valid_algorithm_passes(self): | |
64 | config = VALID_CONFIG.copy() | |
65 | config["rfc2136_algorithm"] = "HMAC-SHA512" | |
66 | dns_test_common.write(config, self.config.rfc2136_credentials) | |
67 | ||
68 | self.auth.perform([self.achall]) | |
69 | ||
70 | ||
71 | class RFC2136ClientTest(unittest.TestCase): | |
72 | ||
73 | def setUp(self): | |
74 | from certbot_dns_rfc2136.dns_rfc2136 import _RFC2136Client | |
75 | ||
76 | self.rfc2136_client = _RFC2136Client(SERVER, NAME, SECRET, dns.tsig.HMAC_MD5) | |
77 | ||
78 | @mock.patch("dns.query.tcp") | |
79 | def test_add_txt_record(self, query_mock): | |
80 | query_mock.return_value.rcode.return_value = dns.rcode.NOERROR | |
81 | # _find_domain | pylint: disable=protected-access | |
82 | self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") | |
83 | ||
84 | self.rfc2136_client.add_txt_record(DOMAIN, "bar", "baz", 42) | |
85 | ||
86 | query_mock.assert_called_with(mock.ANY, SERVER) | |
87 | self.assertTrue("bar. 42 IN TXT \"baz\"" in str(query_mock.call_args[0][0])) | |
88 | ||
89 | @mock.patch("dns.query.tcp") | |
90 | def test_add_txt_record_wraps_errors(self, query_mock): | |
91 | query_mock.side_effect = Exception | |
92 | # _find_domain | pylint: disable=protected-access | |
93 | self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") | |
94 | ||
95 | self.assertRaises( | |
96 | errors.PluginError, | |
97 | self.rfc2136_client.add_txt_record, | |
98 | DOMAIN, "bar", "baz", 42) | |
99 | ||
100 | @mock.patch("dns.query.tcp") | |
101 | def test_add_txt_record_server_error(self, query_mock): | |
102 | query_mock.return_value.rcode.return_value = dns.rcode.NXDOMAIN | |
103 | # _find_domain | pylint: disable=protected-access | |
104 | self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") | |
105 | ||
106 | self.assertRaises( | |
107 | errors.PluginError, | |
108 | self.rfc2136_client.add_txt_record, | |
109 | DOMAIN, "bar", "baz", 42) | |
110 | ||
111 | @mock.patch("dns.query.tcp") | |
112 | def test_del_txt_record(self, query_mock): | |
113 | query_mock.return_value.rcode.return_value = dns.rcode.NOERROR | |
114 | # _find_domain | pylint: disable=protected-access | |
115 | self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") | |
116 | ||
117 | self.rfc2136_client.del_txt_record(DOMAIN, "bar", "baz") | |
118 | ||
119 | query_mock.assert_called_with(mock.ANY, SERVER) | |
120 | self.assertTrue("bar. 0 NONE TXT \"baz\"" in str(query_mock.call_args[0][0])) | |
121 | ||
122 | @mock.patch("dns.query.tcp") | |
123 | def test_del_txt_record_wraps_errors(self, query_mock): | |
124 | query_mock.side_effect = Exception | |
125 | # _find_domain | pylint: disable=protected-access | |
126 | self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") | |
127 | ||
128 | self.assertRaises( | |
129 | errors.PluginError, | |
130 | self.rfc2136_client.del_txt_record, | |
131 | DOMAIN, "bar", "baz") | |
132 | ||
133 | @mock.patch("dns.query.tcp") | |
134 | def test_del_txt_record_server_error(self, query_mock): | |
135 | query_mock.return_value.rcode.return_value = dns.rcode.NXDOMAIN | |
136 | # _find_domain | pylint: disable=protected-access | |
137 | self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") | |
138 | ||
139 | self.assertRaises( | |
140 | errors.PluginError, | |
141 | self.rfc2136_client.del_txt_record, | |
142 | DOMAIN, "bar", "baz") | |
143 | ||
144 | def test_find_domain(self): | |
145 | # _query_soa | pylint: disable=protected-access | |
146 | self.rfc2136_client._query_soa = mock.MagicMock(side_effect=[False, False, True]) | |
147 | ||
148 | # _find_domain | pylint: disable=protected-access | |
149 | domain = self.rfc2136_client._find_domain('foo.bar.'+DOMAIN) | |
150 | ||
151 | self.assertTrue(domain == DOMAIN) | |
152 | ||
153 | def test_find_domain_wraps_errors(self): | |
154 | # _query_soa | pylint: disable=protected-access | |
155 | self.rfc2136_client._query_soa = mock.MagicMock(return_value=False) | |
156 | ||
157 | self.assertRaises( | |
158 | errors.PluginError, | |
159 | # _find_domain | pylint: disable=protected-access | |
160 | self.rfc2136_client._find_domain, | |
161 | 'foo.bar.'+DOMAIN) | |
162 | ||
163 | @mock.patch("dns.query.udp") | |
164 | def test_query_soa_found(self, query_mock): | |
165 | query_mock.return_value = mock.MagicMock(answer=[mock.MagicMock()], flags=dns.flags.AA) | |
166 | query_mock.return_value.rcode.return_value = dns.rcode.NOERROR | |
167 | ||
168 | # _query_soa | pylint: disable=protected-access | |
169 | result = self.rfc2136_client._query_soa(DOMAIN) | |
170 | ||
171 | query_mock.assert_called_with(mock.ANY, SERVER) | |
172 | self.assertTrue(result == True) | |
173 | ||
174 | @mock.patch("dns.query.udp") | |
175 | def test_query_soa_not_found(self, query_mock): | |
176 | query_mock.return_value.rcode.return_value = dns.rcode.NXDOMAIN | |
177 | ||
178 | # _query_soa | pylint: disable=protected-access | |
179 | result = self.rfc2136_client._query_soa(DOMAIN) | |
180 | ||
181 | query_mock.assert_called_with(mock.ANY, SERVER) | |
182 | self.assertTrue(result == False) | |
183 | ||
184 | @mock.patch("dns.query.udp") | |
185 | def test_query_soa_wraps_errors(self, query_mock): | |
186 | query_mock.side_effect = Exception | |
187 | ||
188 | self.assertRaises( | |
189 | errors.PluginError, | |
190 | # _query_soa | pylint: disable=protected-access | |
191 | self.rfc2136_client._query_soa, | |
192 | DOMAIN) | |
193 | ||
194 | ||
195 | if __name__ == "__main__": | |
196 | unittest.main() # pragma: no cover |
0 | Metadata-Version: 1.2 | |
1 | Name: certbot-dns-rfc2136 | |
2 | Version: 0.22.0 | |
3 | Summary: RFC 2136 DNS Authenticator plugin for Certbot | |
4 | Home-page: https://github.com/certbot/certbot | |
5 | Author: Certbot Project | |
6 | Author-email: client-dev@letsencrypt.org | |
7 | License: Apache License 2.0 | |
8 | Description-Content-Type: UNKNOWN | |
9 | Description: UNKNOWN | |
10 | Platform: UNKNOWN | |
11 | Classifier: Development Status :: 3 - Alpha | |
12 | Classifier: Environment :: Plugins | |
13 | Classifier: Intended Audience :: System Administrators | |
14 | Classifier: License :: OSI Approved :: Apache Software License | |
15 | Classifier: Operating System :: POSIX :: Linux | |
16 | Classifier: Programming Language :: Python | |
17 | Classifier: Programming Language :: Python :: 2 | |
18 | Classifier: Programming Language :: Python :: 2.7 | |
19 | Classifier: Programming Language :: Python :: 3 | |
20 | Classifier: Programming Language :: Python :: 3.4 | |
21 | Classifier: Programming Language :: Python :: 3.5 | |
22 | Classifier: Programming Language :: Python :: 3.6 | |
23 | Classifier: Topic :: Internet :: WWW/HTTP | |
24 | Classifier: Topic :: Security | |
25 | Classifier: Topic :: System :: Installation/Setup | |
26 | Classifier: Topic :: System :: Networking | |
27 | Classifier: Topic :: System :: Systems Administration | |
28 | Classifier: Topic :: Utilities | |
29 | Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* |
0 | LICENSE.txt | |
1 | MANIFEST.in | |
2 | README.rst | |
3 | setup.cfg | |
4 | setup.py | |
5 | certbot_dns_rfc2136/__init__.py | |
6 | certbot_dns_rfc2136/dns_rfc2136.py | |
7 | certbot_dns_rfc2136/dns_rfc2136_test.py | |
8 | certbot_dns_rfc2136.egg-info/PKG-INFO | |
9 | certbot_dns_rfc2136.egg-info/SOURCES.txt | |
10 | certbot_dns_rfc2136.egg-info/dependency_links.txt | |
11 | certbot_dns_rfc2136.egg-info/entry_points.txt | |
12 | certbot_dns_rfc2136.egg-info/requires.txt | |
13 | certbot_dns_rfc2136.egg-info/top_level.txt | |
14 | docs/.gitignore | |
15 | docs/Makefile | |
16 | docs/api.rst | |
17 | docs/conf.py | |
18 | docs/index.rst | |
19 | docs/make.bat | |
20 | docs/api/dns_rfc2136.rst⏎ |
0 | acme>=0.21.1 | |
1 | certbot>=0.21.1 | |
2 | dnspython | |
3 | mock | |
4 | setuptools | |
5 | zope.interface | |
6 | ||
7 | [docs] | |
8 | Sphinx>=1.0 | |
9 | sphinx_rtd_theme |
0 | certbot_dns_rfc2136 |
0 | /_build/ |
0 | # Minimal makefile for Sphinx documentation | |
1 | # | |
2 | ||
3 | # You can set these variables from the command line. | |
4 | SPHINXOPTS = | |
5 | SPHINXBUILD = python -msphinx | |
6 | SPHINXPROJ = certbot-dns-rfc2136 | |
7 | SOURCEDIR = . | |
8 | BUILDDIR = _build | |
9 | ||
10 | # Put it first so that "make" without argument is like "make help". | |
11 | help: | |
12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) | |
13 | ||
14 | .PHONY: help Makefile | |
15 | ||
16 | # Catch-all target: route all unknown targets to Sphinx using the new | |
17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). | |
18 | %: Makefile | |
19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)⏎ |
0 | :mod:`certbot_dns_rfc2136.dns_rfc2136` | |
1 | -------------------------------------- | |
2 | ||
3 | .. automodule:: certbot_dns_rfc2136.dns_rfc2136 | |
4 | :members: |
0 | # -*- coding: utf-8 -*- | |
1 | # | |
2 | # certbot-dns-rfc2136 documentation build configuration file, created by | |
3 | # sphinx-quickstart on Thu Jun 15 06:42:51 2017. | |
4 | # | |
5 | # This file is execfile()d with the current directory set to its | |
6 | # containing dir. | |
7 | # | |
8 | # Note that not all possible configuration values are present in this | |
9 | # autogenerated file. | |
10 | # | |
11 | # All configuration values have a default; values that are commented out | |
12 | # serve to show the default. | |
13 | ||
14 | # If extensions (or modules to document with autodoc) are in another directory, | |
15 | # add these directories to sys.path here. If the directory is relative to the | |
16 | # documentation root, use os.path.abspath to make it absolute, like shown here. | |
17 | # | |
18 | import os | |
19 | # import sys | |
20 | # sys.path.insert(0, os.path.abspath('.')) | |
21 | ||
22 | ||
23 | # -- General configuration ------------------------------------------------ | |
24 | ||
25 | # If your documentation needs a minimal Sphinx version, state it here. | |
26 | # | |
27 | needs_sphinx = '1.0' | |
28 | ||
29 | # Add any Sphinx extension module names here, as strings. They can be | |
30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom | |
31 | # ones. | |
32 | extensions = ['sphinx.ext.autodoc', | |
33 | 'sphinx.ext.intersphinx', | |
34 | 'sphinx.ext.todo', | |
35 | 'sphinx.ext.coverage', | |
36 | 'sphinx.ext.viewcode'] | |
37 | ||
38 | autodoc_member_order = 'bysource' | |
39 | autodoc_default_flags = ['show-inheritance', 'private-members'] | |
40 | ||
41 | # Add any paths that contain templates here, relative to this directory. | |
42 | templates_path = ['_templates'] | |
43 | ||
44 | # The suffix(es) of source filenames. | |
45 | # You can specify multiple suffix as a list of string: | |
46 | # | |
47 | # source_suffix = ['.rst', '.md'] | |
48 | source_suffix = '.rst' | |
49 | ||
50 | # The master toctree document. | |
51 | master_doc = 'index' | |
52 | ||
53 | # General information about the project. | |
54 | project = u'certbot-dns-rfc2136' | |
55 | copyright = u'2017, Certbot Project' | |
56 | author = u'Certbot Project' | |
57 | ||
58 | # The version info for the project you're documenting, acts as replacement for | |
59 | # |version| and |release|, also used in various other places throughout the | |
60 | # built documents. | |
61 | # | |
62 | # The short X.Y version. | |
63 | version = u'0' | |
64 | # The full version, including alpha/beta/rc tags. | |
65 | release = u'0' | |
66 | ||
67 | # The language for content autogenerated by Sphinx. Refer to documentation | |
68 | # for a list of supported languages. | |
69 | # | |
70 | # This is also used if you do content translation via gettext catalogs. | |
71 | # Usually you set "language" from the command line for these cases. | |
72 | language = 'en' | |
73 | ||
74 | # List of patterns, relative to source directory, that match files and | |
75 | # directories to ignore when looking for source files. | |
76 | # This patterns also effect to html_static_path and html_extra_path | |
77 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] | |
78 | ||
79 | default_role = 'py:obj' | |
80 | ||
81 | # The name of the Pygments (syntax highlighting) style to use. | |
82 | pygments_style = 'sphinx' | |
83 | ||
84 | # If true, `todo` and `todoList` produce output, else they produce nothing. | |
85 | todo_include_todos = True | |
86 | ||
87 | ||
88 | # -- Options for HTML output ---------------------------------------------- | |
89 | ||
90 | # The theme to use for HTML and HTML Help pages. See the documentation for | |
91 | # a list of builtin themes. | |
92 | # | |
93 | ||
94 | # http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs | |
95 | # on_rtd is whether we are on readthedocs.org | |
96 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' | |
97 | if not on_rtd: # only import and set the theme if we're building docs locally | |
98 | import sphinx_rtd_theme | |
99 | html_theme = 'sphinx_rtd_theme' | |
100 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] | |
101 | # otherwise, readthedocs.org uses their theme by default, so no need to specify it | |
102 | ||
103 | # Theme options are theme-specific and customize the look and feel of a theme | |
104 | # further. For a list of options available for each theme, see the | |
105 | # documentation. | |
106 | # | |
107 | # html_theme_options = {} | |
108 | ||
109 | # Add any paths that contain custom static files (such as style sheets) here, | |
110 | # relative to this directory. They are copied after the builtin static files, | |
111 | # so a file named "default.css" will overwrite the builtin "default.css". | |
112 | html_static_path = ['_static'] | |
113 | ||
114 | ||
115 | # -- Options for HTMLHelp output ------------------------------------------ | |
116 | ||
117 | # Output file base name for HTML help builder. | |
118 | htmlhelp_basename = 'certbot-dns-rfc2136doc' | |
119 | ||
120 | ||
121 | # -- Options for LaTeX output --------------------------------------------- | |
122 | ||
123 | latex_elements = { | |
124 | # The paper size ('letterpaper' or 'a4paper'). | |
125 | # | |
126 | # 'papersize': 'letterpaper', | |
127 | ||
128 | # The font size ('10pt', '11pt' or '12pt'). | |
129 | # | |
130 | # 'pointsize': '10pt', | |
131 | ||
132 | # Additional stuff for the LaTeX preamble. | |
133 | # | |
134 | # 'preamble': '', | |
135 | ||
136 | # Latex figure (float) alignment | |
137 | # | |
138 | # 'figure_align': 'htbp', | |
139 | } | |
140 | ||
141 | # Grouping the document tree into LaTeX files. List of tuples | |
142 | # (source start file, target name, title, | |
143 | # author, documentclass [howto, manual, or own class]). | |
144 | latex_documents = [ | |
145 | (master_doc, 'certbot-dns-rfc2136.tex', u'certbot-dns-rfc2136 Documentation', | |
146 | u'Certbot Project', 'manual'), | |
147 | ] | |
148 | ||
149 | ||
150 | # -- Options for manual page output --------------------------------------- | |
151 | ||
152 | # One entry per manual page. List of tuples | |
153 | # (source start file, name, description, authors, manual section). | |
154 | man_pages = [ | |
155 | (master_doc, 'certbot-dns-rfc2136', u'certbot-dns-rfc2136 Documentation', | |
156 | [author], 1) | |
157 | ] | |
158 | ||
159 | ||
160 | # -- Options for Texinfo output ------------------------------------------- | |
161 | ||
162 | # Grouping the document tree into Texinfo files. List of tuples | |
163 | # (source start file, target name, title, author, | |
164 | # dir menu entry, description, category) | |
165 | texinfo_documents = [ | |
166 | (master_doc, 'certbot-dns-rfc2136', u'certbot-dns-rfc2136 Documentation', | |
167 | author, 'certbot-dns-rfc2136', 'One line description of project.', | |
168 | 'Miscellaneous'), | |
169 | ] | |
170 | ||
171 | ||
172 | ||
173 | ||
174 | # Example configuration for intersphinx: refer to the Python standard library. | |
175 | intersphinx_mapping = { | |
176 | 'python': ('https://docs.python.org/', None), | |
177 | 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), | |
178 | 'certbot': ('https://certbot.eff.org/docs/', None), | |
179 | } |
0 | .. certbot-dns-rfc2136 documentation master file, created by | |
1 | sphinx-quickstart on Thu Jun 15 06:42:51 2017. | |
2 | You can adapt this file completely to your liking, but it should at least | |
3 | contain the root `toctree` directive. | |
4 | ||
5 | Welcome to certbot-dns-rfc2136's documentation! | |
6 | =============================================== | |
7 | ||
8 | .. toctree:: | |
9 | :maxdepth: 2 | |
10 | :caption: Contents: | |
11 | ||
12 | .. toctree:: | |
13 | :maxdepth: 1 | |
14 | ||
15 | api | |
16 | ||
17 | .. automodule:: certbot_dns_rfc2136 | |
18 | :members: | |
19 | ||
20 | ||
21 | ||
22 | Indices and tables | |
23 | ================== | |
24 | ||
25 | * :ref:`genindex` | |
26 | * :ref:`modindex` | |
27 | * :ref:`search` |
0 | @ECHO OFF | |
1 | ||
2 | pushd %~dp0 | |
3 | ||
4 | REM Command file for Sphinx documentation | |
5 | ||
6 | if "%SPHINXBUILD%" == "" ( | |
7 | set SPHINXBUILD=python -msphinx | |
8 | ) | |
9 | set SOURCEDIR=. | |
10 | set BUILDDIR=_build | |
11 | set SPHINXPROJ=certbot-dns-rfc2136 | |
12 | ||
13 | if "%1" == "" goto help | |
14 | ||
15 | %SPHINXBUILD% >NUL 2>NUL | |
16 | if errorlevel 9009 ( | |
17 | echo. | |
18 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, | |
19 | echo.then set the SPHINXBUILD environment variable to point to the full | |
20 | echo.path of the 'sphinx-build' executable. Alternatively you may add the | |
21 | echo.Sphinx directory to PATH. | |
22 | echo. | |
23 | echo.If you don't have Sphinx installed, grab it from | |
24 | echo.http://sphinx-doc.org/ | |
25 | exit /b 1 | |
26 | ) | |
27 | ||
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% | |
29 | goto end | |
30 | ||
31 | :help | |
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% | |
33 | ||
34 | :end | |
35 | popd |
0 | import sys | |
1 | ||
2 | from setuptools import setup | |
3 | from setuptools import find_packages | |
4 | ||
5 | ||
6 | version = '0.22.0' | |
7 | ||
8 | # Remember to update local-oldest-requirements.txt when changing the minimum | |
9 | # acme/certbot version. | |
10 | install_requires = [ | |
11 | 'acme>=0.21.1', | |
12 | 'certbot>=0.21.1', | |
13 | 'dnspython', | |
14 | 'mock', | |
15 | 'setuptools', | |
16 | 'zope.interface', | |
17 | ] | |
18 | ||
19 | docs_extras = [ | |
20 | 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags | |
21 | 'sphinx_rtd_theme', | |
22 | ] | |
23 | ||
24 | setup( | |
25 | name='certbot-dns-rfc2136', | |
26 | version=version, | |
27 | description="RFC 2136 DNS Authenticator plugin for Certbot", | |
28 | url='https://github.com/certbot/certbot', | |
29 | author="Certbot Project", | |
30 | author_email='client-dev@letsencrypt.org', | |
31 | license='Apache License 2.0', | |
32 | python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', | |
33 | classifiers=[ | |
34 | 'Development Status :: 3 - Alpha', | |
35 | 'Environment :: Plugins', | |
36 | 'Intended Audience :: System Administrators', | |
37 | 'License :: OSI Approved :: Apache Software License', | |
38 | 'Operating System :: POSIX :: Linux', | |
39 | 'Programming Language :: Python', | |
40 | 'Programming Language :: Python :: 2', | |
41 | 'Programming Language :: Python :: 2.7', | |
42 | 'Programming Language :: Python :: 3', | |
43 | 'Programming Language :: Python :: 3.4', | |
44 | 'Programming Language :: Python :: 3.5', | |
45 | 'Programming Language :: Python :: 3.6', | |
46 | 'Topic :: Internet :: WWW/HTTP', | |
47 | 'Topic :: Security', | |
48 | 'Topic :: System :: Installation/Setup', | |
49 | 'Topic :: System :: Networking', | |
50 | 'Topic :: System :: Systems Administration', | |
51 | 'Topic :: Utilities', | |
52 | ], | |
53 | ||
54 | packages=find_packages(), | |
55 | include_package_data=True, | |
56 | install_requires=install_requires, | |
57 | extras_require={ | |
58 | 'docs': docs_extras, | |
59 | }, | |
60 | entry_points={ | |
61 | 'certbot.plugins': [ | |
62 | 'dns-rfc2136 = certbot_dns_rfc2136.dns_rfc2136:Authenticator', | |
63 | ], | |
64 | }, | |
65 | test_suite='certbot_dns_rfc2136', | |
66 | ) |