Skip to content

[Phase 3] Add command execution and automation options (LocalCommand, RemoteCommand, etc) #45

Description

@inureyes

Overview

This is Phase 3 of the SSH config parser enhancement roadmap. This phase focuses on adding support for command execution and automation features that enable sophisticated SSH workflows, automatic file synchronization, and environment setup.

Background

Options to Implement

1. LocalCommand + PermitLocalCommand ⭐⭐⭐⭐

Importance: High - Critical for automation workflows

Functionality:

  • Execute local commands after successful SSH connection
  • PermitLocalCommand must be enabled for security
  • Command runs with user's shell on local machine
  • Supports %h, %n, %p, %r, %u token substitution

Use Cases:

  • Automatic file synchronization (rsync)
  • Notification triggers
  • Network configuration changes
  • Smart card/security token operations
  • Environment setup

Examples:

# Auto-sync files on connection
Host dev-server
    PermitLocalCommand yes
    LocalCommand rsync -av ~/project/ %h:~/project/

# Send notification
Host production
    PermitLocalCommand yes
    LocalCommand notify-send "Connected to %h"

# Reload smart card daemon
Host smartcard-required
    PermitLocalCommand yes
    LocalCommand gpg-connect-agent "scd serialno" "learn --force" /bye

Security Considerations:

  • Commands can be dangerous - disabled by default
  • Log all executed commands for audit
  • Validate command paths (similar to ProxyCommand)
  • Consider command timeout (30 seconds)
  • Document security implications

Implementation:

  • Add permit_local_command: Option<bool> to SshHostConfig
  • Add local_command: Option<String> to SshHostConfig
  • Note: bssh won't execute these (client-side), but should parse and store

2. RemoteCommand ⭐⭐⭐

Importance: Medium-High - Useful for specific workflows

Functionality:

  • Execute command on remote host instead of starting shell
  • Alternative to command-line argument
  • Can be combined with RequestTTY

Use Cases:

  • Auto-enter specific shell/environment
  • Execute predefined tasks
  • Container/tmux session attachment
  • Automated testing

Examples:

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

# Jump into specific directory and shell
Host project
    RemoteCommand cd /srv/project && exec zsh
    RequestTTY yes

# Run specific command
Host backup
    RemoteCommand /usr/local/bin/backup.sh
    RequestTTY no

Implementation:

  • Add remote_command: Option<String> to SshHostConfig
  • Parse command (extends to end of line)
  • No special validation needed

3. KnownHostsCommand ⭐⭐⭐

Importance: Medium - Important for dynamic environments

Functionality:

  • Execute command to obtain host keys dynamically
  • Supplements UserKnownHostsFile and GlobalKnownHostsFile
  • Enables dynamic host key management

Use Cases:

  • Cloud environments with dynamic instances
  • Container-based infrastructure
  • Certificate authority integration
  • Centralized key management systems

Examples:

# Fetch keys from key management system
Host *.cloud.example.com
    KnownHostsCommand /usr/local/bin/fetch-host-key %H

# Query certificate authority
Host *
    KnownHostsCommand curl -s https://ca.example.com/hostkey/%H

Security Considerations:

  • Validate executable path
  • Command timeout (10 seconds)
  • Log command execution
  • Validate output format (known_hosts format)

Implementation:

  • Add known_hosts_command: Option<String> to SshHostConfig
  • Validate command path (use existing validate_executable_string)
  • Support %h, %H token substitution
  • Note: Actual execution would be in client code, not parser

4. PermitRemoteOpen ⭐⭐⭐

