mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 17:41:29 +00:00
docs: convert all 9 ASCII diagrams to mermaid
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.
This commit is contained in:
+53
-61
@@ -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<br/>(RWMutex pool, SIGHUP-reloadable)"]
|
||||
Device[device]
|
||||
Handler[handler]
|
||||
Dispatch["SCEPService.dispatchIntuneChallenge"]
|
||||
Validate["intune.ValidateChallenge<br/>(sig + iat/exp + audience)"]
|
||||
Match["claim.DeviceMatchesCSR<br/>(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<br/>c.caSigner signer.Signer"]
|
||||
subgraph Driver["signer.Driver (pluggable)"]
|
||||
File["signer.FileDriver (default)<br/>PEM key on disk"]
|
||||
Memory["signer.MemoryDriver (tests)<br/>in-memory only"]
|
||||
PKCS11["signer.PKCS11Driver (V3-Pro)<br/>HSM token (future)"]
|
||||
Cloud["signer.CloudKMSDriver (V3-Pro)<br/>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.
|
||||
|
||||
+22
-11
@@ -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<br/>~6-7 min"]
|
||||
Frontend["frontend-build<br/>~1 min"]
|
||||
HelmLint["helm-lint<br/>~10 sec"]
|
||||
Vendor["deploy-vendor-e2e<br/>~5 min, depends on go-build-and-test"]
|
||||
Image["image-and-supply-chain<br/>~3 min, parallel"]
|
||||
AnalyzeGo["Analyze (go)<br/>~5 min, parallel"]
|
||||
AnalyzeJS["Analyze (javascript-typescript)<br/>~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.
|
||||
|
||||
+10
-14
@@ -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<br/>(wpa_supplicant / iwd / Apple WiFi)"]
|
||||
AP["WiFi access point (NAS)"]
|
||||
Radius["FreeRADIUS<br/>(validate cert chain)"]
|
||||
CA["certctl CA<br/>(EST profile 'wifi')"]
|
||||
Laptop -->|EAP| AP
|
||||
AP -->|Radius| Radius
|
||||
Radius -.->|trusts| CA
|
||||
Laptop -->|"EST: /simpleenroll, /simplereenroll<br/>(one-time, then renewal)"| CA
|
||||
```
|
||||
|
||||
### certctl-side: EST profile config for 802.1X
|
||||
|
||||
@@ -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<br/>reverse proxy"]
|
||||
Server["certctl :8443"]
|
||||
Client -->|"TLS 1.2/1.3<br/>(allowed TLS 1.2)"| Proxy
|
||||
Proxy -->|"TLS 1.3<br/>(re-encrypts as TLS 1.3)"| Server
|
||||
```
|
||||
|
||||
The reverse proxy:
|
||||
|
||||
+9
-16
@@ -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)<br/><br/>TestQA(t *testing.T)<br/>├─ Part01_Infra<br/>├─ Part02_Auth<br/>├─ Part03_CertCRUD<br/>├─ ...<br/>└─ Part52_HelmChart"]
|
||||
subgraph Stack["certctl demo stack<br/>docker-compose.yml + docker-compose.demo.yml"]
|
||||
Server["certctl-server :8443"]
|
||||
Postgres["postgres :5432"]
|
||||
Agents["certctl-agent (×N)<br/>↑ seed_demo.sql provisions 12 agent rows<br/>(1 active, 2 retired, 9 reserved/sentinel)<br/>for the soft-retire / FSM coverage Parts 55–56 exercise"]
|
||||
end
|
||||
QA --> Stack
|
||||
```
|
||||
|
||||
> **Multi-agent demo stack (Bundle Q / L-004 closure).** The demo
|
||||
|
||||
+7
-15
@@ -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<br/>(Microsoft)"]
|
||||
Connector["Intune Certificate Connector<br/>(customer infra)"]
|
||||
Server["certctl SCEP server<br/>(you)"]
|
||||
Issuer["issuer connector<br/>(local CA / Vault / EJBCA / …)"]
|
||||
Cloud --> Connector --> Server --> Issuer
|
||||
```
|
||||
|
||||
**certctl replaces NDES, not the Connector.** The Intune Certificate
|
||||
|
||||
Reference in New Issue
Block a user