Escrevi este artigo para compartilhar um pouco do que já aprendi no PICK da LinuxTips. Então, pegue sua bebida e me acompanhe.
Tudo começou quando, por vezes, as ferramentas de segurança reportavam vulnerabilidades low/mid e, quando íamos avaliar o que era essa vulnerabilidade, nós sempre acabávamos no grande acordo mental: "não é algo que a gente fez, então não tem como resolver".
Durante as aulas do PICK, conheci a Chainguard. E aí surgiu a ideia de montar este artigo para mostrar como utilizar uma imagem base segura para construir o container da minha aplicação.
Para mostrar isso, vamos containerizar uma aplicação de console bem básica de "hello world" em DotNet ao longo deste artigo, visto que o foco aqui é como montar um Dockerfile para a aplicação de forma mais segura e não a aplicação em si.
Criando a aplicação
Assumindo que você já tenha o SDK do DotNet instalado e configurado em seu ambiente, vamos abrir nosso terminal e começar a criação do projeto.
Vamos criar nossa aplicação usando o template console
da CLI do DotNet. Faremos isso utilizando o comando a seguir:
dotnet new console -o HelloWorldApp
Feito isto, vamos para nosso editor de texto preferido para começar a manipular os arquivos contidos no diretório do projeto.
Com seu editor de texto aberto, vamos alterar o arquivo Program.cs
para que ele tenha nosso "Hello World". Edite seu arquivo para que ele se pareça com o seguinte:
namespace HelloWorldApp
{
static class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
Criando o Dockerfile
Perfeito, agora que você criou nossa aplicação (que tem potencial para hackear a NASA), é hora de criarmos nosso Dockerfile para containerizar nossa aplicação.
Vale lembrar que o Dockerfile precisa ficar no mesmo nível do arquivo csproj, no nosso caso, dentro do diretório HelloWorldApp
.
Para construir nosso Dockerfile, além de utilizar imagens base seguras, vamos utilizar um conceito de organização e performance chamado multi-stage builds.
Primeira etapa
Sem mais delongas, vamos para a primeira linha do nosso Dockerfile:
FROM cgr.dev/chainguard/dotnet-sdk:latest AS build
A imagem base que estamos utilizando possui um escopo reduzido para que apenas haja dependências que satisfaçam o uso do SDK do DotNet.
Portanto, se comparado com o escopo de uma imagem base alpine, por exemplo, as chances de nosso container ter vulnerabilidades que não dizem respeito apenas às dependências do SDK do DotNet são bem menores. E este é o grande diferencial de utilizar as imagens base da Chainguard.
Ainda sobre a primeira linha, perceba que utilizamos um alias para identificar a etapa que será executada. Neste caso, chamamos a etapa atual de build
.
Seguindo em frente, para que nós possamos executar nosso comando que irá compilar nossa aplicação e gerar nossa dll (dotnet publish
), precisaremos antes declarar que nossos arquivos pertencem a um usuário não root para poderem ser compilados. Faremos isto da seguinte forma:
COPY --chown=nonroot:nonroot . /source
Aqui estamos utilizando o comando COPY
para copiarmos todos os arquivos do diretório atual onde está o Dockerfile, sob as permissões de um usuário que não é root, para um diretório dentro do container chamado source
que será utilizado posteriormente.
Por se tratar de uma imagem base segura, algumas operações (como o publish no nosso caso) requerem um pouco mais de atenção a níveis de permissão, visto que deixar coisas serem compiladas a um nível elevado levaria por água abaixo toda a segurança da imagem.
Ao final desta etapa, vamos definir nosso diretório de trabalho padrão e realizar o processo de criação da nossa dll que será direcionada para um diretório chamado Release
. Isto será feito nas linhas a seguir:
WORKDIR /source
RUN dotnet publish --use-current-runtime --self-contained false -o Release
Etapa final
Agora, nesta etapa, não precisamos mais que haja dependências relacionadas ao SDK; precisamos agora ter recursos referentes ao runtime do DotNet para executar nossa dll. Para isso, vamos utilizar a seguinte imagem base:
FROM cgr.dev/chainguard/dotnet-runtime:latest AS final
Após isso, vamos então partir para a definição do nosso diretório de trabalho padrão e vamos agora utilizar a grande vantagem de se utilizar o multi-stage. Como na etapa de build
nós já geramos a nossa dll, podemos então copiar nossa dll para a etapa atual para podermos utilizá-la. Vamos fazer isso da seguinte forma:
WORKDIR /
COPY --from=build /source .
Perceba que no comando COPY
estamos informando que queremos que seja copiado para o contexto raiz .
o que foi gerado no diretório /source
da etapa build
. E é aí que ganhamos organização e performance em nosso Dockerfile, segmentando a criação e reutilização de artefatos.
Por fim, vamos definir qual será nosso comando principal que será executado quando nosso container for iniciado, ou seja, vamos indicar que usemos o DotNet para executar nossa dll. Fazemos isso da seguinte forma:
ENTRYPOINT ["dotnet", "Release/HelloWorldApp.dll"]
Dockerfile Completo
Com tudo isso feito, nosso Dockerfile final deve se parecer com o seguinte:
FROM cgr.dev/chainguard/dotnet-sdk:latest AS build
COPY --chown=nonroot:nonroot . /source
WORKDIR /source
RUN dotnet publish --use-current-runtime --self-contained false -o Release
FROM cgr.dev/chainguard/dotnet-runtime:latest AS final
WORKDIR /
COPY --from=build /source .
ENTRYPOINT ["dotnet", "Release/HelloWorldApp.dll"]
Build e Execução da Imagem
Com nosso Dockerfile criado, é hora de fazer o build da nossa imagem e ver se tudo funciona como o esperado (geralmente é aqui que tudo pega fogo). Para fazer isso, estando no mesmo diretório onde nosso Dockerfile está, vamos executar o seguinte comando:
docker build -t helloworldapp .
Após o build concluído, vamos para o momento mais aguardado: a execução de um container que possuirá nossa dll sendo executada. Para isso, utilize o comando:
docker run --rm helloworldapp
Isso É Tudo, Pessoal
Isto conclui nossa jornada com o uso de imagens base seguras e multi-stage em Dockerfiles. Claramente, você pode se aventurar indo além, por exemplo, criando workflows no GitHub que fazem o scan do código ou do container a cada push/pull request usando ferramentas como o Snyk ou o Trivy.
Agora é com você: abuse e use o que passamos por aqui! Explore outras imagens base, tente entender mais como funcionam, tente refatorar Dockerfiles para utilizar multi-stage. Vá além!
Lembre-se: que a força esteja com você, tenha uma vida longa e próspera e não entre em pânico! Allons-y!
Top comments (0)