cert renewal refactored
This commit is contained in:
@@ -3,68 +3,39 @@
|
|||||||
# Nebula Certificate Renewal Playbook
|
# Nebula Certificate Renewal Playbook
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
#
|
#
|
||||||
# Dieses Playbook erneuert abgelaufene (oder bald ablaufende) Nebula-Zertifikate.
|
# Erneuert Nebula-Zertifikate die ablaufen oder bereits abgelaufen sind.
|
||||||
# Es funktioniert auch für Nodes, die NUR über die Nebula-IP erreichbar sind.
|
# Funktioniert auch für Nodes die NUR über die Nebula-IP erreichbar sind
|
||||||
|
# (remote-only), da Nebula per SIGHUP neu geladen wird ohne den Tunnel zu
|
||||||
|
# trennen.
|
||||||
#
|
#
|
||||||
# STRATEGIE für Remote-Only-Nodes:
|
# VERWENDUNG in Semaphore:
|
||||||
# Ansible erreicht remote-only Nodes über die Nebula-IP. Das bedeutet: Nebula
|
# Playbook: nebula_cert_renewal.yml
|
||||||
# muss während des Rollouts LAUFEN bleiben. Das neue Zertifikat wird auf dem
|
# Inventory: dein bestehendes Nebula-Inventory
|
||||||
# Lighthouse signiert, dann auf den Node kopiert und erst dann per Hot-Reload
|
#
|
||||||
# aktiviert (SIGHUP statt Neustart). So bleibt die Verbindung während des
|
# EXTRA VARS (optional):
|
||||||
# gesamten Vorgangs stabil.
|
# nebula_cert_renew_threshold_days=30 Erneuerung wenn < X Tage bis Ablauf
|
||||||
|
# nebula_cert_force_renew=true Alle Certs erzwungen erneuern
|
||||||
|
# nebula_client_cert_duration=43800h0m0s Laufzeit neuer Certs (5 Jahre)
|
||||||
|
#
|
||||||
|
# REMOTE-ONLY NODES:
|
||||||
|
# Nodes die nur per Nebula erreichbar sind im Inventory so eintragen:
|
||||||
|
# remoteserver ansible_host=10.43.0.5 nebula_internal_ip_addr=10.43.0.5
|
||||||
|
# Nebula wird per SIGHUP neu geladen – die SSH-Verbindung bleibt stabil.
|
||||||
#
|
#
|
||||||
# ABLAUF:
|
# ABLAUF:
|
||||||
# 1. Prüfen welche Certs ablaufen (innerhalb von nebula_cert_renew_threshold_days)
|
# Play 1 (Primary Lighthouse): Certs prüfen, Backup erstellen, neu signieren
|
||||||
# 2. Alte Certs auf dem Lighthouse sichern
|
# Play 2 (alle Nodes): Neue Certs verteilen, Nebula per SIGHUP neu laden
|
||||||
# 3. Neue Certs auf dem Lighthouse signieren
|
# Play 3 (Primary Lighthouse): Abschlussbericht
|
||||||
# 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)
|
# PLAY 1 – Auf dem Primary Lighthouse: prüfen, sichern, neu signieren
|
||||||
# -----------------------------------------------------------------------------
|
# =============================================================================
|
||||||
- name: "Nebula Cert Renewal - Phase 1: Prüfen & Signieren auf Primary Lighthouse"
|
- name: "Nebula Cert Renewal - Schritt 1: Certs auf Primary Lighthouse erneuern"
|
||||||
hosts: "{{ groups['nebula_lighthouse'][0] }}"
|
hosts: nebula_lighthouse[0]
|
||||||
gather_facts: false
|
gather_facts: no
|
||||||
become: true
|
become: yes
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
nebula_cert_renew_threshold_days: 30
|
nebula_cert_renew_threshold_days: 30
|
||||||
@@ -72,214 +43,234 @@
|
|||||||
nebula_client_cert_duration: "43800h0m0s"
|
nebula_client_cert_duration: "43800h0m0s"
|
||||||
nebula_network_cidr: 24
|
nebula_network_cidr: 24
|
||||||
nebula_cert_dir: /opt/nebula
|
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:
|
tasks:
|
||||||
|
|
||||||
- name: Sicherstellen dass python3-dateutil installiert ist (für Datumsvergleich)
|
# -------------------------------------------------------------------------
|
||||||
package:
|
# Ablaufdaten ermitteln
|
||||||
name: python3-dateutil
|
# -------------------------------------------------------------------------
|
||||||
state: present
|
- name: Aktuellen Unix-Timestamp ermitteln
|
||||||
ignore_errors: true
|
command: date +%s
|
||||||
|
register: _now_ts
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Zertifikats-Ablaufdaten für alle Nodes ermitteln
|
- name: Ablaufdatum CA-Zertifikat prüfen
|
||||||
|
command: "{{ nebula_cert_dir }}/nebula-cert print -json -path {{ nebula_cert_dir }}/ca.crt"
|
||||||
|
register: _ca_cert_info
|
||||||
|
changed_when: false
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: CA-Ablaufstatus auswerten
|
||||||
|
set_fact:
|
||||||
|
_ca_expires_soon: >-
|
||||||
|
{{
|
||||||
|
_ca_cert_info.rc != 0 or
|
||||||
|
((_ca_cert_info.stdout | from_json).details.notAfter | int) <
|
||||||
|
(_now_ts.stdout | int + nebula_cert_renew_threshold_days | int * 86400)
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: CA-Zertifikat Ablaufdatum anzeigen
|
||||||
|
debug:
|
||||||
|
msg: >-
|
||||||
|
CA-Zertifikat:
|
||||||
|
{% if _ca_cert_info.rc == 0 %}
|
||||||
|
gültig bis {{ (_ca_cert_info.stdout | from_json).details.notAfter | int | strftime('%Y-%m-%d') }},
|
||||||
|
Erneuerung nötig: {{ _ca_expires_soon }}
|
||||||
|
{% else %}
|
||||||
|
NICHT LESBAR / FEHLT
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
- name: ABBRUCH wenn CA-Zertifikat abläuft
|
||||||
|
fail:
|
||||||
|
msg: >-
|
||||||
|
Das CA-Zertifikat läuft ab oder fehlt!
|
||||||
|
Führe zuerst einen vollständigen Re-Deploy durch:
|
||||||
|
1. rm /opt/nebula/ca.crt /opt/nebula/ca.key /opt/nebula/*.crt /opt/nebula/*.key
|
||||||
|
2. ansible-playbook -i inventory nebula.yml
|
||||||
|
Siehe cert_howto.md für Details.
|
||||||
|
when: _ca_expires_soon | bool
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Alle Nodes prüfen (Lighthouse selbst + alle anderen Gruppen)
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
- name: Cert-Ablaufdaten für alle Nodes ermitteln
|
||||||
command: >
|
command: >
|
||||||
{{ nebula_cert_dir }}/nebula-cert print -json
|
{{ nebula_cert_dir }}/nebula-cert print -json
|
||||||
-path {{ nebula_cert_dir }}/{{ item }}.crt
|
-path {{ nebula_cert_dir }}/{{ item }}.crt
|
||||||
register: _cert_info_raw
|
register: _all_cert_info
|
||||||
loop: "{{ _all_nebula_nodes }}"
|
loop: "{{ groups['all'] | select('ne', inventory_hostname) | list + [inventory_hostname] }}"
|
||||||
changed_when: false
|
changed_when: false
|
||||||
ignore_errors: true
|
ignore_errors: yes
|
||||||
# Fehler ignorieren falls Cert noch nicht existiert
|
|
||||||
|
|
||||||
- name: Ablaufstatus pro Node berechnen
|
- name: Ablaufstatus pro Node berechnen
|
||||||
set_fact:
|
set_fact:
|
||||||
_cert_status: >-
|
_nodes_needing_renewal: >-
|
||||||
{{
|
{{
|
||||||
_cert_status | default({}) | combine({
|
_all_cert_info.results
|
||||||
item.item: {
|
| selectattr('rc', 'ne', 0)
|
||||||
'exists': item.rc == 0,
|
| map(attribute='item')
|
||||||
'expired_or_missing': (item.rc != 0) or (
|
| list
|
||||||
(item.stdout | from_json).details.notAfter
|
+
|
||||||
| int < (ansible_date_time.epoch | int + nebula_cert_renew_threshold_days * 86400)
|
_all_cert_info.results
|
||||||
),
|
| selectattr('rc', 'equalto', 0)
|
||||||
'not_after': (item.rc == 0) | ternary(
|
| selectattr('stdout', 'ne', '')
|
||||||
(item.stdout | from_json).details.notAfter | int | strftime('%Y-%m-%d'),
|
| select('callback', lambda x:
|
||||||
'N/A'
|
(x.stdout | from_json).details.notAfter | int <
|
||||||
)
|
_now_ts.stdout | int + nebula_cert_renew_threshold_days | int * 86400
|
||||||
}
|
)
|
||||||
})
|
| map(attribute='item')
|
||||||
|
| list
|
||||||
}}
|
}}
|
||||||
loop: "{{ _cert_info_raw.results }}"
|
# Vereinfachte Variante ohne Lambda – wir nutzen json_query:
|
||||||
|
|
||||||
|
- name: Nodes mit ablaufenden Certs bestimmen (vereinfacht)
|
||||||
|
set_fact:
|
||||||
|
_renewal_needed: >-
|
||||||
|
{{
|
||||||
|
nebula_cert_force_renew | bool
|
||||||
|
| ternary(
|
||||||
|
groups['all'],
|
||||||
|
_all_cert_info.results
|
||||||
|
| selectattr('rc', 'ne', 0)
|
||||||
|
| map(attribute='item') | list
|
||||||
|
| union(
|
||||||
|
_all_cert_info.results
|
||||||
|
| selectattr('rc', 'equalto', 0)
|
||||||
|
| selectattr('stdout', 'ne', '')
|
||||||
|
| rejectattr('stdout', 'equalto', '')
|
||||||
|
| map(attribute='item') | list
|
||||||
|
| select('search', '.')
|
||||||
|
| list
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
# Hinweis: Die echte Datumsfilterung kommt im nächsten Schritt per Loop
|
||||||
|
|
||||||
|
# Einfacher und robuster: Cert-Status per Loop mit when-Bedingung
|
||||||
|
- name: Liste der zu erneuernden Nodes erstellen
|
||||||
|
set_fact:
|
||||||
|
_renew_list: "{{ _renew_list | default([]) + [item.item] }}"
|
||||||
|
loop: "{{ _all_cert_info.results }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.item }}"
|
label: "{{ item.item }}"
|
||||||
vars:
|
when: >
|
||||||
ansible_date_time:
|
nebula_cert_force_renew | bool or
|
||||||
epoch: "{{ lookup('pipe', 'date +%s') }}"
|
item.rc != 0 or
|
||||||
|
item.stdout == '' or
|
||||||
|
(item.stdout | from_json).details.notAfter | int <
|
||||||
|
(_now_ts.stdout | int + nebula_cert_renew_threshold_days | int * 86400)
|
||||||
|
|
||||||
- name: Status-Übersicht anzeigen
|
- name: Übersicht anzeigen
|
||||||
debug:
|
debug:
|
||||||
msg: >-
|
msg: "Certs zur Erneuerung ({{ _renew_list | default([]) | length }}): {{ _renew_list | default([]) | join(', ') or 'keine' }}"
|
||||||
{{ 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: Playbook beenden wenn keine Certs erneuert werden müssen
|
||||||
|
meta: end_play
|
||||||
|
when: (_renew_list | default([])) | length == 0
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Backup der bestehenden Certs
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
- name: Backup-Verzeichnis anlegen
|
- name: Backup-Verzeichnis anlegen
|
||||||
file:
|
file:
|
||||||
path: "{{ nebula_cert_dir }}/cert_backup_{{ lookup('pipe', 'date +%Y%m%d_%H%M%S') }}"
|
path: "{{ nebula_cert_dir }}/cert_backup_{{ _now_ts.stdout }}"
|
||||||
state: directory
|
state: directory
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: '0700'
|
mode: '0700'
|
||||||
register: _backup_dir
|
register: _backup_dir
|
||||||
|
|
||||||
- name: Backup der bestehenden Certs erstellen
|
- name: Certs sichern
|
||||||
copy:
|
copy:
|
||||||
src: "{{ nebula_cert_dir }}/{{ item }}.crt"
|
src: "{{ nebula_cert_dir }}/{{ item }}.crt"
|
||||||
dest: "{{ _backup_dir.path }}/{{ item }}.crt"
|
dest: "{{ _backup_dir.path }}/{{ item }}.crt"
|
||||||
remote_src: true
|
remote_src: yes
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: '0600'
|
mode: '0600'
|
||||||
loop: "{{ _all_nebula_nodes }}"
|
loop: "{{ _renew_list | default([]) }}"
|
||||||
when: _cert_status[item].exists
|
ignore_errors: yes
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: Ablaufende/fehlende Certs auf Lighthouse löschen (damit nebula-cert neu signiert)
|
- name: Backup-Pfad anzeigen
|
||||||
|
debug:
|
||||||
|
msg: "Backup erstellt in: {{ _backup_dir.path }}"
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Alte Certs löschen damit nebula-cert neu signiert (creates: Guard)
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
- name: Alte Cert-Dateien auf Lighthouse löschen
|
||||||
file:
|
file:
|
||||||
path: "{{ nebula_cert_dir }}/{{ item }}"
|
path: "{{ nebula_cert_dir }}/{{ item[0] }}.{{ item[1] }}"
|
||||||
state: absent
|
state: absent
|
||||||
loop: >-
|
loop: "{{ _renew_list | default([]) | product(['crt', 'key']) | list }}"
|
||||||
{{
|
loop_control:
|
||||||
_cert_status | dict2items
|
label: "{{ item[0] }}.{{ item[1] }}"
|
||||||
| 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)
|
# -------------------------------------------------------------------------
|
||||||
|
# Neue Certs signieren
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
- name: Neue Certs für alle betroffenen Nodes signieren
|
||||||
command: >
|
command: >
|
||||||
{{ nebula_cert_dir }}/nebula-cert sign
|
{{ nebula_cert_dir }}/nebula-cert sign
|
||||||
-name "{{ item }}"
|
-name "{{ item }}"
|
||||||
-ip "{{ hostvars[item].nebula_internal_ip_addr }}/{{ nebula_network_cidr }}"
|
-ip "{{ hostvars[item].nebula_internal_ip_addr }}/{{ hostvars[item].nebula_network_cidr | default(nebula_network_cidr) }}"
|
||||||
-duration "{{ nebula_client_cert_duration }}"
|
-duration "{{ nebula_client_cert_duration }}"
|
||||||
args:
|
args:
|
||||||
chdir: "{{ nebula_cert_dir }}"
|
chdir: "{{ nebula_cert_dir }}"
|
||||||
creates: "{{ nebula_cert_dir }}/{{ item }}.crt"
|
creates: "{{ nebula_cert_dir }}/{{ item }}.crt"
|
||||||
loop: "{{ groups['nebula_lighthouse'] }}"
|
loop: "{{ _renew_list | default([]) }}"
|
||||||
when: >
|
register: _sign_results
|
||||||
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)
|
- name: Signing-Ergebnis anzeigen
|
||||||
command: >
|
debug:
|
||||||
{{ nebula_cert_dir }}/nebula-cert sign
|
msg: "{{ item.item }}: {{ 'neu signiert' if item.changed else 'übersprungen (Cert existiert bereits)' }}"
|
||||||
-name "{{ item }}"
|
loop: "{{ _sign_results.results }}"
|
||||||
-ip "{{ hostvars[item].nebula_internal_ip_addr }}/{{ nebula_network_cidr }}"
|
loop_control:
|
||||||
-duration "{{ nebula_client_cert_duration }}"
|
label: "{{ item.item }}"
|
||||||
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
|
# -------------------------------------------------------------------------
|
||||||
|
# Neue Ablaufdaten zur Kontrolle anzeigen
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
- name: Neue Ablaufdaten prüfen
|
||||||
command: >
|
command: >
|
||||||
{{ nebula_cert_dir }}/nebula-cert print -json
|
{{ nebula_cert_dir }}/nebula-cert print -json
|
||||||
-path {{ nebula_cert_dir }}/{{ item }}.crt
|
-path {{ nebula_cert_dir }}/{{ item }}.crt
|
||||||
register: _new_cert_info
|
register: _new_cert_check
|
||||||
loop: "{{ _all_nebula_nodes }}"
|
loop: "{{ _renew_list | default([]) }}"
|
||||||
changed_when: false
|
changed_when: false
|
||||||
ignore_errors: true
|
ignore_errors: yes
|
||||||
|
|
||||||
- name: Neue Cert-Laufzeiten anzeigen
|
- name: Neue Ablaufdaten anzeigen
|
||||||
debug:
|
debug:
|
||||||
msg: >-
|
msg: >-
|
||||||
{{ item.item }}:
|
{{ item.item }}:
|
||||||
gültig bis {{ (item.stdout | from_json).details.notAfter | int | strftime('%Y-%m-%d %H:%M') }}
|
neues Cert gültig bis
|
||||||
loop: "{{ _new_cert_info.results }}"
|
{{ (item.stdout | from_json).details.notAfter | int | strftime('%Y-%m-%d %H:%M UTC') }}
|
||||||
|
loop: "{{ _new_cert_check.results }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.item }}"
|
label: "{{ item.item }}"
|
||||||
when: item.rc == 0
|
when: item.rc == 0
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# =============================================================================
|
||||||
# PHASE 2: Certs auf Lighthouse-Nodes selbst deployen
|
# PLAY 2 – Auf allen Nodes: neue Certs deployen, Nebula per SIGHUP neu laden
|
||||||
# Nebula auf dem Lighthouse neu laden (SIGHUP - kein Verbindungsabbruch!)
|
#
|
||||||
# -----------------------------------------------------------------------------
|
# Strategie für remote-only Nodes (nur per Nebula-IP erreichbar):
|
||||||
- name: "Nebula Cert Renewal - Phase 2: Certs auf Lighthouse deployen"
|
# - Certs werden per slurp vom Lighthouse geholt (kein SCP, kein temp-file)
|
||||||
hosts: nebula_lighthouse
|
# - Nebula wird per SIGHUP neu geladen (kein systemd stop/start)
|
||||||
gather_facts: false
|
# - SIGHUP lässt bestehende Tunnel aktiv → SSH-Verbindung bleibt stabil
|
||||||
become: true
|
# - serial: 1 stellt sicher dass Lighthouses nie gleichzeitig neu laden
|
||||||
serial: 1 # Lighthouses nacheinander - immer einer aktiv
|
# =============================================================================
|
||||||
|
- name: "Nebula Cert Renewal - Schritt 2: Neue Certs auf alle Nodes deployen"
|
||||||
vars:
|
hosts: all
|
||||||
nebula_cert_dir: /opt/nebula
|
gather_facts: no
|
||||||
nebula_cert_renew_threshold_days: 30
|
become: yes
|
||||||
nebula_cert_force_renew: false
|
# Lighthouses zuerst und einzeln, dann alle anderen gleichzeitig
|
||||||
|
# Ansible sortiert: nebula_lighthouse kommt vor servers/anderen Gruppen
|
||||||
tasks:
|
serial:
|
||||||
|
- 1 # Erste Runde: 1 Node (Primary Lighthouse)
|
||||||
- name: Cert-Dateien vom Primary Lighthouse einlesen
|
- 1 # Zweite Runde: 1 Node (Secondary Lighthouse falls vorhanden)
|
||||||
slurp:
|
- "100%" # Rest: alle gleichzeitig
|
||||||
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:
|
vars:
|
||||||
nebula_cert_dir: /opt/nebula
|
nebula_cert_dir: /opt/nebula
|
||||||
@@ -288,29 +279,29 @@
|
|||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
|
||||||
|
# Prüfen ob dieser Node auf der Renewal-Liste des Lighthouse steht
|
||||||
|
# Wir lesen die Variable vom Lighthouse via hostvars
|
||||||
- name: Prüfen ob dieser Node ein Cert-Renewal benötigt
|
- name: Prüfen ob dieser Node ein Cert-Renewal benötigt
|
||||||
command: >
|
set_fact:
|
||||||
{{ nebula_cert_dir }}/nebula-cert print -json
|
_this_node_needs_renewal: >-
|
||||||
-path {{ nebula_cert_dir }}/{{ inventory_hostname }}.crt
|
{{
|
||||||
register: _local_cert_check
|
inventory_hostname in
|
||||||
changed_when: false
|
(hostvars[groups['nebula_lighthouse'][0]]._renew_list | default([]))
|
||||||
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)
|
- name: Node überspringen wenn kein Renewal nötig
|
||||||
meta: end_host
|
debug:
|
||||||
when: >
|
msg: "{{ inventory_hostname }}: kein Cert-Renewal nötig, überspringe."
|
||||||
not (nebula_cert_force_renew | bool) and
|
when: not _this_node_needs_renewal | bool
|
||||||
_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)
|
- meta: end_host
|
||||||
- name: Cert-Dateien vom Primary Lighthouse einlesen
|
when: not _this_node_needs_renewal | bool
|
||||||
|
|
||||||
|
# Cert-Dateien vom Primary Lighthouse per slurp holen (in-memory, sicher)
|
||||||
|
- name: Neue Cert-Dateien vom Primary Lighthouse einlesen
|
||||||
slurp:
|
slurp:
|
||||||
src: "{{ nebula_cert_dir }}/{{ item }}"
|
src: "{{ nebula_cert_dir }}/{{ item }}"
|
||||||
register: _node_cert_files
|
register: _cert_files
|
||||||
delegate_to: "{{ groups['nebula_lighthouse'][0] }}"
|
delegate_to: "{{ groups['nebula_lighthouse'][0] }}"
|
||||||
loop:
|
loop:
|
||||||
- "{{ inventory_hostname }}.crt"
|
- "{{ inventory_hostname }}.crt"
|
||||||
@@ -324,64 +315,76 @@
|
|||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: '0600'
|
mode: '0600'
|
||||||
loop: "{{ _node_cert_files.results }}"
|
loop: "{{ _cert_files.results }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.item }}"
|
label: "{{ item.item }}"
|
||||||
register: _node_cert_written
|
notify: nebula hot reload
|
||||||
notify: nebula node hot-reload
|
|
||||||
|
- name: Bestätigung
|
||||||
|
debug:
|
||||||
|
msg: "{{ inventory_hostname }}: neue Certs geschrieben, SIGHUP wird gesendet."
|
||||||
|
|
||||||
# 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:
|
handlers:
|
||||||
- name: nebula node hot-reload
|
# SIGHUP statt Neustart: Nebula lädt das neue Cert ohne Tunnel zu trennen.
|
||||||
command: pkill -HUP -f "nebula.*config.yml"
|
# Das ist der Schlüssel für remote-only Nodes: die SSH-Session über Nebula
|
||||||
# Alternativ:
|
# bleibt aktiv während das Cert gewechselt wird.
|
||||||
# systemd: name=nebula state=reloaded
|
- name: nebula hot reload
|
||||||
ignore_errors: true
|
shell: |
|
||||||
|
# Lighthouse oder normaler Node?
|
||||||
|
if systemctl is-active --quiet lighthouse 2>/dev/null; then
|
||||||
|
pkill -HUP -f "nebula.*config.yml" || true
|
||||||
|
echo "SIGHUP an lighthouse gesendet"
|
||||||
|
elif systemctl is-active --quiet nebula 2>/dev/null; then
|
||||||
|
pkill -HUP -f "nebula.*config.yml" || true
|
||||||
|
echo "SIGHUP an nebula gesendet"
|
||||||
|
else
|
||||||
|
echo "Kein aktiver Nebula-Prozess gefunden"
|
||||||
|
fi
|
||||||
|
register: _reload_result
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: SIGHUP-Ergebnis anzeigen
|
||||||
|
debug:
|
||||||
|
msg: "{{ _reload_result.stdout }}"
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# =============================================================================
|
||||||
# PHASE 4: Abschlussbericht
|
# PLAY 3 – Abschlussbericht auf dem Primary Lighthouse
|
||||||
# -----------------------------------------------------------------------------
|
# =============================================================================
|
||||||
- name: "Nebula Cert Renewal - Phase 4: Abschlussbericht"
|
- name: "Nebula Cert Renewal - Schritt 3: Abschlussbericht"
|
||||||
hosts: "{{ groups['nebula_lighthouse'][0] }}"
|
hosts: nebula_lighthouse[0]
|
||||||
gather_facts: false
|
gather_facts: no
|
||||||
become: true
|
become: yes
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
nebula_cert_dir: /opt/nebula
|
nebula_cert_dir: /opt/nebula
|
||||||
_all_nebula_nodes: >-
|
|
||||||
{{
|
|
||||||
(groups['nebula_lighthouse'] + groups.get('servers', []) + groups.get('nebula_nodes', []))
|
|
||||||
| unique
|
|
||||||
}}
|
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
|
||||||
- name: Finale Cert-Ablaufdaten ermitteln
|
- name: Finale Cert-Ablaufdaten für alle Nodes prüfen
|
||||||
command: >
|
command: >
|
||||||
{{ nebula_cert_dir }}/nebula-cert print -json
|
{{ nebula_cert_dir }}/nebula-cert print -json
|
||||||
-path {{ nebula_cert_dir }}/{{ item }}.crt
|
-path {{ nebula_cert_dir }}/{{ item }}.crt
|
||||||
register: _final_cert_info
|
register: _final_certs
|
||||||
loop: "{{ _all_nebula_nodes }}"
|
loop: "{{ groups['all'] }}"
|
||||||
changed_when: false
|
changed_when: false
|
||||||
ignore_errors: true
|
ignore_errors: yes
|
||||||
|
|
||||||
- name: Abschlussbericht
|
- name: Abschlussbericht
|
||||||
debug:
|
debug:
|
||||||
msg: >-
|
msg: >-
|
||||||
{{ item.item | ljust(40) }}
|
{{ '%-45s' | format(item.item) }}
|
||||||
|
{% if item.rc == 0 %}
|
||||||
gültig bis: {{ (item.stdout | from_json).details.notAfter | int | strftime('%Y-%m-%d') }}
|
gültig bis: {{ (item.stdout | from_json).details.notAfter | int | strftime('%Y-%m-%d') }}
|
||||||
loop: "{{ _final_cert_info.results }}"
|
{% else %}
|
||||||
|
*** CERT NICHT LESBAR ***
|
||||||
|
{% endif %}
|
||||||
|
loop: "{{ _final_certs.results }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.item }}"
|
label: "{{ item.item }}"
|
||||||
when: item.rc == 0
|
|
||||||
|
|
||||||
- name: Nodes mit Fehler (Cert nicht lesbar)
|
- name: Hinweis auf Backup
|
||||||
debug:
|
debug:
|
||||||
msg: "WARNUNG: Cert für {{ item.item }} konnte nicht gelesen werden!"
|
msg: >-
|
||||||
loop: "{{ _final_cert_info.results }}"
|
Alte Zertifikate gesichert in:
|
||||||
loop_control:
|
{{ nebula_cert_dir }}/cert_backup_{{ hostvars[inventory_hostname]._now_ts.stdout | default('siehe Lighthouse') }}
|
||||||
label: "{{ item.item }}"
|
|
||||||
when: item.rc != 0
|
|
||||||
|
|||||||
Reference in New Issue
Block a user