Segurança e boas práticas em imagens de container
Nesse artigo vamos ver algumas maneiras de deixar a nossa imagem mais segura em nossos containers.
Nos dias de hoje temos um vasto ecossistema de container que é utilizado seja em um cluster kubernetes, seja em um docker swarm (sim algumas pessoas ainda usam em produção =/ ), ou em serviços lock-in como o ECS.
Porém muita das vezes quando falamos de segurança pensamos em diversos fatores com longas discussões para manter o ambiente seguro, envolvemos equipes de desenvolvedores, devops, network e etc.
É discutido ideias mirabolantes de permissionamento, camadas de firewall, RBAC, policy, ajuste no cloud onde a aplicação esta rodando, caso esteja em alguma cloud, mas por muita das vezes esquecemos o básico ou talvez a primeira coisa a se olhar que é onde sua aplicação realmente ira ficar que é a imagem que ira ficar dentro de um container.
Sei que é estranho falar isso, mas sim as vezes criamos fortalezas com diversas camadas de segurança e esquecemos de olhar para algo teoricamente básico e simples que esta la no começo do processo, e eu vejo isso como o seguinte, é como se criássemos uma fortaleza “intransponível”, com proteções de todos os lados e de todas as formas, mas não cuidássemos do que esta entrando pela porta da frente, seria como se a fortaleza fosse um banco “super seguro” mas não tivesse um detector de metais na entrada, ou não tivesse nenhum guarda para verificar quem esta entrando, na fortaleza super segura.
E é mais ou menos isso que fazemos quando não damos a devida atenção para nossas imagens que colocamos em nossos containers. Devido a isso vamos abordar algumas coisinhas que vão ajudar muito a deixar tudo mais seguro.
O que é Dockerfile e Imagem de Container ?
Antes de mais nada vamos entender o que é uma imagem de container e o que é um dockerfile, para que possamos entender melhor o que estamos falando.
Dockerfile = É um arquivo de texto simples que contém uma série de instruções que o Docker utiliza para construir uma imagem de container. Ele define o ambiente, as dependências, as configurações e os comandos necessários para criar uma imagem que pode ser executada como um container. Você pode imaginar o dockerfile como uma receita que descreve quais são os passos que o docker vai seguir para criar a imagem desejada, colocando configurações personalizadas de acordo com a necessidade da aplicação.
Imagem de Container = Uma imagem de container é um conjunto de componentes que foram empacotados para ser executado em um ambiente de container. Em nosso caso ela vai ser construida a partir do dockerfile, essa imagem é composta pos algumas camadas, e cada camada tem um papel importante para o funcionamento e para o desempenho da aplicação, e também para a sua segurança.
Imagem base ou a golden image = Seria o S.O minimo ali que você precisa para executar algo
Bibliotecas e Binarios = Executaveis e dependencias essenciais para as coisas acontecerem
Dependencias da aplicação = Pacotes especificos que são exigidos pela aplicação como um npm, pip e etc
Configurações e metadados = Variaveis de ambiente, portas, comandos de entrada e etc, são configurações passadas para aquela particularidade.
Montando nosso dockerfile
Agora que sabemos o que basicamente se compoe uma imagem e o que é um dockerfile, vamos criar um exemplo simples para que possamos analisar a segurança da imagem e depois melhorar a mesma.
Mas antes precisamos criar a nossa aplicação, e para isso vamos criar algo bem simples em Go, precisamos então criar alguns arquivos, o primeiro deles é o main.go, que vai ser a nossa aplicação, e o segundo é o go.mod que é o arquivo de configuração do nosso projeto em Go, para que possamos baixar as dependencias da aplicação.
Crie esses arquivos dentro do diretorio /src
Arquivo main.go:
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Olá galerinha acessem https://blog.rafael.bhz.br/")
})
log.Printf("Servidor rodando na porta %s...", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatal(err)
}
}
Arquivo go.mod:
go.mod
1
2
3
module github.com/rafaelfernandessilva
go 1.22.2
Vamos criar nosso dockerfile um diretorio acima.
Dockerfile
1
2
3
4
5
6
7
8
9
10
11
FROM golang:1.22
WORKDIR /app
COPY src/go.mod .
COPY src/main.go ./
RUN go build -o main .
CMD ["./main"]
Com os arquivos da aplicação criado e nosso dockerfile criado, vamos fazer o build da imagem para que possamos analisar a mesma e ver se tem vulnerabilidades e depois melhorar.
Para isso vamos executar o comando de build do docker.
docker build -t app-go:1.0 .
Nesse comando estamos chamando do docker passando como pametro o build, depois o -t para dar um nome para a imagem, no caso app-go:1.0 e por fim o ponto que indica que o dockerfile esta no diretorio atual.
Pronto agora já temos o build da nossa primeira imagem.
Veja que foi feito o build da imagem e podemos ver algumas coisas sobre ela, vamos primeiro ver o tamanho da mesma com o comando:
docker images -f ‘reference=app-go:1.0’
Veja que uma aplicação super simples ficou com mais de 1gb de tamanho. O que não é tão interessante para esse caso visto que temos muita coisa sem necessidade nessa imagem e como e algo compilado não estamos usando boas pratica e isso não esta diretamente ligado a segurança mas já que estamos otimizando nossa imagem temos que levar isso em consideração.
Realizando scan e detectando vulnerabilidades
E para realmente fazer uma analise da segurança da nossa imagem vamos utilizar duas ferramentas extremamente poderosas, que é o docker scout e o trivy.
Ta beleza vamos usar essas ferramentas, mas de onde vem? O que são ? de que se alimentam ?
Docker Scout
O Docker Scout é uma solução focada na segurança proativa da cadeia de suprimentos de software. Ele funciona analisando imagens de containers para identificar e mitigar vulnerabilidades antes que elas se tornem problemas críticos. As principais funcionalidades são:
Análise e SBOM: O Docker Scout analisa suas imagens e compila um inventário detalhado de componentes, chamado de Software Bill of Materials (SBOM).
Detecção de Vulnerabilidades: Esse SBOM é comparado com um banco de dados de vulnerabilidades que é atualizado continuamente, permitindo localizar falhas de segurança específicas nos pacotes e camadas da imagem.
O Trivy é uma ferramenta de segurança abrangente e versátil, amplamente utilizada para escanear não apenas imagens de containers, mas também diversos outros artefatos em busca de falhas de segurança, é uma ferramenta da Aqua Security e diferente do docker scout que é atrelada ao docker o trivy é uma ferramenta de segurança a parte. As principais funcionalidades do trivy:
Detecção de Múltiplas Ameaças: Ele varre as imagens em busca de vulnerabilidades conhecidas (CVEs), configurações incorretas (como erros em arquivos de infraestrutura como código), segredos expostos (como chaves de API e senhas) e licenças de software.
Alvos de Varredura: O Trivy analisa tanto os arquivos internos da imagem quanto os seus metadados/configurações (como as instruções do Dockerfile), identificando, por exemplo, se um container está configurado para rodar como usuário root.
Gestão de SBOM: Assim como as soluções modernas de segurança, o Trivy suporta a geração e a descoberta de SBOMs (Software Bill of Materials), permitindo inventariar todos os componentes de uma imagem para uma análise mais rápida e precisa.
Agora que conhecemos duas ótimas ferramentas para nos ajudar na segurança da nossa imagem vamos então ver como está a nossa situação.
Vamos primeiro utilizar o docker scout para ver se estamos com muitas vulnerabilidades, para isso partindo do principio que temos docker e o docker scout vamos utilizar o comando:
docker scout quickview app-go:1.0
O comando docker scout com o parametro quickview vai nos da um overview resumido sobre como esta nossa situação em relação a imagem, que estamos construindo. Nossa saída vai ser algo semelhante a isso:
Temos também outro comando muito importante O comando docker scout cves que nos vai dar uma visão completa e detalhada de todas as vulnerabilidades (CVEs) identificadas em uma imagem de container.
docker scout cves app-go:1.0
Nessa primeira temos o que foi encontrado:
Podemos também utilizar o trivy, para analisar a situação da nossa imagem:
docker run –rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy
image –severity HIGH,CRITICAL,MEDIUM app-go:1.0
Como vimos o resultado não foi muito bom, temos diversas vulnerabilidades de nível CRITICAL, e isso é algo que devemos nos preocupar, pois essas vulnerabilidades podem ser exploradas e comprometer toda a nossa aplicação e infraestrutura.
Otimizando nossa imagem e elimando vulnerabilidades
Para melhorar a segurança da nossa imagem, vamos realizar alguns procedimentos, o primeiro deles é atualizar a versão do golang que estamos utilizando, pois como vimos em ambos os scan tivemos problema com a versão do golang da nossa aplicação então vamos primeiro atualização a mesma pois é uma das coisas indicadas como fix.
Nossa primeira alteração vai ser buscar a imagem com versão mais recente do golang então altere para FROM golang:1.25, e faça novo builda da imagem.
Para isso vamos alterar o nosso dockerfile para:
1
2
3
4
5
6
7
8
9
10
FROM golang:1.25
WORKDIR /app
COPY src/go.mod .
COPY src/main.go ./
RUN go build -o main .
CMD ["./main"]
Vamos fazer um novo build da imagem com o comando:
docker build -t app-go:2.0 .
Observe que agora estamos utilizando a versão 1.25 do golang, e estamos fazendo o build da imagem com o nome app-go:2.0 para que possamos diferenciar da imagem anterior.
Feito isso vamos rodar novamente o nosso scan, na nova imagem que fizemos o build no meu caso a app-go:2.0
docker run –rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy image –severity HIGH,CRITICAL,MEDIUM app-go:2.0
Agora já temos um cenario bem melhor:
Docker scout:
docker scout cves app-go:2.0
Já não temos alerta de nivel CRITICAL, mas temos uns outros no qual ainda devemos nos preocupar que é gerado devido ao S.O base no qual a imagem esta.
Então vamos dar um jeito nisso, e para isso vamos usar a imagem da chainguard, eles tem com foco ter imagens totalmente seguras sem cve, e também vamos usar uma imagem distroless com as boas práticas de mult stage no nosso dockefile, pois como nossa aplicação é compilada, precisamos apenas do arquivo gerado para podermos executar a mesma.
Vamos fazer uma alteração no nosso arquivo Dockerfile:
Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
FROM cgr.dev/chainguard/go:latest AS builder
WORKDIR /app
COPY src/go.mod ./
RUN go mod download
COPY src/main.go ./
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o main .
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/main /main
USER nonroot:nonroot
ENTRYPOINT ["/main"]
Nessa alteração do dockerfile estamos realizar o multi stage. A pratica de utilizar o multi stage é uma ótima maneira de otimizar a imagem final, pois no primeiro estágio no qual damos o nome de builder, estamos compilando a nossa aplicação realizando o download das dependencias e demais procedimentos necessarios, e no segundo estágio estamos apenas pegando o que é necessario para rodar a aplicação, que é o arquivo main gerado no primeiro estagio.
Com esse novo dockerfile vamos fazer um novo build da imagem:
docker build -t app-go:3.0 .
Agora criando uma imagem com o nome app-go:3.0 para que possamos diferenciar das imagens anteriores, e depois vamos rodar novamente o scan para ver como esta a situação da nossa imagem.
- Trivy
docker run –rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy image –severity HIGH,CRITICAL,MEDIUM app-go:3.0
- Docker scout
docker scout cves app-go:3.0
Agora temos uma imagem mais segura que podemos utilizar em nossa infra estrutura. E ainda tem mais, a nossa imagem agora esta bem menor. Se executarmos o comando:
docker images app-go:3.0 –format ‘{{.Size}}’
Vamos ver que nossa imagem inicial tinha mais 1GB e nossa imagem final tem menos de 20MB.
Com isso acho que já sabemos o basico sobre como melhorar a segurança em nossos contrainer e talvez já podemos dizer se somos o melhor da nossa rua talvez.
Referência:









