-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Problem / Background
Currently, the jump_host field in config.yaml (and in the Defaults, ClusterDefaults, and NodeConfig types) only accepts an OpenSSH ProxyJump-style string: [user@]hostname[:port]. The JumpHost struct in src/jump/parser/host.rs only contains user, host, and port fields — there is no ssh_key field.
As a result, the determine_auth_method function in src/jump/chain/auth.rs uses the same key_path (derived from the cluster/defaults ssh_key setting) for both jump hosts and destination nodes. Users cannot specify a different SSH private key for jump hosts vs destination nodes through config.yaml.
This is a significant limitation in environments where:
- Jump/bastion hosts use a different key than internal destination nodes
- Security policies require separate keys for gateway and target access
- Multi-hop chains require different keys at each hop
The only current workaround is to load all required keys into an SSH agent, which is not always desirable or possible.
Proposed Solution
Support a structured object format for jump_host in config.yaml that includes an optional ssh_key field, while maintaining full backward compatibility with the existing string format.
Desired Config Syntax
New structured format:
clusters:
internal:
nodes:
- host: internal1.private
- host: internal2.private
user: admin
ssh_key: ~/.ssh/destination_key
jump_host:
host: bastion.example.com
user: jumpuser
port: 22
ssh_key: ~/.ssh/jump_host_keyExisting string format (must continue to work):
clusters:
internal:
jump_host: jumpuser@bastion.example.comPer-node override:
clusters:
mixed:
nodes:
- host: node1.internal
jump_host:
host: bastion1.example.com
ssh_key: ~/.ssh/bastion1_key
- host: node2.internal
jump_host: jumpuser@bastion2.example.com
ssh_key: ~/.ssh/default_keyImplementation Plan
1. Add ssh_key field to JumpHost struct
File: src/jump/parser/host.rs
Add an ssh_key: Option<String> field to the JumpHost struct. The field should be None when parsed from the legacy string format and Some(path) when provided via the structured config format.
2. Create a JumpHostConfig enum for serde deserialization
File: src/config/types.rs
Introduce a new type that supports both string and structured formats using #[serde(untagged)]:
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum JumpHostConfig {
/// Legacy string format: "[user@]hostname[:port]"
Simple(String),
/// Structured format with optional ssh_key
Detailed {
host: String,
#[serde(default)]
user: Option<String>,
#[serde(default)]
port: Option<u16>,
#[serde(default)]
ssh_key: Option<String>,
},
}3. Update config types to use JumpHostConfig
File: src/config/types.rs
Change the jump_host field type in Defaults, ClusterDefaults, and NodeConfig::Detailed from Option<String> to Option<JumpHostConfig>.
4. Update determine_auth_method to accept per-jump-host key path
File: src/jump/chain/auth.rs
Modify determine_auth_method to accept and prioritize a per-jump-host key path over the cluster-level key path. The priority should be:
- Jump host's own
ssh_key(from structured config) - Cluster/defaults
ssh_key(existing behavior, used as fallback) - SSH agent / default key discovery (existing behavior)
5. Update jump chain connection logic
File: src/jump/chain/tunnel.rs
Update connect_through_tunnel and related functions to pass the per-jump-host key information from JumpHost.ssh_key through to determine_auth_method.
6. Update config resolution and parsing
Ensure that when JumpHostConfig::Detailed is encountered, it produces a JumpHost with the ssh_key field populated. When JumpHostConfig::Simple is encountered, the existing string parsing produces a JumpHost with ssh_key: None.
7. Update documentation and examples
File: example-config.yaml
Add examples showing both the legacy string format and the new structured format with ssh_key.
Acceptance Criteria
-
JumpHoststruct has anssh_key: Option<String>field - Config YAML supports structured
jump_hostwithhost,user,port,ssh_keyfields - Config YAML continues to support legacy
jump_host: "user@host:port"string format -
#[serde(untagged)]enum correctly deserializes both formats -
determine_auth_methoduses jump host'sssh_keywhen provided, falling back to clusterssh_key - Per-node
jump_hoststructured format works inNodeConfig::Detailed - Global defaults
jump_hoststructured format works inDefaults - Cluster-level
jump_hoststructured format works inClusterDefaults - Environment variable expansion works in jump host
ssh_keypaths (e.g.,$HOME/.ssh/key) -
example-config.yamlincludes examples of the new syntax - Existing tests continue to pass (backward compatibility)
- New unit tests cover structured jump_host deserialization
- New unit tests cover per-jump-host key path resolution in
determine_auth_method
Technical Considerations
- Serde
untaggedordering: TheDetailedvariant must be listed beforeSimplein the enum to ensure serde tries the structured format first. If a YAML object is provided, it should matchDetailed; if a plain string is provided, it should matchSimple. - Path expansion: The
ssh_keyfield in the jump host config should support the same~and$HOMEexpansion as the existingssh_keyfields in cluster defaults. - Multi-hop chains: If multi-hop jump chains are supported (comma-separated ProxyJump syntax), consider how per-hop keys would work. The structured format naturally supports this if each hop is specified separately.
- Backward compatibility: All existing config files must continue to work without modification.
Display/to_connection_string: TheJumpHost::to_connection_string()method should not include thessh_keypath for security reasons (avoid logging key paths).
Related Issues
- fix: Jump host authentication fails with empty SSH agent despite available key files #116 - Jump host authentication fails with empty SSH agent despite available key files
- fix: SSH config ProxyJump directive parsed but never applied during connection #117 - SSH config ProxyJump directive parsed but never applied during connection
- SSH ProxyJump (-J) does not work for file transfers and interactive mode times out #38 - SSH ProxyJump (-J) does not work for file transfers and interactive mode times out
Files to Modify
| File | Change |
|---|---|
src/jump/parser/host.rs |
Add ssh_key field to JumpHost |
src/config/types.rs |
Add JumpHostConfig enum, update Defaults, ClusterDefaults, NodeConfig |
src/jump/chain/auth.rs |
Accept per-jump-host key path in determine_auth_method |
src/jump/chain/tunnel.rs |
Pass per-jump-host key to auth functions |
example-config.yaml |
Add structured jump_host examples |
| Config resolution code | Convert JumpHostConfig to JumpHost with ssh_key populated |