Home > Software design >  Testing Sftp using Apache Mina fails with permission denied in GitLab CI
Testing Sftp using Apache Mina fails with permission denied in GitLab CI

Time:10-28

I'm attempting to push my changes to GitLab that allows me to spin up a local server on the machine and connect to it via SFTP using key auth. When running this in my local all tests pass as expected and I can connect to the Apache Mina server. However, when the tests run on the GitLab pipeline I get a permission denied error, with very little explanation. Any insight on this would be very helpful. Once it gets to sshd.start() is when the exception is thrown

Thanks in advance.

  java.net.SocketException: Permission denied
        at sun.nio.ch.Net.bind0(Native Method)
        at sun.nio.ch.Net.bind(Net.java:433)
        at sun.nio.ch.Net.bind(Net.java:425)
        at sun.nio.ch.AsynchronousServerSocketChannelImpl.bind(AsynchronousServerSocketChannelImpl.java:162)
        at org.apache.sshd.common.io.nio2.Nio2Acceptor.bind(Nio2Acceptor.java:83)
        at org.apache.sshd.common.io.nio2.Nio2Acceptor.bind(Nio2Acceptor.java:173)
        at org.apache.sshd.server.SshServer.start(SshServer.java:340)
        at com.broadridge.gto.gptm.recon.controller.BPSDataLoadControllerTest.startSftpServer(BPSDataLoadControllerTest.java:162)
        at com.broadridge.gto.gptm.recon.controller.BPSDataLoadControllerTest.bpsSftpDataLoad(BPSDataLoadControllerTest.java:120)

CONTROLLER:

   try {
      sftp.execute(
          downloadFileInto(
              "/path/to/file/test",
              positionService::saveAllBpsPositionData));

TEST:

 @Test
  void bpsSftpDataLoad() throws Throwable {
    ReflectionTestUtils.setField(dataLoadController, "wildcard", "test");
    startSftpServer();
    InputStream is =
        new ClassPathResource("/testdata/PositionRecon/position.txt").getInputStream();
    Path virtualFile =
        fileSystem.getPath("/path/to/file/test");
    java.nio.file.Files.createDirectories(virtualFile.getParent());
    java.nio.file.Files.createFile(virtualFile);
    java.nio.file.Files.copy(is, virtualFile, StandardCopyOption.REPLACE_EXISTING);
    mockMvc.perform(
        MockMvcRequestBuilders.request(
            HttpMethod.POST, "/perform-bps-load-sftp/{businessDate}", "06072022"));
    assertEquals(8, bpsPositionsRepo.countByBusinessDate(LocalDate.parse("2022-06-07")));
    assertEquals(3, bpsPriceRepo.countByBusinessDate(LocalDate.parse("2022-06-07")));
    assertEquals(2, bpsMemoRepo.countByBusinessDate(LocalDate.parse("2022-06-07")));
    teardown();
  }

  private void startSftpServer()
      throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
    sshd = SshServer.setUpDefaultServer();
    sshd.setPort(port);
    sshd.setSubsystemFactories(
        Collections.singletonList(new SftpSubsystemFactory.Builder().build()));
    sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());
    sshd.setUserAuthFactories(
        BuiltinUserAuthFactories.parseFactoriesList("publickey").getParsedFactories());
    sshd.setPublickeyAuthenticator(
        new KeySetPublickeyAuthenticator(
            "keySetPKAuth",
            SftpTestConfigUtils.loadPemPublicKeys(
                new ClassPathResource("/keystore/public_test.pem"))));
    fileSystem =
        Jimfs.newFileSystem(Configuration.unix().toBuilder().setAttributeViews("posix").build());
    sshd.setFileSystemFactory(new VirtualFileSystemFactory(fileSystem.getPath("/")));

    sshd.start();
  }

CONFIG:

public class SftpTestConfigUtils {
  public static Set<PublicKey> loadPemPublicKeys(Resource... resources)
      throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    Set<PublicKey> publicKeySet = new HashSet<>();
    for (Resource resource : resources) {
      try (InputStream inputStream = resource.getInputStream();
          Reader reader = new InputStreamReader(inputStream);
          PemReader pemReader = new PemReader(reader)) {
        PemObject pemObject =
            Objects.requireNonNull(pemReader.readPemObject(), "No public key found in "   resource);
        publicKeySet.add(keyFactory.generatePublic(new X509EncodedKeySpec(pemObject.getContent())));
      }
    }
    return publicKeySet;
  }

  public static FailableSupplier<Session, JSchException> getConfig(int port) {
    SftpConfiguration config = new SftpConfiguration();
    config.setPort(port);
    config.setServer("127.0.0.1");
    config.setUsername("sshd");
    config.setPrivateKey(new ClassPathResource("/keystore/test_pk_rsa_mina.pem"));
    return config::createSession;
  }
}
@Configuration
public class SftpSpringConfiguration {
  @Bean
  @ConfigurationProperties(prefix = "recon.data.load.sftp.*")
  public SftpConfiguration sftpFileRetrievalConfiguration() {
    return new SftpConfiguration();
  }

  @Bean
  public SftpFileRetrieval fileRetrieval() {
    return new SftpFileRetrieval(sftpFileRetrievalConfiguration()::createSession);
  }
}
@NoArgsConstructor
@Getter
@Setter
public class SftpConfiguration {
  private String server;
  private String username;
  private Resource privateKey;
  private int port;

  public Session createSession() throws JSchException {
    validate();
    JSch jsch = new JSch();
    try {
      byte[] privateKeyBytes = IOUtils.toByteArray(this.getPrivateKey().getInputStream());
      jsch.addIdentity("user", privateKeyBytes, null, null);
      Arrays.fill(privateKeyBytes, (byte) 0); // Go away Checkmarx!
    } catch (IOException e) {
      throw new RuntimeException("Failed to read private key", e);
    }
    Session session = jsch.getSession(this.getUsername(), this.getServer(), this.getPort());
    Properties config = new Properties();
    config.put("StrictHostKeyChecking", "no");
    session.setConfig(config);
    return session;
  }

SFTP CLIENT:


  public SftpFileRetrieval(@NonNull FailableSupplier<Session, JSchException> sessionFactory) {
    this.sessionFactory = sessionFactory;
  }

  @Override
  public void execute(FailableConsumer<ChannelSftp, Exception> consumer) throws Exception {

    Session session =
        Objects.requireNonNull(sessionFactory.get(), "Null session returned from session factory.");

    ChannelSftp channelSftp = null;

    try {
      session.connect();

      channelSftp = (ChannelSftp) session.openChannel("sftp");

      channelSftp.connect();
      log.info("SFTP channel connected");

      consumer.accept(channelSftp);

    } catch (Exception e) {
      log.warn("Sftp File Retrieval Failed: {}", e.getMessage());
      throw e;
    } finally {
      if (channelSftp != null) {
        closeQuietly(channelSftp::disconnect);
        closeQuietly(channelSftp::exit);
      }
      closeQuietly(session::disconnect);
    }
  }

Note: All of my values for SftpConfiguration class are autowired in by the SpringConfig class, where the values are located in my application.yml

What makes me think this is a GitLab issue is, again when running this on my local system all tests pass as expected, and I have no issue connecting, writing, downloading from the server.

Please let me know if you need to see anymore of the code.

CodePudding user response:

The error indicates the SFTP server logic is getting an error while binding to a TCP port to receive connections. You indicate that it's trying to bind to port 22.

On unix systems, processes not running as root typically can't bind to ports below 1024. This is intended to prevent untrusted processes from taking over the ports used by essential services.

It's also likely that some other process is already bound to port 22. Port 22 is the standard SSH service port, and most unix systems run an SSH server to provide remote access. If your test server could bind to port 22, it might find itself receiving connections from genuine SSH clients not involved in your test.

Unless you want to run your test as root and you're sure that port 22 is unused, you should arrange for the test to use a port number from the range 1024 through 65535.

  • Related