Alfabeto della sicurezza in Kubernetes: autenticazione, autorizzazione, audit

Alfabeto della sicurezza in Kubernetes: autenticazione, autorizzazione, audit

Prima o poi, in ogni sistema sorge la questione della sicurezza: garanzia di autenticazione, separazione dei diritti, audit e altre esigenze. Per Kubernetes sono già state sviluppate numerose soluzioni, che consentono di raggiungere la conformità agli standard anche in ambienti molto esigenti... Questo materiale è dedicato agli aspetti fondamentali della sicurezza, implementati nelle funzionalità integrate di K8s. In particolare, sarà utile a chi inizia ad avvicinarsi a Kubernetes, come punto di partenza per approfondire le questioni relative alla sicurezza.

Autenticazione

In Kubernetes esistono due tipi di utenti:

  • Service Accounts — account gestiti dall'API Kubernetes;
  • Utenti — utenti "normali", gestiti da servizi esterni e indipendenti.

La principale differenza tra questi tipi è che per i Service Accounts esistono oggetti speciali nell'API Kubernetes (sono chiamati proprio così — ServiceAccounts), che sono legati a uno spazio dei nomi e a un insieme di credenziali memorizzate nel cluster in oggetti di tipo Secrets. Tali utenti (Service Accounts) sono principalmente progettati per gestire i diritti di accesso all'API Kubernetes dei processi in esecuzione nel cluster Kubernetes.

Gli utenti comuni non hanno record nell'API di Kubernetes: la loro gestione deve essere effettuata da meccanismi esterni. Sono destinati a persone o processi che operano al di fuori del cluster.

Ogni richiesta all'API è collegata a un Service Account, a un Utente, oppure è considerata anonima.

Le credenziali di autenticazione dell'utente includono:

  • Nome utente — nome utente (sensibile al maiuscolo/minuscolo!);
  • UID — stringa di identificazione dell'utente leggibile dalla macchina, che è "più coerente e unica rispetto al nome utente";
  • Gruppi — elenco dei gruppi cui appartiene l'utente;
  • Extra — campi aggiuntivi che possono essere utilizzati dal meccanismo di autorizzazione.

Kubernetes può utilizzare un'ampia varietà di meccanismi di autenticazione: certificati X509, token Bearer, proxy di autenticazione, HTTP Basic Auth. Questi meccanismi possono implementare un'ampia gamma di schemi di autorizzazione: da un file statico di password a OpenID OAuth2.

Inoltre, è consentito l'uso di più schemi di autorizzazione simultaneamente. Per impostazione predefinita, nel cluster sono utilizzati:

  • token di account di servizio — per gli Account di Servizio;
  • X509 — per gli Utenti.

La questione della gestione degli ServiceAccount esula da questo articolo; per chi desidera approfondire, consiglio di iniziare dalla pagina della documentazione ufficiale. Approfondiremo il funzionamento dei certificati X.509.

Certificati per utenti (X.509)

