Documentation
¶
Overview ¶
Package sshclient implements the SSH transport: authentication, host key verification, proxy traversal and interactive sessions.
The connection method follows zsuroy/ctty's internal/sftpconfig: a trust-on-first-use known_hosts callback, already-trusted host key algorithms offered first, and an authentication chain of agent, identity file, default keys and finally password / keyboard-interactive.
Index ¶
- Constants
- func AcceptNewHostKeyCallback(knownHostsPath string) (ssh.HostKeyCallback, error)
- func DefaultKnownHostsPath() (string, error)
- func ExitCode(err error) int
- func HostKeyAlgorithms(knownHostsPath string, addrs ...string) []string
- func IsAuthFailure(err error) bool
- func RunCommand(ctx context.Context, client *ssh.Client, command []string, ...) error
- type Client
- type DialOptions
- type Direction
- type ExitError
- type InteractiveSession
- type Plan
- type Progress
- type ProgressFunc
- type SFTPClient
- type TransferItem
Constants ¶
const DefaultDialTimeout = 15 * time.Second
DefaultDialTimeout bounds the TCP and handshake phases of a connection.
Variables ¶
This section is empty.
Functions ¶
func AcceptNewHostKeyCallback ¶
func AcceptNewHostKeyCallback(knownHostsPath string) (ssh.HostKeyCallback, error)
AcceptNewHostKeyCallback returns a trust-on-first-use callback: a host already present in known_hosts must match exactly, an unknown host is recorded, and a changed key is rejected. This mirrors OpenSSH's StrictHostKeyChecking=accept-new.
func DefaultKnownHostsPath ¶
DefaultKnownHostsPath returns ~/.ssh/known_hosts.
func ExitCode ¶
ExitCode extracts the remote exit status from err, defaulting to 1 for any non-exit failure so callers can map it straight onto os.Exit.
func HostKeyAlgorithms ¶
HostKeyAlgorithms returns the host key types already trusted for the given dial targets. Offering these first stops a server from presenting a different key type than the one recorded, which would look like a mismatch (golang/go#29286).
func IsAuthFailure ¶
IsAuthFailure reports whether err came from the server rejecting our credentials, as opposed to a network or host key problem. Callers use this to decide whether prompting for a password is worth a retry.
func RunCommand ¶
func RunCommand(ctx context.Context, client *ssh.Client, command []string, stdout, stderr io.Writer) error
RunCommand executes command on the remote host without a PTY and streams its output. The command is a positional vector, not a shell string, and is joined with spaces for the remote shell exactly as OpenSSH does when given trailing arguments.
A non-zero remote exit status is returned as an *ExitError.
Types ¶
type Client ¶
Client is an established SSH connection to a single host.
type DialOptions ¶
type DialOptions struct {
// Password enables password and keyboard-interactive auth. It also unlocks
// passphrase-protected private keys.
Password string
// DisableAgent skips ~/.ssh/ssh-agent discovery.
DisableAgent bool
// KnownHosts overrides the default ~/.ssh/known_hosts path.
KnownHosts string
// InsecureIgnoreHostKey skips host key verification entirely. Intended for
// throwaway test hosts; it makes the connection vulnerable to interception.
InsecureIgnoreHostKey bool
// Timeout bounds the TCP connect and SSH handshake. Zero means DefaultDialTimeout.
Timeout time.Duration
// ProxyJump overrides the host config's ProxyJump.
ProxyJump string
// ProxyCommand overrides the host config's ProxyCommand.
ProxyCommand string
// LookupHost resolves a ProxyJump alias against parsed SSH config. When nil,
// the alias is treated as a literal [user@]host[:port].
LookupHost func(alias string) (config.SSHHost, bool)
}
DialOptions tunes how a connection is established.
type InteractiveSession ¶
type InteractiveSession struct {
// contains filtered or unexported fields
}
InteractiveSession runs a login shell or a single command on a remote host attached to the local terminal.
It implements bubbletea's ExecCommand interface, so a TUI can hand the terminal over to it with tea.Exec and reclaim it once the session ends.
func NewInteractiveSession ¶
func NewInteractiveSession(client *ssh.Client, command []string) *InteractiveSession
NewInteractiveSession prepares a session. An empty command opens the login shell; otherwise the command runs under a PTY like `ssh -t`.
func (*InteractiveSession) Run ¶
func (s *InteractiveSession) Run() error
Run opens the session and blocks until it ends. The remote exit status is returned as an *ExitError so the caller can propagate it.
func (*InteractiveSession) SetStderr ¶
func (s *InteractiveSession) SetStderr(w io.Writer)
SetStderr satisfies bubbletea.ExecCommand.
func (*InteractiveSession) SetStdin ¶
func (s *InteractiveSession) SetStdin(r io.Reader)
SetStdin satisfies bubbletea.ExecCommand.
func (*InteractiveSession) SetStdout ¶
func (s *InteractiveSession) SetStdout(w io.Writer)
SetStdout satisfies bubbletea.ExecCommand.
type Plan ¶
type Plan struct {
Direction Direction
Items []TransferItem
// Dirs are the directories the transfer has to create before it can write,
// parents first. They are remote paths for an upload and local ones for a
// download: the direction decides which end they belong to.
Dirs []string
// Bytes is the total the items add up to.
Bytes int64
}
Plan is a transfer measured before any byte moves.
Measuring first is what gives the overall progress bar a denominator. It also means a path that cannot be read is reported while nothing has been written yet, rather than half way through.
type Progress ¶
type Progress struct {
File string
FileDone int64
FileTotal int64
// Done marks the report that closes a file, so a renderer can be sure the
// file's bar reaches the end and a throttled reporter knows to let it past.
Done bool
TotalDone int64
TotalBytes int64
FilesDone int
FilesTotal int
Rate float64 // bytes per second
ETA time.Duration // zero until the rate means something
}
Progress is one report of how a transfer is going.
It carries the file and the whole transfer at once. The rate and the ETA are worked out here rather than by each caller, so the command line and the browser show the same numbers from the same arithmetic.
type ProgressFunc ¶
type ProgressFunc func(Progress)
ProgressFunc receives progress reports. The copy loop calls it between chunks, so an implementation must not block for long.
type SFTPClient ¶
SFTPClient is an SFTP session riding on an established SSH connection.
It does not own that connection: Close shuts the SFTP channel and leaves the SSH client alone, because the same connection may outlive it — and because sshclient.Client.Close is what releases a ProxyJump's bastions.
func NewSFTPClient ¶
func NewSFTPClient(client *Client) (*SFTPClient, error)
NewSFTPClient opens an SFTP session on client.
The subsystem is tried first, which is what a properly configured host answers to. Hosts whose sshd_config carries no "Subsystem sftp" refuse that request, so sftp-server is then started by name and its stdio handed to the SFTP client instead.
func (*SFTPClient) Close ¶
func (c *SFTPClient) Close() error
Close shuts the SFTP session. The SSH connection underneath is left open.
func (*SFTPClient) PlanDownload ¶
func (c *SFTPClient) PlanDownload(remotePath, localPath string) (*Plan, error)
PlanDownload measures a remote path against where it is going.
It is a method because the measurement happens over the connection: uploads walk the local filesystem, downloads have to walk the host.
func (*SFTPClient) PlanUpload ¶
func (c *SFTPClient) PlanUpload(localPath, remotePath string) (*Plan, error)
PlanUpload measures a local path against where it is going.
What lands where follows cp, scp and rsync. A directory's contents copy into the remote path, while a file keeps its own name when the remote path is a directory — whether that is because the path ends in a slash or because the directory is already there.
It takes a connection because of that last case: whether a path names a directory is not something the string can say. Reading the trailing slash alone is what makes "put a.txt /root" fail with an SFTP error that says nothing about the destination being a directory.
func (*SFTPClient) Run ¶
func (c *SFTPClient) Run(ctx context.Context, plan *Plan, progress ProgressFunc) error
Run carries out plan, reporting progress as it goes.
A cancelled context stops the transfer at the next packet rather than in the middle of a write: both directions copy through pkg/sftp, which moves a file in MaxPacket-sized requests, and the wrappers below check the context on every one of them.
type TransferItem ¶
TransferItem is one file in a transfer plan. Source is on the sending side and Dest on the receiving one, whichever those turn out to be.