Skript Cert Renewal
This commit is contained in:
387
nebula_cert_renewal.yml
Normal file
387
nebula_cert_renewal.yml
Normal file
@@ -0,0 +1,387 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Nebula Certificate Renewal Playbook
|
||||
# =============================================================================
|
||||
#
|
||||
# Dieses Playbook erneuert abgelaufene (oder bald ablaufende) Nebula-Zertifikate.
|
||||
# Es funktioniert auch für Nodes, die NUR über die Nebula-IP erreichbar sind.
|
||||
#
|
||||
# STRATEGIE für Remote-Only-Nodes:
|
||||
# Ansible erreicht remote-only Nodes über die Nebula-IP. Das bedeutet: Nebula
|
||||
# muss während des Rollouts LAUFEN bleiben. Das neue Zertifikat wird auf dem
|
||||
# Lighthouse signiert, dann auf den Node kopiert und erst dann per Hot-Reload
|
||||
# aktiviert (SIGHUP statt Neustart). So bleibt die Verbindung während des
|
||||
# gesamten Vorgangs stabil.
|
||||
#
|
||||
# ABLAUF:
|
||||
# 1. Prüfen welche Certs ablaufen (innerhalb von nebula_cert_renew_threshold_days)
|
||||
# 2. Alte Certs auf dem Lighthouse sichern
|
||||
# 3. Neue Certs auf dem Lighthouse signieren
|
||||
# 4. Neue Certs auf die Nodes kopieren
|
||||
# 5. Nebula per SIGHUP neu laden (kein Verbindungsabbruch)
|
||||
#
|
||||
# VERWENDUNG:
|
||||
#
|
||||
# Alle Nodes prüfen und bei Bedarf erneuern:
|
||||
# ansible-playbook -i inventory nebula_cert_renew.yml
|
||||
#
|
||||
# Nur bestimmte Nodes erneuern:
|
||||
# ansible-playbook -i inventory nebula_cert_renew.yml --limit web01,db01
|
||||
#
|
||||
# Alle Certs erzwungen erneuern (egal ob abgelaufen oder nicht):
|
||||
# ansible-playbook -i inventory nebula_cert_renew.yml -e nebula_cert_force_renew=true
|
||||
#
|
||||
# Schwellwert anpassen (Standard: 30 Tage vor Ablauf):
|
||||
# ansible-playbook -i inventory nebula_cert_renew.yml -e nebula_cert_renew_threshold_days=60
|
||||
#
|
||||
# VARIABLEN (können in group_vars oder per -e übergeben werden):
|
||||
# nebula_cert_renew_threshold_days: 30 # Erneuerung X Tage vor Ablauf
|
||||
# nebula_cert_force_renew: false # true = immer erneuern
|
||||
# nebula_client_cert_duration: "43800h0m0s" # Laufzeit der neuen Certs (5 Jahre)
|
||||
# nebula_network_cidr: 24
|
||||
#
|
||||
# INVENTORY-VORAUSSETZUNGEN:
|
||||
# - Gruppe [nebula_lighthouse] muss existieren
|
||||
# - groups['nebula_lighthouse'][0] ist der Primary Lighthouse (CA-Schlüssel)
|
||||
# - Nodes haben nebula_internal_ip_addr gesetzt
|
||||
# - Remote-only Nodes: ansible_host auf die Nebula-IP setzen
|
||||
#
|
||||
# BEISPIEL INVENTORY:
|
||||
# [nebula_lighthouse]
|
||||
# lighthouse01.example.com nebula_internal_ip_addr=10.43.0.1
|
||||
#
|
||||
# [servers]
|
||||
# web01.example.com nebula_internal_ip_addr=10.43.0.2
|
||||
# # Nur über Nebula erreichbar - ansible_host auf Nebula-IP:
|
||||
# remote01 ansible_host=10.43.0.5 nebula_internal_ip_addr=10.43.0.5
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# PHASE 1: Zertifikate prüfen und ggf. auf dem Lighthouse neu signieren
|
||||
# Läuft auf dem Primary Lighthouse (er besitzt den CA-Key)
|
||||
# -----------------------------------------------------------------------------
|
||||
- name: "Nebula Cert Renewal - Phase 1: Prüfen & Signieren auf Primary Lighthouse"
|
||||
hosts: "{{ groups['nebula_lighthouse'][0] }}"
|
||||
gather_facts: false
|
||||
become: true
|
||||
|
||||
vars:
|
||||
nebula_cert_renew_threshold_days: 30
|
||||
nebula_cert_force_renew: false
|
||||
nebula_client_cert_duration: "43800h0m0s"
|
||||
nebula_network_cidr: 24
|
||||
nebula_cert_dir: /opt/nebula
|
||||
# Alle Nodes aus dem Inventory ermitteln (Lighthouse selbst + alle anderen)
|
||||
_all_nebula_nodes: >-
|
||||
{{
|
||||
(groups['nebula_lighthouse'] + groups.get('servers', []) + groups.get('nebula_nodes', []))
|
||||
| unique
|
||||
}}
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Sicherstellen dass python3-dateutil installiert ist (für Datumsvergleich)
|
||||
package:
|
||||
name: python3-dateutil
|
||||
state: present
|
||||
ignore_errors: true
|
||||
|
||||
- name: Zertifikats-Ablaufdaten für alle Nodes ermitteln
|
||||
command: >
|
||||
{{ nebula_cert_dir }}/nebula-cert print -json
|
||||
-path {{ nebula_cert_dir }}/{{ item }}.crt
|
||||
register: _cert_info_raw
|
||||
loop: "{{ _all_nebula_nodes }}"
|
||||
changed_when: false
|
||||
ignore_errors: true
|
||||
# Fehler ignorieren falls Cert noch nicht existiert
|
||||
|
||||
- name: Ablaufstatus pro Node berechnen
|
||||
set_fact:
|
||||
_cert_status: >-
|
||||
{{
|
||||
_cert_status | default({}) | combine({
|
||||
item.item: {
|
||||
'exists': item.rc == 0,
|
||||
'expired_or_missing': (item.rc != 0) or (
|
||||
(item.stdout | from_json).details.notAfter
|
||||
| int < (ansible_date_time.epoch | int + nebula_cert_renew_threshold_days * 86400)
|
||||
),
|
||||
'not_after': (item.rc == 0) | ternary(
|
||||
(item.stdout | from_json).details.notAfter | int | strftime('%Y-%m-%d'),
|
||||
'N/A'
|
||||
)
|
||||
}
|
||||
})
|
||||
}}
|
||||
loop: "{{ _cert_info_raw.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
vars:
|
||||
ansible_date_time:
|
||||
epoch: "{{ lookup('pipe', 'date +%s') }}"
|
||||
|
||||
- name: Status-Übersicht anzeigen
|
||||
debug:
|
||||
msg: >-
|
||||
{{ item.key }}:
|
||||
Ablauf={{ _cert_status[item.key].not_after }},
|
||||
Erneuerung nötig={{ _cert_status[item.key].expired_or_missing or nebula_cert_force_renew | bool }}
|
||||
loop: "{{ _cert_status | dict2items }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
|
||||
- name: Backup-Verzeichnis anlegen
|
||||
file:
|
||||
path: "{{ nebula_cert_dir }}/cert_backup_{{ lookup('pipe', 'date +%Y%m%d_%H%M%S') }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0700'
|
||||
register: _backup_dir
|
||||
|
||||
- name: Backup der bestehenden Certs erstellen
|
||||
copy:
|
||||
src: "{{ nebula_cert_dir }}/{{ item }}.crt"
|
||||
dest: "{{ _backup_dir.path }}/{{ item }}.crt"
|
||||
remote_src: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0600'
|
||||
loop: "{{ _all_nebula_nodes }}"
|
||||
when: _cert_status[item].exists
|
||||
ignore_errors: true
|
||||
|
||||
- name: Ablaufende/fehlende Certs auf Lighthouse löschen (damit nebula-cert neu signiert)
|
||||
file:
|
||||
path: "{{ nebula_cert_dir }}/{{ item }}"
|
||||
state: absent
|
||||
loop: >-
|
||||
{{
|
||||
_cert_status | dict2items
|
||||
| selectattr('value.expired_or_missing', 'equalto', true)
|
||||
| map(attribute='key')
|
||||
| product(['.crt', '.key'])
|
||||
| map('join')
|
||||
| list
|
||||
}}
|
||||
when: nebula_cert_force_renew | bool or _cert_status[item.split('.crt')[0].split('.key')[0]].expired_or_missing
|
||||
# Lighthouse-eigenes Cert separat behandelt (unten)
|
||||
|
||||
- name: Neue Certs für Lighthouse selbst signieren (falls nötig)
|
||||
command: >
|
||||
{{ nebula_cert_dir }}/nebula-cert sign
|
||||
-name "{{ item }}"
|
||||
-ip "{{ hostvars[item].nebula_internal_ip_addr }}/{{ nebula_network_cidr }}"
|
||||
-duration "{{ nebula_client_cert_duration }}"
|
||||
args:
|
||||
chdir: "{{ nebula_cert_dir }}"
|
||||
creates: "{{ nebula_cert_dir }}/{{ item }}.crt"
|
||||
loop: "{{ groups['nebula_lighthouse'] }}"
|
||||
when: >
|
||||
nebula_cert_force_renew | bool or
|
||||
(_cert_status[item].expired_or_missing | default(true))
|
||||
|
||||
- name: Neue Certs für alle anderen Nodes signieren (falls nötig)
|
||||
command: >
|
||||
{{ nebula_cert_dir }}/nebula-cert sign
|
||||
-name "{{ item }}"
|
||||
-ip "{{ hostvars[item].nebula_internal_ip_addr }}/{{ nebula_network_cidr }}"
|
||||
-duration "{{ nebula_client_cert_duration }}"
|
||||
args:
|
||||
chdir: "{{ nebula_cert_dir }}"
|
||||
creates: "{{ nebula_cert_dir }}/{{ item }}.crt"
|
||||
loop: >-
|
||||
{{
|
||||
_all_nebula_nodes
|
||||
| difference(groups['nebula_lighthouse'])
|
||||
}}
|
||||
when: >
|
||||
nebula_cert_force_renew | bool or
|
||||
(_cert_status[item].expired_or_missing | default(true))
|
||||
|
||||
- name: Neue Ablaufdaten zur Bestätigung anzeigen
|
||||
command: >
|
||||
{{ nebula_cert_dir }}/nebula-cert print -json
|
||||
-path {{ nebula_cert_dir }}/{{ item }}.crt
|
||||
register: _new_cert_info
|
||||
loop: "{{ _all_nebula_nodes }}"
|
||||
changed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Neue Cert-Laufzeiten anzeigen
|
||||
debug:
|
||||
msg: >-
|
||||
{{ item.item }}:
|
||||
gültig bis {{ (item.stdout | from_json).details.notAfter | int | strftime('%Y-%m-%d %H:%M') }}
|
||||
loop: "{{ _new_cert_info.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when: item.rc == 0
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# PHASE 2: Certs auf Lighthouse-Nodes selbst deployen
|
||||
# Nebula auf dem Lighthouse neu laden (SIGHUP - kein Verbindungsabbruch!)
|
||||
# -----------------------------------------------------------------------------
|
||||
- name: "Nebula Cert Renewal - Phase 2: Certs auf Lighthouse deployen"
|
||||
hosts: nebula_lighthouse
|
||||
gather_facts: false
|
||||
become: true
|
||||
serial: 1 # Lighthouses nacheinander - immer einer aktiv
|
||||
|
||||
vars:
|
||||
nebula_cert_dir: /opt/nebula
|
||||
nebula_cert_renew_threshold_days: 30
|
||||
nebula_cert_force_renew: false
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Cert-Dateien vom Primary Lighthouse einlesen
|
||||
slurp:
|
||||
src: "{{ nebula_cert_dir }}/{{ item }}"
|
||||
register: _lh_cert_files
|
||||
delegate_to: "{{ groups['nebula_lighthouse'][0] }}"
|
||||
loop:
|
||||
- "{{ inventory_hostname }}.crt"
|
||||
- "{{ inventory_hostname }}.key"
|
||||
- ca.crt
|
||||
|
||||
- name: Neue Cert/Key/CA auf Lighthouse-Node schreiben
|
||||
copy:
|
||||
dest: "{{ nebula_cert_dir }}/{{ item.item }}"
|
||||
content: "{{ item.content | b64decode }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0600'
|
||||
loop: "{{ _lh_cert_files.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
register: _lh_cert_written
|
||||
notify: nebula lighthouse hot-reload
|
||||
|
||||
# WICHTIG: SIGHUP statt Neustart - Nebula lädt Certs ohne Verbindungsabbruch
|
||||
handlers:
|
||||
- name: nebula lighthouse hot-reload
|
||||
command: pkill -HUP -f "nebula.*config.yml"
|
||||
# Alternativ falls systemd verwendet wird:
|
||||
# systemd: name=lighthouse state=reloaded
|
||||
# Nebula reagiert auf SIGHUP mit Reload der Certs seit v1.6+
|
||||
ignore_errors: true
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# PHASE 3: Certs auf alle regulären Nodes deployen
|
||||
# Remote-only Nodes: Verbindung über Nebula-IP bleibt durch SIGHUP-Strategie stabil
|
||||
# -----------------------------------------------------------------------------
|
||||
- name: "Nebula Cert Renewal - Phase 3: Certs auf Nodes deployen"
|
||||
hosts: "{{ groups.get('servers', []) + groups.get('nebula_nodes', []) | unique }}"
|
||||
gather_facts: false
|
||||
become: true
|
||||
serial: 5 # 5 Nodes gleichzeitig - anpassen nach Bedarf
|
||||
|
||||
vars:
|
||||
nebula_cert_dir: /opt/nebula
|
||||
nebula_cert_renew_threshold_days: 30
|
||||
nebula_cert_force_renew: false
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Prüfen ob dieser Node ein Cert-Renewal benötigt
|
||||
command: >
|
||||
{{ nebula_cert_dir }}/nebula-cert print -json
|
||||
-path {{ nebula_cert_dir }}/{{ inventory_hostname }}.crt
|
||||
register: _local_cert_check
|
||||
changed_when: false
|
||||
ignore_errors: true
|
||||
delegate_to: "{{ groups['nebula_lighthouse'][0] }}"
|
||||
# Wir prüfen auf dem Lighthouse, nicht lokal - der hat das neue Cert
|
||||
|
||||
- name: Cert-Erneuerung überspringen (kein Renewal nötig)
|
||||
meta: end_host
|
||||
when: >
|
||||
not (nebula_cert_force_renew | bool) and
|
||||
_local_cert_check.rc == 0 and
|
||||
((_local_cert_check.stdout | from_json).details.notAfter | int) >
|
||||
(lookup('pipe', 'date +%s') | int + nebula_cert_renew_threshold_days * 86400)
|
||||
|
||||
# Cert-Dateien vom Primary Lighthouse per slurp holen (in-memory, kein temp-file)
|
||||
- name: Cert-Dateien vom Primary Lighthouse einlesen
|
||||
slurp:
|
||||
src: "{{ nebula_cert_dir }}/{{ item }}"
|
||||
register: _node_cert_files
|
||||
delegate_to: "{{ groups['nebula_lighthouse'][0] }}"
|
||||
loop:
|
||||
- "{{ inventory_hostname }}.crt"
|
||||
- "{{ inventory_hostname }}.key"
|
||||
- ca.crt
|
||||
|
||||
- name: Neue Cert/Key/CA auf Node schreiben
|
||||
copy:
|
||||
dest: "{{ nebula_cert_dir }}/{{ item.item }}"
|
||||
content: "{{ item.content | b64decode }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0600'
|
||||
loop: "{{ _node_cert_files.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
register: _node_cert_written
|
||||
notify: nebula node hot-reload
|
||||
|
||||
# SIGHUP: Nebula lädt das neue Cert ohne die bestehende Tunnel-Verbindung zu trennen.
|
||||
# Das ist der entscheidende Trick für remote-only Nodes:
|
||||
# Die SSH-Verbindung über Nebula bleibt aktiv während das Cert neu geladen wird.
|
||||
handlers:
|
||||
- name: nebula node hot-reload
|
||||
command: pkill -HUP -f "nebula.*config.yml"
|
||||
# Alternativ:
|
||||
# systemd: name=nebula state=reloaded
|
||||
ignore_errors: true
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# PHASE 4: Abschlussbericht
|
||||
# -----------------------------------------------------------------------------
|
||||
- name: "Nebula Cert Renewal - Phase 4: Abschlussbericht"
|
||||
hosts: "{{ groups['nebula_lighthouse'][0] }}"
|
||||
gather_facts: false
|
||||
become: true
|
||||
|
||||
vars:
|
||||
nebula_cert_dir: /opt/nebula
|
||||
_all_nebula_nodes: >-
|
||||
{{
|
||||
(groups['nebula_lighthouse'] + groups.get('servers', []) + groups.get('nebula_nodes', []))
|
||||
| unique
|
||||
}}
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Finale Cert-Ablaufdaten ermitteln
|
||||
command: >
|
||||
{{ nebula_cert_dir }}/nebula-cert print -json
|
||||
-path {{ nebula_cert_dir }}/{{ item }}.crt
|
||||
register: _final_cert_info
|
||||
loop: "{{ _all_nebula_nodes }}"
|
||||
changed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Abschlussbericht
|
||||
debug:
|
||||
msg: >-
|
||||
{{ item.item | ljust(40) }}
|
||||
gültig bis: {{ (item.stdout | from_json).details.notAfter | int | strftime('%Y-%m-%d') }}
|
||||
loop: "{{ _final_cert_info.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when: item.rc == 0
|
||||
|
||||
- name: Nodes mit Fehler (Cert nicht lesbar)
|
||||
debug:
|
||||
msg: "WARNUNG: Cert für {{ item.item }} konnte nicht gelesen werden!"
|
||||
loop: "{{ _final_cert_info.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when: item.rc != 0
|
||||
Reference in New Issue
Block a user