Il metodo classico per lavorare con i certificati prevede:

  • generazione della chiave:
    mkdir -p ~/mynewuser/.certs/
    openssl genrsa -out ~/.certs/mynewuser.key 2048
  • generazione della richiesta di certificato:
    openssl req -new -key ~/.certs/mynewuser.key -out ~/.certs/mynewuser.csr -subj "/CN=mynewuser/O=company"
  • elaborazione della richiesta di certificato utilizzando le chiavi CA del cluster Kubernetes, ottenimento del certificato dell'utente (per ottenere il certificato è necessario utilizzare un account che abbia accesso alla chiave del centro di certificazione del cluster Kubernetes, che per default si trova in /etc/kubernetes/pki/ca.key):
    openssl x509 -req -in ~/.certs/mynewuser.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out ~/.certs/mynewuser.crt -days 500
  • creazione del file di configurazione:
    • descrizione del cluster (specificare l'indirizzo e la posizione del file certificato CA specifica per l'installazione del cluster):
      kubectl config set-cluster kubernetes --certificate-authority=/etc/kubernetes/pki/ca.crt --server=https://192.168.100.200:6443
    • oppure — come nonopzione consigliata: non è necessario specificare il certificato radice (in tal caso, kubectl non verificherà la correttezza dell'api-server del cluster):
      kubectl config set-cluster kubernetes --insecure-skip-tls-verify=true --server=https://192.168.100.200:6443
    • aggiunta dell'utente al file di configurazione:
      kubectl config set-credentials mynewuser --client-certificate=.certs/mynewuser.crt --client-key=.certs/mynewuser.key
    • aggiunta del contesto:
      kubectl config set-context mynewuser-context --cluster=kubernetes --namespace=target-namespace --user=mynewuser
    • impostazione del contesto predefinito:
      kubectl config use-context mynewuser-context

Dopo le operazioni sopra indicate, nel file .kube/config verrà creato un config del tipo:

apiVersion: v1
clusters:
- cluster:
    certificate-authority: /etc/kubernetes/pki/ca.crt
    server: https://192.168.100.200:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    namespace: target-namespace
    user: mynewuser
  name: mynewuser-context
current-context: mynewuser-context
kind: Config
preferences: {}
users:
- name: mynewuser
  user:
    client-certificate: /home/mynewuser/.certs/mynewuser.crt
    client-key: /home/mynewuser/.certs/mynewuser.key

Per facilitare il trasferimento della configurazione tra account e server, è utile modificare i valori delle seguenti chiavi:

  • certificate-authority
  • client-certificate
  • client-key

Per farlo, è possibile codificare i file indicati utilizzando base64 e scriverli nella configurazione, aggiungendo il suffisso nel nome delle chiavi -data, cioè ottenendo certificate-authority-data e così via.

Certificati con kubeadm

Con il rilascio di Kubernetes 1.15 la gestione dei certificati è diventata notevolmente più semplice grazie al supporto in versione alpha nel tool kubeadm. Ad esempio, ecco come potrebbe apparire ora la generazione del file di configurazione con le chiavi dell'utente:

kubeadm alpha kubeconfig user --client-name=mynewuser --apiserver-advertise-address 192.168.100.200

NB: Indirizzo advertise può essere visualizzato nella configurazione dell'api-server, che per default si trova in /etc/kubernetes/manifests/kube-apiserver.yaml.

La configurazione risultante verrà stampata in stdout. Deve essere salvata nella ~/.kube/config account dell'utente o in un file specificato nella variabile d'ambiente KUBECONFIG.

Approfondire

Per coloro che desiderano approfondire i temi descritti:

Autenticazione

L'account autorizzato predefinito non ha diritti per le azioni nel cluster. Per concedere permessi, Kubernetes implementa un meccanismo di autorizzazione.

Fino alla versione 1.6, in Kubernetes veniva utilizzato un tipo di autorizzazione chiamato ABAC (Controllo degli accessi basato sugli attributi). Maggiori dettagli possono essere trovati in documentazione ufficiale. Attualmente questo approccio è considerato obsoleto (legacy), tuttavia puoi ancora utilizzarlo insieme ad altri tipi di autorizzazione.

Il metodo attuale (e più flessibile) per la gestione dei diritti di accesso al cluster si chiama RBAC (Controllo degli accessi basato sui ruoli). È stato dichiarato stabile dalla versione Kubernetes 1.8. RBAC implementa un modello di permessi in cui è vietato tutto ciò che non è esplicitamente consentito.
Per abilitare RBAC, è necessario avviare il server API di Kubernetes con il parametro --authorization-mode=RBAC. I parametri sono impostati nel manifesto di configurazione del server API, che di solito si trova nel percorso /etc/kubernetes/manifests/kube-apiserver.yaml, nella sezione comando. Tuttavia, RBAC è di default già abilitato, quindi probabilmente non c'è motivo di preoccuparsene: puoi verificare questo valore authorization-mode (nel già citato kube-apiserver.yaml). Tra i suoi valori potrebbero esserci anche altri tipi di autorizzazione (node, webhook, always allow), ma approfondiremo questi argomenti al di fuori di questo materiale.

A proposito, abbiamo già pubblicato articolo un racconto abbastanza dettagliato sui principi e sulle peculiarità dell'utilizzo di RBAC, quindi mi limiterò a un breve elenco delle basi e degli esempi.

Per gestire l'accesso in Kubernetes tramite RBAC, si utilizzano le seguenti entità API:

  • Ruolo e ClusterRole — ruoli che servono a definire i diritti di accesso:
  • Ruolo permette di descrivere i diritti all'interno di uno spazio dei nomi;
  • ClusterRole — a livello di cluster, inclusi oggetti specifici per il cluster come nodi, URL non risorse (cioè non correlati alle risorse di Kubernetes — ad esempio, /version, /logs, /api*);
  • RoleBinding e ClusterRoleBinding — serve per legare Ruolo e ClusterRole a un utente, a un gruppo di utenti o a un ServiceAccount.

Le entità Role e RoleBinding sono limitate all'ambito del namespace, cioè devono risiedere all'interno di uno stesso spazio dei nomi. Tuttavia, il RoleBinding può riferirsi a un ClusterRole, permettendo così di creare un insieme di autorizzazioni standard e gestire l'accesso tramite esse.

I ruoli descrivono i diritti attraverso set di regole che contengono:

  • gruppi API — vedi. la documentazione ufficiale per apiGroups e output kubectl api-resources;
  • risorse (risorse: pod, namespace, deployment e simili);
  • verbi (verbi: set, update e simili).
  • nomi delle risorse (resourceNames) — per il caso in cui sia necessario fornire accesso a una particolare risorsa e non a tutte le risorse di quel tipo.

Un'analisi più dettagliata dell'autenticazione in Kubernetes può essere trovata nella pagina documentazione ufficiale. In aggiunta a questo, fornirò esempi che illustrano il suo funzionamento.

Esempi di entità RBAC

Semplice Ruolo, che consente di ottenere l'elenco e lo stato dei pod e monitorarli nello spazio dei nomi target-namespace:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: target-namespace
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

Esempio ClusterRole, che consente di ottenere l'elenco e lo stato dei pod e monitorarli in tutto il cluster:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  # non c'è la sezione "namespace" poiché ClusterRole utilizza l'intero cluster
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "watch", "list"]

Esempio RoleBinding, che consente all'utente mynewuser di "leggere" i pod nello spazio dei nomi my-namespace:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: target-namespace
subjects:
- kind: User
  name: mynewuser # il nome utente fa distinzione tra maiuscole e minuscole!
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role # qui deve essere “Role” o “ClusterRole”
  name: pod-reader # nome del Role, che si trova nello stesso namespace,
                   # o nome del ClusterRole, il cui uso
                   # vogliamo autorizzare per l'utente
  apiGroup: rbac.authorization.k8s.io

Audit degli eventi

Schematicamente, l'architettura di Kubernetes può essere rappresentata come segue:

Alfabeto della sicurezza in Kubernetes: autenticazione, autorizzazione, audit

Il componente chiave di Kubernetes responsabile dell'elaborazione delle richieste è api-server. Tutte le operazioni sul cluster passano attraverso di esso. Maggiori dettagli su questi meccanismi interni possono essere trovati nell'articolo "Cosa accade in Kubernetes quando viene eseguito kubectl run?».

L'audit di sistema è una funzionalità interessante in Kubernetes, che è disabilitata per impostazione predefinita. Permette di registrare tutte le richieste all'API di Kubernetes. Come è facile intuire, tutte le azioni relative al controllo e alla modifica dello stato del cluster vengono eseguite tramite quest'API. Una buona descrizione delle sue funzionalità può essere trovata (come al solito) in documentazione ufficiale K8s. In seguito cercherò di esporre l'argomento in modo più semplice.

Quindi, Per abilitare l'audit, dobbiamo passare al container nell'api-server tre parametri obbligatori, di cui parleremo più dettagliatamente qui sotto:

  • --audit-policy-file=/etc/kubernetes/policies/audit-policy.yaml
  • --audit-log-path=/var/log/kube-audit/audit.log
  • --audit-log-format=json

Oltre questi tre parametri necessari, ci sono molte altre impostazioni relative all'audit, dalla rotazione dei log alle descrizioni dei webhook. Ecco un esempio dei parametri di rotazione dei log:

  • --audit-log-maxbackup=10
  • --audit-log-maxsize=100
  • --audit-log-maxage=7

Ma non ci fermeremo a discuterne nei dettagli — tutte le informazioni possono essere trovate nella documentazione di kube-apiserver.

