added new secret logic for vms

This commit is contained in:
Ubuntu
2026-01-09 13:48:41 +00:00
parent 9182782387
commit 120deabb4d
10 changed files with 182 additions and 70 deletions

88
VAULT_RESET.md Normal file
View File

@@ -0,0 +1,88 @@
# Vault Reset Guide
Dieses Dokument beschreibt, wie du HashiCorp Vault komplett zurücksetzt, falls das **Root Token** verloren gegangen ist oder die Instanz irreparabel beschädigt scheint.
**⚠️ ACHTUNG: DATENVERLUST!**
Dieser Vorgang löscht unwiderruflich:
* Alle gespeicherten Secrets (Passwörter, Tokens).
* Alle konfigurierten User, Policies und Engines.
* Das Zertifikat (CA).
Du musst anschließend alle Secrets neu einspielen (via `setup_vault_secrets.sh`).
---
## Schritt 1: Vault Container & Daten löschen
Führe dies auf deinem Management-Host (`vm-management-200`) aus. Wir nutzen SSH, um die Daten auf dem Vault-Server (`vm-docker-apps-301`) zu löschen.
```bash
# 1. Container stoppen und entfernen
ssh -i ~/.ssh/id_ed25519_ansible_prod ansible@10.100.30.11 "docker stop vault-prod && docker rm vault-prod"
# 2. Persistente Daten löschen (File Storage & Zertifikate)
ssh -i ~/.ssh/id_ed25519_ansible_prod ansible@10.100.30.11 "sudo rm -rf /opt/vault/file/* /opt/vault/certs/*"
```
---
## Schritt 2: Neu-Deployment
Jetzt lassen wir Ansible den Container neu starten. Da die Verzeichnisse leer sind, wird das `entrypoint.sh` Skript:
1. Neue Zertifikate generieren.
2. Vault neu initialisieren.
3. Neue Unseal-Keys und ein neues Root-Token erstellen.
```bash
# Wechsel in das Ansible Verzeichnis
cd infrastructure/ansible
# Playbook ausführen (ignoriere 'Permission denied' Fehler bei Secrets, da Vault leer ist)
ansible-playbook -i inventory.ini deploy.yml
```
---
## Schritt 3: Neue Zugangsdaten abholen
Der neue Vault ist nun gestartet. Wir müssen uns die neuen "Schlüssel zum Königreich" holen.
```bash
# 1. Neues Root Token auslesen
# (ACHTUNG: Speichere dieses Token sofort in deinem Passwort-Manager!)
ssh -i ~/.ssh/id_ed25519_ansible_prod ansible@10.100.30.11 "sudo cat /opt/vault/file/init_keys.json"
# 2. Neues CA Zertifikat holen
# (Sonst vertraut dein PC dem neuen Vault nicht)
scp -i ~/.ssh/id_ed25519_ansible_prod ansible@10.100.30.11:/opt/vault/certs/ca.crt ../../vault-ca.crt
```
---
## Schritt 4: Secrets wiederherstellen
Nun nutzen wir unser Setup-Skript, um die Secrets aus deiner (hoffentlich noch vorhandenen oder neu erstellten) `bootstrap.tfvars` wieder einzuspielen.
1. **Vorbereitung:**
Falls du `terraform/bootstrap.tfvars` gelöscht hattest, musst du sie neu erstellen (siehe `README.md` -> Phase 1).
2. **Import:**
```bash
cd ../.. # Ins Root des Repos
./setup_vault_secrets.sh
```
* Gib das **neue** Root-Token ein (aus Schritt 3).
* Das Skript aktiviert die Engines und schreibt die Secrets neu.
---
## Schritt 5: Clients aktualisieren
Da wir eine neue CA haben, musst du das Zertifikat ggf. neu verteilen:
* **Browser:** Importiere das neue `vault-ca.crt` in Firefox/Chrome.
* **Linux Desktop:** Kopiere es nach `/usr/local/share/ca-certificates/` und führe `update-ca-certificates` aus.
* **Terraform:** Das Environment (`VAULT_CACERT`) zeigt meist eh auf die Datei im Repo (`./vault-ca.crt`), die wir in Schritt 3 überschrieben haben -> Sollte direkt gehen.
🎉 **Fertig!** Dein Vault ist frisch aufgesetzt.

