@@ -9,6 +9,7 @@ pub mod configuration_tests;
99pub mod connectivity_tests;
1010
1111// Re-export common SSH testing utilities
12+ use std:: fs;
1213use std:: net:: { IpAddr , SocketAddr } ;
1314use std:: path:: PathBuf ;
1415use std:: time:: { Duration , Instant } ;
@@ -105,9 +106,16 @@ impl SshTestBuilder {
105106
106107 /// Build the SSH client with configured parameters
107108 pub fn build_client ( self ) -> SshClient {
109+ let private_key_path = self . private_key_path . unwrap ( ) ;
110+ let public_key_path = self . public_key_path . unwrap ( ) ;
111+
112+ // CI runners may check out fixture keys with permissive file modes.
113+ // OpenSSH rejects private keys that are readable by group/others.
114+ normalize_private_key_permissions ( & private_key_path) ;
115+
108116 let ssh_credentials = SshCredentials :: new (
109- self . private_key_path . unwrap ( ) ,
110- self . public_key_path . unwrap ( ) ,
117+ private_key_path,
118+ public_key_path,
111119 Username :: new ( self . username . unwrap ( ) ) . unwrap ( ) ,
112120 ) ;
113121
@@ -126,6 +134,26 @@ impl Default for SshTestBuilder {
126134 }
127135}
128136
137+ #[ cfg( unix) ]
138+ fn normalize_private_key_permissions ( private_key_path : & std:: path:: Path ) {
139+ use std:: os:: unix:: fs:: PermissionsExt ;
140+
141+ if private_key_path. exists ( ) {
142+ let mode_600 = fs:: Permissions :: from_mode ( 0o600 ) ;
143+ if let Err ( error) = fs:: set_permissions ( private_key_path, mode_600) {
144+ eprintln ! (
145+ "Warning: failed to enforce 0600 permissions on {}: {error}" ,
146+ private_key_path. display( )
147+ ) ;
148+ }
149+ }
150+ }
151+
152+ #[ cfg( not( unix) ) ]
153+ fn normalize_private_key_permissions ( _private_key_path : & std:: path:: Path ) {
154+ // No-op on non-Unix platforms.
155+ }
156+
129157// =============================================================================
130158// SSH CONNECTIVITY HELPERS
131159// =============================================================================
@@ -188,13 +216,15 @@ pub async fn assert_connectivity_succeeds_eventually(client: &SshClient, max_sec
188216 let socket_addr = test_client. ssh_config ( ) . socket_addr ;
189217 let tcp_probe_result = PortChecker :: new ( ) . is_port_open ( socket_addr) ;
190218 let one_shot_ssh_result = test_client. test_connectivity ( ) ;
219+ let one_shot_execute_result = test_client. execute ( "echo 'SSH connected'" ) ;
191220
192221 eprintln ! (
193222 "\n === SSH Connectivity Failure Diagnostics ===\n \
194223 target: {socket_addr}\n \
195224 retry_window_secs: {max_seconds}\n \
196225 raw_tcp_port_open: {tcp_probe_result:?}\n \
197- one_shot_ssh_connectivity: {one_shot_ssh_result:?}\n "
226+ one_shot_ssh_connectivity: {one_shot_ssh_result:?}\n \
227+ one_shot_ssh_execute: {one_shot_execute_result:?}\n "
198228 ) ;
199229
200230 print_docker_debug_info ( socket_addr. port ( ) ) ;
0 commit comments