Ir para o conteúdo

11 - DevOps

Esta seção descreve, fielmente ao código, como o Quorum (quorum-sec-scan, v0.8.3 — revisão 2026-07-04) é construído, testado, versionado e distribuído. O Quorum é uma ferramenta CLI/Docker de consensus security scanning escrita em Go 1.26; não há serviço de longa duração, frontend web, banco de dados ou API REST. Por isso, "DevOps" aqui significa essencialmente engenharia de release: um pipeline de Integração Contínua (CI) que prova que o consenso entre scanners de fato acontece e mede a cobertura de testes, e um pipeline de Entrega Contínua (CD) que publica imagens Docker assinadas e binários nativos assinados, ambos com atestação de proveniência SLSA e SBOM SPDX atestado, verificados no próprio release. O artefato é imutável e versionado; "deploy" é publicar; "rollback" é reapontar uma moving tag para um digest anterior.

Todos os fatos abaixo derivam diretamente de:


1. Visão geral

flowchart LR
  dev[Desenvolvedor] -->|feature branch| pr[Pull Request]
  pr -->|on: pull_request| ci[ci.yml<br/>build-test + coverage]
  pr -->|on: pull_request| e2e[e2e.yml<br/>consensus]
  ci -->|verde| merge{merge na main}
  e2e -->|verde| merge
  merge -->|push: main| ci2[ci.yml + e2e.yml<br/>reexecuta na main]
  merge -->|push: main + docs/**| docs[docs.yml<br/>GitHub Pages]
  merge -->|tag semver vX.Y.Z| rel[release.yml]
  merge -->|tag semver vX.Y.Z| tm[tag-major.yml<br/>avança v0 / v0.2]
  rel --> ghcr[(GHCR<br/>imagens :full/:slim)]
  rel --> ghrel[(GitHub Release<br/>binários + checksums)]
  rel --> know[(knowledge pack<br/>atestação SLSA)]
  rel --> sig[cosign keyless + proveniência SLSA + SBOM SPDX]
Etapa Workflow Gatilho O que prova / faz
Build, teste e cobertura ci.yml push na main, qualquer pull_request Compila, go vet, go test -race com cobertura atômica, publica o resumo de cobertura, faz upload de coverage.out, constrói o binário, smoke (list-scanners)
Prova de consenso e2e.yml push na main, qualquer pull_request, workflow_dispatch Executa scanners reais sobre alvos conhecidos e falha se nenhum finding for corroborado por dois motores
Documentação docs.yml push na main tocando docs/**, mkdocs.yml ou o próprio workflow; workflow_dispatch Constrói o site bilíngue MkDocs Material (mkdocs-static-i18n) e publica no GitHub Pages
Release release.yml push da tag v[0-9]+.[0-9]+.[0-9]+, workflow_dispatch Publica imagens e binários, atesta o knowledge pack, assina (cosign keyless com retry), atesta e verifica proveniência SLSA + SBOM SPDX
Avanço de moving tag tag-major.yml push da tag v[0-9]+.[0-9]+.[0-9]+ Reposiciona v0 e v0.2 no commit do release (pin da Action)

2. Integração Contínua (CI)

2.1 ci.yml — job build-test

Gatilho: push na main e qualquer pull_request. Runner: ubuntu-latest. Go 1.26 com cache habilitado.

Passos exatos:

# Passo Comando Falha se
1 Checkout actions/checkout@v4
2 Setup Go actions/setup-go@v5 (go-version: "1.26", cache: true)
3 Vet go vet ./... Erro estático/suspeito reportado pelo vet
4 Teste (race + cobertura) go test -race -covermode=atomic -coverprofile=coverage.out ./... Qualquer teste falha ou um data race é detectado
5 Resumo de cobertura go tool cover -func=coverage.out \| tail -1 (também escrito em GITHUB_STEP_SUMMARY) — (informativo)
6 Upload da cobertura actions/upload-artifact@v4 (name: coverage, path: coverage.out, if-no-files-found: warn)
7 Build go build -trimpath -o dist/quorum ./cmd/quorum Falha de compilação
8 Smoke ./dist/quorum list-scanners Binário não executa o comando básico

Pontos relevantes:

  • -race é obrigatório. O orquestrador faz fan-out paralelo com goroutines (um scanner por goroutine), então o detector de race é a primeira linha de defesa contra regressões de concorrência.
  • Cobertura de testes no CI. O go test roda com -covermode=atomic -coverprofile=coverage.out; o passo Coverage summary imprime a linha de total (go tool cover -func) no log e no step summary, e coverage.out é publicado como artefato para inspeção posterior. A cobertura é observada em cada push/PR (não há um gate numérico que falhe o build).
  • Os testes de contrato de cada adaptador rodam contra fixtures em internal/adapter/testdata dentro do go test ./... — não exigem os scanners instalados, sendo determinísticos e rápidos. A cobertura atual abrange os 12 adaptadores (trivy, grype, checkov, kics, dockle, kubescape, polaris, kube-score, terrascan, tfsec, regula, conftest) além dos testes de dados reais (realdata_test.go).
  • O passo Smoke garante que o binário sobe e que o registro de scanners (list-scanners) responde, sem depender da rede ou de scanners externos.

Camada de advisory sob teste. A camada de advisory adicionada na v0.8.3 (opt-in via --advice, pacotes internal/enrich, internal/rag, internal/advisor) é coberta pelo mesmo go test ./... determinístico — incluindo o harness internal/evals que mede a cobertura determinística de remediação, a relevância das referências OWASP e a taxa de verify-the-fix. Esses evals rodam no CI sem modelo pesado, então permanecem rápidos e determinísticos. Sem --advice, a saída é byte a byte idêntica à do núcleo determinístico.

2.2 e2e.yml — job consensus

Este workflow é a prova viva do princípio do produto: "0 findings não é prova de segurança", e consenso só conta com scanners reais, não fixtures. Gatilho: push na main, qualquer pull_request e workflow_dispatch (execução manual).

Sequência:

  1. Checkout + Setup Go 1.26 (com cache).
  2. Build do quorum para dist/quorum e adiciona dist/ e ~/.local/bin ao GITHUB_PATH.
  3. Instala scanners por download direto dos releases (versões fixadas):
  4. Trivy 0.71.2, Grype 0.114.0 (um comentário no workflow avisa que esquemas antigos de vulnerability-DB do Grype são aposentados pela Anchore), Checkov via pipx install checkov.
  5. Verifica scanners e pré-carrega DB: trivy --version, grype version, checkov --version, grype db update (falha cedo se a DB não puder ser baixada), docker pull alpine:3.10 (garante que ambos os motores resolvem a mesma imagem local) e quorum list-scanners.
  6. Consenso de IaC (Trivy + Checkov sobre examples/terraform, --type repo, --offline): exige summary.multiDetected >= 1, caso contrário emite ::error::no cross-engine IaC consensus reached e falha.
  7. Consenso de SCA (Trivy + Grype sobre alpine:3.10, --type image, --offline): mesma regra multiDetected >= 1.
  8. Upload dos relatórios (iac.json, sca.json) como artefato, com if: always().

--offline desabilita consultas de alias no OSV.dev, tornando o e2e determinístico e independente de variação de rede no que diz respeito à correlação de aliases.

Nota sobre o escopo do e2e. O consensus prova cross-engine para SCA (Trivy + Grype) e IaC (Trivy + Checkov). O consenso para MISCONFIG/IaC e K8S_POSTURE entre os demais motores (kics, dockle, kubescape, polaris, kube-score, terrascan, tfsec, regula, conftest) é derivado em tempo de execução via o crosswalk (crosswalk/*.yaml); o e2e mantém o par determinístico mínimo que roda rápido no CI. A camada de advisory não é exercitada aqui — ela é apenas de apresentação e nunca toca o consenso.

flowchart TD
  a[Build quorum] --> b[Instala trivy/grype/checkov<br/>versões fixadas]
  b --> c[grype db update + docker pull alpine:3.10]
  c --> d[scan IaC: trivy+checkov]
  c --> e[scan SCA: trivy+grype]
  d --> f{multiDetected >= 1?}
  e --> g{multiDetected >= 1?}
  f -->|não| x1[::error:: sem consenso IaC -> FAIL]
  g -->|não| x2[::error:: sem consenso SCA -> FAIL]
  f -->|sim| h[upload iac.json/sca.json]
  g -->|sim| h

2.3 docs.yml — site bilíngue MkDocs Material no GitHub Pages

A documentação (esta e as demais seções em docs/) é publicada como site estático no GitHub Pages via MkDocs Material. Desde a v0.8.3 o site é bilíngue — inglês (padrão) e português brasileiro — construído com o plugin mkdocs-static-i18n.

  • Gatilho: push na main tocando docs/**, mkdocs.yml ou .github/workflows/docs.yml; ou workflow_dispatch. Mudanças que não tocam a documentação não reconstroem o site.
  • Permissões: contents: read, pages: write, id-token: write (deploy OIDC do Pages).
  • Concorrência: grupo pages com cancel-in-progress: false — permite uma publicação por vez, sem cancelar um deploy em andamento.
  • Job build: actions/checkout@v4actions/setup-python@v5 (python-version: "3.x") → pip install "mkdocs-material==9.*" "mkdocs-static-i18n==1.*"mkdocs build --site-dir siteactions/configure-pages@v5actions/upload-pages-artifact@v3 (path: site).
  • Job deploy (needs: build): actions/deploy-pages@v4, no ambiente github-pages, expõe a URL publicada (page_url).

O site é uma projeção somente-leitura de docs/. Não há build de aplicação nem estado de runtime — apenas HTML estático gerado a partir de Markdown. Cada página tem uma fonte em inglês (<arquivo>.md) e uma em português (<arquivo>.pt.md); o plugin i18n constrói o seletor de idioma.


3. Git flow e estratégia de branching

O fluxo é o clássico GitHub Flow, baseado em PR para a main, com versionamento por tag semver e moving tags avançadas automaticamente.

gitGraph
  commit id: "main"
  branch feature/xyz
  commit id: "work"
  commit id: "more work"
  checkout main
  merge feature/xyz tag: "PR + CI verde"
  commit id: "v0.8.3" tag: "v0.8.3"

Regras práticas:

  1. A main é a linha de release. Todo trabalho acontece em branches de feature/fix (ex.: ci/auto-advance-major-tag, docs/fix-site-links, como visto no histórico recente).
  2. Toda mudança entra via Pull Request. O PR dispara ci.yml (build-test + coverage) e e2e.yml (consensus). Ambos precisam estar verdes antes do merge.
  3. Merge na main reexecuta ci.yml e e2e.yml no push para a main (defesa em profundidade contra races de merge) e, se docs/** mudou, dispara docs.yml para republicar o site.
  4. Release por tag semver. Apenas tags no formato v[0-9]+.[0-9]+.[0-9]+ (ex.: v0.8.3) disparam release.yml. Não há release automático no merge — a tag é o gesto explícito de publicação.
  5. Moving tags v0 (major) e v0.2 (minor) existem para pinar a GitHub Action: consumidores usam uses: Martinez1991/quorum-sec-scan@v0. Essas tags são avançadas automaticamente por tag-major.yml a cada release semver — não é mais um passo manual. Tanto release.yml quanto tag-major.yml são restritos a tags semver completas (X.Y.Z), então mover o ponteiro da Action não re-dispara um build nem republica imagens.

3.1 tag-major.yml — avanço automático de moving tag

flowchart LR
  t[push tag vX.Y.Z] --> j[job move]
  j --> chk[checkout fetch-depth: 0]
  chk --> calc["deriva major=vX e minor=vX.Y<br/>(remove o sufixo)"]
  calc --> mv["git tag -f -a vX / vX.Y -> mesmo commit"]
  mv --> push["git push -f origin vX e vX.Y"]
  push --> stop["não re-dispara release.yml/tag-major.yml<br/>(escopo semver + push via GITHUB_TOKEN)"]
  • Gatilho: push da tag v[0-9]+.[0-9]+.[0-9]+. Permissão: contents: write.
  • Lógica: a partir de github.ref_name (ex.: v0.8.3) deriva major (v0) e minor (v0.8) e força a atualização de ambas as tags anotadas para github.sha (git tag -f -a + git push -f), como github-actions[bot].
  • Sem loop: o gatilho só aceita tags semver completas, e pushes feitos com GITHUB_TOKEN não disparam novos workflows — por isso mover v0/v0.2 nem reconstrói imagens nem se re-invoca.

Checklist de PR (recomendado)

  • [ ] Branch criada a partir de uma main atualizada.
  • [ ] go vet ./... e go test -race ./... passam localmente.
  • [ ] Novos parsers de adaptador têm um teste de contrato contra uma fixture em internal/adapter/testdata.
  • [ ] Novas correlações de crosswalk (AWS/Azure/GCP/K8s) são respaldadas por saída real de scanner (falso split > falso merge).
  • [ ] CI (build-test) verde; cobertura observada no step summary.
  • [ ] E2E (consensus) verde — o consenso real ainda acontece.
  • [ ] Se a camada de advisory mudou, internal/evals ainda passa (cobertura de remediação, relevância das referências OWASP, taxa de verify-the-fix) e a saída sem --advice continua byte a byte idêntica.
  • [ ] Se você tocou docs/**/mkdocs.yml, confirme que o build do site (docs.yml) está verde nos dois idiomas.
  • [ ] Mensagens de commit seguem prefixos convencionais (fix:, feat:, ci:, etc.); docs:/test:/chore: são filtrados do changelog (ver .goreleaser.yaml).

4. Entrega Contínua (CD) — release.yml

Gatilho do release: push da tag v[0-9]+.[0-9]+.[0-9]+ ou workflow_dispatch (com input version, padrão dev).

Permissões do workflow (menor privilégio necessário):

Permissão Por quê
contents: read (job images/knowledge) / contents: write (job binaries) Ler o repo; criar o release e subir os assets de binário
packages: write Push das imagens para o GHCR
id-token: write OIDC para assinatura keyless com cosign (Sigstore) e atestação keyless
attestations: write Atestação de proveniência de build SLSA e atestação de SBOM SPDX

4.1 Job images

Matriz de variantes (fail-fast: false):

Variante Dockerfile Plataformas Conteúdo
full Dockerfile.full linux/amd64 Todos os scanners embutidos (os binários dos scanners são amd64; DB do grype pré-cacheada com GRYPE_DB_VALIDATE_AGE=false)
slim Dockerfile linux/amd64, linux/arm64 Apenas o orquestrador

Resolução de tags (passo meta), com image=ghcr.io/<owner>/<repo> em minúsculas e version derivada de GITHUB_REF_NAME sem o prefixo v:

Variante Tags publicadas
full :full, :<version>, :<version>-full, :latest
slim :slim, :<version>-slim

Pipeline do job:

  1. Checkout.
  2. Resolve versão e nome da imagem (passo meta, escreve image, version, tags em GITHUB_OUTPUT).
  3. QEMU + Buildx (necessários para o build multi-arch do slim).
  4. Login no GHCR com GITHUB_TOKEN.
  5. Instala cosign (sigstore/cosign-installer@v3).
  6. Build & push via docker/build-push-action@v6 com provenance: true, sbom: true, cache GHA escopado (quorum-<variant>, mode=max) e build-args: VERSION=<version>.
  7. Assina imagem (keyless) com retry: cosign sign --yes "${IMAGE}@${DIGEST}" envolto em um helper retry() (4 tentativas, backoff 15s·i). A assinatura keyless depende do endpoint OIDC do GitHub + Sigstore (Fulcio/Rekor), que ocasionalmente dá timeout em builds longos e pode expirar o token OIDC no meio da assinatura; cada tentativa re-obtém um token novo. Assina o digest do manifesto (multi-arch) uma vez, cobrindo toda tag que aponta para ele.
  8. Atesta proveniência de build SLSA: actions/attest-build-provenance@v2 gera uma atestação SLSA v1 (subject-digest = digest da imagem, push-to-registry: true), registrando como a imagem foi construída (workflow, commit, runner).
  9. Verifica a atestação de proveniência (com retry): gh attestation verify "oci://${IMAGE}@${DIGEST}" --repo ... sob o mesmo retry() — re-verifica ponta a ponta (log de transparência Sigstore + identidade OIDC). Uma atestação quebrada falha o release.
  10. Gera SBOM da imagem (syft): anchore/sbom-action@v0 gera sbom.spdx.json (formato spdx-json) para a imagem por digest.
  11. Atesta o SBOM da imagem: actions/attest-sbom@v2 emite uma atestação SPDX de primeira classe (verificável por gh attestation verify/cosign), com push-to-registry: true, além da atestação sbom: true do BuildKit gerada no passo de build.
  12. Summary: escreve variante, plataformas, digest e tags em GITHUB_STEP_SUMMARY.

4.2 Job binaries

Condição: if: github.ref_type == 'tag' (o GoReleaser precisa da tag). Permissões adicionais: contents: write, id-token: write, attestations: write.

Pipeline:

  1. Checkout com fetch-depth: 0 (o GoReleaser precisa do histórico completo para o changelog).
  2. Setup Go 1.26.
  3. Instala cosign.
  4. Instala syft (anchore/sbom-action/download-syft@v0) — usado pelo GoReleaser para gerar os SBOMs de arquivo.
  5. GoReleaser (goreleaser/goreleaser-action@v6, version: "~> v2", args: release --clean) — conforme .goreleaser.yaml:
  6. Compila CGO_ENABLED=0, -trimpath, ldflags -s -w -X main.version={{.Version}}.
  7. Matriz goos: [linux, darwin, windows] x goarch: [amd64, arm64].
  8. Arquivos quorum_<version>_<os>_<arch> (zip no Windows) incluindo README.md, README.pt-BR.md, LICENSE e o diretório crosswalk/** (empacota os crosswalks padrão — AWS/Azure/GCP/K8s).
  9. SBOM SPDX por arquivo (sboms, artifacts: archive, via syft) — cada release entrega um bill of materials legível por máquina para os binários.
  10. checksums.txt cobrindo todos os artefatos.
  11. Assinatura keyless do arquivo de checksum via cosign sign-blob (produz ${artifact}.sig e ${artifact}.pem). Como o checksum pina os hashes de todos os artefatos, a assinatura sobre ele cobre o release inteiro.
  12. Cria o GitHub Release (draft: false, prerelease: auto) com changelog do GitHub (exclui docs:/test:/chore:).
  13. Atesta proveniência de build SLSA dos binários: subject-checksums: dist/checksums.txt — uma atestação cobrindo cada artefato listado.
  14. Verifica a atestação de proveniência dos binários (com retry): gh attestation verify dist/quorum_*_linux_amd64.tar.gz --repo ... sob o helper retry() (spot-check de um artefato).

4.3 Job knowledge — atestando o knowledge pack de advisory

Novo na v0.8.3. O knowledge pack da camada de advisory — os templates de remediação curados mais o corpus OWASP com digest fixado (knowledge/*.yaml, knowledge/owasp/corpus.yaml) e os mapeamentos crosswalk/** — vai dentro das imagens, mas é também um artefato de dados que um consumidor pode querer verificar de forma independente. Por isso ele ganha proveniência de build SLSA de primeira classe, como as imagens e os binários.

  • Permissões: contents: read, id-token: write (atestação keyless via Sigstore OIDC), attestations: write.
  • Pipeline:
  • Checkout.
  • Computa os checksums do knowledge: uma lista determinística e ordenada de cada arquivo do pack — find knowledge crosswalk -type f \( -name '*.yaml' -o -name '*.yml' \) -print0 | sort -z | xargs -0 sha256sum > knowledge.sha256.
  • Atesta a proveniência do knowledge: actions/attest-build-provenance@v2 com subject-checksums: knowledge.sha256 — uma atestação cobrindo cada arquivo do pack.
  • Verifica a atestação do knowledge (com retry): gh attestation verify knowledge/owasp/corpus.yaml --repo ... — faz spot-check do corpus OWASP contra a atestação recém-criada.

Como o corpus é fixado por digest e agora carrega uma atestação de proveniência, o passo quorum advise-index (que embeda o corpus para retrieval semântico) preserva o mesmo pin, e um consumidor pode verificar o pack com gh attestation verify knowledge/owasp/corpus.yaml antes de confiar no advice que ele alimenta.

flowchart TD
  tag[push tag vX.Y.Z] --> imgs[job: images]
  tag --> bins[job: binaries]
  tag --> know[job: knowledge]

  subgraph images[job images — matriz full/slim]
    m1[meta: resolve tags] --> m2[buildx + qemu]
    m2 --> m3[build-push provenance+sbom]
    m3 --> m4[cosign sign digest + retry]
    m4 --> m5[atesta proveniência SLSA]
    m5 --> m6[gh attestation verify + retry]
    m6 --> m7[syft SBOM spdx-json]
    m7 --> m8[atesta SBOM SPDX]
  end

  subgraph binaries[job binaries — GoReleaser]
    b1[goreleaser release --clean] --> b2[archives + SBOM + checksums.txt]
    b2 --> b3[cosign sign-blob checksums]
    b3 --> b4[GitHub Release]
    b4 --> b5[atesta SLSA sobre checksums]
    b5 --> b6[gh attestation verify spot-check + retry]
  end

  subgraph knowledge[job knowledge — pack de advisory]
    k1[sha256sum knowledge/ + crosswalk/] --> k2[atesta SLSA sobre knowledge.sha256]
    k2 --> k3[gh attestation verify corpus.yaml + retry]
  end

  m8 --> ok1[(GHCR: imagens assinadas + atestadas + SBOM)]
  b6 --> ok2[(GitHub Release: binários assinados + atestados + SBOM)]
  k3 --> ok3[(knowledge pack: atestado por SLSA)]

5. "Deploy" para um CLI/Docker

Não há ambiente de runtime gerenciado por este projeto. Deploy = publicar artefatos imutáveis e verificáveis. A unidade de deploy é o usuário ou pipeline consumidor que faz pull da imagem ou baixa o binário.

Conceito tradicional Equivalente no Quorum
Servidor/ambiente de runtime N/A — não há serviço persistente
Deploy Publicar imagens no GHCR + Release de binários no GitHub
Versão imutável Digest da imagem (@sha256:...) e tag semver
Integridade/origem cosign keyless (OIDC) + atestação de proveniência SLSA + SBOM SPDX atestado (+ knowledge pack atestado por SLSA)
Configuração de runtime Flags de CLI (--type, --scanners, --fail-on, --metrics, --log-format, as flags de advisory --advice, --advice-provider, --fix, ...), passthrough por scanner (QUORUM_<SCANNER>_ARGS) e mounts do Docker

A camada de advisory é configuração opt-in, não um toggle de runtime. Todo comportamento de IA fica desligado por padrão e é selecionado por invocação: --advice liga o enriquecimento determinístico das Fases 0/2 (templates de remediação + RAG OWASP com digest fixado); --advice-provider=local|remote e --fix=suggest adicionam as fases opt-in de LLM. Sem --advice, a saída é byte a byte idêntica à do núcleo determinístico, que continua sem IA.

Como o consumidor verifica antes de usar

Imagem:

cosign verify ghcr.io/martinez1991/quorum-sec-scan:slim \
  --certificate-identity-regexp \
    "https://github.com/Martinez1991/quorum-sec-scan/.github/workflows/release.yml@.*" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

gh attestation verify oci://ghcr.io/martinez1991/quorum-sec-scan:slim \
  --repo Martinez1991/quorum-sec-scan

O próprio action.yml faz isso automaticamente: por padrão (verify: true) ele verifica com cosign ghcr.io/martinez1991/quorum-sec-scan:full (instalando o cosign se necessário) antes de rodar a imagem via docker run. Em scans de imagem local (type: image) a Action monta automaticamente /var/run/docker.sock (input docker-socket), evitando um falso-zero por imagem inacessível. Pinar a imagem por @sha256:... em produção é recomendado (input image). A Action também expõe todos os inputs de advisory (advice, advice-provider, advice-endpoint, advice-model, advice-embed-model, advice-max, advice-cache, advice-allow-egress, advice-api-key, fix): para local ela adiciona automaticamente host-gateway para o container alcançar um endpoint on-host, e para remote encaminha a chave de API via env.

Knowledge pack (o artefato de dados de advisory):

gh attestation verify knowledge/owasp/corpus.yaml \
  --repo Martinez1991/quorum-sec-scan

Binário (de um release):

cosign verify-blob checksums.txt \
  --signature checksums.txt.sig \
  --certificate checksums.txt.pem \
  --certificate-identity-regexp \
    "https://github.com/Martinez1991/quorum-sec-scan/.github/workflows/release.yml@.*" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com
sha256sum -c checksums.txt

6. Rollback

Como os artefatos são imutáveis e versionados, o rollback é uma operação de reapontar/pinar, não "desfazer um deploy".

Para o consumidor (recomendado)

  • Pinar em uma versão exata: troque :latest/:full por :<versão-anterior>-full (ex.: :0.8.2-full) ou, idealmente, pelo digest @sha256:... de um release conhecido-bom.
  • Action: troque @v0 por uma tag fixa anterior (@v0.8.2), ou pine image: no digest desejado.
Cenário Ação de rollback
Nova imagem com regressão Repinar em :<versão-anterior>-full ou no digest anterior
Binário com bug Baixar o asset do release anterior (assinado)
Action @v0 instável Pinar um @vX.Y.Z específico ou pinar image: por digest

Para o mantenedor

  • Re-taguear a moving tag: mover :full/:latest/:v0/:v0.2 de volta para o digest do release anterior. O cosign assina o digest, então a assinatura/atestação do release anterior continua válida ao reapontar a tag. Note que tag-major.yml só avança v0/v0.2 para frente no próximo release semver — um rollback dessas tags é uma intervenção manual do mantenedor.
  • Forward-fix preferido: cortar uma nova tag semver (vX.Y.Z+1) com a correção é o caminho mais limpo, já que cada release é totalmente reconstruído, assinado e atestado — e tag-major.yml re-alinha as moving tags automaticamente.

Por design, uma tag semver existente não é sobrescrita com conteúdo diferente. A tag semver é imutável; as moving tags (full, latest, v0, v0.2) são as que reposicionam.

Checklist de rollback

  • [ ] Identificar a última versão conhecida-boa (tag semver + digest).
  • [ ] Consumidores: repinar na versão/digest anterior (ou @vX.Y.Z da Action).
  • [ ] Mantenedor: se necessário, reapontar as moving tags (v0/v0.2/full/latest) para o digest anterior.
  • [ ] Confirmar cosign verify + gh attestation verify no artefato-alvo.
  • [ ] Abrir um forward-fix e cortar uma nova tag semver o quanto antes (tag-major.yml re-alinha v0/v0.2).

7. Feature flags, Blue-Green e Canary

N/A para o produto. Essas técnicas assumem um serviço de longa duração com tráfego vivo que pode ser roteado, chaveado ou migrado gradualmente. O Quorum é um processo CLI/Docker efêmero: ele roda, produz um relatório (SARIF/JSON/XML) e sai. Não há tráfego, nem réplicas paralelas, nem estado de runtime para chavear.

Técnica Status Justificativa
Feature flags N/A O comportamento é controlado por flags de CLI no momento da invocação (--scanners, --format, --fail-on, --offline, --metrics, --log-format, --advice, --fix, ...) e env de passthrough (QUORUM_<SCANNER>_ARGS); não há toggles dinâmicos de runtime
Blue-Green N/A Não há ambiente "vivo" com pool de tráfego para chavear
Canary N/A (no produto) Não há frota servindo requisições para fazer rollout gradual

Como o CONSUMIDOR pode fazer canary de versões

Embora o produto não suporte canary internamente, o pipeline consumidor pode fazer canary de versões do Quorum com estratégias padrão de CI:

  • Pin escalonado: a maioria dos repositórios pina uma versão estável (digest/@vX.Y.Z); um repositório piloto adota a nova versão antes da adoção ampla.
  • Execução paralela não-bloqueante: rode a nova versão em um job com continue-on-error: true ao lado da versão estável (gate), comparando o summary e o exit-code antes de promover.
  • workflow_dispatch / matriz de versões: compare relatórios de duas tags (estável vs. candidata) sobre o mesmo alvo e só então atualize o pin.
  • Comparação determinística de relatórios: use --offline para reduzir variância de rede ao comparar multiDetected/detectionCount entre versões.
flowchart LR
  repoA[Repos de produção<br/>pin estável @vX.Y.Z] --> use[Quorum estável]
  repoP[Repo piloto / job canary] --> cand[Quorum candidato @vX.Y.Z+1]
  cand -->|compara summary/exit-code| dec{ok?}
  dec -->|sim| promote[promove o pin para a nova versão]
  dec -->|não| keep[mantém o pin anterior]

Uma feature flag de comportamento de scan = simplesmente escolher as flags de CLI por invocação. Não há — nem se deseja — um sistema de flags dinâmicas para um binário de execução única.


8. Resumo de gatilhos e responsabilidades

Workflow pull_request push: main tag vX.Y.Z workflow_dispatch
ci.yml (build-test + coverage) sim sim
e2e.yml (consensus) sim sim sim
docs.yml (GitHub Pages, i18n) sim (paths docs/**, mkdocs.yml, o workflow) sim
release.yml (images+binaries+knowledge) sim sim
tag-major.yml (avança v0/v0.2) sim
Artefato Onde Assinatura Proveniência SBOM Multi-arch
Imagem :full GHCR cosign keyless (retry) SLSA (verificada) SPDX atestado (syft) + BuildKit linux/amd64
Imagem :slim GHCR cosign keyless (retry) SLSA (verificada) SPDX atestado (syft) + BuildKit linux/amd64, linux/arm64
Binários GitHub Release cosign sign-blob (sobre checksums) SLSA (verificada) SPDX por arquivo (syft) linux/darwin/windows x amd64/arm64
Knowledge pack (templates + corpus OWASP + crosswalk) in-image / dados do repo — (listado em checksum) SLSA (verificada) N/A

Premissas

  1. O conteúdo dos Dockerfiles não foi detalhado linha a linha. As variantes :full/:slim e seu conteúdo (scanners embutidos, DB do grype pré-cacheada com GRYPE_DB_VALIDATE_AGE=false) baseiam-se nos comentários do release.yml, no .goreleaser.yaml e na descrição do produto; o passo de build em si (docker/build-push-action@v6) foi documentado a partir do workflow, não de uma análise interna de Dockerfile/Dockerfile.full.
  2. secrets.GITHUB_TOKEN é o token padrão fornecido pelo GitHub Actions; assume-se que os escopos packages: write, id-token: write, attestations: write e pages: write estão disponíveis no repositório (eles são declarados nos workflows).
  3. Branch protection / required checks: o documento descreve o fluxo PR-para-main como prática; não há arquivo de configuração de branch-protection verificável aqui no repositório, então as regras de "required checks" são recomendações alinhadas ao comportamento dos workflows. Da mesma forma, a cobertura é observada (summary + artefato) mas não há um gate numérico que falhe o build.
  4. Comandos de verificação do consumidor (cosign/gh attestation verify) foram extrapolados dos comentários e passos de verificação presentes em release.yml e action.yml; o regexp de identidade usa o owner Martinez1991, conforme action.yml.
  5. v0/v0.2 como moving tags da Action: o reposicionamento é automatizado pelo workflow .github/workflows/tag-major.yml, que roda a cada release semver e força a atualização de v0/v0.2 para o mesmo commit (git tag -f -a + git push -f como github-actions[bot]). Essas tags não re-disparam release.yml/tag-major.yml porque ambos são restritos a tags semver completas e pushes via GITHUB_TOKEN não encadeiam novos workflows.
  6. GitHub Pages: docs.yml publica o site MkDocs Material; habilitar o Pages com "Build and deployment → Source: GitHub Actions" é um pré-requisito de configuração do repositório (documentado no comentário do workflow) que não é verificável apenas pelos arquivos versionados.

Veja também: README.md · README.pt-BR.md · DESIGN.md (supply chain §12, status dos scanners §14).