View File

@@ -54,4 +54,4 @@
include_tasks: deploy_logic_push.yml include_tasks: deploy_logic_push.yml
loop: "{{ host_config.apps }}" loop: "{{ host_config.apps }}"
loop_control: loop_control:
loop_var: app_name loop_var: app_item

View File

@@ -4,19 +4,19 @@
# 1. Validierung # 1. Validierung
- name: "Prüfe App im Katalog" - name: "Prüfe App im Katalog"
stat: stat:
path: "{{ apps_catalog_path }}/{{ app_name }}" path: "{{ apps_catalog_path }}/{{ app_item.name }}"
register: catalog_entry register: catalog_entry
- name: "Skip if missing" - name: "Skip if missing"
debug: debug:
msg: "App {{ app_name }} nicht gefunden." msg: "App {{ app_item.name }} nicht gefunden."
when: not catalog_entry.stat.exists when: not catalog_entry.stat.exists
# 2. Setup # 2. Setup
- name: "Setze Pfade" - name: "Setze Pfade"
set_fact: set_fact:
source_dir: "{{ apps_catalog_path }}/{{ app_name }}" source_dir: "{{ apps_catalog_path }}/{{ app_item.name }}"
target_dir: "{{ base_deploy_path }}/{{ app_name }}" target_dir: "{{ base_deploy_path }}/{{ app_item.name }}"
when: catalog_entry.stat.exists when: catalog_entry.stat.exists
- name: "Erstelle Zielverzeichnis" - name: "Erstelle Zielverzeichnis"
@@ -30,9 +30,9 @@
# Im Pull-Mode brauchen wir ein Token. Wir lesen es aus /root/.vault-token oder ENV # Im Pull-Mode brauchen wir ein Token. Wir lesen es aus /root/.vault-token oder ENV
- name: "Lade Secrets (Lokal)" - name: "Lade Secrets (Lokal)"
set_fact: set_fact:
app_secrets: "{{ lookup('community.hashi_vault.vault_kv2_get', 'apps/' + app_name, engine_mount_point='secret', url=vault_addr, token_path='/root/.vault-token') | default({}) }}" app_secrets: "{{ lookup('community.hashi_vault.vault_kv2_get', 'apps/' + app_item.name, engine_mount_point='secret', url=vault_addr, token_path='/root/.vault-token') | default({}) }}"
ignore_errors: true ignore_errors: true
when: catalog_entry.stat.exists when: catalog_entry.stat.exists and app_item.has_secrets | default(false)
- name: "Erstelle .env" - name: "Erstelle .env"
copy: copy:
@@ -42,7 +42,7 @@
{{ key }}={{ value }} {{ key }}={{ value }}
{% endfor %} {% endfor %}
mode: '0600' mode: '0600'
when: catalog_entry.stat.exists and app_secrets is defined and app_secrets | length > 0 when: catalog_entry.stat.exists and app_item.has_secrets | default(false) and app_secrets is defined and app_secrets | length > 0
# 4. Sync Files (Local Copy) # 4. Sync Files (Local Copy)
- name: "Sync Files" - name: "Sync Files"
@@ -64,4 +64,3 @@
environment: environment:
PATH: "/usr/bin:/usr/local/bin:/snap/bin:{{ ansible_env.PATH }}" PATH: "/usr/bin:/usr/local/bin:/snap/bin:{{ ansible_env.PATH }}"
when: catalog_entry.stat.exists when: catalog_entry.stat.exists

View File

