Dostęp SSH do kontenerów w Kubernetes [PL] 30 September 2024 on Krystian's Keep

W Comtegrze umożliwiamy naszym klientom korzystanie z kontenerów z dostępem do GPU na zasadzie chmury: Comtegra GPU Cloud (CGC). Dla wielu z nich dostęp z poziomu przeglądarki jest wygodny (np. Jupyter Notebook). Z biegiem czasu jednak pojawiły się pytania o bardziej bezpośredni dostęp do powłoki kontenerów. Zainspirowani mechanizmem działania Gita przez SSH, stworzyliśmy rozwiązanie, dzięki któremu użytkownicy mogą zdalnie połączyć się do powłoki swoich kontenerów w Kubernetes. W tym artykule pierwszy raz rzucamy światło na komponenty CGC aby podzielić się efektami naszej pracy z szerszym gronem odbiorców.

Żeby użytkownik mógł bezpiecznie zalogować się do powłoki kontenera, musieliśmy rozwiązać 4 problemy.

  1. Uwierzytelnienie: kto próbuje się zalogować? Czy jest tym, za kogo się podaje?
  2. Autoryzacja: do których kontenerów powinien mieć dostęp?
  3. Powłoka: połączyć użytkownika do powłoki odpowiedniego kontenera.
  4. Zabezpieczenia: użytkownik powinien móc jedynie komunikować się z powłoką kontenera. Pozostałe akcje powinny być zablokowane.

Użytkownicy mogą zalogować się do powłok swoich kontenerów przez SSH. Uwierzytelniani i autoryzowani są za pomocą par kluczy, których publiczną część dodali wcześniej do swojego konta w CGC. Do tego wykorzystujemy opcję AuthorizedKeysCommand serwera OpenSSH (sshd). Kontener wybierają, dodając jego nazwę do polecenia SSH (np. ssh cgc@cgc-api.comtegra.cloud kontener1). Z powłoką łączymy użytkowników za pomocą wywołania systemowego exec i programu kubectl exec.

Uwierzytelnienie

Już od wersji OpenSSH 6.2, opublikowanej w marcu 2013, sshd posiada opcję AuthorizedKeysCommand. Umożliwia ona dynamiczne generowanie list kluczy, które są uprawnione do łączenia się z serwerem. Wcześniej, listy mogły być jedynie ładowane z systemu plików. Oprócz kluczy listy te zawierają opcje, które są przydatne do kontroli dostępu np. wyłączenie przekierowywania portów, agentów i X11, wyłączenie alokacji PTY, wykonywania zawartości plików ~/.ssh/rc.1 Do tych opcji należy też wymuszenie użycia konkretnej powłoki.

W CGC podobnie jak w GitHubie, GitLabie itp. użytkownicy mogą dodać do swojego konta publiczne części kluczy, którymi chcą się posługiwać w komunikacji SSH z platformą. Dzięki temu, możemy zidentyfikować użytkownika na podstawie klucza, jakim próbuje się zalogować. Weryfikacją klucza zajmuje się sshd. Zobaczmy teraz, jak w praktyce możemy uwierzytelnić użytkownika za pomocą opcji AuthorizedKeysCommand na podstawie bazy kluczy publicznych.

W konfiguracji sshd dodajemy następującą linię, która instruuje usługę, aby podczas każdej próby logowania uruchamiała program cgc-keys z argumentami: nazwa użytkownika (%u), typ klucza (%t), klucz (%k).2 To, co ten program wypisze na standardowe wyjście, zostanie potraktowane przez sshd jak zawartość pliku authorized_keys.

AuthorizedKeysCommand /usr/local/bin/cgc-keys "%u" "%t" "%k"

Program cgc-keys najpierw sprawdza, czy użytkownik loguje się jako cgc (ssh cgc@host), a następnie odpytuje bazę danych o użytkownika na podstawie klucza publicznego. Na tym etapie sshd już zweryfikował, że użytkownik posiada prywatną parę do podanego klucza publicznego. Zatem jeśli klucz znaleziono w bazie, to tożsamość pasującego użytkownika można uznać za potwierdzoną.

Przykładowe fragmenty kodu w tym artykule zostały napisane w języku powłoki kompatybilnej z /bin/sh. Obsługa błędów w przykładach została ograniczona dla zwięzłości przykładów.

user="$1"
keytype="$2"
key="$3"

if [ "$user" != "cgc" ]; then
	exit 0
fi

user=$(query_db "username" "$keytype" "$key")
if [ -z "$user" ]; then
	exit 0
fi

Autoryzacja

W CGC użytkownicy mają dostęp do wszystkich kontenerów w przestrzeni nazw, do której należą. Zatem wystarczy, że określimy przestrzeń nazw, do której przyporządkowan=y jest właściciel klucza. Można to zrobić, zastępując zapytanie do bazy poniższym.

namespace=$(query_db "namespace" "$keytype" "$key")
if [ -z "$namespace" ]; then
	exit 0
fi

Powłoka

Na tym etapie użytkownik został uwierzytelniony i autoryzowany do pewnej przestrzeni nazw. Pozostaje rozwiązanie problemu połączenia go do powłoki odpowiedniego kontenera. Tym zajmie się program cgc-shell.

Wspomniałem wcześniej, że program cgc-keys powinien wypisać na standardowe wyjście tekst w formacie authorized_keys. Możemy wykorzystać opcje tego formatu do wymuszenia wykonania programu cgc-shell przez użytkownika. Jeśli wypiszemy linię jak niżej, to użytkownik zostanie zalogowany do serwera, ale zamiast powłoki serwera lub innego, podanego polecenia (np. ssh cgc@host ls) w jego imieniu zostanie uruchomiony program cgc-shell z argumentem $namespace.

echo "command=\"cgc-shell $namespace\" $keytype $key cgc"

Program cgc-keys jest świadomie rozdzielny z programem cgc-shell, gdyż pełnią one inną funkcję. Pierwszy z nich, za pomocą opcji AuthorizedKeysCommandUser uwierzytelnia użytkownika, natomiast drugi stanowi jego powłokę. Strumienie wejścia/wyjścia procesu cgc-shell są połączone z terminalem użytkownika.

Kubernetes umożliwia wykonywanie dowolnych poleceń w kontenerach za pomocą polecenia kubectl exec. Możemy zatem wykonać odpowiednie polecenie kubectl w imieniu użytkownika, aby połączyć go do powłoki żądanego kontenera. Podmianę procesu cgc-shell na kubectl z odpowiednimi argumentami można zrealizować wywołaniem systemowym exec.

W CGC użytkownicy wybierają kontener poprzez podanie nazwy deploymentu (rozlokowania). Takiemu deploymentowi w danej chwili prawie zawsze przypada jeden pod, do którego z kolei przypisany jest zwykle jeden kontener. Oznacza to, że po nazwie deploymentu, którą nadaje użytkownik można zidentyfikować odpowiedni kontener. Warto również zauważyć, że polecenie kubectl exec umożliwia podanie deploymentu. Wtedy samo spróbuje zidentyfikować odpowiedni kontener. Jednak najpierw należy zdobyć od użytkownika odpowiednie argumenty, a mianowicie nazwę deploymentu i polecenie, które zostanie wykonane w kontenerze.

Argumenty te użytkownik może przekazać, dopisując je do polecenia ssh (np. ssh cgc@host deployment1 ls -la). sshd przekaże te argumenty do ssh-shell w zmiennej środowiskowej SSH_ORIGINAL_COMMAND. Pozostaje jedynie oddzielenie nazwy deploymentu od polecenia, które ma zostać wykonane w kontenerze. Do tego można wykorzystać set, $1, shift oraz $@ jak pokazano poniżej.

namespace=$1

set -- $SSH_ORIGINAL_COMMAND

resource="$1"
if [ -z "$resource" ]; then
	echo "Error: resource name not specified" >&2
	exit 1
fi
shift

cmd="$@"
if [ -z "$cmd" ]; then
	cmd='/bin/sh'
fi

exec kubectl --namespace "$namespace" \
	exec --stdin --tty deployment/"$resource" -- $cmd

Zabezpieczenia

Kod i konfiguracja w tym artykule stanowią jedynie przykłady i nie były poddane audytowi bezpieczeństwa.

Pozostało zadbać o bezpieczeństwo tego rozwiązania. Jest klika rzeczy, które możemy zrobić w celu jego poprawy. Wspominałem już, że format authorized_keys umożliwia ustawienie dodatkowych opcji. Jedną z nich jest restrict, która włącza wszystkie ograniczenia. Należy do nich zablokowanie alokacji tty, która jest niezbędna do działania interaktywnej powłoki. Możemy jednak użyć opcji restrict i dodać wyjątek dla blokady tty za pomocą opcji pty.

