diff --git a/VAULT_RESET.md b/VAULT_RESET.md new file mode 100644 index 0000000..8f0bdd2 --- /dev/null +++ b/VAULT_RESET.md @@ -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. + diff --git a/infrastructure/ansible/deploy.yml b/infrastructure/ansible/deploy.yml index 355483c..1823f9e 100644 --- a/infrastructure/ansible/deploy.yml +++ b/infrastructure/ansible/deploy.yml @@ -54,4 +54,4 @@ include_tasks: deploy_logic_push.yml loop: "{{ host_config.apps }}" loop_control: - loop_var: app_name + loop_var: app_item diff --git a/infrastructure/ansible/deploy_logic_pull.yml b/infrastructure/ansible/deploy_logic_pull.yml index 6efd527..c85e2bd 100644 --- a/infrastructure/ansible/deploy_logic_pull.yml +++ b/infrastructure/ansible/deploy_logic_pull.yml @@ -4,19 +4,19 @@ # 1. Validierung - name: "Prüfe App im Katalog" stat: - path: "{{ apps_catalog_path }}/{{ app_name }}" + path: "{{ apps_catalog_path }}/{{ app_item.name }}" register: catalog_entry - name: "Skip if missing" debug: - msg: "App {{ app_name }} nicht gefunden." + msg: "App {{ app_item.name }} nicht gefunden." when: not catalog_entry.stat.exists # 2. Setup - name: "Setze Pfade" set_fact: - source_dir: "{{ apps_catalog_path }}/{{ app_name }}" - target_dir: "{{ base_deploy_path }}/{{ app_name }}" + source_dir: "{{ apps_catalog_path }}/{{ app_item.name }}" + target_dir: "{{ base_deploy_path }}/{{ app_item.name }}" when: catalog_entry.stat.exists - name: "Erstelle Zielverzeichnis" @@ -30,9 +30,9 @@ # Im Pull-Mode brauchen wir ein Token. Wir lesen es aus /root/.vault-token oder ENV - name: "Lade Secrets (Lokal)" 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 - when: catalog_entry.stat.exists + when: catalog_entry.stat.exists and app_item.has_secrets | default(false) - name: "Erstelle .env" copy: @@ -42,7 +42,7 @@ {{ key }}={{ value }} {% endfor %} 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) - name: "Sync Files" @@ -64,4 +64,3 @@ environment: PATH: "/usr/bin:/usr/local/bin:/snap/bin:{{ ansible_env.PATH }}" when: catalog_entry.stat.exists - diff --git a/infrastructure/ansible/deploy_logic_push.yml b/infrastructure/ansible/deploy_logic_push.yml index 48648b6..8d07363 100644 --- a/infrastructure/ansible/deploy_logic_push.yml +++ b/infrastructure/ansible/deploy_logic_push.yml @@ -2,22 +2,22 @@ # Push-Logik: Wir kopieren von Localhost -> Remote Host # 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: - path: "{{ apps_catalog_path }}/{{ app_name }}" + path: "{{ apps_catalog_path }}/{{ app_item.name }}" delegate_to: localhost register: catalog_entry - name: "Fehler: App fehlt im Katalog" 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 # 2. Setup Pfade (Remote) - name: "Setze Zielpfad" set_fact: - source_dir: "{{ apps_catalog_path }}/{{ app_name }}" - target_dir: "{{ base_deploy_path }}/{{ app_name }}" + source_dir: "{{ apps_catalog_path }}/{{ app_item.name }}" + target_dir: "{{ base_deploy_path }}/{{ app_item.name }}" - name: "Erstelle Zielverzeichnis auf Remote" file: @@ -26,18 +26,19 @@ mode: '0755' # 3. Secrets aus Vault (Lokal lookup, Remote copy) +# Nur ausführen, wenn has_secrets: true - name: "Lade Secrets aus Vault (Lokal lookup)" 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 - 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: app_secrets: {} when: app_secrets is undefined - - name: "Erstelle .env Datei auf Remote" copy: dest: "{{ target_dir }}/.env" @@ -46,11 +47,10 @@ {{ key }}={{ value }} {% endfor %} mode: '0600' - when: app_secrets | length > 0 + when: app_item.has_secrets | default(false) and app_secrets | length > 0 # 4. Sync Dateien (Lokal -> Remote) -# Hinweis: 'copy' Modul unterstützt kein 'exclude'. Für Excludes brauchen wir 'synchronize' (rsync) -# oder wir kopieren alles und ignorieren .env Konflikte (da copy sowieso überschreibt) +# Hinweis: 'copy' Modul unterstützt kein 'exclude'. - name: "Synchronisiere App-Dateien (Push)" copy: src: "{{ source_dir }}/" @@ -59,9 +59,8 @@ directory_mode: '0755' # .env im Source wird überschrieben falls existent - # 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: project_src: "{{ target_dir }}" state: present @@ -71,5 +70,3 @@ environment: PATH: "/usr/bin:/usr/local/bin:/snap/bin:{{ ansible_env.PATH }}" register: compose_result - - diff --git a/infrastructure/ansible/pull_deploy.yml b/infrastructure/ansible/pull_deploy.yml index e8a9007..6e81943 100644 --- a/infrastructure/ansible/pull_deploy.yml +++ b/infrastructure/ansible/pull_deploy.yml @@ -59,10 +59,9 @@ # 4. Bereinigung (Pruning) - name: "Ermittle zu löschende Apps" set_fact: - # Apps die installiert sind, aber nicht in wanted_apps stehen - # ACHTUNG: 'vault' sollte ggf. geschützt werden, wenn es manuell läuft? - # Da wir Vault aber auch via GitOps managen (in der Liste), ist das ok. - apps_to_remove: "{{ installed_apps | difference(wanted_apps) }}" + # wanted_apps ist jetzt eine Liste von Objekten. Wir brauchen nur die Namen. + # installed_apps | difference(wanted_apps | map(attribute='name') | list) + apps_to_remove: "{{ installed_apps | difference(wanted_apps | map(attribute='name') | list) }}" - name: "Pruning Loop" include_tasks: prune_logic.yml @@ -85,4 +84,4 @@ include_tasks: deploy_logic_pull.yml loop: "{{ wanted_apps }}" loop_control: - loop_var: app_name + loop_var: app_item diff --git a/infrastructure/apps/whoami/docker-compose.yml b/infrastructure/apps/whoami/docker-compose.yml index c34a966..70133fa 100644 --- a/infrastructure/apps/whoami/docker-compose.yml +++ b/infrastructure/apps/whoami/docker-compose.yml @@ -12,5 +12,4 @@ services: - proxy-sub networks: - proxy-sub: - external: true \ No newline at end of file + proxy-sub: \ No newline at end of file diff --git a/infrastructure/deployments/vm-docker-apps-301.stabify.de.yml b/infrastructure/deployments/vm-docker-apps-301.stabify.de.yml index 85b0513..fe9a161 100644 --- a/infrastructure/deployments/vm-docker-apps-301.stabify.de.yml +++ b/infrastructure/deployments/vm-docker-apps-301.stabify.de.yml @@ -1,7 +1,10 @@ apps: - - vault - - traefik-sub - - whoami - # Hier einfach weitere Apps aus dem Katalog hinzufügen: - # - nextcloud - # - monitoring \ No newline at end of file + - name: vault + has_secrets: false # Vault verwaltet sich selbst (initial) + - name: traefik-sub + has_secrets: false # Aktuell keine Secrets nötig + - name: whoami + has_secrets: false + # Beispiel für später: + # - name: nextcloud + # has_secrets: true diff --git a/infrastructure/deployments/vm-docker-traefik-302.stabify.de.yml b/infrastructure/deployments/vm-docker-traefik-302.stabify.de.yml index 3b01567..911ca90 100644 --- a/infrastructure/deployments/vm-docker-traefik-302.stabify.de.yml +++ b/infrastructure/deployments/vm-docker-traefik-302.stabify.de.yml @@ -1,3 +1,3 @@ apps: - - traefik-edge - - whoami \ No newline at end of file + - name: traefik-edge + has_secrets: true # Benötigt Cloudflare Token diff --git a/setup_vault_secrets.sh b/setup_vault_secrets.sh index d6f7c54..2780482 100755 --- a/setup_vault_secrets.sh +++ b/setup_vault_secrets.sh @@ -7,9 +7,36 @@ VAULT_CA_LOCAL="./vault-ca.crt" # Check if bootstrap vars exist if [ ! -f "$BOOTSTRAP_VARS" ]; then - echo "Fehler: $BOOTSTRAP_VARS nicht gefunden." - echo "Bitte stelle sicher, dass du im Root des Repos bist und die Datei existiert." - exit 1 + echo "⚠️ Warnung: $BOOTSTRAP_VARS nicht gefunden." + 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 + fi fi # Check for Vault CA diff --git a/vault-ca.crt b/vault-ca.crt index 5b28b12..39c9e74 100644 --- a/vault-ca.crt +++ b/vault-ca.crt @@ -1,33 +1,33 @@ -----BEGIN CERTIFICATE----- -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 -EEUWC8tQVPJ1frfcPDKjuwI= +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCocczUOKJI0j4hwQsP +yJgljyJewd9oy71z5NrxNCxdbGlz9B7TwMZ1pEo+JRQBiMnx46zoIfPnHVkGYEW+ +9KDY0JmxqePOt+vfI9lgKw8AijI2ZMEfEVeRx5lFdKQ6zanWI0wTGyrXTV0H0zcn +ir8up9Xbe79TpicN2OGTWcTnUVx0c0wTXp9Jr273iTQ1LjRl/9jud1aQQXGbYecn +xqbdKTY8z/5dqQdw2Us5KXS2Q7GLEcJzFN65Ffg8Fo7MxbceCswf5DolhQZMU+5U +q2dpB7BgTdm857RliveQk4OwDXuNkuPwkqomJyDYkaW7DeJqMIeXh2BXOPhfTj33 +0o1dxXyqbU34yB1tzJ33LIS7ef/p+CfyFrn8HRmfAxUxZkpheSuh1xXiuXRVlRea +U89BSvSjSqi8FRy9FcdavKkaRbfmCYb2ra0vIundNDKTZVgEW2NJwp8CIUrFePYe +3iPVKEglMZFANobl9OfQqq+O0opi4K7KUrrT9/rqRBKpluyVOZHTpyExpdCipQdx +J5RuKEje5bjjTy2Ckom6v6G6BH9CVmvEVsT2VTgPHzmaZfO0ycEOJZZg81ODZCgw +tVIH5TpG69sgrD7CSbntastgLXuWI0IO6qb/vfBnvo8z9rfOZB1371ZWbJgAA8S4 +eH7CNOM/b7ywIUEZIUfnE2jdBwIDAQABo1MwUTAdBgNVHQ4EFgQUkgHnLvktGrP5 +vgdystgellkE80gwHwYDVR0jBBgwFoAUkgHnLvktGrP5vgdystgellkE80gwDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEABzH2VRDrUYTZOVZvlb6U +gerjh2X7e+LKhG/kDXy8GHz6XohUBzXENLq7Bdm58qRJ8Yq40xM9uZJdlWONkJO1 +L8QHbARf7o8REZkfV9YuRMgzh5SCAqNqUlSsVX60WvGPvjZSIFRRmY+3wRyOariG +6INmTGbaFnjNsKMS9jx09SgMuBMlg4GgGmi5SIyPJIOeCgExdK3EXEjIe52tta/O +i8wm9pkbRDOS4kk59ZESAfUe0wMlT5OIcIoq5bQVKScbLofW5Q18M2hzztDN9f7J +6XdjsoBK35S2GcgBeWRmJ/i/woxxjnG51bZ4vahNlBwmQJ5KnLG4qP/6nBDJHv38 +WlF1vdH0bY+aYx6yJrNjRmKuuo67wzYc6IsePHMkTYfht3LC+CaTHxFmZZ4G5uft +71fRiDRSZeRZR/wemfhiRBhdJzeE7ZRh8sEnw9q2j4hSi6x33xysR0WJdtT7p11F +FgfSiLoNhJnHHTKhICCStny/oQD61YtFI2Ha2IlwdHzM3Bg+DFr/CxO7liHXcvt0 +aM2R5E5hsmGjki7yvXQqTJG/j+VXq59ZPr1xQSPsSvZaGXkPMqMJ22Cw/N6s50cM +wpy4v9n0Es6v+CZN1Leo0WtmXDakwYUWI1ZI0yeoxOz9mFEWp/NDHVazOMynEWIw +QvPra+e4ulgvnKYRngLwp9w= -----END CERTIFICATE-----