sshpool

package
v1.35.0 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: GPL-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package sshpool dials an instance's SSH endpoint with its stored credentials and reports how the presented host key relates to the pinned one. Nothing here writes to the database: pinning is the caller's decision, taken on the report this package returns.

Index

Constants

View Source
const HardlinkExtension = "hardlink@openssh.com"

HardlinkExtension is the sftp extension a server advertises when it can make hard links.

Variables

View Source
var ErrConnect = errors.New("could not connect to the SSH host")

ErrConnect marks a failure to reach the host or complete the handshake, as opposed to a fault in what qui stored: the caller answers the two differently, and only the first earns an unreadable pin its precedence.

View Source
var ErrNoExec = errors.New("the connection does not have the exec tier")

ErrNoExec answers an exec on a connection whose probe did not find the exec tier, for example because the key lost its shell since the stored tier was written.

View Source
var ErrNotRemote = errors.New("instance is not in remote filesystem mode")

ErrNotRemote answers a dial for an instance whose row is not in remote filesystem mode. It is not memoised: the row changes without the host doing anything, and the next dial reads it again.

View Source
var ErrOutputLimit = errors.New("command output exceeds the limit")

ErrOutputLimit answers a command that printed more than Output keeps.

View Source
var ErrPinUnusable = errors.New("host key pin is unusable")

ErrPinUnusable marks the errors Connect raises before it dials, when the stored pin is missing or will not decrypt or parse. The pool dials only rows in remote mode, which carry a pin, so there it means a pin that will not decrypt or parse. That lasts until the pin changes, which is why the pool tells it apart from a host that is merely down.

View Source
var ErrPoolClosed = errors.New("ssh pool is closed")

ErrPoolClosed answers a caller that arrives after shutdown began: nothing may dial once Close has run.

Functions

This section is empty.

Types

type Capabilities

type Capabilities struct {
	SFTP     bool
	Statvfs  bool
	Hardlink bool
	Limits   bool
	Exec     bool
	// POSIXShell is a login shell that reads a single-quoted word the POSIX
	// way. Every remote command is quoted for that shell, so a shell that
	// treats a quote differently (fish escapes a closing quote with the
	// backslash before it, tcsh rejects a newline inside a quote and reads the
	// next line as a new command) could run a path as a command, and gets no
	// exec tier.
	POSIXShell  bool
	GNUUserland bool
}

Capabilities is what the server lets us do, probed read-only.

func (*Capabilities) Tier added in v1.33.0

func (c *Capabilities) Tier() models.SSHTier

Tier is the tier these capabilities give: exec needs sftp and a POSIX shell with the GNU tools, and every other host gets sftp. Every pooled connection opens sftp first, so exec without sftp serves nothing.

type Command added in v1.33.0

type Command struct {
	Name    string
	Options []string
	Paths   []string
	// Expression comes after the paths, for find.
	Expression []string
}

Command is one remote command. String quotes every word and puts "--" between the options and the paths, so a raw path never reaches the shell and a path that starts with "-" is never read as an option.

func (Command) String added in v1.33.0

func (c Command) String() string

String is the command line, run under LC_ALL=C so the output does not change with the locale of the host.

type Conn added in v1.33.0

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

Conn runs remote commands on one connection, at most cap(slots) at a time. Pool.Exec gives out a Conn only when the probe of its connection found the exec tier. The probes build their own, before any tier is known.

func (*Conn) Output added in v1.33.0

func (c *Conn) Output(ctx context.Context, cmd Command) ([]byte, error)

Output runs cmd and returns its standard output, at most execOutputLimit bytes, within execTimeout.

func (*Conn) Stream added in v1.33.0

func (c *Conn) Stream(ctx context.Context, cmd Command, stdout io.Writer) error

Stream runs cmd and copies its standard output to stdout. Only ctx ends it.

type Dialer

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

Dialer opens SSH connections: one-shot ones for Test and Confirm, and the long-lived ones Pool keeps through Connect.

func NewDialer

func NewDialer(creds credentialSource) *Dialer

func (*Dialer) Confirm

func (d *Dialer) Confirm(ctx context.Context, inst *models.Instance, hostKey []byte) (*Capabilities, error)

Confirm dials the instance and succeeds only if the host presents hostKey, so the caller can pin the key the user was shown rather than whatever the host offers at pin time. hostKey is in SSH wire format, the form the pin column holds. It then probes the host, on the key it just verified, so the caller can store the tier with the pin.

func (*Dialer) Connect added in v1.31.0

func (d *Dialer) Connect(ctx context.Context, inst *models.Instance) (*ssh.Client, error)

Connect opens a long-lived connection to a host whose key is already pinned. A background connection has no first contact and nobody to confirm one, so unpinned and unreadable both refuse rather than trust what the host presents.

func (*Dialer) Test

func (d *Dialer) Test(ctx context.Context, inst *models.Instance) (*Report, error)

Test dials the instance and reports the host key, its relation to the pin, and — unless the key mismatched or the pin is unreadable — what the server can do.

type MismatchError

type MismatchError struct {
	Presented ssh.PublicKey
	Pinned    ssh.PublicKey
}

MismatchError reports that the host presented a key other than the expected one. Error prints fingerprints only, so the message is safe to log; the fields carry the keys for callers that need them.

func (*MismatchError) Error

func (e *MismatchError) Error() string

type Pool added in v1.31.0

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

Pool keeps one SSH connection per instance, with one SFTP session and at most execSessions exec sessions on it.

func NewPool added in v1.31.0

func NewPool(dialer *Dialer) *Pool

func (*Pool) Close added in v1.31.0

func (p *Pool) Close()

Close drops every connection and refuses every later caller. Called once, at shutdown; a caller mid-dial holds its entry until that dial ends, which the dial timeout bounds.

func (*Pool) Exec added in v1.33.0

func (p *Pool) Exec(ctx context.Context, inst *models.Instance) (*Conn, error)

Exec returns the instance's connection for remote commands, dialing like SFTP. It answers ErrNoExec when the probe of the live connection did not find the exec tier, which happens when the key lost its shell after the caller read the stored tier.

func (*Pool) Invalidate added in v1.31.0

func (p *Pool) Invalidate(instanceID int)

Invalidate ends the instance's connection and forgets its memo. The credential, pin and instance update routes call it after they write the row, so the next dial reads the new values. A mismatch is re-memoised by the next dial, so a refusal survives a spurious call at the cost of one dial. Safe on a nil pool.

func (*Pool) Remove added in v1.31.0

func (p *Pool) Remove(instanceID int)

Remove is Invalidate for an instance that no longer exists: the entry goes with the connection.

func (*Pool) SFTP added in v1.31.0

func (p *Pool) SFTP(ctx context.Context, inst *models.Instance) (*sftp.Client, error)

SFTP returns the instance's SFTP client, dialing if there is no live one. A failed dial is memoised, so a job touching hundreds of paths against a down host pays for one connect attempt, not hundreds.

type Report

type Report struct {
	Status       Status
	HostKey      ssh.PublicKey
	PinnedKey    ssh.PublicKey // mismatch only
	Capabilities *Capabilities // nil on mismatch and pin_unreadable: no session is opened
}

Report is the outcome of Test. A mismatch is a result, not a failure: the user has to see both keys to decide whether the host was re-keyed or replaced.

type Status

type Status string

Status is how the host key the server presented relates to the stored pin.

const (
	StatusUnpinned Status = "unpinned"
	StatusPinned   Status = "pinned"
	StatusMismatch Status = "mismatch"
	// StatusPinUnreadable: the stored pin does not decrypt. The presented key
	// is reported so the user can replace the pin through the same heavy
	// confirmation a mismatch gets; nothing is trusted and nothing is probed.
	StatusPinUnreadable Status = "pin_unreadable"
)

Jump to

Keyboard shortcuts

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