
GCP
Engineering Handbook - Capitulo 08
Google Cloud Master Journey 2026
Engineering Handbook · Capítulo 08
Como transformei o scan de vulnerabilidades em um security gate de CI/CD usando Cloud Build, Artifact Registry e Artifact Analysis.

Este artigo faz parte da série Google Cloud Master Journey 2026, em que estou transformando laboratórios práticos em capítulos de um Engineering Handbook sobre Cloud Engineering, DevSecOps, arquitetura e segurança.
A proposta não é apenas registrar comandos ou badges. Quero transformar cada laboratório em um material que responda perguntas de engenharia:
Neste capítulo, o tema é segurança do processo de build com Cloud Build, Artifact Registry e Artifact Analysis.
O laboratório usado como base foi Secure Builds with Cloud Build (GSP1184).
Durante a prática, construí um pipeline que:
CRITICAL;A ideia central é simples:
Uma imagem não deve chegar ao registry de promoção — e muito menos à produção — apenas porque o Docker build terminou com sucesso.
No capítulo anterior, a segurança foi aplicada no momento do deploy:
Imagem sem attestation
→ Binary Authorization bloqueia no GKE
Neste capítulo, a segurança entra antes:
Imagem com vulnerabilidade crítica
→ Cloud Build interrompe o pipeline
As duas camadas se complementam.
O scan reduz o risco de promover componentes conhecidos como vulneráveis.
Binary Authorization garante que somente artefatos aprovados pelo processo autorizado alcancem o runtime.
Um pipeline pode terminar com status verde e ainda produzir uma imagem insegura.
O comando abaixo pode funcionar perfeitamente:
docker build -t minha-aplicacao:latest .
Mesmo assim, a imagem pode conter:
O sucesso técnico do build responde:
Foi possível produzir a imagem?
O security gate responde:
Essa imagem atende aos requisitos mínimos para continuar?
São perguntas diferentes.
Artifact Analysis é o serviço do Google Cloud para análise e armazenamento de metadados relacionados a artefatos.
No contexto deste laboratório, ele é usado para identificar vulnerabilidades em imagens de contêiner.
A documentação atual distingue duas formas principais de scanning:
Além de vulnerabilidades, Artifact Analysis pode ajudar a entender composição de software, dependências e licenças, conforme o tipo de pacote e o recurso de scanning utilizado.
Esses mecanismos parecem semelhantes, mas resolvem momentos diferentes do ciclo.
É executado após o push da imagem no Artifact Registry.
Vantagens:
Limitação principal:
A imagem já chegou ao registry quando o resultado é produzido.
Isso não é necessariamente um problema quando o registry é tratado como área de quarentena. Mas pode ser insuficiente se o simples push já representar promoção.
Pode analisar uma imagem local ou armazenada no Artifact Registry.
Vantagens:
O laboratório combina os dois modelos:
O primeiro Dockerfile usou uma imagem Debian 11 do ambiente App Engine:
FROM gcr.io/google-appengine/debian11
RUN apt update && apt install python3-pip -y
WORKDIR /app
COPY . ./
RUN pip3 install Flask==1.1.4
RUN pip3 install gunicorn==20.1.0
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app
A aplicação era propositalmente simples:
import os
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
name = os.environ.get("NAME", "Worlds")
return "Hello {}!".format(name)
if __name__ == "__main__":
app.run(
debug=True,
host="0.0.0.0",
port=int(os.environ.get("PORT", 8080)),
)
O primeiro pipeline tinha apenas a etapa de build:
steps:
- id: build
name: gcr.io/cloud-builders/docker
args:
- build
- -t
- us-central1-docker.pkg.dev/${PROJECT_ID}/artifact-scanning-repo/sample-image
- .
waitFor:
- "-"
Nesse momento, a imagem existia no ambiente do build, mas ainda não havia sido publicada no Artifact Registry.
O repositório foi criado com:
gcloud artifacts repositories create artifact-scanning-repo \
--repository-format=docker \
--location=us-central1 \
--description="Docker repository"
Depois o pipeline recebeu uma etapa de push:
- id: push
name: gcr.io/cloud-builders/docker
args:
- push
- us-central1-docker.pkg.dev/${PROJECT_ID}/artifact-scanning-repo/sample-image
E a imagem foi declarada no bloco images:
images:
- us-central1-docker.pkg.dev/${PROJECT_ID}/artifact-scanning-repo/sample-image
A configuração final dessa etapa ficou:
O Artifact Registry passa a ser o registro central do artefato e o ponto de integração com Artifact Analysis.
Depois que a imagem foi enviada, Artifact Analysis iniciou o scan automático.
O resultado pôde ser consultado em:
Artifact Registry
→ artifact-scanning-repo
→ sample-image
→ digest
→ Vulnerabilities
Um detalhe importante é que o scan está associado ao digest da imagem.
Adicionar ou alterar uma tag não cria um novo conteúdo. Portanto, não deveria ser confundido com um novo artefato.
As três tags podem apontar para o mesmo digest e, consequentemente, para o mesmo conjunto de resultados.
A imagem foi construída localmente no Cloud Shell:
docker build \
-t us-central1-docker.pkg.dev/${PROJECT_ID}/artifact-scanning-repo/sample-image \
.
O scan foi solicitado com:
gcloud artifacts docker images scan \
us-central1-docker.pkg.dev/${PROJECT_ID}/artifact-scanning-repo/sample-image \
--format="value(response.scan)" > scan_id.txt
O arquivo scan_id.txt recebeu a localização do relatório.
Depois, as vulnerabilidades foram consultadas:
gcloud artifacts docker images list-vulnerabilities \
"$(cat scan_id.txt)"
O laboratório não espera que uma pessoa leia manualmente todo o relatório.
O dado é consumido por uma regra:
export SEVERITY=CRITICAL
gcloud artifacts docker images list-vulnerabilities \
"$(cat scan_id.txt)" \
--format="value(vulnerability.effectiveSeverity)" \
| if grep -Fxq "${SEVERITY}"; then
echo "Failed vulnerability check for ${SEVERITY} level"
else
echo "No ${SEVERITY} Vulnerabilities found"
fi
A imagem inicial retornou:
Failed vulnerability check for CRITICAL level
Esse resultado se tornou a base do security gate.
effectiveSeverityA severidade de uma vulnerabilidade pode depender do contexto do pacote, da distribuição e da forma como a fonte de vulnerabilidade classifica o problema.
O campo effectiveSeverity fornece a severidade efetiva apresentada pelo scanner para aquela occurrence.
No laboratório, a política foi binária:
Existe CRITICAL?
→ falhar
Em produção, essa regra pode precisar considerar mais dimensões:
Voltaremos a isso na seção Além do laboratório.
O pipeline foi ampliado para quatro fases principais:
build
→ scan
→ severity check
→ push
A etapa de scan:
- id: scan
name: gcr.io/cloud-builders/gcloud
entrypoint: bash
args:
- -c
- |
(gcloud artifacts docker images scan \
us-central1-docker.pkg.dev/${PROJECT_ID}/artifact-scanning-repo/sample-image \
--location=us \
--format="value(response.scan)") > /workspace/scan_id.txt
O arquivo é salvo em /workspace, diretório compartilhado entre os passos do build.
A etapa de decisão:
- id: severity-check
name: gcr.io/cloud-builders/gcloud
entrypoint: bash
args:
- -c
- |
gcloud artifacts docker images list-vulnerabilities \
$(cat /workspace/scan_id.txt) \
--format="value(vulnerability.effectiveSeverity)" \
| if grep -Fxq CRITICAL; then
echo "Failed vulnerability check for CRITICAL level"
exit 1
else
echo "No CRITICAL vulnerability found"
exit 0
fi
O exit 1 é o mecanismo que interrompe o pipeline.
O build vulnerável deveria falhar. Isso não é defeito no pipeline.
É exatamente o comportamento de segurança desejado.
Normalmente, sucesso significa status verde.
Em um security gate, a interpretação é diferente.
Há dois resultados corretos:
O erro seria permitir que ambos seguissem o mesmo caminho.
O Dockerfile foi substituído:
FROM python:3.12-alpine
WORKDIR /app
COPY . ./
RUN pip3 install Flask==3.0.3
RUN pip3 install gunicorn==22.0.0
RUN pip3 install Werkzeug==3.0.3
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 main:app
As mudanças principais foram:
O mesmo pipeline foi executado novamente.
Dessa vez:
scan
→ nenhuma CRITICAL
→ retag :good
→ push
Esse ponto demonstra um princípio importante:
A política permanece; o artefato é que precisa evoluir para atendê-la.
Não enfraquecemos o gate para fazer o build passar.
Corrigimos a imagem.
Depois de entender o fluxo, transformei o laboratório em um script Bash que:
PROJECT_ID e PROJECT_NUMBER;us-central1;cloudbuild.yaml;Check my progress.A automação não substitui o aprendizado. Ela remove trabalho repetitivo depois que o modelo foi compreendido.
O laboratório demonstra um gate funcional, mas simplifica decisões importantes.
A regra do laboratório é:
CRITICAL → bloquear
Essa regra é um bom início, mas risco real depende de contexto.
Uma vulnerabilidade crítica pode estar:
Ao mesmo tempo, uma vulnerabilidade HIGH pode estar:
Uma política madura pode combinar:
severidade
+ exploitabilidade
+ exposição
+ fix disponível
+ criticidade do serviço
+ idade da vulnerabilidade
Um gate excessivamente rígido pode bloquear imagens que não têm correção disponível.
Isso incentiva bypasses informais.
Uma política operacional precisa de:
Contêineres podem conter vulnerabilidades em:
A cobertura depende do tipo de scanning e dos formatos suportados.
Por isso, uma pipeline completa pode combinar:
Uma vulnerabilidade pode não aparecer diretamente no requirements.txt.
Ela pode ter sido introduzida por outra biblioteca.
aplicação
→ Flask
→ dependência transitiva
→ pacote vulnerável
O relatório precisa ser conectado à árvore de dependências e ao caminho do arquivo dentro da imagem.
Tags como:
python:3.12-alpine
são mutáveis.
O conteúdo associado pode mudar.
Em produção, eu avaliaria pin por digest:
FROM python:3.12-alpine@sha256:...
Isso aumenta reprodutibilidade.
Mas cria outra responsabilidade:
atualizar deliberadamente o digest para receber patches.
Reprodutibilidade e atualização precisam coexistir.
Uma imagem segura hoje pode ficar vulnerável amanhã.
Novos CVEs são publicados continuamente.
Por isso, a estratégia não deve depender apenas de mudanças no código da aplicação.
É necessário reconstruir imagens quando:
Artifact Analysis atualiza os metadados de vulnerabilidades ao longo do tempo.
Mas descobrir uma nova vulnerabilidade não corrige a imagem em execução.
Precisamos de processos para:
O laboratório usa o formato clássico:
PROJECT_NUMBER@cloudbuild.gserviceaccount.com
Em projetos novos, Cloud Build pode usar a conta padrão do Compute Engine.
Por isso, eu não assumiria a identidade.
Verificaria:
gcloud builds get-default-service-account
Em produção, a recomendação mais segura é usar uma conta de serviço dedicada e explicitamente configurada.
A conta do build precisa de acesso para:
Ela não deveria receber permissões amplas no projeto inteiro por conveniência.
Uma arquitetura mais segura usa:
Evitaria:
ENV API_KEY=...
COPY credentials.json /app/
RUN echo "$TOKEN" > arquivo
Cada instrução pode criar camadas recuperáveis.
Segredos devem ser fornecidos de forma controlada no build ou no runtime, usando mecanismos próprios e IAM.
Um scanner confiável não compensa um builder comprometido.
É necessário proteger:
cloudbuild.yaml.Builds com acesso a recursos privados ou requisitos de isolamento podem usar Private Pools.
Isso permite controlar:
O desenho deve impedir que builds não confiáveis alcancem recursos sensíveis desnecessariamente.
O laboratório usa um único repositório.
Em produção, eu separaria estados de confiança.
A promoção pode ser feita por digest, evitando reconstruir o artefato.
Espalhar scripts grep CRITICAL por dezenas de repositórios cria inconsistência.
Eu centralizaria:
Isso pode ser implementado com:
O scan responde:
Quais vulnerabilidades conhecidas aparecem nos componentes detectados?
Uma SBOM responde:
Quais componentes fazem parte do artefato?
Proveniência responde:
Onde, quando e como o artefato foi produzido?
Uma cadeia madura combina os três.
Uma imagem pode não conter CVEs conhecidos e ainda ser maliciosa.
Exemplos:
Vulnerability scanning é uma camada, não prova total de segurança.
Ambientes diferentes podem ter exigências diferentes.
Exemplo:
| Ambiente | Política |
|---|---|
| Desenvolvimento | bloquear CRITICAL com fix |
| Homologação | bloquear CRITICAL e HIGH exploráveis |
| Produção | exigir scan, SBOM, provenance, assinatura e aprovação |
As diferenças precisam ser explícitas, não atalhos manuais.
Eu acompanharia:
Um gate sem métricas pode virar apenas atrito.
Para uma plataforma SaaS, eu implementaria:
Pull Request
→ testes
→ build
→ SBOM
→ scan
→ security gate
→ registry de release
→ attestation
→ deploy
Cada produto poderia compartilhar o mesmo template de segurança.
Isso reduz divergências entre projetos.
Em um sistema institucional, o pipeline poderia exigir:
O objetivo é evitar que um artefato construído fora do processo oficial alcance o ambiente institucional.
Em um sistema de saúde, o scan não resolve privacidade nem segurança clínica, mas reduz risco de componentes vulneráveis na plataforma.
Eu adicionaria:
| Capacidade | Google Cloud | AWS |
|---|---|---|
| Build gerenciado | Cloud Build | CodeBuild |
| Registry | Artifact Registry | ECR |
| Scan automático | Artifact Analysis | ECR Enhanced Scanning / Inspector |
| Scan sob demanda | On-Demand Scanning | Inspector/ECR e scanners integrados |
| IAM de pipeline | Service Account | IAM Role |
| Segurança no deploy | Binary Authorization | Gatekeeper/Ratify, Kyverno ou controles próprios |
| KMS | Cloud KMS | AWS KMS |
| Auditoria | Cloud Audit Logs | CloudTrail / CloudWatch |
As equivalências não são perfeitas. O modelo de integração e enforcement difere entre plataformas.
Uma solução real pode envolver custos de:
Também existe custo de tempo no pipeline.
Um scan que aumenta muito a duração pode prejudicar a experiência do desenvolvedor.
Por isso, eu avaliaria:
O laboratório usou us-central1 para o Artifact Registry.
Misturar regiões na URI causa falhas de push ou consulta.
O build precisa conseguir acessar On-Demand Scanning.
O papel deve ser concedido à conta que realmente executa o build.
O primeiro pipeline de CI/CD deveria falhar.
Tratar essa falha como problema e remover o gate destruiria o objetivo do laboratório.
O relatório pode levar algum tempo para ficar disponível.
Scripts precisam considerar retry ou espera.
A maior parte dos findings iniciais vinha do sistema operacional e dos pacotes presentes na base, não da pequena aplicação Python.
Indentação, escape de $, pipes e códigos de saída definem se o gate realmente funciona.
Um grep mal escrito pode permitir imagens que deveriam ser bloqueadas.
O laboratório não aprofunda:
Essas decisões fazem parte da arquitetura operacional.
Antes de criar um gate, eu responderia:
Qual ameaça estamos reduzindo?
CVEs conhecidos, dependência comprometida, base antiga ou artefato fora do pipeline?
Qual é o threshold?
Apenas CRITICAL, HIGH explorável ou vulnerabilidade com fix disponível?
Onde o gate atua?
Pull Request, build, push, promoção ou deploy?
Qual artefato é promovido?
A mesma imagem por digest ou uma reconstrução?
Como funcionam exceções?
Quem aprova, por quanto tempo e com qual evidência?
Como reagimos a CVE nova?
Rebuild, rollback, ticket automático ou bloqueio de novos deploys?
Quem pode alterar a política?
Desenvolvedores do produto, plataforma ou segurança?
Quais métricas mostram eficácia?
Findings, tempo de correção, bypasses e builds bloqueados.
A ferramenta deve implementar uma política de risco, não substituir essa política.
Principais conceitos utilizados neste artigo.
Serviço para armazenar e distribuir imagens e pacotes.
Serviço de scanning e gerenciamento de metadados sobre artefatos.
Scan disparado quando uma imagem é enviada ao Artifact Registry.
Atualização contínua dos metadados de vulnerabilidade de imagens já analisadas.
Scan solicitado explicitamente para uma imagem local ou remota.
Registro de que uma vulnerabilidade foi identificada em um artefato específico.
Classificação da gravidade de uma vulnerabilidade.
Severidade efetiva reportada para a occurrence no contexto analisado.
Regra que permite ou impede a continuação do pipeline.
Imagem usada como ponto de partida do Dockerfile.
Hash imutável que identifica o conteúdo da imagem.
Software Bill of Materials: inventário dos componentes de software presentes no artefato.
Evidência verificável de onde, quando e como o artefato foi produzido.
Movimento controlado de um artefato aprovado entre estágios ou registries.
O laboratório reforça habilidades importantes para Cloud, DevOps, DevSecOps e arquitetura:
Para Associate Cloud Engineer, ele reforça operação de serviços, IAM, CLI e automação.
Para Professional Cloud DevOps Engineer e Professional Cloud Security Engineer, os conceitos de supply chain e security gates são ainda mais centrais.
O laboratório começou com um pipeline capaz apenas de construir uma imagem.
Depois, esse pipeline passou a:
O aprendizado principal não foi executar um comando de scan.
Foi entender como um finding técnico pode se transformar em uma política operacional automática.
A primeira imagem falhou porque continha vulnerabilidades críticas.
Em vez de enfraquecer a regra, a imagem foi corrigida.
O mesmo pipeline foi executado novamente e permitiu a promoção.
Essa é a essência de um secure build:
o artefato precisa provar que atende aos requisitos antes de avançar.
No capítulo anterior, protegemos o deploy.
Neste capítulo, protegemos o build.
Juntos, eles formam uma cadeia de defesa mais forte:
Código
→ build seguro
→ scan
→ promoção controlada
→ attestation
→ autorização no deploy
→ runtime
No próximo capítulo da Google Cloud Master Journey 2026, a trilha continua aprofundando a segurança de containers e da cadeia de entrega.
Os próximos temas incluem:
A segurança do runtime começa no primeiro arquivo consumido pelo build.
As respostas da comunidade chegam em breve.