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
116 changes: 113 additions & 3 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -952,9 +952,119 @@ Comprehensive test coverage including:
- Single source of truth for authentication logic
- Easier to add new authentication methods
- Consistent behavior across all bssh commands
- Reduced bug surface area
- Improved code maintainability
- Better test coverage

### 4.2 Sudo Password Support (`security/sudo.rs`)

**Status:** Implemented (2025-12-10) as Issue #74

**Overview:**
The sudo password module provides secure handling of sudo authentication for commands that require elevated privileges. When enabled with the `-S` flag, bssh automatically detects sudo password prompts in command output and injects the password without user intervention.

**Architecture Components:**

1. **SudoPassword Struct (`security/sudo.rs`)**
- Wraps password string with automatic memory clearing via `zeroize` crate
- Uses `Arc` for safe sharing across async tasks
- Debug output redacts password content

```rust
#[derive(Clone, ZeroizeOnDrop)]
pub struct SudoPassword {
inner: Arc<SudoPasswordInner>,
}
```

2. **Prompt Detection Patterns**
- Case-insensitive matching against common sudo prompts
- Supports various Linux distributions:
- `[sudo] password for <user>:`
- `Password:`
- `<user>'s password:`
- Also detects failure patterns like "Sorry, try again"

3. **Password Injection Flow**
```
Command Execution
|
+--> PTY Channel Opened (required for sudo interaction)
| |
| Output Monitoring
| |
| [Sudo Prompt Detected?] -- No --> Continue
| |Yes
| Send Password + Newline
| |
+--- Continue Monitoring
```

**Implementation Details:**

```rust
// Prompt detection patterns
pub const SUDO_PROMPT_PATTERNS: &[&str] = &[
"[sudo] password for ",
"password for ",
"password:",
"'s password:",
"sudo password",
"enter password",
"[sudo]",
];

// Failure detection patterns
pub const SUDO_FAILURE_PATTERNS: &[&str] = &[
"sorry, try again",
"incorrect password",
"authentication failure",
"permission denied",
];
```

**SSH Channel Integration (`tokio_client/channel_manager.rs`):**
- Executes command with PTY allocation (required for sudo to send prompts)
- Monitors both stdout and stderr for sudo prompts
- Uses `channel.data()` to write password to stdin when prompt detected
- Password sent only once per execution to prevent retry loops

```rust
pub async fn execute_with_sudo(
&self,
command: &str,
sender: Sender<CommandOutput>,
sudo_password: &SudoPassword,
) -> Result<u32, Error>
```

**Security Considerations:**
- Password stored using `zeroize` crate for automatic memory clearing
- Password never logged or printed in any output
- PTY required for proper sudo interaction (prevents stdin echo issues)
- Environment variable option (`BSSH_SUDO_PASSWORD`) with security warnings

**Execution Path Integration:**
1. CLI flag `-S/--sudo-password` triggers password prompt
2. Password wrapped in `Arc<SudoPassword>` for sharing across nodes
3. `ExecutionConfig` carries optional `sudo_password` field
4. Both streaming and non-streaming execution paths support sudo
5. Per-node execution uses `execute_with_sudo()` when password present

**Usage Patterns:**
```bash
# Basic usage - prompts for password before execution
bssh -S -C production "sudo apt update"

# Combined with SSH agent authentication
bssh -A -S -C production "sudo systemctl restart nginx"

# Environment variable (not recommended)
export BSSH_SUDO_PASSWORD="password"
bssh -S -C production "sudo apt update"
```

**Limitations:**
- Single password for all nodes (cannot handle different passwords per node)
- Assumes all nodes use the same sudo configuration
- Password cached for session duration (cleared on command completion)

