Thank you for reading this post, don't forget to subscribe!
При сборке Docker-образов иногда необходимо использовать секреты (например, пароль к приватному репозиторию пакетов), которые не должны в конечном итоге оставаться в образе. В последних версиях Docker этого можно добиться благодаря BuildKit
Самые очевидные варианты работы с секретами при сборке Docker-образа - копирование с последующим удалением, например, для Dockerfile
:
1 2 3 4 5 6 7 8 9 10 11 12 |
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 .
можно увидеть слой образа до удаления файла с секретом в логе сборки, который будет выглядеть примерно так (вывод сильно сокращен):
1 2 3 4 5 6 7 8 9 10 |
... 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
чтобы увидеть список слоев образа:
1 2 3 4 5 6 7 8 9 10 |
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
):
1 2 3 4 5 6 7 8 |
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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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"] |
1 2 3 4 5 6 7 8 9 10 11 |
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"] ... |
1 2 3 4 5 6 7 |
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
):
1 2 3 4 5 6 7 8 9 10 11 12 |
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"] |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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 |
В истории нашего секрета не видно:
1 2 3 4 5 6 7 8 |
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
оставляем неизменным):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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, когда на первом этапе делается сборка с использованием секретов переданных любым способом - через аргументы сборки или копирование файла, но в финальный образ попадают только результаты сборки (например, бинарный файл) без секретов в слоях.