lazyconn

package
v0.19.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 13, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

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

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

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

type Grant struct {
	Ticket   *ticket.Ticket
	Decision *ticket.Decision
	Secret   []byte
}

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

type Pool[C io.Closer] struct {
	// contains filtered or unexported fields
}

Pool holds at most one connection per profile. It is safe for concurrent use.

func New

func New[C io.Closer](cfg Config[C]) (*Pool[C], error)

New checks cfg and returns a pool with no connection open.

func (*Pool[C]) Acquire

func (p *Pool[C]) Acquire(ctx context.Context, r Request) (conn C, release func(), err error)

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.

func (*Pool[C]) Close

func (p *Pool[C]) Close() error

Close closes every open connection, in use or not, and refuses further Acquires.

func (*Pool[C]) Connected

func (p *Pool[C]) Connected(profile string) bool

Connected reports whether profile's connection is open.

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.

type Request

type Request struct {
	Profile string
	// Requester, Workspace and Note are shown with the ticket (display
	// only, untrusted).
	Requester ticket.Requester
	Workspace string
	Note      string
}

Request is one agent request that needs a profile's connection.

type Timer

type Timer interface{ Stop() bool }

Timer is a pending idle close (*time.Timer).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL