Abecadło bezpieczeństwa w Kubernetes: autoryzacja, uwierzytelnianie, audyt

Abecadło bezpieczeństwa w Kubernetes: autoryzacja, uwierzytelnianie, audyt

Prędzej czy później w eksploatacji każdego systemu pojawia się kwestia bezpieczeństwa: zapewnienia uwierzytelniania, podziału uprawnień, audytu i innych zadań. Dla Kubernetes już stworzono wiele rozwiązań, które pozwalają osiągnąć zgodność ze standardami nawet w bardzo wymagających środowiskach… Ten materiał koncentruje się na podstawowych aspektach bezpieczeństwa, zrealizowanych w ramach wbudowanych mechanizmów K8s. W pierwszej kolejności będzie przydatny tym, którzy zaczynają zapoznawać się z Kubernetes, jako punkt wyjścia do studiowania kwestii związanych z bezpieczeństwem.

Autoryzacja

W Kubernetes są dwa typy użytkowników:

  • Konta usługowe — konta zarządzane przez API Kubernetes;
  • Użytkownicy — „normalni” użytkownicy, zarządzani przez zewnętrzne, niezależne usługi.

Podstawowa różnica między tymi typami polega na tym, że dla konta usługowego istnieją specjalne obiekty w API Kubernetes (nazywają się po prostu ServiceAccounts), które są przypisane do przestrzeni nazw i zestawu danych autoryzacyjnych przechowywanych w klastrze w obiektach typu Secrets. Tacy użytkownicy (konta usługowe) są przeznaczeni głównie do zarządzania uprawnieniami dostępu do API Kubernetes procesów działających w klastrze Kubernetes.

Zwykli użytkownicy nie mają wpisów w API Kubernetes: ich zarządzanie powinno odbywać się za pomocą zewnętrznych mechanizmów. Są przeznaczeni dla ludzi lub procesów żyjących poza klastrem.

Każde zapytanie do API jest przypisane albo do konta usługowego, albo do użytkownika, albo jest uznawane za anonimowe.

Dane identyfikacyjne użytkownika obejmują:

  • Nazwa użytkownika — nazwę użytkownika (zależy od wielkości liter!);
  • UID — maszyna-czytelna ciąg identyfikacji użytkownika, który „jest bardziej spójny i unikalny niż nazwa użytkownika”;
  • Groups — lista grup, do których należy użytkownik;
  • Extra — dodatkowe pola, które mogą być używane przez mechanizm autoryzacji.

Kubernetes może korzystać z wielu mechanizmów uwierzytelniania: certyfikaty X509, tokeny typu Bearer, autoryzujące proxy, HTTP Basic Auth. Dzięki tym mechanizmom można zrealizować wiele schematów autoryzacji: od statycznego pliku z hasłami po OpenID OAuth2.

Co więcej, dopuszcza się wykorzystanie wielu schematów autoryzacji jednocześnie. Domyślnie w klastrze używane są:

  • tokeny konta usługowego — dla kont usługowych;
  • X509 — dla użytkowników.

Pytanie o zarządzanie ServiceAccounts wykracza poza zakres tego artykułu, a osobom pragnącym zapoznać się z tym zagadnieniem, polecam rozpocząć od strony oficjalnej dokumentacji. My zajmiemy się bardziej szczegółowo kwestią działania certyfikatów X509.

Certyfikaty dla użytkowników (X.509)

Klasyczny sposób pracy z certyfikatami polega na:

  • generacji klucza:
    mkdir -p ~/mynewuser/.certs/
    openssl genrsa -out ~/.certs/mynewuser.key 2048
  • generacji żądania certyfikatu:
    openssl req -new -key ~/mynewuser/.certs/mynewuser.key -out ~/.certs/mynewuser.csr -subj "/CN=mynewuser/O=company"
  • przetwarzaniu żądania certyfikatu przy użyciu kluczy CA klastra Kubernetes, uzyskaniu certyfikatu użytkownika (aby uzyskać certyfikat, należy użyć konta z dostępem do klucza centrum certyfikacji klastra Kubernetes, który domyślnie znajduje się w /etc/kubernetes/pki/ca.key):
    openssl x509 -req -in ~/mynewuser/.certs/mynewuser.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out ~/mynewuser/.certs/mynewuser.crt -days 500
  • utworzeniu pliku konfiguracyjnego:
    • opis klastra (podaj adres i lokalizację pliku certyfikatu CA dla konkretnej instalacji klastra):
      kubectl config set-cluster kubernetes --certificate-authority=/etc/kubernetes/pki/ca.crt --server=https://192.168.100.200:6443
    • lub — jak niezalecana opcja — można nie podawać certyfikatu głównego (wtedy kubectl nie będzie sprawdzać poprawności api-servera klastra):
      kubectl config set-cluster kubernetes --insecure-skip-tls-verify=true --server=https://192.168.100.200:6443
    • dodaniu użytkownika do pliku konfiguracyjnego:
      kubectl config set-credentials mynewuser --client-certificate=.certs/mynewuser.crt --client-key=.certs/mynewuser.key
    • dodaniu kontekstu:
      kubectl config set-context mynewuser-context --cluster=kubernetes --namespace=target-namespace --user=mynewuser
    • ustawieniu kontekstu domyślnego:
      kubectl config use-context mynewuser-context

Po wykonaniu powyższych operacji w pliku .kube/config zostanie utworzony konfig w następującej formie:

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

Aby ułatwić przenoszenie konfiguracji między kontami i serwerami, przydatne jest edytowanie wartości następujących kluczy:

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

Aby to zrobić, można zakodować wskazane pliki za pomocą base64 i wpisać je w konfiguracji, dodając do nazw kluczy przyrostek -data, tzn. uzyskując certificate-authority-data itp.

Certyfikaty z kubeadm

Z wydaniem Kubernetes 1.15 Praca z certyfikatami stała się znacznie prostsza dzięki wersji alfa jej wsparcia w narzędziu kubeadm. Na przykład, oto jak może wyglądać generowanie pliku konfiguracyjnego z kluczami użytkownika:

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

NB: Wymagany adres reklamowy można zobaczyć w konfiguracji api-server, która domyślnie znajduje się w /etc/kubernetes/manifests/kube-apiserver.yaml.

Wynikowa konfiguracja zostanie wyświetlona w stdout. Należy ją zachować w ~/.kube/config koncie użytkownika lub w pliku wskazanym w zmiennej środowiskowej KUBECONFIG.

Zagłębić się

Dla tych, którzy chcą dokładniej zrozumieć opisane zagadnienia:

Autoryzacja

Domyślne konto autoryzowane nie ma praw do działań w klastrze. W celu nadania uprawnień w Kubernetes wdrożono mechanizm autoryzacji.

Do wersji 1.6 w Kubernetes stosowano typ autoryzacji zwany ABAC (kontrola dostępu oparta na atrybutach). Szczegóły na ten temat znajdziesz w oficjalnej dokumentacji. Aktualnie podejście to uważane jest za przestarzałe (legacy), jednak możesz je nadal wykorzystywać równolegle z innymi typami autoryzacji.

Aktualny (i bardziej elastyczny) sposób podziału praw dostępu do klastra nazywa się RBAC (Kontrola dostępu oparta na rolach). Została ogłoszona stabilną w wersji Kubernetes 1.8. RBAC wdraża model praw, w którym wszystko, co nie jest wyraźnie dozwolone, jest zabronione.
Aby włączyć RBAC, należy uruchomić serwer api Kubernetes z parametrem --authorization-mode=RBAC. Parametry ustawiane są w manifeście konfiguracji api-server, który domyślnie znajduje się pod ścieżką /etc/kubernetes/manifests/kube-apiserver.yaml, w sekcji komenda. Niemniej jednak, RBAC jest domyślnie włączony, więc nie ma się o co martwić: można to sprawdzić po wartości authorization-mode (w już wspomnianym kube-apiserver.yaml). Nawiasem mówiąc, wśród jego wartości mogą się też znaleźć inne typy autoryzacji (node, webhook, zawsze zezwól), ale ich omówienie zostawimy poza zakresem tego materiału.

Tak przy okazji, opublikowaliśmy już artykuł dość szczegółową opowieść o zasadach i cechach pracy z RBAC, więc dalej ograniczę się do krótkiego wymienienia podstawowych zasad i przykładów.

Do zarządzania dostępem w Kubernetes poprzez RBAC używane są następujące jednostki API:

  • Role i ClusterRole — role, które służą do opisywania praw dostępu:
  • Role pozwala opisać uprawnienia w ramach przestrzeni nazw;
  • ClusterRole — w ramach klastra, w tym do obiektów specyficznych dla klastra, takich jak węzły, non-resources urls (tj. niepowiązanych z zasobami Kubernetes — na przykład, /version, /logs, /api*);
  • RoleBinding i ClusterRoleBinding — służy do powiązania Role i ClusterRole z użytkownikiem, grupą użytkowników lub ServiceAccount.

Podmioty Role i RoleBinding są ograniczone do przestrzeni nazw, tj. muszą znajdować się w obrębie jednej przestrzeni nazw. Jednak RoleBinding może odnosić się do ClusterRole, co pozwala na stworzenie zestawu typowych uprawnień i zarządzanie dostępem przy ich użyciu.

Role opisują uprawnienia za pomocą zestawów reguł, które zawierają:

  • grupy API — patrz dokumentację oficjalną na apiGroups i wyjście kubectl api-resources;
  • zasoby (resources: pod, namespace, deployment itp.);
  • czasowniki (verbs: set, update itd.).
  • nazwy zasobów (resourceNames) — w przypadku, gdy trzeba udzielić dostępu do konkretnego zasobu, a nie do wszystkich zasobów tego typu.

Szczegółowe omówienie autoryzacji w Kubernetes można znaleźć na stronie oficjalnej dokumentacji. Zamiast tego (a ściślej mówiąc — w dodatku do tego) podam przykłady, które ilustrują jej działanie.

Przykłady podmiotów RBAC

Prosta Role, które pozwala uzyskać listę i status podów oraz śledzić je w przestrzeni nazw 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"]

Przykład ClusterRole, co pozwala uzyskać listę i status podów oraz śledzić je w całym klastrze:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  # sekcji "namespace" nie ma, ponieważ ClusterRole obejmuje cały klaster
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "watch", "list"]

Przykład RoleBinding, co pozwala użytkownikowi mynewuser »czytać« pody w przestrzeni nazw my-namespace:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: target-namespace
subjects:
- kind: User
  name: mynewuser # nazwa użytkownika wrażliwa na wielkość liter!
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role # tutaj powinno być “Role” lub “ClusterRole”
  name: pod-reader # nazwa Role, która znajduje się w tej samej przestrzeni nazw,
                   # lub nazwa ClusterRole, której
                   # użycie chcemy zezwolić użytkownikowi
  apiGroup: rbac.authorization.k8s.io

Audyt zdarzeń

Schematycznie architekturę Kubernetes można przedstawić w następujący sposób:

Abecadło bezpieczeństwa w Kubernetes: autoryzacja, uwierzytelnianie, audyt

Kluczowy komponent Kubernetes odpowiedzialny za przetwarzanie żądań — api-server. Wszystkie operacje na klastrze przechodzą przez niego. Więcej o tych wewnętrznych mechanizmach można przeczytać w artykule „Co się dzieje w Kubernetes podczas uruchamiania kubectl run?».

