# ============================================================================= # GitHub Actions — Full DevSecOps Pipeline # ============================================================================= # Stages: SAST → Build → Scan → Test → Sign → Deploy # ============================================================================= name: DevSecOps Pipeline on: push: branches: [main] pull_request: branches: [main] env: REGISTRY: ecr.aws/devsecops IMAGE_NAME: ${{ github.repository }} permissions: id-token: write contents: read security-events: write jobs: # ========================================================================= # Stage 1: SAST + Secret Scanning # ========================================================================= sast: name: SAST & Secret Scan runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Semgrep SAST uses: semgrep/semgrep-action@v1 with: config: >- p/security-audit p/secrets p/owasp-top-ten publishToken: ${{ secrets.SEMGREP_TOKEN }} - name: Trivy Secret Scan uses: aquasecurity/trivy-action@master with: scan-type: fs scanners: secret exit-code: 1 severity: CRITICAL,HIGH - name: Checkov IaC Scan uses: bridgecrewio/checkov-action@master with: directory: terraform/ framework: terraform output_format: sarif output_file: checkov.sarif soft_fail: false - name: Upload SARIF uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: . # ========================================================================= # Stage 2: Build # ========================================================================= build: name: Build & Push needs: sast runs-on: ubuntu-latest outputs: image_tag: ${{ steps.meta.outputs.tags }} image_digest: ${{ steps.build.outputs.digest }} steps: - uses: actions/checkout@v4 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN }} role-session-name: github-actions aws-region: us-east-1 - name: Login to ECR uses: aws-actions/amazon-ecr-login@v2 - name: Docker Meta id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=sha,prefix= type=ref,event=branch type=semver,pattern={{version}} - name: Build id: build uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max build-args: | BUILD_DATE=${{ github.event.head_commit.timestamp }} # ========================================================================= # Stage 3: Container Security Scan # ========================================================================= scan: name: Container Security Scan needs: build runs-on: ubuntu-latest steps: - name: Trivy Vulnerability Scan uses: aquasecurity/trivy-action@master with: image-ref: ${{ needs.build.outputs.image_tag }} format: sarif output: trivy.sarif exit-code: 1 severity: CRITICAL,HIGH ignore-unfixed: true - name: Generate SBOM uses: anchore/sbom-action@v0 with: image: ${{ needs.build.outputs.image_tag }} format: spdx-json output-file: sbom.spdx.json - name: Upload SBOM uses: actions/upload-artifact@v4 with: name: sbom path: sbom.spdx.json # ========================================================================= # Stage 4: Integration Tests + DAST # ========================================================================= test: name: Integration Test & DAST needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Integration Tests run: | docker compose -f docker-compose.test.yml up --abort-on-container-exit - name: OWASP ZAP Full Scan uses: zaproxy/action-full-scan@v0.10.0 with: target: https://staging.platform.internal rules_file_name: zap-rules.tsv cmd_options: '-a -j' fail_action: true # ========================================================================= # Stage 5: Sign & Attest # ========================================================================= sign: name: Sign & Attest needs: [build, scan] runs-on: ubuntu-latest steps: - name: Cosign Install uses: sigstore/cosign-installer@v3 - name: Sign Image run: | cosign sign --yes ${{ needs.build.outputs.image_tag }}@${{ needs.build.outputs.image_digest }} - name: Attest SBOM run: | cosign attest --yes \ --predicate sbom.spdx.json \ --type spdxjson \ ${{ needs.build.outputs.image_tag }}@${{ needs.build.outputs.image_digest }} # ========================================================================= # Stage 6: Deploy (ArgoCD Sync) # ========================================================================= deploy-staging: name: Deploy → Staging needs: [sign, test] runs-on: ubuntu-latest environment: staging steps: - name: Update Kustomize Image Tag run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" cd k8s/workloads/${{ matrix.workload }} kustomize edit set image ${{ env.IMAGE_NAME }}=${{ needs.build.outputs.image_tag }} git commit -am "chore: update image tag for staging" git push - name: ArgoCD Sync run: | argocd app sync staging-app --grpc-web deploy-prod: name: Deploy → Production needs: deploy-staging runs-on: ubuntu-latest environment: production steps: - name: ArgoCD Sync run: | argocd app sync prod-app --grpc-web - name: Smoke Test run: | curl -sf https://platform.internal/healthz || exit 1