{"id":700,"date":"2026-02-21T16:47:55","date_gmt":"2026-02-21T15:47:55","guid":{"rendered":"https:\/\/secure-pipelines.com\/ci-cd-security\/lab-sbom-pipeline-generate-attest-verify-syft-cosign-2\/"},"modified":"2026-03-25T05:20:25","modified_gmt":"2026-03-25T04:20:25","slug":"lab-sbom-pipeline-generate-attest-verify-syft-cosign-2","status":"publish","type":"post","link":"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/lab-sbom-pipeline-generate-attest-verify-syft-cosign-2\/","title":{"rendered":"Lab: Construcci\u00f3n de un Pipeline de SBOM \u2014 Generar, Atestar y Verificar con Syft y Cosign"},"content":{"rendered":"<h2>Descripci\u00f3n General<\/h2>\n<p>Los Software Bills of Materials (SBOMs) se est\u00e1n convirtiendo r\u00e1pidamente en un componente obligatorio de la transparencia en la cadena de suministro de software. \u00d3rdenes ejecutivas, marcos regulatorios como NIST SSDF y est\u00e1ndares de la industria ahora requieren que las organizaciones produzcan, distribuyan y verifiquen SBOMs para cada lanzamiento de software. Un SBOM enumera cada componente, biblioteca y dependencia dentro de tu software \u2014 brindando a los consumidores la capacidad de evaluar riesgos, rastrear vulnerabilidades y verificar la procedencia.<\/p>\n<p>En este laboratorio pr\u00e1ctico, construir\u00e1s un <strong>pipeline completo de SBOM<\/strong> desde cero. Al finalizar, ser\u00e1s capaz de:<\/p>\n<ul>\n<li>Generar SBOMs en formatos SPDX y CycloneDX usando <strong>Syft<\/strong><\/li>\n<li>Escanear SBOMs en busca de vulnerabilidades conocidas usando <strong>Grype<\/strong><\/li>\n<li>Adjuntar SBOMs como attestations firmadas a im\u00e1genes de contenedores usando <strong>Cosign<\/strong><\/li>\n<li>Automatizar todo el flujo de trabajo en <strong>GitHub Actions<\/strong> y <strong>GitLab CI<\/strong><\/li>\n<li>Aplicar requisitos de attestation de SBOM en el despliegue usando <strong>Kyverno<\/strong><\/li>\n<li>Comparar SBOMs entre versiones para detectar cambios en las dependencias<\/li>\n<\/ul>\n<p>Este laboratorio refleja flujos de trabajo de producci\u00f3n reales utilizados por equipos que adoptan SLSA, in-toto y seguridad de cadena de suministro basada en Sigstore.<\/p>\n<h2>Prerrequisitos<\/h2>\n<p>Antes de comenzar, aseg\u00farate de tener las siguientes herramientas instaladas y configuradas:<\/p>\n<table>\n<thead>\n<tr>\n<th>Herramienta<\/th>\n<th>Prop\u00f3sito<\/th>\n<th>Instalaci\u00f3n<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>Syft<\/strong><\/td>\n<td>Generaci\u00f3n de SBOM<\/td>\n<td><code>curl -sSfL https:\/\/raw.githubusercontent.com\/anchore\/syft\/main\/install.sh | sh -s -- -b \/usr\/local\/bin<\/code><\/td>\n<\/tr>\n<tr>\n<td><strong>Grype<\/strong><\/td>\n<td>Escaneo de vulnerabilidades<\/td>\n<td><code>curl -sSfL https:\/\/raw.githubusercontent.com\/anchore\/grype\/main\/install.sh | sh -s -- -b \/usr\/local\/bin<\/code><\/td>\n<\/tr>\n<tr>\n<td><strong>Cosign<\/strong><\/td>\n<td>Firma y attestation<\/td>\n<td><code>go install github.com\/sigstore\/cosign\/v2\/cmd\/cosign@latest<\/code><\/td>\n<\/tr>\n<tr>\n<td><strong>Docker<\/strong><\/td>\n<td>Construcci\u00f3n de contenedores<\/td>\n<td><a href=\"https:\/\/docs.docker.com\/get-docker\/\" target=\"_blank\" rel=\"noopener\">docs.docker.com<\/a><\/td>\n<\/tr>\n<tr>\n<td><strong>GitHub CLI (gh)<\/strong><\/td>\n<td>Acceso al repositorio y GHCR<\/td>\n<td><code>brew install gh<\/code> o <a href=\"https:\/\/cli.github.com\/\" target=\"_blank\" rel=\"noopener\">cli.github.com<\/a><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Tambi\u00e9n necesitas una <strong>cuenta de GitHub<\/strong> con acceso a GitHub Container Registry (GHCR), y una aplicaci\u00f3n b\u00e1sica con un <code>Dockerfile<\/code>.<\/p>\n<h2>Configuraci\u00f3n del Entorno<\/h2>\n<p>Crearemos una aplicaci\u00f3n m\u00ednima de Node.js, la contenedorizaremos y la subiremos a GHCR. Esta imagen se convierte en el objetivo de todos los ejercicios posteriores de SBOM.<\/p>\n<h3>Paso 1: Crear el repositorio de prueba<\/h3>\n<pre><code class=\"language-bash\">mkdir sbom-pipeline-lab &amp;&amp; cd sbom-pipeline-lab\ngit init\n<\/code><\/pre>\n<h3>Paso 2: Crear una aplicaci\u00f3n simple de Node.js<\/h3>\n<pre><code class=\"language-bash\">cat &gt; package.json &lt;&lt;'EOF'\n{\n  \"name\": \"sbom-lab-app\",\n  \"version\": \"1.0.0\",\n  \"description\": \"SBOM pipeline lab application\",\n  \"main\": \"server.js\",\n  \"dependencies\": {\n    \"express\": \"^4.18.2\",\n    \"lodash\": \"^4.17.21\",\n    \"axios\": \"^1.6.0\"\n  }\n}\nEOF\n<\/code><\/pre>\n<pre><code class=\"language-bash\">cat &gt; server.js &lt;&lt;'EOF'\nconst express = require('express');\nconst app = express();\n\napp.get('\/', (req, res) =&gt; {\n  res.json({ status: 'ok', message: 'SBOM Pipeline Lab' });\n});\n\napp.listen(3000, () =&gt; console.log('Listening on port 3000'));\nEOF\n<\/code><\/pre>\n<h3>Paso 3: Crear el Dockerfile<\/h3>\n<pre><code class=\"language-dockerfile\">FROM node:20-alpine\nWORKDIR \/app\nCOPY package*.json .\/\nRUN npm install --production\nCOPY . .\nEXPOSE 3000\nCMD [\"node\", \"server.js\"]\n<\/code><\/pre>\n<h3>Paso 4: Construir y subir la imagen del contenedor<\/h3>\n<pre><code class=\"language-bash\"># Authenticate to GHCR\necho $GITHUB_TOKEN | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin\n\n# Build the image\ndocker build -t ghcr.io\/YOUR_GITHUB_USERNAME\/sbom-lab-app:v1.0.0 .\n\n# Push to GHCR\ndocker push ghcr.io\/YOUR_GITHUB_USERNAME\/sbom-lab-app:v1.0.0\n<\/code><\/pre>\n<p>Reemplaza <code>YOUR_GITHUB_USERNAME<\/code> con tu nombre de usuario real de GitHub a lo largo de este laboratorio. Una vez completada la subida, anota el digest completo de la imagen \u2014 lo necesitar\u00e1s para las operaciones de firma.<\/p>\n<pre><code class=\"language-bash\"># Capture the image digest\nexport IMAGE=$(docker inspect --format='{{index .RepoDigests 0}}' ghcr.io\/YOUR_GITHUB_USERNAME\/sbom-lab-app:v1.0.0)\necho $IMAGE\n# Output: ghcr.io\/YOUR_GITHUB_USERNAME\/sbom-lab-app@sha256:abc123...\n<\/code><\/pre>\n<h2>Ejercicio 1: Generar SBOM con Syft<\/h2>\n<p>Syft es una herramienta de c\u00f3digo abierto de Anchore que genera SBOMs analizando im\u00e1genes de contenedores, sistemas de archivos y archivos comprimidos. Soporta m\u00faltiples formatos de salida incluyendo SPDX y CycloneDX \u2014 los dos est\u00e1ndares de SBOM dominantes.<\/p>\n<h3>Generar un SBOM en formato SPDX<\/h3>\n<pre><code class=\"language-bash\">syft $IMAGE -o spdx-json=sbom.spdx.json\n<\/code><\/pre>\n<p>Inspecciona la salida:<\/p>\n<pre><code class=\"language-bash\">cat sbom.spdx.json | jq '.packages | length'\n# Output: 287 (count varies based on image content)\n\n# View detected packages\ncat sbom.spdx.json | jq '.packages[] | {name: .name, version: .versionInfo, license: .licenseDeclared}' | head -40\n<\/code><\/pre>\n<h3>Generar un SBOM en formato CycloneDX<\/h3>\n<pre><code class=\"language-bash\">syft $IMAGE -o cyclonedx-json=sbom.cyclonedx.json\n<\/code><\/pre>\n<p>Inspecciona la salida de CycloneDX:<\/p>\n<pre><code class=\"language-bash\">cat sbom.cyclonedx.json | jq '.components | length'\n\n# View components with licenses\ncat sbom.cyclonedx.json | jq '.components[] | {name: .name, version: .version, type: .type}' | head -40\n<\/code><\/pre>\n<h3>SPDX vs. CycloneDX: Diferencias Clave<\/h3>\n<table>\n<thead>\n<tr>\n<th>Caracter\u00edstica<\/th>\n<th>SPDX<\/th>\n<th>CycloneDX<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>Origen<\/strong><\/td>\n<td>Linux Foundation<\/td>\n<td>OWASP<\/td>\n<\/tr>\n<tr>\n<td><strong>Enfoque Principal<\/strong><\/td>\n<td>Cumplimiento de licencias y PI<\/td>\n<td>Seguridad y an\u00e1lisis de riesgos<\/td>\n<\/tr>\n<tr>\n<td><strong>Est\u00e1ndar ISO<\/strong><\/td>\n<td>ISO\/IEC 5962:2021<\/td>\n<td>ECMA-424<\/td>\n<\/tr>\n<tr>\n<td><strong>Datos de Vulnerabilidades<\/strong><\/td>\n<td>Soporte nativo limitado<\/td>\n<td>Campo <code>vulnerabilities<\/code> de primera clase<\/td>\n<\/tr>\n<tr>\n<td><strong>Formatos<\/strong><\/td>\n<td>JSON, RDF, XML, YAML, tag-value<\/td>\n<td>JSON, XML, Protobuf<\/td>\n<\/tr>\n<tr>\n<td><strong>Mejor Para<\/strong><\/td>\n<td>Cumplimiento regulatorio, auditor\u00edas de licencias<\/td>\n<td>DevSecOps, seguimiento de vulnerabilidades<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Para este laboratorio, usamos principalmente SPDX JSON porque es el formato requerido por muchos est\u00e1ndares de adquisici\u00f3n gubernamental y se integra bien con las attestations de Cosign. Sin embargo, ambos formatos son compatibles con Grype y Cosign.<\/p>\n<h3>Generar un resumen legible por humanos<\/h3>\n<pre><code class=\"language-bash\">syft $IMAGE -o syft-table\n<\/code><\/pre>\n<p>Esto imprime una tabla de todos los paquetes con nombre, versi\u00f3n y tipo \u2014 \u00fatil para una revisi\u00f3n r\u00e1pida durante el desarrollo.<\/p>\n<h2>Ejercicio 2: Escanear SBOM en Busca de Vulnerabilidades con Grype<\/h2>\n<p>Grype es el esc\u00e1ner de vulnerabilidades de Anchore. Puede escanear im\u00e1genes de contenedores directamente, pero tambi\u00e9n puede escanear un archivo SBOM \u2014 lo cual es significativamente m\u00e1s r\u00e1pido porque omite el paso de an\u00e1lisis de la imagen.<\/p>\n<h3>Escanear el SBOM<\/h3>\n<pre><code class=\"language-bash\">grype sbom:.\/sbom.spdx.json\n<\/code><\/pre>\n<p>Salida de ejemplo:<\/p>\n<pre><code class=\"language-text\">NAME          INSTALLED  FIXED-IN   TYPE  VULNERABILITY   SEVERITY\nlodash        4.17.21               npm   CVE-2025-XXXXX  Medium\nnode          20.10.0    20.11.1    apk   CVE-2024-22019  High\nlibcrypto3    3.1.4-r1   3.1.4-r3   apk   CVE-2024-0727   Medium\n<\/code><\/pre>\n<h3>Filtrar por severidad<\/h3>\n<pre><code class=\"language-bash\"># Show only Critical and High vulnerabilities\ngrype sbom:.\/sbom.spdx.json --fail-on critical\n\n# Output results in JSON for CI processing\ngrype sbom:.\/sbom.spdx.json -o json &gt; vulnerabilities.json\n\n# Count vulnerabilities by severity\ncat vulnerabilities.json | jq '[.matches[].vulnerability.severity] | group_by(.) | map({severity: .[0], count: length})'\n<\/code><\/pre>\n<h3>Escaneo de imagen vs. escaneo de SBOM<\/h3>\n<p>Existe una distinci\u00f3n importante entre escanear la imagen directamente y escanear el SBOM:<\/p>\n<pre><code class=\"language-bash\"># Direct image scan \u2014 Grype pulls and analyzes the image layers\ngrype $IMAGE\n\n# SBOM scan \u2014 Grype reads the pre-generated package list\ngrype sbom:.\/sbom.spdx.json\n<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th>Enfoque<\/th>\n<th>Velocidad<\/th>\n<th>Precisi\u00f3n<\/th>\n<th>Caso de Uso<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Escaneo directo de imagen<\/td>\n<td>M\u00e1s lento (descarga capas)<\/td>\n<td>Descubre todos los paquetes<\/td>\n<td>Primer an\u00e1lisis, desarrollo local<\/td>\n<\/tr>\n<tr>\n<td>Escaneo de SBOM<\/td>\n<td>R\u00e1pido (lee archivo JSON)<\/td>\n<td>Limitado al contenido del SBOM<\/td>\n<td>Pipelines de CI, escaneos repetidos, auditor\u00eda<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>El escaneo de SBOM es tan completo como el propio SBOM. Si Syft omiti\u00f3 un paquete (por ejemplo, un binario compilado est\u00e1ticamente), Grype no encontrar\u00e1 vulnerabilidades para \u00e9l. Para m\u00e1xima cobertura, genera el SBOM con la opci\u00f3n <code>--catalogers all<\/code> de Syft.<\/p>\n<h2>Ejercicio 3: Adjuntar SBOM como Attestation de Cosign<\/h2>\n<p>Una attestation es una declaraci\u00f3n firmada sobre un artefacto de software. Al adjuntar el SBOM como attestation, vinculas criptogr\u00e1ficamente el SBOM al digest espec\u00edfico de la imagen \u2014 cualquiera puede verificar que el SBOM fue producido para esa imagen exacta y no ha sido alterado.<\/p>\n<h3>Adjuntar la attestation del SBOM<\/h3>\n<p>Usando el flujo keyless (Sigstore Fulcio) de Cosign:<\/p>\n<pre><code class=\"language-bash\">cosign attest --predicate sbom.spdx.json \\\n  --type spdxjson \\\n  --yes \\\n  $IMAGE\n<\/code><\/pre>\n<p>Este comando:<\/p>\n<ol>\n<li>Abre un navegador para autenticaci\u00f3n OIDC (firma keyless v\u00eda Fulcio)<\/li>\n<li>Crea una attestation in-toto con tu SBOM como predicado<\/li>\n<li>Firma la attestation y la registra en el log de transparencia Rekor<\/li>\n<li>Sube la attestation al registry junto con la imagen<\/li>\n<\/ol>\n<h3>Verificar la attestation<\/h3>\n<pre><code class=\"language-bash\">cosign verify-attestation \\\n  --type spdxjson \\\n  --certificate-identity=YOUR_EMAIL@example.com \\\n  --certificate-oidc-issuer=https:\/\/accounts.google.com \\\n  $IMAGE | jq '.payload | @base64d | fromjson | .predicate'\n<\/code><\/pre>\n<p>Reemplaza la identidad del certificado y el emisor OIDC con los valores que correspondan a tu identidad de Sigstore. En entornos de CI (GitHub Actions), estos ser\u00edan:<\/p>\n<pre><code class=\"language-bash\">--certificate-identity=https:\/\/github.com\/YOUR_ORG\/YOUR_REPO\/.github\/workflows\/build.yml@refs\/heads\/main\n--certificate-oidc-issuer=https:\/\/token.actions.githubusercontent.com\n<\/code><\/pre>\n<h3>Inspeccionar la attestation en el registry<\/h3>\n<pre><code class=\"language-bash\"># List referrers (OCI 1.1 registries)\ncosign tree $IMAGE\n<\/code><\/pre>\n<p>Deber\u00edas ver la attestation listada como un referrer adjunto al manifiesto de la imagen. La attestation se almacena como un artefacto OCI en el mismo repositorio, etiquetada con la convenci\u00f3n <code>sha256-&lt;digest&gt;.att<\/code>.<\/p>\n<h2>Ejercicio 4: Construir el Pipeline Completo en GitHub Actions<\/h2>\n<p>Ahora automatizamos todo en un \u00fanico flujo de trabajo de CI\/CD. Este pipeline construye la imagen, genera el SBOM, escanea vulnerabilidades y adjunta el SBOM como una attestation firmada.<\/p>\n<p>Crea <code>.github\/workflows\/sbom-pipeline.yml<\/code>:<\/p>\n<pre><code class=\"language-yaml\">name: SBOM Pipeline\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}\n\npermissions:\n  contents: read\n  packages: write\n  id-token: write  # Required for Cosign keyless signing\n  attestations: write\n\njobs:\n  build-and-attest:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions\/checkout@v4\n\n      - name: Set up Docker Buildx\n        uses: docker\/setup-buildx-action@v3\n\n      - name: Log in to GHCR\n        uses: docker\/login-action@v3\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Build and push image\n        id: build\n        uses: docker\/build-push-action@v5\n        with:\n          context: .\n          push: true\n          tags: ${{ env.REGISTRY }}\/${{ env.IMAGE_NAME }}:${{ github.sha }}\n\n      - name: Get image digest\n        id: digest\n        run: |\n          echo \"image=${{ env.REGISTRY }}\/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}\" &gt;&gt; $GITHUB_OUTPUT\n\n      - name: Install Syft\n        uses: anchore\/sbom-action\/download-syft@v0\n\n      - name: Generate SPDX SBOM\n        run: |\n          syft ${{ steps.digest.outputs.image }} -o spdx-json=sbom.spdx.json\n          echo \"### SBOM Summary\" &gt;&gt; $GITHUB_STEP_SUMMARY\n          echo \"Package count: $(cat sbom.spdx.json | jq '.packages | length')\" &gt;&gt; $GITHUB_STEP_SUMMARY\n\n      - name: Install Grype\n        uses: anchore\/scan-action\/download-grype@v4\n\n      - name: Scan SBOM for vulnerabilities\n        run: |\n          grype sbom:.\/sbom.spdx.json -o json &gt; vulnerabilities.json\n          grype sbom:.\/sbom.spdx.json -o table &gt;&gt; $GITHUB_STEP_SUMMARY\n\n          # Fail if critical vulnerabilities are found\n          CRITICAL_COUNT=$(cat vulnerabilities.json | jq '[.matches[] | select(.vulnerability.severity==\"Critical\")] | length')\n          echo \"Critical vulnerabilities: $CRITICAL_COUNT\"\n          if [ \"$CRITICAL_COUNT\" -gt 0 ]; then\n            echo \"::error::Found $CRITICAL_COUNT critical vulnerabilities. Failing pipeline.\"\n            exit 1\n          fi\n\n      - name: Install Cosign\n        uses: sigstore\/cosign-installer@v3\n\n      - name: Attest SBOM\n        run: |\n          cosign attest --predicate sbom.spdx.json \\\n            --type spdxjson \\\n            --yes \\\n            ${{ steps.digest.outputs.image }}\n\n      - name: Verify attestation\n        run: |\n          cosign verify-attestation \\\n            --type spdxjson \\\n            --certificate-identity-regexp=\"https:\/\/github.com\/${{ github.repository }}\/\" \\\n            --certificate-oidc-issuer=https:\/\/token.actions.githubusercontent.com \\\n            ${{ steps.digest.outputs.image }}\n\n      - name: Upload SBOM as artifact\n        uses: actions\/upload-artifact@v4\n        with:\n          name: sbom-spdx\n          path: sbom.spdx.json\n          retention-days: 90\n\n      - name: Upload vulnerability report\n        uses: actions\/upload-artifact@v4\n        if: always()\n        with:\n          name: vulnerability-report\n          path: vulnerabilities.json\n          retention-days: 90\n<\/code><\/pre>\n<p>Este flujo de trabajo utiliza <strong>firma keyless<\/strong> mediante la identidad OIDC de GitHub Actions. No se almacenan claves privadas en los secrets \u2014 Cosign obtiene un certificado de corta duraci\u00f3n de la CA Fulcio de Sigstore, y el evento de firma se registra en el log de transparencia Rekor.<\/p>\n<p>El pipeline <strong>falla si se detectan vulnerabilidades cr\u00edticas<\/strong>. Ajusta el umbral cambiando el filtro de severidad en el paso de escaneo de Grype.<\/p>\n<h2>Ejercicio 5: Construir el Pipeline en GitLab CI<\/h2>\n<p>El pipeline equivalente en GitLab CI utiliza las mismas herramientas pero se adapta al modelo basado en stages de GitLab y su registry de contenedores integrado.<\/p>\n<p>Crea <code>.gitlab-ci.yml<\/code>:<\/p>\n<pre><code class=\"language-yaml\">stages:\n  - build\n  - sbom\n  - scan\n  - attest\n\nvariables:\n  IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA\n  IMAGE_DIGEST: \"\"\n\nbuild:\n  stage: build\n  image: docker:24\n  services:\n    - docker:24-dind\n  variables:\n    DOCKER_TLS_CERTDIR: \"\/certs\"\n  script:\n    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY\n    - docker build -t $IMAGE .\n    - docker push $IMAGE\n    - |\n      DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' $IMAGE)\n      echo \"IMAGE_DIGEST=$DIGEST\" &gt;&gt; build.env\n  artifacts:\n    reports:\n      dotenv: build.env\n\ngenerate-sbom:\n  stage: sbom\n  image: alpine:3.19\n  needs: [build]\n  before_script:\n    - apk add --no-cache curl jq\n    - curl -sSfL https:\/\/raw.githubusercontent.com\/anchore\/syft\/main\/install.sh | sh -s -- -b \/usr\/local\/bin\n  script:\n    - syft $IMAGE_DIGEST -o spdx-json=sbom.spdx.json\n    - echo \"Package count:\" $(cat sbom.spdx.json | jq '.packages | length')\n  artifacts:\n    paths:\n      - sbom.spdx.json\n    expire_in: 90 days\n\nscan-vulnerabilities:\n  stage: scan\n  image: alpine:3.19\n  needs: [generate-sbom]\n  before_script:\n    - apk add --no-cache curl jq\n    - curl -sSfL https:\/\/raw.githubusercontent.com\/anchore\/grype\/main\/install.sh | sh -s -- -b \/usr\/local\/bin\n  script:\n    - grype sbom:.\/sbom.spdx.json -o json &gt; vulnerabilities.json\n    - grype sbom:.\/sbom.spdx.json -o table\n    - |\n      CRITICAL_COUNT=$(cat vulnerabilities.json | jq '[.matches[] | select(.vulnerability.severity==\"Critical\")] | length')\n      echo \"Critical vulnerabilities found: $CRITICAL_COUNT\"\n      if [ \"$CRITICAL_COUNT\" -gt 0 ]; then\n        echo \"ERROR: Critical vulnerabilities detected. Failing pipeline.\"\n        exit 1\n      fi\n  artifacts:\n    paths:\n      - vulnerabilities.json\n    expire_in: 90 days\n    when: always\n\nattest-sbom:\n  stage: attest\n  image: alpine:3.19\n  needs: [build, generate-sbom, scan-vulnerabilities]\n  id_tokens:\n    SIGSTORE_ID_TOKEN:\n      aud: sigstore\n  before_script:\n    - apk add --no-cache curl\n    - curl -sSfL https:\/\/github.com\/sigstore\/cosign\/releases\/latest\/download\/cosign-linux-amd64 -o \/usr\/local\/bin\/cosign\n    - chmod +x \/usr\/local\/bin\/cosign\n  script:\n    - cosign attest --predicate sbom.spdx.json\n        --type spdxjson\n        --yes\n        $IMAGE_DIGEST\n    - cosign verify-attestation\n        --type spdxjson\n        --certificate-identity-regexp=\"https:\/\/gitlab.com\/$CI_PROJECT_PATH\/\/\"\n        --certificate-oidc-issuer=https:\/\/gitlab.com\n        $IMAGE_DIGEST\n<\/code><\/pre>\n<p>Diferencias clave con respecto a la versi\u00f3n de GitHub Actions:<\/p>\n<ul>\n<li>GitLab utiliza <strong>stages<\/strong> en lugar de un solo job con steps<\/li>\n<li>El digest de la imagen se pasa entre stages mediante artefactos <code>dotenv<\/code><\/li>\n<li>El token OIDC de GitLab se expone a trav\u00e9s de <code>id_tokens<\/code> y la variable <code>SIGSTORE_ID_TOKEN<\/code><\/li>\n<li>El emisor OIDC para verificaci\u00f3n es <code>https:\/\/gitlab.com<\/code><\/li>\n<\/ul>\n<h2>Ejercicio 6: Verificar SBOM en el Despliegue<\/h2>\n<p>Generar y atestar SBOMs es solo la mitad de la historia. El verdadero beneficio de seguridad viene de <strong>aplicar la verificaci\u00f3n en el momento del despliegue<\/strong> \u2014 rechazando cualquier imagen que carezca de una attestation de SBOM v\u00e1lida.<\/p>\n<h3>Opci\u00f3n A: Verificar en un script de despliegue<\/h3>\n<pre><code class=\"language-bash\">#!\/bin\/bash\n# deploy.sh \u2014 Verify SBOM attestation before deploying\nset -euo pipefail\n\nIMAGE=\"$1\"\n\necho \"Verifying SBOM attestation for: $IMAGE\"\n\ncosign verify-attestation \\\n  --type spdxjson \\\n  --certificate-identity-regexp=\"https:\/\/github.com\/YOUR_ORG\/YOUR_REPO\/\" \\\n  --certificate-oidc-issuer=https:\/\/token.actions.githubusercontent.com \\\n  \"$IMAGE\" &gt; \/dev\/null 2&gt;&amp;1\n\nif [ $? -eq 0 ]; then\n  echo \"SBOM attestation verified successfully.\"\n  kubectl set image deployment\/myapp myapp=\"$IMAGE\"\nelse\n  echo \"ERROR: SBOM attestation verification failed. Deployment blocked.\"\n  exit 1\nfi\n<\/code><\/pre>\n<h3>Opci\u00f3n B: Aplicar con Kyverno en Kubernetes<\/h3>\n<p>Kyverno es un motor de pol\u00edticas nativo de Kubernetes que puede verificar firmas de im\u00e1genes y attestations en el momento de admisi\u00f3n. La siguiente pol\u00edtica <strong>rechaza cualquier pod<\/strong> cuya imagen carezca de una attestation de SBOM v\u00e1lida.<\/p>\n<pre><code class=\"language-yaml\">apiVersion: kyverno.io\/v1\nkind: ClusterPolicy\nmetadata:\n  name: require-sbom-attestation\nspec:\n  validationFailureAction: Enforce\n  webhookTimeoutSeconds: 30\n  rules:\n    - name: check-sbom-attestation\n      match:\n        any:\n          - resources:\n              kinds:\n                - Pod\n      verifyImages:\n        - imageReferences:\n            - \"ghcr.io\/YOUR_ORG\/*\"\n          attestations:\n            - type: spdxjson\n              attestors:\n                - entries:\n                    - keyless:\n                        subject: \"https:\/\/github.com\/YOUR_ORG\/YOUR_REPO\/.github\/workflows\/sbom-pipeline.yml@refs\/heads\/main\"\n                        issuer: \"https:\/\/token.actions.githubusercontent.com\"\n                        rekor:\n                          url: https:\/\/rekor.sigstore.dev\n              conditions:\n                - all:\n                    - key: \"{{ len(spdxVersion) }}\"\n                      operator: GreaterThan\n                      value: \"0\"\n<\/code><\/pre>\n<p>Aplica la pol\u00edtica:<\/p>\n<pre><code class=\"language-bash\">kubectl apply -f require-sbom-attestation.yaml\n<\/code><\/pre>\n<h3>Prueba de admisi\u00f3n: imagen con attestation<\/h3>\n<pre><code class=\"language-bash\"># This should be ADMITTED\nkubectl run test-admitted \\\n  --image=ghcr.io\/YOUR_ORG\/sbom-lab-app@sha256:abc123... \\\n  --restart=Never\n\n# Expected output:\n# pod\/test-admitted created\n<\/code><\/pre>\n<h3>Prueba de admisi\u00f3n: imagen sin attestation<\/h3>\n<pre><code class=\"language-bash\"># This should be REJECTED\nkubectl run test-rejected \\\n  --image=ghcr.io\/YOUR_ORG\/sbom-lab-app:unattested \\\n  --restart=Never\n\n# Expected output:\n# Error from server: admission webhook \"mutate.kyverno.svc-fail\" denied the request:\n# resource Pod\/default\/test-rejected was blocked due to the following policies:\n# require-sbom-attestation:\n#   check-sbom-attestation: 'image verification failed for ghcr.io\/YOUR_ORG\/sbom-lab-app:unattested:\n#     attestation spdxjson not found'\n<\/code><\/pre>\n<p>Este es el bucle de aplicaci\u00f3n que hace que los SBOMs sean operacionalmente significativos \u2014 sin verificaci\u00f3n en el despliegue, la generaci\u00f3n de SBOM es solo documentaci\u00f3n.<\/p>\n<h2>Ejercicio 7: Diferencias de SBOM Entre Versiones<\/h2>\n<p>Cuando lanzas una nueva versi\u00f3n, necesitas entender qu\u00e9 cambi\u00f3 en tu \u00e1rbol de dependencias. Comparar SBOMs entre versiones revela paquetes a\u00f1adidos, eliminados y actualizados \u2014 informaci\u00f3n cr\u00edtica para evaluar nuevos riesgos.<\/p>\n<h3>Paso 1: Generar SBOMs para dos versiones<\/h3>\n<pre><code class=\"language-bash\"># Generate SBOM for v1.0.0\nsyft ghcr.io\/YOUR_GITHUB_USERNAME\/sbom-lab-app:v1.0.0 -o spdx-json=sbom-v1.spdx.json\n\n# Generate SBOM for v1.1.0 (after updating dependencies)\nsyft ghcr.io\/YOUR_GITHUB_USERNAME\/sbom-lab-app:v1.1.0 -o spdx-json=sbom-v2.spdx.json\n<\/code><\/pre>\n<h3>Paso 2: Extraer listas de paquetes<\/h3>\n<pre><code class=\"language-bash\"># Extract sorted package lists\ncat sbom-v1.spdx.json | jq -r '.packages[] | \"\\(.name)@\\(.versionInfo)\"' | sort &gt; packages-v1.txt\ncat sbom-v2.spdx.json | jq -r '.packages[] | \"\\(.name)@\\(.versionInfo)\"' | sort &gt; packages-v2.txt\n<\/code><\/pre>\n<h3>Paso 3: Comparar las listas de paquetes<\/h3>\n<pre><code class=\"language-bash\">diff --unified packages-v1.txt packages-v2.txt\n<\/code><\/pre>\n<p>Salida de ejemplo:<\/p>\n<pre><code class=\"language-diff\">--- packages-v1.txt\n+++ packages-v2.txt\n@@ -12,7 +12,8 @@\n  express@4.18.2\n  lodash@4.17.21\n- axios@1.6.0\n+ axios@1.7.2\n+ helmet@7.1.0\n  mime-types@2.1.35\n<\/code><\/pre>\n<h3>Paso 4: Identificar vulnerabilidades reci\u00e9n introducidas<\/h3>\n<pre><code class=\"language-bash\"># Scan only the new\/changed packages\ngrype sbom:.\/sbom-v2.spdx.json -o json &gt; vulns-v2.json\ngrype sbom:.\/sbom-v1.spdx.json -o json &gt; vulns-v1.json\n\n# Compare vulnerability counts\necho \"v1.0.0 vulnerabilities: $(cat vulns-v1.json | jq '.matches | length')\"\necho \"v1.1.0 vulnerabilities: $(cat vulns-v2.json | jq '.matches | length')\"\n\n# Find NEW vulnerabilities in v1.1.0\ncomm -13 \\\n  <(cat vulns-v1.json | jq -r '.matches[].vulnerability.id' | sort -u) \\\n  <(cat vulns-v2.json | jq -r '.matches[].vulnerability.id' | sort -u)\n<\/code><\/pre>\n<p>Este proceso de comparaci\u00f3n se puede automatizar en CI para comentar en pull requests con los cambios de dependencias, dando a los revisores visibilidad sobre el impacto en la cadena de suministro antes de fusionar.<\/p>\n<h2>Limpieza<\/h2>\n<p>Elimina los recursos creados durante este laboratorio:<\/p>\n<pre><code class=\"language-bash\"># Delete local files\nrm -f sbom.spdx.json sbom.cyclonedx.json vulnerabilities.json\nrm -f sbom-v1.spdx.json sbom-v2.spdx.json packages-v1.txt packages-v2.txt\nrm -f vulns-v1.json vulns-v2.json\n\n# Remove the test image from GHCR (requires gh CLI)\ngh api -X DELETE \/user\/packages\/container\/sbom-lab-app\/versions\/PACKAGE_VERSION_ID\n\n# Remove the Kyverno policy\nkubectl delete clusterpolicy require-sbom-attestation\n\n# Remove the test pods\nkubectl delete pod test-admitted test-rejected --ignore-not-found\n\n# Remove the local repository\ncd .. &amp;&amp; rm -rf sbom-pipeline-lab\n<\/code><\/pre>\n<h2>Conclusiones Clave<\/h2>\n<ul>\n<li><strong>Los SBOMs son una base para la seguridad de la cadena de suministro<\/strong> \u2014 proporcionan el inventario que permite el escaneo de vulnerabilidades, el cumplimiento de licencias y la verificaci\u00f3n de procedencia.<\/li>\n<li><strong>Syft genera SBOMs en todos los formatos principales<\/strong> \u2014 SPDX para cumplimiento regulatorio, CycloneDX para flujos de trabajo de seguridad, ambos compatibles en todo el ecosistema.<\/li>\n<li><strong>Escanear el SBOM es m\u00e1s r\u00e1pido que escanear la imagen<\/strong> \u2014 usa Grype con el prefijo <code>sbom:<\/code> en pipelines de CI para ciclos de retroalimentaci\u00f3n r\u00e1pidos sin descargar capas de imagen.<\/li>\n<li><strong>Las attestations de Cosign vinculan criptogr\u00e1ficamente los SBOMs a las im\u00e1genes<\/strong> \u2014 las attestations firmadas demuestran que un SBOM espec\u00edfico fue producido para un digest de imagen espec\u00edfico, almacenado en el registry junto a la imagen.<\/li>\n<li><strong>La verificaci\u00f3n en el despliegue es lo que hace que los SBOMs sean aplicables<\/strong> \u2014 sin control de admisi\u00f3n (Kyverno, OPA Gatekeeper o scripts de despliegue), los SBOMs permanecen como documentaci\u00f3n sin fuerza.<\/li>\n<li><strong>Las comparaciones de SBOM revelan cambios en la cadena de suministro entre versiones<\/strong> \u2014 automatizar la comparaci\u00f3n de dependencias en CI da a los equipos visibilidad sobre nuevos riesgos antes de que el c\u00f3digo llegue a producci\u00f3n.<\/li>\n<\/ul>\n<h2>Pr\u00f3ximos Pasos<\/h2>\n<p>Contin\u00faa construyendo tu pr\u00e1ctica de seguridad de cadena de suministro con estas gu\u00edas relacionadas:<\/p>\n<ul>\n<li><a href=\"\/es\/ci-cd-security\/artifact-provenance-attestations-slsa-in-toto\/\">Procedencia de Artefactos y Attestations<\/a> \u2014 Aprende c\u00f3mo los frameworks SLSA e in-toto extienden las attestations m\u00e1s all\u00e1 de los SBOMs para cubrir la procedencia de construcci\u00f3n, la integridad del c\u00f3digo fuente y las pol\u00edticas de despliegue.<\/li>\n<li><a href=\"\/es\/ci-cd-security\/signing-verifying-container-images-sigstore-cosign\/\">Firma y Verificaci\u00f3n de Im\u00e1genes de Contenedores con Sigstore y Cosign<\/a> \u2014 Profundiza en la firma keyless, certificados Fulcio, logs de transparencia Rekor y pol\u00edticas de verificaci\u00f3n de firmas de im\u00e1genes.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Descripci\u00f3n General Los Software Bills of Materials (SBOMs) se est\u00e1n convirtiendo r\u00e1pidamente en un componente obligatorio de la transparencia en la cadena de suministro de software. \u00d3rdenes ejecutivas, marcos regulatorios como NIST SSDF y est\u00e1ndares de la industria ahora requieren que las organizaciones produzcan, distribuyan y verifiquen SBOMs para cada lanzamiento de software. Un SBOM &#8230; <a title=\"Lab: Construcci\u00f3n de un Pipeline de SBOM \u2014 Generar, Atestar y Verificar con Syft y Cosign\" class=\"read-more\" href=\"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/lab-sbom-pipeline-generate-attest-verify-syft-cosign-2\/\" aria-label=\"Leer m\u00e1s sobre Lab: Construcci\u00f3n de un Pipeline de SBOM \u2014 Generar, Atestar y Verificar con Syft y Cosign\">Leer m\u00e1s<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[55,59],"tags":[],"post_folder":[],"class_list":["post-700","post","type-post","status-publish","format-standard","hentry","category-ci-cd-security","category-software-supply-chain"],"_links":{"self":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/700","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/comments?post=700"}],"version-history":[{"count":0,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/700\/revisions"}],"wp:attachment":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/media?parent=700"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/categories?post=700"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/tags?post=700"},{"taxonomy":"post_folder","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/post_folder?post=700"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}