copr: rework letsencrypt

We need two-phase configuration for Let's Encrypt:

1. initialize; the letsencrypt automation, this is happening only
   once per VM (when it is spawned)
2. periodic; using certbot-renew.timer.

Both those phases are now wrapped into copr/certbot role.

Phase 1. needs to be done before the web-server is started (so
include the role on appropriate place in the playbook), so we can
do 'certbot --standalone' (bounds to port 80).
Phase 2. is accomplished using the running web-server, using
'certbot renew --webroot'.

If (and only if) the certificate is renewed, web server needs to
be restarted (and lighttpd needs to have post-processed
certificate format).  So we also need the deploy hook script in
hand (two actually, lighttpd/httpd) so 'certbot renew'
automatically does what we expect it to do.
This commit is contained in:
Pavel Raiskup
2019-07-19 11:01:40 +02:00
parent cc15118a29
commit 3cc27430b6
18 changed files with 158 additions and 256 deletions

View File

@@ -29,3 +29,13 @@ csi_security_category: Moderate
csi_primary_contact: "msuchy (mirek), frostyx, dturecek, praiskup IRC #fedora-admin, #fedora-buildsys"
csi_purpose: Provide the testing environment of copr's backend
csi_relationship: This host is the testing environment for the cloud infrastructure of copr's backend
# consumed by roles/copr/certbot
letsencrypt:
predefined_deploy_script: lighttpd
certificates:
copr-be-dev.cloud.fedoraproject.org:
domains:
- copr-be-dev.cloud.fedoraproject.org
challenge_dir: /var/lib/copr/public_html
mail: copr-devel@lists.fedorahosted.org

View File

@@ -18,3 +18,13 @@ frontend_base_url: "https://copr-fe-dev.cloud.fedoraproject.org"
dist_git_base_url: "copr-dist-git-dev.fedorainfracloud.org"
ansible_ifcfg_blacklist: true
# consumed by roles/copr/certbot
letsencrypt:
predefined_deploy_script: httpd
certificates:
copr-fe-dev.cloud.fedoraproject.org:
domains:
- copr-fe-dev.cloud.fedoraproject.org
challenge_dir: /var/www/html
mail: copr-devel@lists.fedorahosted.org

View File

@@ -2,3 +2,13 @@
tcp_ports: [22, 80, 443]
datacenter: cloud
freezes: false
# consumed by roles/copr/certbot
letsencrypt:
predefined_deploy_script: httpd
certificates:
copr-dist-git.fedorainfracloud.org:
domains:
- copr-dist-git.fedorainfracloud.org
challenge_dir: /var/www/html
mail: copr-devel@lists.fedorahosted.org

View File

@@ -3,3 +3,13 @@ tcp_ports: [22, 80, 443]
datacenter: cloud
freezes: false
devel: true
# consumed by roles/copr/certbot
letsencrypt:
predefined_deploy_script: httpd
certificates:
copr-dist-git-dev.fedorainfracloud.org:
domains:
- copr-dist-git-dev.fedorainfracloud.org
challenge_dir: /var/www/html
mail: copr-devel@lists.fedorahosted.org

View File

@@ -1,75 +0,0 @@
# This playbook is inspired by
# https://mikeshultz.com/setting-up-lets-encrypt-with-lighttpd-and-certbot.html
# Particularly it follows "(Option B) The Hard Way"
- name: Install required packages
dnf:
state: latest
name: certbot
- name: Create The Web Root
file:
path: /var/certbot/public_html/.well-known/
state: directory
owner: lighttpd
group: lighttpd
mode: g+s
- name: certbot, correct fcontext mapping the web root
sefcontext:
target: '/var/certbot/public_html/.well-known(/.*)'
setype: httpd_sys_content_t
state: present
- name: certbot, restorecon the web root
file:
path: /var/certbot/public_html/.well-know
state: directory
setype: httpd_sys_content_t
- name: Check that cert file exists
stat:
path: "/etc/letsencrypt/live/copr-be-dev.cloud.fedoraproject.org/cert.pem"
register: stat_cert
# This is only for initial setup
# In such case, lighttpd config expects certificate files in /etc/letsencrypt/live,
# but it doesn't exist yet and therefore lighttpd refuses to start at all.
# We will use certbot standalone server to get certificates and then let the playbook to
# parse them and start the lighttpd again.
# Once we have at least some existing certificate files, this step will be skipped.
- name: Should admin run certbot?
fail:
msg:
- "There are no certificates yet"
- "Please temporarily stop lighttpd and run:"
- " certbot certonly --standalone --manual-public-ip-logging-ok -d copr-be-dev.cloud.fedoraproject.org"
- "Let it stopped and re-run the playbook."
when:
- stat_cert.stat.exists == False
- name: Reformat Cert Files For Lighttpd
shell: cat /etc/letsencrypt/live/copr-be-dev.cloud.fedoraproject.org/privkey.pem /etc/letsencrypt/live/copr-be-dev.cloud.fedoraproject.org/cert.pem > /etc/letsencrypt/live/copr-be-dev.cloud.fedoraproject.org/combined.pem
- name: Give Lighty Permissions
file:
path: "{{ item }}"
group: lighttpd
mode: g+x
with_items:
- /etc/letsencrypt
- /etc/letsencrypt/live
- name: Service lighttpd should be running
service:
name: lighttpd
state: started
- name: Install config for cert renewal
template: src="sysconfig/certbot" dest="/etc/sysconfig/certbot" owner=root group=root
- name: Automatize cert renewal
service:
name: certbot-renew.timer
state: started
enabled: yes

