AuthBox documentation — User guide

Documentation embedded in this build.

A day in the harbour watch

Forty-odd capabilities work together in this product, and no single scenario shows all of them at once. This page is the closest thing to that: one story, in six acts, that re-sequences steps every other part of this repository has already proven on its own into a morning at a project called the harbour watch. It runs three ways — as a live demonstration you narrate, as a workshop where you type the commands, and as an unattended test that never lets it drift.

The cast. The project is the harbour watch. CN=operator administers it. CN=liaison and CN=analyst are its analysts, cleared differently. CN=novice arrives partway through the morning with nothing but an invitation. CN=auditor is the inspector general, who signs nothing and reads everything.

Where it runs. Every act performs a real step against a running deployment; it drives no fixture of its own. Bring one up first:

$ make all-in-one-federated

Then run the walkthrough against it:

$ make walkthrough PACE=1

PACE=1 is the demonstration: each act narrates itself in two or three sentences, names the console or portal page to open, and waits for you to press Enter before it performs the step and prints what it found. MODE=guide is the workshop: each act prints the exact command — or the exact click, when there is one — and waits for you (or the room) to run it, then checks the state that command should have left, so the presenter and the script are looking at the same facts. Neither flag is the test: make walkthrough alone runs straight through with no pauses, which is the same phase make all-in-one-federated already runs at the end of its own composed smoke, so this page's six acts never quietly stop matching what the product does.

Whichever way you run it, each act ends in one line stating the fact it just proved, and a refusal along the way names, in a full sentence, what it wanted and where to look.

Act 1 — Arrival

A new joiner has no password to set and no account to activate. An invitation arrives, the client generates a key that never leaves the joiner's own machine, and what comes back is a certificate naming them. They open their own page and read what that certificate authorizes.

$ authboxctl invite create "CN=novice,O=authbox,C=US" -server <the admin surface> \
    -ca ca.pem -cert admin.pem -key admin-key.pem -issuance identity -ttl 1h

The token in that response is printed exactly once and cannot be recovered from the record afterward, so it is held for the next step and nowhere else:

$ authboxclient enroll -dir joiner-store -server <the enrolment intake> \
    -ca ca.pem -subject "CN=novice,O=authbox,C=US" \
    -invitation "$(cat joiner.token)" -passphrase-file joiner-store/pass

The client generates the key in its own software credential store, fetches the enrolment profile first so a request the profile would refuse never leaves the machine, and claims the invitation at the intake. What comes back is a certificate binding exactly the subject the invitation named — never anyone else's — and the joiner's own page, reached through the front door on that certificate, names them.

Open: the person's own page — You, Missions, Your requests, Agreements.

Act 2 — Need to know

Two analysts ask for the same record. Neither knows, until the door answers, which of them is cleared for it: the cleared one gets the record back with its classification stated on the wire; the other gets a refusal and a signed receipt of it. Then the same record, sealed, is copied onto both of their disks.

Open: the marked route — try it with the operator's certificate, then with the analyst's.

$ curl --cert operator.pem --key operator-key.pem https://records.<domain>/records/s-1

The cleared reader's response carries X-AuthBox-Marking on the wire — the label travels with the body rather than living only in a policy somewhere. The uncleared reader's request answers 403 with a replaced body (never a truncated one — the record was never copied) and an X-AuthBox-Receipt proving the refusal happened, naming no mission, no clause, and no marking, because a refused caller learns nothing about what they were refused.

Then the sealed copy, which either analyst may hold freely — records-sealed.<domain> forwards ciphertext to anyone, the way a file server or a backup does:

$ tdfopen -ca ca.pem -cert analyst.pem -key analyst-key.pem \
    -via 127.0.0.1:443 -sni kas.<domain> \
    open s-1.tdf.manifest s-1.tdf.payload

For the uncleared analyst this exits 3 and says not cleared — the key access door asked the clearance authority and was refused. Run the identical command with the cleared analyst's certificate and it opens: the plaintext that comes back is byte-identical to what the unsealed route serves, because the sealed copy and the served record are the same file. A copy on a USB stick is exactly this: ciphertext until its holder asks the door, and the door asks the same authority it always asks.

Act 3 — Standing agreements

The analyst needs access the project has not given them. They ask from their own page; the project's admin assigns it from the workspace, in writing, with a window; the analyst signs the acceptance with the key behind their own certificate — a thing no browser can do on its own — and the door opens.

Open: the harbour watch's workspace — members, missions, agreements, the requests waiting on a decision.

