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