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:
.github/workflows/ci.yml— jobbuild-test(com cobertura)..github/workflows/e2e.yml— jobconsensus..github/workflows/release.yml— jobsimages,binarieseknowledge..github/workflows/tag-major.yml— jobmove(avança as moving tagsv0/v0.2a cada release semver)..github/workflows/docs.yml— jobsbuild/deploy(site bilíngue MkDocs Material no GitHub Pages)..goreleaser.yaml— build dos binários nativos.action.yml— GitHub Action composta que encapsula a imagem:full.
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 testroda com-covermode=atomic -coverprofile=coverage.out; o passoCoverage summaryimprime a linha de total (go tool cover -func) no log e no step summary, ecoverage.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/testdatadentro dogo 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, pacotesinternal/enrich,internal/rag,internal/advisor) é coberta pelo mesmogo test ./...determinístico — incluindo o harnessinternal/evalsque 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:
- Checkout + Setup Go 1.26 (com cache).
- Build do quorum para
dist/quorume adicionadist/e~/.local/binaoGITHUB_PATH. - Instala scanners por download direto dos releases (versões fixadas):
- Trivy
0.71.2, Grype0.114.0(um comentário no workflow avisa que esquemas antigos de vulnerability-DB do Grype são aposentados pela Anchore), Checkov viapipx install checkov. - 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) equorum list-scanners. - Consenso de IaC (Trivy + Checkov sobre
examples/terraform,--type repo,--offline): exigesummary.multiDetected >= 1, caso contrário emite::error::no cross-engine IaC consensus reachede falha. - Consenso de SCA (Trivy + Grype sobre
alpine:3.10,--type image,--offline): mesma regramultiDetected >= 1. - Upload dos relatórios (
iac.json,sca.json) como artefato, comif: always().
--offlinedesabilita 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
consensusprova 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:
pushnamaintocandodocs/**,mkdocs.ymlou.github/workflows/docs.yml; ouworkflow_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
pagescomcancel-in-progress: false— permite uma publicação por vez, sem cancelar um deploy em andamento. - Job
build:actions/checkout@v4→actions/setup-python@v5(python-version: "3.x") →pip install "mkdocs-material==9.*" "mkdocs-static-i18n==1.*"→mkdocs build --site-dir site→actions/configure-pages@v5→actions/upload-pages-artifact@v3(path: site). - Job
deploy(needs: build):actions/deploy-pages@v4, no ambientegithub-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:
- 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). - Toda mudança entra via Pull Request. O PR dispara
ci.yml(build-test + coverage) ee2e.yml(consensus). Ambos precisam estar verdes antes do merge. - Merge na
mainreexecutaci.ymlee2e.ymlnopushpara amain(defesa em profundidade contra races de merge) e, sedocs/**mudou, disparadocs.ymlpara republicar o site. - Release por tag semver. Apenas tags no formato
v[0-9]+.[0-9]+.[0-9]+(ex.:v0.8.3) disparamrelease.yml. Não há release automático no merge — a tag é o gesto explícito de publicação. - Moving tags
v0(major) ev0.2(minor) existem para pinar a GitHub Action: consumidores usamuses: Martinez1991/quorum-sec-scan@v0. Essas tags são avançadas automaticamente portag-major.ymla cada release semver — não é mais um passo manual. Tantorelease.ymlquantotag-major.ymlsã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:
pushda tagv[0-9]+.[0-9]+.[0-9]+. Permissão:contents: write. - Lógica: a partir de
github.ref_name(ex.:v0.8.3) derivamajor(v0) eminor(v0.8) e força a atualização de ambas as tags anotadas paragithub.sha(git tag -f -a+git push -f), comogithub-actions[bot]. - Sem loop: o gatilho só aceita tags semver completas, e pushes feitos com
GITHUB_TOKENnão disparam novos workflows — por isso moverv0/v0.2nem reconstrói imagens nem se re-invoca.
Checklist de PR (recomendado)¶
- [ ] Branch criada a partir de uma
mainatualizada. - [ ]
go vet ./...ego 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/evalsainda passa (cobertura de remediação, relevância das referências OWASP, taxa de verify-the-fix) e a saída sem--advicecontinua 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:
- Checkout.
- Resolve versão e nome da imagem (passo
meta, escreveimage,version,tagsemGITHUB_OUTPUT). - QEMU + Buildx (necessários para o build multi-arch do
slim). - Login no GHCR com
GITHUB_TOKEN. - Instala cosign (
sigstore/cosign-installer@v3). - Build & push via
docker/build-push-action@v6comprovenance: true,sbom: true, cache GHA escopado (quorum-<variant>,mode=max) ebuild-args: VERSION=<version>. - Assina imagem (keyless) com retry:
cosign sign --yes "${IMAGE}@${DIGEST}"envolto em um helperretry()(4 tentativas, backoff15s·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. - Atesta proveniência de build SLSA:
actions/attest-build-provenance@v2gera uma atestação SLSA v1 (subject-digest= digest da imagem,push-to-registry: true), registrando como a imagem foi construída (workflow, commit, runner). - Verifica a atestação de proveniência (com retry):
gh attestation verify "oci://${IMAGE}@${DIGEST}" --repo ...sob o mesmoretry()— re-verifica ponta a ponta (log de transparência Sigstore + identidade OIDC). Uma atestação quebrada falha o release. - Gera SBOM da imagem (syft):
anchore/sbom-action@v0gerasbom.spdx.json(formatospdx-json) para a imagem por digest. - Atesta o SBOM da imagem:
actions/attest-sbom@v2emite uma atestação SPDX de primeira classe (verificável porgh attestation verify/cosign), compush-to-registry: true, além da atestaçãosbom: truedo BuildKit gerada no passo de build. - 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:
- Checkout com
fetch-depth: 0(o GoReleaser precisa do histórico completo para o changelog). - Setup Go 1.26.
- Instala cosign.
- Instala syft (
anchore/sbom-action/download-syft@v0) — usado pelo GoReleaser para gerar os SBOMs de arquivo. - GoReleaser (
goreleaser/goreleaser-action@v6,version: "~> v2",args: release --clean) — conforme.goreleaser.yaml: - Compila
CGO_ENABLED=0,-trimpath,ldflags -s -w -X main.version={{.Version}}. - Matriz
goos: [linux, darwin, windows]xgoarch: [amd64, arm64]. - Arquivos
quorum_<version>_<os>_<arch>(zip no Windows) incluindoREADME.md,README.pt-BR.md,LICENSEe o diretóriocrosswalk/**(empacota os crosswalks padrão — AWS/Azure/GCP/K8s). - 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. checksums.txtcobrindo todos os artefatos.- Assinatura keyless do arquivo de checksum via
cosign sign-blob(produz${artifact}.sige${artifact}.pem). Como o checksum pina os hashes de todos os artefatos, a assinatura sobre ele cobre o release inteiro. - Cria o GitHub Release (
draft: false,prerelease: auto) com changelog do GitHub (excluidocs:/test:/chore:). - Atesta proveniência de build SLSA dos binários:
subject-checksums: dist/checksums.txt— uma atestação cobrindo cada artefato listado. - Verifica a atestação de proveniência dos binários (com retry):
gh attestation verify dist/quorum_*_linux_amd64.tar.gz --repo ...sob o helperretry()(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@v2comsubject-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 comgh attestation verify knowledge/owasp/corpus.yamlantes 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:
--adviceliga o enriquecimento determinístico das Fases 0/2 (templates de remediação + RAG OWASP com digest fixado);--advice-provider=local|remotee--fix=suggestadicionam 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):
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/:fullpor:<versão-anterior>-full(ex.::0.8.2-full) ou, idealmente, pelo digest@sha256:...de um release conhecido-bom. - Action: troque
@v0por uma tag fixa anterior (@v0.8.2), ou pineimage: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.2de 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 quetag-major.ymlsó avançav0/v0.2para 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 — etag-major.ymlre-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.Zda Action). - [ ] Mantenedor: se necessário, reapontar as moving tags (
v0/v0.2/full/latest) para o digest anterior. - [ ] Confirmar
cosign verify+gh attestation verifyno artefato-alvo. - [ ] Abrir um forward-fix e cortar uma nova tag semver o quanto antes (
tag-major.ymlre-alinhav0/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: trueao lado da versão estável (gate), comparando osummarye oexit-codeantes 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
--offlinepara reduzir variância de rede ao compararmultiDetected/detectionCountentre 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¶
- O conteúdo dos Dockerfiles não foi detalhado linha a linha. As variantes
:full/:slime seu conteúdo (scanners embutidos, DB do grype pré-cacheada comGRYPE_DB_VALIDATE_AGE=false) baseiam-se nos comentários dorelease.yml, no.goreleaser.yamle 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 deDockerfile/Dockerfile.full. secrets.GITHUB_TOKENé o token padrão fornecido pelo GitHub Actions; assume-se que os escopospackages: write,id-token: write,attestations: writeepages: writeestão disponíveis no repositório (eles são declarados nos workflows).- Branch protection / required checks: o documento descreve o fluxo PR-para-
maincomo 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. - Comandos de verificação do consumidor (cosign/
gh attestation verify) foram extrapolados dos comentários e passos de verificação presentes emrelease.ymleaction.yml; o regexp de identidade usa o ownerMartinez1991, conformeaction.yml. v0/v0.2como 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 dev0/v0.2para o mesmo commit (git tag -f -a+git push -fcomogithub-actions[bot]). Essas tags não re-disparamrelease.yml/tag-major.ymlporque ambos são restritos a tags semver completas e pushes viaGITHUB_TOKENnão encadeiam novos workflows.- GitHub Pages:
docs.ymlpublica 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).