Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 66 additions & 28 deletions .github/workflows/scan-pentest.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# Nightly + tagged-release DAST pen-test against a live all-in-one instance.
# Nightly and tagged-release DAST pen-test against a live all-in-one instance.
# Chained after "Build Docker Images" so it scans the freshly published image.
#
# Brings up the same consul+vault+traefik+DB+AIO compose stack the terraform-
# provider tests use, discovers the served edges, ingests existing scan output
# (code-scanning alerts + SBOM) for correlation, runs open DAST tooling
# (baseline + API + template scans), optionally forwards results to a pluggable
# analysis endpoint (configured via secrets; degrades to DAST-only if unset), and

Check warning on line 8 in .github/workflows/scan-pentest.yml

View workflow job for this annotation

GitHub Actions / Lint and Validate

8:81 [line-length] line too long (81 > 80 characters)
# publishes a consolidated report. Report-only: it never fails the build.

name: "Scan: Pen Test (DAST)"

on:
schedule:
# after the nightly image republish (23:00) and nightly integration (04:00)
- cron: "0 5 * * *"
push:
tags:
- "v**"
# Chained after the images are published for a nightly or vX.Y.Z release, so the

Check warning on line 14 in .github/workflows/scan-pentest.yml

View workflow job for this annotation

GitHub Actions / Lint and Validate

14:81 [line-length] line too long (82 > 80 characters)
# freshly built all-in-one image is what gets scanned (not the prior one).
workflow_run:
workflows: ["Build Docker Images"]
types: [completed]
workflow_dispatch:
inputs:
aio_image_tag:
Expand All @@ -23,7 +23,7 @@
required: false
default: "ghcr.io/janssenproject/jans/all-in-one:0.0.0-nightly"
full_scan:
description: "Run the full active scan (slower) instead of baseline only"

Check warning on line 26 in .github/workflows/scan-pentest.yml

View workflow job for this annotation

GitHub Actions / Lint and Validate

26:81 [line-length] line too long (81 > 80 characters)
type: boolean
default: false

Expand All @@ -31,22 +31,29 @@
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# Per-release group; never cancel an in-progress scan (completion matters).
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref }}

Check warning on line 35 in .github/workflows/scan-pentest.yml

View workflow job for this annotation

GitHub Actions / Lint and Validate

35:81 [line-length] line too long (90 > 80 characters)
cancel-in-progress: false

jobs:
pentest:
name: DAST pen-test
if: github.repository == 'JanssenProject/jans'
# Manual dispatch, or a successful images build for a nightly / vX.Y.Z release.

Check warning on line 41 in .github/workflows/scan-pentest.yml

View workflow job for this annotation

GitHub Actions / Lint and Validate

41:81 [line-length] line too long (83 > 80 characters)
if: >-
github.repository == 'JanssenProject/jans' &&
(github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
(github.event.workflow_run.head_branch == 'nightly' ||
startsWith(github.event.workflow_run.head_branch, 'v'))))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
runs-on: ubuntu-latest
permissions:
contents: read
security-events: read # pentest_ingest_scans.py lists code-scanning alerts via github.token

Check warning on line 51 in .github/workflows/scan-pentest.yml

View workflow job for this annotation

GitHub Actions / Lint and Validate

51:81 [line-length] line too long (99 > 80 characters)
id-token: write # keyless cosign sign-blob for the release report bundles

Check warning on line 52 in .github/workflows/scan-pentest.yml

View workflow job for this annotation

GitHub Actions / Lint and Validate

52:81 [line-length] line too long (87 > 80 characters)
env:
JANS_FQDN: pentest-${{ github.run_id }}.jans.test
JANS_PERSISTENCE: MYSQL
AIO_IMAGE_TAG: ${{ github.event.inputs.aio_image_tag || 'ghcr.io/janssenproject/jans/all-in-one:0.0.0-nightly' }}

Check warning on line 56 in .github/workflows/scan-pentest.yml

View workflow job for this annotation

GitHub Actions / Lint and Validate

56:81 [line-length] line too long (119 > 80 characters)
FULL_SCAN: ${{ github.event.inputs.full_scan || 'false' }}
# Present only when configured; the analysis step is skipped when empty.
PENTEST_AI_ENDPOINT: ${{ secrets.PENTEST_AI_ENDPOINT }}
Expand All @@ -55,7 +62,7 @@
PENTEST_AI_API_VERSION: ${{ vars.PENTEST_AI_API_VERSION || '2023-06-01' }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0

Check warning on line 65 in .github/workflows/scan-pentest.yml

View workflow job for this annotation

GitHub Actions / Lint and Validate

65:81 [line-length] line too long (92 > 80 characters)
with:
egress-policy: audit

Expand All @@ -67,6 +74,26 @@
- name: Map FQDN to localhost (traefik publishes :443 on the runner)
run: echo "127.0.0.1 ${JANS_FQDN}" | sudo tee -a /etc/hosts > /dev/null

- name: Resolve AIO image
env:
EVENT: ${{ github.event_name }}
WF_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
INPUT_TAG: ${{ github.event.inputs.aio_image_tag }}
run: |
# dispatch input wins; a vX.Y.Z release maps to the version-tagged image

Check warning on line 83 in .github/workflows/scan-pentest.yml

View workflow job for this annotation

GitHub Actions / Lint and Validate

83:81 [line-length] line too long (82 > 80 characters)
# (build-docker-images tags all-in-one:<version>, no leading v); nightly
# and everything else use the moving nightly image.
repo=ghcr.io/janssenproject/jans/all-in-one
if [ -n "$INPUT_TAG" ]; then
TAG="$INPUT_TAG"
elif [ "$EVENT" = "workflow_run" ] && [ "${WF_HEAD_BRANCH#v}" != "$WF_HEAD_BRANCH" ]; then
TAG="${repo}:${WF_HEAD_BRANCH#v}"
else
TAG="${repo}:0.0.0-nightly"
fi
echo "AIO_IMAGE_TAG=${TAG}" >> "$GITHUB_ENV"
echo "resolved AIO_IMAGE_TAG=${TAG}"
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Bring up AIO target
run: bash automation/ci/run_aio_for_tf.sh

Expand All @@ -76,42 +103,57 @@
PENTEST_TARGETS: targets.json
run: |
python3 .github/workflows/scripts/pentest_discover_endpoints.py
jq -r '.targets[]' targets.json > targets.txt
# Scanner containers run as non-root; give them a world-writable dir for
# inputs/outputs (fixes ZAP "AccessDenied /zap/wrk/zap.json").
mkdir -p dast && chmod 777 dast
jq -r '.targets[]' targets.json > dast/targets.txt
cat targets.json

- name: Ingest existing scan results
env:
GH_TOKEN: ${{ github.token }}
PENTEST_CONTEXT: context.json
PENTEST_RELEASE_TAG: ${{ github.ref_type == 'tag' && github.ref_name || 'nightly' }}
PENTEST_RELEASE_TAG: ${{ github.event.workflow_run.head_branch || 'nightly' }}
run: python3 .github/workflows/scripts/pentest_ingest_scans.py || true

- name: DAST — baseline scan
timeout-minutes: 30
env:
JANS_URL: https://${{ env.JANS_FQDN }}
run: |
docker run --rm --network host --add-host "${JANS_FQDN}:127.0.0.1" -v "$PWD:/zap/wrk:rw" \
timeout 1500 docker run --rm --network host --add-host "${JANS_FQDN}:127.0.0.1" -v "$PWD/dast:/zap/wrk:rw" \
ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py -t "$JANS_URL" -J zap.json -I || true

- name: DAST — API scan (OpenAPI-seeded, full only)
if: env.FULL_SCAN == 'true'
timeout-minutes: 65
run: |
spec=$(jq -r '.openapi_specs[0] // empty' targets.json)
if [ -n "$spec" ]; then
docker run --rm --network host --add-host "${JANS_FQDN}:127.0.0.1" -v "$PWD:/zap/wrk:rw" \
timeout 3600 docker run --rm --network host --add-host "${JANS_FQDN}:127.0.0.1" -v "$PWD/dast:/zap/wrk:rw" \
ghcr.io/zaproxy/zaproxy:stable \
zap-api-scan.py -t "$spec" -f openapi -J zap-api.json -I || true
Comment thread
moabu marked this conversation as resolved.
Outdated
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
else
echo "no OpenAPI spec discovered; skipping API scan"
fi

- name: DAST — template scan
timeout-minutes: 300 # backstop; the shell timeout below fires first
run: |
docker run --rm --network host --add-host "${JANS_FQDN}:127.0.0.1" -v "$PWD:/data:rw" \
# Release-only: run the FULL template set to completion. The image ships no
# templates, so nuclei installs them on first run (do NOT pass -duc, which
# skips that install). interactsh stays enabled for OOB (SSRF/RCE) coverage.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
# -no-mhe keeps scanning a hardened IDP that resets many probes (default
# max-host-error would skip the host). -timeout/-retries bound per-request
# waits. Wrapped in `timeout` (< the step backstop) so a true hang still
# yields partial results and a green step.
timeout 17400 docker run --rm --network host --add-host "${JANS_FQDN}:127.0.0.1" -v "$PWD/dast:/data:rw" \
projectdiscovery/nuclei:latest \
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
-l /data/targets.txt -jsonl -o /data/nuclei.jsonl \
-severity low,medium,high,critical || true
-severity low,medium,high,critical \
-timeout 10 -retries 1 -no-mhe \
|| echo "nuclei ended (timeout or non-zero exit); using partial results"

- name: Analysis (Messages API)
if: env.PENTEST_AI_ENDPOINT != ''
Expand All @@ -122,8 +164,8 @@
--arg sys "$SYS" \
--slurpfile targets targets.json \
--slurpfile context context.json \
--arg zap "$(cat zap.json 2>/dev/null || echo '{}')" \
--arg nuclei "$(cat nuclei.jsonl 2>/dev/null || echo '')" \
--arg zap "$(cat dast/zap.json 2>/dev/null || echo '{}')" \
--arg nuclei "$(cat dast/nuclei.jsonl 2>/dev/null || echo '')" \
'{
model: $model,
max_tokens: 4096,
Expand Down Expand Up @@ -155,17 +197,13 @@
id: meta
env:
EVENT: ${{ github.event_name }}
REF_TYPE: ${{ github.ref_type }}
REF_NAME: ${{ github.ref_name }}
WF_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
# Target release the report describes and uploads to:
# tag push -> the vX.Y.Z release; scheduled nightly -> the nightly
# release; ad-hoc dispatch -> none (artifact only).
if [ "$REF_TYPE" = "tag" ]; then
RELEASE="$REF_NAME"
elif [ "$EVENT" = "schedule" ]; then
RELEASE="nightly"
# workflow_run (nightly / vX.Y.Z) -> that release; dispatch -> none (artifact only).
if [ "$EVENT" = "workflow_run" ]; then
RELEASE="$WF_HEAD_BRANCH"
Comment thread
moabu marked this conversation as resolved.
Outdated
else
RELEASE=""
fi
Expand All @@ -187,7 +225,7 @@
run: |
pip install --quiet --require-hashes -r .github/workflows/scripts/requirements-pentest.txt
python3 .github/workflows/scripts/pentest_report.py \
--zap zap.json --nuclei nuclei.jsonl \
--zap dast/zap.json --nuclei dast/nuclei.jsonl \
--context context.json --analysis analysis.json \
--meta meta.json --pdf \
--logo docs/assets/logo/janssen_project_transparent_630px_182px.png \
Expand All @@ -211,7 +249,7 @@
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1

- name: Sign and upload report to release
# Runs for tagged releases and scheduled nightly (both have a release).
# Runs for tagged releases (dispatch produces the artifact only).
if: steps.meta.outputs.release != ''
env:
# Publishing to an existing published release needs a PAT; GITHUB_TOKEN
Expand Down
3 changes: 2 additions & 1 deletion docs/contribute/ci-cd/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ flowchart TD
BP -->|workflow_run: completed| BPK[build-packages.yml]
BDI -->|workflow_run: completed| TA[test-tf-authz-action.yml]
BDI -->|workflow_run: completed| TJ[test-tf-authz-jwt.yml]
BDI -->|workflow_run: nightly/v*| PT[scan-pentest.yml]
REL[release published] -.waits on run.-> BD[build-docs.yml]
```

Expand All @@ -48,7 +49,7 @@ flowchart TD
| Mechanism | Where | Note |
|---|---|---|
| tag push (PAT) | `release-trigger`, `build-nightly` | a `GITHUB_TOKEN`-pushed tag does not trigger workflows, so a PAT (`MOAUTO_WORKFLOW_TOKEN`) pushes the tag |
| `workflow_run` | `build-docker-images`, `build-packages` listen on `Build & Publish`; tf-authz tests listen on `Build Docker Images` | loose coupling by workflow `name:`; renaming a `name:` breaks its listeners |
| `workflow_run` | `build-docker-images`, `build-packages` listen on `Build & Publish`; tf-authz tests and `scan-pentest` (nightly/`v*`) listen on `Build Docker Images` | loose coupling by workflow `name:`; renaming a `name:` breaks its listeners |
| `workflow_call` | `release-cedarling` (reusable), `slsa-github-generator` | true reusable workflows |
| `workflow_dispatch` | most build/release workflows | manual entry points |

Expand Down
15 changes: 8 additions & 7 deletions docs/contribute/ci-cd/security-scanning.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ output, and where results land, then describes the pen-test that correlates them

## Pen-test (DAST)

`scan-pentest.yml` runs nightly and on tagged releases. It is **report-only** — it
never fails the build.
`scan-pentest.yml` runs after "Build Docker Images" completes for a nightly or
tagged (`v**`) release — so it scans the freshly published all-in-one image — and
on manual dispatch. It runs the full DAST template set to completion. It is
**report-only** — it never fails the build.

Flow:

Expand All @@ -42,12 +44,11 @@ Flow:
Absent the secrets, the scan is DAST-only.
6. **Report** — `scripts/pentest_report.py` merges everything into
`pentest-report.{pdf,json,md,sarif}`. The PDF carries the Janssen logo header
and a run-metadata block (target release — `nightly` vs `vX.Y.Z` — AIO image,
and a run-metadata block (target release — `nightly` or `vX.Y.Z` — AIO image,
persistence, scan type, trigger, commit and run URL) above the severity-ranked
findings table. All formats upload as a workflow artifact; for scheduled
nightly and tagged-release runs they are also cosign-signed and attached to the
corresponding release (the `nightly` prerelease or the `vX.Y.Z` release).
Ad-hoc `workflow_dispatch` runs produce the artifact only.
findings table. All formats upload as a workflow artifact; for nightly and
tagged-release runs they are also cosign-signed and attached to the corresponding
release. Manual `workflow_dispatch` runs produce the artifact only.

### Configuration

Expand Down
2 changes: 1 addition & 1 deletion docs/contribute/ci-cd/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ One row per workflow under `.github/workflows/`. See
| `scan-sonar.yml` | push/PR, dispatch | SonarCloud quality/security scan per module. |
| `scan-scorecard.yml` | push main, weekly | OpenSSF Scorecard. |
| `scan-sbom.yml` | tag `v**`/`nightly` | enriched SBOM + compliance reports to release assets. |
| `scan-pentest.yml` | cron 05:00, tag `v**`, dispatch | DAST pen-test against the live AIO (report-only). |
| `scan-pentest.yml` | `workflow_run` (Build Docker Images) for nightly/`v**`, dispatch | full DAST pen-test against the live AIO (report-only). |

## Ops

Expand Down
Loading