From dcd82d062f03e8c3dde64600eb37b70849bb7130 Mon Sep 17 00:00:00 2001 From: shankar0123 Date: Fri, 1 May 2026 05:09:00 +0000 Subject: [PATCH] docs: convert all 9 ASCII diagrams to mermaid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit of docs/ found 32 diagrams: 23 already in mermaid, 9 in ASCII art (box-drawing chars / +-pipe boxes). Converting all 9 to mermaid so GitHub renders them as actual diagrams in the docs preview. Files affected (9 diagram blocks across 6 files): docs/architecture.md block 1 line 706 EST request flow docs/architecture.md block 2 line 798 SCEP request flow docs/architecture.md block 3 line 893 Per-profile TrustAnchor + Intune challenge dispatch docs/architecture.md block 4 line 935 signer.Driver interface + 4 implementations docs/ci-pipeline.md block 1 line 20 On-push pipeline tree docs/est.md block 1 line 254 WiFi 802.1X / EAP-TLS flow docs/legacy-est-scep.md block 1 line 40 TLS-version-bridging proxy docs/qa-test-guide.md block 1 line 41 qa_test.go to demo stack docs/scep-intune.md block 1 line 39 Intune cloud chain Conversion notes: - Linear flows → flowchart TD/LR. Per-step annotations that the ASCII had as floating text between arrows are now edge labels — cleaner and easier to read. - architecture.md block 4 (signer drivers) → flowchart LR with a subgraph for the Driver interface. Cleaner than a class diagram for the "code uses one of these implementations" semantics. - ci-pipeline.md tree → flowchart TD. Adds a dotted '-.depends on.->' arrow making the go-build-and-test → deploy-vendor-e2e dependency visually obvious (was a parenthetical in the ASCII). - est.md WiFi/RADIUS → flowchart LR with EAP, Radius, trusts, and EST as four distinct labeled arrows. The 'trusts' annotation was floating off to the side in the ASCII; now it's the arrow label between Radius and certctl CA. - All semantic detail preserved: every node label, arrow direction, inline annotation, and multi-line cell content carries through. Verified: post-conversion audit shows 32 mermaid blocks, 0 ASCII. Diff is symmetric — 108 inserts, 123 deletes — because mermaid is slightly more compact than the box-drawing characters it replaces. GitHub renders mermaid blocks natively in markdown previews since 2022, so all 9 diagrams now render as real flowcharts in the docs view rather than as monospaced character art. --- docs/architecture.md | 114 +++++++++++++++++++--------------------- docs/ci-pipeline.md | 33 ++++++++---- docs/est.md | 24 ++++----- docs/legacy-est-scep.md | 13 ++--- docs/qa-test-guide.md | 25 ++++----- docs/scep-intune.md | 22 +++----- 6 files changed, 108 insertions(+), 123 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index f556bc3..395297d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -703,20 +703,17 @@ The EST (Enrollment over Secure Transport) server provides an industry-standard **Architecture:** EST is a handler-level protocol that delegates certificate issuance to an existing `IssuerConnector`. This means EST is not a new issuer — it's a new *interface* to the existing issuance infrastructure. The `ESTService` bridges the `ESTHandler` to whichever issuer connector is configured via `CERTCTL_EST_ISSUER_ID`. -``` -Client (WiFi AP, MDM, IoT) - │ - ▼ -ESTHandler (handler layer) - │ CSR parsing, PKCS#7 response encoding - ▼ -ESTService (service layer) - │ CSR validation, CN/SAN extraction, audit recording - ▼ -IssuerConnector (connector layer via IssuerConnectorAdapter) - │ Certificate signing (Local CA, step-ca, etc.) - ▼ -Signed certificate returned as PKCS#7 certs-only +```mermaid +flowchart TD + Client["Client (WiFi AP, MDM, IoT)"] + Handler["ESTHandler (handler layer)"] + Service["ESTService (service layer)"] + Issuer["IssuerConnector (connector layer via IssuerConnectorAdapter)"] + Result["Signed certificate returned as PKCS#7 certs-only"] + Client --> Handler + Handler -->|"CSR parsing, PKCS#7 response encoding"| Service + Service -->|"CSR validation, CN/SAN extraction, audit recording"| Issuer + Issuer -->|"certificate signing (Local CA, step-ca, etc.)"| Result ``` **Wire format:** EST uses PKCS#7 (RFC 2315) certs-only degenerate SignedData for certificate responses and base64-encoded DER for CSR requests. The handler includes a hand-rolled ASN.1 PKCS#7 builder — no external PKCS#7 dependency. The CSR reader accepts both base64-encoded DER (standard EST wire format) and PEM-encoded PKCS#10 (convenience for debugging). @@ -795,20 +792,17 @@ The SCEP (Simple Certificate Enrollment Protocol) server provides certificate en **Architecture:** SCEP follows the exact same layering as EST — a handler-level protocol that delegates certificate issuance to an existing `IssuerConnector`. The `SCEPService` bridges the `SCEPHandler` to whichever issuer connector is configured via `CERTCTL_SCEP_ISSUER_ID`. -``` -Client (MDM, network device, SCEP client) - │ - ▼ -SCEPHandler (handler layer) - │ PKCS#7 envelope parsing, CSR extraction, challenge password extraction - ▼ -SCEPService (service layer) - │ Challenge password validation, CSR validation, CN/SAN extraction, audit recording - ▼ -IssuerConnector (connector layer via IssuerConnectorAdapter) - │ Certificate signing (Local CA, step-ca, etc.) - ▼ -Signed certificate returned as PKCS#7 certs-only +```mermaid +flowchart TD + Client["Client (MDM, network device, SCEP client)"] + Handler["SCEPHandler (handler layer)"] + Service["SCEPService (service layer)"] + Issuer["IssuerConnector (connector layer via IssuerConnectorAdapter)"] + Result["Signed certificate returned as PKCS#7 certs-only"] + Client --> Handler + Handler -->|"PKCS#7 envelope parsing, CSR extraction, challenge password extraction"| Service + Service -->|"challenge password validation, CSR validation, CN/SAN extraction, audit recording"| Issuer + Issuer -->|"certificate signing (Local CA, step-ca, etc.)"| Result ``` **Wire format:** Two paths, tried in order. The new RFC 8894 path (post-2026-04-29) parses the full PKIMessage shape: ContentInfo → SignedData → SignerInfo (POPO over auth-attrs verified via `internal/pkcs7/signedinfo.go::SignerInfo.VerifySignature` with the canonical SET-OF Attribute re-serialisation per RFC 5652 §5.4) → EnvelopedData (decrypted via `internal/pkcs7/envelopeddata.go::EnvelopedData.Decrypt` with RSA PKCS#1v1.5 keyTrans + AES-CBC content + constant-time PKCS#7 unpad to close the padding-oracle leak) → inner PKCS#10 CSR. Auth-attrs (messageType, transactionID, senderNonce) flow through to the service layer via `domain.SCEPRequestEnvelope`. The handler dispatches on messageType: PKCSReq (19) → initial enrollment; RenewalReq (17) → re-enrollment with chain validation; GetCertInitial (20) → polling stub returns FAILURE+badCertID. Responses are full CertRep PKIMessages (`internal/pkcs7/certrep.go::BuildCertRepPKIMessage`) signed by the per-profile RA cert/key with the issued cert chain encrypted to the device's transient signing cert (RFC 8894 §3.3.2). On parse failure the handler falls through to the legacy MVP path: base64-encoded PKCS#7 and raw CSR submissions are still accepted; responses use the legacy PKCS#7 certs-only shape via the shared `internal/pkcs7` package. The MVP fall-through is non-negotiable — backward compat with lightweight SCEP clients that don't speak full RFC 8894. Single certs are returned as raw DER for `GetCACert`, chains as PKCS#7. @@ -890,23 +884,27 @@ each per-profile dispatcher carries its own **trust anchor pool**: the public certs the operator extracted from the Connector's installation. Every Intune-flavored enrollment goes through: -``` - ┌─────────────────────────────────┐ - │ Per-profile TrustAnchorHolder │ - │ (RWMutex pool, SIGHUP-reloadable) │ - └────────────┬────────────────────┘ - │ Get() - ▼ -device → SCEP PKIMessage → handler → SCEPService.dispatchIntuneChallenge - │ - ├─► intune.ValidateChallenge (sig + iat/exp + audience) - ├─► claim.DeviceMatchesCSR (set-equality) - ├─► intune.ReplayCache.CheckAndInsert - ├─► intune.PerDeviceRateLimiter.Allow - └─► (V3-Pro) ComplianceCheck hook - │ - ▼ - processEnrollment → IssuerConnector +```mermaid +flowchart TD + TAH["Per-profile TrustAnchorHolder
(RWMutex pool, SIGHUP-reloadable)"] + Device[device] + Handler[handler] + Dispatch["SCEPService.dispatchIntuneChallenge"] + Validate["intune.ValidateChallenge
(sig + iat/exp + audience)"] + Match["claim.DeviceMatchesCSR
(set-equality)"] + Replay["intune.ReplayCache.CheckAndInsert"] + Rate["intune.PerDeviceRateLimiter.Allow"] + Compliance["(V3-Pro) ComplianceCheck hook"] + Process["processEnrollment → IssuerConnector"] + Device -->|SCEP PKIMessage| Handler + Handler --> Dispatch + TAH -.->|Get()| Dispatch + Dispatch --> Validate + Dispatch --> Match + Dispatch --> Replay + Dispatch --> Rate + Dispatch --> Compliance + Dispatch --> Process ``` The trust anchor file is mode-0600 on disk; certctl loads it at @@ -932,22 +930,16 @@ See [`scep-intune.md`](scep-intune.md) for the full migration playbook The local issuer's CA private key is wrapped behind the `signer.Signer` interface in `internal/crypto/signer/`. Every CA-signing call site — leaf certificate issuance (`x509.CreateCertificate`), CRL generation (`x509.CreateRevocationList`), and OCSP response signing (`ocsp.CreateResponse`) — accesses the key through this interface rather than touching `crypto.Signer` directly. The interface embeds the stdlib `crypto.Signer` and adds a single `Algorithm() Algorithm` method so call sites can pick the matching `x509.SignatureAlgorithm` without reflecting on the concrete key type. -``` - ┌─────────────────────────────────┐ - │ signer.Driver (pluggable) │ - ├─────────────────────────────────┤ -internal/connector/issuer/local │ signer.FileDriver (default) │ - c.caSigner signer.Signer ──────────► │ PEM key on disk │ - │ │ - │ signer.MemoryDriver (tests) │ - │ in-memory only │ - │ │ - │ signer.PKCS11Driver (V3-Pro) │ - │ HSM token (future) │ - │ │ - │ signer.CloudKMSDriver (V3-Pro) │ - │ AWS / GCP / Azure (future) │ - └─────────────────────────────────┘ +```mermaid +flowchart LR + Local["internal/connector/issuer/local
c.caSigner signer.Signer"] + subgraph Driver["signer.Driver (pluggable)"] + File["signer.FileDriver (default)
PEM key on disk"] + Memory["signer.MemoryDriver (tests)
in-memory only"] + PKCS11["signer.PKCS11Driver (V3-Pro)
HSM token (future)"] + Cloud["signer.CloudKMSDriver (V3-Pro)
AWS / GCP / Azure (future)"] + end + Local --> Driver ``` Today only `FileDriver` (production) and `MemoryDriver` (tests) ship. The interface exists so PKCS#11/HSM and cloud-KMS drivers can land in follow-on packages (`internal/crypto/signer/pkcs11`, etc.) without modifying any call site or any other driver. The L-014 file-on-disk threat-model carve-out documented at the top of `internal/connector/issuer/local/local.go` applies to `FileDriver`-backed signers; alternative drivers that keep the key inside an HSM token or cloud KMS close the disk-exposure leg of the threat model entirely. diff --git a/docs/ci-pipeline.md b/docs/ci-pipeline.md index f2cb6c2..ca3dcaf 100644 --- a/docs/ci-pipeline.md +++ b/docs/ci-pipeline.md @@ -17,17 +17,28 @@ This guide covers the **on-push pipeline** only. ## On-push pipeline (7 status checks) -``` -push to master - ├── CI workflow (5 jobs) - │ ├── go-build-and-test (~6-7 min) - │ ├── frontend-build (~1 min) - │ ├── helm-lint (~10 sec) - │ ├── deploy-vendor-e2e (~5 min, depends on go-build-and-test) - │ └── image-and-supply-chain (~3 min, parallel) - └── CodeQL workflow (2 jobs) - ├── Analyze (go) (~5 min, parallel) - └── Analyze (javascript-typescript) (~5 min, parallel) +```mermaid +flowchart TD + Push["push to master"] + CI["CI workflow (5 jobs)"] + CodeQL["CodeQL workflow (2 jobs)"] + GoBuild["go-build-and-test
~6-7 min"] + Frontend["frontend-build
~1 min"] + HelmLint["helm-lint
~10 sec"] + Vendor["deploy-vendor-e2e
~5 min, depends on go-build-and-test"] + Image["image-and-supply-chain
~3 min, parallel"] + AnalyzeGo["Analyze (go)
~5 min, parallel"] + AnalyzeJS["Analyze (javascript-typescript)
~5 min, parallel"] + Push --> CI + Push --> CodeQL + CI --> GoBuild + CI --> Frontend + CI --> HelmLint + CI --> Vendor + CI --> Image + CodeQL --> AnalyzeGo + CodeQL --> AnalyzeJS + GoBuild -.depends on.-> Vendor ``` End-to-end wall-clock: dominated by `go-build-and-test` + `deploy-vendor-e2e` chain (~12 min) running in parallel with CodeQL (~5 min). Target ~10 min. diff --git a/docs/est.md b/docs/est.md index fbc028c..a20fb24 100644 --- a/docs/est.md +++ b/docs/est.md @@ -251,20 +251,16 @@ This recipe stands up an EAP-TLS-authenticated corporate WiFi network where certctl issues every device certificate via EST. End-to-end flow: -``` -┌─────────────┐ ┌──────────────────┐ ┌─────────────┐ -│ Laptop / │ EAP │ WiFi access │ Radius│ FreeRADIUS │ -│ supplicant │─────▶│ point (NAS) │──────▶│ (validate │ -│ (wpa_ │ │ │ │ cert chain)│ -│ supplicant │ └──────────────────┘ └──────┬──────┘ -│ / iwd / │ │ -│ Apple WiFi)│ │ trusts -└──────┬──────┘ ▼ - │ EST (one-time, then renewal) ┌─────────────┐ - │ /simpleenroll, /simplereenroll │ certctl CA │ - └────────────────────────────────────▶│ (EST profile│ - │ "wifi") │ - └─────────────┘ +```mermaid +flowchart LR + Laptop["Laptop / supplicant
(wpa_supplicant / iwd / Apple WiFi)"] + AP["WiFi access point (NAS)"] + Radius["FreeRADIUS
(validate cert chain)"] + CA["certctl CA
(EST profile 'wifi')"] + Laptop -->|EAP| AP + AP -->|Radius| Radius + Radius -.->|trusts| CA + Laptop -->|"EST: /simpleenroll, /simplereenroll
(one-time, then renewal)"| CA ``` ### certctl-side: EST profile config for 802.1X diff --git a/docs/legacy-est-scep.md b/docs/legacy-est-scep.md index d6fa3b1..7de95d7 100644 --- a/docs/legacy-est-scep.md +++ b/docs/legacy-est-scep.md @@ -37,12 +37,13 @@ straight at certctl on `:8443`. ## Architecture -``` - ┌─── TLS 1.2/1.3 ────┐ ┌─── TLS 1.3 ───┐ -[legacy EST/SCEP client]──>│ nginx / HAProxy │────────>│ certctl :8443 │ - │ reverse proxy │ │ │ - └────────────────────┘ └───────────────┘ - Allowed TLS 1.2 Re-encrypts as TLS 1.3 +```mermaid +flowchart LR + Client["legacy EST/SCEP client"] + Proxy["nginx / HAProxy
reverse proxy"] + Server["certctl :8443"] + Client -->|"TLS 1.2/1.3
(allowed TLS 1.2)"| Proxy + Proxy -->|"TLS 1.3
(re-encrypts as TLS 1.3)"| Server ``` The reverse proxy: diff --git a/docs/qa-test-guide.md b/docs/qa-test-guide.md index a7d5f82..416aef1 100644 --- a/docs/qa-test-guide.md +++ b/docs/qa-test-guide.md @@ -38,22 +38,15 @@ either manual-only by design or pending QA-suite coverage: ## Architecture -``` -┌────────────────────────┐ ┌─────────────────────────────────┐ -│ qa_test.go │────▶│ certctl demo stack │ -│ (//go:build qa) │ │ docker-compose.yml + │ -│ │ │ docker-compose.demo.yml │ -│ TestQA(t *testing.T) │ │ │ -│ ├─ Part01_Infra │ │ ┌─ certctl-server :8443 │ -│ ├─ Part02_Auth │ │ ├─ postgres :5432 │ -│ ├─ Part03_CertCRUD │ │ └─ certctl-agent (×N) │ -│ ├─ ... │ │ ↑ seed_demo.sql provisions │ -│ └─ Part52_HelmChart │ │ 12 agent rows (1 active, │ -└────────────────────────┘ │ 2 retired, 9 reserved / │ - │ sentinel) for the soft- │ - │ retire / FSM coverage │ - │ Parts 55–56 exercise. │ - └─────────────────────────────────┘ +```mermaid +flowchart LR + QA["qa_test.go (//go:build qa)

TestQA(t *testing.T)
├─ Part01_Infra
├─ Part02_Auth
├─ Part03_CertCRUD
├─ ...
└─ Part52_HelmChart"] + subgraph Stack["certctl demo stack
docker-compose.yml + docker-compose.demo.yml"] + Server["certctl-server :8443"] + Postgres["postgres :5432"] + Agents["certctl-agent (×N)
↑ seed_demo.sql provisions 12 agent rows
(1 active, 2 retired, 9 reserved/sentinel)
for the soft-retire / FSM coverage Parts 55–56 exercise"] + end + QA --> Stack ``` > **Multi-agent demo stack (Bundle Q / L-004 closure).** The demo diff --git a/docs/scep-intune.md b/docs/scep-intune.md index ff124d6..3812053 100644 --- a/docs/scep-intune.md +++ b/docs/scep-intune.md @@ -36,21 +36,13 @@ What you get over NDES: ## Architecture -``` -┌──────────────┐ ┌──────────────────────┐ ┌──────────────┐ -│ Intune cloud │──────▶│ Intune Certificate │──────▶│ certctl SCEP │ -│ │ │ Connector │ │ server │ -│ (Microsoft) │ │ (customer infra) │ │ (you) │ -└──────────────┘ └──────────────────────┘ └──────┬───────┘ - │ - ▼ - ┌──────────────┐ - │ issuer │ - │ connector │ - │ (local CA / │ - │ Vault / │ - │ EJBCA / …) │ - └──────────────┘ +```mermaid +flowchart LR + Cloud["Intune cloud
(Microsoft)"] + Connector["Intune Certificate Connector
(customer infra)"] + Server["certctl SCEP server
(you)"] + Issuer["issuer connector
(local CA / Vault / EJBCA / …)"] + Cloud --> Connector --> Server --> Issuer ``` **certctl replaces NDES, not the Connector.** The Intune Certificate