View File

@@ -93,12 +93,13 @@
notify:
- restart lighttpd
# TODO: move production to Let's Encrypt as well as devel
- name: install certificates for production
when: not devel
import_tasks: "install_certs.yml"
- name: letsencrypt cert
import_tasks: "letsencrypt.yml"
include_role: name=copr/certbot
when: devel
tags:
- config

View File

@@ -1,54 +0,0 @@
## NOTE ##
# If a hook is set here then it will be used for all
# certificates and will override any per certificate
# hook configuration in place.
# Command to be run in a shell before obtaining any
# certificates. Intended primarily for renewal, where it
# can be used to temporarily shut down a webserver that
# might conflict with the standalone plugin. This will
# only be called if a certificate is actually to be
# obtained/renewed. When renewing several certificates
# that have identical pre-hooks, only the first will be
# executed.
#
# An example to stop the MTA before updating certs would be
# PRE_HOOK="--pre-hook 'systemctl stop postfix'"
PRE_HOOK=""
# Command to be run in a shell after attempting to
# obtain/renew certificates. Can be used to deploy
# renewed certificates, or to restart any servers that
# were stopped by --pre-hook. This is only run if an
# attempt was made to obtain/renew a certificate. If
# multiple renewed certificates have identical post-
# hooks, only one will be run.
#
# An example to restart httpd would be:
# POST_HOOK="--post-hook 'systemctl restart httpd'"
POST_HOOK=""
# Command to be run in a shell once for each
# successfully renewed certificate. For this command,
# the shell variable $RENEWED_LINEAGE will point to the
# config live subdirectory containing the new certs and
# keys; the shell variable $RENEWED_DOMAINS will contain
# a space-delimited list of renewed cert domains
#
# An example to run a script to alert each cert would be:
# RENEW_HOOK="--renew-hook /usr/local/bin/cert-notifier.sh"
RENEW_HOOK=""
# Any other misc arguments for the renewal
# See certbot -h renew for full list
#
# An example to force renewal for certificates not due yet
# CERTBOT_ARGS="--force-renewal"
#
# The following command is produced
# certbot renew **CERTBOT_ARGS
{% if devel %}
CERTBOT_ARGS="--webroot -w /var/certbot/public_html --cert-name copr-be-dev.cloud.fedoraproject.org"
{% else %}
CERTBOT_ARGS=""
{% endif %}

View File