Come già accennato, tutti i parametri vengono impostati nel manifesto con la configurazione del api-server (per impostazione predefinita /etc/kubernetes/manifests/kube-apiserver.yaml), nella sezione comando. Torniamo ai 3 parametri obbligatori e analizziamoli:

  1. audit-policy-file — il percorso del file YAML contenente la descrizione della politica di auditing. Torneremo al suo contenuto, ma per ora è importante notare che il file deve essere leggibile dal processo dell'api-server. Pertanto è necessario montarlo all'interno del contenitore, per fare ciò si può aggiungere il seguente codice nelle sezioni appropriate della configurazione:
      volumeMounts:
        - mountPath: /etc/kubernetes/policies
          name: policies
          readOnly: true
      volumes:
      - hostPath:
          path: /etc/kubernetes/policies
          type: DirectoryOrCreate
        name: policies
  2. audit-log-path — il percorso del file di log. Anche questo percorso deve essere accessibile al processo dell'api-server, quindi lo descriviamo analogamente per il montaggio:
      volumeMounts:
        - mountPath: /var/log/kube-audit
          name: logs
          readOnly: false
      volumes:
      - hostPath:
          path: /var/log/kube-audit
          type: DirectoryOrCreate
        name: logs
  3. audit-log-format — il formato del log di auditing. Per impostazione predefinita è json, ma è disponibile anche un formato testuale obsoleto (legacy).

Politica di auditing

Ora parliamo del file menzionato che descrive la politica di logging. Il primo concetto di audit policy è livello, livello di logging. Le opzioni sono le seguenti:

  • Nessuno — non loggare;
  • Metadata — registrare i metadati della richiesta: utente, ora della richiesta, risorsa target (pod, namespace, ecc.), tipo di azione (verb) e così via;
  • Richiesta — registrare i metadati e il corpo della richiesta;
  • RequestResponse — registrare i metadati, il corpo della richiesta e il corpo della risposta.

Gli ultimi due livelli (Richiesta e RequestResponse) non registrano le richieste che non accedono a risorse (accessi a cosiddetti URL non risorse).

Inoltre, tutte le richieste passano attraverso diverse fasi:

  • RequestReceived — fase in cui la richiesta è stata ricevuta dal gestore e non è stata ancora inoltrata ulteriormente nella catena di gestori;
  • ResponseStarted — intestazioni della risposta inviate, ma prima dell'invio del corpo della risposta. Viene generato per richieste lunghe (ad esempio, watch);
  • ResponseComplete — corpo della risposta inviato, non ci saranno ulteriori informazioni inviate;
  • Panic — eventi generati quando viene rilevata una situazione anomala.

Per saltare alcune fasi, è possibile utilizzare omitStages.

Nel file di politica possiamo descrivere diverse sezioni con vari livelli di registrazione. Verrà applicata la prima regola appropriata trovata nella descrizione della policy.

Il demone kubelet monitora i cambiamenti del manifesto con la configurazione dell'api-server e, quando ne rileva, riavvia il contenitore con l'api-server. Ma c'è un dettaglio importante: le modifiche nel file di policy verranno ignorate da esso. Dopo aver apportato modifiche al file di policy, sarà necessario riavviare manualmente l'api-server. Poiché l'api-server è eseguito come static pod, il comando kubectl delete non porterà al suo riavvio. Sarà necessario eseguire manualmente docker stop sui kube-master dove è stata modificata la policy di audit:

docker stop $(docker ps | grep k8s_kube-apiserver | awk '{print $1}')

Quando l'audit è abilitato, è importante ricordare che c'è un aumento del carico sul kube-apiserver. In particolare, aumenta il consumo di memoria per memorizzare il contesto delle richieste. La registrazione nei log inizia solo dopo l'invio dell'intestazione della risposta. Inoltre, il carico dipende dalla configurazione della policy di audit.

Esempi di policy

Esaminiamo la struttura dei file di policy con degli esempi.

Ecco un semplice file policy, per registrare tutto a livello Metadata:

apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata

In policy si può specificare un elenco di utenti (Utenti e ServiceAccounts) e gruppi di utenti. Ad esempio, in questo modo ignoreremo gli utenti di sistema, ma registreremo tutto il resto a livello Richiesta:

apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  - level: None
    userGroups:
      - "system:serviceaccounts"
      - "system:nodes"
    users:
      - "system:anonymous"
      - "system:apiserver"
      - "system:kube-controller-manager"
      - "system:kube-scheduler"
  - level: Request

C'è anche la possibilità di descrivere i target:

  • spazi dei nomi (namespaces);
  • verbi (verbi: get, update, elimina e altri);
  • risorse (risorse, e precisamente: pod, configmaps ecc.) e gruppi di risorse (apiGroups).

Attenzione! Le risorse e i gruppi di risorse (gruppi API, ovvero apiGroups), così come le loro versioni installate nel cluster, possono essere ottenuti tramite i comandi:

