Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b49ac2bc5 | ||
|
|
c25877c700 | ||
|
|
941c009c9c |
@@ -1,5 +1,9 @@
|
|||||||
name: Build and Push Medusa
|
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:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@@ -7,17 +11,76 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- 'workloads/commerce-backend/**'
|
- 'workloads/commerce-backend/**'
|
||||||
- '.gitea/workflows/build-medusa.yaml'
|
- '.gitea/workflows/build-medusa.yaml'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- 'workloads/commerce-backend/**'
|
||||||
|
- '.gitea/workflows/build-medusa.yaml'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
jobs:
|
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:
|
build:
|
||||||
name: Construir y publicar Medusa
|
name: Construir y publicar Medusa
|
||||||
|
needs: gitleaks
|
||||||
|
if: github.event_name == 'push'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 45
|
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:
|
steps:
|
||||||
- name: Checkout del código
|
- name: Checkout del código
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@@ -48,6 +111,82 @@ jobs:
|
|||||||
exit 1
|
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
|
- name: Login en Gitea Registry
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
@@ -56,15 +195,122 @@ jobs:
|
|||||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
logout: true
|
logout: true
|
||||||
|
|
||||||
- name: Construir y subir imagen
|
# 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
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: workloads/commerce-backend/
|
context: ${{ env.APP_DIR }}/
|
||||||
file: workloads/commerce-backend/Dockerfile
|
file: ${{ env.APP_DIR }}/Dockerfile
|
||||||
push: true
|
push: false
|
||||||
|
load: true
|
||||||
tags: |
|
tags: |
|
||||||
gitea.cruzcloud.net/devops/ecommerce-medusa:${{ steps.vars.outputs.VERSION }}
|
${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.VERSION }}
|
||||||
gitea.cruzcloud.net/devops/ecommerce-medusa:latest
|
${{ 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 }}"
|
||||||
|
|
||||||
- name: Verificar promoción segura
|
- name: Verificar promoción segura
|
||||||
id: promotion
|
id: promotion
|
||||||
@@ -91,8 +337,8 @@ jobs:
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
VERSION="${{ steps.vars.outputs.VERSION }}"
|
VERSION="${{ steps.vars.outputs.VERSION }}"
|
||||||
MANIFEST="workloads/ecommerce/commerce/medusa.yaml"
|
MANIFEST="${{ env.MANIFEST_FILE }}"
|
||||||
IMAGE="gitea.cruzcloud.net/devops/ecommerce-medusa"
|
IMAGE="${{ env.IMAGE_NAME }}"
|
||||||
|
|
||||||
git config user.name "gitea-actions"
|
git config user.name "gitea-actions"
|
||||||
git config user.email "[email protected]"
|
git config user.email "[email protected]"
|
||||||
@@ -115,3 +361,14 @@ jobs:
|
|||||||
-m "chore(gitops): deploy Medusa ${VERSION} [skip ci]"
|
-m "chore(gitops): deploy Medusa ${VERSION} [skip ci]"
|
||||||
|
|
||||||
git push origin HEAD:main
|
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 "========================================"
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhjg9/nC0u+iEANiHkVJY8iN+LZo+
|
||||||
|
VFMF7XG/oC64W3/SfwrPgt+ZIqF6t+ceyrNuEgugajvUdpigz1PHEqQKLw==
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
@@ -16,7 +16,7 @@ spec:
|
|||||||
- name: gitea-registry-secret
|
- name: gitea-registry-secret
|
||||||
containers:
|
containers:
|
||||||
- name: web
|
- name: web
|
||||||
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.91
|
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.104
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
env:
|
env:
|
||||||
|
|||||||
Reference in New Issue
Block a user