This is an automated email from the ASF dual-hosted git repository.

fmariani pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new b9cce2588d4a Generate Key at runtime for camel infra sftp
b9cce2588d4a is described below

commit b9cce2588d4a041390dfe5990c1e287e16bc4a18
Author: Croway <[email protected]>
AuthorDate: Tue Dec 23 14:46:34 2025 +0100

    Generate Key at runtime for camel infra sftp
---
 .../test/infra/ftp/services/FtpInfraService.java   |  18 ++++
 .../services/embedded/EmbeddedConfiguration.java   |   3 +
 .../embedded/FtpsEmbeddedInfraService.java         |  55 ++++++++++-
 .../embedded/SftpEmbeddedInfraService.java         | 110 ++++++++++++++++++++-
 .../test/infra/ftp/services/embedded/SftpUtil.java |  24 ++++-
 .../src/main/resources/hostkey.pem                 |  15 +++
 .../src/main/resources/server.jks                  | Bin 0 -> 1961 bytes
 7 files changed, 219 insertions(+), 6 deletions(-)

diff --git 
a/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/FtpInfraService.java
 
b/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/FtpInfraService.java
index 6857564805ed..9b1a505c8e1b 100644
--- 
a/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/FtpInfraService.java
+++ 
b/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/FtpInfraService.java
@@ -53,4 +53,22 @@ public interface FtpInfraService extends 
InfrastructureService {
     default String directoryName() {
         return "myTestDirectory";
     }
+
+    /**
+     * Returns the SSH host key fingerprint in SHA256 format (SFTP only).
+     *
+     * @return the host key fingerprint or null if not applicable
+     */
+    default String hostKeyFingerprint() {
+        return null;
+    }
+
+    /**
+     * Returns the known_hosts entry for this server (SFTP only).
+     *
+     * @return the known_hosts entry or null if not applicable
+     */
+    default String knownHostsEntry() {
+        return null;
+    }
 }
diff --git 
a/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/embedded/EmbeddedConfiguration.java
 