Audyt systemu to interesująca funkcja w Kubernetes, która domyślnie jest wyłączona. Pozwala ona na logowanie wszystkich żądań do API Kubernetes. Jak łatwo się domyślić, przez to API wykonywane są wszystkie działania związane z kontrolą i zmianą stanu klastra. Dobre opisy jej możliwości można (jak zwykle) znaleźć w oficjalnej dokumentacji K8s. Następnie postaram się przedstawić temat prostszym językiem.

Otóż Aby włączyć audyt,musimy przekazać kontenerowi w api-server trzy obowiązkowe parametry, o których więcej informacji znajdziesz poniżej:

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

Oprócz tych trzech niezbędnych parametrów istnieje wiele dodatkowych ustawień związanych z audytem: od rotacji logów po opisy webhooków. Przykład parametrów rotacji logów:

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

Jednak nie będziemy się nad tym rozwodzić — wszystkie szczegóły można znaleźć w dokumentacji kube-apiserver.

Jak już wspomniano, wszystkie parametry ustawia się w manifeście konfiguracji api-server (domyślnie /etc/kubernetes/manifests/kube-apiserver.yaml), w sekcji komenda. Wróćmy do trzech obowiązkowych parametrów i przyjrzyjmy się im bliżej:

  1. audit-policy-file — ścieżka do pliku YAML z opisem polityki (policy) audytu. Do jego zawartości jeszcze wrócimy, ale na razie zauważmy, że plik musi być dostępny do odczytu przez proces api-server’a. Dlatego należy zamontować go wewnątrz kontenera, dodając do odpowiednich sekcji konfiguracji następujący kod:
      volumeMounts:
        - mountPath: \/etc\/kubernetes\/policies
          name: policies
          readOnly: true
      volumes:
      - hostPath:
          path: \/etc\/kubernetes\/policies
          type: DirectoryOrCreate
        name: policies
  2. audit-log-path — ścieżka do pliku logu. Ścieżka również musi być dostępna dla procesu api-server’a, dlatego podobnie opisujemy jego montowanie:
      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 — format logu audytu. Domyślnie jest to json., ale dostępny jest także przestarzały format tekstowy (legacy).

Polityka audytu

Teraz o wspomnianym pliku z opisem polityki logowania. Pierwszym pojęciem polityki audytu — jest to level, poziom logowania.Można wyróżnić następujące poziomy:

  • Brak — nie logować;
  • Metadata — logować metadane żądania: użytkownika, czas żądania, docelowy zasób (pod, namespace itd.), typ działania (verb) itd.;
  • Request — logować metadane oraz treść żądania;
  • RequestResponse — logować metadane, treść żądania oraz treść odpowiedzi.

Ostatnie dwa poziomy (Request i RequestResponse) nie logują zapytań, które nie odnosiły się do zasobów (odwołania do tak zwanych non-resources urls).

Ponadto wszystkie zapytania przechodzą przez kilka etapów:

  • RequestReceived — etap, kiedy zapytanie zostało odebrane przez przetwarzający blok i jeszcze nie zostało przekazane dalej w łańcuchu przetwarzania;
  • ResponseStarted — nagłówki odpowiedzi zostały wysłane, ale przed wysłaniem ciała odpowiedzi. Generowane dla długoterminowych zapytań (na przykład, watch);
  • ResponseComplete — ciało odpowiedzi zostało wysłane, więcej informacji nie będzie wysyłane;
  • Panic — zdarzenia są generowane, gdy wykryta zostanie sytuacja awaryjna.

Aby pominąć jakiekolwiek etapy, można użyć omitStages.

W pliku polityki możemy opisać kilka sekcji z różnymi poziomami logowania. Stosowane będzie pierwsze odpowiednie zasady znalezione w opisie policy.

Demon kubelet śledzi zmianę manifestu z konfiguracją api-server i po wykryciu takich zmian ponownie uruchamia kontener z api-server. Ale jest jeden ważny szczegół: zmiany w pliku policy będą przez nie ignorowane. Po wprowadzeniu zmian w pliku policy należy ręcznie zrestartować api-server. Ponieważ api-server jest uruchamiany jako static pod, polecenie kubectl delete nie spowoduje jego ponownego uruchomienia. Należy ręcznie wykonać docker stop na kube-masterach, gdzie zmieniono politykę audytu:

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

