Безопасная работа с секретами при сборке docker-образов

Apr 30, 2021 07:14 · 776 words · 4 minute read docker

При сборке Docker-образов иногда необходимо использовать секреты (например, пароль к приватному репозиторию пакетов), которые не должны в конечном итоге оставаться в образе. В последних версиях Docker этого можно добиться благодаря BuildKit - давайте разберемся!

Самые очевидные варианты работы с секретами при сборке Docker-образа - копирование с последующим удалением, например, для Dockerfile:

FROM node:14.16.1-alpine

WORKDIR /app

COPY docker/server.js server.js
COPY docker/package.json package.json

COPY npmrc /root/.npmrc
RUN npm install
RUN rm -rf /root/.npmrc

CMD ["node", "server.js"]

При выполнении команды

docker build -t ealebed/demo:dev .

можно увидеть слой образа до удаления файла с секретом в логе сборки, который будет выглядеть примерно так (вывод сильно сокращен):

...
Step 6/8 : RUN npm install
 ---> Running in be570142f74a
Removing intermediate container be570142f74a
 ---> 77f58d40c15c
Step 7/8 : RUN rm -rf /root/.npmrc
 ---> Running in cf35e4e5e024
Removing intermediate container cf35e4e5e024
 ---> 63759e72b83b
...

или же можно воспользоваться командой docker history чтобы увидеть список слоев образа:

docker image history ealebed/demo:dev
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
264465b2b53f   5 seconds ago    /bin/sh -c #(nop)  CMD ["node" "server.js"]     0B        
63759e72b83b   5 seconds ago    /bin/sh -c rm -rf /root/.npmrc                  0B        
77f58d40c15c   6 seconds ago    /bin/sh -c npm install                          3.41MB    
40557b7ad6a2   10 seconds ago   /bin/sh -c #(nop) COPY file:19b517f4110b66d0…   328B      
000045a416ee   10 seconds ago   /bin/sh -c #(nop) COPY file:e5c47cc4e3126ec8…   313B      
b90043498273   10 seconds ago   /bin/sh -c #(nop) COPY file:0f0bfcfcfa06b293…   650B      
53053044a91c   10 seconds ago   /bin/sh -c #(nop) WORKDIR /app                  0B        
...

Слой 77f58d40c15c является полноценным docker-образом и может быть запущен в качестве контейнера, в котором будет находиться “удаленный” секрет (.npmrc):

docker run --rm -it 77f58d40c15c ls -la /root
total 24
drwx------    1 root     root          4096 Apr 30 07:44 .
drwxr-xr-x    1 root     root          4096 Apr 30 08:12 ..
drwx------    3 root     root          4096 Apr 30 07:44 .config
drwx------    4 root     root          4096 Apr 14 23:19 .gnupg
drwxr-xr-x    4 root     root          4096 Apr 30 07:44 .npm
-rw-r--r--    1 root     root           328 Jan 27 13:49 .npmrc

Второй очевидный и также небезопасный вариант - передавать секреты при сборке образа с помощью аргументов. Для демонстрации изменяем наш Dockerfile:

FROM node:14.16.1-alpine

WORKDIR /app

ARG SECRET

COPY docker/server.js server.js
COPY docker/package.json package.json

RUN echo ${SECRET}
RUN npm install

CMD ["node", "server.js"]
docker build --build-arg SECRET=MyS3cr3t -t ealebed/demo:dev1 .
...
Step 5/7 : COPY docker/package.json package.json
 ---> ea15d9a69360
Step 6/7 : RUN echo ${SECRET}
 ---> Running in 7a8dda5566ff
MyS3cr3t
Removing intermediate container 7a8dda5566ff
 ---> 1002000560e2