Note: This was already included in Phase 2 (#44), but listing here for completeness as it relates to command execution workflows.


Additional Automation Options

5. ForkAfterAuthentication ⭐⭐

Functionality: Fork ssh into background after authentication
Use Case: Persistent background connections, multiplexing

6. SessionType ⭐⭐

Functionality: Specify session type (none, subsystem, default)
Use Case: SFTP subsystem, port forwarding only

7. StdinNull ⭐⭐

Functionality: Redirect stdin from /dev/null
Use Case: Background operations, scripting

Technical Implementation

Files to Modify

Parser:

  • src/ssh/ssh_config/parser.rs:95-460 - Add new option parsing

Types:

  • src/ssh/ssh_config/types.rs:22-68 - Add new fields to SshHostConfig

Security:

  • src/ssh/ssh_config/security.rs - Validate command strings

Implementation Details

// In types.rs
pub struct SshHostConfig {
    // ... existing fields ...
    
    // Command execution
    pub permit_local_command: Option<bool>,
    pub local_command: Option<String>,
    pub remote_command: Option<String>,
    pub known_hosts_command: Option<String>,
    
    // Additional automation
    pub fork_after_authentication: Option<bool>,
    pub session_type: Option<String>,
    pub stdin_null: Option<bool>,
}
// In parser.rs parse_option()
"permitlocalcommand" => {
    if args.is_empty() {
        anyhow::bail!("PermitLocalCommand requires a value at line {line_number}");
    }
    host.permit_local_command = Some(parse_yes_no(args[0], line_number)?);
}
"localcommand" => {
    if args.is_empty() {
        anyhow::bail!("LocalCommand requires a value at line {line_number}");
    }
    let command = args.join(" ");
    // Security validation similar to ProxyCommand
    validate_executable_string(&command, "LocalCommand", line_number)?;
    host.local_command = Some(command);
}
"remotecommand" => {
    if args.is_empty() {
        anyhow::bail!("RemoteCommand requires a value at line {line_number}");
    }
    // No validation needed - runs on remote host
    host.remote_command = Some(args.join(" "));
}
"knownhostscommand" => {
    if args.is_empty() {
        anyhow::bail!("KnownHostsCommand requires a value at line {line_number}");
    }
    let command = args.join(" ");
    validate_executable_string(&command, "KnownHostsCommand", line_number)?;
    host.known_hosts_command = Some(command);
}
"forkafterauthentication" => {
    if args.is_empty() {
        anyhow::bail!("ForkAfterAuthentication requires a value at line {line_number}");
    }
    host.fork_after_authentication = Some(parse_yes_no(args[0], line_number)?);
}
"sessiontype" => {
    if args.is_empty() {
        anyhow::bail!("SessionType requires a value at line {line_number}");
    }
    let value = args[0].to_lowercase();
    if !["none", "subsystem", "default"].contains(&value.as_str()) {
        anyhow::bail!("Invalid SessionType value at line {line_number}");
    }
    host.session_type = Some(value);
}
"stdinnull" => {
    if args.is_empty() {
        anyhow::bail!("StdinNull requires a value at line {line_number}");
    }
    host.stdin_null = Some(parse_yes_no(args[0], line_number)?);
}

Security Validation

Command Execution Security:

  • Reuse existing validate_executable_string() from security.rs
  • Log warnings for potentially dangerous commands
  • Document that PermitLocalCommand is disabled by default
  • Add timeout recommendations in documentation

Token Substitution Support:
Implement token substitution for LocalCommand and KnownHostsCommand:

  • %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

Testing Requirements

LocalCommand/PermitLocalCommand:

  • Parse PermitLocalCommand yes/no
  • Parse LocalCommand with tokens
  • Validate command paths
  • Security warnings for risky commands

RemoteCommand:

  • Parse multi-word commands
  • Handle special characters
  • Integration with RequestTTY

KnownHostsCommand:

  • Parse with path validation
  • Token substitution in command
  • Command string escaping

Additional Options:

  • SessionType value validation
  • ForkAfterAuthentication parsing
  • StdinNull parsing

Success Criteria

  • PermitLocalCommand parses yes/no
  • LocalCommand parses and validates commands
  • RemoteCommand stores full command string
  • KnownHostsCommand validates executable paths
  • Token substitution documented (implementation in client)
  • Security warnings for dangerous commands
  • SessionType validates allowed values
  • Test coverage >85%
  • Security documentation complete
  • No breaking changes

Dependencies

References

Priority: Medium-High - Important for automation and workflows

Estimated Complexity: Medium - Straightforward parsing with security validation

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions