Documentation
¶
Overview ¶
Package lazyconn opens a lock's backend connections only when an agent's request needs one, on a decision: a lock starts disconnected; the first request for a profile sends a connect ticket (ticket.KindConnect) to the hub, the connection opens on a signed approval, is shared by the requests that follow, and closes once it has been idle for Config.Idle. The next request asks again.
When the profile needs its secret from the decider (credentials "ask", or an expired Vault token: Profile.NeedSecret), the ticket carries a one-use X25519 key of the lock (Subject.SecretTo); `lox run` seals the secret to it in the decision (ticket.SealSecret), the hub relays a box it cannot open, and the pool opens it (Decision.OpenSecret) for Dial only. The agent never sees it: it only gets a connection or an error.
locksql and lockmq use one Pool per lock, the hub client as the Asker (hubclient.Client.Ask).
Index ¶
Constants ¶
const ( // DefaultIdle is how long an unused connection stays open. DefaultIdle = 2 * time.Minute // DefaultAskTimeout is how long a connect ticket waits for a decision // (its Expires). DefaultAskTimeout = 10 * time.Minute // DefaultMaxAge is the age past which a connect approval is not acted // on (ticket.Decision.Usable). DefaultMaxAge = 5 * time.Minute )
Defaults of Config.
Variables ¶
var ErrClosed = errors.New("lazyconn: pool closed")
ErrClosed: the pool is closed.
Functions ¶
This section is empty.
Types ¶
type Asker ¶
type Asker interface {
Ask(ctx context.Context, t *ticket.Ticket, screen render.MaskedScreen) (*ticket.Decision, error)
}
Asker sends a connect ticket and waits for its decision: hubclient.Client.Ask. It sets the ticket's instance, key, screen and touched kinds, signs it, and returns the decision verified under the pinned hub key and matching the ticket, approval or denial.
type Config ¶
type Config[C io.Closer] struct { // Lock is the product ("locksql"), the ticket's Source.Lock. Lock string Asker Asker // Profile reads a profile's configuration at each connect; an error // fails the request without a ticket. Profile func(name string) (Profile, error) // Dial opens the backend connection for profile name on g. Dial func(ctx context.Context, name string, g Grant) (C, error) // Idle, AskTimeout and MaxAge default to DefaultIdle, // DefaultAskTimeout and DefaultMaxAge when <= 0. Idle, AskTimeout, MaxAge time.Duration // Now is the clock (nil: time.Now); AfterFunc schedules the idle // close (nil: time.AfterFunc). Now func() time.Time AfterFunc func(time.Duration, func()) Timer // OnEvent, if set, is called for every Event, outside the pool's // lock. OnEvent func(Event) }
Config configures a Pool. Lock, Asker, Profile and Dial are required.
type Event ¶
type Event struct {
Kind EventKind
Profile string
Ticket string
Decision *ticket.Decision
Err error
}
Event is one change of a profile's connection, for the lock's audit: Decision names the deciding rule (Decision.Rule) and facade.
type EventKind ¶
type EventKind string
EventKind is what happened to a profile's connection.
const ( // EventConnected: a connection opened on Decision. EventConnected EventKind = "connected" // EventDenied: the decider refused the connection (Decision). EventDenied EventKind = "denied" // EventFailed: asking or connecting failed (Err). EventFailed EventKind = "failed" // EventClosed: the connection closed, idle or with the pool. EventClosed EventKind = "closed" )
type Grant ¶
Grant is what Dial connects with: the lock's signed ticket, the verified approval, and the secret the decider sealed to the lock (nil when the profile did not need one). The pool wipes Secret when Dial returns: Dial copies what it keeps.
type Pool ¶
Pool holds at most one connection per profile. It is safe for concurrent use.
func (*Pool[C]) Acquire ¶
Acquire returns the connection of r.Profile, asking for it first when none is open: concurrent requests for a profile share one ticket and one connection. release gives the connection back (once; later calls do nothing); the last release starts the idle timer. A denial is ticket.ErrDenied with the reason; a later Acquire asks again.
type Profile ¶
type Profile struct {
// Endpoint is the backend, without userinfo ("db.internal:5432/shop").
Endpoint string
// Credential is the profile's credential source (ticket.Credential*).
Credential string
// NeedSecret asks the decider for the secret (credentials "ask", an
// expired Vault token): the ticket carries a one-use key and Dial gets
// the secret in Grant.Secret.
NeedSecret bool
// PolicyFP is the fingerprint of the policy in force (ticket.HashOf).
PolicyFP string
// Risk is the ticket's risk class (ticket.KindRisks of connect); ""
// is unlock with NeedSecret, prod-write in production, read otherwise.
Risk ticket.Risk
// Production marks a production profile; NeverAuto keeps an auto
// rule from approving the connection.
Production, NeverAuto bool
// Masking masks the screen (render.MaskScreen; zero: every detector).
Masking render.Masking
}
Profile is what a connect ticket asks for one profile, as the lock's configuration says at the time of the request.