Documentation
¶
Overview ¶
Package plans is a lock's store of plans awaiting a decision: one file per plan in the lock's volume (0600 in a 0700 directory), surviving restarts. A plan is bound to the peer UID that made it, the requesting agent's token, the policy fingerprint and the plan hash; it runs once.
The store keeps the product's plan as an opaque body: the product recomputes the plan hash from it before running (Check), and runs nothing whose binding moved.
One Store owns a store directory: Open locks it (a second Open fails with ErrLocked until Close), so that a decision nonce is accepted once whatever the number of consoles on a volume. A Store is safe for concurrent use.
Index ¶
- Constants
- Variables
- func HashToken(token string) string
- type Binding
- type Caller
- type Option
- type Plan
- type State
- type Store
- func (s *Store) Begin(id string, c Caller, nonce string) error
- func (s *Store) ByTicket(ticketID string, c Caller) (*Plan, error)
- func (s *Store) Check(id string, c Caller, policyFP, planHash string) error
- func (s *Store) Close() error
- func (s *Store) Create(b Binding, body json.RawMessage, ttl time.Duration) (*Plan, error)
- func (s *Store) End(id string, c Caller, st State, reason string) error
- func (s *Store) ExpireApproval(id string, c Caller, approvedAt time.Time, reason string) error
- func (s *Store) ExpiredByTicket(ticketID string, c Caller) (*Plan, error)
- func (s *Store) FindPending(c Caller, planHash string) (*Plan, bool)
- func (s *Store) Finish(id string, c Caller, ok bool, reason string) error
- func (s *Store) Get(id string, c Caller) (*Plan, error)
- func (s *Store) MarkPending(id string, c Caller, t *ticket.Ticket) error
- func (s *Store) MarkPendingClear(id string, c Caller, t *ticket.Ticket, clear *ticket.Screen) error
- func (s *Store) MarkTold(id string) error
- func (s *Store) Pending() []*Plan
- func (s *Store) Prune() (int, error)
- func (s *Store) Renew(id string, policyFP, planHash string, inputs map[string]string, checked bool) (*Plan, error)
- func (s *Store) RenewalOf(id string) (*Plan, error)
- func (s *Store) TicketPlan(ticketID string) (*Plan, error)
- func (s *Store) Untold() []*Plan
- func (s *Store) WithdrawAll(reason string) ([]*Plan, error)
Constants ¶
const ( // MaxPlans bounds the live (not final) plans of a store. MaxPlans = 1000 // MaxBody bounds a plan's body. MaxBody = 1 << 20 // Retention keeps a final plan this long before Prune removes it. Retention = 24 * time.Hour )
Defaults.
Variables ¶
var ( // ErrNoSuchPlan: unknown, pruned or expired plan id. ErrNoSuchPlan = errors.New("plans: no such plan") // ErrWrongPeer: the plan was made by another peer UID. ErrWrongPeer = errors.New("plans: plan made by another peer") // ErrWrongRequester: the plan was made by another agent (token). ErrWrongRequester = errors.New("plans: plan made by another agent") // ErrStale: the plan hash or policy fingerprint moved since planning. ErrStale = errors.New("plans: plan is stale") // ErrUsed: the plan already ran, or is in a final state. ErrUsed = errors.New("plans: plan already used") // ErrReplay: the decision nonce was consumed before. ErrReplay = errors.New("plans: decision nonce already consumed") // ErrState: the transition is not allowed from the plan's state. ErrState = errors.New("plans: transition not allowed") // ErrFull: the store holds MaxPlans live plans. ErrFull = errors.New("plans: too many plans") // ErrUnsafe: the store directory or a file in it has unsafe // permissions; nothing is loaded. ErrUnsafe = errors.New("plans: unsafe permissions") // ErrLocked: another Store (another console on the same volume) has // the directory open. ErrLocked = errors.New("plans: the store is open in another process") )
var ErrNotExpired = errors.New("plans: plan has not expired")
ErrNotExpired: Renew of a plan that is not Expired.
var ErrRenewed = errors.New("plans: plan already renewed")
ErrRenewed: the expired plan was renewed already; Renew returns the renewal.
Functions ¶
Types ¶
type Binding ¶
type Binding struct {
UID int `json:"uid"`
// Agent is the requesting agent's label (display only).
Agent string `json:"agent"`
// TokenHash is HashToken of the agent's token ("" without one).
TokenHash string `json:"token_hash"`
PolicyFP string `json:"policy_fp"`
PlanHash string `json:"plan_hash"`
// Inputs are the digests of the plan hash's inputs by name
// (ticket.InputDigests), when the product gives them: a renewal shows
// which of them moved (ticket.DiffInputs).
Inputs map[string]string `json:"inputs,omitempty"`
}
Binding is what a plan is bound to.
type Caller ¶
type Caller struct {
UID int
// Token is the agent's lea token (LEA_AGENT_TOKEN), "" without lea.
Token string
// contains filtered or unexported fields
}
Caller is who presents a plan id.
func OwnerOf ¶ added in v0.5.0
OwnerOf is the caller a plan is bound to, for the lock itself acting on the plan for its agent (a renewal the human asked for on the hub). It is a capability of code holding the store, which can act on any plan anyway: never build a caller for a client with it (a product builds those from its clients' credentials).
type Plan ¶
type Plan struct {
ID string `json:"id"`
State State `json:"state"`
Binding Binding `json:"binding"`
// Ticket is the hub ticket id, once pending, and SignedTicket the
// ticket as the lock signed and pushed it: a decision is matched
// against it (ticket.Decision.Match), never against one rebuilt.
Ticket string `json:"ticket,omitempty"`
SignedTicket json.RawMessage `json:"signed_ticket,omitempty"`
// Nonce is the decision nonce consumed when the plan began executing.
Nonce string `json:"nonce,omitempty"`
// Reason says why the plan reached its final state (denial reason,
// failure, what went stale).
Reason string `json:"reason,omitempty"`
// Untold: the plan's ticket was withdrawn here (WithdrawAll, a start
// without the hub) and the hub has not been told yet; the next start
// in hub mode tells it (hubclient.Client.Sync), then MarkTold.
Untold bool `json:"untold,omitempty"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
Expires *time.Time `json:"expires,omitempty"`
// Body is the product's plan, opaque to the store.
Body json.RawMessage `json:"body"`
// Clear is the clear screen of the plan's ticket: it stays in the
// lock's volume and leaves only sealed (a reveal, a device).
Clear *ticket.Screen `json:"clear,omitempty"`
// ApprovedAt is when an approval that aged before the run was given
// (an Expired plan whose decision came too late to be used).
ApprovedAt *time.Time `json:"approved_at,omitempty"`
// Renews is the expired plan this one asks again for; RenewedBy, on
// an expired plan, the plan that renews it.
Renews string `json:"renews,omitempty"`
RenewedBy string `json:"renewed_by,omitempty"`
// RenewChecked: a renewal whose binding the lock computed again (its
// Inputs and hashes are the lock's current view).
RenewChecked bool `json:"renew_checked,omitempty"`
}
Plan is one stored plan.
type State ¶
type State string
State of a plan.
const ( // Planned: made, never run. Planned State = "planned" // Pending: run asked, a ticket awaits the human. Pending State = "pending" // Executing: a decision was accepted and consumed; the plan is running. Executing State = "executing" Executed State = "executed" Failed State = "failed" // Stale: the binding moved (plan hash, policy) before it ran. Stale State = "stale" Denied State = "denied" Withdrawn State = "withdrawn" Expired State = "expired" )
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is a directory of plans.
func Open ¶
Open opens or creates the store in dir (created 0700). It refuses a directory, plan or nonce file readable or writable by group or others, and loads every plan.
func (*Store) Begin ¶
Begin starts executing plan id under the decision nonce: single use. The nonce is recorded durably before the plan changes state; a nonce seen before is ErrReplay, a plan not Planned or Pending is ErrUsed. Without a hub (standalone), nonce is "".
func (*Store) Check ¶
Check compares a plan's binding with what the product recomputed just before running it: a different policy fingerprint or plan hash marks the plan Stale and returns ErrStale.
func (*Store) Close ¶
Close releases the store directory for another Open. The Store must not be used after it.
func (*Store) Create ¶
Create stores a new Planned plan bound to b, with the product's body, expiring after ttl unless run (0: never).
func (*Store) End ¶
End moves a plan that has not begun to a final state: Denied, Withdrawn, Stale or Expired, with reason. It is how the lock records a decision other than an approval, or a ticket withdrawn.
func (*Store) ExpireApproval ¶ added in v0.5.0
ExpireApproval ends a pending plan Expired because its approval, given at approvedAt, aged before the run: a renewal says so.
func (*Store) ExpiredByTicket ¶ added in v0.5.0
ExpiredByTicket returns the expired plan of ticket ticketID, for caller c (its binding: same peer UID, same agent token): what a renewal starts from. A plan of the ticket in any other state is ErrNotExpired.
func (*Store) FindPending ¶
FindPending returns the pending plan of caller c (peer UID and agent token) with planHash, if any: a second run of the same plan reuses its ticket instead of opening a new one.
func (*Store) Get ¶
Get returns plan id as caller c may see it: same peer UID, same agent token. An expired plan is ErrNoSuchPlan.
func (*Store) MarkPending ¶
MarkPending records that plan id waits on t, the ticket the lock signed for it, until t.Expires (nil: no expiry). t must carry the plan's hash and policy fingerprint.
func (*Store) MarkPendingClear ¶ added in v0.5.0
MarkPendingClear is MarkPending keeping clear, the clear screen of the ticket, in the plan (Plan.Clear) for a later reveal.
func (*Store) MarkTold ¶ added in v0.4.0
MarkTold records that the hub was told plan id's ticket is withdrawn.
func (*Store) Prune ¶
Prune expires plans whose time is up and removes final plans older than Retention. Their nonces stay recorded. It returns how many it removed.
func (*Store) Renew ¶ added in v0.5.0
func (s *Store) Renew(id string, policyFP, planHash string, inputs map[string]string, checked bool) (*Plan, error)
Renew makes a new Planned plan asking again for expired plan id: the same body, binding and clear screen, under the policy fingerprint, plan hash and inputs the lock computed now. The expired plan records it (RenewedBy); a plan already renewed returns its renewal with ErrRenewed.
func (*Store) TicketPlan ¶ added in v0.5.0
TicketPlan returns the plan of ticket ticketID whatever its caller: for the lock itself (a request of the hub about one of its tickets), never for a client.
func (*Store) Untold ¶ added in v0.4.0
Untold lists the withdrawn plans whose ticket the hub was not told about, oldest first.
func (*Store) WithdrawAll ¶
WithdrawAll ends every pending plan as Withdrawn, as a lock starting without its hub does, and returns them, oldest first. They are marked Untold: the hub still shows their ticket until a start in hub mode withdraws it there (Untold, MarkTold).