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
145 changes: 145 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1986,6 +1986,151 @@ forwarding:
- `BSSH_FORWARD_RETRIES`: Retry attempts
- `BSSH_FORWARD_BUFFER`: Buffer size

## SSH Configuration Parser

The SSH configuration parser provides comprehensive support for OpenSSH configuration files, implementing various configuration options in multiple phases for maintainability and feature completeness.

### Architecture

The parser is implemented as a modular system with the following structure:

```
src/ssh/ssh_config/
├── parser/
│ ├── core.rs # Core parsing logic with 2-pass strategy
│ ├── helpers.rs # Helper functions (parse_yes_no, etc.)
│ ├── options/ # Option parsing modules
│ │ ├── authentication.rs
│ │ ├── basic.rs
│ │ ├── command.rs # Phase 3: Command execution options
│ │ ├── connection.rs
│ │ ├── control.rs
│ │ ├── environment.rs
│ │ ├── forwarding.rs
│ │ ├── proxy.rs
│ │ ├── security.rs
│ │ └── ui.rs
│ └── tests.rs
├── security/ # Security validation
│ ├── string_validation.rs
│ └── path_validation.rs
├── types.rs # Core data structures
└── resolver.rs # Host configuration resolution
```

### Implementation Phases

The SSH configuration parser has been implemented in multiple phases:

#### Phase 1: Basic Options (Completed)
- **Option=Value syntax**: Support for both space and equals-separated options
- **Basic options**: Hostname, User, Port, IdentityFile
- **Authentication**: PubkeyAuthentication, PasswordAuthentication
- **Connection**: ServerAliveInterval, ConnectTimeout, etc.

#### Phase 2: Certificate Authentication and Port Forwarding (Completed)
- **Certificate support**: CertificateFile, CASignatureAlgorithms
- **Advanced forwarding**: GatewayPorts, ExitOnForwardFailure, PermitRemoteOpen
- **Hostbased auth**: HostbasedAuthentication, HostbasedAcceptedAlgorithms

#### Phase 3: Command Execution and Automation (Completed)
Command execution options enable sophisticated automation workflows:

##### LocalCommand and PermitLocalCommand
- **Purpose**: Execute commands locally after SSH connection
- **Security**: Requires explicit PermitLocalCommand=yes
- **Token substitution**: Supports %h, %H, %n, %p, %r, %u tokens
- **Validation**: Commands are validated against injection attacks
- **Use cases**: File synchronization, notifications, environment setup

##### RemoteCommand
- **Purpose**: Execute command on remote host instead of shell
- **Security**: No local validation (runs on remote)
- **Use cases**: Auto-attach tmux, enter specific environments

##### KnownHostsCommand
- **Purpose**: Dynamically fetch host keys
- **Security**: Command path validation, timeout protection
- **Token substitution**: Supports %h and %H tokens
- **Use cases**: Cloud environments, certificate authorities

##### Additional Automation Options
- **ForkAfterAuthentication**: Fork SSH into background after auth
- **SessionType**: Control session type (none/subsystem/default)
- **StdinNull**: Redirect stdin from /dev/null for scripting

### Security Model

The parser implements multiple layers of security validation:

#### Command Injection Prevention
```rust
// Security validation for executable commands
fn validate_executable_string(value: &str, option_name: &str, line_number: usize) -> Result<()> {
// Check for dangerous shell metacharacters
const DANGEROUS_CHARS: &[char] = &[
';', // Command separator
'&', // Background/separator
'|', // Pipe
'`', // Command substitution
'$', // Variable/command expansion
'>', // Redirection
'<', // Redirection
'\n', // Newline
'\r', // Carriage return
'\0', // Null byte
];
// ... validation logic
}
```

#### Token Substitution Security
The parser validates SSH tokens while preventing injection:
- **Valid tokens**: %h (hostname), %H (hostname), %n (original), %p (port), %r (remote user), %u (local user), %% (literal %)
- **Invalid patterns**: Detected and rejected during parsing
- **Substitution timing**: Tokens are validated but not substituted by parser (client responsibility)

#### Path Validation
- Tilde expansion support with security checks
- Prevention of path traversal attacks
- Validation against sensitive system paths
- Symlink resolution safety

### Testing Strategy

Comprehensive test coverage includes:

1. **Unit tests**: Each option parser module has internal tests
2. **Integration tests**: Full configuration parsing scenarios
3. **Security tests**: Injection attempts, malformed input
4. **Edge cases**: Empty values, whitespace, special characters

Test files:
- `src/ssh/ssh_config/parser/options/command.rs`: Unit tests for command options
- `tests/ssh_config_command_options_test.rs`: Integration tests for Phase 3

### Performance Considerations

- **Two-pass parsing**: Handles Include directives efficiently
- **Lazy resolution**: Configuration merging only when needed
- **String allocation**: Minimized through careful use of references
- **Validation caching**: Results cached where possible

### Future Enhancements

Planned phases for complete OpenSSH compatibility:

#### Phase 4: Include and Match Directives
- **Include**: Recursive configuration file inclusion
- **Match**: Conditional configuration blocks
- **Pattern matching**: Host patterns with wildcards

#### Phase 5: Advanced Features
- **ProxyCommand**: Custom proxy commands
- **ProxyJump**: Multi-hop SSH connections
- **ControlMaster**: Connection multiplexing
- **Additional options**: As needed for compatibility

## Dependencies and Licensing

All dependencies are compatible with Apache-2.0 licensing:
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `ExitOnForwardFailure` - Terminate connection when port forwarding fails
- `PermitRemoteOpen` - Specify allowed destinations for remote TCP port forwarding (maximum 1000 entries)

- **SSH Configuration: Command Execution and Automation Options**
- `PermitLocalCommand` - Allow execution of local commands after successful SSH connection (yes/no, default: no)
- `LocalCommand` - Execute local command after connection with token substitution support (%h, %H, %n, %p, %r, %u, %%)
- `RemoteCommand` - Execute command on remote host instead of starting interactive shell
- `KnownHostsCommand` - Execute command to obtain host keys dynamically (supports token substitution)
- `ForkAfterAuthentication` - Fork SSH process to background after successful authentication (yes/no)
- `SessionType` - Specify session type: none (port forwarding only), subsystem (e.g., SFTP), or default (shell)
- `StdinNull` - Redirect stdin from /dev/null for background operations and scripting (yes/no)

- **Security Enhancements**
- Path validation to prevent usage of sensitive system files (e.g., /etc/passwd, /etc/shadow)
- Memory exhaustion prevention with entry limits for certificates and forwarding rules
- Algorithm list validation with maximum entry limits
- Deduplication for certificate files and remote forwarding destinations
- Command injection prevention for LocalCommand and KnownHostsCommand
- Token validation to prevent invalid substitution patterns
- Dangerous character detection in command strings (semicolons, backticks, pipes, etc.)

### Changed
- **SSH Config Parser**: Refactored into modular structure for better maintainability
Expand Down
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,30 @@ These options provide fine-grained control over SSH port forwarding:
| **ExitOnForwardFailure** | Terminate connection if port forwarding fails (yes/no) | `ExitOnForwardFailure yes` |
| **PermitRemoteOpen** | Allowed destinations for remote forwarding (max 1000) | `PermitRemoteOpen localhost:8080` |

### Command Execution and Automation Options

These options enable powerful automation workflows and command execution features:

| Option | Description | Example |
|--------|-------------|---------|
| **PermitLocalCommand** | Allow local command execution after connection (yes/no, default: no) | `PermitLocalCommand yes` |
| **LocalCommand** | Execute local command after successful connection (supports tokens: %h, %H, %n, %p, %r, %u) | `LocalCommand rsync -av ~/project/ %h:~/project/` |
| **RemoteCommand** | Execute command on remote host instead of shell | `RemoteCommand tmux attach -t dev \|\| tmux new -s dev` |
| **KnownHostsCommand** | Command to fetch host keys dynamically (supports tokens) | `KnownHostsCommand /usr/local/bin/fetch-host-key %H` |
| **ForkAfterAuthentication** | Fork to background after authentication (yes/no) | `ForkAfterAuthentication yes` |
| **SessionType** | Session type: none/subsystem/default | `SessionType none` |
| **StdinNull** | Redirect stdin from /dev/null (yes/no) | `StdinNull yes` |

**Token Substitution:**
LocalCommand and KnownHostsCommand support the following tokens:
- `%h` - Remote hostname (from config)
- `%H` - Remote hostname (as specified on command line)
- `%n` - Original hostname
- `%p` - Remote port
- `%r` - Remote username
- `%u` - Local username
- `%%` - Literal percent sign

### SSH Config Examples

#### Certificate-based Authentication
Expand Down Expand Up @@ -422,13 +446,41 @@ Host *.secure.prod.example.com
PermitRemoteOpen cache.internal:6379
```

#### Command Execution and Automation

