Skip to content

Utworzenie klastra Vault

Przed Tobą opowieść o tym, jak wdrożyliśmy 3-węzłowy klaster HashiCorp Vault z High Availability przy użyciu nowoczesnych praktyk Infrastructure as Code. Artykuł zawiera konkretne przykłady z naszych repozytoriów, architekturę systemu oraz praktyczne learningi z procesu.

  • Secrets Management: Przechowywanie i rotacja haseł, API keys, kluczy SSH
  • Encryption as a Service: Szyfrowanie i deszyfrowanie danych na żądanie
  • Certificate Management: Automatyczne generowanie i zarządzanie certyfikatami TLS
  • Access Control: Granularne uprawnienia dzięki ACL politykom
  • Audit Logging: Pełna historia dostępu do sekretów

W środowisku produkcyjnym nie możesz sobie pozwolić na to, by Vault był Single Point of Failure. Dlatego właśnie tworzymy klaster z High Availability:

  • Redundancja: Jeśli jeden węzeł padnie, pozostałe 2 pracują dalej
  • Load Balancing: Rozkład ruchu między węzły
  • Failover: Automatyczne przełączanie w przypadku awarii
  • Distributed Storage: Dane replikowane na wszystkich węzłach

⚙️ ansible

Automatyzacja provisioning’u klastra Vault

Playbooki Ansible do instalacji i konfiguracji:

  • Vault (3-węzłowy klaster HA)
  • Consul (storage backend)
  • Keepalived + HAProxy (HA proxy layer)

🔗 Odwiedź

🏗️ iac-vault

Infrastructure as Code dla Vault (OpenTofu)

Zarządzanie:

  • Secret Engines (KV v2)
  • Auth Methods (userpass, AppRole, JWT)
  • ACL Policies (26 polityk)
  • PKI Hierarchy (Root + Intermediate CA)

🔗 Odwiedź


  1. Utworzenie 3 maszyn wirtualnych
  2. Ustawienie im adresów DNS i IP (w tym VIP)
  3. Utworzenie repozytoriów
  4. Utworzenie palybooka Ansible instalującego cluster VAULT
  5. Utworzenie IaC za pomocą OpenTofu do zarządzania Vaultem

Zanim przejdziemy do kodu, pokażmy jak wygląda architektura:

Vault HA

KomponentaRolaPortIlość
KeepalivedVirtual IP (VRRP)1123 instancje
HAProxyTLS termination + SNI4433 instancje
VaultSecrets engine82003 węzły (1 leader + 2 standby)
ConsulStorage backend (HA)85013 węzły

Utworzenie palybooka Ansible instalującego cluster VAULT

Section titled “Utworzenie palybooka Ansible instalującego cluster VAULT”

Pierwsza faza naszego projektu infrastrukturalnego polegała na przygotowaniu procesu automatyzacji za pomocą Ansible. Z uwagi na to, że to jest pierwszy projekt Ansible w organizacji, musieliśmy od podstaw opracować zestaw roli Ansible, które systematycznie hartują wirtualne maszyny zgodnie z najlepszymi praktykami bezpieczeństwa.

Stworzyliśmy modularną kolekcję ról, z których każda odpowiada za konkretny aspekt konfiguracji systemu. Podejście to zapewnia czytelność, wielokrotnego użytku oraz łatwość utrzymania kodu infrastrukturalnej.

  • set-timezone – ustawienie strefy czasowej zgodnie ze standardami naszego środowiska
  • set-hostname – konfiguracja nazwy hosta maszyny
  • users-management – zarządzanie kontami użytkowników, tworzenie dedykowanych kont serwisowych
  • sudo – ustalenie polityki uprawnień i reguł eskalacji privilegiów
  • ssh-hardening – zastosowanie restrykcyjnych polityk SSH, w tym wyłączenie logowania z użytkownikiem root, zmiana portów domyślnych oraz ograniczenie metod autentykacji
  • install-packages – instalacja niezbędnych pakietów oraz narzędzi diagnostycznych, uwzględniająca tylko te zależności, które faktycznie wykorzystujemy
  • install-keepalived-vip – konfiguracja wirtualnego IP (VIP) z użyciem keepalived, umożliwiająca failover w klastrach
  • haproxy – wdrażanie load balancera HAProxy do dystrybucji ruchu między węzłami
  • certificates – zarządzanie certyfikatami SSL/TLS, krityczne dla bezpiecznej komunikacji

Każda rola została zwersjpnowana i hostowana w dedykowanym repozytorium GitLab, co umożliwia niezależne iteracje, testowanie oraz utrzymanie. Ten systematyczny podejścia pozwolił nam wybudować powtarzalny i wiarygodny proces przygotowania maszyn od zera.

Część 2: Zmienne Konfiguracyjne dla clustra vault

Section titled “Część 2: Zmienne Konfiguracyjne dla clustra vault”

Konfiguracja nie jest hardkodowana w playbooku — wszystko przechowujemy w YAML:

