diff --git a/src/main/java/io/jenkins/plugins/jfrog/CliEnvConfigurator.java b/src/main/java/io/jenkins/plugins/jfrog/CliEnvConfigurator.java index f63682bb..6cf4cdc9 100644 --- a/src/main/java/io/jenkins/plugins/jfrog/CliEnvConfigurator.java +++ b/src/main/java/io/jenkins/plugins/jfrog/CliEnvConfigurator.java @@ -1,10 +1,13 @@ package io.jenkins.plugins.jfrog; import hudson.EnvVars; +import hudson.FilePath; import io.jenkins.plugins.jfrog.actions.JFrogCliConfigEncryption; import io.jenkins.plugins.jfrog.configuration.JenkinsProxyConfiguration; import org.apache.commons.lang3.StringUtils; +import java.io.IOException; + /** * Configures JFrog CLI environment variables for the job. * @@ -26,25 +29,30 @@ public class CliEnvConfigurator { * Configure the JFrog CLI environment variables, according to the input job's env. * * @param env - Job's environment variables - * @param jfrogHomeTempDir - Calculated JFrog CLI home dir + * @param jfrogHomeTempDir - Calculated JFrog CLI home dir (FilePath on the agent) * @param encryptionKey - Random encryption key to encrypt the CLI config + * @throws IOException if the encryption key file cannot be written + * @throws InterruptedException if the operation is interrupted */ - static void configureCliEnv(EnvVars env, String jfrogHomeTempDir, JFrogCliConfigEncryption encryptionKey) { + static void configureCliEnv(EnvVars env, FilePath jfrogHomeTempDir, JFrogCliConfigEncryption encryptionKey) throws IOException, InterruptedException { // Setting Jenkins job name as the default build-info name env.putIfAbsent(JFROG_CLI_BUILD_NAME, env.get("JOB_NAME")); // Setting Jenkins build number as the default build-info number env.putIfAbsent(JFROG_CLI_BUILD_NUMBER, env.get("BUILD_NUMBER")); // Setting the specific build URL env.putIfAbsent(JFROG_CLI_BUILD_URL, env.get("BUILD_URL")); - // Set up a temporary Jfrog CLI home directory for a specific run - env.put(JFROG_CLI_HOME_DIR, jfrogHomeTempDir); + // Set up a temporary Jfrog CLI home directory for a specific run. + // Use getRemote() to get the path as seen by the agent. + env.put(JFROG_CLI_HOME_DIR, jfrogHomeTempDir.getRemote()); if (StringUtils.isAllBlank(env.get(HTTP_PROXY_ENV), env.get(HTTPS_PROXY_ENV))) { // Set up HTTP/S proxy setupProxy(env); } if (encryptionKey.shouldEncrypt()) { - // Set up a random encryption key to make sure no raw text secrets are stored in the file system - env.putIfAbsent(JFROG_CLI_ENCRYPTION_KEY, encryptionKey.getKeyOrFilePath()); + // Write the encryption key file on the agent (not controller) using FilePath. + // This ensures the file exists where the JFrog CLI runs (Docker/remote agent). + String keyFilePath = encryptionKey.writeKeyFile(jfrogHomeTempDir); + env.putIfAbsent(JFROG_CLI_ENCRYPTION_KEY, keyFilePath); } } diff --git a/src/main/java/io/jenkins/plugins/jfrog/JfStep.java b/src/main/java/io/jenkins/plugins/jfrog/JfStep.java index 636cef40..c36b72e4 100644 --- a/src/main/java/io/jenkins/plugins/jfrog/JfStep.java +++ b/src/main/java/io/jenkins/plugins/jfrog/JfStep.java @@ -196,7 +196,7 @@ public Launcher.ProcStarter setupJFrogEnvironment(Run run, EnvVars env, La run.addAction(jfrogCliConfigEncryption); } FilePath jfrogHomeTempDir = Utils.createAndGetJfrogCliHomeTempDir(workspace, String.valueOf(run.getNumber())); - CliEnvConfigurator.configureCliEnv(env, jfrogHomeTempDir.getRemote(), jfrogCliConfigEncryption); + CliEnvConfigurator.configureCliEnv(env, jfrogHomeTempDir, jfrogCliConfigEncryption); Launcher.ProcStarter jfLauncher = launcher.launch().envs(env).pwd(workspace).stdout(listener); // Configure all servers, skip if all server ids have already been configured. if (shouldConfig(jfrogHomeTempDir)) { diff --git a/src/main/java/io/jenkins/plugins/jfrog/JfrogBuilder.java b/src/main/java/io/jenkins/plugins/jfrog/JfrogBuilder.java index 77c7c63d..bc914f95 100644 --- a/src/main/java/io/jenkins/plugins/jfrog/JfrogBuilder.java +++ b/src/main/java/io/jenkins/plugins/jfrog/JfrogBuilder.java @@ -247,7 +247,7 @@ private Launcher.ProcStarter setupJFrogEnvironment( } FilePath jfrogHomeTempDir = Utils.createAndGetJfrogCliHomeTempDir(workspace, String.valueOf(run.getNumber())); - CliEnvConfigurator.configureCliEnv(env, jfrogHomeTempDir.getRemote(), jfrogCliConfigEncryption); + CliEnvConfigurator.configureCliEnv(env, jfrogHomeTempDir, jfrogCliConfigEncryption); Launcher.ProcStarter jfLauncher = launcher.launch().envs(env).pwd(workspace).stdout(cliOutputListener); // Configure all servers, skip if all server ids have already been configured. diff --git a/src/main/java/io/jenkins/plugins/jfrog/actions/JFrogCliConfigEncryption.java b/src/main/java/io/jenkins/plugins/jfrog/actions/JFrogCliConfigEncryption.java index 94a0627c..1d5f3bde 100644 --- a/src/main/java/io/jenkins/plugins/jfrog/actions/JFrogCliConfigEncryption.java +++ b/src/main/java/io/jenkins/plugins/jfrog/actions/JFrogCliConfigEncryption.java @@ -1,13 +1,11 @@ package io.jenkins.plugins.jfrog.actions; import hudson.EnvVars; +import hudson.FilePath; import hudson.model.Action; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.UUID; import static io.jenkins.plugins.jfrog.CliEnvConfigurator.JFROG_CLI_HOME_DIR; @@ -19,7 +17,10 @@ **/ public class JFrogCliConfigEncryption implements Action { private boolean shouldEncrypt; - private String keyOrPath; + // The encryption key content (32 characters) + private String key; + // The path to the key file (set when writeKeyFile is called) + private String keyFilePath; public JFrogCliConfigEncryption(EnvVars env) { if (env.containsKey(JFROG_CLI_HOME_DIR)) { @@ -29,41 +30,43 @@ public JFrogCliConfigEncryption(EnvVars env) { } this.shouldEncrypt = true; // UUID is a cryptographically strong encryption key. Without the dashes, it contains exactly 32 characters. - String workspacePath = env.get("WORKSPACE"); - if (workspacePath == null || workspacePath.isEmpty()) { - workspacePath = System.getProperty("java.io.tmpdir"); - } - Path encryptionDir = Paths.get(workspacePath, ".jfrog", "encryption"); - try { - Files.createDirectories(encryptionDir); - String fileName = UUID.randomUUID().toString() + ".key"; - Path keyFilePath = encryptionDir.resolve(fileName); - String encryptionKeyContent = UUID.randomUUID().toString().replaceAll("-", ""); - Files.write(keyFilePath, encryptionKeyContent.getBytes(StandardCharsets.UTF_8)); - this.keyOrPath =keyFilePath.toString(); - } catch (IOException e) { - throw new RuntimeException(e); - } + this.key = UUID.randomUUID().toString().replaceAll("-", ""); } - public String getKey() { - if (this.keyOrPath == null || this.keyOrPath.isEmpty()) { + /** + * Writes the encryption key to a file in the specified directory on the agent. + * Uses FilePath to ensure the file is written on the remote agent, not the controller. + * + * @param jfrogHomeTempDir - The JFrog CLI home temp directory (FilePath on the agent) + * @return The path to the key file (as seen by the agent) + * @throws IOException if the file cannot be written + * @throws InterruptedException if the operation is interrupted + */ + public String writeKeyFile(FilePath jfrogHomeTempDir) throws IOException, InterruptedException { + if (this.key == null || this.key.isEmpty()) { return null; } - try { - byte[] keyBytes = Files.readAllBytes(Paths.get(this.keyOrPath)); - return new String(keyBytes, StandardCharsets.UTF_8).trim(); - } catch (IOException e) { - System.err.println("Error reading encryption key file: " + e.getMessage()); - return null; + // If key file was already written, return the existing path + if (this.keyFilePath != null) { + return this.keyFilePath; } + // Use FilePath operations to write on the agent (not controller) + FilePath encryptionDir = jfrogHomeTempDir.child("encryption"); + encryptionDir.mkdirs(); + String fileName = UUID.randomUUID().toString() + ".key"; + FilePath keyFile = encryptionDir.child(fileName); + keyFile.write(this.key, StandardCharsets.UTF_8.name()); + // getRemote() returns the path as seen by the agent + this.keyFilePath = keyFile.getRemote(); + return this.keyFilePath; } - public String getKeyOrFilePath() { - if (this.keyOrPath == null || this.keyOrPath.isEmpty()) { - return null; - } - return this.keyOrPath; + public String getKey() { + return this.key; + } + + public String getKeyFilePath() { + return this.keyFilePath; } public boolean shouldEncrypt() { diff --git a/src/test/java/io/jenkins/plugins/jfrog/CliEnvConfiguratorProxyTest.java b/src/test/java/io/jenkins/plugins/jfrog/CliEnvConfiguratorProxyTest.java index 8cfeca91..982b3e60 100644 --- a/src/test/java/io/jenkins/plugins/jfrog/CliEnvConfiguratorProxyTest.java +++ b/src/test/java/io/jenkins/plugins/jfrog/CliEnvConfiguratorProxyTest.java @@ -4,6 +4,8 @@ import org.junit.Before; import org.junit.Test; +import java.io.IOException; + import static io.jenkins.plugins.jfrog.CliEnvConfigurator.*; import static org.junit.Assert.assertNull; @@ -22,7 +24,7 @@ public void setUp() { } @Test - public void configureCliEnvHttpProxyTest() { + public void configureCliEnvHttpProxyTest() throws IOException, InterruptedException { proxyConfiguration.port = 80; invokeConfigureCliEnv(); assertEnv(envVars, HTTP_PROXY_ENV, "http://acme.proxy.io:80"); @@ -31,7 +33,7 @@ public void configureCliEnvHttpProxyTest() { } @Test - public void configureCliEnvHttpsProxyTest() { + public void configureCliEnvHttpsProxyTest() throws IOException, InterruptedException { proxyConfiguration.port = 443; invokeConfigureCliEnv(); assertEnv(envVars, HTTP_PROXY_ENV, "https://acme.proxy.io:443"); @@ -40,7 +42,7 @@ public void configureCliEnvHttpsProxyTest() { } @Test - public void configureCliEnvHttpProxyAuthTest() { + public void configureCliEnvHttpProxyAuthTest() throws IOException, InterruptedException { proxyConfiguration.port = 80; proxyConfiguration.username = "andor"; proxyConfiguration.password = "RogueOne"; @@ -51,7 +53,7 @@ public void configureCliEnvHttpProxyAuthTest() { } @Test - public void configureCliEnvHttpsProxyAuthTest() { + public void configureCliEnvHttpsProxyAuthTest() throws IOException, InterruptedException { proxyConfiguration.port = 443; proxyConfiguration.username = "andor"; proxyConfiguration.password = "RogueOne"; @@ -62,14 +64,14 @@ public void configureCliEnvHttpsProxyAuthTest() { } @Test - public void configureCliEnvNoOverrideHttpTest() { + public void configureCliEnvNoOverrideHttpTest() throws IOException, InterruptedException { envVars.put(HTTP_PROXY_ENV, "http://acme2.proxy.io:777"); invokeConfigureCliEnv(); assertEnv(envVars, HTTP_PROXY_ENV, "http://acme2.proxy.io:777"); } @Test - public void configureCliEnvNoOverrideTest() { + public void configureCliEnvNoOverrideTest() throws IOException, InterruptedException { envVars.put(HTTP_PROXY_ENV, "http://acme2.proxy.io:80"); envVars.put(HTTPS_PROXY_ENV, "http://acme2.proxy.io:443"); invokeConfigureCliEnv(); diff --git a/src/test/java/io/jenkins/plugins/jfrog/CliEnvConfiguratorTest.java b/src/test/java/io/jenkins/plugins/jfrog/CliEnvConfiguratorTest.java index 14f000d8..af99e115 100644 --- a/src/test/java/io/jenkins/plugins/jfrog/CliEnvConfiguratorTest.java +++ b/src/test/java/io/jenkins/plugins/jfrog/CliEnvConfiguratorTest.java @@ -1,14 +1,19 @@ package io.jenkins.plugins.jfrog; import hudson.EnvVars; +import hudson.FilePath; import io.jenkins.plugins.jfrog.actions.JFrogCliConfigEncryption; import io.jenkins.plugins.jfrog.configuration.JenkinsProxyConfiguration; import jenkins.model.Jenkins; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.jvnet.hudson.test.JenkinsRule; +import java.io.File; +import java.io.IOException; + import static io.jenkins.plugins.jfrog.CliEnvConfigurator.*; import static org.junit.Assert.*; @@ -19,6 +24,8 @@ public class CliEnvConfiguratorTest { @Rule public JenkinsRule jenkinsRule = new JenkinsRule(); + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); JenkinsProxyConfiguration proxyConfiguration; EnvVars envVars; @@ -31,30 +38,41 @@ public void setUp() { } @Test - public void configureCliEnvBasicTest() { - invokeConfigureCliEnv("a/b/c", new JFrogCliConfigEncryption(envVars)); + public void configureCliEnvBasicTest() throws IOException, InterruptedException { + File jfrogHomeDir = tempFolder.newFolder("jfrog-home"); + FilePath jfrogHomeTempDir = new FilePath(jfrogHomeDir); + invokeConfigureCliEnv(jfrogHomeTempDir, new JFrogCliConfigEncryption(envVars)); assertEnv(envVars, JFROG_CLI_BUILD_NAME, "buildName"); assertEnv(envVars, JFROG_CLI_BUILD_NUMBER, "1"); assertEnv(envVars, JFROG_CLI_BUILD_URL, "https://acme.jenkins.io"); - assertEnv(envVars, JFROG_CLI_HOME_DIR, "a/b/c"); + assertEnv(envVars, JFROG_CLI_HOME_DIR, jfrogHomeDir.getAbsolutePath()); } @Test - public void configEncryptionTest() { + public void configEncryptionTest() throws IOException, InterruptedException { JFrogCliConfigEncryption configEncryption = new JFrogCliConfigEncryption(envVars); assertTrue(configEncryption.shouldEncrypt()); assertEquals(32, configEncryption.getKey().length()); - invokeConfigureCliEnv("a/b/c", configEncryption); - assertEnv(envVars, JFROG_CLI_ENCRYPTION_KEY, configEncryption.getKeyOrFilePath()); + File jfrogHomeDir = tempFolder.newFolder("jfrog-home-enc"); + FilePath jfrogHomeTempDir = new FilePath(jfrogHomeDir); + invokeConfigureCliEnv(jfrogHomeTempDir, configEncryption); + // The encryption key file is created in jfrogHomeTempDir/encryption/ to work in Docker containers + String keyFilePath = envVars.get(JFROG_CLI_ENCRYPTION_KEY); + assertNotNull(keyFilePath); + assertTrue(keyFilePath.startsWith(jfrogHomeDir.getAbsolutePath())); + assertTrue(keyFilePath.contains("encryption")); + assertTrue(keyFilePath.endsWith(".key")); + assertEquals(keyFilePath, configEncryption.getKeyFilePath()); } @Test - public void configEncryptionWithHomeDirTest() { + public void configEncryptionWithHomeDirTest() throws IOException, InterruptedException { // Config JFROG_CLI_HOME_DIR to disable key encryption envVars.put(JFROG_CLI_HOME_DIR, "/a/b/c"); JFrogCliConfigEncryption configEncryption = new JFrogCliConfigEncryption(envVars); - invokeConfigureCliEnv("", configEncryption); + File emptyDir = tempFolder.newFolder("empty"); + invokeConfigureCliEnv(new FilePath(emptyDir), configEncryption); assertFalse(configEncryption.shouldEncrypt()); assertFalse(envVars.containsKey(JFROG_CLI_ENCRYPTION_KEY)); @@ -64,11 +82,12 @@ void assertEnv(EnvVars envVars, String key, String expectedValue) { assertEquals(expectedValue, envVars.get(key)); } - void invokeConfigureCliEnv() { - this.invokeConfigureCliEnv("", new JFrogCliConfigEncryption(envVars)); + void invokeConfigureCliEnv() throws IOException, InterruptedException { + File emptyDir = tempFolder.newFolder("default"); + this.invokeConfigureCliEnv(new FilePath(emptyDir), new JFrogCliConfigEncryption(envVars)); } - void invokeConfigureCliEnv(String jfrogHomeTempDir, JFrogCliConfigEncryption configEncryption) { + void invokeConfigureCliEnv(FilePath jfrogHomeTempDir, JFrogCliConfigEncryption configEncryption) throws IOException, InterruptedException { setProxyConfiguration(); configureCliEnv(envVars, jfrogHomeTempDir, configEncryption); }