pve-cluster: Phase 4 als Rolle – Admin-Gruppe/-Konten, Healer-Token, Benachrichtigungen, Firewall mit IPSET management (Tasks 4.1–4.4)
ci / test (push) Failing after 21s
ci / test (push) Failing after 21s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
5c790cc8d4
commit
81602373fc
@@ -0,0 +1,188 @@
|
||||
- name: Site-Datei laden
|
||||
ansible.builtin.set_fact:
|
||||
knaster_pve: "{{ lookup('ansible.builtin.file', pve_site_file) | from_yaml }}"
|
||||
delegate_to: localhost
|
||||
|
||||
# ---------- 4.1 Benutzer, Gruppen, Rollen, Token ----------
|
||||
- name: Rollen, Gruppen, Benutzer und ACLs lesen
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
python3 - <<'PY'
|
||||
import json, subprocess
|
||||
def get(p): return json.loads(subprocess.check_output(["pvesh", "get", p, "--output-format", "json"]))
|
||||
print(json.dumps({
|
||||
"roles": [r["roleid"] for r in get("/access/roles")],
|
||||
"groups": [g["groupid"] for g in get("/access/groups")],
|
||||
"users": [u["userid"] for u in get("/access/users")],
|
||||
"acl": [[a["path"], a["ugid"], a["roleid"]] for a in get("/access/acl")],
|
||||
}))
|
||||
PY
|
||||
args: {executable: /bin/bash}
|
||||
register: access_raw
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
|
||||
- name: Zugangsdaten als Fakt
|
||||
ansible.builtin.set_fact:
|
||||
pve_access: "{{ access_raw.stdout | from_json }}"
|
||||
|
||||
- name: Rolle für den Healer (nur Ein/Aus und Lesen der GPU-VMs)
|
||||
ansible.builtin.command: pveum role add {{ pve_healer_role }} --privs "{{ pve_healer_privs }}"
|
||||
when: pve_healer_role not in pve_access.roles
|
||||
changed_when: true
|
||||
|
||||
- name: Admin-Gruppe
|
||||
ansible.builtin.command: pveum group add {{ knaster_pve.access.admin_group }} --comment "Administratoren (knaster), root@pam nur Notfall"
|
||||
when: knaster_pve.access.admin_group not in pve_access.groups
|
||||
changed_when: true
|
||||
|
||||
- name: Admin-Gruppe darf alles (Administrator auf /)
|
||||
ansible.builtin.command: pveum acl modify / --groups {{ knaster_pve.access.admin_group }} --roles Administrator
|
||||
when: ['/', '@' ~ knaster_pve.access.admin_group, 'Administrator'] not in pve_access.acl
|
||||
changed_when: true
|
||||
|
||||
# Ohne Passwort: das setzt scripts/pve-admin-user.sh vom Laptop (zusammen mit TOTP und Recovery-Keys)
|
||||
- name: Persönliche Admin-Konten
|
||||
ansible.builtin.command: >
|
||||
pveum user add {{ item }}@pve --groups {{ knaster_pve.access.admin_group }}
|
||||
--comment "persönlicher Admin, Passwort/TOTP per pve-admin-user.sh"
|
||||
loop: "{{ knaster_pve.access.admin_users }}"
|
||||
when: (item ~ '@pve') not in pve_access.users
|
||||
changed_when: true
|
||||
|
||||
- name: Healer-Benutzer
|
||||
ansible.builtin.command: pveum user add {{ pve_healer_user }} --comment "knaster Healer (SPEC-healer), nur Token"
|
||||
when: pve_healer_user not in pve_access.users
|
||||
changed_when: true
|
||||
|
||||
- name: Healer darf GPU-VMs im Pool gpu schalten
|
||||
ansible.builtin.command: pveum acl modify /pool/gpu --users {{ pve_healer_user }} --roles {{ pve_healer_role }}
|
||||
when: ['/pool/gpu', pve_healer_user, pve_healer_role] not in pve_access.acl
|
||||
changed_when: true
|
||||
|
||||
- name: Healer-Token lesen
|
||||
ansible.builtin.command: pveum user token list {{ pve_healer_user }} --output-format json
|
||||
register: healer_tokens
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
|
||||
- name: Healer-Token anlegen (Geheimnis nur einmal sichtbar)
|
||||
ansible.builtin.command: >
|
||||
pveum user token add {{ pve_healer_user }} {{ pve_healer_token }} --privsep 0
|
||||
--comment "knaster Healer auf infra" --output-format json
|
||||
register: healer_token_new
|
||||
when: healer_tokens.stdout | from_json | selectattr('tokenid', 'equalto', pve_healer_token) | list | length == 0
|
||||
no_log: true
|
||||
changed_when: true
|
||||
|
||||
- name: Healer-Token auf ops ablegen (nur beim Anlegen)
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ pve_healer_token_file }}"
|
||||
content: |
|
||||
PVE_USER={{ pve_healer_user }}
|
||||
PVE_TOKEN_NAME={{ pve_healer_token }}
|
||||
PVE_TOKEN_VALUE={{ (healer_token_new.stdout | from_json).value }}
|
||||
mode: "0600"
|
||||
delegate_to: localhost
|
||||
when: healer_token_new is not skipped
|
||||
no_log: true
|
||||
|
||||
- name: Healer-Token-Datei auf ops prüfen
|
||||
ansible.builtin.stat:
|
||||
path: "{{ pve_healer_token_file }}"
|
||||
delegate_to: localhost
|
||||
register: healer_file
|
||||
|
||||
- name: Hinweis, wenn der Token existiert, aber die Datei fehlt
|
||||
ansible.builtin.debug:
|
||||
msg: "Token {{ pve_healer_user }}!{{ pve_healer_token }} existiert, {{ pve_healer_token_file }} fehlt – Token entfernen (pveum user token remove) und Lauf wiederholen."
|
||||
when: not healer_file.stat.exists
|
||||
|
||||
# ---------- 4.3 Benachrichtigungen ----------
|
||||
- name: Benachrichtigungsziele und Matcher lesen
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
python3 - <<'PY'
|
||||
import json, subprocess
|
||||
def get(p): return json.loads(subprocess.check_output(["pvesh", "get", p, "--output-format", "json"]))
|
||||
print(json.dumps({
|
||||
"sendmail": {e["name"]: e for e in get("/cluster/notifications/endpoints/sendmail")},
|
||||
"matchers": {m["name"]: m for m in get("/cluster/notifications/matchers")},
|
||||
}))
|
||||
PY
|
||||
args: {executable: /bin/bash}
|
||||
register: notify_raw
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
|
||||
- name: Benachrichtigungen als Fakt
|
||||
ansible.builtin.set_fact:
|
||||
pve_notify: "{{ notify_raw.stdout | from_json }}"
|
||||
|
||||
# Zustellung direkt an die MX von mygodel.de (Postfix auf dem Knoten, 13.10. getestet: 250 queued)
|
||||
- name: Mail-Ziel knaster-mail
|
||||
ansible.builtin.command: >
|
||||
pvesh create /cluster/notifications/endpoints/sendmail --name knaster-mail
|
||||
--mailto {{ knaster_pve.access.mail_to }} --comment "Proxmox-Ereignisse an das KI-Postfach"
|
||||
when: "'knaster-mail' not in pve_notify.sendmail"
|
||||
register: notify_target
|
||||
changed_when: true
|
||||
|
||||
- name: Matcher knaster-alles (jedes Ereignis an knaster-mail)
|
||||
ansible.builtin.command: >
|
||||
pvesh create /cluster/notifications/matchers --name knaster-alles --target knaster-mail
|
||||
--comment "Backup, Fencing, Updates, Replikation"
|
||||
when: "'knaster-alles' not in pve_notify.matchers"
|
||||
changed_when: true
|
||||
|
||||
- name: Standard-Matcher abschalten (sonst kommt jede Mail doppelt)
|
||||
ansible.builtin.command: pvesh set /cluster/notifications/matchers/default-matcher --disable 1
|
||||
when: not (pve_notify.matchers['default-matcher'].disable | default(false))
|
||||
changed_when: true
|
||||
|
||||
- name: Testnachricht über knaster-mail
|
||||
ansible.builtin.command: pvesh create /cluster/notifications/targets/knaster-mail/test
|
||||
when: notify_target is changed
|
||||
changed_when: false
|
||||
|
||||
# ---------- 4.4 Firewall ----------
|
||||
- name: Firewall-Regelwerk des Clusters schreiben
|
||||
ansible.builtin.template:
|
||||
src: cluster.fw.j2
|
||||
dest: /etc/pve/firewall/cluster.fw
|
||||
mode: "0640"
|
||||
unsafe_writes: true
|
||||
register: cluster_fw
|
||||
|
||||
- name: Regelwerk übersetzen (Syntaxprüfung, ändert nichts)
|
||||
ansible.builtin.command: pve-firewall compile
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
|
||||
- name: Totmannschalter stellen (Firewall nach {{ pve_firewall_deadman_seconds }} s wieder aus)
|
||||
ansible.builtin.shell: |
|
||||
systemctl stop knaster-fw-totmann.timer 2>/dev/null || true
|
||||
systemd-run --on-active={{ pve_firewall_deadman_seconds }} --unit knaster-fw-totmann --description "knaster: Firewall-Rollback" sed -i 's/^enable: 1$/enable: 0/' /etc/pve/firewall/cluster.fw
|
||||
when: cluster_fw is changed and knaster_pve.firewall.enable
|
||||
changed_when: true
|
||||
|
||||
- name: Warten, bis alle Knoten das Regelwerk geladen haben
|
||||
ansible.builtin.pause:
|
||||
seconds: 15
|
||||
when: cluster_fw is changed
|
||||
|
||||
- name: Prüfung von ops aus (SSH und 8006 zu jedem Knoten)
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
ssh -o BatchMode=yes -o ConnectTimeout=5 root@{{ hostvars[item].ansible_host }} 'pve-firewall status'
|
||||
curl -sk -m 5 -o /dev/null -w '%{http_code}\n' https://{{ hostvars[item].ansible_host }}:8006/ | grep -q 200
|
||||
args: {executable: /bin/bash}
|
||||
loop: "{{ groups['pve_all'] | sort }}"
|
||||
delegate_to: localhost
|
||||
changed_when: false
|
||||
when: cluster_fw is changed and knaster_pve.firewall.enable
|
||||
|
||||
- name: Totmannschalter aufheben (Prüfung bestanden)
|
||||
ansible.builtin.command: systemctl stop knaster-fw-totmann.timer
|
||||
when: cluster_fw is changed and knaster_pve.firewall.enable
|
||||
changed_when: true
|
||||
Reference in New Issue
Block a user