Co zrobić gdy nie mamy w kontenerze roota i trzeba debugować sieć

Często debugowanie kontenerów w kubernetesie wymaga przeanalizowania ruchu, jaki wchodzi lub wychodzi do systemu. Nie ma z tym problemu jeśli mamy dostęp do konta administratora u możemy uruchomić netstat lub tcpdump aby popatrzeć w pakiety. Co jeśli nie ma do tego dostępu?

Znajdź proces

Na początek zacznijmy od znalezienia procesu, który obsługuje ruch na porcie 80:

$ ps
bash: ps: command not found

Będzie trudniej… przejdźmy to katalogu /proc. Część z podkatalogów odpowiada procesom. Zwykle w kontenerach oraz w Linuksie proces z PID o numerze 1 odpowiada pierwszemu procesowi w systemie/kontenerze (np. /bin/init). Mamy zatem sporo różnych wpisów w proc:

$ ls -l /proc
total 0
dr-xr-xr-x  9 root  root     0 Dec 19 11:59 1
dr-xr-xr-x  9 nginx nginx    0 Dec 27 08:09 29
dr-xr-xr-x  9 nginx nginx    0 Dec 27 08:09 30
dr-xr-xr-x  9 nginx nginx    0 Dec 27 08:09 31
dr-xr-xr-x  9 nginx nginx    0 Dec 27 08:09 32
dr-xr-xr-x  9 root  root     0 Dec 27 08:05 33
dr-xr-xr-x  9 root  root     0 Dec 27 08:06 55
dr-xr-xr-x  9 nginx nginx    0 Dec 27 08:09 56
dr-xr-xr-x  9 nginx nginx    0 Dec 27 08:09 63
drwxrwxrwt  2 root  root    40 Dec 19 11:59 acpi
dr-xr-xr-x 13 root  root     0 Dec 19 11:59 asound
...

Każdy z nich odpowiada osobnemu procesowi. Po nazwie właściciela można stwierdzić co to za proces, ale aby upewnić się, sprawdźmy wpis exe w takim katalogu:

$ ls -lh /proc/29/exe
lrwxrwxrwx 1 nginx nginx 0 Dec 27 08:10 /proc/29/exe -> /usr/sbin/nginx

exe to link symboliczny do pliku wykonywalnego w systemie plików. Mamy zatem ustalonego podejrzanego oraz jego proces.

Jakie porty są otwarte przez proces

Aby sprawdzić z czego korzysta dany proces zwykle można urzyć lsof lub netstat. W dobrze zabezpieczonych systemach nie mamy takiej możliwości. Zamiast tego możemy podglądnąć listę otwartych prze nginx plików:

$ ls -lh /proc/29/fd/
total 0
lrwx------ 1 nginx nginx 64 Dec 27 08:13 0 -> /dev/null
l-wx------ 1 nginx nginx 64 Dec 27 08:13 1 -> 'pipe:[37844]'
lrwx------ 1 nginx nginx 64 Dec 27 08:13 10 -> 'anon_inode:[eventfd]'
lrwx------ 1 nginx nginx 64 Dec 27 08:13 11 -> 'anon_inode:[eventfd]'
lrwx------ 1 nginx nginx 64 Dec 27 08:13 12 -> 'socket:[40105]'
lrwx------ 1 nginx nginx 64 Dec 27 08:13 13 -> 'socket:[40107]'
l-wx------ 1 nginx nginx 64 Dec 27 08:13 2 -> 'pipe:[37845]'
lrwx------ 1 nginx nginx 64 Dec 27 08:13 3 -> 'socket:[40103]'
l-wx------ 1 nginx nginx 64 Dec 27 08:13 4 -> 'pipe:[37845]'
l-wx------ 1 nginx nginx 64 Dec 27 08:13 5 -> 'pipe:[37844]'
lrwx------ 1 nginx nginx 64 Dec 27 08:13 6 -> 'socket:[40099]'
lrwx------ 1 nginx nginx 64 Dec 27 08:13 7 -> 'socket:[40100]'
lrwx------ 1 nginx nginx 64 Dec 27 08:13 8 -> 'socket:[40102]'
lrwx------ 1 nginx nginx 64 Dec 27 08:13 9 -> 'anon_inode:[eventpoll]'

Pliki z numerami (dokładniej deskryptorami, możliwe, że kojarzysz to z funkcji open w C) 0 i 1 to standardowe wejście i wyjście. Za ich pomocą program komunikuje się z użytkownikiem w konsoli. Tutaj jest wejście jest ustawione na /dev/null a wyjście przekierowane do pipe. To, co nas interesuje, to pliki opisane jako socket:[numer]. Są to połączenia sieciowe. Nazwa symlinka to deskryptor pliku/socketu widziany po stronie aplikacji. Numer widoczny po socket:… to identyfikator obiektu po stronie jądra systemu, tzw. inode.

Jak sprawdzić który inode odpowiada socketowi, nasłuchującemu na porcie 80? Popatrzmy w plik /proc/net/tcp:

$ cat /proc/net/tcp
  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode                                                     
   0: 00000000:0050 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 40099 1 0000000000000000 100 0 0 10 0

Pokazuje on otwarte połączenia sieciowe. Ostatnia kolumna to inode połączenia (socketu). Jeśli poszukasz jej w /proc/29/fd, to znajdziesz podobny wpis, dla deskryptora pliku nr 6. Teraz wystarczy popatrzeć na local_address i zamienić numer portu 0050 na wartość dziesiętną i wyjdzie port 80, na którym słucha nasza aplikacja.

Wszystko to używając jedynie ls oraz cat z konta zwykłego użytkownika, które zwykle nie są usuwane podczas zabezpieczania kontenerów