connector

package
v0.2.4 Latest Latest
Warning

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

Go to latest
Published: Jan 11, 2026 License: GPL-3.0 Imports: 25 Imported by: 0

Documentation

Overview

Package connector provides PTY-based SSH connection management with credential injection and session recording support.

The connector spawns SSH as a child process in a pseudo-terminal, enabling interactive sessions with automatic password injection and host key handling.

Connection Flow

The Connector handles the complete SSH session lifecycle:

  1. Spawns ssh in a PTY for interactive I/O
  2. Detects and responds to password prompts
  3. Handles host key verification (accept-once mode optional)
  4. Proxies terminal I/O between user and SSH process
  5. Propagates window resize events (SIGWINCH)

Credential Injection

When credentials are provided, the connector monitors SSH output for password prompts and injects the password automatically. Passwords are held in secure memory via the secret package and wiped after use.

Host Key Handling

The connector supports an "accept-once" mode where unknown host keys are automatically accepted for the current session only, without persisting to known_hosts. This is useful for temporary or ephemeral connections.

Recording Integration

Sessions can be recorded via asciinema. Use MaybeWrapWithRecording to check recording configuration and spawn a recording wrapper if enabled. The recording wrapper re-executes nssh under asciinema.

Connection Testing

The TestConnection function performs a non-interactive connection test to diagnose connectivity and compatibility issues. This is used by the automatic compatibility fix system.

Example

conn := connector.NewConnector(hostname, username, password, sshArgs)
conn.SetTimeouts(&cfg.SSH.Connection)
if err := conn.Run(ctx); err != nil {
    // Handle connection error
}

Package connector provides PTY-based SSH connection handling.

Index

Constants

View Source
const (
	DefaultRingBufferSize       = 2048
	DefaultPasswordFilterWindow = 100 * time.Millisecond
)

Default configuration values.

View Source
const (

	// TimingConfigLoad is emitted after config file is loaded.
	TimingConfigLoad = "config_load"

	// TimingCredentialLookup is emitted after credential resolution completes.
	TimingCredentialLookup = "credential_lookup"

	// TimingPTYStart is emitted after PTY allocation completes.
	TimingPTYStart = "pty_start"

	// TimingFirstRead is emitted when first data is read from PTY (SSH banner/prompt).
	TimingFirstRead = "first_read"

	// TimingPasswordPrompt is emitted when password prompt is detected (time since session start).
	TimingPasswordPrompt = "password_prompt"

	// TimingPasswordSent is emitted after password is injected (duration of injection).
	TimingPasswordSent = "password_sent"

	// TimingSessionEnd is emitted when the session ends (total session duration).
	TimingSessionEnd = "session_end"

	// TimingTotal is emitted with the total connection time (from connector.Run() start to end).
	TimingTotal = "total"
)

Timing event names used throughout the connector and CLI.

Variables

This section is empty.

Functions

func EmitWithValue

func EmitWithValue(name string, duration time.Duration)

EmitWithValue logs a timing event with a custom duration value. Useful when timing was measured elsewhere.

func MaybeWrapWithRecording

func MaybeWrapWithRecording(hostname string, args []string) (bool, error)

MaybeWrapWithRecording checks if recording is enabled and wraps the connection. Returns true if recording was started (caller should exit), false to continue normally.

func TimingEnabled

func TimingEnabled() bool

TimingEnabled returns true if detailed timing instrumentation is enabled. Timing is enabled when NSSH_DEBUG=1 environment variable is set.

Types

type Connector

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

Connector manages a PTY-based SSH connection with credential injection.

func NewConnector

func NewConnector(host, user string, pass *secret.Secret, sshArgs []string) *Connector

NewConnector creates a new PTY connector for SSH connections.

func (*Connector) GetStdinReader

func (c *Connector) GetStdinReader() io.Reader

GetStdinReader returns an io.Reader that reads from the connector's stdin channel. This allows diverting stdin to other consumers (like UI prompts) while ensuring no data is lost if ensureStdinReader has already consumed it.

func (*Connector) Run

func (c *Connector) Run(ctx context.Context) error

Run is the main entry point for the connector. It handles the full lifecycle including host key restart if needed.

func (*Connector) SetAcceptOnceMode

func (c *Connector) SetAcceptOnceMode(mode string)

SetAcceptOnceMode configures how AcceptOnce handles host keys: "pin" (default) pre-seeds a temp known_hosts with the observed key; "accept-new" uses TOFU.

func (*Connector) SetResolvedEndpoint

func (c *Connector) SetResolvedEndpoint(host, port string)

SetResolvedEndpoint sets the concrete hostname and port derived from SSH config. Used for host-key pinning (keyscan) when the user connects via an alias. NOT used as the SSH command target - we use the alias for proper config matching.

func (*Connector) SetTimeouts

func (c *Connector) SetTimeouts(cfg *config.SSHConnectionConfig)

SetTimeouts configures connection timeouts from config.

type HostKeyAction

type HostKeyAction int

HostKeyAction represents user's choice for host key handling.

const (
	HostKeyReject       HostKeyAction = iota // Reject connection
	HostKeyAcceptOnce                        // Accept for this session only (temp known_hosts)
	HostKeyAcceptAlways                      // Answer "yes" - SSH adds to real known_hosts
)

Host key actions for interactive verification prompts.

type HostKeyResult

type HostKeyResult int

HostKeyResult indicates how the caller should proceed after host key handling.

const (
	HostKeyResultContinue HostKeyResult = iota // Connection proceeds normally
	HostKeyResultAbort                         // User rejected, exit
	HostKeyResultRestart                       // Need to restart with temp known_hosts
)

Host key result codes indicating how the caller should proceed.

type RingBuffer

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

RingBuffer provides a fixed-size circular buffer for prompt detection. It maintains a sliding window of the most recent N bytes, ensuring password prompts are never split across truncation boundaries.

Thread-safety: RingBuffer is NOT thread-safe. Callers must synchronize access when used from multiple goroutines.

func NewRingBuffer

func NewRingBuffer(size int) *RingBuffer

NewRingBuffer creates a ring buffer with the specified capacity.

func (*RingBuffer) Len

func (r *RingBuffer) Len() int

Len returns the number of bytes currently in the buffer.

func (*RingBuffer) LinearBytes

func (r *RingBuffer) LinearBytes() []byte

LinearBytes returns buffer contents as a contiguous slice. The returned slice is only valid until the next Write call.

Performance: Uses zero-copy fast path when data hasn't wrapped. When data wraps around, copies to pre-allocated linearBuf.

func (*RingBuffer) Reset

func (r *RingBuffer) Reset()

Reset clears the buffer.

func (*RingBuffer) Write

func (r *RingBuffer) Write(p []byte)

Write appends bytes to the buffer, overwriting oldest data if full. Uses bulk copy operations for performance in the PTY hot path.

type TestConfig

type TestConfig struct {
	Timeout  time.Duration  // Connection timeout (default 10s)
	Password *secret.Secret // Password for auth testing (nil = BatchMode)
	Port     string         // SSH port (empty = use SSH config default)
	// UseSystemKnownHosts controls whether probes write to the user's real
	// known_hosts file. Default false uses a temp file to avoid persistence.
	UseSystemKnownHosts bool
	// ConfigFile specifies a custom SSH config file to use (-F flag).
	// If empty, uses the default SSH config.
	ConfigFile string
}

TestConfig configures the connection test.

func DefaultTestConfig

func DefaultTestConfig() TestConfig

DefaultTestConfig returns sensible defaults for connection testing.

type TestResult

type TestResult struct {
	Success    bool   // Whether the connection/auth succeeded
	ExitCode   int    // SSH exit code
	Stderr     string // SSH verbose output (combined stdout/stderr from PTY)
	AuthMethod string // Detected auth method from verbose output
}

TestResult holds the outcome of a connection test.

func TestConnection

func TestConnection(ctx context.Context, hostname, username string, cfg TestConfig) (*TestResult, error)

TestConnection runs a non-interactive SSH connection test. It uses -vv for verbose output to capture negotiation details. If password is nil, uses BatchMode (compat-only testing). If password is provided, injects it via PTY (full auth testing).

type TimingEvent

type TimingEvent struct {
	Name    string
	Started time.Time
}

TimingEvent represents a named timing measurement point. Use StartTiming() to create a new event, then call Emit() when the operation completes. The duration and event name will be written to stderr in a parseable format.

func StartTiming

func StartTiming(name string) *TimingEvent

StartTiming creates a new timing event with the given name. The event's start time is recorded immediately. Call Emit() when the operation completes to log the duration.

Example:

t := StartTiming("pty_start")
defer t.Emit()
// ... operation ...

func (*TimingEvent) Emit

func (t *TimingEvent) Emit()

Emit logs the timing event to stderr if timing is enabled. The output format is: NSSH_TIMING:<event_name>:<duration_ms> This format is designed to be easily parsed by benchmark tools.

If timing is not enabled (NSSH_DEBUG != "1"), this is a no-op.

Jump to

Keyboard shortcuts

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