Skip to content
121 changes: 120 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,87 @@ Focus on more impactful optimizations like:
- Early termination on critical failures
- Parallel DNS resolution

### 6. Node Management (`node.rs`)
### 6. SSH Configuration Caching (`ssh/config_cache.rs`)

**Status:** Implemented (Phase 4, 2025-08-28)

**Design Motivation:**
SSH configuration files are frequently accessed and parsed during bssh operations, especially for multi-node commands. Caching eliminates redundant file I/O and parsing overhead, providing significant performance improvements for repeated operations.

**Implementation Details:**
- **LRU Cache:** Uses `lru` crate with configurable size (default: 100 entries)
- **TTL Support:** Time-to-live expiration (default: 5 minutes)
- **File Modification Detection:** Automatic cache invalidation via file mtime comparison
- **Thread Safety:** `Arc<RwLock<LruCache>>` for concurrent access
- **Global Instance:** Lazy-initialized singleton via `once_cell`

**Cache Entry Structure:**
```rust
struct CacheEntry {
config: SshConfig, // Parsed SSH configuration
cached_at: Instant, // Creation timestamp
file_mtime: SystemTime, // File modification time
access_count: u64, // Number of accesses
last_accessed: Instant, // Last access timestamp
}
```

**Cache Invalidation Strategy:**
1. **TTL Expiration:** Remove entries older than configured TTL
2. **File Modification:** Detect changes via mtime comparison
3. **LRU Eviction:** Remove least recently used entries when full
4. **Manual Maintenance:** Periodic cleanup of expired entries

**API Design:**
```rust
// Cached versions (recommended)
SshConfig::load_from_file_cached(path)?;
SshConfig::load_default_cached()?;

// Original versions (still supported)
SshConfig::load_from_file(path)?;
SshConfig::load_default()?;

// Direct cache access
GLOBAL_CACHE.stats();
GLOBAL_CACHE.clear();
GLOBAL_CACHE.maintain();
```

**Configuration (Environment Variables):**
- `BSSH_CACHE_ENABLED=true/false` - Enable/disable caching (default: true)
- `BSSH_CACHE_SIZE=100` - Maximum entries (default: 100)
- `BSSH_CACHE_TTL=300` - TTL in seconds (default: 300)

**Performance Impact:**
- **Cache Hits:** 10-100x faster than file access
- **Reduced I/O:** Eliminates repeated file reads
- **Lower CPU:** Avoids re-parsing SSH config syntax
- **Memory Overhead:** ~1KB per cached config entry

**CLI Integration:**
New `cache-stats` command provides comprehensive monitoring:
```bash
bssh cache-stats # Basic statistics
bssh cache-stats --detailed # Per-entry information
bssh cache-stats --clear # Clear cache
bssh cache-stats --maintain # Remove expired entries
```

**Security Considerations:**
- Path canonicalization prevents traversal attacks
- No sensitive data cached (only configuration)
- Atomic cache operations prevent corruption
- Safe defaults for security-critical environments

**Test Coverage:**
- 10 comprehensive test cases covering all scenarios
- Cache hit/miss behavior validation
- File modification detection testing
- TTL expiration and LRU eviction testing
- Thread safety and concurrent access testing

### 7. Node Management (`node.rs`)

**Design Decisions:**
- Flexible parsing supporting multiple formats
Expand Down Expand Up @@ -285,6 +365,45 @@ Focus on more impactful optimizations like:
2. **Pipelining:** Send multiple commands in single session
3. **Compression:** Enable SSH compression for large outputs
4. **Caching:** Cache host keys and authentication
5. **Environment Variable Caching:** Cache safe environment variables for path expansion

### Environment Variable Caching (Added 2025-01-28)

To improve performance during SSH configuration path expansion, bssh implements a comprehensive environment variable cache:

**Implementation:** `src/ssh/ssh_config/env_cache.rs`
- Thread-safe LRU cache with configurable TTL (default: 30 seconds)
- Whitelisted safe variables only (HOME, USER, SSH_AUTH_SOCK, etc.)
- O(1) lookups using HashMap storage
- Automatic expiration and size-based eviction

**Performance Impact:**
- 6x faster path expansion (387µs → 60µs in benchmarks)
- 99%+ cache hit rate in typical usage
- Reduces system calls from repeated `std::env::var()` calls
- Memory overhead: ~50 environment variables max (configurable)

**Security Features:**
- Only whitelisted safe variables are cached
- Dangerous variables (PATH, LD_PRELOAD, etc.) are blocked
- Defense-in-depth: both cache and path expansion validate safety
- TTL prevents stale values from persisting

**Configuration:**
- `BSSH_ENV_CACHE_TTL`: Cache TTL in seconds (default: 30)
- `BSSH_ENV_CACHE_SIZE`: Max cache entries (default: 50)
- `BSSH_ENV_CACHE_ENABLED`: Enable/disable caching (default: true)

**Usage Pattern:**
```rust
// Automatic caching during path expansion
let expanded = expand_path_internal("${HOME}/.ssh/config");

// Direct cache access (for advanced use)
if let Ok(Some(home)) = GLOBAL_ENV_CACHE.get_env_var("HOME") {
// Use cached HOME value
}
```

## Interactive Mode Architecture

Expand Down
74 changes: 74 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ signal-hook = "0.3.18"
atty = "0.2.14"
arrayvec = "0.7.6"
smallvec = "1.13.2"
lru = "0.12"

