Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27a1e75a4c | ||
|
|
1bcae76102 |
@@ -1,9 +1,5 @@
|
||||
name: Build and Push Medusa
|
||||
|
||||
# Sin anchors/aliases de YAML (&x / *x) a propósito -- el parser de
|
||||
# workflows de Gitea Actions no los resuelve en el bloque "on:" y
|
||||
# descarta el archivo completo con "unknown on type". Las dos listas de
|
||||
# paths quedan duplicadas literalmente (mismo criterio que build.yaml).
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -11,76 +7,17 @@ on:
|
||||
paths:
|
||||
- 'workloads/commerce-backend/**'
|
||||
- '.gitea/workflows/build-medusa.yaml'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'workloads/commerce-backend/**'
|
||||
- '.gitea/workflows/build-medusa.yaml'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
# Corre en push y en pull_request, siempre antes que build. Mismo
|
||||
# criterio que el frontend: si hay un secreto commiteado, el job build
|
||||
# nunca arranca (needs: gitleaks).
|
||||
gitleaks:
|
||||
name: Escaneo de Secretos (Gitleaks)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout del código
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Instalar Gitleaks
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
GITLEAKS_VERSION="8.21.2"
|
||||
curl -sSfL \
|
||||
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
|
||||
-o /tmp/gitleaks.tar.gz
|
||||
tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks
|
||||
chmod +x /tmp/gitleaks
|
||||
/tmp/gitleaks version
|
||||
|
||||
- name: Escanear secretos en el árbol de archivos
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/gitleaks detect \
|
||||
--source=workloads/commerce-backend \
|
||||
--no-git \
|
||||
--redact \
|
||||
--report-format=json \
|
||||
--report-path=gitleaks-report.json \
|
||||
--exit-code=1
|
||||
|
||||
- name: Publicar reporte de Gitleaks
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: gitleaks-report
|
||||
path: gitleaks-report.json
|
||||
if-no-files-found: ignore
|
||||
|
||||
build:
|
||||
name: Construir y publicar Medusa
|
||||
needs: gitleaks
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
|
||||
env:
|
||||
APP_DIR: workloads/commerce-backend
|
||||
MANIFEST_FILE: workloads/ecommerce/commerce/medusa.yaml
|
||||
IMAGE_NAME: gitea.cruzcloud.net/devops/ecommerce-medusa
|
||||
|
||||
steps:
|
||||
- name: Checkout del código
|
||||
uses: actions/checkout@v3
|
||||
@@ -111,82 +48,6 @@ jobs:
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Instalar Trivy
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TRIVY_VERSION="0.74.0"
|
||||
curl -sSfL \
|
||||
"https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" \
|
||||
-o /tmp/trivy.tar.gz
|
||||
tar -xzf /tmp/trivy.tar.gz -C /tmp trivy
|
||||
chmod +x /tmp/trivy
|
||||
/tmp/trivy version
|
||||
|
||||
# Escanea el manifiesto Kubernetes real de Medusa (no la imagen).
|
||||
# Informativo por ahora -- mismo criterio que el frontend, se
|
||||
# revisan los hallazgos en conjunto antes de decidir qué bloquea.
|
||||
- name: Escanear manifiestos Kubernetes (Trivy IaC)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/trivy config \
|
||||
--severity CRITICAL,HIGH,MEDIUM \
|
||||
--exit-code 0 \
|
||||
"${MANIFEST_FILE}"
|
||||
|
||||
# Mismo motivo que en build.yaml: el runner ejecuta el job ya
|
||||
# dentro de un contenedor propio que habla con el daemon Docker
|
||||
# del host (sibling containers, no Docker-in-Docker) -- correr
|
||||
# Semgrep como container aparte falla montando rutas que no
|
||||
# existen en el host real. Se instala nativo en un venv.
|
||||
- name: Instalar Semgrep
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 -m venv /tmp/semgrep-venv
|
||||
/tmp/semgrep-venv/bin/pip install --quiet "semgrep==1.173.0"
|
||||
/tmp/semgrep-venv/bin/semgrep --version
|
||||
|
||||
# Ruleset adaptado al stack real de este repo: commerce-backend es
|
||||
# una API de Medusa v2 (Node.js/TypeScript sobre Express), no una
|
||||
# app Next.js con SSR -- confirmado en package.json (sin
|
||||
# dependencia de "next", react/react-dom solo llegan como
|
||||
# dependencia transitiva del admin-sdk de Medusa, no hay código de
|
||||
# UI propio en este directorio). Por eso se usa p/typescript en
|
||||
# vez de p/typescript + p/react + p/nextjs del frontend.
|
||||
# Modo auditoría, igual que el frontend: sin --error, primera
|
||||
# vuelta para revisar hallazgos antes de decidir qué bloquea.
|
||||
- name: Escaneo SAST (Semgrep) — modo auditoría, no bloquea
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/semgrep-venv/bin/semgrep scan \
|
||||
--config=p/typescript \
|
||||
--config=p/security-audit \
|
||||
--config=p/owasp-top-ten \
|
||||
--json \
|
||||
--output=semgrep-report.json \
|
||||
"${APP_DIR}"
|
||||
|
||||
echo "=== Resumen Semgrep ==="
|
||||
/tmp/semgrep-venv/bin/python -c "
|
||||
import json
|
||||
data = json.load(open('semgrep-report.json'))
|
||||
results = data.get('results', [])
|
||||
print(f'Hallazgos: {len(results)}')
|
||||
for r in results:
|
||||
print(f\" [{r['extra']['severity']}] {r['check_id']} - {r['path']}:{r['start']['line']}\")
|
||||
"
|
||||
|
||||
- name: Publicar reporte de Semgrep
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: semgrep-report
|
||||
path: semgrep-report.json
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Login en Gitea Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
@@ -195,122 +56,15 @@ jobs:
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
logout: true
|
||||
|
||||
# push: false — igual que el frontend, la imagen se queda cargada
|
||||
# en el daemon local para poder escanearla con Trivy antes de
|
||||
# subirla al registry.
|
||||
- name: Construir Imagen
|
||||
- name: Construir y subir imagen
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: ${{ env.APP_DIR }}/
|
||||
file: ${{ env.APP_DIR }}/Dockerfile
|
||||
push: false
|
||||
load: true
|
||||
context: workloads/commerce-backend/
|
||||
file: workloads/commerce-backend/Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.VERSION }}
|
||||
${{ env.IMAGE_NAME }}:latest
|
||||
|
||||
# CRITICAL bloquea el pipeline: no se sube una imagen con una CVE
|
||||
# crítica conocida y con fix disponible.
|
||||
- name: Escanear imagen (Trivy) — CRITICAL bloquea
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/trivy image \
|
||||
--severity CRITICAL \
|
||||
--exit-code 1 \
|
||||
--ignore-unfixed \
|
||||
"${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}"
|
||||
|
||||
# HIGH solo informa por ahora — mismo criterio que el frontend.
|
||||
- name: Escanear imagen (Trivy) — HIGH informativo
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/trivy image \
|
||||
--severity HIGH \
|
||||
--exit-code 0 \
|
||||
--ignore-unfixed \
|
||||
"${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}"
|
||||
|
||||
- name: Instalar Syft
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SYFT_VERSION="1.51.0"
|
||||
curl -sSfL \
|
||||
"https://github.com/anchore/syft/releases/download/v${SYFT_VERSION}/syft_${SYFT_VERSION}_linux_amd64.tar.gz" \
|
||||
-o /tmp/syft.tar.gz
|
||||
tar -xzf /tmp/syft.tar.gz -C /tmp syft
|
||||
chmod +x /tmp/syft
|
||||
/tmp/syft version
|
||||
|
||||
- name: Generar SBOM (Syft)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/syft "${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}" \
|
||||
-o cyclonedx-json=sbom.cdx.json \
|
||||
-o spdx-json=sbom.spdx.json
|
||||
|
||||
- name: Publicar SBOM
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sbom-${{ steps.vars.outputs.VERSION }}
|
||||
path: |
|
||||
sbom.cdx.json
|
||||
sbom.spdx.json
|
||||
if-no-files-found: ignore
|
||||
|
||||
# Login ya se hizo arriba; recién acá se sube, después de que la
|
||||
# imagen pasó el gate de CRITICAL.
|
||||
- name: Subir Imagen al Registry
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker push "${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}"
|
||||
docker push "${IMAGE_NAME}:latest"
|
||||
|
||||
- name: Instalar Cosign
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
COSIGN_VERSION="3.1.3"
|
||||
curl -sSfL \
|
||||
"https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign-linux-amd64" \
|
||||
-o /tmp/cosign
|
||||
chmod +x /tmp/cosign
|
||||
/tmp/cosign version
|
||||
|
||||
# Mismo par de llaves que el frontend (COSIGN_PRIVATE_KEY /
|
||||
# COSIGN_PASSWORD ya existen como secrets a nivel de repo, no hace
|
||||
# falta un secret nuevo por app): una sola identidad de firma para
|
||||
# todo el registry de este lab. La llave pública se commitea en
|
||||
# cada directorio de app (workloads/commerce-backend/cosign.pub)
|
||||
# para que la verificación quede local a cada workflow, igual que
|
||||
# el frontend.
|
||||
- name: Firmar Imagen (Cosign)
|
||||
shell: bash
|
||||
env:
|
||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
||||
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/cosign sign \
|
||||
--key env://COSIGN_PRIVATE_KEY \
|
||||
--use-signing-config=false \
|
||||
--tlog-upload=false \
|
||||
--yes \
|
||||
"${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}"
|
||||
|
||||
- name: Verificar Firma (smoke test)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/cosign verify \
|
||||
--key "${APP_DIR}/cosign.pub" \
|
||||
--insecure-ignore-tlog=true \
|
||||
"${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}"
|
||||
gitea.cruzcloud.net/devops/ecommerce-medusa:${{ steps.vars.outputs.VERSION }}
|
||||
gitea.cruzcloud.net/devops/ecommerce-medusa:latest
|
||||
|
||||
- name: Verificar promoción segura
|
||||
id: promotion
|
||||
@@ -337,8 +91,8 @@ jobs:
|
||||
set -euo pipefail
|
||||
|
||||
VERSION="${{ steps.vars.outputs.VERSION }}"
|
||||
MANIFEST="${{ env.MANIFEST_FILE }}"
|
||||
IMAGE="${{ env.IMAGE_NAME }}"
|
||||
MANIFEST="workloads/ecommerce/commerce/medusa.yaml"
|
||||
IMAGE="gitea.cruzcloud.net/devops/ecommerce-medusa"
|
||||
|
||||
git config user.name "gitea-actions"
|
||||
git config user.email "[email protected]"
|
||||
@@ -361,14 +115,3 @@ jobs:
|
||||
-m "chore(gitops): deploy Medusa ${VERSION} [skip ci]"
|
||||
|
||||
git push origin HEAD:main
|
||||
|
||||
- name: Resumen del pipeline
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
echo "========================================"
|
||||
echo "ARI Shopping Commerce Backend (Medusa)"
|
||||
echo "Versión: ${{ steps.vars.outputs.VERSION }}"
|
||||
echo "Commit: ${{ github.sha }}"
|
||||
echo "Promoción GitOps: ${{ steps.promotion.outputs.promote }}"
|
||||
echo "========================================"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
name: Build and Push Docs Portal
|
||||
|
||||
# Sin anchors/aliases de YAML (&x / *x) a propósito -- el parser de
|
||||
# workflows de Gitea Actions no los resuelve en el bloque "on:" y
|
||||
# descarta el archivo completo con "unknown on type". Las dos listas de
|
||||
# paths quedan duplicadas literalmente (mismo criterio que build.yaml).
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -10,14 +14,71 @@ on:
|
||||
- 'workloads/docs-portal/requirements.txt'
|
||||
- 'workloads/docs-portal/Dockerfile'
|
||||
- '.gitea/workflows/deploy-docs.yaml'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'workloads/docs-portal/docs/**'
|
||||
- 'workloads/docs-portal/mkdocs.yml'
|
||||
- 'workloads/docs-portal/requirements.txt'
|
||||
- 'workloads/docs-portal/Dockerfile'
|
||||
- '.gitea/workflows/deploy-docs.yaml'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
# Corre en push y en pull_request, siempre antes que build. Mismo
|
||||
# criterio que el frontend: si hay un secreto commiteado, el job build
|
||||
# nunca arranca (needs: gitleaks).
|
||||
gitleaks:
|
||||
name: Escaneo de Secretos (Gitleaks)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout del código
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Instalar Gitleaks
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
GITLEAKS_VERSION="8.21.2"
|
||||
curl -sSfL \
|
||||
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
|
||||
-o /tmp/gitleaks.tar.gz
|
||||
tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks
|
||||
chmod +x /tmp/gitleaks
|
||||
/tmp/gitleaks version
|
||||
|
||||
- name: Escanear secretos en el árbol de archivos
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/gitleaks detect \
|
||||
--source=workloads/docs-portal \
|
||||
--no-git \
|
||||
--redact \
|
||||
--report-format=json \
|
||||
--report-path=gitleaks-report.json \
|
||||
--exit-code=1
|
||||
|
||||
- name: Publicar reporte de Gitleaks
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: gitleaks-report
|
||||
path: gitleaks-report.json
|
||||
if-no-files-found: ignore
|
||||
|
||||
build:
|
||||
name: Construir y publicar Docs Portal
|
||||
needs: gitleaks
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
@@ -56,6 +117,83 @@ jobs:
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Instalar Trivy
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TRIVY_VERSION="0.74.0"
|
||||
curl -sSfL \
|
||||
"https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" \
|
||||
-o /tmp/trivy.tar.gz
|
||||
tar -xzf /tmp/trivy.tar.gz -C /tmp trivy
|
||||
chmod +x /tmp/trivy
|
||||
/tmp/trivy version
|
||||
|
||||
# Escanea todo el directorio de la app (Deployment, Service,
|
||||
# Ingress y Dockerfile), no un único MANIFEST_FILE como el
|
||||
# frontend -- docs-portal tiene varios manifiestos K8s separados
|
||||
# (deployment.yaml, service.yaml, ingress.yaml) en vez de uno
|
||||
# solo, así que un único archivo dejaría fuera la mayoría del
|
||||
# directorio. Informativo por ahora, mismo criterio que el resto.
|
||||
- name: Escanear manifiestos Kubernetes (Trivy IaC)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/trivy config \
|
||||
--severity CRITICAL,HIGH,MEDIUM \
|
||||
--exit-code 0 \
|
||||
"${APP_DIR}"
|
||||
|
||||
# Mismo motivo que en build.yaml: sibling containers, no
|
||||
# Docker-in-Docker -- Semgrep corre nativo en un venv.
|
||||
- name: Instalar Semgrep
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 -m venv /tmp/semgrep-venv
|
||||
/tmp/semgrep-venv/bin/pip install --quiet "semgrep==1.173.0"
|
||||
/tmp/semgrep-venv/bin/semgrep --version
|
||||
|
||||
# Ruleset adaptado: docs-portal no tiene código de aplicación
|
||||
# propio (es contenido Markdown + configuración de MkDocs), así
|
||||
# que ni p/typescript ni p/react/p/nextjs del frontend aplican
|
||||
# acá. Se usa p/python -- el único código ejecutable real en este
|
||||
# directorio sería un hook/plugin de Python de MkDocs, si algún
|
||||
# día se agrega uno. Hoy no hay ningún archivo .py en
|
||||
# workloads/docs-portal, así que 0 hallazgos es el resultado
|
||||
# esperado, no un falso negativo -- se deja el stage para que
|
||||
# detecte código nuevo el día que se agregue, sin tener que
|
||||
# recordar volver a tocar el pipeline. Modo auditoría, igual que
|
||||
# el resto: no bloquea.
|
||||
- name: Escaneo SAST (Semgrep) — modo auditoría, no bloquea
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/semgrep-venv/bin/semgrep scan \
|
||||
--config=p/python \
|
||||
--config=p/security-audit \
|
||||
--json \
|
||||
--output=semgrep-report.json \
|
||||
"${APP_DIR}"
|
||||
|
||||
echo "=== Resumen Semgrep ==="
|
||||
/tmp/semgrep-venv/bin/python -c "
|
||||
import json
|
||||
data = json.load(open('semgrep-report.json'))
|
||||
results = data.get('results', [])
|
||||
print(f'Hallazgos: {len(results)}')
|
||||
for r in results:
|
||||
print(f\" [{r['extra']['severity']}] {r['check_id']} - {r['path']}:{r['start']['line']}\")
|
||||
"
|
||||
|
||||
- name: Publicar reporte de Semgrep
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: semgrep-report
|
||||
path: semgrep-report.json
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Login en Gitea Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
@@ -64,18 +202,129 @@ jobs:
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
logout: true
|
||||
|
||||
# mkdocs build --strict corre dentro del propio Dockerfile (stage de
|
||||
# build), así que un nav/link roto rompe este paso antes de publicar.
|
||||
- name: Construir y subir imagen
|
||||
# mkdocs build --strict corre dentro del propio Dockerfile (stage
|
||||
# de build), así que un nav/link roto rompe este paso antes de
|
||||
# publicar. push: false / load: true -- igual que el frontend, la
|
||||
# imagen queda cargada localmente para escanearla con Trivy antes
|
||||
# de subirla.
|
||||
- name: Construir Imagen
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: workloads/docs-portal/
|
||||
file: workloads/docs-portal/Dockerfile
|
||||
push: true
|
||||
context: ${{ env.APP_DIR }}/
|
||||
file: ${{ env.APP_DIR }}/Dockerfile
|
||||
push: false
|
||||
load: true
|
||||
tags: |
|
||||
${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.VERSION }}
|
||||
${{ env.IMAGE_NAME }}:latest
|
||||
|
||||
# CRITICAL bloquea el pipeline: no se sube una imagen con una CVE
|
||||
# crítica conocida y con fix disponible.
|
||||
# --timeout 15m0s: agregado preventivamente. Se vio en vivo, al
|
||||
# correr este mismo comando sin el flag en commerce-backend, que
|
||||
# el default de Trivy (5m) no alcanza en este host bajo carga
|
||||
# ("context deadline exceeded" a los 4m52s) -- la imagen de
|
||||
# docs-portal es chica, pero no cuesta nada blindar el mismo
|
||||
# comando en los tres pipelines.
|
||||
- name: Escanear imagen (Trivy) — CRITICAL bloquea
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/trivy image \
|
||||
--severity CRITICAL \
|
||||
--exit-code 1 \
|
||||
--ignore-unfixed \
|
||||
--timeout 15m0s \
|
||||
"${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}"
|
||||
|
||||
# HIGH solo informa por ahora — mismo criterio que el resto.
|
||||
- name: Escanear imagen (Trivy) — HIGH informativo
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/trivy image \
|
||||
--severity HIGH \
|
||||
--exit-code 0 \
|
||||
--ignore-unfixed \
|
||||
--timeout 15m0s \
|
||||
"${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}"
|
||||
|
||||
- name: Instalar Syft
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SYFT_VERSION="1.51.0"
|
||||
curl -sSfL \
|
||||
"https://github.com/anchore/syft/releases/download/v${SYFT_VERSION}/syft_${SYFT_VERSION}_linux_amd64.tar.gz" \
|
||||
-o /tmp/syft.tar.gz
|
||||
tar -xzf /tmp/syft.tar.gz -C /tmp syft
|
||||
chmod +x /tmp/syft
|
||||
/tmp/syft version
|
||||
|
||||
- name: Generar SBOM (Syft)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/syft "${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}" \
|
||||
-o cyclonedx-json=sbom.cdx.json \
|
||||
-o spdx-json=sbom.spdx.json
|
||||
|
||||
- name: Publicar SBOM
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sbom-${{ steps.vars.outputs.VERSION }}
|
||||
path: |
|
||||
sbom.cdx.json
|
||||
sbom.spdx.json
|
||||
if-no-files-found: ignore
|
||||
|
||||
# Login ya se hizo arriba; recién acá se sube, después de que la
|
||||
# imagen pasó el gate de CRITICAL.
|
||||
- name: Subir Imagen al Registry
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker push "${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}"
|
||||
docker push "${IMAGE_NAME}:latest"
|
||||
|
||||
- name: Instalar Cosign
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
COSIGN_VERSION="3.1.3"
|
||||
curl -sSfL \
|
||||
"https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign-linux-amd64" \
|
||||
-o /tmp/cosign
|
||||
chmod +x /tmp/cosign
|
||||
/tmp/cosign version
|
||||
|
||||
# Mismo par de llaves que el frontend y commerce-backend (secrets
|
||||
# ya existentes a nivel de repo). Llave pública commiteada en
|
||||
# workloads/docs-portal/cosign.pub.
|
||||
- name: Firmar Imagen (Cosign)
|
||||
shell: bash
|
||||
env:
|
||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
||||
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/cosign sign \
|
||||
--key env://COSIGN_PRIVATE_KEY \
|
||||
--use-signing-config=false \
|
||||
--tlog-upload=false \
|
||||
--yes \
|
||||
"${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}"
|
||||
|
||||
- name: Verificar Firma (smoke test)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/cosign verify \
|
||||
--key "${APP_DIR}/cosign.pub" \
|
||||
--insecure-ignore-tlog=true \
|
||||
"${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}"
|
||||
|
||||
- name: Verificar promoción segura
|
||||
id: promotion
|
||||
shell: bash
|
||||
@@ -123,3 +372,14 @@ jobs:
|
||||
-m "chore(gitops): deploy Docs Portal ${VERSION} [skip ci]"
|
||||
|
||||
git push origin HEAD:main
|
||||
|
||||
- name: Resumen del pipeline
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
echo "========================================"
|
||||
echo "CruzCloud Lab Docs Portal"
|
||||
echo "Versión: ${{ steps.vars.outputs.VERSION }}"
|
||||
echo "Commit: ${{ github.sha }}"
|
||||
echo "Promoción GitOps: ${{ steps.promotion.outputs.promote }}"
|
||||
echo "========================================"
|
||||
|
||||
Reference in New Issue
Block a user