Use o Docker Buildkit para configurar de forma segura a sua imagem Docker para acessar recursos privados
Este artigo foi originalmente publicado no Medium em 30 de julho de 2021.
Desafios no acesso a informações protegidas
Acessar recursos privados, como um repositório Nexus ou GitHub, de dentro de uma imagem Docker sem vazar qualquer informação relacionada à segurança é altamente valorizado.
Primeiro, nunca incluiremos credenciais ou qualquer outra informação segura diretamente no Dockerfile.
Segundo, é possível criar uma compilação em várias etapas¹ e limpar a imagem final e as camadas de qualquer informação privada. No entanto, essa não é uma solução direta e depende muito de quem está desenvolvendo.
Finalmente, alguns de nós podem recorrer ao uso de argumentos — ARG — e passar as informações no comando docker build². No entanto, isso sozinho deixa as informações secretas disponíveis nas camadas da imagem. Mostrarei um exemplo disso a seguir.
Usando argumentos para construir uma imagem
Considere o Dockerfile a seguir, que faz o download de um arquivo de um repositório Nexus:
FROM ubuntu
ARG USERNAME
ARG PASSWORD
RUN apt-get update && \
apt-get upgrade && \
apt-get install -y curl
RUN curl -o nginx_policy.yaml -u $USERNAME:$PASSWORD http://nexus:8081/repository/raw/policy/nginx-policy.yaml
Usando o comando docker build, podemos criar a imagem com sucesso:
docker build -t secret:args --build-arg USERNAME=$user --build-arg PASSWORD=$pass .
Sending build context to Docker daemon 3.072kB
Step 1/5 : FROM ubuntu
---> c29284518f49
Step 2/5 : ARG USERNAME
---> Using cache
---> 720c9732f5db
Step 3/5 : ARG PASSWORD
---> Using cache
---> 193f8044461b
Step 4/5 : RUN apt-get update && apt-get upgrade && apt-get install -y curl
---> Using cache
---> 894b791e5ec3
Step 5/5 : RUN curl -o nginx_policy.yaml -u $USERNAME:$PASSWORD http://172.17.0.2:8081/repository/raw/policy/nginx-policy.yaml
---> Using cache
---> e4050d5c1743
Successfully built e4050d5c1743
Successfully tagged secret:args
Eu utilizei as variáveis de ambiente na opção build-arg para não armazenar as credenciais no histórico do sistema operacional. Além disso, na imagem, eu não estou salvando essa informação em nenhum lugar.
O problema com essa abordagem aparece quando você executa o comando docker history nesta imagem:
➜ docker history secret:args_env
IMAGE CREATED CREATED BY SIZE COMMENT
f11cb0b139f3 2 minutes ago |2 PASSWORD=admin123 USERNAME=admin /bin/sh … 0B
6ca5f3fc074a 2 minutes ago |2 PASSWORD=admin123 USERNAME=admin /bin/sh … 0B
193f8044461b 43 minutes ago /bin/sh -c #(nop) ARG PASSWORD 0B
720c9732f5db 43 minutes ago /bin/sh -c #(nop) ARG USERNAME 0B
c29284518f49 3 days ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 3 days ago /bin/sh -c #(nop) ADD file:5c3d9d2597e01d1ce… 72.8MB
Claramente, podemos ver que as credenciais estão armazenadas nos metadados da imagem. Apesar de nossos melhores esforços, ainda estamos vazando informações confidenciais. O BuildKit³ foi integrado⁴ ao Docker exatamente para nos ajudar a manter a segurança.
Hablitando o Buildkit
O Buildkit está presente no Docker desde a versão 18.06 e atualmente suporta apenas contêineres Linux. Para ativar as compilações do Buildkit, defina a variável de ambiente DOCKER_BUILDKIT=1 ao invocar o comando docker build, como:
$ DOCKER_BUILDKIT=1 docker build .
ou configure a opção de configuração do daemon⁵ para true e reinicie-o:
{ “features”: { “buildkit”: true } }
Informações secretas do Docker Build
A nova opçāo --secret para o comando docker build permite ao usuário passar informações secretas para serem usadas no Dockerfile para construir imagens do Docker de forma segura, de modo que essas informações não sejam armazenadas na imagem final.
Para usar esse recurso, precisamos substituir o frontend padrão em nosso Dockerfile. Na primeira linha do Dockerfile, insira:
# syntax=docker/dockerfile:1.2
A opçāo --mount foi adicionada ao comando RUN para permitir que o contêiner de compilação acesse arquivos seguros, como chaves privadas, sem incorporá-los na imagem.
# shows secret from default secret location:RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
Construindo um contêiner com o Buildkit
Por exemplo, com uma informação secreta armazenada em um arquivo de texto:
echo 'SUPER_SECRET_PASSWORD' > mysecret.txt
E com um Dockerfile que especifica o uso de um frontend BuildKit docker/dockerfile:1.2, o segredo pode ser acessado ao executar um RUN:
# syntax=docker/dockerfile:1.2FROM alpine
# shows secret from default secret location:RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
Este Dockerfile é apenas para demonstrar que o segredo pode ser acessado. Como você pode ver, o segredo é impresso na saída da compilação. A imagem final construída não terá o arquivo de segredo:
$ DOCKER_BUILDKIT=1 docker build --no-cache --progress=plain -t secret:buildkit --secret id=mysecret,src=mysecret.txt .
#1 [internal] load build definition from Dockerfile
#1 sha256:5c65425f6fc0d6c65a5ddd6784812097b4eff778b9bcbf39bc708aacbad59abd
#1 transferring dockerfile: 196B done
#1 DONE 0.0s
#2 [internal] load .dockerignore
#2 sha256:35c8e51716823a06d8dbd04a2594a31a90f3d02bb55a0b8c3e9f2c1b44f901c7
#2 transferring context: 2B done
#2 DONE 0.0s
#3 resolve image config for docker.io/docker/dockerfile:1.2
#3 sha256:b239a20f31d7f1e5744984df3d652780f1a82c37554dd73e1ad47c8eb05b0d69
#3 DONE 2.5s
#4 docker-image://docker.io/docker/dockerfile:1.2@sha256:e2a8561e419ab1ba6b2fe6cbdf49fd92b95912df1cf7d313c3e2230a333fdbcc
#4 sha256:37e0c519b0431ef5446f4dd0a4588ba695f961e9b0e800cd8c7f5ba6165af727
#4 resolve docker.io/docker/dockerfile:1.2@sha256:e2a8561e419ab1ba6b2fe6cbdf49fd92b95912df1cf7d313c3e2230a333fdbcc done
#4 CACHED
#5 [internal] load metadata for docker.io/library/alpine:latest
#5 sha256:d4fb25f5b5c00defc20ce26f2efc4e288de8834ed5aa59dff877b495ba88fda6
#5 DONE 0.0s
#6 [1/2] FROM docker.io/library/alpine
#6 sha256:665ba8b2cdc0cb0200e2a42a6b3c0f8f684089f4cd1b81494fbb9805879120f7
#6 CACHED
#7 [2/2] RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
#7 sha256:75601a522ebe80ada66dedd9dd86772ca932d30d7e1b11bba94c04aa55c237de
#7 0.494 SUPER_SECRET_PASSWORD#7 DONE 0.5s
#8 exporting to image
#8 sha256:e8c613e07b0b7ff33893b694f7759a10d42e180f2b4dc349fb57dc6b71dcab00
#8 exporting layers 0.0s done
#8 writing image sha256:8df0fe20ceab547858702c32456d962b99d66fd40ab29f4e191ac0b52e383039 done
#8 DONE 0.0s
Ao executar o comando docker history, não conseguimos ver nenhuma informação relacionada ao segredo:
$ docker history secret:buildkit
IMAGE CREATED CREATED BY SIZE COMMENT7da5afe48753 16 seconds ago RUN /bin/sh -c cat /run/secrets/mysecret # b… 0B buildkit.dockerfile.v0
<missing> 4 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:f278386b0cef68136… 5.6MB
Em vez de usar um arquivo de segredo, é possível passar a informação do segredo usando uma variável de ambiente⁷, como:
$ export PASSWORD=SUPER_SECRET_PASSWORD
$ DOCKER_BUILDKIT=1 docker build --no-cache --progress=plain -t secret:buildkit --secret id=mysecret,env=PASSWORD .
Conclusāo
Este artigo explicou os passos necessários para tornar o uso de informações restritas mais seguro dentro de contêineres Docker.
Ao definir uma variável de ambiente antes da compilação do Docker e fazer algumas alterações no seu Dockerfile, você pode evitar o vazamento de credenciais usadas para configurar seus contêineres Docker.
O BuildKit do Docker não apenas suporta segredos, mas também outros pontos de montagem de compilação, como cache e SSH. Comece ou continue a explorar o BuildKit e entenda como ele pode ajudar a melhorar a segurança dos seus ambientes.
Referências
https://docs.docker.com/develop/develop-images/multistage-build/
https://docs.docker.com/develop/develop-images/build_enhancements/
https://docs.docker.com/develop/develop-images/build_enhancements/#overriding-default-frontends
https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/syntax.md
https://medium.com/@tonistiigi/build-secrets-and-ssh-forwarding-in-docker-18-09-ae8161d066
Comments