@@ -0,0 +1,60 @@
- name: install certbot package
package: name=certbot state=present
- name: install certbot config
template: src=certbot.j2 dest=/etc/sysconfig/certbot
mode=0644
- name: install certbot deploy script
template: src={{ letsencrypt.predefined_deploy_script }}
dest=/usr/libexec/auto-certbot-deploy
mode=0755
when: letsencrypt.predefined_deploy_script is defined
- name: check whether we need to initialize letsencrypt first
stat: path="/etc/letsencrypt/live/{{ item.key }}"
register: le_stat_checks
with_dict: "{{ letsencrypt.certificates }}"
- name: initialize certbot configuration
shell: |
certbot certonly --standalone \
-w {{ item.item.value.challenge_dir }} \
-d {{ item.item.value.domains | join(' -d ') }} \
--cert-name {{ item.item.key }} \
-m {{ item.item.value.mail }} \
--agree-tos \
-n >> /tmp/call
when:
- not item.stat.exists
with_items: "{{ le_stat_checks.results }}"
- name: configure certbot to use webroot next time
ini_file: dest="/etc/letsencrypt/renewal/{{ item.item.key }}.conf"
section=renewalparams
option=authenticator
value=webroot
with_items: "{{ le_stat_checks.results }}"
- name: configure certbot to use webroot next time
ini_file: dest="/etc/letsencrypt/renewal/{{ item.item.key }}.conf"
section=renewalparams
option=webroot_path
value="{{ item.item.value.challenge_dir }},"
with_items: "{{ le_stat_checks.results }}"
- name: post init script
shell: |
/usr/libexec/auto-certbot-deploy \
--init {{ item.item.key }}
when:
- letsencrypt.predefined_deploy_script is defined
- not item.stat.exists
with_items: "{{ le_stat_checks.results }}"
- name: Automatize cert renewal
service:
name: certbot-renew.timer
state: started
enabled: yes

View File

@@ -0,0 +1,3 @@
- include_tasks: letsencrypt.yml
when:
- letsencrypt is defined

View File

@@ -0,0 +1,13 @@
# Usually it is enough to do `certbot renew --dry-run` to check that certbot is
# configured correctly (anytime, without rate limits). You can though (up to 5x
# a week) try force renewal. It is both affecting your letsencrypt quota, and
# it requires web server restart. But if you still want to try, please
# uncomment the following config option, and hit `systemctl start
# certbot-renew.service`. WARNING WARNING Don't forget to comment it again!
#CERTBOT_ARGS="--force-renewal"
{% if letsencrypt.predefined_deploy_script %}
# Execute the script after certificate renewal, usually this is needed to
# restart web server, etc.
DEPLOY_HOOK="--deploy-hook /usr/libexec/auto-certbot-deploy"
{% endif %}

View File

@@ -0,0 +1,5 @@
#! /bin/sh
case $1 in --init) exit 0 ;; esac
systemctl restart httpd

View File

@@ -0,0 +1,26 @@
#! /bin/sh
set -e
pem_fixup()
{
cat "/etc/letsencrypt/live/$1/cert.pem" \
"/etc/letsencrypt/live/$1/privkey.pem" \
> "/etc/letsencrypt/live/$1/combined.pem"
}
case $1 in
--init)
pem_fixup "$2"
exit 0
;;
esac
# Automatically called by certbot.
test -z "$RENEWED_DOMAINS" && echo "you are not certbot" && exit 1
for domain in $RENEWED_DOMAINS; do
pem_fixup "$domain"
done
systemctl restart lighttpd

View File

@@ -56,6 +56,9 @@
notify:
- reload httpd
- name: letsencrypt for copr-dist-git
include_role: name=copr/certbot
- name: install copr-dist-git httpd config
copy: src="httpd/{{ item }}" dest="/etc/httpd/conf.d/{{ item }}"
with_items:
@@ -65,34 +68,10 @@
notify:
- reload httpd
- name: letsencrypt cert
include_role: name=certbot
- template: src="ssl.conf.j2" dest="/etc/httpd/conf.d/ssl.conf" owner=root group=root mode=0644
notify:
- reload httpd
- name: certbot, correct fcontext mapping the web root
sefcontext:
target: '/srv/web/acme-challenge/.well-known(/.*)'
setype: httpd_sys_content_t
state: present
- name: certbot, restorecon the web root
file:
path: /srv/web/acme-challenge/.well-known
state: directory
setype: httpd_sys_content_t
- name: Install config for cert renewal
template: src="sysconfig/certbot" dest="/etc/sysconfig/certbot" owner=root group=root
- name: Automatize cert renewal
service:
name: certbot-renew.timer
state: started
enabled: yes
- name: temporary logrotation fix until copr-dist-git 0.26 is released & deployed
copy: src="logrotate.d/copr-dist-git" dest="/etc/logrotate.d/copr-dist-git"

View File

@@ -1,9 +1,3 @@
<VirtualHost *:80>
RewriteEngine on
RewriteRule ^/\.well-known/(.*) /srv/web/acme-challenge/.well-known/$1 [L]
RewriteRule "^/?(.*)" "https://%{HTTP_HOST}/$1" [L,R=301,NE]
</VirtualHost>
Listen 443 https
SSLPassPhraseDialog exec:/usr/libexec/httpd-ssl-pass-dialog

