├── .gitignore ├── CONTRIBUTING.rst ├── FULL_INSTALL.rst ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── Vagrantfile ├── certbot-deploy-hook-example ├── certbot_haproxy ├── __init__.py ├── authenticator.py ├── constants.py ├── tests │ ├── __init__.py │ ├── test_authenticator.py │ └── test_constants.py └── util.py ├── dev_start.sh ├── docs ├── .gitignore ├── Makefile ├── _static │ └── .gitignore ├── _templates │ └── .gitignore ├── api.rst ├── api │ ├── authenticator.rst │ └── constants.rst ├── conf.py ├── index.rst └── make.bat ├── examples ├── .gitignore ├── generate-csr.sh └── openssl.cnf ├── hsmpatch.py ├── provisioning_client.sh ├── provisioning_server.sh ├── setup.py └── tests ├── boulder-integration.sh └── integration └── _common.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | var 12 | sdist 13 | develop-eggs 14 | .installed.cfg 15 | 16 | # Installer logs 17 | pip-log.txt 18 | 19 | # Unit test / coverage reports 20 | .coverage 21 | .tox 22 | htmlcov 23 | 24 | # Translations 25 | *.mo 26 | 27 | # Virtualenv 28 | venv 29 | virtualenv 30 | include 31 | lib 32 | local 33 | man 34 | share 35 | Scripts 36 | .Python 37 | 38 | # MacOSX 39 | .DS_Store 40 | 41 | # LE HAProxy dev stuff 42 | working 43 | .vagrant* 44 | certbot.log 45 | 46 | # tmp files: 47 | *.swp 48 | 49 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | Development: Getting started 2 | ----------------------------- 3 | 4 | In order to run tests against the Let's Encrypt API we will run a Boulder 5 | server, which is the exact same server Let's Encrypt is running. The server is 6 | started in Virtual Box using Vagrant. To prevent the installation of any 7 | components and dependencies from cluttering up your computer there is also a 8 | client Virtual Box instance. Both of these machines can be setup and started by 9 | running the ``dev_start.sh`` script. This sets up a local boulder server and the 10 | letsencrypt client, so don't worry if it takes more than an hour. 11 | 12 | Vagrant machines 13 | ================ 14 | The ``dev_start.sh`` script boots two virtual machines. The first is named 15 | 'boulder' and runs a development instance of the boulder server. The second is 16 | 'lehaproxy' and runs the client. To test if the machines are setup correctly, 17 | you can SSH into the 'lehaproxy' machine, by running ``vagrant ssh 18 | lehaproxy``. Next, go to the /lehaproxy directory and run 19 | ``./tests/boulder-integration.sh``. This runs a modified version of certbot's 20 | boulder-integration test, which tests the HAProxy plugin. If the test succeeds, 21 | your development environment is setup correctly. 22 | 23 | Development: Running locally without sudo 24 | ----------------------------------------- 25 | 26 | You can't run certbot without root privileges because it needs to access 27 | ``/etc/letsencrypt``, however you can tell it not to use ``/etc/`` and use some 28 | other path in your home directory. 29 | 30 | .. code:: bash 31 | 32 | mkdir ~/projects/certbot-haproxy/working 33 | mkdir ~/projects/certbot-haproxy/working/config 34 | mkdir ~/projects/certbot-haproxy/working/logs 35 | cat <> ~/.config/letsencrypt/cli.ini 36 | work-dir=~/projects/certbot-haproxy/working/ 37 | logs-dir=~/projects/certbot-haproxy/working/logs/ 38 | config-dir=~/projects/certbot-haproxy/working/config 39 | EOF 40 | 41 | Now you can run Certbot without root privileges. 42 | 43 | Further time savers during development.. 44 | ---------------------------------------- 45 | The following options can be saved in the ``cli.ini`` file for the following 46 | reasons. 47 | 48 | - ``agree-tos``: During each request for a certificate you need to agree to the 49 | terms of service of Let's Encrypt, automatically accept them every time. 50 | - ``no-self-upgrade``: Tell LE to not upgrade itself. Could be very annoying 51 | when stuff starts to suddenly break, that worked just fine before. 52 | - ``register-unsafely-without-email``: Tell LE that you don't want to be 53 | notified by e-mail when certificates are about to expire or when the TOS 54 | changes, if you don't you will need to enter a valid e-mail address for 55 | every test run. 56 | - ``text``: Disable the curses UI, and use the plain CLI version instead. 57 | - ``domain example.org``: Enter a default domain name to request a certificate 58 | for, so you don't have to specify it every time. 59 | - ``configurator certbot-haproxy:haproxy``: Test with the HAProxy plugin every 60 | time. 61 | 62 | .. code:: bash 63 | 64 | cat <> ~/.config/letsencrypt/cli.ini 65 | agree-tos=True 66 | no-self-upgrade=True 67 | register-unsafely-without-email=True 68 | text=True 69 | domain=example.org 70 | authenticator=certbot-haproxy:haproxy-authenticator 71 | installer=certbot-haproxy:haproxy-installer 72 | EOF 73 | 74 | Setuptools version conflict 75 | --------------------------- 76 | 77 | Most likely the ``python-setuptools`` version in your os's repositories is 78 | quite outdated. You will need to install a newer version, to do this you can 79 | run: 80 | 81 | .. code:: bash 82 | 83 | pip install --upgrade setuptools 84 | 85 | Since pip is part of ``python-setuptools``, you need to have it installed before 86 | you can update. 87 | 88 | Making a `.deb` debian package 89 | ------------------------------ 90 | 91 | Requirements: 92 | 93 | - python stdeb: pip install --upgrade stdeb 94 | - dh clean: apt-get install dh-make 95 | 96 | Run the following commands in your vagrant machine: 97 | 98 | .. code:: bash 99 | 100 | apt-file update 101 | python setup.py sdist 102 | # py2dsc has a problem with vbox mounted folders 103 | mv dist/certbot-haproxy-.tar.gz ~ 104 | cd ~ 105 | py2dsc certbot-haproxy-.tar.gz 106 | cd deb_dist/certbot-haproxy- 107 | # NOTE: Not signed, no signed changes (with -uc and -us) 108 | # NOTE: Add the package to the ghtools repo 109 | dpkg-buildpackage -rfakeroot -uc -us 110 | 111 | -------------------------------------------------------------------------------- /FULL_INSTALL.rst: -------------------------------------------------------------------------------- 1 | .. _full_server_setup 2 | Full server setup 3 | ================= 4 | 5 | This document describes how to set up a server running HAProxy with certbot and 6 | the certbot-haproxy plugin. The installation below assumes you are running 7 | Debian Jessie but it should be almost entirely the same process on Ubuntu. 8 | 9 | First add the backports repo for Jessie to your apt sources. 10 | 11 | .. note:: 12 | 13 | This will not work for Ubuntu, you will need to use another source, 14 | check which version comes with your version of Ubuntu, if it is a version 15 | below 0.8, you need to find a back port PPA or download certbot from source. 16 | 17 | .. code:: bash 18 | 19 | echo "deb http://ftp.debian.org/debian jessie-backports main" >> \ 20 | /etc/apt/sources.list.d/jessie-backports.list 21 | 22 | Now update, upgrade and install some requirements: 23 | 24 | - **Some utilities:** ``sudo`` ``tcpdump`` ``ufw`` ``git`` ``curl`` ``wget`` 25 | - **OpenSSL and CA certificates:** ``openssl`` ``ca-certificates`` 26 | - **Build dependencies:** ``build-essential`` ``libffi-dev`` ``libssl-dev`` ``python-dev`` 27 | - **Python and related:** ``python`` ``python-setuptools`` 28 | - **HAProxy:** ``haproxy`` 29 | - **Python dependency managing:** ``pip`` 30 | 31 | .. code:: bash 32 | 33 | apt-get update 34 | apt-get upgrade -y 35 | apt-get install -y \ 36 | sudo tcpdump ufw git curl wget \ 37 | openssl ca-certificates \ 38 | build-essential libffi-dev libssl-dev python-dev \ 39 | python python-setuptools \ 40 | haproxy 41 | 42 | easy_install pip 43 | pip install --upgrade setuptools 44 | 45 | We also installed a simple firewall above, but it is not yet configured, let's 46 | do that now: 47 | 48 | .. code:: bash 49 | 50 | ufw allow ssh 51 | ufw allow http 52 | ufw allow https 53 | ufw default deny incoming 54 | ufw --force enable 55 | 56 | .. warning:: 57 | 58 | You probably want a little more protection for a production proxy 59 | than just this simple firewall, but it's out of the scope of this readme. 60 | 61 | Now that we have all dependencies, it's time to start a process that may take 62 | quite some time to complete. HAProxy comes with a DH parameters file that is 63 | considered weak. We need to generate a new dhparams.pem file with a prime of at 64 | least ``2048`` bit length, you can also opt for ``3072`` or ``4096``. This can 65 | take hours on lower specification hardware, but will still take minutes on 66 | faster hardware, especially with ``4096`` bit primes. Run this is in a separate 67 | ssh session or use ``screen`` of ``tmux`` to allow this to run in the 68 | background. 69 | 70 | .. code:: bash 71 | 72 | openssl dhparam -out /opt/certbot/dhparams.pem 2048 73 | 74 | Now set a hostname. 75 | 76 | .. code:: bash 77 | 78 | echo "[INSERT YOUR HOSTNAME HERE]" > /etc/hostname 79 | hostname -F /etc/hostname 80 | 81 | Run as unprivileged user 82 | ++++++++++++++++++++++++ 83 | 84 | If you want to run Certbot in an unprivileged mode, keep reading, otherwise, 85 | skip to the installation of Certbot. 86 | 87 | Certbot normally requires access to the ``/etc/`` directory, which is owned by 88 | root and therefore, Certbot needs to run as root. However, we don't like it 89 | when processes run as root, most especially when they are opening ports on a 90 | public network interface.. 91 | 92 | In order to let Certbot run as an unprivileged user, we will: 93 | 94 | - Create a ``certbot`` user with a home directory on the system so the 95 | automatic renewal of certificates can be run by this user. 96 | - Tell Certbot that the working directories are located in ``certbot``'s home 97 | directory. 98 | - Optionally: add your own user account to the Certbot user's group so you can 99 | run Certbot manually. 100 | - Allow HAProxy to access the certificates that are generated by Certbot. 101 | - Allow the certbot user to restart the HAProxy server. 102 | 103 | Lastly, to do automatic renewal of certificates, we will create a systemd timer 104 | and a service to start at every boot and every 12 hours, at a random time off 105 | the day, in order to not collectively DDOS Let's Encrypts service. 106 | 107 | .. code:: bash 108 | 109 | useradd -s /bin/bash -m -d /opt/certbot certbot 110 | usermod -a -G certbot haproxy # Allow HAProxy access to the certbot certs 111 | mkdir -p /opt/certbot/logs 112 | mkdir -p /opt/certbot/config 113 | mkdir -p /opt/certbot/.config/letsencrypt 114 | 115 | If you need to use Certbot from your user account, or if you have a daemon 116 | running on your proxy server, that configures domains on your proxy, e.g.: in a 117 | web hosting environment - you can add those users to the ``certbot`` group. 118 | 119 | .. code:: bash 120 | 121 | usermod -a -G certbot [ADD YOUR USER HERE] 122 | 123 | You will also need to tell your user what the working directory of your Certbot 124 | setup is (``/opt/certbot/``). Certbot allows you to create a configuration file 125 | with default settings in the users' home dir: 126 | ``opt/certbot/.config/letsencrypt/cli.ini``. 127 | 128 | Besides the working directory. 129 | 130 | .. code:: bash 131 | 132 | mkdir -p /opt/certbot/.config/letsencrypt 133 | cat < /opt/certbot/.config/letsencrypt/cli.ini 134 | work-dir=/opt/certbot/ 135 | logs-dir=/opt/certbot/logs/ 136 | config-dir=/opt/certbot/config 137 | EOF 138 | 139 | Next time you run Certbot, it will use our new working directory. 140 | 141 | Now to allow the certbot user to restart HAProxy, put the following in the 142 | sudoers file: 143 | 144 | .. code:: bash 145 | 146 | cat <> /etc/sudoers 147 | %certbot ALL=NOPASSWD: /bin/systemctl restart haproxy 148 | EOF 149 | 150 | Installing certbot-haproxy 151 | ++++++++++++++++++++++++++ 152 | 153 | Now we haven't done one very essential thing yet, install ``certbot-haproxy``. 154 | Since our plugin is in an alpha stage, we did not package it yet. You will need 155 | to get it from our Gitlab server. 156 | 157 | .. code:: bash 158 | 159 | git clone https://code.greenhost.net/open/certbot-haproxy.git 160 | cd ./certbot-haproxy/ 161 | sudo pip install ./ 162 | 163 | Continue reading ``_ after the quick installation instructions, at 164 | :ref:`haproxy_config` 165 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2015 Electronic Frontier Foundation and others 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | Apache License 16 | Version 2.0, January 2004 17 | http://www.apache.org/licenses/ 18 | 19 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 20 | 21 | 1. Definitions. 22 | 23 | "License" shall mean the terms and conditions for use, reproduction, 24 | and distribution as defined by Sections 1 through 9 of this document. 25 | 26 | "Licensor" shall mean the copyright owner or entity authorized by 27 | the copyright owner that is granting the License. 28 | 29 | "Legal Entity" shall mean the union of the acting entity and all 30 | other entities that control, are controlled by, or are under common 31 | control with that entity. For the purposes of this definition, 32 | "control" means (i) the power, direct or indirect, to cause the 33 | direction or management of such entity, whether by contract or 34 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 35 | outstanding shares, or (iii) beneficial ownership of such entity. 36 | 37 | "You" (or "Your") shall mean an individual or Legal Entity 38 | exercising permissions granted by this License. 39 | 40 | "Source" form shall mean the preferred form for making modifications, 41 | including but not limited to software source code, documentation 42 | source, and configuration files. 43 | 44 | "Object" form shall mean any form resulting from mechanical 45 | transformation or translation of a Source form, including but 46 | not limited to compiled object code, generated documentation, 47 | and conversions to other media types. 48 | 49 | "Work" shall mean the work of authorship, whether in Source or 50 | Object form, made available under the License, as indicated by a 51 | copyright notice that is included in or attached to the work 52 | (an example is provided in the Appendix below). 53 | 54 | "Derivative Works" shall mean any work, whether in Source or Object 55 | form, that is based on (or derived from) the Work and for which the 56 | editorial revisions, annotations, elaborations, or other modifications 57 | represent, as a whole, an original work of authorship. For the purposes 58 | of this License, Derivative Works shall not include works that remain 59 | separable from, or merely link (or bind by name) to the interfaces of, 60 | the Work and Derivative Works thereof. 61 | 62 | "Contribution" shall mean any work of authorship, including 63 | the original version of the Work and any modifications or additions 64 | to that Work or Derivative Works thereof, that is intentionally 65 | submitted to Licensor for inclusion in the Work by the copyright owner 66 | or by an individual or Legal Entity authorized to submit on behalf of 67 | the copyright owner. For the purposes of this definition, "submitted" 68 | means any form of electronic, verbal, or written communication sent 69 | to the Licensor or its representatives, including but not limited to 70 | communication on electronic mailing lists, source code control systems, 71 | and issue tracking systems that are managed by, or on behalf of, the 72 | Licensor for the purpose of discussing and improving the Work, but 73 | excluding communication that is conspicuously marked or otherwise 74 | designated in writing by the copyright owner as "Not a Contribution." 75 | 76 | "Contributor" shall mean Licensor and any individual or Legal Entity 77 | on behalf of whom a Contribution has been received by Licensor and 78 | subsequently incorporated within the Work. 79 | 80 | 2. Grant of Copyright License. Subject to the terms and conditions of 81 | this License, each Contributor hereby grants to You a perpetual, 82 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 83 | copyright license to reproduce, prepare Derivative Works of, 84 | publicly display, publicly perform, sublicense, and distribute the 85 | Work and such Derivative Works in Source or Object form. 86 | 87 | 3. Grant of Patent License. Subject to the terms and conditions of 88 | this License, each Contributor hereby grants to You a perpetual, 89 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 90 | (except as stated in this section) patent license to make, have made, 91 | use, offer to sell, sell, import, and otherwise transfer the Work, 92 | where such license applies only to those patent claims licensable 93 | by such Contributor that are necessarily infringed by their 94 | Contribution(s) alone or by combination of their Contribution(s) 95 | with the Work to which such Contribution(s) was submitted. If You 96 | institute patent litigation against any entity (including a 97 | cross-claim or counterclaim in a lawsuit) alleging that the Work 98 | or a Contribution incorporated within the Work constitutes direct 99 | or contributory patent infringement, then any patent licenses 100 | granted to You under this License for that Work shall terminate 101 | as of the date such litigation is filed. 102 | 103 | 4. Redistribution. You may reproduce and distribute copies of the 104 | Work or Derivative Works thereof in any medium, with or without 105 | modifications, and in Source or Object form, provided that You 106 | meet the following conditions: 107 | 108 | (a) You must give any other recipients of the Work or 109 | Derivative Works a copy of this License; and 110 | 111 | (b) You must cause any modified files to carry prominent notices 112 | stating that You changed the files; and 113 | 114 | (c) You must retain, in the Source form of any Derivative Works 115 | that You distribute, all copyright, patent, trademark, and 116 | attribution notices from the Source form of the Work, 117 | excluding those notices that do not pertain to any part of 118 | the Derivative Works; and 119 | 120 | (d) If the Work includes a "NOTICE" text file as part of its 121 | distribution, then any Derivative Works that You distribute must 122 | include a readable copy of the attribution notices contained 123 | within such NOTICE file, excluding those notices that do not 124 | pertain to any part of the Derivative Works, in at least one 125 | of the following places: within a NOTICE text file distributed 126 | as part of the Derivative Works; within the Source form or 127 | documentation, if provided along with the Derivative Works; or, 128 | within a display generated by the Derivative Works, if and 129 | wherever such third-party notices normally appear. The contents 130 | of the NOTICE file are for informational purposes only and 131 | do not modify the License. You may add Your own attribution 132 | notices within Derivative Works that You distribute, alongside 133 | or as an addendum to the NOTICE text from the Work, provided 134 | that such additional attribution notices cannot be construed 135 | as modifying the License. 136 | 137 | You may add Your own copyright statement to Your modifications and 138 | may provide additional or different license terms and conditions 139 | for use, reproduction, or distribution of Your modifications, or 140 | for any such Derivative Works as a whole, provided Your use, 141 | reproduction, and distribution of the Work otherwise complies with 142 | the conditions stated in this License. 143 | 144 | 5. Submission of Contributions. Unless You explicitly state otherwise, 145 | any Contribution intentionally submitted for inclusion in the Work 146 | by You to the Licensor shall be under the terms and conditions of 147 | this License, without any additional terms or conditions. 148 | Notwithstanding the above, nothing herein shall supersede or modify 149 | the terms of any separate license agreement you may have executed 150 | with Licensor regarding such Contributions. 151 | 152 | 6. Trademarks. This License does not grant permission to use the trade 153 | names, trademarks, service marks, or product names of the Licensor, 154 | except as required for reasonable and customary use in describing the 155 | origin of the Work and reproducing the content of the NOTICE file. 156 | 157 | 7. Disclaimer of Warranty. Unless required by applicable law or 158 | agreed to in writing, Licensor provides the Work (and each 159 | Contributor provides its Contributions) on an "AS IS" BASIS, 160 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 161 | implied, including, without limitation, any warranties or conditions 162 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 163 | PARTICULAR PURPOSE. You are solely responsible for determining the 164 | appropriateness of using or redistributing the Work and assume any 165 | risks associated with Your exercise of permissions under this License. 166 | 167 | 8. Limitation of Liability. In no event and under no legal theory, 168 | whether in tort (including negligence), contract, or otherwise, 169 | unless required by applicable law (such as deliberate and grossly 170 | negligent acts) or agreed to in writing, shall any Contributor be 171 | liable to You for damages, including any direct, indirect, special, 172 | incidental, or consequential damages of any character arising as a 173 | result of this License or out of the use or inability to use the 174 | Work (including but not limited to damages for loss of goodwill, 175 | work stoppage, computer failure or malfunction, or any and all 176 | other commercial damages or losses), even if such Contributor 177 | has been advised of the possibility of such damages. 178 | 179 | 9. Accepting Warranty or Additional Liability. While redistributing 180 | the Work or Derivative Works thereof, You may choose to offer, 181 | and charge a fee for, acceptance of support, warranty, indemnity, 182 | or other liability obligations and/or rights consistent with this 183 | License. However, in accepting such obligations, You may act only 184 | on Your own behalf and on Your sole responsibility, not on behalf 185 | of any other Contributor, and only if You agree to indemnify, 186 | defend, and hold each Contributor harmless for any liability 187 | incurred by, or claims asserted against, such Contributor by reason 188 | of your accepting any such warranty or additional liability. 189 | 190 | END OF TERMS AND CONDITIONS 191 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.txt 2 | include README.rst 3 | recursive-include docs * 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | HAProxy plugin for Certbot 2 | ========================== 3 | 4 | .. contents:: Table of Contents 5 | 6 | About 7 | ----- 8 | 9 | This is a certbot plugin for using certbot in combination with a HAProxy setup. 10 | Its advantage over using the standalone certbot is that it automatically places 11 | certificates in the correct directory and restarts HAProxy afterwards. It should 12 | also enable you to very easily do automatic certificate renewal. 13 | 14 | Furthermore, you can configure HAProxy to handle Boulder's authentication using 15 | the HAProxy authenticator of this plugin. 16 | 17 | It was created for use with `Greenhost's`_ shared hosting environment and can be 18 | useful to you in the following cases: 19 | 20 | - If you use HAProxy and have several domains for which you want to enable Let's 21 | Encrypt certificates. 22 | - If you yourself have a shared hosting platform that uses HAProxy to redirect 23 | to your client's websites. 24 | - Actually any case in which you want to automatically restart HAProxy after you 25 | request a new certificate. 26 | 27 | .. _Greenhost's: https://greenhost.net 28 | 29 | This plugin does not configure HAProxy for you, because HAProxy configurations 30 | can can vary a great deal. Please read the installation instructions on how to 31 | configure HAProxy for use with the plugin. If you have a good idea on how we can 32 | implement automatic HAProxy configuration, you are welcome to create a merge 33 | request or an issue. 34 | 35 | Dropped installer support in version 0.2.0+ 36 | ------------------------------------------ 37 | 38 | In version 0.2.0 the installer component is dropped. Originally the installer 39 | component made sure to place the certificates in the right directory for haproxy 40 | by combining the key and the crt. This was done because original versions of 41 | certbot executed the hooks after every domain renewal. 42 | 43 | New versions of certbot have more fine grained post install hooks. With those 44 | hooks more flexibility is added for installation. An example script and command 45 | is added in version 0.2.0+ 46 | 47 | The example script for deploy is `certbot-deploy-hook-example` 48 | 49 | 50 | Installing: Requirements 51 | ------------------------ 52 | 53 | Currently this plugin has been tested on Debian Jessie, but it will most likely 54 | work on Ubuntu 14.04+ too. If you are running Debian Wheezy, you may need to 55 | take additional steps during the installation. Thus, the requirements are: 56 | 57 | - Debian Jessie (or higher) or Ubuntu Trusty (or higher). 58 | - Python 3.0+ (Python 2.7 is still supported to be compatible with older 59 | operating systems) 60 | it has not been tested yet). 61 | - HAProxy 1.6+ 62 | - Certbot 0.19+ 63 | 64 | Installing: Getting started 65 | --------------------------- 66 | 67 | The installation below assumes you are running Debian Stretch but it should be 68 | almost entirely the same process on Ubuntu. 69 | 70 | If you are still using Jessie, you have to add the backports repo for Jessie. 71 | 72 | .. note:: 73 | 74 | This will not work for Ubuntu, you will need to use another source, 75 | check which version comes with your version of Ubuntu, if it is a version 76 | below 0.19, you need to find a back port PPA or download certbot from source. 77 | 78 | .. code:: bash 79 | 80 | echo "deb http://ftp.debian.org/debian jessie-backports main" >> \ 81 | /etc/apt/sources.list.d/jessie-backports.list 82 | 83 | Now update, upgrade and install some requirements: 84 | 85 | - **Some utilities:** ``sudo`` ``tcpdump`` ``ufw`` ``git`` ``curl`` ``wget`` 86 | - **OpenSSL and CA certificates:** ``openssl`` ``ca-certificates`` 87 | - **Build dependencies:** ``build-essential`` ``libffi-dev`` ``libssl-dev`` ``python-dev`` 88 | - **Python and related:** ``python`` ``python-setuptools`` 89 | - **HAProxy:** ``haproxy`` 90 | - **Python dependency managing:** ``pip`` 91 | 92 | .. code:: bash 93 | 94 | apt-get update 95 | apt-get upgrade -y 96 | apt-get install -y \ 97 | sudo tcpdump ufw git curl wget \ 98 | openssl ca-certificates \ 99 | build-essential libffi-dev libssl-dev python-dev \ 100 | python python-setuptools \ 101 | haproxy python3-pip python3-setuptools 102 | 103 | easy_install pip 104 | pip install --upgrade setuptools 105 | 106 | We also installed a simple firewall above, but it is not yet configured, let's 107 | do that now: 108 | 109 | .. code:: bash 110 | 111 | ufw allow ssh 112 | ufw allow http 113 | ufw allow https 114 | ufw default deny incoming 115 | ufw --force enable 116 | 117 | .. warning:: 118 | 119 | You probably want a little more protection for a production proxy 120 | than just this simple firewall, but it's out of the scope of this readme. 121 | 122 | Now that we have all dependencies, it's time to start a process that may take 123 | quite some time to complete. HAProxy comes with a DH parameters file that is 124 | considered weak. We need to generate a new dhparams.pem file with a prime of at 125 | least ``2048`` bit length, you can also opt for ``3072`` or ``4096``. This can 126 | take hours on lower specification hardware, but will still take minutes on 127 | faster hardware, especially with ``4096`` bit primes. Run this is in a separate 128 | ssh session or use ``screen`` of ``tmux`` to allow this to run in the 129 | background. 130 | 131 | .. code:: bash 132 | 133 | openssl dhparam -out /opt/certbot/dhparams.pem 2048 134 | 135 | Now set a hostname. 136 | 137 | .. code:: bash 138 | 139 | echo "[INSERT YOUR HOSTNAME HERE]" > /etc/hostname 140 | hostname -F /etc/hostname 141 | 142 | If you want to run Certbot in an unprivileged mode, keep reading, otherwise, 143 | skip to the installation of Certbot. 144 | 145 | Certbot normally requires access to the ``/etc/`` directory, which is owned by 146 | root and therefore, Certbot needs to run as root. However, we don't like it 147 | when processes run as root, most especially when they are opening ports on a 148 | public network interface.. 149 | 150 | In order to let Certbot run as an unprivileged user, we will: 151 | 152 | - Create a ``certbot`` user with a home directory on the system so the 153 | automatic renewal of certificates can be run by this user. 154 | - Tell Certbot that the working directories are located in ``certbot``'s home 155 | directory. 156 | - Optionally: add your own user account to the Certbot user's group so you can 157 | run Certbot manually. 158 | - Allow HAProxy to access the certificates that are generated by Certbot. 159 | - Allow the certbot user to restart the HAProxy server. 160 | 161 | Lastly, to do automatic renewal of certificates, we will create a systemd timer 162 | and a service to start at every boot and every 12 hours, at a random time off 163 | the day, in order to not collectively DDOS Let's Encrypts service. 164 | 165 | .. code:: bash 166 | 167 | useradd -s /bin/bash -m -d /opt/certbot certbot 168 | usermod -a -G certbot haproxy # Allow HAProxy access to the certbot certs 169 | mkdir -p /opt/certbot/logs 170 | mkdir -p /opt/certbot/config 171 | mkdir -p /opt/certbot/.config/letsencrypt 172 | 173 | If you need to use Certbot from your user account, or if you have a daemon 174 | running on your proxy server, that configures domains on your proxy, e.g.: in a 175 | web hosting environment - you can add those users to the ``certbot`` group. 176 | 177 | .. code:: bash 178 | 179 | usermod -a -G certbot [ADD YOUR USER HERE] 180 | 181 | You will also need to tell your user what the working directory of your Certbot 182 | setup is (``/opt/certbot/``). Certbot allows you to create a configuration file 183 | with default settings in the users' home dir: 184 | ``opt/certbot/.config/letsencrypt/cli.ini``. 185 | 186 | Besides the working directory. 187 | 188 | .. code:: bash 189 | 190 | mkdir -p /opt/certbot/.config/letsencrypt 191 | cat < /opt/certbot/.config/letsencrypt/cli.ini 192 | work-dir=/opt/certbot/ 193 | logs-dir=/opt/certbot/logs/ 194 | config-dir=/opt/certbot/config 195 | EOF 196 | 197 | Next time you run Certbot, it will use our new working directory. 198 | 199 | Now to allow the certbot user to restart HAProxy, put the following in the 200 | sudoers file: 201 | 202 | .. code:: bash 203 | 204 | cat <> /etc/sudoers 205 | %certbot ALL=NOPASSWD: /bin/systemctl restart haproxy 206 | EOF 207 | 208 | Now we haven't done one very essential thing yet, install ``certbot-haproxy``. 209 | Since our plugin is in an alpha stage, we did not package it yet. You will need 210 | to get it from our Gitlab server. 211 | 212 | .. code:: bash 213 | 214 | git clone https://code.greenhost.net/open/certbot-haproxy.git 215 | cd ./certbot-haproxy/ 216 | sudo pip install ./ 217 | 218 | 219 | Let's Encrypt's CA server will try to contact your proxy on port 80, which is 220 | most likely in use for your and/or your customers' websites. So we have 221 | configured our plugin to open port ``8000`` to verify control over the domain 222 | instead. Therefore we need to forward verification requests on port 80 to port 223 | 8000 internally. 224 | 225 | The sample below contains all that is required for a working load-balancing 226 | HAProxy setup that also forwards these verification requests. But it is 227 | probably not "copy-paste compatible" with your setup. So you need to piece 228 | together a configuration that works for you. 229 | 230 | .. code:: 231 | 232 | cat < /etc/haproxy/haproxy.cfg 233 | global 234 | log /dev/log local0 235 | log /dev/log local1 notice 236 | chroot /var/lib/haproxy 237 | stats socket /run/haproxy/admin.sock mode 660 level admin 238 | stats timeout 30s 239 | user haproxy 240 | group haproxy 241 | daemon 242 | 243 | # Default ciphers to use on SSL-enabled listening sockets. 244 | # Cipher suites chosen by following logic: 245 | # - Bits of security 128>256 (weighing performance vs added security) 246 | # - Key exchange: EECDH>DHE (faster first) 247 | # - Mode: GCM>CBC (streaming cipher over block cipher) 248 | # - Ephemeral: All use ephemeral key exchanges 249 | # - Explicitly disable weak ciphers and SSLv3 250 | ssl-default-bind-ciphers AES128+AESGCM+EECDH+SHA256:AES128+EECDH:AES128+AESGCM+DHE:AES128+EDH:AES256+AESGCM+EECDH:AES256+EECDH:AES256+AESGCM+EDH:AES256+EDH:-SHA:AES128+AESGCM+EECDH+SHA256:AES128+EECDH:AES128+AESGCM+DHE:AES128+EDH:AES256+AESGCM+EECDH:AES256+EECDH:AES256+AESGCM+EDH:AES256+EDH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!3DES:!DSS 251 | #ssl-default-bind-options no-sslv3 no-tls-tickets force-tlsv12 252 | ssl-default-bind-options no-sslv3 no-tls-tickets 253 | ssl-dh-param-file /opt/certbot/dhparams.pem 254 | 255 | defaults 256 | log global 257 | mode http 258 | option httplog 259 | option dontlognull 260 | timeout connect 5000 261 | timeout client 50000 262 | timeout server 50000 263 | errorfile 400 /etc/haproxy/errors/400.http 264 | errorfile 403 /etc/haproxy/errors/403.http 265 | errorfile 408 /etc/haproxy/errors/408.http 266 | errorfile 500 /etc/haproxy/errors/500.http 267 | errorfile 502 /etc/haproxy/errors/502.http 268 | errorfile 503 /etc/haproxy/errors/503.http 269 | errorfile 504 /etc/haproxy/errors/504.http 270 | 271 | frontend http-in 272 | # Listen on port 80 273 | bind \*:80 274 | # Listen on port 443 275 | # Uncomment after running certbot for the first time, a certificate 276 | # needs to be installed *before* HAProxy will be able to start when this 277 | # directive is not commented. 278 | # 279 | bind \*:443 ssl crt /opt/certbot/haproxy_fullchains/__fallback.pem crt /opt/certbot/haproxy_fullchains 280 | 281 | # Forward Certbot verification requests to the certbot-haproxy plugin 282 | acl is_certbot path_beg -i /.well-known/acme-challenge 283 | rspadd Strict-Transport-Security:\ max-age=31536000;\ includeSubDomains;\ preload 284 | rspadd X-Frame-Options:\ DENY 285 | use_backend certbot if is_certbot 286 | # The default backend is a cluster of 4 Apache servers that you need to 287 | # host. 288 | default_backend nodes 289 | 290 | backend certbot 291 | log global 292 | mode http 293 | server certbot 127.0.0.1:8000 294 | 295 | # You can also configure separate domains to force a redirect from port 80 296 | # to 443 like this: 297 | # redirect scheme https if !{ ssl_fc } and [PUT YOUR DOMAIN NAME HERE] 298 | 299 | backend nodes 300 | log global 301 | balance roundrobin 302 | option forwardfor 303 | option http-server-close 304 | option httpclose 305 | http-request set-header X-Forwarded-Port %[dst_port] 306 | http-request add-header X-Forwarded-Proto https if { ssl_fc } 307 | option httpchk HEAD / HTTP/1.1\r\nHost:localhost 308 | server node1 127.0.0.1:8080 check 309 | server node2 127.0.0.1:8080 check 310 | server node3 127.0.0.1:8080 check 311 | server node4 127.0.0.1:8080 check 312 | # If redirection from port 80 to 443 is to be forced, uncomment the next 313 | # line. Keep in mind that the bind \*:443 line should be uncommented and a 314 | # certificate should be present for all domains 315 | redirect scheme https if !{ ssl_fc } 316 | 317 | EOF 318 | 319 | systemctl restart haproxy 320 | 321 | Now you can try to run Certbot with the plugin as the Authenticator. 322 | If you already have websites configured in your HAProxy setup, you 323 | may try to install a certificate now. 324 | 325 | .. code:: bash 326 | 327 | certbot certonly --authenticator certbot-haproxy:haproxy-authenticator \ 328 | --deploy-hook /path/to/your/install/script 329 | 330 | If you want your ``certbot`` to always use our Authenticator, you 331 | can add this to your configuration file: 332 | 333 | .. code:: bash 334 | 335 | cat <> $HOME/.config/letsencrypt/cli.ini 336 | authenticator=certbot-haproxy:haproxy-authenticator 337 | EOF 338 | 339 | If you need to run in unattended mode, there are a bunch of arguments you need 340 | to set in order for Certbot to generate a certificate for you. 341 | 342 | - ``--domain [DOMAIN NAME]`` The domain name you want SSL to be enabled for. 343 | - ``--agree-tos`` Tell Certbot you agree with its `TOS`_ 344 | - ``--email [EMAIL ADDRESS]`` An e-mail address where issues with certificates 345 | can be sent to, as well as changes in the `TOS`_. Or you could supply 346 | ``--register-unsafely-without-email`` but this is not recommended. 347 | 348 | .. _TOS: https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf 349 | 350 | After you run certbot successfully once, there will be 2 certificate files in 351 | the certificate directory. This is a pre-requisite for HAProxy to start with 352 | the ``bind *:443 [..]`` directive in the configuration. 353 | 354 | You can auto renew certificates by using the systemd service and timer below. 355 | They are set to run every 12 hours because certificates that *will not* expire 356 | soon will not be replaced but certificates that *will* expire soon, will be 357 | replaced in a timely manner. The timer also starts the renewal process 2 358 | minutes after the server boots, this is done so renewal starts immediately 359 | after the server has been offline for a long time. 360 | 361 | .. code:: bash 362 | 363 | cat < /etc/systemd/system/letsencrypt.timer 364 | [Unit] 365 | Description=Run Let's Encrypt every 12 hours 366 | 367 | [Timer] 368 | # Time to wait after booting before we run first time 369 | OnBootSec=2min 370 | # Time between running each consecutive time 371 | OnUnitActiveSec=12h 372 | Unit=letsencrypt.service 373 | 374 | [Install] 375 | WantedBy=timers.target 376 | EOF 377 | 378 | cat < /etc/systemd/system/letsencrypt.service 379 | [Unit] 380 | Description=Renew Let's Encrypt Certificates 381 | 382 | [Service] 383 | Type=simple 384 | User=certbot 385 | ExecStart=/usr/bin/certbot renew -q --deploy-hook /path/to/deploy/script 386 | EOF 387 | 388 | # Enable the timer and start it, this is not necessary for the service, 389 | # since the timer starts it. 390 | systemctl enable letsencrypt.timer 391 | systemctl start letsencrypt.timer 392 | 393 | 394 | Development: Getting started 395 | ----------------------------- 396 | 397 | In order to run tests against the Let's Encrypt API we will run a Boulder 398 | server, which is the exact same server Let's Encrypt is running. The server is 399 | started in Virtual Box using Vagrant. To prevent the installation of any 400 | components and dependencies from cluttering up your computer there is also a 401 | client Virtual Box instance. Both of these machines can be setup and started by 402 | running the ``dev_start.sh`` script. This sets up a local boulder server and the 403 | letsencrypt client, so don't worry if it takes more than an hour. 404 | 405 | Vagrant machines 406 | ================ 407 | The ``dev_start.sh`` script boots two virtual machines. The first is named 408 | 'boulder' and runs a development instance of the boulder server. The second is 409 | 'lehaproxy' and runs the client. To test if the machines are setup correctly, 410 | you can SSH into the 'lehaproxy' machine, by running ``vagrant ssh 411 | lehaproxy``. Next, go to the /lehaproxy directory and run 412 | ``./tests/boulder-integration.sh``. This runs a modified version of certbot's 413 | boulder-integration test, which tests the HAProxy plugin. If the test succeeds, 414 | your development environment is setup correctly. 415 | 416 | Development: Running locally without sudo 417 | ----------------------------------------- 418 | 419 | You can't run certbot without root privileges because it needs to access 420 | ``/etc/letsencrypt``, however you can tell it not to use ``/etc/`` and use some 421 | other path in your home directory. 422 | 423 | .. code:: bash 424 | 425 | mkdir ~/projects/certbot-haproxy/working 426 | mkdir ~/projects/certbot-haproxy/working/config 427 | mkdir ~/projects/certbot-haproxy/working/logs 428 | cat <> ~/.config/letsencrypt/cli.ini 429 | work-dir=~/projects/certbot-haproxy/working/ 430 | logs-dir=~/projects/certbot-haproxy/working/logs/ 431 | config-dir=~/projects/certbot-haproxy/working/config 432 | EOF 433 | 434 | Now you can run Certbot without root privileges. 435 | 436 | Further time savers during development.. 437 | ---------------------------------------- 438 | The following options can be saved in the ``cli.ini`` file for the following 439 | reasons. 440 | 441 | - ``agree-tos``: During each request for a certificate you need to agree to the 442 | terms of service of Let's Encrypt, automatically accept them every time. 443 | - ``no-self-upgrade``: Tell LE to not upgrade itself. Could be very annoying 444 | when stuff starts to suddenly break, that worked just fine before. 445 | - ``register-unsafely-without-email``: Tell LE that you don't want to be 446 | notified by e-mail when certificates are about to expire or when the TOS 447 | changes, if you don't you will need to enter a valid e-mail address for 448 | every test run. 449 | - ``text``: Disable the curses UI, and use the plain CLI version instead. 450 | - ``domain example.org``: Enter a default domain name to request a certificate 451 | for, so you don't have to specify it every time. 452 | - ``configurator certbot-haproxy:haproxy``: Test with the HAProxy plugin every 453 | time. 454 | 455 | .. code:: bash 456 | 457 | cat <> ~/.config/letsencrypt/cli.ini 458 | agree-tos=True 459 | no-self-upgrade=True 460 | register-unsafely-without-email=True 461 | text=True 462 | domain=example.org 463 | authenticator=certbot-haproxy:haproxy-authenticator 464 | EOF 465 | 466 | Setuptools version conflict 467 | --------------------------- 468 | 469 | Most likely the ``python-setuptools`` version in your os's repositories is 470 | quite outdated. You will need to install a newer version, to do this you can 471 | run: 472 | 473 | .. code:: bash 474 | 475 | pip install --upgrade setuptools 476 | 477 | Since pip is part of ``python-setuptools``, you need to have it installed before 478 | you can update. 479 | 480 | Making a `.deb` debian package 481 | ------------------------------ 482 | 483 | Requirements: 484 | 485 | - python stdeb: pip install --upgrade stdeb 486 | - dh clean: apt-get install dh-make 487 | 488 | Run the following commands in your vagrant machine: 489 | 490 | .. code:: bash 491 | 492 | apt-file update 493 | python3 setup.py sdist 494 | # py2dsc has a problem with vbox mounted folders 495 | mv dist/certbot-haproxy-.tar.gz ~ 496 | cd ~ 497 | py2dsc --with-python3=True certbot-haproxy-.tar.gz 498 | cd deb_dist/certbot-haproxy- 499 | # NOTE: Not signed, no signed changes (with -uc and -us) 500 | # NOTE: Add the package to the ghtools repo 501 | dpkg-buildpackage -rfakeroot -uc -us 502 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | VAGRANTFILE_API_VERSION=2 4 | PROJECT_NAME = "lehaproxy" 5 | CLIENT_MEMORY=1024 6 | CLIENT_CPU_COUNT = 2 7 | CLIENT_IOAPIC = "on" 8 | CLIENT_NAT_DNS_HOSTRESOLVER="on" 9 | SERVER_MEMORY=2048 10 | SERVER_CPU_COUNT = 2 11 | SERVER_IOAPIC = "on" 12 | SERVER_NAT_DNS_HOSTRESOLVER="on" 13 | ENVS = { 14 | 'PROJECT_NAME' => PROJECT_NAME, 15 | 'PROJECT_TZ' => "Europe/Amsterdam", 16 | 'PROJECT_CLIENT_HOSTNAME' => PROJECT_NAME + ".local", 17 | 'PROJECT_SERVER_HOSTNAME' => "boulder.local", 18 | 'PROJECT_SERVER_IP' => "192.168.33.111", 19 | 'PROJECT_CLIENT_IP' => "192.168.33.222" 20 | } 21 | 22 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 23 | 24 | #config.hostmanager.enabled = true 25 | #config.hostmanager.manage_host = true 26 | config.vbguest.auto_update = true 27 | config.vbguest.no_remote = false 28 | 29 | config.vm.define "boulder", autostart: true do |server| 30 | server.vm.box = "debian/jessie64" 31 | server.vm.hostname = "boulder.local" 32 | server.vm.network :private_network, ip: ENVS['PROJECT_SERVER_IP'] 33 | server.vm.synced_folder ".", "/boulder/", type: "virtualbox" 34 | server.vm.provision "shell" do |s| 35 | s.path = './provisioning_server.sh' 36 | s.env = ENVS 37 | end 38 | server.vm.provider :virtualbox do |vb| 39 | vb.customize ["modifyvm", :id, "--memory", SERVER_MEMORY] 40 | vb.customize ["modifyvm", :id, "--cpus", SERVER_CPU_COUNT] 41 | vb.customize ["modifyvm", :id, "--ioapic", SERVER_IOAPIC] 42 | vb.customize ["modifyvm", :id, "--natdnshostresolver1", SERVER_NAT_DNS_HOSTRESOLVER] 43 | end 44 | end 45 | 46 | config.vm.define "lehaproxy", autostart: true do |client| 47 | client.vm.box = "debian/jessie64" 48 | client.vm.hostname = PROJECT_NAME + ".local" 49 | client.vm.network :private_network, ip: ENVS['PROJECT_CLIENT_IP'] 50 | client.vm.synced_folder ".", "/" + PROJECT_NAME + "/", type: "virtualbox" 51 | client.vm.provision "shell" do |s| 52 | s.path = './provisioning_client.sh' 53 | s.env = ENVS 54 | end 55 | client.vm.provider :virtualbox do |vb| 56 | vb.customize ["modifyvm", :id, "--memory", CLIENT_MEMORY] 57 | vb.customize ["modifyvm", :id, "--cpus", CLIENT_CPU_COUNT] 58 | vb.customize ["modifyvm", :id, "--ioapic", CLIENT_IOAPIC] 59 | vb.customize ["modifyvm", :id, "--natdnshostresolver1", CLIENT_NAT_DNS_HOSTRESOLVER] 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /certbot-deploy-hook-example: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import re 5 | import sys 6 | 7 | # Certbot sets an environment variable RENEWED_LINEAGE, which points to the 8 | # path of the renewed certificate. We use that path to determine and find 9 | # the files for the currently renewed certificated 10 | lineage=os.environ.get('RENEWED_LINEAGE') 11 | 12 | # If nothing renewed, exit 13 | if not lineage: 14 | sys.exit() 15 | 16 | # From the linage, we strip the 'domain name', which is the last part 17 | # of the path. 18 | result = re.match(r'.*/live/(.+)$', lineage) 19 | 20 | # If we can not recognize the path, we exit with 1 21 | if not result: 22 | sys.exit(1) 23 | 24 | # Extract the domain name 25 | domain = result.group(1) 26 | 27 | # Define a path for HAproxy where you want to write the .pem file. 28 | deploy_path="/etc/haproxy/ssl/" + domain + ".pem" 29 | 30 | # The source files can be found in below paths, constructed with the lineage 31 | # path 32 | source_key = lineage + "/privkey.pem" 33 | source_chain = lineage + "/fullchain.pem" 34 | 35 | # HAproxy requires to combine the key and chain in one .pem file 36 | with open(deploy_path, "w") as deploy, \ 37 | open(source_key, "r") as key, \ 38 | open(source_chain, "r") as chain: 39 | deploy.write(key.read()) 40 | deploy.write(chain.read()) 41 | 42 | # Here you can add your service reload command. Which will be executed after 43 | # every renewal, which is fine if you only have a few domains. 44 | 45 | # Alternative is to add the reload to the --post-hook. In that case it is only 46 | # run once after all renewals. That would be the use-case if you have a large 47 | # number of different certificates served by HAproxy. 48 | 49 | 50 | -------------------------------------------------------------------------------- /certbot_haproxy/__init__.py: -------------------------------------------------------------------------------- 1 | """Certbot HAProxy plugin.""" 2 | -------------------------------------------------------------------------------- /certbot_haproxy/authenticator.py: -------------------------------------------------------------------------------- 1 | """HAProxy Authenticator. 2 | 3 | The HAProxy Authenticator is an extension of the "standalone" authenticator 4 | that is part of certbot. It limits its functionality to only support the 5 | `http-01` challenge because `tls-sni-01` checks the challenge by connecting to 6 | port 443. We can't proxy requests to certbot because we can't see the 7 | requested uri until the request is decrypted, and we can't do decryption in 8 | HAProxy because `tls-sni-01` expects to do a TLS handshake. 9 | 10 | This authenticator creates its own ephemeral TCP listener on the necessary port 11 | in order to respond to incoming `http-01` challenges from the certificate 12 | authority. You need to forward port requests for `/.well-known/acme-challenge/` 13 | on port 80 to the configured value for `haproxy-http-01-port` (default:8000). 14 | This can be achieved by adding this example to your haproxy configuration 15 | file:: 16 | 17 | default_backend nodes 18 | 19 | acl is_certbot path_beg -i /.well-known/acme-challenge 20 | use_backend certbot if is_certbot 21 | 22 | backend certbot 23 | log global 24 | mode http 25 | server certbot 127.0.0.1:8000 26 | 27 | backend nodes 28 | log global 29 | mode http 30 | option tcplog 31 | balance roundrobin 32 | option forwardfor 33 | option http-server-close 34 | option httpclose 35 | http-request set-header X-Forwarded-Port %[dst_port] 36 | http-request add-header X-Forwarded-Proto https if { ssl_fc } 37 | option httpchk HEAD / HTTP/1.1\\r\\nHost:localhost 38 | server node1 127.0.0.1:8080 check 39 | server node2 127.0.0.1:8080 check 40 | server node3 127.0.0.1:8080 check 41 | server node4 127.0.0.1:8080 check 42 | 43 | For instructions on how to make HAProxy serve certificates that were created 44 | with this authenticator, read the documentation of the 45 | `.certbot_haproxy.installer` 46 | """ 47 | import logging 48 | 49 | import zope.component 50 | import zope.interface 51 | 52 | from acme import challenges 53 | 54 | from certbot import interfaces 55 | from certbot.plugins import standalone 56 | 57 | logger = logging.getLogger(__name__) # pylint:disable=invalid-name 58 | 59 | 60 | @zope.interface.implementer(interfaces.IAuthenticator) 61 | @zope.interface.provider(interfaces.IPluginFactory) 62 | class HAProxyAuthenticator(standalone.Authenticator): 63 | """HAProxy Authenticator.""" 64 | 65 | description = "Certbot standalone authenticator with HAProxy preset." 66 | 67 | def __init__(self, *args, **kwargs): 68 | super(HAProxyAuthenticator, self).__init__(*args, **kwargs) 69 | self.config.http01_port = self.conf('haproxy_http_01_port') 70 | 71 | @classmethod 72 | def add_parser_arguments(cls, add): 73 | """ 74 | This method adds extra CLI arguments to the plugin. 75 | The arguments can be retrieved by asking for corresponding names 76 | in `self.conf([argument name])` 77 | 78 | .. note:: This overrides a method defined in the parent, we 79 | are deliberately not calling super() because it would add 80 | arguments that are not supported. 81 | 82 | :param func add: The function to be called to add an argument. 83 | """ 84 | add( 85 | "haproxy-http-01-port", 86 | help=( 87 | "Port to open internally (default=8000), you're expected to" 88 | " forward requests to port 80 to it." 89 | ), 90 | type=int, 91 | default=8000 92 | ) 93 | 94 | @property 95 | def supported_challenges(self): 96 | """ 97 | Challenges supported by this plugin: only http-01 98 | See introduction for reasoning. 99 | 100 | :returns: List of supported challenges 101 | :rtype: list 102 | """ 103 | return [challenges.HTTP01] 104 | 105 | @staticmethod 106 | def more_info(): 107 | """ 108 | This info string only appears in the curses UI in the plugin 109 | selection sequence. 110 | """ 111 | return ( 112 | "This authenticator creates its own ephemeral TCP listener" 113 | " on the configured internal port (default=8000) in order to" 114 | " respond to incoming http-01 challenges from the certificate" 115 | " authority. In order for this port to be reached, you need to" 116 | " configure HAProxy to forward any requests to any domain on the" 117 | " http-01 port (default:80), ending in" 118 | " `/.well-known/acme-challenge/` to the http-01 port (hint:8000)." 119 | ) 120 | -------------------------------------------------------------------------------- /certbot_haproxy/constants.py: -------------------------------------------------------------------------------- 1 | """HAProxy plugin constants. 2 | 3 | Operation system specific constants are saved in this module as dictionaries, 4 | e.g.: `CLI_DEFAULTS_DEBIAN_JESSIE`. Currently these are defined for: 5 | 6 | - Debian Jessie (8) 7 | - Debian Wheezy (7) 8 | - Ubuntu Trusty (14.04) 9 | - Ubuntu Utopic (14.10) 10 | - Ubuntu Vivid (15.04) 11 | - Ubuntu Wily (15.10) 12 | - Ubuntu Xenial (16.04) 13 | 14 | You can define new lists below following the instructions hereafter, please 15 | consider making a pull-request when you do so, so others may benefit of your 16 | work too. 17 | 18 | .. attribute:: CLI_DEFAULTS_OS_NAME['service_manager'] 19 | 20 | A string containing the name of the servicemanager executable of the 21 | OS, e.g.: `systemctl` (systemd) on Debian >= 8 and Ubuntu >= 22 | 16.04; `service` on Debian Wheezy and Ubuntu =< 14.04. 23 | 24 | .. attribute:: CLI_DEFAULTS_OS_NAME['restart_cmd'] 25 | 26 | The command to restart HAProxy, this is defined as a sequence of 27 | commands and arguments, this is done so commands and arguments can be 28 | safely escaped, read more about this `here`_. 29 | 30 | .. _here: https://docs.python.org/2/library/subprocess.html 31 | 32 | .. attribute:: CLI_DEFAULTS_OS_NAME['conftest_cmd'] 33 | 34 | The command to test the HAProxy configuration, this is most likely 35 | `haproxy -c -f [path_to_configuration]` for HAProxy. This 36 | command should return exit code `0` if the configuration is correct 37 | and non-`0` if there were configuration errors. 38 | 39 | .. attribute:: CLI_DEFAULTS_OS_NAME['haproxy_config'] 40 | 41 | The path to the HAProxy configuration file. 42 | 43 | .. attribute:: CLI_DEFAULTS_OS_NAME['crt_directory'] 44 | 45 | The directory in which HAProxy is configured to search for SSL 46 | certificates. 47 | 48 | .. note:: This directory needs to be writeable by the user that runs 49 | certbot. 50 | """ 51 | 52 | import logging 53 | import re 54 | from distutils.version import LooseVersion 55 | from certbot import util 56 | from certbot import errors 57 | from certbot_haproxy.util import MemoiseNoArgs 58 | 59 | RE_HAPROXY_DOMAIN_ACL = re.compile( 60 | r'\s*acl (?P[0-9a-z_\-.]+) ' 61 | r'hdr\(host\) -i ' 62 | r'(?P' # Start group "domain" 63 | r'(?:[0-9-a-z](?:[a-z0-9-]{0,61}[a-z0-9]\.)+)' # (sub-)domain parts 64 | r'(?:[0-9-a-z](?:[a-z0-9-]{0,61}[a-z0-9]))' # TLD part 65 | r')' # End group "domain" 66 | ) 67 | 68 | CLI_DEFAULTS_DEBIAN_BASED_SYSTEMD_OS = dict( 69 | service_manager='systemctl', 70 | version_cmd=['/usr/sbin/haproxy', '-v'], 71 | restart_cmd=['sudo', 'systemctl', 'restart', 'haproxy'], 72 | # Needs the config file as an argument: 73 | conftest_cmd=['/usr/sbin/haproxy', '-c', '-f'], 74 | haproxy_config='/etc/haproxy/haproxy.cfg', 75 | # Needs to be writeable by the user that will run certbot 76 | crt_directory='/opt/certbot/haproxy_fullchains', 77 | ) 78 | 79 | CLI_DEFAULTS_DEBIAN_BASED_PRE_SYSTEMD_OS = dict( 80 | service_manager='service', 81 | version_cmd=['/usr/sbin/haproxy', '-v'], 82 | restart_cmd=['service', 'haproxy', 'restart'], 83 | # Needs the config file as an argument: 84 | conftest_cmd=['/usr/sbin/haproxy', '-c', '-f'], 85 | haproxy_config='/etc/haproxy/haproxy.cfg', 86 | # Needs to be writeable by the user that will run certbot 87 | crt_directory='/opt/certbot/haproxy_fullchains', 88 | ) 89 | 90 | CLI_DEFAULTS = { 91 | "debian": { 92 | '_min_version': '7', 93 | '_max_version': '9', 94 | '7': CLI_DEFAULTS_DEBIAN_BASED_PRE_SYSTEMD_OS, 95 | '8': CLI_DEFAULTS_DEBIAN_BASED_SYSTEMD_OS, 96 | '9': CLI_DEFAULTS_DEBIAN_BASED_SYSTEMD_OS 97 | }, 98 | "ubuntu": { 99 | '_min_version': '14.04', 100 | '_max_version': '18.04', 101 | '14.04': CLI_DEFAULTS_DEBIAN_BASED_PRE_SYSTEMD_OS, 102 | '14.10': CLI_DEFAULTS_DEBIAN_BASED_PRE_SYSTEMD_OS, 103 | '15.04': CLI_DEFAULTS_DEBIAN_BASED_SYSTEMD_OS, 104 | '15.10': CLI_DEFAULTS_DEBIAN_BASED_SYSTEMD_OS, 105 | '16.04': CLI_DEFAULTS_DEBIAN_BASED_SYSTEMD_OS, 106 | '16.10': CLI_DEFAULTS_DEBIAN_BASED_SYSTEMD_OS, 107 | '17.04': CLI_DEFAULTS_DEBIAN_BASED_SYSTEMD_OS, 108 | '17.10': CLI_DEFAULTS_DEBIAN_BASED_SYSTEMD_OS, 109 | '18.04': CLI_DEFAULTS_DEBIAN_BASED_SYSTEMD_OS, 110 | '18.10': CLI_DEFAULTS_DEBIAN_BASED_SYSTEMD_OS, 111 | '19.04': CLI_DEFAULTS_DEBIAN_BASED_SYSTEMD_OS 112 | } 113 | } 114 | 115 | logger = logging.getLogger(__name__) # pylint:disable=invalid-name 116 | 117 | 118 | @MemoiseNoArgs # Cache the return value 119 | def os_analyse(): 120 | """ 121 | Returns tuple containing the OS distro and version corresponding with 122 | supported versions and caches the result. Output is cached. 123 | 124 | :returns: (distro, version_nr) 125 | :rtype: tuple 126 | """ 127 | os_info = util.get_os_info() 128 | distro = os_info[0].lower() 129 | version = os_info[1] 130 | if distro not in CLI_DEFAULTS: 131 | raise errors.NotSupportedError( 132 | "We're sorry, your OS %s %s is currently not supported :(" 133 | " you may be able to get this plugin working by defining a list of" 134 | " CLI_DEFAULTS in our `constants` module. Please consider making " 135 | " a pull-request if you do!" 136 | ) 137 | 138 | if version not in CLI_DEFAULTS[distro]: 139 | min_version = CLI_DEFAULTS[distro]['_min_version'] 140 | max_version = CLI_DEFAULTS[distro]['_max_version'] 141 | if LooseVersion(version) < LooseVersion(min_version): 142 | raise errors.NotSupportedError( 143 | "The OS you are using (%s %s) is not supported by this" 144 | " plugin, minimum supported version is %s %s", 145 | distro, version, distro, version 146 | ) 147 | elif LooseVersion(version) > LooseVersion(max_version): 148 | logger.warn( 149 | "Your OS version \"%s %s\" is not officially supported by" 150 | " this plugin yet. Will try to run with the most recent" 151 | " set of constants (%s %s), your mileage may vary.", 152 | distro, version, distro, max_version 153 | ) 154 | version = max_version 155 | else: 156 | # Version within range but not occurring in CLI_DEFAULTS 157 | versions = CLI_DEFAULTS[distro] 158 | # Only items whose contents stripped of "." are digits, e.g.: 16.04 159 | versions = [v for v in versions if v.replace(".", "").isdigit()] 160 | compare = LooseVersion(version) 161 | for index, versionno in enumerate(sorted(versions)): 162 | # Find the highest supported version number _under_ the 163 | # detected version number. In other words: the detected version 164 | # number should be smaller than next one in the loop, but 165 | # bigger than the current one. 166 | 167 | # Next version number is? 168 | peek = versions[index+1] 169 | 170 | if LooseVersion(peek) > compare > LooseVersion(versionno): 171 | logger.warn( 172 | "Your OS version \"%s %s\" is not officially supported" 173 | " by this plugin yet. Will try to run with the most" 174 | " recent set of constants of a version before your" 175 | " os's (%s %s), your mileage may vary.", 176 | distro, version, distro, versionno 177 | ) 178 | version = versionno 179 | break 180 | 181 | return (distro, version) 182 | 183 | 184 | def os_constant(key): 185 | """Get a constant value for operating system 186 | 187 | :param str key: name of cli constant 188 | :return: value of constant for active os 189 | """ 190 | distro, version = os_analyse() 191 | return CLI_DEFAULTS[distro][version][key] 192 | -------------------------------------------------------------------------------- /certbot_haproxy/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Certbot HAProxy Tests""" 2 | from __future__ import print_function 3 | import unittest 4 | 5 | 6 | def load_tests(loader, tests, pattern=None): 7 | """Find all python files in the tests folder""" 8 | if pattern is None: 9 | pattern = 'test_*.py' 10 | print("loader: ", loader) 11 | 12 | suite = loader.discover('certbot_haproxy/tests', pattern=pattern) 13 | suite.addTests(tests) 14 | return suite 15 | -------------------------------------------------------------------------------- /certbot_haproxy/tests/test_authenticator.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import mock 3 | import os 4 | 5 | from certbot_haproxy.authenticator import HAProxyAuthenticator 6 | from acme import challenges 7 | 8 | class TestAuthenticator(unittest.TestCase): 9 | 10 | test_domain = 'le.wtf' 11 | 12 | """Test the relevant functions of the certbot_haproxy installer""" 13 | 14 | def setUp(self): 15 | mock_le_config = mock.MagicMock( 16 | # TODO: Don't know what we need here 17 | ) 18 | self.authenticator = HAProxyAuthenticator( 19 | config=mock_le_config, name="authenticator") 20 | 21 | def test_more_info(self): 22 | info = self.authenticator.more_info() 23 | self.assertIsInstance(info, str) 24 | 25 | @mock.patch("certbot_haproxy.authenticator.logger") 26 | @mock.patch("certbot.util.logger") 27 | def test_add_parser_arguments(self, util_logger, certbot_logger): 28 | """Weak test taken from apache plugin tests""" 29 | self.authenticator.add_parser_arguments(mock.MagicMock()) 30 | self.assertEqual(certbot_logger.error.call_count, 0) 31 | self.assertEqual(util_logger.error.call_count, 0) 32 | 33 | def test_supported_challenges(self): 34 | chal = self.authenticator.supported_challenges 35 | self.assertIsInstance(chal, list) 36 | self.assertTrue(challenges.HTTP01 in chal) 37 | -------------------------------------------------------------------------------- /certbot_haproxy/tests/test_constants.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from mock import patch 3 | from certbot.errors import NotSupportedError 4 | from certbot_haproxy import constants 5 | 6 | class ConstantsTest(unittest.TestCase): 7 | """ 8 | Test that the right constants are chosen and the correct warnings are 9 | given or Exceptions are raised. 10 | """ 11 | 12 | CLI_DEFAULTS = { 13 | "debian": { 14 | '_min_version': '7', 15 | '_max_version': '9', 16 | '7': 7, 17 | '8': 8, 18 | '9': 9 19 | }, 20 | "ubuntu": { 21 | '_min_version': '14.04', 22 | '_max_version': '16.04', 23 | '14.04': 1404, 24 | '14.10': 1410, 25 | '15.04': 1504, 26 | '15.10': 1510, 27 | '16.04': 1604 28 | } 29 | } 30 | 31 | @patch('certbot_haproxy.constants.CLI_DEFAULTS', new=CLI_DEFAULTS) 32 | @patch('certbot.util.get_os_info', return_value=['debian', '8']) 33 | def test_os_analyse_supported(self, *mocks): 34 | """ Test a supported version.. """ 35 | self.assertEqual( 36 | constants.os_analyse(caching_disabled=True), 37 | ('debian', '8') 38 | ) 39 | 40 | @patch('certbot_haproxy.constants.CLI_DEFAULTS', new=CLI_DEFAULTS) 41 | @patch('certbot.util.get_os_info', return_value=['debian', '10']) 42 | @patch('certbot_haproxy.constants.logger') 43 | def test_os_analyse_unsupported_new(self, m_logger, *mocks): 44 | """ Test an unsupported, too new version.. """ 45 | self.assertEqual( 46 | constants.os_analyse(caching_disabled=True), 47 | ('debian', '9') 48 | ) 49 | m_logger.warn.assert_called_once() 50 | 51 | @patch('certbot_haproxy.constants.CLI_DEFAULTS', new=CLI_DEFAULTS) 52 | @patch('certbot.util.get_os_info', return_value=['debian', '6']) 53 | def test_os_analyse_unsupported_old(self, *mocks): 54 | """ Test an unsupported too old version.. """ 55 | with self.assertRaises(NotSupportedError): 56 | constants.os_analyse(caching_disabled=True) 57 | 58 | @patch('certbot_haproxy.constants.CLI_DEFAULTS', new=CLI_DEFAULTS) 59 | @patch('certbot.util.get_os_info', return_value=['centos', '7']) 60 | def test_os_analyse_unsupported_distro(self, *mocks): 61 | """ Test an unsupported OS/distro.. """ 62 | with self.assertRaises(NotSupportedError): 63 | constants.os_analyse(caching_disabled=True) 64 | 65 | 66 | if __name__ == '__main__': 67 | unittest.main() 68 | -------------------------------------------------------------------------------- /certbot_haproxy/util.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility functions. 3 | """ 4 | from builtins import object 5 | 6 | from OpenSSL import crypto 7 | import socket 8 | 9 | 10 | class MemoiseNoArgs(object): # pylint:disable=too-few-public-methods 11 | """ 12 | Remember the output of a function with NO arguments so it does not have 13 | to be determined after the first time it's called. 14 | """ 15 | def __init__(self, function): 16 | self.function = function 17 | self.memo = None 18 | 19 | def __call__(self, caching_disabled=False): 20 | if self.memo is None or caching_disabled: 21 | self.memo = self.function() 22 | return self.memo 23 | 24 | 25 | class Memoise(object): # pylint:disable=too-few-public-methods 26 | """ 27 | Remember the output of a function with NO arguments so it does not have 28 | to be determined after the first time it's called. 29 | """ 30 | def __init__(self, function): 31 | self.function = function 32 | self.memo = {} 33 | 34 | def __call__(self, caching_disabled=False, *args): 35 | if args not in self.memo or caching_disabled: 36 | self.memo[args] = self.function(*args) 37 | return self.memo[args] 38 | 39 | 40 | def create_self_signed_cert(bits=2048, **kwargs): 41 | """ 42 | Create a self-signed certificate 43 | """ 44 | # Generate private/public key pair 45 | key = crypto.PKey() 46 | key.generate_key(crypto.TYPE_RSA, bits) 47 | 48 | # Set X.509 attributes and self-sign 49 | cert = crypto.X509() 50 | 51 | attributes = { 52 | 'countryName': u"FU", 53 | 'stateOrProvinceName': u"Oceania", 54 | 'localityName': u"London", 55 | 'organizationName': u"Ministry of Truth", 56 | 'organizationalUnitName': u"Ministry of Truth", 57 | 'commonName': socket.gethostname() 58 | } 59 | 60 | subject = cert.get_subject() 61 | for attribute, default in attributes.items(): 62 | subject.__setattr__(attribute, kwargs.pop(attribute, default)) 63 | 64 | cert.set_serial_number(kwargs.pop('serialnr', 1984)) 65 | cert.gmtime_adj_notBefore(0) 66 | cert.gmtime_adj_notAfter(315360000) # 10*365*24*60*60 67 | cert.set_issuer(cert.get_subject()) 68 | cert.set_pubkey(key) 69 | cert.sign(key, 'sha256') 70 | 71 | return ( 72 | crypto.dump_privatekey(crypto.FILETYPE_PEM, key), 73 | crypto.dump_certificate(crypto.FILETYPE_PEM, cert) 74 | ) 75 | -------------------------------------------------------------------------------- /dev_start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CMDS="vagrant" 4 | DEPS="vagrant" 5 | VAGRANT_PLUGINS_REQUIRED=("vagrant-hostmanager" "vagrant-vbguest") 6 | 7 | VERBOSE=0 8 | for arg in "$@"; do 9 | if [ "${arg}" = "-v" -o "${arg}" = "--verbose" ]; then 10 | VERBOSE=1 11 | echo "Verbose mode enabled" 12 | fi 13 | done 14 | 15 | commands_exist () { 16 | DEPS_MISSING=0 17 | for cmd in $1; do 18 | if ! type "${cmd}" &> /dev/null; then 19 | DEPS_MISSING=1 20 | echo "Dependency '${cmd}' is not installed." 21 | fi 22 | done 23 | return $DEPS_MISSING 24 | } 25 | 26 | function_defined() { 27 | type "$1" &> /dev/null; 28 | } 29 | 30 | please_install () { 31 | if [ -f /etc/redhat-release ] ; then 32 | PKMGR=$(which yum) 33 | elif [ -f /etc/debian_version ] ; then 34 | PKMGR=$(which apt-get) 35 | fi 36 | echo 37 | echo "Before running this script, please run:" 38 | echo "${PKMGR} install $1" 39 | } 40 | 41 | log () { 42 | if [ $VERBOSE -eq 1 ]; then 43 | echo "$1" 44 | fi 45 | } 46 | 47 | SUDO=0 48 | do_sudo () { 49 | if [ $SUDO -eq 0 ]; then 50 | echo "Your hosts file does not contain the required entries, will need" 51 | echo "root privileges to set them.." 52 | sudo ls &> /dev/null 53 | SUDO=1 54 | fi 55 | sudo bash -c "$@" 56 | } 57 | 58 | if ! commands_exist "${CMDS}"; then 59 | log "Missing one or more dependencies." 60 | please_install "${DEPS}" 61 | exit 1 62 | fi 63 | 64 | log "Checking for vagrant plugins.." 65 | INSTALLED=$(vagrant plugin list | awk '{print $1;}' | xargs) 66 | for PLUGIN in "${VAGRANT_PLUGINS_REQUIRED[@]}"; do 67 | if [[ $INSTALLED != *$plugin* ]]; then 68 | log "Installing vagrant plugin \"${PLUGIN}\"" 69 | vagrant plugin install "${PLUGIN}" 70 | fi 71 | done 72 | 73 | if ! grep -Fxq "192.168.33.222 testsite.nl" /etc/hosts; then 74 | do_sudo "echo '192.168.33.222 testsite.nl' >> /etc/hosts" 75 | fi 76 | 77 | log "Starting Boulder CA server instance.." 78 | if vagrant up boulder; then 79 | log "Starting LE HAProxy client vm.." 80 | vagrant up lehaproxy 81 | else 82 | log "ERROR: Couldn't start boulder server!" 83 | exit 1 84 | fi 85 | 86 | echo "You can now connect to the Vagrant instance:" 87 | echo "vagrant ssh lehaproxy" 88 | echo "After connecting please run:" 89 | echo "cd /lehaproxy/; source /lehaproxy_venv/bin/activate" 90 | echo "You can now run certbot with the HAProxy plugin installed:" 91 | echo "certbot run" 92 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | /_build/ 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(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/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/certbot-haproxy.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/certbot-haproxy.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/certbot-haproxy" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/certbot-haproxy" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/_static/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greenhost/certbot-haproxy/db1ed68148a510d63589447e045f160fa13e437b/docs/_static/.gitignore -------------------------------------------------------------------------------- /docs/_templates/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greenhost/certbot-haproxy/db1ed68148a510d63589447e045f160fa13e437b/docs/_templates/.gitignore -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | API Documentation 3 | ================= 4 | 5 | .. toctree:: 6 | :glob: 7 | 8 | api/** 9 | -------------------------------------------------------------------------------- /docs/api/authenticator.rst: -------------------------------------------------------------------------------- 1 | :mod:`certbot_haproxy.authenticator` 2 | -------------------------------------- 3 | 4 | .. automodule:: certbot_haproxy.authenticator 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/api/constants.rst: -------------------------------------------------------------------------------- 1 | :mod:`certbot_haproxy.constants` 2 | -------------------------------- 3 | 4 | .. automodule:: certbot_haproxy.constants 5 | :members: 6 | 7 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # certbot-haproxy documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Oct 18 13:39:26 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import shlex 18 | 19 | import mock 20 | 21 | 22 | here = os.path.abspath(os.path.dirname(__file__)) 23 | 24 | # If extensions (or modules to document with autodoc) are in another directory, 25 | # add these directories to sys.path here. If the directory is relative to the 26 | # documentation root, use os.path.abspath to make it absolute, like shown here. 27 | sys.path.insert(0, os.path.abspath(os.path.join(here, '..'))) 28 | 29 | # -- General configuration ------------------------------------------------ 30 | 31 | # If your documentation needs a minimal Sphinx version, state it here. 32 | needs_sphinx = '1.0' 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be 35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 36 | # ones. 37 | extensions = [ 38 | 'sphinx.ext.autodoc', 39 | 'sphinx.ext.intersphinx', 40 | 'sphinx.ext.todo', 41 | 'sphinx.ext.coverage', 42 | 'sphinx.ext.viewcode', 43 | ] 44 | 45 | autodoc_member_order = 'bysource' 46 | autodoc_default_flags = ['show-inheritance', 'private-members'] 47 | 48 | # Add any paths that contain templates here, relative to this directory. 49 | templates_path = ['_templates'] 50 | 51 | # The suffix(es) of source filenames. 52 | # You can specify multiple suffix as a list of string: 53 | # source_suffix = ['.rst', '.md'] 54 | source_suffix = '.rst' 55 | 56 | # The encoding of source files. 57 | #source_encoding = 'utf-8-sig' 58 | 59 | # The master toctree document. 60 | master_doc = 'index' 61 | 62 | # General information about the project. 63 | project = u'certbot-haproxy' 64 | copyright = u'2014-2015, Let\'s Encrypt Project' 65 | author = u'Certbot Project' 66 | 67 | # The version info for the project you're documenting, acts as replacement for 68 | # |version| and |release|, also used in various other places throughout the 69 | # built documents. 70 | # 71 | # The short X.Y version. 72 | version = '0' 73 | # The full version, including alpha/beta/rc tags. 74 | release = '0' 75 | 76 | # The language for content autogenerated by Sphinx. Refer to documentation 77 | # for a list of supported languages. 78 | # 79 | # This is also used if you do content translation via gettext catalogs. 80 | # Usually you set "language" from the command line for these cases. 81 | language = 'en' 82 | 83 | # There are two options for replacing |today|: either, you set today to some 84 | # non-false value, then it is used: 85 | #today = '' 86 | # Else, today_fmt is used as the format for a strftime call. 87 | #today_fmt = '%B %d, %Y' 88 | 89 | # List of patterns, relative to source directory, that match files and 90 | # directories to ignore when looking for source files. 91 | exclude_patterns = ['_build'] 92 | 93 | # The reST default role (used for this markup: `text`) to use for all 94 | # documents. 95 | default_role = 'py:obj' 96 | 97 | # If true, '()' will be appended to :func: etc. cross-reference text. 98 | #add_function_parentheses = True 99 | 100 | # If true, the current module name will be prepended to all description 101 | # unit titles (such as .. function::). 102 | #add_module_names = True 103 | 104 | # If true, sectionauthor and moduleauthor directives will be shown in the 105 | # output. They are ignored by default. 106 | #show_authors = False 107 | 108 | # The name of the Pygments (syntax highlighting) style to use. 109 | pygments_style = 'sphinx' 110 | 111 | # A list of ignored prefixes for module index sorting. 112 | #modindex_common_prefix = [] 113 | 114 | # If true, keep warnings as "system message" paragraphs in the built documents. 115 | #keep_warnings = False 116 | 117 | # If true, `todo` and `todoList` produce output, else they produce nothing. 118 | todo_include_todos = True 119 | 120 | 121 | # -- Options for HTML output ---------------------------------------------- 122 | 123 | # The theme to use for HTML and HTML Help pages. See the documentation for 124 | # a list of builtin themes. 125 | 126 | # http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs 127 | # on_rtd is whether we are on readthedocs.org 128 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 129 | if not on_rtd: # only import and set the theme if we're building docs locally 130 | import sphinx_rtd_theme 131 | html_theme = 'sphinx_rtd_theme' 132 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 133 | # otherwise, readthedocs.org uses their theme by default, so no need to specify it 134 | 135 | # Theme options are theme-specific and customize the look and feel of a theme 136 | # further. For a list of options available for each theme, see the 137 | # documentation. 138 | #html_theme_options = {} 139 | 140 | # Add any paths that contain custom themes here, relative to this directory. 141 | #html_theme_path = [] 142 | 143 | # The name for this set of Sphinx documents. If None, it defaults to 144 | # " v documentation". 145 | #html_title = None 146 | 147 | # A shorter title for the navigation bar. Default is the same as html_title. 148 | #html_short_title = None 149 | 150 | # The name of an image file (relative to this directory) to place at the top 151 | # of the sidebar. 152 | #html_logo = None 153 | 154 | # The name of an image file (within the static path) to use as favicon of the 155 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 156 | # pixels large. 157 | #html_favicon = None 158 | 159 | # Add any paths that contain custom static files (such as style sheets) here, 160 | # relative to this directory. They are copied after the builtin static files, 161 | # so a file named "default.css" will overwrite the builtin "default.css". 162 | html_static_path = ['_static'] 163 | 164 | # Add any extra paths that contain custom files (such as robots.txt or 165 | # .htaccess) here, relative to this directory. These files are copied 166 | # directly to the root of the documentation. 167 | #html_extra_path = [] 168 | 169 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 170 | # using the given strftime format. 171 | #html_last_updated_fmt = '%b %d, %Y' 172 | 173 | # If true, SmartyPants will be used to convert quotes and dashes to 174 | # typographically correct entities. 175 | #html_use_smartypants = True 176 | 177 | # Custom sidebar templates, maps document names to template names. 178 | #html_sidebars = {} 179 | 180 | # Additional templates that should be rendered to pages, maps page names to 181 | # template names. 182 | #html_additional_pages = {} 183 | 184 | # If false, no module index is generated. 185 | #html_domain_indices = True 186 | 187 | # If false, no index is generated. 188 | #html_use_index = True 189 | 190 | # If true, the index is split into individual pages for each letter. 191 | #html_split_index = False 192 | 193 | # If true, links to the reST sources are added to the pages. 194 | #html_show_sourcelink = True 195 | 196 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 197 | #html_show_sphinx = True 198 | 199 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 200 | #html_show_copyright = True 201 | 202 | # If true, an OpenSearch description file will be output, and all pages will 203 | # contain a tag referring to it. The value of this option must be the 204 | # base URL from which the finished HTML is served. 205 | #html_use_opensearch = '' 206 | 207 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 208 | #html_file_suffix = None 209 | 210 | # Language to be used for generating the HTML full-text search index. 211 | # Sphinx supports the following languages: 212 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 213 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 214 | #html_search_language = 'en' 215 | 216 | # A dictionary with options for the search language support, empty by default. 217 | # Now only 'ja' uses this config value 218 | #html_search_options = {'type': 'default'} 219 | 220 | # The name of a javascript file (relative to the configuration directory) that 221 | # implements a search results scorer. If empty, the default will be used. 222 | #html_search_scorer = 'scorer.js' 223 | 224 | # Output file base name for HTML help builder. 225 | htmlhelp_basename = 'certbot-haproxydoc' 226 | 227 | # -- Options for LaTeX output --------------------------------------------- 228 | 229 | latex_elements = { 230 | # The paper size ('letterpaper' or 'a4paper'). 231 | #'papersize': 'letterpaper', 232 | 233 | # The font size ('10pt', '11pt' or '12pt'). 234 | #'pointsize': '10pt', 235 | 236 | # Additional stuff for the LaTeX preamble. 237 | #'preamble': '', 238 | 239 | # Latex figure (float) alignment 240 | #'figure_align': 'htbp', 241 | } 242 | 243 | # Grouping the document tree into LaTeX files. List of tuples 244 | # (source start file, target name, title, 245 | # author, documentclass [howto, manual, or own class]). 246 | latex_documents = [ 247 | (master_doc, 'certbot-haproxy.tex', u'certbot-haproxy Documentation', 248 | u'Certbot Project', 'manual'), 249 | ] 250 | 251 | # The name of an image file (relative to this directory) to place at the top of 252 | # the title page. 253 | #latex_logo = None 254 | 255 | # For "manual" documents, if this is true, then toplevel headings are parts, 256 | # not chapters. 257 | #latex_use_parts = False 258 | 259 | # If true, show page references after internal links. 260 | #latex_show_pagerefs = False 261 | 262 | # If true, show URL addresses after external links. 263 | #latex_show_urls = False 264 | 265 | # Documents to append as an appendix to all manuals. 266 | #latex_appendices = [] 267 | 268 | # If false, no module index is generated. 269 | #latex_domain_indices = True 270 | 271 | 272 | # -- Options for manual page output --------------------------------------- 273 | 274 | # One entry per manual page. List of tuples 275 | # (source start file, name, description, authors, manual section). 276 | man_pages = [ 277 | (master_doc, 'certbot-haproxy', u'certbot-haproxy Documentation', 278 | [author], 1) 279 | ] 280 | 281 | # If true, show URL addresses after external links. 282 | #man_show_urls = False 283 | 284 | 285 | # -- Options for Texinfo output ------------------------------------------- 286 | 287 | # Grouping the document tree into Texinfo files. List of tuples 288 | # (source start file, target name, title, author, 289 | # dir menu entry, description, category) 290 | texinfo_documents = [ 291 | (master_doc, 'certbot-haproxy', u'certbot-haproxy Documentation', 292 | author, 'certbot-haproxy', 'One line description of project.', 293 | 'Miscellaneous'), 294 | ] 295 | 296 | # Documents to append as an appendix to all manuals. 297 | #texinfo_appendices = [] 298 | 299 | # If false, no module index is generated. 300 | #texinfo_domain_indices = True 301 | 302 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 303 | #texinfo_show_urls = 'footnote' 304 | 305 | # If true, do not generate a @detailmenu in the "Top" node's menu. 306 | #texinfo_no_detailmenu = False 307 | 308 | 309 | intersphinx_mapping = { 310 | 'python': ('https://docs.python.org/', None), 311 | 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), 312 | 'certbot': ('https://certbot.eff.org/docs/', None), 313 | } 314 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. certbot-haproxy documentation master file, created by 2 | sphinx-quickstart on Sun Oct 18 13:39:26 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Contents: 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | 11 | 12 | .. toctree:: 13 | :maxdepth: 1 14 | 15 | api 16 | 17 | 18 | .. automodule:: certbot_haproxy 19 | :members: 20 | 21 | .. include:: ../README.rst 22 | 23 | Indices and tables 24 | ================== 25 | 26 | * :ref:`genindex` 27 | * :ref:`modindex` 28 | * :ref:`search` 29 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\certbot-haproxy.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\certbot-haproxy.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | # generate-csr.sh: 2 | /key.pem 3 | /csr.der -------------------------------------------------------------------------------- /examples/generate-csr.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This script generates a simple SAN CSR to be used with Let's Encrypt 3 | # CA. Mostly intended for "auth --csr" testing, but, since it's easily 4 | # auditable, feel free to adjust it and use it on your production web 5 | # server. 6 | 7 | if [ "$#" -lt 1 ] 8 | then 9 | echo "Usage: $0 domain [domain...]" >&2 10 | exit 1 11 | fi 12 | 13 | domains="DNS:$1" 14 | shift 15 | for x in "$@" 16 | do 17 | domains="$domains,DNS:$x" 18 | done 19 | 20 | SAN="$domains" openssl req -config "${OPENSSL_CNF:-openssl.cnf}" \ 21 | -new -nodes -subj '/' -reqexts san \ 22 | -out "${CSR_PATH:-csr.der}" \ 23 | -keyout "${KEY_PATH:-key.pem}" \ 24 | -newkey rsa:2048 \ 25 | -outform DER 26 | # 512 or 1024 too low for Boulder, 2048 is smallest for tests 27 | 28 | echo "You can now run: certbot auth --csr ${CSR_PATH:-csr.der}" 29 | -------------------------------------------------------------------------------- /examples/openssl.cnf: -------------------------------------------------------------------------------- 1 | [ req ] 2 | distinguished_name = req_distinguished_name 3 | [ req_distinguished_name ] 4 | [ san ] 5 | subjectAltName=${ENV::SAN} 6 | -------------------------------------------------------------------------------- /hsmpatch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | """ 3 | Patch the HSM config file to set correct settings for use with a Vagrant 4 | development setup. 5 | 6 | Note: this used to be a simple patch file but since the format changed, it 7 | seems better to parse the file, change the json object and dump it back to the 8 | file. 9 | """ 10 | import simplejson as json 11 | import yaml 12 | import sys 13 | import os.path 14 | 15 | MAX_RECURSION = 100 16 | 17 | PATCHES = { 18 | "test/config/va.json": { 19 | "va": { 20 | "portConfig": { 21 | "httpPort": 80, 22 | "httpsPort": 443 23 | } 24 | } 25 | }, 26 | "test/rate-limit-policies.yml": { 27 | "certificatesPerName": { 28 | "threshold": 1000 29 | }, 30 | "certificatesPerFQDNSet": { 31 | "threshold": 1000 32 | } 33 | }, 34 | "test/test-ca.key-pkcs11.json": { 35 | "module": "/usr/lib/softhsm/libsofthsm.so", 36 | } 37 | } 38 | 39 | 40 | def recursive_update(old_obj, new_obj, depth=0): 41 | if depth > MAX_RECURSION: 42 | raise RuntimeError("Maximum recursion level reached.") 43 | 44 | if isinstance(new_obj, dict): 45 | for key, value in new_obj.items(): 46 | old_obj[key] = recursive_update( 47 | old_obj[key], new_obj[key], depth+1) 48 | elif isinstance(new_obj, (list, tuple)): 49 | # Merge lists/tuples. 50 | old_obj = old_obj + new_obj 51 | else: 52 | # Set strings, integers, etc. and set() so arrays can be 53 | # overridden. 54 | old_obj = new_obj 55 | return old_obj 56 | 57 | 58 | def patch_yaml(file, obj): 59 | with open(file, "r") as fp: 60 | yaml_obj = yaml.load(fp) 61 | yaml_obj = recursive_update(yaml_obj, obj) 62 | with open(file, "w") as fp: 63 | yaml.dump(yaml_obj, fp, default_flow_style=False) 64 | 65 | 66 | def patch_json(file, obj): 67 | with open(file, "r") as fp: 68 | json_obj = json.load(fp) 69 | json_obj = recursive_update(json_obj, obj) 70 | with open(file, "w") as fp: 71 | json.dump(json_obj, fp, indent=4) 72 | 73 | 74 | if __name__ == '__main__': 75 | try: 76 | for patch_file, patch_obj in PATCHES.items(): 77 | _, file_extension = os.path.splitext(patch_file) 78 | if file_extension in (".yml", ".yaml"): 79 | patch_yaml(patch_file, patch_obj) 80 | elif file_extension in (".json", ".js"): 81 | patch_json(patch_file, patch_obj) 82 | else: 83 | raise NotImplementedError( 84 | "Can't patch files with %s extension" % file_extension) 85 | print("Patched {}".format(os.path.abspath(patch_file))) 86 | 87 | except (OSError, IOError), exc: 88 | print( 89 | "Failed to patch the HSM for development, reason: {}".format(exc)) 90 | sys.exit(1) 91 | -------------------------------------------------------------------------------- /provisioning_client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | echo "$PROJECT_TZ" > /etc/timezone 3 | dpkg-reconfigure -f noninteractive tzdata 4 | export DEBIAN_FRONTEND="noninteractive" 5 | echo "deb http://ftp.debian.org/debian jessie-backports main" >> \ 6 | /etc/apt/sources.list.d/jessie-backports.list 7 | apt-get update 8 | apt-get upgrade -y 9 | apt-get install -y \ 10 | sudo htop net-tools tcpdump ufw git haproxy tmux watch curl wget \ 11 | openssl ca-certificates build-essential libffi-dev \ 12 | python python-setuptools python-dev libssl-dev apache2 13 | 14 | apt-get install -y -t jessie-backports certbot 15 | 16 | easy_install pip 17 | pip install --upgrade setuptools 18 | 19 | pip install virtualenv 20 | 21 | ufw allow ssh 22 | ufw allow http 23 | ufw allow https 24 | ufw allow 8080 25 | ufw default deny incoming 26 | ufw --force enable 27 | 28 | echo "${PROJECT_CLIENT_HOSTNAME}" > /etc/hostname 29 | hostname -F /etc/hostname 30 | 31 | virtualenv "/${PROJECT_NAME}_venv" -p /usr/bin/python 32 | chown -R vagrant: "/${PROJECT_NAME}_venv/" 33 | source "/${PROJECT_NAME}_venv/bin/activate" 34 | cd "/${PROJECT_NAME}" 35 | pip install --editable . 36 | 37 | cat <> /etc/hosts 38 | ${PROJECT_CLIENT_IP} le.wtf 39 | ${PROJECT_CLIENT_IP} le1.wtf 40 | ${PROJECT_CLIENT_IP} le2.wtf 41 | ${PROJECT_CLIENT_IP} le3.wtf 42 | ${PROJECT_CLIENT_IP} testsite.nl 43 | EOF 44 | 45 | mkdir -p "/${PROJECT_NAME}/working/logs" 46 | mkdir -p "/${PROJECT_NAME}/working/config" 47 | chown -R vagrant: "/${PROJECT_NAME}/working" 48 | mkdir -p /home/vagrant/.config/letsencrypt 49 | # TODO: Maybe change greenhost.nl to something that is not example.org and yet 50 | # does work. 51 | cat < /home/vagrant/.config/letsencrypt/cli.ini 52 | work-dir=/${PROJECT_NAME}/working/ 53 | logs-dir=/${PROJECT_NAME}/working/logs/ 54 | config-dir=/${PROJECT_NAME}/working/config 55 | agree-tos = True 56 | no-self-upgrade = True 57 | register-unsafely-without-email = True 58 | text = True 59 | debug = True 60 | verbose = True 61 | authenticator certbot-haproxy:haproxy-authenticator 62 | installer certbot-haproxy:haproxy-installer 63 | server http://boulder.local/directory 64 | EOF 65 | chown -R vagrant: /home/vagrant/.config/letsencrypt 66 | 67 | cat <> /root/.bashrc 68 | alias ll='ls -l' 69 | alias la='ls -A' 70 | alias l='ls -CF' 71 | EOF 72 | 73 | cat <> /home/vagrant/.bashrc 74 | alias ll='ls -l' 75 | alias la='ls -A' 76 | alias l='ls -CF' 77 | source /lehaproxy_venv/bin/activate 78 | EOF 79 | 80 | # Allow haproxy to read the dirs of the le plugin 81 | # TODO: Does this even work with the `chroot` directive? 82 | usermod -a -G vagrant haproxy 83 | 84 | mkdir -p /opt/certbot/haproxy_fullchains 85 | chown -R vagrant: /opt/certbot/ 86 | 87 | cat < /etc/haproxy/haproxy.cfg 88 | global 89 | log /dev/log local0 90 | log /dev/log local1 notice 91 | chroot /var/lib/haproxy 92 | stats socket /run/haproxy/admin.sock mode 660 level admin 93 | stats timeout 30s 94 | user haproxy 95 | group haproxy 96 | daemon 97 | 98 | # Default ciphers to use on SSL-enabled listening sockets. 99 | # Cipher suites chosen by following logic: 100 | # - Bits of security 128>256 (weighing performance vs added security) 101 | # - Key exchange: EECDH>DHE (faster first) 102 | # - Mode: GCM>CBC (streaming cipher over block cipher) 103 | # - Ephemeral: All use ephemeral key exchanges 104 | # - Explicitly disable weak ciphers and SSLv3 105 | ssl-default-bind-ciphers AES128+AESGCM+EECDH:AES128+EECDH:AES128+AESGCM+DHE:AES128+EDH:AES256+AESGCM+EECDH:AES256+EECDH:AES256+AESGCM+EDH:AES256+EDH:!SHA:!MD5:!RC4:!DES:!DSS 106 | ssl-default-bind-options no-sslv3 107 | 108 | defaults 109 | log global 110 | mode http 111 | option httplog 112 | option dontlognull 113 | timeout connect 5000 114 | timeout client 50000 115 | timeout server 50000 116 | errorfile 400 /etc/haproxy/errors/400.http 117 | errorfile 403 /etc/haproxy/errors/403.http 118 | errorfile 408 /etc/haproxy/errors/408.http 119 | errorfile 500 /etc/haproxy/errors/500.http 120 | errorfile 502 /etc/haproxy/errors/502.http 121 | errorfile 503 /etc/haproxy/errors/503.http 122 | errorfile 504 /etc/haproxy/errors/504.http 123 | 124 | frontend http-in 125 | # Listen on port 80 126 | bind *:80 127 | mode http 128 | # Listen on port 443 129 | # Uncomment after running certbot for the first time, a certificate 130 | # needs to be installed *before* HAProxy will be able to start when this 131 | # directive is not commented. 132 | # 133 | ## bind *:443 ssl crt /opt/certbot/haproxy_fullchains 134 | 135 | # Forward Cerbot verification requests to the certbot-haproxy plugin 136 | acl is_certbot path_beg -i /.well-known/acme-challenge 137 | use_backend certbot if is_certbot 138 | 139 | backend certbot 140 | log global 141 | mode http 142 | server certbot 127.0.0.1:8000 143 | 144 | # If redirection from port 80 to 443 is to be forced, uncomment the next 145 | # line. Keep in mind that the bind *:443 line should be uncommented and a 146 | # certificate should be present for all domains 147 | # redirect scheme https if !{ ssl_fc } 148 | 149 | # You can also configure separate domains to force a redirect from port 80 150 | # to 443 like this: 151 | # redirect scheme https if !{ ssl_fc } and [PUT YOUR DOMAIN NAME HERE] 152 | 153 | # The default backend is a cluster of 4 Apache servers that you need to 154 | # host. 155 | default_backend nodes 156 | 157 | backend nodes 158 | log global 159 | mode http 160 | option tcplog 161 | balance roundrobin 162 | option forwardfor 163 | option http-server-close 164 | option httpclose 165 | http-request set-header X-Forwarded-Port %[dst_port] 166 | http-request add-header X-Forwarded-Proto https if { ssl_fc } 167 | option httpchk HEAD / HTTP/1.1\r\nHost:localhost 168 | server node1 127.0.0.1:8080 check 169 | server node2 127.0.0.1:8080 check 170 | server node3 127.0.0.1:8080 check 171 | server node4 127.0.0.1:8080 check 172 | EOF 173 | 174 | cat < /etc/apache2/sites-enabled/000-default.conf 175 | 176 | ServerName testsite.nl 177 | 178 | ServerAdmin webmaster@localhost 179 | DocumentRoot /var/www/html 180 | 181 | LogLevel error 182 | 183 | ErrorLog \${APACHE_LOG_DIR}/error.log 184 | CustomLog \${APACHE_LOG_DIR}/access.log combined 185 | 186 | EOF 187 | 188 | cat < /etc/apache2/ports.conf 189 | Listen 8080 190 | EOF 191 | 192 | # Insert a line into the sudoers file that makes our user able to restart 193 | # haproxy (which it needs to do after every certificate edit) 194 | bash -c 'echo "vagrant ALL=NOPASSWD: /bin/systemctl restart haproxy" 195 | | (EDITOR="tee -a" visudo)' 196 | 197 | 198 | systemctl restart apache2 199 | systemctl restart haproxy 200 | 201 | # Scripts that run certificate renewal for all certificates every 12 hours. Only 202 | # certificates that are due are renewed. 203 | cat < /etc/systemd/system/letsencrypt.service 204 | [Unit] 205 | Description=Renew Let's Encrypt Certificates 206 | 207 | [Service] 208 | Type=simple 209 | User=vagrant 210 | ExecStart=/usr/bin/certbot renew -q 211 | EOF 212 | 213 | cat < /etc/systemd/system/letsencrypt.timer 214 | [Unit] 215 | Description=Run Let's Encrypt every 12 hours 216 | 217 | [Timer] 218 | # Time to wait after booting before we run first time 219 | OnBootSec=2min 220 | # Time between running each consecutive time 221 | OnUnitActiveSec=12h 222 | Unit=letsencrypt.service 223 | 224 | [Install] 225 | WantedBy=timers.target 226 | EOF 227 | 228 | # Reload for when there were already other scripts in place. 229 | systemctl daemon-reload 230 | # Enable and start the timer, which runs the service. 231 | systemctl enable letsencrypt.timer 232 | systemctl start letsencrypt.timer 233 | 234 | echo "Provisioning completed." 235 | -------------------------------------------------------------------------------- /provisioning_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | set -ev 3 | echo "$PROJECT_TZ" > /etc/timezone 4 | dpkg-reconfigure -f noninteractive tzdata 5 | export DEBIAN_FRONTEND="noninteractive" 6 | 7 | # Install go 1.5 8 | if [ ! -f go1.5.linux-amd64.tar.gz ]; then 9 | wget -q https://storage.googleapis.com/golang/go1.5.linux-amd64.tar.gz 10 | fi 11 | tar -C /usr/local -xzf go1.5.linux-amd64.tar.gz 12 | 13 | # Set GOROOT and GOPATH so that GO knows where it is and where it can install 14 | # deps 15 | if ! grep -Fxq "export GOROOT=/usr/local/go" ~/.variables; then 16 | echo "export GOROOT=/usr/local/go" >> ~/.variables 17 | fi 18 | if ! grep -Fxq "export GOPATH=/gopath" ~/.variables; then 19 | echo "export GOPATH=/gopath" >> ~/.variables 20 | fi 21 | if ! grep -Fxq "export GO15VENDOREXPERIMENT=1" ~/.variables; then 22 | echo "export GO15VENDOREXPERIMENT=1" >> ~/.variables 23 | fi 24 | # Add go to PATH variable 25 | if ! grep -Fxq "export PATH=\$PATH:\$GOPATH/bin:\$GOROOT/bin" ~/.variables; then 26 | echo "export PATH=\$PATH:\$GOPATH/bin:\$GOROOT/bin" >> ~/.variables 27 | fi 28 | 29 | if ! grep -Fxq "source ~/.variables" ~/.bashrc; then 30 | echo "source ~/.variables" >> ~/.bashrc 31 | fi 32 | if ! grep -Fxq "127.0.0.1 boulder boulder-rabbitmq boulder-mysql" /etc/hosts; then 33 | echo '127.0.0.1 boulder boulder-rabbitmq boulder-mysql' >> /etc/hosts 34 | fi 35 | 36 | cat <> /root/.bashrc 37 | alias ll='ls -lah' 38 | alias la='ls -A' 39 | alias l='ls -CF' 40 | EOF 41 | 42 | source ~/.variables 43 | 44 | # Add repo for MariaDb 45 | sudo apt-get install -y software-properties-common 46 | sudo apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 0xcbcb082a1bb943db 47 | sudo add-apt-repository 'deb [arch=amd64,i386] http://mirror.i3d.net/pub/mariadb/repo/10.1/debian jessie main' 48 | 49 | apt-get update 50 | apt-get upgrade -y 51 | 52 | apt-get install -y \ 53 | sudo htop net-tools tcpdump ufw git curl g++ \ 54 | openssl ca-certificates \ 55 | python2.7 python-setuptools python-virtualenv \ 56 | rabbitmq-server make libltdl-dev mariadb-server nginx-light \ 57 | softhsm libsofthsm-dev vim 58 | 59 | echo boulder.local > /etc/hostname 60 | hostname -F /etc/hostname 61 | 62 | ufw allow ssh 63 | ufw allow http 64 | ufw allow 4000 65 | ufw allow 8000 66 | ufw allow 8001 67 | ufw allow 8002 68 | ufw allow 8003 69 | ufw allow 8004 70 | ufw allow 8005 71 | ufw default deny incoming 72 | ufw --force enable 73 | 74 | # Create new go directory for GOPATH 75 | # Paths needed for installing go dependencies 76 | mkdir -p /gopath/bin 77 | mkdir -p /gopath/src 78 | 79 | virtualenv /boulder_venv -p /usr/bin/python2 80 | source /boulder_venv/bin/activate 81 | 82 | # Install godep 83 | go get github.com/tools/godep 84 | 85 | # Goose is needed by the setup script (hope this will be fixed soon) 86 | go get bitbucket.org/liamstask/goose/cmd/goose 87 | 88 | # Install boulder into the gopath 89 | go get -d github.com/letsencrypt/boulder/... 90 | 91 | # Enter the boulder directory 92 | cd $GOPATH/src/github.com/letsencrypt/boulder 93 | 94 | # Install alle dependencies 95 | godep restore 96 | 97 | # Remaining setup 98 | ./test/setup.sh 99 | 100 | # Apply softhsm configuration 101 | ./test/make-softhsm.sh 102 | 103 | # Add softhsm configuration to .variables 104 | if ! grep -Fxq "export SOFTHSM_CONF=$PWD/test/softhsm.conf" ~/.variables; then 105 | echo "export SOFTHSM_CONF=$PWD/test/softhsm.conf" >> ~/.variables 106 | fi 107 | 108 | # Change pkcs to softhsm and IP to 192.168.33.111 and set high thresholds for rate limiting 109 | if grep -Fq "/usr/local/lib/libpkcs11-proxy.so" test/test-ca.key-pkcs11.json; then 110 | pip install simplejson pyyaml 111 | /boulder/hsmpatch.py 112 | fi 113 | 114 | cat < /etc/nginx/sites-available/wfe 115 | server { 116 | listen 80; 117 | location / { 118 | proxy_pass http://localhost:4000; 119 | proxy_redirect http://localhost:4000/ \$scheme://\$host:80/; 120 | } 121 | } 122 | EOF 123 | 124 | ln -fs /etc/nginx/sites-available/wfe /etc/nginx/sites-enabled/wfe 125 | rm -rfv /etc/nginx/sites-enabled/default 126 | systemctl restart nginx 127 | 128 | cat < /lib/systemd/system/boulder.service 129 | [Unit] 130 | Description=Boulder Server 131 | After=network.target 132 | Wants=mariadb.service,rabbitmq.service 133 | [Service] 134 | Type=simple 135 | KillMode=mixed 136 | RemainAfterExit=no 137 | Restart=always 138 | Environment="GOROOT=/usr/local/go" 139 | Environment="GOPATH=/gopath" 140 | Environment="PATH=/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/gopath/bin:/usr/local/go/bin" 141 | Environment="GO15VENDOREXPERIMENT=1" 142 | Environment="SOFTHSM_CONF=/gopath/src/github.com/letsencrypt/boulder/test/softhsm.conf" 143 | Environment="FAKE_DNS=192.168.33.222" 144 | WorkingDirectory=/gopath/src/github.com/letsencrypt/boulder/ 145 | ExecStart=/boulder_venv/bin/python ./start.py 146 | [Install] 147 | WantedBy=multi-user.target 148 | EOF 149 | 150 | systemctl enable boulder.service 151 | systemctl start boulder.service 152 | 153 | 154 | echo "Provisioning completed." 155 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from setuptools import setup 4 | from setuptools import find_packages 5 | 6 | own_version = '0.2.0' 7 | certbot_version = '0.8.1' 8 | 9 | # Please update tox.ini when modifying dependency version requirements 10 | install_requires = [ 11 | 'acme>={0}'.format(certbot_version), 12 | 'certbot>={0}'.format(certbot_version), 13 | # For pkg_resources. >=1.0 so pip resolves it to a version cryptography 14 | # will tolerate; see #2599: 15 | 'setuptools>=1.0', 16 | 'zope.component', 17 | 'zope.interface', 18 | 'future', 19 | ] 20 | 21 | if sys.version_info < (2, 7): 22 | install_requires.append('mock<1.1.0') 23 | else: 24 | install_requires.append('mock') 25 | 26 | docs_extras = [ 27 | 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags 28 | 'sphinx_rtd_theme', 29 | ] 30 | 31 | long_description = ( 32 | "This is a plugin for Certbot, it enables automatically authenticating " 33 | "domains ans retrieving certificates. It can also restart HAProxy after " 34 | "new certificates are installed. However, it will not configure HAProxy " 35 | "because. HAProxy is unlikely to be used for small/simple setups like what" 36 | " Apache or NGiNX are more likely to be used for. HAProxy configurations " 37 | "vary greatly, any configuration this plugin could define is most likely " 38 | "not applicable in your environment." 39 | ) 40 | 41 | haproxy_authenticator = 'certbot_haproxy.authenticator:HAProxyAuthenticator' 42 | 43 | setup( 44 | name='certbot-haproxy', 45 | version=own_version, 46 | description="HAProxy plugin for Certbot", 47 | long_description=long_description, 48 | url='https://code.greenhost.net/open/certbot-haproxy', 49 | author="Greenhost BV", 50 | author_email='lehaproxy@greenhost.net', 51 | license='Apache License 2.0', 52 | classifiers=[ 53 | 'Development Status :: 3 - Alpha', 54 | 'Environment :: Plugins', 55 | 'Intended Audience :: System Administrators', 56 | 'License :: OSI Approved :: Apache Software License', 57 | 'Operating System :: POSIX :: Linux', 58 | 'Programming Language :: Python', 59 | 'Programming Language :: Python :: 2', 60 | 'Programming Language :: Python :: 2.6', 61 | 'Programming Language :: Python :: 2.7', 62 | 'Topic :: Internet :: WWW/HTTP', 63 | 'Topic :: Security', 64 | 'Topic :: System :: Installation/Setup', 65 | 'Topic :: System :: Networking', 66 | 'Topic :: System :: Systems Administration', 67 | 'Topic :: Utilities', 68 | ], 69 | 70 | packages=find_packages(), 71 | include_package_data=True, 72 | install_requires=install_requires, 73 | extras_require={ 74 | 'docs': docs_extras, 75 | }, 76 | entry_points={ 77 | 'certbot.plugins': [ 78 | 'haproxy-authenticator = %s' % haproxy_authenticator, 79 | ], 80 | }, 81 | # test_suite='certbot_haproxy', 82 | ) 83 | -------------------------------------------------------------------------------- /tests/boulder-integration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -xe 2 | # Simple integration test. Make sure to activate virtualenv beforehand 3 | # (source venv/bin/activate) and that you are running Boulder test 4 | # instance (see ./boulder-start.sh). 5 | # 6 | # Environment variables: 7 | # SERVER: Passed as "certbot --server" argument. 8 | # 9 | # Note: this script is called by Boulder integration test suite! 10 | 11 | set -v 12 | 13 | . ./tests/integration/_common.sh 14 | export PATH="/usr/sbin:$PATH" # /usr/sbin/nginx 15 | 16 | export GOPATH="${GOPATH:-/tmp/go}" 17 | export PATH="$GOPATH/bin:$PATH" 18 | 19 | if [ `uname` = "Darwin" ];then 20 | readlink="greadlink" 21 | else 22 | readlink="readlink" 23 | fi 24 | 25 | common_no_force_renew() { 26 | certbot_test_no_force_renew \ 27 | --authenticator certbot-haproxy:haproxy-authenticator\ 28 | --installer certbot-haproxy:haproxy-installer\ 29 | --certbot-haproxy:haproxy-installer-haproxy-ca-common-name \ 30 | "h2ppy\ h2cker\ fake\ CA" \ 31 | "$@" 32 | } 33 | 34 | common() { 35 | common_no_force_renew \ 36 | --renew-by-default \ 37 | "$@" 38 | } 39 | 40 | # common --domains le1.wtf --standalone-supported-challenges tls-sni-01 auth 41 | common --domains le2.wtf --standalone-supported-challenges http-01 run 42 | common -a manual -d le.wtf auth --rsa-key-size 4096 43 | 44 | export CSR_PATH="${root}/csr.der" KEY_PATH="${root}/key.pem" \ 45 | OPENSSL_CNF=examples/openssl.cnf 46 | ./examples/generate-csr.sh le3.wtf 47 | common auth --csr "$CSR_PATH" \ 48 | --cert-path "${root}/csr/cert.pem" \ 49 | --chain-path "${root}/csr/chain.pem" \ 50 | --fullchain-path "${root}/csr/fullchain.pem" 51 | openssl x509 -in "${root}/csr/cert.pem" -text 52 | openssl x509 -in "${root}/csr/chain.pem" -text 53 | 54 | # TODO: key-path was "${root}/csr/key.pem", so maybe make that work as well? 55 | common --domains le3.wtf install \ 56 | --cert-path "${root}/csr/cert.pem" \ 57 | --key-path "${root}/key.pem" 58 | 59 | CheckCertCount() { 60 | CERTCOUNT=`ls "${root}/conf/archive/le.wtf/cert"* | wc -l` 61 | if [ "$CERTCOUNT" -ne "$1" ] ; then 62 | echo Wrong cert count, not "$1" `ls "${root}/conf/archive/le.wtf/"*` 63 | exit 1 64 | fi 65 | } 66 | 67 | CheckCertCount 1 68 | # This won't renew (because it's not time yet) 69 | common_no_force_renew renew 70 | CheckCertCount 1 71 | 72 | # --renew-by-default is used, so renewal should occur 73 | common renew 74 | CheckCertCount 2 75 | 76 | # This will renew because the expiry is less than 10 years from now 77 | sed -i "4arenew_before_expiry = 4 years" "$root/conf/renewal/le.wtf.conf" 78 | common_no_force_renew renew --rsa-key-size 2048 79 | CheckCertCount 3 80 | 81 | # The 4096 bit setting should persist to the first renewal, but be overriden in the second 82 | 83 | size1=`wc -c ${root}/conf/archive/le.wtf/privkey1.pem | cut -d" " -f1` 84 | size2=`wc -c ${root}/conf/archive/le.wtf/privkey2.pem | cut -d" " -f1` 85 | size3=`wc -c ${root}/conf/archive/le.wtf/privkey3.pem | cut -d" " -f1` 86 | # 4096 bit PEM keys are about ~3270 bytes, 2048 ones are about 1700 bytes 87 | if [ "$size1" -lt 3000 ] || [ "$size2" -lt 3000 ] || [ "$size3" -gt 1800 ] ; then 88 | echo key sizes violate assumptions: 89 | ls -l "${root}/conf/archive/le.wtf/privkey"* 90 | exit 1 91 | fi 92 | 93 | # ECDSA 94 | openssl ecparam -genkey -name secp384r1 -out "${root}/privkey-p384.pem" 95 | SAN="DNS:ecdsa.le.wtf" openssl req -new -sha256 \ 96 | -config "${OPENSSL_CNF:-openssl.cnf}" \ 97 | -key "${root}/privkey-p384.pem" \ 98 | -subj "/" \ 99 | -reqexts san \ 100 | -outform der \ 101 | -out "${root}/csr-p384.der" 102 | common auth --csr "${root}/csr-p384.der" \ 103 | --cert-path "${root}/csr/cert-p384.pem" \ 104 | --chain-path "${root}/csr/chain-p384.pem" \ 105 | --fullchain-path "${root}/csr/fullchain-p384.pem" 106 | openssl x509 -in "${root}/csr/cert-p384.pem" -text | grep 'ASN1 OID: secp384r1' 107 | 108 | # OCSP Must Staple 109 | common auth --must-staple --domains "must-staple.le.wtf" 110 | openssl x509 -in "${root}/conf/live/must-staple.le.wtf/cert.pem" -text | grep '1.3.6.1.5.5.7.1.24' 111 | 112 | # revoke by account key 113 | common revoke --cert-path "$root/conf/live/le.wtf/cert.pem" 114 | # revoke by cert key 115 | common revoke --cert-path "$root/conf/live/le2.wtf/cert.pem" \ 116 | --key-path "$root/conf/live/le2.wtf/privkey.pem" 117 | 118 | echo "\n\nFinished successfully!" 119 | -------------------------------------------------------------------------------- /tests/integration/_common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "xxx$root" = "xxx" ]; 4 | then 5 | # The -t is required on OS X. It provides a template file path for 6 | # the kernel to use. 7 | root="$(mktemp -d -t leitXXXX)" 8 | echo "Root integration tests directory: $root" 9 | fi 10 | store_flags="--config-dir $root/conf --work-dir $root/work" 11 | store_flags="$store_flags --logs-dir $root/logs" 12 | export root store_flags 13 | 14 | certbot_test () { 15 | certbot_test_no_force_renew \ 16 | --renew-by-default \ 17 | "$@" 18 | } 19 | 20 | certbot_test_no_force_renew () { 21 | certbot \ 22 | --server "${SERVER:-http://boulder.local:4000/directory}" \ 23 | --no-verify-ssl \ 24 | --tls-sni-01-port 5001 \ 25 | --http-01-port 8000 \ 26 | --manual-test-mode \ 27 | $store_flags \ 28 | --non-interactive \ 29 | --no-redirect \ 30 | --agree-tos \ 31 | --register-unsafely-without-email \ 32 | --debug \ 33 | "$@" 34 | } 35 | --------------------------------------------------------------------------------