Docker i --userns-remap, jak zarządzać uprawnieniami do woluminu w celu udostępniania danych między hostem a kontenerem?

97

W dockerze pliki utworzone w kontenerach mają zazwyczaj nieprzewidywalną własność podczas sprawdzania ich z hosta. Właścicielem plików na woluminie jest domyślnie root (identyfikator UID 0), ale gdy tylko konta użytkowników innych niż root są zaangażowane w kontenerze i zapisują w systemie plików, właściciele stają się mniej lub bardziej losowi z punktu widzenia hosta.

Jest to problem, gdy trzeba uzyskać dostęp do danych woluminu z hosta przy użyciu tego samego konta użytkownika, które wywołuje polecenia dockera.

Typowe obejścia to

  • wymuszanie identyfikatorów użytkowników podczas ich tworzenia w plikach Dockerfiles (nieprzenośnych)
  • przekazanie identyfikatora UID użytkownika hosta do docker runpolecenia jako zmiennej środowiskowej, a następnie uruchomienie niektórych chownpoleceń na woluminach w skrypcie punktu wejścia.

Oba te rozwiązania mogą zapewnić pewną kontrolę nad rzeczywistymi uprawnieniami poza kontenerem.

Spodziewałem się, że ostatecznym rozwiązaniem tego problemu będą przestrzenie nazw użytkowników. Przeprowadziłem kilka testów z niedawno wydaną wersją 1.10 i opcją --userns-remap ustawioną na moje konto na komputerze. Nie jestem jednak pewien, czy może to ułatwić uporanie się z własnością plików na zamontowanych woluminach, obawiam się, że w rzeczywistości może być odwrotnie.

Załóżmy, że uruchomię ten podstawowy kontener

docker run -ti -v /data debian:jessie /bin/bash
echo 'hello' > /data/test.txt
exit

A następnie sprawdź zawartość od hosta:

ls -lh /var/lib/docker/100000.100000/volumes/<some-id>/_data/

-rw-r--r-- 1 100000 100000 6 Feb  8 19:43 test.txt

Ten numer „100000” jest pod-UID mojego użytkownika hosta, ale ponieważ nie odpowiada on identyfikatorowi mojego użytkownika, nadal nie mogę edytować pliku test.txt bez uprawnień. Ten użytkownik podrzędny nie wydaje się mieć żadnego powiązania z moim rzeczywistym zwykłym użytkownikiem poza platformą docker. Nie jest odwzorowany.

Obejścia wspomniane wcześniej w tym poście, które polegały na wyrównywaniu identyfikatorów UID między hostem a kontenerem, nie działają już z powodu UID->sub-UIDmapowania występującego w przestrzeni nazw.

W takim razie, czy istnieje sposób na uruchomienie dockera z włączoną przestrzenią nazw użytkownika (w celu zwiększenia bezpieczeństwa), jednocześnie umożliwiając użytkownikowi hosta z uruchomionym dockerem posiadanie plików wygenerowanych na woluminach?

Stéphane C.
źródło
Myślę, że jeśli zamierzasz udostępniać woluminy między hostem a kontenerem, przestrzenie nazw użytkowników nie będą częścią rozwiązania. Twoja druga opcja („przekazanie UID użytkownika hosta do polecenia docker run jako zmiennej środowiskowej, a następnie uruchomienie niektórych poleceń chown na woluminach w skrypcie punktu wejścia”) jest prawdopodobnie najlepszym rozwiązaniem.
larsks
4
Sam Docker nie wydaje się zachęcać do używania zapisywalnych woluminów zamontowanych na hoście. Ponieważ nie korzystam z usługi w chmurze i używam tylko własnych zaufanych obrazów, teraz zastanawiam się, czy korzyści związane z bezpieczeństwem użytkownika NS są warte poświęcenia tak dużej wygody.
Stéphane C.
@ StéphaneC. czy znalazłeś może lepsze podejście?
Osiemdziesiąt osiem
4
Niestety nie, nieużywanie przestrzeni nazw użytkownika i przekazywanie identyfikatorów UID z hosta jest nadal moją opcją. Mam nadzieję, że w przyszłości będzie odpowiedni sposób mapowania użytkowników. Wątpię, ale wciąż mam otwarte oczy.
Stéphane C.

Odpowiedzi:

46