# inventory/group_vars/vault/vault.yml
inv_group_vault_address: "https://vault.rachuna-net.pl"
inv_vault_tls_disabled: false
inv_vault_disable_mlock: true  # Wymagane w homelab/VM
inv_consul_config:
  address: "localhost:8501"
  token: "{{ token_from_vault }}"
  scheme: https
# inventory/group_vars/vault/consul.yml
inv_group_consul_config:
  datacenter: "vault"
  bootstrap_expect: 3  # WAŻNE: oczekujemy 3 węzłów
  ui: true
  acl:
    enabled: true
# inventory/group_vars/vault/keepalived.yml
inv_group_keepalived_config:
  vrrp:
    virtual_router_id: 53
    interface: eth0
    virtual_ip: 10.3.1.253/24
    priority:
      vault-1022: 200  # Primary
      vault-1023: 120  # Secondary
      vault-1024: 110  # Backup

Część 3: Sekwencja Ról w playbook install.yml

Section titled “Część 3: Sekwencja Ról w playbook install.yml”

Playbook install.yml aplikuje role w konkretnej kolejności:

# playbooks/install.yml
---
- name: Provision Vault HA Cluster
  hosts: vault
  become: true
  roles:
    - set-timezone              # 1. Lokalizacja
    - users-management          # 2. Użytkownicy + SSH keys
    - set-hostname              # 3. FQDN (np. vault-1022.rachuna-net.pl)
    - install-packages          # 4. Systemy pakiety (curl, openssl, etc)
    - install-keepalived-vip    # 5. VRRP Virtual IP
    - certificates              # 6. TLS z Vault PKI (bootstrap)
    - haproxy                   # 7. Load balancer
    - install-consul            # 8. Consul storage backend
    - install-vault             # 9. Vault daemon
    - vault-auto-unseal         # 10. Automatyczne unsealing

Dlaczego ta konkretna kolejność?

  • Musimy najpierw ustawić hostname, bo Consul i Vault ich używają
  • TLS certificates musimy pobrać zanim skonfigurujemy HAProxy (który ich potrzebuje)
  • Consul musi być uruchomiony zanim Vault (bo Vault go używa jako storage)
  • vault-auto-unseal (opcjonalnie) jest ostatnią rolą (timer, który będzie automatycznie Vault’a odblokowywać)
# Przygotowanie
source .envrc  # Załaduj unseal keys
ansible-galaxy install -r requirements.yml  # Pobierz external role

# Dry run (bez zmian)
ansible-playbook -i inventory/hosts.yml playbooks/install.yml --check

# Pełna provisioning
ansible-playbook -i inventory/hosts.yml playbooks/install.yml -v

# Obserwuj logi w czasie rzeczywistym
ansible vault -m shell -a "journalctl -f -u vault"

Co się dzieje?

  1. Ansible łączy się do każdego węzła (vault-1022, 1023, 1024) via SSH
  2. Na każdym węźle uruchamia role w kolejności
  3. Role instalują pakiety, konfigurują usługi, startują demony
  4. Po ~5-10 minutach masz gotowy 3-węzłowy klaster

Utworzenie IaC za pomocą OpenTofu do zarządzania Vaultem

Section titled “Utworzenie IaC za pomocą OpenTofu do zarządzania Vaultem”

Część 5: Konfiguracja Vault za pomocą IaC (OpenTofu)

Section titled “Część 5: Konfiguracja Vault za pomocą IaC (OpenTofu)”

Po provisioning’u infrastruktury mamy gotowy klaster Vault, ale jest on pusty — brak polityk ACL, brak auth methods, brak sekretów.

iac-vault/
├── main.tf.json             # Root module (orkestruje wszystko)
├── policies/                # ACL policies (26 plików)
├── auth/                    # Auth methods (userpass, approle, jwt)
├── users/                   # User accounts
├── kv/                      # Secrets KV v2
├── pki/                     # PKI CA + intermediate certificates
├── approles/                # AppRole definitions
└── tools/
    ├── create-user-account.sh  # Automated user creation
    └── tofu-plan.sh

W naszym projekcie używamy .tf.json zamiast .tf (HCL). Dlaczego?

  • HCL jest czytelniejszy dla ludzi
  • JSON jest łatwiejszy do generowania automatycznie
  • Mniej konfiguracji “magii”, bardziej explicitnie
// main.tf.json
{
  "module": {
    "kv": {
      "source": "./kv/"
    },
    "users": {
      "source": "./users/"
    },
    "auth": {
      "source": "./auth/"
    },
    "pki": {
      "source": "./pki/"
    }
  }
}

Serio — to wszystko! Każdy moduł zawiera swoją logikę.

Vault jest oparty na principle of least privilege — każdy użytkownik/aplikacja ma dostęp tylko do tego, co potrzebuje.

# policies/admin.tf.json
{
  "resource": {
    "vault_policy": {
      "admin": {
        "name": "admin",
        "policy": "path \"*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\", \"sudo\"]\n}"
      }
    }
  }
}
# policies/kv_user_read.tf.json
{
  "resource": {
    "vault_policy": {
      "kv_user_read": {
        "name": "kv_user_read",
        "policy": "path \"users/data/myuser/*\" {\n  capabilities = [\"read\", \"list\"]\n}"
      }
    }
  }
}

