certbot-nginx-0.40.0/0002755000175000017500000000000013560356440014556 5ustar ericaerica00000000000000certbot-nginx-0.40.0/setup.cfg0000644000175000017500000000010313560356440016367 0ustar ericaerica00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 certbot-nginx-0.40.0/LICENSE.txt0000644000175000017500000002732013560356424016405 0ustar ericaerica00000000000000 Copyright 2015 Electronic Frontier Foundation and others Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Incorporating code from nginxparser Copyright 2014 Fatih Erikli Licensed MIT Text of Apache License ====================== Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Text of MIT License =================== Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. certbot-nginx-0.40.0/README.rst0000644000175000017500000000003113560356424016237 0ustar ericaerica00000000000000Nginx plugin for Certbot certbot-nginx-0.40.0/PKG-INFO0000644000175000017500000000242613560356440015655 0ustar ericaerica00000000000000Metadata-Version: 2.1 Name: certbot-nginx Version: 0.40.0 Summary: Nginx plugin for Certbot Home-page: https://github.com/letsencrypt/letsencrypt Author: Certbot Project Author-email: client-dev@letsencrypt.org License: Apache License 2.0 Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Plugins Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Security Classifier: Topic :: System :: Installation/Setup Classifier: Topic :: System :: Networking Classifier: Topic :: System :: Systems Administration Classifier: Topic :: Utilities Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* Provides-Extra: docs certbot-nginx-0.40.0/certbot_nginx.egg-info/0002755000175000017500000000000013560356440021115 5ustar ericaerica00000000000000certbot-nginx-0.40.0/certbot_nginx.egg-info/top_level.txt0000644000175000017500000000001613560356440023642 0ustar ericaerica00000000000000certbot_nginx certbot-nginx-0.40.0/certbot_nginx.egg-info/PKG-INFO0000644000175000017500000000242613560356440022214 0ustar ericaerica00000000000000Metadata-Version: 2.1 Name: certbot-nginx Version: 0.40.0 Summary: Nginx plugin for Certbot Home-page: https://github.com/letsencrypt/letsencrypt Author: Certbot Project Author-email: client-dev@letsencrypt.org License: Apache License 2.0 Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Plugins Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Security Classifier: Topic :: System :: Installation/Setup Classifier: Topic :: System :: Networking Classifier: Topic :: System :: Systems Administration Classifier: Topic :: Utilities Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* Provides-Extra: docs certbot-nginx-0.40.0/certbot_nginx.egg-info/requires.txt0000644000175000017500000000017413560356440023515 0ustar ericaerica00000000000000acme>=0.29.0 certbot>=0.35.0 mock PyOpenSSL pyparsing>=1.5.5 setuptools zope.interface [docs] Sphinx>=1.0 sphinx_rtd_theme certbot-nginx-0.40.0/certbot_nginx.egg-info/dependency_links.txt0000644000175000017500000000000113560356440025161 0ustar ericaerica00000000000000 certbot-nginx-0.40.0/certbot_nginx.egg-info/entry_points.txt0000644000175000017500000000011013560356440024401 0ustar ericaerica00000000000000[certbot.plugins] nginx = certbot_nginx.configurator:NginxConfigurator certbot-nginx-0.40.0/certbot_nginx.egg-info/SOURCES.txt0000644000175000017500000000675213560356440023011 0ustar ericaerica00000000000000LICENSE.txt MANIFEST.in README.rst setup.cfg setup.py certbot_nginx/__init__.py certbot_nginx/configurator.py certbot_nginx/constants.py certbot_nginx/display_ops.py certbot_nginx/http_01.py certbot_nginx/nginxparser.py certbot_nginx/obj.py certbot_nginx/parser.py certbot_nginx/parser_obj.py certbot_nginx.egg-info/PKG-INFO certbot_nginx.egg-info/SOURCES.txt certbot_nginx.egg-info/dependency_links.txt certbot_nginx.egg-info/entry_points.txt certbot_nginx.egg-info/requires.txt certbot_nginx.egg-info/top_level.txt certbot_nginx/tests/__init__.py certbot_nginx/tests/configurator_test.py certbot_nginx/tests/display_ops_test.py certbot_nginx/tests/http_01_test.py certbot_nginx/tests/nginxparser_test.py certbot_nginx/tests/obj_test.py certbot_nginx/tests/parser_obj_test.py certbot_nginx/tests/parser_test.py certbot_nginx/tests/util.py certbot_nginx/tests/testdata/etc_nginx/broken.conf certbot_nginx/tests/testdata/etc_nginx/comment_in_file.conf certbot_nginx/tests/testdata/etc_nginx/edge_cases.conf certbot_nginx/tests/testdata/etc_nginx/foo.conf certbot_nginx/tests/testdata/etc_nginx/mime.types certbot_nginx/tests/testdata/etc_nginx/minimalistic_comments.conf certbot_nginx/tests/testdata/etc_nginx/multiline_quotes.conf certbot_nginx/tests/testdata/etc_nginx/nginx.conf certbot_nginx/tests/testdata/etc_nginx/server.conf certbot_nginx/tests/testdata/etc_nginx/sites-enabled/default certbot_nginx/tests/testdata/etc_nginx/sites-enabled/example.com certbot_nginx/tests/testdata/etc_nginx/sites-enabled/globalssl.com certbot_nginx/tests/testdata/etc_nginx/sites-enabled/headers.com certbot_nginx/tests/testdata/etc_nginx/sites-enabled/ipv6.com certbot_nginx/tests/testdata/etc_nginx/sites-enabled/ipv6ssl.com certbot_nginx/tests/testdata/etc_nginx/sites-enabled/migration.com certbot_nginx/tests/testdata/etc_nginx/sites-enabled/sslon.com certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default certbot_nginx/tls_configs/options-ssl-nginx-old.conf certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf certbot_nginx/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf certbot_nginx/tls_configs/options-ssl-nginx.conf docs/.gitignore docs/Makefile docs/api.rst docs/conf.py docs/index.rst docs/make.bat docs/_static/.gitignore docs/_templates/.gitignore docs/api/nginxparser.rst docs/api/obj.rst docs/api/parser.rstcertbot-nginx-0.40.0/MANIFEST.in0000644000175000017500000000024413560356424016314 0ustar ericaerica00000000000000include LICENSE.txt include README.rst recursive-include docs * recursive-include certbot_nginx/tests/testdata * recursive-include certbot_nginx/tls_configs *.conf certbot-nginx-0.40.0/docs/0002755000175000017500000000000013560356440015506 5ustar ericaerica00000000000000certbot-nginx-0.40.0/docs/api.rst0000644000175000017500000000013113560356424017004 0ustar ericaerica00000000000000================= API Documentation ================= .. toctree:: :glob: api/** certbot-nginx-0.40.0/docs/_templates/0002755000175000017500000000000013560356440017643 5ustar ericaerica00000000000000certbot-nginx-0.40.0/docs/_templates/.gitignore0000644000175000017500000000000013560356424021621 0ustar ericaerica00000000000000certbot-nginx-0.40.0/docs/.gitignore0000644000175000017500000000001113560356424017466 0ustar ericaerica00000000000000/_build/ certbot-nginx-0.40.0/docs/Makefile0000644000175000017500000001641513560356424017155 0ustar ericaerica00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/certbot-nginx.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/certbot-nginx.qhc" applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/certbot-nginx" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/certbot-nginx" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." certbot-nginx-0.40.0/docs/api/0002755000175000017500000000000013560356440016257 5ustar ericaerica00000000000000certbot-nginx-0.40.0/docs/api/obj.rst0000644000175000017500000000014613560356424017564 0ustar ericaerica00000000000000:mod:`certbot_nginx.obj` ---------------------------- .. automodule:: certbot_nginx.obj :members: certbot-nginx-0.40.0/docs/api/parser.rst0000644000175000017500000000015713560356424020310 0ustar ericaerica00000000000000:mod:`certbot_nginx.parser` ------------------------------- .. automodule:: certbot_nginx.parser :members: certbot-nginx-0.40.0/docs/api/nginxparser.rst0000644000175000017500000000017613560356424021355 0ustar ericaerica00000000000000:mod:`certbot_nginx.nginxparser` ------------------------------------ .. automodule:: certbot_nginx.nginxparser :members: certbot-nginx-0.40.0/docs/_static/0002755000175000017500000000000013560356440017134 5ustar ericaerica00000000000000certbot-nginx-0.40.0/docs/_static/.gitignore0000644000175000017500000000000013560356424021112 0ustar ericaerica00000000000000certbot-nginx-0.40.0/docs/index.rst0000644000175000017500000000102313560356424017343 0ustar ericaerica00000000000000.. certbot-nginx documentation master file, created by sphinx-quickstart on Sun Oct 18 13:39:39 2015. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to certbot-nginx's documentation! ============================================= Contents: .. toctree:: :maxdepth: 2 .. toctree:: :maxdepth: 1 api .. automodule:: certbot_nginx :members: Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` certbot-nginx-0.40.0/docs/conf.py0000644000175000017500000002406013560356424017007 0ustar ericaerica00000000000000# -*- coding: utf-8 -*- # # certbot-nginx documentation build configuration file, created by # sphinx-quickstart on Sun Oct 18 13:39:39 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys from certbot.compat import os import shlex here = os.path.abspath(os.path.dirname(__file__)) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath(os.path.join(here, '..'))) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', ] autodoc_member_order = 'bysource' autodoc_default_flags = ['show-inheritance', 'private-members'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'certbot-nginx' copyright = u'2014-2015, Let\'s Encrypt Project' author = u'Let\'s Encrypt Project' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0' # The full version, including alpha/beta/rc tags. release = '0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = 'en' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. default_role = 'py:obj' # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs # on_rtd is whether we are on readthedocs.org on_rtd = os.environ.get('READTHEDOCS', None) == 'True' if not on_rtd: # only import and set the theme if we're building docs locally import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # otherwise, readthedocs.org uses their theme by default, so no need to specify it # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' #html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value #html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. #html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'certbot-nginxdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', # Latex figure (float) alignment #'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'certbot-nginx.tex', u'certbot-nginx Documentation', u'Let\'s Encrypt Project', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'certbot-nginx', u'certbot-nginx Documentation', [author], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'certbot-nginx', u'certbot-nginx Documentation', author, 'certbot-nginx', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False intersphinx_mapping = { 'python': ('https://docs.python.org/', None), 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), 'certbot': ('https://certbot.eff.org/docs/', None), } certbot-nginx-0.40.0/docs/make.bat0000644000175000017500000001613213560356424017116 0ustar ericaerica00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled echo. coverage to run coverage check of the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) REM Check if sphinx-build is available and fallback to Python version if any %SPHINXBUILD% 2> nul if errorlevel 9009 goto sphinx_python goto sphinx_ok :sphinx_python set SPHINXBUILD=python -m sphinx.__init__ %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) :sphinx_ok if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\certbot-nginx.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\certbot-nginx.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "coverage" ( %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage if errorlevel 1 exit /b 1 echo. echo.Testing of coverage in the sources finished, look at the ^ results in %BUILDDIR%/coverage/python.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end certbot-nginx-0.40.0/setup.py0000644000175000017500000000507113560356425016274 0ustar ericaerica00000000000000from setuptools import setup from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys version = '0.40.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', 'certbot>=0.35.0', 'mock', 'PyOpenSSL', 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? 'setuptools', 'zope.interface', ] docs_extras = [ 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags 'sphinx_rtd_theme', ] class PyTest(TestCommand): user_options = [] def initialize_options(self): TestCommand.initialize_options(self) self.pytest_args = '' def run_tests(self): import shlex # import here, cause outside the eggs aren't loaded import pytest errno = pytest.main(shlex.split(self.pytest_args)) sys.exit(errno) setup( name='certbot-nginx', version=version, description="Nginx plugin for Certbot", url='https://github.com/letsencrypt/letsencrypt', author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', 'Topic :: System :: Networking', 'Topic :: System :: Systems Administration', 'Topic :: Utilities', ], packages=find_packages(), include_package_data=True, install_requires=install_requires, extras_require={ 'docs': docs_extras, }, entry_points={ 'certbot.plugins': [ 'nginx = certbot_nginx.configurator:NginxConfigurator', ], }, test_suite='certbot_nginx', tests_require=["pytest"], cmdclass={"test": PyTest}, ) certbot-nginx-0.40.0/certbot_nginx/0002755000175000017500000000000013560356440017423 5ustar ericaerica00000000000000certbot-nginx-0.40.0/certbot_nginx/parser_obj.py0000644000175000017500000003525313560356424022133 0ustar ericaerica00000000000000""" This file contains parsing routines and object classes to help derive meaning from raw lists of tokens from pyparsing. """ import abc import logging import six from certbot import errors from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module logger = logging.getLogger(__name__) COMMENT = " managed by Certbot" COMMENT_BLOCK = ["#", COMMENT] class Parsable(object): """ Abstract base class for "Parsable" objects whose underlying representation is a tree of lists. :param .Parsable parent: This object's parsed parent in the tree """ __metaclass__ = abc.ABCMeta def __init__(self, parent=None): self._data = [] # type: List[object] self._tabs = None self.parent = parent @classmethod def parsing_hooks(cls): """Returns object types that this class should be able to `parse` recusrively. The order of the objects indicates the order in which the parser should try to parse each subitem. :returns: A list of Parsable classes. :rtype list: """ return (Block, Sentence, Statements) @staticmethod @abc.abstractmethod def should_parse(lists): """ Returns whether the contents of `lists` can be parsed into this object. :returns: Whether `lists` can be parsed as this object. :rtype bool: """ raise NotImplementedError() @abc.abstractmethod def parse(self, raw_list, add_spaces=False): """ Loads information into this object from underlying raw_list structure. Each Parsable object might make different assumptions about the structure of raw_list. :param list raw_list: A list or sublist of tokens from pyparsing, containing whitespace as separate tokens. :param bool add_spaces: If set, the method can and should manipulate and insert spacing between non-whitespace tokens and lists to delimit them. :raises .errors.MisconfigurationError: when the assumptions about the structure of raw_list are not met. """ raise NotImplementedError() @abc.abstractmethod def iterate(self, expanded=False, match=None): """ Iterates across this object. If this object is a leaf object, only yields itself. If it contains references other parsing objects, and `expanded` is set, this function should first yield itself, then recursively iterate across all of them. :param bool expanded: Whether to recursively iterate on possible children. :param callable match: If provided, an object is only iterated if this callable returns True when called on that object. :returns: Iterator over desired objects. """ raise NotImplementedError() @abc.abstractmethod def get_tabs(self): """ Guess at the tabbing style of this parsed object, based on whitespace. If this object is a leaf, it deducts the tabbing based on its own contents. Other objects may guess by calling `get_tabs` recursively on child objects. :returns: Guess at tabbing for this object. Should only return whitespace strings that does not contain newlines. :rtype str: """ raise NotImplementedError() @abc.abstractmethod def set_tabs(self, tabs=" "): """This tries to set and alter the tabbing of the current object to a desired whitespace string. Primarily meant for objects that were constructed, so they can conform to surrounding whitespace. :param str tabs: A whitespace string (not containing newlines). """ raise NotImplementedError() def dump(self, include_spaces=False): """ Dumps back to pyparsing-like list tree. The opposite of `parse`. Note: if this object has not been modified, `dump` with `include_spaces=True` should always return the original input of `parse`. :param bool include_spaces: If set to False, magically hides whitespace tokens from dumped output. :returns: Pyparsing-like list tree. :rtype list: """ return [elem.dump(include_spaces) for elem in self._data] class Statements(Parsable): """ A group or list of "Statements". A Statement is either a Block or a Sentence. The underlying representation is simply a list of these Statement objects, with an extra `_trailing_whitespace` string to keep track of the whitespace that does not precede any more statements. """ def __init__(self, parent=None): super(Statements, self).__init__(parent) self._trailing_whitespace = None # ======== Begin overridden functions @staticmethod def should_parse(lists): return isinstance(lists, list) def set_tabs(self, tabs=" "): """ Sets the tabbing for this set of statements. Does this by calling `set_tabs` on each of the child statements. Then, if a parent is present, sets trailing whitespace to parent tabbing. This is so that the trailing } of any Block that contains Statements lines up with parent tabbing. """ for statement in self._data: statement.set_tabs(tabs) if self.parent is not None: self._trailing_whitespace = "\n" + self.parent.get_tabs() def parse(self, raw_list, add_spaces=False): """ Parses a list of statements. Expects all elements in `raw_list` to be parseable by `type(self).parsing_hooks`, with an optional whitespace string at the last index of `raw_list`. """ if not isinstance(raw_list, list): raise errors.MisconfigurationError("Statements parsing expects a list!") # If there's a trailing whitespace in the list of statements, keep track of it. if raw_list and isinstance(raw_list[-1], six.string_types) and raw_list[-1].isspace(): self._trailing_whitespace = raw_list[-1] raw_list = raw_list[:-1] self._data = [parse_raw(elem, self, add_spaces) for elem in raw_list] def get_tabs(self): """ Takes a guess at the tabbing of all contained Statements by retrieving the tabbing of the first Statement.""" if self._data: return self._data[0].get_tabs() return "" def dump(self, include_spaces=False): """ Dumps this object by first dumping each statement, then appending its trailing whitespace (if `include_spaces` is set) """ data = super(Statements, self).dump(include_spaces) if include_spaces and self._trailing_whitespace is not None: return data + [self._trailing_whitespace] return data def iterate(self, expanded=False, match=None): """ Combines each statement's iterator. """ for elem in self._data: for sub_elem in elem.iterate(expanded, match): yield sub_elem # ======== End overridden functions def _space_list(list_): """ Inserts whitespace between adjacent non-whitespace tokens. """ spaced_statement = [] # type: List[str] for i in reversed(six.moves.xrange(len(list_))): spaced_statement.insert(0, list_[i]) if i > 0 and not list_[i].isspace() and not list_[i-1].isspace(): spaced_statement.insert(0, " ") return spaced_statement class Sentence(Parsable): """ A list of words. Non-whitespace words are typically separated with whitespace tokens. """ # ======== Begin overridden functions @staticmethod def should_parse(lists): """ Returns True if `lists` can be parseable as a `Sentence`-- that is, every element is a string type. :param list lists: The raw unparsed list to check. :returns: whether this lists is parseable by `Sentence`. """ return isinstance(lists, list) and len(lists) > 0 and \ all([isinstance(elem, six.string_types) for elem in lists]) def parse(self, raw_list, add_spaces=False): """ Parses a list of string types into this object. If add_spaces is set, adds whitespace tokens between adjacent non-whitespace tokens.""" if add_spaces: raw_list = _space_list(raw_list) if not isinstance(raw_list, list) or \ any([not isinstance(elem, six.string_types) for elem in raw_list]): raise errors.MisconfigurationError("Sentence parsing expects a list of string types.") self._data = raw_list def iterate(self, expanded=False, match=None): """ Simply yields itself. """ if match is None or match(self): yield self def set_tabs(self, tabs=" "): """ Sets the tabbing on this sentence. Inserts a newline and `tabs` at the beginning of `self._data`. """ if self._data[0].isspace(): return self._data.insert(0, "\n" + tabs) def dump(self, include_spaces=False): """ Dumps this sentence. If include_spaces is set, includes whitespace tokens.""" if not include_spaces: return self.words return self._data def get_tabs(self): """ Guesses at the tabbing of this sentence. If the first element is whitespace, returns the whitespace after the rightmost newline in the string. """ first = self._data[0] if not first.isspace(): return "" rindex = first.rfind("\n") return first[rindex+1:] # ======== End overridden functions @property def words(self): """ Iterates over words, but without spaces. Like Unspaced List. """ return [word.strip("\"\'") for word in self._data if not word.isspace()] def __getitem__(self, index): return self.words[index] def __contains__(self, word): return word in self.words class Block(Parsable): """ Any sort of bloc, denoted by a block name and curly braces, like so: The parsed block: block name { content 1; content 2; } might be represented with the list [names, contents], where names = ["block", " ", "name", " "] contents = [["\n ", "content", " ", "1"], ["\n ", "content", " ", "2"], "\n"] """ def __init__(self, parent=None): super(Block, self).__init__(parent) self.names = None # type: Sentence self.contents = None # type: Block @staticmethod def should_parse(lists): """ Returns True if `lists` can be parseable as a `Block`-- that is, it's got a length of 2, the first element is a `Sentence` and the second can be a `Statements`. :param list lists: The raw unparsed list to check. :returns: whether this lists is parseable by `Block`. """ return isinstance(lists, list) and len(lists) == 2 and \ Sentence.should_parse(lists[0]) and isinstance(lists[1], list) def set_tabs(self, tabs=" "): """ Sets tabs by setting equivalent tabbing on names, then adding tabbing to contents.""" self.names.set_tabs(tabs) self.contents.set_tabs(tabs + " ") def iterate(self, expanded=False, match=None): """ Iterator over self, and if expanded is set, over its contents. """ if match is None or match(self): yield self if expanded: for elem in self.contents.iterate(expanded, match): yield elem def parse(self, raw_list, add_spaces=False): """ Parses a list that resembles a block. The assumptions that this routine makes are: 1. the first element of `raw_list` is a valid Sentence. 2. the second element of `raw_list` is a valid Statement. If add_spaces is set, we call it recursively on `names` and `contents`, and add an extra trailing space to `names` (to separate the block's opening bracket and the block name). """ if not Block.should_parse(raw_list): raise errors.MisconfigurationError("Block parsing expects a list of length 2. " "First element should be a list of string types (the bloc names), " "and second should be another list of statements (the bloc content).") self.names = Sentence(self) if add_spaces: raw_list[0].append(" ") self.names.parse(raw_list[0], add_spaces) self.contents = Statements(self) self.contents.parse(raw_list[1], add_spaces) self._data = [self.names, self.contents] def get_tabs(self): """ Guesses tabbing by retrieving tabbing guess of self.names. """ return self.names.get_tabs() def _is_comment(parsed_obj): """ Checks whether parsed_obj is a comment. :param .Parsable parsed_obj: :returns: whether parsed_obj represents a comment sentence. :rtype bool: """ if not isinstance(parsed_obj, Sentence): return False return parsed_obj.words[0] == "#" def _is_certbot_comment(parsed_obj): """ Checks whether parsed_obj is a "managed by Certbot" comment. :param .Parsable parsed_obj: :returns: whether parsed_obj is a "managed by Certbot" comment. :rtype bool: """ if not _is_comment(parsed_obj): return False if len(parsed_obj.words) != len(COMMENT_BLOCK): return False for i, word in enumerate(parsed_obj.words): if word != COMMENT_BLOCK[i]: return False return True def _certbot_comment(parent, preceding_spaces=4): """ A "Managed by Certbot" comment. :param int preceding_spaces: Number of spaces between the end of the previous statement and the comment. :returns: Sentence containing the comment. :rtype: .Sentence """ result = Sentence(parent) result.parse([" " * preceding_spaces] + COMMENT_BLOCK) return result def _choose_parser(parent, list_): """ Choose a parser from type(parent).parsing_hooks, depending on whichever hook returns True first. """ hooks = Parsable.parsing_hooks() if parent: hooks = type(parent).parsing_hooks() for type_ in hooks: if type_.should_parse(list_): return type_(parent) raise errors.MisconfigurationError( "None of the parsing hooks succeeded, so we don't know how to parse this set of lists.") def parse_raw(lists_, parent=None, add_spaces=False): """ Primary parsing factory function. :param list lists_: raw lists from pyparsing to parse. :param .Parent parent: The parent containing this object. :param bool add_spaces: Whether to pass add_spaces to the parser. :returns .Parsable: The parsed object. :raises errors.MisconfigurationError: If no parsing hook passes, and we can't determine which type to parse the raw lists into. """ parser = _choose_parser(parent, lists_) parser.parse(lists_, add_spaces) return parser certbot-nginx-0.40.0/certbot_nginx/tls_configs/0002755000175000017500000000000013560356440021735 5ustar ericaerica00000000000000certbot-nginx-0.40.0/certbot_nginx/tls_configs/options-ssl-nginx-old.conf0000644000175000017500000000130513560356424026772 0ustar ericaerica00000000000000# 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. ssl_session_cache shared:le_nginx_SSL:10m; 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"; certbot-nginx-0.40.0/certbot_nginx/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf0000644000175000017500000000131513560356424032060 0ustar ericaerica00000000000000# 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. ssl_session_cache shared:le_nginx_SSL:10m; 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"; certbot-nginx-0.40.0/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf0000644000175000017500000000133613560356424030144 0ustar ericaerica00000000000000# 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. ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; 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"; certbot-nginx-0.40.0/certbot_nginx/tls_configs/options-ssl-nginx.conf0000644000175000017500000000134613560356424026223 0ustar ericaerica00000000000000# 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. ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; 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"; certbot-nginx-0.40.0/certbot_nginx/configurator.py0000644000175000017500000014001513560356424022500 0ustar ericaerica00000000000000"""Nginx Configuration""" # https://github.com/PyCQA/pylint/issues/73 from distutils.version import LooseVersion # pylint: disable=no-name-in-module,import-error import logging import re import socket import subprocess import tempfile import time import pkg_resources import OpenSSL import zope.interface from acme import challenges from acme import crypto_util as acme_crypto_util from acme.magic_typing import List, Dict, Set # pylint: disable=unused-import, no-name-in-module from certbot import constants as core_constants from certbot import crypto_util from certbot import errors from certbot import interfaces from certbot import util from certbot.compat import os from certbot.plugins import common from certbot_nginx import constants from certbot_nginx import display_ops from certbot_nginx import http_01 from certbot_nginx import nginxparser from certbot_nginx import obj # pylint: disable=unused-import from certbot_nginx import parser NAME_RANK = 0 START_WILDCARD_RANK = 1 END_WILDCARD_RANK = 2 REGEX_RANK = 3 NO_SSL_MODIFIER = 4 logger = logging.getLogger(__name__) @zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller) @zope.interface.provider(interfaces.IPluginFactory) class NginxConfigurator(common.Installer): # pylint: disable=too-many-instance-attributes,too-many-public-methods """Nginx configurator. .. todo:: Add proper support for comments in the config. Currently, config files modified by the configurator will lose all their comments. :ivar config: Configuration. :type config: :class:`~certbot.interfaces.IConfig` :ivar parser: Handles low level parsing :type parser: :class:`~certbot_nginx.parser` :ivar str save_notes: Human-readable config change notes :ivar reverter: saves and reverts checkpoints :type reverter: :class:`certbot.reverter.Reverter` :ivar tup version: version of Nginx """ description = "Nginx Web Server plugin" DEFAULT_LISTEN_PORT = '80' # SSL directives that Certbot can add when installing a new certificate. SSL_DIRECTIVES = ['ssl_certificate', 'ssl_certificate_key', 'ssl_dhparam'] @classmethod def add_parser_arguments(cls, add): default_server_root = _determine_default_server_root() add("server-root", default=constants.CLI_DEFAULTS["server_root"], help="Nginx server root directory. (default: %s)" % default_server_root) add("ctl", default=constants.CLI_DEFAULTS["ctl"], help="Path to the " "'nginx' binary, used for 'configtest' and retrieving nginx " "version number.") @property def nginx_conf(self): """Nginx config file path.""" return os.path.join(self.conf("server_root"), "nginx.conf") def __init__(self, *args, **kwargs): """Initialize an Nginx Configurator. :param tup version: version of Nginx as a tuple (1, 4, 7) (used mostly for unittesting) :param tup openssl_version: version of OpenSSL linked to Nginx as a tuple (1, 4, 7) (used mostly for unittesting) """ version = kwargs.pop("version", None) openssl_version = kwargs.pop("openssl_version", None) super(NginxConfigurator, self).__init__(*args, **kwargs) # Verify that all directories and files exist with proper permissions self._verify_setup() # Files to save self.save_notes = "" # For creating new vhosts if no names match self.new_vhost = None # List of vhosts configured per wildcard domain on this run. # used by deploy_cert() and enhance() self._wildcard_vhosts = {} # type: Dict[str, List[obj.VirtualHost]] self._wildcard_redirect_vhosts = {} # type: Dict[str, List[obj.VirtualHost]] # Add number of outstanding challenges self._chall_out = 0 # These will be set in the prepare function self.parser = None self.version = version self.openssl_version = openssl_version self._enhance_func = {"redirect": self._enable_redirect, "ensure-http-header": self._set_http_header, "staple-ocsp": self._enable_ocsp_stapling} self.reverter.recovery_routine() @property def mod_ssl_conf_src(self): """Full absolute path to SSL configuration file source.""" # Why all this complexity? Well, we want to support Mozilla's intermediate # recommendations. But TLS1.3 is only supported by newer versions of Nginx. # And as for session tickets, our ideal is to turn them off across the board. # But! Turning them off at all is only supported with new enough versions of # Nginx. And older versions of OpenSSL have a bug that leads to browser errors # given certain configurations. While we'd prefer to have forward secrecy, we'd # rather fail open than error out. Unfortunately, Nginx can be compiled against # many versions of OpenSSL. So we have to check both for the two different features, # leading to four different combinations of options. # For a complete history, check out https://github.com/certbot/certbot/issues/7322 use_tls13 = self.version >= (1, 13, 0) session_tix_off = self.version >= (1, 5, 9) and self.openssl_version and\ LooseVersion(self.openssl_version) >= LooseVersion('1.0.2l') if use_tls13: if session_tix_off: config_filename = "options-ssl-nginx.conf" else: config_filename = "options-ssl-nginx-tls13-session-tix-on.conf" else: if session_tix_off: config_filename = "options-ssl-nginx-tls12-only.conf" else: config_filename = "options-ssl-nginx-old.conf" return pkg_resources.resource_filename( "certbot_nginx", os.path.join("tls_configs", config_filename)) @property def mod_ssl_conf(self): """Full absolute path to SSL configuration file.""" return os.path.join(self.config.config_dir, constants.MOD_SSL_CONF_DEST) @property def updated_mod_ssl_conf_digest(self): """Full absolute path to digest of updated SSL configuration file.""" return os.path.join(self.config.config_dir, constants.UPDATED_MOD_SSL_CONF_DIGEST) def install_ssl_options_conf(self, options_ssl, options_ssl_digest): """Copy Certbot's SSL options file into the system's config dir if required.""" return common.install_version_controlled_file(options_ssl, options_ssl_digest, self.mod_ssl_conf_src, constants.ALL_SSL_OPTIONS_HASHES) # This is called in determine_authenticator and determine_installer def prepare(self): """Prepare the authenticator/installer. :raises .errors.NoInstallationError: If Nginx ctl cannot be found :raises .errors.MisconfigurationError: If Nginx is misconfigured """ # Verify Nginx is installed if not util.exe_exists(self.conf('ctl')): raise errors.NoInstallationError( "Could not find a usable 'nginx' binary. Ensure nginx exists, " "the binary is executable, and your PATH is set correctly.") # Make sure configuration is valid self.config_test() self.parser = parser.NginxParser(self.conf('server-root')) # Set Version if self.version is None: self.version = self.get_version() if self.openssl_version is None: self.openssl_version = self._get_openssl_version() self.install_ssl_options_conf(self.mod_ssl_conf, self.updated_mod_ssl_conf_digest) self.install_ssl_dhparams() # Prevent two Nginx plugins from modifying a config at once try: util.lock_dir_until_exit(self.conf('server-root')) except (OSError, errors.LockError): logger.debug('Encountered error:', exc_info=True) raise errors.PluginError('Unable to lock {0}'.format(self.conf('server-root'))) # Entry point in main.py for installing cert def deploy_cert(self, domain, cert_path, key_path, chain_path=None, fullchain_path=None): """Deploys certificate to specified virtual host. .. note:: Aborts if the vhost is missing ssl_certificate or ssl_certificate_key. .. note:: This doesn't save the config files! :raises errors.PluginError: When unable to deploy certificate due to a lack of directives or configuration """ if not fullchain_path: raise errors.PluginError( "The nginx plugin currently requires --fullchain-path to " "install a cert.") vhosts = self.choose_vhosts(domain, create_if_no_match=True) for vhost in vhosts: self._deploy_cert(vhost, cert_path, key_path, chain_path, fullchain_path) def _deploy_cert(self, vhost, cert_path, key_path, chain_path, fullchain_path): # pylint: disable=unused-argument """ Helper function for deploy_cert() that handles the actual deployment this exists because we might want to do multiple deployments per domain originally passed for deploy_cert(). This is especially true with wildcard certificates """ cert_directives = [['\n ', 'ssl_certificate', ' ', fullchain_path], ['\n ', 'ssl_certificate_key', ' ', key_path]] self.parser.update_or_add_server_directives(vhost, cert_directives) logger.info("Deploying Certificate to VirtualHost %s", vhost.filep) self.save_notes += ("Changed vhost at %s with addresses of %s\n" % (vhost.filep, ", ".join(str(addr) for addr in vhost.addrs))) self.save_notes += "\tssl_certificate %s\n" % fullchain_path self.save_notes += "\tssl_certificate_key %s\n" % key_path def _choose_vhosts_wildcard(self, domain, prefer_ssl, no_ssl_filter_port=None): """Prompts user to choose vhosts to install a wildcard certificate for""" if prefer_ssl: vhosts_cache = self._wildcard_vhosts preference_test = lambda x: x.ssl else: vhosts_cache = self._wildcard_redirect_vhosts preference_test = lambda x: not x.ssl # Caching! if domain in vhosts_cache: # Vhosts for a wildcard domain were already selected return vhosts_cache[domain] # Get all vhosts whether or not they are covered by the wildcard domain vhosts = self.parser.get_vhosts() # Go through the vhosts, making sure that we cover all the names # present, but preferring the SSL or non-SSL vhosts filtered_vhosts = {} for vhost in vhosts: # Ensure we're listening non-sslishly on no_ssl_filter_port if no_ssl_filter_port is not None: if not self._vhost_listening_on_port_no_ssl(vhost, no_ssl_filter_port): continue for name in vhost.names: if preference_test(vhost): # Prefer either SSL or non-SSL vhosts filtered_vhosts[name] = vhost elif name not in filtered_vhosts: # Add if not in list previously filtered_vhosts[name] = vhost # Only unique VHost objects dialog_input = set([vhost for vhost in filtered_vhosts.values()]) # Ask the user which of names to enable, expect list of names back return_vhosts = display_ops.select_vhost_multiple(list(dialog_input)) for vhost in return_vhosts: if domain not in vhosts_cache: vhosts_cache[domain] = [] vhosts_cache[domain].append(vhost) return return_vhosts ####################### # Vhost parsing methods ####################### def _choose_vhost_single(self, target_name): matches = self._get_ranked_matches(target_name) vhosts = [x for x in [self._select_best_name_match(matches)] if x is not None] return vhosts def choose_vhosts(self, target_name, create_if_no_match=False): """Chooses a virtual host based on the given domain name. .. note:: This makes the vhost SSL-enabled if it isn't already. Follows Nginx's server block selection rules preferring blocks that are already SSL. .. 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 MisconfigurationError. :returns: ssl vhosts associated with name :rtype: list of :class:`~certbot_nginx.obj.VirtualHost` """ if util.is_wildcard_domain(target_name): # Ask user which VHosts to support. vhosts = self._choose_vhosts_wildcard(target_name, prefer_ssl=True) else: vhosts = self._choose_vhost_single(target_name) if not vhosts: if create_if_no_match: # result will not be [None] because it errors on failure vhosts = [self._vhost_from_duplicated_default(target_name, True, str(self.config.https_port))] else: # No matches. Raise a misconfiguration error. raise errors.MisconfigurationError( ("Cannot find a VirtualHost matching domain %s. " "In order for Certbot to correctly perform the challenge " "please add a corresponding server_name directive to your " "nginx configuration for every domain on your certificate: " "https://nginx.org/en/docs/http/server_names.html") % (target_name)) # Note: if we are enhancing with ocsp, vhost should already be ssl. for vhost in vhosts: if not vhost.ssl: self._make_server_ssl(vhost) return vhosts def ipv6_info(self, port): """Returns tuple of booleans (ipv6_active, ipv6only_present) ipv6_active is true if any server block listens ipv6 address in any port ipv6only_present is true if ipv6only=on option exists in any server block ipv6 listen directive for the specified port. :param str port: Port to check ipv6only=on directive for :returns: Tuple containing information if IPv6 is enabled in the global configuration, and existence of ipv6only directive for specified port :rtype: tuple of type (bool, bool) """ # port should be a string, but it's easy to mess up, so let's # make sure it is one port = str(port) vhosts = self.parser.get_vhosts() ipv6_active = False ipv6only_present = False for vh in vhosts: for addr in vh.addrs: if addr.ipv6: ipv6_active = True if addr.ipv6only and addr.get_port() == port: ipv6only_present = True return (ipv6_active, ipv6only_present) def _vhost_from_duplicated_default(self, domain, allow_port_mismatch, port): """if allow_port_mismatch is False, only server blocks with matching ports will be used as a default server block template. """ if self.new_vhost is None: default_vhost = self._get_default_vhost(domain, allow_port_mismatch, port) self.new_vhost = self.parser.duplicate_vhost(default_vhost, remove_singleton_listen_params=True) self.new_vhost.names = set() self._add_server_name_to_vhost(self.new_vhost, domain) return self.new_vhost def _add_server_name_to_vhost(self, vhost, domain): vhost.names.add(domain) name_block = [['\n ', 'server_name']] for name in vhost.names: name_block[0].append(' ') name_block[0].append(name) self.parser.update_or_add_server_directives(vhost, name_block) def _get_default_vhost(self, domain, allow_port_mismatch, port): """Helper method for _vhost_from_duplicated_default; see argument documentation there""" vhost_list = self.parser.get_vhosts() # if one has default_server set, return that one all_default_vhosts = [] port_matching_vhosts = [] for vhost in vhost_list: for addr in vhost.addrs: if addr.default: all_default_vhosts.append(vhost) if self._port_matches(port, addr.get_port()): port_matching_vhosts.append(vhost) break if len(port_matching_vhosts) == 1: return port_matching_vhosts[0] elif len(all_default_vhosts) == 1 and allow_port_mismatch: return all_default_vhosts[0] # TODO: present a list of vhosts for user to choose from raise errors.MisconfigurationError("Could not automatically find a matching server" " block for %s. Set the `server_name` directive to use the Nginx installer." % domain) def _get_ranked_matches(self, target_name): """Returns a ranked list of vhosts that match target_name. The ranking gives preference to SSL vhosts. :param str target_name: The name to match :returns: list of dicts containing the vhost, the matching name, and the numerical rank :rtype: list """ vhost_list = self.parser.get_vhosts() return self._rank_matches_by_name_and_ssl(vhost_list, target_name) def _select_best_name_match(self, matches): """Returns the best name match of a ranked list of vhosts. :param list matches: list of dicts containing the vhost, the matching name, and the numerical rank :returns: the most matching vhost :rtype: :class:`~certbot_nginx.obj.VirtualHost` """ if not matches: return None elif matches[0]['rank'] in [START_WILDCARD_RANK, END_WILDCARD_RANK, START_WILDCARD_RANK + NO_SSL_MODIFIER, END_WILDCARD_RANK + NO_SSL_MODIFIER]: # Wildcard match - need to find the longest one rank = matches[0]['rank'] wildcards = [x for x in matches if x['rank'] == rank] return max(wildcards, key=lambda x: len(x['name']))['vhost'] # Exact or regex match return matches[0]['vhost'] def _rank_matches_by_name(self, vhost_list, target_name): """Returns a ranked list of vhosts from vhost_list that match target_name. This method should always be followed by a call to _select_best_name_match. :param list vhost_list: list of vhosts to filter and rank :param str target_name: The name to match :returns: list of dicts containing the vhost, the matching name, and the numerical rank :rtype: list """ # Nginx chooses a matching server name for a request with precedence: # 1. exact name match # 2. longest wildcard name starting with * # 3. longest wildcard name ending with * # 4. first matching regex in order of appearance in the file matches = [] for vhost in vhost_list: name_type, name = parser.get_best_match(target_name, vhost.names) if name_type == 'exact': matches.append({'vhost': vhost, 'name': name, 'rank': NAME_RANK}) elif name_type == 'wildcard_start': matches.append({'vhost': vhost, 'name': name, 'rank': START_WILDCARD_RANK}) elif name_type == 'wildcard_end': matches.append({'vhost': vhost, 'name': name, 'rank': END_WILDCARD_RANK}) elif name_type == 'regex': matches.append({'vhost': vhost, 'name': name, 'rank': REGEX_RANK}) return sorted(matches, key=lambda x: x['rank']) def _rank_matches_by_name_and_ssl(self, vhost_list, target_name): """Returns a ranked list of vhosts from vhost_list that match target_name. The ranking gives preference to SSLishness before name match level. :param list vhost_list: list of vhosts to filter and rank :param str target_name: The name to match :returns: list of dicts containing the vhost, the matching name, and the numerical rank :rtype: list """ matches = self._rank_matches_by_name(vhost_list, target_name) for match in matches: if not match['vhost'].ssl: match['rank'] += NO_SSL_MODIFIER return sorted(matches, key=lambda x: x['rank']) def choose_redirect_vhosts(self, target_name, port, create_if_no_match=False): """Chooses a single virtual host for redirect enhancement. Chooses the vhost most closely matching target_name that is listening to port without using ssl. .. 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 str port: port number :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 MisconfigurationError. :returns: vhosts associated with name :rtype: list of :class:`~certbot_nginx.obj.VirtualHost` """ if util.is_wildcard_domain(target_name): # Ask user which VHosts to enhance. vhosts = self._choose_vhosts_wildcard(target_name, prefer_ssl=False, no_ssl_filter_port=port) else: matches = self._get_redirect_ranked_matches(target_name, port) vhosts = [x for x in [self._select_best_name_match(matches)]if x is not None] if not vhosts and create_if_no_match: vhosts = [self._vhost_from_duplicated_default(target_name, False, port)] return vhosts def _port_matches(self, test_port, matching_port): # test_port is a number, matching is a number or "" or None if matching_port == "" or matching_port is None: # if no port is specified, Nginx defaults to listening on port 80. return test_port == self.DEFAULT_LISTEN_PORT return test_port == matching_port def _vhost_listening_on_port_no_ssl(self, vhost, port): found_matching_port = False if not vhost.addrs: # if there are no listen directives at all, Nginx defaults to # listening on port 80. found_matching_port = (port == self.DEFAULT_LISTEN_PORT) else: for addr in vhost.addrs: if self._port_matches(port, addr.get_port()) and not addr.ssl: found_matching_port = True if found_matching_port: # make sure we don't have an 'ssl on' directive return not self.parser.has_ssl_on_directive(vhost) return False def _get_redirect_ranked_matches(self, target_name, port): """Gets a ranked list of plaintextish port-listening vhosts matching target_name Filter all hosts for those listening on port without using ssl. Rank by how well these match target_name. :param str target_name: The name to match :param str port: port number as a string :returns: list of dicts containing the vhost, the matching name, and the numerical rank :rtype: list """ all_vhosts = self.parser.get_vhosts() def _vhost_matches(vhost, port): return self._vhost_listening_on_port_no_ssl(vhost, port) matching_vhosts = [vhost for vhost in all_vhosts if _vhost_matches(vhost, port)] return self._rank_matches_by_name(matching_vhosts, target_name) def get_all_names(self): """Returns all names found in the Nginx Configuration. :returns: All ServerNames, ServerAliases, and reverse DNS entries for virtual host addresses :rtype: set """ all_names = set() # type: Set[str] for vhost in self.parser.get_vhosts(): all_names.update(vhost.names) for addr in vhost.addrs: host = addr.get_addr() if common.hostname_regex.match(host): # If it's a hostname, add it to the names. all_names.add(host) elif not common.private_ips_regex.match(host): # If it isn't a private IP, do a reverse DNS lookup try: if addr.ipv6: host = addr.get_ipv6_exploded() socket.inet_pton(socket.AF_INET6, host) else: socket.inet_pton(socket.AF_INET, host) all_names.add(socket.gethostbyaddr(host)[0]) except (socket.error, socket.herror, socket.timeout): continue return util.get_filtered_names(all_names) def _get_snakeoil_paths(self): """Generate invalid certs that let us create ssl directives for Nginx""" # TODO: generate only once tmp_dir = os.path.join(self.config.work_dir, "snakeoil") le_key = crypto_util.init_save_key( key_size=1024, key_dir=tmp_dir, keyname="key.pem") key = OpenSSL.crypto.load_privatekey( OpenSSL.crypto.FILETYPE_PEM, le_key.pem) cert = acme_crypto_util.gen_ss_cert(key, domains=[socket.gethostname()]) cert_pem = OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, cert) cert_file, cert_path = util.unique_file( os.path.join(tmp_dir, "cert.pem"), mode="wb") with cert_file: cert_file.write(cert_pem) return cert_path, le_key.file def _make_server_ssl(self, vhost): """Make a server SSL. Make a server SSL by adding new listen and SSL directives. :param vhost: The vhost to add SSL to. :type vhost: :class:`~certbot_nginx.obj.VirtualHost` """ https_port = self.config.https_port ipv6info = self.ipv6_info(https_port) ipv6_block = [''] ipv4_block = [''] # If the vhost was implicitly listening on the default Nginx port, # have it continue to do so. if not vhost.addrs: listen_block = [['\n ', 'listen', ' ', self.DEFAULT_LISTEN_PORT]] self.parser.add_server_directives(vhost, listen_block) if vhost.ipv6_enabled(): ipv6_block = ['\n ', 'listen', ' ', '[::]:{0}'.format(https_port), ' ', 'ssl'] if not ipv6info[1]: # ipv6only=on is absent in global config ipv6_block.append(' ') ipv6_block.append('ipv6only=on') if vhost.ipv4_enabled(): ipv4_block = ['\n ', 'listen', ' ', '{0}'.format(https_port), ' ', 'ssl'] snakeoil_cert, snakeoil_key = self._get_snakeoil_paths() ssl_block = ([ ipv6_block, ipv4_block, ['\n ', 'ssl_certificate', ' ', snakeoil_cert], ['\n ', 'ssl_certificate_key', ' ', snakeoil_key], ['\n ', 'include', ' ', self.mod_ssl_conf], ['\n ', 'ssl_dhparam', ' ', self.ssl_dhparams], ]) self.parser.add_server_directives( vhost, ssl_block) ################################## # enhancement methods (IInstaller) ################################## def supported_enhancements(self): # pylint: disable=no-self-use """Returns currently supported enhancements.""" return ['redirect', 'ensure-http-header', 'staple-ocsp'] def enhance(self, domain, enhancement, options=None): """Enhance configuration. :param str domain: domain to enhance :param str enhancement: enhancement type defined in :const:`~certbot.constants.ENHANCEMENTS` :param options: options for the enhancement See :const:`~certbot.constants.ENHANCEMENTS` documentation for appropriate parameter. """ try: return self._enhance_func[enhancement](domain, options) except (KeyError, ValueError): raise errors.PluginError( "Unsupported enhancement: {0}".format(enhancement)) except errors.PluginError: logger.warning("Failed %s for %s", enhancement, domain) raise def _has_certbot_redirect(self, vhost, domain): test_redirect_block = _test_block_from_block(_redirect_block_for_domain(domain)) return vhost.contains_list(test_redirect_block) def _set_http_header(self, domain, header_substring): """Enables header identified by header_substring on domain. If the vhost is listening plaintextishly, separates out the relevant directives into a new server block, and only add header directive to HTTPS block. :param str domain: the domain to enable header for. :param str header_substring: String to uniquely identify a header. e.g. Strict-Transport-Security, Upgrade-Insecure-Requests :returns: Success :raises .errors.PluginError: If no viable HTTPS host can be created or set with header header_substring. """ vhosts = self.choose_vhosts(domain) if not vhosts: raise errors.PluginError( "Unable to find corresponding HTTPS host for enhancement.") for vhost in vhosts: if vhost.has_header(header_substring): raise errors.PluginEnhancementAlreadyPresent( "Existing %s header" % (header_substring)) # if there is no separate SSL block, break the block into two and # choose the SSL block. if vhost.ssl and any([not addr.ssl for addr in vhost.addrs]): _, vhost = self._split_block(vhost) header_directives = [ ['\n ', 'add_header', ' ', header_substring, ' '] + constants.HEADER_ARGS[header_substring], ['\n']] self.parser.add_server_directives(vhost, header_directives) def _add_redirect_block(self, vhost, domain): """Add redirect directive to vhost """ redirect_block = _redirect_block_for_domain(domain) self.parser.add_server_directives( vhost, redirect_block, insert_at_top=True) def _split_block(self, vhost, only_directives=None): """Splits this "virtual host" (i.e. this nginx server block) into separate HTTP and HTTPS blocks. :param vhost: The server block to break up into two. :param list only_directives: If this exists, only duplicate these directives when splitting the block. :type vhost: :class:`~certbot_nginx.obj.VirtualHost` :returns: tuple (http_vhost, https_vhost) :rtype: tuple of type :class:`~certbot_nginx.obj.VirtualHost` """ http_vhost = self.parser.duplicate_vhost(vhost, only_directives=only_directives) def _ssl_match_func(directive): return 'ssl' in directive def _ssl_config_match_func(directive): return self.mod_ssl_conf in directive def _no_ssl_match_func(directive): return 'ssl' not in directive # remove all ssl addresses and related directives from the new block for directive in self.SSL_DIRECTIVES: self.parser.remove_server_directives(http_vhost, directive) self.parser.remove_server_directives(http_vhost, 'listen', match_func=_ssl_match_func) self.parser.remove_server_directives(http_vhost, 'include', match_func=_ssl_config_match_func) # remove all non-ssl addresses from the existing block self.parser.remove_server_directives(vhost, 'listen', match_func=_no_ssl_match_func) return http_vhost, vhost def _enable_redirect(self, domain, unused_options): """Redirect all equivalent HTTP traffic to ssl_vhost. If the vhost is listening plaintextishly, separate out the relevant directives into a new server block and add a rewrite directive. .. note:: This function saves the configuration :param str domain: domain to enable redirect for :param unused_options: Not currently used :type unused_options: Not Available """ port = self.DEFAULT_LISTEN_PORT # If there are blocks listening plaintextishly on self.DEFAULT_LISTEN_PORT, # choose the most name-matching one. vhosts = self.choose_redirect_vhosts(domain, port) if not vhosts: logger.info("No matching insecure server blocks listening on port %s found.", self.DEFAULT_LISTEN_PORT) return for vhost in vhosts: self._enable_redirect_single(domain, vhost) def _enable_redirect_single(self, domain, vhost): """Redirect all equivalent HTTP traffic to ssl_vhost. If the vhost is listening plaintextishly, separate out the relevant directives into a new server block and add a rewrite directive. .. note:: This function saves the configuration :param str domain: domain to enable redirect for :param `~obj.Vhost` vhost: vhost to enable redirect for """ if vhost.ssl: http_vhost, _ = self._split_block(vhost, ['listen', 'server_name']) # Add this at the bottom to get the right order of directives return_404_directive = [['\n ', 'return', ' ', '404']] self.parser.add_server_directives(http_vhost, return_404_directive) vhost = http_vhost if self._has_certbot_redirect(vhost, domain): logger.info("Traffic on port %s already redirecting to ssl in %s", self.DEFAULT_LISTEN_PORT, vhost.filep) else: # Redirect plaintextish host to https self._add_redirect_block(vhost, domain) logger.info("Redirecting all traffic on port %s to ssl in %s", self.DEFAULT_LISTEN_PORT, vhost.filep) def _enable_ocsp_stapling(self, domain, chain_path): """Include OCSP response in TLS handshake :param str domain: domain to enable OCSP response for :param chain_path: chain file path :type chain_path: `str` or `None` """ vhosts = self.choose_vhosts(domain) for vhost in vhosts: self._enable_ocsp_stapling_single(vhost, chain_path) def _enable_ocsp_stapling_single(self, vhost, chain_path): """Include OCSP response in TLS handshake :param str vhost: vhost to enable OCSP response for :param chain_path: chain file path :type chain_path: `str` or `None` """ if self.version < (1, 3, 7): raise errors.PluginError("Version 1.3.7 or greater of nginx " "is needed to enable OCSP stapling") if chain_path is None: raise errors.PluginError( "--chain-path is required to enable " "Online Certificate Status Protocol (OCSP) stapling " "on nginx >= 1.3.7.") stapling_directives = [ ['\n ', 'ssl_trusted_certificate', ' ', chain_path], ['\n ', 'ssl_stapling', ' ', 'on'], ['\n ', 'ssl_stapling_verify', ' ', 'on'], ['\n']] try: self.parser.add_server_directives(vhost, stapling_directives) except errors.MisconfigurationError as error: logger.debug(str(error)) raise errors.PluginError("An error occurred while enabling OCSP " "stapling for {0}.".format(vhost.names)) self.save_notes += ("OCSP Stapling was enabled " "on SSL Vhost: {0}.\n".format(vhost.filep)) self.save_notes += "\tssl_trusted_certificate {0}\n".format(chain_path) self.save_notes += "\tssl_stapling on\n" self.save_notes += "\tssl_stapling_verify on\n" ###################################### # Nginx server management (IInstaller) ###################################### def restart(self): """Restarts nginx server. :raises .errors.MisconfigurationError: If either the reload fails. """ nginx_restart(self.conf('ctl'), self.nginx_conf) def config_test(self): # pylint: disable=no-self-use """Check the configuration of Nginx for errors. :raises .errors.MisconfigurationError: If config_test fails """ try: util.run_script([self.conf('ctl'), "-c", self.nginx_conf, "-t"]) except errors.SubprocessError as err: raise errors.MisconfigurationError(str(err)) def _verify_setup(self): """Verify the setup to ensure safe operating environment. Make sure that files/directories are setup with appropriate permissions Aim for defensive coding... make sure all input files have permissions of root. """ util.make_or_verify_dir(self.config.work_dir, core_constants.CONFIG_DIRS_MODE) util.make_or_verify_dir(self.config.backup_dir, core_constants.CONFIG_DIRS_MODE) util.make_or_verify_dir(self.config.config_dir, core_constants.CONFIG_DIRS_MODE) def _nginx_version(self): """Return results of nginx -V :returns: version text :rtype: str :raises .PluginError: Unable to run Nginx version command """ try: proc = subprocess.Popen( [self.conf('ctl'), "-c", self.nginx_conf, "-V"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) text = proc.communicate()[1] # nginx prints output to stderr except (OSError, ValueError) as error: logger.debug(str(error), exc_info=True) raise errors.PluginError( "Unable to run %s -V" % self.conf('ctl')) return text def get_version(self): """Return version of Nginx Server. Version is returned as tuple. (ie. 2.4.7 = (2, 4, 7)) :returns: version :rtype: tuple :raises .PluginError: Unable to find Nginx version or version is unsupported """ text = self._nginx_version() version_regex = re.compile(r"nginx version: ([^/]+)/([0-9\.]*)", re.IGNORECASE) version_matches = version_regex.findall(text) sni_regex = re.compile(r"TLS SNI support enabled", re.IGNORECASE) sni_matches = sni_regex.findall(text) ssl_regex = re.compile(r" --with-http_ssl_module") ssl_matches = ssl_regex.findall(text) if not version_matches: raise errors.PluginError("Unable to find Nginx version") if not ssl_matches: raise errors.PluginError( "Nginx build is missing SSL module (--with-http_ssl_module).") if not sni_matches: raise errors.PluginError("Nginx build doesn't support SNI") product_name, product_version = version_matches[0] if product_name != 'nginx': logger.warning("NGINX derivative %s is not officially supported by" " certbot", product_name) nginx_version = tuple([int(i) for i in product_version.split(".")]) # nginx < 0.8.48 uses machine hostname as default server_name instead of # the empty string if nginx_version < (0, 8, 48): raise errors.NotSupportedError("Nginx version must be 0.8.48+") return nginx_version def _get_openssl_version(self): """Return version of OpenSSL linked to Nginx. Version is returned as string. If no version can be found, empty string is returned. :returns: openssl_version :rtype: str :raises .PluginError: Unable to run Nginx version command """ text = self._nginx_version() matches = re.findall(r"running with OpenSSL ([^ ]+) ", text) if not matches: matches = re.findall(r"built with OpenSSL ([^ ]+) ", text) if not matches: logger.warning("NGINX configured with OpenSSL alternatives is not officially" "supported by Certbot.") return "" return matches[0] def more_info(self): """Human-readable string to help understand the module""" return ( "Configures Nginx to authenticate and install HTTPS.{0}" "Server root: {root}{0}" "Version: {version}".format( os.linesep, root=self.parser.config_root, version=".".join(str(i) for i in self.version)) ) ################################################### # Wrapper functions for Reverter class (IInstaller) ################################################### def save(self, title=None, temporary=False): """Saves all changes to the configuration files. :param str title: The title of the save. If a title is given, the configuration will be saved as a new checkpoint and put in a timestamped directory. :param bool temporary: Indicates whether the changes made will be quickly reversed in the future (ie. challenges) :raises .errors.PluginError: If there was an error in an attempt to save the configuration, or an error creating a checkpoint """ save_files = set(self.parser.parsed.keys()) self.add_to_checkpoint(save_files, self.save_notes, temporary) self.save_notes = "" # Change 'ext' to something else to not override existing conf files self.parser.filedump(ext='') if title and not temporary: self.finalize_checkpoint(title) def recovery_routine(self): """Revert all previously modified files. Reverts all modified files that have not been saved as a checkpoint :raises .errors.PluginError: If unable to recover the configuration """ super(NginxConfigurator, self).recovery_routine() self.new_vhost = None self.parser.load() def revert_challenge_config(self): """Used to cleanup challenge configurations. :raises .errors.PluginError: If unable to revert the challenge config. """ self.revert_temporary_config() self.new_vhost = None self.parser.load() def rollback_checkpoints(self, rollback=1): """Rollback saved checkpoints. :param int rollback: Number of checkpoints to revert :raises .errors.PluginError: If there is a problem with the input or the function is unable to correctly revert the configuration """ super(NginxConfigurator, self).rollback_checkpoints(rollback) self.new_vhost = None self.parser.load() ########################################################################### # Challenges Section for IAuthenticator ########################################################################### def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use """Return list of challenge preferences.""" return [challenges.HTTP01] # Entry point in main.py for performing challenges def perform(self, achalls): """Perform the configuration related challenge. This function currently assumes all challenges will be fulfilled. If this turns out not to be the case in the future. Cleanup and outstanding challenges will have to be designed better. """ self._chall_out += len(achalls) responses = [None] * len(achalls) http_doer = http_01.NginxHttp01(self) for i, achall in enumerate(achalls): # Currently also have chall_doer hold associated index of the # challenge. This helps to put all of the responses back together # when they are all complete. http_doer.add_chall(achall, i) http_response = http_doer.perform() # Must restart in order to activate the challenges. # Handled here because we may be able to load up other challenge types self.restart() # Go through all of the challenges and assign them to the proper place # in the responses return value. All responses must be in the same order # as the original challenges. for i, resp in enumerate(http_response): responses[http_doer.indices[i]] = resp return responses # called after challenges are performed def cleanup(self, achalls): """Revert all challenges.""" self._chall_out -= len(achalls) # If all of the challenges have been finished, clean up everything if self._chall_out <= 0: self.revert_challenge_config() self.restart() def _test_block_from_block(block): test_block = nginxparser.UnspacedList(block) parser.comment_directive(test_block, 0) return test_block[:-1] def _redirect_block_for_domain(domain): updated_domain = domain match_symbol = '=' if util.is_wildcard_domain(domain): match_symbol = '~' updated_domain = updated_domain.replace('.', r'\.') updated_domain = updated_domain.replace('*', '[^.]+') updated_domain = '^' + updated_domain + '$' redirect_block = [[ ['\n ', 'if', ' ', '($host', ' ', match_symbol, ' ', '%s)' % updated_domain, ' '], [['\n ', 'return', ' ', '301', ' ', 'https://$host$request_uri'], '\n ']], ['\n']] return redirect_block def nginx_restart(nginx_ctl, nginx_conf): """Restarts the Nginx Server. .. todo:: Nginx restart is fatal if the configuration references non-existent SSL cert/key files. Remove references to /etc/letsencrypt before restart. :param str nginx_ctl: Path to the Nginx binary. """ try: proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf, "-s", "reload"]) proc.communicate() if proc.returncode != 0: # Maybe Nginx isn't running # Write to temporary files instead of piping because of communication issues on Arch # https://github.com/certbot/certbot/issues/4324 with tempfile.TemporaryFile() as out: with tempfile.TemporaryFile() as err: nginx_proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf], stdout=out, stderr=err) nginx_proc.communicate() if nginx_proc.returncode != 0: # Enter recovery routine... raise errors.MisconfigurationError( "nginx restart failed:\n%s\n%s" % (out.read(), err.read())) except (OSError, ValueError): raise errors.MisconfigurationError("nginx restart failed") # Nginx can take a moment to recognize a newly added TLS SNI servername, so sleep # for a second. TODO: Check for expected servername and loop until it # appears or return an error if looping too long. time.sleep(1) def _determine_default_server_root(): if os.environ.get("CERTBOT_DOCS") == "1": default_server_root = "%s or %s" % (constants.LINUX_SERVER_ROOT, constants.FREEBSD_DARWIN_SERVER_ROOT) else: default_server_root = constants.CLI_DEFAULTS["server_root"] return default_server_root certbot-nginx-0.40.0/certbot_nginx/http_01.py0000644000175000017500000001752613560356424021267 0ustar ericaerica00000000000000"""A class that performs HTTP-01 challenges for Nginx""" import logging from acme import challenges from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot.compat import os from certbot.plugins import common from certbot_nginx import obj from certbot_nginx import nginxparser logger = logging.getLogger(__name__) class NginxHttp01(common.ChallengePerformer): """HTTP-01 authenticator for Nginx :ivar configurator: NginxConfigurator object :type configurator: :class:`~nginx.configurator.NginxConfigurator` :ivar list achalls: Annotated class:`~certbot.achallenges.KeyAuthorizationAnnotatedChallenge` challenges :param list indices: Meant to hold indices of challenges in a larger array. NginxHttp01 is capable of solving many challenges at once which causes an indexing issue within NginxConfigurator who must return all responses in order. Imagine NginxConfigurator maintaining state about where all of the challenges, possibly of different types, belong in the response array. This is an optional utility. """ def __init__(self, configurator): super(NginxHttp01, self).__init__(configurator) self.challenge_conf = os.path.join( configurator.config.config_dir, "le_http_01_cert_challenge.conf") def perform(self): """Perform a challenge on Nginx. :returns: list of :class:`certbot.acme.challenges.HTTP01Response` :rtype: list """ if not self.achalls: return [] responses = [x.response(x.account_key) for x in self.achalls] # Set up the configuration self._mod_config() # Save reversible changes self.configurator.save("HTTP Challenge", True) return responses def _mod_config(self): """Modifies Nginx config to include server_names_hash_bucket_size directive and server challenge blocks. :raises .MisconfigurationError: Unable to find a suitable HTTP block in which to include authenticator hosts. """ included = False include_directive = ['\n', 'include', ' ', self.challenge_conf] root = self.configurator.parser.config_root bucket_directive = ['\n', 'server_names_hash_bucket_size', ' ', '128'] main = self.configurator.parser.parsed[root] for line in main: if line[0] == ['http']: body = line[1] found_bucket = False posn = 0 for inner_line in body: if inner_line[0] == bucket_directive[1]: if int(inner_line[1]) < int(bucket_directive[3]): body[posn] = bucket_directive found_bucket = True posn += 1 if not found_bucket: body.insert(0, bucket_directive) if include_directive not in body: body.insert(0, include_directive) included = True break if not included: raise errors.MisconfigurationError( 'Certbot could not find a block to include ' 'challenges in %s.' % root) config = [self._make_or_mod_server_block(achall) for achall in self.achalls] config = [x for x in config if x is not None] config = nginxparser.UnspacedList(config) logger.debug("Generated server block:\n%s", str(config)) self.configurator.reverter.register_file_creation( True, self.challenge_conf) with open(self.challenge_conf, "w") as new_conf: nginxparser.dump(config, new_conf) def _default_listen_addresses(self): """Finds addresses for a challenge block to listen on. :returns: list of :class:`certbot_nginx.obj.Addr` to apply :rtype: list """ addresses = [] # type: List[obj.Addr] default_addr = "%s" % self.configurator.config.http01_port ipv6_addr = "[::]:{0}".format( self.configurator.config.http01_port) port = self.configurator.config.http01_port ipv6, ipv6only = self.configurator.ipv6_info(port) if ipv6: # If IPv6 is active in Nginx configuration if not ipv6only: # If ipv6only=on is not already present in the config ipv6_addr = ipv6_addr + " ipv6only=on" addresses = [obj.Addr.fromstring(default_addr), obj.Addr.fromstring(ipv6_addr)] logger.info(("Using default addresses %s and %s for authentication."), default_addr, ipv6_addr) else: addresses = [obj.Addr.fromstring(default_addr)] logger.info("Using default address %s for authentication.", default_addr) return addresses def _get_validation_path(self, achall): return os.sep + os.path.join(challenges.HTTP01.URI_ROOT_PATH, achall.chall.encode("token")) def _make_server_block(self, achall): """Creates a server block for a challenge. :param achall: Annotated HTTP-01 challenge :type achall: :class:`certbot.achallenges.KeyAuthorizationAnnotatedChallenge` :param list addrs: addresses of challenged domain :class:`list` of type :class:`~nginx.obj.Addr` :returns: server block for the challenge host :rtype: list """ addrs = self._default_listen_addresses() block = [['listen', ' ', addr.to_string(include_default=False)] for addr in addrs] # Ensure we 404 on any other request by setting a root document_root = os.path.join( self.configurator.config.work_dir, "http_01_nonexistent") block.extend([['server_name', ' ', achall.domain], ['root', ' ', document_root], self._location_directive_for_achall(achall) ]) # TODO: do we want to return something else if they otherwise access this block? return [['server'], block] def _location_directive_for_achall(self, achall): validation = achall.validation(achall.account_key) validation_path = self._get_validation_path(achall) location_directive = [['location', ' ', '=', ' ', validation_path], [['default_type', ' ', 'text/plain'], ['return', ' ', '200', ' ', validation]]] return location_directive def _make_or_mod_server_block(self, achall): """Modifies a server block to respond to a challenge. :param achall: Annotated HTTP-01 challenge :type achall: :class:`certbot.achallenges.KeyAuthorizationAnnotatedChallenge` """ try: vhosts = self.configurator.choose_redirect_vhosts(achall.domain, '%i' % self.configurator.config.http01_port, create_if_no_match=True) except errors.MisconfigurationError: # Couldn't find either a matching name+port server block # or a port+default_server block, so create a dummy block return self._make_server_block(achall) # len is max 1 because Nginx doesn't authenticate wildcards # if len were or vhosts None, we would have errored vhost = vhosts[0] # Modify existing server block location_directive = [self._location_directive_for_achall(achall)] self.configurator.parser.add_server_directives(vhost, location_directive) rewrite_directive = [['rewrite', ' ', '^(/.well-known/acme-challenge/.*)', ' ', '$1', ' ', 'break']] self.configurator.parser.add_server_directives(vhost, rewrite_directive, insert_at_top=True) return None certbot-nginx-0.40.0/certbot_nginx/obj.py0000644000175000017500000002236513560356424020557 0ustar ericaerica00000000000000"""Module contains classes used by the Nginx Configurator.""" import re import six from certbot.plugins import common ADD_HEADER_DIRECTIVE = 'add_header' class Addr(common.Addr): r"""Represents an Nginx address, i.e. what comes after the 'listen' directive. According to the `documentation`_, this may be address[:port], port, or unix:path. The latter is ignored here. The default value if no directive is specified is \*:80 (superuser) or \*:8000 (otherwise). If no port is specified, the default is 80. If no address is specified, listen on all addresses. .. _documentation: http://nginx.org/en/docs/http/ngx_http_core_module.html#listen .. todo:: Old-style nginx configs define SSL vhosts in a separate block instead of using 'ssl' in the listen directive. :param str addr: addr part of vhost address, may be hostname, IPv4, IPv6, "", or "\*" :param str port: port number or "\*" or "" :param bool ssl: Whether the directive includes 'ssl' :param bool default: Whether the directive includes 'default_server' :param bool default: Whether this is an IPv6 address :param bool ipv6only: Whether the directive includes 'ipv6only=on' """ UNSPECIFIED_IPV4_ADDRESSES = ('', '*', '0.0.0.0') CANONICAL_UNSPECIFIED_ADDRESS = UNSPECIFIED_IPV4_ADDRESSES[0] def __init__(self, host, port, ssl, default, ipv6, ipv6only): # pylint: disable=too-many-arguments super(Addr, self).__init__((host, port)) self.ssl = ssl self.default = default self.ipv6 = ipv6 self.ipv6only = ipv6only self.unspecified_address = host in self.UNSPECIFIED_IPV4_ADDRESSES @classmethod def fromstring(cls, str_addr): """Initialize Addr from string.""" parts = str_addr.split(' ') ssl = False default = False ipv6 = False ipv6only = False host = '' port = '' # The first part must be the address addr = parts.pop(0) # Ignore UNIX-domain sockets if addr.startswith('unix:'): return None # IPv6 check ipv6_match = re.match(r'\[.*\]', addr) if ipv6_match: ipv6 = True # IPv6 handling host = ipv6_match.group() # The rest of the addr string will be the port, if any port = addr[ipv6_match.end()+1:] else: # IPv4 handling tup = addr.partition(':') if re.match(r'^\d+$', tup[0]): # This is a bare port, not a hostname. E.g. listen 80 host = '' port = tup[0] else: # This is a host-port tuple. E.g. listen 127.0.0.1:* host = tup[0] port = tup[2] # The rest of the parts are options; we only care about ssl and default while parts: nextpart = parts.pop() if nextpart == 'ssl': ssl = True elif nextpart == 'default_server': default = True elif nextpart == 'default': default = True elif nextpart == "ipv6only=on": ipv6only = True return cls(host, port, ssl, default, ipv6, ipv6only) def to_string(self, include_default=True): """Return string representation of Addr""" parts = '' if self.tup[0] and self.tup[1]: parts = "%s:%s" % self.tup elif self.tup[0]: parts = self.tup[0] else: parts = self.tup[1] if self.default and include_default: parts += ' default_server' if self.ssl: parts += ' ssl' return parts def __str__(self): return self.to_string() def __repr__(self): return "Addr(" + self.__str__() + ")" def __hash__(self): # pylint: disable=useless-super-delegation # Python 3 requires explicit overridden for __hash__ # See certbot-apache/certbot_apache/obj.py for more information return super(Addr, self).__hash__() def super_eq(self, other): """Check ip/port equality, with IPv6 support. """ # If both addresses got an unspecified address, then make sure the # host representation in each match when doing the comparison. if self.unspecified_address and other.unspecified_address: return common.Addr((self.CANONICAL_UNSPECIFIED_ADDRESS, self.tup[1]), self.ipv6) == \ common.Addr((other.CANONICAL_UNSPECIFIED_ADDRESS, other.tup[1]), other.ipv6) return super(Addr, self).__eq__(other) def __eq__(self, other): if isinstance(other, self.__class__): return (self.super_eq(other) and self.ssl == other.ssl and self.default == other.default) return False class VirtualHost(object): # pylint: disable=too-few-public-methods """Represents an Nginx Virtualhost. :ivar str filep: file path of VH :ivar set addrs: Virtual Host addresses (:class:`set` of :class:`Addr`) :ivar set names: Server names/aliases of vhost (:class:`list` of :class:`str`) :ivar list raw: The raw form of the parsed server block :ivar bool ssl: SSLEngine on in vhost :ivar bool enabled: Virtual host is enabled :ivar list path: The indices into the parsed file used to access the server block defining the vhost """ def __init__(self, filep, addrs, ssl, enabled, names, raw, path): # pylint: disable=too-many-arguments """Initialize a VH.""" self.filep = filep self.addrs = addrs self.names = names self.ssl = ssl self.enabled = enabled self.raw = raw self.path = path def __str__(self): addr_str = ", ".join(str(addr) for addr in sorted(self.addrs, key=str)) # names might be a set, and it has different representations in Python # 2 and 3. Force it to be a list here for consistent outputs return ("file: %s\n" "addrs: %s\n" "names: %s\n" "ssl: %s\n" "enabled: %s" % (self.filep, addr_str, list(self.names), self.ssl, self.enabled)) def __repr__(self): return "VirtualHost(" + self.__str__().replace("\n", ", ") + ")\n" def __eq__(self, other): if isinstance(other, self.__class__): return (self.filep == other.filep and sorted(self.addrs, key=str) == sorted(other.addrs, key=str) and self.names == other.names and self.ssl == other.ssl and self.enabled == other.enabled and self.path == other.path) return False def __hash__(self): return hash((self.filep, tuple(self.path), tuple(self.addrs), tuple(self.names), self.ssl, self.enabled)) def has_header(self, header_name): """Determine if this server block has a particular header set. :param str header_name: The name of the header to check for, e.g. 'Strict-Transport-Security' """ found = _find_directive(self.raw, ADD_HEADER_DIRECTIVE, header_name) return found is not None def contains_list(self, test): """Determine if raw server block contains test list at top level """ for i in six.moves.range(0, len(self.raw) - len(test) + 1): if self.raw[i:i + len(test)] == test: return True return False def ipv6_enabled(self): """Return true if one or more of the listen directives in vhost supports IPv6""" for a in self.addrs: if a.ipv6: return True return False def ipv4_enabled(self): """Return true if one or more of the listen directives in vhost are IPv4 only""" if not self.addrs: return True for a in self.addrs: if not a.ipv6: return True return False def display_repr(self): """Return a representation of VHost to be used in dialog""" return ( "File: {filename}\n" "Addresses: {addrs}\n" "Names: {names}\n" "HTTPS: {https}\n".format( filename=self.filep, addrs=", ".join(str(addr) for addr in self.addrs), names=", ".join(self.names), https="Yes" if self.ssl else "No")) def _find_directive(directives, directive_name, match_content=None): """Find a directive of type directive_name in directives. If match_content is given, Searches for `match_content` in the directive arguments. """ if not directives or isinstance(directives, six.string_types): return None # If match_content is None, just match on directive type. Otherwise, match on # both directive type -and- the content! if directives[0] == directive_name and \ (match_content is None or match_content in directives): return directives matches = (_find_directive(line, directive_name, match_content) for line in directives) return next((m for m in matches if m is not None), None) certbot-nginx-0.40.0/certbot_nginx/parser.py0000644000175000017500000007173213560356424021303 0ustar ericaerica00000000000000"""NginxParser is a member object of the NginxConfigurator class.""" import copy import functools import glob import logging import re import pyparsing import six from certbot import errors from certbot.compat import os from certbot_nginx import obj from certbot_nginx import nginxparser from acme.magic_typing import Union, Dict, Set, Any, List, Tuple # pylint: disable=unused-import, no-name-in-module logger = logging.getLogger(__name__) class NginxParser(object): """Class handles the fine details of parsing the Nginx Configuration. :ivar str root: Normalized absolute path to the server root directory. Without trailing slash. :ivar dict parsed: Mapping of file paths to parsed trees """ def __init__(self, root): self.parsed = {} # type: Dict[str, Union[List, nginxparser.UnspacedList]] self.root = os.path.abspath(root) self.config_root = self._find_config_root() # Parse nginx.conf and included files. # TODO: Check sites-available/ as well. For now, the configurator does # not enable sites from there. self.load() def load(self): """Loads Nginx files into a parsed tree. """ self.parsed = {} self._parse_recursively(self.config_root) def _parse_recursively(self, filepath): """Parses nginx config files recursively by looking at 'include' directives inside 'http' and 'server' blocks. Note that this only reads Nginx files that potentially declare a virtual host. :param str filepath: The path to the files to parse, as a glob """ # pylint: disable=too-many-nested-blocks filepath = self.abs_path(filepath) trees = self._parse_files(filepath) for tree in trees: for entry in tree: if _is_include_directive(entry): # Parse the top-level included file self._parse_recursively(entry[1]) elif entry[0] == ['http'] or entry[0] == ['server']: # Look for includes in the top-level 'http'/'server' context for subentry in entry[1]: if _is_include_directive(subentry): self._parse_recursively(subentry[1]) elif entry[0] == ['http'] and subentry[0] == ['server']: # Look for includes in a 'server' context within # an 'http' context for server_entry in subentry[1]: if _is_include_directive(server_entry): self._parse_recursively(server_entry[1]) def abs_path(self, path): """Converts a relative path to an absolute path relative to the root. Does nothing for paths that are already absolute. :param str path: The path :returns: The absolute path :rtype: str """ if not os.path.isabs(path): return os.path.normpath(os.path.join(self.root, path)) return os.path.normpath(path) def _build_addr_to_ssl(self): """Builds a map from address to whether it listens on ssl in any server block """ servers = self._get_raw_servers() addr_to_ssl = {} # type: Dict[Tuple[str, str], bool] for filename in servers: for server, _ in servers[filename]: # Parse the server block to save addr info parsed_server = _parse_server_raw(server) for addr in parsed_server['addrs']: addr_tuple = addr.normalized_tuple() if addr_tuple not in addr_to_ssl: addr_to_ssl[addr_tuple] = addr.ssl addr_to_ssl[addr_tuple] = addr.ssl or addr_to_ssl[addr_tuple] return addr_to_ssl def _get_raw_servers(self): # pylint: disable=cell-var-from-loop # type: () -> Dict """Get a map of unparsed all server blocks """ servers = {} # type: Dict[str, Union[List, nginxparser.UnspacedList]] for filename in self.parsed: tree = self.parsed[filename] servers[filename] = [] srv = servers[filename] # workaround undefined loop var in lambdas # Find all the server blocks _do_for_subarray(tree, lambda x: len(x) >= 2 and x[0] == ['server'], lambda x, y: srv.append((x[1], y))) # Find 'include' statements in server blocks and append their trees for i, (server, path) in enumerate(servers[filename]): new_server = self._get_included_directives(server) servers[filename][i] = (new_server, path) return servers def get_vhosts(self): # pylint: disable=cell-var-from-loop """Gets list of all 'virtual hosts' found in Nginx configuration. Technically this is a misnomer because Nginx does not have virtual hosts, it has 'server blocks'. :returns: List of :class:`~certbot_nginx.obj.VirtualHost` objects found in configuration :rtype: list """ enabled = True # We only look at enabled vhosts for now servers = self._get_raw_servers() vhosts = [] for filename in servers: for server, path in servers[filename]: # Parse the server block into a VirtualHost object parsed_server = _parse_server_raw(server) vhost = obj.VirtualHost(filename, parsed_server['addrs'], parsed_server['ssl'], enabled, parsed_server['names'], server, path) vhosts.append(vhost) self._update_vhosts_addrs_ssl(vhosts) return vhosts def _update_vhosts_addrs_ssl(self, vhosts): """Update a list of raw parsed vhosts to include global address sslishness """ addr_to_ssl = self._build_addr_to_ssl() for vhost in vhosts: for addr in vhost.addrs: addr.ssl = addr_to_ssl[addr.normalized_tuple()] if addr.ssl: vhost.ssl = True def _get_included_directives(self, block): """Returns array with the "include" directives expanded out by concatenating the contents of the included file to the block. :param list block: :rtype: list """ result = copy.deepcopy(block) # Copy the list to keep self.parsed idempotent for directive in block: if _is_include_directive(directive): included_files = glob.glob( self.abs_path(directive[1])) for incl in included_files: try: result.extend(self.parsed[incl]) except KeyError: pass return result def _parse_files(self, filepath, override=False): """Parse files from a glob :param str filepath: Nginx config file path :param bool override: Whether to parse a file that has been parsed :returns: list of parsed tree structures :rtype: list """ files = glob.glob(filepath) # nginx on unix calls glob(3) for this # XXX Windows nginx uses FindFirstFile, and # should have a narrower call here trees = [] for item in files: if item in self.parsed and not override: continue try: with open(item) as _file: parsed = nginxparser.load(_file) self.parsed[item] = parsed trees.append(parsed) except IOError: logger.warning("Could not open file: %s", item) except pyparsing.ParseException as err: logger.debug("Could not parse file: %s due to %s", item, err) return trees def _find_config_root(self): """Return the Nginx Configuration Root file.""" location = ['nginx.conf'] for name in location: if os.path.isfile(os.path.join(self.root, name)): return os.path.join(self.root, name) raise errors.NoInstallationError( "Could not find Nginx root configuration file (nginx.conf)") def filedump(self, ext='tmp', lazy=True): """Dumps parsed configurations into files. :param str ext: The file extension to use for the dumped files. If empty, this overrides the existing conf files. :param bool lazy: Only write files that have been modified """ # Best-effort atomicity is enforced above us by reverter.py for filename in self.parsed: tree = self.parsed[filename] if ext: filename = filename + os.path.extsep + ext try: if lazy and not tree.is_dirty(): continue out = nginxparser.dumps(tree) logger.debug('Writing nginx conf tree to %s:\n%s', filename, out) with open(filename, 'w') as _file: _file.write(out) except IOError: logger.error("Could not open file for writing: %s", filename) def parse_server(self, server): """Parses a list of server directives, accounting for global address sslishness. :param list server: list of directives in a server block :rtype: dict """ addr_to_ssl = self._build_addr_to_ssl() parsed_server = _parse_server_raw(server) _apply_global_addr_ssl(addr_to_ssl, parsed_server) return parsed_server def has_ssl_on_directive(self, vhost): """Does vhost have ssl on for all ports? :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost in question :returns: True if 'ssl on' directive is included :rtype: bool """ server = vhost.raw for directive in server: if not directive: continue elif _is_ssl_on_directive(directive): return True return False def add_server_directives(self, vhost, directives, insert_at_top=False): """Add directives to the server block identified by vhost. This method modifies vhost to be fully consistent with the new directives. ..note :: It's an error to try and add a nonrepeatable directive that already exists in the config block with a conflicting value. ..todo :: Doesn't match server blocks whose server_name directives are split across multiple conf files. :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost whose information we use to match on :param list directives: The directives to add :param bool insert_at_top: True if the directives need to be inserted at the top of the server block instead of the bottom """ self._modify_server_directives(vhost, functools.partial(_add_directives, directives, insert_at_top)) def update_or_add_server_directives(self, vhost, directives, insert_at_top=False): """Add or replace directives in the server block identified by vhost. This method modifies vhost to be fully consistent with the new directives. ..note :: When a directive with the same name already exists in the config block, the first instance will be replaced. Otherwise, the directive will be appended/prepended to the config block as in add_server_directives. ..todo :: Doesn't match server blocks whose server_name directives are split across multiple conf files. :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost whose information we use to match on :param list directives: The directives to add :param bool insert_at_top: True if the directives need to be inserted at the top of the server block instead of the bottom """ self._modify_server_directives(vhost, functools.partial(_update_or_add_directives, directives, insert_at_top)) def remove_server_directives(self, vhost, directive_name, match_func=None): """Remove all directives of type directive_name. :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost to remove directives from :param string directive_name: The directive type to remove :param callable match_func: Function of the directive that returns true for directives to be deleted. """ self._modify_server_directives(vhost, functools.partial(_remove_directives, directive_name, match_func)) def _update_vhost_based_on_new_directives(self, vhost, directives_list): new_server = self._get_included_directives(directives_list) parsed_server = self.parse_server(new_server) vhost.addrs = parsed_server['addrs'] vhost.ssl = parsed_server['ssl'] vhost.names = parsed_server['names'] vhost.raw = new_server def _modify_server_directives(self, vhost, block_func): filename = vhost.filep try: result = self.parsed[filename] for index in vhost.path: result = result[index] if not isinstance(result, list) or len(result) != 2: raise errors.MisconfigurationError("Not a server block.") result = result[1] block_func(result) self._update_vhost_based_on_new_directives(vhost, result) except errors.MisconfigurationError as err: raise errors.MisconfigurationError("Problem in %s: %s" % (filename, str(err))) def duplicate_vhost(self, vhost_template, remove_singleton_listen_params=False, only_directives=None): """Duplicate the vhost in the configuration files. :param :class:`~certbot_nginx.obj.VirtualHost` vhost_template: The vhost whose information we copy :param bool remove_singleton_listen_params: If we should remove parameters from listen directives in the block that can only be used once per address :param list only_directives: If it exists, only duplicate the named directives. Only looks at first level of depth; does not expand includes. :returns: A vhost object for the newly created vhost :rtype: :class:`~certbot_nginx.obj.VirtualHost` """ # TODO: https://github.com/certbot/certbot/issues/5185 # put it in the same file as the template, at the same level new_vhost = copy.deepcopy(vhost_template) enclosing_block = self.parsed[vhost_template.filep] for index in vhost_template.path[:-1]: enclosing_block = enclosing_block[index] raw_in_parsed = copy.deepcopy(enclosing_block[vhost_template.path[-1]]) if only_directives is not None: new_directives = nginxparser.UnspacedList([]) for directive in raw_in_parsed[1]: if directive and directive[0] in only_directives: new_directives.append(directive) raw_in_parsed[1] = new_directives self._update_vhost_based_on_new_directives(new_vhost, new_directives) enclosing_block.append(raw_in_parsed) new_vhost.path[-1] = len(enclosing_block) - 1 if remove_singleton_listen_params: for addr in new_vhost.addrs: addr.default = False addr.ipv6only = False for directive in enclosing_block[new_vhost.path[-1]][1]: if directive and directive[0] == 'listen': # Exclude one-time use parameters which will cause an error if repeated. # https://nginx.org/en/docs/http/ngx_http_core_module.html#listen exclude = set(('default_server', 'default', 'setfib', 'fastopen', 'backlog', 'rcvbuf', 'sndbuf', 'accept_filter', 'deferred', 'bind', 'ipv6only', 'reuseport', 'so_keepalive')) for param in exclude: # See: github.com/certbot/certbot/pull/6223#pullrequestreview-143019225 keys = [x.split('=')[0] for x in directive] if param in keys: del directive[keys.index(param)] return new_vhost def _parse_ssl_options(ssl_options): if ssl_options is not None: try: with open(ssl_options) as _file: return nginxparser.load(_file) except IOError: logger.warning("Missing NGINX TLS options file: %s", ssl_options) except pyparsing.ParseBaseException as err: logger.debug("Could not parse file: %s due to %s", ssl_options, err) return [] def _do_for_subarray(entry, condition, func, path=None): """Executes a function for a subarray of a nested array if it matches the given condition. :param list entry: The list to iterate over :param function condition: Returns true iff func should be executed on item :param function func: The function to call for each matching item """ if path is None: path = [] if isinstance(entry, list): if condition(entry): func(entry, path) else: for index, item in enumerate(entry): _do_for_subarray(item, condition, func, path + [index]) def get_best_match(target_name, names): """Finds the best match for target_name out of names using the Nginx name-matching rules (exact > longest wildcard starting with * > longest wildcard ending with * > regex). :param str target_name: The name to match :param set names: The candidate server names :returns: Tuple of (type of match, the name that matched) :rtype: tuple """ exact = [] wildcard_start = [] wildcard_end = [] regex = [] for name in names: if _exact_match(target_name, name): exact.append(name) elif _wildcard_match(target_name, name, True): wildcard_start.append(name) elif _wildcard_match(target_name, name, False): wildcard_end.append(name) elif _regex_match(target_name, name): regex.append(name) if exact: # There can be more than one exact match; e.g. eff.org, .eff.org match = min(exact, key=len) return ('exact', match) if wildcard_start: # Return the longest wildcard match = max(wildcard_start, key=len) return ('wildcard_start', match) if wildcard_end: # Return the longest wildcard match = max(wildcard_end, key=len) return ('wildcard_end', match) if regex: # Just return the first one for now match = regex[0] return ('regex', match) return (None, None) def _exact_match(target_name, name): return target_name == name or '.' + target_name == name def _wildcard_match(target_name, name, start): # Degenerate case if name == '*': return True parts = target_name.split('.') match_parts = name.split('.') # If the domain ends in a wildcard, do the match procedure in reverse if not start: parts.reverse() match_parts.reverse() # The first part must be a wildcard or blank, e.g. '.eff.org' first = match_parts.pop(0) if first != '*' and first != '': return False target_name = '.'.join(parts) name = '.'.join(match_parts) # Ex: www.eff.org matches *.eff.org, eff.org does not match *.eff.org return target_name.endswith('.' + name) def _regex_match(target_name, name): # Must start with a tilde if len(name) < 2 or name[0] != '~': return False # After tilde is a perl-compatible regex try: regex = re.compile(name[1:]) return re.match(regex, target_name) except re.error: # pragma: no cover # perl-compatible regexes are sometimes not recognized by python return False def _is_include_directive(entry): """Checks if an nginx parsed entry is an 'include' directive. :param list entry: the parsed entry :returns: Whether it's an 'include' directive :rtype: bool """ return (isinstance(entry, list) and len(entry) == 2 and entry[0] == 'include' and isinstance(entry[1], six.string_types)) def _is_ssl_on_directive(entry): """Checks if an nginx parsed entry is an 'ssl on' directive. :param list entry: the parsed entry :returns: Whether it's an 'ssl on' directive :rtype: bool """ return (isinstance(entry, list) and len(entry) == 2 and entry[0] == 'ssl' and entry[1] == 'on') def _add_directives(directives, insert_at_top, block): """Adds directives to a config block.""" for directive in directives: _add_directive(block, directive, insert_at_top) if block and '\n' not in block[-1]: # could be " \n " or ["\n"] ! block.append(nginxparser.UnspacedList('\n')) def _update_or_add_directives(directives, insert_at_top, block): """Adds or replaces directives in a config block.""" for directive in directives: _update_or_add_directive(block, directive, insert_at_top) if block and '\n' not in block[-1]: # could be " \n " or ["\n"] ! block.append(nginxparser.UnspacedList('\n')) INCLUDE = 'include' REPEATABLE_DIRECTIVES = set(['server_name', 'listen', INCLUDE, 'rewrite', 'add_header']) COMMENT = ' managed by Certbot' COMMENT_BLOCK = [' ', '#', COMMENT] def comment_directive(block, location): """Add a ``#managed by Certbot`` comment to the end of the line at location. :param list block: The block containing the directive to be commented :param int location: The location within ``block`` of the directive to be commented """ next_entry = block[location + 1] if location + 1 < len(block) else None if isinstance(next_entry, list) and next_entry: if len(next_entry) >= 2 and next_entry[-2] == "#" and COMMENT in next_entry[-1]: return elif isinstance(next_entry, nginxparser.UnspacedList): next_entry = next_entry.spaced[0] else: next_entry = next_entry[0] block.insert(location + 1, COMMENT_BLOCK[:]) if next_entry is not None and "\n" not in next_entry: block.insert(location + 2, '\n') def _comment_out_directive(block, location, include_location): """Comment out the line at location, with a note of explanation.""" comment_message = ' duplicated in {0}'.format(include_location) # add the end comment # create a dumpable object out of block[location] (so it includes the ;) directive = block[location] new_dir_block = nginxparser.UnspacedList([]) # just a wrapper new_dir_block.append(directive) dumped = nginxparser.dumps(new_dir_block) commented = dumped + ' #' + comment_message # add the comment directly to the one-line string new_dir = nginxparser.loads(commented) # reload into UnspacedList # add the beginning comment insert_location = 0 if new_dir[0].spaced[0] != new_dir[0][0]: # if there's whitespace at the beginning insert_location = 1 new_dir[0].spaced.insert(insert_location, "# ") # comment out the line new_dir[0].spaced.append(";") # directly add in the ;, because now dumping won't work properly dumped = nginxparser.dumps(new_dir) new_dir = nginxparser.loads(dumped) # reload into an UnspacedList block[location] = new_dir[0] # set the now-single-line-comment directive back in place def _find_location(block, directive_name, match_func=None): """Finds the index of the first instance of directive_name in block. If no line exists, use None.""" return next((index for index, line in enumerate(block) \ if line and line[0] == directive_name and (match_func is None or match_func(line))), None) def _is_whitespace_or_comment(directive): """Is this directive either a whitespace or comment directive?""" return len(directive) == 0 or directive[0] == '#' def _add_directive(block, directive, insert_at_top): if not isinstance(directive, nginxparser.UnspacedList): directive = nginxparser.UnspacedList(directive) if _is_whitespace_or_comment(directive): # whitespace or comment block.append(directive) return location = _find_location(block, directive[0]) # Append or prepend directive. Fail if the name is not a repeatable directive name, # and there is already a copy of that directive with a different value # in the config file. # handle flat include files directive_name = directive[0] def can_append(loc, dir_name): """ Can we append this directive to the block? """ return loc is None or (isinstance(dir_name, six.string_types) and dir_name in REPEATABLE_DIRECTIVES) err_fmt = 'tried to insert directive "{0}" but found conflicting "{1}".' # Give a better error message about the specific directive than Nginx's "fail to restart" if directive_name == INCLUDE: # in theory, we might want to do this recursively, but in practice, that's really not # necessary because we know what file we're talking about (and if we don't recurse, we # just give a worse error message) included_directives = _parse_ssl_options(directive[1]) for included_directive in included_directives: included_dir_loc = _find_location(block, included_directive[0]) included_dir_name = included_directive[0] if not _is_whitespace_or_comment(included_directive) \ and not can_append(included_dir_loc, included_dir_name): if block[included_dir_loc] != included_directive: raise errors.MisconfigurationError(err_fmt.format(included_directive, block[included_dir_loc])) else: _comment_out_directive(block, included_dir_loc, directive[1]) if can_append(location, directive_name): if insert_at_top: # Add a newline so the comment doesn't comment # out existing directives block.insert(0, nginxparser.UnspacedList('\n')) block.insert(0, directive) comment_directive(block, 0) else: block.append(directive) comment_directive(block, len(block) - 1) elif block[location] != directive: raise errors.MisconfigurationError(err_fmt.format(directive, block[location])) def _update_directive(block, directive, location): block[location] = directive comment_directive(block, location) def _update_or_add_directive(block, directive, insert_at_top): if not isinstance(directive, nginxparser.UnspacedList): directive = nginxparser.UnspacedList(directive) if _is_whitespace_or_comment(directive): # whitespace or comment block.append(directive) return location = _find_location(block, directive[0]) # we can update directive if location is not None: _update_directive(block, directive, location) return _add_directive(block, directive, insert_at_top) def _is_certbot_comment(directive): return '#' in directive and COMMENT in directive def _remove_directives(directive_name, match_func, block): """Removes directives of name directive_name from a config block if match_func matches. """ while True: location = _find_location(block, directive_name, match_func=match_func) if location is None: return # if the directive was made by us, remove the comment following if location + 1 < len(block) and _is_certbot_comment(block[location + 1]): del block[location + 1] del block[location] def _apply_global_addr_ssl(addr_to_ssl, parsed_server): """Apply global sslishness information to the parsed server block """ for addr in parsed_server['addrs']: addr.ssl = addr_to_ssl[addr.normalized_tuple()] if addr.ssl: parsed_server['ssl'] = True def _parse_server_raw(server): """Parses a list of server directives. :param list server: list of directives in a server block :rtype: dict """ addrs = set() # type: Set[obj.Addr] ssl = False # type: bool names = set() # type: Set[str] apply_ssl_to_all_addrs = False for directive in server: if not directive: continue if directive[0] == 'listen': addr = obj.Addr.fromstring(" ".join(directive[1:])) if addr: addrs.add(addr) if addr.ssl: ssl = True elif directive[0] == 'server_name': names.update(x.strip('"\'') for x in directive[1:]) elif _is_ssl_on_directive(directive): ssl = True apply_ssl_to_all_addrs = True if apply_ssl_to_all_addrs: for addr in addrs: addr.ssl = True return { 'addrs': addrs, 'ssl': ssl, 'names': names } certbot-nginx-0.40.0/certbot_nginx/__init__.py0000644000175000017500000000003413560356424021531 0ustar ericaerica00000000000000"""Certbot nginx plugin.""" certbot-nginx-0.40.0/certbot_nginx/display_ops.py0000644000175000017500000000257113560356424022330 0ustar ericaerica00000000000000"""Contains UI methods for Nginx operations.""" import logging import zope.component from certbot import interfaces import certbot.display.util as display_util logger = logging.getLogger(__name__) def select_vhost_multiple(vhosts): """Select multiple Vhosts to install the certificate for :param vhosts: Available Nginx VirtualHosts :type vhosts: :class:`list` of type `~obj.Vhost` :returns: List of VirtualHosts :rtype: :class:`list`of type `~obj.Vhost` """ if not vhosts: return list() tags_list = [vhost.display_repr()+"\n" for vhost in vhosts] # Remove the extra newline from the last entry if tags_list: tags_list[-1] = tags_list[-1][:-1] code, names = zope.component.getUtility(interfaces.IDisplay).checklist( "Which server blocks would you like to modify?", tags=tags_list, force_interactive=True) if code == display_util.OK: return_vhosts = _reversemap_vhosts(names, vhosts) return return_vhosts return [] def _reversemap_vhosts(names, vhosts): """Helper function for select_vhost_multiple for mapping string representations back to actual vhost objects""" return_vhosts = list() for selection in names: for vhost in vhosts: if vhost.display_repr().strip() == selection.strip(): return_vhosts.append(vhost) return return_vhosts certbot-nginx-0.40.0/certbot_nginx/tests/0002755000175000017500000000000013560356440020565 5ustar ericaerica00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/util.py0000644000175000017500000001101513560356424022112 0ustar ericaerica00000000000000"""Common utilities for certbot_nginx.""" import copy import shutil import tempfile import unittest import josepy as jose import mock import pkg_resources import zope.component from certbot import configuration from certbot import util from certbot.compat import os from certbot.plugins import common from certbot.tests import util as test_util from certbot_nginx import configurator from certbot_nginx import nginxparser class NginxTest(unittest.TestCase): # pylint: disable=too-few-public-methods def setUp(self): super(NginxTest, self).setUp() self.temp_dir, self.config_dir, self.work_dir = common.dir_setup( "etc_nginx", "certbot_nginx.tests") self.logs_dir = tempfile.mkdtemp('logs') self.config_path = os.path.join(self.temp_dir, "etc_nginx") self.rsa512jwk = jose.JWKRSA.load(test_util.load_vector( "rsa512_key.pem")) def tearDown(self): # Cleanup opened resources after a test. This is usually done through atexit handlers in # Certbot, but during tests, atexit will not run registered functions before tearDown is # called and instead will run them right before the entire test process exits. # It is a problem on Windows, that does not accept to clean resources before closing them. util._release_locks() # pylint: disable=protected-access shutil.rmtree(self.temp_dir) shutil.rmtree(self.config_dir) shutil.rmtree(self.work_dir) shutil.rmtree(self.logs_dir) def get_data_filename(filename): """Gets the filename of a test data file.""" return pkg_resources.resource_filename( "certbot_nginx.tests", os.path.join( "testdata", "etc_nginx", filename)) def get_nginx_configurator( config_path, config_dir, work_dir, logs_dir, version=(1, 6, 2), openssl_version="1.0.2g"): """Create an Nginx Configurator with the specified options.""" backups = os.path.join(work_dir, "backups") with mock.patch("certbot_nginx.configurator.NginxConfigurator." "config_test"): with mock.patch("certbot_nginx.configurator.util." "exe_exists") as mock_exe_exists: mock_exe_exists.return_value = True config = configurator.NginxConfigurator( config=mock.MagicMock( nginx_server_root=config_path, le_vhost_ext="-le-ssl.conf", config_dir=config_dir, work_dir=work_dir, logs_dir=logs_dir, backup_dir=backups, temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"), in_progress_dir=os.path.join(backups, "IN_PROGRESS"), server="https://acme-server.org:443/new", http01_port=80, https_port=5001, ), name="nginx", version=version, openssl_version=openssl_version) config.prepare() # Provide general config utility. nsconfig = configuration.NamespaceConfig(config.config) zope.component.provideUtility(nsconfig) return config def filter_comments(tree): """Filter comment nodes from parsed configurations.""" def traverse(tree): """Generator dropping comment nodes""" for entry in tree: # key, values = entry spaceless = [e for e in entry if not nginxparser.spacey(e)] if spaceless: key = spaceless[0] values = spaceless[1] if len(spaceless) > 1 else None else: key = values = "" if isinstance(key, list): new = copy.deepcopy(entry) new[1] = filter_comments(values) yield new else: if key != '#' and spaceless: yield spaceless return list(traverse(tree)) def contains_at_depth(haystack, needle, n): """Is the needle in haystack at depth n? Return true if the needle is present in one of the sub-iterables in haystack at depth n. Haystack must be an iterable. """ # Specifically use hasattr rather than isinstance(..., collections.Iterable) # because we want to include lists but reject strings. if not hasattr(haystack, '__iter__') or hasattr(haystack, 'strip'): return False if n == 0: return needle in haystack for item in haystack: if contains_at_depth(item, needle, n - 1): return True return False certbot-nginx-0.40.0/certbot_nginx/tests/parser_obj_test.py0000644000175000017500000002555213560356424024335 0ustar ericaerica00000000000000""" Tests for functions and classes in parser_obj.py """ import unittest import mock from certbot_nginx.parser_obj import parse_raw from certbot_nginx.parser_obj import COMMENT_BLOCK class CommentHelpersTest(unittest.TestCase): def test_is_comment(self): from certbot_nginx.parser_obj import _is_comment self.assertTrue(_is_comment(parse_raw(['#']))) self.assertTrue(_is_comment(parse_raw(['#', ' literally anything else']))) self.assertFalse(_is_comment(parse_raw(['not', 'even', 'a', 'comment']))) def test_is_certbot_comment(self): from certbot_nginx.parser_obj import _is_certbot_comment self.assertTrue(_is_certbot_comment( parse_raw(COMMENT_BLOCK))) self.assertFalse(_is_certbot_comment( parse_raw(['#', ' not a certbot comment']))) self.assertFalse(_is_certbot_comment( parse_raw(['#', ' managed by Certbot', ' also not a certbot comment']))) self.assertFalse(_is_certbot_comment( parse_raw(['not', 'even', 'a', 'comment']))) def test_certbot_comment(self): from certbot_nginx.parser_obj import _certbot_comment, _is_certbot_comment comment = _certbot_comment(None) self.assertTrue(_is_certbot_comment(comment)) self.assertEqual(comment.dump(), COMMENT_BLOCK) self.assertEqual(comment.dump(True), [' '] + COMMENT_BLOCK) self.assertEqual(_certbot_comment(None, 2).dump(True), [' '] + COMMENT_BLOCK) class ParsingHooksTest(unittest.TestCase): def test_is_sentence(self): from certbot_nginx.parser_obj import Sentence self.assertFalse(Sentence.should_parse([])) self.assertTrue(Sentence.should_parse([''])) self.assertTrue(Sentence.should_parse(['word'])) self.assertTrue(Sentence.should_parse(['two', 'words'])) self.assertFalse(Sentence.should_parse([[]])) self.assertFalse(Sentence.should_parse(['word', []])) def test_is_block(self): from certbot_nginx.parser_obj import Block self.assertFalse(Block.should_parse([])) self.assertFalse(Block.should_parse([''])) self.assertFalse(Block.should_parse(['two', 'words'])) self.assertFalse(Block.should_parse([[[]], []])) self.assertFalse(Block.should_parse([['block_name'], ['hi', []], []])) self.assertFalse(Block.should_parse([['block_name'], 'lol'])) self.assertTrue(Block.should_parse([['block_name'], ['hi', []]])) self.assertTrue(Block.should_parse([['hello'], []])) self.assertTrue(Block.should_parse([['block_name'], [['many'], ['statements'], 'here']])) self.assertTrue(Block.should_parse([['if', ' ', '(whatever)'], ['hi']])) def test_parse_raw(self): fake_parser1 = mock.Mock() fake_parser1.should_parse = lambda x: True fake_parser2 = mock.Mock() fake_parser2.should_parse = lambda x: False # First encountered "match" should parse. parse_raw([]) fake_parser1.called_once() fake_parser2.not_called() fake_parser1.reset_mock() # "match" that returns False shouldn't parse. parse_raw([]) fake_parser1.not_called() fake_parser2.called_once() @mock.patch("certbot_nginx.parser_obj.Parsable.parsing_hooks") def test_parse_raw_no_match(self, parsing_hooks): from certbot import errors fake_parser1 = mock.Mock() fake_parser1.should_parse = lambda x: False parsing_hooks.return_value = (fake_parser1,) self.assertRaises(errors.MisconfigurationError, parse_raw, []) parsing_hooks.return_value = tuple() self.assertRaises(errors.MisconfigurationError, parse_raw, []) def test_parse_raw_passes_add_spaces(self): fake_parser1 = mock.Mock() fake_parser1.should_parse = lambda x: True parse_raw([]) fake_parser1.parse.called_with([None]) parse_raw([], add_spaces=True) fake_parser1.parse.called_with([None, True]) class SentenceTest(unittest.TestCase): def setUp(self): from certbot_nginx.parser_obj import Sentence self.sentence = Sentence(None) def test_parse_bad_sentence_raises_error(self): from certbot import errors self.assertRaises(errors.MisconfigurationError, self.sentence.parse, 'lol') self.assertRaises(errors.MisconfigurationError, self.sentence.parse, [[]]) self.assertRaises(errors.MisconfigurationError, self.sentence.parse, [5]) def test_parse_sentence_words_hides_spaces(self): og_sentence = ['\r\n', 'hello', ' ', ' ', '\t\n ', 'lol', ' ', 'spaces'] self.sentence.parse(og_sentence) self.assertEqual(self.sentence.words, ['hello', 'lol', 'spaces']) self.assertEqual(self.sentence.dump(), ['hello', 'lol', 'spaces']) self.assertEqual(self.sentence.dump(True), og_sentence) def test_parse_sentence_with_add_spaces(self): self.sentence.parse(['hi', 'there'], add_spaces=True) self.assertEqual(self.sentence.dump(True), ['hi', ' ', 'there']) self.sentence.parse(['one', ' ', 'space', 'none'], add_spaces=True) self.assertEqual(self.sentence.dump(True), ['one', ' ', 'space', ' ', 'none']) def test_iterate(self): expected = [['1', '2', '3']] self.sentence.parse(['1', ' ', '2', ' ', '3']) for i, sentence in enumerate(self.sentence.iterate()): self.assertEqual(sentence.dump(), expected[i]) def test_set_tabs(self): self.sentence.parse(['tabs', 'pls'], add_spaces=True) self.sentence.set_tabs() self.assertEqual(self.sentence.dump(True)[0], '\n ') self.sentence.parse(['tabs', 'pls'], add_spaces=True) def test_get_tabs(self): self.sentence.parse(['no', 'tabs']) self.assertEqual(self.sentence.get_tabs(), '') self.sentence.parse(['\n \n ', 'tabs']) self.assertEqual(self.sentence.get_tabs(), ' ') self.sentence.parse(['\n\t ', 'tabs']) self.assertEqual(self.sentence.get_tabs(), '\t ') self.sentence.parse(['\n\t \n', 'tabs']) self.assertEqual(self.sentence.get_tabs(), '') class BlockTest(unittest.TestCase): def setUp(self): from certbot_nginx.parser_obj import Block self.bloc = Block(None) self.name = ['server', 'name'] self.contents = [['thing', '1'], ['thing', '2'], ['another', 'one']] self.bloc.parse([self.name, self.contents]) def test_iterate(self): # Iterates itself normally self.assertEqual(self.bloc, next(self.bloc.iterate())) # Iterates contents while expanded expected = [self.bloc.dump()] + self.contents for i, elem in enumerate(self.bloc.iterate(expanded=True)): self.assertEqual(expected[i], elem.dump()) def test_iterate_match(self): # can match on contents while expanded from certbot_nginx.parser_obj import Block, Sentence expected = [['thing', '1'], ['thing', '2']] for i, elem in enumerate(self.bloc.iterate(expanded=True, match=lambda x: isinstance(x, Sentence) and 'thing' in x.words)): self.assertEqual(expected[i], elem.dump()) # can match on self self.assertEqual(self.bloc, next(self.bloc.iterate( expanded=True, match=lambda x: isinstance(x, Block) and 'server' in x.names))) def test_parse_with_added_spaces(self): import copy self.bloc.parse([copy.copy(self.name), self.contents], add_spaces=True) self.assertEqual(self.bloc.dump(), [self.name, self.contents]) self.assertEqual(self.bloc.dump(True), [ ['server', ' ', 'name', ' '], [['thing', ' ', '1'], ['thing', ' ', '2'], ['another', ' ', 'one']]]) def test_bad_parse_raises_error(self): from certbot import errors self.assertRaises(errors.MisconfigurationError, self.bloc.parse, [[[]], [[]]]) self.assertRaises(errors.MisconfigurationError, self.bloc.parse, ['lol']) self.assertRaises(errors.MisconfigurationError, self.bloc.parse, ['fake', 'news']) def test_set_tabs(self): self.bloc.set_tabs() self.assertEqual(self.bloc.names.dump(True)[0], '\n ') for elem in self.bloc.contents.dump(True)[:-1]: self.assertEqual(elem[0], '\n ') self.assertEqual(self.bloc.contents.dump(True)[-1][0], '\n') def test_get_tabs(self): self.bloc.parse([[' \n \t', 'lol'], []]) self.assertEqual(self.bloc.get_tabs(), ' \t') class StatementsTest(unittest.TestCase): def setUp(self): from certbot_nginx.parser_obj import Statements self.statements = Statements(None) self.raw = [ ['sentence', 'one'], ['sentence', 'two'], ['and', 'another'] ] self.raw_spaced = [ ['\n ', 'sentence', ' ', 'one'], ['\n ', 'sentence', ' ', 'two'], ['\n ', 'and', ' ', 'another'], '\n\n' ] def test_set_tabs(self): self.statements.parse(self.raw) self.statements.set_tabs() for statement in self.statements.iterate(): self.assertEqual(statement.dump(True)[0], '\n ') def test_set_tabs_with_parent(self): # Trailing whitespace should inherit from parent tabbing. self.statements.parse(self.raw) self.statements.parent = mock.Mock() self.statements.parent.get_tabs.return_value = '\t\t' self.statements.set_tabs() for statement in self.statements.iterate(): self.assertEqual(statement.dump(True)[0], '\n ') self.assertEqual(self.statements.dump(True)[-1], '\n\t\t') def test_get_tabs(self): self.raw[0].insert(0, '\n \n \t') self.statements.parse(self.raw) self.assertEqual(self.statements.get_tabs(), ' \t') self.statements.parse([]) self.assertEqual(self.statements.get_tabs(), '') def test_parse_with_added_spaces(self): self.statements.parse(self.raw, add_spaces=True) self.assertEqual(self.statements.dump(True)[0], ['sentence', ' ', 'one']) def test_parse_bad_list_raises_error(self): from certbot import errors self.assertRaises(errors.MisconfigurationError, self.statements.parse, 'lol not a list') def test_parse_hides_trailing_whitespace(self): self.statements.parse(self.raw + ['\n\n ']) self.assertTrue(isinstance(self.statements.dump()[-1], list)) self.assertTrue(self.statements.dump(True)[-1].isspace()) self.assertEqual(self.statements.dump(True)[-1], '\n\n ') def test_iterate(self): self.statements.parse(self.raw) expected = [['sentence', 'one'], ['sentence', 'two']] for i, elem in enumerate(self.statements.iterate(match=lambda x: 'sentence' in x)): self.assertEqual(expected[i], elem.dump()) if __name__ == "__main__": unittest.main() # pragma: no cover certbot-nginx-0.40.0/certbot_nginx/tests/configurator_test.py0000644000175000017500000014141713560356424024710 0ustar ericaerica00000000000000# pylint: disable=too-many-public-methods """Test for certbot_nginx.configurator.""" import unittest import OpenSSL import mock from acme import challenges from acme import messages from certbot import achallenges from certbot import crypto_util from certbot import errors from certbot.compat import os from certbot.tests import util as certbot_test_util from certbot_nginx import obj from certbot_nginx import parser from certbot_nginx.configurator import _redirect_block_for_domain from certbot_nginx.nginxparser import UnspacedList from certbot_nginx.tests import util class NginxConfiguratorTest(util.NginxTest): """Test a semi complex vhost configuration.""" def setUp(self): super(NginxConfiguratorTest, self).setUp() self.config = util.get_nginx_configurator( self.config_path, self.config_dir, self.work_dir, self.logs_dir) @mock.patch("certbot_nginx.configurator.util.exe_exists") def test_prepare_no_install(self, mock_exe_exists): mock_exe_exists.return_value = False self.assertRaises( errors.NoInstallationError, self.config.prepare) def test_prepare(self): self.assertEqual((1, 6, 2), self.config.version) self.assertEqual(11, len(self.config.parser.parsed)) @mock.patch("certbot_nginx.configurator.util.exe_exists") @mock.patch("certbot_nginx.configurator.subprocess.Popen") def test_prepare_initializes_version(self, mock_popen, mock_exe_exists): mock_popen().communicate.return_value = ( "", "\n".join(["nginx version: nginx/1.6.2", "built by clang 6.0 (clang-600.0.56)" " (based on LLVM 3.5svn)", "TLS SNI support enabled", "configure arguments: --prefix=/usr/local/Cellar/" "nginx/1.6.2 --with-http_ssl_module"])) mock_exe_exists.return_value = True self.config.version = None self.config.config_test = mock.Mock() self.config.prepare() self.assertEqual((1, 6, 2), self.config.version) def test_prepare_locked(self): server_root = self.config.conf("server-root") from certbot import util as certbot_util certbot_util._LOCKS[server_root].release() # pylint: disable=protected-access self.config.config_test = mock.Mock() certbot_test_util.lock_and_call(self._test_prepare_locked, server_root) @mock.patch("certbot_nginx.configurator.util.exe_exists") def _test_prepare_locked(self, unused_exe_exists): try: self.config.prepare() except errors.PluginError as err: err_msg = str(err) self.assertTrue("lock" in err_msg) self.assertTrue(self.config.conf("server-root") in err_msg) else: # pragma: no cover self.fail("Exception wasn't raised!") @mock.patch("certbot_nginx.configurator.socket.gethostbyaddr") def test_get_all_names(self, mock_gethostbyaddr): mock_gethostbyaddr.return_value = ('155.225.50.69.nephoscale.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"}) def test_supported_enhancements(self): self.assertEqual(['redirect', 'ensure-http-header', 'staple-ocsp'], self.config.supported_enhancements()) def test_enhance(self): self.assertRaises( errors.PluginError, self.config.enhance, 'myhost', 'unknown_enhancement') def test_get_chall_pref(self): self.assertEqual([challenges.HTTP01], self.config.get_chall_pref('myhost')) def test_save(self): filep = self.config.parser.abs_path('sites-enabled/example.com') mock_vhost = obj.VirtualHost(filep, None, None, None, set(['.example.com', 'example.*']), None, [0]) self.config.parser.add_server_directives( mock_vhost, [['listen', ' ', '5001', ' ', 'ssl']]) self.config.save() # pylint: disable=protected-access parsed = self.config.parser._parse_files(filep, override=True) self.assertEqual([[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*'], ['listen', '5001', 'ssl'], ['#', parser.COMMENT]]]], parsed[0]) def test_choose_vhosts_alias(self): self._test_choose_vhosts_common('alias', 'server_conf') def test_choose_vhosts_example_com(self): self._test_choose_vhosts_common('example.com', 'example_conf') def test_choose_vhosts_localhost(self): self._test_choose_vhosts_common('localhost', 'localhost_conf') def test_choose_vhosts_example_com_uk_test(self): self._test_choose_vhosts_common('example.com.uk.test', 'example_conf') def test_choose_vhosts_www_example_com(self): self._test_choose_vhosts_common('www.example.com', 'example_conf') def test_choose_vhosts_test_www_example_com(self): self._test_choose_vhosts_common('test.www.example.com', 'foo_conf') def test_choose_vhosts_abc_www_foo_com(self): self._test_choose_vhosts_common('abc.www.foo.com', 'foo_conf') def test_choose_vhosts_www_bar_co_uk(self): self._test_choose_vhosts_common('www.bar.co.uk', 'localhost_conf') def test_choose_vhosts_ipv6_com(self): self._test_choose_vhosts_common('ipv6.com', 'ipv6_conf') def _test_choose_vhosts_common(self, name, conf): conf_names = {'localhost_conf': set(['localhost', r'~^(www\.)?(example|bar)\.']), 'server_conf': set(['somename', 'another.alias', 'alias']), 'example_conf': set(['.example.com', 'example.*']), 'foo_conf': set(['*.www.foo.com', '*.www.example.com']), 'ipv6_conf': set(['ipv6.com'])} conf_path = {'localhost': "etc_nginx/nginx.conf", 'alias': "etc_nginx/nginx.conf", 'example.com': "etc_nginx/sites-enabled/example.com", 'example.com.uk.test': "etc_nginx/sites-enabled/example.com", 'www.example.com': "etc_nginx/sites-enabled/example.com", 'test.www.example.com': "etc_nginx/foo.conf", 'abc.www.foo.com': "etc_nginx/foo.conf", 'www.bar.co.uk': "etc_nginx/nginx.conf", 'ipv6.com': "etc_nginx/sites-enabled/ipv6.com"} conf_path = {key: os.path.normpath(value) for key, value in conf_path.items()} vhost = self.config.choose_vhosts(name)[0] path = os.path.relpath(vhost.filep, self.temp_dir) self.assertEqual(conf_names[conf], vhost.names) self.assertEqual(conf_path[name], path) # IPv6 specific checks if name == "ipv6.com": self.assertTrue(vhost.ipv6_enabled()) # Make sure that we have SSL enabled also for IPv6 addr self.assertTrue( any([True for x in vhost.addrs if x.ssl and x.ipv6])) def test_choose_vhosts_bad(self): bad_results = ['www.foo.com', 'example', 't.www.bar.co', '69.255.225.155'] for name in bad_results: self.assertRaises(errors.MisconfigurationError, self.config.choose_vhosts, name) def test_ipv6only(self): # ipv6_info: (ipv6_active, ipv6only_present) self.assertEqual((True, False), self.config.ipv6_info("80")) # Port 443 has ipv6only=on because of ipv6ssl.com vhost self.assertEqual((True, True), self.config.ipv6_info("443")) def test_ipv6only_detection(self): self.config.version = (1, 3, 1) self.config.deploy_cert( "ipv6.com", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") for addr in self.config.choose_vhosts("ipv6.com")[0].addrs: self.assertFalse(addr.ipv6only) def test_more_info(self): self.assertTrue('nginx.conf' in self.config.more_info()) def test_deploy_cert_requires_fullchain_path(self): self.config.version = (1, 3, 1) self.assertRaises(errors.PluginError, self.config.deploy_cert, "www.example.com", "example/cert.pem", "example/key.pem", "example/chain.pem", None) @mock.patch('certbot_nginx.parser.NginxParser.update_or_add_server_directives') def test_deploy_cert_raise_on_add_error(self, mock_update_or_add_server_directives): mock_update_or_add_server_directives.side_effect = errors.MisconfigurationError() self.assertRaises( errors.PluginError, self.config.deploy_cert, "migration.com", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") def test_deploy_cert(self): server_conf = self.config.parser.abs_path('server.conf') nginx_conf = self.config.parser.abs_path('nginx.conf') example_conf = self.config.parser.abs_path('sites-enabled/example.com') self.config.version = (1, 3, 1) # Get the default SSL vhost self.config.deploy_cert( "www.example.com", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") self.config.deploy_cert( "another.alias", "/etc/nginx/cert.pem", "/etc/nginx/key.pem", "/etc/nginx/chain.pem", "/etc/nginx/fullchain.pem") self.config.save() self.config.parser.load() parsed_example_conf = util.filter_comments(self.config.parser.parsed[example_conf]) parsed_server_conf = util.filter_comments(self.config.parser.parsed[server_conf]) parsed_nginx_conf = util.filter_comments(self.config.parser.parsed[nginx_conf]) self.assertEqual([[['server'], [ ['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*'], ['listen', '5001', 'ssl'], ['ssl_certificate', 'example/fullchain.pem'], ['ssl_certificate_key', 'example/key.pem'], ['include', self.config.mod_ssl_conf], ['ssl_dhparam', self.config.ssl_dhparams], ]]], parsed_example_conf) self.assertEqual([['server_name', 'somename', 'alias', 'another.alias']], parsed_server_conf) self.assertTrue(util.contains_at_depth( parsed_nginx_conf, [['server'], [ ['listen', '8000'], ['listen', 'somename:8080'], ['include', 'server.conf'], [['location', '/'], [['root', 'html'], ['index', 'index.html', 'index.htm']]], ['listen', '5001', 'ssl'], ['ssl_certificate', '/etc/nginx/fullchain.pem'], ['ssl_certificate_key', '/etc/nginx/key.pem'], ['include', self.config.mod_ssl_conf], ['ssl_dhparam', self.config.ssl_dhparams], ]], 2)) def test_deploy_cert_add_explicit_listen(self): migration_conf = self.config.parser.abs_path('sites-enabled/migration.com') self.config.deploy_cert( "summer.com", "summer/cert.pem", "summer/key.pem", "summer/chain.pem", "summer/fullchain.pem") self.config.save() self.config.parser.load() parsed_migration_conf = util.filter_comments(self.config.parser.parsed[migration_conf]) self.assertEqual([['server'], [ ['server_name', 'migration.com'], ['server_name', 'summer.com'], ['listen', '80'], ['listen', '5001', 'ssl'], ['ssl_certificate', 'summer/fullchain.pem'], ['ssl_certificate_key', 'summer/key.pem'], ['include', self.config.mod_ssl_conf], ['ssl_dhparam', self.config.ssl_dhparams], ]], parsed_migration_conf[0]) @mock.patch("certbot_nginx.configurator.http_01.NginxHttp01.perform") @mock.patch("certbot_nginx.configurator.NginxConfigurator.restart") @mock.patch("certbot_nginx.configurator.NginxConfigurator.revert_challenge_config") def test_perform_and_cleanup(self, mock_revert, mock_restart, mock_http_perform): # Only tests functionality specific to configurator.perform # Note: As more challenges are offered this will have to be expanded achall = achallenges.KeyAuthorizationAnnotatedChallenge( challb=messages.ChallengeBody( chall=challenges.HTTP01(token=b"m8TdO1qik4JVFtgPPurJmg"), uri="https://ca.org/chall1_uri", status=messages.Status("pending"), ), domain="example.com", account_key=self.rsa512jwk) expected = [ achall.response(self.rsa512jwk), ] mock_http_perform.return_value = expected[:] responses = self.config.perform([achall]) self.assertEqual(mock_http_perform.call_count, 1) self.assertEqual(responses, expected) self.config.cleanup([achall]) self.assertEqual(0, self.config._chall_out) # pylint: disable=protected-access self.assertEqual(mock_revert.call_count, 1) self.assertEqual(mock_restart.call_count, 2) @mock.patch("certbot_nginx.configurator.subprocess.Popen") def test_get_version(self, mock_popen): mock_popen().communicate.return_value = ( "", "\n".join(["nginx version: nginx/1.4.2", "built by clang 6.0 (clang-600.0.56)" " (based on LLVM 3.5svn)", "TLS SNI support enabled", "configure arguments: --prefix=/usr/local/Cellar/" "nginx/1.6.2 --with-http_ssl_module"])) self.assertEqual(self.config.get_version(), (1, 4, 2)) mock_popen().communicate.return_value = ( "", "\n".join(["nginx version: nginx/0.9", "built by clang 6.0 (clang-600.0.56)" " (based on LLVM 3.5svn)", "TLS SNI support enabled", "configure arguments: --with-http_ssl_module"])) self.assertEqual(self.config.get_version(), (0, 9)) mock_popen().communicate.return_value = ( "", "\n".join(["blah 0.0.1", "built by clang 6.0 (clang-600.0.56)" " (based on LLVM 3.5svn)", "TLS SNI support enabled", "configure arguments: --with-http_ssl_module"])) self.assertRaises(errors.PluginError, self.config.get_version) mock_popen().communicate.return_value = ( "", "\n".join(["nginx version: nginx/1.4.2", "TLS SNI support enabled"])) self.assertRaises(errors.PluginError, self.config.get_version) mock_popen().communicate.return_value = ( "", "\n".join(["nginx version: nginx/1.4.2", "built by clang 6.0 (clang-600.0.56)" " (based on LLVM 3.5svn)", "configure arguments: --with-http_ssl_module"])) self.assertRaises(errors.PluginError, self.config.get_version) mock_popen().communicate.return_value = ( "", "\n".join(["nginx version: nginx/0.8.1", "built by clang 6.0 (clang-600.0.56)" " (based on LLVM 3.5svn)", "TLS SNI support enabled", "configure arguments: --with-http_ssl_module"])) self.assertRaises(errors.NotSupportedError, self.config.get_version) mock_popen.side_effect = OSError("Can't find program") self.assertRaises(errors.PluginError, self.config.get_version) @mock.patch("certbot_nginx.configurator.subprocess.Popen") def test_get_openssl_version(self, mock_popen): # pylint: disable=protected-access mock_popen().communicate.return_value = ( "", """ nginx version: nginx/1.15.5 built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) built with OpenSSL 1.0.2g 1 Mar 2016 TLS SNI support enabled configure arguments: """) self.assertEqual(self.config._get_openssl_version(), "1.0.2g") mock_popen().communicate.return_value = ( "", """ nginx version: nginx/1.15.5 built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) built with OpenSSL 1.0.2-beta1 1 Mar 2016 TLS SNI support enabled configure arguments: """) self.assertEqual(self.config._get_openssl_version(), "1.0.2-beta1") mock_popen().communicate.return_value = ( "", """ nginx version: nginx/1.15.5 built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) built with OpenSSL 1.0.2 1 Mar 2016 TLS SNI support enabled configure arguments: """) self.assertEqual(self.config._get_openssl_version(), "1.0.2") mock_popen().communicate.return_value = ( "", """ nginx version: nginx/1.15.5 built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) built with OpenSSL 1.0.2g 1 Mar 2016 (running with OpenSSL 1.0.2a 1 Mar 2016) TLS SNI support enabled configure arguments: """) self.assertEqual(self.config._get_openssl_version(), "1.0.2a") mock_popen().communicate.return_value = ( "", """ nginx version: nginx/1.15.5 built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) built with LibreSSL 2.2.2 TLS SNI support enabled configure arguments: """) self.assertEqual(self.config._get_openssl_version(), "") mock_popen().communicate.return_value = ( "", """ nginx version: nginx/1.15.5 built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) TLS SNI support enabled configure arguments: """) self.assertEqual(self.config._get_openssl_version(), "") @mock.patch("certbot_nginx.configurator.subprocess.Popen") def test_nginx_restart(self, mock_popen): mocked = mock_popen() mocked.communicate.return_value = ('', '') mocked.returncode = 0 self.config.restart() @mock.patch("certbot_nginx.configurator.subprocess.Popen") def test_nginx_restart_fail(self, mock_popen): mocked = mock_popen() mocked.communicate.return_value = ('', '') mocked.returncode = 1 self.assertRaises(errors.MisconfigurationError, self.config.restart) @mock.patch("certbot_nginx.configurator.subprocess.Popen") def test_no_nginx_start(self, mock_popen): mock_popen.side_effect = OSError("Can't find program") self.assertRaises(errors.MisconfigurationError, self.config.restart) @mock.patch("certbot.util.run_script") def test_config_test_bad_process(self, mock_run_script): mock_run_script.side_effect = errors.SubprocessError self.assertRaises(errors.MisconfigurationError, self.config.config_test) @mock.patch("certbot.util.run_script") def test_config_test(self, _): self.config.config_test() @mock.patch("certbot.reverter.Reverter.recovery_routine") def test_recovery_routine_throws_error_from_reverter(self, mock_recovery_routine): mock_recovery_routine.side_effect = errors.ReverterError("foo") self.assertRaises(errors.PluginError, self.config.recovery_routine) @mock.patch("certbot.reverter.Reverter.rollback_checkpoints") def test_rollback_checkpoints_throws_error_from_reverter(self, mock_rollback_checkpoints): mock_rollback_checkpoints.side_effect = errors.ReverterError("foo") self.assertRaises(errors.PluginError, self.config.rollback_checkpoints) @mock.patch("certbot.reverter.Reverter.revert_temporary_config") def test_revert_challenge_config_throws_error_from_reverter(self, mock_revert_temporary_config): mock_revert_temporary_config.side_effect = errors.ReverterError("foo") self.assertRaises(errors.PluginError, self.config.revert_challenge_config) @mock.patch("certbot.reverter.Reverter.add_to_checkpoint") def test_save_throws_error_from_reverter(self, mock_add_to_checkpoint): mock_add_to_checkpoint.side_effect = errors.ReverterError("foo") self.assertRaises(errors.PluginError, self.config.save) def test_get_snakeoil_paths(self): # pylint: disable=protected-access cert, key = self.config._get_snakeoil_paths() self.assertTrue(os.path.exists(cert)) self.assertTrue(os.path.exists(key)) with open(cert) as cert_file: OpenSSL.crypto.load_certificate( OpenSSL.crypto.FILETYPE_PEM, cert_file.read()) with open(key) as key_file: OpenSSL.crypto.load_privatekey( OpenSSL.crypto.FILETYPE_PEM, key_file.read()) def test_redirect_enhance(self): # Test that we successfully add a redirect when there is # a listen directive expected = UnspacedList(_redirect_block_for_domain("www.example.com"))[0] example_conf = self.config.parser.abs_path('sites-enabled/example.com') self.config.enhance("www.example.com", "redirect") generated_conf = self.config.parser.parsed[example_conf] self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) # Test that we successfully add a redirect when there is # no listen directive migration_conf = self.config.parser.abs_path('sites-enabled/migration.com') self.config.enhance("migration.com", "redirect") expected = UnspacedList(_redirect_block_for_domain("migration.com"))[0] generated_conf = self.config.parser.parsed[migration_conf] self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) def test_split_for_redirect(self): example_conf = self.config.parser.abs_path('sites-enabled/example.com') self.config.deploy_cert( "example.org", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") self.config.enhance("www.example.com", "redirect") generated_conf = self.config.parser.parsed[example_conf] self.assertEqual( [[['server'], [ ['server_name', '.example.com'], ['server_name', 'example.*'], [], ['listen', '5001', 'ssl'], ['#', ' managed by Certbot'], ['ssl_certificate', 'example/fullchain.pem'], ['#', ' managed by Certbot'], ['ssl_certificate_key', 'example/key.pem'], ['#', ' managed by Certbot'], ['include', self.config.mod_ssl_conf], ['#', ' managed by Certbot'], ['ssl_dhparam', self.config.ssl_dhparams], ['#', ' managed by Certbot'], [], []]], [['server'], [ [['if', '($host', '=', 'www.example.com)'], [ ['return', '301', 'https://$host$request_uri']]], ['#', ' managed by Certbot'], [], ['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*'], ['return', '404'], ['#', ' managed by Certbot'], [], [], []]]], generated_conf) def test_split_for_headers(self): example_conf = self.config.parser.abs_path('sites-enabled/example.com') self.config.deploy_cert( "example.org", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") self.config.enhance("www.example.com", "ensure-http-header", "Strict-Transport-Security") generated_conf = self.config.parser.parsed[example_conf] self.assertEqual( [[['server'], [ ['server_name', '.example.com'], ['server_name', 'example.*'], [], ['listen', '5001', 'ssl'], ['#', ' managed by Certbot'], ['ssl_certificate', 'example/fullchain.pem'], ['#', ' managed by Certbot'], ['ssl_certificate_key', 'example/key.pem'], ['#', ' managed by Certbot'], ['include', self.config.mod_ssl_conf], ['#', ' managed by Certbot'], ['ssl_dhparam', self.config.ssl_dhparams], ['#', ' managed by Certbot'], [], [], ['add_header', 'Strict-Transport-Security', '"max-age=31536000"', 'always'], ['#', ' managed by Certbot'], [], []]], [['server'], [ ['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*'], [], [], []]]], generated_conf) def test_http_header_hsts(self): example_conf = self.config.parser.abs_path('sites-enabled/example.com') self.config.enhance("www.example.com", "ensure-http-header", "Strict-Transport-Security") expected = ['add_header', 'Strict-Transport-Security', '"max-age=31536000"', 'always'] generated_conf = self.config.parser.parsed[example_conf] self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) def test_multiple_headers_hsts(self): headers_conf = self.config.parser.abs_path('sites-enabled/headers.com') self.config.enhance("headers.com", "ensure-http-header", "Strict-Transport-Security") expected = ['add_header', 'Strict-Transport-Security', '"max-age=31536000"', 'always'] generated_conf = self.config.parser.parsed[headers_conf] self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) def test_http_header_hsts_twice(self): self.config.enhance("www.example.com", "ensure-http-header", "Strict-Transport-Security") self.assertRaises( errors.PluginEnhancementAlreadyPresent, self.config.enhance, "www.example.com", "ensure-http-header", "Strict-Transport-Security") @mock.patch('certbot_nginx.obj.VirtualHost.contains_list') def test_certbot_redirect_exists(self, mock_contains_list): # Test that we add no redirect statement if there is already a # redirect in the block that is managed by certbot # Has a certbot redirect mock_contains_list.return_value = True with mock.patch("certbot_nginx.configurator.logger") as mock_logger: self.config.enhance("www.example.com", "redirect") self.assertEqual(mock_logger.info.call_args[0][0], "Traffic on port %s already redirecting to ssl in %s") def test_redirect_dont_enhance(self): # Test that we don't accidentally add redirect to ssl-only block with mock.patch("certbot_nginx.configurator.logger") as mock_logger: self.config.enhance("geese.com", "redirect") self.assertEqual(mock_logger.info.call_args[0][0], 'No matching insecure server blocks listening on port %s found.') def test_double_redirect(self): # Test that we add one redirect for each domain example_conf = self.config.parser.abs_path('sites-enabled/example.com') self.config.enhance("example.com", "redirect") self.config.enhance("example.org", "redirect") expected1 = UnspacedList(_redirect_block_for_domain("example.com"))[0] expected2 = UnspacedList(_redirect_block_for_domain("example.org"))[0] generated_conf = self.config.parser.parsed[example_conf] self.assertTrue(util.contains_at_depth(generated_conf, expected1, 2)) self.assertTrue(util.contains_at_depth(generated_conf, expected2, 2)) def test_staple_ocsp_bad_version(self): self.config.version = (1, 3, 1) self.assertRaises(errors.PluginError, self.config.enhance, "www.example.com", "staple-ocsp", "chain_path") def test_staple_ocsp_no_chain_path(self): self.assertRaises(errors.PluginError, self.config.enhance, "www.example.com", "staple-ocsp", None) def test_staple_ocsp_internal_error(self): self.config.enhance("www.example.com", "staple-ocsp", "chain_path") # error is raised because the server block has conflicting directives self.assertRaises(errors.PluginError, self.config.enhance, "www.example.com", "staple-ocsp", "different_path") def test_staple_ocsp(self): chain_path = "example/chain.pem" self.config.enhance("www.example.com", "staple-ocsp", chain_path) example_conf = self.config.parser.abs_path('sites-enabled/example.com') generated_conf = self.config.parser.parsed[example_conf] self.assertTrue(util.contains_at_depth( generated_conf, ['ssl_trusted_certificate', 'example/chain.pem'], 2)) self.assertTrue(util.contains_at_depth( generated_conf, ['ssl_stapling', 'on'], 2)) self.assertTrue(util.contains_at_depth( generated_conf, ['ssl_stapling_verify', 'on'], 2)) def test_deploy_no_match_default_set(self): default_conf = self.config.parser.abs_path('sites-enabled/default') foo_conf = self.config.parser.abs_path('foo.conf') del self.config.parser.parsed[foo_conf][2][1][0][1][0] # remove default_server self.config.version = (1, 3, 1) self.config.deploy_cert( "www.nomatch.com", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") self.config.save() self.config.parser.load() parsed_default_conf = util.filter_comments(self.config.parser.parsed[default_conf]) self.assertEqual([[['server'], [['listen', 'myhost', 'default_server'], ['listen', 'otherhost', 'default_server'], ['server_name', '"www.example.org"'], [['location', '/'], [['root', 'html'], ['index', 'index.html', 'index.htm']]]]], [['server'], [['listen', 'myhost'], ['listen', 'otherhost'], ['server_name', 'www.nomatch.com'], [['location', '/'], [['root', 'html'], ['index', 'index.html', 'index.htm']]], ['listen', '5001', 'ssl'], ['ssl_certificate', 'example/fullchain.pem'], ['ssl_certificate_key', 'example/key.pem'], ['include', self.config.mod_ssl_conf], ['ssl_dhparam', self.config.ssl_dhparams]]]], parsed_default_conf) self.config.deploy_cert( "nomatch.com", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") self.config.save() self.config.parser.load() parsed_default_conf = util.filter_comments(self.config.parser.parsed[default_conf]) self.assertTrue(util.contains_at_depth(parsed_default_conf, "nomatch.com", 3)) def test_deploy_no_match_default_set_multi_level_path(self): default_conf = self.config.parser.abs_path('sites-enabled/default') foo_conf = self.config.parser.abs_path('foo.conf') del self.config.parser.parsed[default_conf][0][1][0] del self.config.parser.parsed[default_conf][0][1][0] self.config.version = (1, 3, 1) self.config.deploy_cert( "www.nomatch.com", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") self.config.save() self.config.parser.load() parsed_foo_conf = util.filter_comments(self.config.parser.parsed[foo_conf]) self.assertEqual([['server'], [['listen', '*:80', 'ssl'], ['server_name', 'www.nomatch.com'], ['root', '/home/ubuntu/sites/foo/'], [['location', '/status'], [[['types'], [['image/jpeg', 'jpg']]]]], [['location', '~', 'case_sensitive\\.php$'], [['index', 'index.php'], ['root', '/var/root']]], [['location', '~*', 'case_insensitive\\.php$'], []], [['location', '=', 'exact_match\\.php$'], []], [['location', '^~', 'ignore_regex\\.php$'], []], ['ssl_certificate', 'example/fullchain.pem'], ['ssl_certificate_key', 'example/key.pem']]], parsed_foo_conf[1][1][1]) def test_deploy_no_match_no_default_set(self): default_conf = self.config.parser.abs_path('sites-enabled/default') foo_conf = self.config.parser.abs_path('foo.conf') del self.config.parser.parsed[default_conf][0][1][0] del self.config.parser.parsed[default_conf][0][1][0] del self.config.parser.parsed[foo_conf][2][1][0][1][0] self.config.version = (1, 3, 1) self.assertRaises(errors.MisconfigurationError, self.config.deploy_cert, "www.nomatch.com", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") def test_deploy_no_match_fail_multiple_defaults(self): self.config.version = (1, 3, 1) self.assertRaises(errors.MisconfigurationError, self.config.deploy_cert, "www.nomatch.com", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") def test_deploy_no_match_multiple_defaults_ok(self): foo_conf = self.config.parser.abs_path('foo.conf') self.config.parser.parsed[foo_conf][2][1][0][1][0][1] = '*:5001' self.config.version = (1, 3, 1) self.config.deploy_cert("www.nomatch.com", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") def test_deploy_no_match_add_redirect(self): default_conf = self.config.parser.abs_path('sites-enabled/default') foo_conf = self.config.parser.abs_path('foo.conf') del self.config.parser.parsed[foo_conf][2][1][0][1][0] # remove default_server self.config.version = (1, 3, 1) self.config.deploy_cert( "www.nomatch.com", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") self.config.deploy_cert( "nomatch.com", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") self.config.enhance("www.nomatch.com", "redirect") self.config.save() self.config.parser.load() expected = UnspacedList(_redirect_block_for_domain("www.nomatch.com"))[0] generated_conf = self.config.parser.parsed[default_conf] self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) @mock.patch('certbot.reverter.logger') @mock.patch('certbot_nginx.parser.NginxParser.load') def test_parser_reload_after_config_changes(self, mock_parser_load, unused_mock_logger): self.config.recovery_routine() self.config.revert_challenge_config() self.config.rollback_checkpoints() self.assertTrue(mock_parser_load.call_count == 3) def test_choose_vhosts_wildcard(self): # pylint: disable=protected-access mock_path = "certbot_nginx.display_ops.select_vhost_multiple" with mock.patch(mock_path) as mock_select_vhs: vhost = [x for x in self.config.parser.get_vhosts() if 'summer.com' in x.names][0] mock_select_vhs.return_value = [vhost] vhs = self.config._choose_vhosts_wildcard("*.com", prefer_ssl=True) # Check that the dialog was called with migration.com self.assertTrue(vhost in mock_select_vhs.call_args[0][0]) # And the actual returned values self.assertEqual(len(vhs), 1) self.assertEqual(vhs[0], vhost) def test_choose_vhosts_wildcard_redirect(self): # pylint: disable=protected-access mock_path = "certbot_nginx.display_ops.select_vhost_multiple" with mock.patch(mock_path) as mock_select_vhs: vhost = [x for x in self.config.parser.get_vhosts() if 'summer.com' in x.names][0] mock_select_vhs.return_value = [vhost] vhs = self.config._choose_vhosts_wildcard("*.com", prefer_ssl=False) # Check that the dialog was called with migration.com self.assertTrue(vhost in mock_select_vhs.call_args[0][0]) # And the actual returned values self.assertEqual(len(vhs), 1) self.assertEqual(vhs[0], vhost) def test_deploy_cert_wildcard(self): # pylint: disable=protected-access mock_choose_vhosts = mock.MagicMock() vhost = [x for x in self.config.parser.get_vhosts() if 'geese.com' in x.names][0] mock_choose_vhosts.return_value = [vhost] self.config._choose_vhosts_wildcard = mock_choose_vhosts mock_d = "certbot_nginx.configurator.NginxConfigurator._deploy_cert" with mock.patch(mock_d) as mock_dep: self.config.deploy_cert("*.com", "/tmp/path", "/tmp/path", "/tmp/path", "/tmp/path") self.assertTrue(mock_dep.called) self.assertEqual(len(mock_dep.call_args_list), 1) self.assertEqual(vhost, mock_dep.call_args_list[0][0][0]) @mock.patch("certbot_nginx.display_ops.select_vhost_multiple") def test_deploy_cert_wildcard_no_vhosts(self, mock_dialog): # pylint: disable=protected-access mock_dialog.return_value = [] self.assertRaises(errors.PluginError, self.config.deploy_cert, "*.wild.cat", "/tmp/path", "/tmp/path", "/tmp/path", "/tmp/path") @mock.patch("certbot_nginx.display_ops.select_vhost_multiple") def test_enhance_wildcard_ocsp_after_install(self, mock_dialog): # pylint: disable=protected-access vhost = [x for x in self.config.parser.get_vhosts() if 'geese.com' in x.names][0] self.config._wildcard_vhosts["*.com"] = [vhost] self.config.enhance("*.com", "staple-ocsp", "example/chain.pem") self.assertFalse(mock_dialog.called) @mock.patch("certbot_nginx.display_ops.select_vhost_multiple") def test_enhance_wildcard_redirect_or_ocsp_no_install(self, mock_dialog): vhost = [x for x in self.config.parser.get_vhosts() if 'summer.com' in x.names][0] mock_dialog.return_value = [vhost] self.config.enhance("*.com", "staple-ocsp", "example/chain.pem") self.assertTrue(mock_dialog.called) @mock.patch("certbot_nginx.display_ops.select_vhost_multiple") def test_enhance_wildcard_double_redirect(self, mock_dialog): # pylint: disable=protected-access vhost = [x for x in self.config.parser.get_vhosts() if 'summer.com' in x.names][0] self.config._wildcard_redirect_vhosts["*.com"] = [vhost] self.config.enhance("*.com", "redirect") self.assertFalse(mock_dialog.called) def test_choose_vhosts_wildcard_no_ssl_filter_port(self): # pylint: disable=protected-access mock_path = "certbot_nginx.display_ops.select_vhost_multiple" with mock.patch(mock_path) as mock_select_vhs: mock_select_vhs.return_value = [] self.config._choose_vhosts_wildcard("*.com", 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) class InstallSslOptionsConfTest(util.NginxTest): """Test that the options-ssl-nginx.conf file is installed and updated properly.""" def setUp(self): super(InstallSslOptionsConfTest, self).setUp() self.config = util.get_nginx_configurator( self.config_path, self.config_dir, self.work_dir, self.logs_dir) def _call(self): self.config.install_ssl_options_conf(self.config.mod_ssl_conf, self.config.updated_mod_ssl_conf_digest) def _current_ssl_options_hash(self): return crypto_util.sha256sum(self.config.mod_ssl_conf_src) def _assert_current_file(self): self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), self._current_ssl_options_hash()) def test_no_file(self): # prepare should have placed a file there self._assert_current_file() os.remove(self.config.mod_ssl_conf) self.assertFalse(os.path.isfile(self.config.mod_ssl_conf)) self._call() self._assert_current_file() def test_current_file(self): self._assert_current_file() self._call() self._assert_current_file() def _mock_hash_except_ssl_conf_src(self, fake_hash): # Write a bad file in place so that update tests fail if no update occurs. # We're going to pretend this file (the currently installed conf file) # actually hashes to `fake_hash` for the update tests. with open(self.config.mod_ssl_conf, "w") as f: f.write("bogus") sha256 = crypto_util.sha256sum def _hash(filename): return sha256(filename) if filename == self.config.mod_ssl_conf_src else fake_hash return _hash def test_prev_file_updates_to_current(self): from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES with mock.patch('certbot.crypto_util.sha256sum', new=self._mock_hash_except_ssl_conf_src(ALL_SSL_OPTIONS_HASHES[0])): self._call() self._assert_current_file() def test_prev_file_updates_to_current_old_nginx(self): from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES self.config.version = (1, 5, 8) with mock.patch('certbot.crypto_util.sha256sum', new=self._mock_hash_except_ssl_conf_src(ALL_SSL_OPTIONS_HASHES[0])): self._call() self._assert_current_file() def test_manually_modified_current_file_does_not_update(self): with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: mod_ssl_conf.write("a new line for the wrong hash\n") with mock.patch("certbot.plugins.common.logger") as mock_logger: self._call() self.assertFalse(mock_logger.warning.called) self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf_src), self._current_ssl_options_hash()) self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), self._current_ssl_options_hash()) def test_manually_modified_past_file_warns(self): with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: mod_ssl_conf.write("a new line for the wrong hash\n") with open(self.config.updated_mod_ssl_conf_digest, "w") as f: f.write("hashofanoldversion") with mock.patch("certbot.plugins.common.logger") as mock_logger: self._call() self.assertEqual(mock_logger.warning.call_args[0][0], "%s has been manually modified; updated file " "saved to %s. We recommend updating %s for security purposes.") self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf_src), self._current_ssl_options_hash()) # only print warning once with mock.patch("certbot.plugins.common.logger") as mock_logger: self._call() self.assertFalse(mock_logger.warning.called) def test_current_file_hash_in_all_hashes(self): from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES self.assertTrue(self._current_ssl_options_hash() in ALL_SSL_OPTIONS_HASHES, "Constants.ALL_SSL_OPTIONS_HASHES must be appended" " with the sha256 hash of self.config.mod_ssl_conf when it is updated.") def test_ssl_config_files_hash_in_all_hashes(self): """ It is really critical that all TLS Nginx config files have their SHA256 hash registered in constants.ALL_SSL_OPTIONS_HASHES. Otherwise Certbot will mistakenly assume that the config file has been manually edited by the user, and will refuse to update it. This test ensures that all necessary hashes are present. """ from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES import pkg_resources all_files = [ pkg_resources.resource_filename("certbot_nginx", os.path.join("tls_configs", x)) for x in ("options-ssl-nginx.conf", "options-ssl-nginx-old.conf", "options-ssl-nginx-tls12-only.conf") ] self.assertTrue(all_files) for one_file in all_files: file_hash = crypto_util.sha256sum(one_file) self.assertTrue(file_hash in ALL_SSL_OPTIONS_HASHES, "Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " "hash of {0} when it is updated.".format(one_file)) def test_nginx_version_uses_correct_config(self): self.config.version = (1, 5, 8) self.config.openssl_version = "1.0.2g" # shouldn't matter self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), "options-ssl-nginx-old.conf") self._call() self._assert_current_file() self.config.version = (1, 5, 9) self.config.openssl_version = "1.0.2l" self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), "options-ssl-nginx-tls12-only.conf") self._call() self._assert_current_file() self.config.version = (1, 13, 0) self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), "options-ssl-nginx.conf") self._call() self._assert_current_file() self.config.version = (1, 13, 0) self.config.openssl_version = "1.0.2k" self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), "options-ssl-nginx-tls13-session-tix-on.conf") class DetermineDefaultServerRootTest(certbot_test_util.ConfigTestCase): """Tests for certbot_nginx.configurator._determine_default_server_root.""" def _call(self): from certbot_nginx.configurator import _determine_default_server_root return _determine_default_server_root() @mock.patch.dict(os.environ, {"CERTBOT_DOCS": "1"}) def test_docs_value(self): self._test(expect_both_values=True) @mock.patch.dict(os.environ, {}) def test_real_values(self): self._test(expect_both_values=False) def _test(self, expect_both_values): server_root = self._call() if expect_both_values: self.assertIn("/usr/local/etc/nginx", server_root) self.assertIn("/etc/nginx", server_root) else: self.assertTrue(server_root == "/etc/nginx" or server_root == "/usr/local/etc/nginx") if __name__ == "__main__": unittest.main() # pragma: no cover certbot-nginx-0.40.0/certbot_nginx/tests/__init__.py0000644000175000017500000000003213560356424022671 0ustar ericaerica00000000000000"""Certbot Nginx Tests""" certbot-nginx-0.40.0/certbot_nginx/tests/obj_test.py0000644000175000017500000002305613560356424022756 0ustar ericaerica00000000000000"""Test the helper objects in certbot_nginx.obj.""" import unittest import itertools class AddrTest(unittest.TestCase): """Test the Addr class.""" def setUp(self): from certbot_nginx.obj import Addr self.addr1 = Addr.fromstring("192.168.1.1") self.addr2 = Addr.fromstring("192.168.1.1:* ssl") self.addr3 = Addr.fromstring("192.168.1.1:80") self.addr4 = Addr.fromstring("*:80 default_server ssl") self.addr5 = Addr.fromstring("myhost") self.addr6 = Addr.fromstring("80 default_server spdy") self.addr7 = Addr.fromstring("unix:/var/run/nginx.sock") self.addr8 = Addr.fromstring("*:80 default ssl") def test_fromstring(self): self.assertEqual(self.addr1.get_addr(), "192.168.1.1") self.assertEqual(self.addr1.get_port(), "") self.assertFalse(self.addr1.ssl) self.assertFalse(self.addr1.default) self.assertEqual(self.addr2.get_addr(), "192.168.1.1") self.assertEqual(self.addr2.get_port(), "*") self.assertTrue(self.addr2.ssl) self.assertFalse(self.addr2.default) self.assertEqual(self.addr3.get_addr(), "192.168.1.1") self.assertEqual(self.addr3.get_port(), "80") self.assertFalse(self.addr3.ssl) self.assertFalse(self.addr3.default) self.assertEqual(self.addr4.get_addr(), "*") self.assertEqual(self.addr4.get_port(), "80") self.assertTrue(self.addr4.ssl) self.assertTrue(self.addr4.default) self.assertEqual(self.addr5.get_addr(), "myhost") self.assertEqual(self.addr5.get_port(), "") self.assertFalse(self.addr5.ssl) self.assertFalse(self.addr5.default) self.assertEqual(self.addr6.get_addr(), "") self.assertEqual(self.addr6.get_port(), "80") self.assertFalse(self.addr6.ssl) self.assertTrue(self.addr6.default) self.assertTrue(self.addr8.default) self.assertEqual(None, self.addr7) def test_str(self): self.assertEqual(str(self.addr1), "192.168.1.1") self.assertEqual(str(self.addr2), "192.168.1.1:* ssl") self.assertEqual(str(self.addr3), "192.168.1.1:80") self.assertEqual(str(self.addr4), "*:80 default_server ssl") self.assertEqual(str(self.addr5), "myhost") self.assertEqual(str(self.addr6), "80 default_server") self.assertEqual(str(self.addr8), "*:80 default_server ssl") def test_to_string(self): self.assertEqual(self.addr1.to_string(), "192.168.1.1") self.assertEqual(self.addr2.to_string(), "192.168.1.1:* ssl") self.assertEqual(self.addr3.to_string(), "192.168.1.1:80") self.assertEqual(self.addr4.to_string(), "*:80 default_server ssl") self.assertEqual(self.addr4.to_string(include_default=False), "*:80 ssl") self.assertEqual(self.addr5.to_string(), "myhost") self.assertEqual(self.addr6.to_string(), "80 default_server") self.assertEqual(self.addr6.to_string(include_default=False), "80") def test_eq(self): from certbot_nginx.obj import Addr new_addr1 = Addr.fromstring("192.168.1.1 spdy") self.assertEqual(self.addr1, new_addr1) self.assertNotEqual(self.addr1, self.addr2) self.assertFalse(self.addr1 == 3333) def test_equivalent_any_addresses(self): from certbot_nginx.obj import Addr any_addresses = ("0.0.0.0:80 default_server ssl", "80 default_server ssl", "*:80 default_server ssl", "80 default ssl") for first, second in itertools.combinations(any_addresses, 2): self.assertEqual(Addr.fromstring(first), Addr.fromstring(second)) # Also, make sure ports are checked. self.assertNotEqual(Addr.fromstring(any_addresses[0]), Addr.fromstring("0.0.0.0:443 default_server ssl")) # And they aren't equivalent to a specified address. for any_address in any_addresses: self.assertNotEqual( Addr.fromstring("192.168.1.2:80 default_server ssl"), Addr.fromstring(any_address)) def test_set_inclusion(self): from certbot_nginx.obj import Addr set_a = set([self.addr1, self.addr2]) addr1b = Addr.fromstring("192.168.1.1") addr2b = Addr.fromstring("192.168.1.1:* ssl") set_b = set([addr1b, addr2b]) self.assertEqual(set_a, set_b) class VirtualHostTest(unittest.TestCase): """Test the VirtualHost class.""" def setUp(self): from certbot_nginx.obj import VirtualHost from certbot_nginx.obj import Addr raw1 = [ ['listen', '69.50.225.155:9000'], [['if', '($scheme', '!=', '"https") '], [['return', '301', 'https://$host$request_uri']] ], ['#', ' managed by Certbot'] ] self.vhost1 = VirtualHost( "filep", set([Addr.fromstring("localhost")]), False, False, set(['localhost']), raw1, []) raw2 = [ ['listen', '69.50.225.155:9000'], [['if', '($scheme', '!=', '"https") '], [['return', '301', 'https://$host$request_uri']] ] ] self.vhost2 = VirtualHost( "filep", set([Addr.fromstring("localhost")]), False, False, set(['localhost']), raw2, []) raw3 = [ ['listen', '69.50.225.155:9000'], ['rewrite', '^(.*)$', '$scheme://www.domain.com$1', 'permanent'] ] self.vhost3 = VirtualHost( "filep", set([Addr.fromstring("localhost")]), False, False, set(['localhost']), raw3, []) raw4 = [ ['listen', '69.50.225.155:9000'], ['server_name', 'return.com'] ] self.vhost4 = VirtualHost( "filp", set([Addr.fromstring("localhost")]), False, False, set(['localhost']), raw4, []) raw_has_hsts = [ ['listen', '69.50.225.155:9000'], ['server_name', 'return.com'], ['add_header', 'always', 'set', 'Strict-Transport-Security', '\"max-age=31536000\"'], ] self.vhost_has_hsts = VirtualHost( "filep", set([Addr.fromstring("localhost")]), False, False, set(['localhost']), raw_has_hsts, []) def test_eq(self): from certbot_nginx.obj import Addr from certbot_nginx.obj import VirtualHost vhost1b = VirtualHost( "filep", set([Addr.fromstring("localhost blah")]), False, False, set(['localhost']), [], []) self.assertEqual(vhost1b, self.vhost1) self.assertEqual(str(vhost1b), str(self.vhost1)) self.assertFalse(vhost1b == 1234) def test_str(self): stringified = '\n'.join(['file: filep', 'addrs: localhost', "names: ['localhost']", 'ssl: False', 'enabled: False']) self.assertEqual(stringified, str(self.vhost1)) def test_has_header(self): self.assertTrue(self.vhost_has_hsts.has_header('Strict-Transport-Security')) self.assertFalse(self.vhost_has_hsts.has_header('Bogus-Header')) self.assertFalse(self.vhost1.has_header('Strict-Transport-Security')) self.assertFalse(self.vhost1.has_header('Bogus-Header')) def test_contains_list(self): from certbot_nginx.obj import VirtualHost from certbot_nginx.obj import Addr from certbot_nginx.configurator import _test_block_from_block test_block = [ ['\n ', 'return', ' ', '301', ' ', 'https://$host$request_uri'], ['\n'] ] test_needle = _test_block_from_block(test_block) test_haystack = [['listen', '80'], ['root', '/var/www/html'], ['index', 'index.html index.htm index.nginx-debian.html'], ['server_name', 'two.functorkitten.xyz'], ['listen', '443 ssl'], ['#', ' managed by Certbot'], ['ssl_certificate', '/etc/letsencrypt/live/two.functorkitten.xyz/fullchain.pem'], ['#', ' managed by Certbot'], ['ssl_certificate_key', '/etc/letsencrypt/live/two.functorkitten.xyz/privkey.pem'], ['#', ' managed by Certbot'], ['return', '301', 'https://$host$request_uri'], ['#', ' managed by Certbot'], []] vhost_haystack = VirtualHost( "filp", set([Addr.fromstring("localhost")]), False, False, set(['localhost']), test_haystack, []) test_bad_haystack = [['listen', '80'], ['root', '/var/www/html'], ['index', 'index.html index.htm index.nginx-debian.html'], ['server_name', 'two.functorkitten.xyz'], ['listen', '443 ssl'], ['#', ' managed by Certbot'], ['ssl_certificate', '/etc/letsencrypt/live/two.functorkitten.xyz/fullchain.pem'], ['#', ' managed by Certbot'], ['ssl_certificate_key', '/etc/letsencrypt/live/two.functorkitten.xyz/privkey.pem'], ['#', ' managed by Certbot'], [['if', '($scheme', '!=', '"https")'], [['return', '302', 'https://$host$request_uri']] ], ['#', ' managed by Certbot'], []] vhost_bad_haystack = VirtualHost( "filp", set([Addr.fromstring("localhost")]), False, False, set(['localhost']), test_bad_haystack, []) self.assertTrue(vhost_haystack.contains_list(test_needle)) self.assertFalse(vhost_bad_haystack.contains_list(test_needle)) if __name__ == "__main__": unittest.main() # pragma: no cover certbot-nginx-0.40.0/certbot_nginx/tests/testdata/0002755000175000017500000000000013560356440022376 5ustar ericaerica00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/0002755000175000017500000000000013560356440024354 5ustar ericaerica00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/broken.conf0000644000175000017500000000017513560356424026506 0ustar ericaerica00000000000000# A faulty configuration file pid logs/nginx.pid; events { worker_connections 1024; } include foo.conf; @@@ certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/multiline_quotes.conf0000644000175000017500000000111113560356424030617 0ustar ericaerica00000000000000# Test nginx configuration file with multiline quoted strings. # Good example of usage for multilined quoted values is when # using Openresty's Lua directives and you wish to keep the # inline Lua code readable. http { server { listen *:443; # because there should be no other port open. location / { body_filter_by_lua 'ngx.ctx.buffered = (ngx.ctx.buffered or "") .. string.sub(ngx.arg[1], 1, 1000) if ngx.arg[2] then ngx.var.resp_body = ngx.ctx.buffered end'; } } } certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/edge_cases.conf0000644000175000017500000000110313560356424027300 0ustar ericaerica00000000000000# This is not a valid nginx config file but it tests edge cases in valid nginx syntax server { server_name simple; } server { server_name with.if; location ~ ^/services/.+$ { if ($request_filename ~* \.(ttf|woff)$) { add_header Access-Control-Allow-Origin "*"; } } } server { server_name with.complicated.headers; location ~* \.(?:gif|jpe?g|png)$ { add_header Pragma public; add_header Cache-Control 'public, must-revalidate, proxy-revalidate' "test,;{}" foo; blah "hello;world"; try_files $uri @rewrites; } } certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/minimalistic_comments.conf0000644000175000017500000000030513560356424031610 0ustar ericaerica00000000000000# Use bar.conf when it's a full moon! include foo.conf; # Kilroy was here check_status; server { # # Don't forget to open up your firewall! # listen 1234; # listen 80; } certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/nginx.conf0000644000175000017500000000517413560356424026355 0ustar ericaerica00000000000000# standard default nginx config user nobody; worker_processes 1; error_log logs/error.log; error_log logs/error.log notice; error_log logs/error.log info; pid logs/nginx.pid; events { worker_connections 1024; } empty { } include foo.conf; http { include mime.types; include sites-enabled/*; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log logs/access.log main; sendfile on; tcp_nopush on; keepalive_timeout 0; gzip on; server { listen 8080; server_name localhost; server_name ~^(www\.)?(example|bar)\.; charset koi8-r; access_log logs/host.access.log main; location / { root html; index index.html index.htm; } error_page 404 /404.html; # redirect server error pages to the static page /50x.html error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } # proxy the PHP scripts to Nginx listening on 127.0.0.1:80 # location ~ \.php$ { proxy_pass http://127.0.0.1; } # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # location ~ \.php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; } # deny access to .htaccess files, if Nginx's document root # concurs with nginx's one # location ~ /\.ht { deny all; } } # another virtual host using mix of IP-, name-, and port-based configuration # server { listen 8000; listen somename:8080; include server.conf; location / { root html; index index.html index.htm; } } # HTTPS server # #server { # listen 443 ssl; # server_name localhost; # ssl_certificate cert.pem; # ssl_certificate_key cert.key; # ssl_session_cache shared:SSL:1m; # ssl_session_timeout 5m; # ssl_ciphers HIGH:!aNULL:!MD5; # ssl_prefer_server_ciphers on; # location / { # root html; # index index.html index.htm; # } #} #include conf.d/test.conf; } certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/foo.conf0000644000175000017500000000103413560356424026004 0ustar ericaerica00000000000000# a test nginx conf user www-data; http { server { listen *:80 default_server ssl; server_name *.www.foo.com *.www.example.com; root /home/ubuntu/sites/foo/; location /status { types { image/jpeg jpg; } } location ~ case_sensitive\.php$ { index index.php; root /var/root; } location ~* case_insensitive\.php$ {} location = exact_match\.php$ {} location ^~ ignore_regex\.php$ {} } } certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/0002755000175000017500000000000013560356440027771 5ustar ericaerica00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/0002755000175000017500000000000013560356440032640 5ustar ericaerica00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/0002755000175000017500000000000013560356440033763 5ustar ericaerica00000000000000././@LongLink0000000000000000000000000000015700000000000011220 Lustar 00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_paramscertbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/s0000644000175000017500000000072113560356424034150 0ustar ericaerica00000000000000scgi_param REQUEST_METHOD $request_method; scgi_param REQUEST_URI $request_uri; scgi_param QUERY_STRING $query_string; scgi_param CONTENT_TYPE $content_type; scgi_param DOCUMENT_URI $document_uri; scgi_param DOCUMENT_ROOT $document_root; scgi_param SCGI 1; scgi_param SERVER_PROTOCOL $server_protocol; scgi_param REMOTE_ADDR $remote_addr; scgi_param REMOTE_PORT $remote_port; scgi_param SERVER_PORT $server_port; scgi_param SERVER_NAME $server_name; ././@LongLink0000000000000000000000000000016400000000000011216 Lustar 00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rulescertbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/n0000644000175000017500000001225013560356424034143 0ustar ericaerica00000000000000################################## ## INTERNAL RULES IDS:1-10 ## ################################## #weird_request : 1 #big_body : 2 #no_content_type : 3 #MainRule "str:yesone" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999; ################################## ## SQL Injections IDs:1000-1099 ## ################################## MainRule "rx:select|union|update|delete|insert|table|from|ascii|hex|unhex" "msg:sql keywords" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1000; MainRule "str:\"" "msg:double quote" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1001; MainRule "str:0x" "msg:0x, possible hex encoding" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:2" id:1002; ## Hardcore rules MainRule "str:/*" "msg:mysql comment (/*)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1003; MainRule "str:*/" "msg:mysql comment (*/)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1004; MainRule "str:|" "msg:mysql keyword (|)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1005; MainRule "rx:&&" "msg:mysql keyword (&&)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1006; ## end of hardcore rules MainRule "str:--" "msg:mysql comment (--)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1007; MainRule "str:;" "msg:; in stuff" "mz:BODY|URL|ARGS" "s:$SQL:4" id:1008; MainRule "str:=" "msg:equal in var, probable sql/xss" "mz:ARGS|BODY" "s:$SQL:2" id:1009; MainRule "str:(" "msg:parenthesis, probable sql/xss" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1010; MainRule "str:)" "msg:parenthesis, probable sql/xss" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1011; MainRule "str:'" "msg:simple quote" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1013; MainRule "str:\"" "msg:double quote" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1014; MainRule "str:," "msg:, in stuff" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1015; MainRule "str:#" "msg:mysql comment (#)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1016; ############################### ## OBVIOUS RFI IDs:1100-1199 ## ############################### MainRule "str:http://" "msg:html comment tag" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1100; MainRule "str:https://" "msg:html comment tag" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1101; MainRule "str:ftp://" "msg:html comment tag" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1102; MainRule "str:php://" "msg:html comment tag" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1103; ####################################### ## Directory traversal IDs:1200-1299 ## ####################################### MainRule "str:.." "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1200; MainRule "str:/etc/passwd" "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1202; MainRule "str:c:\\" "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1203; MainRule "str:cmd.exe" "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1204; MainRule "str:\\" "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1205; #MainRule "str:/" "msg:slash in args" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:2" id:1206; ######################################## ## Cross Site Scripting IDs:1300-1399 ## ######################################## MainRule "str:<" "msg:html open tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1302; MainRule "str:>" "msg:html close tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1303; MainRule "str:'" "msg:simple quote" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1306; MainRule "str:\"" "msg:double quote" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1307; MainRule "str:(" "msg:parenthesis" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1308; MainRule "str:)" "msg:parenthesis" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1309; MainRule "str:[" "msg:html close comment tag" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1310; MainRule "str:]" "msg:html close comment tag" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1311; MainRule "str:~" "msg:html close comment tag" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1312; MainRule "str:;" "msg:semi coma" "mz:ARGS|URL|BODY" "s:$XSS:8" id:1313; MainRule "str:`" "msg:grave accent !" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1314; MainRule "rx:%[2|3]." "msg:double encoding !" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1315; #################################### ## Evading tricks IDs: 1400-1500 ## #################################### MainRule "str:&#" "msg: utf7/8 encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1400; MainRule "str:%U" "msg: M$ encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1401; MainRule negative "rx:multipart/form-data|application/x-www-form-urlencoded" "msg:Content is neither multipart/x-www-form.." "mz:$HEADERS_VAR:Content-type" "s:$EVADE:4" id:1402; ############################# ## File uploads: 1500-1600 ## ############################# MainRule "rx:.ph*|.asp*" "msg:asp/php file upload!" "mz:FILE_EXT" "s:$UPLOAD:8" id:1500; ././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utfcertbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/w0000644000175000017500000000600013560356424034150 0ustar ericaerica00000000000000# This map is not a full windows-1251 <> utf8 map: it does not # contain Serbian and Macedonian letters. If you need a full map, # use contrib/unicode2nginx/win-utf map instead. charset_map windows-1251 utf-8 { 82 E2809A; # single low-9 quotation mark 84 E2809E; # double low-9 quotation mark 85 E280A6; # ellipsis 86 E280A0; # dagger 87 E280A1; # double dagger 88 E282AC; # euro 89 E280B0; # per mille 91 E28098; # left single quotation mark 92 E28099; # right single quotation mark 93 E2809C; # left double quotation mark 94 E2809D; # right double quotation mark 95 E280A2; # bullet 96 E28093; # en dash 97 E28094; # em dash 99 E284A2; # trade mark sign A0 C2A0; #   A1 D18E; # capital Byelorussian short U A2 D19E; # small Byelorussian short u A4 C2A4; # currency sign A5 D290; # capital Ukrainian soft G A6 C2A6; # borken bar A7 C2A7; # section sign A8 D081; # capital YO A9 C2A9; # (C) AA D084; # capital Ukrainian YE AB C2AB; # left-pointing double angle quotation mark AC C2AC; # not sign AD C2AD; # soft hyphen AE C2AE; # (R) AF D087; # capital Ukrainian YI B0 C2B0; # ° B1 C2B1; # plus-minus sign B2 D086; # capital Ukrainian I B3 D196; # small Ukrainian i B4 D291; # small Ukrainian soft g B5 C2B5; # micro sign B6 C2B6; # pilcrow sign B7 C2B7; # · B8 D191; # small yo B9 E28496; # numero sign BA D194; # small Ukrainian ye BB C2BB; # right-pointing double angle quotation mark BF D197; # small Ukrainian yi C0 D090; # capital A C1 D091; # capital B C2 D092; # capital V C3 D093; # capital G C4 D094; # capital D C5 D095; # capital YE C6 D096; # capital ZH C7 D097; # capital Z C8 D098; # capital I C9 D099; # capital J CA D09A; # capital K CB D09B; # capital L CC D09C; # capital M CD D09D; # capital N CE D09E; # capital O CF D09F; # capital P D0 D0A0; # capital R D1 D0A1; # capital S D2 D0A2; # capital T D3 D0A3; # capital U D4 D0A4; # capital F D5 D0A5; # capital KH D6 D0A6; # capital TS D7 D0A7; # capital CH D8 D0A8; # capital SH D9 D0A9; # capital SHCH DA D0AA; # capital hard sign DB D0AB; # capital Y DC D0AC; # capital soft sign DD D0AD; # capital E DE D0AE; # capital YU DF D0AF; # capital YA E0 D0B0; # small a E1 D0B1; # small b E2 D0B2; # small v E3 D0B3; # small g E4 D0B4; # small d E5 D0B5; # small ye E6 D0B6; # small zh E7 D0B7; # small z E8 D0B8; # small i E9 D0B9; # small j EA D0BA; # small k EB D0BB; # small l EC D0BC; # small m ED D0BD; # small n EE D0BE; # small o EF D0BF; # small p F0 D180; # small r F1 D181; # small s F2 D182; # small t F3 D183; # small u F4 D184; # small f F5 D185; # small kh F6 D186; # small ts F7 D187; # small ch F8 D188; # small sh F9 D189; # small shch FA D18A; # small hard sign FB D18B; # small y FC D18C; # small soft sign FD D18D; # small e FE D18E; # small yu FF D18F; # small ya } ././@LongLink0000000000000000000000000000016400000000000011216 Lustar 00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/s0002755000175000017500000000000013560356440034146 5ustar ericaerica00000000000000././@LongLink0000000000000000000000000000017300000000000011216 Lustar 00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/defaultcertbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/s0000644000175000017500000000504113560356424034150 0ustar ericaerica00000000000000# You may add here your # server { # ... # } # statements for each of your virtual hosts to this file ## # You should look at the following URL's in order to grasp a solid understanding # of Nginx configuration files in order to fully unleash the power of Nginx. # http://wiki.nginx.org/Pitfalls # http://wiki.nginx.org/QuickStart # http://wiki.nginx.org/Configuration # # Generally, you will want to move this file somewhere, and start with a clean # file but keep this around for reference. Or just disable in sites-enabled. # # Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples. ## server { listen 80 default_server; listen [::]:80 default_server ipv6only=on; root /usr/share/nginx/html; index index.html index.htm; # Make site accessible from http://localhost/ server_name localhost; location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; # Uncomment to enable naxsi on this location # include /etc/nginx/naxsi.rules } # Only for nginx-naxsi used with nginx-naxsi-ui : process denied requests #location /RequestDenied { # proxy_pass http://127.0.0.1:8080; #} #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # #error_page 500 502 503 504 /50x.html; #location = /50x.html { # root /usr/share/nginx/html; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # fastcgi_split_path_info ^(.+\.php)(/.+)$; # # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini # # # With php5-cgi alone: # fastcgi_pass 127.0.0.1:9000; # # With php5-fpm: # fastcgi_pass unix:/var/run/php5-fpm.sock; # fastcgi_index index.php; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} } # another virtual host using mix of IP-, name-, and port-based configuration # #server { # listen 8000; # listen somename:8080; # server_name somename alias another.alias; # root html; # index index.html index.htm; # # location / { # try_files $uri $uri/ =404; # } #} # HTTPS server # #server { # listen 443; # server_name localhost; # # root html; # index index.html index.htm; # # ssl on; # ssl_certificate cert.pem; # ssl_certificate_key cert.key; # # ssl_session_timeout 5m; # # ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; # ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES"; # ssl_prefer_server_ciphers on; # # location / { # try_files $uri $uri/ =404; # } #} ././@LongLink0000000000000000000000000000016000000000000011212 Lustar 00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_paramscertbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/p0000644000175000017500000000026413560356424034147 0ustar ericaerica00000000000000proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; ././@LongLink0000000000000000000000000000015600000000000011217 Lustar 00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.confcertbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/n0000644000175000017500000000310113560356424034136 0ustar ericaerica00000000000000user www-data; worker_processes 4; pid /run/nginx.pid; events { worker_connections 768; # multi_accept on; } http { ## # Basic Settings ## sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; # server_tokens off; # server_names_hash_bucket_size 64; # server_name_in_redirect off; include /etc/nginx/mime.types; default_type application/octet-stream; ## # Logging Settings ## access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; ## # Gzip Settings ## gzip on; gzip_disable "msie6"; # gzip_vary on; # gzip_proxied any; # gzip_comp_level 6; # gzip_buffers 16 8k; # gzip_http_version 1.1; # gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; ## # nginx-naxsi config ## # Uncomment it if you installed nginx-naxsi ## #include /etc/nginx/naxsi_core.rules; ## # nginx-passenger config ## # Uncomment it if you installed nginx-passenger ## #passenger_root /usr; #passenger_ruby /usr/bin/ruby; ## # Virtual Host Configs ## include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; } #mail { # # See sample authentication script at: # # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript # # # auth_http localhost/auth.php; # # pop3_capabilities "TOP" "USER"; # # imap_capabilities "IMAP4rev1" "UIDPLUS"; # # server { # listen localhost:110; # protocol pop3; # proxy on; # } # # server { # listen localhost:143; # protocol imap; # proxy on; # } #} ././@LongLink0000000000000000000000000000016200000000000011214 Lustar 00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_paramscertbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/f0000644000175000017500000000161713560356424034140 0ustar ericaerica00000000000000fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SCRIPT_FILENAME $request_filename; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; fastcgi_param HTTPS $https if_not_empty; # PHP only, required if PHP was built with --enable-force-cgi-redirect fastcgi_param REDIRECT_STATUS 200; ././@LongLink0000000000000000000000000000016200000000000011214 Lustar 00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/s0002755000175000017500000000000013560356440034146 5ustar ericaerica00000000000000././@LongLink0000000000000000000000000000017100000000000011214 Lustar 00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/defaultcertbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/s0000644000175000017500000000504113560356424034150 0ustar ericaerica00000000000000# You may add here your # server { # ... # } # statements for each of your virtual hosts to this file ## # You should look at the following URL's in order to grasp a solid understanding # of Nginx configuration files in order to fully unleash the power of Nginx. # http://wiki.nginx.org/Pitfalls # http://wiki.nginx.org/QuickStart # http://wiki.nginx.org/Configuration # # Generally, you will want to move this file somewhere, and start with a clean # file but keep this around for reference. Or just disable in sites-enabled. # # Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples. ## server { listen 80 default_server; listen [::]:80 default_server ipv6only=on; root /usr/share/nginx/html; index index.html index.htm; # Make site accessible from http://localhost/ server_name localhost; location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; # Uncomment to enable naxsi on this location # include /etc/nginx/naxsi.rules } # Only for nginx-naxsi used with nginx-naxsi-ui : process denied requests #location /RequestDenied { # proxy_pass http://127.0.0.1:8080; #} #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # #error_page 500 502 503 504 /50x.html; #location = /50x.html { # root /usr/share/nginx/html; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # fastcgi_split_path_info ^(.+\.php)(/.+)$; # # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini # # # With php5-cgi alone: # fastcgi_pass 127.0.0.1:9000; # # With php5-fpm: # fastcgi_pass unix:/var/run/php5-fpm.sock; # fastcgi_index index.php; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} } # another virtual host using mix of IP-, name-, and port-based configuration # #server { # listen 8000; # listen somename:8080; # server_name somename alias another.alias; # root html; # index index.html index.htm; # # location / { # try_files $uri $uri/ =404; # } #} # HTTPS server # #server { # listen 443; # server_name localhost; # # root html; # index index.html index.htm; # # ssl on; # ssl_certificate cert.pem; # ssl_certificate_key cert.key; # # ssl_session_timeout 5m; # # ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; # ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES"; # ssl_prefer_server_ciphers on; # # location / { # try_files $uri $uri/ =404; # } #} ././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-wincertbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/k0000644000175000017500000000341513560356424034143 0ustar ericaerica00000000000000charset_map koi8-r windows-1251 { 80 88; # euro 95 95; # bullet 9A A0; #   9E B7; # · A3 B8; # small yo A4 BA; # small Ukrainian ye A6 B3; # small Ukrainian i A7 BF; # small Ukrainian yi AD B4; # small Ukrainian soft g AE A2; # small Byelorussian short u B0 B0; # ° B3 A8; # capital YO B4 AA; # capital Ukrainian YE B6 B2; # capital Ukrainian I B7 AF; # capital Ukrainian YI B9 B9; # numero sign BD A5; # capital Ukrainian soft G BE A1; # capital Byelorussian short U BF A9; # (C) C0 FE; # small yu C1 E0; # small a C2 E1; # small b C3 F6; # small ts C4 E4; # small d C5 E5; # small ye C6 F4; # small f C7 E3; # small g C8 F5; # small kh C9 E8; # small i CA E9; # small j CB EA; # small k CC EB; # small l CD EC; # small m CE ED; # small n CF EE; # small o D0 EF; # small p D1 FF; # small ya D2 F0; # small r D3 F1; # small s D4 F2; # small t D5 F3; # small u D6 E6; # small zh D7 E2; # small v D8 FC; # small soft sign D9 FB; # small y DA E7; # small z DB F8; # small sh DC FD; # small e DD F9; # small shch DE F7; # small ch DF FA; # small hard sign E0 DE; # capital YU E1 C0; # capital A E2 C1; # capital B E3 D6; # capital TS E4 C4; # capital D E5 C5; # capital YE E6 D4; # capital F E7 C3; # capital G E8 D5; # capital KH E9 C8; # capital I EA C9; # capital J EB CA; # capital K EC CB; # capital L ED CC; # capital M EE CD; # capital N EF CE; # capital O F0 CF; # capital P F1 DF; # capital YA F2 D0; # capital R F3 D1; # capital S F4 D2; # capital T F5 D3; # capital U F6 C6; # capital ZH F7 C2; # capital V F8 DC; # capital soft sign F9 DB; # capital Y FA C7; # capital Z FB D8; # capital SH FC DD; # capital E FD D9; # capital SHCH FE D7; # capital CH FF DA; # capital hard sign } ././@LongLink0000000000000000000000000000016700000000000011221 Lustar 00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/n0000644000175000017500000000033613560356424034145 0ustar ericaerica00000000000000[nx_extract] username = naxsi_web password = test port = 8081 rules_path = /etc/nginx/naxsi_core.rules [nx_intercept] port = 8080 [sql] dbtype = sqlite username = root password = hostname = 127.0.0.1 dbname = naxsi_sig ././@LongLink0000000000000000000000000000015700000000000011220 Lustar 00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rulescertbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/n0000644000175000017500000000043713560356424034147 0ustar ericaerica00000000000000# Sample rules file for default vhost. LearningMode; SecRulesEnabled; #SecRulesDisabled; DeniedUrl "/RequestDenied"; ## check rules CheckRule "$SQL >= 8" BLOCK; CheckRule "$RFI >= 8" BLOCK; CheckRule "$TRAVERSAL >= 4" BLOCK; CheckRule "$EVADE >= 4" BLOCK; CheckRule "$XSS >= 8" BLOCK; ././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utfcertbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/k0000644000175000017500000000432213560356424034141 0ustar ericaerica00000000000000# This map is not a full koi8-r <> utf8 map: it does not contain # box-drawing and some other characters. Besides this map contains # several koi8-u and Byelorussian letters which are not in koi8-r. # If you need a full and standard map, use contrib/unicode2nginx/koi-utf # map instead. charset_map koi8-r utf-8 { 80 E282AC; # euro 95 E280A2; # bullet 9A C2A0; #   9E C2B7; # · A3 D191; # small yo A4 D194; # small Ukrainian ye A6 D196; # small Ukrainian i A7 D197; # small Ukrainian yi AD D291; # small Ukrainian soft g AE D19E; # small Byelorussian short u B0 C2B0; # ° B3 D081; # capital YO B4 D084; # capital Ukrainian YE B6 D086; # capital Ukrainian I B7 D087; # capital Ukrainian YI B9 E28496; # numero sign BD D290; # capital Ukrainian soft G BE D18E; # capital Byelorussian short U BF C2A9; # (C) C0 D18E; # small yu C1 D0B0; # small a C2 D0B1; # small b C3 D186; # small ts C4 D0B4; # small d C5 D0B5; # small ye C6 D184; # small f C7 D0B3; # small g C8 D185; # small kh C9 D0B8; # small i CA D0B9; # small j CB D0BA; # small k CC D0BB; # small l CD D0BC; # small m CE D0BD; # small n CF D0BE; # small o D0 D0BF; # small p D1 D18F; # small ya D2 D180; # small r D3 D181; # small s D4 D182; # small t D5 D183; # small u D6 D0B6; # small zh D7 D0B2; # small v D8 D18C; # small soft sign D9 D18B; # small y DA D0B7; # small z DB D188; # small sh DC D18D; # small e DD D189; # small shch DE D187; # small ch DF D18A; # small hard sign E0 D0AE; # capital YU E1 D090; # capital A E2 D091; # capital B E3 D0A6; # capital TS E4 D094; # capital D E5 D095; # capital YE E6 D0A4; # capital F E7 D093; # capital G E8 D0A5; # capital KH E9 D098; # capital I EA D099; # capital J EB D09A; # capital K EC D09B; # capital L ED D09C; # capital M EE D09D; # capital N EF D09E; # capital O F0 D09F; # capital P F1 D0AF; # capital YA F2 D0A0; # capital R F3 D0A1; # capital S F4 D0A2; # capital T F5 D0A3; # capital U F6 D096; # capital ZH F7 D092; # capital V F8 D0AC; # capital soft sign F9 D0AB; # capital Y FA D097; # capital Z FB D0A8; # capital SH FC D0AD; # capital E FD D0A9; # capital SHCH FE D0A7; # capital CH FF D0AA; # capital hard sign } ././@LongLink0000000000000000000000000000015600000000000011217 Lustar 00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.typescertbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/m0000644000175000017500000000404513560356424034145 0ustar ericaerica00000000000000types { text/html html htm shtml; text/css css; text/xml xml rss; image/gif gif; image/jpeg jpeg jpg; application/x-javascript js; application/atom+xml atom; text/mathml mml; text/plain txt; text/vnd.sun.j2me.app-descriptor jad; text/vnd.wap.wml wml; text/x-component htc; image/png png; image/tiff tif tiff; image/vnd.wap.wbmp wbmp; image/x-icon ico; image/x-jng jng; image/x-ms-bmp bmp; image/svg+xml svg svgz; application/java-archive jar war ear; application/json json; application/mac-binhex40 hqx; application/msword doc; application/pdf pdf; application/postscript ps eps ai; application/rtf rtf; application/vnd.ms-excel xls; application/vnd.ms-powerpoint ppt; application/vnd.wap.wmlc wmlc; application/vnd.google-earth.kml+xml kml; application/vnd.google-earth.kmz kmz; application/x-7z-compressed 7z; application/x-cocoa cco; application/x-java-archive-diff jardiff; application/x-java-jnlp-file jnlp; application/x-makeself run; application/x-perl pl pm; application/x-pilot prc pdb; application/x-rar-compressed rar; application/x-redhat-package-manager rpm; application/x-sea sea; application/x-shockwave-flash swf; application/x-stuffit sit; application/x-tcl tcl tk; application/x-x509-ca-cert der pem crt; application/x-xpinstall xpi; application/xhtml+xml xhtml; application/zip zip; application/octet-stream bin exe dll; application/octet-stream deb; application/octet-stream dmg; application/octet-stream eot; application/octet-stream iso img; application/octet-stream msi msp msm; application/ogg ogx; audio/midi mid midi kar; audio/mpeg mpga mpega mp2 mp3 m4a; audio/ogg oga ogg spx; audio/x-realaudio ra; audio/webm weba; video/3gpp 3gpp 3gp; video/mp4 mp4; video/mpeg mpeg mpg mpe; video/ogg ogv; video/quicktime mov; video/webm webm; video/x-flv flv; video/x-mng mng; video/x-ms-asf asx asf; video/x-ms-wmv wmv; video/x-msvideo avi; } ././@LongLink0000000000000000000000000000016000000000000011212 Lustar 00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_paramscertbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/u0000644000175000017500000000102413560356424034147 0ustar ericaerica00000000000000uwsgi_param QUERY_STRING $query_string; uwsgi_param REQUEST_METHOD $request_method; uwsgi_param CONTENT_TYPE $content_type; uwsgi_param CONTENT_LENGTH $content_length; uwsgi_param REQUEST_URI $request_uri; uwsgi_param PATH_INFO $document_uri; uwsgi_param DOCUMENT_ROOT $document_root; uwsgi_param SERVER_PROTOCOL $server_protocol; uwsgi_param UWSGI_SCHEME $scheme; uwsgi_param REMOTE_ADDR $remote_addr; uwsgi_param REMOTE_PORT $remote_port; uwsgi_param SERVER_PORT $server_port; uwsgi_param SERVER_NAME $server_name; certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/0002755000175000017500000000000013560356440027073 5ustar ericaerica00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/globalssl.com0000644000175000017500000000022013560356424031547 0ustar ericaerica00000000000000server { server_name globalssl.com; listen 4.8.2.6:57; } server { server_name globalsslsetssl.com; listen 4.8.2.6:57 ssl; } certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/headers.com0000644000175000017500000000012713560356424031206 0ustar ericaerica00000000000000server { server_name headers.com; add_header X-Content-Type-Options nosniff; } certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/example.com0000644000175000017500000000020513560356424031223 0ustar ericaerica00000000000000server { listen 69.50.225.155:9000; listen 127.0.0.1; server_name .example.com; server_name example.*; } certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/migration.com0000644000175000017500000000057713560356424031575 0ustar ericaerica00000000000000server { server_name migration.com; server_name summer.com; } server { listen 443 ssl; server_name migration.com; server_name geese.com; ssl_certificate cert.pem; ssl_certificate_key cert.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; } certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/ipv6ssl.com0000644000175000017500000000023413560356424031200 0ustar ericaerica00000000000000server { listen 443 ssl; listen [::]:443 ssl ipv6only=on; listen 5001 ssl; listen [::]:5001 ssl ipv6only=on; server_name ipv6ssl.com; } certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/sslon.com0000644000175000017500000000015713560356424030734 0ustar ericaerica00000000000000server { server_name sslon.com; ssl on; ssl_certificate snakeoil.cert; ssl_certificate_key snakeoil.key; } certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/default0000644000175000017500000000032313560356424030440 0ustar ericaerica00000000000000server { listen myhost default_server; listen otherhost default_server; server_name "www.example.org"; location / { root html; index index.html index.htm; } } certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/ipv6.com0000644000175000017500000000011013560356424030447 0ustar ericaerica00000000000000server { listen 80; listen [::]:80; server_name ipv6.com; } certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/server.conf0000644000175000017500000000005513560356424026531 0ustar ericaerica00000000000000server_name somename alias another.alias; certbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/comment_in_file.conf0000644000175000017500000000003113560356424030344 0ustar ericaerica00000000000000# a comment inside a filecertbot-nginx-0.40.0/certbot_nginx/tests/testdata/etc_nginx/mime.types0000644000175000017500000000000013560356424026357 0ustar ericaerica00000000000000certbot-nginx-0.40.0/certbot_nginx/tests/parser_test.py0000644000175000017500000005462213560356424023503 0ustar ericaerica00000000000000"""Tests for certbot_nginx.parser.""" import glob import re import shutil import unittest from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot.compat import os from certbot_nginx import nginxparser from certbot_nginx import obj from certbot_nginx import parser from certbot_nginx.tests import util class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods """Nginx Parser Test.""" def tearDown(self): shutil.rmtree(self.temp_dir) shutil.rmtree(self.config_dir) shutil.rmtree(self.work_dir) def test_root_normalized(self): path = os.path.join(self.temp_dir, "etc_nginx/////" "ubuntu_nginx/../../etc_nginx") nparser = parser.NginxParser(path) self.assertEqual(nparser.root, self.config_path) def test_root_absolute(self): curr_dir = os.getcwd() try: # On Windows current directory may be on a different drive than self.tempdir. # However a relative path between two different drives is invalid. So we move to # self.tempdir to ensure that we stay on the same drive. os.chdir(self.temp_dir) nparser = parser.NginxParser(os.path.relpath(self.config_path)) self.assertEqual(nparser.root, self.config_path) finally: os.chdir(curr_dir) def test_root_no_trailing_slash(self): nparser = parser.NginxParser(self.config_path + os.path.sep) self.assertEqual(nparser.root, self.config_path) def test_load(self): """Test recursive conf file parsing. """ nparser = parser.NginxParser(self.config_path) nparser.load() self.assertEqual(set([nparser.abs_path(x) for x in ['foo.conf', 'nginx.conf', 'server.conf', 'sites-enabled/default', 'sites-enabled/example.com', 'sites-enabled/headers.com', 'sites-enabled/migration.com', 'sites-enabled/sslon.com', 'sites-enabled/globalssl.com', 'sites-enabled/ipv6.com', 'sites-enabled/ipv6ssl.com']]), set(nparser.parsed.keys())) self.assertEqual([['server_name', 'somename', 'alias', 'another.alias']], nparser.parsed[nparser.abs_path('server.conf')]) self.assertEqual([[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*']]]], nparser.parsed[nparser.abs_path( 'sites-enabled/example.com')]) def test_abs_path(self): nparser = parser.NginxParser(self.config_path) if os.name != 'nt': self.assertEqual('/etc/nginx/*', nparser.abs_path('/etc/nginx/*')) self.assertEqual(os.path.join(self.config_path, 'foo/bar'), nparser.abs_path('foo/bar')) else: self.assertEqual('C:\\etc\\nginx\\*', nparser.abs_path('C:\\etc\\nginx\\*')) self.assertEqual(os.path.join(self.config_path, 'foo\\bar'), nparser.abs_path('foo\\bar')) def test_filedump(self): nparser = parser.NginxParser(self.config_path) nparser.filedump('test', lazy=False) # pylint: disable=protected-access 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( glob.glob(nparser.abs_path('sites-enabled/*.test')))) self.assertEqual([[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*']]]], parsed[0]) def test__do_for_subarray(self): # pylint: disable=protected-access mylists = [([[2], [3], [2]], [[0], [2]]), ([[2], [3], [4]], [[0]]), ([[4], [3], [2]], [[2]]), ([], []), (2, []), ([[[2], [3], [2]], [[2], [3], [2]]], [[0, 0], [0, 2], [1, 0], [1, 2]]), ([[[0], [3], [2]], [[2], [3], [2]]], [[0, 2], [1, 0], [1, 2]]), ([[[0], [3], [4]], [[2], [3], [2]]], [[1, 0], [1, 2]]), ([[[0], [3], [4]], [[5], [3], [2]]], [[1, 2]]), ([[[0], [3], [4]], [[5], [3], [0]]], [])] for mylist, result in mylists: paths = [] # type: List[List[int]] parser._do_for_subarray(mylist, lambda x: isinstance(x, list) and len(x) >= 1 and x[0] == 2, lambda x, y, pts=paths: pts.append(y)) self.assertEqual(paths, result) def test_get_vhosts_global_ssl(self): nparser = parser.NginxParser(self.config_path) vhosts = nparser.get_vhosts() vhost = obj.VirtualHost(nparser.abs_path('sites-enabled/globalssl.com'), [obj.Addr('4.8.2.6', '57', True, False, False, False)], True, True, set(['globalssl.com']), [], [0]) globalssl_com = [x for x in vhosts if 'globalssl.com' in x.filep][0] self.assertEqual(vhost, globalssl_com) def test_get_vhosts(self): nparser = parser.NginxParser(self.config_path) vhosts = nparser.get_vhosts() vhost1 = obj.VirtualHost(nparser.abs_path('nginx.conf'), [obj.Addr('', '8080', False, False, False, False)], False, True, set(['localhost', r'~^(www\.)?(example|bar)\.']), [], [10, 1, 9]) vhost2 = obj.VirtualHost(nparser.abs_path('nginx.conf'), [obj.Addr('somename', '8080', False, False, False, False), obj.Addr('', '8000', False, False, False, False)], False, True, set(['somename', 'another.alias', 'alias']), [], [10, 1, 12]) vhost3 = obj.VirtualHost(nparser.abs_path('sites-enabled/example.com'), [obj.Addr('69.50.225.155', '9000', False, False, False, False), obj.Addr('127.0.0.1', '', False, False, False, False)], False, True, set(['.example.com', 'example.*']), [], [0]) vhost4 = obj.VirtualHost(nparser.abs_path('sites-enabled/default'), [obj.Addr('myhost', '', False, True, False, False), obj.Addr('otherhost', '', False, True, False, False)], False, True, set(['www.example.org']), [], [0]) vhost5 = obj.VirtualHost(nparser.abs_path('foo.conf'), [obj.Addr('*', '80', True, True, False, False)], True, True, set(['*.www.foo.com', '*.www.example.com']), [], [2, 1, 0]) self.assertEqual(13, 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] self.assertEqual(vhost4, default) fooconf = [x for x in vhosts if 'foo.conf' in x.filep][0] self.assertEqual(vhost5, fooconf) localhost = [x for x in vhosts if 'localhost' in x.names][0] self.assertEqual(vhost1, localhost) somename = [x for x in vhosts if 'somename' in x.names][0] self.assertEqual(vhost2, somename) def test_has_ssl_on_directive(self): nparser = parser.NginxParser(self.config_path) mock_vhost = obj.VirtualHost(None, None, None, None, None, [['listen', 'myhost default_server'], ['server_name', 'www.example.org'], [['location', '/'], [['root', 'html'], ['index', 'index.html index.htm']]] ], None) self.assertFalse(nparser.has_ssl_on_directive(mock_vhost)) mock_vhost.raw = [['listen', '*:80', 'default_server', 'ssl'], ['server_name', '*.www.foo.com', '*.www.example.com'], ['root', '/home/ubuntu/sites/foo/']] self.assertFalse(nparser.has_ssl_on_directive(mock_vhost)) mock_vhost.raw = [['listen', '80 ssl'], ['server_name', '*.www.foo.com', '*.www.example.com']] self.assertFalse(nparser.has_ssl_on_directive(mock_vhost)) mock_vhost.raw = [['listen', '80'], ['ssl', 'on'], ['server_name', '*.www.foo.com', '*.www.example.com']] self.assertTrue(nparser.has_ssl_on_directive(mock_vhost)) def test_remove_server_directives(self): nparser = parser.NginxParser(self.config_path) mock_vhost = obj.VirtualHost(nparser.abs_path('nginx.conf'), None, None, None, set(['localhost', r'~^(www\.)?(example|bar)\.']), None, [10, 1, 9]) example_com = nparser.abs_path('sites-enabled/example.com') names = set(['.example.com', 'example.*']) mock_vhost.filep = example_com mock_vhost.names = names mock_vhost.path = [0] nparser.add_server_directives(mock_vhost, [['foo', 'bar'], ['ssl_certificate', '/etc/ssl/cert2.pem']]) nparser.remove_server_directives(mock_vhost, 'foo') nparser.remove_server_directives(mock_vhost, 'ssl_certificate') self.assertEqual(nparser.parsed[example_com], [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*'], []]]]) def test_add_server_directives(self): nparser = parser.NginxParser(self.config_path) mock_vhost = obj.VirtualHost(nparser.abs_path('nginx.conf'), None, None, None, set(['localhost', r'~^(www\.)?(example|bar)\.']), None, [10, 1, 9]) nparser.add_server_directives(mock_vhost, [['foo', 'bar'], ['\n ', 'ssl_certificate', ' ', '/etc/ssl/cert.pem']]) ssl_re = re.compile(r'\n\s+ssl_certificate /etc/ssl/cert.pem') dump = nginxparser.dumps(nparser.parsed[nparser.abs_path('nginx.conf')]) self.assertEqual(1, len(re.findall(ssl_re, dump))) example_com = nparser.abs_path('sites-enabled/example.com') names = set(['.example.com', 'example.*']) mock_vhost.filep = example_com mock_vhost.names = names mock_vhost.path = [0] nparser.add_server_directives(mock_vhost, [['foo', 'bar'], ['ssl_certificate', '/etc/ssl/cert2.pem']]) nparser.add_server_directives(mock_vhost, [['foo', 'bar']]) from certbot_nginx.parser import COMMENT self.assertEqual(nparser.parsed[example_com], [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*'], ['foo', 'bar'], ['#', COMMENT], ['ssl_certificate', '/etc/ssl/cert2.pem'], ['#', COMMENT], [], [] ]]]) server_conf = nparser.abs_path('server.conf') names = set(['alias', 'another.alias', 'somename']) mock_vhost.filep = server_conf mock_vhost.names = names mock_vhost.path = [] self.assertRaises(errors.MisconfigurationError, nparser.add_server_directives, mock_vhost, [['foo', 'bar'], ['ssl_certificate', '/etc/ssl/cert2.pem']]) def test_comment_is_repeatable(self): nparser = parser.NginxParser(self.config_path) example_com = nparser.abs_path('sites-enabled/example.com') mock_vhost = obj.VirtualHost(example_com, None, None, None, set(['.example.com', 'example.*']), None, [0]) nparser.add_server_directives(mock_vhost, [['\n ', '#', ' ', 'what a nice comment']]) nparser.add_server_directives(mock_vhost, [['\n ', 'include', ' ', nparser.abs_path('comment_in_file.conf')]]) from certbot_nginx.parser import COMMENT self.assertEqual(nparser.parsed[example_com], [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*'], ['#', ' ', 'what a nice comment'], [], ['include', nparser.abs_path('comment_in_file.conf')], ['#', COMMENT], []]]] ) def test_replace_server_directives(self): nparser = parser.NginxParser(self.config_path) target = set(['.example.com', 'example.*']) filep = nparser.abs_path('sites-enabled/example.com') mock_vhost = obj.VirtualHost(filep, None, None, None, target, None, [0]) nparser.update_or_add_server_directives( mock_vhost, [['server_name', 'foobar.com']]) from certbot_nginx.parser import COMMENT self.assertEqual( nparser.parsed[filep], [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', 'foobar.com'], ['#', COMMENT], ['server_name', 'example.*'], [] ]]]) mock_vhost.names = set(['foobar.com', 'example.*']) nparser.update_or_add_server_directives( mock_vhost, [['ssl_certificate', 'cert.pem']]) self.assertEqual( nparser.parsed[filep], [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', 'foobar.com'], ['#', COMMENT], ['server_name', 'example.*'], [], ['ssl_certificate', 'cert.pem'], ['#', COMMENT], [], ]]]) def test_get_best_match(self): target_name = 'www.eff.org' names = [set(['www.eff.org', 'irrelevant.long.name.eff.org', '*.org']), set(['eff.org', 'ww2.eff.org', 'test.www.eff.org']), set(['*.eff.org', '.www.eff.org']), set(['.eff.org', '*.org']), set(['www.eff.', 'www.eff.*', '*.www.eff.org']), set(['example.com', r'~^(www\.)?(eff.+)', '*.eff.*']), set(['*', r'~^(www\.)?(eff.+)']), set(['www.*', r'~^(www\.)?(eff.+)', '.test.eff.org']), set(['*.org', r'*.eff.org', 'www.eff.*']), set(['*.www.eff.org', 'www.*']), set(['*.org']), set([]), set(['example.com'])] winners = [('exact', 'www.eff.org'), (None, None), ('exact', '.www.eff.org'), ('wildcard_start', '.eff.org'), ('wildcard_end', 'www.eff.*'), ('regex', r'~^(www\.)?(eff.+)'), ('wildcard_start', '*'), ('wildcard_end', 'www.*'), ('wildcard_start', '*.eff.org'), ('wildcard_end', 'www.*'), ('wildcard_start', '*.org'), (None, None), (None, None)] for i, winner in enumerate(winners): self.assertEqual(winner, parser.get_best_match(target_name, names[i])) def test_comment_directive(self): # pylint: disable=protected-access block = nginxparser.UnspacedList([ ["\n", "a", " ", "b", "\n"], ["c", " ", "d"], ["\n", "e", " ", "f"]]) from certbot_nginx.parser import comment_directive, COMMENT_BLOCK comment_directive(block, 1) comment_directive(block, 0) self.assertEqual(block.spaced, [ ["\n", "a", " ", "b", "\n"], COMMENT_BLOCK, "\n", ["c", " ", "d"], COMMENT_BLOCK, ["\n", "e", " ", "f"]]) def test_comment_out_directive(self): server_block = nginxparser.loads(""" server { listen 80; root /var/www/html; index star.html; server_name *.functorkitten.xyz; ssl_session_timeout 1440m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; }""") block = server_block[0][1] from certbot_nginx.parser import _comment_out_directive _comment_out_directive(block, 4, "blah1") _comment_out_directive(block, 5, "blah2") _comment_out_directive(block, 6, "blah3") self.assertEqual(block.spaced, [ ['\n ', 'listen', ' ', '80'], ['\n ', 'root', ' ', '/var/www/html'], ['\n ', 'index', ' ', 'star.html'], ['\n\n ', 'server_name', ' ', '*.functorkitten.xyz'], ['\n ', '#', ' ssl_session_timeout 1440m; # duplicated in blah1'], [' ', '#', ' ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # duplicated in blah2'], ['\n\n ', '#', ' ssl_prefer_server_ciphers on; # duplicated in blah3'], '\n ']) def test_parse_server_raw_ssl(self): server = parser._parse_server_raw([ #pylint: disable=protected-access ['listen', '443'] ]) self.assertFalse(server['ssl']) server = parser._parse_server_raw([ #pylint: disable=protected-access ['listen', '443', 'ssl'] ]) self.assertTrue(server['ssl']) server = parser._parse_server_raw([ #pylint: disable=protected-access ['listen', '443'], ['ssl', 'off'] ]) self.assertFalse(server['ssl']) server = parser._parse_server_raw([ #pylint: disable=protected-access ['listen', '443'], ['ssl', 'on'] ]) self.assertTrue(server['ssl']) def test_parse_server_raw_unix(self): server = parser._parse_server_raw([ #pylint: disable=protected-access ['listen', 'unix:/var/run/nginx.sock'] ]) self.assertEqual(len(server['addrs']), 0) def test_parse_server_global_ssl_applied(self): nparser = parser.NginxParser(self.config_path) server = nparser.parse_server([ ['listen', '443'] ]) self.assertTrue(server['ssl']) def test_duplicate_vhost(self): nparser = parser.NginxParser(self.config_path) vhosts = nparser.get_vhosts() default = [x for x in vhosts if 'default' in x.filep][0] new_vhost = nparser.duplicate_vhost(default, remove_singleton_listen_params=True) nparser.filedump(ext='') # check properties of new vhost self.assertFalse(next(iter(new_vhost.addrs)).default) self.assertNotEqual(new_vhost.path, default.path) # check that things are written to file correctly new_nparser = parser.NginxParser(self.config_path) new_vhosts = new_nparser.get_vhosts() new_defaults = [x for x in new_vhosts if 'default' in x.filep] self.assertEqual(len(new_defaults), 2) new_vhost_parsed = new_defaults[1] self.assertFalse(next(iter(new_vhost_parsed.addrs)).default) self.assertEqual(next(iter(default.names)), next(iter(new_vhost_parsed.names))) self.assertEqual(len(default.raw), len(new_vhost_parsed.raw)) self.assertTrue(next(iter(default.addrs)).super_eq(next(iter(new_vhost_parsed.addrs)))) def test_duplicate_vhost_remove_ipv6only(self): nparser = parser.NginxParser(self.config_path) vhosts = nparser.get_vhosts() ipv6ssl = [x for x in vhosts if 'ipv6ssl' in x.filep][0] new_vhost = nparser.duplicate_vhost(ipv6ssl, remove_singleton_listen_params=True) nparser.filedump(ext='') for addr in new_vhost.addrs: self.assertFalse(addr.ipv6only) identical_vhost = nparser.duplicate_vhost(ipv6ssl, remove_singleton_listen_params=False) nparser.filedump(ext='') called = False for addr in identical_vhost.addrs: if addr.ipv6: self.assertTrue(addr.ipv6only) called = True self.assertTrue(called) if __name__ == "__main__": unittest.main() # pragma: no cover certbot-nginx-0.40.0/certbot_nginx/tests/nginxparser_test.py0000644000175000017500000004050113560356424024536 0ustar ericaerica00000000000000"""Test for certbot_nginx.nginxparser.""" import copy import operator import tempfile import unittest from pyparsing import ParseException from certbot_nginx.nginxparser import ( RawNginxParser, loads, load, dumps, dump, UnspacedList) from certbot_nginx.tests import util FIRST = operator.itemgetter(0) class TestRawNginxParser(unittest.TestCase): """Test the raw low-level Nginx config parser.""" def test_assignments(self): parsed = RawNginxParser.assignment.parseString('root /test;').asList() self.assertEqual(parsed, ['root', ' ', '/test']) parsed = RawNginxParser.assignment.parseString('root /test;foo bar;').asList() self.assertEqual(parsed, ['root', ' ', '/test'], ['foo', ' ', 'bar']) def test_blocks(self): parsed = RawNginxParser.block.parseString('foo {}').asList() self.assertEqual(parsed, [['foo', ' '], []]) parsed = RawNginxParser.block.parseString('location /foo{}').asList() self.assertEqual(parsed, [['location', ' ', '/foo'], []]) parsed = RawNginxParser.block.parseString('foo { bar foo ; }').asList() self.assertEqual(parsed, [['foo', ' '], [[' ', 'bar', ' ', 'foo', ' '], ' ']]) def test_nested_blocks(self): parsed = RawNginxParser.block.parseString('foo { bar {} }').asList() block, content = parsed self.assertEqual(FIRST(content), [[' ', 'bar', ' '], []]) self.assertEqual(FIRST(block), 'foo') def test_dump_as_string(self): dumped = dumps(UnspacedList([ ['user', ' ', 'www-data'], [['\n', 'server', ' '], [ ['\n ', 'listen', ' ', '80'], ['\n ', 'server_name', ' ', 'foo.com'], ['\n ', 'root', ' ', '/home/ubuntu/sites/foo/'], [['\n\n ', 'location', ' ', '/status', ' '], [ ['\n ', 'check_status', ''], [['\n\n ', 'types', ' '], [['\n ', 'image/jpeg', ' ', 'jpg']]], ]] ]]])) self.assertEqual(dumped.split('\n'), 'user www-data;\n' 'server {\n' ' listen 80;\n' ' server_name foo.com;\n' ' root /home/ubuntu/sites/foo/;\n' '\n' ' location /status {\n' ' check_status;\n' '\n' ' types {\n' ' image/jpeg jpg;}}}'.split('\n')) def test_parse_from_file(self): with open(util.get_data_filename('foo.conf')) as handle: parsed = util.filter_comments(load(handle)) self.assertEqual( parsed, [['user', 'www-data'], [['http'], [[['server'], [ ['listen', '*:80', 'default_server', 'ssl'], ['server_name', '*.www.foo.com', '*.www.example.com'], ['root', '/home/ubuntu/sites/foo/'], [['location', '/status'], [ [['types'], [['image/jpeg', 'jpg']]], ]], [['location', '~', r'case_sensitive\.php$'], [ ['index', 'index.php'], ['root', '/var/root'], ]], [['location', '~*', r'case_insensitive\.php$'], []], [['location', '=', r'exact_match\.php$'], []], [['location', '^~', r'ignore_regex\.php$'], []] ]]]]] ) def test_parse_from_file2(self): with open(util.get_data_filename('edge_cases.conf')) as handle: parsed = util.filter_comments(load(handle)) self.assertEqual( parsed, [[['server'], [['server_name', 'simple']]], [['server'], [['server_name', 'with.if'], [['location', '~', '^/services/.+$'], [[['if', '($request_filename', '~*', '\\.(ttf|woff)$)'], [['add_header', 'Access-Control-Allow-Origin', '"*"']]]]]]], [['server'], [['server_name', 'with.complicated.headers'], [['location', '~*', '\\.(?:gif|jpe?g|png)$'], [['add_header', 'Pragma', 'public'], ['add_header', 'Cache-Control', '\'public, must-revalidate, proxy-revalidate\'', '"test,;{}"', 'foo'], ['blah', '"hello;world"'], ['try_files', '$uri', '@rewrites']]]]]]) def test_parse_from_file3(self): with open(util.get_data_filename('multiline_quotes.conf')) as handle: parsed = util.filter_comments(load(handle)) self.assertEqual( parsed, [[['http'], [[['server'], [['listen', '*:443'], [['location', '/'], [['body_filter_by_lua', '\'ngx.ctx.buffered = (ngx.ctx.buffered or "")' ' .. string.sub(ngx.arg[1], 1, 1000)\n' ' ' 'if ngx.arg[2] then\n' ' ' 'ngx.var.resp_body = ngx.ctx.buffered\n' ' end\'']]]]]]]]) def test_abort_on_parse_failure(self): with open(util.get_data_filename('broken.conf')) as handle: self.assertRaises(ParseException, load, handle) def test_dump_as_file(self): with open(util.get_data_filename('nginx.conf')) as handle: parsed = load(handle) parsed[-1][-1].append(UnspacedList([['server'], [['listen', ' ', '443', ' ', 'ssl'], ['server_name', ' ', 'localhost'], ['ssl_certificate', ' ', 'cert.pem'], ['ssl_certificate_key', ' ', 'cert.key'], ['ssl_session_cache', ' ', 'shared:SSL:1m'], ['ssl_session_timeout', ' ', '5m'], ['ssl_ciphers', ' ', 'HIGH:!aNULL:!MD5'], [['location', ' ', '/'], [['root', ' ', 'html'], ['index', ' ', 'index.html', ' ', 'index.htm']]]]])) with tempfile.TemporaryFile(mode='w+t') as f: dump(parsed, f) f.seek(0) parsed_new = load(f) self.assertEqual(parsed, parsed_new) def test_comments(self): with open(util.get_data_filename('minimalistic_comments.conf')) as handle: parsed = load(handle) with tempfile.TemporaryFile(mode='w+t') as f: dump(parsed, f) f.seek(0) parsed_new = load(f) self.assertEqual(parsed, parsed_new) self.assertEqual(parsed_new, [ ['#', " Use bar.conf when it's a full moon!"], ['include', 'foo.conf'], ['#', ' Kilroy was here'], ['check_status'], [['server'], [['#', ''], ['#', " Don't forget to open up your firewall!"], ['#', ''], ['listen', '1234'], ['#', ' listen 80;']]], ]) def test_issue_518(self): parsed = loads('if ($http_accept ~* "webp") { set $webp "true"; }') self.assertEqual(parsed, [ [['if', '($http_accept', '~*', '"webp")'], [['set', '$webp', '"true"']]] ]) def test_comment_in_block(self): parsed = loads("""http { # server{ }""") self.assertEqual(parsed, [ [['http'], [['#', ' server{']]] ]) def test_access_log(self): # see issue #3798 parsed = loads('access_log syslog:server=unix:/dev/log,facility=auth,' 'tag=nginx_post,severity=info custom;') self.assertEqual(parsed, [ ['access_log', 'syslog:server=unix:/dev/log,facility=auth,tag=nginx_post,severity=info', 'custom'] ]) def test_add_header(self): # see issue #3798 parsed = loads('add_header Cache-Control no-cache,no-store,must-revalidate,max-age=0;') self.assertEqual(parsed, [ ['add_header', 'Cache-Control', 'no-cache,no-store,must-revalidate,max-age=0'] ]) def test_map_then_assignment_in_block(self): # see issue #3798 test_str = """http { map $http_upgrade $connection_upgrade { default upgrade; '' close; "~Opera Mini" 1; *.example.com 1; } one; }""" parsed = loads(test_str) self.assertEqual(parsed, [ [['http'], [ [['map', '$http_upgrade', '$connection_upgrade'], [ ['default', 'upgrade'], ["''", 'close'], ['"~Opera Mini"', '1'], ['*.example.com', '1'] ]], ['one'] ]] ]) def test_variable_name(self): parsed = loads('try_files /typo3temp/tx_ncstaticfilecache/' '$host${request_uri}index.html @nocache;') self.assertEqual(parsed, [ ['try_files', '/typo3temp/tx_ncstaticfilecache/$host${request_uri}index.html', '@nocache'] ]) def test_weird_blocks(self): test = r""" if ($http_user_agent ~ MSIE) { rewrite ^(.*)$ /msie/$1 break; } if ($http_cookie ~* "id=([^;]+)(?:;|$)") { set $id $1; } if ($request_method = POST) { return 405; } if ($request_method) { return 403; } if ($args ~ post=140){ rewrite ^ http://example.com/; } location ~ ^/users/(.+\.(?:gif|jpe?g|png))$ { alias /data/w3/images/$1; } proxy_set_header X-Origin-URI ${scheme}://${http_host}/$request_uri; """ parsed = loads(test) self.assertEqual(parsed, [[['if', '($http_user_agent', '~', 'MSIE)'], [['rewrite', '^(.*)$', '/msie/$1', 'break']]], [['if', '($http_cookie', '~*', '"id=([^;]+)(?:;|$)")'], [['set', '$id', '$1']]], [['if', '($request_method', '=', 'POST)'], [['return', '405']]], [['if', '($request_method)'], [['return', '403']]], [['if', '($args', '~', 'post=140)'], [['rewrite', '^', 'http://example.com/']]], [['location', '~', '^/users/(.+\\.(?:gif|jpe?g|png))$'], [['alias', '/data/w3/images/$1']]], ['proxy_set_header', 'X-Origin-URI', '${scheme}://${http_host}/$request_uri']] ) def test_edge_cases(self): # quotes parsed = loads(r'"hello\""; # blah "heh heh"') self.assertEqual(parsed, [['"hello\\""'], ['#', ' blah "heh heh"']]) # if with comment parsed = loads("""if ($http_cookie ~* "id=([^;]+)(?:;|$)") { # blah ) }""") self.assertEqual(parsed, [[['if', '($http_cookie', '~*', '"id=([^;]+)(?:;|$)")'], [['#', ' blah )']]]]) # end paren test = """ one"test"; ("two"); "test")red; "test")"blue"; "test")"three; (one"test")one; one"; one"test; one"test"one; """ parsed = loads(test) self.assertEqual(parsed, [ ['one"test"'], ['("two")'], ['"test")red'], ['"test")"blue"'], ['"test")"three'], ['(one"test")one'], ['one"'], ['one"test'], ['one"test"one'] ]) self.assertRaises(ParseException, loads, r'"test"one;') # fails self.assertRaises(ParseException, loads, r'"test;') # fails # newlines test = """ server_name foo.example.com bar.example.com \ baz.example.com qux.example.com; server_name foo.example.com bar.example.com baz.example.com qux.example.com; """ parsed = loads(test) self.assertEqual(parsed, [ ['server_name', 'foo.example.com', 'bar.example.com', 'baz.example.com', 'qux.example.com'], ['server_name', 'foo.example.com', 'bar.example.com', 'baz.example.com', 'qux.example.com'] ]) # variable weirdness parsed = loads("directive $var ${var} $ ${};") self.assertEqual(parsed, [['directive', '$var', '${var}', '$', '${}']]) self.assertRaises(ParseException, loads, "server {server_name test.com};") self.assertEqual(loads("blag${dfgdfg};"), [['blag${dfgdfg}']]) self.assertRaises(ParseException, loads, "blag${dfgdf{g};") class TestUnspacedList(unittest.TestCase): """Test the UnspacedList data structure""" def setUp(self): self.a = ["\n ", "things", " ", "quirk"] self.b = ["y", " "] self.l = self.a[:] self.l2 = self.b[:] self.ul = UnspacedList(self.l) self.ul2 = UnspacedList(self.l2) def test_construction(self): self.assertEqual(self.ul, ["things", "quirk"]) self.assertEqual(self.ul2, ["y"]) def test_append(self): ul3 = copy.deepcopy(self.ul) ul3.append("wise") self.assertEqual(ul3, ["things", "quirk", "wise"]) self.assertEqual(ul3.spaced, self.a + ["wise"]) def test_add(self): ul3 = self.ul + self.ul2 self.assertEqual(ul3, ["things", "quirk", "y"]) self.assertEqual(ul3.spaced, self.a + self.b) self.assertEqual(self.ul.spaced, self.a) ul3 = self.ul + self.l2 self.assertEqual(ul3, ["things", "quirk", "y"]) self.assertEqual(ul3.spaced, self.a + self.b) def test_extend(self): ul3 = copy.deepcopy(self.ul) ul3.extend(self.ul2) self.assertEqual(ul3, ["things", "quirk", "y"]) self.assertEqual(ul3.spaced, self.a + self.b) self.assertEqual(self.ul.spaced, self.a) def test_set(self): ul3 = copy.deepcopy(self.ul) ul3[0] = "zither" l = ["\n ", "zather", "zest"] ul3[1] = UnspacedList(l) self.assertEqual(ul3, ["zither", ["zather", "zest"]]) self.assertEqual(ul3.spaced, [self.a[0], "zither", " ", l]) def test_get(self): self.assertRaises(IndexError, self.ul2.__getitem__, 2) self.assertRaises(IndexError, self.ul2.__getitem__, -3) def test_insert(self): x = UnspacedList( [['\n ', 'listen', ' ', '69.50.225.155:9000'], ['\n ', 'listen', ' ', '127.0.0.1'], ['\n ', 'server_name', ' ', '.example.com'], ['\n ', 'server_name', ' ', 'example.*'], '\n', ['listen', ' ', '5001', ' ', 'ssl']]) x.insert(5, "FROGZ") self.assertEqual(x, [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*'], ['listen', '5001', 'ssl'], 'FROGZ']) self.assertEqual(x.spaced, [['\n ', 'listen', ' ', '69.50.225.155:9000'], ['\n ', 'listen', ' ', '127.0.0.1'], ['\n ', 'server_name', ' ', '.example.com'], ['\n ', 'server_name', ' ', 'example.*'], '\n', ['listen', ' ', '5001', ' ', 'ssl'], 'FROGZ']) def test_rawlists(self): ul3 = copy.deepcopy(self.ul) ul3.insert(0, "some") ul3.append("why") ul3.extend(["did", "whether"]) del ul3[2] self.assertEqual(ul3, ["some", "things", "why", "did", "whether"]) def test_is_dirty(self): self.assertEqual(False, self.ul2.is_dirty()) ul3 = UnspacedList([]) ul3.append(self.ul) self.assertEqual(False, self.ul.is_dirty()) self.assertEqual(True, ul3.is_dirty()) ul4 = UnspacedList([[1], [2, 3, 4]]) self.assertEqual(False, ul4.is_dirty()) ul4[1][2] = 5 self.assertEqual(True, ul4.is_dirty()) if __name__ == '__main__': unittest.main() # pragma: no cover certbot-nginx-0.40.0/certbot_nginx/tests/display_ops_test.py0000644000175000017500000000301413560356424024522 0ustar ericaerica00000000000000"""Test certbot_apache.display_ops.""" import unittest from certbot.display import util as display_util from certbot.tests import util as certbot_util from certbot_nginx import parser from certbot_nginx.display_ops import select_vhost_multiple from certbot_nginx.tests import util class SelectVhostMultiTest(util.NginxTest): """Tests for certbot_nginx.display_ops.select_vhost_multiple.""" def setUp(self): super(SelectVhostMultiTest, self).setUp() nparser = parser.NginxParser(self.config_path) self.vhosts = nparser.get_vhosts() def test_select_no_input(self): self.assertFalse(select_vhost_multiple([])) @certbot_util.patch_get_utility() def test_select_correct(self, mock_util): mock_util().checklist.return_value = ( display_util.OK, [self.vhosts[3].display_repr(), self.vhosts[2].display_repr()]) vhs = select_vhost_multiple([self.vhosts[3], self.vhosts[2], self.vhosts[1]]) self.assertTrue(self.vhosts[2] in vhs) self.assertTrue(self.vhosts[3] in vhs) self.assertFalse(self.vhosts[1] in vhs) @certbot_util.patch_get_utility() def test_select_cancel(self, mock_util): mock_util().checklist.return_value = (display_util.CANCEL, "whatever") vhs = select_vhost_multiple([self.vhosts[2], self.vhosts[3]]) self.assertFalse(vhs) if __name__ == "__main__": unittest.main() # pragma: no cover certbot-nginx-0.40.0/certbot_nginx/tests/http_01_test.py0000644000175000017500000001261613560356424023463 0ustar ericaerica00000000000000"""Tests for certbot_nginx.http_01""" import unittest import mock import six from acme import challenges from certbot import achallenges from certbot.plugins import common_test from certbot.tests import acme_util from certbot_nginx.obj import Addr from certbot_nginx.tests import util class HttpPerformTest(util.NginxTest): """Test the NginxHttp01 challenge.""" account_key = common_test.AUTH_KEY achalls = [ achallenges.KeyAuthorizationAnnotatedChallenge( challb=acme_util.chall_to_challb( challenges.HTTP01(token=b"kNdwjwOeX0I_A8DXt9Msmg"), "pending"), domain="www.example.com", account_key=account_key), achallenges.KeyAuthorizationAnnotatedChallenge( challb=acme_util.chall_to_challb( challenges.HTTP01( token=b"\xba\xa9\xda?