@@ -2,22 +2,22 @@
# Push-Logik: Wir kopieren von Localhost -> Remote Host # Push-Logik: Wir kopieren von Localhost -> Remote Host
# 1. Validierung (Lokal) # 1. Validierung (Lokal)
- name: "Prüfe ob App '{{ app_name }}' im Katalog existiert (Lokal)" - name: "Prüfe ob App '{{ app_item.name }}' im Katalog existiert (Lokal)"
stat: stat:
path: "{{ apps_catalog_path }}/{{ app_name }}" path: "{{ apps_catalog_path }}/{{ app_item.name }}"
delegate_to: localhost delegate_to: localhost
register: catalog_entry register: catalog_entry
- name: "Fehler: App fehlt im Katalog" - name: "Fehler: App fehlt im Katalog"
fail: fail:
msg: "App '{{ app_name }}' nicht gefunden in {{ apps_catalog_path }}" msg: "App '{{ app_item.name }}' nicht gefunden in {{ apps_catalog_path }}"
when: not catalog_entry.stat.exists when: not catalog_entry.stat.exists
# 2. Setup Pfade (Remote) # 2. Setup Pfade (Remote)
- name: "Setze Zielpfad" - name: "Setze Zielpfad"
set_fact: set_fact:
source_dir: "{{ apps_catalog_path }}/{{ app_name }}" source_dir: "{{ apps_catalog_path }}/{{ app_item.name }}"
target_dir: "{{ base_deploy_path }}/{{ app_name }}" target_dir: "{{ base_deploy_path }}/{{ app_item.name }}"
- name: "Erstelle Zielverzeichnis auf Remote" - name: "Erstelle Zielverzeichnis auf Remote"
file: file:
@@ -26,18 +26,19 @@
mode: '0755' mode: '0755'
# 3. Secrets aus Vault (Lokal lookup, Remote copy) # 3. Secrets aus Vault (Lokal lookup, Remote copy)
# Nur ausführen, wenn has_secrets: true
- name: "Lade Secrets aus Vault (Lokal lookup)" - name: "Lade Secrets aus Vault (Lokal lookup)"
set_fact: set_fact:
app_secrets: "{{ lookup('community.hashi_vault.vault_kv2_get', 'apps/' + app_name, engine_mount_point='secret', url=lookup('env', 'VAULT_ADDR') | default('https://10.100.30.11:8200'), token=lookup('env', 'VAULT_TOKEN')) | default({}) }}" app_secrets: "{{ lookup('community.hashi_vault.vault_kv2_get', 'apps/' + app_item.name, engine_mount_point='secret', url=lookup('env', 'VAULT_ADDR') | default('https://10.100.30.11:8200'), token=lookup('env', 'VAULT_TOKEN'), validate_certs=false) | default({}) }}"
delegate_to: localhost delegate_to: localhost
ignore_errors: true when: app_item.has_secrets | default(false)
ignore_errors: true # Trotzdem ignorieren, falls Vault down ist oder Secret fehlt
- name: "Setze app_secrets default wenn leer" - name: "Setze app_secrets default wenn leer oder skipped"
set_fact: set_fact:
app_secrets: {} app_secrets: {}
when: app_secrets is undefined when: app_secrets is undefined
- name: "Erstelle .env Datei auf Remote" - name: "Erstelle .env Datei auf Remote"
copy: copy:
dest: "{{ target_dir }}/.env" dest: "{{ target_dir }}/.env"
@@ -46,11 +47,10 @@
{{ key }}={{ value }} {{ key }}={{ value }}
{% endfor %} {% endfor %}
mode: '0600' mode: '0600'
when: app_secrets | length > 0 when: app_item.has_secrets | default(false) and app_secrets | length > 0
# 4. Sync Dateien (Lokal -> Remote) # 4. Sync Dateien (Lokal -> Remote)
# Hinweis: 'copy' Modul unterstützt kein 'exclude'. Für Excludes brauchen wir 'synchronize' (rsync) # Hinweis: 'copy' Modul unterstützt kein 'exclude'.
# oder wir kopieren alles und ignorieren .env Konflikte (da copy sowieso überschreibt)
- name: "Synchronisiere App-Dateien (Push)" - name: "Synchronisiere App-Dateien (Push)"
copy: copy:
src: "{{ source_dir }}/" src: "{{ source_dir }}/"
@@ -59,9 +59,8 @@
directory_mode: '0755' directory_mode: '0755'
# .env im Source wird überschrieben falls existent # .env im Source wird überschrieben falls existent
# 5. Docker Compose Deployment (Remote) # 5. Docker Compose Deployment (Remote)
- name: "Deploy {{ app_name }} mit Docker Compose" - name: "Deploy {{ app_item.name }} mit Docker Compose"
community.docker.docker_compose_v2: community.docker.docker_compose_v2:
project_src: "{{ target_dir }}" project_src: "{{ target_dir }}"
state: present state: present
@@ -71,5 +70,3 @@
environment: environment:
PATH: "/usr/bin:/usr/local/bin:/snap/bin:{{ ansible_env.PATH }}" PATH: "/usr/bin:/usr/local/bin:/snap/bin:{{ ansible_env.PATH }}"
register: compose_result register: compose_result

