Documentation
¶
Overview ¶
Package console is the human side of locksql: it holds the credentials and the only database connection, enforces the approved policy, shows every query to the human for approval, masks the results and writes the audit trail. Clients (the CLI and the MCP server) reach it over the per-profile socket and never see a secret.
Index ¶
- Constants
- Variables
- func Run(ctx context.Context, o Options) error
- func SameUserRefused(sys *sysconf.Config) error
- type Approver
- type Beller
- type Decision
- type IO
- type Key
- type KeySource
- type LineSource
- type Options
- type Outcome
- type Result
- type Server
- func (s *Server) CheckPolicy()
- func (s *Server) Command(ctx context.Context, line string)
- func (s *Server) End(reason string)
- func (s *Server) Ended() (string, bool)
- func (s *Server) Handle(ctx context.Context, req ipc.Request) ipc.Response
- func (s *Server) Key(k Key)
- func (s *Server) Serve(ctx context.Context, ln net.Listener, lines <-chan string) error
- func (s *Server) Tick()
- type ServerConfig
- type Sized
- type StatusLine
- type Terminal
- func (t *Terminal) Ask(ctx context.Context, prompt string, timeout time.Duration) (string, bool)
- func (t *Terminal) AskSecret(ctx context.Context, prompt string) ([]byte, error)
- func (t *Terminal) Bell()
- func (t *Terminal) Interactive() error
- func (t *Terminal) Keys() <-chan Key
- func (t *Terminal) Lines() <-chan string
- func (t *Terminal) Println(s string)
- func (t *Terminal) Restore()
- func (t *Terminal) SetKeyHandler(f func(Key))
- func (t *Terminal) SetStatus(s string)
- func (t *Terminal) Width() int
Constants ¶
const ( // The idle timeout and the maximum session length are the profile's // limits.idle_timeout and limits.max_session. ApprovalTimeout = 5 * time.Minute PlanTTL = 10 * time.Minute // PolicyPoll is how often the config files are checked for changes. PolicyPoll = 2 * time.Second )
Session timeouts, spec §6.
const FingerDeadline = 30 * time.Second
FingerDeadline bounds the wait for a finger after a typed yes.
const ResponseQuantum = 250 * time.Millisecond
ResponseQuantum levels the time a statement takes, as the client sees it: the answer to query.run, success or failure, leaves at the next multiple of it after the run started. A query cannot turn the time it takes into a fine-grained signal (a timing side channel).
Variables ¶
var ErrConfig = errors.New("configuration error")
ErrConfig marks usage and configuration errors (exit code 3).
var ErrTooManyNoMatch = errors.New("fingerprint: three consecutive non-matching fingers")
ErrTooManyNoMatch is set on the third consecutive no-match: the caller ends the console.
Functions ¶
func Run ¶
Run is `locksql console`: the start-up sequence of spec §6, then the request loop until Ctrl-C, logout, :quit or a timeout.
func SameUserRefused ¶ added in v0.1.2
SameUserRefused is the refusal of a console without a separated setup (sys nil, as sysconf.Load returns it), or nil.
Types ¶
type Approver ¶ added in v0.6.0
type Approver interface {
Approve(ctx context.Context, d Decision) Result
// Mode is the audit value: "session" or "desktop+fingerprint".
Mode() string
}
Approver asks the human for one decision. Each call is one decision: a fingerprint match never carries over.
type Beller ¶ added in v0.5.0
type Beller interface {
Bell()
}
Beller is implemented by an IO that can ring the terminal bell, so that a human away from the console notices a prompt waiting for them.
type Decision ¶ added in v0.6.0
type Decision struct {
// Prompt is the typed prompt, already framed by the caller.
Prompt string
// Expected is the typed answer: "y" (any case) or the production
// profile name (exact).
Expected string
// Production selects the exact match.
Production bool
// Retype is the typed answer that asks to retype placeholders ("" when
// not offered).
Retype string
}
Decision names what a human is approving.
type IO ¶
type IO interface {
Println(s string)
// Ask prints prompt and reads one line. It returns false when no answer
// came within timeout, when ctx ended (Ctrl-C, client gone) or when the
// terminal is closed.
Ask(ctx context.Context, prompt string, timeout time.Duration) (string, bool)
// AskSecret reads a line with echo off.
AskSecret(ctx context.Context, prompt string) ([]byte, error)
}
IO is the console's terminal. Ask and AskSecret discard pending input before prompting, so type-ahead never answers a prompt.
type Key ¶ added in v0.5.0
type Key int
Key is a control key the human pressed, outside a line. Keys reach the console only from its own terminal: no client can send one.
type KeySource ¶ added in v0.5.0
KeySource is implemented by an IO that reports the control keys pressed in the console: on Keys between prompts, to the handler during one.
type LineSource ¶
type LineSource interface {
Lines() <-chan string
}
LineSource is implemented by an IO that also delivers the lines the human types between requests (console commands such as :review).
type Options ¶
type Options struct {
Profile string
// Auto starts the console in auto mode (--auto); Shift+Tab switches
// the mode while it runs.
Auto bool
// AllowUnmask lets clients ask for unmasked output (--allow-unmask).
AllowUnmask bool
// ShowResults starts with the clear results on (--show-results);
// Ctrl-O switches them while the console runs.
ShowResults bool
Cwd string
IO IO
// Now defaults to time.Now.
Now func() time.Time
// StateDir defaults to config.StateDir().
StateDir string
// Version is the console version reported to clients.
Version string
// TTY is the console's terminal device (nil when unknown): its owner
// must be the console's account in a separated setup.
TTY *os.File
}
Options configure Run.
type Outcome ¶ added in v0.6.0
type Outcome int
Outcome of an approval.
const ( Approved Outcome = iota // Denied is a typed answer that is not the expected one; Answer holds it. Denied // Unanswered: no answer within the timeout, or ctx ended. Unanswered RetypeRequested // NoFinger: desktop mode, typed yes, then no match, timeout or reader // error; Err holds it. NoFinger )
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server serves client requests one at a time against one session. It is not safe for concurrent use: Serve runs every request on one goroutine.
func NewServer ¶
func NewServer(cfg ServerConfig) (*Server, error)
NewServer checks cfg and returns a server enforcing cfg.Policy.
func (*Server) CheckPolicy ¶
func (s *Server) CheckPolicy()
CheckPolicy re-reads the policy from the config files. Changes that only tighten are applied at once; a loosening waits for :review, and plans are refused with CodePolicyPending meanwhile.
func (*Server) Handle ¶
Handle serves one request. It is the whole client surface of the console: none of the methods loosens the policy.
func (*Server) Serve ¶
Serve accepts clients on ln and serves their requests one at a time until ctx ends or the session ends (logout, timeouts, :quit). lines are the console commands typed by the human (nil for none); the control keys come from the IO when it is a KeySource. ln is closed on return, which removes the socket.
type ServerConfig ¶
type ServerConfig struct {
// Policy is the approved policy the server enforces.
Policy config.Policy
// RulesPath is the PII rules file: <project>/.locksql/pii.toml, or
// pii.toml in the user config directory outside a project.
RulesPath string
// StateDir and ApprovedKey locate the approved policy file.
StateDir string
ApprovedKey string
Session engine.Session
// DBUser is the database account in use (it may have been asked for).
DBUser string
Databases []string
Audit *audit.Log
IO IO
// Approver takes every human decision; nil means typed approvals.
Approver Approver
// Fingerprint is true when Approver is the fingerprint approver
// (level 2): a typed answer alone then never widens anything.
Fingerprint bool
// Boundary names the desktop boundary for the status screen ("" in
// the session shape).
Boundary string
Now func() time.Time
// Auto starts in auto mode: on non-production profiles, statements
// allowed by the tier and the weight check run without a prompt (never
// unmask, never REFUSE). Shift+Tab switches the mode while the console
// runs.
Auto bool
// AllowUnmask starts with unmasked output allowed: clients may ask for
// it, and each such query is approved in the console. Without it an
// unmask plan is refused before anything else. :unmask switches it
// while the console runs.
AllowUnmask bool
// ShowResults starts with the clear results on: the result of each
// masked run printed in the console, in clear; the client still gets
// it masked. Ctrl-O switches them while the console runs.
ShowResults bool
Version string
// LoadPolicy re-reads the current policy from the config files. Nil
// disables CheckPolicy.
LoadPolicy func() (config.Policy, error)
// Reconnect opens a new session after a lost connection, under p, the
// profile in force at that time. Nil means a lost connection ends the
// console.
Reconnect func(ctx context.Context, p config.Profile) (engine.Session, error)
// Quantum levels the response time of query.run (ResponseQuantum in
// the real console, 0 to disable).
Quantum time.Duration
// PeerAllowed decides which clients are served, from the kernel's
// credentials of the peer. Nil serves the console's own account only.
PeerAllowed func(ipc.Cred) bool
// Health is reported in status for locksql doctor.
Health *ipc.Health
}
ServerConfig holds what a Server needs once the start-up sequence is done. Run builds it; tests build it directly.
type Sized ¶ added in v0.5.0
type Sized interface {
Width() int
}
Sized is implemented by an IO that knows its width in columns.
type StatusLine ¶ added in v0.5.0
type StatusLine interface {
SetStatus(string)
}
StatusLine is implemented by an IO that keeps a status row on screen.
type Terminal ¶
type Terminal struct {
// contains filtered or unexported fields
}
Terminal is the IO of a real console: prompts and messages go to out, answers and console commands are read from in, which must be the controlling terminal.
Interactive puts the terminal in cbreak mode: the Terminal does the line editing itself, so that it also sees the control keys (Shift+Tab, Ctrl-O) and can keep a status line on the bottom row, above which the output scrolls. One goroutine reads in from the first prompt on, so that the console can wait for a request and for a typed command at the same time. Before each prompt the kernel's pending input is flushed and the lines already read are dropped: type-ahead never answers a prompt.
func NewTerminal ¶
NewTerminal returns the IO over in and out (normally os.Stdin and os.Stdout).
func (*Terminal) AskSecret ¶
AskSecret reads a line with echo off. Before the reader goroutine runs it uses secrets.PromptPassword; in cbreak mode it stops echoing itself; otherwise it turns the kernel echo off on in.
func (*Terminal) Interactive ¶ added in v0.5.0
Interactive puts the terminal in cbreak mode (no line discipline, no kernel echo; signals and output post-processing kept) and starts reading it. Restore undoes it. An input that is not a terminal (a test pipe) stays as it is: lines are read, keys and the status line are off.
func (*Terminal) Keys ¶ added in v0.5.0
Keys delivers the control keys pressed while no prompt is active.
func (*Terminal) Restore ¶ added in v0.5.0
func (t *Terminal) Restore()
Restore ends the status line and puts the terminal back in the mode it had before Interactive.
func (*Terminal) SetKeyHandler ¶ added in v0.5.0
SetKeyHandler sets the function that handles a control key pressed while a prompt waits. It runs on the goroutine that asked.