ssh

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2026 License: MIT Imports: 28 Imported by: 0

README

internal/ssh

SSH connection management, command execution, SFTP transfers, and port forwarding for ssh-mcp.

Purpose

This package is the core SSH layer of ssh-mcp. It manages connections to multiple named servers, executes commands in exec or persistent-shell mode, performs SFTP file transfers with path validation, and manages local and remote port forwarding. It also defines the typed error contract (ToolError with stable codes) that the MCP tools surface to agents.

Contents

  • client.go – the SSHClient interface and realClient implementation. Handles connection establishment (direct or via proxy), keepalive goroutine, and mutex-protected access to the underlying *ssh.Client. Also defines ExecResult.
  • manager.go – ConnectionManager for multiple named servers. Supports lazy connection, concurrent-attempt deduplication, reconnection with exponential backoff, parallel ConnectAll, graceful Disconnect, Invalidate to force a reconnect on next use, and Reload to atomically swap the configuration. Reload closes connections for removed or changed servers, preserves connections for unchanged servers, and always rebuilds command policies from the new config.
  • auth.go – BuildAuthMethods constructs SSH auth methods in order: private key, agent, password, keyboard-interactive. Only configured methods are included.
  • exec.go – ExecCommand runs a command in exec mode. Applies the command template, validates against the policy, allocates a PTY when enabled, caps output, enforces timeout, and extracts exit code and signal.
  • shell.go – persistent shell session mode. Uses random marker lines to frame each command and capture its output and exit code, with a ready-detection handshake and per-session mutex.
  • sftp.go – SFTPClient wraps github.com/pkg/sftp with local and remote path validation and concurrent transfer support for uploads, downloads, directory sync, and remote listings.
  • forward.go – ForwardManager for local and remote port forwarding with open, close, and list actions; tracks active forwards per connection.
  • proxy.go – dialProxy dials a target through a SOCKS5, HTTP CONNECT, or HTTPS CONNECT proxy.
  • errors.go – ToolError type, Code constants (stable strings such as SSH_CONNECTION_FAILED, COMMAND_TIMEOUT), NewToolError, AsToolError, and CodeFromError.
  • shell_test.go, integration_test.go, parallel_integration_test.go, reload_test.go – unit and integration tests, including tests for ConnectionManager.Reload.

Integration with the project

  • Created by internal/mcp.New, which passes the loaded *config.Config to NewConnectionManager and registers the MCP tools that call into it.
  • Depends on internal/config (server configs, timeouts), internal/policy (command validation), and internal/paths (SFTP path validation).
  • Uses golang.org/x/crypto/ssh for the SSH protocol, github.com/pkg/sftp for SFTP, and golang.org/x/net/proxy for SOCKS5.

Notes

  • HostKeyCallback is set to ssh.InsecureIgnoreHostKey. This is a known caveat: host key verification is not performed. Operators in security-sensitive environments should be aware of this before deploying.
  • The keepalive goroutine sends keepalive@openssh.com global requests at keepalive_interval and closes the client after keepalive_count_max consecutive failures, forcing a reconnect on next use.
  • ToolError carries a stable Code and a Retriable flag so agent-side retry logic can decide whether to retry a failed operation. The error codes mirror the original TypeScript implementation to keep that contract stable.
  • All access to the underlying *ssh.Client is guarded by a read/write mutex to prevent data races between keepalive, close, session creation, and connection checks.

Documentation

Overview

Package ssh provides SSH connection management, command execution, SFTP file transfers, and port forwarding for the ssh-mcp server.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BuildAuthMethods

func BuildAuthMethods(srv *config.ServerConfig) ([]ssh.AuthMethod, error)

BuildAuthMethods constructs the list of SSH authentication methods from the server configuration. The order is: private key, agent, password, keyboard-interactive. Only configured methods are included. Returns an error if no method can be constructed.

func SFTPTimeout

func SFTPTimeout(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc)

SFTPTimeout applies the configured SFTP timeout to the context.

Types

type Code

type Code string

Code is a stable error code that agents can inspect to decide retry logic.

const (
	CodeCommandValidationFailed Code = "COMMAND_VALIDATION_FAILED"
	CodeCommandExecutionError   Code = "COMMAND_EXECUTION_ERROR"
	CodeOutputLimitExceeded     Code = "OUTPUT_LIMIT_EXCEEDED"
	CodeCommandTimeout          Code = "COMMAND_TIMEOUT"
	CodeSSHConnectionFailed     Code = "SSH_CONNECTION_FAILED"
	CodeSSHConnectionTimeout    Code = "SSH_CONNECTION_TIMEOUT"
	CodeSSHAuthMissing          Code = "SSH_AUTHENTICATION_MISSING"
	CodeLocalPathNotAllowed     Code = "LOCAL_PATH_NOT_ALLOWED"
	CodeRemotePathNotAllowed    Code = "REMOTE_PATH_NOT_ALLOWED"
	CodeLocalFileReadFailed     Code = "LOCAL_FILE_READ_FAILED"
	CodeLocalFileWriteFailed    Code = "LOCAL_FILE_WRITE_FAILED"
	CodeOperationTimeout        Code = "OPERATION_TIMEOUT"
	CodeSFTPError               Code = "SFTP_ERROR"
	CodeUnsupportedInShellMode  Code = "UNSUPPORTED_IN_SHELL_MODE"
	CodeUnknownError            Code = "UNKNOWN_ERROR"
)

