DEV Community

Matheus Andrade
Matheus Andrade

Posted on • Edited on

06 Exemplos de Uso de Credenciais (04 não recomendadas) durante o Build de Imagens Docker

Em quase todo o processo de construção de imagens Docker, há a necessidade de baixar artefatos (ex: zip, war, jar, etc.) de alguma URL para compor a imagem de nossa aplicação. Por vezes, estas URLs exigem autenticação e precisamos de credenciais para ter acesso ao conteúdo.

Como construir imagens Docker sem comprometer credenciais, como usuários e senhas de acesso?

Os exemplos abaixo seguirão uma ordem, aparentemente natural de boas práticas, onde há uma preocupação em não expor informação sensível na imagem Docker gerada, além de continuar garantindo sua portabilidade.

O Desafio: Contextualizando a situação

Suponha que tenhamos a missão de construir a imagem de uma simples aplicação Java, cujo artefato buildado encontra-se disponível em uma URL que é o repositório de artefatos de sua empresa. (https://artefatos.empresa.com.br/)

Detalhe 01 : a URL do repositório exige autenticação e você possui as credenciais de acesso.

Detalhe 02 : a imagem será compatilhada para outras empresas/clientes implantarem a aplicação.

Exemplo 01: Codando "na mão grande" as credenciais

Eu nem deveria começar por este exemplo, porque acredito que ninguém deve fazer assim. Contudo, vamos evoluindo exemplo a exemplo. Analisemos o Dockerfile abaixo:

FROM alpine
WORKDIR /
RUN apk add curl
RUN curl -u francisco:abc123 -o app.jar "https://artefatos.empresa.com.br/app-1.2.3.jar"
ENTRYPOINT ["java", "-XX:+UnlockExperimentalVMOptions", "-Djava.security.egd=file:/dev/./urandom","-jar","app.jar"]
Enter fullscreen mode Exit fullscreen mode

Se preocupe somente com a segunda linha da intrução RUN, onde foi escrito literalmente as credenciais de acesso. Nem preciso dizer que isso aqui é bem ruim, não é? Mas vamos buildar a imagem e taguear com uma versão:

docker build -t app:01 .
Enter fullscreen mode Exit fullscreen mode

A partir da imagem gerada (e que será distribuída para outras pessoas), façamos uma inspeção:

docker image history app:01
Enter fullscreen mode Exit fullscreen mode

Está lá, para todo o mundo ver, as credenciais de acesso:

IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
e4d60e8650d7   2 minutes ago   /bin/sh -c #(nop)  ENTRYPOINT ["java" "-XX:+…   0B
68e3c9068540   2 minutes ago   /bin/sh -c curl -u francisco:abc123 -o app.j…   1.23kB
c1a9701b7132   5 minutes ago   /bin/sh -c apk add curl                         3.1MB
c44f6da443d7   5 minutes ago   /bin/sh -c #(nop) WORKDIR /                     0B
389fef711851   6 days ago      /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>      6 days ago      /bin/sh -c #(nop) ADD file:ec475c2abb2d46435…   5.58MB
Enter fullscreen mode Exit fullscreen mode

Exemplo 02: Usando ENVs para as credenciais

Uma evolução do exemplo acima, seria a tentativa de buildar a imagem com a utilização de ENVs. Segue o Dockerfile:

FROM alpine
ENV USER francisco
ENV PASSWORD abc123
WORKDIR /
RUN apk add curl
RUN curl -u $USER:$PASSWORD -o app.jar "https://artefatos.empresa.com.br/app-1.2.3.jar"
ENTRYPOINT ["java", "-XX:+UnlockExperimentalVMOptions", "-Djava.security.egd=file:/dev/./urandom","-jar","app.jar"]
Enter fullscreen mode Exit fullscreen mode

Possa ser que você ache que agora não vai persistir as credenciais de acesso, pois leu que a instrução ENV não cria layer na imagem. Vamos conferir?

docker build -t app:02 .
Enter fullscreen mode Exit fullscreen mode

Inspecionando a imagem com o comando docker image inspect app:02:

[...]
 "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "USER=francisco",
                "PASSWORD=abc123"
            ],
            "Cmd": null,
[...]
Enter fullscreen mode Exit fullscreen mode

E com o mesmo comando do exemplo anterior docker image history:

IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
284ee6fffad5   3 minutes ago   /bin/sh -c #(nop)  ENTRYPOINT ["java" "-XX:+…"   0B
904ec10bf219   3 minutes ago   /bin/sh -c curl -u $USER:$PASSWORD -o app.ja…   14B
cc4d45436465   3 minutes ago   /bin/sh -c apk add curl                         3.1MB
35e0fe7f8c92   3 minutes ago   /bin/sh -c #(nop) WORKDIR /                     0B
51c378578679   3 minutes ago   /bin/sh -c #(nop)  ENV PASSWORD=abc123          0B
9ab41a3c7fa5   3 minutes ago   /bin/sh -c #(nop)  ENV USER=francisco           0B
389fef711851   6 days ago      /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>      6 days ago      /bin/sh -c #(nop) ADD file:ec475c2abb2d46435…   5.58MB
Enter fullscreen mode Exit fullscreen mode

Abordarei em outro post, com mais detalhes, as questões de quando layers são geradas ou não. Por enquanto, nos atentemos que é possível a identificação/visualização das credencenciais com a inspeção da imagem gerada.

Código 03: Usando variáveis de ambiente e RUN em uma mesma LAYER

Nova ideia: utilizar variáveis de ambiente junto instrução RUN para a construção de uma única camada (layer) da imagem. O novo Dockerfile ficaria assim:

FROM alpine
WORKDIR /
RUN apk add curl
RUN export USER="francisco" \
    && export PASSWORD="abc123" \
    && curl -u $USER:$PASSWORD -o app.jar "https://artefatos.empresa.com.br/app-1.2.3.jar"
ENTRYPOINT ["java", "-XX:+UnlockExperimentalVMOptions", "-Djava.security.egd=file:/dev/./urandom","-jar","app.jar"]
Enter fullscreen mode Exit fullscreen mode

Gerando versão 03 da aplicação:

docker build -t app:03
Enter fullscreen mode Exit fullscreen mode

E vamos conferir o resultado, analisando a imagem gerada com o docker image history:

IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
406c7ce82cf0   37 seconds ago   /bin/sh -c #(nop)  ENTRYPOINT ["java" "-XX:+…   0B
325c0a507c68   37 seconds ago   /bin/sh -c export USER="francisco" && export …  14B
c1a9701b7132   24 minutes ago   /bin/sh -c apk add curl                         3.1MB
c44f6da443d7   24 minutes ago   /bin/sh -c #(nop) WORKDIR /                     0B
389fef711851   6 days ago       /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>      6 days ago       /bin/sh -c #(nop) ADD file:ec475c2abb2d46435…   5.58MB
Enter fullscreen mode Exit fullscreen mode

