Безопасная работа с секретами при сборке docker-образов
Apr 30, 2021 07:14 · 776 words · 4 minute read
При сборке 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, когда на первом этапе делается сборка с использованием секретов переданных любым способом - через аргументы сборки или копирование файла, но в финальный образ попадают только результаты сборки (например, бинарный файл) без секретов в слоях.