From 960d95a924805891b375630a2af9b6e2e87f47d4 Mon Sep 17 00:00:00 2001 From: transcaffeine Date: Sun, 19 May 2024 20:40:43 +0200 Subject: [PATCH] feat: add finallycoffee.base.lego role --- roles/lego/defaults/main.yml | 66 ++++++++++++ roles/lego/files/lego_run.sh | 10 ++ roles/lego/handlers/main.yml | 5 + roles/lego/tasks/main.yml | 150 ++++++++++++++++++++++++++ roles/lego/templates/lego.timer.j2 | 9 ++ roles/lego/templates/lego@.service.j2 | 13 +++ roles/lego/vars/main.yml | 16 +++ 7 files changed, 269 insertions(+) create mode 100644 roles/lego/defaults/main.yml create mode 100644 roles/lego/files/lego_run.sh create mode 100644 roles/lego/handlers/main.yml create mode 100644 roles/lego/tasks/main.yml create mode 100644 roles/lego/templates/lego.timer.j2 create mode 100644 roles/lego/templates/lego@.service.j2 create mode 100644 roles/lego/vars/main.yml diff --git a/roles/lego/defaults/main.yml b/roles/lego/defaults/main.yml new file mode 100644 index 0000000..7abaac9 --- /dev/null +++ b/roles/lego/defaults/main.yml @@ -0,0 +1,66 @@ +--- +lego_user: "lego" +lego_version: "4.16.1" +lego_instance: default +lego_base_path: "/opt/lego" +lego_cert_user: "acme-{{ lego_instance }}" +lego_cert_group: "{{ lego_cert_user }}" +lego_instance_base_path: "{{ lego_base_path }}/instances" +lego_instance_path: "{{ lego_instance_base_path }}/{{ lego_instance }}" + +lego_cert_domains: [] +lego_cert_key_type: ec256 +lego_cert_days_to_renew: 30 +lego_acme_account_email: ~ +lego_acme_challenge_type: http +lego_acme_challenge_provider: ~ +lego_letsencrypt_server_urls: + qa: "https://acme-staging-v02.api.letsencrypt.org/directory" + prod: "https://acme-v02.api.letsencrypt.org/directory" +lego_acme_server_url: "{{ lego_letsencrypt_server_urls.qa }}" + +lego_base_environment: + LEGO_CERT_USER: "{{ lego_cert_user }}" + LEGO_CERT_GROUP: "{{ lego_cert_group }}" + LEGO_CERT_STORE_PATH: "{{ lego_instance_path }}" + LEGO_CERT_DAYS_TO_RENEW: "{{ lego_cert_days_to_renew }}" + LEGO_KEY_TYPE: "{{ lego_cert_key_type }}" + LEGO_ACME_CHALLENGE_TYPE: "{{ lego_acme_challenge_type }}" + LEGO_ACME_SERVER: "{{ lego_acme_server_url }}" + LEGO_COMMAND_ARGS: "{{ lego_command_args }}" + +lego_base_command_config: + server: "{{ lego_acme_server_url }}" + accept_tos: true + email: "{{ lego_acme_account_email }}" + path: "{{ lego_instance_path }}" + key_type: "{{ lego_cert_key_type }}" + +lego_acme_challenge_config: >- + {{ {lego_acme_challenge_type: lego_acme_challenge_provider} }} + +lego_systemd_unit_path: "/etc/systemd/system" +lego_systemd_template_unit_name: "lego@.service" +lego_systemd_template_unit_file: "{{ lego_systemd_template_unit_name }}.j2" +lego_systemd_service_name: "lego@{{ lego_instance }}.service" +lego_systemd_environment: >- + {{ lego_base_environment | combine(lego_environment | default({})) }} +lego_full_command_config: >- + {{ lego_base_command_config + | combine(lego_acme_challenge_config) + | combine(lego_command_config | default({})) }} + +lego_systemd_timer_name: "lego-{{ lego_instance }}.timer" +lego_systemd_timer_template: lego.timer.j2 +lego_systemd_timer_calendar: "*-*-* *:00/15:00" + +lego_architecture: "amd64" +lego_os: "linux" + +lego_release_archive_server: "https://github.com" +lego_release_archive_filename: >- + lego_v{{ lego_version }}_{{ lego_os }}_{{ lego_architecture }}.tar.gz +lego_release_archive_url: >- + {{ lego_release_archive_server }}/go-acme/lego/releases/download/v{{ lego_version }}/{{ lego_release_archive_filename }} +lego_release_archive_file_path: "/tmp/{{ lego_release_archive_filename }}" +lego_release_archive_path: "/tmp/lego_v{{ lego_version }}_{{ lego_os }}_{{ lego_architecture }}" diff --git a/roles/lego/files/lego_run.sh b/roles/lego/files/lego_run.sh new file mode 100644 index 0000000..e318eee --- /dev/null +++ b/roles/lego/files/lego_run.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +LEGO_BINARY=$(/usr/bin/env which lego) + +FILES_IN_DIR=$(find "$LEGO_CERT_STORE_PATH/certificates" | wc -l) +if [[ $FILES_IN_DIR -gt 2 ]]; then + $LEGO_BINARY $LEGO_COMMAND_ARGS renew --days=$LEGO_CERT_DAYS_TO_RENEW +else + $LEGO_BINARY $LEGO_COMMAND_ARGS run +fi diff --git a/roles/lego/handlers/main.yml b/roles/lego/handlers/main.yml new file mode 100644 index 0000000..b4f7ef5 --- /dev/null +++ b/roles/lego/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Ensure systemd daemon is reloaded + ansible.builtin.systemd: + daemon_reload: true + listen: systemd_reload diff --git a/roles/lego/tasks/main.yml b/roles/lego/tasks/main.yml new file mode 100644 index 0000000..368e841 --- /dev/null +++ b/roles/lego/tasks/main.yml @@ -0,0 +1,150 @@ +--- +- name: Ensure lego cert group is created + ansible.builtin.group: + name: "{{ lego_cert_group }}" + state: present + system: true + +- name: Ensure lego cert user is created + ansible.builtin.user: + name: "{{ lego_cert_user }}" + state: present + system: true + create_home: false + groups: + - "{{ lego_cert_group }}" + append: true + +- name: Ensure lego user is created + ansible.builtin.user: + name: "{{ lego_user }}" + state: present + system: true + create_home: false + groups: + - "{{ lego_cert_group }}" + append: true + +- name: Ensure lego is installed + block: + - name: Check if lego is present + ansible.builtin.command: + cmd: which lego + changed_when: false + failed_when: false + register: lego_binary_info + + - name: Download lego from source + ansible.builtin.get_url: + url: "{{ lego_release_archive_url }}" + url_username: "{{ lego_release_archive_url_username | default(omit) }}" + url_password: "{{ lego_release_archive_url_password | default(omit) }}" + dest: "{{ lego_release_archive_file_path }}" + when: lego_binary_info.rc != 0 + + - name: Create folder to uncompress into + ansible.builtin.file: + dest: "{{ lego_release_archive_path }}" + state: directory + when: lego_binary_info.rc != 0 + + - name: Uncompress lego source archive + ansible.builtin.unarchive: + src: "{{ lego_release_archive_file_path }}" + dest: "{{ lego_release_archive_path }}" + remote_src: true + when: lego_binary_info.rc != 0 + + - name: Ensure lego binary is present in PATH + ansible.builtin.copy: + src: "{{ lego_release_archive_path }}/lego" + dest: "/usr/local/bin/lego" + mode: "u+rwx,g+rx,o+rx" + remote_src: true + when: lego_binary_info.rc != 0 + + - name: Ensure intermediate data is gone + ansible.builtin.file: + path: "{{ item }}" + state: absent + loop: + - "{{ lego_release_archive_path }}" + - "{{ lego_release_archive_file_path }}" + when: lego_binary_info.rc != 0 + +- name: Ensure lego base path exists + ansible.builtin.file: + path: "{{ lego_base_path }}" + state: directory + mode: "0755" + +- name: Ensure template unit file is present + ansible.builtin.template: + src: "{{ lego_systemd_template_unit_file }}" + dest: "{{ lego_systemd_unit_path }}/{{ lego_systemd_template_unit_name }}" + notify: + - systemd_reload + +- name: Ensure env file is templated + ansible.builtin.copy: + content: |+ + {% for entry in lego_systemd_environment | dict2items %} + {{ entry.key }}={{ entry.value }} + {% endfor %} + dest: "{{ lego_base_path }}/{{ lego_instance }}.conf" + +- name: Ensure timer unit is templated + ansible.builtin.template: + src: "{{ lego_systemd_timer_template }}" + dest: "{{ lego_systemd_unit_path }}/{{ lego_systemd_timer_name }}" + notify: + - systemd_reload + +- name: Ensure handling script is templated + ansible.builtin.copy: + src: "lego_run.sh" + dest: "{{ lego_base_path }}/run.sh" + mode: "0755" + +- name: Ensure per-instance base path is created + ansible.builtin.file: + path: "{{ lego_instance_path }}" + state: directory + owner: "{{ lego_cert_user }}" + group: "{{ lego_cert_group }}" + mode: "0755" + +- name: Ensure per-instance sub folders are created with correct permissions + ansible.builtin.file: + path: "{{ item.path }}" + state: directory + owner: "{{ item.owner | default(lego_cert_user) }}" + group: "{{ item.group | default(lego_cert_group) }}" + mode: "{{ item.mode }}" + loop: + - path: "{{ lego_instance_path }}/secrets" + mode: "0750" + - path: "{{ lego_instance_path }}/accounts" + mode: "0770" + - path: "{{ lego_instance_path }}/certificates" + mode: "0775" + loop_control: + label: "{{ item.path }}" + +- name: Ensure systemd daemon is reloaded + meta: flush_handlers + +- name: Ensure systemd timer is enabled + ansible.builtin.systemd_service: + name: "{{ lego_systemd_timer_name }}" + enabled: true + +- name: Ensure systemd timer is started + ansible.builtin.systemd_service: + name: "{{ lego_systemd_timer_name }}" + state: "started" + +- name: Ensure systemd service is started once to obtain the certificate + ansible.builtin.systemd_service: + name: "{{ lego_systemd_service_name }}" + state: "started" diff --git a/roles/lego/templates/lego.timer.j2 b/roles/lego/templates/lego.timer.j2 new file mode 100644 index 0000000..7f3093b --- /dev/null +++ b/roles/lego/templates/lego.timer.j2 @@ -0,0 +1,9 @@ +[Unit] +Description=Run lego@{{ lego_instance}}.service + +[Timer] +OnCalendar={{ lego_systemd_timer_calendar }} +Unit=lego@{{ lego_instance }}.service + +[Install] +WantedBy=timers.target diff --git a/roles/lego/templates/lego@.service.j2 b/roles/lego/templates/lego@.service.j2 new file mode 100644 index 0000000..373444a --- /dev/null +++ b/roles/lego/templates/lego@.service.j2 @@ -0,0 +1,13 @@ +[Unit] +Description=Run lego (letsencrypt client in go) + +[Service] +Type=oneshot +EnvironmentFile={{ lego_base_path }}/%i.conf +User=acme-%i +Group=acme-%i +ExecStart={{ lego_base_path }}/run.sh + +[Install] +WantedBy=basic.target +DefaultInstance=default diff --git a/roles/lego/vars/main.yml b/roles/lego/vars/main.yml new file mode 100644 index 0000000..c962c8b --- /dev/null +++ b/roles/lego/vars/main.yml @@ -0,0 +1,16 @@ +--- + +lego_domain_command_args: >- + {% for domain in lego_cert_domains %} + --domains={{ domain }} + {%- endfor %} + +lego_config_command_args: >- + {% for key in lego_full_command_config %} + --{{ key | replace("_", "-") }} + {%- if lego_full_command_config[key] != None and lego_full_command_config[key] != '' -%} + ={{ lego_full_command_config[key] }} + {%- endif -%} + {%- endfor -%} + +lego_command_args: "{{ lego_domain_command_args }} {{ lego_config_command_args }}"