gate

package
v0.8.1 Latest Latest
Warning

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

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

Documentation

Overview

Package gate is lockhttp's data plane: the HTTP(S) proxy every request of the agents goes through. It decides each request with the policy the console approved (it never reads a policy file), holds what needs a human, asks lockhub, and audits what it lets through or refuses.

Index

Constants

View Source
const ApprovalHubRun = "hub-run"

ApprovalHubRun is the approval of the record of a request run after the hub approved it within the hold. The decision itself is the record hubApprove writes (approval "hub", with the ticket, the approver and the hub's signature): lockhub's `lox audit verify --lock` takes every approved or denied record with approval "hub" for a hub decision, so the run must not carry that value (it would read as an unsigned decision, or a second approval of the ticket).

View Source
const AuditRetention = 30 * 24 * time.Hour

AuditRetention is how long the gate keeps its daily audit files.

View Source
const ContractVersion = 1

ContractVersion is the version of the files lea writes for the gate.

View Source
const MinSecretLen = 8

MinSecretLen is the shortest secret value the gate accepts: shorter values would match ordinary text in the leak check.

View Source
const RedirectTTL = 60 * time.Second

RedirectTTL is how long a listed CDN redirect target stays open.

View Source
const SecretsVersion = 1

SecretsVersion is the version of the gate's secrets file.

View Source
const SingleUseTTL = 10 * time.Minute

SingleUseTTL is how long a single-use authorisation waits for its request.

Variables

View Source
var ErrNoConsole = errors.New("no lockhttp console is running")

ErrNoConsole means no console answers.

Functions

func BodyFingerprint

func BodyFingerprint(contentType string, body []byte) string

BodyFingerprint is the normalised fingerprint of a body: multipart parts without their boundary, canonical JSON without volatile fields, else the raw bytes.

func Preview

func Preview(contentType string, body []byte) string

Preview is the masked start of a body for the approval screen (decision D-body): textual bodies only, personal data and secrets masked, at most wire.MaxPreview bytes.

func PushRefs

func PushRefs(body []byte) []string

PushRefs reads the ref updates of a git-receive-pack request body (pkt-lines "old new ref\0caps" up to the flush packet).

func RequestFingerprint

func RequestFingerprint(container, method, url string, h http.Header, bodyFP string) string

RequestFingerprint binds an ask (and a single-use authorisation) to a request: container, method, URL, media type, every end-to-end header and the normalised body.

Types

type AuditLog

type AuditLog struct {
	// contains filtered or unexported fields
}

AuditLog is the gate's JSONL audit of every decision it applies: FREE, DENY, and the outcome of each ask (the console audits the human's side): one file per UTC day, gate-YYYY-MM-DD.jsonl, kept 30 days. With no directory it writes to w (stderr) or nowhere.

func NewAuditLog

func NewAuditLog(dir string, w io.Writer, now func() time.Time) (*AuditLog, error)

NewAuditLog opens dir (created 0700) or, when dir is "", writes to w.

func (*AuditLog) Append

func (l *AuditLog) Append(rec wire.Record)

Append writes one record.

func (*AuditLog) Close

func (l *AuditLog) Close()

Close closes the current file.

type CertSource

type CertSource interface {
	Certificate(ctx context.Context, host string) (*tls.Certificate, error)
}

CertSource issues the leaf certificate presented to a client for host (MITM). The CA key stays with the signer.

type Config

type Config struct {
	Profile string
	// Upstream is the proxy the gate sends through (squid on loopback);
	// nil dials the target itself, refusing private addresses.
	Upstream *url.URL
	Sources  *Sources
	Creds    *Credentials
	// Secrets holds the values the policy's injections name (nil: none).
	Secrets *Secrets
	Console Console
	Audit   *AuditLog
	// StatusFile is rewritten when the console state or the pending
	// counts change ("" for none).
	StatusFile string
	// Certs enables MITM; nil decides CONNECTs at the domain.
	Certs CertSource
	// UpstreamTLS verifies the targets (nil: the system roots). The gate
	// never skips verification.
	UpstreamTLS *tls.Config
	// Dial replaces the network dialer (tests).
	Dial func(ctx context.Context, network, addr string) (net.Conn, error)
	// Resolve replaces the resolver of direct mode (tests).
	Resolve func(ctx context.Context, host string) ([]netip.Addr, error)
	// Hub sends the asks to lockhub (required: the gate is hub-attached
	// only); the console serves the policy.
	Hub *Hub
	// PolicyPoll is how often the policy is fetched (2 s by default).
	PolicyPoll time.Duration
	Now        func() time.Time
	// Log receives operational lines (not the audit).
	Log io.Writer
}