Step 7/7 : CMD ["node", "server.js"]
...
docker history ealebed/demo:dev1                                          
IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
b2ae58f7e419   4 seconds ago   /bin/sh -c #(nop)  CMD ["node" "server.js"]     0B        
1002000560e2   4 seconds ago   |1 SECRET=MyS3cr3t /bin/sh -c echo ${SECRET}    0B        
ea15d9a69360   5 seconds ago   /bin/sh -c #(nop) COPY file:e5c47cc4e3126ec8…   313B      
05a4e4495ace   5 seconds ago   /bin/sh -c #(nop) COPY file:0f0bfcfcfa06b293…   650B      
2165e6fc4b31   5 seconds ago   /bin/sh -c #(nop)  ARG SECRET                   0B        
...

Становится понятно, что использовать оба вышеприведенных варианта небезопасно. Как быть?

Начиная с версии 18.09, в Docker есть поддержка флага --secret для команды docker build, которая позволяет передавать файлы с секретами в момент сборки образа и не оставлять их с каких-либо промежуточных слоях образа, а с версии 20.10 появилась дополнительная возможность подгружать секреты из переменных окружения, а не только из файлов.

Рассмотрим пример (специально выводим содержимое секрета с помощью команды cat):

FROM node:14.16.1-alpine

WORKDIR /app

RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret

COPY docker/server.js server.js
COPY docker/package.json package.json

#RUN npm install

CMD ["node", "server.js"]
export DOCKER_BUILDKIT=1

docker build --no-cache --progress=plain --secret id=mysecret,src=mytestsecret.txt -t ealebed/demo:dev2 .
#1 [internal] load build definition from Dockerfile
#1 sha256:698ea931c3c033fe631d9efb60e9a5e8509b14868e5adfefe668695a5dd443d1
#1 transferring dockerfile: 262B 0.0s done
#1 DONE 0.0s

#2 [internal] load .dockerignore
...

#6 [3/5] RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
#6 sha256:46001a8829ee7caeecfb56be0690078c406408050e4c5324861b39df6008d5b7
#6 0.278 MyS3cr3t
#6 DONE 0.3s

...
#10 writing image sha256:905609f1b7591088afbbba46aa1857c71c1cdbbf3bba7209053f2f8114887ef8 done
#10 naming to docker.io/ealebed/demo:dev2 done
#10 DONE 0.0s

В истории нашего секрета не видно:

docker history ealebed/demo:dev2
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
905609f1b759   39 seconds ago   CMD ["node" "server.js"]                        0B        buildkit.dockerfile.v0
<missing>      39 seconds ago   COPY docker/package.json package.json # buil…   313B      buildkit.dockerfile.v0
<missing>      39 seconds ago   COPY docker/server.js server.js # buildkit      650B      buildkit.dockerfile.v0
<missing>      39 seconds ago   RUN /bin/sh -c cat /run/secrets/mysecret # b…   0B        buildkit.dockerfile.v0
<missing>      2 days ago       WORKDIR /app                                    0B        buildkit.dockerfile.v0
...

Аналогичную ситуацию наблюдаем при использовании секретов из переменных окружения (Dockerfile оставляем неизменным):

export MYSECRET=theverysecretpassword
docker build --no-cache --progress=plain --secret id=mysecret,env=MYSECRET -t ealebed/demo:dev3 .
#1 [internal] load build definition from Dockerfile
#1 sha256:9c36dd7edc2d1b2c9015ea42012361fe7bf1f4a080eb340a38bf648c86812a8e
#1 transferring dockerfile: 37B done
#1 DONE 0.0s

...

#5 [3/5] RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
#5 sha256:46001a8829ee7caeecfb56be0690078c406408050e4c5324861b39df6008d5b7
#5 0.264 theverysecretpassword
#5 DONE 0.3s

...

А еще, безусловно, для сокрытия секретов можно использовать multi-stage build, когда на первом этапе делается сборка с использованием секретов переданных любым способом - через аргументы сборки или копирование файла, но в финальный образ попадают только результаты сборки (например, бинарный файл) без секретов в слоях.

tweet Share