
In Kubernetes l'obiettivo è automatizzare e ridurre al minimo la duplicazione del codice. Anche il consumo dei secret da un secret manager dovrebbe seguire lo stesso principio. Vediamo come fare.
Questo articolo prosegue il discorso iniziato in un mio post precedente sull'automazione dell'iniezione dei secret nei Pod Kubernetes quando si utilizza Vault.
I secret in Kubernetes restano testo in chiaro codificato in base64.
È vero che si possono cifrare i secret a riposo, ma sul Pod stesso non sono cifrati e la chiave di cifratura è globale per Etcd (dove i secret vengono memorizzati): in un cluster multi-tenant questo introduce un rischio, perché tutti i secret sono cifrati con la stessa chiave globale.
Iniettare in modo sicuro i secret da AWS, GCP o Vault in un Pod Kubernetes
GCP KMS permette di creare una chiave envelope a livello applicativo per affrontare il problema. Resta però il fatto che, alla fine della catena, il Pod consuma i secret in chiaro.
Oltre a Hashicorp Vault, ci sono i secret manager di AWS e GCP.
AWS e GCP non offrono molte delle funzionalità di Vault — backend di autenticazione, audit degli accessi ai secret, revoca o secret dinamici — ma mettono comunque a disposizione un meccanismo simile per memorizzare i secret con versionamento.
La sfida: consumare i secret nei Pod Kubernetes
Consumare questi secret in un Pod Kubernetes non è banale: serve uno script wrapper personalizzato che gestisca l'autenticazione e l'esposizione dei secret come variabili d'ambiente. Lo script wrapper deve poi essere sostituito dal processo wrapper, in modo che erediti tutte le variabili d'ambiente e diventi PID: 1 — condizione fondamentale per gestire correttamente i segnali di terminazione nei container.
In Kubernetes cerchiamo di automatizzare e ridurre al minimo il codice duplicato necessario per gestire questo tipo di attività di supporto (un altro esempio è l'automazione di DNS e certificati). Il consumo dei secret da un secret manager dovrebbe seguire la stessa filosofia.
Alcune aziende preferiscono non gestire un'installazione Vault e, già solo per questo motivo, possono optare per i secret manager di AWS o GCP.
I webhook di Kubernetes che devi conoscere
Kubernetes integra un admission controller in grado di intercettare le richieste al Kubernetes API server prima che l'oggetto venga persistito, ma dopo che la richiesta è stata autenticata e autorizzata.
Nel binario kube-apiserver sono integrati due controller speciali:
- MutatingAdmissionWebhook
- ValidatingAdmissionWebhook
Si occupano rispettivamente della mutazione e della validazione delle richieste dopo le fasi di autenticazione e autorizzazione.
I controller mutating possono modificare gli oggetti che ammettono, mentre quelli validating possono ammettere o rifiutare un oggetto sull'API server.
Questi oggetti webhook si installano come un qualunque oggetto Kubernetes tramite kubectl (contengono l'indirizzo https a cui risiede la logica vera e propria del webhook).
Una volta installati, ogni richiesta passerà attraverso questa logica.
Il Mutation Webhook Secrets Consumer
Il codice e i concetti che seguono si basano in larga misura sull'ottimo lavoro di Banzai Cloud, a cui va tutto il merito (vi consiglio di leggere il loro blog: è davvero ottimo!).
Le principali differenze tra i webhook di Banzai Cloud e i miei sono:
- Possibilità di utilizzare i secret manager di AWS, GCP e Vault.
- Rimozione di consulTemplates e dei secret dinamici dal webhook di Banzai Cloud.
- Possibilità di autenticare Vault non solo con un backend Kubernetes, ma anche con un backend GCP.
- Selezione esplicita dei secret dal secret manager oppure recupero di tutti i secret.
- Uso del path del secret come directory, con recupero di tutti i secret sottostanti (limitato al primo livello).
- Uso dei nomi dei secret come chiavi quando il path termina con
/e ogni valore è un valore singolo:
L'obiettivo è gestire meglio il contenuto dei secret, riducendo la probabilità di errori nell'aggiungerli o modificarli e diminuendo il numero di passaggi richiesti (lettura, append, aggiornamento). 7. Path con wildcard: ad esempio, se il path termina con / e ci sono più secret che iniziano con db_, il path può essere /secret/path/db_*. 8. Rilevamento automatico se il secret è KV1 o KV2 (non serve più anteporre /data nel path).
Come funziona il Mutation Webhook
Ogni nuova richiesta all'API server passa attraverso questo webhook.
Il webhook verifica se l'oggetto è un Pod e lo modifica solo se contiene specifiche annotation, ad esempio:
https://gist.github.com/innovia/3e58eeb38668c9f4da4f1f76a736fbca
Quando trova queste annotation, modifica l'oggetto Pod nel modo seguente:
- Aggiunge un volume in-memory condiviso
- Aggiunge un init container con il binario vault-env e un comando per copiarlo nel volume condiviso
- Cambia il comando del Pod in vault-env
- Aggiunge le variabili d'ambiente di Vault (ROLE, CA_PATH, SECRET_PATH) per semplificare le operazioni con Vault.
Da tenere presente:
Il webhook non aggiunge i secret come variabili d'ambiente sul Pod. Nessun dato riservato viene mai scritto su disco, neanche temporaneamente, né è visibile sull'oggetto Pod modificato.
Tutti i secret restano in memoria e sono visibili solo al processo che li richiede.
Usare i chart senza container.command e container.args espliciti
Il webhook è ora in grado di determinare l'ENTRYPOINT e il CMD del container grazie ai metadati dell'immagine recuperati dall'image registry. Questi dati restano in cache fino al riavvio del Pod del webhook. Se il registry è accessibile pubblicamente (senza autenticazione) non serve fare nulla; se invece richiede autenticazione, le credenziali devono essere disponibili nella sezione imagePullSecrets del Pod.
Praticamente tutti i chart consentono di impostare le podAnnotations: per l'elenco completo delle annotation disponibili si rimanda alla pagina README del webhook.
Consumare i secret al di fuori di Kubernetes
Lo strumento secrets-consumer-env si esegue impostando alcune variabili d'ambiente — per tutte le opzioni disponibili si veda il README.md.
Installazione del webhook
Prima di installare il chart è necessario creare un namespace dedicato. Il motivo è l'ordine con cui le risorse dei chart vengono applicate: Helm raccoglie tutte le risorse di un dato chart e delle sue dipendenze, le raggruppa per tipo e le installa secondo un ordine predefinito (vedi qui — Helm 2.10).
La MutatingWebhookConfiguration viene creata prima del Pod backend che funge da webhook vero e proprio. Kubernetes vorrebbe applicare la mutazione anche a quel Pod, ma il webhook non è ancora pronto a mutare nulla (ricorsione infinita nella logica).
export WEBHOOK_NS=`<namepsace>`WEBHOOK_NS=${WEBHOOK_NS:-vault-secrets-webhook}kubectl create namespace "${WEBHOOK_NS}"kubectl label ns "${WEBHOOK_NS}" name="${WEBHOOK_NS}"Scaricare il chart:
git clone https://github.com/innovia/secrets-consumer-webhook.gitInstallare il chart:
helm upgrade --namespace ${WEBHOOK_NS} --install secrets-consumer-webhook secrets-consumer-webhook --waitNOTA: --wait è necessario a causa di problemi di timing di Helm; per i dettagli si veda questo issue.
Note sui cluster privati GKE
Quando Google configura il control plane per i cluster privati, imposta automaticamente il VPC peering tra la rete del cluster Kubernetes e un progetto Google gestito separatamente.
Le regole generate automaticamente aprono solo le porte 10250 e 443 tra master e nodi. Per usare il componente webhook con un cluster privato GKE è quindi necessario configurare una regola firewall aggiuntiva che consenta al CIDR dei master di raggiungere il Pod del webhook sulla porta 8443.
Per maggiori informazioni su come aggiungere regole firewall per i nodi del control plane GKE, si rimanda alla documentazione GKE.
Secret espliciti vs. non espliciti (recupero completo)
Puoi scegliere quali secret esporre al tuo processo oppure recuperare tutti i secret per un determinato path/nome.
Per selezionare in modo esplicito i secret dal secret manager, aggiungi al tuo Pod una variabile d'ambiente seguendo questa convenzione:
env:- name: <variable name to export> value: vault:<vault key name from secret>Configurare l'autenticazione Vault con backend Kubernetes
Vault può autenticarsi a Kubernetes utilizzando un service account Kubernetes.
Lo fa tramite un altro service account, chiamato vault-reviewer, dotato del permesso auth-delegator che gli consente di passare un token di service account per l'autenticazione al master Kubernetes.
Quando l'autenticazione a Kubernetes va a buon fine, Vault restituisce un client token utilizzabile per il login a Vault. Vault verifica poi una mappatura tra ruolo Vault, service account, namespace e policy per consentire o negare l'accesso.
Il token del service account vault-reviewer viene configurato all'interno di Vault tramite la CLI di Vault.
Creiamo il service account per il vault-reviewer.
https://gist.github.com/innovia/5435f2336e4dd0045dbb5842880b3334
Nota: se Vault è installato in un altro namespace, ricordati di aggiornare il file di conseguenza.
kubectl apply -f vault-reviewer.yamlAbilita il backend di autenticazione Kubernetes:
$ vault loginToken (will be hidden):$ vault auth enable kubernetesSuccess! Enabled kubernetes auth method at: kubernetes/Configurare Vault con il token e la CA del Vault-reviewer:
Nota: se Vault è installato in un altro namespace, aggiungi il flag -n dopo ogni comando kubectl.
$ VAULT_SA_TOKEN_NAME=$(kubectl get sa vault-reviewer -o jsonpath="{.secrets[*]['name']}")$ SA_JWT_TOKEN=$(kubectl get secret "$VAULT_SA_TOKEN_NAME" -o jsonpath="{.data.token}" | base64 --decode; echo)$ SA_CA_CRT=$(kubectl get secret "$VAULT_SA_TOKEN_NAME" -o jsonpath="{.data['ca\.crt']}" | base64 --decode; echo)$ vault write auth/kubernetes/config token_reviewer_jwt="$SA_JWT_TOKEN" kubernetes_host=https://kubernetes.default kubernetes_ca_cert="$SA_CA_CRT"Success! Data written to: auth/kubernetes/configEsempio di mappatura di un ruolo a un service account e a un namespace in Vault
vault write auth/kubernetes/role/tester bound_service_account_names=tester bound_service_account_namespaces=default policies=test_policy ttl=1hConfigurare l'autenticazione Vault con backend GCP
Abilita il metodo di autenticazione Google Cloud:
$ vault auth enable gcpConfigura le credenziali del metodo di autenticazione:
$ vault write auth/gcp/config \ credentials=@/path/to/credentials.jsonSe utilizzi credenziali di istanza o vuoi specificare le credenziali tramite una variabile d'ambiente, puoi saltare questo passaggio.
Crea un ruolo con nome:
Per un ruolo di tipo iam:
$ vault write auth/gcp/role/my-iam-role \ type="iam" \ policies="dev,prod" \ bound_service_accounts="[email protected]"Per un ruolo di tipo gce:
$ vault write auth/gcp/role/my-gce-role \ type="gce" \ policies="dev,prod" \ bound_projects="my-project1,my-project2" \ bound_zones="us-east1-b" \ bound_labels="foo:bar,zip:zap" \ bound_service_accounts="[email protected]"Permessi GCP necessari
Permessi del server Vault
Per i ruoli Vault di tipo iam, a Vault possono essere assegnati i seguenti ruoli:
roles/iam.serviceAccountKeyAdminPer i ruoli Vault di tipo gce, a Vault possono essere assegnati i seguenti ruoli:
roles/compute.viewerSe invece preferisci creare un ruolo personalizzato con i soli permessi GCP strettamente necessari, usa il seguente elenco:
iam.serviceAccounts.getiam.serviceAccountKeys.getcompute.instances.getcompute.instanceGroups.listPermessi per autenticarsi verso Vault
Attenzione: i permessi indicati in precedenza sono assegnati ai server Vault. Il service account IAM o l'istanza GCE che si autentica verso Vault deve invece avere il seguente ruolo:
roles/iam.serviceAccountTokenCreatorIl codice sorgente è consultabile qui: