265 lines
10 KiB
YAML
265 lines
10 KiB
YAML
# Ansible Roles for managing Auengun.net Infrastructure & Testing/Learning.
|
|
# Source available at git.auengun.net/homelab/ansible-collection
|
|
# Copyright (C) 2023 GregoryDosh
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
# SPDX-FileCopyrightText: 2023 GregoryDosh
|
|
---
|
|
- name: Seed Healtcheck Cron Minute
|
|
ansible.builtin.set_fact:
|
|
SSH_RENEW_RANDOM_MINUTE: "{{ SSH_RENEW_RANDOM_MINUTE | default(59 | random(seed=inventory_hostname_short)) }}"
|
|
|
|
- name: Get Healthcheck API Ping URL for Renewal
|
|
ansible.builtin.uri:
|
|
url: https://healthchecks.auengun.net/api/v2/checks/
|
|
method: POST
|
|
body_format: json
|
|
status_code: [200, 201]
|
|
return_content: true
|
|
headers:
|
|
Content-Type: application/json
|
|
X-Api-Key: "{{ HEALTHCHECK_SITE_API_KEY }}"
|
|
body: |
|
|
{
|
|
"name": "{{ STEP_HC_RENEWAL_NAME }} - Post Renewal",
|
|
"tz": "{{ HOST_TZ }}",
|
|
"timeout": {{ STEP_HC_RENEWAL_TIMEOUT }},
|
|
"grace": {{ STEP_HC_RENEWAL_GRACE }},
|
|
"tags": "{{ STEP_HC_RENEWAL_TAGS }}",
|
|
"unique": [
|
|
"name"
|
|
]
|
|
}
|
|
register: _hc_api_response_renewal
|
|
delegate_to: localhost
|
|
until: _hc_api_response_renewal.status == 200 or _hc_api_response_renewal.status == 201
|
|
# 5 minutes at 60 * 5 seconds
|
|
retries: 60
|
|
delay: 5
|
|
|
|
- ansible.builtin.set_fact:
|
|
_hc_ping_url: "{{ _hc_api_response_renewal.json.ping_url }}"
|
|
|
|
- name: Get existing ACME cert SANs (if exist)
|
|
become: true
|
|
ansible.builtin.shell: |
|
|
STEPPATH={{ STEP_PATH }} {{ STEP_BIN_ABSOLUTE_PATH }} certificate inspect {{ STEP_CERTS_PATH }}{{ STEP_CERTS_ACME_CRT }} --format json | jq --sort-keys '.names'
|
|
no_log: true
|
|
ignore_errors: true
|
|
changed_when: false
|
|
register: _cert_existing_acme_san
|
|
|
|
- name: Get existing SSH cert SANs (if exist)
|
|
become: true
|
|
ansible.builtin.shell: |
|
|
STEPPATH={{ STEP_PATH }} {{ STEP_BIN_ABSOLUTE_PATH }} ssh inspect {{ STEP_CERTS_PATH }}{{ STEP_CERTS_SSH_HOST_CERT }} --format json | jq --sort-keys '.Principals'
|
|
no_log: true
|
|
ignore_errors: true
|
|
changed_when: false
|
|
register: _cert_existing_ssh_san
|
|
|
|
- when: _cert_existing_acme_san.stdout
|
|
ansible.builtin.set_fact:
|
|
_cert_existing_acme_san_json: |
|
|
{{ _cert_existing_acme_san.stdout | from_json | sort | to_json(indent=4) }}
|
|
|
|
- when: _cert_existing_ssh_san.stdout
|
|
ansible.builtin.set_fact:
|
|
_cert_existing_ssh_san_json: |
|
|
{{ _cert_existing_ssh_san.stdout | from_json | sort | to_json(indent=4) }}
|
|
|
|
- when: not _cert_existing_acme_san.stdout
|
|
ansible.builtin.set_fact:
|
|
_cert_existing_acme_san_json: ""
|
|
|
|
- when: not _cert_existing_ssh_san.stdout
|
|
ansible.builtin.set_fact:
|
|
_cert_existing_ssh_san_json: ""
|
|
|
|
- ansible.builtin.set_fact:
|
|
_cert_acme_existing_san_match: |
|
|
{{ (_cert_existing_acme_san_json == _cert_san_json) | bool }}
|
|
_cert_ssh_existing_san_match: |
|
|
{{ (_cert_existing_ssh_san_json == _cert_san_json) | bool }}
|
|
|
|
- name: Check if ACME cert needs renewal
|
|
become: true
|
|
ansible.builtin.shell: |
|
|
STEPPATH={{ STEP_PATH }} {{ STEP_BIN_ABSOLUTE_PATH }} certificate needs-renewal {{ STEP_CERTS_PATH }}{{ STEP_CERTS_ACME_CRT }} > /dev/stderr || true
|
|
register: _cert_acme_needs_renewal
|
|
changed_when: "'certificate does not need renewal' not in _cert_acme_needs_renewal.stderr"
|
|
|
|
- name: Check if SSH cert needs renewal
|
|
become: true
|
|
ansible.builtin.shell: |
|
|
STEPPATH={{ STEP_PATH }} {{ STEP_BIN_ABSOLUTE_PATH }} ssh needs-renewal {{ STEP_CERTS_PATH }}{{ STEP_CERTS_SSH_HOST_CERT }} > /dev/stderr || true
|
|
register: _cert_ssh_needs_renewal
|
|
changed_when: "'certificate does not need renewal' not in _cert_ssh_needs_renewal.stderr"
|
|
|
|
- name: ACME Cert
|
|
when: (_cert_acme_needs_renewal.changed or not _cert_acme_existing_san_match) or (_cert_ssh_needs_renewal.changed or not _cert_ssh_existing_san_match)
|
|
become: true
|
|
block:
|
|
- name: Create ACME cert
|
|
ansible.builtin.shell: |
|
|
STEPPATH={{ STEP_PATH }} {{ STEP_BIN_ABSOLUTE_PATH }} ca certificate \
|
|
{{ CERT_SAN[0] }} \
|
|
{{ STEP_CERTS_PATH }}{{ STEP_CERTS_ACME_CRT }} \
|
|
{{ STEP_CERTS_PATH }}{{ STEP_CERTS_ACME_KEY }} \
|
|
--ca-url {{ STEP_BOOTSTRAP_URL }} \
|
|
--provisioner {{ STEP_CERTS_ACME_CA_PROVISIONER }} \
|
|
{% if STEP_WEBROOT_PATH | length > 0 %}
|
|
--webroot={{ STEP_WEBROOT_PATH }} \
|
|
{% endif %}
|
|
{% for san in CERT_SAN %}
|
|
--san {{ san }} \
|
|
{% endfor %}
|
|
--force
|
|
register: _create_acme_cert
|
|
notify:
|
|
- Restart ACME-Renewal-Service
|
|
|
|
- name: adjust cert permissions
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
mode: "0644"
|
|
loop:
|
|
- "{{ STEP_CERTS_PATH }}{{ STEP_CERTS_ACME_KEY }}"
|
|
- "{{ STEP_CERTS_PATH }}{{ STEP_CERTS_ACME_CRT }}"
|
|
|
|
- name: SSH cert
|
|
when: _cert_ssh_needs_renewal.changed or not _cert_ssh_existing_san_match
|
|
become: true
|
|
block:
|
|
- name: Create SSH cert
|
|
ansible.builtin.shell: |
|
|
STEPPATH={{ STEP_PATH }} {{ STEP_BIN_ABSOLUTE_PATH }} ssh certificate \
|
|
{{ CERT_SAN[0] }} \
|
|
{{ STEP_CERTS_PATH }}{{ STEP_CERTS_SSH_PRIVATE_KEY }} \
|
|
--ca-url {{ STEP_BOOTSTRAP_URL }} \
|
|
--insecure \
|
|
--no-password \
|
|
--host \
|
|
--x5c-cert {{ STEP_CERTS_PATH }}{{ STEP_CERTS_ACME_CRT }} \
|
|
--x5c-key {{ STEP_CERTS_PATH }}{{ STEP_CERTS_ACME_KEY }} \
|
|
{% for san in CERT_SAN %}
|
|
--principal {{ san }} \
|
|
{% endfor %}
|
|
--force
|
|
|
|
- name: ensure SSH cert permissions
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
mode: "0600"
|
|
loop:
|
|
- "{{ STEP_CERTS_PATH }}{{ STEP_CERTS_SSH_PRIVATE_KEY }}"
|
|
- "{{ STEP_CERTS_PATH }}{{ STEP_CERTS_SSH_HOST_CERT }}"
|
|
|
|
- name: generate ssh roots for validating user certs
|
|
ansible.builtin.shell: |
|
|
STEPPATH={{ STEP_PATH }} {{ STEP_BIN_ABSOLUTE_PATH }} ssh config --roots > {{ STEP_CERTS_PATH }}{{ STEP_CERTS_SSH_TRUSTED_USER_CA_KEYS }}
|
|
changed_when: false
|
|
|
|
- name: Step SSH Config
|
|
become: true
|
|
ansible.builtin.blockinfile:
|
|
path: /etc/ssh/sshd_config
|
|
backup: yes
|
|
validate: "{{ SSHD_BIN_ABSOLUTE_PATH }} -T -f %s"
|
|
marker: "# {mark} Step SSH Configuration https://ca.auengun.net (ANSIBLE MANAGED) -->"
|
|
block: |
|
|
Match all
|
|
TrustedUserCAKeys {{ STEP_CERTS_PATH }}{{ STEP_CERTS_SSH_TRUSTED_USER_CA_KEYS }}
|
|
HostKey {{ STEP_CERTS_PATH }}{{ STEP_CERTS_SSH_PRIVATE_KEY }}
|
|
HostCertificate {{ STEP_CERTS_PATH }}{{ STEP_CERTS_SSH_HOST_CERT }}
|
|
notify:
|
|
- "Restart ssh"
|
|
|
|
- name: Acme Cert Renewal Service (systemd)
|
|
when: ansible_service_mgr == 'systemd'
|
|
become: true
|
|
block:
|
|
- name: create systemd post exec script
|
|
ansible.builtin.template:
|
|
src: "{{ role_path }}/templates/post-renewal-exec.sh.j2"
|
|
dest: "{{ STEP_CONFIG_PATH }}{{ CERT_RENEWAL_SERVICE_NAME }}-post-renewal-exec.sh"
|
|
owner: "{{ STEP_USER_NAME }}"
|
|
group: "{{ STEP_GROUP_NAME }}"
|
|
mode: "0744"
|
|
register: _systemd_post_exec_script
|
|
|
|
- name: call initial script on update/create (or ACME cert renew)
|
|
when: _systemd_post_exec_script.changed or _create_acme_cert.changed
|
|
ansible.builtin.shell: |
|
|
{{ STEP_CONFIG_PATH }}{{ CERT_RENEWAL_SERVICE_NAME }}-post-renewal-exec.sh
|
|
|
|
- name: create systemd service
|
|
ansible.builtin.template:
|
|
src: "{{ role_path }}/templates/etc/systemd/system/cert-renew.service.j2"
|
|
dest: "/etc/systemd/system/{{ CERT_RENEWAL_SERVICE_NAME }}.service"
|
|
owner: "{{ STEP_USER_NAME }}"
|
|
group: "{{ STEP_GROUP_NAME }}"
|
|
mode: "0644"
|
|
register: _cert_renewal_service
|
|
notify:
|
|
- Restart ACME-Renewal-Service
|
|
|
|
- name: force systemd to reread configs
|
|
when: _cert_renewal_service.changed
|
|
ansible.builtin.systemd_service:
|
|
daemon_reload: true
|
|
|
|
- name: enable service
|
|
ansible.builtin.systemd_service:
|
|
name: "{{ CERT_RENEWAL_SERVICE_NAME }}.service"
|
|
state: started
|
|
enabled: true
|
|
|
|
- name: SSH Cert Renewal Service (crontab)
|
|
become: true
|
|
block:
|
|
- ansible.builtin.include_role:
|
|
name: auengun.homelab.cron_healthcheck_script
|
|
vars:
|
|
HEALTHCHECK_NAME: "{{ STEP_HC_RENEWAL_NAME }}"
|
|
HEALTHCHECK_TAGS: "{{ STEP_HC_RENEWAL_TAGS }}"
|
|
HEALTHCHECK_SCHEDULE_TZ: "{{ HOST_TZ }}"
|
|
HEALTHCHECK_CRON_USER: root
|
|
HEALTHCHECK_FILE_NAME: hc-renew-ssh
|
|
HEALTHCHECK_CRON_HOUR: "*/12" # Every 12 hours
|
|
HEALTHCHECK_FILE_CONTENT: |
|
|
SSH_RENEW=$(STEPPATH={{ STEP_PATH }} {{ STEP_BIN_ABSOLUTE_PATH }} ssh renew --force {{ STEP_CERTS_PATH }}{{ STEP_CERTS_SSH_HOST_CERT }} {{ STEP_CERTS_PATH }}{{ STEP_CERTS_SSH_PRIVATE_KEY }} 2>&1)
|
|
EXIT_STATUS="$?"
|
|
if [ "$EXIT_STATUS" -ne 0 ]; then
|
|
$logger "ERROR: unable to renew ssh: $SSH_RENEW"
|
|
exit "$EXIT_STATUS"
|
|
else
|
|
$logger "INFO: Renewed SSH Host Keys: {{ STEP_CERTS_PATH }}{{ STEP_CERTS_SSH_HOST_CERT }} {{ STEP_CERTS_PATH }}{{ STEP_CERTS_SSH_PRIVATE_KEY }}"
|
|
fi
|
|
|
|
SSHD_RESTART=$({{ SSHD_BIN_ABSOLUTE_PATH }} -t && {% if SERVICE_BIN_ABSOLUTE_PATH %}{{ SERVICE_BIN_ABSOLUTE_PATH }} ssh restart{% else %}{{ SYSTEMCTL_BIN_ABSOLUTE_PATH }} {% if (_systemd_version | int) < 229 %}reload-or-try-restart{% else %}try-reload-or-restart{% endif %} sshd{% endif %})
|
|
EXIT_STATUS="$?"
|
|
if [ "$EXIT_STATUS" -ne 0 ]; then
|
|
$logger "ERROR: unable to restart sshd: $SSHD_RESTART"
|
|
exit "$EXIT_STATUS"
|
|
else
|
|
$logger "INFO: Restarted sshd to pick up changes"
|
|
fi
|
|
|
|
- name: call healthcheck on SSH cert renew
|
|
when: _cert_ssh_needs_renewal.changed or not _cert_ssh_existing_san_match
|
|
become: true
|
|
ansible.builtin.shell: hc-renew-ssh
|