0

Spring batch project. There is a SshIService that accepts ssh commands and executes them. When the project runs in the Jenkins pipeline, it is able to create the ssh connections and my SshIService runs just fine.

While writing the Integration test for this SshIService service, I created a dummy SSH server which this service can talk to.

Problem: In the Integration test, when I am sending commands like mkdir src/test/resources/Folder1 ; ls src/test/resources/Folder1 ; the error is:

mkdir: ls: File exists
mkdir: src/test/resources/Folder1: File exists
mkdir: ;: File exists

The service is not considering these are separate commands and rather as a parameter for mkdir which is WRONG! The problem is not specific to mkdir. Every time, more than 1 command separated by a semi-colon is executed in the Integration test, the issue arises.

I don't want to make changes to SshIService coz the entire project had been working using that logic. The issues seem to be in the SSH server created for the Integration test.

Code for Integration test

@SpringBootTest
@ContextConfiguration(classes = {SshIService.class, SshSftpConfiguration.class})
@ActiveProfiles("test")
public class SshSftpIntegrationTest {

    @Autowired
    private SshIService SshIService;

    private SshServer dummy_sshd;

    @SneakyThrows
    @BeforeEach
    public void setUp() {
        dummy_sshd = SshServer.setUpDefaultServer();
        dummy_sshd.setPort(9999);
        dummy_sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());
        dummy_sshd.setPasswordAuthenticator((username, password, session) -> username.equals("someUserName") && password.equals("somePassword"));
        dummy_sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
        dummy_sshd.setCommandFactory(new ProcessShellCommandFactory());
        dummy_sshd.start();
    }

    @SneakyThrows
    @AfterEach
    public void cleanup() {
        dummy_sshd.stop();
    }

    @SneakyThrows
    @Test
    public void sendCommandTest() {
        SshIService.sendCommand("mkdir src/test/resources/Folder1 ; ls src/test/resources/Folder1 ;");
        //Some assertions!
    }
}

Code for SshIService

@Slf4j
@Service
public class SshIService {
    private final SshSftpConfiguration sshSftpConfiguration;
    private final JSch sshTunnel;

    private Session sshSession;
    private ChannelSftp channelSftp;

    public SshIService(SshSftpConfiguration sshSftpConfiguration,
                          JSch sshTunnel) {
        this.sshSftpConfiguration = sshSftpConfiguration;
        this.sshTunnel = sshTunnel;
    }

    public String sendCommand(String command) throws IOException, JSchException, InterruptedException {
        try {
            connect();
            Channel channel = sshSession.openChannel("exec");
            ((ChannelExec) channel).setCommand(command);
            channel.setInputStream(null);
            ((ChannelExec) channel).setErrStream(System.err);
            InputStream in = channel.getInputStream();
            channel.connect();
            StringBuilder output = new StringBuilder();
            int bytesRead;
            byte[] buffer = new byte[1024];
            while (!channel.isClosed()) {
                while ((bytesRead = in.read(buffer, 0, buffer.length)) != -1) {
                    output.append(new String(buffer, 0, bytesRead));
                }
                Thread.sleep(100);
            }
            channel.disconnect();
            return cmdOut;
        } catch (IOException | JSchException | InterruptedException ioX) {
            log.error("Error: {}", ioX.getMessage());
            throw ioX;
        }
    }

    public String getHostAddress() throws UnknownHostException {
        return InetAddress.getLocalHost().getHostAddress();
    }

    public void connect() {
        try {
            if (sshSession == null || !sshSession.isConnected()) {
                sshSession = sshTunnel.getSession(sshSftpConfiguration.getUsername(), sshSftpConfiguration.getHost());
                sshSession.setPort(sshSftpConfiguration.getPort());
                sshSession.setPassword(sshSftpConfiguration.getPassword());
                sshSession.setConfig("StrictHostKeyChecking", "no");
            }

            if (sshSession != null && !sshSession.isConnected()) {
                sshSession.connect();
            }

            if (sshSession != null && channelSftp == null || !channelSftp.isConnected()) {
                channelSftp = (ChannelSftp) sshSession.openChannel("sftp");
                channelSftp.connect();
            }

        } catch (JSchException e) {
            log.error(ExceptionUtils.getStackTrace(e));
        }
    }

    public ChannelSftp getChannelSftp() {
        return channelSftp;
    }

    public void setChannelSftp(ChannelSftp channelSftp) {
        this.channelSftp = channelSftp;
    }

    public Session getSshSession() {
        return sshSession;
    }

    public void setSshSession(Session sshSession) {
        this.sshSession = sshSession;
    }
}

Any suggestions?

10
  • Given that you have a ChannelSftp why don't you use its native methods (mkDir() , ls(), etc) instead of using an exec channel? Commented Mar 24, 2021 at 19:55
  • I can't change the code in SshIService class. Whatever has to be done, it has to be in Integration tests.
    – P_user526
    Commented Mar 24, 2021 at 20:11
  • But you can use SshIService.getChannelSftp().mkDir() after calling connect() instead of calling sendCommand() to use the exec channel. Commented Mar 24, 2021 at 20:14
  • In the integration test, when I tried sshSftpService.getChannelSftp().mkdir("src/test/resources/Folder3/test");, sshSftpService.getChannelSftp() is null.
    – P_user526
    Commented Mar 24, 2021 at 20:20
  • You need to call connect() first - the field is set up there. Commented Mar 24, 2021 at 20:21

2 Answers 2

2

You are not connecting to a real server over SSH, you are connecting to an Apache Mina SSH server, with only an SFTP subsystem.

That doesn't mean it can accept arbitrary OS/shell commands.

https://mina.apache.org/sshd-project/

SSHD does not really aim at being a replacement for the SSH client or SSH server from Unix operating systems, but rather provides support for Java based applications requiring SSH support.

1
  • Is there any other server than the Apache Mina SSH server or the SftpSubsystemFactory, which can be used here, that would support concatenated commands? Or any other alternative to test the sendCommand()
    – P_user526
    Commented Mar 25, 2021 at 16:05
0

try SshIService.sendCommand("mkdir src/test/resources/Folder1 && ls src/test/resources/Folder1 ");

6
  • It ended up creating a folder with the name "&&" as well!
    – P_user526
    Commented Mar 24, 2021 at 20:13
  • i think it is a syntax error try SshIService.sendCommand("mkdir src/test/resources/Folder1 ;" ) SshIService.sendCommand("ls src/test/resources/Folder1 ;" ) Commented Mar 24, 2021 at 20:27
  • The goal is to be to send multiple ssh commands (separated by ';') as one input parameter to the sendCommand() method and verify the result.
    – P_user526
    Commented Mar 24, 2021 at 20:38
  • does not recognize ";" you can try with a pipe SshIService.sendCommand ("mkdir src / test / resources / Folder1 | ls src / test / resources / Folder1"); Commented Mar 25, 2021 at 11:47
  • Still same issue. mkdir: src: File exists mkdir: /: Is a directory mkdir: /: Is a directory mkdir: /: Is a directory mkdir: src: File exists mkdir: /: Is a directory mkdir: test: File exists mkdir: /: Is a directory mkdir: resources: File exists mkdir: /: Is a directory mkdir: Folder1: File exists
    – P_user526
    Commented Mar 25, 2021 at 15:22

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.