Documentation
¶
Overview ¶
Package remote is SSH: the machines kakel can reach, and what runs on them.
One Conn is one connection to one machine, and more than one thing can ride on it at a time. Today that means shells; tunnels and file transfers are the reason the connection is kept separate from them. Closing the connection closes everything riding on it.
A Conn is safe to use from several goroutines. A Shell is not, beyond what it documents.
Index ¶
- Constants
- Variables
- func AgentHolds(fingerprint string) (bool, error)
- func BookPath() (string, error)
- func CommandName(name string) string
- func DefaultKeyPath() (string, error)
- func KeyPathProblem(path string) error
- func QuickID() string
- func ReachWindow(ctx context.Context, r Reach) (*serve.Window, error)
- func WindowFiles(ctx context.Context, name string, win *serve.Window) (*serve.FileSession, *sftp.Client, error)
- func WindowFilesOn(ctx context.Context, name, host string, win *serve.Window) (*serve.FileSession, *sftp.Client, error)
- type Ask
- type Book
- func (b *Book) Err() error
- func (b *Book) Hosts() []Host
- func (b *Book) Kind(name string) (Kind, bool)
- func (b *Book) Lookup(name string) (Host, bool)
- func (b *Book) LookupID(id string) (Host, bool)
- func (b *Book) NameOf(id string) (string, bool)
- func (b *Book) Names() []string
- func (b *Book) Path() string
- func (b *Book) Put(h Host, under string) error
- func (b *Book) Reload() error
- func (b *Book) Remove(name string) error
- func (b *Book) RemoveID(id string) error
- func (b *Book) Route(name string) ([]Host, error)
- func (b *Book) RouteID(id string) ([]Host, error)
- type Config
- type Conn
- func (c *Conn) Close() error
- func (c *Conn) Closed() bool
- func (c *Conn) Dial(ctx context.Context, target string) (net.Conn, error)
- func (c *Conn) FileSubsystem(ctx context.Context) (*FileRelay, error)
- func (c *Conn) Files(ctx context.Context) (*Files, error)
- func (c *Conn) OpenTunnel(ctx context.Context, cfg TunnelConfig) (*Forwarder, error)
- func (c *Conn) Ping(ctx context.Context) (time.Duration, error)
- func (c *Conn) Shell(ctx context.Context, cfg ShellConfig) (*Shell, error)
- func (c *Conn) String() string
- func (c *Conn) Through(ctx context.Context, cfg Config) (*Conn, error)
- func (c *Conn) Via() *Conn
- func (c *Conn) Wait() error
- type Counter
- type FileRelay
- type Files
- type Forwarder
- type Host
- type HostKey
- type Kind
- type LockedKey
- type NewKey
- type Notice
- type OwnedShell
- type Question
- type Reach
- type Ring
- func (r *Ring) AgentGaveUp(why error)
- func (r *Ring) AgentTrouble() error
- func (r *Ring) Forget(path string)
- func (r *Ring) Has(path string) bool
- func (r *Ring) Lock()
- func (r *Ring) Paths() []string
- func (r *Ring) Signers() []ssh.Signer
- func (r *Ring) Unlock(ctx context.Context, path string, ask Ask) (ssh.Signer, error)
- type SSHConfigHost
- type Shell
- type ShellConfig
- type Tunnel
- type TunnelConfig
- type TunnelKind
Constants ¶
const BookFile = "servers.json"
BookFile is what the saved servers are kept in, in the directory conf gives kakel.
const KeyPerm = 0o600
KeyPerm is the mode a new private key file is made with.
It says who may read the key only where a mode says anything. On Windows a file has no mode to read and the key is as private as the directory holding it, which is why MakeKey insists on a full path rather than writing one wherever kakel was started.
const PubPerm = 0o644
PubPerm is the mode the public half is made with. It is public, and it is pasted into files other people read.
const ServePort = 2222
ServePort is the port a kakel serves on when none is given.
const WindowKind = "Kakel"
WindowKind is what the server list calls an entry that is another kakel serving, for the message that tells a user their machine is really one of those. Only the label: a saved server keeps whether it is one as Window.
A constant here rather than in the window, so the message and the dialog cannot drift apart.
Variables ¶
var ErrCloseAbandoned = errors.New(
"the file session's close never came back, so it was left to the connection")
ErrCloseAbandoned says an SFTP client's own close never came back after its session was closed, so it was left for the connection to end.
var ErrClosed = errors.New("the connection is closed")
ErrClosed is returned when something is opened on a connection that has already been closed.
var ErrHostKeyRefused = errors.New("the host key was not accepted")
ErrHostKeyRefused is what a connection fails with when the user said no to trusting a machine's host key.
var ErrNoAnswer = errors.New("the machine did not answer")
ErrNoAnswer is what an open fails with when the far machine takes the request and never answers it.
var ErrUnsaveable = errors.New("the server list could not be read, so it will not be written over")
ErrUnsaveable is returned when the book cannot be written because it could not be read.
var ErrWrongPassphrase = errors.New("the passphrase did not unlock it")
ErrWrongPassphrase is a passphrase that did not open the key.
Its own error because it is the one failure here that is the user's to put right, and the only one worth asking about again. x/crypto's own wording for it is "x509: decryption password incorrect", which is neither this window's vocabulary nor, for an OpenSSH key, true.
Functions ¶
func AgentHolds ¶
AgentHolds reports whether the running SSH agent holds a key with this fingerprint, in the form ssh.FingerprintSHA256 gives.
A question worth asking about the key that opens the secrets. A slot key is derived from a signature over a challenge kept in the clear in the vault file, ed25519 signs the same way every time, and an agent signs whatever blob it is handed without looking at it. So one forwarded session to a machine that has been taken over is enough to hand over the vault, to anybody who also has a copy of the file.
An answer of false covers both "the agent does not have it" and "there is no agent to ask", and the error says which. It is a snapshot either way: a key can be added to the agent a minute later.
func CommandName ¶
CommandName reduces a server name to the part a command id is built from. Names hold spaces and capitals; an id is lowercase and has none, so that a key binding naming one is readable.
It lives here because a Book has to refuse two names that reduce to the same thing: one of the two would be unreachable from the menu and the palette.
func DefaultKeyPath ¶
DefaultKeyPath is where a new key goes when the user names nowhere: beside the keys ssh already looks for, under a name kakel's own.
Its own name so the first thing the dialog offers is not the one path most likely to be taken already.
func KeyPathProblem ¶
KeyPathProblem is why a key cannot be made at path, as MakeKey would say it, or nil: no path, one that is not full, or a key or its public half already there. A dialog asks it before it closes, so what was typed is not lost to a failure.
func QuickID ¶
func QuickID() string
QuickID is an id for a connection made without a saved server, which no saved server has, nor can: theirs are hex alone.
func ReachWindow ¶
ReachWindow does the part that must not run on the goroutine that draws: reading the disk, unlocking a key, and the handshake itself.
func WindowFiles ¶
func WindowFiles(ctx context.Context, name string, win *serve.Window) ( *serve.FileSession, *sftp.Client, error)
WindowFiles starts SFTP on a window taken over, over a channel of its own.
Bounded by channelTimeout, the channel and the SFTP greeting together, for the reason Conn.Files is bounded: it runs on the goroutine that draws, and a window that is still on the network and no longer answering would otherwise stop this one. A caller that cancels ctx gives up sooner. A channel that arrives after that is closed rather than left open on the far window.
name is the window as the caller knows it, which is what the error has to say. What comes back is the stream and the SFTP client speaking over it, both the caller's to close.
func WindowFilesOn ¶
func WindowFilesOn(ctx context.Context, name, host string, win *serve.Window) ( *serve.FileSession, *sftp.Client, error)
WindowFilesOn starts SFTP on a machine a window taken over is connected to, over a channel of its own.
host is that machine as the window taken over names it, and the bytes go through that window: this one has no connection of its own to it. An empty host asks for the machine the window itself is on, which is what WindowFiles does. Bounded the way WindowFiles is.
Types ¶
type Ask ¶
type Ask interface {
// Passphrase unlocks a private key file. A passphrase that does not
// open the key is asked for again, so a typo is a second try rather
// than a connection that quietly signs in some other way -- or does
// not sign in at all. Cancelling is what stops the asking.
Passphrase(ctx context.Context, key LockedKey) (string, error)
// Password is the account password, asked only after key
// authentication has been tried.
Password(ctx context.Context, user, host string) (string, error)
// Question is keyboard-interactive authentication: whatever the
// server decided to ask, which is usually a one-time code.
Question(ctx context.Context, q Question) ([]string, error)
// TrustHostKey asks whether to connect to a host that is not in
// known_hosts. Answering yes records the key.
TrustHostKey(ctx context.Context, key HostKey) (bool, error)
// Notice is something a server said rather than asked: a link to
// open, most often, which is how a server that signs people in
// through a browser tells them where to go. The handshake goes on
// waiting while they do it, and finishes on its own once they have.
//
// It must return as soon as the message is on its way to the user.
// Nothing is waiting for an answer, and a client that sat on this
// would hold up the very handshake the user is being asked to
// unblock.
//
// The message stops being worth showing when ctx is done, which is
// when the connection has been made, has failed, or was given up
// on.
Notice(ctx context.Context, n Notice)
}
Ask is how a connection reaches the user for a secret or a decision.
Every method but Notice blocks until the user answers or ctx is cancelled. They are called from the goroutine that is connecting, never from the one drawing, so an implementation has to hand the question to the window and wait for the reply.
Returning an error stops the connection, and the error is what the caller is told. That is what cancelling a dialog means: the user said no, so nothing else is tried and no second question is asked.
type Book ¶
type Book struct {
// contains filtered or unexported fields
}
Book is the saved list of machines.
A Book that could not be read refuses to save. A file nobody could parse is somebody's list of servers, and replacing it with an empty one loses it for good; the failure is reported and the user gets to repair the file.
Every change re-reads the file first, so a second window's work is never written over by a list built from a stale read.
A Book is safe to use from several goroutines.
func LoadBook ¶
LoadBook reads the saved servers.
It always returns a usable Book, and an error when the file could not be read. That book holds no servers and will not save, so a window can still open and connect to things typed by hand while the user is told what is wrong with their file.
A file that is not there is not an error: it is what the first run looks like.
func UnusableBook ¶
UnusableBook returns a book that holds nothing and will not save, because of err.
It exists for a caller that could not work out where the file lives. Handing back a plain empty Book instead would give a window that silently offers to save servers it has nowhere to put.
func (*Book) Hosts ¶
Hosts returns the saved machines, by name. The slice is a copy, deep enough that a caller cannot change what is saved without saving it.
func (*Book) Kind ¶
Kind says what the book holds under a name, ignoring case the way Lookup does.
A caller that only wants these asks for them rather than for the Host: this is asked for every row of every frame, and cloning a machine and its key files to read one flag off it is work for nothing.
func (*Book) Lookup ¶
Lookup returns the machine with a name, ignoring case so a name typed into the palette still finds it.
func (*Book) NameOf ¶
NameOf returns what the machine with an id is called now, and false when the list no longer has it.
func (*Book) Names ¶
Names is what the saved machines are called, in the order the book holds them.
A caller that only wants the names asks for these rather than for Hosts: a Host carries its key files, and cloning those to read a name off is work for nothing.
func (*Book) Put ¶
Put adds a machine or replaces the one with the same name, and saves.
under is the name it is replacing, for a rename. An empty one means it is being added, and a name already taken is refused.
type Config ¶
type Config struct {
// Host is the name or address to connect to. Port defaults to 22.
Host string
Port int
// User defaults to the local username.
User string
// KnownHosts lists the files to verify the host key against. Empty
// means the usual ~/.ssh/known_hosts.
KnownHosts []string
// Identities lists private key files to try. Empty means the usual
// ~/.ssh/id_ed25519, id_ecdsa and id_rsa. A file named here that
// cannot be read or decrypted fails the connection; one of the
// defaults is skipped.
Identities []string
// NoAgent skips the SSH agent even when one is running, and
// NoIdentities skips key files entirely, for a connection that
// should only ever offer a password.
NoAgent bool
NoIdentities bool
// ForwardAgent carries this machine's SSH agent to the far end, so a
// second hop from there signs with the keys held here.
//
// It is for the whole connection, not one pane: while it is on,
// anyone who is root on that machine can sign with these keys.
ForwardAgent bool
// KeysOnly offers keys and nothing else, which is what taking over
// another kakel window does: that end accepts no password and no
// keyboard-interactive.
KeysOnly bool
// NoRing leaves out the keys already unlocked, for a connection that
// must offer only the key files it was named.
NoRing bool
// Ask reaches the user for a passphrase, a password, a one-time code
// or a decision about an unknown host key. A nil one refuses
// anything that would need asking: encrypted keys are skipped, no
// password is offered, and an unknown host is a hard failure.
Ask Ask
// Saying is told each step of connecting as it is tried, for showing
// somebody what a connection is doing. A nil one is not called.
//
// It is called from whichever goroutine is connecting, which is not
// the one that draws, so an implementation that touches a window has
// to hand the work to whatever does.
Saying func(what string)
// Wrong is told the steps that did not go well: a passphrase that
// did not unlock a key, an SSH agent that stopped answering. A
// window draws these differently from the steps that went well, so
// an account of a connection that had trouble does not read like one
// that had none. A nil one sends them to Saying instead.
Wrong func(what string)
// Ring holds keys already unlocked, so a passphrase is asked for
// once and then used for every connection. A nil one holds nothing
// and keeps nothing.
Ring *Ring
// HostKeyCallback replaces known-hosts checking entirely.
//
// This exists so tests can pin a generated host key. Setting it in a
// real client — to ssh.InsecureIgnoreHostKey, say — removes the only
// defence against a man-in-the-middle, and nothing else here will
// notice.
HostKeyCallback ssh.HostKeyCallback
// contains filtered or unexported fields
}
Config describes a machine to connect to.
func ParseTarget ¶
ParseTarget splits [user@]host[:port] into a config.
The host may be an IPv6 literal, which is why the port is not simply split on the first colon: "::1" is an address, not a host and a port. A bracketed literal with a port, "[::1]:22", is the only unambiguous spelling and the one net.SplitHostPort understands.
func (Config) SameMachine ¶
SameMachine reports whether two configs name the same login on the same machine.
The window keeps one connection per name, and a name that meant a different machine from one moment to the next would put a terminal on whichever was connected to first.
type Conn ¶
type Conn struct {
// contains filtered or unexported fields
}
Conn is a live connection to one machine.
func Connect ¶
Connect opens a connection to a machine and authenticates.
Cancelling ctx gives up, including while a dialog is waiting for a passphrase: authentication can sit on a question nobody is going to answer, and a window that is closing must not wait for it.
func ConnectBy ¶ added in v0.12.1
func ConnectBy(ctx context.Context, dial func(ctx context.Context, addr string) (net.Conn, error), cfg Config) (*Conn, error)
ConnectBy opens a connection over a stream dial makes, in place of a TCP connection from this machine: one a kakel window opens from the machine it is on, say. Nothing rides on anything here: whatever dial goes through closes the stream when it goes.
func (*Conn) Close ¶
Close ends everything riding on the connection and then the connection itself. It is idempotent, and a second caller waits for the first to finish rather than reporting a success that has not happened.
func (*Conn) Closed ¶
Closed reports whether the connection is no longer usable, so a caller can tell a session that ended by itself from the connection going under it.
Both ways it can go: this window closed it, or the transport under it ended because the machine dropped off the network. The second one is nobody's decision here, so a connection that only asked whether Close had been called would say a dead machine was still reachable.
func (*Conn) Dial ¶
Dial opens a stream to target, as the far machine reaches it: its own loopback for "localhost", and anything its network does.
func (*Conn) FileSubsystem ¶
FileSubsystem starts an SFTP subsystem on the connection and hands back the stream it speaks over.
No SFTP client is made: the caller relays the bytes rather than reading files itself. Starting it is bounded by channelTimeout, both round trips together, the way Conn.Files is. A caller that cancels ctx ends it sooner.
func (*Conn) Files ¶
Files starts an SFTP session on the connection.
One per call rather than one shared: each has its own channel, and two browser panes on one machine should not wait on each other's reads.
Starting it is bounded by channelTimeout, all three round trips together, because it runs on the goroutine that draws. A caller that cancels ctx ends it sooner. Neither touches a session that was started.
func (*Conn) OpenTunnel ¶
OpenTunnel starts forwarding a port.
The listener is opened before this returns, so a port already in use is reported here rather than from a goroutine nobody is watching.
Asking the far machine to listen is the only part that waits for it, and is bounded by channelTimeout because it runs on the goroutine that draws. A caller that cancels ctx ends it sooner. Neither touches a tunnel that was opened.
func (*Conn) Ping ¶ added in v0.4.0
Ping asks the server something it answers at once, OpenSSH's keepalive, and reports how long the answer took: the round trip. A server that refuses the request has still answered.
func (*Conn) Shell ¶
Shell starts a program on the connection and returns it as a byte stream. Several may run on one connection at a time.
Opening it is bounded by channelTimeout, all three round trips together, because it runs on the goroutine that draws. A caller that cancels ctx ends it sooner. Neither touches a shell that was opened: the program then runs until it exits or the shell is closed.
func (*Conn) Through ¶
Through opens a connection to another machine from this one.
No local port is opened for it. The second connection is carried inside a channel of the first, which is what ssh -J does and what a saved server reached "through" another one means. It rides on this connection: closing this one closes it too.
type Counter ¶
type Counter interface {
// Wrap returns a writer that counts what is written through it. out
// says the bytes are leaving this machine.
Wrap(w io.Writer, out bool) io.Writer
}
Counter is told what moves through a tunnel.
It is an interface so that remote does not have to know what the panel counts with. An implementation must be safe to use from several goroutines: every stream writes through it at once.
type FileRelay ¶
type FileRelay struct {
// contains filtered or unexported fields
}
FileRelay is a raw SFTP subsystem on a connection, for carrying somebody else's file session rather than reading files here.
It rides on the connection: closing the connection closes it, so a machine that goes ends the relay going through it.
func (*FileRelay) Close ¶
Close ends the subsystem and lets go of the connection's record of it.
It sends the machine a channel close and nothing more. A read already waiting on the channel ends when that machine answers the close, or when the connection to it goes; a machine that has stopped answering leaves that read where it is, so a caller has to be able to walk away from it.
type Files ¶
type Files struct {
// contains filtered or unexported fields
}
Files is an SFTP session on a connection.
It rides on the connection: closing the connection closes it, and the file browser and every transfer going through it stop.
type Forwarder ¶
type Forwarder struct {
// contains filtered or unexported fields
}
Forwarder is a tunnel that is open.
It rides on the connection: closing the connection closes the tunnel and every stream going through it.
func OpenTunnelThrough ¶
func OpenTunnelThrough(via string, dial func(ctx context.Context, target string) (net.Conn, error), cfg TunnelConfig) (*Forwarder, error)
OpenTunnelThrough forwards a port here, or a SOCKS proxy, to what dial reaches from another machine, called via: one this window reaches some other way than a connection of its own, such as through another kakel window. The far machine listening, a remote forward, is refused: only a connection of its own can ask that. Closing the tunnel is the caller's, when that way of reaching the machine goes.
func (*Forwarder) Addr ¶
Addr returns the address the tunnel is listening on, which is where the port ended up when the tunnel asked for any free one.
func (*Forwarder) Close ¶
Close stops the tunnel and cuts every stream going through it, and lets go of the connection's record of it.
func (*Forwarder) Streams ¶
Streams returns how many connections are going through the tunnel right now.
type Host ¶
type Host struct {
// Name is what the user calls this machine. It is what the panel
// shows and what Via names, so it has to be unique in a Book.
Name string `json:"name"`
// ID is what the window knows this machine by, and never changes.
//
// Not the name: a name can be given up in a rename and given to
// another machine, and something that remembered only the name
// would follow it there. The Book gives one out and keeps it; the
// user never sees it.
ID string `json:"id,omitempty"`
// Address and Port are the machine itself. A zero port means 22.
Address string `json:"address"`
Port int `json:"port,omitempty"`
// User is the account to log in as. Empty means the local username.
User string `json:"user,omitempty"`
// Via is the ID of another saved host to reach this one through,
// with no local port opened for it.
//
// The id and not the name, so a jump host renamed is still the one
// this goes through, and a server saved since under the name it
// gave up is not. A Book given a name here, in a list saved before
// servers had ids or in one edited by hand, turns it into the id of
// the server with that name.
Via string `json:"via,omitempty"`
// Identities lists private key files to prefer. Empty means the
// usual three under ~/.ssh.
Identities []string `json:"identities,omitempty"`
// Term is the TERM value sent to the machine. Empty means
// xterm-256color.
Term string `json:"term,omitempty"`
// Folders are directories on this machine that were worth opening
// a file browser at, before there were favourites. They are moved to
// favourites once, and read for nothing else.
Folders []string `json:"folders,omitempty"`
// Setup says the shell on this machine is taught to say where it
// is and where each command starts, by kakel typing one line in
// as the shell starts.
//
// Off unless the user asks for it: the line goes into whatever
// login shell the account has, and an unusual one would answer
// with an error rather than doing it.
Setup bool `json:"shellSetup,omitempty"`
// ForwardAgent carries this machine's SSH agent to the server, so a
// second hop from there signs with the keys held here.
//
// Off unless the user asks for it: while it is on, anyone who is
// root on that server can sign with these keys.
ForwardAgent bool `json:"forwardAgent,omitempty"`
// Window says this is another kakel serving, taken over rather
// than logged in to.
//
// Address and Port are where it serves, and the first of Identities
// is the key to offer it. User and Term mean nothing to one: there
// is no account to log in to. Via is the saved server the window,
// or its relay, is reached through.
Window bool `json:"window,omitempty"`
// Code is the code of a window served through a relay, which is
// reached by the relay and the code rather than an address and a
// port: Address is then the relay's URL, with its secret.
Code string `json:"code,omitempty"`
}
Host is a machine the user has saved.
It holds no secret and never will. A passphrase is asked for when it is needed and kept in the Ring for as long as the window is open; a password is asked for every time.
func HostFromTarget ¶
HostFromTarget builds a host from what a user typed, taking the name from the address when none was given.
func (Host) Config ¶
Config returns what Connect needs to reach this machine. What asks the user, and what holds the keys already unlocked, is the caller's to fill in.
func (Host) KeyFile ¶
KeyFile is the key file a saved window offers, or empty for whatever is to hand.
func (Host) ServeAddr ¶
ServeAddr returns where a saved window serves, as host:port, or as the relay's URL with the code after a # for one served through a relay.
func (Host) Shell ¶
func (h Host) Shell(cols, rows int) ShellConfig
Shell returns what to run on this machine at a given size.
func (Host) Target ¶
Target returns the machine as a user would type it: user@host:port, leaving out the parts that are the default.
func (Host) Validate ¶
Validate reports what is wrong with a host, or nil.
The address goes into known_hosts once its key is trusted, so it is checked the same way a typed target is: neither this nor knownhosts.Line escapes anything, and a newline in it would write a second, unasked-for line of trust.
type HostKey ¶
type HostKey struct {
// Addr is the host:port being connected to.
Addr string
// Shown is what to call the machine when Addr is not it, as for a
// window reached through a relay; empty when Addr is.
Shown string
// Key is what the server presented.
Key ssh.PublicKey
}
HostKey is a host key offered by a server that is not in known_hosts.
func (HostKey) Fingerprint ¶
Fingerprint returns the SHA256 fingerprint, spelled the way ssh and ssh-keygen spell it so a user can compare the two.
type Kind ¶
type Kind struct {
// Name is the book's own spelling of the name, which can differ in
// case from the name asked about.
Name string
// Window says the machine is another kakel, and Serve is where
// that one serves.
Window bool
Serve string
}
Kind is what a Book holds under a name, without the key files a Host carries.
type LockedKey ¶
type LockedKey struct {
// Path is the key file being unlocked.
Path string
// Wrong counts the passphrases already refused for this file, and is
// zero the first time it is asked. Anything above zero means the
// last answer did not open the key, which the dialog has to say: a
// question asked twice with no word of why reads as a question that
// was not heard.
Wrong int
}
LockedKey is a private key file that will not open without a passphrase.
type NewKey ¶
type NewKey struct {
// Path is the private half, and Pub the public half beside it.
Path string
Pub string
// Line is the public half as one line, which is what goes into an
// authorized_keys file.
Line string
}
NewKey is a key pair that has just been written to disk.
func MakeKey ¶
MakeKey writes a new ed25519 key pair at path, with the public half at path with ".pub" on the end.
ed25519 because it is what ssh-keygen makes by default now, every server kakel can reach takes it, and it has no size to choose.
The path has to be an absolute one. A relative path would put a private key wherever kakel happened to be started, which on Windows is also the whole of what keeps it private.
An empty passphrase leaves the key unencrypted, the way ssh-keygen does when the user presses return at the prompt.
type Notice ¶
type Notice struct {
// User and Host name the connection the message came from.
User, Host string
// Name and Instruction are the server's own wording, and may be
// empty. Text is the message of a banner, which has neither.
Name, Instruction, Text string
}
Notice is something a server told the user during authentication.
User and Host are ours. Everything else is the server's own wording and is not to be trusted, for the same reason a Question's is not: a message the user cannot tell from the window's own could send them somewhere of the server's choosing.
type OwnedShell ¶
type OwnedShell struct {
*Shell
// contains filtered or unexported fields
}
OwnedShell is a shell that carries its own connection and closes it after the shell, so Conn.Close closing that shell as a rider does not recurse.
func StartShell ¶
func StartShell(ctx context.Context, cfg Config, sh ShellConfig) (*OwnedShell, error)
StartShell opens a connection and runs one shell on it.
It is the one-shot form, for a caller that wants a single remote shell and nothing else. A caller that wants more than one thing on a machine should Connect and keep the Conn.
func (*OwnedShell) Close ¶
func (o *OwnedShell) Close() error
Close ends the shell and then the connection carrying it.
func (*OwnedShell) Conn ¶
func (o *OwnedShell) Conn() *Conn
Conn returns the connection the shell is running on.
type Question ¶
type Question struct {
// User and Host name the connection the question came from.
User, Host string
// Name and Instruction are the server's own wording, and may be
// empty.
Name, Instruction string
// Prompts is what to ask, and Echo says which answers may be shown
// as they are typed. The two are the same length.
Prompts []string
Echo []bool
}
Question is what a server asked for during keyboard-interactive authentication.
User and Host are ours. Everything else is the server's own wording and is not to be trusted: a server that chose "Unlock a private key" and a plausible key path could otherwise produce a dialog the user cannot tell from the local one, and be handed the passphrase to their private key. A dialog has to say which machine is asking, in words the server cannot write.
type Reach ¶
type Reach struct {
// Addr is where the window serves, which its host key is kept
// under, and KeyFile the key to offer it.
Addr, KeyFile string
// Through makes the connection the window is reached over, in place
// of a TCP connection to Addr: a stream through a relay, or through
// a jump server. A nil one dials Addr from this machine.
Through func(ctx context.Context) (net.Conn, error)
// Shown is what to call the window, when Addr is not it.
Shown string
Ring *Ring
Ask Ask
// Known says where the windows already reached are recorded. Given
// rather than looked up, so a test can hand over a file of its own.
Known func() (string, error)
// Agent is where the keys an SSH agent holds come from: the socket to
// hold until signing is done, and the agent speaking over it. A nil
// one asks the agent running on this machine.
//
// Listing is left to the ladder to call, so the same bound and the
// same "it did not answer" applies here as anywhere else.
Agent func() (io.Closer, agent.Agent, error)
// Saying is told what is being done now, for the row to show. It is
// called from the goroutine doing it, so it hands the work to
// whatever draws. A nil one is not called.
Saying func(what string)
// Wrong is told the steps that did not go well, which a window draws
// differently from the ones that did. A nil one sends them to
// Saying instead.
Wrong func(what string)
// Patience is how long the other end has to get through the
// handshake. Zero asks the serve package for its own.
Patience time.Duration
}
Reach is everything reaching another kakel window needs.
type Ring ¶
type Ring struct {
// contains filtered or unexported fields
}
Ring holds private keys that have been unlocked.
A passphrase is asked for once and the key is then used for every connection, which is the whole reason the ring exists: opening a second server should not ask again.
Nothing here reaches disk and nothing here holds a passphrase. Only the decrypted key stays, in memory, until the window closes or the user locks the ring.
A Ring is safe to use from several goroutines.
func (*Ring) AgentGaveUp ¶
AgentGaveUp remembers that the SSH agent did not answer.
An agent that accepts a connection and then says nothing costs every connection the wait to find that out. Remembering it costs the first one only. Locking the keys forgets it, for an agent that has been started or unstuck since.
func (*Ring) AgentTrouble ¶
AgentTrouble returns why the agent was given up on, or nil when it has not been.
func (*Ring) Lock ¶
func (r *Ring) Lock()
Lock drops every key, and forgets that the SSH agent did not answer.
It is how the user says "forget what you know about my credentials", and an agent started or unstuck since is one of those things.
type SSHConfigHost ¶ added in v0.4.0
SSHConfigHost is one machine an OpenSSH client config names, as much of it as a saved server holds: the alias it goes by, the host it reaches, the user, the port, the first key file, and the first hop of its ProxyJump, by its alias.
func ReadSSHConfig ¶ added in v0.4.0
func ReadSSHConfig(file string) ([]SSHConfigHost, error)
ReadSSHConfig reads the machines an OpenSSH client config at path names, in the order they come, through the files it includes.
A machine is a Host pattern with no * or ? in it, which names one alias. Its settings are taken as ssh takes them: from every block that applies to it, wildcard blocks such as Host * included, in the order they come, the first value of each setting the one kept. A Match block's conditions are not followed, and its settings are not taken. A relative Include is read from ~/.ssh, as ssh reads it for a user's config.
func (SSHConfigHost) Host ¶ added in v0.4.0
func (h SSHConfigHost) Host() Host
Host is h as a saved server: named after its alias, reached at its HostName, or at the alias when it has none, through Jump.
type Shell ¶
type Shell struct {
// contains filtered or unexported fields
}
Shell is a program running on a connection, as a byte stream and a size. It satisfies session.Session, so nothing above it can tell a remote shell from a local one.
No local pseudo-terminal is involved: the SSH session channel already carries the byte stream, and pty-req and window-change carry the size.
func (*Shell) Close ¶
Close hangs the program up and lets go of the connection's record of it. The connection itself stays open: other shells and tunnels may still be riding on it.
func (*Shell) ReportLate ¶
ReportLate takes a function for a failure that no caller can be given back, which is what a window-change failing amounts to: the send outlives the Resize that asked for it and may outlive Close.
It is called from the goroutine that was sending, so an implementation that touches a window has to hand the work on. Set it before the shell is resized.
func (*Shell) Resize ¶
Resize reports a new window size in character cells. It records the size and comes straight back, so a drag never waits on the machine; sendSize puts it on the wire.
A window-change that failed is reported one request late, because x/crypto hands a write that failed to whoever writes next: the next Resize returns it, Close returns what no Resize came back for, and a ReportLate hook takes it instead when one is set. A shell that has been closed says so, as Write does.
type ShellConfig ¶
type ShellConfig struct {
// Command is what to run. Empty asks for the user's login shell.
Command []string
// Dir is the directory to run Command in. Empty runs wherever the
// login lands. It is ignored without a Command: a shell to type into
// is started where the far end starts it.
Dir string
// Cols and Rows are the initial window size.
Cols, Rows int
// Term is the TERM value sent to the remote. Defaults to
// xterm-256color.
Term string
}
ShellConfig describes one shell or command to run on a connection.
type Tunnel ¶
type Tunnel struct {
// Kind is which machine listens.
Kind TunnelKind
// Listen is where connections are accepted, as host:port, read on
// whichever machine is doing the listening. An empty host means that
// machine only, which is what ssh does and what keeps a tunnel from
// being a hole for everything on the network. Port 0 asks for any
// free port.
//
// For a remote forward this is what the far machine is asked for.
// Where it really binds is its own decision: a server set to share
// forwarded ports binds them for its whole network whatever the
// client asked.
Listen string
// Target is where they are sent, as host:port, read on whichever
// machine is doing the connecting. A dynamic tunnel has none: every
// stream says where it wants to go.
Target string
}
Tunnel describes a forwarded port.
func (Tunnel) Exposed ¶
Exposed reports whether the tunnel accepts connections from other machines rather than only from the one it listens on.
It exists so the window can say so before opening one: a forward on 0.0.0.0 lets anything that can reach the machine through the tunnel, with no password and no log.
An address that does not parse is not exposed because it is not opened at all: Validate refuses it first.
type TunnelConfig ¶
type TunnelConfig struct {
// Tunnel is the forward itself.
Tunnel Tunnel
// Count records what moves, for the panel. It may be nil.
Count Counter
// OnError is told about a failure on one stream, which leaves the
// tunnel open: the next stream may work. It is called from the
// goroutines carrying the streams, so it must be safe to call from
// any of them.
OnError func(error)
// OnStopped is told when the tunnel has ended on its own, because
// the listener failed. It is called once, from the goroutine that
// was accepting, and never for a tunnel that was closed on purpose.
OnStopped func(error)
}
TunnelConfig is what to open a tunnel with.
type TunnelKind ¶
type TunnelKind uint8
TunnelKind is which machine listens.
const ( // LocalForward listens on this machine and connects from the far // end. It is what ssh -L does: a port here stands for a service // over there. LocalForward TunnelKind = iota // RemoteForward listens on the far machine and connects from this // one. It is what ssh -R does. RemoteForward // DynamicForward listens on this machine and speaks SOCKS5, so a // browser or a command with a proxy setting reaches whatever it asks // for as the far machine sees it. It is what ssh -D does. DynamicForward )
func (TunnelKind) String ¶
func (k TunnelKind) String() string
String names a kind the way a dialog does.