Skip to content

Commit

Permalink
fix:SC-SAST: Provide ability to pass custom targs/sargs when starting…
Browse files Browse the repository at this point in the history
… scan (fixes #449)
  • Loading branch information
Sangamesh Vijaykumar committed Oct 24, 2024
1 parent 19ceffd commit 0020c73
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +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.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

Expand Down Expand Up @@ -50,28 +55,38 @@ 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.")
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();
MultipartBody body = unirest.post("/rest/v2/job")
.multiPartContent()
.field("zipFile", createZipFile(), "application/zip")
.field("username", userName, "text/plain")
.field("scaVersion", sensorVersion, "text/plain")
.field("clientVersion", sensorVersion, "text/plain")
.field("scaRuntimeArgs", optionsProvider.getScanStartOptions().getScaRuntimeArgs(), "text/plain")
.field("jobType", optionsProvider.getScanStartOptions().getJobType().name(), "text/plain");
.field("jobType", optionsProvider.getScanStartOptions().getJobType().name(), "text/plain")
.field("scaRuntimeArgs", constructSCAArgs(), "text/plain");

body = updateBody(body, "email", email);
body = updateBody(body, "buildId", optionsProvider.getScanStartOptions().getBuildId());
body = updateBody(body, "pvId", getAppVersionId());
body = updateBody(body, "poolUuid", getSensorPoolUuid());
body = updateBody(body, "uploadToken", getUploadToken());
body = updateBody(body, "dotNetRequired", String.valueOf(optionsProvider.getScanStartOptions().isDotNetRequired()));
body = updateBody(body, "dotNetFrameworkRequiredVersion", optionsProvider.getScanStartOptions().getDotNetVersion());

JsonNode response = body.asObject(JsonNode.class).getBody();
if ( !response.has("token") ) {
throw new IllegalStateException("Unexpected response when submitting scan job: "+response);
Expand All @@ -80,7 +95,80 @@ public final JsonNode getJsonNode(UnirestInstance unirest) {
return SCSastControllerScanJobHelper.getScanJobDescriptor(unirest, scanJobToken, StatusEndpointVersion.v1).asJsonNode();
}

@Override
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 @@ -144,15 +232,39 @@ private File createZipFile() {
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());
// TODO Add rule files, filter files, issue template

for (Entry<String, Set<String>> fileArgsMap : compressedFilesMap.entrySet()) {
Set<String> files = fileArgsMap.getValue();
for (String file : files) {
addFile(zout, file, optionsProvider.getScanStartOptions().getPayloadFile());
}
}
}
return zipFile;
} catch (IOException e) {
throw new RuntimeException("Error creating job file", e);
}
}

private void addFile(ZipOutputStream zout, String fileName, File file) throws IOException {
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));
byte[] buffer = new byte[1024];
Expand All @@ -170,3 +282,39 @@ private static final class PublishToAppVersionResolverMixin extends AbstractSSCA
public final boolean hasValue() { return StringUtils.isNotBlank(appVersionNameOrId); }
}
}


class ScanArgument {
private boolean isFileArgument;
private String argKey;
private String argValue;

public void setFileArgument(boolean isFileArgument) {
this.isFileArgument = isFileArgument;
}

public void setArgKey(String argKey) {
this.argKey = argKey;
}

public void setArgValue(String argValue) {
this.argValue = argValue;
}

public boolean isFileArgument() {
return isFileArgument;
}

public String getArgKey() {
return argKey;
}

public String getArgValue() {
return argValue;
}

@Override
public String toString() {
return "Argument " + argKey + (isFileArgument? " is a file argument with value " : " is not a file argument with value ") + argValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

public interface ISCSastScanStartOptions {
String getBuildId();
String getScaRuntimeArgs();
// String getScaRuntimeArgs();
boolean isDotNetRequired();
String getDotNetVersion();
File getPayloadFile();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,14 @@ public class SCSastScanStartMbsOptions implements ISCSastScanStartOptions {
@Getter private String buildId;
@Getter private final boolean dotNetRequired = false;
@Getter private final String dotNetVersion = null;
@Getter private final String scaRuntimeArgs = ""; // TODO Provide options
@Getter private SCSastControllerJobType jobType = SCSastControllerJobType.SCAN_JOB;

@Option(names = {"-m", "--mbs-file"}, required= true)
public void setMbsFile(File mbsFile) {
this.payloadFile = mbsFile;
setMbsProperties(mbsFile);
}

private void setMbsProperties(File mbsFile) {
try ( FileSystem fs = FileSystems.newFileSystem(mbsFile.toPath()) ) {
Path mbsManifest = fs.getPath("MobileBuildSession.manifest");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ public class SCSastScanStartPackageOptions implements ISCSastScanStartOptions {
@Getter private final String buildId = null; // TODO ScanCentral Client doesn't allow for specifying build id; should we provide a CLI option for this?
@Getter private boolean dotNetRequired;
@Getter private String dotNetVersion;
@Getter private final String scaRuntimeArgs = "";
@Getter private SCSastControllerJobType jobType = SCSastControllerJobType.TRANSLATION_AND_SCAN_JOB;

@Option(names = {"-p", "--package-file"}, required = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ 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 0020c73

Please sign in to comment.