From 74a305ef1e2198e43d44487de973362dd7c81f73 Mon Sep 17 00:00:00 2001 From: Brian Lee Date: Fri, 16 Jun 2023 20:07:35 -0700 Subject: [PATCH] Fix corner case when ansible_user: root --- tasks/certificates.yml | 104 ++++++++++------------- tasks/dhparams.yml | 1 + tasks/lego.yml | 60 +++++++++++++ tasks/main.yml | 29 +++---- tasks/nginx_conf.yml | 44 ---------- tasks/{setup-acme.yml => setup-user.yml} | 12 +++ 6 files changed, 131 insertions(+), 119 deletions(-) create mode 100644 tasks/lego.yml delete mode 100644 tasks/nginx_conf.yml rename tasks/{setup-acme.yml => setup-user.yml} (76%) diff --git a/tasks/certificates.yml b/tasks/certificates.yml index 9ed4240..39b3ca5 100644 --- a/tasks/certificates.yml +++ b/tasks/certificates.yml @@ -1,64 +1,4 @@ --- -# These tasks run in a loop for each domain so that we can check for existing certificates -# and only order new ones if they don't already exist. - -- name: "Check for an existing certificate for {{ acme_domain.domain }}" - ansible.builtin.stat: - path: "{{ lego_path }}/certificates/{{ acme_domain.domain }}.crt" - register: lego_cert - delegate_to: localhost - tags: lego - -- name: Instruct lego to register an account and order a new certificate if one doesn't already exist. - set_fact: - lego_command: "{{ 'renew' if lego_cert.stat.exists else 'run'}}" - delegate_to: localhost - tags: lego - -- name: Order acme certificates without waiting for propogation of TXT record to all authoritative name servers. - ansible.builtin.command: - cmd: > - lego --path {{ lego_path }} --dns {{ acme_domain.provider }} --domains {{ acme_domain.domain }} --email {{ acme_email }} --dns.disable-cp --accept-tos {{ lego_command }} - register: lego_result - delegate_to: localhost - changed_when: False - ignore_errors: true - tags: lego - environment: -# EASYDNS_TOKEN: "{{ EASYDNS_TOKEN }}" -# EASYDNS_KEY: "{{ EASYDNS_KEY }}" - NAMECHEAP_API_USER: "{{ NAMECHEAP_API_USER }}" - NAMECHEAP_API_KEY: "{{ NAMECHEAP_API_KEY }}" - -- name: Print lego output with dns.disable-cp - ansible.builtin.debug: - var: lego_result - delegate_to: localhost - tags: lego - - # --dns.disable-cp: disables the need to wait the propagation of the TXT record to all authoritative name servers. - # I haven't yet figured out why it only works sporadically with or without this option. -- name: Retry the last command if necessary, but wait for propogation of TXT record to all authoritative name servers. - ansible.builtin.command: - cmd: > - lego --path {{ lego_path }} --dns {{ acme_domain.provider }} --domains {{ acme_domain.domain }} --email {{ acme_email }} --accept-tos {{ lego_command }} - when: lego_result.failed - register: lego_result - delegate_to: localhost - changed_when: False - tags: lego - environment: -# EASYDNS_TOKEN: "{{ EASYDNS_TOKEN }}" -# EASYDNS_KEY: "{{ EASYDNS_KEY }}" - NAMECHEAP_API_USER: "{{ NAMECHEAP_API_USER }}" - NAMECHEAP_API_KEY: "{{ NAMECHEAP_API_KEY }}" - -- name: Print lego output without dns.disable-cp - ansible.builtin.debug: - var: lego_result - delegate_to: localhost - tags: lego - - name: "Copy certificate files for {{ acme_domain.domain }}." ansible.builtin.copy: src: "{{ lego_path }}/certificates/{{ acme_domain.domain }}.{{ file_extension }}" @@ -73,3 +13,47 @@ - issuer.crt loop_control: loop_var: file_extension + +- name: Configure nginx TLSv1.2 for {{ acme_domain.domain }} + ansible.builtin.import_role: + name: nginxinc.nginx_core.nginx_config + allow_duplicates: true + tags: nginx + vars: + nginx_config_http_template_enable: true + nginx_config_http_template: + - template_file: http/default.conf.j2 + deployment_location: "/etc/nginx/acme_{{ acme_domain.domain }}.conf" + backup: false + config: + core: + server_name: "{{ acme_domain.domain }}" + ssl: + certificate: "{{ acme_path }}/certificates/{{ acme_domain.domain }}.crt" + certificate_key: "{{ acme_path }}/certificates/{{ acme_domain.domain }}.key" + trusted_certificate: "{{ acme_path }}/certificates/{{ acme_domain.domain }}.issuer.crt" + ciphers: ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 + dhparam: "{{ nginx_config_dhparam }}" + ecdh_curve: X25519:secp521r1:secp384r1 + prefer_server_ciphers: true + protocols: + - TLSv1.2 + - TLSv1.3 + session_cache: + shared: + name: "{{ acme_domain.domain }}" + size: 1M + session_tickets: false + session_timeout: 1d + ocsp: true + ocsp_cache: + name: cache + size: 64k + stapling: true + stapling_verify: true + ocsp_responder: http://r3.o.lencr.org + headers: + add_headers: + - name: Strict-Transport-Security + value: '"max-age=7776000"' + always: true \ No newline at end of file diff --git a/tasks/dhparams.yml b/tasks/dhparams.yml index 7fd5162..ce67653 100644 --- a/tasks/dhparams.yml +++ b/tasks/dhparams.yml @@ -5,6 +5,7 @@ register: dhparams delegate_to: localhost tags: dhparams + become: false - name: Use previously generated dhparams to reduce deployment time by several minutes. ansible.builtin.copy: diff --git a/tasks/lego.yml b/tasks/lego.yml new file mode 100644 index 0000000..8257cf4 --- /dev/null +++ b/tasks/lego.yml @@ -0,0 +1,60 @@ +--- +# These tasks run in a loop for each domain so that we can check for existing certificates +# and only order new ones if they don't already exist. + +- name: "Check for an existing certificate for {{ acme_domain.domain }}" + ansible.builtin.stat: + path: "{{ lego_path }}/certificates/{{ acme_domain.domain }}.crt" + register: lego_cert + delegate_to: localhost + tags: lego + +- name: Instruct lego to register an account and order a new certificate if one doesn't already exist. + set_fact: + lego_command: "{{ 'renew' if lego_cert.stat.exists else 'run'}}" + delegate_to: localhost + tags: lego + +- name: Order acme certificates without waiting for propogation of TXT record to all authoritative name servers. + ansible.builtin.command: + cmd: > + lego --path {{ lego_path }} --dns {{ acme_domain.provider }} --domains {{ acme_domain.domain }} --email {{ acme_email }} --dns.disable-cp --accept-tos {{ lego_command }} + register: lego_result + delegate_to: localhost + changed_when: False + ignore_errors: true + tags: lego + environment: +# EASYDNS_TOKEN: "{{ EASYDNS_TOKEN }}" +# EASYDNS_KEY: "{{ EASYDNS_KEY }}" + NAMECHEAP_API_USER: "{{ NAMECHEAP_API_USER }}" + NAMECHEAP_API_KEY: "{{ NAMECHEAP_API_KEY }}" + +- name: Print lego output with dns.disable-cp + ansible.builtin.debug: + var: lego_result + delegate_to: localhost + tags: lego + + # --dns.disable-cp: disables the need to wait the propagation of the TXT record to all authoritative name servers. + # I haven't yet figured out why it only works sporadically with or without this option. +- name: Retry the last command if necessary, but wait for propogation of TXT record to all authoritative name servers. + ansible.builtin.command: + cmd: > + lego --path {{ lego_path }} --dns {{ acme_domain.provider }} --domains {{ acme_domain.domain }} --email {{ acme_email }} --accept-tos {{ lego_command }} + when: lego_result.failed + register: lego_result + delegate_to: localhost + changed_when: False + tags: lego + environment: +# EASYDNS_TOKEN: "{{ EASYDNS_TOKEN }}" +# EASYDNS_KEY: "{{ EASYDNS_KEY }}" + NAMECHEAP_API_USER: "{{ NAMECHEAP_API_USER }}" + NAMECHEAP_API_KEY: "{{ NAMECHEAP_API_KEY }}" + +- name: Print lego output without dns.disable-cp + ansible.builtin.debug: + var: lego_result + delegate_to: localhost + tags: lego diff --git a/tasks/main.yml b/tasks/main.yml index c778652..d96fead 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -2,36 +2,35 @@ - name: Assert all secrets have been configured. ansible.builtin.assert: that: -# - EASYDNS_TOKEN != '' -# - EASYDNS_KEY != '' - NAMECHEAP_API_USER != '' - NAMECHEAP_API_KEY != '' fail_msg: "FAILED: Secrets have not been configured." no_log: true -- name: Set up the acme system user and group. - import_tasks: setup-acme.yml +- name: Set up the ACME system user and group. + import_tasks: setup-user.yml + become: true -- name: Add nginx user to the acme group. - ansible.builtin.user: - name: "{{ nginx_user }}" - groups: "{{ acme_system_group }}" - append: true - when: acme_system_user != "root" - -- name: Run lego looped task to order or renew certificates for all acme domains. - include_tasks: certificates.yml +- name: Run lego looped task to order or renew certificates for all ACME domains. + include_tasks: + file: lego.yml + apply: + become: false loop: "{{ acme_domains }}" loop_control: loop_var: acme_domain tags: lego -- name: Loop through the domain list (again) to configure nginx for each ACME domain - include_tasks: nginx_conf.yml +- name: Loop through the domain list (again) to copy certs and configure nginx for each ACME domain + include_tasks: + file: certificates.yml + apply: + become: true loop: "{{ acme_domains }}" loop_control: loop_var: acme_domain tags: nginx - import_tasks: dhparams.yml + become: true tags: dhparams diff --git a/tasks/nginx_conf.yml b/tasks/nginx_conf.yml deleted file mode 100644 index 2e13e7c..0000000 --- a/tasks/nginx_conf.yml +++ /dev/null @@ -1,44 +0,0 @@ ---- -- name: Configure nginx TLSv1.2 for {{ acme_domain.domain }} - ansible.builtin.import_role: - name: nginxinc.nginx_core.nginx_config - allow_duplicates: true - tags: nginx - vars: - nginx_config_http_template_enable: true - nginx_config_http_template: - - template_file: http/default.conf.j2 - deployment_location: "/etc/nginx/acme_{{ acme_domain.domain }}.conf" - backup: false - config: - core: - server_name: "{{ acme_domain.domain }}" - ssl: - certificate: "{{ acme_path }}/certificates/{{ acme_domain.domain }}.crt" - certificate_key: "{{ acme_path }}/certificates/{{ acme_domain.domain }}.key" - trusted_certificate: "{{ acme_path }}/certificates/{{ acme_domain.domain }}.issuer.crt" - ciphers: ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 - dhparam: "{{ nginx_config_dhparam }}" - ecdh_curve: X25519:secp521r1:secp384r1 - prefer_server_ciphers: true - protocols: - - TLSv1.2 - - TLSv1.3 - session_cache: - shared: - name: "{{ acme_domain.domain }}" - size: 1M - session_tickets: false - session_timeout: 1d - ocsp: true - ocsp_cache: - name: cache - size: 64k - stapling: true - stapling_verify: true - ocsp_responder: http://r3.o.lencr.org - headers: - add_headers: - - name: Strict-Transport-Security - value: '"max-age=7776000"' - always: true \ No newline at end of file diff --git a/tasks/setup-acme.yml b/tasks/setup-user.yml similarity index 76% rename from tasks/setup-acme.yml rename to tasks/setup-user.yml index 754081d..2506f9e 100644 --- a/tasks/setup-acme.yml +++ b/tasks/setup-user.yml @@ -8,6 +8,7 @@ - /usr/sbin patterns: nologin register: nologin_bin + become: true - name: Create the acme group ansible.builtin.group: @@ -15,6 +16,7 @@ state: present system: true when: acme_system_group != "root" + become: true - name: Create the acme system user ansible.builtin.user: @@ -25,6 +27,7 @@ create_home: false home: "{{ acme_path }}" when: acme_system_user != "root" + become: true - name: Ensure acme_path exists. ansible.builtin.file: @@ -33,3 +36,12 @@ group: "{{ acme_system_group }}" state: directory mode: '0750' + become: true + +- name: Add nginx user to the ACME group. + ansible.builtin.user: + name: "{{ nginx_user }}" + groups: "{{ acme_system_group }}" + append: true + when: acme_system_user != "root" + become: true