
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 , 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 . 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
- opis klastra (podaj adres i lokalizację pliku certyfikatu CA dla konkretnej instalacji klastra):
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.keyAby 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 Praca z certyfikatami stała się znacznie prostsza dzięki wersji alfa jej wsparcia w . 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:
- na temat pracy z certyfikatami w oficjalnej dokumentacji Kubernetes;
- , w którym zagadnienie certyfikatów zostało poruszone od strony praktycznej.
- na temat uwierzytelniania w Kubernetes.
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 . 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 (). Została ogłoszona stabilną w wersji . 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ż 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:
-
RoleiClusterRole— role, które służą do opisywania praw dostępu: -
Rolepozwala 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*); -
RoleBindingiClusterRoleBinding— służy do powiązaniaRoleiClusterRolez 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 na apiGroups i wyjście
kubectl api-resources; - zasoby (resources:
pod,namespace,deploymentitp.); - czasowniki (verbs:
set,updateitd.). - 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 . 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.ioAudyt zdarzeń
Schematycznie architekturę Kubernetes można przedstawić w następujący sposób:

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 „».
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 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 .
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:
-
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 -
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 -
audit-log-format— format logu audytu. Domyślnie jest tojson., 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 , 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: RequestIstnieje również możliwość opisywania celów:
- przestrzeni nazw (
namespaces); - czasowniki (verbs:
get,update,usuńi innych); - zasoby (resources, a mianowicie:
pod,configmapsitd.) 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-versionsPoniższa polityka audytu jest podana jako przykład najlepszych praktyk w :
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: MetadataInny dobry przykład polityki audytu — .
Aby szybko reagować na zdarzenia audytu, istnieje możliwość opisania webhooka. To zagadnienie jest omówione w , 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