kubectl api-resources
kubectl api-versions

La seguente audit policy è fornita come dimostrazione delle migliori pratiche nella documentazione di Alibaba Cloud:

apiVersion: audit.k8s.io/v1beta1
kind: Policy
# Non registrare la fase RequestReceived
omitStages:
  - "RequestReceived"
rules:
  # Non registrare eventi considerati di poco conto e non pericolosi:
  - level: None
    users: ["system:kube-proxy"]
    verbs: ["watch"]
    resources:
      - group: "" # questo è il gruppo api con un nome vuoto, a cui appartengono
                  # le risorse fondamentali di Kubernetes, chiamate “core”
        resources: ["endpoints", "services"]
  - level: None
    users: ["system:unsecured"]
    namespaces: ["kube-system"]
    verbs: ["get"]
    resources:
      - group: "" # core
        resources: ["configmaps"]
  - level: None
    users: ["kubelet"]
    verbs: ["get"]
    resources:
      - group: "" # core
        resources: ["nodes"]
  - level: None
    userGroups: ["system:nodes"]
    verbs: ["get"]
    resources:
      - group: "" # core
        resources: ["nodes"]
  - level: None
    users:
      - system:kube-controller-manager
      - system:kube-scheduler
      - system:serviceaccount:kube-system:endpoint-controller
    verbs: ["get", "update"]
    namespaces: ["kube-system"]
    resources:
      - group: "" # core
        resources: ["endpoints"]
  - level: None
    users: ["system:apiserver"]
    verbs: ["get"]
    resources:
      - group: "" # core
        resources: ["namespaces"]
  # Non registrare richieste a URL di sola lettura:
  - level: None
    nonResourceURLs:
      - /healthz*
      - /version
      - /swagger*
  # Non registrare messaggi relativi a risorse di tipo “eventi”:
  - level: None
    resources:
      - group: "" # core
        resources: ["events"]
  # Le risorse di tipo Secret, ConfigMap e TokenReview possono contenere dati
  # sensibili, quindi registriamo solo i metadati delle relative richieste
  - level: Metadata
    resources:
      - group: "" # core
        resources: ["secrets", "configmaps"]
      - group: authentication.k8s.io
        resources: ["tokenreviews"]
  # Le azioni di tipo get, list e watch possono essere onerose; non le registriamo
  - level: Request
    verbs: ["get", "list", "watch"]
    resources:
      - group: "" # core
      - group: "admissionregistration.k8s.io"
      - group: "apps"
      - group: "authentication.k8s.io"
      - group: "authorization.k8s.io"
      - group: "autoscaling"
      - group: "batch"
      - group: "certificates.k8s.io"
      - group: "extensions"
      - group: "networking.k8s.io"
      - group: "policy"
      - group: "rbac.authorization.k8s.io"
      - group: "settings.k8s.io"
      - group: "storage.k8s.io"
  # Livello di registrazione predefinito per le risorse standard dell'API
  - level: RequestResponse
    resources:
      - group: "" # core
      - group: "admissionregistration.k8s.io"
      - group: "apps"
      - group: "authentication.k8s.io"
      - group: "authorization.k8s.io"
      - group: "autoscaling"
      - group: "batch"
      - group: "certificates.k8s.io"
      - group: "extensions"
      - group: "networking.k8s.io"
      - group: "policy"
      - group: "rbac.authorization.k8s.io"
      - group: "settings.k8s.io"
      - group: "storage.k8s.io"
  # Livello di registrazione predefinito per tutte le altre richieste
  - level: Metadata

Un altro buon esempio di policy di audit è il profilo utilizzato in GCE.

Per una reazione rapida agli eventi di audit, c'è la possibilità di descrivere un webhook. Questa questione è trattata in documentazione ufficiale, la lascio al di fuori di questo articolo.

Risultati

L'articolo offre una panoramica dei meccanismi di base per garantire la sicurezza nei cluster Kubernetes, permettendo la creazione di account personalizzati per gli utenti, la separazione dei loro diritti e la registrazione delle loro azioni. Spero che sia utile a chi ha affrontato tali questioni in teoria o nella pratica. Consiglio anche di consultare l'elenco di altri materiali sulla sicurezza in Kubernetes, fornito in 'P.S.', dove potreste trovare dettagli interessanti per le vostre problematiche attuali.

P.S.

Leggete anche nel nostro blog:

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster