Sytuacja kobiet w IT w 2024 roku
5.08.20209 min
Luc Juggery

Luc JuggeryFreelance Docker & Kubernetes trainer

Uruchamianie kontenera jako użytkownik non-root w Dockerze

Poznaj kilka sposobów na uruchomienie kontenera w Dockerze jako użytkownik, który nie jest rootem.

Uruchamianie kontenera jako użytkownik non-root w Dockerze

Uruchamianie procesów w kontenerze za pomocną użytkownika, który nie jest rootem, jest dobrą praktyką. Odbywa się to zwykle za pomocą instrukcji USER w pliku Dockerfile, ale brak tej instrukcji nie musi jednak oznaczać, że proces jest uruchamiany jako root.

Dlaczego to dobra praktyka?

Root w kontenerze to ten sam (uid 0), który ma maszynie hosta. Jeśli użytkownik zdoła wyjść z aplikacji działającej jako root w kontenerze, może on wtedy uzyskać dostęp do hosta z tym samym użytkownikiem root.

Dostęp byłby jeszcze łatwiejszy do uzyskania, gdyby kontener był uruchamiany z niewłaściwymi flagami lub miał zamontowane foldery hosta z uprawnieniami do zapisu/odczytu.

Uruchamianie kontenera MongoDB

Polecam wypróbowanie Play With Docker (PWD). Jest to platforma, na której możesz przetestować wszystkie najnowsze funkcje Dockera bez konieczności instalowania czegokolwiek lokalnie. Kiedy już jesteś w PWD, możesz utworzyć instancję i poczujesz się tak, jakby się było w powłoce wirtualki Linuksa.


Uwaga:
pod maską będziesz mieć powłokę, ale w kontenerze Alpine, w którym jest zainstalowany demon Dockera. Nazywa się on DinD, czyli dla Dockera w Dockerze, ponieważ demon w kontenerze działa sam. W terminalu uruchommy kontener oparty na obrazie MongoDB:

[node1] (local) [email protected] ~
$ docker container run -d -p 27017:27017 --name mongo mongo:4.0
8cce38822a23bbacb5349c5af63c50f1d2e371029f5b6332b1144fcc4f8cb723


Sprawdź na komputerze hosta, który użytkownik uruchamia proces mongod:

[node1] (local) [email protected] ~
$ ps aux | grep mongo
1143 999 0:00 mongod --bind_ip_all


Z powyższego wyniku można wywnioskować, że użytkownik zidentyfikowany przez uid 999 to ten, który jest właścicielem procesu mongod. Sprawdźmy istniejących użytkowników na hoście:

$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/bin/sh
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/spool/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
postgres:x:70:70::/var/lib/postgresql:/bin/sh
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
dockremap:x:100:101:Linux User,,,:/home/dockremap:/bin/false


Nie ma użytkownika z identyfikatorem uid 999, dlatego w poprzednim poleceniu nie można dopasować żadnej nazwy użytkownika.

Dockerfile

Oto Dockerfile, którego użyłem do stworzenia obrazu bazy danych MongoDB 4.0:

Nie ma w nim instrukcji USER, ale widzimy, że w obrazie tworzony jest użytkownik mongodb, który następnie jest dodawany do grupy o tej samej nazwie utworzonej w tym samym czasie. Służy do tego poniższa instrukcja:

RUN groupadd -r mongodb && useradd -r -g mongodb mongodb


Ponieważ nie jest to określone w instrukcji USER w pliku Dockerfile, użytkownik ten nie jest używany podczas tworzenia obrazu; wszystko odbywa się z rootem. Ale jeśli przyjrzymy się bliżej końcowi pliku Dockerfile, zobaczymy instrukcje ENTRYPOINT i CMD.

ENTRYPOINT ["docker-entrypoint.sh"]

CMD ["mongod"]


Jak zapewne wiesz, połączenie tych dwóch instrukcji definiuje polecenie, które jest wykonywane, gdy uruchamiamy kontener z obrazu mongo. Polecenie wygląda wtedy następująco:

$ docker-entrypoint.sh mongod

ENTRYPOINT

Przyjrzyjmy się teraz kodowi pliku docker-entrypoint.sh:

Początek poniższego kodu jest bardzo interesujący. Jest to część, w której użytkownik wykonujący proces zmienia się z roota na mongodb dzięki narzędziu gosu.

# allow the container to be started with ` — user
# all mongo* commands should be dropped to the correct user
if [[ “$originalArgOne” == mongo* ]] && [ “$(id -u)” = ‘0’ ]; then
if [ “$originalArgOne” = ‘mongod’ ];
then chown -R mongodb /data/configdb /data/db
fi
# make sure we can write to stdout and stderr as “mongodb”
# (for our “initdb” code later; see “ — logpath” below)
chown --dereference mongodb “/proc/$$/fd/1” “/proc/$$/fd/2” || :
exec gosu mongodb “$BASH_SOURCE” “$@”
fi