View File

@@ -59,10 +59,9 @@
# 4. Bereinigung (Pruning) # 4. Bereinigung (Pruning)
- name: "Ermittle zu löschende Apps" - name: "Ermittle zu löschende Apps"
set_fact: set_fact:
# Apps die installiert sind, aber nicht in wanted_apps stehen # wanted_apps ist jetzt eine Liste von Objekten. Wir brauchen nur die Namen.
# ACHTUNG: 'vault' sollte ggf. geschützt werden, wenn es manuell läuft? # installed_apps | difference(wanted_apps | map(attribute='name') | list)
# Da wir Vault aber auch via GitOps managen (in der Liste), ist das ok. apps_to_remove: "{{ installed_apps | difference(wanted_apps | map(attribute='name') | list) }}"
apps_to_remove: "{{ installed_apps | difference(wanted_apps) }}"
- name: "Pruning Loop" - name: "Pruning Loop"
include_tasks: prune_logic.yml include_tasks: prune_logic.yml
@@ -85,4 +84,4 @@
include_tasks: deploy_logic_pull.yml include_tasks: deploy_logic_pull.yml
loop: "{{ wanted_apps }}" loop: "{{ wanted_apps }}"
loop_control: loop_control:
loop_var: app_name loop_var: app_item

View File

@@ -13,4 +13,3 @@ services:
networks: networks:
proxy-sub: proxy-sub:
external: true

View File

@@ -1,7 +1,10 @@
apps: apps:
- vault - name: vault
- traefik-sub has_secrets: false # Vault verwaltet sich selbst (initial)
- whoami - name: traefik-sub
# Hier einfach weitere Apps aus dem Katalog hinzufügen: has_secrets: false # Aktuell keine Secrets nötig
# - nextcloud - name: whoami
# - monitoring has_secrets: false
# Beispiel für später:
# - name: nextcloud
# has_secrets: true

View File

@@ -1,3 +1,3 @@
apps: apps:
- traefik-edge - name: traefik-edge
- whoami has_secrets: true # Benötigt Cloudflare Token

View File

@@ -7,10 +7,37 @@ VAULT_CA_LOCAL="./vault-ca.crt"
# Check if bootstrap vars exist # Check if bootstrap vars exist
if [ ! -f "$BOOTSTRAP_VARS" ]; then if [ ! -f "$BOOTSTRAP_VARS" ]; then
echo "Fehler: $BOOTSTRAP_VARS nicht gefunden." echo "⚠️ Warnung: $BOOTSTRAP_VARS nicht gefunden."
echo "Bitte stelle sicher, dass du im Root des Repos bist und die Datei existiert." read -p "Soll eine neue leere Bootstrap-Datei erstellt werden? (y/n) " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
cat > "$BOOTSTRAP_VARS" << EOF
# terraform/bootstrap.tfvars
use_vault = false
# Proxmox Credentials
proxmox_api_url = "https://10.100.0.2:8006/api2/json"
proxmox_api_token_id = "root@pam!terraform"
proxmox_api_token_secret = "CHANGE_ME"
# OPNsense Credentials
opnsense_uri = "https://10.100.0.1:4443"
opnsense_api_key = "CHANGE_ME"
opnsense_api_secret = "CHANGE_ME"
# VM User Config
ci_user = "ansible"
ci_password = "InitialPassword123!"
ssh_public_key = "ssh-ed25519 CHANGE_ME"
EOF
echo "✅ Datei erstellt. Bitte editiere '$BOOTSTRAP_VARS' und trage deine Secrets ein."
echo "Führe das Skript danach erneut aus."
exit 0
else
echo "Abbruch."
exit 1 exit 1
fi fi
fi
# Check for Vault CA # Check for Vault CA
if [ ! -f "$VAULT_CA_LOCAL" ]; then if [ ! -f "$VAULT_CA_LOCAL" ]; then

View File

@@ -1,33 +1,33 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
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
EEUWC8tQVPJ1frfcPDKjuwI= QvPra+e4ulgvnKYRngLwp9w=
-----END CERTIFICATE----- -----END CERTIFICATE-----