Przy włączonym audycie ważne jest, aby pamiętać, że na kube-apiserver obciążenie wzrasta. W szczególności, zwiększa się zużycie pamięci na przechowywanie kontekstu zapytań. Rejestracja w logu rozpoczyna się dopiero po wysłaniu nagłówka odpowiedzi. Obciążenie również zależy od konfiguracji polityki audytu.

Przykłady polityk

Rozważmy strukturę plików policy na przykładach.

Oto prosty plik policy, aby rejestrować wszystko na poziomie Metadata:

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

W policy można określić listę użytkowników (Użytkownicy i ServiceAccounts) oraz grup użytkowników. Na przykład, w ten sposób zignorujemy użytkowników systemowych, ale zarejestrujemy wszystko inne na poziomie Request:

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

Istnieje również możliwość opisywania celów:

  • przestrzeni nazw (namespaces);
  • czasowniki (verbs: get, update, usuń i innych);
  • zasoby (resources, a mianowicie: pod, configmaps itd.) oraz grup zasobów (apiGroups).

Zwróć uwagę! Zasoby i grupy zasobów (grupy API, tzn. apiGroups), a także ich wersje zainstalowane w klastrze, można uzyskać za pomocą poleceń:

kubectl api-resources
kubectl api-versions

Poniższa polityka audytu jest podana jako przykład najlepszych praktyk w dokumentacji Alibaba Cloud:

apiVersion: audit.k8s.io/v1beta1
kind: Policy
# Nie logować etapu RequestReceived
omitStages:
  - "RequestReceived"
rules:
  # Nie logować zdarzeń uznawanych za mało znaczące i niezagrażające:
  - level: None
    users: ["system:kube-proxy"]
    verbs: ["watch"]
    resources:
      - group: "" # to api group z pustą nazwą, któremu podlegają
                  # podstawowe zasoby Kubernetes, zwane “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"]
  # Nie logować dostępu do read-only URLs:
  - level: None
    nonResourceURLs:
      - /healthz*
      - /version
      - /swagger*
  # Nie logować komunikatów dotyczących typu zasobów “zdarzenia”:
  - level: None
    resources:
      - group: "" # core
        resources: ["events"]
  # Zasoby typu Secret, ConfigMap i TokenReview mogą zawierać poufne dane,
  # dlatego logujemy tylko metadane związane z takimi żądaniami
  - level: Metadata
    resources:
      - group: "" # core
        resources: ["secrets", "configmaps"]
      - group: authentication.k8s.io
        resources: ["tokenreviews"]
  # Działania typu get, list i watch mogą być zasobochłonne; nie logujemy ich
  - 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"
  # Poziom logowania domyślnie dla standardowych zasobów 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"
  # Poziom logowania domyślnie dla wszystkich pozostałych żądań
  - level: Metadata

Inny dobry przykład polityki audytu — profil używany w GCE.

Aby szybko reagować na zdarzenia audytu, istnieje możliwość opisania webhooka. To zagadnienie jest omówione w oficjalnej dokumentacji, pozostawię go poza zakresem tego artykułu.

Podsumowanie

Artykuł zawiera przegląd mechanizmów podstawowego zabezpieczenia w klastrach Kubernetes, które umożliwiają tworzenie spersonalizowanych kont użytkowników, dzielenie ich praw oraz rejestrowanie ich działań. Mam nadzieję, że będzie przydatny dla tych, którzy napotykają takie kwestie w teorii lub już w praktyce. Polecam również zapoznać się z listą innych materiałów na temat bezpieczeństwa w Kubernetes, która została podana w "P.S.", być może znajdziesz wśród nich potrzebne szczegóły dotyczące aktualnych problemów.

P.S.

Przeczytaj także na naszym blogu:

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster