From ea07745bbb8038c7e1105ade70c7c9eca19148b5 Mon Sep 17 00:00:00 2001 From: Valmik Jangla Date: Thu, 19 May 2016 15:41:12 +0530 Subject: [PATCH] Added command for adding Let's Encrypt SSL to site --- bench/cli.py | 5 +- bench/commands/__init__.py | 4 +- bench/commands/setup.py | 9 +++ bench/commands/utils.py | 5 ++ bench/config/lets_encrypt.py | 95 ++++++++++++++++++++++++++ bench/config/procfile.py | 4 +- bench/config/production_setup.py | 8 +-- bench/config/templates/letsencrypt.cfg | 20 ++++++ requirements.txt | 1 + 9 files changed, 141 insertions(+), 10 deletions(-) create mode 100755 bench/config/lets_encrypt.py mode change 100644 => 100755 bench/config/procfile.py mode change 100644 => 100755 bench/config/production_setup.py create mode 100755 bench/config/templates/letsencrypt.cfg diff --git a/bench/cli.py b/bench/cli.py index 3a53cd40..14f72db6 100644 --- a/bench/cli.py +++ b/bench/cli.py @@ -47,9 +47,10 @@ def check_uid(): sys.exit(1) def cmd_requires_root(): - if len(sys.argv) > 2 and sys.argv[2] in ('production', 'sudoers'): + if len(sys.argv) > 2 and sys.argv[2] in ('production', 'sudoers', 'lets-encrypt'): return True - if len(sys.argv) > 2 and sys.argv[1] in ('patch',): + #Changed > to >=, unsure if will cause the apolcaypse + if len(sys.argv) >= 2 and sys.argv[1] in ('patch', 'renew-lets-encrypt'): return True def change_dir(): diff --git a/bench/commands/__init__.py b/bench/commands/__init__.py index d66c05c6..962ad199 100644 --- a/bench/commands/__init__.py +++ b/bench/commands/__init__.py @@ -30,7 +30,7 @@ bench_command.add_command(switch_to_v5) from bench.commands.utils import (start, restart, set_nginx_port, set_ssl_certificate, set_ssl_certificate_key, set_url_root, - set_mariadb_host, set_default_site, download_translations, shell, backup_site, backup_all_sites, release) + set_mariadb_host, set_default_site, download_translations, shell, backup_site, backup_all_sites, release, renew_lets_encrypt) bench_command.add_command(start) bench_command.add_command(restart) bench_command.add_command(set_nginx_port) @@ -44,7 +44,7 @@ bench_command.add_command(shell) bench_command.add_command(backup_site) bench_command.add_command(backup_all_sites) bench_command.add_command(release) - +bench_command.add_command(renew_lets_encrypt) from bench.commands.setup import setup bench_command.add_command(setup) diff --git a/bench/commands/setup.py b/bench/commands/setup.py index c62876a7..56b33609 100644 --- a/bench/commands/setup.py +++ b/bench/commands/setup.py @@ -63,6 +63,14 @@ def setup_env(): setup_env() +@click.command('lets-encrypt') +@click.argument('site') +def setup_letsencrypt(site): + "Setup lets-encrypt for site" + from bench.config.lets_encrypt import setup_letsencrypt + setup_letsencrypt(site, bench_path='.') + + @click.command('procfile') def setup_procfile(): "Setup Procfile for bench start" @@ -88,6 +96,7 @@ setup.add_command(setup_sudoers) setup.add_command(setup_nginx) setup.add_command(setup_supervisor) setup.add_command(setup_redis) +setup.add_command(setup_letsencrypt) setup.add_command(setup_production) setup.add_command(setup_auto_update) setup.add_command(setup_backups) diff --git a/bench/commands/utils.py b/bench/commands/utils.py index a3f8bdbf..78b697b9 100644 --- a/bench/commands/utils.py +++ b/bench/commands/utils.py @@ -76,6 +76,11 @@ def download_translations(): from bench.utils import download_translations_p download_translations_p() +@click.command('renew-lets-encrypt') +def renew_lets_encrypt(): + "Renew Let's Encrypt certificate" + from bench.config.lets_encrypt import renew_certs + renew_certs() @click.command() def shell(bench='.'): diff --git a/bench/config/lets_encrypt.py b/bench/config/lets_encrypt.py new file mode 100755 index 00000000..f82f5391 --- /dev/null +++ b/bench/config/lets_encrypt.py @@ -0,0 +1,95 @@ +import bench, os, click, errno, urllib +from bench.utils import exec_cmd, update_site_config, CommandFailedError +from bench.config.common_site_config import update_config as update_common_config +from bench.config.nginx import make_nginx_conf +from bench.config.production_setup import service +from bench.config.common_site_config import get_config +from crontab import CronTab + +def setup_letsencrypt(site, bench_path): + + site_path = os.path.join(bench_path, "sites", site, "site_config.json") + if not os.path.exists(os.path.dirname(site_path)): + print "No site named "+site + return + + click.confirm('Running this will stop the nginx service temporarily causing your sites to go offline\n' + 'Do you want to continue?', + abort=True) + + if not get_config(bench_path).get("dns_multitenant"): + print "You cannot setup SSL without DNS Multitenancy" + return + + create_config(site) + run_certbot_and_setup_ssl(site, bench_path) + setup_crontab() + + +def create_config(site): + config = bench.env.get_template('letsencrypt.cfg').render(domain=site) + config_path = '/etc/letsencrypt/configs/{site}.cfg'.format(site=site) + create_dir_if_missing(config_path) + + with open(config_path, 'w') as f: + f.write(config) + + +def run_certbot_and_setup_ssl(site, bench_path): + service('nginx', 'stop') + get_certbot() + + try: + exec_cmd("{path} --config /etc/letsencrypt/configs/{site}.cfg certonly".format(path=get_certbot_path(), site=site)) + except CommandFailedError: + service('nginx', 'start') + print "There was a problem trying to setup SSL for your site" + return + + ssl_path = "/etc/letsencrypt/live/{site}/".format(site=site) + + ssl_config = { "ssl_certificate": os.path.join(ssl_path, "fullchain.pem"), + "ssl_certificate_key": os.path.join(ssl_path, "privkey.pem") } + + update_site_config(site, ssl_config, bench=bench_path) + make_nginx_conf(bench_path) + + service('nginx', 'start') + + +def setup_crontab(): + job_command = 'sudo service nginx stop && /opt/certbot-auto renew && sudo service nginx start' + user_crontab = CronTab(user=True) + if job_command not in str(user_crontab): + job = user_crontab.new(command=job_command, comment="Renew lets-encrypt every month") + job.month.every(1) + job.enable() + user_crontab.write() + + +def create_dir_if_missing(path): + if not os.path.exists(os.path.dirname(path)): + os.makedirs(os.path.dirname(path)) + + +def get_certbot(): + certbot_path = get_certbot_path() + create_dir_if_missing(certbot_path) + + if not os.path.isfile(certbot_path): + urllib.urlretrieve ("https://dl.eff.org/certbot-auto", certbot_path) + os.chmod(certbot_path, 0744) + + +def get_certbot_path(): + return "/opt/certbot-auto" + + +def renew_certs(): + click.confirm('Running this will stop the nginx service temporarily causing your sites to go offline\n' + 'Do you want to continue?', + abort=True) + + service('nginx', 'start') + exec_cmd("{path} renew".format(path=get_certbot_path())) + service('nginx', 'stop') diff --git a/bench/config/procfile.py b/bench/config/procfile.py old mode 100644 new mode 100755 index 5fa47124..598d0a3a --- a/bench/config/procfile.py +++ b/bench/config/procfile.py @@ -1,6 +1,6 @@ import bench, os, click -from bench.utils import find_executable -from bench.app import use_rq +from bench.utils import find_executable +from bench.app import use_rq from bench.config.common_site_config import get_config def setup_procfile(bench_path, force=False): diff --git a/bench/config/production_setup.py b/bench/config/production_setup.py old mode 100644 new mode 100755 index e164f11b..4461224f --- a/bench/config/production_setup.py +++ b/bench/config/production_setup.py @@ -27,14 +27,14 @@ def setup_production(user, bench='.'): if os.environ.get('NO_SERVICE_RESTART'): return - restart_service('nginx') + service('nginx', 'restart') -def restart_service(service): +def service(service, option): if os.path.basename(get_program(['systemctl']) or '') == 'systemctl' and is_running_systemd(): - exec_cmd("{service_manager} restart {service}".format(service_manager='systemctl', service=service)) + exec_cmd("{service_manager} {option} {service}".format(service_manager='systemctl', option=option, service=service)) elif os.path.basename(get_program(['service']) or '') == 'service': - exec_cmd("{service_manager} {service} restart ".format(service_manager='service', service=service)) + exec_cmd("{service_manager} {service} {option} ".format(service_manager='service', service=service, option=option)) else: # look for 'service_manager' and 'service_manager_command' in environment service_manager = os.environ.get("BENCH_SERVICE_MANAGER") diff --git a/bench/config/templates/letsencrypt.cfg b/bench/config/templates/letsencrypt.cfg new file mode 100755 index 00000000..ba57815a --- /dev/null +++ b/bench/config/templates/letsencrypt.cfg @@ -0,0 +1,20 @@ +# This is an example of the kind of things you can do in a configuration file. +# All flags used by the client can be configured here. Run Certbot with +# "--help" to learn more about the available options. + +# Use a 4096 bit RSA key instead of 2048 +rsa-key-size = 4096 + +# Uncomment and update to register with the specified e-mail address +#email = email@domain.com + +# Uncomment and update to generate certificates for the specified +# domains. +domains = {{ domain }} + +# Uncomment to use a text interface instead of ncurses +text = True + +# Uncomment to use the standalone authenticator on port 443 +authenticator = standalone +standalone-supported-challenges = tls-sni-01 diff --git a/requirements.txt b/requirements.txt index 2629b148..f5a239a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,6 @@ jinja2 virtualenv requests honcho +python-crontab semantic_version GitPython==0.3.2.rc1