A monorepo demonstrating service-to-service mTLS for a Virtual Power Plant: a grid dispatch orchestrator and distributed DER site agents (batteries) that mutually authenticate with SPIFFE-bound Ed25519 certificates before exchanging telemetry and dispatch commands.
Three packages sit at the repo root, each with its own pyproject.toml. The
root pyproject.toml holds shared tooling defaults (ruff, pytest) inherited by
every package.
| Package | Role | Description |
|---|---|---|
vpp-mtls/ |
shared library | mTLS primitives only: SSL context builders and SPIFFE identity helpers. No authorization policy lives here. |
dispatch-service/ |
mTLS server | Grid orchestrator. Requires a trusted client cert (authN) and enforces the site allowlist (authZ). |
der-agent/ |
mTLS client | DER site controller. Presents its identity and verifies the dispatcher's SPIFFE identity. |
Root-level demo bootstrap (not part of any service):
pki.py— issues the demo CA + workload certificates intocerts/.run_demo.sh— issues the PKI, starts the server, runs three agents.
docs/system-design.md— architecture, identity/trust model, mTLS handshake, authN-vs-authZ, and production mapping (with mermaid diagrams).
- Python 3.11+
cryptography(only needed bypki.pyto mint the demo PKI)
python -m venv .venv && source .venv/bin/activate
pip install "cryptography>=42"The services import vpp-mtls directly from the repo (no install step needed);
each entry script adds the sibling package to sys.path.
./run_demo.shThis issues the PKI, starts the mTLS dispatch service, and runs three site agents.
mTLS answers "who are you?" — not "what may you do?". Three peers make the split explicit:
| Peer | Cert issuer | Allowlisted? | Result |
|---|---|---|---|
site-42 |
Grid CA (trusted) | ✅ yes | 200 → dispatch command |
site-99 |
Grid CA (trusted) | ❌ no | 403 → authenticated but not authorized |
rogue-site |
Rogue CA (untrusted) | — | TLS handshake refused (UNKNOWN_CA) |
rogue-site never reaches application code — mTLS rejects it at the handshake.
site-99 authenticates fine but is denied by the allowlist, which lives in
dispatch-service, not in the shared
vpp-mtls library.
- Swap
pki.pyfor SPIFFE/SPIRE or cert-manager to issue and auto-rotate short-lived SVIDs — no long-lived keys on disk. - Push mTLS into a service mesh (Istio/Linkerd) sidecar so services carry no TLS logic; keep the allowlist as an explicit authorization layer (or OPA).