Config configures a gate.

type Console

type Console interface {
	Policy(ctx context.Context) (wire.PolicyResult, error)
}

Console is the gate's view of the console: the approved policy. The human's answers come from the hub (Hub).

type Credentials

type Credentials struct {
	// contains filtered or unexported fields
}

Credentials implements decide.Credentials from credentials.json; with no file the check is off. An unreadable file fails closed: every credential is unknown.

func NewCredentials

func NewCredentials(path string) *Credentials

NewCredentials loads path ("" for none).

func (*Credentials) Known

func (c *Credentials) Known(container, via, sum string) (bool, bool)

Known implements decide.Credentials.

func (*Credentials) Reload

func (c *Credentials) Reload()

Reload reads the file again when it changed.

type Gate

type Gate struct {
	// contains filtered or unexported fields
}

Gate is a running data plane.

func New

func New(cfg Config) *Gate

New returns a gate.

func (*Gate) Policy

func (g *Gate) Policy() (*policy.Policy, string)

Policy returns the policy in force (nil: none received yet) and its fingerprint.

func (*Gate) Run

func (g *Gate) Run(ctx context.Context)

Run polls the policy, runs the ask queue and reloads the source files until ctx ends.

func (*Gate) Serve

func (g *Gate) Serve(ctx context.Context, ln net.Listener) error

Serve accepts connections on ln until it is closed.

func (*Gate) SetPolicy

func (g *Gate) SetPolicy(p *policy.Policy, fp string)

SetPolicy installs a policy (tests and the poller).

func (*Gate) Stopped added in v0.3.0

func (g *Gate) Stopped() <-chan struct{}

Stopped is closed once Run's queue stopped and every ask it was answering ended (in hub mode, their tickets withdrawn).

type Grants

type Grants struct {
	// contains filtered or unexported fields
}