Uwaga:
w Dockerfile widać, że narzędzie gosu jest jednym z pakietów instalowanych podczas tworzenia obrazu.

Obrazy oparte na Ubuntu

Pierwsza instrukcja w pliku Dockerfile wskazuje na to, że ubuntu:xenial jest obrazem podstawowym, z którego tworzony jest obraz mongo. Uruchomimy teraz interaktywny kontener oparty na Ubuntu i wylistujemy istniejących użytkowników:

$ docker container run -ti ubuntu:xenial

root@9e367c3d9ca1:/# cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
_apt:x:104:65534::/nonexistent:/bin/false


Utwórzmy teraz użytkownika i grupę:

root@9e367c3d9ca1:/# groupadd -r mygrp && useradd -r -g mygrp myuser


i jeszcze raz wymienimy użytkowników:

root@9e367c3d9ca1:/# cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
_apt:x:104:65534::/nonexistent:/bin/false
myuser:x:999:999::/home/myuser:


Widzimy nowego użytkownika utworzonego jako uid 999, który jest numerem UID pierwszego użytkownika utworzonego z nowego obrazu ubuntu:xenial. UID ten jest używany do uruchamiania procesu mongod, jak widzieliśmy wcześniej. Przypomnijmy:

[node1] (local) [email protected] ~
$ ps aux | grep mongo
1143 999 0:00 mongod --bind_ip_all

Obrazy oparte na Alpine

Obrazy aplikacji nie muszą być oparte na ubuntu:xenial. Wiele z nich bazuje na Alpine (niewielka dystrybucja nastawiona na bezpieczeństwo). Dodajmy nowego użytkownika ze świeżego kontenera Alpine i sprawdźmy jego uid.

$ docker container run -ti alpine:3.8
/ # adduser -D myuser
/ # cat /etc/passwd
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/bin/sh
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/spool/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
postgres:x:70:70::/var/lib/postgresql:/bin/sh
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
myuser:x:1000:1000:Linux User,,,:/home/myuser:


Jak widać, identyfikator pierwszego użytkownika w obrazie alpine to 1000, co różni się od uid 999, czyli obrazu ubuntu. Jeśli dodamy użytkownika w obrazie Alpine i uruchomimy proces z tym użytkownikiem (na przykład używając instrukcji USER w pliku Dockerfile), zobaczymy uid 1000 jako właściciela procesu. Spróbujmy.

Użyjmy prostego pliku Dockerfile, który dodaje użytkownika do obrazu Alpine i definiuje podstawowe polecenie 1000 command.

FROM alpine:3.8
RUN adduser -D myuser
USER myuser
ENTRYPOINT [“sleep”]
CMD [“1000”]


Zbudujmy obraz:

$ docker image build -t sleep:1.0 .
Sending build context to Docker daemon 1.775MB
Step 1/5 : FROM alpine:3.8
3.8: Pulling from library/alpine
4fe2ade4980c: Pull complete
Digest: sha256:621c2f39f8133acb8e64023a94dbdf0d5ca81896102b9e57c0dc184cadaf5528
Status: Downloaded newer image for alpine:3.8
— -> 196d12cf6ab1
Step 2/5 : RUN adduser -D myuser
— -> Running in a7474167f27d
Removing intermediate container a7474167f27d
— -> 7a17f0862780
Step 3/5 : USER myuser
— -> Running in b0a7eea711a4
Removing intermediate container b0a7eea711a4
— -> d63533ce5be1
Step 4/5 : ENTRYPOINT [“sleep”]
— -> Running in f0dfc3ea4495
Removing intermediate container f0dfc3ea4495
— -> 763dd8ac4f40
Step 5/5 : CMD [“1000”]
— -> Running in 14db1ea262f9
Removing intermediate container 14db1ea262f9
— -> 978294e76184
Successfully built 978294e76184
Successfully tagged sleep:1.0


Uruchom następnie kontener z nowo utworzonego obrazu:

[node1] (local) [email protected] ~
$ docker container run -d sleep:1.0
534e340780a89b3a86917aff2c20405dadbd7d50cfe5cb03e9cb6786a0517f21


Jeśli sprawdzimy właściciela procesu sleep w hoscie, to zobaczymy, że należy on do użytkownika uid 1000, czyli tego, który stworzył obraz.

[node1] (local) [email protected] ~
$ ps aux | grep sleep
1181 1000 0:00 sleep 1000

Podsumowanie

Mam nadzieję, że powyższe przykłady pomogą w zrozumieniu kilku sposobów, na które można uruchomić kontenerem z użytkownikiem, który nie jest rootem — niezależnie od tego, czy robimy to poprzez instrukcję USER w Dockerfile, czy zmianę użytkownika podczas uruchamiania (zazwyczaj robi się to przy pomocy skryptu entrypoint).

Jest jeszcze jeden sposób, którego nie poruszyliśmy — użycie flagi --user.


Oryginał tekstu w języku angielskim możesz przeczytać tutaj.

<p>Loading...</p>