Skip to content

Commit

Permalink
Add new scan config
Browse files Browse the repository at this point in the history
  • Loading branch information
talarian1 committed Sep 8, 2023
1 parent eb649ef commit 9b1092e
Show file tree
Hide file tree
Showing 292 changed files with 146 additions and 70 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.jfrog.ide.idea.scan;

import com.jfrog.ide.common.configuration.ServerConfig;
import com.jfrog.ide.common.nodes.EosIssueNode;
import com.jfrog.ide.common.nodes.SastIssueNode;
import com.jfrog.ide.common.nodes.FileIssueNode;
import com.jfrog.ide.common.nodes.FileTreeNode;
import com.jfrog.ide.common.nodes.subentities.SourceCodeScanType;
Expand All @@ -21,24 +21,21 @@
/**
* @author Tal Arian
*/
public class EosScannerExecutor extends ScanBinaryExecutor {
public class SastScannerExecutor extends ScanBinaryExecutor {
private static final List<String> SCANNER_ARGS = List.of("zd");
private static final boolean RUN_WITH_CONFIG_FILE = false;
private static final boolean RUN_WITH_NEW_CONFIG_FILE = true;
private static final List<PackageManagerType> SUPPORTED_PACKAGE_TYPES = List.of(PackageManagerType.PYPI, PackageManagerType.NPM, PackageManagerType.YARN, PackageManagerType.GRADLE, PackageManagerType.MAVEN);

public EosScannerExecutor(Log log, ServerConfig serverConfig) {
public SastScannerExecutor(Log log, ServerConfig serverConfig) {
this(log, serverConfig, null, true);
}

public EosScannerExecutor(Log log, ServerConfig serverConfig, String binaryDownloadUrl, boolean useJFrogReleases) {
super(SourceCodeScanType.EOS, binaryDownloadUrl, log, serverConfig, useJFrogReleases);
public SastScannerExecutor(Log log, ServerConfig serverConfig, String binaryDownloadUrl, boolean useJFrogReleases) {
super(SourceCodeScanType.SAST, binaryDownloadUrl, log, serverConfig, useJFrogReleases);
}

public List<JFrogSecurityWarning> execute(ScanConfig.Builder inputFileBuilder, Runnable checkCanceled) throws IOException, InterruptedException {
// The EOS scanner is expected to run on the project's root directory without a config file.
// inputFileBuilder roots should always contain a single root project in our use cases.
Path executionDir = Paths.get(inputFileBuilder.Build().getRoots().get(0));
return super.execute(inputFileBuilder, SCANNER_ARGS, checkCanceled, RUN_WITH_CONFIG_FILE, executionDir.toFile());
return super.execute(inputFileBuilder, SCANNER_ARGS, checkCanceled, RUN_WITH_NEW_CONFIG_FILE);
}

@Override
Expand All @@ -52,7 +49,7 @@ List<FileTreeNode> createSpecificFileIssueNodes(List<JFrogSecurityWarning> warni
results.put(warning.getFilePath(), fileNode);
}

FileIssueNode issueNode = new EosIssueNode(warning.getRuleID(),
FileIssueNode issueNode = new SastIssueNode(warning.getRuleID(),
warning.getFilePath(), warning.getLineStart(), warning.getColStart(), warning.getLineEnd(), warning.getColEnd(),
warning.getScannerSearchTarget(), warning.getLineSnippet(), warning.getCodeFlows(), warning.getSeverity(), warning.getRuleID());
fileNode.addIssue(issueNode);
Expand All @@ -62,7 +59,7 @@ List<FileTreeNode> createSpecificFileIssueNodes(List<JFrogSecurityWarning> warni

@Override
public Feature getScannerFeatureName() {
// TODO: change to EOS feature when Xray entitlement service support it.
// TODO: change to SASST feature when Xray entitlement service support it.
return Feature.CONTEXTUAL_ANALYSIS;
}

Expand Down
18 changes: 7 additions & 11 deletions src/main/java/com/jfrog/ide/idea/scan/ScanBinaryExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,10 @@ public String getBinaryDownloadURL() {
abstract List<JFrogSecurityWarning> execute(ScanConfig.Builder inputFileBuilder, Runnable checkCanceled) throws IOException, InterruptedException, URISyntaxException;

protected List<JFrogSecurityWarning> execute(ScanConfig.Builder inputFileBuilder, List<String> args, Runnable checkCanceled) throws IOException, InterruptedException {
return execute(inputFileBuilder, args, checkCanceled, true, binaryTargetPath.toFile().getParentFile());
return execute(inputFileBuilder, args, checkCanceled, false);
}

protected List<JFrogSecurityWarning> execute(ScanConfig.Builder inputFileBuilder, List<String> args, Runnable checkCanceled, boolean createInputFile, File executionDir) throws IOException, InterruptedException {
protected List<JFrogSecurityWarning> execute(ScanConfig.Builder inputFileBuilder, List<String> args, Runnable checkCanceled, boolean newConfigFormat) throws IOException, InterruptedException {
if (!shouldExecute()) {
return List.of();
}
Expand All @@ -143,20 +143,16 @@ protected List<JFrogSecurityWarning> execute(ScanConfig.Builder inputFileBuilder
inputFileBuilder.output(outputFilePath.toString());
inputFileBuilder.scanType(scanType);
ScanConfig inputParams = inputFileBuilder.Build();
CommandExecutor commandExecutor = new CommandExecutor(binaryTargetPath.toString(), createEnvWithCredentials());
args = new ArrayList<>(args);
if (createInputFile) {
inputFile = createTempRunInputFile(new ScansConfig(List.of(inputParams)));
args.add(inputFile.toString());
} else {
args.add(outputFilePath.toString());
}
inputFile = newConfigFormat ? createTempRunInputFile(new NewScanConfig(inputParams)) : createTempRunInputFile(new ScansConfig(List.of(inputParams)));
args.add(inputFile.toString());

Logger log = Logger.getInstance();
// The following logging is done outside the commandExecutor because the commandExecutor log level is set to INFO.
// As it is an internal binary execution, the message should be printed for DEBUG use only.
log.debug(String.format("Executing command: %s %s", binaryTargetPath.toString(), join(" ", args)));
CommandResults commandResults = commandExecutor.exeCommand(executionDir, args,
CommandExecutor commandExecutor = new CommandExecutor(binaryTargetPath.toString(), createEnvWithCredentials());
CommandResults commandResults = commandExecutor.exeCommand(binaryTargetPath.toFile().getParentFile(), args,
null, new NullLog(), MAX_EXECUTION_MINUTES, TimeUnit.MINUTES);
if (commandResults.getExitValue() == USER_NOT_ENTITLED) {
log.debug("User not entitled for advance security scan");
Expand Down Expand Up @@ -288,7 +284,7 @@ protected void downloadBinary() throws IOException {
}
}

Path createTempRunInputFile(ScansConfig scanInput) throws IOException {
Path createTempRunInputFile(Object scanInput) throws IOException {
ObjectMapper om = createYAMLMapper();
Path tempDir = Files.createTempDirectory("");
Path inputPath = Files.createTempFile(tempDir, "", ".yaml");
Expand Down
114 changes: 100 additions & 14 deletions src/main/java/com/jfrog/ide/idea/scan/SourceCodeScannerManager.java
Original file line number Diff line number Diff line change
@@ -1,48 +1,61 @@
package com.jfrog.ide.idea.scan;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.jfrog.ide.common.log.ProgressIndicator;
import com.jfrog.ide.common.nodes.*;
import com.jfrog.ide.common.nodes.DependencyNode;
import com.jfrog.ide.common.nodes.FileTreeNode;
import com.jfrog.ide.common.nodes.VulnerabilityNode;
import com.jfrog.ide.common.nodes.subentities.SourceCodeScanType;
import com.jfrog.ide.idea.configuration.GlobalSettings;
import com.jfrog.ide.idea.inspections.JFrogSecurityWarning;
import com.jfrog.ide.idea.log.Logger;
import com.jfrog.ide.idea.log.ProgressIndicatorImpl;
import com.jfrog.ide.idea.scan.data.PackageManagerType;
import com.jfrog.ide.idea.scan.data.ScanConfig;
import com.jfrog.ide.idea.scan.data.applications.JFrogApplicationsConfig;
import com.jfrog.ide.idea.scan.data.applications.ModuleConfig;
import com.jfrog.ide.idea.scan.data.applications.ScannerConfig;
import com.jfrog.ide.idea.ui.LocalComponentsTree;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.jetbrains.annotations.NotNull;

import javax.swing.tree.TreeNode;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;

import static com.jfrog.ide.common.log.Utils.logError;
import static com.jfrog.ide.common.utils.Utils.createYAMLMapper;
import static com.jfrog.ide.idea.scan.ScannerBase.createRunnable;
import static com.jfrog.ide.idea.ui.configuration.ConfigVerificationUtils.*;
import static com.jfrog.ide.idea.utils.Utils.getProjectBasePath;
import static org.apache.commons.lang3.StringUtils.defaultIfEmpty;

public class SourceCodeScannerManager {
private final Path jfrogApplictionsConfigPath;
private final AtomicBoolean scanInProgress = new AtomicBoolean(false);
private final ApplicabilityScannerExecutor applicability = new ApplicabilityScannerExecutor(Logger.getInstance(), GlobalSettings.getInstance().getServerConfig());

private final Collection<ScanBinaryExecutor> scanners = initScannersCollection();

private final Map<SourceCodeScanType, ScanBinaryExecutor> scanners = initScannersCollection();
protected Project project;
protected PackageManagerType packageType;
private static final String SKIP_FOLDERS_SUFFIX = "*/**";

public SourceCodeScannerManager(Project project) {
this.project = project;
jfrogApplictionsConfigPath = getProjectBasePath(project).resolve(".jfrog").resolve("jfrog-apps-config.yml");
}

public SourceCodeScannerManager(Project project, PackageManagerType packageType) {
Expand Down Expand Up @@ -113,7 +126,11 @@ public void run(@NotNull com.intellij.openapi.progress.ProgressIndicator indicat
log.info("Advanced source code scan is already in progress");
return;
}
sourceCodeScanAndUpdate(new ProgressIndicatorImpl(indicator), ProgressManager::checkCanceled, log);
try {
sourceCodeScanAndUpdate(new ProgressIndicatorImpl(indicator), ProgressManager::checkCanceled, log);
} catch (IOException e) {
logError(Logger.getInstance(), "Failed to run advanced source code scanning.", e, true);
}
}

@Override
Expand All @@ -131,13 +148,35 @@ public void onThrowable(@NotNull Throwable error) {
executor.submit(createRunnable(sourceCodeScanTask, latch, log));
}

private void sourceCodeScanAndUpdate(ProgressIndicator indicator, Runnable checkCanceled, Logger log) {
private void sourceCodeScanAndUpdate(ProgressIndicator indicator, Runnable checkCanceled, Logger log) throws IOException {
indicator.setText("Running advanced source code scanning");
JFrogApplicationsConfig projectConfig = parseJFrogApplicationsConfig();

if (projectConfig != null) {
for (ModuleConfig moduleConfig : projectConfig.getModules())
scan(moduleConfig, indicator, checkCanceled, log);
}
scan(null, indicator, checkCanceled, log);
}

private void scan(ModuleConfig moduleConfig, ProgressIndicator indicator, Runnable checkCanceled, Logger log) {
double fraction = 0;
for (ScanBinaryExecutor scanner : scanners) {
for (SourceCodeScanType scannerType : scanners.keySet()) {
checkCanceled.run();
ScanBinaryExecutor scanner = scanners.get(scannerType);
ScannerConfig scannerConfig = null;
if (moduleConfig != null) {
// If requested skip the scanner.
if (moduleConfig.getExcludeScanners() != null && moduleConfig.getExcludeScanners().contains(scannerType.toString().toLowerCase())) {
log.debug(String.format("Skipping %s scanning", scannerType.toString().toLowerCase()));
continue;
}
if (moduleConfig.getScanners() != null) {
scannerConfig = moduleConfig.getScanners().get(scannerType.toString().toLowerCase());
}
}
try {
List<JFrogSecurityWarning> scanResults = scanner.execute(createBasicScannerInput(), checkCanceled);
List<JFrogSecurityWarning> scanResults = scanner.execute(createBasicScannerInput(moduleConfig, scannerConfig), checkCanceled);
addSourceCodeScanResults(scanner.createSpecificFileIssueNodes(scanResults));
} catch (IOException | URISyntaxException | InterruptedException e) {
logError(log, "", e, true);
Expand All @@ -147,6 +186,15 @@ private void sourceCodeScanAndUpdate(ProgressIndicator indicator, Runnable check
}
}

private JFrogApplicationsConfig parseJFrogApplicationsConfig() throws IOException {
ObjectMapper mapper = createYAMLMapper();
File config = jfrogApplictionsConfigPath.toFile();
if (config.exists()) {
return mapper.readValue(config, JFrogApplicationsConfig.class);
}
return null;
}

private void addSourceCodeScanResults(List<FileTreeNode> fileTreeNodes) {
if (fileTreeNodes.isEmpty()) {
return;
Expand All @@ -160,6 +208,44 @@ private ScanConfig.Builder createBasicScannerInput() {
return new ScanConfig.Builder().roots(List.of(getProjectBasePath(project).toAbsolutePath().toString())).skippedFolders(convertToSkippedFolders(excludePattern));
}

private ScanConfig.Builder createBasicScannerInput(ModuleConfig config, ScannerConfig scannerConfig) {
if (config == null) {
return createBasicScannerInput();
}

// Scanner's working dirs (roots)
List<String> workingDirs = new ArrayList<>();
String projectBasePath = defaultIfEmpty(config.getSourceRoot(), getProjectBasePath(project).toAbsolutePath().toString());
if (scannerConfig != null && CollectionUtils.isEmpty(scannerConfig.getWorkingDirs())) {
for (String workingDir : scannerConfig.getWorkingDirs()) {
workingDirs.add(Paths.get(projectBasePath).resolve(workingDir).toAbsolutePath().toString());
}
} else {
// Default: ".", the application's root directory.
workingDirs.add(projectBasePath);
}

// Module exclude patterns
List<String> skippedFolders = new ArrayList<>(config.getExcludePatterns());
if (scannerConfig != null) {
// Adds scanner specific exclude patterns if exists
skippedFolders.addAll(scannerConfig.getExcludePatterns());
}
String excludePattern = GlobalSettings.getInstance().getServerConfig().getExcludedPaths();
// If exclude patterns was not provided, use the configured IDE patterns.
skippedFolders = skippedFolders.isEmpty() ? convertToSkippedFolders(excludePattern) : skippedFolders;

// Extra scanners params
List<String> excludeRules = null;
String language = null;
if (scannerConfig != null) {
excludeRules = scannerConfig.getExcludedRules();
language = scannerConfig.getLanguage();
}

return new ScanConfig.Builder().roots(workingDirs).skippedFolders(skippedFolders).excludedRules(excludeRules).language(language);
}

/**
* Splits the users' configured ExcludedPaths glob pattern to a list
* of simplified patterns by avoiding the use of "{}".
Expand Down Expand Up @@ -215,11 +301,11 @@ private Map<String, List<VulnerabilityNode>> mapDirectIssuesByCve(Collection<Fil
return issues;
}

private Collection<ScanBinaryExecutor> initScannersCollection() {
return List.of(
new SecretsScannerExecutor(Logger.getInstance(), GlobalSettings.getInstance().getServerConfig()),
new IACScannerExecutor(Logger.getInstance(), GlobalSettings.getInstance().getServerConfig()),
new EosScannerExecutor(Logger.getInstance(), GlobalSettings.getInstance().getServerConfig())
);
private Map<SourceCodeScanType, ScanBinaryExecutor> initScannersCollection() {
Map<SourceCodeScanType, ScanBinaryExecutor> scanners = new HashMap<>();
scanners.put(SourceCodeScanType.SECRETS, new SecretsScannerExecutor(Logger.getInstance(), GlobalSettings.getInstance().getServerConfig()));
scanners.put(SourceCodeScanType.IAC, new IACScannerExecutor(Logger.getInstance(), GlobalSettings.getInstance().getServerConfig()));
scanners.put(SourceCodeScanType.SAST, new SastScannerExecutor(Logger.getInstance(), GlobalSettings.getInstance().getServerConfig()));
return scanners;
}
}
27 changes: 13 additions & 14 deletions src/main/java/com/jfrog/ide/idea/scan/data/ScanConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import com.fasterxml.jackson.annotation.JsonProperty;
import com.jfrog.ide.common.nodes.subentities.SourceCodeScanType;
import lombok.Getter;

import java.util.List;

@Getter
public class ScanConfig {
@JsonProperty("type")
private SourceCodeScanType scanType;

@JsonProperty("language")
private String language;
@JsonProperty("roots")
Expand All @@ -16,12 +19,12 @@ public class ScanConfig {
private String output;
@JsonProperty("grep-disable")
private Boolean grepDisable;

@JsonProperty("cve-whitelist")
private List<String> cves;

@JsonProperty("skipped-folders")
private List<String> skippedFolders;
@JsonProperty("excluded-rules")
private List<String> excludedRules;

@SuppressWarnings("unused")
ScanConfig() {
Expand All @@ -35,6 +38,7 @@ public class ScanConfig {
this.cves = builder.cves;
this.grepDisable = builder.grepDisable;
this.skippedFolders = builder.skippedFolders;
this.excludedRules = builder.excludedRules;
}

@SuppressWarnings("unused")
Expand All @@ -47,27 +51,15 @@ public void setScanType(SourceCodeScanType scanType) {
this.scanType = scanType;
}

public String getLanguage() {
return language;
}

public void setLanguage(String language) {
this.language = language;
}

public List<String> getRoots() {
return roots;
}

@SuppressWarnings("unused")
public void setRoots(List<String> roots) {
this.roots = roots;
}

public String getOutput() {
return output;
}

@SuppressWarnings("unused")
public void setOutput(String output) {
this.output = output;
Expand Down Expand Up @@ -112,6 +104,7 @@ public static class Builder {
private Boolean grepDisable;
private List<String> cves;
private List<String> skippedFolders;
private List<String> excludedRules;

public Builder() {
}
Expand Down Expand Up @@ -155,6 +148,12 @@ public Builder skippedFolders(List<String> skippedFolders) {
return this;
}

@SuppressWarnings("unused")
public Builder excludedRules(List<String> excludedRules) {
this.excludedRules = excludedRules;
return this;
}

public ScanConfig Build() {
return new ScanConfig(this);
}
Expand Down
Loading

0 comments on commit 9b1092e

Please sign in to comment.