**Future Enhancements:**
- Support for additional authentication methods (hardware tokens, certificates)
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable changes to bssh will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- **Sudo Password Support** (Issue #74, PR #78)
- `-S/--sudo-password` flag for automated sudo authentication
- Securely prompts for sudo password before command execution
- Automatically detects and responds to sudo password prompts
- Works with both streaming and non-streaming execution modes
- `BSSH_SUDO_PASSWORD` environment variable support (with security warnings)
- Uses `secrecy` crate for secure memory handling
- Password cleared from memory immediately after use

## [1.3.0] - 2025-12-10

### Added
Expand Down
11 changes: 11 additions & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ owo-colors = "4.2.2"
unicode-width = "0.2.0"
terminal_size = "0.4.3"
once_cell = "1.20"
zeroize = "1.8"
zeroize = { version = "1.8", features = ["derive"] }
secrecy = { version = "0.10.3", features = ["serde"] }
rustyline = "17.0.1"
crossterm = "0.29"
ratatui = "0.29"
Expand Down
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,12 @@ bssh -A -C production "systemctl status nginx"
# Use password authentication (will prompt for password)
bssh --password -H "user@host.com" "uptime"

# Use sudo password for privileged commands (prompts securely)
bssh -S -C production "sudo apt update && sudo apt upgrade -y"

# Combine sudo password with SSH agent authentication
bssh -A -S -C production "sudo systemctl restart nginx"

# Use encrypted SSH key (will prompt for passphrase)
bssh -i ~/.ssh/encrypted_key -C production "df -h"

Expand Down Expand Up @@ -350,6 +356,44 @@ bssh -A -J bastion.example.com user@internal-server "uptime"
bssh -i ~/.ssh/prod_key -J "jump1,jump2" -C production "df -h"
```

### Sudo Password Support

bssh supports automatic sudo password injection for commands that require elevated privileges. When enabled, bssh will:
1. Securely prompt for the sudo password before command execution
2. Detect sudo password prompts in command output
3. Automatically inject the password when prompted
4. Clear the password from memory after use

```bash
# Basic sudo command (will prompt for sudo password)
bssh -S -C production "sudo apt update"

# Combine with SSH agent authentication
bssh -A -S -C production "sudo systemctl restart nginx"

# Multiple sudo commands in a single session
bssh -S -C production "sudo apt update && sudo apt upgrade -y"

# Sudo with specific SSH key
bssh -i ~/.ssh/admin_key -S -C production "sudo reboot"
```

**Environment Variable Alternative:**

For automation scenarios, you can use the `BSSH_SUDO_PASSWORD` environment variable:

```bash
# NOT RECOMMENDED for security reasons
export BSSH_SUDO_PASSWORD="your-password"
bssh -S -C production "sudo apt update"
```

**Security Warnings:**
- Environment variables may be visible in process listings
- Avoid storing passwords in shell history
- The `-S` flag with secure prompt is the recommended approach
- Password is automatically cleared from memory after use using `zeroize`

## Environment Variables

bssh supports configuration via environment variables:
Expand All @@ -372,6 +416,14 @@ bssh supports configuration via environment variables:

- **`SSH_AUTH_SOCK`**: SSH agent socket path (Unix-like systems)

### Sudo Password Variable

- **`BSSH_SUDO_PASSWORD`**: Sudo password for automated sudo authentication
- **WARNING**: Not recommended for security reasons
- Environment variables may be visible in process listings
- Use the `-S` flag with secure prompt instead
- Example: `BSSH_SUDO_PASSWORD=password bssh -S -C prod "sudo apt update"`

## Configuration

### Configuration Priority Order
Expand Down Expand Up @@ -822,6 +874,7 @@ Options:
-i, --identity <IDENTITY> SSH private key file path (prompts for passphrase if encrypted)
-A, --use-agent Use SSH agent for authentication (Unix/Linux/macOS only)
-P, --password Use password authentication (will prompt for password)
-S, --sudo-password Prompt for sudo password to auto-respond to sudo prompts
-J, --jump-host <JUMP_HOSTS> Comma-separated list of jump hosts (ProxyJump)
-L, --local-forward <SPEC> Local port forwarding [bind_address:]port:host:hostport
-R, --remote-forward <SPEC> Remote port forwarding [bind_address:]port:host:hostport
Expand Down
54 changes: 54 additions & 0 deletions docs/man/bssh.1
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,33 @@ Use password authentication. When this option is specified, bssh will
prompt for the password securely without echoing it to the terminal.
This is useful for systems that don't have SSH keys configured.

.TP
.BR \-S ", " \-\-sudo\-password
Prompt for sudo password to automatically respond to sudo prompts.
When this option is specified, bssh will:
.RS
.IP \[bu] 2
Securely prompt for sudo password before execution (no terminal echo)
.IP \[bu] 2
Detect sudo password prompts in command output
.IP \[bu] 2
Automatically inject the password when prompted
.RE
.IP
Alternatively, set the
.B BSSH_SUDO_PASSWORD
environment variable (not recommended for security reasons).
.IP
Security notes:
.RS
.IP \[bu] 2
Password is stored using secure memory handling (secrecy crate)
.IP \[bu] 2
Password is cleared from memory immediately after use
.IP \[bu] 2
Password is never logged or printed in any output
.RE

.TP
.BR \-f ", " \-\-filter " " \fIPATTERN\fR
Filter hosts by pattern (supports wildcards like 'web*').
Expand Down Expand Up @@ -1066,6 +1093,20 @@ Use password authentication:
Prompts for password interactively
.RE

.TP
Execute sudo commands with automatic password injection:
.B bssh -S -C production "sudo apt update && sudo apt upgrade -y"
.RS
Prompts for sudo password once, then automatically responds to sudo prompts on all nodes
.RE

.TP
Combine sudo with SSH agent authentication:
.B bssh -A -S -C production "sudo systemctl restart nginx"
.RS
Uses SSH agent for connection and sudo password for privilege escalation
.RE

.TP
Use encrypted SSH key:
.B bssh -i ~/.ssh/encrypted_key -C production "df -h"
Expand Down Expand Up @@ -1387,6 +1428,19 @@ attacks while allowing flexible jump host configurations.
.br
Example: BSSH_MAX_JUMP_HOSTS=20 bssh -J host1,host2,...,host20 target

.TP
.B BSSH_SUDO_PASSWORD
Sudo password for automated sudo authentication. When set along with the
.B -S
flag, bssh will use this password instead of prompting interactively.
.br
.B WARNING:
Using environment variables for passwords is not recommended for production
use as they may be visible in process listings, shell history, or logs.
Prefer the interactive prompt for security-sensitive operations.
.br
Example: BSSH_SUDO_PASSWORD=mypassword bssh -S -C cluster "sudo apt update"

.TP
.B USER
Used as default username when not specified
Expand Down
10 changes: 10 additions & 0 deletions src/app/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ use bssh::{
},
config::InteractiveMode,
pty::PtyConfig,
security::get_sudo_password,
};
use std::path::{Path, PathBuf};
use std::sync::Arc;

#[cfg(target_os = "macos")]
use super::initialization::determine_use_keychain;
Expand Down Expand Up @@ -368,6 +370,13 @@ async fn handle_exec_command(cli: &Cli, ctx: &AppContext, command: &str) -> Resu
#[cfg(target_os = "macos")]
let use_keychain = determine_use_keychain(&ctx.ssh_config, hostname.as_deref());

// Get sudo password if flag is set
let sudo_password = if cli.sudo_password {
Some(Arc::new(get_sudo_password(true)?))
} else {
None
};

let params = ExecuteCommandParams {
nodes: ctx.nodes.clone(),
command,
Expand All @@ -390,6 +399,7 @@ async fn handle_exec_command(cli: &Cli, ctx: &AppContext, command: &str) -> Resu
},
require_all_success: cli.require_all_success,
check_all_nodes: cli.check_all_nodes,
sudo_password,
};
execute_command(params).await
}
Expand Down
7 changes: 7 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ pub struct Cli {
)]
pub password: bool,

#[arg(
short = 'S',
long = "sudo-password",
help = "Prompt for sudo password to automatically respond to sudo prompts\nWhen enabled, bssh will:\n 1. Securely prompt for sudo password before execution\n 2. Detect sudo password prompts in command output\n 3. Automatically inject the password when prompted\n\nAlternatively, set BSSH_SUDO_PASSWORD environment variable (not recommended)\nSecurity: Password is cleared from memory after use"
)]
pub sudo_password: bool,

#[arg(
short = 'J',
long = "jump-host",
Expand Down
Loading