Vault wspiera wiele sposobów na uwierzytelnianie:

Userpass (najprostszy):

// auth/userpass.tf.json
{
  "resource": {
    "vault_auth_backend": {
      "userpass": {
        "type": "userpass"
      }
    }
  }
}

AppRole (dla aplikacji/robotów):

// auth/approle.tf.json.txt (disabled for now)
{
  "resource": {
    "vault_auth_backend": {
      "approle": {
        "type": "approle"
      }
    }
  }
}

Aby włączyć AppRole, wystarczy zmienić .tf.json.txt na .tf.json.

Użytkownicy są tworzeni za pomocą modułu vault-userpass-account:

// users/mrachuna.tf.json
{
  "module": {
    "mrachuna": {
      "source": "git::https://gitlab.com/pl.rachuna-net/vault-userpass-account?ref=v1.1.0",
      "default_password_kv_path": "users/defaults_passwords",
      "users": {
        "mrachuna": {
          "policies": ["admin"],
          "ttl": "720h"
        }
      }
    }
  }
}

Moduł:

  1. Generuje losowe hasło (24 znaki + znaki specjalne)
  2. Tworzy użytkownika w Vault’a userpass auth method
  3. Przechowuje hasło tymczasowo w KV (do odebrania przez admina)
  4. Tworzy policy do czytania KV użytkownika

Po tofu apply:

# Pobierz wygenerowane hasło
tofu output -raw mrachuna_password

# Daj użytkownikowi — poproś żeby zmienił hasło
vault login -method=userpass username=mrachuna

KV v2 to przechowawka klucz-wartość dla sekretów:

# Struktura (koncepuje z Vaulta)
users/
├── data/mrachuna/         # Sekrety użytkownika mrachuna
├── metadata/mrachuna/     # Metadane (wersje, bity)
└── ...

Konfiguracja w IaC:

// kv/main.tf.json
{
  "resource": {
    "vault_mount": {
      "kv": {
        "path": "users",
        "type": "kv",
        "options": {
          "version": "2"
        }
      }
    }
  }
}

Najfajniejsza część — Vault może być Certificate Authority:

// pki/root.tf.json
{
  "resource": {
    "vault_pki_secret_backend": {
      "root": {
        "path": "pki_root",
        "max_lease_ttl_seconds": 315360000
      }
    },
    "vault_pki_secret_backend_root_cert": {
      "root": {
        "backend": "pki_root",
        "type": "internal",
        "common_name": "rachuna-net.pl CA",
        "ttl": "87600h"
      }
    }
  }
}
// pki/intermediate.tf.json
{
  "resource": {
    "vault_pki_secret_backend": {
      "intermediate": {
        "path": "pki_int_prod",
        "max_lease_ttl_seconds": 31536000
      }
    }
  }
}

Rezultat: Ansible role certificates może automatycznie pobierać certyfikaty TLS dla vault.rachuna-net.pl od Vault’a:

# Ansible pobiera:
curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
  https://vault.rachuna-net.pl:8200/v1/pki_int_prod/issue/vault-domain \
  -d @request.json | jq '.data.certificate' > /opt/vault/tls/vault.crt
# 1. Automated tool
export GITLAB_TOKEN="glpat-..."
./tools/create-user-account.sh john_doe

# Tool:
# - Tworzy branch: feat/user-john_doe
# - Tworzy plik: users/john_doe.tf.json
# - Commits + pushuje
# - Tworzy MR

# 2. Review MR w GitLab

# 3. Merge

# 4. CI/CD aplikuje w Vaulcie
# (GitLab CI automatycznie uruchamia: tofu apply)

# 5. Admin pobiera hasło
tofu output -raw john_doe_password

# 6. Daj hasło użytkownikowi
# 7. Użytkownik się loguje i zmienia hasło
vault login -method=userpass username=john_doe
vault auth list

# 8. Usuń temporary password
vault kv delete users/defaults_passwords/john_doe

OpenTofu musi gdzieś przechowywać state (mapę między plikami a zasobami w Vaulcie).

Używamy GitLab HTTP backend:

# tofu init
tofu init -backend-config="address=https://gitlab.com/api/v4/projects/78249750/terraform/state/production"

State jest przechowywany w GitLab’a, a nie jest przechowywany lokalnie.


Jeśli chcesz go pogłębiać — polecam:

ZasóbCzego się nauczysz
Vault DocsAll about Vault
Consul DocsStorage backend
Ansible DocsProvisioning
OpenTofu DocsIaC language
Our ReposKod + CLAUDE.md files

Maciej Rachuna | rachuna-net.pl

Artykuł bazuje na realnym wdrożeniu w środowisku homelab. Kod, learnings, i best practices pochodzą z praktyki, a nie z teorii. Dokumentacja znajduje się tutaj