diff --git a/README.md b/README.md index 1917594..d52b15b 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,11 @@ mini-auth is two things: of the family: the vision, the catalog, the architecture, the recursive integrations, the open design questions, and the roadmap — the full architecture reference. +> **Which doc do I want?** Two doors, pick one: **[docs/TEACHING.md](docs/TEACHING.md)** to *learn* +> (concepts → labs → security, in teaching order), or **[docs/DIRECTION.md](docs/DIRECTION.md)** to +> *understand the architecture* (vision, catalog, runtime map). Everything else — `GLOSSARY.md`, +> `LEARNING.md`, per-service `README.md`s — is reference the two doors link you to when you need it. + > 🎓 **New here? Start with [docs/TEACHING.md](docs/TEACHING.md).** It's a course-style track > (concepts → hands-on labs → security) that teaches authentication & authorization using the family > as the worked example, and it routes you to the right next step in one screen. diff --git a/TODO.md b/TODO.md index e92ab2a..d8a1a1a 100644 --- a/TODO.md +++ b/TODO.md @@ -107,7 +107,7 @@ student reading the keyring can't see the layer the diagram promises. ## P3 -### 7. [ ] Reduce doc entry-point overload +### 7. [x] Reduce doc entry-point overload **Why:** A newcomer landing in `docs/` faces ~8 index-like files; `TEACHING-OUTLINE.md` (a design-history doc) sits next to the live syllabus with a near-identical name and will be opened by mistake. @@ -119,7 +119,7 @@ mistake. > for the syllabus, and fix any links that referenced it. Tighten the framing so `LEARNING.md` reads as > "once you've done the course, re-walk the source," not a competing start point. -### 8. [ ] Add a "Learning?" banner to each service README + render diagrams for editor readers +### 8. [x] Add a "Learning?" banner to each service README + render diagrams for editor readers **Why:** Per-service READMEs are reference docs with no pointer back into the curriculum, and the one visual layer (mermaid) degrades to raw source for the code-first reader working in an editor. @@ -130,7 +130,7 @@ visual layer (mermaid) degrades to raw source for the code-first reader working > pre-rendered SVG/PNG alongside each `.md`, or add one line per diagram telling editor readers how to > view it (e.g. paste into mermaid.live). -### 9. [ ] Add an mTLS / workload-identity concept-stage doc +### 9. [x] Add an mTLS / workload-identity concept-stage doc **Why:** mini-ca ships and the glossary defines CSR/PoP/SAN/EKU, but there's no concept doc on *why* mTLS / how chain-to-root validation works, and mini-ca is absent from the stage ladder (only a code-tour capstone). @@ -141,7 +141,7 @@ code-tour capstone). > with a "Now read it" box into mini-ca's `ca/` package. Add it to the stage ladder in > `docs/TEACHING.md` so the mini-ca capstone has a concept stage behind it. -### 10. [ ] Reframe or upgrade the mini-console "exercise harness" +### 10. [x] Reframe or upgrade the mini-console "exercise harness" **Why:** All reviewers flagged that the harness flows are integration smoke-tests a learner *watches*, not exercises they *do* (make a choice, get feedback). The framing oversells "exercise." @@ -152,7 +152,7 @@ not exercises they *do* (make a choice, get feedback). The framing oversells "ex > supply a grant/scope and have the harness show the resulting allow vs deny decision. If you change > behavior, keep results secret-free and run `./gradlew build`. -### 11. [ ] (Optional) Add a "Stage 0.5: the tools you'll use" primer +### 11. [x] (Optional) Add a "Stage 0.5: the tools you'll use" primer **Why:** Every lab assumes curl, env vars, background jobs, and jq/python. Lowers the on-ramp for the bottom edge of the stated audience. diff --git a/docs/LEARNING.md b/docs/LEARNING.md index 9289cc3..7b74916 100644 --- a/docs/LEARNING.md +++ b/docs/LEARNING.md @@ -5,10 +5,11 @@ each other, so reading them in directory order (or alphabetically) drops you int deep end. This is the order that builds the concepts up before they're used — it follows the family's actual dependency DAG, smallest and most-foundational first. -> **Concepts-first vs. code-first.** This is the *code-first* path — it walks the source in -> dependency order. If you'd rather build the *ideas* first (with hands-on labs and a security -> track), start at [`TEACHING.md`](TEACHING.md) and come here when you want to read the -> implementation. The two are designed to cross-link: each teaching stage points at the files below. +> **Not a competing start point.** If you haven't done the course yet, start at +> [`TEACHING.md`](TEACHING.md) — it builds the *ideas* first, with hands-on labs and a security +> track. Come here once you've done that, to re-walk the same territory as *source*, in dependency +> order instead of teaching order. The two are designed to cross-link: each teaching stage points at +> the files below. **How to use this.** At each stop, read the named files (and run the quick-start where there is one), then move on. Each stop lists the *concept you learn there* — that's the diff --git a/docs/TEACHING.md b/docs/TEACHING.md index 5b7f1e9..4e8aa36 100644 --- a/docs/TEACHING.md +++ b/docs/TEACHING.md @@ -10,6 +10,10 @@ > **First time on this machine?** Read [`SETUP.md`](SETUP.md) before your first lab — a five-minute > preflight (JDK 21, the first build needs network) plus a rescue list for the usual step-1 dead ends > (port-in-use on 8455/8466/8477…, wrong JDK, offline build, stray background servers). +> +> **New to the command line?** [`tutorials/00-tools-you-will-use.md`](tutorials/00-tools-you-will-use.md) +> is an optional primer on `curl`, exporting env vars, backgrounding/killing a server, and decoding a +> JWT with `jq` or `python3` — skip it if all of that is already muscle memory. You are standing in front of four kinds of document. Keep them straight and the repo opens up: @@ -57,6 +61,7 @@ proves it. Do them in order and each one earns the next. (This is a different or | Stage | You'll be able to… | Concept | Lab | | --- | --- | --- | --- | | **0** | Name which problem each `mini-` service solves | [`authn-vs-authz`](concepts/authn-vs-authz.md) | — | +| **0.5** *(optional)* | Use `curl`, env vars, background jobs, and `jq`/`python3` comfortably | — | [`00-tools-you-will-use`](tutorials/00-tools-you-will-use.md) | | **1** | See a decision as a pure function; explain deny-by-default | [`authorization-model`](concepts/authorization-model.md) | [`01-resolve-a-principal`](tutorials/01-resolve-a-principal.md) | | **2** | Say what a signed token *is* and verify one offline, by hand | [`what-a-token-is`](concepts/what-a-token-is.md) | [`02-build-and-verify-a-token-by-hand`](tutorials/02-build-and-verify-a-token-by-hand.md) ← **keystone** | | **3** | Trace a machine identity end to end | [`oauth-and-oidc-flows`](concepts/oauth-and-oidc-flows.md) | [`03-machine-identity-end-to-end`](tutorials/03-machine-identity-end-to-end.md) | @@ -64,6 +69,7 @@ proves it. Do them in order and each one earns the next. (This is a different or | **4** | Run a human SSO login: PKCE, passkeys, sessions, refresh | [`sessions-vs-tokens`](concepts/sessions-vs-tokens.md) | [`04-human-sso-end-to-end`](tutorials/04-human-sso-end-to-end.md) | | **5** | Gate a no-auth app via forward-auth | *(reuse stage 4)* | [`05-gate-a-no-auth-app`](tutorials/05-gate-a-no-auth-app.md) | | **6** | Explain how the family protects its own keys | [`envelope-encryption-and-kms`](concepts/envelope-encryption-and-kms.md) | [`06-protect-the-signing-keys`](tutorials/06-protect-the-signing-keys.md) | +| **7** | Explain mTLS and chain-to-root validation for workload identity | [`certificates-and-mtls`](concepts/certificates-and-mtls.md) | *(no lab yet — a code-tour capstone over `mini-ca`)* | | **∥** | Carry the family's security reflexes anywhere | [`secure-design-invariants`](concepts/secure-design-invariants.md) | the [`security/`](security/) threat labs | **The keystone is stage 2.** Building and verifying a token signature by hand — then tampering one diff --git a/docs/concepts/certificates-and-mtls.md b/docs/concepts/certificates-and-mtls.md new file mode 100644 index 0000000..e96f818 --- /dev/null +++ b/docs/concepts/certificates-and-mtls.md @@ -0,0 +1,132 @@ +# Certificates and mTLS — proving identity between *workloads* + +> **Concept doc (explanation).** The capstone concept behind **mini-ca**. New terms link to +> [`GLOSSARY.md`](../GLOSSARY.md#certificates-mini-ca); the rationale for shipping a small internal +> CA instead of a full PKI lives in [`DIRECTION.md`](../DIRECTION.md). There is no dedicated lab yet +> — this doc ends with a "Now read it" code tour of `services/mini-ca`'s `ca/` package instead. +> Read [`what-a-token-is.md`](what-a-token-is.md) first: a certificate is a *different* proof +> primitive for a *different* actor (a workload, not a person or a caller with a bearer token), but +> it reuses the same signature-verification idea. + +If you take one idea from this doc, take this: + +> **A certificate is a public key with a signed statement attached: "the CA that everyone already +> trusts vouches that this public key belongs to this name." mTLS is TLS where *both* sides present +> one of these and check the other's, so a *workload* — not a person typing a password, not a bearer +> token in a header — proves who it is using a private key it never has to send anywhere.** + +--- + +## The problem certificates solve + +Tokens (stage 2) and passkeys (stage 3.5) both answer "who is the **caller**" — a request arrives, +carrying a bearer token or having just finished a login ceremony. But **service-to-service** +traffic in a homelab has no caller in that sense: `mini-gateway` calling `mini-oidc`'s JWKS +endpoint, or one internal service calling another, is a *workload* talking to a *workload*. Neither +side is a human who can complete a passkey ceremony, and a long-lived shared secret baked into +config is exactly the kind of static credential the family's invariants try to avoid. + +**mTLS (mutual TLS)** answers this: TLS's normal server-authentication handshake, run in *both* +directions. Each side holds a private key and presents a certificate; each side verifies the +other's certificate against a **trust anchor** both were configured to trust. Nothing secret ever +crosses the wire — only signatures and public keys, exactly like a token's JWS or a passkey's +assertion. + +--- + +## What's actually inside a certificate + +An [X.509](../GLOSSARY.md#certificates-mini-ca) certificate is a small signed document: + +- **A public key** — the thing being vouched for. +- **A subject** — whose key this is (a distinguished name, plus modern TLS's real check, the + [SAN](../GLOSSARY.md#certificates-mini-ca) list of DNS names/IPs the cert is valid for). +- **A validity window** (`notBefore`/`notAfter`) and a **serial number** (how revocation refers to + it). +- **Extensions** that constrain what the key may be used for — `BasicConstraints` (is this a CA?), + `KeyUsage`, and [EKU](../GLOSSARY.md#certificates-mini-ca) (`clientAuth`+`serverAuth` for an mTLS + leaf). +- **A signature** over all of the above, made by the issuer's private key. + +Verifying a certificate is the same offline-signature check you already know from stage 2 — "does +this signature, over these bytes, verify against this public key" — pointed at *a name* instead of +*a claim set*. + +--- + +## The CSR → issue → verify flow + +A workload never hands its private key to anyone, including the CA. Instead: + +1. **The workload generates its own key pair** and keeps the private key. It builds a + [**CSR**](../GLOSSARY.md#certificates-mini-ca) (Certificate Signing Request, PKCS#10): "here is my + public key, here is the subject/SANs I want, and here is that whole request **self-signed** by my + own private key." +2. **The CA verifies [proof-of-possession](../GLOSSARY.md#certificates-mini-ca) (PoP)** — checking + the CSR's self-signature against the public key *inside* the CSR. This proves the requester + really holds the private key matching the public key it's asking to be certified — without the + CA ever seeing that private key. +3. **The CA issues a leaf**: same subject and public key, plus the mTLS extension set, a fresh + random serial, and a **short TTL** — then signs the whole thing with the CA's own private key. +4. **A verifier checks the chain to root**: does the leaf's signature verify against the CA's public + key, is the CA the verifier's configured trust anchor, is the leaf still inside its validity + window, and (for mTLS) does the presented SAN match who the verifier expected to be talking to? + Any failure means "don't trust this peer" — there is no partial credit. + +> **Why proof-of-possession matters.** Without step 2, anyone could submit a CSR containing *your* +> public key and a subject naming *your* service — and get a valid-looking certificate for an +> identity they don't hold the private key for. PoP is what ties "I'm requesting this identity" to +> "I can prove I hold the matching secret," the same shape as a passkey's challenge-response. + +--- + +## Why leaf TTLs are short + +A CA can't "un-sign" a certificate — once issued, the signature is valid math forever. Two +mechanisms claw back a compromised or retired certificate, and mini-ca leans on the cheaper one: + +- **Revocation** — the CA publishes a list of revoked serials; every verifier must fetch and check + it. This requires a live, reachable, freshness-checked revocation channel (a CRL or OCSP + responder) — real machinery a small homelab CA would rather not build. +- **Short TTLs** — if a leaf expires in hours or days, a leaked key is only useful for that narrow + window, and rotation (get a new leaf before the old one expires) becomes routine rather than an + incident response. **This is the primary control mini-ca relies on.** + +> **Honest seam.** mini-ca still publishes a revocation list (`GET /revocations`) as the kill switch +> for the case a short TTL isn't fast enough — but it's a plain JSON list, not a signed DER CRL or an +> OCSP responder. See `services/mini-ca/README.md`'s non-goals: this is a deliberate, documented +> educational simplification, not an oversight. + +--- + +## Where mini-ca fits the family + +mini-ca doesn't reinvent crypto to do any of this — BouncyCastle builds and verifies the PKIX +structures, exactly the "vetted library" reflex the rest of the family follows. And mini-ca's own +CA private key is protected the same way the token plane's signing keys are: it's persisted as a +one-record `SigningKeys` document and, with `--kms-*` configured, **envelope-wrapped under +mini-kms** — the same recursive integration `envelope-encryption-and-kms.md` describes, reused +verbatim rather than rebuilt. + +--- + +## Now read it + +- **The issuing engine:** `services/mini-ca` → `ca/CertificateAuthority#issueFromCsr` — verifies + proof-of-possession (`csr.isSignatureValid(...)`), builds the extension set (`BasicConstraints`, + `KeyUsage`, `ExtendedKeyUsage` client+server auth, authority/subject key identifiers, SANs), and + signs with a fresh random serial and a short TTL. One `CaIssuanceException` for any malformed or + invalid CSR — no oracle for which check failed. +- **The root and key material:** `ca/CaKeys` — EC P-256 keygen, the self-signed root, and + `randomSerial()`. +- **Hand-rolled PEM:** `ca/Pem` — the encode/decode this family writes itself rather than pull in a + dependency just for base64-with-line-wraps. +- **Bootstrap and lifecycle:** `service/CaService` — mints a fresh CA on first run (else loads it), + issues/renews/revokes, and maintains the issuance log + revocation list; the CA private key flows + through the same `DocumentStore` SPI `KmsSigningKeyStore` implements. + +There's no hands-on lab for this stage yet — read the code above with a CSR you generate yourself +(`openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -keyout leaf.key -out +leaf.csr`), `POST /issue` it to mini-ca (admin-guarded; see `services/mini-ca/README.md`), and decode +what comes back (`openssl x509 -in leaf.pem -noout -text`) to see the extension set this doc +described land in a real certificate. diff --git a/docs/concepts/envelope-encryption-and-kms.md b/docs/concepts/envelope-encryption-and-kms.md index 069af8a..8ec92be 100644 --- a/docs/concepts/envelope-encryption-and-kms.md +++ b/docs/concepts/envelope-encryption-and-kms.md @@ -115,4 +115,6 @@ the family eating its own dog food — and the capstone that ties envelope encry the `kms1:` envelope, `rewrap` for rotation). Lab: [`06-protect-the-signing-keys.md`](../tutorials/06-protect-the-signing-keys.md) — start mini-kms, -run an issuer with `--kms-*`, and confirm only ciphertext touches disk. +run an issuer with `--kms-*`, and confirm only ciphertext touches disk. Then continue to stage 7, +[`certificates-and-mtls.md`](certificates-and-mtls.md) — the CA private key you just saw wrapped is +itself a `SigningKeys` document, reusing this exact integration. diff --git a/docs/design/mini-console.md b/docs/design/mini-console.md index fbdeeb1..3be6ba8 100644 --- a/docs/design/mini-console.md +++ b/docs/design/mini-console.md @@ -292,6 +292,12 @@ All POST routes carry a hidden `_csrf` field; the handler rejects a missing/mism ## 5. Exercise / test harness (test mode) +**Framing, honestly.** Most of these flows are integration smoke tests a learner *watches* run — +click Run, observe pass/fail/skip — not exercises where a learner makes a choice. The one exception +is `GatewayVerifyFlow`: the operator supplies the path, an access token, and (optionally) a +scope-excluded path, so it's a genuine predict-then-observe device (see the README's harness +section). Don't oversell the rest as "exercises" in the pedagogical sense. + The harness is an **I/O-free engine** (`harness/`) given the wired clients; each flow returns an `ExerciseResult(name, Status, steps, redactedDetail)`. **"Pass" is defined per flow as a cryptographic/state assertion verified offline**, not just an HTTP 200. Results render as green/red rows with per-step detail; **no step ever records a secret** — tokens/secrets are replaced with `«redacted»` and only non-secret facts (kid, sub, exp, serial, status code) are shown. | Flow | Drives | "Pass" = (verified offline) | diff --git a/docs/diagrams/README.md b/docs/diagrams/README.md index fc14fff..64fef68 100644 --- a/docs/diagrams/README.md +++ b/docs/diagrams/README.md @@ -4,8 +4,10 @@ > depends on whom). These are the **sequence** diagrams — *what happens, in what order*, for the > flows the course teaches. The concept and tutorial docs embed them. -Each diagram is mermaid (GitHub renders it inline). They show the **wired** runtime paths; where a -step is a designed-but-not-wired seam, the diagram says so and links +Each diagram is mermaid (GitHub renders it inline). **Reading source in an editor instead?** Every +diagram file has a one-line note above its code block pointing you to + — paste the block in to render it. They show the **wired** runtime paths; +where a step is a designed-but-not-wired seam, the diagram says so and links [`concepts/honest-seams.md`](../concepts/honest-seams.md). | Diagram | Flow | Used by | diff --git a/docs/diagrams/auth-code-pkce.md b/docs/diagrams/auth-code-pkce.md index 91a311a..3534d50 100644 --- a/docs/diagrams/auth-code-pkce.md +++ b/docs/diagrams/auth-code-pkce.md @@ -6,6 +6,8 @@ > [`sessions-vs-tokens.md`](../concepts/sessions-vs-tokens.md). Lab: > [`04`](../tutorials/04-human-sso-end-to-end.md). +> **Reading this in an editor, not GitHub?** Paste the block below into to render it. + ```mermaid sequenceDiagram autonumber diff --git a/docs/diagrams/client-credentials.md b/docs/diagrams/client-credentials.md index 9de8d7e..f1ad913 100644 --- a/docs/diagrams/client-credentials.md +++ b/docs/diagrams/client-credentials.md @@ -5,6 +5,8 @@ > [`what-a-token-is.md`](../concepts/what-a-token-is.md) + [`oauth-and-oidc-flows.md`](../concepts/oauth-and-oidc-flows.md). > Lab: [`02`](../tutorials/02-build-and-verify-a-token-by-hand.md) / [`03`](../tutorials/03-machine-identity-end-to-end.md). +> **Reading this in an editor, not GitHub?** Paste the block below into to render it. + ```mermaid sequenceDiagram autonumber diff --git a/docs/diagrams/forward-auth.md b/docs/diagrams/forward-auth.md index 1d11e68..0f1a85b 100644 --- a/docs/diagrams/forward-auth.md +++ b/docs/diagrams/forward-auth.md @@ -5,6 +5,8 @@ > [`authn-vs-authz.md`](../concepts/authn-vs-authz.md) (authZ at the edge). Lab: > [`05`](../tutorials/05-gate-a-no-auth-app.md). +> **Reading this in an editor, not GitHub?** Paste the block below into to render it. + ```mermaid sequenceDiagram autonumber diff --git a/docs/diagrams/kms-wrap-on-save.md b/docs/diagrams/kms-wrap-on-save.md index e4ab5d0..fc888fa 100644 --- a/docs/diagrams/kms-wrap-on-save.md +++ b/docs/diagrams/kms-wrap-on-save.md @@ -5,6 +5,8 @@ > [`envelope-encryption-and-kms.md`](../concepts/envelope-encryption-and-kms.md). Lab: > [`06`](../tutorials/06-protect-the-signing-keys.md). +> **Reading this in an editor, not GitHub?** Paste the block below into to render it. + ```mermaid sequenceDiagram autonumber diff --git a/docs/TEACHING-OUTLINE.md b/docs/meta/TEACHING-OUTLINE.md similarity index 94% rename from docs/TEACHING-OUTLINE.md rename to docs/meta/TEACHING-OUTLINE.md index d785351..eff1f7f 100644 --- a/docs/TEACHING-OUTLINE.md +++ b/docs/meta/TEACHING-OUTLINE.md @@ -3,10 +3,12 @@ > **What this file is.** The original *blueprint* for the course-style documentation set under > `docs/` that teaches **how authentication and authorization work in practice**, using the `mini-*` > services as the worked example. It is kept as a design/history record of how the set was planned. +> It lives under `docs/meta/` (contributor/design history), not `docs/`, so it can't be mistaken for +> the live syllabus. > > **Status:** ✅ realized. The set this file planned now exists — see the concepts, tutorials, howtos, > diagrams, and security track under `docs/`. **If you're here to learn, don't start here:** the live -> syllabus is [`TEACHING.md`](TEACHING.md). This outline is for contributors curious about the design. +> syllabus is [`TEACHING.md`](../TEACHING.md). This outline is for contributors curious about the design. --- @@ -17,9 +19,9 @@ The repo already ships three excellent **orientation** docs and a reference laye | Existing doc | What it is | Role in the new set | | --- | --- | --- | -| [`DIRECTION.md`](DIRECTION.md) | The **map** — vision, component catalog, runtime relationships, roadmap, the "wired vs. designed" note. | The authoritative *why/architecture*. Concept docs **hand off** to it; never restate it. | -| [`GLOSSARY.md`](GLOSSARY.md) | The **dictionary** — every term, defined once. | New docs **link** terms here on first use and never redefine. Missing a term? Add it *here*. | -| [`LEARNING.md`](LEARNING.md) | The **reading order** — the source files in dependency-DAG order. | Complementary: tutorials teach the *concepts in teaching order*, then LEARNING.md walks the *code*. Cross-link both ways. | +| [`DIRECTION.md`](../DIRECTION.md) | The **map** — vision, component catalog, runtime relationships, roadmap, the "wired vs. designed" note. | The authoritative *why/architecture*. Concept docs **hand off** to it; never restate it. | +| [`GLOSSARY.md`](../GLOSSARY.md) | The **dictionary** — every term, defined once. | New docs **link** terms here on first use and never redefine. Missing a term? Add it *here*. | +| [`LEARNING.md`](../LEARNING.md) | The **reading order** — the source files in dependency-DAG order. | Complementary: tutorials teach the *concepts in teaching order*, then LEARNING.md walks the *code*. Cross-link both ways. | | Service `README.md` + OpenAPI | **Reference** — authoritative endpoints, flags, contracts. | Labs/how-tos link out for exact syntax (which drifts); never copy command tables. | | `services/*/docs/security/*.md` | **Threat-model findings** — issue → fix → why → tests. | Pre-built case studies; the new security track *frames and sequences* them. | @@ -55,7 +57,7 @@ docs/ howto/ # HOW-TO — task-oriented, for someone who already gets it security/ # ATTACK & DEFENSE — framings over services/*/docs/security diagrams/ # shared sequence/flow diagrams (mermaid), referenced by the above - (existing) DIRECTION.md GLOSSARY.md LEARNING.md TEACHING-OUTLINE.md + (existing) DIRECTION.md GLOSSARY.md LEARNING.md meta/TEACHING-OUTLINE.md ``` `TEACHING.md` is the new entry point; the root `README.md` gains a link to it. diff --git a/docs/tutorials/00-tools-you-will-use.md b/docs/tutorials/00-tools-you-will-use.md new file mode 100644 index 0000000..1304e08 --- /dev/null +++ b/docs/tutorials/00-tools-you-will-use.md @@ -0,0 +1,96 @@ +# Stage 0.5 — the tools you'll use + +> **Optional pre-reading.** Every lab from here on assumes you're comfortable with `curl`, +> environment variables, running something in the background, and pulling a field out of a JSON +> response. If all of that is already muscle memory, skip straight to +> [`01-resolve-a-principal.md`](01-resolve-a-principal.md). If any of it isn't, five minutes here +> will save you from fighting the terminal instead of learning the auth concepts the labs are +> actually about. + +## `curl` — talking to a service by hand + +The labs never wrap requests in a script; you type the `curl` command yourself so the shape of the +HTTP call stays visible. The pattern you'll reuse constantly: + +```bash +curl -s -XPOST http://127.0.0.1:8466/admin/roles \ + -H "Authorization: Bearer $MINIDIR_ADMIN_TOKEN" \ + -H 'Content-Type: application/json' \ + -d '{"id": "reader", "grants": [{"action": "read", "resource": "doc:*"}]}' +``` + +- `-s` — silent (suppress the progress meter; you still get the response body). +- `-XPOST` — the HTTP method (`-XPOST`, `-XGET` — the default is `GET` if you omit it). +- `-H` — one header per flag; every admin call in this repo needs an `Authorization: Bearer ` + header. +- `-d` — the request body. Single-quote it so your shell doesn't try to expand the `$` or `"` + inside the JSON. + +Add `-i` to see the response status line and headers (useful when a lab asks you to check a status +code, e.g. `302` or `401`), or `-v` when something's failing and you want to see the whole exchange. + +## Exporting environment variables + +Every admin token in this repo is read from an environment variable — **never** a CLI flag, and +never logged. You'll export one per service you start: + +```bash +export MINIDIR_ADMIN_TOKEN="$(openssl rand -hex 32)" +``` + +That variable is now set for the rest of *this shell session* — every `curl` and every `server` +process you launch from this same terminal can see it. If you open a new terminal tab, it's gone; +export it again there, or put your exports in a small `.envrc`-style file you `source` at the start +of a session. Check what's set with `echo "$MINIDIR_ADMIN_TOKEN"` (careful — that prints the +secret to your scrollback). + +## Running things in the background, and stopping them + +Each lab starts one or more long-running servers. You need them running *while* you run `curl` +commands in the same terminal, which means backgrounding them: + +```bash +services/mini-directory/build/install/mini-directory/bin/mini-directory \ + --port 8466 --data-dir ~/.mini-directory & # the trailing & backgrounds it +``` + +The shell prints a job number and a process id (PID) — `[1] 12345`. Useful commands: + +- `jobs` — list what's running in the background in *this* shell. +- `fg %1` — bring job 1 to the foreground (so `Ctrl-C` can stop it). +- `kill 12345` — stop it directly by PID (`kill %1` works too, using the job number). +- `lsof -i :8466` (or `ss -ltnp | grep 8466`) — find out *what* is holding a port, when a lab's + server won't start because the port's already in use (see [`SETUP.md`](../SETUP.md)'s + troubleshooting section for the full rescue steps). + +If you'd rather not juggle background jobs, open one terminal tab per server instead and run each +one in the foreground — slower to switch between, but nothing to background or kill. + +## Reading a token: `jq` or `python` + +A JWT is three base64url segments joined by `.` — `header.payload.signature`. The labs ask you to +decode the payload and read the claims (`sub`, `exp`, `grants`, …) by hand. Two ways to do it, +depending on what you have installed: + +```bash +# jq (if you have it) — pull the token out of a JSON response and decode the payload +TOKEN=$(curl -s ... | jq -r .access_token) +echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq . + +# python3 (ships on most systems) — no jq required +python3 -c " +import base64, json, sys +payload = sys.argv[1].split('.')[1] +payload += '=' * (-len(payload) % 4) # restore the base64 padding curl/JWT libs strip +print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2)) +" "$TOKEN" +``` + +The `-r` flag to `jq` strips the surrounding quotes so you get the raw token string, not +`"eyJ..."`. The padding line in the Python version exists because base64url tokens omit the `=` +padding standard base64 expects — you'll hit this exact wrinkle again in lab 02 when you verify a +signature by hand. + +--- + +Ready. Continue to [`01-resolve-a-principal.md`](01-resolve-a-principal.md) — stage 1. diff --git a/libs/mini-policy/README.md b/libs/mini-policy/README.md index 0537d1c..2ecbe84 100644 --- a/libs/mini-policy/README.md +++ b/libs/mini-policy/README.md @@ -1,5 +1,10 @@ # mini-policy +> 🎓 **Learning?** This is stage 1 of the course — read +> [`authorization-model`](../../docs/concepts/authorization-model.md) then run lab +> [`01-resolve-a-principal`](../../docs/tutorials/01-resolve-a-principal.md). Full syllabus: +> [docs/TEACHING.md](../../docs/TEACHING.md). + The family's **authorization decision function** — one tiny, dependency-free library that answers a single question for every service: *may **this** principal perform **this** action on **this** resource?* It is the shared replacement for the per-service authorization checks the family used to diff --git a/libs/mini-token/README.md b/libs/mini-token/README.md index 8bf1dec..95ceab2 100644 --- a/libs/mini-token/README.md +++ b/libs/mini-token/README.md @@ -1,5 +1,10 @@ # mini-token +> 🎓 **Learning?** This is stage 2's keystone — read +> [`what-a-token-is`](../../docs/concepts/what-a-token-is.md) then run lab +> [`02-build-and-verify-a-token-by-hand`](../../docs/tutorials/02-build-and-verify-a-token-by-hand.md). +> Full syllabus: [docs/TEACHING.md](../../docs/TEACHING.md). + The family's shared **token plane**: the Ed25519-signed JWS/JWT machinery, the JWKS model, the signing-key lifecycle (rotation), the revocation denylist, the audit log, the published `grants` claim contract, a small persistence SPI, **and** the shared browser-SSO session store. It was diff --git a/services/mini-ca/README.md b/services/mini-ca/README.md index 429a6f5..58b22bb 100644 --- a/services/mini-ca/README.md +++ b/services/mini-ca/README.md @@ -1,5 +1,10 @@ # mini-ca +> 🎓 **Learning?** Read [`certificates-and-mtls`](../../docs/concepts/certificates-and-mtls.md) for +> why mTLS and how chain-to-root validation works, then browse the `ca/` package below (this +> capstone has no lab yet — it's a code-tour). Full syllabus: +> [docs/TEACHING.md](../../docs/TEACHING.md). + A small **internal certificate authority** for the homelab. It issues and renews **short-lived** X.509 leaf certificates — for mTLS between the minis and for homelab services — from PKCS#10 CSRs, keeps an issuance log and a revocation list, and protects its own CA private key by **wrapping it diff --git a/services/mini-console/README.md b/services/mini-console/README.md index 60a8759..759cd93 100644 --- a/services/mini-console/README.md +++ b/services/mini-console/README.md @@ -1,5 +1,9 @@ # mini-console +> 🎓 **Learning?** This ties the whole course together rather than teaching one concept — after +> finishing the stage ladder in [docs/TEACHING.md](../../docs/TEACHING.md), run the harness's +> `FullChainFlow` here to watch `identity → token → gateway verifies → resource` happen in one place. + **mini-console** is the optional unified **admin console** over the whole `mini-` family — one loopback place to browse mini-directory identities, rotate signing keys, manage mini-kms key groups, read audit/issuance logs, register OIDC clients, and **smoke-test the family end to end** — instead @@ -59,6 +63,17 @@ service the operator did not wire reads "not configured" — never a fabricated ## Exercise harness (what "pass" means) +**Read this framing honestly: most of these flows are watch-it-work integration smoke tests, not +exercises where you make a choice.** You click Run, the flow drives real requests against the wired +services, and you watch pass/fail/skip come back with the cryptographic reasoning shown per step — +valuable as a demonstration, but you aren't predicting anything or being asked to choose. + +**The one flow that is genuinely interactive is Gateway forward-auth.** You supply the path, an +access token, and (optionally) a path outside that token's scope — three inputs *you* choose — and +the harness reports the family's actual allow/deny/forbid decision for exactly the case you built. +Do the tutorials' **predict-then-run** thing here: write down what you expect (allowed? forbidden? +redirected to login?) *before* you click Run, the same discipline the labs use. + Each flow returns a result whose **steps are secret-free by contract** (tokens/secrets become `«redacted»`; only non-secret facts — a kid, sub, exp, serial, status — are shown). "Pass" is a cryptographic/state assertion verified **offline**, not just an HTTP 200. A flow honestly reports diff --git a/services/mini-console/src/main/java/com/codeheadsystems/miniconsole/pages/HarnessPages.java b/services/mini-console/src/main/java/com/codeheadsystems/miniconsole/pages/HarnessPages.java index b2cdeff..d403d3b 100644 --- a/services/mini-console/src/main/java/com/codeheadsystems/miniconsole/pages/HarnessPages.java +++ b/services/mini-console/src/main/java/com/codeheadsystems/miniconsole/pages/HarnessPages.java @@ -206,6 +206,9 @@