View File

@@ -1,54 +0,0 @@
## NOTE ##
# If a hook is set here then it will be used for all
# certificates and will override any per certificate
# hook configuration in place.
# Command to be run in a shell before obtaining any
# certificates. Intended primarily for renewal, where it
# can be used to temporarily shut down a webserver that
# might conflict with the standalone plugin. This will
# only be called if a certificate is actually to be
# obtained/renewed. When renewing several certificates
# that have identical pre-hooks, only the first will be
# executed.
#
# An example to stop the MTA before updating certs would be
# PRE_HOOK="--pre-hook 'systemctl stop postfix'"
PRE_HOOK=""
# Command to be run in a shell after attempting to
# obtain/renew certificates. Can be used to deploy
# renewed certificates, or to restart any servers that
# were stopped by --pre-hook. This is only run if an
# attempt was made to obtain/renew a certificate. If
# multiple renewed certificates have identical post-
# hooks, only one will be run.
#
# An example to restart httpd would be:
# POST_HOOK="--post-hook 'systemctl restart httpd'"
POST_HOOK=""
# Command to be run in a shell once for each
# successfully renewed certificate. For this command,
# the shell variable $RENEWED_LINEAGE will point to the
# config live subdirectory containing the new certs and
# keys; the shell variable $RENEWED_DOMAINS will contain
# a space-delimited list of renewed cert domains
#
# An example to run a script to alert each cert would be:
# RENEW_HOOK="--renew-hook /usr/local/bin/cert-notifier.sh"
RENEW_HOOK=""
# Any other misc arguments for the renewal
# See certbot -h renew for full list
#
# An example to force renewal for certificates not due yet
# CERTBOT_ARGS="--force-renewal"
#
# The following command is produced
# certbot renew **CERTBOT_ARGS
{% if devel %}
CERTBOT_ARGS="--webroot -w /srv/web/acme-challenge --cert-name copr-dist-git-dev.fedorainfracloud.org"
{% else %}
CERTBOT_ARGS="--webroot -w /srv/web/acme-challenge --cert-name copr-dist-git.fedorainfracloud.org"
{% endif %}

View File

@@ -1,38 +0,0 @@
- name: letsencrypt cert
include_role: name=certbot
when: devel
tags:
- config
- name: Check that cert file exists
stat:
path: "/etc/letsencrypt/live/{{ copr_frontend_public_hostname }}/cert.pem"
register: stat_cert
- name: Should admin run certbot?
fail:
msg: Please see roles/certbot/README step (2) and manually run certbot
when:
- stat_cert.stat.exists == False
- devel
- name: certbot, correct fcontext mapping the web root
sefcontext:
target: '/srv/web/acme-challenge/.well-known(/.*)'
setype: httpd_sys_content_t
state: present
- name: certbot, restorecon the web root
file:
path: /srv/web/acme-challenge/.well-known
state: directory
setype: httpd_sys_content_t
- name: Install config for cert renewal
template: src="sysconfig/certbot" dest="/etc/sysconfig/certbot" owner=root group=root
- name: Automatize cert renewal
service:
name: certbot-renew.timer
state: started
enabled: yes

View File

@@ -98,14 +98,16 @@
- nb
- kevin
# TODO: move production to lets-encrypt as well
- name: install ssl certificates for production
import_tasks: "install_certs.yml"
when: not devel
tags:
- config
# development servers use Let's Encrypt
- name: install letsencrypt ssl certificates for dev
import_tasks: "letsencrypt.yml"
include_role: name=copr/certbot
when: devel
tags:
- config

View File

@@ -27,11 +27,11 @@ WSGIScriptAlias / /usr/share/copr/coprs_frontend/application
Require all granted
</Directory>
{% if devel %}
{% if letsencrypt -%}
RewriteEngine on
RewriteRule ^/\.well-known/(.*) /srv/web/acme-challenge/.well-known/$1 [L]
RewriteRule ^/\.well-known/(.*) /var/www/html/.well-known/$1 [L]
RewriteRule "^/?(.*)" "https://%{HTTP_HOST}/$1" [L,R=301,NE]
{% endif %}
{%- endif %}
</VirtualHost>