Jeśli możesz wcześniej ustawić użytkowników i grupy, można przypisać identyfikatory UID i GID w tak specyficzny sposób, aby użytkownicy hosta odpowiadali użytkownikom w przestrzeni nazw wewnątrz kontenerów.

Oto przykład (Ubuntu 14.04, Docker 1.10):

  1. Utwórz użytkowników ze stałymi identyfikatorami numerycznymi:

    useradd -u 5000 ns1
    
    groupadd -g 500000 ns1-root
    groupadd -g 501000 ns1-user1
    
    useradd -u 500000 -g ns1-root ns1-root
    useradd -u 501000 -g ns1-user1 ns1-user1 -m
    
  2. Ręcznie edytuj automatycznie generowane zakresy identyfikatorów podrzędnych w plikach /etc/subuidi /etc/subgid:

    ns1:500000:65536
    

    (uwaga, nie ma zapisów dla ns1-rooti z ns1-user1powodu MAX_UIDi MAX_GIDograniczeń /etc/login.defs)

  3. Włącz przestrzenie nazw użytkowników w /etc/default/docker:

    DOCKER_OPTS="--userns-remap=ns1"
    

    Zrestartuj demona service docker restart, upewnij się, że /var/lib/docker/500000.500000katalog został utworzony.

    Teraz masz pojemniki wewnątrz rooti user1, i na komputerze - ns1-rooti ns1-user1, z pasującymi identyfikatorami

    UPDATE: aby zagwarantować, że użytkownicy inni niż root mają stałe identyfikatory w kontenerach (np. User1 1000: 1000), utwórz je jawnie podczas budowania obrazu.

Jazda testowa:

  1. Przygotuj katalog woluminu

    mkdir /vol1
    chown ns1-root:ns1-root /vol1
    
  2. Wypróbuj z pojemnika

    docker run --rm -ti -v /vol1:/vol1 busybox sh
    echo "Hello from container" > /vol1/file
    exit
    
  3. Spróbuj od gospodarza

    passwd ns1-root
    login ns1-root
    cat /vol1/file
    echo "can write" >> /vol1/file
    

Nie przenośny i wygląda jak hack, ale działa.

amartynov
źródło
3
Bardzo ciekawe i zasługuje na +1. Ale nadal musisz się upewnić, że user1 na twoim obrazie ma przypisany UID 1000. W przeciwnym razie nie możesz być pewien, że otrzyma UID 501000 na hoście. Przy okazji, czy jesteśmy absolutnie pewni, że formuła jest zawsze subUID lower bound + UID in imagetaka, jeśli uruchamiamy wiele różnych obrazów z użytkownikiem o identyfikatorze ustawionym na 1000?
Stéphane C.
@ StéphaneC. Słuszna uwaga! Dodano uwagę na temat poprawiania identyfikatorów w obrazach. Jeśli chodzi o formułę, zamierzam dalej eksperymentować z moimi własnymi obrazami i zaktualizuję odpowiedź, jeśli coś znajdę
amartynov
1
Jeśli ręcznie rozmieszczasz użytkowników i grupy na hoście iw kontenerach, czy naprawdę potrzebujesz funkcji „przestrzeń nazw użytkowników”?
Tristan
1
Przestrzeń nazw, którą tworzysz, oddziela użytkowników hostów od użytkowników kontenerów, ale możesz potrzebować wielu przestrzeni nazw dla kontenerów, zwłaszcza gdy oficjalne obrazy (takie jak mysql) tworzą użytkownika bez jawnego UID. Jak radzisz sobie z wieloma przestrzeniami nazw, kiedy opcja --userns-remap oczekuje tylko jednej?
Tristan
2
@amartynov Czy mogę zapytać, dlaczego zadałeś sobie trud określenia UID (5000) dla swojego użytkownika „ns1”? Ponieważ jest to nazwa (nie UID), do której odwołujesz się w plikach subuid i subgid, wydaje się, że nie powinno mieć znaczenia, jaki UID otrzyma ten użytkownik. Czy brakuje mi jakiegoś związku, jak może sugerować podobieństwo między 5000 a 500000?
Jollymorphic
3

Jednym ze sposobów obejścia tego problemu jest dynamiczne przypisywanie identyfikatora użytkownika w czasie kompilacji w celu dopasowania do hosta.

Przykład Dockerfile:

FROM ubuntu
# Defines argument which can be passed during build time.
ARG UID=1000
# Create a user with given UID.
RUN useradd -d /home/ubuntu -ms /bin/bash -g root -G sudo -u $UID ubuntu
# Switch to ubuntu user by default.
USER ubuntu
# Check the current uid of the user.
RUN id
# ...