É... também não deu certo. :(

Código 04: Usando ARGS

Conhecemos o atributo ARGS, 'similar' a ENV, e percebemos que podemos ganhar uma mobilidade ao passar as credenciais de acesso no comando de execução do build da imagem (não ter as credenciais escritas no Dockerfile!)

FROM alpine
ARG USER=fake
ARG PASSWORD=fake
WORKDIR /
RUN apk add curl
RUN curl -u $USER:$PASSWORD -o app.jar "https://artefatos.empresa.com.br/app-1.2.3.jar"
ENTRYPOINT ["java", "-XX:+UnlockExperimentalVMOptions", "-Djava.security.egd=file:/dev/./urandom","-jar","app.jar"]
Enter fullscreen mode Exit fullscreen mode

E o comando para buildar a imagem seria:

docker build --build-arg USER=francisco --build-arg PASSWORD=abc123 -t app:04 .
Enter fullscreen mode Exit fullscreen mode

Contudo, ao analisar a imagem com o docker image history:

IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
e9956a610c1f   5 minutes ago   /bin/sh -c #(nop)  ENTRYPOINT ["java" "-XX:+…   0B
4bcdfdea08f9   5 minutes ago   |2 PASSWORD=abc123 USER=francisco /bin/sh -c…   14B
1f56fb7ca086   5 minutes ago   |2 PASSWORD=abc123 USER=francisco /bin/sh -c…   3.1MB
0ccf29f3aadf   5 minutes ago   /bin/sh -c #(nop) WORKDIR /                     0B
556f62d21750   5 minutes ago   /bin/sh -c #(nop)  ARG PASSWORD=fake            0B
a726e9660660   5 minutes ago   /bin/sh -c #(nop)  ARG USER=fake                0B
389fef711851   6 days ago      /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>      6 days ago      /bin/sh -c #(nop) ADD file:ec475c2abb2d46435…   5.58MB
Enter fullscreen mode Exit fullscreen mode

Se olharmos a documentação neste link, perceberemos que aconteceu exatamente como o descrito: credenciais ainda visíveis.

Código 05: Adicionando Script auxiliar

Podemos tentar fazer um workaround: utilizar um script intermediário para auxiliar no download do artefato, atrás de uma URL autenticada. Nosso projeto agora ficaria com os seguintes arquivos:

  • Dockerfile
  • build.sh

Conteúdo do build.sh:

#!/bin/sh
curl -u francisco:abc123 -o app.jar "https://artefatos.empresa.com.br/app-1.2.3.jar"
Enter fullscreen mode Exit fullscreen mode

E o Dockerfile, faz uso do artefato já baixado:

FROM alpine
WORKDIR /
COPY app.jar /
ENTRYPOINT ["java", "-XX:+UnlockExperimentalVMOptions", "-Djava.security.egd=file:/dev/./urandom","-jar","app.jar"]
Enter fullscreen mode Exit fullscreen mode

A sequência de ações seria:

chmod +x build.sh
./build.sh
docker build -t app:05 .
Enter fullscreen mode Exit fullscreen mode

Percebe-se claramente ao inspecionar a imagem que não há credenciais de acesso nela, confere? Coube a um script auxiliar realizar a autenticação (utilizando as credenciais de acesso), baixar o artefato e apresentá-lo ao contexto do build da imagem.

It works!

Código 06: multi-stage builds

Apesar do exemplo anterior ter cumprido seu propósito (imagem docker sem exposição de credenciais sensíveis), tive a sensação (e espero que você também) de que algo ficou estranho: Tivemos que recorrer a arquivos auxiliares, fora do escopo do Docker para resolver o problema.

Se analisarmos um pouco, basicamente o que fizemos foi dividir o processo de build em 2 estágios:

  • estágio 01 que baixou os artefatos sob credenciais (script auxiliar)
  • estágio 02 que compilou a imagem (Docker)

Lendo mais a documentação da Docker, percebemos que podemos fazer similar utilizando o conceito de multi-stage builds.

Nosso Dockerfile:

FROM alpine AS download
ARG USER
ARG PASSWORD
RUN apk add curl
RUN curl -u $USER:$PASSWORD -o /app.jar "https://artefatos.empresa.com.br/app-1.2.3.jar"

FROM alpine
WORKDIR /
COPY --from=download /app.jar .
ENTRYPOINT ["java", "-XX:+UnlockExperimentalVMOptions", "-Djava.security.egd=file:/dev/./urandom","-jar","app.jar"]
Enter fullscreen mode Exit fullscreen mode

Perceba que fizemos exatamente a mesma coisa, agora usando apenas o Docker: Dividimos nosso build em 2 estágios.

  • no primeiro estágio, uma imagem intermediária é criada (e suas respectivas layers e informações) com o download da aplicação, atrás da autenticação.
  • no segundo estágio, a imagem em construção (a oficial) copia apenas o app.jar da imagem anterior.

Fácil perceber que, na imagem final não visualizaremos as credenciais. Além disso, a imagem intermediária é destruída ao final do processo de build.

IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
72ef8382df33   3 minutes ago   /bin/sh -c #(nop)  ENTRYPOINT ["java" "-XX:+…   0B
32a3816fdcd3   3 minutes ago   /bin/sh -c #(nop) COPY file:297d62e63eaf8490…   14B
29385377e66f   3 minutes ago   /bin/sh -c #(nop) WORKDIR /                     0B
389fef711851   6 days ago      /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>      6 days ago      /bin/sh -c #(nop) ADD file:ec475c2abb2d46435…   5.58MB
Enter fullscreen mode Exit fullscreen mode

Funciona! E parece-nos uma solução bem inteligente, não acha?

PLUS: BuildKit

Uma funcionalidade "nova" no Docker é o uso do BuildKit para ajudar no manuseio de credenciais durante o processo de construção de imagens.

Ainda não brinquei com este recurso, assim tentarei em breve atualizar este post com um exemplo adicional.

Caso deseje ver como funciona, não deixe de ler a documentação oficial

Post originalmente publicado em meu site pessoal https://matheuslao.dev/posts/uso-credenciais-construcao-imagens-docker/

Top comments (0)