Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 182 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,17 +390,198 @@ Focus on more impactful optimizations like:

### 6. SSH Configuration File Support (`ssh/ssh_config/*`)

**Status:** Fully Implemented (2025-08-28)
**Status:** Fully Implemented (2025-08-28), Enhanced with Include/Match (2025-10-21)

**Features:**
- Complete SSH config file parsing with `-F` option
- Auto-loads from `~/.ssh/config` by default
- Supports 40+ SSH directives (Host, HostName, User, Port, IdentityFile, ProxyJump, etc.)
- **Include directive** with glob pattern support (OpenSSH 7.3+)
- **Match directive** with conditional configuration (host, user, localuser, exec, all)
- Wildcard pattern matching (`*`, `?`) and negation (`!`)
- Environment variable expansion in paths
- First-match-wins resolution (SSH-compatible)
- CLI arguments override config values

#### Include Directive Implementation

**Module:** `ssh/ssh_config/include.rs`

The Include directive allows loading configuration from external files, enabling modular SSH config management.

**Key Features:**
- **Glob Pattern Support:** `Include ~/.ssh/config.d/*`
- **Multiple Patterns:** `Include /etc/ssh/*.conf ~/.ssh/private/*`
- **Lexical Ordering:** Files matched by globs are processed in sorted order
- **Recursive Includes:** Included files can themselves contain Include directives
- **Tilde Expansion:** `~` expands to user home directory
- **Environment Variables:** Supports `${VAR}` expansion in paths

**Security Protections:**
- Maximum include depth: 10 levels (prevents infinite recursion)
- Maximum included files: 100 (DoS prevention)
- Cycle detection: Prevents circular Include references
- Permission warnings: Alerts on world-writable config files
- Path validation: Prevents directory traversal attacks

**Processing Order (SSH Spec Compliant):**
Include directives are processed at the location where they appear, not at the end:
```
# Main config
Host *.example.com
User defaultuser

Include ~/.ssh/config.d/* # ← Files inserted HERE

Host specific.example.com
Port 2222
```

**Implementation Algorithm:**
```rust
fn process_file_with_includes(file, content, context) -> Vec<IncludedFile> {
for line in content.lines() {
if is_include_directive(line) {
// Save accumulated content before Include
save_current_content();
// Recursively process included files at this location
include_files = resolve_include_pattern(pattern);
for inc_file in include_files {
result.append(process_file_with_includes(inc_file));
}
} else {
accumulate_line();
}
}
save_remaining_content();
}
```

#### Match Directive Implementation

**Module:** `ssh/ssh_config/match_directive.rs`

The Match directive provides conditional configuration based on connection criteria, more powerful than Host patterns.

**Supported Conditions:**
- `host <pattern>` - Match hostname patterns (supports wildcards)
- `user <pattern>` - Match remote username
- `localuser <pattern>` - Match local username (auto-detected via `whoami`)
- `exec <command>` - Match based on command exit status
- `all` - Matches all connections

**Condition Logic:**
- Multiple conditions use AND semantics (all must match)
- Example: `Match host *.prod.com user admin` requires BOTH conditions

**Match vs Host Precedence:**
Both Host and Match blocks are evaluated in order of appearance. First match wins per option.

**Examples:**
```ssh
# Match production hosts for admin user
Match host *.prod.example.com user admin
ForwardAgent yes
IdentityFile ~/.ssh/prod_admin_key

# Match developer's local machine
Match localuser developer
RequestTTY yes
ForwardX11 yes

# Match VPN-connected machines
Match exec "test -f /tmp/vpn-connected"
ProxyJump vpn-gateway.example.com

# Global defaults
Match all
ServerAliveInterval 60
ServerAliveCountMax 3
```

**exec Condition Security:**
The exec condition executes commands, requiring security measures:
- **Command Validation:** Blocks dangerous patterns (rm, dd, pipes, redirects, etc.)
- **Timeout:** 5-second execution limit
- **Length Limit:** Maximum 1024 bytes
- **Variable Expansion:** Supports %h (hostname), %u (user), %l (local user)
- **Environment:** Sets SSH_MATCH_* environment variables
- **Logging:** All exec commands are logged for audit

Blocked patterns in exec commands:
```rust
const DANGEROUS_PATTERNS: &[&str] = &[
"rm ", "dd ", "mkfs", "format", "fdisk",
">", "|", ";", "&", "`", "$(",
];
```

**MatchContext Evaluation:**
```rust
pub struct MatchContext {
hostname: String, // Connection target
remote_user: Option<String>, // Remote username (if specified)
local_user: String, // Current user (auto-detected)
variables: HashMap<String, String>, // For exec expansion
}
```

#### Parser Architecture (2-Pass Design)

**Pass 1: Include Resolution**
- Process Include directives recursively
- Build complete configuration by inserting included files at directive locations
- Detect cycles and enforce depth limits
- Result: Flat list of configuration chunks in proper order

**Pass 2: Block Parsing**
- Parse Host and Match blocks
- Parse configuration options within each block
- Support both `Option Value` and `Option=Value` syntax
- Validate options and enforce security rules

**Module Structure:**
```
ssh/ssh_config/
├── mod.rs # Public API and coordination
├── parser.rs # 2-pass parsing logic
├── types.rs # SshHostConfig, ConfigBlock enums
├── include.rs # Include directive processing
├── match_directive.rs # Match condition evaluation
├── resolver.rs # Configuration resolution with Match support
├── pattern.rs # Wildcard pattern matching
├── path.rs # Path expansion utilities
└── security/ # Security validation modules
├── checks.rs
├── path_validation.rs
└── string_validation.rs
```

**Configuration Resolution with Match:**
```rust
pub fn find_host_config(hosts: &[SshHostConfig], hostname: &str) -> SshHostConfig {
let context = MatchContext::new(hostname, remote_user);

for host_config in hosts {
let should_apply = match host_config.block_type {
Host(patterns) => matches_host_pattern(hostname, patterns),
Match(conditions) => match_block.matches(&context),
};

if should_apply {
merge_host_config(&mut result, host_config);
}
}
}
```

**Test Coverage:**
- Include: glob patterns, cycle detection, depth limits, ordering
- Match: all condition types, AND logic, precedence
- Integration: Include + Match combinations, nested includes
- Security: exec validation, path traversal prevention
- **Total:** 62 tests passing

### 7. SSH Configuration Caching (`ssh/config_cache/*`)

**Status:** Implemented (2025-08-28), Refactored (2025-10-17)
Expand Down
7 changes: 7 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ lru = "0.16"
uuid = { version = "1.10", features = ["v4"] }
fastrand = "2.2"
tokio-util = "0.7"
shell-words = "1.1"

[dev-dependencies]
tempfile = "3"
Expand Down
Loading