Następnie zbuduj jako:

docker build --build-arg UID=$UID -t mycontainer .

i działaj jako:

docker run mycontainer

Jeśli masz istniejący kontener, utwórz kontener opakowujący zawierający Dockerfile:

FROM someexistingcontainer
ARG UID=1000
USER root
# This assumes you've the existing user ubuntu.
RUN usermod -u $UID ubuntu
USER ubuntu

Można to zawinąć w docker-compose.yml:

version: '3.4'
services:
  myservice:
    command: id
    image: myservice
    build:
      context: .
    volumes:
    - /data:/data:rw

Następnie skompiluj i uruchom jako:

docker-compose build --build-arg UID=$UID myservice; docker-compose run myservice
kenorb
źródło
1
Nie chciałbym wyglądać nieprzyjaźnie, ale jest to w rzeczywistości jedno z obejść wymienionych w pierwotnym pytaniu, ale nie jest to rozwiązanie i niezwiązane z przestrzeniami nazw użytkowników.
Stéphane C.
@ StéphaneC. Czy możesz skomentować to powiązane pytanie? stackoverflow.com/questions/60274418/…
nadmierna zmiana
-1

Możesz uniknąć problemów z uprawnieniami, używając docker cppolecenia .

Własność jest ustawiana na użytkownika i grupę podstawową w miejscu docelowym. Na przykład pliki kopiowane do kontenera są tworzone z UID:GIDkontem użytkownika root. Pliki kopiowane na komputer lokalny są tworzone przy UID:GIDużyciu użytkownika, który wywołał docker cppolecenie.

Oto przykład przełączony do użycia docker cp:

$ docker run -ti -v /data debian:jessie /bin/bash
root@e33bb735a70f:/# echo 'hello' > /data/test.txt
root@e33bb735a70f:/# exit
exit
$ docker volume ls
DRIVER              VOLUME NAME
local               f073d0e001fb8a95ad8d919a5680e72b21a457f62a40d671b63c62ae0827bf93
$ sudo ls -l /var/lib/docker/100000.100000/volumes/f073d0e001fb8a95ad8d919a5680e72b21a457f62a40d671b63c62ae0827bf93/_data
total 4
-rw-r--r-- 1 100000 100000 6 Oct  6 10:34 test.txt
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                          PORTS               NAMES
e33bb735a70f        debian:jessie       "/bin/bash"         About a minute ago   Exited (0) About a minute ago                       determined_hypatia
$ docker cp determined_hypatia:/data/test.txt .
$ ls -l test.txt 
-rw-r--r-- 1 don don 6 Oct  6 10:34 test.txt
$ cat test.txt
hello
$ 

Jeśli jednak chcesz tylko odczytać pliki z kontenera, nie potrzebujesz nazwanego woluminu. Ten przykład używa nazwanego kontenera zamiast nazwanego woluminu:

$ docker run -ti --name sandbox1 debian:jessie /bin/bash
root@93d098233cf3:/# echo 'howdy' > /tmp/test.txt
root@93d098233cf3:/# exit
exit
$ docker cp sandbox1:/tmp/test.txt .
$ ls -l test.txt
-rw-r--r-- 1 don don 6 Oct  6 10:52 test.txt
$ cat test.txt
howdy
$ 

Uważam, że nazwane woluminy są przydatne, gdy chcę skopiować pliki do kontenera, jak opisano w tym pytaniu .

Don Kirkby
źródło
Ale docker cpwiąże się z powielaniem danych. Co więcej, zgodnie z dokumentem, podczas kopiowania danych do kontenera ustawia identyfikatory własności według użytkownika root, który często nie jest kontem, na którym działa aplikacja kontenerowa. Nie widzę, jak to rozwiązuje nasz problem.
Stéphane C.
Masz rację, @ ​​Stéphane, chodzi o powielanie danych. Jednak tworzenie kopii plików pozwala przypisać różne prawa własności i uprawnienia na hoście i w kontenerze. docker cpdaje Ci pełną kontrolę nad własnością pliku podczas przesyłania strumieniowego archiwum tar do kontenera lub z niego. Możesz dostosować własność i uprawnienia każdego wpisu w pliku tar podczas przesyłania strumieniowego, więc nie jesteś ograniczony do użytkownika root.
Don Kirkby