Grants holds what approvals open: connect grants (container × host × port, for the policy's connect_grant), single-use authorisations (request fingerprint, 10 min) and redirect exemptions (container × exact URL, 60 s, GET only).

func NewGrants

func NewGrants(now func() time.Time) *Grants

NewGrants returns empty grants.

func (*Grants) Connect

func (g *Grants) Connect(container, hostport string) bool

Connect reports a live connect grant.

func (*Grants) GrantConnect

func (g *Grants) GrantConnect(container, hostport string, d time.Duration)

GrantConnect opens container × host:port for d.

func (*Grants) GrantOnce

func (g *Grants) GrantOnce(fp string)

GrantOnce adds a single-use authorisation for fingerprint fp.

func (*Grants) GrantRedirect

func (g *Grants) GrantRedirect(container, url string)

GrantRedirect opens the exact URL for container's next GET.

func (*Grants) Sweep

func (g *Grants) Sweep()

Sweep drops expired entries.

func (*Grants) UseOnce

func (g *Grants) UseOnce(fp string) bool

UseOnce consumes the single-use authorisation of fp.

func (*Grants) UseRedirect

func (g *Grants) UseRedirect(container, url string) bool

UseRedirect consumes a redirect exemption.

type Hub added in v0.3.0

type Hub struct {
	Client *hubclient.Client
	Plans  *plans.Store
	// Hosts are the names of the hub on its network and Nets its
	// addresses: the gate refuses them as targets, whatever internal_hosts
	// allows, so that an agent never reaches the hub through the gate.
	Hosts []string
	Nets  []netip.Prefix
	// Poll is how often a held ask looks for its decision (1 s by
	// default); the decisions arrive by the client's long poll.
	Poll time.Duration
	// contains filtered or unexported fields
}

Hub sends the gate's asks to lockhub instead of the console: each held request becomes a plan and a signed ticket, and a decision that verifies under the pinned hub key grants the same single-use pass (or connect grant) a console approval does. The console still serves the policy.

func (*Hub) Close added in v0.3.0

func (h *Hub) Close() error

Close closes the plan store (it holds its directory for this process): after the gate stopped (Gate.Stopped), never while asks wait.

type LockkitConsole

type LockkitConsole struct {
	Profile string
	// Cwd is where the client looks for a project (none: "/").
	Cwd string
	// contains filtered or unexported fields
}

LockkitConsole dials the console with lockkit's client, for the policy poll.

func (*LockkitConsole) Policy

Policy implements Console.

type Outcome

type Outcome struct {
	Approved bool
	Code     string
	Reason   string
	// Ticket is the hub ticket an E_PENDING ask waits on (hub mode).
	Ticket string
}

Outcome is the answer to an ask.

type Queue

type Queue struct {
	// contains filtered or unexported fields
}

Queue holds the asks and sends them to the hub, side by side (a hub decision may take hours). A human decision is shared only by requests of identical fingerprint; requests under the same key with another fingerprint are separate items.

func NewQueue

func NewQueue(approve func(ctx context.Context, fp string, p wire.ApproveParams) (wire.ApproveResult, error)) *Queue

NewQueue returns a queue answering its items with approve.

func (*Queue) Pending

func (q *Queue) Pending() int

Pending is the number of queued items.

func (*Queue) Run

func (q *Queue) Run(ctx context.Context)

Run sends the queued items until ctx ends.

func (*Queue) Submit

func (q *Queue) Submit(fp, key string, p wire.ApproveParams, onApprove func(clientGone bool)) (<-chan Outcome, func())

Submit adds a waiter for the request of fingerprint fp. It returns the channel the outcome arrives on and a cancel func the waiter calls when it stops waiting (the item stays queued).

type Refusal

type Refusal struct {
	Code   string
	Reason string
	// Ticket is the hub ticket the refusal is about, "" for none.
	Ticket string
}

Refusal is the refusal of an approval (by the hub, or a request the gate cannot ask about).

func (*Refusal) Error

func (r *Refusal) Error() string

type Secrets added in v0.8.0

type Secrets struct {
	// contains filtered or unexported fields
}

Secrets holds the values of the gate's secrets file, reloaded when the file changes. An unreadable, group- or world-readable or invalid file is broken: every injection fails closed (E_INJECT), and the values last loaded stay in the leak check.

func NewSecrets added in v0.8.0

func NewSecrets(path string) *Secrets

NewSecrets loads path ("" for none: no injection, no leak check).

func (*Secrets) Active added in v0.8.0

func (s *Secrets) Active() bool

Active reports whether the gate holds any secret value: the streams the agent sends are then scanned for them.

func (*Secrets) Err added in v0.8.0

func (s *Secrets) Err() error

Err is the file's last load error (nil when it loaded, or with no file).

func (*Secrets) Forms added in v0.8.0

func (s *Secrets) Forms() *formSet

Forms returns the forms of every value ever loaded (nil with none).

func (*Secrets) Leak added in v0.8.0

func (s *Secrets) Leak(parts ...[]byte) (string, bool)

Leak reports the name of a secret found in what the agent sent: its path, query, header values or held body, as is, URL-escaped (in any case), hex or base64 (at any alignment). The agent never holds an injected secret: one in its request is refused, whatever the host.

func (*Secrets) LeakNames added in v0.8.0

func (s *Secrets) LeakNames(hosts, headerNames []string) (name, why string)

LeakNames is Leak for the parts that are names: the hosts (the absolute URL's, the Host header, a CONNECT authority) and the header names, matched ignoring case, dots and percent-escapes (see formSet.findFold). While the gate holds a secret, a host longer than maxHostName or a header name longer than maxFoldName is not scanned: why says so, and the request is refused. name is the secret found.

func (*Secrets) Reload added in v0.8.0

func (s *Secrets) Reload()

Reload reads the file again when it changed.

func (*Secrets) Value added in v0.8.0

func (s *Secrets) Value(name string) (string, error)

Value returns the secret named name.

type Sources

type Sources struct {
	// contains filtered or unexported fields
}

Sources resolves who sent a connection, from the sources.json lea writes. With no file, every connection is container "local".

func NewSources

func NewSources(path string) *Sources

NewSources loads path ("" for none: every connection is "local").

func (*Sources) Err

func (s *Sources) Err() error

Err is the load error, if any.

func (*Sources) Reload

func (s *Sources) Reload()

Reload reads the file again when it changed. A file that cannot be read or parsed fails closed: every lookup is refused until it is fixed.

func (*Sources) Resolve

func (s *Sources) Resolve(local netip.Addr, user, token, leaSource string) (wire.Source, error)

Resolve names the source of a request that arrived on local address local, with the agent and token of its Proxy-Authorization (if any) and the X-Lea-Source header (keyproxy only).

type Status

type Status struct {
	Version            int            `json:"version"`
	Console            string         `json:"console"`
	Pending            int            `json:"pending"`
	PendingByContainer map[string]int `json:"pending_by_container"`
	PolicyFP           string         `json:"policy_fp,omitempty"`
	Updated            string         `json:"updated"`
}

Status is the status file lea reads (docs/lea.md §5).

Jump to

Keyboard shortcuts

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