Wstęp teoretyczny
Aby instancja Vault była gotowa do działania po jej zainstalowaniu, musimy wykonać dwie rzecz:
- zaincjalizować Vaulta
- wykonać unsealing, czyli brzydko tłumacząc odpieczętować Vaulta
Unsealing musimy wykonywać przy każdym uruchomieniu Vaulta. Jest tak dlatego, że Vault po uruchomieniu nie ma dostępu do pamięci, w której są zapisane wszystkie nasze sekrety. Dlatego konieczne jest dostarczenie do Vaulta klucz, za pomocą którego będzie on w stanie odszyfrować dane.
Inicjalizacja z kolei jest jednorazową akcją. Podczas incjalizacji Vault generuje następujące klucze:
- unseal keys
- encryption keys
- root token
Jak łatwo się domyślić Unseal keys są używane do wykonania operacji Unseal.
Możemy wyróżnić trzy opcje Unsealowania Vaulta:
- ręczny unsealing
- Auto-unseal
- Transit Unseal – de facto jedna z opcji Auto-unseal
Manualny unseal
Ręczny unsealing jest najprostszy i nie wymaga żadnej dodatkowej konfiguracji. Vault generuje root key (nie mylić z root tokenem) i wykorzystując algorytm Shamir’s Secret Sharing dzieli klucza na kawałki. Podczas inicjalizacji możemy określić na ile części ma zostać podzielony root key oraz ile części będzie wymagane do wykonania ręcznego unseala. Takie “częściowe klucze” rozdysponowuje się do różnych osób odpowiedzialnych za zarządzanie Vaultem. Zwiększa to bezpieczeństwo, gdyż pojedyncza osoba nie jest w posiadaniu root key i tym samym nie jest w stanie odszyfrować wszystkich danych. Ręczny unseal jest opcją niezależną od systemów zewnętrznych i bardzo elastyczną. Może jednak być bolesny w utrzymaniu, gdy mamy wiele klastrów Vaulta, wiele kluczy i wielu posiadaczy kluczy.
Auto-unseal
Automatyczny unseal zmniejsza złożoność operacyjną i czyni zarządzanie Vaultem mniej wymagającym. W tym podejściu delegujemy odpowiedzialność za zabezpieczenie root key do zewnętrznej usługi np. AWS KMS lub do fizycznego modułu szyfrującego np. HSM. Vault skonfigurowany do automatycznego unselowania, sam łączy się z serwisem/urządzeniem i wykonuje odpieczętowanie. Nie jest już wymagana żadna ręczna akcja.
Transit Auto-unseal
Powiedzieliśmy sobie, że Transit Auto-unseal jest jedną z opcji Auto-unseal. To co wyróżnia to podejście, to fakt, że nie polegamy na zewnętrznej usłudze/urządzeniu, ale na innej instancji Vaulta (najczęściej takiej, nad którą również trzymamy piecze). W ten sposób możemy umieścić jednego centralnego Vaulta, który będzie odpowiedzialny za Auto-unseal innych instancji Vaulta.
Transit Auto-unseal setup
Czas na konkrety. W dalszej części pokaże Ci jak skonfigurować Vaulta, aby działał on w trybie Transit Auto-unseal. Będziemy działać oczywiście w Kubernetesie więc potrzebny będzie Ci klaster np. lokalny minikube bądź k3s.
Na początek w celu odizolowania środowisk tworzymy oddzielne namespace.
# namespace dla centralnego Vaulta, który będzie unselował inne Vaulty
kubectl create ns vault
# namespace dla Vault, którego będziemy unsealować
kubectl create ns vault-a
Na pierwszy ogień idzie instalacja Vaulta centralnego. Dla ułatwienia posłużymy się Helmowymi Chartami. Poniżej konfiguracja Vaulta HA z dwoma replikami. Vault centralny będzie miał konfiguracje do unselowania ręcznego, ale równie dobrze mógłby być skonfigurowany unsealing automatyczny z użyciem AWS KMS.
server:
affinity: ""
ha:
enabled: true
replicas: 2
raft:
enabled: true
# przełącza namespace na Vaulta centralnego
kns vault
helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault hashicorp/vault -f vault-central-helm-values.yml
Pora na incjalizacje Vaulta centralnego. Poniższą komendą zanicjujemy Vaulta i podzielimy root key na 4 części (unseal keys). Definijujemy takż, że 2 unseal keys będą wymagane, aby utworzyć root key w celu unselowania Vaulta, aby ten mógł utworzyć encrytpion key. To właśnie encryption key jest używany przez Vaulta do szyfrowanie i odszyfrowywanie sekretów.
kubectl exec vault-0 -- vault operator init \
-key-shares=4 \
-key-threshold=2 \
-format=json > vault-central-keys.json
Vault wygeneruje 4 unseal key zakodowane w base64 i hex oraz root token odpowiedzialny za autoryzację w Vault. Root token posłuży nam jeszcze później.
{
"unseal_keys_b64": [
"4Wm5BYsNal+zMbsb3ewNbi6zLtKIOXz3L+NFX7jw0/3T",
"miasg31FmPJqx9LrnPaVEuG639fvjAqZF3gp4ZlKw+wK",
"EyVw9nQH/T+3zsa4HbPJ2s15l6B5MizMKQlKqs9taFzX",
"zc7eU9MEvy9AaV4FPSQe7Jla2LcqSjS8KNPFDlQs0Rcg"
],
"unseal_keys_hex": [
"e169b9058b0d6a5fb331bb1bddec0d6e2eb32ed288397cf72fe3455fb8f0d3fdd3",
"9a26ac837d4598f26ac7d2eb9cf69512e1badfd7ef8c0a99177829e1994ac3ec0a",
"132570f67407fd3fb7cec6b81db3c9dacd7997a079322ccc29094aaacf6d685cd7",
"cdcede53d304bf2f40695e053d241eec995ad8b72a4a34bc28d3c50e542cd11720"
],
"unseal_shares": 4,
"unseal_threshold": 2,
"recovery_keys_b64": [],
"recovery_keys_hex": [],
"recovery_keys_shares": 0,
"recovery_keys_threshold": 0,
"root_token": "hvs.NbXRWfYNI4PmA860aBlC4onU"
Gdy mamy już unseal keys czas na unselowania centralnego Vaulta. Mamy uruchomione dwie repliki Vaulta i obie z nich musimy ręcznie unselować. W tym celu do każdego poda musimy przekazać conajmniej 2 unseal_keys_b64 zgodnie z progiem, jaki skonfigurowaliśmy podczas incjalizacji.
kubectl exec vault-0 -- vault operator unseal 4Wm5BYsNal+zMbsb3ewNbi6zLtKIOXz3L+NFX7jw0/3T
kubectl exec vault-0 -- vault operator unseal miasg31FmPJqx9LrnPaVEuG639fvjAqZF3gp4ZlKw+wK
Powtarzamy krok dla drugiego poda ale wcześniej musimy podłączyć drugiego Vaulta do klastra Raft, gdyż taki typ storage jest domyślnie skonfigurowany.
kubectl exec -ti vault-1 -- vault operator raft join http://vault-0.vault-internal:8200
kubectl exec vault-1 -- vault operator unseal 4Wm5BYsNal+zMbsb3ewNbi6zLtKIOXz3L+NFX7jw0/3T
kubectl exec vault-1 -- vault operator unseal miasg31FmPJqx9LrnPaVEuG639fvjAqZF3gp4ZlKw+wK
Czas na skonfigurowanie kluczowego elementu odpowiedzialnego za ochronę i przekazywanie root key. – Transit Secret Engine. Transit Secret Engine jest jednym z typów sekretów jaki oferuje Vault. Można go określi mianem “encryption as a service” i porównać do usługi AWS KMS.
# zestaw tunel do Vaulta w oddzielnym oknie terminala
kubectl port-forward vault-0 -n vault 8200:8200
# ustaw adres Vaulta aby używać lokalnie Vault CLI
export VAULT_ADDR=http://127.0.0.1:8200
# użyj 'root_token' wygenerowanego podczas incjalizacji do autryzacji w Vault
vault login
# utwórz transit secret engine
vault secrets enable transit
vault write -f transit/keys/autounseal
Utworzymy teraz token autoryzujący wszystkie Vaulty do Vaulta centralnego i jego Transit Secret Engine. Do tego potrzebna jest nam również powiązana polityka. Zapisz poniższą politykę do pliku autounseal-policy.hcl.
path "transit/encrypt/autounseal" {
capabilities = [ "update" ]
}
path "transit/decrypt/autounseal" {
capabilities = [ "update" ]
}
# utwórz politykę
vault policy write autounseal autounseal-policy.hcl
# utwórz Vault token
$ vault token create -orphan -policy=autounseal -period=24h
Key Value
--- -----
token hvs.CAESIP_A7TaC9kt4yUeqg5_bJNiOJElb4UbA01xoV9Rk4ei6Gh4KHGh2cy5zVXpaa3A1MG9uOEZrNXN2a3J0TGl0cHU
token_accessor wkTM4nsF0ehkRvIuBD9cedHC
token_duration 24h
token_renewable true
token_policies ["autounseal" "default"]
identity_policies []
policies ["autounseal" "default"]
Token utworzony. To właśnie za jego pomocą będziemy mogli autoryzować inne Vaulty w Vaulcie centralnym, który będzie służył do unselowania. Zauważ, że token ten jest orphan co oznacza, że nie ma tokenu nadrzędnego (parent tokenu) a więc nie będzie on usuwany wraz z przodkiem. Co warto zaznaczyć, auto-unseal token domyślnie odnawiany będzie automatycznie.
Czas ma przygotowanie Helm Chartu dla drugiego Vaulta. Jak widzisz poniżej skonfigurowany został transit unsealing. Konfiguracja zawiera adres Vaulta centralnego, jak i token wygenerowany w poprzednim kroku.
server:
standalone:
enabled: true
config: |
disable_mlock = true
ui=true
storage "file" {
path = "/vault/data"
}
listener "tcp" {
address = "127.0.0.1:8200"
tls_disable = "true"
}
seal "transit" {
address = "http://vault.vault:8200"
token = "hvs.CAESIP_A7TaC9kt4yUeqg5_bJNiOJElb4UbA01xoV9Rk4ei6Gh4KHGh2cy5zVXpaa3A1MG9uOEZrNXN2a3J0TGl0cHU"
disable_renewal = "false"
key_name = "autounseal"
mount_path = "transit/"
tls_skip_verify = "true"
}
Zainstaluj Vaulta w dedykowanym namespace.
kns vault-a
helm install vault hashicorp/vault -f vault-auto-unseal-helm-values.yml
Ostatnim krokiem jest podobnie jak w przypadku Vaulta centralnego incjalizacja.
kubectl exec -it vault-0 -- vault operator init
Recovery Key 1: FFMLznSZq9wh/0CJwKLJWKkI9BrK/hjF6ySDYl9a19Ie
Recovery Key 2: qRfrdpkuEcXsF+dFh1Geru8VHkiL/hWUW+vY25twlwT1
Recovery Key 3: dX8sed7Dv8kI8kfFuYWDeQlagoikEVBpV5lZqH4ORnEh
Recovery Key 4: TCCplv+KvZHEOlICQU6eb67hGccufiqcZGkSiGlpQPkx
Recovery Key 5: ictL+c9czgMO+ME8qoTcGpgsvymEcORN7MkrpDE28x4a
Initial Root Token: hvs.6umGyyta9xrjq0q7Cv09Hr8X
Success! Vault is initialized
Możesz teraz zweryfikować czy Vault poprawnie przeprowadził proces automatycznego unselowania.
kubectl exec -it vault-a -- vault status
Key Value
--- -----
Recovery Seal Type shamir
Initialized true
Sealed false
Total Recovery Shares 5
Threshold 3
Version 1.12.0
Build Date 2022-10-10T18:14:33Z
Storage Type file
Cluster Name vault-cluster-7a11a0ae
Cluster ID a883d977-e70a-6367-3148-9c7a2c246897
HA Enabled false
Zauważ różnicę, że Vault z automatycznym unselowaniem podczas incjalizacji generuje Recovery keys zamiast Unseal Keys. Kluczy odzyskiwania nie można użyć do unselowania Vaulta. Pełnią one jedynie funkcje autoryzacyjne. Pozwalają np. wygenerować nowy root token.
Podsumowanie
Jedną z praktyk metodyki DevOps jest automatyzacja. Chcemy automatyzować procesy, aby zredukować ilość manualnych akcji. Benefitem jest nie tylko zaoszczędzony czas, ale również zmniejszenie podatności na błąd ludzki. Niewątpliwie Auto-unseal Vaulta jest opcją, która świetnie wpisuje się w te założenia. Jednakże z punktu widzenia bezpieczeństwa, czasami wprowadzenie manualnego kroku wymagającego interwencji człowieka jest przydatne, a niekiedy konieczne. W powyższej konfiguracji Transit Auto-unseal mamy połączenie dwóch podejść. Manualny proces unselowania Vaulta centralnego oraz automatyczny proces unselowania Vaultów zależnych. Warto również zaznaczyć, że nasze rozwiązanie jest cloud-agnostic. Nie opieramy się tutaj na żadnej zewnętrznej usłudze szyfrującej. Nasze rozwiązanie jest więc łatwiej przenaszalne i mamy nad nim większą kontrolę. Słabym punktem tego setupu jest jednak wprowadzenie elementu krytycznego w całej tej układance. Taki elementem jest właśnie Vault centralny. Jakkolwiek awaria tego komponentu może przysporzyć sporo problemów. Pytaniem, które należy sobie tutaj postawić jest, jak zapewnić wysoką dostępność i odporność na błędy dla krytycznego Vaulta. To jednak temat na odrębne przemyślenia i post.