From aae261a68e9bd824cd73afe58bb6cd210295656f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Castrill=C3=B3n?= Date: Wed, 24 Sep 2025 22:04:03 -0400 Subject: [PATCH 01/15] Add job deploy --- .github/workflows/main.yml | 36 +++++++++++++++++++++++++++++++++++- kind.yaml | 7 +++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 kind.yaml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3996bcb..46ecbd8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -163,4 +163,38 @@ jobs: # - 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 }}" \ No newline at end of file + # 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 \ No newline at end of file diff --git a/kind.yaml b/kind.yaml new file mode 100644 index 0000000..1e46fea --- /dev/null +++ b/kind.yaml @@ -0,0 +1,7 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: + - role: control-plane + extraPortMappings: + - containerPort: 30080 + hostPort: 30080 From c9122693b8132426b2d085f7f1fc8ecb76b9b931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Castrill=C3=B3n?= Date: Wed, 24 Sep 2025 22:06:44 -0400 Subject: [PATCH 02/15] Add job deploy --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 46ecbd8..baa1390 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -172,7 +172,7 @@ jobs: deploy: name: Deploy a Kubernetes local (kind/minikube) runs-on: self-hosted - needs: [sign] + needs: [container_scan] steps: - uses: actions/checkout@v4 - name: Cargar imagen local al clúster kind From 3c47950584ae7cc08d837f6a035bd7849cf77b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Castrill=C3=B3n?= Date: Wed, 24 Sep 2025 22:12:17 -0400 Subject: [PATCH 03/15] Add job deploy --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index baa1390..dab083a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ env: # Parámetros de despliegue local IMAGE_NAME: demo-app IMAGE_TAG: local - KIND_CLUSTER: kind + KIND_CLUSTER: devsecops SERVICE_RELEASE_NAME: demo jobs: @@ -167,7 +167,7 @@ jobs: # ────────────────────────────────────────────────────────────────────────────── - # Slide 15 — Deploy a K8s local + DAST (ZAP) + # Deploy a K8s local + DAST (ZAP) # ────────────────────────────────────────────────────────────────────────────── deploy: name: Deploy a Kubernetes local (kind/minikube) @@ -176,7 +176,7 @@ jobs: 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} + run: kind load docker-image demo-app:local --name devsecops - name: Helm upgrade/install run: | helm upgrade --install ${SERVICE_RELEASE_NAME} charts/demo-app \ From da52a3b268d58fe59c92fe766a898eedaa7d8c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Castrill=C3=B3n?= Date: Wed, 24 Sep 2025 22:16:45 -0400 Subject: [PATCH 04/15] Add job deploy --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dab083a..b1499dc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -179,12 +179,12 @@ jobs: run: kind load docker-image demo-app:local --name devsecops - name: Helm upgrade/install run: | - helm upgrade --install ${SERVICE_RELEASE_NAME} charts/demo-app \ + helm upgrade --install demo 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 + run: kubectl rollout status deploy/demo --timeout=180s dast: name: DAST (OWASP ZAP baseline) @@ -193,7 +193,7 @@ jobs: steps: - name: Port-forward al Service y ejecutar ZAP run: | - kubectl port-forward svc/${SERVICE_RELEASE_NAME} 8080:80 & echo $! > pf.pid + kubectl port-forward svc/demo 8080:80 & echo $! > pf.pid sleep 3 docker run --rm -t owasp/zap2docker-stable \ zap-baseline.py -t http://localhost:8080 -x zap.xml From 8f39fbb6f56457e0ba64e41fc16661fdb1006bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Castrill=C3=B3n?= Date: Wed, 24 Sep 2025 22:23:16 -0400 Subject: [PATCH 05/15] Add job deploy --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b1499dc..799df40 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -179,12 +179,12 @@ jobs: run: kind load docker-image demo-app:local --name devsecops - name: Helm upgrade/install run: | - helm upgrade --install demo charts/demo-app \ + helm upgrade --install demo-app 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/demo --timeout=180s + run: kubectl rollout status deploy/demo-app --timeout=180s dast: name: DAST (OWASP ZAP baseline) From e6716b39c5d0a9ee3baa4c31b7c57a8a7412117d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Castrill=C3=B3n?= Date: Thu, 2 Oct 2025 23:48:23 -0400 Subject: [PATCH 06/15] Enable cosign --- .github/workflows/main.yml | 50 +++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 799df40..4620618 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -126,31 +126,31 @@ jobs: - 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 + 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 From aae08c4ffb6179519b5d033eff605e0077fcaeb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Castrill=C3=B3n?= Date: Fri, 3 Oct 2025 00:35:35 -0400 Subject: [PATCH 07/15] Deploy to docker hub --- .github/workflows/main.yml | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4620618..60712f2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -94,7 +94,33 @@ jobs: with: image: demo-app:local artifact-name: sbom.spdx.json # queda como artefacto del job - # ────────────────────────────────────────────────────────────────────────────── + + publish: + name: Publish to Docker Hub + runs-on: ubuntu-latest + needs: [sast] + if: github.event_name == 'push' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push image + uses: docker/build-push-action@v4 + with: + context: ${{ env.APP_DIR }} + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest + + + # ───────────────────────────────────────────────────────────────────────────── # Trivy + Firma/Verify (cosign) # ────────────────────────────────────────────────────────────────────────────── container_scan: From 53c301c2dbe2e4f3aa5b5d23b4e39270c773f561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Castrill=C3=B3n?= Date: Fri, 3 Oct 2025 00:38:08 -0400 Subject: [PATCH 08/15] Minor fix --- .github/workflows/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 60712f2..0d55c70 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -99,7 +99,6 @@ jobs: name: Publish to Docker Hub runs-on: ubuntu-latest needs: [sast] - if: github.event_name == 'push' steps: - name: Checkout uses: actions/checkout@v4 From f86dcdc62da8119ac7373db3b8beca7385da6fcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Castrill=C3=B3n?= Date: Fri, 3 Oct 2025 00:43:25 -0400 Subject: [PATCH 09/15] Minor fix --- .github/workflows/main.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0d55c70..b1a01a2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -104,9 +104,9 @@ jobs: uses: actions/checkout@v4 - name: Log in to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} + username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push image @@ -115,8 +115,8 @@ jobs: context: ${{ env.APP_DIR }} push: true tags: | - ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} - ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest + ${{ vars.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} + ${{ vars.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest # ───────────────────────────────────────────────────────────────────────────── From d77e2eeee997e31bdd66fdd009047627d91cb08f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Castrill=C3=B3n?= Date: Fri, 3 Oct 2025 01:11:35 -0400 Subject: [PATCH 10/15] Change to github registry --- .github/workflows/main.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b1a01a2..bff5bca 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -96,18 +96,19 @@ jobs: artifact-name: sbom.spdx.json # queda como artefacto del job publish: - name: Publish to Docker Hub + name: Publish to GitHub Container Registry runs-on: ubuntu-latest needs: [sast] steps: - name: Checkout uses: actions/checkout@v4 - - name: Log in to Docker Hub - uses: docker/login-action@v3 + - name: Log in to GitHub Container Registry + uses: docker/login-action@v2 with: - username: ${{ vars.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push image uses: docker/build-push-action@v4 @@ -115,8 +116,8 @@ jobs: context: ${{ env.APP_DIR }} push: true tags: | - ${{ vars.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} - ${{ vars.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest + ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} + ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest # ───────────────────────────────────────────────────────────────────────────── From 185331f850162b92e34de004b475ac63db697146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Castrill=C3=B3n?= <712317+kerobero@users.noreply.github.com> Date: Thu, 9 Oct 2025 02:01:48 +0000 Subject: [PATCH 11/15] Analyze published image --- .github/workflows/main.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bff5bca..28ec4f1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -126,19 +126,19 @@ jobs: container_scan: name: Container & deps scan (Trivy) runs-on: self-hosted - needs: [build] + needs: [push_image] 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 image (CRITICAL,HIGH) + uses: aquasecurity/trivy-action@0.28.0 + with: + scan-type: image + image-ref: 'ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ github.sha }}' + 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 From d7c570ed15538b14eb31dbf678fde124c778687d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Castrill=C3=B3n?= Date: Wed, 8 Oct 2025 22:03:23 -0400 Subject: [PATCH 12/15] Minor fix --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 28ec4f1..366ec7a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -126,7 +126,7 @@ jobs: container_scan: name: Container & deps scan (Trivy) runs-on: self-hosted - needs: [push_image] + needs: [publish] steps: - name: Trivy image (CRITICAL,HIGH) uses: aquasecurity/trivy-action@0.28.0 From b60e13ddd1b07ae76b433ac063ad8a55d2f9d67b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Castrill=C3=B3n?= <712317+kerobero@users.noreply.github.com> Date: Thu, 9 Oct 2025 02:09:01 +0000 Subject: [PATCH 13/15] Change runner --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 28ec4f1..bd5e546 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -125,7 +125,7 @@ jobs: # ────────────────────────────────────────────────────────────────────────────── container_scan: name: Container & deps scan (Trivy) - runs-on: self-hosted + runs-on: ubuntu-latest needs: [push_image] steps: - name: Trivy image (CRITICAL,HIGH) From 0975da23ac62600241af7c9efd8eac11e490d8c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Castrill=C3=B3n?= Date: Wed, 8 Oct 2025 22:11:57 -0400 Subject: [PATCH 14/15] Minor fix --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bd5e546..3222199 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -126,7 +126,7 @@ jobs: container_scan: name: Container & deps scan (Trivy) runs-on: ubuntu-latest - needs: [push_image] + needs: [publish] steps: - name: Trivy image (CRITICAL,HIGH) uses: aquasecurity/trivy-action@0.28.0 From 8abf5f25c38352bb98056cb64a9beeea1643d1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Castrill=C3=B3n?= <712317+kerobero@users.noreply.github.com> Date: Thu, 9 Oct 2025 02:18:03 +0000 Subject: [PATCH 15/15] Login to ghcr --- .github/workflows/main.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3222199..c414a3e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -128,11 +128,17 @@ jobs: runs-on: ubuntu-latest needs: [publish] steps: + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Trivy image (CRITICAL,HIGH) uses: aquasecurity/trivy-action@0.28.0 with: scan-type: image - image-ref: 'ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ github.sha }}' + image-ref: 'ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest' format: sarif output: trivy-image.sarif ignore-unfixed: true