func CodeFromError

func CodeFromError(err error, fallback Code) Code

CodeFromError extracts the Code from a ToolError, returning fallback if the error is not a ToolError.

type ConnectionManager

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

ConnectionManager manages SSH connections for multiple named servers. It supports lazy connection, reconnection with backoff, and graceful shutdown.

func NewConnectionManager

func NewConnectionManager(cfg *config.Config) *ConnectionManager

NewConnectionManager creates a manager from the given configuration. Connections are not established until Connect or ConnectAll is called.

func (*ConnectionManager) Config

func (m *ConnectionManager) Config() *config.Config

Config returns the underlying configuration.

func (*ConnectionManager) ConnectAll

func (m *ConnectionManager) ConnectAll(ctx context.Context) error

ConnectAll connects to all configured servers in parallel. Errors for individual servers are logged but do not stop other connections.

func (*ConnectionManager) Disconnect

func (m *ConnectionManager) Disconnect()

Disconnect closes all connections and marks the manager as closed.

func (*ConnectionManager) GetClient

func (m *ConnectionManager) GetClient(ctx context.Context, name string) (SSHClient, error)

GetClient returns the SSHClient for the named server, connecting lazily if needed. If name is empty, the default (first) server is used.

func (*ConnectionManager) Invalidate

func (m *ConnectionManager) Invalidate(name string)

Invalidate closes the connection for the named server, forcing a reconnect on the next GetClient call.

func (*ConnectionManager) ListServers

func (m *ConnectionManager) ListServers() []string

ListServers returns the names of all configured servers.

func (*ConnectionManager) Policy

func (m *ConnectionManager) Policy(name string) *policy.Policy

Policy returns the policy for the named server.

func (*ConnectionManager) Reload

func (m *ConnectionManager) Reload(newCfg *config.Config)

Reload atomically replaces the manager's configuration with newCfg.

For each server:

  • If the server was removed, its connection is closed.
  • If the server's config changed in any way, its connection is closed so the next GetClient reconnects with the new settings.
  • If the server is unchanged, its connection is preserved.

Policies are always rebuilt from the new config so that whitelist and blacklist changes take effect immediately without reconnection.

If a new server's policy fails to compile, the old policy for that server name is retained (if any) so the server remains usable.

type ExecResult

type ExecResult struct {
	Stdout    string `json:"stdout"`
	Stderr    string `json:"stderr"`
	ExitCode  int    `json:"exitCode"`
	Duration  string `json:"duration"`
	Truncated bool   `json:"truncated"`
	Signal    string `json:"signal,omitempty"`
}

ExecResult holds the result of a command execution.

func ExecCommand

func ExecCommand(ctx context.Context, client SSHClient, command string, directory string, timeout time.Duration) (*ExecResult, error)

ExecCommand runs a command on the remote server in exec mode. It handles PTY allocation, output capping, timeout, and exit code extraction. The command is validated against the server's policy before execution.

func ExecCommandBypass

func ExecCommandBypass(ctx context.Context, client SSHClient, command string, directory string, timeout time.Duration) (*ExecResult, error)

ExecCommandBypass runs a command without policy validation. This is used by the status collector, which validates each probe command individually before batching them into a single script. The combined script would not match any whitelist pattern, so it must bypass the policy check.

type ForwardAction

type ForwardAction string

ForwardAction specifies what to do with a forward.

const (
	ForwardOpen  ForwardAction = "open"
	ForwardClose ForwardAction = "close"
	ForwardList  ForwardAction = "list"
)

type ForwardDirection

type ForwardDirection string

ForwardDirection specifies whether a forward is local or remote.

const (
	ForwardLocal  ForwardDirection = "local"
	ForwardRemote ForwardDirection = "remote"
)

type ForwardEntry

type ForwardEntry struct {
	ID         string           `json:"id"`
	Direction  ForwardDirection `json:"direction"`
	LocalAddr  string           `json:"localAddr"`
	RemoteAddr string           `json:"remoteAddr"`
	// contains filtered or unexported fields
}

ForwardEntry represents an active port forward.

type ForwardManager

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

ForwardManager manages SSH port forwards for a single SSH connection.

func NewForwardManager

func NewForwardManager() *ForwardManager

NewForwardManager creates a new ForwardManager.

