Dávno jsou pryč časy jednoduchých textových souborů, kde byly tyto hodnoty uloženy. Kam tyto hodnoty vhodně a bezpečně uložit by měla zodpovědně řešit každá firma nebo projekt, kde se vyvíjí nová aplikace či spravuje stávající infrastruktura.
Co se dozvíte v článku
V poslední době získává na popularitě orchestrace a správa aplikací pomocí platformy Kubernetes, proto se v tomto článku zaměříme na tuto platformu a práci s citlivými hodnotami (secrets) pomocí nástroje external-secrets.
Kubernetes přímo nabízí a doporučuje objekt Secret, do kterého je vhodné tyto hodnoty ukládat. Na rozdíl od objektu ConfigMap, který slouží primárně pro ukládání běžných a necitlivých konfiguračních hodnot.
Ukládání Kubernetes manifestů včetně definice citlivých hodnot v gitovském repozitáři není ideální, jelikož tyto citlivé hodnoty by zde byly uloženy a navzdory možnostem omezení přístupu podle rolí (RBAC) mohou být k dispozici případným útočníkům a komukoli, kdo k tomuto repozitáři má přístup, včetně historie, i když už byly hodnoty například smazány.
Logicky by bylo tedy vhodné tyto hodnoty vkládat dynamickým způsobem. Navíc pokud je více prostředí nebo samotných aplikací – může chybějící či nevhodný nástroj na správu hesel pěkně znepříjemnit a zkomplikovat život.
Naopak, pokud se zvolí správný a robustní správce hesel, který si vezme na starost správu těchto hodnot, usnadní to práci administrátorů a zároveň to přinese i značné zvýšení bezpečnosti.
External Secrets
Jaké máme možnosti? Pro své řešení jsme si zvolili open-source nástroj external-secrets, který integraci s Kubernetes nativně nabízí. V rámci Kubernetes objektu – ExternalSecret definujeme název nebo cestu k secretu, který máme uložený v externím uložišti (v našem případě cloudu).
Pro ten si nástroj sáhne a dynamicky ho vloží do objektu – Secret, který pak můžeme využít v rámci konfigurace aplikace. Nástroj běží jako služba kontinuálně a pravidelně kontroluje aktuální hodnotu. V případě, že se změní – například dojde k její aktualizaci – nahraje opět novou hodnotu do objektu Secret.
Nástroj external-secrets nabízí integraci pro všechny poskytovatele veřejných cloudů a jejich systémy správy secretů.
Například:
- AWS – Parameter Store a Secret Store
- Google Cloud – Secrets Manager
- Azure – KeyVault
- Hashicorp Vault
Výhody uložení citlivých hodnot v cloudu jsou:
- Šifrování secretů při přenosu a jejich uložení,
- přístup k secretům je logovaný a auditovatelný,
- změny hodnot mohou být verzovány – lze se vrátit k předchozí verzi,
- secrety se mohou jednoduše rotovat.
V rámci ukázky si předvedeme napojení na Azure Keyvault.
Další alternativy
- Sealed Secrets – umožňuje uložení citlivých údajů přímo v repozitáři, hodnoty se pak dešifrují přímo v Kubernetes pomocí běžícího controlleru a ukládají se také jako Secret
- SOPS – také umožňuje uložení citlivých údajů přímo v repozitáři – narozdíl od Sealed Secrets však nevyžaduje běžící službu v Kubernetes – dešifrování probíhá před samotným deploymentem do Kubernetes například v rámci pipeliny
- Aplikační závislost – použití např. Azure KeyVault SDK pro .NET/Java, kdy aplikace referencuje secret store přímo. Nevýhodou je explicitní závislost aplikace na jednom druhu secret storu např. KeyVault a v důsledku i vendor lock.
Ukázka
Co potřebujeme mít připravené, než začneme:
- Účet Azure a přístup k CLI,
- azure-cli – nástroj pro příkazovou řádku,
- běžící Kubernetes cluster (v našem případě verze 1.2×),
- Helm – external-secrets budeme instalovat pomocí Helmu.
Nastavení prostředí Azure
Service Principal
Vytvoříme novou resource groupu:
az group create --location westeurope --name es-example
Dále vytvoříme novou Active Directory aplikaci – service principala:
AD_APP_ID=$(az ad app create --display-name es-example --query appId | tr -d \")
Vygenerujeme si heslo pro SP. Uložte si výstup tohoto příkazu, kde máte appId, password a tenant ID, které dále použijeme:
az ad app credential reset --id $AD_APP_ID
{
"appId": "xxxxxxx",
"password": "xxxxxxx",
"tenant": "xxxxxxx"
}
Azure KeyVault
Vytvoříme samotný KeyVault:
az keyvault create --location westeurope --name es-example --resource-group es-example
Zkušební Keyvault secret s hodnotou „test“:
az keyvault secret set --name es-example --vault-name es-example --value test
Nastavíme policy, aby k němu mohl nově vytvořený SP přistupovat:
az keyvault set-policy--name es-example--spn $AD_APP_ID --secret-permissionsget
Nyní máme service principala a Keyvault nastavený.
Poznámka: v tomto případě autorizace na Azure probíhá skrze SP a jeho ClientId a ClientSecret, který má životnost maximálně dva roky a není proto vždy vhodnou volbou (vzhledem k nutnosti manuální rotace) oproti preferovanému použití Managed identity nebo Workload identity federation (v době psaní článku pouze preview verze).
Konfigurace Externals Secrets
Nainstalujeme external-secrets pomocí Helmu:
helm repo addexternal-secrets https://charts.external-secrets.io helm install external-secrets external-secrets/external-secrets -n external-secrets --create-namespace
kubectl get pods -n external-secrets NAME READY STATUS RESTARTS AGE external-secrets-8f7d97cb-nb29s 1/1 Running 0 4m43s external-secrets-cert-controller-bd9964f5d-lpzbv 1/1 Running 0 4m43s external-secrets-webhook-94f6c58f5-htw5h 1/1 Running 0 4m43s
V rámci Kubernetes si vložíme získaný appId a secret do Kubernetes secretu:
kubectl create secret generic azure-secret-sp --from-literal=ClientID=<appId> --from-literal=ClientSecret=<password>
Pro napojení na Azure Keyvault potřebujeme vytvořit SecretStore. Doplníme tenantId:
cat << EOF | kubectl apply -f -
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: azure-backend
spec:
provider:
azurekv:
tenantId:
vaultUrl: "https://es-example.vault.azure.net"
authSecretRef:
clientId:
name: azure-secret-sp
key: ClientID
clientSecret:
name: azure-secret-sp
key: ClientSecret
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: es-example
spec:
secretStoreRef:
kind: SecretStore
name: azure-backend
target:
name: azure-secret
data:
- secretKey: es-example
remoteRef:
key: es-example
EOF
Test
Nyní zkontrolujeme status, jestli se external-secrets bylo schopné připojit na Azure Keyvault:
kubectl get es NAME STORE REFRESH INTERVAL STATUS READY es-example azure-keyvault 1h SecretSynced True
Necháme si vypsat hodnotu secretu:
kubectl get secret azure-secret -o jsonpath='{.data.es-example}' | base64 -dtest
Vidíme, že připojení proběhlo úspěšně a hodnota se správně propsala do secretu.
Automatické načítání změn
V této velmi jednoduché ukázce jsme si ukázali možnost dynamického vkládání do Kubernetes secretu pomocí nástroje external-secrets.
Konfigurační hodnoty jsou bezpečně drženy v externím službě Azure KeyVault a external-secrets se nám sám automaticky stará o propagování hodnot do provozního prostředí, aniž by hodnoty exponoval.
Téměř dokonalé. Proč „téměř“? Je i zde prostor pro zlepšení? Ano, aktuálně je potřeba při aktualizaci hodnoty secretu pro načtení pody aplikace restartovat. Toto je možné automatizovat například dalším nástrojem Reloader, který dokáže rozpoznat změnu secretu v Kubernetes a automaticky všechny pody aplikace restartovat, tak aby si nové změny načetly. To si ale necháme na příště.
