## https://sploitus.com/exploit?id=AFB1D80C-48C2-5C6A-8F2E-2512DDEB2E4D
# GRC-OSCAL โ continuous compliance, demonstrated
A working proof of concept for **continuous, auditable compliance automation**.
It takes a real regulatory control all the way from a machine-readable catalog
through to a boardroom-ready statement โ fully automated, fully traceable,
100% open source, on a single VM or a single `docker compose up`.
---
## Why this matters
Traditional GRC runs on Word documents, screenshots and quarterly panic
cycles. This demo shows a different operating model:
- **Controls as code.** The control is written once, in OSCAL โ NIST's
machine-readable grammar for risk and compliance. No ambiguity between what
the policy says, what the auditor reads, and what the machine checks.
- **Evidence on demand.** A script pulls live configuration straight from the
tenant in seconds. No ticket queue, no screenshot, no stale artefact.
- **Traceable by construction.** Every run produces a valid OSCAL Assessment
Result. From regulator clause to executive statement every step is an
auditable artefact with a UUID and a timestamp.
- **Boardroom-friendly.** The dashboard speaks plain language, but every tile
on it is backed by the underlying OSCAL trace. Technical and non-technical
stakeholders look at the same screen.
- **Always on.** The pipeline runs on demand, on a schedule, or on every
commit to your infrastructure โ audit becomes a query, not a project.
Built around **one** sample control so the value is legible without drowning
in scope. The same pattern scales to a full control library.
---
## What you see
One ISO 27001 control โ **Annex A 8.5 ยท Secure authentication** โ narrowed
to a concrete objective:
> **Guest users accessing our resources must be protected by MFA.**
The demo pulls Conditional Access policies from Microsoft Entra via Microsoft
Graph, evaluates them against the control's criteria, writes the result as a
valid OSCAL Assessment Results document, stores it in Postgres with full
history, and renders the outcome in a fixed-viewport dashboard where every
stage of the pipeline is visible.
The **OSCAL chain** the demo produces and validates end-to-end:
```
catalog โ profile โ SSP โ assessment plan โ assessment results
the the the how the what happened,
control controls system control is why, and
library we care we are evaluated linked to
about assessing every policy
```
Three scenarios ship in the box:
| Scenario | Evidence source | Expected outcome |
|-----------------------|------------------------|-------------------------------------------------|
| Compliant (mock) | bundled fixture | pass ยท low risk |
| Non-compliant (mock) | bundled fixture | fail ยท high risk ยท points at the disabled policy |
| Live tenant | Microsoft Graph | whatever your real Entra configuration says |
---
## Quick start
Two supported paths. **Use Docker if you just want to see it work.** The native
path is there if you want to iterate on the code.
### 1. Recommended โ Docker only
Requirements: Docker Engine with the Compose v2 plugin. Nothing else.
```bash
git clone https://github.com/yopazerbot/grc-demo-poc-oscal.git
cd grc-demo-poc-oscal
cp .env.example .env # edit POSTGRES_PASSWORD (required)
docker compose --profile full up -d --build # builds the API image, starts Postgres + API
```
The `--build` flag ensures Compose builds the image locally instead of
trying to pull `grc-oscal-api` from a registry (it isn't published). The
`Makefile` target `make compose-up` already does this; only include
`--build` when running `docker compose` directly.
Open [http://127.0.0.1:8000](http://127.0.0.1:8000).
Seed one compliant and one non-compliant run so the dashboard has history:
```bash
docker compose exec api bash scripts/run_pipeline.sh --mock-pass
docker compose exec api bash scripts/run_pipeline.sh --mock-fail
```
Stop:
```bash
docker compose --profile full down
```
### 2. Native โ install on a Linux VM
Requirements: Ubuntu 24.04 (or similar) with sudo access. Convenient when you
want hot-reload while editing Python, React, or OSCAL JSON.
```bash
sudo mkdir -p /opt/grc-lab && sudo chown "$USER:$USER" /opt/grc-lab
git clone https://github.com/yopazerbot/grc-demo-poc-oscal.git /opt/grc-lab/repo
cd /opt/grc-lab/repo
sudo bash scripts/bootstrap.sh # installs Docker, Python venv, oscal-cli
cp .env.example .env # edit POSTGRES_PASSWORD
make demo # builds SPA, seeds pass + fail, starts the UI
```
Open [http://127.0.0.1:8000](http://127.0.0.1:8000). Run `make` with no target
for the full command menu.
---
## Configuration
Every secret and host-specific value lives in `.env` (gitignored). The only
required value is `POSTGRES_PASSWORD` โ everything else has a sensible default.
### Optional โ live Microsoft Graph mode
To run the pipeline against a real Entra tenant instead of the bundled
fixtures, you need an app registration with **`Policy.Read.All`** (application
permission) and admin consent:
1. **Entra ID โ App registrations โ New registration** (any name).
2. Note the **Application (client) ID** and the **Directory (tenant) ID**.
3. **Certificates & secrets โ New client secret** โ copy the *value*.
4. **API permissions โ Microsoft Graph โ Application permissions โ
`Policy.Read.All` โ Grant admin consent**.
You can supply those three values **either way**:
- **Durable** โ put them in `.env` (persists across restarts):
```
AZURE_TENANT_ID=...
AZURE_CLIENT_ID=...
AZURE_CLIENT_SECRET=...
```
- **In-session** โ click the **โ gear icon** in the dashboard header to open
the **Settings drawer**, paste them in, hit *Save*. Values live only in the
API process memory for the current session and are gone on restart.
Useful during a live demo or when you don't want credentials on disk yet.
The secret is never echoed back by the API; it can only be overwritten or
cleared.
The header pill flips from **`mocks only`** to **`live ready`** once all three
Azure keys are set (from either source). `POSTGRES_*` is intentionally **not**
editable at runtime โ changing DB creds mid-flight would desync the API from
the database.
No tenant? Skip this section. The `mock-pass` and `mock-fail` scenarios are
deterministic and travel in the repo; the evaluator takes the identical code
path for live and mock evidence.
---
## Repository layout
```
grc-demo-poc-oscal/
โโโ catalog/ OSCAL catalog (one ISO 27001 control)
โโโ profile/ OSCAL profile (selects a-8-5 from the catalog)
โโโ ssp/ System Security Plan (Demo Intranet + Entra ID)
โโโ assessment/ assessment plan + generated results (gitignored)
โโโ evidence/
โ โโโ mock/ bundled compliant + non-compliant fixtures
โ โโโ runs/ live evidence captures (gitignored)
โโโ scripts/ collect, evaluate, generate, store, api, pipeline
โโโ dashboard/ React 19 + Vite 6 + Tailwind v4 SPA
โโโ docker-compose.yml postgres (default) + full-stack API (profile)
โโโ Dockerfile multi-stage build: SPA + Python + oscal-cli
โโโ Makefile one-word commands for the whole demo
```
---
## The pipeline
Four stages, wrapped by one command:
```
collect evidence โ evaluate โ generate OSCAL AR โ store in Postgres
```
Run it once:
```bash
# Docker path
docker compose exec api bash scripts/run_pipeline.sh --mock-pass
docker compose exec api bash scripts/run_pipeline.sh --mock-fail
docker compose exec api bash scripts/run_pipeline.sh --live
# Native path
make run # mock-pass
make run-fail # mock-fail
make run-live # live
```
Exit codes: `0` = control passed, `1` = control failed (control is
non-compliant โ the pipeline itself was healthy), `>1` = infrastructure
error. Re-runs are idempotent on `run_id`; the row is upserted, never
duplicated.
### OSCAL validation
Every artefact is validated strictly through
[compliance-trestle](https://github.com/IBM/compliance-trestle)'s pydantic
models. When NIST [`oscal-cli`](https://github.com/usnistgov/oscal-cli) is
available, it runs as a second opinion against the official metaschema.
```bash
make validate # native
make compose-validate # Docker
```
A failure leaves the file on disk for inspection and surfaces the first
schema violation.
---
## The dashboard
Fixed-viewport single-page app (React 19 ยท Vite 6 ยท Tailwind v4 ยท
framer-motion). Seven panels on a 4ร2 grid:
| Row | Panels |
|-----|------------------------------------------------------------------|
| 1 | Control ยท Evidence ยท Evaluation ยท OSCAL |
| 2 | Decision ยท Runtime (spans 2 cols) ยท Executive |
Highlights:
- **FlowTimeline** at the top shows the current stage of the pipeline; clickable,
with left-to-right filling connectors and a pulsing active indicator.
- **ScenarioSwitcher** flips between compliant-mock, non-compliant-mock, and
live in one click.
- **Settings drawer** (โ in the header) โ paste Azure credentials (and display
labels like `ORG_NAME` / `TENANT_LABEL`) into the UI without editing `.env`
or restarting. Values are held in the API process memory for the current
session. The header pill reports **`live ready`** as soon as all three Azure
keys are present (from the drawer or `.env`).
- **Abstracted code visibility** โ each panel carries a small 4-6 line code
snippet card with `input โ` / `output โ` chips, so the audience sees the
logic behind each stage without a firehose of source.
- **Keyboard**: `R` run ยท `โ` / `โ` step through stages ยท `0` reset ยท `T` theme.
### API routes
The SPA is driven by a small FastAPI backend, bound to `127.0.0.1` only.
| Route | Purpose |
|-------------------------------|------------------------------------------------------------------------------|
| `GET /api/health` | liveness + DB reachability |
| `GET /api/artifacts` | compact metadata for every OSCAL artefact in the chain |
| `GET /api/runs?limit=N` | recent rows from `assessment_runs` |
| `GET /api/runs/latest` | latest row |
| `GET /api/runs/{run_id}` | full row with raw evidence + OSCAL Assessment Results |
| `POST /api/runs` | trigger the full pipeline (body: `{ "mode": "mock-pass\|mock-fail\|live" }`) |
| `GET /api/settings` | per-key status of runtime-adjustable env vars (no secret values echoed) |
| `PUT /api/settings` | set / clear runtime env overrides (body: `{ "values": { KEY: "value", ... } }`) |
| `DELETE /api/settings` | wipe every runtime env override |
---
## Auto-start on boot (native path only)
Two systemd units under `scripts/systemd/` make the stack come up on reboot:
```bash
sudo bash scripts/install-services.sh # idempotent
systemctl status grc-postgres grc-api
journalctl -u grc-api -f
sudo bash scripts/uninstall-services.sh # clean removal (keeps DB + .env)
```
The installer templates the units with `$SUDO_USER` and `$GRC_LAB_DIR`, so
the same scripts work whether you installed under `/opt/grc-lab` or a custom
path. Not needed with Docker โ `docker compose`'s `restart: unless-stopped`
already handles it.
---
## Command reference
```bash
make # show the full target list
```
Frequent targets:
| Target | What it does |
|-----------------------|------------------------------------------------------|
| `make demo` | Build SPA, seed pass + fail, start the UI (native) |
| `make compose-up` | Full stack in containers |
| `make run` / `run-fail` / `run-live` | One pipeline run |
| `make validate` | Validate every OSCAL artefact |
| `make history` | Recent assessment runs from Postgres |
| `make status` | systemd + docker + recent runs at a glance |
| `make reset` | Drop DB volume + generated artefacts (destructive) |
---
## Design principles
- **Everything via `.env`.** No secret or host-specific value is committed.
- **Idempotent by default.** Every script can be re-run without worry. No
duplicate rows, no half-written artefacts.
- **Same code path for mock and live.** The evaluator does not know whether
evidence came from Microsoft Graph or a bundled fixture.
- **Portable.** Scripts resolve the Python interpreter in order: `$GRC_PY` โ
`./.venv` โ `${GRC_LAB_DIR}/.venv` โ `PATH`. OSCAL artefacts reference each
other with relative paths.
- **One codebase, two deployment shapes.** Docker-only and native installation
share the same scripts, artefacts, and dashboard.
---
## Extending the demo
Adding a second control is a few targeted edits, not a rewrite:
1. Append the control to `catalog/iso-27001-2022-subset.json`.
2. Select it in `profile/demo-profile.json`.
3. Implement it in `ssp/demo-intranet.json` (new `implemented-requirement` +
`statement`).
4. Add per-criterion checks + an evaluator wrapper in `scripts/evaluate.py`.
5. Add a scenario in `dashboard/src/data/scenarios.ts`.
The pipeline and the dashboard generalise over the set; the OSCAL chain stays
coherent because every step is strictly validated.
---
## Reference
- **OSCAL** โ [https://pages.nist.gov/OSCAL/](https://pages.nist.gov/OSCAL/)
- **compliance-trestle** โ [https://github.com/IBM/compliance-trestle](https://github.com/IBM/compliance-trestle)
- **NIST oscal-cli** โ [https://github.com/usnistgov/oscal-cli](https://github.com/usnistgov/oscal-cli)
- **Microsoft Graph โ Conditional Access** โ
[https://learn.microsoft.com/graph/api/resources/conditionalaccesspolicy](https://learn.microsoft.com/graph/api/resources/conditionalaccesspolicy)