Skip to content

Commit

Permalink
chore: Various updates
Browse files Browse the repository at this point in the history
  • Loading branch information
rsenden committed Oct 24, 2024
1 parent 0020c73 commit b8e7434
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 115 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

Expand All @@ -40,6 +40,7 @@
import kong.unirest.MultipartBody;
import kong.unirest.UnirestInstance;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
Expand All @@ -55,29 +56,21 @@ public final class SCSastControllerScanStartCommand extends AbstractSCSastContro
@Mixin private SCSastSensorPoolResolverMixin.OptionalOption sensorPoolResolver;
@Mixin private PublishToAppVersionResolverMixin sscAppVersionResolver;
@Option(names = "--ssc-ci-token") private String ciToken;
@Option(names = { "--sargs", "--scan-args" }, description = "Runtime scan arguments to Source Analyzer.")
@Option(names = { "--sargs", "--scan-args" })
private String scanArguments = "";

private Map<String, Set<String>> scanFileArgs = new HashMap<String, Set<String>>();
private Map<String, Set<String>> compressedFilesMap = new HashMap<String, Set<String>>();
private Set<ScanArgument> scanArgumentsSet;

// TODO Add options for specifying (custom) rules file(s), filter file(s) and project template
// TODO Add options for pool selection

@Override
public final JsonNode getJsonNode(UnirestInstance unirest) {
String sensorVersion = normalizeSensorVersion(optionsProvider.getScanStartOptions().getSensorVersion());

processScanArguments();
var scanArgsHelper = ScanArgsHelper.parse(scanArguments);
MultipartBody body = unirest.post("/rest/v2/job")
.multiPartContent()
.field("zipFile", createZipFile(), "application/zip")
.field("zipFile", createZipFile(scanArgsHelper.getInputFileToZipEntryMap()), "application/zip")
.field("username", userName, "text/plain")
.field("scaVersion", sensorVersion, "text/plain")
.field("clientVersion", sensorVersion, "text/plain")
.field("jobType", optionsProvider.getScanStartOptions().getJobType().name(), "text/plain")
.field("scaRuntimeArgs", constructSCAArgs(), "text/plain");
.field("scaRuntimeArgs", scanArgsHelper.getScanArgs(), "text/plain");

body = updateBody(body, "email", email);
body = updateBody(body, "buildId", optionsProvider.getScanStartOptions().getBuildId());
Expand All @@ -95,79 +88,6 @@ public final JsonNode getJsonNode(UnirestInstance unirest) {
return SCSastControllerScanJobHelper.getScanJobDescriptor(unirest, scanJobToken, StatusEndpointVersion.v1).asJsonNode();
}

private String constructSCAArgs() {
StringBuffer buffer = new StringBuffer();
for (ScanArgument scanArgument : scanArgumentsSet) {
String argKey = scanArgument.getArgKey();
String argValue = scanArgument.getArgValue();
boolean fileArgument = scanArgument.isFileArgument();
if (fileArgument) {
Set<String> scanArgFiles = compressedFilesMap.get(argKey);
if (null != scanArgFiles) {
int size = scanArgFiles.size();
for (String scanArgumentFileName : scanArgFiles) {
buffer.append(argKey);
buffer.append(" \'");
buffer.append(scanArgumentFileName);
buffer.append("\'");
if (size > 1) {
buffer.append(" ");
--size;
}
}
}
compressedFilesMap.remove(argKey);
} else {
buffer.append(argKey);
if (argValue!=null) {
buffer.append(" ");
buffer.append(argValue);
}
}
buffer.append(" ");
}
return buffer.toString().trim();
}

private void processScanArguments() {
scanArgumentsSet = processScanRuntimeArgs();
for (ScanArgument scanArgument : scanArgumentsSet) {
if (scanArgument.isFileArgument()) {
String key = scanArgument.getArgKey();
String value = scanArgument.getArgValue();
scanFileArgs.computeIfAbsent(key, k -> new HashSet<>()).add(value);
}
}
updateFileNamesToUniqueName();
}

private Set<ScanArgument> processScanRuntimeArgs() {
Set<ScanArgument> scanArgsSet = new HashSet<ScanArgument>();
String[] parts = scanArguments.split(" (?=(?:[^\']*\'[^\']*\')*[^\']*$)");

ScanArgument scanArgument = null;
for (String part : parts) {
String key = null;
String value = null;
boolean isFileArg = false;

if (part.startsWith("-")) {
key = part.trim();
scanArgument = new ScanArgument();
scanArgument.setArgKey(key);
} else {
if (part.startsWith("file:")) {
isFileArg = true;
}
value = part.replace("file:", "").replace("'", "");
scanArgument.setArgValue(value);
scanArgument.setFileArgument(isFileArg);
}
scanArgsSet.add(scanArgument);
}
return scanArgsSet;
}

@Override
public final String getActionCommandResult() {
return "SCAN_REQUESTED";
Expand Down Expand Up @@ -225,19 +145,16 @@ private final MultipartBody updateBody(MultipartBody body, String field, String
return StringUtils.isBlank(value) ? body : body.field(field, value, "text/plain");
}

private File createZipFile() {
private File createZipFile(Map<File, String> extraFiles) {
try {
File zipFile = File.createTempFile("zip", ".zip");
zipFile.deleteOnExit();
try (FileOutputStream fout = new FileOutputStream(zipFile); ZipOutputStream zout = new ZipOutputStream(fout)) {
final String fileName = (optionsProvider.getScanStartOptions().getJobType() == SCSastControllerJobType.TRANSLATION_AND_SCAN_JOB) ? "translation.zip" : "session.mbs";
addFile( zout, fileName, optionsProvider.getScanStartOptions().getPayloadFile());

for (Entry<String, Set<String>> fileArgsMap : compressedFilesMap.entrySet()) {
Set<String> files = fileArgsMap.getValue();
for (String file : files) {
addFile(zout, file, optionsProvider.getScanStartOptions().getPayloadFile());
}
for (var extraFile : extraFiles.entrySet() ) {
addFile(zout, extraFile.getValue(), extraFile.getKey());
}
}
return zipFile;
Expand All @@ -246,24 +163,6 @@ private File createZipFile() {
}
}

private void updateFileNamesToUniqueName() {
for (Entry<String, Set<String>> fileArg : scanFileArgs.entrySet()) {
String argName = fileArg.getKey();
Set<String> argValues = fileArg.getValue();
Set<String> compressedFileNames = new HashSet<String>();
for (String argValue : argValues) {
String uniqueFileName = constructUniqueFileName(argValue);
compressedFileNames.add(uniqueFileName);

}
compressedFilesMap.put(argName, compressedFileNames);
}
}

private String constructUniqueFileName(String argValue) {
return argValue.replaceAll("[^A-Za-z0-9.]", "_");
}

private void addFile(ZipOutputStream zout, String fileName, File file) throws IOException {
try ( FileInputStream in = new FileInputStream(file)) {
zout.putNextEntry(new ZipEntry(fileName));
Expand All @@ -281,9 +180,49 @@ private static final class PublishToAppVersionResolverMixin extends AbstractSSCA
@Getter private String appVersionNameOrId;
public final boolean hasValue() { return StringUtils.isNotBlank(appVersionNameOrId); }
}

@RequiredArgsConstructor
private static final class ScanArgsHelper {
@Getter private final String scanArgs;
@Getter private final Map<File, String> inputFileToZipEntryMap;

public static final ScanArgsHelper parse(String scanArgs) {
List<String> newArgs = new ArrayList<>();
Map<File, String> inputFileToZipEntryMap = new LinkedHashMap<>();
String[] parts = scanArgs.split(" (?=(?:[^\']*\'[^\']*\')*[^\']*$)");
for ( var part: parts ) {
var inputFileName = getInputFileName(part);
if ( inputFileName==null ) {
newArgs.add(part.replace("'", "\""));
} else {
var inputFile = new File(inputFileName);
if ( !inputFile.canRead() ) {
throw new IllegalArgumentException("Can't read file "+inputFileName+" as specified in --sargs");
}
// Re-use existing zip entry name if same file was processed before
var zipEntryFileName = inputFileToZipEntryMap.getOrDefault(inputFile, getZipEntryFileName(inputFileName));
newArgs.add("\""+zipEntryFileName+"\"");
inputFileToZipEntryMap.put(inputFile, zipEntryFileName);
}
}
return new ScanArgsHelper(String.join(" ", newArgs), inputFileToZipEntryMap);
}

private static final String getInputFileName(String part) {
var pattern = Pattern.compile("^'?file:'?([^\']*)'?$");
var matcher = pattern.matcher(part);
return matcher.matches() ? matcher.group(1) : null;
}

private static final String getZipEntryFileName(String orgFileName) {
return orgFileName.replaceAll("[^A-Za-z0-9.]", "_");
}
}
}




class ScanArgument {
private boolean isFileArgument;
private String argKey;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

public interface ISCSastScanStartOptions {
String getBuildId();
// String getScaRuntimeArgs();
boolean isDotNetRequired();
String getDotNetVersion();
File getPayloadFile();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ fcli.sc-sast.scan.start.package-file = Package file to scan.
fcli.sc-sast.scan.start.notify = Email address to which to send a scan completion notification.
fcli.sc-sast.scan.start.sensor-version = Version of the sensor on which the package should be scanned. Officially, you should select the same sensor version as the version of the ScanCentral Client used to create the package.
fcli.sc-sast.scan.start.publish-to = Publish scan results to the given SSC application version once the scan has completed.
fcli.sc-sast.scan.start.sargs = Fortify Static Code Analyzer scan arguments, see ScanCentral SAST documentation for supported \
scan arguments for your ScanCentral SAST version. Multiple scan arguments must be provided as a single option argument, \
arguments containing spaces must be embedded in single quotes, and local files must be referenced through the 'file:' prefix. \
Note that contrary to fcli, scan arguments usually start with a single dash, not double dashes. For example: \
%n --sargs "-quick -filter 'file:./my filters.txt'"
fcli.sc-sast.scan.status.usage.header = Get status for a previously submitted scan request.
fcli.sc-sast.scan.wait-for.usage.header = Wait for one or more scans to reach or exit specified scan statuses.
fcli.sc-sast.scan.wait-for.usage.description.0 = Although this command offers a lot of options to cover many \
Expand All @@ -141,7 +146,6 @@ fcli.sc-sast.scan.wait-for.any-scan-state=One or more scan states against which
fcli.sc-sast.scan.wait-for.any-publish-state=One or more scan publishing states against which to match the given scans.
fcli.sc-sast.scan.wait-for.any-ssc-state=One or more SSC artifact processing states against which to match the given scans.
fcli.sc-sast.scan-job.resolver.jobToken = Scan job token.
fcli.sc-sast.scan.start.sargs = Fortify Source Analyzer runtime scan arguments.

# fcli sc-sast sensor
fcli.sc-sast.sensor.usage.header = Manage ScanCentral SAST sensors.
Expand Down

0 comments on commit b8e7434

Please sign in to comment.