Expand description
The actor ticket: the appliance’s word that this human may renew this tenant — minted by the product’s appliance, verified here.
§Which way this one points, and why that is the whole point
Every other signed thing in this crate travels monetize → appliance: a
crate::signing::verify_fact is monetize’s verdict about a tenant, and a
crate::signing::GoAhead is monetize’s permission to grow a data set.
Both are verified on the appliance, against monetize’s key.
A ticket travels the other way. monetize authenticates exactly one
caller on its gRPC surface — a shared bearer, and an empty one means open
(monetize-server/src/auth.rs) — so Purchase.Start cannot tell the
product’s console from the product’s operator, and certainly cannot tell one
browser session from another. When a customer renews their own
subscription, the thing monetize needs and does not have is evidence that
the human driving the console holds the tenant they are paying for.
The appliance is the one party that knows. It holds the identity table, the
keys and the grants; on gunnar a principal’s id is the namespace it owns
(AuthTable::owns_account, a string equality with no row to get wrong). So
the appliance decides, and says so in a ticket monetize can check without
asking anybody.
§Why monetize verifies a signature instead of calling the appliance back
The obvious alternative — monetize asks the appliance “is this ticket yours?” — needs no new key material at all, because a product plugin already holds an authenticated client to its own appliance. It was rejected for one reason, and it is the reason this whole path exists:
It puts the appliance in the critical path of its own renewal.
A renewal is what a customer does when the box is full, suspended, or down. A design in which the box must be reachable before it can be paid for cannot serve the case it was built for. A signature is checked offline, so a ticket minted while the box was up is still good when it is not.
§What a ticket is NOT
A ticket is permission for one verb, never a widening of any other. It
authorises purpose: "renew" on exactly the tenant it names. It cannot
authorise an order (new caps), it cannot name a second tenant, and its
absence changes nothing — a request with no ticket is the operator path,
unchanged.
§Shape
{"v":1,"issuer":"gunnar","product":"gunnar","tenant":"alice",
"purpose":"renew","nonce":"<one line, ≤256 B>",
"issued_unix_ms":<i64>,"expires_unix_ms":<i64>,
"signature":"<base64, standard alphabet, padded>"}Ed25519 over ticket_message: the canonical JSON of every field except
signature. The same canonical form and the same base64 alphabet a
crate::signing::GoAhead uses, so there is one of each in this crate and
not two.
Structs§
- Actor
Ticket - The appliance’s word that one principal may act for one tenant.
- Seen
Nonces - The nonces already spent, so a ticket is single use.
Constants§
- MAX_
CLOCK_ SKEW_ MS - How far ahead of the verifier’s clock a ticket may claim to have been issued. Two boxes, two clocks; gunnar’s own gRPC credential allows the same order of slack.
- MAX_
LIFE_ MS - The longest life a ticket may be minted with.
- PURPOSE_
ORDER - An order: more room, at a cap the customer chose.
- PURPOSE_
READ - Reading back what this tenant has already been sold. Names no caps, and buys nothing.
- PURPOSE_
RENEW - Another period of what the tenant already holds. Names no caps.
Functions§
- ticket_
message - The bytes a ticket’s signature covers: every field except
signature. - verify_
ticket - Parse, verify and admit a ticket — or refuse it, saying which rule it broke.