From 066d95535e124f171bef6c84874f7f3cfd264b6d Mon Sep 17 00:00:00 2001 From: AnsibleGuy Date: Thu, 4 Nov 2021 22:05:17 +0100 Subject: [PATCH] created tasks to handle minimal-ca, self-signed and letsencrypt-certbot certificates --- README.md | 114 ++++++++-- defaults/main.yml | 83 ++++++- filter_plugins/utils.py | 32 ++- playbook.yml | 6 +- requirements.yml | 4 +- tasks/debian/ca_full.yml | 9 + tasks/debian/letsencrypt/apache.yml | 29 +++ tasks/debian/letsencrypt/apache_cleanup.yml | 13 ++ .../letsencrypt/apache_dependencies.yml | 23 ++ tasks/debian/letsencrypt/cert.yml | 58 +++++ tasks/debian/letsencrypt/main.yml | 86 ++++++++ tasks/debian/letsencrypt/nginx.yml | 29 +++ tasks/debian/letsencrypt/nginx_cleanup.yml | 13 ++ tasks/debian/main.yml | 6 - tasks/internal/ca_minimal.yml | 106 +++++++++ tasks/internal/cert.yml | 202 ++++++++++++++++++ tasks/internal/main.yml | 22 ++ tasks/main.yml | 26 ++- .../apache2/sites-enabled/le_dummy.conf.j2 | 9 + templates/etc/nginx/sites-enabled/le_dummy.j2 | 9 + ....infra_certs.LetsEncryptCertbot.service.j2 | 10 + ...uy.infra_certs.LetsEncryptCertbot.timer.j2 | 13 ++ 22 files changed, 861 insertions(+), 41 deletions(-) create mode 100644 tasks/debian/ca_full.yml create mode 100644 tasks/debian/letsencrypt/apache.yml create mode 100644 tasks/debian/letsencrypt/apache_cleanup.yml create mode 100644 tasks/debian/letsencrypt/apache_dependencies.yml create mode 100644 tasks/debian/letsencrypt/cert.yml create mode 100644 tasks/debian/letsencrypt/main.yml create mode 100644 tasks/debian/letsencrypt/nginx.yml create mode 100644 tasks/debian/letsencrypt/nginx_cleanup.yml delete mode 100644 tasks/debian/main.yml create mode 100644 tasks/internal/ca_minimal.yml create mode 100644 tasks/internal/cert.yml create mode 100644 tasks/internal/main.yml create mode 100644 templates/etc/apache2/sites-enabled/le_dummy.conf.j2 create mode 100644 templates/etc/nginx/sites-enabled/le_dummy.j2 create mode 100644 templates/etc/systemd/system/ansibleguy.infra_certs.LetsEncryptCertbot.service.j2 create mode 100644 templates/etc/systemd/system/ansibleguy.infra_certs.LetsEncryptCertbot.timer.j2 diff --git a/README.md b/README.md index 2f5d887..e19f898 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,28 @@ -# Ansible Role for certificate generation +# Certificate Generator Role **Tested:** * Debian 11 ## Functionality -* Package installation +* **Package installation** * Ansible dependencies (_minimal_) - * -* Configuration - * Two Possible Modes - * Generate Self-Signed certificate - * Create an internal-ca and generate certificates using it - * Default config: + * Crypto Dependencies + + +* **Configuration** + * **Four Possible Modes**: + * Generate **Self-Signed** certificate + * Use a **minimal Certificate Authority** to create signed certificates + * Configure **LetsEncrypt-Certbot** to generate publicly valid certificates + * Supported for Nginx and Apache + * Host needs to have a valid public dns record pointed at it + * Needs to be publicly reachable over port 80/tcp + * _Use a proper **Certificate Authority** (_full PKI_) to create **signed certificates**_ => not yet available + + + * **Default config**: * Mode => Self-Signed - * Default opt-ins: - * - * Default opt-outs: - * ## Info @@ -28,6 +33,10 @@ * **Note:** Most of this functionality can be opted in or out using the main defaults file and variables! +* **Note:** The certificate file-name (_name variable as defined or else CommonName_) will be updated: + * spaces are transformed into underlines + * all Characters except "0-9a-zA-Z." are removed + * the file-extension (_crt/chain.crt/key/csr_) will be appended ## Requirements @@ -36,20 +45,89 @@ ## Usage -Define the config as needed: +### Notes +The **self-signed and minimal-ca** modes will only create a single certificate per run. + +Re-runs can save some overhead by using the 'certs' tag. + + +The **LetsEncrypt** mode will create/remove multiple certificates as defined. + + +### Config + +Example for LetsEncrypt config: ```yaml -app: - +certs: + mode: 'le_certbot' + path: '/etc/apache2/ssl' + letsencrypt: + certs: + myNiceSite: + domains: ['myRandomSite.net', 'ansibleguy.net'] + email: 'certs@template.ansibleguy.net' + service: 'apache' ``` +Example for Self-Signed config: + +```yaml +certs: + mode: 'selfsigned' + path: '/etc/nginx/ssl' + group_key: 'nginx' + owner_cert: 'nginx' + cert: + cn: 'My great certificate!' + org: 'AnsibleGuy' + country: 'AT' + email: 'certs@template.ansibleguy.net' + domains: ['mySoGreat.site', 'ansibleguy.net'] + ips: ['192.168.44.2'] + pwd: !vault ... +``` + +Example for minimal-CA config: + +```yaml +certs: + mode: 'ca' + path: '/etc/ca/certs' + mode_key: '0400' + cert: + name: 'custom_file_name' # extension will be appended + cn: 'My great certificate!' + org: 'AnsibleGuy' + country: 'AT' + email: 'certs@template.ansibleguy.net' + domains: ['mySoGreat.site', 'ansibleguy.net'] + ca: + path: '/etc/ca' + cn: 'SUPER CertificateAuthority' + org: 'AnsibleGuy' + country: 'AT' + email: 'certs@template.ansibleguy.net' + pwd: !vault ... +``` + +Using the minimal-CA you can create multiple certificates signed by the CA by re-running the role with changed 'cert' settings. + + +You might want to use 'ansible-vault' to encrypt your passwords: +```bash +ansible-vault encrypt_string +``` + +### Execution + Run the playbook: ```bash -ansible-playbook -K -D -i inventory/hosts.yml playbook.yml +ansible-playbook -K -D -i inventory/hosts.yml playbook.yml --ask-vault-pass ``` There are also some useful **tags** available: -* base => only configure basics; sites will not be touched -* sites +* certs => ignore ca tasks; only generate certs +* selfsigned * config * certs diff --git a/defaults/main.yml b/defaults/main.yml index 3e473a0..5fc99a0 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,8 +1,85 @@ --- # default config => is overwritten by provided config -default_app: {} +default_certs: + mode: 'selfsigned' # selfsigned, ca, ca_min, le_certbot + path: '/etc/certs' -APP_CONFIG: "{{ default_app | combine(app, recursive=true) }}" + cert: + name: + key_size: 4096 # 1024, 2048, 4096 + key_type: 'RSA' + cipher: 'AES-256-CBC' # see: 'openssl list -cipher-algorithms' + digest: 'sha256' + regenerate: 'partial_idempotence' + pwd: + domains: [] + ips: [] -default_instance_config: {} + # certificate config + cn: 'Ansible Certificate' + org: + ou: + country: + state: + locality: + email: # if using letsencrypt you might pass an email per domain => see letsencrypt-certs + key_usage: 'serverAuth' # serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, ocspSigning + ocsp_staple: false + crl_distribution: [] +# - full_name: +# - "URI:https://ca.example.com/revocations.crl" +# crl_issuer: +# - "URI:https://ca.example.com/" +# reasons: +# - key_compromise +# - ca_compromise +# - cessation_of_operation + valid_days: 730 + + mode_key: '0640' + mode_cert: '0644' + + owner_key: 'root' + group_key: 'root' + owner_cert: 'root' + group_cert: 'root' + + extension_cert: 'crt' + extension_key: 'key' + extension_csr: 'csr' + + letsencrypt: + path: '/etc/letsencrypt' + service: # apache, nginx + renew_timer: 'Mon *-*-* 01:00:00' + verbosity: 'v' + certs: {} # see 'default_le_certbot_cert_config' + renew: false # if a renewal should be started by the role; the renewal service will auto-renew the certificates otherwise + + ca: + path: '/etc/certs/ca' + valid_days: 7300 + key_size: 8192 # 1024, 2048, 4096, 8192 + key_type: 'RSA' + cipher: 'AES-256-CBC' # see: 'openssl list -cipher-algorithms' + digest: 'sha512' + regenerate: 'partial_idempotence' + pwd: + + # certificate config + cn: 'CA Certificate' + org: + ou: + country: + state: + locality: + email: + + +CERT_CONFIG: "{{ default_certs | combine(certs, recursive=true) }}" + +default_le_certbot_cert_config: + domains: [] + state: 'present' + email: diff --git a/filter_plugins/utils.py b/filter_plugins/utils.py index 226737c..cae1c90 100644 --- a/filter_plugins/utils.py +++ b/filter_plugins/utils.py @@ -1,4 +1,5 @@ from re import sub as regex_replace +from re import match as regex_match class FilterModule(object): @@ -6,16 +7,35 @@ class FilterModule(object): def filters(self): return { "safe_key": self.safe_key, - "fallback": self.fallback, + "valid_domain": self.valid_domain, + "valid_ip": self.valid_ip, + "check_email": self.check_email, } @staticmethod def safe_key(key: str) -> str: - return regex_replace('[^0-9a-zA-Z]+', '', key.replace(' ', '_')) + return regex_replace(r'[^0-9a-zA-Z\.]+', '', key.replace(' ', '_')) @staticmethod - def fallback(opt1: str, opt2: str) -> str: - if opt1 not in [None, '', 'None', 'none', ' ']: - return opt1 + def valid_domain(domain: str) -> bool: + expr = r'^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$' + return True if regex_match(expr, domain) is not None else False + + @staticmethod + def valid_ip(ip: str) -> bool: + expr_ipv4 = r'^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$' + expr_ipv6 = r'^(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}$' + + if regex_match(expr_ipv4, ip) is not None or regex_match(expr_ipv6, ip) is not None: + return True + + return False + + @staticmethod + def check_email(certs: dict) -> bool: + for settings in certs.values(): + if 'email' not in settings or settings['email'] in ['', ' ', None, 'null', 'None']: + return False + + return True - return opt2 diff --git a/playbook.yml b/playbook.yml index 2fcea1d..09e2031 100644 --- a/playbook.yml +++ b/playbook.yml @@ -1,9 +1,9 @@ --- -# ansible-playbook -K -D -i inventory/hosts.yml playbook.yml +# ansible-playbook -K -D -i inventory/hosts.yml playbook.yml --ask-vault-pass -- hosts: all # should be limited +- hosts: localhost # should be limited become: true gather_facts: yes roles: - - ansibleguy.ROLE + - ansibleguy.infra_certs diff --git a/requirements.yml b/requirements.yml index 1e2a9ef..907da69 100644 --- a/requirements.yml +++ b/requirements.yml @@ -2,5 +2,5 @@ # install: ansible-galaxy install -r requirements.yml collections: [] -# - name: 'community.general' -# source: 'https://galaxy.ansible.com' + - name: 'community.crypto' + source: 'https://galaxy.ansible.com' diff --git a/tasks/debian/ca_full.yml b/tasks/debian/ca_full.yml new file mode 100644 index 0000000..21c68e7 --- /dev/null +++ b/tasks/debian/ca_full.yml @@ -0,0 +1,9 @@ +--- + +# creating ca with full pki +# to be continued (; + +- name: Certificates | Debian | Internal | CA | Not yet implemented + ansible.builtin.debug: + msg: "The certificate mode 'ca_full' is not yet implemented!" + tags: ca diff --git a/tasks/debian/letsencrypt/apache.yml b/tasks/debian/letsencrypt/apache.yml new file mode 100644 index 0000000..99dfd75 --- /dev/null +++ b/tasks/debian/letsencrypt/apache.yml @@ -0,0 +1,29 @@ +--- + +- name: Certificates | Debian | LetsEncrypt Certbot | Apache | Install package + ansible.builtin.package: + name: ['python3-certbot-apache'] + state: present + +- name: Certificates | Debian | LetsEncrypt Certbot | Apache | Checking sites + ansible.builtin.shell: 'ls /etc/apache2/sites-enabled/' + changed_when: false + register: enabled_apache_sites + +- name: Certificates | Debian | LetsEncrypt Certbot | Apache | Deploying temporary apache site + ansible.builtin.template: + src: 'templates/etc/apache2/sites-enabled/le_dummy.conf.j2' + dest: '/etc/apache2/sites-enabled/tmp_le_dummy.conf' + owner: 'root' + group: 'root' + mode: 0644 + register: tmp_site_enable + when: enabled_apache_sites.stdout == '' + +- name: Certificates | Debian | LetsEncrypt Certbot | Apache | Reloading apache + ansible.builtin.systemd: + name: 'apache2.service' + state: reloaded + when: + - enabled_apache_sites.stdout == '' + - tmp_site_enable.changed diff --git a/tasks/debian/letsencrypt/apache_cleanup.yml b/tasks/debian/letsencrypt/apache_cleanup.yml new file mode 100644 index 0000000..7245415 --- /dev/null +++ b/tasks/debian/letsencrypt/apache_cleanup.yml @@ -0,0 +1,13 @@ +--- + +- name: Certificates | Debian | LetsEncrypt Certbot | Apache | Disable temporary site + ansible.builtin.file: + state: absent + path: '/etc/apache2/sites-enabled/tmp_le_dummy.conf' + register: tmp_site_disable + +- name: Certificates | Debian | LetsEncrypt Certbot | Apache | Reloading apache + ansible.builtin.systemd: + name: 'apache2.service' + state: reloaded + when: tmp_site_disable.changed diff --git a/tasks/debian/letsencrypt/apache_dependencies.yml b/tasks/debian/letsencrypt/apache_dependencies.yml new file mode 100644 index 0000000..bf562cc --- /dev/null +++ b/tasks/debian/letsencrypt/apache_dependencies.yml @@ -0,0 +1,23 @@ +--- + +- name: Apache | Debian | LetsEncrypt Certbot | Dependencies | Deploying temporary apache site + ansible.builtin.template: + src: 'templates/etc/apache2/sites-available/le_dummy.conf.j2' + dest: '/etc/apache2/sites-available/tmp_le_dummy.conf' + owner: 'root' + group: 'root' + mode: 0644 + +- name: Apache | Debian | LetsEncrypt Certbot | Dependencies | Enable apache site + ansible.builtin.file: + state: link + src: '/etc/apache2/sites-available/tmp_le_dummy.conf' + dest: '/etc/apache2/sites-enabled/tmp_le_dummy.conf' + owner: 'root' + group: 'root' + mode: 0644 + +- name: Apache | Debian | LetsEncrypt Certbot | Dependencies | Reload apache + ansible.builtin.systemd: + name: 'apache2.service' + state: reloaded diff --git a/tasks/debian/letsencrypt/cert.yml b/tasks/debian/letsencrypt/cert.yml new file mode 100644 index 0000000..f8fe22d --- /dev/null +++ b/tasks/debian/letsencrypt/cert.yml @@ -0,0 +1,58 @@ +--- + +# todo: check domains registered in current certificate (certbot certificates) and remove it if there are more than configured before re-configuring it + +- name: "Certificates | Debian | LetsEncrypt Certbot | {{ le_name }} | Creating directory" + ansible.builtin.file: + path: "{{ le_path }}" + state: directory + owner: 'root' + group: 'root' + mode: 0755 + +- name: "Certificates | Debian | LetsEncrypt Certbot | {{ le_name }} | Command to be executed" + ansible.builtin.debug: + msg: "certbot certonly --non-interactive --agree-tos --no-redirect + --{{ CERT_CONFIG.letsencrypt.service }} --cert-name {{ le_name }} + -{{ CERT_CONFIG.letsencrypt.verbosity }} + --rsa-key-size {{ le_cert.key_size | default(CERT_CONFIG.cert.key_size, true) }} + --config-dir {{ CERT_CONFIG.letsencrypt.path }} + {% for domain in le_cert.domains %}{% if domain | valid_domain %}--domain {{ domain }} {% endif %}{% endfor %} + {% if le_cert.email is not none %}--email {{ le_cert.email }} {% elif CERT_CONFIG.cert.email | default(none, true) is not none %}--email {{ CERT_CONFIG.cert.email }} {% endif %}" + when: existing_certs_raw.stdout.find(name) == -1 + +- name: "Certificates | Debian | LetsEncrypt Certbot | {{ le_name }} | Starting certbot" + ansible.builtin.command: "certbot certonly --non-interactive --agree-tos --no-redirect + --{{ CERT_CONFIG.letsencrypt.service }} --cert-name {{ le_name }} + -{{ CERT_CONFIG.letsencrypt.verbosity }} + --rsa-key-size {{ le_cert.key_size | default(CERT_CONFIG.cert.key_size, true) }} + --config-dir {{ CERT_CONFIG.letsencrypt.path }} + {% for domain in le_cert.domains %}{% if domain | valid_domain %}--domain {{ domain }} {% endif %}{% endfor %} + {% if le_cert.email is not none %}--email {{ le_cert.email }} {% elif CERT_CONFIG.cert.email | default(none, true) is not none %}--email {{ CERT_CONFIG.cert.email }} {% endif %}" + when: existing_certs_raw.stdout.find(name) == -1 + +- name: "Certificates | Debian | LetsEncrypt Certbot | {{ le_name }} | Linking cert" + ansible.builtin.file: + state: link + src: "{{ item.src }}" + dest: "{{ item.dst }}" + mode: "{{ CERT_CONFIG.mode_cert }}" + owner: "{{ CERT_CONFIG.owner_cert }}" + group: "{{ CERT_CONFIG.group_cert }}" + follow: true + force: true + loop: + - {'dst': "{{ CERT_CONFIG.path }}/{{ le_name }}.{{ CERT_CONFIG.extension_cert }}", 'src': "{{ le_path }}/cert.pem"} + - {'dst': "{{ CERT_CONFIG.path }}/{{ le_name }}.chain.{{ CERT_CONFIG.extension_cert }}", 'src': "{{ le_path }}/chain.pem"} + - {'dst': "{{ CERT_CONFIG.path }}/{{ le_name }}.fullchain.{{ CERT_CONFIG.extension_cert }}", 'src': "{{ le_path }}/fullchain.pem"} + +- name: "Certificates | Debian | LetsEncrypt Certbot | {{ le_name }} | Linking key" + ansible.builtin.file: + state: link + src: "{{ le_path }}/privkey.pem" + dest: "{{ CERT_CONFIG.path }}/{{ le_name }}.{{ CERT_CONFIG.extension_key }}" + mode: "{{ CERT_CONFIG.mode_key }}" + owner: "{{ CERT_CONFIG.owner_key }}" + group: "{{ CERT_CONFIG.group_key }}" + follow: true + force: true diff --git a/tasks/debian/letsencrypt/main.yml b/tasks/debian/letsencrypt/main.yml new file mode 100644 index 0000000..5e388ac --- /dev/null +++ b/tasks/debian/letsencrypt/main.yml @@ -0,0 +1,86 @@ +--- + +- name: Certificates | Debian | LetsEncrypt Certbot | Checking config + ansible.builtin.fail: + msg: "The required configuration was not provided! + Needed: 'certs.letsencrypt.certs', 'certs.letsencrypt.service', + 'certs.letsencrypt.email or certs.letsencrypt.email.certs.email'" + when: > + CERT_CONFIG.letsencrypt.certs | length == 0 or + CERT_CONFIG.letsencrypt.service is none | default(none, true) or + (CERT_CONFIG.letsencrypt.email | default(none, true) is none and not CERT_CONFIG.letsencrypt.certs|check_email) + +- name: Certificates | Debian | LetsEncrypt Certbot | Checking service + ansible.builtin.fail: + msg: "You need to supply a supported LetsEncrypt Certbot service to use! (apache/nginx)" + when: "CERT_CONFIG.letsencrypt.service | default(none, true) is none or CERT_CONFIG.letsencrypt.service not in ['apache', 'nginx']" + +- name: Certificates | Debian | LetsEncrypt Certbot | Configure for Apache2 + ansible.builtin.import_tasks: apache.yml + when: CERT_CONFIG.letsencrypt.service == 'apache' + +- name: Certificates | Debian | LetsEncrypt Certbot | Configure for Nginx + ansible.builtin.import_tasks: nginx.yml + when: CERT_CONFIG.letsencrypt.service == 'nginx' + +- name: Certificates | Debian | LetsEncrypt Certbot | Pulling existing certs + ansible.builtin.shell: 'certbot certificates' + register: existing_certs_raw + changed_when: false + +- name: Certificates | Debian | LetsEncrypt Certbot | Adding certificates + ansible.builtin.include_tasks: cert.yml + when: + - le_cert.domains | length > 0 + - le_cert.state == 'present' + vars: + le_cert: "{{ default_le_certbot_cert_config | combine(cert_item.value, recursive=true) }}" + le_name: "{{ cert_item.key | safe_key }}" + le_path: "{{ CERT_CONFIG.letsencrypt.path }}/live/{{ name }}" + loop_control: + loop_var: cert_item + with_dict: "{{ CERT_CONFIG.letsencrypt.certs }}" + + +- name: Certificates | Debian | LetsEncrypt Certbot | Removing certificates + ansible.builtin.command: "certbot revoke --cert-name {{ le_name }} && certbot delete --cert-name {{ le_name }}" + when: + - le_cert.state != 'present' + - existing_certs_raw.stdout.find(le_name) != -1 + vars: + le_cert: "{{ default_le_certbot_cert_config | combine(cert_item.value, recursive=true) }}" + le_name: "{{ cert_item.key | safe_key }}" + loop_control: + loop_var: cert_item + with_dict: "{{ CERT_CONFIG.letsencrypt.certs }}" + +- name: Certificates | Debian | LetsEncrypt Certbot | Cleanup for Apache2 + ansible.builtin.import_tasks: apache_cleanup.yml + when: CERT_CONFIG.letsencrypt.service == 'apache' + +- name: Certificates | Debian | LetsEncrypt Certbot | Cleanup for Nginx + ansible.builtin.import_tasks: nginx_cleanup.yml + when: CERT_CONFIG.letsencrypt.service == 'nginx' + +- name: Certificates | Debian | LetsEncrypt Certbot | Adding service for certbot renewal + ansible.builtin.template: + src: "templates/etc/systemd/system/{{ item }}.j2" + dest: "/etc/systemd/system/{{ item }}" + owner: 'root' + group: 'root' + mode: 0644 + with_items: + - 'ansibleguy.infra_certs.LetsEncryptCertbot.service' + - 'ansibleguy.infra_certs.LetsEncryptCertbot.timer' + +- name: Certificates | Debian | LetsEncrypt Certbot | Enabling cert-renewal timer + ansible.builtin.systemd: + daemon_reload: yes + name: 'ansibleguy.infra_certs.LetsEncryptCertbot.timer' + enabled: yes + state: started + +- name: Certificates | Debian | LetsEncrypt Certbot | Running renewal + ansible.builtin.command: 'certbot renew --force-renewal' + when: CERT_CONFIG.letsencrypt.renew + ignore_errors: true diff --git a/tasks/debian/letsencrypt/nginx.yml b/tasks/debian/letsencrypt/nginx.yml new file mode 100644 index 0000000..da365dc --- /dev/null +++ b/tasks/debian/letsencrypt/nginx.yml @@ -0,0 +1,29 @@ +--- + +- name: Certificates | Debian | LetsEncrypt Certbot | Nginx | Install package + ansible.builtin.package: + name: ['python3-certbot-nginx'] + state: present + +- name: Certificates | Debian | LetsEncrypt Certbot | Nginx | Checking sites + ansible.builtin.shell: 'ls /etc/nginx/sites-enabled/' + changed_when: false + register: enabled_nginx_sites + +- name: Certificates | Debian | LetsEncrypt Certbot | Nginx | Deploying temporary apache site + ansible.builtin.template: + src: 'templates/etc/nginx/sites-enabled/le_dummy.j2' + dest: '/etc/nginx/sites-enabled/tmp_le_dummy' + owner: 'root' + group: 'root' + mode: 0644 + register: tmp_site_enable + when: enabled_nginx_sites.stdout == '' + +- name: Certificates | Debian | LetsEncrypt Certbot | Nginx | Reloading apache + ansible.builtin.systemd: + name: 'nginx.service' + state: reloaded + when: + - enabled_nginx_sites.stdout == '' + - tmp_site_enable.changed diff --git a/tasks/debian/letsencrypt/nginx_cleanup.yml b/tasks/debian/letsencrypt/nginx_cleanup.yml new file mode 100644 index 0000000..cd750e6 --- /dev/null +++ b/tasks/debian/letsencrypt/nginx_cleanup.yml @@ -0,0 +1,13 @@ +--- + +- name: Certificates | Debian | LetsEncrypt Certbot | Nginx | Disable temporary site + ansible.builtin.file: + state: absent + path: '/etc/nginx/sites-enabled/tmp_le_dummy' + register: tmp_site_disable + +- name: Certificates | Debian | LetsEncrypt Certbot | Nginx | Reloading apache + ansible.builtin.systemd: + name: 'nginx.service' + state: reloaded + when: tmp_site_disable.changed diff --git a/tasks/debian/main.yml b/tasks/debian/main.yml deleted file mode 100644 index cd683db..0000000 --- a/tasks/debian/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- - -- name: ROLE | Debian | Task - ansible.builtin.apt: - pkg: "{{ something }}" - tags: [base] diff --git a/tasks/internal/ca_minimal.yml b/tasks/internal/ca_minimal.yml new file mode 100644 index 0000000..7a4a88d --- /dev/null +++ b/tasks/internal/ca_minimal.yml @@ -0,0 +1,106 @@ +--- + +# creating a minimal ca + +- name: Certificates | Internal | Minimal CA | Creating ca directory + ansible.builtin.file: + path: "{{ CERT_CONFIG.ca.path }}" + state: directory + +- name: Certificates | Internal | Minimal CA | Generate ca private key (encrypted key) + community.crypto.openssl_privatekey: + path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_key }}" + passphrase: "{{ CERT_CONFIG.ca.pwd }}" + cipher: "{{ CERT_CONFIG.ca.cipher }}" + size: "{{ CERT_CONFIG.ca.key_size }}" + type: "{{ CERT_CONFIG.ca.key_type }}" + regenerate: "{{ CERT_CONFIG.ca.regenerate }}" + mode: "{{ CERT_CONFIG.mode_key }}" + owner: "{{ CERT_CONFIG.owner_key }}" + group: "{{ CERT_CONFIG.group_key }}" + no_log: true + when: CERT_CONFIG.ca.pwd | default(none, true) is not none + +- name: Certificates | Internal | Minimal CA | Generate ca private key (plain key) + community.crypto.openssl_privatekey: + path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_key }}" + size: "{{ CERT_CONFIG.ca.key_size }}" + type: "{{ CERT_CONFIG.ca.key_type }}" + regenerate: "{{ CERT_CONFIG.ca.regenerate }}" + mode: "{{ CERT_CONFIG.mode_key }}" + owner: "{{ CERT_CONFIG.owner_key }}" + group: "{{ CERT_CONFIG.group_key }}" + no_log: true + when: CERT_CONFIG.ca.pwd | default(none, true) is none + +# NOTE: for details see https://www.openssl.org/docs/man1.0.2/man5/x509v3_config.html + +- name: Certificates | Internal | Minimal CA | Generating ca signing-request (encrypted key) + community.crypto.openssl_csr: + path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_csr }}" + privatekey_path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_key }}" + privatekey_passphrase: "{{ CERT_CONFIG.ca.pwd }}" + basic_constraints: ['CA:TRUE', 'pathlen:2'] + basic_constraints_critical: true + key_usage: ['cRLSign', 'digitalSignature', 'keyCertSign'] + key_usage_critical: true + digest: "{{ CERT_CONFIG.ca.digest }}" + common_name: "{{ CERT_CONFIG.ca.cn }}" + organization_name: "{{ CERT_CONFIG.ca.org }}" + country_name: "{{ CERT_CONFIG.ca.country }}" + state_or_province_name: "{{ CERT_CONFIG.ca.state }}" + locality_name: "{{ CERT_CONFIG.ca.locality }}" + email_address: "{{ CERT_CONFIG.ca.email }}" + mode: "{{ CERT_CONFIG.mode_cert }}" + owner: "{{ CERT_CONFIG.owner_cert }}" + group: "{{ CERT_CONFIG.group_cert }}" + no_log: true + when: CERT_CONFIG.ca.pwd | default(none, true) is not none + +- name: Certificates | Internal | Minimal CA | Generating ca signing-request (plain key) + community.crypto.openssl_csr: + path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_csr }}" + privatekey_path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_key }}" + basic_constraints: ['CA:TRUE', 'pathlen:2'] + basic_constraints_critical: true + key_usage: ['cRLSign', 'digitalSignature', 'keyCertSign'] + key_usage_critical: true + digest: "{{ CERT_CONFIG.ca.digest }}" + common_name: "{{ CERT_CONFIG.ca.cn }}" + organization_name: "{{ CERT_CONFIG.ca.org }}" + country_name: "{{ CERT_CONFIG.ca.country }}" + state_or_province_name: "{{ CERT_CONFIG.ca.state }}" + locality_name: "{{ CERT_CONFIG.ca.locality }}" + email_address: "{{ CERT_CONFIG.ca.email }}" + mode: "{{ CERT_CONFIG.mode_cert }}" + owner: "{{ CERT_CONFIG.owner_cert }}" + group: "{{ CERT_CONFIG.group_cert }}" + no_log: true + when: CERT_CONFIG.ca.pwd | default(none, true) is none + +- name: Certificates | Internal | Minimal CA | Generating ca certificate (encrypted key) + community.crypto.x509_certificate: + path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_cert }}" + csr_path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_csr }}" + privatekey_path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_key }}" + privatekey_passphrase: "{{ CERT_CONFIG.ca.pwd }}" + provider: selfsigned + valid_in: "{{ CERT_CONFIG.ca.valid_days }}d" + mode: "{{ CERT_CONFIG.mode_cert }}" + owner: "{{ CERT_CONFIG.owner_cert }}" + group: "{{ CERT_CONFIG.group_cert }}" + no_log: true + when: CERT_CONFIG.ca.pwd | default(none, true) is not none + +- name: Certificates | Internal | Minimal CA | Generating ca certificate (plain key) + community.crypto.x509_certificate: + path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_cert }}" + privatekey_path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_key }}" + csr_path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_csr }}" + provider: selfsigned + valid_in: "{{ CERT_CONFIG.ca.valid_days }}d" + mode: "{{ CERT_CONFIG.mode_cert }}" + owner: "{{ CERT_CONFIG.owner_cert }}" + group: "{{ CERT_CONFIG.group_cert }}" + no_log: true + when: CERT_CONFIG.ca.pwd | default(none, true) is none diff --git a/tasks/internal/cert.yml b/tasks/internal/cert.yml new file mode 100644 index 0000000..8d05c56 --- /dev/null +++ b/tasks/internal/cert.yml @@ -0,0 +1,202 @@ +--- + +- name: Certificates | Internal | Cert | Generate private key (encrypted) + community.crypto.openssl_privatekey: + path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_key }}" + cipher: "{{ CERT_CONFIG.cert.cipher }}" + size: "{{ CERT_CONFIG.cert.key_size }}" + type: "{{ CERT_CONFIG.cert.key_type }}" + passphrase: "{{ CERT_CONFIG.cert.pwd }}" + regenerate: "{{ CERT_CONFIG.cert.regenerate }}" + mode: "{{ CERT_CONFIG.mode_key }}" + owner: "{{ CERT_CONFIG.owner_key }}" + group: "{{ CERT_CONFIG.group_key }}" + no_log: true + when: CERT_CONFIG.cert.pwd | default(none, true) is not none + +- name: Certificates | Internal | Cert | Generate private key (plain) + community.crypto.openssl_privatekey: + path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_key }}" + size: "{{ CERT_CONFIG.cert.key_size }}" + type: "{{ CERT_CONFIG.cert.key_type }}" + regenerate: "{{ CERT_CONFIG.cert.regenerate }}" + mode: "{{ CERT_CONFIG.mode_key }}" + owner: "{{ CERT_CONFIG.owner_key }}" + group: "{{ CERT_CONFIG.group_key }}" + no_log: true + when: CERT_CONFIG.cert.pwd | default(none, true) is none + +- name: Certificates | Internal | Cert | Setting SAN + ansible.builtin.set_fact: + cert_san: "{% for domain in CERT_CONFIG.cert.domains %} + {% if domain | valid_domain %}DNS:{{ domain }}{% if not loop.last %},{% endif %}{% endif %} + {% endfor %} + {% for ip in CERT_CONFIG.cert.ips %} + {% if ip | valid_ip %},IP:{{ ip }}{% endif %} + {% endfor %}" + +- name: Certificates | Internal | Cert | Generating signing-request (encrypted key) + community.crypto.openssl_csr: + path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_csr }}" + privatekey_path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_key }}" + privatekey_passphrase : "{{ CERT_CONFIG.cert.pwd }}" + digest: "{{ CERT_CONFIG.cert.digest }}" + common_name: "{{ CERT_CONFIG.cert.cn }}" + organization_name: "{{ CERT_CONFIG.cert.org }}" + country_name: "{{ CERT_CONFIG.cert.country }}" + state_or_province_name: "{{ CERT_CONFIG.cert.state }}" + locality_name: "{{ CERT_CONFIG.cert.locality }}" + email_address: "{{ CERT_CONFIG.cert.email }}" + extended_key_usage: "{{ CERT_CONFIG.cert.key_usage }}" + ocsp_must_staple: "{{ CERT_CONFIG.cert.ocsp_staple }}" + crl_distribution_points: "{{ CERT_CONFIG.cert.crl_distribution }}" + subject_alt_name: "{{ cert_san | replace(' ', '') | default('DNS:localhost', true) }}" + mode: "{{ CERT_CONFIG.mode_cert }}" + owner: "{{ CERT_CONFIG.owner_cert }}" + group: "{{ CERT_CONFIG.group_cert }}" + no_log: true + when: CERT_CONFIG.cert.pwd | default(none, true) is not none + +- name: Certificates | Internal | Cert | Generating signing-request (plain key) + community.crypto.openssl_csr: + path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_csr }}" + privatekey_path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_key }}" + digest: "{{ CERT_CONFIG.cert.digest }}" + common_name: "{{ CERT_CONFIG.cert.cn }}" + organization_name: "{{ CERT_CONFIG.cert.org }}" + country_name: "{{ CERT_CONFIG.cert.country }}" + state_or_province_name: "{{ CERT_CONFIG.cert.state }}" + locality_name: "{{ CERT_CONFIG.cert.locality }}" + email_address: "{{ CERT_CONFIG.cert.email }}" + extended_key_usage: "{{ CERT_CONFIG.cert.key_usage }}" + ocsp_must_staple: "{{ CERT_CONFIG.cert.ocsp_staple }}" + crl_distribution_points: "{{ CERT_CONFIG.cert.crl_distribution }}" + subject_alt_name: "{{ cert_san | replace(' ', '') | default('DNS:localhost', true) }}" + mode: "{{ CERT_CONFIG.mode_cert }}" + owner: "{{ CERT_CONFIG.owner_cert }}" + group: "{{ CERT_CONFIG.group_cert }}" + no_log: true + when: CERT_CONFIG.cert.pwd | default(none, true) is none + +- name: Certificates | Internal | Cert | Self-Signed | Generating certificate (encrypted key) + community.crypto.x509_certificate: + path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_cert }}" + privatekey_path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_key }}" + privatekey_passphrase: "{{ CERT_CONFIG.cert.pwd }}" + csr_path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_csr }}" + provider: selfsigned + valid_in: "{{ CERT_CONFIG.cert.valid_days }}d" + mode: "{{ CERT_CONFIG.mode_cert }}" + owner: "{{ CERT_CONFIG.owner_cert }}" + group: "{{ CERT_CONFIG.group_cert }}" + no_log: true + when: + - CERT_CONFIG.cert.pwd | default(none, true) is not none + - CERT_CONFIG.mode == 'selfsigned' + +- name: Certificates | Internal | Cert | Self-Signed | Generating certificate (plain key) + community.crypto.x509_certificate: + path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_cert }}" + privatekey_path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_key }}" + csr_path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_csr }}" + provider: selfsigned + valid_in: "{{ CERT_CONFIG.cert.valid_days }}d" + mode: "{{ CERT_CONFIG.mode_cert }}" + owner: "{{ CERT_CONFIG.owner_cert }}" + group: "{{ CERT_CONFIG.group_cert }}" + no_log: true + when: + - CERT_CONFIG.cert.pwd | default(none, true) is none + - CERT_CONFIG.mode == 'selfsigned' + +- name: Certificates | Internal | Cert | CA-Signed | Generating certificate (encrypted key; encrypted ca-key) + community.crypto.x509_certificate: + path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_cert }}" + privatekey_path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_key }}" + privatekey_passphrase: "{{ CERT_CONFIG.cert.pwd }}" + csr_path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_csr }}" + valid_in: "{{ CERT_CONFIG.cert.valid_days }}d" + provider: ownca + ownca_path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_cert }}" + ownca_privatekey_path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_key }}" + ownca_privatekey_passphrase: "{{ CERT_CONFIG.ca.pwd }}" + mode: "{{ CERT_CONFIG.mode_cert }}" + owner: "{{ CERT_CONFIG.owner_cert }}" + group: "{{ CERT_CONFIG.group_cert }}" + no_log: true + when: + - CERT_CONFIG.ca.pwd | default(none, true) is not none + - CERT_CONFIG.cert.pwd | default(none, true) is not none + - CERT_CONFIG.mode == 'ca' + +- name: Certificates | Internal | Cert | CA-Signed | Generating certificate (plain key; encrypted ca-key) + community.crypto.x509_certificate: + path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_cert }}" + privatekey_path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_key }}" + csr_path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_csr }}" + valid_in: "{{ CERT_CONFIG.cert.valid_days }}d" + provider: ownca + ownca_path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_cert }}" + ownca_privatekey_path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_key }}" + ownca_privatekey_passphrase: "{{ CERT_CONFIG.ca.pwd }}" + mode: "{{ CERT_CONFIG.mode_cert }}" + owner: "{{ CERT_CONFIG.owner_cert }}" + group: "{{ CERT_CONFIG.group_cert }}" + no_log: true + when: + - CERT_CONFIG.ca.pwd | default(none, true) is not none + - CERT_CONFIG.cert.pwd | default(none, true) is none + - CERT_CONFIG.mode == 'ca' + +- name: Certificates | Internal | Cert | CA-Signed | Generating certificate (encrypted key; plain ca-key) + community.crypto.x509_certificate: + path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_cert }}" + privatekey_path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_key }}" + privatekey_passphrase: "{{ CERT_CONFIG.cert.pwd }}" + csr_path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_csr }}" + valid_in: "{{ CERT_CONFIG.cert.valid_days }}d" + provider: ownca + ownca_path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_cert }}" + ownca_privatekey_path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_key }}" + mode: "{{ CERT_CONFIG.mode_cert }}" + owner: "{{ CERT_CONFIG.owner_cert }}" + group: "{{ CERT_CONFIG.group_cert }}" + no_log: true + when: + - CERT_CONFIG.ca.pwd | default(none, true) is none + - CERT_CONFIG.cert.pwd | default(none, true) is not none + - CERT_CONFIG.mode == 'ca' + +- name: Certificates | Internal | Cert | CA-Signed | Generating certificate (plain key; plain ca-key) + community.crypto.x509_certificate: + path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_cert }}" + privatekey_path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_key }}" + csr_path: "{{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_csr }}" + valid_in: "{{ CERT_CONFIG.cert.valid_days }}d" + provider: ownca + ownca_path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_cert }}" + ownca_privatekey_path: "{{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_key }}" + mode: "{{ CERT_CONFIG.mode_cert }}" + owner: "{{ CERT_CONFIG.owner_cert }}" + group: "{{ CERT_CONFIG.group_cert }}" + no_log: true + when: + - CERT_CONFIG.ca.pwd | default(none, true) is none + - CERT_CONFIG.cert.pwd | default(none, true) is none + - CERT_CONFIG.mode == 'ca' + +- name: Certificates | Internal | Cert | CA-Signed | Creating chained certificate + ansible.builtin.shell: "cat {{ CERT_CONFIG.path }}/{{ name }}.{{ CERT_CONFIG.extension_cert }} + {{ CERT_CONFIG.ca.path }}/ca.{{ CERT_CONFIG.extension_cert }} > + {{ CERT_CONFIG.path }}/{{ name }}.chain.{{ CERT_CONFIG.extension_cert }}" + args: + creates: "{{ CERT_CONFIG.path }}/{{ name }}.chain.{{ CERT_CONFIG.extension_cert }}" + when: CERT_CONFIG.mode == 'ca' + +- name: Certificates | Internal | Cert | CA-Signed | Setting privileges on chained certificate + ansible.builtin.file: + path: "{{ CERT_CONFIG.path }}/{{ name }}.chain.{{ CERT_CONFIG.extension_cert }}" + mode: "{{ CERT_CONFIG.mode_cert }}" + owner: "{{ CERT_CONFIG.owner_cert }}" + group: "{{ CERT_CONFIG.group_cert }}" + when: CERT_CONFIG.mode == 'ca' diff --git a/tasks/internal/main.yml b/tasks/internal/main.yml new file mode 100644 index 0000000..28b7be1 --- /dev/null +++ b/tasks/internal/main.yml @@ -0,0 +1,22 @@ +--- + +- name: Certificates | Internal | Installing dependencies + ansible.builtin.package: + pkg: ['python3-cryptography'] + tags: [certs, ca] + +- name: Certificates | Internal | Creating cert directory + ansible.builtin.file: + path: "{{ CERT_CONFIG.path }}" + state: directory + tags: [certs, ca] + +- name: Certificates | Internal | Minimal CA + ansible.builtin.import_tasks: ca_minimal.yml + when: CERT_CONFIG.mode == 'ca' + tags: [ca] + +- name: Certificates | Internal | Cert + ansible.builtin.import_tasks: cert.yml + when: "CERT_CONFIG.mode in ['ca', 'selfsigned']" + tags: [certs] diff --git a/tasks/main.yml b/tasks/main.yml index 3317b5d..ad69746 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,5 +1,25 @@ --- -- name: ROLE | Processing debian config - ansible.builtin.import_tasks: debian/main.yml - when: "ansible_distribution|lower in ['debian', 'ubuntu']" +- name: Certificates | Checking config + ansible.builtin.fail: + msg: "The required configuration was not provided! + Needed: 'certs'" + when: certs is undefined + +- name: Certificates | Setting name + ansible.builtin.set_fact: + name: "{% if CERT_CONFIG.cert.name is not none %}{{ CERT_CONFIG.cert.name | safe_key }}{% else %}{{ CERT_CONFIG.cert.cn | safe_key }}{% endif %}" + +- name: Certificates | Internal signed + ansible.builtin.include_tasks: internal/main.yml + when: "CERT_CONFIG.mode in ['ca_full', 'ca', 'selfsigned']" + +- name: Certificates | Internal | CA + ansible.builtin.include_tasks: debian/ca_full.yml + when: CERT_CONFIG.mode == 'ca_full' + +- name: Certificates | Debian | Letsencrypt + ansible.builtin.include_tasks: debian/letsencrypt/main.yml + when: + - CERT_CONFIG.mode == 'le_certbot' + - "ansible_distribution|lower in ['debian', 'ubuntu']" diff --git a/templates/etc/apache2/sites-enabled/le_dummy.conf.j2 b/templates/etc/apache2/sites-enabled/le_dummy.conf.j2 new file mode 100644 index 0000000..ab4f4b4 --- /dev/null +++ b/templates/etc/apache2/sites-enabled/le_dummy.conf.j2 @@ -0,0 +1,9 @@ +# {{ ansible_managed }} +# ansibleguy.infra_certs - dummy site used for letsencrypt certbot + + + ServerName dummy.letsencrypt.localhost + ServerAdmin webmaster@localhost + ErrorLog {{ APACHE_CONFIG.log.path }}/error.log + CustomLog {{ APACHE_CONFIG.log.path }}/access.log combined + diff --git a/templates/etc/nginx/sites-enabled/le_dummy.j2 b/templates/etc/nginx/sites-enabled/le_dummy.j2 new file mode 100644 index 0000000..78345fd --- /dev/null +++ b/templates/etc/nginx/sites-enabled/le_dummy.j2 @@ -0,0 +1,9 @@ +# {{ ansible_managed }} +# ansibleguy.infra_certs - dummy site used for letsencrypt certbot + +server { + listen 80; + server_name dummy.letsencrypt.localhost; + error_log {{ NGINX_CONFIG.log.path }}/error.log; + access_log {{ NGINX_CONFIG.log.path }}/access.log; +} diff --git a/templates/etc/systemd/system/ansibleguy.infra_certs.LetsEncryptCertbot.service.j2 b/templates/etc/systemd/system/ansibleguy.infra_certs.LetsEncryptCertbot.service.j2 new file mode 100644 index 0000000..b677f23 --- /dev/null +++ b/templates/etc/systemd/system/ansibleguy.infra_certs.LetsEncryptCertbot.service.j2 @@ -0,0 +1,10 @@ +# {{ ansible_managed }} +# ansibleguy.infra_certs + +[Unit] +Description=Service to renew LetsEncrypt Certificates using certbot + +[Service] +Type=oneshot +ExecStart=certbot renew -{{ CERT_CONFIG.letsencrypt.verbosity }} --non-interactive --agree-tos --renew-with-new-domains +SuccessExitStatus=0 diff --git a/templates/etc/systemd/system/ansibleguy.infra_certs.LetsEncryptCertbot.timer.j2 b/templates/etc/systemd/system/ansibleguy.infra_certs.LetsEncryptCertbot.timer.j2 new file mode 100644 index 0000000..29ac212 --- /dev/null +++ b/templates/etc/systemd/system/ansibleguy.infra_certs.LetsEncryptCertbot.timer.j2 @@ -0,0 +1,13 @@ +# {{ ansible_managed }} +# ansibleguy.infra_certs + +[Unit] +Description=Timer to renew LetsEncrypt Certificates using certbot + +[Timer] +OnCalendar={{ CERT_CONFIG.letsencrypt.renew_timer }} +Persistent=false +WakeSystem=false + +[Install] +WantedBy=multi-user.target