@@ -997,6 +997,314 @@ func TestSSH(t *testing.T) {
997997 assert .Equal (t , unicodeChars , string (output ))
998998 },
999999 },
1000+ {
1001+ name : "connection with cipher and MAC preferences" ,
1002+ run : func (t * testing.T , environment * Environment , device * models.Device ) {
1003+ config := & ssh.ClientConfig {
1004+ User : fmt .Sprintf ("%s@%s.%s" , ShellHubAgentUsername , ShellHubNamespaceName , device .Name ),
1005+ Auth : []ssh.AuthMethod {
1006+ ssh .Password (ShellHubAgentPassword ),
1007+ },
1008+ HostKeyCallback : ssh .InsecureIgnoreHostKey (), //nolint:gosec
1009+ Config : ssh.Config {
1010+ Ciphers : []string {
1011+ "aes256-ctr" , "aes192-ctr" , "aes128-ctr" ,
1012+ "aes256-gcm@openssh.com" , "aes128-gcm@openssh.com" ,
1013+ },
1014+ MACs : []string {
1015+ "hmac-sha2-256-etm@openssh.com" ,
1016+ "hmac-sha2-512-etm@openssh.com" ,
1017+ "hmac-sha2-256" ,
1018+ "hmac-sha2-512" ,
1019+ },
1020+ },
1021+ }
1022+
1023+ conn , err := ssh .Dial ("tcp" , fmt .Sprintf ("localhost:%s" , environment .services .Env ("SHELLHUB_SSH_PORT" )), config )
1024+ require .NoError (t , err )
1025+ defer conn .Close ()
1026+
1027+ sess , err := conn .NewSession ()
1028+ require .NoError (t , err )
1029+ defer sess .Close ()
1030+
1031+ output , err := sess .Output ("echo -n 'cipher test'" )
1032+ require .NoError (t , err )
1033+ assert .Equal (t , "cipher test" , string (output ))
1034+ },
1035+ },
1036+ {
1037+ name : "multiple concurrent SSH sessions" ,
1038+ run : func (t * testing.T , environment * Environment , device * models.Device ) {
1039+ config := & ssh.ClientConfig {
1040+ User : fmt .Sprintf ("%s@%s.%s" , ShellHubAgentUsername , ShellHubNamespaceName , device .Name ),
1041+ Auth : []ssh.AuthMethod {
1042+ ssh .Password (ShellHubAgentPassword ),
1043+ },
1044+ HostKeyCallback : ssh .InsecureIgnoreHostKey (), //nolint:gosec
1045+ }
1046+
1047+ const numConnections = 5
1048+ var wg sync.WaitGroup
1049+ errors := make (chan error , numConnections )
1050+
1051+ for i := range numConnections {
1052+ wg .Add (1 )
1053+ go func (id int ) {
1054+ defer wg .Done ()
1055+
1056+ conn , err := ssh .Dial ("tcp" , fmt .Sprintf ("localhost:%s" , environment .services .Env ("SHELLHUB_SSH_PORT" )), config )
1057+ if err != nil {
1058+ errors <- fmt .Errorf ("connection %d failed: %w" , id , err )
1059+
1060+ return
1061+ }
1062+ defer conn .Close ()
1063+
1064+ sess , err := conn .NewSession ()
1065+ if err != nil {
1066+ errors <- fmt .Errorf ("session %d failed: %w" , id , err )
1067+
1068+ return
1069+ }
1070+ defer sess .Close ()
1071+
1072+ expected := fmt .Sprintf ("session-%d" , id )
1073+ output , err := sess .Output (fmt .Sprintf ("echo -n '%s'" , expected ))
1074+ if err != nil {
1075+ errors <- fmt .Errorf ("command %d failed: %w" , id , err )
1076+
1077+ return
1078+ }
1079+
1080+ if string (output ) != expected {
1081+ errors <- fmt .Errorf ("unexpected output from session %d: got %q, want %q" , id , string (output ), expected )
1082+ }
1083+ }(i )
1084+ }
1085+
1086+ wg .Wait ()
1087+ close (errors )
1088+
1089+ for err := range errors {
1090+ require .NoError (t , err )
1091+ }
1092+ },
1093+ },
1094+ {
1095+ name : "connection with strict host key checking simulation" ,
1096+ run : func (t * testing.T , environment * Environment , device * models.Device ) {
1097+ var learnedKey ssh.PublicKey
1098+ config1 := & ssh.ClientConfig {
1099+ User : fmt .Sprintf ("%s@%s.%s" , ShellHubAgentUsername , ShellHubNamespaceName , device .Name ),
1100+ Auth : []ssh.AuthMethod {
1101+ ssh .Password (ShellHubAgentPassword ),
1102+ },
1103+ HostKeyCallback : func (hostname string , remote net.Addr , key ssh.PublicKey ) error {
1104+ learnedKey = key
1105+
1106+ return nil
1107+ },
1108+ }
1109+
1110+ conn1 , err := ssh .Dial ("tcp" , fmt .Sprintf ("localhost:%s" , environment .services .Env ("SHELLHUB_SSH_PORT" )), config1 )
1111+ require .NoError (t , err )
1112+ conn1 .Close ()
1113+
1114+ config2 := & ssh.ClientConfig {
1115+ User : fmt .Sprintf ("%s@%s.%s" , ShellHubAgentUsername , ShellHubNamespaceName , device .Name ),
1116+ Auth : []ssh.AuthMethod {
1117+ ssh .Password (ShellHubAgentPassword ),
1118+ },
1119+ HostKeyCallback : func (hostname string , remote net.Addr , key ssh.PublicKey ) error {
1120+ if ! bytes .Equal (key .Marshal (), learnedKey .Marshal ()) {
1121+ return fmt .Errorf ("host key mismatch" )
1122+ }
1123+
1124+ return nil
1125+ },
1126+ }
1127+
1128+ conn2 , err := ssh .Dial ("tcp" , fmt .Sprintf ("localhost:%s" , environment .services .Env ("SHELLHUB_SSH_PORT" )), config2 )
1129+ require .NoError (t , err )
1130+ defer conn2 .Close ()
1131+ },
1132+ },
1133+ {
1134+ name : "connection with keep-alive and heartbeat" ,
1135+ run : func (t * testing.T , environment * Environment , device * models.Device ) {
1136+ config := & ssh.ClientConfig {
1137+ User : fmt .Sprintf ("%s@%s.%s" , ShellHubAgentUsername , ShellHubNamespaceName , device .Name ),
1138+ Auth : []ssh.AuthMethod {
1139+ ssh .Password (ShellHubAgentPassword ),
1140+ },
1141+ HostKeyCallback : ssh .InsecureIgnoreHostKey (), //nolint:gosec
1142+ Timeout : 10 * time .Second ,
1143+ }
1144+
1145+ conn , err := ssh .Dial ("tcp" , fmt .Sprintf ("localhost:%s" , environment .services .Env ("SHELLHUB_SSH_PORT" )), config )
1146+ require .NoError (t , err )
1147+ defer conn .Close ()
1148+
1149+ go func () {
1150+ ticker := time .NewTicker (2 * time .Second )
1151+ defer ticker .Stop ()
1152+ for range 3 {
1153+ <- ticker .C
1154+ _ , _ , err := conn .SendRequest ("keepalive@shellhub.io" , true , nil )
1155+ if err != nil {
1156+ t .Logf ("Keep-alive failed: %v" , err )
1157+
1158+ return
1159+ }
1160+ }
1161+ }()
1162+
1163+ time .Sleep (8 * time .Second )
1164+
1165+ sess , err := conn .NewSession ()
1166+ require .NoError (t , err )
1167+ defer sess .Close ()
1168+
1169+ output , err := sess .Output ("echo -n 'alive after keepalive'" )
1170+ require .NoError (t , err )
1171+ assert .Equal (t , "alive after keepalive" , string (output ))
1172+ },
1173+ },
1174+ {
1175+ name : "connection with subsystem request (sftp)" ,
1176+ run : func (t * testing.T , environment * Environment , device * models.Device ) {
1177+ config := & ssh.ClientConfig {
1178+ User : fmt .Sprintf ("%s@%s.%s" , ShellHubAgentUsername , ShellHubNamespaceName , device .Name ),
1179+ Auth : []ssh.AuthMethod {
1180+ ssh .Password (ShellHubAgentPassword ),
1181+ },
1182+ HostKeyCallback : ssh .InsecureIgnoreHostKey (), //nolint:gosec
1183+ }
1184+
1185+ conn , err := ssh .Dial ("tcp" , fmt .Sprintf ("localhost:%s" , environment .services .Env ("SHELLHUB_SSH_PORT" )), config )
1186+ require .NoError (t , err )
1187+ defer conn .Close ()
1188+
1189+ sess , err := conn .NewSession ()
1190+ require .NoError (t , err )
1191+ defer sess .Close ()
1192+
1193+ err = sess .RequestSubsystem ("sftp" )
1194+ require .NoError (t , err )
1195+
1196+ stdin , err := sess .StdinPipe ()
1197+ require .NoError (t , err )
1198+
1199+ stdout , err := sess .StdoutPipe ()
1200+ require .NoError (t , err )
1201+
1202+ initPacket := []byte {0 , 0 , 0 , 5 , 1 , 0 , 0 , 0 , 3 } // SSH_FXP_INIT with version 3
1203+ _ , err = stdin .Write (initPacket )
1204+ require .NoError (t , err )
1205+
1206+ response := make ([]byte , 9 )
1207+ n , err := stdout .Read (response )
1208+ require .NoError (t , err )
1209+ assert .Equal (t , 9 , n )
1210+ assert .Equal (t , byte (2 ), response [4 ]) // SSH_FXP_VERSION
1211+ },
1212+ },
1213+ {
1214+ name : "connection with pseudo-terminal modes" ,
1215+ run : func (t * testing.T , environment * Environment , device * models.Device ) {
1216+ config := & ssh.ClientConfig {
1217+ User : fmt .Sprintf ("%s@%s.%s" , ShellHubAgentUsername , ShellHubNamespaceName , device .Name ),
1218+ Auth : []ssh.AuthMethod {
1219+ ssh .Password (ShellHubAgentPassword ),
1220+ },
1221+ HostKeyCallback : ssh .InsecureIgnoreHostKey (), //nolint:gosec
1222+ }
1223+
1224+ conn , err := ssh .Dial ("tcp" , fmt .Sprintf ("localhost:%s" , environment .services .Env ("SHELLHUB_SSH_PORT" )), config )
1225+ require .NoError (t , err )
1226+ defer conn .Close ()
1227+
1228+ sess , err := conn .NewSession ()
1229+ require .NoError (t , err )
1230+ defer sess .Close ()
1231+
1232+ modes := ssh.TerminalModes {
1233+ ssh .ECHO : 0 , // Disable echo
1234+ ssh .TTY_OP_ISPEED : 14400 , // Input speed
1235+ ssh .TTY_OP_OSPEED : 14400 , // Output speed
1236+ ssh .ICRNL : 1 , // Map CR to NL on input
1237+ ssh .OPOST : 1 , // Enable output processing
1238+ }
1239+
1240+ err = sess .RequestPty ("xterm-256color" , 24 , 80 , modes )
1241+ require .NoError (t , err )
1242+
1243+ stdin , err := sess .StdinPipe ()
1244+ require .NoError (t , err )
1245+
1246+ stdout , err := sess .StdoutPipe ()
1247+ require .NoError (t , err )
1248+
1249+ err = sess .Shell ()
1250+ require .NoError (t , err )
1251+
1252+ _ , err = stdin .Write ([]byte ("stty -echo && echo 'no echo test' && exit\n " ))
1253+ require .NoError (t , err )
1254+
1255+ buffer := make ([]byte , 1024 )
1256+ n , err := stdout .Read (buffer )
1257+ require .NoError (t , err )
1258+ assert .Greater (t , n , 0 )
1259+ },
1260+ },
1261+ {
1262+ name : "connection with signal handling" ,
1263+ run : func (t * testing.T , environment * Environment , device * models.Device ) {
1264+ config := & ssh.ClientConfig {
1265+ User : fmt .Sprintf ("%s@%s.%s" , ShellHubAgentUsername , ShellHubNamespaceName , device .Name ),
1266+ Auth : []ssh.AuthMethod {
1267+ ssh .Password (ShellHubAgentPassword ),
1268+ },
1269+ HostKeyCallback : ssh .InsecureIgnoreHostKey (), //nolint:gosec
1270+ }
1271+
1272+ conn , err := ssh .Dial ("tcp" , fmt .Sprintf ("localhost:%s" , environment .services .Env ("SHELLHUB_SSH_PORT" )), config )
1273+ require .NoError (t , err )
1274+ defer conn .Close ()
1275+
1276+ sess , err := conn .NewSession ()
1277+ require .NoError (t , err )
1278+ defer sess .Close ()
1279+
1280+ err = sess .RequestPty ("xterm" , 24 , 80 , ssh.TerminalModes {})
1281+ require .NoError (t , err )
1282+
1283+ stdin , err := sess .StdinPipe ()
1284+ require .NoError (t , err )
1285+
1286+ err = sess .Shell ()
1287+ require .NoError (t , err )
1288+
1289+ _ , err = stdin .Write ([]byte ("sleep 30 &\n " ))
1290+ require .NoError (t , err )
1291+
1292+ time .Sleep (100 * time .Millisecond )
1293+
1294+ err = sess .Signal (ssh .SIGINT )
1295+ if err != nil {
1296+ t .Logf ("Signal sending not supported: %v" , err )
1297+ }
1298+
1299+ err = sess .Signal (ssh .SIGTERM )
1300+ if err != nil {
1301+ t .Logf ("Signal sending not supported: %v" , err )
1302+ }
1303+
1304+ _ , err = stdin .Write ([]byte ("echo 'signal test done'\n " ))
1305+ require .NoError (t , err )
1306+ },
1307+ },
10001308 }
10011309
10021310 ctx := context .Background ()
0 commit comments