Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions src/main/java/io/jenkins/plugins/jfrog/CliEnvConfigurator.java
Original file line number Diff line number Diff line change
@@ -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.
*
Expand All @@ -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);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/io/jenkins/plugins/jfrog/JfStep.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/io/jenkins/plugins/jfrog/JfrogBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)) {
Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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");
Expand All @@ -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");
Expand All @@ -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";
Expand All @@ -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";
Expand All @@ -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();
Expand Down
41 changes: 30 additions & 11 deletions src/test/java/io/jenkins/plugins/jfrog/CliEnvConfiguratorTest.java
Original file line number Diff line number Diff line change
@@ -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.*;

Expand All @@ -19,6 +24,8 @@
public class CliEnvConfiguratorTest {
@Rule
public JenkinsRule jenkinsRule = new JenkinsRule();
@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();
JenkinsProxyConfiguration proxyConfiguration;
EnvVars envVars;

Expand All @@ -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));
Expand All @@ -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);
}
Expand Down
Loading