diff --git a/README.md b/README.md index 7a344376647dac7500bf39e03602a379103ae291..7aa8b738ce73fd02f318babfe58c7bf23cb21bf1 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,50 @@ -ansible-certbot -========= +# hdacloud.certs -Deploy certbot and enable auto-renew. +Role to obtain certificates from Let's Encrypt using either [certbot][certbot] or [lego-acme][lego-acme]. +Refer to the variables below to obtain certificates. -Role Variables --------------- +## Role Variables -```yaml ---- -# certbot settings -certbot_dns_challenge: false # default use webserver, true to obtain certificate using dns challenge -certbot_lego_version: "4.11.0" # lego version - Let's Encrypt client -certbot_dns_provider: "designate" # list of available providers https://go-acme.github.io/lego/dns/ -dns_provider_auth_env_variables: # variables required to authenticate dns provider - OS_AUTH_URL: "https://openstack.example.org" - OS_REGION_NAME: "RegionOne" - OS_AUTH_TYP: "v3applicationcredential" # default is to use application credential rather than password - OS_APPLICATION_CREDENTIAL_ID: "{{ vault_os_application_credential_id }}" - OS_APPLICATION_CREDENTIAL_SECRET: "{{ vault_os_application_credential_secret }}" - -certbot_fqdn: - - example.de -certbot_admin_email: "admin@example.de" -# certbot_webroot: "/var/www/example" # if undefined use --standalone - -# LEGO Settings -lego_extra_flags: [] - -# renewal settings -certbot_renewal_hook_file_name: "example" -certbot_renewal_hook: | - #!/bin/sh - - cp {{ certbot_live_dir }}/fullchain.pem {{ certbot_application_dir }} - cp {{ certbot_live_dir }}/privkey.pem {{ certbot_application_dir }} - - systemctl restart {{ certbot_application }}.service -``` +The role variables are split up into three categories. A general one and two specialized sets of variables +depending on whether or not you are using `dns_challenge` or not. + +### General Settings + +| Variable | Description | Type | Default | +|---|---|---|---| +| `cert_fqdns` | List of FQDNs the certificate should be valid for | `List<string>` | `[]` | +| `admin_email` | Email for expiry notifications | `string` | `''` | +| `dns_challenge` | Whether to use a dns challenge for obtaining certificates. Refer to 'DNS challenge settings' if true, otherwise to section 'WebServer Settings' | `boolean` | `false` | +| `renewal_hook_file_name` | Filename of the renewal script file | `string` | `''` | +| `renewal_hook` | Contents of the renewal script. this should be used to restart services after a new certificate has been obtained. | `string` | `''` | + + +### DNS Challenge: `true` + +These settings need to be provided when a DNS challenge is used. -Example Playbook ----------------- +| Variable | Description | Type | Default | +|---|---|---|---| +| `lego_version` | The version of the [lego-acme][lego-acme] client binary to be used. | `string` | `4.11.0` | +| `lego_extra_flags` | Additional arguments / flags to be passed to the `lego` command. | `List<string>` | `[]` | +| `lego_dns_provider` | The DNS provider to be used for the DNS challenge. A full list of the supported providers can be found [here][lego-dns-providers]. | `string` | `designate` | +| `dns_provider_auth_env_variables` | Dictionary of environment variables used by the selected DNS provider. | `Dictionary<string, string>` | `{}` | + + +### DNS Challenge: `false` + +These settings optionally need to be provided when no DNS challenge is used. + +| Variable | Description | Type | Default | +|---|---|---|---| +| `certbot_webroot` | If you are already running a web server on your instance provide a path that is served on port `80` here, to allow the HTTP challenge to be completed when obtaining the certificate. | `string` | `undefined` | + +## Example Playbook ```yaml # requirements.yaml roles: - - name: hdacloud.certbot + - name: hdacloud.certs src: git+https://code.fbi.h-da.de/hdacloud/ansible_certbot version: main ``` @@ -59,15 +59,18 @@ roles: In some cases you might need the certificates split up into the cert itself and a separate fullchain certificate chain. For this case you need to add the `--no-bundle` option to the `lego_extra_flags` array. Then add the -following to the `certbot_renewal_hook` script (replacing variables accordingly): +following to the `renewal_hook` script (replacing variables accordingly): ```sh -rm "/etc/letsencrypt/live/fullchain.pem" -cat "/etc/letsencrypt/live/certificates/{{ certbot_fqdn_first }}.crt" >> "/etc/letsencrypt/live/fullchain.pem" && -cat "/etc/letsencrypt/live/certificates/{{ certbot_fqdn_first }}.issuer.crt" >> "/etc/letsencrypt/live/fullchain.pem" +rm -f /var/lib/lego/fullchain.pem +cat /var/lib/lego/certificates/<cert_fqdns_first>.crt >> <wherever>/fullchain.pem && +cat /var/lib/lego/certificates/<cert_fqdns_first>.issuer.crt >> <wherever>/fullchain.pem ``` -License -------- +# License See [LICENSE](LICENSE) + +[lego-acme]: https://go-acme.github.io/lego/ 'LEGO ACME' +[certbot]: https://certbot.eff.org/ 'EFF Certbot' +[lego-dns-providers]: https://go-acme.github.io/lego/dns/#dns-providers 'LEGO: DNS Providers' diff --git a/defaults/main.yml b/defaults/main.yml index eea0c5c98de5b73360c6324944a4b551e2566abf..ab9861e95758a5a2695a0e9701050c364d600b4f 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -14,30 +14,15 @@ certbot_timer_name: Fedora: "certbot-renew.timer" # certbot settings -certbot_dns_challenge: false # default use webserver, true to obtain certificate using dns challenge -certbot_lego_version: "4.11.0" # lego version - Let's Encrypt client -certbot_dns_provider: "designate" # list of available providers https://go-acme.github.io/lego/dns/ -dns_provider_auth_env_variables: # variables required to authenticate dns provider - OS_AUTH_URL: "https://openstack.example.org" - OS_REGION_NAME: "RegionOne" - OS_AUTH_TYP: "v3applicationcredential" # default is to use application credential rather than password - OS_APPLICATION_CREDENTIAL_ID: "{{ vault_os_application_credential_id }}" - OS_APPLICATION_CREDENTIAL_SECRET: "{{ vault_os_application_credential_secret }}" +dns_challenge: false # default use webserver, true to obtain certificate using dns challenge +lego_version: "4.11.0" # lego version - Let's Encrypt client +lego_dns_provider: "designate" # list of available providers https://go-acme.github.io/lego/dns/ +dns_provider_auth_env_variables: {} # variables required to authenticate dns provider -certbot_fqdn: +cert_fqdns: - example.de -certbot_admin_email: "admin@example.de" -# certbot_webroot: "/var/www/example" # if undefined use --standalone +admin_email: "admin@example.de" + # LEGO Settings lego_extra_flags: [] - -# renewal settings -certbot_renewal_hook_file_name: "example" -certbot_renewal_hook: | - #!/bin/sh - - cp {{ certbot_live_dir }}/fullchain.pem {{ certbot_application_dir }} - cp {{ certbot_live_dir }}/privkey.pem {{ certbot_application_dir }} - - systemctl restart {{ certbot_application }}.service diff --git a/tasks/dns-challenge.yml b/tasks/dns-challenge.yml index 8916ec2e04cd16dc2b21f127d376f67847b66c15..9ea5d7d8eb3071027501d57d2719800aa945a959 100644 --- a/tasks/dns-challenge.yml +++ b/tasks/dns-challenge.yml @@ -1,68 +1,51 @@ - name: Fetch Binary ansible.builtin.get_url: - url: "https://github.com/go-acme/lego/releases/download/v{{ certbot_lego_version }}/lego_v{{ certbot_lego_version }}_linux_amd64.tar.gz" - dest: "/tmp/lego_v{{ certbot_lego_version }}_linux_amd64.tar.gz" + url: "https://github.com/go-acme/lego/releases/download/v{{ lego_version }}/lego_v{{ lego_version }}_linux_amd64.tar.gz" + dest: "/tmp/lego_v{{ lego_version }}_linux_amd64.tar.gz" mode: "0644" - name: Unpack Archive ansible.builtin.unarchive: - src: "/tmp/lego_v{{ certbot_lego_version }}_linux_amd64.tar.gz" + src: "/tmp/lego_v{{ lego_version }}_linux_amd64.tar.gz" dest: "/tmp" remote_src: true - name: Copy Binary ansible.builtin.copy: src: /tmp/lego - dest: /usr/bin/lego + dest: /usr/local/bin/lego owner: root group: root mode: '0755' remote_src: true become: true -- name: Check Whether Cert Exists - ansible.builtin.stat: - path: "{{ certbot_live_dir }}/fullchain.pem" - register: lecert +- name: Create lego directories become: true - -- name: Ensure Deploy Hook Dir Exists ansible.builtin.file: - path: "{{ item }}" state: directory + path: "{{ lego_path }}/renewal-hooks" # all intermediate subdirectories will be created mode: "0755" owner: root group: root - loop: - - /etc/letsencrypt/renewal-hooks - - /etc/letsencrypt/renewal-hooks/deploy + +- name: Check Whether Cert Exists + ansible.builtin.stat: + path: "{{ lego_path }}/certificates/{{ cert_fqdns_first }}.crt" + register: lego_cert become: true - name: Request Cert If Necessary - DNS Challenge - when: not lecert.stat.exists + when: not lego_cert.stat.exists become: true - block: - - name: Request Cert - ansible.builtin.command: >- - lego -a --dns {{ certbot_dns_provider }} - --email {{ certbot_admin_email }} -d {{ lego_dflag }} - --path {{ certbot_live_dir }} - run --no-bundle - environment: "{{ dns_provider_auth_env_variables }}" - register: lego - changed_when: lego.rc == 0 - - - name: Mirror Letsencrypt Structure - ansible.builtin.copy: - src: "{{ item.src }}" - dest: "{{ item.dest }}" - owner: root - group: root - mode: '0600' - remote_src: true - loop: - - { src: "{{ certbot_live_dir }}/certificates/{{ certbot_fqdn_first }}.crt", dest: "{{ certbot_live_dir }}/cert.pem" } - - { src: "{{ certbot_live_dir }}/certificates/{{ certbot_fqdn_first }}.key", dest: "{{ certbot_live_dir }}/privkey.pem" } + ansible.builtin.command: >- + lego -a --dns {{ lego_dns_provider }} + --email {{ admin_email }} -d {{ lego_dflag }} + --path {{ lego_path }} + run {% for f in lego_extra_flags %}{{ f }} {% endfor %} + environment: "{{ dns_provider_auth_env_variables }}" + register: lego + changed_when: lego.rc == 0 - name: Render Systemd Files become: true @@ -76,18 +59,18 @@ - name: Render Systemd Service File ansible.builtin.template: src: templates/dns-challenge.service.j2 - dest: /lib/systemd/system/dns-challenge.service + dest: /etc/systemd/system/dns-challenge.service mode: "0644" - name: Render Systemd Timer File ansible.builtin.template: src: templates/dns-challenge.timer.j2 - dest: /lib/systemd/system/dns-challenge.timer + dest: /etc/systemd/system/dns-challenge.timer mode: "0644" - name: Setup renewal hook ansible.builtin.include_tasks: "renewal-hook.yml" - when: certbot_renewal_hook is defined + when: renewal_hook is defined - name: Enable LEGO Renew Timer ansible.builtin.systemd: diff --git a/tasks/main.yml b/tasks/main.yml index a97d708121d71398f8e54f12ca9ae0962ee188e1..cb686c25063f58d063c56d28d28a8ef52cb5db85 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,8 +1,8 @@ --- - name: Obtain Cert Using Web Server ansible.builtin.include_tasks: "webserver.yml" - when: not certbot_dns_challenge + when: not dns_challenge - name: Obtain Cert DNS Challenge ansible.builtin.include_tasks: "dns-challenge.yml" - when: certbot_dns_challenge + when: dns_challenge diff --git a/tasks/renewal-hook.yml b/tasks/renewal-hook.yml index a24a31c7a80e31b7ec008a6f7b7e7c1a5c558c3e..5eb30f89cecad84e3a80270ce8cda2c0dc6dee82 100644 --- a/tasks/renewal-hook.yml +++ b/tasks/renewal-hook.yml @@ -1,7 +1,7 @@ --- - name: Setup Renewal Hook ansible.builtin.copy: - dest: "/etc/letsencrypt/renewal-hooks/deploy/{{ certbot_renewal_hook_file_name }}" - content: "{{ certbot_renewal_hook }}" + dest: "{{ dns_challenge | ternary(lego_path + '/renewal-hooks/', '/etc/letsencrypt/renewal-hooks/deploy/') }}{{ renewal_hook_file_name }}" + content: "{{ renewal_hook }}" mode: "0755" become: true diff --git a/tasks/webserver.yml b/tasks/webserver.yml index 06f4e649f3a990c95b379bb6af3a0c5fbc71a23d..f2e96f89ead8980de72f13c2dd97e61c4f061ff3 100644 --- a/tasks/webserver.yml +++ b/tasks/webserver.yml @@ -28,20 +28,17 @@ - name: Ensure Deploy Hook Dir Exists ansible.builtin.file: - path: "{{ item }}" + path: /etc/letsencrypt/renewal-hooks/deploy state: directory mode: "0755" owner: root group: root - loop: - - /etc/letsencrypt/renewal-hooks - - /etc/letsencrypt/renewal-hooks/deploy become: true - name: Request Cert If Necessary - Standalone ansible.builtin.command: >- certbot certonly --standalone --noninteractive --agree-tos - --email {{ certbot_admin_email }} -d {{ certbot_dflag }} + --email {{ admin_email }} -d {{ certbot_dflag }} when: not lecert.stat.exists and not certbot_webroot is defined register: cbstandalone changed_when: cbstandalone.rc == 0 @@ -59,7 +56,7 @@ - name: Request Cert If Necessary - Webroot ansible.builtin.command: >- certbot certonly --webroot --webroot-path {{ certbot_webroot }} --noninteractive --agree-tos - --email {{ certbot_admin_email }} -d {{ certbot_dflag }} + --email {{ admin_email }} -d {{ certbot_dflag }} when: not lecert.stat.exists and certbot_webroot is defined register: cbwebroot changed_when: cbwebroot.rc == 0 @@ -67,7 +64,7 @@ - name: Setup Certbot renewal hook ansible.builtin.include_tasks: "renewal-hook.yml" - when: certbot_renewal_hook is defined + when: renewal_hook is defined - name: Enable Letsencrypt Renew Timer Based On Distribution become: true diff --git a/templates/dns-challenge.service.j2 b/templates/dns-challenge.service.j2 index 695172dbb39851e1faa54b3980912e1b2fd2a7ce..433f085812c2a3ba9e73ad74218f05235a316dbe 100644 --- a/templates/dns-challenge.service.j2 +++ b/templates/dns-challenge.service.j2 @@ -5,7 +5,5 @@ Description=LEGO DNS challenge [Service] Type=oneshot -ExecStart=/usr/bin/lego -a --dns {{ certbot_dns_provider }} --email {{ certbot_admin_email }} -d {{ lego_dflag }} --path {{ certbot_live_dir }} renew {% for f in lego_extra_flags %}{{ f }} {% endfor %} {{ '--renew-hook /etc/letsencrypt/renewal-hooks/deploy/' + certbot_renewal_hook_file_name if certbot_renewal_hook is defined else ''}} -ExecStartPost=cp {{ certbot_live_dir }}/certificates/{{ certbot_fqdn_first }}.crt {{ certbot_live_dir }}/cert.pem -ExecStartPost=cp {{ certbot_live_dir }}/certificates/{{ certbot_fqdn_first }}.key {{ certbot_live_dir }}/privkey.pem +ExecStart=/usr/local/bin/lego -a --dns {{ lego_dns_provider }} --email {{ admin_email }} -d {{ lego_dflag }} --path {{ lego_path }} renew {% for f in lego_extra_flags %}{{ f }} {% endfor %} {{ '--renew-hook ' + lego_path + '/renewal-hooks/' + renewal_hook_file_name if renewal_hook is defined else ''}} EnvironmentFile=/etc/default/dns-challenge.env diff --git a/vars/main.yml b/vars/main.yml index e55bd962c4da69fa300b421949e5cbbc31ccc611..2c1154e6a275cee553bc16cec29225e684703c20 100644 --- a/vars/main.yml +++ b/vars/main.yml @@ -1,11 +1,16 @@ --- -certbot_fqdn_first: >- +cert_fqdns_first: >- {{ - certbot_fqdn + cert_fqdns | first | replace("*.", "") | trim(".") }} -certbot_live_dir: "/etc/letsencrypt/live/{{ certbot_fqdn_first }}" -certbot_dflag: "{{ certbot_fqdn | map('trim', '.') | join(',') }}" -lego_dflag: "{{ certbot_fqdn | map('trim', '.') | join(' -d ') }}" + +# Certbot +certbot_live_dir: "/etc/letsencrypt/live/{{ cert_fqdns_first }}" +certbot_dflag: "{{ cert_fqdns | map('trim', '.') | join(',') }}" + +# LEGO (when DNS challenge) +lego_path: /var/lib/lego +lego_dflag: "{{ cert_fqdns | map('trim', '.') | join(' -d ') }}"