DEV Community

Gabriel Batista
Gabriel Batista

Posted on

Usando Imagens Base Seguras

Hello There!

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
Enter fullscreen mode Exit fullscreen mode

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!");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 .
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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 .
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)