Skip to content

Add job deploy

Add job deploy #68

Workflow file for this run

name: devsecops-pipeline

Check failure on line 1 in .github/workflows/main.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/main.yml

Invalid workflow file

(Line: 175, Col: 13): Job 'deploy' depends on unknown job 'sign'., (Line: 192, Col: 13): Job 'dast' depends on job 'deploy' which creates a cycle in the dependency graph.
on:
pull_request:
push:
branches: ["main"]
workflow_dispatch:
permissions:
contents: read
security-events: write
packages: write # útil si luego publicas en GHCR
discussions: write
pull-requests: write
concurrency:
group: devsecops-${{ github.ref }}
cancel-in-progress: true
env:
# 🔁 Cambia esto para probar cada escenario (apps/10-secrets-leak, 20-sast-bugs, etc.)
APP_DIR: apps/10-secrets-leak
# Parámetros de despliegue local
IMAGE_NAME: demo-app
IMAGE_TAG: local
KIND_CLUSTER: kind
SERVICE_RELEASE_NAME: demo
jobs:
# ──────────────────────────────────────────────────────────────────────────────
# Secrets + SAST
# ──────────────────────────────────────────────────────────────────────────────
secrets:
name: Secrets scanning (Gitleaks)
runs-on: ubuntu-latest
steps:
- name: Clean gitleaks
run: rm -f /tmp/gitleaks.tmp
- uses: actions/checkout@v4
with:
fetch-depth: 0 # para análisis que miran historial
- name: Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Si usas un gitleaks.toml propio, añade inputs/vars según la acción
sast:
name: SAST (Semgrep)
runs-on: ubuntu-latest
needs: [secrets]
steps:
- uses: actions/checkout@v4
- name: self-hosted (no bloqueante)
run: |
docker run --rm -v "$PWD:/src" returntocorp/semgrep:latest \
semgrep scan --config p/ci --config .semgrep
- name: Export SARIF (para pestaña Security)
run: |
docker run --rm -v "$PWD:/src" returntocorp/semgrep:latest \
semgrep scan --config p/ci --config .semgrep --sarif -o semgrep.sarif || true
- uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: semgrep.sarif }
# ──────────────────────────────────────────────────────────────────────────────
# IaC + Build + SBOM
# ──────────────────────────────────────────────────────────────────────────────
# iac:
# name: IaC scan (Checkov)
# runs-on: ubuntu-latest
# needs: [sast]
# steps:
# - uses: actions/checkout@v4
# - name: Checkov (K8s/Terraform)
# uses: bridgecrewio/checkov-action@master
# with:
# directory: .
# quiet: true
# soft_fail: false # pon true si no quieres bloquear (no recomendado)
build:
name: Build + SBOM
runs-on: ubuntu-latest
needs: [sast]
steps:
- uses: actions/checkout@v4
- name: Build imagen de la app objetivo
run: |
cd "${APP_DIR}"
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
- name: SBOM (Syft)
uses: anchore/sbom-action@v0
with:
image: demo-app:local
artifact-name: sbom.spdx.json # queda como artefacto del job
# ──────────────────────────────────────────────────────────────────────────────
# Trivy + Firma/Verify (cosign)
# ──────────────────────────────────────────────────────────────────────────────
container_scan:
name: Container & deps scan (Trivy)
runs-on: self-hosted
needs: [build]
steps:
# - name: Trivy image (CRITICAL,HIGH)
# uses: aquasecurity/trivy-action@0.28.0
# with:
# scan-type: fs
# image-ref: demo-app:local
# format: sarif
# output: trivy-image.sarif
# ignore-unfixed: true
# severity: CRITICAL,HIGH
# - uses: github/codeql-action/upload-sarif@v3
# with: { sarif_file: trivy-image.sarif }
- name: Trivy fs (SCA sobre el repo)
uses: aquasecurity/trivy-action@0.28.0
with:
scan-type: fs
scan-ref: .
format: sarif
output: trivy-fs.sarif
ignore-unfixed: true
severity: CRITICAL,HIGH
- uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: trivy-fs.sarif }
# sign:
# name: Supply chain gate (cosign sobre SBOM)
# runs-on: self-hosted
# needs: [container_scan]
# env:
# COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
# steps:
# - uses: actions/checkout@v4
# - name: Instalar cosign
# run: |
# COSIGN_URL="https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64"
# curl -sSLf "$COSIGN_URL" -o /usr/local/bin/cosign
# chmod +x /usr/local/bin/cosign
# - name: Generar claves (si no existen)
# run: |
# test -f cosign.key || cosign generate-key-pair
# - name: Descargar SBOM del job anterior
# uses: actions/download-artifact@v4
# with:
# name: sbom.spdx.json
# path: .
# - name: Firmar SBOM (sign-blob)
# run: cosign sign-blob --yes --key cosign.key sbom.spdx.json <<< "$COSIGN_PASSWORD"
# - name: Verificar firma del SBOM (gate)
# run: cosign verify-blob --key cosign.pub --signature sbom.spdx.json.sig sbom.spdx.json
# ── Alternativa (comentada) si publicas la imagen en GHCR y quieres firmar la imagen:
# - name: Login GHCR
# run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
# - name: Push a GHCR
# run: |
# export IMG="ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ github.sha }}"
# docker tag ${IMAGE_NAME}:${IMAGE_TAG} "$IMG"
# docker push "$IMG"
# - name: Sign imagen en GHCR
# run: cosign sign --yes --key cosign.key "ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ github.sha }}" <<< "$COSIGN_PASSWORD"
# - name: Verify imagen (gate)
# run: cosign verify --key cosign.pub "ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ github.sha }}"
# ──────────────────────────────────────────────────────────────────────────────
# Slide 15 — Deploy a K8s local + DAST (ZAP)
# ──────────────────────────────────────────────────────────────────────────────
deploy:
name: Deploy a Kubernetes local (kind/minikube)
runs-on: self-hosted
needs: [sign]
steps:
- uses: actions/checkout@v4
- name: Cargar imagen local al clúster kind
run: kind load docker-image ${IMAGE_NAME}:${IMAGE_TAG} --name ${KIND_CLUSTER}
- name: Helm upgrade/install
run: |
helm upgrade --install ${SERVICE_RELEASE_NAME} charts/demo-app \
--set image.repository=${IMAGE_NAME} \
--set image.tag=${IMAGE_TAG} \
--set service.type=NodePort
- name: Esperar readiness
run: kubectl rollout status deploy/${SERVICE_RELEASE_NAME} --timeout=180s
dast:
name: DAST (OWASP ZAP baseline)
runs-on: self-hosted
needs: [deploy]
steps:
- name: Port-forward al Service y ejecutar ZAP
run: |
kubectl port-forward svc/${SERVICE_RELEASE_NAME} 8080:80 & echo $! > pf.pid
sleep 3
docker run --rm -t owasp/zap2docker-stable \
zap-baseline.py -t http://localhost:8080 -x zap.xml
kill $(cat pf.pid) || true