Admission Webhook for K8s
1. Panoramica e documenti per Casbin K8s-Gatekeeper
Casbin K8s-GateKeeper è un webhook di ammissione Kubernetes che integra Casbin come strumento di Controllo di Accesso. Utilizzando Casbin K8s-GateKeeper, puoi stabilire regole flessibili per autorizzare o intercettare qualsiasi operazione su risorse K8s, SENZA scrivere alcun pezzo di codice, ma solo poche righe di configurazioni dichiarative di modelli e politiche di Casbin, che fanno parte del linguaggio ACL (Access Control List) di Casbin.
Casbin K8s-GateKeeper è sviluppato e mantenuto dalla comunità Casbin. Il repository di questo progetto è disponibile qui: https://github.com/casbin/k8s-gatekeeper
0.1 Un Esempio Semplice
Ad esempio, non è necessario scrivere alcun codice, ma utilizzare le seguenti righe di configurazione per ottenere questa funzione: "Proibire l'uso di immagini con alcuni tag specificati in qualsiasi distribuzione":
Model:
[request_definition]
r = obj
[policy_definition]
p = obj,eft
[policy_effect]
e = !some(where (p.eft == deny))
[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
contain(split(accessWithWildcard(${OBJECT}.Spec.Template.Spec.Containers , "*", "Image"),":",1) , p.obj)
And Policy:
p, "1.14.1",deny
Questi sono scritti nel linguaggio ordinario Casbin ACL. Supponendo che tu abbia già letto i capitoli su di essi, sarà molto facile da capire.
Casbin K8s-GateKeeper presenta i seguenti vantaggi:
- Facile da usare. Scrivere diverse righe di ACL è molto meglio che scrivere molto codice.
- Permette aggiornamenti dinamici delle configurazioni. Non è necessario spegnere l'intero plugin per modificare le configurazioni.
- È flessibile. È possibile creare regole arbitrarie su qualsiasi risorsa K8s, che possono essere esplorate con
kubectl gatekeeper
. - Semplifica l'implementazione del webhook di ammissione K8s, che è molto complicato. Non è necessario sapere cos'è il webhook di ammissione K8s o come scrivere codice per esso. Tutto ciò che devi fare è conoscere la risorsa su cui vuoi mettere vincoli e poi scrivere ACL di Casbin. Tutti sanno che K8s è complesso, ma utilizzando Casbin K8s-Gatekeeper, puoi risparmiare tempo.
- È mantenuto dalla comunità di Casbin. Sentiti libero di contattarci se qualcosa riguardo questo plugin ti confonde o se incontri problemi durante il tentativo.
1.1 Come funziona Casbin K8s-Gatekeeper?
K8s-Gatekeeper è un webhook di ammissione per K8s che utilizza Casbin per applicare regole di controllo degli accessi arbitrarie definite dall'utente, aiutando a prevenire qualsiasi operazione su K8s che l'amministratore non desidera.
Casbin è una potente e efficiente libreria open-source per il controllo degli accessi. Fornisce supporto per l'applicazione dell'autorizzazione basata su vari modelli di controllo degli accessi. Per maggiori dettagli su Casbin, consulta Panoramica.
I webhook di ammissione in K8s sono callback HTTP che ricevono richieste di ammissione e fanno qualcosa con esse. In particolare, K8s-Gatekeeper è un tipo speciale di webhook di ammissione: 'ValidatingAdmissionWebhook', che può decidere se accettare o rifiutare questa richiesta di ammissione o meno. Per quanto riguarda le richieste di ammissione, sono richieste HTTP che descrivono un'operazione su risorse specifiche di K8s (ad esempio, la creazione/eliminazione di una distribuzione). Per ulteriori informazioni sui webhook di ammissione, consulta la documentazione ufficiale di K8s.
1.2 Un Esempio Illustrativo di Come Funziona
Ad esempio, quando qualcuno desidera creare un deployment contenente un pod che esegue nginx (utilizzando kubectl o i client K8s), K8s genererà una richiesta di ammissione, che (se tradotta in formato YAML) potrebbe essere simile a questa:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.1
ports:
- containerPort: 80
Questa richiesta passerà attraverso il processo di tutti i middleware mostrati nell'immagine, incluso il nostro K8s-Gatekeeper. K8s-Gatekeeper può rilevare tutti gli enforcer di Casbin memorizzati in etcd di K8s, che viene creato e mantenuto dall'utente (tramite kubectl
o il client Go che forniamo). Ogni enforcer contiene un modello Casbin e una politica Casbin. La richiesta di ammissione sarà elaborata da ogni enforcer, uno alla volta, e solo superando tutti gli enforcer una richiesta può essere accettata da questo K8s-Gatekeeper.
(Se non capisci cosa sia un enforcer Casbin, un modello o una politica, consulta questo documento: Iniziare).
Ad esempio, per qualche motivo, l'amministratore vuole vietare l'uso dell'immagine 'nginx:1.14.1' mentre permette 'nginx:1.3.1'. Può essere creato un enforcer contenente la seguente regola e politica (Spiegheremo come creare un enforcer, cosa siano questi modelli e politiche e come scriverli nel seguente capitolo).
Model:
[request_definition]
r = obj
[policy_definition]
p = obj,eft
[policy_effect]
e = !some(where (p.eft == deny))
[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Image") == p.obj
Policy:
p, "nginx:1.13.1",allow
p, "nginx:1.14.1",deny
Creando un enforcer contenente il modello e la politica sopra menzionati, la precedente richiesta di ammissione verrà rifiutata da questo enforcer, il che significa che K8s non creerà questo deployment.
2 Installa K8s-gatekeeper
Esistono tre metodi disponibili per l'installazione di K8s-gatekeeper: Webhook esterno, Webhook interno e Helm.
Nota: Questi metodi sono destinati solo agli utenti che desiderano provare K8s-gatekeeper e non sono sicuri. Se desideri utilizzarlo in un ambiente produttivo, ti preghiamo di assicurarti di aver letto Capitolo 5. Impostazioni avanzate e di apportare eventuali modifiche necessarie prima dell'installazione.
2.1 Webhook interno
2.1.1 Passo 1: Costruire l'immagine
Per il metodo del webhook interno, il webhook stesso sarà implementato come servizio all'interno di Kubernetes. Per creare il servizio e la distribuzione necessari, è necessario creare un'immagine di K8s-gatekeeper. Puoi creare la tua immagine eseguendo il seguente comando:
docker build --target webhook -t k8s-gatekeeper .
Questo comando creerà un'immagine locale chiamata 'k8s-gatekeeper:latest'.
Nota: Se stai utilizzando minikube, esegui eval $(minikube -p minikube docker-env)
prima di eseguire 'docker build'.
2.1.2 Passo 2: Configurare servizi e distribuzioni per K8s-gatekeeper
Esegui i seguenti comandi:
kubectl apply -f config/rbac.yaml
kubectl apply -f config/webhook_deployment.yaml
kubectl apply -f config/webhook_internal.yaml
Questo farà partire l'esecuzione di K8s-gatekeeper, e puoi confermarlo eseguendo kubectl get pods
.
2.1.3 Passo 3: Installare le risorse CRD per K8s-gatekeeper
Esegui i seguenti comandi:
kubectl apply -f config/auth.casbin.org_casbinmodels.yaml
kubectl apply -f config/auth.casbin.org_casbinpolicies.yaml
2.2 Webhook esterno
Per il metodo del webhook esterno, K8s-gatekeeper sarà in esecuzione al di fuori di Kubernetes, e Kubernetes accederà a K8s-gatekeeper come farebbe per un sito web regolare. Kubernetes ha un requisito obbligatorio che il webhook di ammissione deve essere HTTPS. A scopo di prova di K8s-gatekeeper, abbiamo fornito un set di certificati e una chiave privata (anche se questo non è sicuro). Se preferisci utilizzare il tuo certificato, consulta Capitolo 5. Impostazioni avanzate per istruzioni su come regolare il certificato e la chiave privata.
Il certificato che forniamo è emesso per 'webhook.domain.local'. Quindi, modifica l'host (ad esempio, /etc/hosts) e indirizza 'webhook.domain.local' all'indirizzo IP su cui K8s-gatekeeper è in esecuzione.
Quindi esegui il seguente comando:
go mod tidy
go mod vendor
go run cmd/webhook/main.go
kubectl apply -f config/auth.casbin.org_casbinmodels.yaml
kubectl apply -f config/auth.casbin.org_casbinpolicies.yaml
kubectl apply -f config/webhook_external.yaml
2.3 Installa K8s-gatekeeper tramite Helm
2.3.1 Passo 1: Costruisci l'immagine
Si prega di fare riferimento a Capitolo 2.1.1.
2.3.2 Installazione di Helm
Esegui il comando helm install k8sgatekeeper ./k8sgatekeeper
.
3. Prova K8s-gatekeeper
3.1 Creare Modello e Policy di Casbin
Hai due metodi per creare un modello e una policy: tramite kubectl o tramite il go-client che forniamo.
3.1.1 Creare/Aggiornare Modello e Policy di Casbin tramite kubectl
In K8s-gatekeeper, il modello di Casbin è memorizzato in una risorsa CRD chiamata 'CasbinModel'. La sua definizione si trova in config/auth.casbin.org_casbinmodels.yaml
.
Ci sono esempi in example/allowed_repo/model.yaml
. Presta attenzione ai seguenti campi:
- metadata.name: il nome del modello. Questo nome DEVE essere lo stesso del nome dell'oggetto CasbinPolicy correlato a questo modello, in modo che K8s-gatekeeper possa accoppiarli e creare un enforcer.
- spec.enable: se questo campo è impostato su "false", questo modello (così come l'oggetto CasbinPolicy correlato a questo modello) sarà ignorato.
- spec.modelText: una stringa che contiene il testo del modello di un modello Casbin.
La Politica Casbin è memorizzata in un'altra risorsa CRD chiamata 'CasbinPolicy', la cui definizione può essere trovata in config/auth.casbin.org_casbinpolicies.yaml
.
Ci sono esempi in example/allowed_repo/policy.yaml
. Presta attenzione ai seguenti campi:
- metadata.name: il nome della politica. Questo nome DEVE essere identico al nome dell'oggetto CasbinModel correlato a questa policy, in modo che K8s-gatekeeper possa accoppiarli e creare un enforcer.
- spec.policyItem: una stringa che contiene il testo della policy di un modello Casbin.
Dopo aver creato i tuoi file CasbinModel e CasbinPolicy, utilizza il seguente comando per applicarli:
kubectl apply -f <filename>
Una volta creato un accoppiamento di CasbinModel e CasbinPolicy, K8s-gatekeeper sarà in grado di rilevarlo entro 5 secondi.
3.1.2 Creare/Aggiornare il Modello e la Policy Casbin tramite il go-client che forniamo
Capiamo che potrebbero esserci situazioni in cui non è conveniente utilizzare la shell per eseguire comandi direttamente su un nodo del cluster K8s, come quando si sta costruendo una piattaforma cloud automatica per la tua azienda. Pertanto, abbiamo sviluppato un go-client per creare e mantenere CasbinModel e CasbinPolicy.
La libreria go-client si trova in pkg/client
.
In client.go
, forniamo una funzione per creare un client.
func NewK8sGateKeeperClient(externalClient bool) (*K8sGateKeeperClient, error)
Il parametro externalClient
determina se K8s-gatekeeper è in esecuzione all'interno del cluster K8s o meno.
In model.go
, forniamo varie funzioni per creare, eliminare e modificare CasbinModel. Puoi scoprire come utilizzare queste interfacce in model_test.go
.
In policy.go
, forniamo varie funzioni per creare, eliminare e modificare CasbiPolicy. Puoi scoprire come utilizzare queste interfacce in policy_test.go
.
3.1.2 Prova se K8s-gatekeeper funziona
Supponiamo che tu abbia già creato il modello e la politica esatti in example/allowed_repo
. Ora, prova il seguente comando:
kubectl apply -f example/allowed_repo/testcase/reject_1.yaml
Dovresti scoprire che K8s rifiuterà questa richiesta e menzionerà che il webhook è il motivo per cui questa richiesta è stata rifiutata. Tuttavia, quando provi ad applicare example/allowed_repo/testcase/approve_2.yaml
, sarà accettato.
4. Come Scrivere Modelli e Politiche con K8s-gatekeeper
Prima di tutto, assicurati di essere familiare con la grammatica di base dei Modelli e delle Politiche di Casbin. Se non lo sei, per favore leggi prima la sezione Iniziare. In questo capitolo, supponiamo che tu già comprenda cosa sono i Modelli e le Politiche di Casbin.
4.1 Definizione della Richiesta del Modello
Quando K8s-gatekeeper autorizza una richiesta, l'input è sempre un oggetto: l'oggetto Go della Richiesta di Admissione. Ciò significa che l'enforcer sarà sempre utilizzato in questo modo:
ok, err := enforcer.Enforce(admission)
dove admission
è un oggetto AdmissionReview
definito dall'API ufficiale di Go di K8s "k8s.io/api/admission/v1"
. Puoi trovare la definizione di questa struct in questo repository: https://github.com/kubernetes/api/blob/master/admission/v1/types.go. Per ulteriori informazioni, puoi anche fare riferimento a https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#webhook-request-and-response.
Pertanto, per qualsiasi modello utilizzato da K8s-gatekeeper, la definizione di request_definition
dovrebbe sempre essere come questa:
[request_definition]
r = obj
Il nome 'obj' non è obbligatorio, purché il nome sia coerente con il nome utilizzato nella parte [matchers]
.
4.2 Matchers del Modello
Si suppone che si utilizzi la funzionalità ABAC di Casbin per scrivere le proprie regole. Tuttavia, l'evaluator di espressioni integrato in Casbin non supporta l'indicizzazione in mappe o array (slice), né l'espansione di array. Pertanto, K8s-gatekeeper fornisce varie 'funzioni Casbin' come estensioni per implementare queste funzionalità. Se ritieni che la tua richiesta non possa essere soddisfatta da queste estensioni, sentiti libero di aprire un problema (issue) o creare una richiesta di pull (pull request).
Se non sei familiare con le funzioni Casbin, puoi fare riferimento a Funzione per ulteriori informazioni.
Ecco le funzioni di estensione:
4.2.1 Funzioni di Estensione
4.2.1.1 access
Access viene utilizzato per risolvere il problema che Casbin non supporta l'indicizzazione in mappe o array. L'esempio example/allowed_repo/model.yaml
dimostra l'uso di questa funzione:
[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Image") == p.obj
In questo matcher, access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Image")
è equivalente a r.obj.Request.Object.Object.Spec.Template.Spec.Containers[0].Image
, dove r.obj.Request.Object.Object.Spec.Template.Spec.Containers
è una slice.
Access può anche chiamare funzioni semplici che non hanno parametri e restituiscono un singolo valore. L'esempio example/container_resource_limit/model.yaml
dimostra questo:
[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
parseFloat(access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Resources","Limits","cpu","Value")) >= parseFloat(p.cpu) && \
parseFloat(access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Resources","Limits","memory","Value")) >= parseFloat(p.memory)
In questo matcher, access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Resources","Limits","cpu","Value")
è equivalente a r.obj.Request.Object.Object.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"].Value()
, dove r.obj.Request.Object.Object.Spec.Template.Spec.Containers[0].Resources.Limits
è una mappa, e Value()
è una funzione semplice che non ha parametri e restituisce un singolo valore.
4.2.1.2 accessWithWildcard
A volte, potresti avere una richiesta del genere: tutti gli elementi in un array devono avere un prefisso "aaa". Tuttavia, Casbin non supporta i cicli for
. Con accessWithWildcard
e la funzionalità di "espansione mappa/slice", puoi facilmente implementare una tale richiesta.
Ad esempio, supponiamo che a.b.c
sia un array [aaa,bbb,ccc,ddd,eee]
, allora il risultato di accessWithWildcard(a,"b","c","*")
sarà una slice [aaa,bbb,ccc,ddd,eee]
. Utilizzando il carattere jolly *
, la slice viene espansa.
Allo stesso modo, il carattere jolly può essere utilizzato più di una volta. Ad esempio, il risultato di accessWithWildcard(a,"b","c","*","*")
sarà [a.b.c[0][0], a.b.c[0][1], ..., a.b.c[1][0], a.b.c[1][1], ...]
.
4.2.1.3 Funzioni che supportano argomenti a lunghezza variabile
Nell'espressione dell'evaluator di Casbin, quando un parametro è un array, verrà automaticamente espanso come argomento a lunghezza variabile. Utilizzando questa funzionalità per supportare l'espansione di array/slice/mappe, abbiamo anche integrato diverse funzioni che accettano un array/slice come parametro:
- contain(): accetta più parametri e restituisce se qualche parametro (eccetto l'ultimo parametro) è uguale all'ultimo parametro.
- split(a,b,c...,sep,index): restituisce una slice che contiene
[splits(a,sep)[index], splits(b,sep)[index], splits(a,sep)[index], ...]
. len()
: restituisce la lunghezza dell'argomento a lunghezza variabile.matchRegex(a, b, c..., regex)
: restituisce se tutti i parametri dati (a
,b
,c
, ...) corrispondono alla regex data.
Ecco un esempio in example/disallowed_tag/model.yaml
:
[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
contain(split(accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image"),":",1) , p.obj)
Supponendo che accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers, "*", "Image")
restituisca ["a:b", "c:d", "e:f", "g:h"]
, poiché splits supporta argomenti a lunghezza variabile e esegue l'operazione di split su ciascun elemento, verrà selezionato e restituito l'elemento all'indice 1. Pertanto, split(accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers, "*", "Image"), ":", 1)
restituisce ["b", "d", "f", "h"]
. E contain(split(accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers, "*", "Image"), ":", 1), p.obj)
restituisce se p.obj
è contenuto in ["b", "d", "f", "h"]
.
4.2.1.2 Funzioni di Conversione del Tipo
ParseFloat()
: Converte un numero intero in un float (ciò è necessario perché qualsiasi numero utilizzato in un confronto deve essere convertito in un float).ToString()
: Converte un oggetto in una stringa. Questo oggetto deve avere un tipo di base stringa (ad esempio, un oggetto di tipoXXX
quando c'è una dichiarazionetype XXX string
).- IsNil(): Restituisce se il parametro è nil.
5. Impostazioni Avanzate
5.1 Informazioni sui Certificati
In Kubernetes (k8s), è obbligatorio che un webhook utilizzi HTTPS. Ci sono due approcci per raggiungere questo obiettivo:
- Utilizzare certificati autofirmati (gli esempi in questo repository utilizzano questo metodo)
- Utilizzare un certificato normale
5.1.1 Certificati autofirmati
Utilizzare un certificato autofirmato significa che l'Autorità di Certificazione (CA) che emette il certificato non è una delle CA ben note. Pertanto, devi far conoscere questa CA a k8s.
Attualmente, l'esempio in questo repository utilizza una CA autocostruita, la cui chiave privata e il certificato sono memorizzati rispettivamente in config/certificate/ca.crt
e config/certificate/ca.key
. Il certificato per il webhook è config/certificate/server.crt
, che è emesso dalla CA autocostruita. I domini di questo certificato sono "webhook.domain.local" (per il webhook esterno) e "casbin-webhook-svc.default.svc" (per il webhook interno).
Le informazioni sulla CA vengono passate a k8s tramite i file di configurazione del webhook. Sia config/webhook_external.yaml
che config/webhook_internal.yaml
hanno un campo chiamato "CABundle", che contiene una stringa codificata in base64 del certificato della CA.
Nel caso in cui sia necessario modificare il certificato/dominio (ad esempio, se desideri inserire questo webhook in un altro namespace di k8s utilizzando un webhook interno, o se desideri modificare il dominio utilizzando un webhook esterno), è necessario seguire le seguenti procedure:
Generare una nuova CA:
Generare la chiave privata per la CA fittizia:
openssl genrsa -des3 -out ca.key 2048
Rimuovi la protezione tramite password della chiave privata:
openssl rsa -in ca.key -out ca.key
Genera una chiave privata per il server webhook:
openssl genrsa -des3 -out server.key 2048
openssl rsa -in server.key -out server.keyUtilizza la CA autogenerata per firmare il certificato per il webhook:
Copia il file di configurazione di openssl del tuo sistema per uso temporaneo. Puoi scoprire la posizione del file di configurazione eseguendo
openssl version -a
, di solito chiamatoopenssl.cnf
.Nel file di configurazione:
Trova il paragrafo
[req]
e aggiungi la seguente riga:req_extensions = v3_req
Trova il paragrafo
[v3_req]
e aggiungi la seguente riga:subjectAltName = @alt_names
Aggiungi le seguenti righe al file:
[alt_names]
DNS.2=<The domain you want>Nota: Sostituisci 'casbin-webhook-svc.default.svc' con il nome del servizio reale del tuo servizio se decidi di modificare il nome del servizio.
Utilizza il file di configurazione modificato per generare un file di richiesta di certificato:
openssl req -new -nodes -keyout server.key -out server.csr -config openssl.cnf
Utilizza la CA auto-prodotta per rispondere alla richiesta e firmare il certificato:
openssl x509 -req -days 3650 -in server.csr -out server.crt -CA ca.crt -CAkey ca.key -CAcreateserial -extensions v3_req -extensions SAN -extfile openssl.cnf
Sostituisci il campo 'CABundle': Aggiorna questo campo con il nuovo certificato.
Se stai utilizzando helm, è necessario applicare modifiche simili ai grafici helm.
5.1.2 Certificati legali
Se utilizzi certificati legali, non è necessario seguire tutte queste procedure. Rimuovi il campo "CABundle" in config/webhook_external.yaml
e config/webhook_internal.yaml
, e modifica il dominio in questi file con il dominio di tua proprietà.