Skip to content

Commit cfeb876

Browse files
berejmajsiewer
authored andcommitted
sbom fix
1 parent 1e77011 commit cfeb876

1 file changed

Lines changed: 132 additions & 70 deletions

File tree

  • backend/src/main/java/io/mixeway/mixewayflowapi/integrations/scanner/sca/service

backend/src/main/java/io/mixeway/mixewayflowapi/integrations/scanner/sca/service/CdxGenService.java

Lines changed: 132 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
import ch.qos.logback.core.spi.ScanException;
44
import io.mixeway.mixewayflowapi.db.entity.CodeRepo;
55
import io.mixeway.mixewayflowapi.db.entity.CodeRepoBranch;
6-
import lombok.RequiredArgsConstructor;
76
import lombok.extern.log4j.Log4j2;
7+
import org.springframework.beans.factory.annotation.Value;
88
import org.springframework.stereotype.Service;
9+
import org.springframework.util.StringUtils;
910

1011
import java.io.BufferedReader;
1112
import java.io.File;
1213
import java.io.IOException;
1314
import java.io.InputStreamReader;
15+
import java.util.Map;
1416
import java.util.concurrent.ExecutorService;
1517
import java.util.concurrent.Executors;
1618
import java.util.concurrent.TimeUnit;
@@ -25,20 +27,44 @@
2527
*/
2628
@Service
2729
@Log4j2
28-
@RequiredArgsConstructor
2930
public class CdxGenService {
3031

32+
/**
33+
* Prepended to {@code PATH} for cdxgen and related subprocesses so the same JVM
34+
* can resolve {@code mvn}, {@code npm}, {@code gradle}, etc. as in an interactive shell
35+
* (e.g. {@code /opt/tools/bin:/usr/local/bin} in Docker).
36+
*/
37+
@Value("${scan.subprocess.path-extra:}")
38+
private String pathExtra;
39+
40+
/**
41+
* If set, exported as {@code JAVA_HOME} for subprocesses when the environment
42+
* does not already define it. Otherwise {@code java.home} of the running JVM is used
43+
* when {@code JAVA_HOME} is missing (helps Maven/Gradle invoked by cdxgen).
44+
*/
45+
@Value("${scan.subprocess.java-home:}")
46+
private String javaHomeOverride;
47+
48+
/**
49+
* When true (default), runs {@code pipreqs .} before cdxgen if {@code pipreqs} is on {@code PATH}.
50+
* Set to false to match a manual {@code cdxgen} run and avoid overwriting {@code requirements.txt}.
51+
*/
52+
@Value("${scan.cdxgen.run-pipreqs:true}")
53+
private boolean runPipreqs;
54+
55+
@Value("${proxy.host:#{null}}")
56+
private String proxyHost;
57+
58+
@Value("${proxy.port:#{null}}")
59+
private Integer proxyPort;
60+
3161
/**
3262
* Generates the SBOM (Software Bill of Materials) file using the cdxgen tool.
3363
*
34-
* <p>This method executes the cdxgen command in the specified repository directory,
35-
* redirecting both standard output and error streams to prevent blocking.
36-
* It conditionally sets environment variables for proxy configuration if the
37-
* system properties <code>proxy.host</code> and <code>proxy.port</code> are provided.
38-
* The method waits for the process to complete, with a timeout of 2 minutes.
39-
* If the process exceeds the timeout, it is forcibly terminated.
40-
* After the process completes, the method validates the generated <code>bom.json</code>
41-
* file by checking for its existence and ensuring it has content.</p>
64+
* <p>This method executes cdxgen in the specified repository directory with
65+
* {@link ProcessBuilder#environment()} configured for CDXGEN/CDX_* variables and optional proxy.
66+
* Optional {@code scan.subprocess.path-extra} widens {@code PATH} so language toolchains
67+
* match non-interactive vs login-shell setups.</p>
4268
*
4369
* @param repoDir the directory of the repository where cdxgen will run
4470
* @param codeRepo the code repository entity
@@ -51,70 +77,35 @@ public void generateBom(String repoDir, CodeRepo codeRepo, CodeRepoBranch codeRe
5177
throws IOException, InterruptedException {
5278
log.info("[CdxGen] Starting SBOM generation for: {} branch: {}", codeRepo.getName(), codeRepoBranch.getName());
5379

54-
// Step 1: Verify if 'pipreqs' command is available
55-
boolean isPipreqsAvailable = false;
56-
try {
57-
ProcessBuilder pbCheckPipreqs = new ProcessBuilder("sh", "-c", "command -v pipreqs");
58-
pbCheckPipreqs.redirectErrorStream(true);
59-
Process pCheckPipreqs = pbCheckPipreqs.start();
60-
int exitCode = pCheckPipreqs.waitFor();
61-
if (exitCode == 0) {
62-
isPipreqsAvailable = true;
63-
log.debug("[CdxGen] 'pipreqs' is available.");
64-
} else {
65-
log.debug("[CdxGen] 'pipreqs' is not available.");
66-
}
67-
} catch (IOException e) {
68-
// Command not found
69-
log.debug("[CdxGen] Exception while checking for 'pipreqs': {}", e.getMessage());
70-
}
71-
72-
// Step 2: If available, execute 'pipreqs .' in repoDir
73-
if (isPipreqsAvailable) {
74-
log.debug("[CdxGen] Executing 'pipreqs .' in {}", repoDir);
75-
ProcessBuilder pbPipreqs = new ProcessBuilder("pipreqs", ".");
76-
pbPipreqs.directory(new File(repoDir));
77-
pbPipreqs.redirectOutput(ProcessBuilder.Redirect.INHERIT);
78-
pbPipreqs.redirectError(ProcessBuilder.Redirect.INHERIT);
79-
Process pPipreqs = pbPipreqs.start();
80-
81-
// Wait for 'pipreqs' to finish
82-
boolean finished = pPipreqs.waitFor(10, TimeUnit.MINUTES);
83-
if (!finished) {
84-
log.debug("[CdxGen] 'pipreqs' did not finish within 10 minutes. Terminating process.");
85-
pPipreqs.destroyForcibly();
86-
} else {
87-
int exitCode = pPipreqs.exitValue();
88-
if (exitCode != 0) {
89-
log.debug("[CdxGen] 'pipreqs' exited with non-zero exit code: {}", exitCode);
90-
} else {
91-
log.debug("[CdxGen] 'pipreqs' executed successfully.");
92-
}
93-
}
80+
if (runPipreqs) {
81+
runPipreqsIfAvailable(repoDir);
9482
}
9583

96-
// Step 3: Proceed with executing 'cdxgen'
97-
String proxyHost = System.getProperty("proxy.host");
98-
String proxyPort = System.getProperty("proxy.port");
99-
String command;
100-
101-
if (proxyHost != null && proxyPort != null) {
102-
command = "CDXGEN_DEBUG_MODE=debug "
103-
+ "CDX_MAVEN_INCLUDE_TEST_SCOPE=false "
104-
+ "HTTP_PROXY=http://" + proxyHost + ":" + proxyPort + " "
105-
+ "HTTPS_PROXY=http://" + proxyHost + ":" + proxyPort + " "
106-
+ "cdxgen --recurse --required-only --output sbom.json .";
107-
log.info("[CdxGen] Proxy settings applied: {}:{}", proxyHost, proxyPort);
108-
} else {
109-
command = "CDXGEN_DEBUG_MODE=debug CDX_MAVEN_INCLUDE_TEST_SCOPE=false cdxgen --recurse --required-only --output sbom.json .";
110-
}
111-
112-
// Use 'sh -c' to execute the command in a shell
113-
ProcessBuilder pb = new ProcessBuilder("sh", "-c", command);
84+
ProcessBuilder pb = new ProcessBuilder(
85+
"cdxgen",
86+
"--recurse",
87+
"--required-only",
88+
"--output",
89+
"sbom.json",
90+
"."
91+
);
11492
pb.directory(new File(repoDir));
11593
pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
11694
pb.redirectError(ProcessBuilder.Redirect.PIPE);
11795

96+
applyScannerEnvironment(pb);
97+
98+
Map<String, String> env = pb.environment();
99+
env.put("CDXGEN_DEBUG_MODE", "debug");
100+
env.put("CDX_MAVEN_INCLUDE_TEST_SCOPE", "false");
101+
102+
if (StringUtils.hasText(proxyHost) && proxyPort != null) {
103+
String proxyUrl = "http://" + proxyHost + ":" + proxyPort;
104+
env.put("HTTP_PROXY", proxyUrl);
105+
env.put("HTTPS_PROXY", proxyUrl);
106+
log.info("[CdxGen] Proxy settings applied: {}:{}", proxyHost, proxyPort);
107+
}
108+
118109
Process process = pb.start();
119110

120111
ExecutorService executorService = Executors.newFixedThreadPool(2);
@@ -142,7 +133,6 @@ public void generateBom(String repoDir, CodeRepo codeRepo, CodeRepoBranch codeRe
142133
}
143134
});
144135

145-
// Wait for 'cdxgen' to finish with a timeout of 30 minutes
146136
boolean finished = process.waitFor(30, TimeUnit.MINUTES);
147137
if (!finished) {
148138
log.warn("[CdxGen] SBOM generation did not finish within 30 minutes. Terminating process.");
@@ -166,7 +156,6 @@ public void generateBom(String repoDir, CodeRepo codeRepo, CodeRepoBranch codeRe
166156
}
167157
}
168158

169-
// Validate the 'sbom.json' file
170159
File bomFile = new File(repoDir, "sbom.json");
171160
if (bomFile.exists()) {
172161
if (bomFile.length() > 0) {
@@ -179,6 +168,79 @@ public void generateBom(String repoDir, CodeRepo codeRepo, CodeRepoBranch codeRe
179168
}
180169
}
181170

171+
private void runPipreqsIfAvailable(String repoDir) throws IOException, InterruptedException {
172+
boolean isPipreqsAvailable = false;
173+
try {
174+
ProcessBuilder pbCheckPipreqs = new ProcessBuilder("sh", "-c", "command -v pipreqs");
175+
applyScannerEnvironment(pbCheckPipreqs);
176+
pbCheckPipreqs.redirectErrorStream(true);
177+
Process pCheckPipreqs = pbCheckPipreqs.start();
178+
int exitCode = pCheckPipreqs.waitFor();
179+
if (exitCode == 0) {
180+
isPipreqsAvailable = true;
181+
log.debug("[CdxGen] 'pipreqs' is available.");
182+
} else {
183+
log.debug("[CdxGen] 'pipreqs' is not available.");
184+
}
185+
} catch (IOException e) {
186+
log.debug("[CdxGen] Exception while checking for 'pipreqs': {}", e.getMessage());
187+
}
188+
189+
if (!isPipreqsAvailable) {
190+
return;
191+
}
192+
193+
log.debug("[CdxGen] Executing 'pipreqs .' in {}", repoDir);
194+
ProcessBuilder pbPipreqs = new ProcessBuilder("pipreqs", ".");
195+
pbPipreqs.directory(new File(repoDir));
196+
applyScannerEnvironment(pbPipreqs);
197+
pbPipreqs.redirectOutput(ProcessBuilder.Redirect.INHERIT);
198+
pbPipreqs.redirectError(ProcessBuilder.Redirect.INHERIT);
199+
Process pPipreqs = pbPipreqs.start();
200+
201+
boolean finished = pPipreqs.waitFor(10, TimeUnit.MINUTES);
202+
if (!finished) {
203+
log.debug("[CdxGen] 'pipreqs' did not finish within 10 minutes. Terminating process.");
204+
pPipreqs.destroyForcibly();
205+
} else {
206+
int exitCode = pPipreqs.exitValue();
207+
if (exitCode != 0) {
208+
log.debug("[CdxGen] 'pipreqs' exited with non-zero exit code: {}", exitCode);
209+
} else {
210+
log.debug("[CdxGen] 'pipreqs' executed successfully.");
211+
}
212+
}
213+
}
214+
215+
/**
216+
* Ensures subprocesses see the same toolchains as typical CLI usage: optional {@code PATH}
217+
* prefix, {@code JAVA_HOME}, and a defined {@code HOME} when missing.
218+
*/
219+
private void applyScannerEnvironment(ProcessBuilder pb) {
220+
Map<String, String> env = pb.environment();
182221

222+
if (StringUtils.hasText(pathExtra)) {
223+
String path = env.getOrDefault("PATH", "");
224+
env.put("PATH", pathExtra + File.pathSeparator + path);
225+
log.debug("[CdxGen] PATH prefix applied (scan.subprocess.path-extra)");
226+
}
183227

228+
if (!StringUtils.hasText(env.get("JAVA_HOME"))) {
229+
if (StringUtils.hasText(javaHomeOverride)) {
230+
env.put("JAVA_HOME", javaHomeOverride.trim());
231+
} else {
232+
String jvmHome = System.getProperty("java.home");
233+
if (StringUtils.hasText(jvmHome)) {
234+
env.put("JAVA_HOME", jvmHome);
235+
}
236+
}
237+
}
238+
239+
if (!StringUtils.hasText(env.get("HOME"))) {
240+
String userHome = System.getProperty("user.home");
241+
if (StringUtils.hasText(userHome)) {
242+
env.put("HOME", userHome);
243+
}
244+
}
245+
}
184246
}

0 commit comments

Comments
 (0)