Skip to main content

Module ticket

Module ticket 

Source
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§

ActorTicket
The appliance’s word that one principal may act for one tenant.
SeenNonces
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.