From 733122f0df926f6c9b01d4e99935ad8d7f4f5433 Mon Sep 17 00:00:00 2001 From: Ian Wienand Date: Tue, 16 Apr 2019 09:41:14 +1000 Subject: [PATCH] Use handlers for letsencrypt cert updates This change proposes calling a handler each time a certificate is created/updated. The handler name is based on the name of the certificate given in the letsencrypt_certs variable, as described in the role documentation. Because Ansible considers calling a handler with no listeners an error this means each letsencrypt user will need to provide a handler. One simple option illustrated here is just to produce a stamp file. This can facilitate cross-playbook and even cross-orchestration-tool communication. For example, puppet or other ansible playbooks can detect this stamp file and schedule their reloads, etc. then remove the stamp file. It is conceivable more complex listeners could be setup via other roles, etc. should the need arise. A test is added to make sure the stamp file is created for the letsencrypt test hosts, which are always generating a new certificate in the gate test. Change-Id: I4e0609c4751643d6e0c8d9eaa38f184e0ce5452e --- .../host_vars/graphite01.opendev.org.yaml | 2 +- .../handlers/main.yaml | 32 +++++++++++++++++++ .../handlers/touch_file.yaml | 5 +++ .../letsencrypt-create-certs/tasks/acme.yaml | 1 + .../letsencrypt-request-certs/README.rst | 13 ++++++-- .../letsencrypt01.opendev.org.yaml.j2 | 4 +-- .../letsencrypt02.opendev.org.yaml.j2 | 2 +- testinfra/test_letsencrypt.py | 14 ++++++++ 8 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 playbooks/roles/letsencrypt-create-certs/handlers/main.yaml create mode 100644 playbooks/roles/letsencrypt-create-certs/handlers/touch_file.yaml diff --git a/playbooks/host_vars/graphite01.opendev.org.yaml b/playbooks/host_vars/graphite01.opendev.org.yaml index 270086733a..ca16d650d0 100644 --- a/playbooks/host_vars/graphite01.opendev.org.yaml +++ b/playbooks/host_vars/graphite01.opendev.org.yaml @@ -1,4 +1,4 @@ letsencrypt_certs: - main: + graphite01-main: - graphite01.opendev.org - graphite.opendev.org diff --git a/playbooks/roles/letsencrypt-create-certs/handlers/main.yaml b/playbooks/roles/letsencrypt-create-certs/handlers/main.yaml new file mode 100644 index 0000000000..24fb8a4eab --- /dev/null +++ b/playbooks/roles/letsencrypt-create-certs/handlers/main.yaml @@ -0,0 +1,32 @@ +# Handlers for "letsencrypt update {{ key }}" events +# +# Note that because Ansible requires every called handler to have a +# listener, every host will need to provide a handler somehow. +# +# NOTE(ianw): as at 04/2019 it seems that something like +# +# listen: letsencrypt updated letsencrypt01-main-service +# +# doesn't actually register the handler. May be a bug or a feature to +# do with import_tasks; currently unsure. + +- name: letsencrypt updated graphite01-main + import_tasks: touch_file.yaml + vars: + touch_file: '/tmp/letsencrypt-graphite01-main.stamp' + +# Gate testing hosts: +- name: letsencrypt updated letsencrypt01-main-service + import_tasks: touch_file.yaml + vars: + touch_file: '/tmp/letsencrypt01-main-service.stamp' + +- name: letsencrypt updated letsencrypt01-other-service + import_tasks: touch_file.yaml + vars: + touch_file: '/tmp/letsencrypt01-other-service.stamp' + +- name: letsencrypt updated letsencrypt02-main-service + import_tasks: touch_file.yaml + vars: + touch_file: '/tmp/letsencrypt02-main-service.stamp' diff --git a/playbooks/roles/letsencrypt-create-certs/handlers/touch_file.yaml b/playbooks/roles/letsencrypt-create-certs/handlers/touch_file.yaml new file mode 100644 index 0000000000..eb34fbe6db --- /dev/null +++ b/playbooks/roles/letsencrypt-create-certs/handlers/touch_file.yaml @@ -0,0 +1,5 @@ +- name: 'Touch {{ touch_file }}' + file: + path: '{{ touch_file }}' + state: touch + diff --git a/playbooks/roles/letsencrypt-create-certs/tasks/acme.yaml b/playbooks/roles/letsencrypt-create-certs/tasks/acme.yaml index f16c9372aa..278b284db8 100644 --- a/playbooks/roles/letsencrypt-create-certs/tasks/acme.yaml +++ b/playbooks/roles/letsencrypt-create-certs/tasks/acme.yaml @@ -10,5 +10,6 @@ chdir: /opt/acme.sh/ environment: LETSENCRYPT_STAGING: '{{ "1" if letsencrypt_use_staging else "0" }}' + notify: 'letsencrypt updated {{ item.key }}' # Keys generated! \ No newline at end of file diff --git a/playbooks/roles/letsencrypt-request-certs/README.rst b/playbooks/roles/letsencrypt-request-certs/README.rst index 0e54613134..d03216263d 100644 --- a/playbooks/roles/letsencrypt-request-certs/README.rst +++ b/playbooks/roles/letsencrypt-request-certs/README.rst @@ -35,16 +35,25 @@ provision process. .. code-block:: yaml letsencrypt_certs: - main: + hostname-main-cert: - hostname01.opendev.org - hostname.opendev.org - secondary: + hostname-secondary-cert: - foo.opendev.org will ultimately result in two certificates being provisioned on the host in ``/etc/letsencrypt-certs/hostname01.opendev.org`` and ``/etc/letsencrypt-certs/foo.opendev.org``. + Note the creation role ``letsencrypt-create-certs`` will call a + handler ``letsencrypt updated {{ key }}`` (for example, + ``letsencrypt updated hostname-main-cert``) when that certificate + is created or updated. Because Ansible errors if a handler is + called with no listeners, you *must* define a listener for event. + ``letsencrypt-create-certs`` has ``handlers/main.yaml`` where + handlers can be defined. Since handlers reside in a global + namespace, you should choose an appropriately unique name. + Note that each entry will require a ``CNAME`` pointing the ACME challenge domain to the TXT record that will be created in the signing domain. For example above, the following records would need diff --git a/playbooks/zuul/templates/host_vars/letsencrypt01.opendev.org.yaml.j2 b/playbooks/zuul/templates/host_vars/letsencrypt01.opendev.org.yaml.j2 index f116c5219a..b2b4974cb2 100644 --- a/playbooks/zuul/templates/host_vars/letsencrypt01.opendev.org.yaml.j2 +++ b/playbooks/zuul/templates/host_vars/letsencrypt01.opendev.org.yaml.j2 @@ -1,7 +1,7 @@ letsencrypt_certs: - main: + letsencrypt01-main-service: - letsencrypt01.opendev.org - letsencrypt.opendev.org - alias.opendev.org - secondary: + letsencrypt01-other-service: - someotherservice.opendev.org \ No newline at end of file diff --git a/playbooks/zuul/templates/host_vars/letsencrypt02.opendev.org.yaml.j2 b/playbooks/zuul/templates/host_vars/letsencrypt02.opendev.org.yaml.j2 index 9ba000715a..a056956cfe 100644 --- a/playbooks/zuul/templates/host_vars/letsencrypt02.opendev.org.yaml.j2 +++ b/playbooks/zuul/templates/host_vars/letsencrypt02.opendev.org.yaml.j2 @@ -1,4 +1,4 @@ letsencrypt_certs: - main: + letsencrypt02-main-service: - letsencrypt02.opendev.org - letsencrypt.opendev.org diff --git a/testinfra/test_letsencrypt.py b/testinfra/test_letsencrypt.py index 0a4f2bd323..6e1e28a7ee 100644 --- a/testinfra/test_letsencrypt.py +++ b/testinfra/test_letsencrypt.py @@ -68,3 +68,17 @@ def test_certs_created(host): else: pytest.skip() + +def test_updated_handler(host): + if host.backend.get_hostname() == 'letsencrypt01.opendev.org': + stamp_file = host.file('/tmp/letsencrypt01-main-service.stamp') + assert stamp_file.exists + stamp_file = host.file('/tmp/letsencrypt01-other-service.stamp') + assert stamp_file.exists + + elif host.backend.get_hostname() == 'letsencrypt02.opendev.org': + stamp_file = host.file('/tmp/letsencrypt02-main-service.stamp') + assert stamp_file.exists + + else: + pytest.skip()