ssh

package
v0.0.0-...-c7fee84 Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
var ErrHostKeyMismatch = errors.New("SSH host identity mismatch")

ErrHostKeyMismatch marks a descriptor whose immutable identity does not match the requested host.

View Source
var ErrInvalidSessionAlias = errors.New("invalid Amika SSH host alias")

ErrInvalidSessionAlias marks a host value that is not a safe v2 Amika alias.

View Source
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

func Alias(sandboxID string) string

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

func BuildProxyCommand(binaryPath string) (string, error)

BuildProxyCommand renders the ProxyCommand line that reaches amika through the given executable path.

func BuildSessionAlias

func BuildSessionAlias(name, id, environment string) (string, error)

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

func BuildSessionSSHArgv(forward []string, nameIdx int, alias string) []string

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

func BuildWSLProxyCommand(distro, binaryPath string) (string, error)

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

func EnsureInclude(paths basedir.Paths) error

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

func ExecSSH(client *apiclient.Client, name string, forcePTY bool, extraArgs []string) error

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

func ExecSessionSSH(alias string, argv []string) error

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

func GenerateIdentity(privatePath string) (string, error)

GenerateIdentity creates or validates an unencrypted user-owned Ed25519 keypair and returns its canonical public key.

func ImportIdentity

func ImportIdentity(publicPath string) (identityPath, canonicalPublicKey string, err error)

ImportIdentity validates a public key and its conventional matching private key path without copying private material.

func KnownHostLine

func KnownHostLine(alias, hostPublicKey string) (string, error)

KnownHostLine returns one canonical alias-keyed Ed25519 pin.

func MirrorToWindows

func MirrorToWindows(paths basedir.Paths, target wslbridge.Target) error

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

func ParseProxyCommand(proxyCommand string) (string, error)

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

func ResolveProxyCommand() (string, error)

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

func UpsertHost(paths basedir.Paths, entry HostEntry) (string, error)

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

func UpsertSessionHost(paths basedir.Paths, alias string) error

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

type Destination struct {
	User    string
	Host    string
	Port    int
	Options []string
}

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

func NewHostEntry(sandboxID, sandboxName, destination, expiresAt string) (HostEntry, error)

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

type HostKeyPinStore interface {
	Pin(string, string) error
}

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

type SandboxAlias struct {
	Name        string
	ID          string
	Environment string
}

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

type SessionConfig struct {
	IdentityFile   string
	KnownHostsFile string
}

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

type SessionDialer interface {
	Dial(context.Context, string, string) (Stream, error)
}

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 Stream

type Stream interface {
	io.Reader
	io.Writer
	io.Closer
}

Stream is the binary WebSocket abstraction used by the stdio proxy.

type WebSocketDialer

type WebSocketDialer struct {
	HTTPClient *http.Client
}

WebSocketDialer sends the connect credential only in the Authorization header and disables compression for opaque SSH ciphertext.

func (WebSocketDialer) Dial

func (d WebSocketDialer) Dial(ctx context.Context, connectURL, credential string) (Stream, error)

Dial opens one bounded binary WebSocket stream.

Jump to

Keyboard shortcuts

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