Documentation
¶
Overview ¶
Package console is the human side of a lockkit product: it holds the credentials and the only backend connection, enforces the approved policy, shows every request that needs a human decision on an approval screen, and writes the audit trail. Clients (a product's CLI and MCP server) reach it over the per-profile socket and never see a secret.
A product supplies a Backend (its methods, its connection, its checks and its policy) and calls Run. The pieces Run is made of — the terminal, the approvers, the isolation checks, the request loop — are exported for products that assemble their own console.
Index ¶
- Constants
- Variables
- func Bell(io IO)
- func CloseVerifier(v fingerprint.Verifier)
- func Decode(req ipc.Request, v any) *ipc.Response
- func DenyReason(ans string) string
- func ErrResp(id int64, code int, msg string) ipc.Response
- func FormatChanges(changes []config.Change) []string
- func Harden() error
- func Level(ctx context.Context, quantum time.Duration, start time.Time)
- func Loosens(changes []config.Change) bool
- func OKResp(id int64, v any) ipc.Response
- func PeerFrom(ctx context.Context) (ipc.Cred, bool)
- func PeerText(ctx context.Context) string
- func PendingResp(id int64, lock string, p Pending) ipc.Response
- func PolicyPendingOf(env Env) bool
- func ProcessText(pid int) string
- func Run(ctx context.Context, p kit.Product, b Backend, o Options) error
- func SameUserRefusal(p kit.Product) string
- func SameUserRefused(p kit.Product, sys *sysconf.Config, allowSameUser bool) error
- func StampPeer(ctx context.Context, b *audit.Base)
- func StatusBoundaryRow(v StatusView) string
- func StatusModeRow(StatusView, Words) stringdeprecated
- func StatusPendingRow(v StatusView) string
- func StatusResultsRow(v StatusView, w Words) string
- func StatusUnmaskRow(v StatusView) string
- func WithPeer(ctx context.Context, c ipc.Cred) context.Context
- func WithdrawnResp(id int64, tk string) ipc.Responsedeprecated
- type Approval
- type Approver
- type Ask
- type Asker
- type AsyncApprover
- type Backend
- type Beller
- type ChangeFormatter
- type ConnectionComparer
- type Credentials
- type Decision
- type EndError
- type Env
- type Finding
- type Handler
- type Hub
- type HubOptions
- type HubTicket
- type IO
- type Isolation
- func (iso *Isolation) Boundary() string
- func (iso *Isolation) Level() int
- func (iso *Isolation) Listen(path string) (net.Listener, error)
- func (iso *Isolation) PeerCheck() func(ipc.Cred) bool
- func (iso *Isolation) RefuseX11IfProduction(production bool) error
- func (iso *Isolation) SocketPath(projectKey, profile string) (string, error)
- type IsolationEnv
- type Key
- type KeySource
- type LineSource
- type Loop
- type Options
- type Outcome
- type PIIBackend
- type Pending
- type Policy
- type PolicyApplier
- type Profile
- type Result
- type Screen
- type Server
- func (s *Server) Approve(ctx context.Context, id int64, sc Screen) Approval
- func (s *Server) Asker() Asker
- func (s *Server) Audit(rec audit.Record)
- func (s *Server) AuditBase(ctx context.Context, b audit.Base)
- func (s *Server) Auto() booldeprecated
- func (s *Server) CheckPolicy()
- func (s *Server) Close()
- 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) Hub() *Hub
- func (s *Server) Key(k Key)
- func (s *Server) Level(ctx context.Context, start time.Time)
- func (s *Server) Loop() *Loop
- func (s *Server) Lost()
- func (s *Server) Policy() Policy
- func (s *Server) PolicyPending() bool
- func (s *Server) PolicyRefused() bool
- func (s *Server) Println(line string)
- func (s *Server) Safe(text string) string
- func (s *Server) Sanitizer() *sanitize.Sanitizer
- func (s *Server) Session(ctx context.Context) (Session, error)
- func (s *Server) ShowResults() bool
- func (s *Server) Status() StatusResult
- func (s *Server) Tick()
- func (s *Server) UnmaskAllowed() bool
- func (s *Server) WriteAudit(rec audit.Record) error
- type ServerConfig
- type Session
- type Sized
- type StatusAnswerer
- type StatusLine
- type StatusLiner
- type StatusResult
- type StatusRower
- type StatusView
- 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
- type Withdrawer
- type Withdrawndeprecated
- type Worder
- type Words
Constants ¶
const ( // ApprovalTimeout bounds every human answer. ApprovalTimeout = 5 * time.Minute // PolicyPoll is how often the config files are checked for changes. PolicyPoll = 2 * time.Second // ResponseQuantum levels the response time of a request whose answer // could otherwise tell the agent how long a human or a backend took. ResponseQuantum = 250 * time.Millisecond )
Timeouts of a console session. The idle timeout and the maximum session length are the profile's.
const CredentialsNone = "none"
CredentialsNone is the credentials mode of a backend that needs no secret (a local file, a socket the account owns).
const FingerDeadline = 30 * time.Second
FingerDeadline bounds the wait for a finger after a typed yes.
const HubApproval = "hub"
HubApproval is the audit approval of a request handed to an AsyncApprover.
const StandaloneReason = "the lock was restarted without its hub (standalone)"
StandaloneReason is the reason of the plans a standalone start withdraws, as the hub and the plans store record it.
Deprecated: there is no standalone start any more.
Variables ¶
var ErrConfig = errors.New("configuration error")
ErrConfig marks usage and configuration errors (exit code 3).
var ErrNoHub = fmt.Errorf("%w: the console is launched by `lox up` only: --hub, --hub-key-file and --instance are required", ErrConfig)
ErrNoHub refuses a console started without its hub: --hub, --hub-key-file and --instance are required, as `lox up` writes them.
var ErrPolicyOnly = errors.New("this console serves its policy only and approves no request")
ErrPolicyOnly refuses a request on a policy-only console (Options.PolicyOnly, PolicyOnlyApprover): it serves its policy and decides no request, which another process's hub decides.
var ErrPolicyOnlyHub = fmt.Errorf("%w: a policy-only console takes no hub options (--hub, --hub-key-file, --instance, ...)", ErrConfig)
ErrPolicyOnlyHub refuses a policy-only console given hub options: it has no hub, and a console with both would be ambiguous about who decides its requests.
var ErrSecretTimeout = errors.New("console: no password entered")
ErrSecretTimeout is AskSecret's error when no answer comes within ApprovalTimeout.
var ErrTooManyNoMatch = errors.New("fingerprint: three consecutive non-matching fingers")
ErrTooManyNoMatch is set on the third consecutive no-match: the caller ends the console.
var Paint = ui.Painter{On: true}
Paint styles console lines.
Functions ¶
func CloseVerifier ¶
func CloseVerifier(v fingerprint.Verifier)
CloseVerifier releases a verifier that holds a connection (fprintd's).
func Decode ¶
Decode decodes the params of req into v, refusing unknown fields; nil when they decode (or are absent), else the error response.
func DenyReason ¶
DenyReason is the reason of an answer "n <text>" or "no <text>" (n and no in any case), trimmed, cut at 200 bytes and escaped; "" for any other answer.
func FormatChanges ¶
FormatChanges renders a policy diff, loosenings in red. Every value is escaped: a pattern or a host could otherwise carry terminal escapes that hide a loosening from the human.
func Harden ¶
func Harden() error
Harden keeps the secret out of core dumps and away from same-user ptrace and /proc/<pid>/mem readers: RLIMIT_CORE=0 and PR_SET_DUMPABLE=0.
func Level ¶
Level waits until the next multiple of quantum since start, so that the response time tells nothing finer than quantum (0 or less: no wait).
func PendingResp ¶ added in v0.3.0
PendingResp is the answer to a request waiting for a decision taken elsewhere: a result, not an error, so that the agent stops and waits rather than retries. It names p.Instance when set (hub mode).
func PolicyPendingOf ¶ added in v0.7.0
PolicyPendingOf reports a loosening awaiting :review in the console behind env.
func ProcessText ¶
ProcessText names the process behind pid on Linux: its executable path when the kernel lets the console read it (a process of its own account), else the name the process gave itself, labelled as such; "" elsewhere. The uid and pid are the kernel's; the name is the process's choice.
func Run ¶
Run is a product's `console` command, launched by `lox up` only (the hub options are required, ErrNoHub, unless Options.PolicyOnly): the isolation checks, the policy review, the production confirmation, the credentials and the connection, the backend's checks, the socket, then the request loop until Ctrl-C, logout, :quit or a timeout.
func SameUserRefusal ¶
SameUserRefusal is the one text of the same-user refusal of p.
func SameUserRefused ¶
SameUserRefused is the refusal of a console of p without a separated setup (sys nil, as sysconf.Load returns it), or nil.
func StampPeer ¶
StampPeer sets the peer fields of b from the client behind ctx: its uid, pid and account name. The process name, chosen by the agent, stays on the screen (PeerText) and out of the audit log.
func StatusBoundaryRow ¶ added in v0.7.0
func StatusBoundaryRow(v StatusView) string
StatusBoundaryRow is the value of the boundary row; "" in the session shape (no row).
func StatusModeRow
deprecated
added in
v0.7.0
func StatusModeRow(StatusView, Words) string
StatusModeRow is the value of the former mode row: lockhub decides every request.
Deprecated: auto mode is gone and the console's :status has no mode row; it goes in the next minor release.
func StatusPendingRow ¶ added in v0.7.0
func StatusPendingRow(v StatusView) string
StatusPendingRow is the value of the policy change row.
func StatusResultsRow ¶ added in v0.7.0
func StatusResultsRow(v StatusView, w Words) string
StatusResultsRow is the value of the results row.
func StatusUnmaskRow ¶ added in v0.7.0
func StatusUnmaskRow(v StatusView) string
StatusUnmaskRow is the value of the unmask row.
func WithdrawnResp
deprecated
added in
v0.4.0
Types ¶
type Approval ¶
type Approval struct {
Approved bool
// Auto is never set: auto mode is gone.
//
// Deprecated: it goes in the next minor release.
Auto bool
// Retype: the human typed Screen.Retype.
Retype bool
// Refusal is the answer to send when neither Approved nor Retype: the
// human denied (with the reason they typed after "n"), the approval
// timed out, the client left, or no finger matched. For a Pending
// request it is the pending answer (PendingResp).
Refusal ipc.Response
// Pending: an AsyncApprover took the request; it is decided elsewhere.
Pending *Pending
}
Approval is the outcome of Env.Approve.
type Approver ¶
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.
func NewDesktopTypedApprover ¶
NewDesktopTypedApprover is the level-1 approver: typed answers in a window the agent can type into, audited apart from the session shape.
func NewFingerprintApprover ¶
func NewFingerprintApprover(io IO, v fingerprint.Verifier, finger string, deadline time.Duration) Approver
NewFingerprintApprover wraps the typed approval with a fingerprint of the dedicated finger (system.toml `finger`), named in the prompt (level 2).
func NewTypedApprover ¶
NewTypedApprover is the session approver: typed answers in a login session of the console account, audited as "session".
type Ask ¶ added in v0.3.0
type Ask struct {
// Frame is the colour of the screen's frame.
Frame string
// Production asks for the profile name typed instead of "y".
Production bool
// Retype, when set, is an answer that asks to retype values; Ask then
// returns Approval.Retype. RetypeNote follows it in the prompt:
// "(<Retype> to retype<RetypeNote>) ". An answer equal to the approval
// answer is not offered.
Retype, RetypeNote string
// For names the decision in the fingerprint prompt (Decision.For); ""
// is "to approve the request above".
For string
// Screen is the approval screen (Decision.Screen).
Screen *Screen
}
Ask is the prompt that closes an approval screen.
type Asker ¶ added in v0.3.0
type Asker struct {
IO IO
Approver Approver
// Profile is the profile name, typed to approve on production.
Profile string
// Lock names the product in a pending answer (kit.Product.Name).
Lock string
// End ends the session (too many non-matching fingers); nil: ignored.
End func(reason string)
// Waiting, when set, is told when a decision starts (true) and ends
// (false) waiting for the human.
Waiting func(on bool)
}
Asker takes the human decisions of a console: it closes an approval screen with the prompt and turns the answer into an Approval. Server uses one; a product that draws its own approval screen (and prompts inside it, for values to type) uses one too, so that every approval is decided in one place.
func AskerOf ¶ added in v0.7.0
AskerOf returns the Asker of the console behind env: a Handler that draws its own approval screen (prompts inside the frame, values to type) decides with it, so that a switch pressed meanwhile is refused and a missing finger ends the session as with Env.Approve.
type AsyncApprover ¶ added in v0.3.0
type AsyncApprover interface {
Approver
// Submit hands d over. Pending.Ticket is set even when err is not nil
// (the hub refused it or is unreachable): the request stays pending,
// is never asked on the terminal instead, and the approver pushes the
// ticket again.
Submit(ctx context.Context, d Decision) (Pending, error)
}
AsyncApprover is an Approver whose decisions are taken elsewhere (by lockhub): Submit hands the request over and returns at once, and the request is answered "pending" instead of waiting for a human here. The decision comes back later, through the hub, never through this console. Approve still takes the console's own decisions (a policy change, unmask) on the terminal.
func PolicyOnlyApprover ¶ added in v0.9.1
func PolicyOnlyApprover(base Approver) AsyncApprover
PolicyOnlyApprover is the approver of a policy-only console (Options.PolicyOnly): every request (a Decision with a Screen, through Asker.Ask) is refused with ErrPolicyOnly, never asked on the terminal nor approved; the console's own decisions (a policy change, unmask) go to base. Run uses it; a product with its own console lifecycle uses it in place of Hub.Approver.
type Backend ¶
type Backend interface {
// Methods are the product's IPC methods, beyond the console's own
// (hello, status, change.request, logout). A name the console serves
// is refused at start.
Methods() map[string]Handler
// Connect opens a session with the credentials the console obtained
// (asked, from the OS keychain, or from Vault) to the policy in force
// (c.Policy). It is called at start and again after a lost connection
// or an expired login. The console wipes c.Secret once Connect returns.
// The session handed back to the backend (Env.Session, Checks) may be
// wrapped: Unwrap returns the backend's own.
Connect(ctx context.Context, c Credentials) (Session, error)
// Checks audits a new session: what the backend account can do beyond
// the profile (a red warning, typed "continue" to go on), or a reason
// to refuse it. On a production profile every finding refuses.
Checks(ctx context.Context, s Session) []Finding
// LoadPolicy reads the current policy from the config files. The
// console calls it at start and every PolicyPoll.
LoadPolicy() (Policy, error)
// Status is the product's part of the status method's answer.
Status(s Session) any
}
Backend is what a product plugs into the console: its methods, its connection to the backend it guards, the checks of that connection and its policy. Everything else — the terminal, the isolation, the approval screen, the modes, the policy review, the session limits, the audit log, the socket — is the console's.
type Beller ¶
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 ChangeFormatter ¶ added in v0.7.0
ChangeFormatter is implemented by a backend that renders a policy diff itself (at start, on a tightening and at :review). Every value must be escaped, as FormatChanges does.
type ConnectionComparer ¶ added in v0.7.0
ConnectionComparer is implemented by a backend whose connection depends on more than Profile's Where, User, Host and Port (a TLS setting, a tunnel): a policy change for which ConnectionChanged reports true ends the session, as the open connection no longer matches the policy. It adds to the console's own check, which a change of those four fields always trips.
type Credentials ¶
type Credentials struct {
User string
// Secret is nil for a profile with credentials "none".
Secret []byte
// Policy is the approved policy in force, the one to connect to: a
// backend reads its endpoint from it, never from the config files (an
// unconfirmed edit). Reconnections pass the policy in force then. Nil
// when the console that calls Connect has none (a product's own tests).
Policy Policy
}
Credentials are what Connect needs: the user and its secret.
type Decision ¶
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
// For names the decision in the fingerprint prompt ("to approve the
// request"): a finger is never offered without saying what it decides.
For string
// Screen is the approval screen the decision is about (nil for the
// console's own decisions). A terminal approver ignores it: the screen
// is already on the terminal; an AsyncApprover sends it on.
Screen *Screen
}
Decision names what a human is approving.
type EndError ¶ added in v0.7.0
EndError, returned by ServerConfig.Reconnect (wrapped or not), ends the session with Reason.
type Env ¶
type Env interface {
// Approve shows s and hands it to the approver: under Run, lockhub
// decides (the answer is Pending). The decision is audited before
// Approve returns.
Approve(ctx context.Context, id int64, s Screen) Approval
// Auto is always false: auto mode is gone, lockhub decides.
//
// Deprecated: it goes in the next minor release.
Auto() bool
// ShowResults reports the clear results switch (Ctrl-O).
ShowResults() bool
// UnmaskAllowed reports whether clients may ask for unmasked output
// (:unmask), each such request approved by the human.
UnmaskAllowed() bool
// Session returns the live session, reconnecting first when the login
// expired (credentials_ttl, a Vault lease) or the connection was lost
// (Lost).
Session(ctx context.Context) (Session, error)
// Lost drops the session after a lost connection: the next Session
// reconnects.
Lost()
// Policy is the approved policy in force.
Policy() Policy
// Audit writes rec as is; AuditBase stamps b with the profile and the
// client behind ctx first.
Audit(rec audit.Record)
AuditBase(ctx context.Context, b audit.Base)
// Println writes one console line; Safe escapes client text for it.
Println(line string)
Safe(s string) string
// Sanitizer scrubs error text for a client or the audit log.
Sanitizer() *sanitize.Sanitizer
// Level levels a response time (Options.Quantum) from start.
Level(ctx context.Context, start time.Time)
// End ends the session after the current request.
End(reason string)
}
Env is what a Handler may use of the console.
type Finding ¶
type Finding struct {
// Refuse stops the console (or the reconnection) whatever the profile.
Refuse bool
Text string
}
Finding is one result of Checks.
type Handler ¶
Handler serves one product method. env is the console: the approval screen, the modes, the session, the audit log.
type Hub ¶ added in v0.3.0
type Hub struct {
Client *hubclient.Client
Store *plans.Store
Instance string
// contains filtered or unexported fields
}
Hub is a console's hub mode: the plans awaiting a decision and the hub client. A product runs a decided ticket with Client.Begin, then Client.Finish.
func HubOf ¶ added in v0.3.0
HubOf returns env's hub (nil only for a Server built without one, in tests).
func OpenHub ¶ added in v0.3.0
OpenHub opens the hub mode of product p: it checks o, reads the pinned hub key and the instance key, and opens the plans and the hub state under the product's state directory (stateDir as Options.StateDir; now nil is time.Now). Run calls it; a product with its own console lifecycle calls it too, then Approver and Watch.
func (*Hub) Approver ¶ added in v0.3.0
func (h *Hub) Approver(base Approver) AsyncApprover
Approver is the hub approver: requests (a Decision with a Screen) go to the hub, the console's own decisions to base.
func (*Hub) Close ¶ added in v0.3.0
Close releases the plans store (its directory lock): Run closes its hub when it returns.
func (*Hub) RenewResp ¶ added in v0.5.0
RenewResp answers the run of expired ticket tk (Client.Begin returned ticket.ErrExpired): the ticket is renewed (Client.Renew, decision H1) and the agent hears pending with the new ticket, which it runs once decided. A ticket that cannot be renewed answers CodeTimeout.
type HubOptions ¶ added in v0.3.0
type HubOptions struct {
// URL is the hub's base URL ("http://lockhub:8443").
URL string
// KeyFile holds the pinned hub key (hubclient.ReadHubKey).
KeyFile string
// Instance is the lock instance's name (hubclient.ValidInstance).
Instance string
// PlainHTTP lets URL be http:// to a hub on a network of its own
// (--hub-plain-http, hubclient.Config.PlainHTTP).
PlainHTTP bool
// DevicesFile holds the approval devices this lock pins
// (--devices-file, hubclient.ReadDevices): decisions taken on a device
// must carry its signature, and the clear screen is sealed to each.
DevicesFile string
// RequireDevice lists the risk classes whose approvals must be signed
// by a pinned device (--require-device, comma-separated).
RequireDevice string
// SealClear seals the clear screen to the pinned devices
// (--seal-clear, hubclient.Config.SealClear).
SealClear bool
// FacadeKeysFile holds the facade keys this lock pins
// (--facade-keys-file, hubclient.ReadFacades): a reveal (Ctrl-O on a
// local facade) is sealed only to a key one of them signed.
FacadeKeysFile string
// Home is the lock's home volume, where the instance key lives
// (hubclient.InstanceKey, made by `hub-key`); "" is the user's home.
Home string
}
HubOptions attach a console to its hub: requests are decided on lockhub, never on this terminal. They are the console flags `lox up` writes: --hub URL --hub-key-file PATH --instance NAME, all three required. There is no discovery and no standalone mode: a console started without them refuses to start (it is launched by `lox up` only).
func (HubOptions) Args ¶ added in v0.4.0
func (h HubOptions) Args() []string
Args are the flags of h, as Register reads them back: what a product that starts its console again (a handoff) passes on. An invalid --instance or --require-device goes as refusedValue. The hub URL goes as scheme://host[:port] only (hubclient.CanonicalURL), a command line being readable by every account of the machine; one OpenHub would refuse goes as refusedHubURL, which the new console refuses too.
func (HubOptions) Enabled ¶ added in v0.3.0
func (h HubOptions) Enabled() bool
Enabled reports whether any hub flag is set.
func (*HubOptions) Register ¶ added in v0.3.0
func (h *HubOptions) Register(fs *flag.FlagSet)
Register adds the hub flags to fs.
type HubTicket ¶ added in v0.3.0
type HubTicket struct {
// PlanID is the plan in Hub.Store, made by Caller.
PlanID string
Caller plans.Caller
Risk ticket.Risk
// Masked is the screen as it leaves the lock (render.MaskScreen).
Masked render.MaskedScreen
// Requester and Workspace are shown by the hub (display only).
Requester ticket.Requester
Workspace string
}
HubTicket is what a product adds to a Screen in hub mode: the plan the decision is about and the masked screen the hub shows. A Screen without one is refused in hub mode, never asked on the terminal.
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 Isolation ¶
type Isolation struct {
Product kit.Product
Sys *sysconf.Config
Display sysconf.Display
Self int
GID int
// Desktop is the desktop shape; Verifier is then its fingerprint
// reader (nil at level 1 and in the session shape).
Desktop bool
Verifier fingerprint.Verifier
// contains filtered or unexported fields
}
Isolation is how the console is kept apart from the agent: in a separated setup (sysconf) the console runs as the service account and serves the client group over a shared socket, either in its own login session (the session shape: the kernel keeps the agent out of its terminal) or in a terminal of the console account opened on the agent's desktop (the desktop shape: every approval also takes a fingerprint). Without one it refuses to start (except when IsolationEnv.AllowSameUser is set by a product's test build).
func CheckIsolation ¶
CheckIsolation applies the separation rules before any prompt and before the console writes any state. Warnings go to the terminal; a violation is an error. The production rule on X11 needs the profile and is applied later, by RefuseX11IfProduction.
func (*Isolation) Boundary ¶
Boundary names where the approval screen stands, for the status screen.
func (*Isolation) Level ¶
Level is the security level of the isolation: 2 for the desktop shape with a fingerprint reader, 1 for the desktop shape without, 0 for the session shape (the kernel is the boundary).
func (*Isolation) PeerCheck ¶
PeerCheck decides which peers are served: the console's own account and, in a separated setup, allowed_uids and the client group. The group is looked up at every connection: a uid removed from it loses access at its next connection, not at the next console start. Nil means the console's own uid only.
func (*Isolation) RefuseX11IfProduction ¶
RefuseX11IfProduction applies the production rule once the profile is known: a production profile never runs in an X11 session.
type IsolationEnv ¶
type IsolationEnv struct {
LoadSys func() (*sysconf.Config, error)
Display func() sysconf.Display
UID func() int
UserName func() (string, error)
LoginUID func() (int, bool)
TerminalOwner func() (int, bool)
ClientAllowed func(sys *sysconf.Config, uid int) bool
ClientGID func(sys *sysconf.Config) (int, error)
// AccountExists reports whether uid names an account.
AccountExists func(uid int) bool
// Relay reports a session opened through a relay (sysconf.SessionRelay).
Relay func() string
// MachinectlUIDs lists the callers of running `machinectl shell`
// processes (sysconf.MachinectlShellUIDs).
MachinectlUIDs func() ([]int, bool)
// DesktopEnv reports <PREFIX>DESKTOP=1: the desktop shape is asked for.
DesktopEnv func() bool
// DisplaySet reports a non-empty DISPLAY.
DisplaySet func() bool
// ParentChain lists the ancestors of the process, from the parent up
// (sysconf.ParentChain).
ParentChain func() ([]sysconf.Proc, error)
// NewVerifier opens the fingerprint reader of user.
NewVerifier func(user string) (fingerprint.Verifier, error)
// EnrolledTimeout bounds the start-up question to fprintd; past it, the
// reader counts as unavailable. 0 is 10s.
EnrolledTimeout time.Duration
// AllowSameUser lets the console run without a separated setup, in the
// agent's account. Only a product's test build sets it (a build tag):
// no config file, flag, environment variable or client may.
AllowSameUser bool
}
IsolationEnv holds what CheckIsolation reads from the system; tests replace its functions (RealIsolationEnv is the real one).
func RealIsolationEnv ¶
func RealIsolationEnv(p kit.Product, tty *os.File) IsolationEnv
RealIsolationEnv is the environment of a console of p on terminal tty (nil when unknown).
type Key ¶
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 ¶
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 Loop ¶
type Loop struct {
IO IO
// Handle serves one request (required).
Handle func(ctx context.Context, req ipc.Request) ipc.Response
// Command runs a line typed by the human between requests.
Command func(ctx context.Context, line string)
// Key handles a control key pressed between prompts; with a KeySource
// IO it also handles the keys pressed during a prompt.
Key func(Key)
// Tick runs every second (session timeouts).
Tick func()
// Poll runs every PolicyPoll (config file changes).
Poll func()
// Ended reports the end of the session (required); End ends it.
Ended func() bool
End func(reason string)
// Refresh runs after every event (the status row); Clear on return.
Refresh func()
Clear func()
// 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
}
Loop is the console's request loop: it accepts clients, checks their peer credentials and serves their requests one at a time on one goroutine, between the lines and control keys the human types and the periodic ticks. A request whose client goes away has its context cancelled, which cancels a pending approval prompt.
func (*Loop) 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 Options ¶
type Options struct {
// Profile is the profile to serve; ProjectRoot the project whose
// config holds it ("" for the user config).
Profile string
ProjectRoot string
IO IO
// AllowUnmask and ShowResults are the start-up switches (both refused
// at level 2; ShowResults refused at level 1 too).
AllowUnmask, ShowResults bool
// Auto is refused: auto mode is gone, lockhub decides every request.
//
// Deprecated: kept so that products still setting it build; Run
// refuses it. It goes in the next minor release.
Auto bool
// StateDir is the state base (kit.StateBase() when empty).
StateDir string
// TTY is the console's terminal device (nil when unknown).
TTY *os.File
// Now defaults to time.Now.
Now func() time.Time
// Quantum levels response times (Env.Level): 0 is ResponseQuantum, a
// negative value none.
Quantum time.Duration
// Sanitizer scrubs error text; nil is sanitize.New().
Sanitizer *sanitize.Sanitizer
// Isolation replaces the system the isolation checks read (tests and
// a product's test build); nil is RealIsolationEnv.
Isolation *IsolationEnv
// Harden replaces Harden (tests).
Harden func() error
// VaultRead replaces secrets.VaultRead (tests).
VaultRead func(ctx context.Context, path string) (*secrets.VaultCredentials, error)
// Keychain replaces the product's OS keychain (tests).
Keychain *secrets.Keychain
// Ready, when set, is told the socket path once the console listens.
Ready func(socket string)
// Hub decides requests on lockhub (HubOptions): required, Run refuses
// to start without it (ErrNoHub), unless PolicyOnly.
Hub HubOptions
// PolicyOnly runs a console that serves its policy and decides no
// request: the console of a lock whose requests are decided by another
// process's hub (lockhttp's, whose gate asks lockhub; `lox up` starts
// such a console without hub flags). It starts without Hub, which must
// then be empty (ErrPolicyOnlyHub), and without OnHub; every request a
// handler hands to Env.Approve is refused (ErrPolicyOnly), never asked
// here. The console's own decisions (its policy at start and at a
// loosening, :unmask) are still asked on its terminal.
PolicyOnly bool
// OnHub, when set, is called with the console's hub once it is open,
// before any request is served: where a product registers its
// hubclient.Recompute (hub.Client.SetRecompute). It runs before the
// policy review, so Run may still fail and close the hub; Run sets the
// client's OnRequest itself (Hub.Requests), replacing any set here.
OnHub func(*Hub)
}
Options configure Run.
type Outcome ¶
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 PIIBackend ¶
type PIIBackend interface {
// PIIRules returns the mask and allow rules of p.
PIIRules(p Policy) (mask, allow []string)
// CheckMask validates a pattern sent by a client and returns its
// canonical form; an error is shown to the client (invalid params).
CheckMask(pattern string) (string, error)
// SaveMask adds the canonical pattern to the config file as it is on
// disk, so the human's unconfirmed edits are neither lost nor applied.
SaveMask(pattern string) error
// WithMask returns p with the canonical pattern added (canonical form,
// sorted lists); p itself is not changed.
WithMask(p Policy, pattern string) Policy
}
PIIBackend is implemented by a backend whose policy has mask rules: the console then serves pii.list and pii.add for it. A pattern's syntax (db.table.column, a JSON path, a header name) is the product's. Adding a mask rule only tightens the policy, so it applies at once.
type Pending ¶ added in v0.3.0
type Pending struct {
// Ticket is the id the agent presents to run the request once decided.
Ticket string
// Instance is the lock instance the ticket is on (hub mode).
Instance string
}
Pending is a request handed to an AsyncApprover.
type Policy ¶
Policy is a product's policy: the diff and description of config.Policy, plus the profile the console needs. Its JSON encoding is what the console fingerprints and stores as the approved policy, so it must be canonical (sorted lists, defaults applied) and decode back into the same type.
type PolicyApplier ¶ added in v0.7.0
PolicyApplier is implemented by a backend that prepares itself for a policy before it is in force (compiles its rules). While the console runs, an error refuses the policy, which then is neither saved nor applied: a loosening stays out, and a tightening the backend cannot apply ends the session (it would otherwise serve under the looser one). On an error the backend must keep the policy it had (the console does not apply that one again). NewServer calls it with the start policy and fails on an error (Run has then saved the start policy already, and stops before serving).
type Profile ¶
type Profile struct {
Name string
Production bool
// Where names the backend for the human (host:port, a path): it is
// shown escaped.
Where string
// Host, Port and DefaultPort name the OS keychain item of the secret
// ("<profile>@<host>:<port>").
Host string
Port, DefaultPort int
// User is the backend account; "" asks the human (or comes from Vault).
User string
// Credentials is config.CredentialsAsk, CredentialsKeychain,
// CredentialsVault or CredentialsNone; VaultPath is read for "vault".
Credentials string
VaultPath string
// CredentialsTTL asks for the secret again (and reconnects) once the
// connection is that old; 0 keeps it for the session.
CredentialsTTL time.Duration
// IdleTimeout ends the session after that long without activity,
// MaxSession after that long in all. 0 takes 20m and 4h.
IdleTimeout, MaxSession time.Duration
}
Profile is what the console needs to know of a profile.
type Screen ¶
type Screen struct {
// Header follows the profile name on the top line ("db / shop · tier read").
Header string
// Requester names the client; "" takes the peer behind the request.
Requester string
// Note is the agent's one-line reason (untrusted, escaped, audited).
Note string
// Body are the lines of the request; Warnings are shown in red.
Body []string
Warnings []string
// Production forces the production prompt; the profile's own flag
// always does.
Production bool
// NeverAuto is never approved without a human's own decision on it,
// by the hub's auto mode or a renewal (an unmasked read, a
// destructive-looking operation): ticket.Flags.NeverAuto.
NeverAuto bool
// Red frames the screen in red outside production too.
Red bool
// Retype, when set, is an answer that asks to retype values; Approve
// then returns Approval.Retype.
Retype string
// Record, when set, turns the decision record into the product's own
// (with its fields); nil writes the audit.Base.
Record func(audit.Base) audit.Record
// Hub is the plan and masked screen for the hub, in hub mode
// (HubOf(env) not nil); a request without one is refused there.
Hub *HubTicket
}
Screen is the content of an approval screen, entirely supplied by the product. The console frames it (red on production), closes it with the prompt and keeps the rules: typed profile name on production, timeout, fingerprint at level 2, audit of the decision.
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) Approve ¶
Approve implements Env: it frames sc, hands it to the approver (the hub, under Run) and audits the decision.
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.
func (*Server) Handle ¶
Handle serves one request. None of the console's methods loosens the policy.
func (*Server) PolicyPending ¶
PolicyPending reports a loosening awaiting :review: a product refuses requests meanwhile (ipc.CodePolicyPending) when its policy matters to them.
func (*Server) PolicyRefused ¶ added in v0.7.0
PolicyRefused reports a loosening in the config files that the human refused (at start or at :review) and that is still there: the last approved policy applies, which a product may tell its clients.
func (*Server) Status ¶
func (s *Server) Status() StatusResult
Status is the status method's answer (without StatusAnswerer).
func (*Server) Tick ¶
func (s *Server) Tick()
Tick ends the session on idle or maximum session timeout, as set by the policy in force.
func (*Server) WriteAudit ¶ added in v0.7.0
WriteAudit writes rec as is, like Env.Audit (no profile, peer or AuditRecord stamping: rec is the product's own record), and returns the failure: a product that records an approval itself refuses the request when it cannot be written (fail closed). Failures count towards ending the session as for the console's own records.
type ServerConfig ¶
type ServerConfig struct {
Product kit.Product
Backend Backend
// Policy is the approved policy the server enforces.
Policy Policy
// StateDir and ApprovedKey locate the approved policy file ("" keeps
// adoptions in memory).
StateDir string
ApprovedKey string
Session Session
Audit *audit.Log
IO IO
// Approver takes every human decision; nil means typed approvals.
Approver Approver
// Level is the isolation level (2: every decision takes a finger, and
// a typed answer alone never widens anything).
Level int
// Boundary names the desktop boundary for the status screen.
Boundary string
Now func() time.Time
// AllowUnmask and ShowResults are the switches at start.
AllowUnmask, ShowResults bool
// Auto is refused by NewServer: auto mode is gone, lockhub decides.
//
// Deprecated: kept so that products still setting it build; it goes
// in the next minor release.
Auto bool
// Reconnect opens a new session (credentials, Connect, Checks) under
// the profile in force; nil (and ReconnectPolicy nil) means a lost
// connection ends the console. An EndError it returns ends the session
// with its reason.
Reconnect func(ctx context.Context, p Profile) (Session, error)
// ReconnectPolicy, when set, is used instead of Reconnect and gets the
// whole policy in force.
ReconnectPolicy func(ctx context.Context, p Policy) (Session, error)
// Quantum levels response times (Env.Level).
Quantum time.Duration
// PeerAllowed decides which clients are served; nil: the console's own
// account.
PeerAllowed func(ipc.Cred) bool
// Sanitizer scrubs error text; nil is sanitize.New().
Sanitizer *sanitize.Sanitizer
// Refused is the fingerprint of a loosening the human refused at start,
// not announced again until the files change.
Refused string
// RefuseProduction re-applies the start-up refusals of a production
// profile (an X11 session, core dumps that could not be disabled) when
// the policy in force becomes production; nil refuses nothing.
RefuseProduction func() error
// Hub is the console's hub (Run sets it, HubOptions being required);
// nil for a policy-only console (Options.PolicyOnly) and in tests of
// the server itself.
Hub *Hub
}
ServerConfig holds what a Server needs once the start-up sequence is done. Run builds it; tests build it directly.
type Sized ¶
type Sized interface {
Width() int
}
Sized is implemented by an IO that knows its width in columns.
type StatusAnswerer ¶ added in v0.7.0
type StatusAnswerer interface {
StatusAnswer(c StatusResult, s Session) any
}
StatusAnswerer is implemented by a backend that answers the status method itself: its StatusAnswer value is sent instead of StatusResult with Backend.Status under "product" (a product whose clients read a flat answer). c is the console's part; s is nil while disconnected.
type StatusLine ¶
type StatusLine interface {
SetStatus(string)
}
StatusLine is implemented by an IO that keeps a status row on screen.
type StatusLiner ¶ added in v0.7.0
type StatusLiner interface {
StatusLineParts(v StatusView) (suffix string, parts []string)
}
StatusLiner is implemented by a backend that adds to the status row: Suffix follows the profile name, Parts come after the results switch.
type StatusResult ¶
type StatusResult struct {
Profile string `json:"profile"`
Production bool `json:"production"`
// AutoApprove is always false: auto mode is gone, lockhub decides.
//
// Deprecated: kept on the wire for older clients; it goes in the next
// minor release.
AutoApprove bool `json:"auto_approve"`
AllowUnmask bool `json:"allow_unmask"`
ShowResults bool `json:"show_results"`
IdleTimeoutInS int `json:"idle_timeout_in_s"`
SessionEndsInS int `json:"session_ends_in_s"`
// Level is the security level: 0 session shape, 1 or 2 desktop shape.
Level int `json:"level"`
Product any `json:"product,omitempty"`
}
StatusResult answers the status method: the console's part, and the product's (Backend.Status) under "product".
type StatusRower ¶ added in v0.7.0
type StatusRower interface {
StatusRows(v StatusView) [][2]string
}
StatusRower is implemented by a backend that lists its own :status rows (label, value; values escaped by the backend). The console aligns them.
type StatusView ¶ added in v0.7.0
type StatusView struct {
// Status is the console's part of the status answer (Product unset:
// the status row is drawn every second, Backend.Status is not called
// for it).
Status StatusResult
// Profile and Policy are the approved policy in force.
Profile Profile
Policy Policy
// ShowResults and AllowUnmask are the switches.
ShowResults, AllowUnmask bool
// Auto is always false: auto mode is gone, lockhub decides.
//
// Deprecated: it goes in the next minor release.
Auto bool
// Pending: a loosening awaits :review. Requests counts the change
// requests queued.
Pending bool
Requests int
// Boundary names the desktop boundary ("" in the session shape); Level
// is the security level (2: every decision takes a finger).
Boundary string
Level int
// Session is the live session, nil while disconnected.
Session Session
}
StatusView is what the console knows when it draws :status or the status row, for the product hooks that draw them.
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 ¶
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) Restore ¶
func (t *Terminal) Restore()
Restore ends the status line and puts the terminal back in the mode it had before Interactive.
func (*Terminal) SetKeyHandler ¶
SetKeyHandler sets the function that handles a control key pressed while a prompt waits. It runs on the goroutine that asked.
type Withdrawer ¶ added in v0.3.0
type Withdrawer interface {
Withdraw(ctx context.Context, d Decision, ticket, reason string) error
}
Withdrawer is an AsyncApprover that can take back a ticket it submitted: a pending request the console could not record is withdrawn.
type Withdrawn
deprecated
added in
v0.4.0
type Withdrawn struct {
Instance string
// Tickets are the tickets withdrawn now.
Tickets []string
}
Withdrawn is what a standalone start withdrew from the hub mode of one instance.
Deprecated: there is no standalone start any more.
func WithdrawStandalone
deprecated
added in
v0.4.0
WithdrawStandalone was the rollback of hub mode (a console started without --hub after running with it). It is inert now: it withdraws nothing, touches no file and returns nil, nil. A console is hub-attached only (or policy-only, without plans), so there is no standalone start whose withdrawals it would do, and a call left in a product must not end the pending plans of a hub instance whose console is merely stopped.
Deprecated: there is no standalone start any more; Run no longer calls it. It goes in the next minor release.
type Worder ¶ added in v0.7.0
type Worder interface {
Words() Words
}
Worder is implemented by a backend that words the console's lines in its product's terms (statements rather than requests). An empty field keeps the console's own text.
type Words ¶ added in v0.7.0
type Words struct {
// AutoStart, AutoOn, Manual, NoAutoProduction, NoAutoHub, StatusAuto,
// StatusManual and ReviewAuto are never printed: auto mode is gone,
// lockhub decides every request.
//
// Deprecated: kept so that products still setting them build; they go
// in the next minor release.
AutoStart, AutoOn, Manual, NoAutoProduction, NoAutoHub, StatusAuto, StatusManual, ReviewAuto string
// ResultsOn is printed when the clear results are switched on;
// StatusResults is their row of :status when on.
ResultsOn, StatusResults string
// UnmaskAsk is the question that allows unmasked output; UnmaskOff is
// printed when it is turned off.
UnmaskAsk, UnmaskOff string
// ChangeQueued words a change request from a client (already escaped).
ChangeQueued func(change string) string
// Reconnecting is printed before a reconnection.
Reconnecting string
// TTLReached is printed when credentials_ttl ends a connection.
TTLReached func(ttl time.Duration, credentials string) string
// ProductionChecks words the end of a session whose checks found
// something once the profile became production: the console line and
// the end reason.
ProductionChecks func(p Profile, findings []Finding) (line, reason string)
// ConnLost is the error a Handler gets from Env.Session when the
// connection is lost and cannot be opened again.
ConnLost string
}
Words are the console lines a product may word itself. Each is the whole line, colours excepted; an empty one keeps the console's text. The console prints them as they are: a line built from config or server text (a finding, a credentials mode, a change) must be escaped by the product (sanitize.Terminal), as the console's own lines are.