| Servicio | Digest (SHA256) | Firmado | SBOM | Vuln Scan | CVEs Críticos |
|---|---|---|---|---|---|
| agriflow-api Node.js 22 |
sha256:4a8b2c... | Firmado | SBOM adjunto | Escaneado | 0 |
| agriflow-ml Python 3.12 |
sha256:9f3d7a... | Firmado | SBOM adjunto | Escaneado | 0 |
| agriflow-worker Node.js 22 |
sha256:c12e8b... | Firmado | SBOM adjunto | Escaneado | 2 Medium |
| agriflow-frontend React / Nginx |
sha256:7b5f1d... | Firmado | SBOM adjunto | Escaneado | 0 |
Enforce; ningún pod no firmado puede entrar al namespace production.
name: Build, Push & Sign Image on: push: branches: [main] tags: ['v*'] env: REGISTRY: ghcr.io ORG: agriflow-io jobs: build-and-sign: runs-on: ubuntu-latest strategy: matrix: service: [agriflow-api, agriflow-ml, agriflow-worker, agriflow-frontend] permissions: contents: read packages: write id-token: write # Requerido para firma keyless Sigstore steps: - name: Checkout uses: actions/checkout@v4 - name: Login a GitHub Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build y Push imagen id: build run: | IMAGE=${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service }} SHA_TAG=${IMAGE}:${{ github.sha }} docker build -t ${SHA_TAG} ./services/${{ matrix.service }} docker push ${SHA_TAG} # Exportar digest para usarlo en firma DIGEST=$(docker inspect --format '{{index .RepoDigests 0}}' ${SHA_TAG}) echo "digest=${DIGEST}" >> $GITHUB_OUTPUT - name: Instalar Cosign uses: sigstore/cosign-installer@v3 - name: Firmar imagen (keyless) run: | cosign sign --yes ${{ steps.build.outputs.digest }} env: COSIGN_EXPERIMENTAL: "1" - name: Instalar syft (SBOM) uses: anchore/sbom-action/download-syft@v0 - name: Generar y adjuntar SBOM run: | syft ${{ steps.build.outputs.digest }} -o spdx-json > sbom.spdx.json cosign attest --yes \ --predicate sbom.spdx.json \ --type spdxjson \ ${{ steps.build.outputs.digest }} - name: Escanear vulnerabilidades (grype) uses: anchore/scan-action@v4 with: image: ${{ steps.build.outputs.digest }} output-format: json fail-build: false - name: Adjuntar resultados de escaneo run: | cosign attest --yes \ --predicate results.json \ --type vuln \ ${{ steps.build.outputs.digest }}
# ── Verificar firma de imagen ──────────────────────────────────── cosign verify \ --certificate-identity-regexp="https://github.com/agriflow-io/.*" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ ghcr.io/agriflow-io/agriflow-api@sha256:4a8b2c... # Salida esperada: Verification for ghcr.io/agriflow-io/agriflow-api@sha256:4a8b2c -- The following checks were performed on each of these signatures: - The cosign claims were validated - Existence of the claims in the transparency log was verified offline - The code-signing certificate claims were validated # ── Verificar y ver SBOM adjunto ──────────────────────────────── cosign verify-attestation \ --type spdxjson \ --certificate-identity-regexp="https://github.com/agriflow-io/.*" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ ghcr.io/agriflow-io/agriflow-api@sha256:4a8b2c... \ | jq '.payload' | base64 -d | jq '.' # ── Ver resultados de vulnerabilidades ─────────────────────────── cosign verify-attestation \ --type vuln \ --certificate-identity-regexp="https://github.com/agriflow-io/.*" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ ghcr.io/agriflow-io/agriflow-api@sha256:4a8b2c... \ | jq '.payload' | base64 -d | jq '.predicate.scanner.result.matches[] | {severity, package: .artifact.name}' # ── Buscar en el Rekor transparency log ────────────────────────── rekor-cli search --sha sha256:4a8b2c...
ghcr.io/agriflow-io/* sin firma válida de GitHub Actions.
apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: verify-agriflow-signatures annotations: policies.kyverno.io/title: Verify AgriFlow Image Signatures policies.kyverno.io/description: | Verifica que todas las imágenes de ghcr.io/agriflow-io/* estén firmadas por GitHub Actions usando Sigstore keyless signing. spec: validationFailureAction: Enforce background: false rules: - name: verify-cosign-keyless match: any: - resources: kinds: ["Pod"] namespaces: ["production"] verifyImages: - imageReferences: - "ghcr.io/agriflow-io/*" mutateDigest: true # Mutar tag → digest (inmutable) verifyDigest: true attestors: - entries: - keyless: subject: "https://github.com/agriflow-io/*" issuer: "https://token.actions.githubusercontent.com" rekor: url: "https://rekor.sigstore.dev" attestations: - predicateType: "https://spdx.dev/Document" attestors: - entries: - keyless: subject: "https://github.com/agriflow-io/*" issuer: "https://token.actions.githubusercontent.com" --- # Aplicar política: # kubectl apply -f k8s/policies/verify-image-signatures.yaml # kubectl get clusterpolicy verify-agriflow-signatures
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "agriflow-api-4a8b2c",
"documentNamespace": "https://github.com/agriflow-io/agriflow-api/sbom-4a8b2c",
"creationInfo": {
"created": "2026-06-16T09:14:32Z",
"creators": ["Tool: syft-1.3.0", "Organization: agriflow-io"]
},
"packages": [
{
"SPDXID": "SPDXRef-express-4.18.2",
"name": "express",
"versionInfo": "4.18.2",
"licenseConcluded": "MIT",
"downloadLocation": "https://registry.npmjs.org/express/-/express-4.18.2.tgz"
},
/* ... 247 paquetes más */
]
}
-
1Firma keyless en CI
Usar Sigstore keyless en GitHub Actions. Sin gestión de claves, las firmas están ligadas a la identidad OIDC del workflow. Se usa
id-token: writey se registra en Rekor. -
2Firmar todas las imágenes
Firmar en CI/CD y verificar antes del despliegue. Ninguna imagen sin firma puede llegar a
production. -
3SBOM en cada build
Generar y adjuntar SBOM con syft en cada build. Requerido para cumplimiento (Executive Order 14028) y seguimiento de vulnerabilidades.
-
4Verificar en admission control
Usar Kyverno para verificar firmas en el Kubernetes admission level. La política está en modo
Enforce, no soloAudit. -
5Rekor transparency log
Las firmas keyless se registran en Rekor. Proporciona un audit trail inmutable de quién firmó qué y cuándo — clave para SOC 2.
-
6Attestations de proveniencia SLSA
Adjuntar attestations SLSA que prueban dónde y cómo se construyó cada imagen. Objetivo: SLSA Level 3 para AgriFlow.
-
7Referenciar por digest, no por tag
Usar
image@sha256:...en lugar deimage:latest. Los tags pueden sobreescribirse; los digests son inmutables. Kyverno muta automáticamente. -
8Adjuntar resultados de vulnerability scan
Adjuntar resultados de grype como attestations. Verificar ausencia de CVEs críticos antes de aprobar despliegue en producción.