diff --git a/services/mini-directory/README.md b/services/mini-directory/README.md index 9fa0dea..f23d18d 100644 --- a/services/mini-directory/README.md +++ b/services/mini-directory/README.md @@ -1,5 +1,10 @@ # mini-directory +> 🎓 **Learning?** This is stage 1 of the course — read +> [`authorization-model`](../../docs/concepts/authorization-model.md) then run lab +> [`01-resolve-a-principal`](../../docs/tutorials/01-resolve-a-principal.md). Full syllabus: +> [docs/TEACHING.md](../../docs/TEACHING.md). + **mini-directory** is the single **identity source of truth** for the mini- family. It owns **humans**, **service accounts**, **groups**, and **roles**, plus the grant mappings between them — and its defining job is **resolution**: turning any stored account into a diff --git a/services/mini-gateway/README.md b/services/mini-gateway/README.md index bd5c272..89ea363 100644 --- a/services/mini-gateway/README.md +++ b/services/mini-gateway/README.md @@ -1,5 +1,10 @@ # Mini Gateway +> 🎓 **Learning?** This is stage 5 of the course — read +> [`sessions-vs-tokens`](../../docs/concepts/sessions-vs-tokens.md) then run lab +> [`05-gate-a-no-auth-app`](../../docs/tutorials/05-gate-a-no-auth-app.md). Full syllabus: +> [docs/TEACHING.md](../../docs/TEACHING.md). + **mini-gateway** is the **forward-auth endpoint** for a reverse proxy. A proxy (Traefik ForwardAuth, Caddy `forward_auth`, nginx `auth_request`) calls it *before* forwarding each request; mini-gateway validates the caller, evaluates the target route, and answers **allow / deny / log-in**. diff --git a/services/mini-idp/README.md b/services/mini-idp/README.md index 5c87cb8..688ff0e 100644 --- a/services/mini-idp/README.md +++ b/services/mini-idp/README.md @@ -1,5 +1,10 @@ # Mini IDP +> 🎓 **Learning?** This is stage 3 of the course — read +> [`oauth-and-oidc-flows`](../../docs/concepts/oauth-and-oidc-flows.md) then run lab +> [`03-machine-identity-end-to-end`](../../docs/tutorials/03-machine-identity-end-to-end.md). Full +> syllabus: [docs/TEACHING.md](../../docs/TEACHING.md). + **mini-idp** is a small, standalone, single-machine **identity provider**. It registers clients, issues short-lived [Ed25519](https://ed25519.cr.yp.to/)-signed JWT access tokens via the OAuth 2.0 **client-credentials** grant, and publishes its public signing keys as a **JWKS** so any verifier diff --git a/services/mini-kms/README.md b/services/mini-kms/README.md index f9e6489..04c2c28 100644 --- a/services/mini-kms/README.md +++ b/services/mini-kms/README.md @@ -1,5 +1,10 @@ # Mini KMS +> 🎓 **Learning?** This is stage 6's capstone — read +> [`envelope-encryption-and-kms`](../../docs/concepts/envelope-encryption-and-kms.md) then run lab +> [`06-protect-the-signing-keys`](../../docs/tutorials/06-protect-the-signing-keys.md). Full +> syllabus: [docs/TEACHING.md](../../docs/TEACHING.md). + A small, single-machine **Key Management Service** in Java that provides **envelope encryption** with **rotatable keys** to local services over sockets. diff --git a/services/mini-oidc/README.md b/services/mini-oidc/README.md index d8b77f2..7cd2b51 100644 --- a/services/mini-oidc/README.md +++ b/services/mini-oidc/README.md @@ -1,5 +1,11 @@ # Mini OIDC +> 🎓 **Learning?** This is stage 4 of the course — read +> [`oauth-and-oidc-flows`](../../docs/concepts/oauth-and-oidc-flows.md) and +> [`what-a-passkey-is`](../../docs/concepts/what-a-passkey-is.md), then run lab +> [`04-human-sso-end-to-end`](../../docs/tutorials/04-human-sso-end-to-end.md). Full syllabus: +> [docs/TEACHING.md](../../docs/TEACHING.md). + **mini-oidc** is the **human SSO / OpenID Provider** for the mini- family. It runs the OAuth 2.0 **authorization-code flow with [PKCE](https://datatracker.ietf.org/doc/html/rfc7636)**, authenticates people with **passkeys** ([WebAuthn](https://www.w3.org/TR/webauthn-2/)), and issues **ID + access