echo "restrict,pty,command=\"cgc-shell $namespace\" $keytype $key cgc"

Oprócz opcji restrict możemy również umocnić konfigurację samego serwera. Poniżej znajdują się przydatne opcje sshd i ich wartości.

PermitRootLogin no
AuthorizedKeysFile /dev/null
AuthorizedKeysCommandUser nobody
PasswordAuthentication no
KbdInteractiveAuthentication no
GatewayPorts no
PermitUserEnvironment no

Programy cgc-keys i cgc-shell wymagają innego zestawu uprawnień. cgc-keys wymaga jedynie dostępu do bazy danych, więc jest uruchamiany jako użytkownik systemowy o właśnie takich uprawnieniach (opcja AuthorizedKeysCommandUser). cgc-shell wymaga natomiast jedynie dostępu do powłoki kontenerów i jest uruchamiany dopiero po pomyślnym uwierzytelnieniu i autoryzacji użytkownika, co zmniejsza powierzchnię ataku na taki system. W tym przykładzie cgc-shell zostanie uruchomiony jako użytkownik systemowy cgc.

A propos uprawnień do powłoki kontenerów, uprawnienia użytkownika Kubernetes, którego używa kubectl powinny być minimalne. W tym przypadku wymagane są jedynie poniższe uprawnienia.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: exec-all
rules:
- {apiGroups: ['apps'], resources: ['deployments'], verbs: ['get']}
- {apiGroups: ['batch'], resources: ['jobs'], verbs: ['get']}
- {apiGroups: [''], resources: ['pods'], verbs: ['list']}
- {apiGroups: [''], resources: ['pods/exec'], verbs: ['create']}

Dodatkowym zabezpieczeniem może być uruchomienie sshd na oddzielnym serwerze, maszynie wirtualnej, kontenerze. Może być to dobrym sposobem na izolację tej usługi i dalsze zmniejszenie powierzchni ataku.

CGC-SSH w praktyce

Pokazaliśmy jak wykorzystać opcję AuthorizedKeysCommand i kilka innych mechanizmów serwera OpenSSH do stworzenia rozwiązania, które umożliwia użytkownikom połączenie się do powłok kontenerów Kubernetes przez klienta SSH. Po wprowadzeniu tej funkcji do CGC jeden z naszych najbardziej wymagających klientów napisał:

[…] uważamy, że narzędzia cgc przez ostatnie parę miesięcy wzbogaciły się o bardzo dużo świetnej z naszego punktu widzenia funkcjonalności […] Co tu dużo mówić - kawał dobrej roboty po Państwa stronie :) Dziękujemy.

Takie wiadomości powodują, że my — inżynierowie — mamy zapał do pracy i do dalszego rozwijania produktów.

Podziękowania

Dziękujemy Drew DeVault, założycielowi platformy sourcehut za publikację artykułu What happens when you push to git.sr.ht, and why was it so slow?, który stanowił dla nas inspirację i źródło wiedzy na temat niestandardowych aplikacji serwera OpenSSH.

Copyright 2024 Comtegra S.A.


  1. https://man.openbsd.org/sshd.8#AUTHORIZED_KEYS_FILE_FORMAT ↩︎

  2. https://man.openbsd.org/sshd_config.5#TOKENS ↩︎

Have a comment on one of my posts? Start a discussion in my public inbox by sending an email to ~krystianch/public-inbox@lists.sr.ht [mailing list etiquette]

Articles from blogs I read

Neurodivergence and accountability in free software

In November of last year, I wrote Richard Stallman’s political discourse on sex, which argues that Richard Stallman, the founder of and present-day voting member of the board of directors of the Free Software Foundation (FSF), endorses and advocates for a ha…

via Drew DeVault's blog September 25, 2024

Status update, September 2024

Hi! Once again, this status update will be rather short due to limited time bandwidth. I hope to be able to allocate a bit more time slots for my open-source projects next month. We’re getting closer to a new Sway release (fingers crossed), with lots of help f…

via emersion September 20, 2024

What's cooking on SourceHut? September 2024

Hello everyone! It has been some time since we last wrote a “What’s cooking” for you. We’d like to resume this tradition as of this September. We haven’t been totally radio silent – you can get caught up on what’s been happening over these past two years rea…

via Blogs on Sourcehut September 16, 2024

Generated by openring