In the workspace: New mission → name it, mark it clearance: SECRET, require clearance: min SECRET, approvers the project, discoverable. The mission's id is minted at the authority from the project's own name and the one you typed — nobody writes the whole id by hand.

At /missions, the analyst opens the card for the mission the project just made discoverable to its own members, writes a paragraph saying why they need it, and presses Ask for this mission. On the workspace, under Requests to decide, the project's admin assigns it a one-hour window. Then, where the key actually is:

$ authboxctl requests accept <ticket> -server <the admin surface> -ca ca.pem \
    -cert liaison.pem -key liaison-key.pem

That command is printed on the analyst's own requests page — third in the order of three ways to sign, beside a smart-card link and a security key, because a browser will not hand a certificate's private key to page script under any of them. Once it runs, the attribute appears on the analyst's own page and the project's door — the one fronting the harbour watch's own hostname — now admits them, and refuses a stranger to the project on the identical route, port, and certificate policy.

Act 4 — The project ships a service

The harbour watch runs a service of its own now. Nobody files a ticket and nobody copies a key: the project's admin makes one administrative call, the key is born inside the chosen delivery and handed over exactly once, the certificate renews itself with nobody awake for it, and the project can take it back without asking an operator.

Open: the directory — the service's own record, owned by the project rather than by any person.

$ curl --cert operator.pem --key operator-key.pem \
    -d '{"name":"<service>","delivery":"courier","profile":"service"}' \
    <the admin surface>/admin/v1/groups/<the project>/services/new

The DN that comes back is derived from the project's own name — nobody typed it — and it carries the project in every certificate issued under it. The courier delivery hands the key and certificate over exactly once, to a claimant holding no certificate at all — the invitation itself is the authentication — and a second claim of the same token is refused; nothing here kept a copy to reuse. Renewal by possession then reissues the certificate with nothing claimed in the request but the certificate presenting itself, because the identity was created carrying the self-renewal attribute. And withdrawal:

$ curl --cert operator.pem --key operator-key.pem -d '{"serial":"<serial>"}' \
    <the admin surface>/admin/v1/groups/<the project>/services/<service>/revoke-request

filed by the project's own admin, on a project-scoped operation that needs none of the standing an operator's own revocation desk requires — and the withdrawn identity never renews again.

Act 5 — Federation

A field officer sits in an enclave that cannot even resolve headquarters, let alone reach it, and headquarters answers anyway — through the door in front of the enclave, on headquarters' own certificate, with the enclave terminating nothing. Then the inspector general suspends an analyst at the authority, and the edge refuses them within one poll, without anybody telling it to.

Open: headquarters' own console, reached from a box that publishes no port for it at all.

$ authboxctl entity suspend "CN=analyst,O=authbox,C=US" -reason "on leave" \
    -server <the admin surface> -ca ca.pem -cert admin.pem -key admin-key.pem

That is a signed act — the console offers no button for it, because a browser will not hand a certificate's private key to page script — and within one poll of the writer's signed bundle, the analyst's own record answers refused at the edge, even though their certificate is untouched: unexpired, on no revocation list, issued by the same intermediate. The store is what refuses, not the credential. Reinstating lifts it on the same schedule.

Act 6 — The accreditor

The inspector general asks for evidence, not assurances. There is no history table anywhere in this product; a person's story is computed, on request, from the records that already hold it. A receipt is checked against the signed audit export it claims to describe. The evidence register says which test holds which claim up, and that register is what an accreditor is actually handed.

Open: the console — an entity, then its history.

The entity's history page states, in words, how far back it reaches and what it does and does not hold of any federated half — never silently. A decision's receipt, checked against the export the composed run already sealed:

$ authboxctl receipt reconcile <receipt.jws> -audit <exports> -jwks jwks.json \
    -anchor-key audit-anchor=<anchor-pub.pem>

prints, among other things, N matched, N contradicted, N unbacked, N invalid. A freshly minted receipt is almost always unbacked — the export was sealed an instant before the decision it describes — and that is not by itself an accusation: it says the export may simply not carry the segment yet, which is a different fact from a decision that was never logged at all. Contradicted or invalid are the ones worth stopping for. Then the register itself, unauthenticated because a buyer and an accreditor read it before they hold a certificate:

$ curl https://<domain>:8080/site/evidence.html

and the OSCAL component definition beside it in the repository, generated from the same registers that page renders.

What comes next

A recorded run of all six acts, unpaced, is at the walkthrough transcript. Every command above is exact; a test fails the build if the page and the script it describes ever disagree.