| | @@ -0,0 +1,84 @@ |
| + | # Secure CI/CD Pipeline |
| + | |
| + | A GitHub Actions pipeline that gates every push and pull request on four security |
| + | checks before code is allowed to merge, then reports the run back to a SOC for |
| + | visibility. The sample app is a small Flask task API; the point of the repo is the |
| + | pipeline around it. |
| + | |
| + | The checks run as separate jobs so a failure tells you exactly which gate tripped |
| + | instead of one long log you have to scroll through. |
| + | |
| + | ```mermaid |
| + | flowchart TD |
| + | push[push / pull_request] --> lint[ruff lint] |
| + | lint --> sast[Semgrep SAST] |
| + | lint --> secrets[gitleaks secret scan] |
| + | lint --> deps[pip-audit dependencies] |
| + | sast --> test[pytest + coverage] |
| + | secrets --> test |
| + | deps --> test |
| + | test --> notify[notify SOC webhook] |
| + | ``` |
| + | |
| + | ## The gates |
| + | |
| + | | Job | Tool | What it stops | |
| + | |-----|------|---------------| |
| + | | `lint` | ruff | style + a security rule set (`S`) on top of the usual lint | |
| + | | `sast` | Semgrep | the OWASP/Flask rule packs plus four custom rules in `.semgrep/rules.yml` | |
| + | | `secrets` | gitleaks | committed credentials, full history scanned on PRs | |
| + | | `dependencies` | pip-audit | known-vulnerable pinned dependencies | |
| + | | `test` | pytest | regressions, with coverage reported | |
| + | | `notify-soc` | `scripts/notify_soc.py` | nothing - it posts the run outcome to the SOC | |
| + | |
| + | `lint` runs first as a cheap fail-fast. The three security scans fan out in |
| + | parallel, `test` waits on all of them, and the SOC notification runs last with |
| + | `if: always()` so the SOC hears about failures too, not just green runs. |
| + | |
| + | ## Custom Semgrep rules |
| + | |
| + | The packs catch the common cases; `.semgrep/rules.yml` adds the ones I kept seeing |
| + | slip through: |
| + | |
| + | - Flask started with `debug=True` (Werkzeug debugger RCE) |
| + | - `subprocess` with `shell=True` on a non-literal argument |
| + | - `jwt.decode` with signature verification disabled |
| + | - binding to `0.0.0.0` without an explicit reason |
| + | |
| + | The four rules running against a deliberately vulnerable file - every finding is |
| + | Blocking, so the SAST job fails the pipeline before the code can merge: |
| + | |
| + |  |
| + | |
| + | ## SARIF and the Security tab |
| + | |
| + | Semgrep emits SARIF that gets uploaded with `github/codeql-action/upload-sarif`, so |
| + | findings show up under the repo's Security tab and as inline PR annotations rather |
| + | than only in the job log. |
| + | |
| + | The dependency gate is `pip-audit`, which fails the build on a pinned package with a |
| + | known advisory: |
| + | |
| + |  |
| + | |
| + | ## Run it locally |
| + | |
| + | ```bash |
| + | make install |
| + | make all |
| + | ``` |
| + | |
| + | `make all` runs the same gates the pipeline does. You need `semgrep` and `gitleaks` |
| + | on your PATH for the `sast` and `secrets` targets; everything else is pip-installed |
| + | by `make install`. |
| + | |
| + | ## SOC integration |
| + | |
| + | The last job posts the run outcome - repo, commit, actor, status, and a link back to |
| + | the run - to a Shuffle webhook. In my lab that webhook feeds the |
| + | [SOC automation lab](../soc-automation-lab), so a failed security gate opens a case |
| + | in TheHive the same way a Wazuh alert does. Set the `SHUFFLE_WEBHOOK_URL` repository |
| + | secret to wire it up; without it the job no-ops instead of failing. |
| + | |
| + | More detail in [docs/pipeline.md](docs/pipeline.md) and |
| + | [docs/soc-integration.md](docs/soc-integration.md). |