Documentation
¶
Overview ¶
Package ssh abstracts how the CLI connects editors and shells to remote sandboxes over SSH: minting connection details from the API, generating a stable per-sandbox host alias in ~/.ssh/amika.conf, and execing ssh.
Index ¶
- Variables
- func Alias(sandboxID string) string
- func BuildProxyCommand(binaryPath string) (string, error)
- func BuildSessionAlias(name, id, environment string) (string, error)
- func BuildSessionSSHArgv(forward []string, nameIdx int, alias string) []string
- func BuildWSLProxyCommand(distro, binaryPath string) (string, error)
- func ConfigureSession(paths basedir.Paths, session SessionConfig) error
- func DispatchSSH(client *apiclient.Client, name string, extraArgs []string, ...) error
- func EnsureInclude(paths basedir.Paths) error
- func ExecSSH(client *apiclient.Client, name string, forcePTY bool, extraArgs []string) error
- func ExecSessionSSH(alias string, argv []string) error
- func GenerateIdentity(privatePath string) (string, error)
- func ImportIdentity(publicPath string) (identityPath, canonicalPublicKey string, err error)
- func KnownHostLine(alias, hostPublicKey string) (string, error)
- func MirrorToWindows(paths basedir.Paths, target wslbridge.Target) error
- func ParseProxyCommand(proxyCommand string) (string, error)
- func PrepareSessionHost(creator SessionCreator, pins HostKeyPinStore, sandboxID string, alias string) (*apiclient.SSHSession, error)
- func PrepareSessionTarget(paths basedir.Paths, creator SessionCreator, sandboxName string, ...) (string, error)
- func ProxySession(ctx context.Context, creator SessionCreator, dialer SessionDialer, ...) error
- func Render(state HostsState) string
- func RenderSessionConfig(environment, proxyCommand string, session SessionConfig) (string, error)
- func RenderWindows(state HostsState, target wslbridge.Target) (string, error)
- func ResolveProxyCommand() (string, error)
- func SaveState(paths basedir.Paths, state HostsState) error
- func UpsertHost(paths basedir.Paths, entry HostEntry) (string, error)
- func UpsertSessionHost(paths basedir.Paths, alias string) error
- func ValidateSessionConfig(session SessionConfig) error
- func WriteAmikaConfig(paths basedir.Paths, state HostsState) error
- type Destination
- type FileHostKeyPinStore
- type HostEntry
- type HostKeyPinStore
- type HostsState
- type SandboxAlias
- type SessionConfig
- type SessionCreator
- type SessionDialer
- type SessionHostEntry
- type Stream
- type WebSocketDialer
Constants ¶
This section is empty.
Variables ¶
var ErrHostKeyMismatch = errors.New("SSH host identity mismatch")
ErrHostKeyMismatch marks a descriptor whose immutable identity does not match the requested host.
var ErrInvalidSessionAlias = errors.New("invalid Amika SSH host alias")
ErrInvalidSessionAlias marks a host value that is not a safe v2 Amika alias.
var ErrUnsafeBinaryPath = errors.New("unsafe amika executable path for ProxyCommand")
ErrUnsafeBinaryPath marks an amika executable path that cannot be safely embedded in a ProxyCommand line.
Functions ¶
func Alias ¶
Alias returns the stable SSH host alias for a sandbox id. Cursor keys its Remote-SSH workspace (and agent chat) off this string, so it must depend only on the immutable id and never on the rotating connection token.
func BuildProxyCommand ¶
BuildProxyCommand renders the ProxyCommand line that reaches amika through the given executable path.
func BuildSessionAlias ¶
BuildSessionAlias combines a safe human name, immutable sandbox id, and the control-plane environment slug into a v2 host alias.
The environment goes second-to-last rather than first because the sandbox name may itself contain dots: parsing has to pop fixed-position segments off the right and let the name absorb whatever remains.
func BuildSessionSSHArgv ¶
BuildSessionSSHArgv assembles the argv for the system ssh binary from the arguments forwarded to it, swapping the sandbox name at nameIdx for the managed v2 alias. Substituting in place rather than prepending the alias preserves ssh's own grammar, "ssh [options] destination [command]": options written before the sandbox name stay before the destination, where ssh reads them as client options, and anything after it stays after, where ssh reads it as the remote command.
func BuildWSLProxyCommand ¶
BuildWSLProxyCommand renders the ProxyCommand a Windows OpenSSH client uses to reach a session alias: it re-enters the owning WSL distribution and execs the same Linux amika binary the Linux-side proxy would. The distribution name and binary path are held to the alias and proxy path safety rules, so a corrupted mirror source can only ever produce the fixed proxy invocation.
func ConfigureSession ¶
func ConfigureSession(paths basedir.Paths, session SessionConfig) error
ConfigureSession persists the shared session identity plus the ProxyCommand for the environment this process points at, then regenerates ~/.ssh/amika.conf without disturbing legacy provider-native host entries.
The environment and the executable path are resolved here rather than passed in so every caller records the same thing, and so the ProxyCommand is rebuilt from the live process on each run. A path persisted by an earlier run may name a binary that has since moved or been replaced by one that cannot serve as a proxy.
func DispatchSSH ¶
func DispatchSSH(client *apiclient.Client, name string, extraArgs []string, stdout, stderr io.Writer) error
DispatchSSH runs a one-off ssh command against the sandbox as a child process (no process replacement, no PTY) and returns once it completes, wiring its output to stdout/stderr. Unlike ExecSSH it hands control back to the caller, so callers can act on the result afterward (e.g. emit a JSON envelope only after the dispatch has actually succeeded). All the failure-prone setup (fetching connection details, resolving the ssh binary) happens before the process runs, so a failure is reported instead of leaving a half-done state.
func EnsureInclude ¶
EnsureInclude makes sure ~/.ssh/config pulls in the managed amika.conf via an Include directive near the top (Include must precede Host blocks to take effect, since ssh resolves options first-match-wins). It is idempotent and creates ~/.ssh/config if absent.
func ExecSSH ¶
ExecSSH replaces the current process with an interactive ssh session to the named sandbox. It fetches fresh connection details from the API and execs the system ssh binary directly, forwarding any extra args (e.g. a remote command).
func ExecSessionSSH ¶
ExecSessionSSH replaces the current process with OpenSSH targeting a strict v2 alias whose ProxyCommand fetches a fresh session per dial. argv is the complete ssh argument list, as built by BuildSessionSSHArgv.
func GenerateIdentity ¶
GenerateIdentity creates or validates an unencrypted user-owned Ed25519 keypair and returns its canonical public key.
func ImportIdentity ¶
ImportIdentity validates a public key and its conventional matching private key path without copying private material.
func KnownHostLine ¶
KnownHostLine returns one canonical alias-keyed Ed25519 pin.
func MirrorToWindows ¶
MirrorToWindows publishes the current managed SSH state to the Windows side: it renders the config for a Windows client, writes it as the mirrored amika.conf, adds the Include line to the Windows ssh config, and copies the identity and host-key pins the rendered config references, restricting the identity's Windows ACLs. The Windows copies are regenerated artifacts; the Linux-side state stays the only source of truth.
func ParseProxyCommand ¶
ParseProxyCommand returns the executable path from a managed ProxyCommand line, rejecting any line whose argv tail is not the fixed proxy invocation or whose path is not shell-safe.
func PrepareSessionHost ¶
func PrepareSessionHost( creator SessionCreator, pins HostKeyPinStore, sandboxID string, alias string, ) (*apiclient.SSHSession, error)
PrepareSessionHost fetches a descriptor and pins its key before OpenSSH is launched.
func PrepareSessionTarget ¶
func PrepareSessionTarget( paths basedir.Paths, creator SessionCreator, sandboxName string, sandboxID string, ) (string, error)
PrepareSessionTarget readies one sandbox for a v2 dial and returns its host alias: it builds the alias, writes the strict wildcard session config, and pins the sandbox's host key.
Shared by every command that hands a `*.amika` alias to system OpenSSH (`sandbox ssh`, `scp`), so they cannot drift in how the identity is checked or the host key is pinned.
func ProxySession ¶
func ProxySession( ctx context.Context, creator SessionCreator, dialer SessionDialer, alias string, stdin io.Reader, stdout io.Writer, ) error
ProxySession creates a fresh descriptor, dials it with header credentials, and copies opaque bytes between OpenSSH standard I/O and the WebSocket.
func Render ¶
func Render(state HostsState) string
Render produces the contents of ~/.ssh/amika.conf from the state. Each block is a stable `Host amika-<id>` alias preceded by the sandbox name as a comment so the file stays human-readable even though it is keyed by id.
func RenderSessionConfig ¶
func RenderSessionConfig(environment, proxyCommand string, session SessionConfig) (string, error)
RenderSessionConfig renders one environment's wildcard block. The pattern is scoped to `*.<environment>.amika` rather than `*.amika` so each control plane gets its own ProxyCommand: a bare `*.amika` would also match every other environment's aliases, and OpenSSH takes the first value it finds for an option.
func RenderWindows ¶
func RenderWindows(state HostsState, target wslbridge.Target) (string, error)
RenderWindows renders the managed config for a Windows OpenSSH client. Provider-native host blocks pass through unchanged, since their destinations are reachable from Windows directly; session blocks reference the mirrored identity and pin files and dial through wsl.exe into the distribution that owns the session state.
func ResolveProxyCommand ¶
ResolveProxyCommand builds the ProxyCommand for the amika executable the current process should be reached through.
func SaveState ¶
func SaveState(paths basedir.Paths, state HostsState) error
SaveState writes the SSH hosts state atomically with owner-only permissions.
func UpsertHost ¶
UpsertHost records (or refreshes) the managed SSH host for a sandbox: it updates the JSON state, regenerates ~/.ssh/amika.conf, ensures ~/.ssh/config includes it, and returns the stable alias to connect to. This is the single entry point editors use so connection details stay behind one seam.
func UpsertSessionHost ¶
UpsertSessionHost records a concrete direct-WebSocket SSH alias for editor discovery while its wildcard session block continues to provide the actual connection settings.
func ValidateSessionConfig ¶
func ValidateSessionConfig(session SessionConfig) error
ValidateSessionConfig reports whether ConfigureSession would accept this session, without writing anything.
It exists so a caller can reject a bad configuration before taking an action it cannot undo elsewhere. `secret ssh-keygen` uploads the public key and only then persists the session, so without this check a config ConfigureSession refuses (an identity path containing whitespace, say) would surface only after the remote key had already been replaced, leaving the stored key and the local identity out of step. Both share resolveSessionRendering, so the check and the write cannot drift apart.
func WriteAmikaConfig ¶
func WriteAmikaConfig(paths basedir.Paths, state HostsState) error
WriteAmikaConfig renders the state to ~/.ssh/amika.conf atomically.
Types ¶
type Destination ¶
Destination is an ssh destination string decomposed into the parts a caller needs to rebuild a connection: the user, host, port, and any other ssh options (e.g. -i, -F, -o) preserved in their original order. Options excludes the port (surfaced separately as Port) and the trailing "[user@]host" target.
func ParseDestination ¶
func ParseDestination(dest string) (Destination, error)
ParseDestination decomposes an ssh destination string such as "token@ssh.app.daytona.io", "-p 2222 user@host", or "-i /key -o Foo=bar host" into its user, host, port, and remaining options. The port is returned via Port (from "-p PORT" or "-pPORT") and the login name via User (from "-l NAME" or "-lNAME", which — like ssh — takes precedence over a "user@" in the target, and is never forwarded as an option since scp's -l means a bandwidth limit); every other option is preserved in Options in its original order so a caller can forward it. It errors on a missing port/login value or more than one host.
type FileHostKeyPinStore ¶
type FileHostKeyPinStore struct {
Path string
}
FileHostKeyPinStore atomically maintains the dedicated alias-keyed known hosts file.
func (FileHostKeyPinStore) Pin ¶
func (s FileHostKeyPinStore) Pin(alias, hostPublicKey string) error
Pin adds a new alias pin, accepts an identical existing pin, and refuses a changed key.
type HostEntry ¶
type HostEntry struct {
SandboxID string `json:"sandbox_id"`
SandboxName string `json:"sandbox_name"`
HostName string `json:"host_name"`
User string `json:"user"`
Port int `json:"port,omitempty"`
ExpiresAt string `json:"expires_at,omitempty"`
}
HostEntry is one managed SSH host: a stable alias for a sandbox plus the last-known rotating connection details used to render its config block.
func NewHostEntry ¶
NewHostEntry builds a managed host entry for a sandbox from its ssh destination string and identity. The destination is parsed (rather than split on whitespace) so an explicit port survives into the rendered config.
type HostKeyPinStore ¶
HostKeyPinStore atomically creates or verifies an alias-keyed known-host pin. An existing different key must fail closed.
type HostsState ¶
type HostsState struct {
Hosts []HostEntry `json:"hosts"`
// SessionConfig holds the key material shared by every environment. State
// written before session blocks became per-environment also carried a
// ProxyCommand here; that field no longer exists, so decoding drops it and
// the next write regenerates the blocks from SessionProxyCommands.
SessionConfig *SessionConfig `json:"session_config,omitempty"`
// SessionProxyCommands maps an environment slug to the amika invocation
// that proxies its aliases, one entry per control plane this machine has
// opened a v2 session against.
SessionProxyCommands map[string]string `json:"session_proxy_commands,omitempty"`
// SessionHosts lists concrete v2 aliases for editors that discover SSH
// connections by enumerating Host entries, rather than accepting an alias
// supplied directly. Their connection settings come from the wildcard
// session blocks below, not these intentionally empty entries.
SessionHosts []SessionHostEntry `json:"session_hosts,omitempty"`
}
HostsState is the source of truth from which ~/.ssh/amika.conf is rendered.
func LoadState ¶
func LoadState(paths basedir.Paths) (HostsState, error)
LoadState reads the SSH hosts state, returning an empty state if the file does not exist yet.
func (*HostsState) Upsert ¶
func (s *HostsState) Upsert(entry HostEntry)
Upsert adds or replaces the entry for a sandbox id, keeping entries sorted by id so the rendered config is deterministic.
func (*HostsState) UpsertSessionHost ¶
func (s *HostsState) UpsertSessionHost(alias string)
UpsertSessionHost adds a concrete v2 host alias if it is not already present, keeping entries sorted so the rendered config is deterministic.
type SandboxAlias ¶
SandboxAlias identifies the immutable sandbox id and the control-plane environment parsed from a v2 host alias.
func ParseSessionAlias ¶
func ParseSessionAlias(alias string) (SandboxAlias, error)
ParseSessionAlias pops the environment and sandbox id off the right so dotted sandbox names remain intact.
type SessionConfig ¶
SessionConfig describes the local key material shared by every environment's session block: the private key OpenSSH authenticates with and the file its host-key pins live in.
Neither is per-environment. One private key authenticates to every control plane, each of which holds its own uploaded copy of the public key, so an identity imported while pointed at one environment stays in effect for all of them. Only the ProxyCommand varies per environment, and it is stored separately in HostsState.
type SessionCreator ¶
type SessionCreator interface {
CreateSSHSession(string) (*apiclient.SSHSession, error)
}
SessionCreator creates a fresh transport descriptor for each SSH dial.
type SessionDialer ¶
SessionDialer opens a binary stream with a credential sent outside argv.
type SessionHostEntry ¶
type SessionHostEntry struct {
Alias string `json:"alias"`
}
SessionHostEntry is a concrete direct-WebSocket SSH alias advertised to editors. The alias is self-describing and validated with ParseSessionAlias.
type WebSocketDialer ¶
WebSocketDialer sends the connect credential only in the Authorization header and disables compression for opaque SSH ciphertext.