func (*ForwardManager) CloseAll

func (fm *ForwardManager) CloseAll()

CloseAll closes all active port forwards.

func (*ForwardManager) CloseForward

func (fm *ForwardManager) CloseForward(id string) error

CloseForward closes a port forward by ID.

func (*ForwardManager) ListForwards

func (fm *ForwardManager) ListForwards() []*ForwardEntry

ListForwards returns all active port forwards.

func (*ForwardManager) OpenLocalForward

func (fm *ForwardManager) OpenLocalForward(ctx context.Context, client SSHClient, localAddr, remoteAddr string) (*ForwardEntry, error)

OpenLocalForward opens a local port forward: local -> remote via SSH. The forward persists until explicitly closed via CloseForward or CloseAll, independent of the request context.

func (*ForwardManager) OpenRemoteForward

func (fm *ForwardManager) OpenRemoteForward(ctx context.Context, client SSHClient, localAddr, remoteAddr string) (*ForwardEntry, error)

OpenRemoteForward opens a remote port forward: remote -> local via SSH. The forward persists until explicitly closed via CloseForward or CloseAll, independent of the request context.

type SFTPClient

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

SFTPClient wraps an sftp.Client with concurrent transfer support and path validation.

func NewSFTPClient

func NewSFTPClient(sshClient SSHClient) (*SFTPClient, error)

NewSFTPClient creates a new SFTP wrapper from an SSH client.

func (*SFTPClient) Close

func (s *SFTPClient) Close() error

Close closes the SFTP client.

func (*SFTPClient) DirSync

func (s *SFTPClient) DirSync(ctx context.Context, direction, localPath, remotePath string) error

DirSync recursively syncs a directory between local and remote.

func (*SFTPClient) Download

func (s *SFTPClient) Download(ctx context.Context, remotePath, localPath string) error

Download transfers a remote file to the local machine using concurrent SFTP. It downloads to a temporary file first, then atomically renames it.

func (*SFTPClient) ReadDir

func (s *SFTPClient) ReadDir(ctx context.Context, remotePath string) ([]os.FileInfo, error)

ReadDir lists the contents of a remote directory.

func (*SFTPClient) Upload

func (s *SFTPClient) Upload(ctx context.Context, localPath, remotePath string) error

Upload transfers a local file to the remote server using concurrent SFTP.

type SSHClient

type SSHClient interface {
	// Connect establishes the SSH connection.
	Connect(ctx context.Context) error
	// Close closes the underlying SSH connection.
	Close() error
	// IsConnected reports whether the connection is alive.
	IsConnected() bool
	// NewSession opens a new SSH session for command execution.
	NewSession() (*ssh.Session, error)
	// Client returns the underlying *ssh.Client (for SFTP, forwarding, etc.).
	Client() *ssh.Client
	// Config returns the server configuration.
	Config() *config.ServerConfig
	// Policy returns the command policy for this server.
	Policy() *policy.Policy
}

SSHClient is the interface for SSH operations. The real implementation wraps *ssh.Client; tests use fakes.

func NewClient

func NewClient(srv *config.ServerConfig, pol *policy.Policy) SSHClient

NewClient creates a new SSHClient for the given server configuration.

type ShellRunner

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

ShellRunner manages a persistent shell session for a single SSH connection. Commands are serialized via a command queue.

func NewShellRunner

func NewShellRunner(client SSHClient) *ShellRunner

NewShellRunner creates a new ShellRunner for the given client.

func (*ShellRunner) Close

func (r *ShellRunner) Close() error

Close closes the shell session.

func (*ShellRunner) EnsureReady

func (r *ShellRunner) EnsureReady(ctx context.Context) error

EnsureReady opens the shell session if not already open and waits for the shell to be ready.

func (*ShellRunner) ExecCommand

func (r *ShellRunner) ExecCommand(ctx context.Context, command string, directory string, timeout time.Duration) (*ExecResult, error)

ExecCommand runs a command in shell mode.

type ToolError

type ToolError struct {
	Code      Code
	Message   string
	Retriable bool
}

ToolError is a typed error carrying a stable code and a retriable flag. It mirrors the error contract of the original TypeScript implementation so agent-side retry logic remains the same.

func AsToolError

func AsToolError(err error, fallback Code) *ToolError

AsToolError converts an arbitrary error into a ToolError. If the error is already a ToolError it is returned as-is. Otherwise it is wrapped with the provided fallback code.

func NewToolError

func NewToolError(code Code, message string, retriable bool) *ToolError

NewToolError creates a ToolError with the given code, message, and retriable flag.

func (*ToolError) Error

func (e *ToolError) Error() string

Error implements the error interface.

func (*ToolError) Unwrap

func (e *ToolError) Unwrap() error

Unwrap allows errors.Is and errors.As to inspect the underlying cause.

Jump to

Keyboard shortcuts

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