Sicurezza e Controllo di Accesso
Modello
Una tipica rcichiesta API dal client kubectl
allo API Server di Kubernetes passa possibilmente attraveso alcune fasi di sicurezza:
Le fasi sono:
- Autenticazione
- Autorizzazione RBAC
- Controllo di Accesso
RBAC è il modello di autorizzazjone usato da Kubernetes: Role Based Access Control.
Questo modello implementa il Principio del Minimo Privilegio: tutte le azioni sono proibite a meno che esista una regola che le consenta.
Esempio: L'utente pippo vuole creare un Deployment app1 nel namespace util.
- il modulo di autenticazione determina se pippo è un utente reale o un impostore
- il modulo di autorizzazione determina se pippo ha il permesso di creare un Deloyment nel namespace util
- il controllo di accesso applica le policy di creazione di Deployment
Autenticazione
Livello indicato anche come AuthN (auth-enne).
Le richieste API includono credenziali e il modulo le verifica. Se la verifica fallisce, viene ritornato un errore HTTP 401.
Il modulo non è parte di Kubernetes, che non si occupa di gestione account. Questa è fornita dall'esterno, possibilmente con un plugin, p.es. il Cloud Provider fornisce il plugin e le credenziali di autenticazione.
Le credenziali di autenticazione sono mantenute nel file di kubeconfig, che nel caso di Linux è $HOME/.kube/config
.
less ~/.kube/config
...
users:
- name: kind-kind
user:
client-certificate-data: ...
client-key-data: ...
...
Si può vedere la configurazione più concisamente col comando:
kubectl config view
Nel nostro caso è un Certificato Client, supportato da ogni versione di Kubernetes.
Questo certificato è stato prodotto da Kind in fase di installazione del cluster. Alternativamente si possono usare strumenti esterni per generarlo, e inserirlo nel file di configurazione.
Si può ispezionare il certificato degli utenti installando l'utility jq
:
sudo apt install jq
E dando il comando:
kubectl config view --raw -o json \
| jq ".users[] | select(.name==\"$(kubectl config current-context)\")" \
| jq -r '.user["client-certificate-data"]' \
| base64 -d | openssl x509 -text
Autorizzazione
Abbreviato in AuthZ (auth-zi).
Si occupa di tre aspetti:
- users
- actions
- resuorces
Descrive quali users possono compire quali actions su che tipo di resources.
Un esempio può essere dato dalla seguente tabella:
User (subject) | Action | Resource |
---|---|---|
Bao | create | Pods |
Kalila | list | Deployments |
Josh | delete | ServiceAccounts |
RBAC si basa su due concetti:
- Role - definisce un insieme di permessi
- RoleBinding - concede i permessi a utenti
Role
Un esempio di Manifest che definisce un Role:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: shield
name: read-deployments
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "watch", "list"]
Notare la particolare apiVersion
.
Notare le direttive:
namespace
- si applica a questo spazio nomiapiGroups
- indica la componente gruppo del campoapiVersion
- per esempio sappiamo che i Deployments hanno apiVersion
apps/v1
- se
apiGroups
è omesso o seapiGroups: [""]
, allora è lo apiGroup core, p.es. Services che ha soloapiVersion: v1
- per esempio sappiamo che i Deployments hanno apiVersion
resources
- quali oggetti Kubernetes sono influitiverbs
- quali comandi dikubectl
si possono usare
Per avere una lista dei verbi disponibili per ogni risorsa:
kubectl api-resources --sort-by name -o wide
Nei campi apiGroups
, resources
e verbs
si può mettere un asterisco (*
) che indica tutti i valori.
Per esempio:
...
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
...
RoleBinding
Un Role deve essere collegato ad un utente con un RoleBinding.
Esempio:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-deployments
namespace: shield
subjects:
- kind: User
name: sky
# This is the authenticated user
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: read-deployments
# This is the Role to bind to the user
apiGroup: rbac.authorization.k8s.io
L'utente autenticato "sky" può compiere un comando come: kubectl get deployments -n shield
.
Con adeguata progettazione dei Role e RoleBinding si ottiene che certi utenti autenticati possano compiere solo certe azioni solo su certi oggetti di un determinato spazio nomi.
Cluster Level
Vi sono in realtà 4 oggetti RBAC:
- Roles
- ClusterRoles
- RoleBindings
- ClusterRoleBindings
Roles
e RoleBindings
sono specifici di namespace.
ClusterRoles
e ClusterRoleBindings
si applicano all'intero cluster.
ClusterRole
Esempio:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: read-deployments
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "watch", "list"]
NOTA
Questo è solo un esempio. In generale non è una buona idea modificare i ClusterRole o ClusterRoleBinding se non si procede com molta cautela
Una serie di ClusterRole sono definiti nel cluster. Per vederli:
kubectl get clusterrole
Molti sono creati all'atto dell'installazione del cluster. Alcuni sono stati aggiunti da Manifests.
Per vedere i dettagli di un ClusterRole, p.es.:
kubectl describe clusterrole metallb-system:controller
ClusterRoleBinding
Vedere tutti i ClusterRoleBinding:
kubectl get vlusterrolebinding
Vedere i dettagli di uno, p.es.:
kubectl describe clusterrolebinding system:basic-user
Name: system:basic-user
Labels: kubernetes.io/bootstrapping=rbac-defaults
Annotations: rbac.authorization.kubernetes.io/autoupdate: true
Role:
Kind: ClusterRole
Name: system:basic-user
Subjects:
Kind Name Namespace
---- ---- ---------
Group system:authenticated
Sequenza
Il cluster ispeziona il certificato dell'utente che accede, ricercando il campo "Subject:". Lo possiamo vedere da:
kubectl config view --raw -o json \
| jq ".users[] | select(.name==\"$(kubectl config current-context)\")" \
| jq -r '.user["client-certificate-data"]' \
| base64 -d | openssl x509 -text | grep "Subject:"
Subject: O = system:masters, CN = kubernetes-admin
Ne estrae la proprietà O
che ha valore system:masters
. Questo è un gruppo predefinito che equivale agli amministratori globali di sistema.
Il ClusterRoleBinding corrispondente, predefinito, è cluster-admin
. Lo si può vedere con:
kubectl describe clusterrolebinding cluster-admin
Name: cluster-admin
Labels: kubernetes.io/bootstrapping=rbac-defaults
Annotations: rbac.authorization.kubernetes.io/autoupdate: true
Role:
Kind: ClusterRole
Name: cluster-admin
Subjects:
Kind Name Namespace
---- ---- ---------
Group system:masters
Il ClusterRole associato è cluster-admin
. Possiamo ispezionarlo con:
kubectl describe clusterrole cluster-admin
Name: cluster-admin
Labels: kubernetes.io/bootstrapping=rbac-defaults
Annotations: rbac.authorization.kubernetes.io/autoupdate: true
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
*.* [] [] [*]
[*] [] [*]
Questo equivale in pratica a tutti i permessi su tutti gli spazi nomi.
Controllo Accesso
E' basato su una serie di Policy ed è implementato da degli Admission Controllers.
Vi sono due tipi di Admission Controllers:
- validating - controllano la validità di una richiesta ma non possono cambiarla
- mutating - possono modificare una richiesta
Gli Admission Controllers di tipo Mutating sono eseguiti per primi.
Un'implementazione di cluster ha un numero considerevole di Admission Controllers. Sono controllati da un numero considerevole di settaggi nei Manifest delle risorse gestite.
Per esempio, il settaggio .spec.containers.imagePullPolicy
corrisponde al controller AlwaysPullImages
.
La possibilità di loro gestione è determinata dall'implementazione del cluster, ed è di solito eseguita col comando kube-apiserver
.
Nell'implementazione cluster Kind questo comando non è direttamente usabile ma è interno al pod kube-apiserver-kind-control-plane
nello spazio nomi kube-system
.
Per vedere i Controllers installati in Kind, il comando può essere:
kubectl exec kube-apiserver-kind-control-plane \
-n kube-system -- kube-apiserver --help | \
grep enable-admission-plugins | grep enabled | \
cut -d. -f2
Sfortunatamente il controllo fine può essere eseguito solo modificando il codice sorgente di Kind.
Esempi
Il Manifest di creazione del servizio di Metallb fornisce numerosi esempi d'uso di controllo di accesso.
Creazione di ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: metallb
name: controller
namespace: metallb-system
Definizione di ClusterRole
Il ClusterRole è globale di cluster.
Una serie di regole, ciascuna contraddistinta da apiGroups/resources
.
Quali sono i verbi, cioè le azioni, che chi possiede questo ClusterRole può compiere sugli apiGroups/resources
.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app: metallb
name: metallb-system:controller
rules:
- apiGroups:
- ''
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- ''
resources:
- services/status
verbs:
- update
- apiGroups:
- ''
resources:
- events
verbs:
- create
- patch
- apiGroups:
- policy
resourceNames:
- controller
resources:
- podsecuritypolicies
verbs:
- use
Definizione di Role
Locale di namespace. Serie di regole serie di regole, ciascuna contraddistinta da apiGroups/resources
.
Quali sono i verbi, cioè le azioni, che chi possiede questo ClusterRole può compiere sugli apiGroups/resources
.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
app: metallb
name: config-watcher
namespace: metallb-system
rules:
- apiGroups:
- ''
resources:
- configmaps
verbs:
- get
- list
- watch
Chi possiede il ruolo config-watcher
, solo nel namespace metallb-system
: su tutti i ConfigMaps può dare i comandi kubectl get
, kubectl list
e kubectl watch
.
In realtà non sono comandi client di kubectl
, sono verbi della API REST che vengono inviati allo API Server.
Definizione di RoleBinding
Associazione tra ServiceAccount e Role.
Questo RoleBinding si chiama config-watcher
, che è anche il nome di un Role: non confondersi, sono due oggetti diversi.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app: metallb
name: config-watcher
namespace: metallb-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: config-watcher
subjects:
- kind: ServiceAccount
name: controller
- kind: ServiceAccount
name: speaker
DaemonSet
Finalmente il Daemonset contiene specifiche di chi può operare.
Contiene anche molti altri elementi interessanti, utili per imparare seguendo gli esempi.
Prima l'intera specifica:
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
app: metallb
component: speaker
name: speaker
namespace: metallb-system
spec:
selector:
matchLabels:
app: metallb
component: speaker
template:
metadata:
annotations:
prometheus.io/port: '7472'
prometheus.io/scrape: 'true'
labels:
app: metallb
component: speaker
spec:
containers:
- args:
- --port=7472
- --config=config
- --log-level=info
env:
- name: METALLB_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: METALLB_HOST
valueFrom:
fieldRef:
fieldPath: status.hostIP
- name: METALLB_ML_BIND_ADDR
valueFrom:
fieldRef:
fieldPath: status.podIP
# needed when another software is also using memberlist / port 7946
# when changing this default you also need to update the container ports definition
# and the PodSecurityPolicy hostPorts definition
#- name: METALLB_ML_BIND_PORT
# value: "7946"
- name: METALLB_ML_LABELS
value: "app=metallb,component=speaker"
- name: METALLB_ML_SECRET_KEY
valueFrom:
secretKeyRef:
name: memberlist
key: secretkey
image: quay.io/metallb/speaker:v0.12.1
name: speaker
ports:
- containerPort: 7472
name: monitoring
- containerPort: 7946
name: memberlist-tcp
- containerPort: 7946
name: memberlist-udp
protocol: UDP
livenessProbe:
httpGet:
path: /metrics
port: monitoring
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
readinessProbe:
httpGet:
path: /metrics
port: monitoring
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_RAW
drop:
- ALL
readOnlyRootFilesystem: true
hostNetwork: true
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: speaker
terminationGracePeriodSeconds: 2
La linea:
serviceAccountName: speaker
specifica che questo DaemonSet opera come account Speaker
, definito sopra, che ha sicuramente un RoleBinding ad un Role, e quest'ultimo lista le azioni che può compiere sulle varie risorse.
Le linee:
nodeSelector:
kubernetes.io/os: linux
richiedono che il sistema operativo sottostante sia Linux.
Le linee:
tolerations:
- effect: NoSchedule
key: node-role.kubernetes.io/master
operator: Exists
richiedono che il pod del DaemonSet corrente non venga schedulato sul Master del cluster.
Il Security Context del contenitore del pod è particolarmente interessante:
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_RAW
drop:
- ALL
readOnlyRootFilesystem: true
L'account che opera i pod di questo DaemonSet, che è speaker
:
- non può dare comandi equivalenti a
sudo
che compiano un Privilege Escalation - se usa il filesystem di root (quello del container), è in sola lettura
- tutti i capabilities sono tolti eccetto NET_RAW, evidentemente il programma inserito nell'immagine usa solo un socket 'Raw', come fa ad esempio ICMP.
Notare le linee:
- name: METALLB_ML_BIND_ADDR
valueFrom:
fieldRef:
fieldPath: status.podIP
Sono l'assegnazione di valore ad una variabile d'ambiente, prese dal campo status.podIP
. Quando il pod è creato e allocato ad un nodo, il suo indirizzo IP è già stato assegnato.
L'interessante è che questo campo non esiste nel Manifest, che descrive il desired state, ma è parte del current state del pod appena creato.
Notare anche i probe Liveness e Readiness.
Deployment
Il Deployment è anch'esso interessante:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: metallb
component: controller
name: controller
namespace: metallb-system
spec:
revisionHistoryLimit: 3
selector:
matchLabels:
app: metallb
component: controller
template:
metadata:
annotations:
prometheus.io/port: '7472'
prometheus.io/scrape: 'true'
labels:
app: metallb
component: controller
spec:
containers:
- args:
- --port=7472
- --config=config
- --log-level=info
env:
- name: METALLB_ML_SECRET_NAME
value: memberlist
- name: METALLB_DEPLOYMENT
value: controller
image: quay.io/metallb/controller:v0.12.1
name: controller
ports:
- containerPort: 7472
name: monitoring
livenessProbe:
httpGet:
path: /metrics
port: monitoring
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
readinessProbe:
httpGet:
path: /metrics
port: monitoring
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- all
readOnlyRootFilesystem: true
nodeSelector:
kubernetes.io/os: linux
securityContext:
runAsNonRoot: true
runAsUser: 65534
fsGroup: 65534
serviceAccountName: controller
terminationGracePeriodSeconds: 0
Vi sono due securityContext
, uno a livello container e uno a livello pod. Quest'ultimo è:
securityContext:
runAsNonRoot: true
runAsUser: 65534
fsGroup: 65534
serviceAccountName: controller
L'utente controller
, che gestisce questo deployment, se ha interazioni con il Linux sottostante, è mappato all'utente di sistema 65534, gruppo di sistema 65534.
Le annotazioni:
annotations:
prometheus.io/port: '7472'
prometheus.io/scrape: 'true'
vengono usate dall'utility Prometheus se esistente.