[dev-dependencies]
tempfile = "3"
mockito = "1"
once_cell = "1.21.3"
tokio-test = "0.4"
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ A high-performance SSH client with **SSH-compatible syntax** for both single-hos
- **Cross-Platform**: Works on Linux and macOS
- **Output Management**: Save command outputs to files per node with detailed logging
- **Interactive Mode**: Interactive shell sessions with single-node or multiplexed multi-node support
- **SSH Config Caching**: High-performance caching of SSH configurations with TTL and file modification detection
- **Configurable Timeouts**: Set command execution timeouts with support for unlimited execution (timeout=0)

## Installation
Expand Down
22 changes: 19 additions & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ use std::path::PathBuf;
version,
before_help = "\n\nBackend.AI SSH - Parallel command execution across cluster nodes",
about = "Backend.AI SSH - SSH-compatible parallel command execution tool",
long_about = "bssh is a high-performance SSH client with parallel execution capabilities.\nIt can be used as a drop-in replacement for SSH (single host) or as a powerful cluster management tool (multiple hosts).\n\nThe tool provides secure file transfer using SFTP and supports SSH keys, SSH agent, and password authentication.\nIt automatically detects Backend.AI multi-node session environments.",
after_help = "EXAMPLES:\n SSH Mode:\n bssh user@host # Interactive shell\n bssh admin@server.com \"uptime\" # Execute command\n bssh -p 2222 -i ~/.ssh/key user@host # Custom port and key\n\n Multi-Server Mode:\n bssh -C production \"systemctl status\" # Use cluster config\n bssh -H \"web1,web2,web3\" \"df -h\" # Direct hosts\n\n File Operations:\n bssh -C staging upload file.txt /tmp/ # Upload to cluster\n bssh -H host1,host2 download /etc/hosts ./backups/\n\n Other Commands:\n bssh list # List configured clusters\n bssh -C production ping # Test connectivity\n\nDeveloped and maintained as part of the Backend.AI project.\nFor more information: https://github.com/lablup/bssh"
long_about = "bssh is a high-performance SSH client with parallel execution capabilities.\nIt can be used as a drop-in replacement for SSH (single host) or as a powerful cluster management tool (multiple hosts).\n\nThe tool provides secure file transfer using SFTP and supports SSH keys, SSH agent, and password authentication.\nIt automatically detects Backend.AI multi-node session environments.\n\nSSH Configuration Support:\n- Reads standard SSH config files (defaulting to ~/.ssh/config)\n- Supports Host patterns, HostName, User, Port, IdentityFile, StrictHostKeyChecking\n- ProxyJump, and many other SSH configuration directives\n- CLI arguments override SSH config values following SSH precedence rules",
after_help = "EXAMPLES:\n SSH Mode:\n bssh user@host # Interactive shell\n bssh admin@server.com \"uptime\" # Execute command\n bssh -p 2222 -i ~/.ssh/key user@host # Custom port and key\n bssh -F ~/.ssh/myconfig webserver # Use custom SSH config\n\n Multi-Server Mode:\n bssh -C production \"systemctl status\" # Use cluster config\n bssh -H \"web1,web2,web3\" \"df -h\" # Direct hosts\n bssh -F /etc/ssh/ssh_config -H web* # SSH config with wildcards\n\n File Operations:\n bssh -C staging upload file.txt /tmp/ # Upload to cluster\n bssh -H host1,host2 download /etc/hosts ./backups/\n\n Other Commands:\n bssh list # List configured clusters\n bssh -C production ping # Test connectivity\n\n SSH Config Example (~/.ssh/config):\n Host web*\n HostName web.example.com\n User webuser\n Port 2222\n IdentityFile ~/.ssh/web_key\n StrictHostKeyChecking yes\n\nDeveloped and maintained as part of the Backend.AI project.\nFor more information: https://github.com/lablup/bssh"
)]
pub struct Cli {
/// SSH destination in format: [user@]hostname[:port] or ssh://[user@]hostname[:port]
Expand Down Expand Up @@ -137,7 +137,7 @@ pub struct Cli {
short = 'F',
long = "ssh-config",
value_name = "configfile",
help = "Specifies an alternative SSH configuration file"
help = "Specifies an alternative SSH configuration file\nSupports standard SSH config format with Host, HostName, User, Port, IdentityFile, etc.\nDefaults to ~/.ssh/config if not specified and file exists"
)]
pub ssh_config: Option<PathBuf>,

Expand Down Expand Up @@ -299,6 +299,22 @@ pub enum Commands {
)]
work_dir: Option<String>,
},

#[command(
about = "Display SSH config cache statistics",
long_about = "Shows detailed statistics and debug information about the SSH configuration cache.\nIncludes hit rates, cache size, eviction counts, and entry details.\nUseful for performance monitoring and cache tuning.\n\nCache can be configured via environment variables:\n BSSH_CACHE_ENABLED=true/false - Enable/disable caching\n BSSH_CACHE_SIZE=100 - Maximum cache entries\n BSSH_CACHE_TTL=300 - TTL in seconds",
after_help = "Examples:\n bssh cache-stats # Show basic statistics\n bssh cache-stats --detailed # Show per-entry information\n bssh cache-stats --clear # Clear cache and show stats"
)]
CacheStats {
#[arg(long, help = "Show detailed per-entry information")]
detailed: bool,

#[arg(long, help = "Clear the cache before showing statistics")]
clear: bool,

#[arg(long, help = "Perform cache maintenance (remove expired entries)")]
maintain: bool,
},
}

impl Cli {
Expand Down
Loading