b/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/embedded/EmbeddedConfiguration.java
index 823a026e0741..06d777dbac49 100644
--- 
a/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/embedded/EmbeddedConfiguration.java
+++ 
b/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/embedded/EmbeddedConfiguration.java
@@ -92,6 +92,9 @@ public class EmbeddedConfiguration {
                                               + 
"zFd/Bk51E65UTmmSrmW0O1ohtzi6HzsDPjXgCtlTt3FqTcfFfI92IlTr4JWqC9UK1QT1ZTeng0MkPQmv68hDANHbt5CpETZHjW5q4OOgWhV"
                                               + 
"vj5IyOC2NZHtKlJBkdsMAa15ouOOJLzBvAvbqOR/yUROsEiQ==";
 
+    static final String BUNDLED_HOST_KEY_RESOURCE = "hostkey.pem";
+    static final String BUNDLED_KEYSTORE_RESOURCE = "server.jks";
+
     private String testDirectory;
     private List<User> users = new ArrayList<>();
     private User admin;
diff --git 
a/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/embedded/FtpsEmbeddedInfraService.java
 
b/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/embedded/FtpsEmbeddedInfraService.java
index ab04f99ab6eb..f9fb40d06057 100644
--- 
a/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/embedded/FtpsEmbeddedInfraService.java
+++ 
b/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/embedded/FtpsEmbeddedInfraService.java
@@ -18,18 +18,27 @@
 package org.apache.camel.test.infra.ftp.services.embedded;
 
 import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
 
 import org.apache.camel.spi.annotations.InfraService;
 import org.apache.camel.test.infra.ftp.services.FtpInfraService;
 import org.apache.ftpserver.FtpServerFactory;
 import org.apache.ftpserver.listener.ListenerFactory;
 import org.apache.ftpserver.ssl.SslConfigurationFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @InfraService(service = FtpInfraService.class,
               description = "Embedded FTPS Server",
               serviceAlias = { "ftps" })
 public class FtpsEmbeddedInfraService extends FtpEmbeddedInfraService {
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(FtpsEmbeddedInfraService.class);
+
     /**
      * Use a default constructor with a default security configuration for 
camel jbang
      */
@@ -68,7 +77,8 @@ public class FtpsEmbeddedInfraService extends 
FtpEmbeddedInfraService {
         SslConfigurationFactory sslConfigFactory = new 
SslConfigurationFactory();
         
sslConfigFactory.setSslProtocol(embeddedConfiguration.getSecurityConfiguration().getAuthValue());
 
-        sslConfigFactory.setKeystoreFile(new 
File(embeddedConfiguration.getKeyStore()));
+        File keystoreFile = 
resolveKeystoreFile(embeddedConfiguration.getKeyStore());
+        sslConfigFactory.setKeystoreFile(keystoreFile);
         
sslConfigFactory.setKeystoreType(embeddedConfiguration.getKeyStoreType());
         
sslConfigFactory.setKeystoreAlgorithm(embeddedConfiguration.getKeyStoreAlgorithm());
         
sslConfigFactory.setKeystorePassword(embeddedConfiguration.getKeyStorePassword());
@@ -77,7 +87,7 @@ public class FtpsEmbeddedInfraService extends 
FtpEmbeddedInfraService {
         
sslConfigFactory.setClientAuthentication(embeddedConfiguration.getSecurityConfiguration().getAuthValue());
 
         if (embeddedConfiguration.getSecurityConfiguration().isClientAuth()) {
-            sslConfigFactory.setTruststoreFile(new 
File(embeddedConfiguration.getKeyStore()));
+            sslConfigFactory.setTruststoreFile(keystoreFile);
             
sslConfigFactory.setTruststoreType(embeddedConfiguration.getKeyStoreType());
             
sslConfigFactory.setTruststoreAlgorithm(embeddedConfiguration.getKeyStoreAlgorithm());
             
sslConfigFactory.setTruststorePassword(embeddedConfiguration.getKeyStorePassword());
@@ -85,4 +95,45 @@ public class FtpsEmbeddedInfraService extends 
FtpEmbeddedInfraService {
 
         return sslConfigFactory;
     }
+
+    /**
+     * Resolves the keystore file, trying file path first, then classpath 
resource.
+     *
+     * @param  configuredPath the configured keystore file path
+     * @return                the resolved keystore file
+     */
+    private File resolveKeystoreFile(String configuredPath) {
+        // First try the configured file path
+        File keystoreFile = new File(configuredPath);
+        if (keystoreFile.exists()) {
+            LOG.debug("Using keystore file: {}", 
keystoreFile.getAbsolutePath());
+            return keystoreFile;
+        }
+
+        // Fall back to classpath resource
+        return extractKeystoreFromClasspath();
+    }
+
+    /**
+     * Extracts the bundled keystore from classpath to a temporary file.
+     *
+     * @return the temporary file containing the keystore
+     */
+    private File extractKeystoreFromClasspath() {
+        try (InputStream is = getClass().getClassLoader()
+                
.getResourceAsStream(EmbeddedConfiguration.BUNDLED_KEYSTORE_RESOURCE)) {
+            if (is != null) {
+                Path tempFile = Files.createTempFile("ftps-keystore-", ".jks");
+                Files.copy(is, tempFile, StandardCopyOption.REPLACE_EXISTING);
+                tempFile.toFile().deleteOnExit();
+                LOG.info("Using bundled keystore from classpath, extracted to: 
{}", tempFile);
+                return tempFile.toFile();
+            }
+        } catch (IOException e) {
+            LOG.warn("Failed to extract bundled keystore from classpath: {}", 
e.getMessage());
+        }
+
+        throw new IllegalStateException(
+                "Keystore file not found and bundled keystore not available in 
classpath");
+    }
 }
diff --git 
a/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/embedded/SftpEmbeddedInfraService.java
 
b/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/embedded/SftpEmbeddedInfraService.java
index 924f2ad7cc30..dda41be51919 100644
--- 
a/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/embedded/SftpEmbeddedInfraService.java
+++ 
b/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/embedded/SftpEmbeddedInfraService.java
@@ -23,6 +23,8 @@ import java.net.InetSocketAddress;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.security.KeyPair;
+import java.security.PublicKey;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.BiConsumer;
@@ -33,14 +35,19 @@ import 
org.apache.camel.test.infra.common.services.ContainerEnvironmentUtil;
 import org.apache.camel.test.infra.ftp.common.FtpProperties;
 import org.apache.camel.test.infra.ftp.services.FtpInfraService;
 import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
 import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
+import org.apache.sshd.common.keyprovider.ClassLoadableResourceKeyPairProvider;
 import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.helpers.AbstractSession;
 import org.apache.sshd.common.signature.BuiltinSignatures;
 import org.apache.sshd.common.signature.Signature;
 import org.apache.sshd.scp.server.ScpCommandFactory;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
 import org.apache.sshd.sftp.server.SftpSubsystemFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -95,7 +102,7 @@ public class SftpEmbeddedInfraService extends 
AbstractService implements FtpInfr
             sshd.setPort(port);
         }
 
-        sshd.setKeyPairProvider(new 
FileKeyPairProvider(Paths.get(embeddedConfiguration.getKeyPairFile())));
+        sshd.setKeyPairProvider(createKeyPairProvider());
         sshd.setSubsystemFactories(Collections.singletonList(new 
SftpSubsystemFactory()));
         sshd.setCommandFactory(new ScpCommandFactory());
         sshd.setPasswordAuthenticator((username, password, session) -> true);
@@ -124,6 +131,52 @@ public class SftpEmbeddedInfraService extends 
AbstractService implements FtpInfr
         return (username, key, session) -> true;
     }
 
+    private KeyPairProvider createKeyPairProvider() {
+        // 1. First try: Use existing file on disk if configured path exists
+        Path keyPairPath = Paths.get(embeddedConfiguration.getKeyPairFile());
+        if (Files.exists(keyPairPath)) {
+            LOG.debug("Using existing host key file: {}", keyPairPath);
+            return new FileKeyPairProvider(keyPairPath);
+        }
+
+        // 2. Second try: Load bundled host key from classpath
+        KeyPairProvider classpathProvider = loadKeyFromClasspath();
+        if (classpathProvider != null) {
+            return classpathProvider;
+        }
+
+        // 3. Last resort: Generate a new host key
+        Path generatedKeyPath = testDirectory().resolve("hostkey.ser");
+        LOG.info("Host key file not found at {}. Generating new host key at: 
{}", keyPairPath, generatedKeyPath);
+        SimpleGeneratorHostKeyProvider provider = new 
SimpleGeneratorHostKeyProvider(generatedKeyPath);
+        provider.setAlgorithm("RSA");
+        provider.setKeySize(2048);
+        return provider;
+    }
+
+    /**
+     * Attempts to load the bundled host key from the classpath.
+     *
+     * @return KeyPairProvider if the bundled key is available and can be 
loaded, null otherwise
+     */
+    private KeyPairProvider loadKeyFromClasspath() {
+        try {
+            ClassLoadableResourceKeyPairProvider provider
+                    = new 
ClassLoadableResourceKeyPairProvider(EmbeddedConfiguration.BUNDLED_HOST_KEY_RESOURCE);
+
+            // Verify the key can actually be loaded
+            Iterable<KeyPair> keyPairs = provider.loadKeys(null);
+            if (keyPairs != null && keyPairs.iterator().hasNext()) {
+                LOG.info("Using bundled host key from classpath: {}",
+                        EmbeddedConfiguration.BUNDLED_HOST_KEY_RESOURCE);
+                return provider;
+            }
+        } catch (Exception e) {
+            LOG.debug("Failed to load bundled host key from classpath: {}", 
e.getMessage());
+        }
+        return null;
+    }
+
     public void tearDown() {
         tearDownServer();
     }
@@ -187,4 +240,59 @@ public class SftpEmbeddedInfraService extends 
AbstractService implements FtpInfr
     public int port() {
         return port;
     }
+
+    /**
+     * Returns the SSH host key fingerprint in SHA256 format. This can be used 
to verify the server identity when
+     * connecting.
+     *
+     * @return the host key fingerprint (e.g., "SHA256:...") or null if 
unavailable
+     */
+    @Override
+    public String hostKeyFingerprint() {
+        try {
+            PublicKey publicKey = getHostPublicKey();
+            if (publicKey != null) {
+                return KeyUtils.getFingerPrint(publicKey);
+            }
+        } catch (Exception e) {
+            LOG.warn("Failed to get host key fingerprint: {}", e.getMessage(), 
e);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the known_hosts entry for this server.
+     *
+     * @return the known_hosts entry or null if unavailable
+     */
+    @Override
+    public String knownHostsEntry() {
+        try {
+            PublicKey publicKey = getHostPublicKey();
+            if (publicKey != null) {
+                StringBuilder sb = new StringBuilder();
+                PublicKeyEntry.appendPublicKeyEntry(sb, publicKey);
+                return String.format("[%s]:%d %s",
+                        embeddedConfiguration.getServerAddress(), port, sb);
+            }
+        } catch (Exception e) {
+            LOG.warn("Failed to get known_hosts entry: {}", e.getMessage(), e);
+        }
+        return null;
+    }
+
+    private PublicKey getHostPublicKey() {
+        if (sshd == null) {
+            return null;
+        }
+        try {
+            Iterable<KeyPair> keyPairs = 
sshd.getKeyPairProvider().loadKeys(null);
+            for (KeyPair keyPair : keyPairs) {
+                return keyPair.getPublic();
+            }
+        } catch (Exception e) {
+            LOG.debug("Failed to load host key: {}", e.getMessage(), e);
+        }
+        return null;
+    }
 }
diff --git 
a/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/embedded/SftpUtil.java
 
b/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/embedded/SftpUtil.java
index 59172af0934f..d850a503e7ef 100644
--- 
a/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/embedded/SftpUtil.java
+++ 
b/test-infra/camel-test-infra-ftp/src/main/java/org/apache/camel/test/infra/ftp/services/embedded/SftpUtil.java
@@ -17,8 +17,12 @@
 
 package org.apache.camel.test.infra.ftp.services.embedded;
 
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.security.KeyPair;
 
+import org.apache.sshd.common.keyprovider.ClassLoadableResourceKeyPairProvider;
 import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -48,10 +52,24 @@ public final class SftpUtil {
 
     private static boolean doCheck(String keyPairFile) {
         try {
-            FileKeyPairProvider provider = new 
FileKeyPairProvider(Paths.get(keyPairFile));
+            // First try the file path
+            Path keyPath = Paths.get(keyPairFile);
+            if (Files.exists(keyPath)) {
+                FileKeyPairProvider provider = new 
FileKeyPairProvider(keyPath);
+                provider.loadKeys(null);
+                return true;
+            }
 
-            provider.loadKeys(null);
-            return true;
+            // Fall back to classpath resource
+            ClassLoadableResourceKeyPairProvider classpathProvider
+                    = new 
ClassLoadableResourceKeyPairProvider(EmbeddedConfiguration.BUNDLED_HOST_KEY_RESOURCE);
+            Iterable<KeyPair> keys = classpathProvider.loadKeys(null);
+            if (keys != null && keys.iterator().hasNext()) {
+                return true;
+            }
+
+            LOG.warn("No host key available at {} or in classpath", 
keyPairFile);
+            return false;
         } catch (Exception e) {
             String name = System.getProperty("os.name");
             String message = e.getMessage();
diff --git a/test-infra/camel-test-infra-ftp/src/main/resources/hostkey.pem 
b/test-infra/camel-test-infra-ftp/src/main/resources/hostkey.pem
new file mode 100644
index 000000000000..6b6974c78e60
--- /dev/null
+++ b/test-infra/camel-test-infra-ftp/src/main/resources/hostkey.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDdfIWeSV4o68dRrKSzFd/Bk51E65UTmmSrmW0O1ohtzi6HzsDP
+jXgCtlTt3FqTcfFfI92IlTr4JWqC9UK1QT1ZTeng0MkPQmv68hDANHbt5CpETZHj
+W5q4OOgWhVvj5IyOC2NZHtKlJBkdsMAa15ouOOJLzBvAvbqOR/yUROsEiQIDAQAB
+AoGBANG3JDW6NoP8rF/zXoeLgLCj+tfVUPSczhGFVrQkAk4mWfyRkhN0WlwHFOec
+K89MpkV1ij/XPVzU4MNbQ2yod1KiDylzvweYv+EaEhASCmYNs6LS03punml42SL9
+97tOmWfVJXxlQoLiY6jHPU97vTc65k8gL+gmmrpchsW0aqmZAkEA/c8zfmKvY37T
+cxcLLwzwsqqH7g2KZGTf9aRmx2ebdW+QKviJJhbdluDgl1TNNFj5vCLznFDRHiqJ
+wq0wkZ39cwJBAN9l5v3kdXj21UrurNPdlV0n2GZBt2vblooQC37XHF97r2zM7Ou+
+Lg6MyfJClyguhWL9dxnGbf3btQ0l3KDstxMCQCRaiEqjAfIjWVATzeNIXDWLHXso
+b1kf5cA+cwY+vdKdTy4IeUR+Y/DXdvPWDqpf0C11aCVMohdLCn5a5ikFUycCQDhV
+K/BuAallJNfmY7JxN87r00fF3ojWMJnT/fIYMFFrkQrwifXQWTDWE76BSDibsosJ
+u1TGksnm8zrDh2UVC/0CQFrHTiSl/3DHvWAbOJawGKg46cnlDcAhSyV8Frs8/dlP
+7YGG3eqkw++lsghqmFO6mRUTKsBmiiB2wgLGhL5pyYY=
+-----END RSA PRIVATE KEY-----
diff --git a/test-infra/camel-test-infra-ftp/src/main/resources/server.jks 
b/test-infra/camel-test-infra-ftp/src/main/resources/server.jks
new file mode 100644
index 000000000000..28f294b3e9a9
Binary files /dev/null and 
b/test-infra/camel-test-infra-ftp/src/main/resources/server.jks differ

Reply via email to