```ssh-config
# Development server with automatic file synchronization
Host dev-server
User developer
PermitLocalCommand yes
LocalCommand rsync -av ~/project/ %h:~/project/

# Auto-attach to tmux session on connection
Host project-server
RemoteCommand tmux attach -t project || tmux new -s project
RequestTTY yes

# Cloud instances with dynamic host key fetching
Host *.cloud.example.com
KnownHostsCommand /usr/local/bin/fetch-cloud-key %H
StrictHostKeyChecking accept-new

# Background SSH tunnel for port forwarding
Host tunnel
ForkAfterAuthentication yes
SessionType none
LocalForward 8080 internal-server:80
StdinNull yes
```

#### Complete Example with Include and Match

```ssh-config
# Base security settings
Host *
HostbasedAuthentication no
ExitOnForwardFailure no
PermitLocalCommand no

# Production certificate configuration
Host *.prod.example.com
Expand All @@ -442,6 +494,11 @@ Match host *.secure.prod.example.com
ExitOnForwardFailure yes
PermitRemoteOpen localhost:8080

# Development hosts with automation
Match host *.dev.example.com
PermitLocalCommand yes
LocalCommand notify-send "Connected to %h"

# Specific host overrides
Host web.secure.prod.example.com
User webadmin
Expand Down
116 changes: 116 additions & 0 deletions docs/man/bssh.1
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,100 @@ Example:
.br
.I PermitRemoteOpen db.internal:5432

.SS Command Execution and Automation Options
.TP
.B PermitLocalCommand
Specifies whether to allow execution of local commands after successful SSH connection.
Commands are executed on the local machine with the user's shell.
Default is no for security reasons.
.br
Example:
.I PermitLocalCommand yes

.TP
.B LocalCommand
Specifies a local command to execute after successfully connecting to the remote host.
Requires PermitLocalCommand to be enabled. Supports token substitution:
.RS
.IP \[bu] 2
.B %h
- Remote hostname (from config)
.IP \[bu] 2
.B %H
- Remote hostname (as specified on command line)
.IP \[bu] 2
.B %n
- Original hostname
.IP \[bu] 2
.B %p
- Remote port
.IP \[bu] 2
.B %r
- Remote username
.IP \[bu] 2
.B %u
- Local username
.IP \[bu] 2
.B %%
- Literal percent sign
.RE
.IP
Example:
.I LocalCommand rsync -av ~/project/ %h:~/project/

.TP
.B RemoteCommand
Specifies a command to execute on the remote host instead of starting an interactive shell.
Alternative to providing command on the command line.
.br
Example:
.I RemoteCommand tmux attach -t dev || tmux new -s dev

.TP
.B KnownHostsCommand
Specifies a command to execute to obtain host keys dynamically.
Supplements UserKnownHostsFile and GlobalKnownHostsFile.
The command output must be in known_hosts format.
Supports token substitution (%h, %H, etc.).
.br
Example:
.I KnownHostsCommand /usr/local/bin/fetch-host-key %H

.TP
.B ForkAfterAuthentication
Specifies whether to fork the SSH process into the background after successful authentication.
Useful for persistent background connections. Default is no.
.br
Example:
.I ForkAfterAuthentication yes

.TP
.B SessionType
Specifies the type of session to request from the server.
Valid values are:
.RS
.IP \[bu] 2
.B none
- No session (port forwarding only)
.IP \[bu] 2
.B subsystem
- Request a subsystem (e.g., SFTP)
.IP \[bu] 2
.B default
- Standard shell session (default)
.RE
.IP
Example:
.I SessionType none

.TP
.B StdinNull
Specifies whether to redirect stdin from /dev/null.
Useful for background operations and scripting. Default is no.
.br
Example:
.I StdinNull yes

.SS SSH Config Example with New Options
.nf
# ~/.ssh/config
Expand All @@ -383,6 +477,28 @@ Host *.secure.prod.example.com
ExitOnForwardFailure yes
PermitRemoteOpen localhost:8080
PermitRemoteOpen db.internal:5432

# Development server with automatic file sync
Host dev-server
User developer
PermitLocalCommand yes
LocalCommand rsync -av ~/project/ %h:~/project/

# Auto-attach to tmux session
Host project-server
RemoteCommand tmux attach -t project || tmux new -s project
RequestTTY yes

# Dynamic host key fetching for cloud instances
Host *.cloud.example.com
KnownHostsCommand /usr/local/bin/fetch-cloud-key %H

# Background SSH tunnel
Host tunnel
ForkAfterAuthentication yes
SessionType none
LocalForward 8080 internal-server:80
StdinNull yes
.fi

.SH BACKEND.AI INTEGRATION
Expand Down
Loading