Skip to content

Commit

Permalink
feat: fcli fod: Add preview commands for starting and managing DAST…
Browse files Browse the repository at this point in the history
… Automated scans
  • Loading branch information
rsenden committed Jan 31, 2024
1 parent 78505cb commit db898ee
Show file tree
Hide file tree
Showing 39 changed files with 1,601 additions and 142 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,24 @@ public class FoDOutputHelperMixins {
public static class DownloadLatest extends OutputHelperMixins.TableNoQuery {
public static final String CMD_NAME = "download-latest";
}
public static class SetupSast extends OutputHelperMixins.DetailsNoQuery {
public static class SetupSast extends OutputHelperMixins.TableNoQuery {
public static final String CMD_NAME = "setup-sast";
}
public static class SetupDast extends OutputHelperMixins.DetailsNoQuery {
public static class SetupDast extends OutputHelperMixins.TableNoQuery {
public static final String CMD_NAME = "setup-dast";
}
public static class SetupMobile extends OutputHelperMixins.DetailsNoQuery {
public static class SetupMobile extends OutputHelperMixins.TableNoQuery {
public static final String CMD_NAME = "setup-mobile";
}
public static class SetupWebsite extends OutputHelperMixins.TableNoQuery {
public static final String CMD_NAME = "setup-website";
}
public static class SetupWorkflow extends OutputHelperMixins.TableNoQuery {
public static final String CMD_NAME = "setup-workflow";
}
public static class SetupApi extends OutputHelperMixins.TableNoQuery {
public static final String CMD_NAME = "setup-api";
}
public static class GetConfig extends OutputHelperMixins.DetailsNoQuery {
public static final String CMD_NAME = "get-config";
}
Expand Down Expand Up @@ -94,4 +103,8 @@ public static class StartLegacy extends OutputHelperMixins.TableNoQuery {
public static class GetConfigLegacy extends OutputHelperMixins.DetailsNoQuery {
public static final String CMD_NAME = "get-config-legacy";
}

public static class UploadFile extends OutputHelperMixins.TableNoQuery {
public static final String CMD_NAME = "upload-file";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public class FoDUrls {
public static final String ENTITLEMENTS = ApiBase + "/tenant-entitlements";
public static final String OSS_SCANS = ApiBase + "/releases/{relId}/open-source-scans";
public static final String OSS_SCANS_START = OSS_SCANS + "/start-scan";
public static final String DAST_AUTOMATED_SCANS = ApiBase + "/releases/{relId}/dast-automated-scans";
public static final String REPORTS = ApiBase + "/reports";
public static final String REPORT = ApiBase + "/reports/{reportId}";
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/**
* Copyright 2023 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors (Open Text) are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*/
package com.fortify.cli.fod._common.rest.helper;
Expand Down Expand Up @@ -41,30 +41,30 @@ public static final ArrayNode findMatching(HttpRequest<?> request, Predicate<Jso
.filter(predicate)
.collect(JsonHelper.arrayNodeCollector());
}

public static final JsonNode findUnique(HttpRequest<?> request, String... filters) {
return getUnique(findMatching(request, filters));
}

public static final JsonNode findUnique(HttpRequest<?> request, Predicate<JsonNode> predicate) {
return getUnique(findMatching(request, predicate));
}

public static final JsonNode getUnique(ArrayNode nodes) {
switch (nodes.size()) {
case 0: return null;
case 1: return nodes.get(0);
default: throw new IllegalStateException("Multiple matches found");
}
}

private static final Predicate<JsonNode> asPredicate(String filter) {
int idx = filter.indexOf(':');
String key = filter.substring(0, idx);
String value = filter.substring(idx+1);
return node -> matches(node, key, value);
}

private static final boolean matches(JsonNode node, String fieldName, String valueToMatch) {
JsonNode valueNode = node==null ? null : node.get(fieldName);
return valueNode==null ? false : valueToMatch.equals(valueNode.asText());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/*******************************************************************************
* Copyright 2021, 2023 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*******************************************************************************/

Expand All @@ -32,12 +32,32 @@
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;

// TODO Based on some recent messages on FortifySSC, potentially we may need
// TODO Based on some recent messages on FortifySSC, potentially we may need
// chunked uploads for SC DAST as well, so consider refactoring into a
// generic class in fcli-common.
public final class FoDFileTransferHelper {
private static final int chunkSize = FoDConstants.DEFAULT_CHUNK_SIZE;

@SneakyThrows
public static final JsonNode upload(UnirestInstance unirest, HttpRequest<?> baseRequest, File f) {
if (!f.exists() || !f.canRead()) {
throw new IllegalArgumentException("Could not read file: " + f.getPath());
}
String body = null;
try ( FoDProgressMonitor uploadMonitor = new FoDProgressMonitor("Upload") ) {
body = unirest.request(baseRequest.getHttpMethod().name(), baseRequest.getUrl())
.noCharset()
.multiPartContent()
.field("file", f)
.uploadMonitor(uploadMonitor)
.asString()
.getBody();
} catch (Exception e) {
throw new RuntimeException("Error uploading file", e);
}
return new ObjectMapper().readTree(body);
}

@SneakyThrows
public static final JsonNode uploadChunked(UnirestInstance unirest, HttpRequest<?> baseRequest, File f) {
if (!f.exists() || !f.canRead()) {
Expand All @@ -64,7 +84,7 @@ public static final JsonNode uploadChunked(UnirestInstance unirest, HttpRequest<
}

lastBody = unirest.request(
String.valueOf(baseRequest.getHttpMethod()),
String.valueOf(baseRequest.getHttpMethod()),
getUri(baseRequest, fragmentNumber++, offset))
.contentType("application/octet-stream")
.header("Accept", "application/json")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*******************************************************************************
* Copyright 2021, 2023 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*******************************************************************************/
package com.fortify.cli.fod._common.scan.cli.cmd;

import com.fasterxml.jackson.databind.JsonNode;
import com.fortify.cli.common.cli.mixin.CommonOptionMixins;
import com.fortify.cli.common.cli.util.CommandGroup;
import com.fortify.cli.common.output.transform.IActionCommandResultSupplier;
import com.fortify.cli.fod._common.cli.mixin.FoDDelimiterMixin;
import com.fortify.cli.fod._common.output.cli.AbstractFoDJsonNodeOutputCommand;
import com.fortify.cli.fod._common.rest.helper.FoDFileTransferHelper;
import com.fortify.cli.fod.release.cli.mixin.FoDReleaseByQualifiedNameOrIdResolverMixin;
import kong.unirest.HttpRequest;
import kong.unirest.UnirestInstance;
import picocli.CommandLine.Mixin;

@CommandGroup("*-scan-upload-file")
public abstract class AbstractFoDScanFileUploadCommand extends AbstractFoDJsonNodeOutputCommand implements IActionCommandResultSupplier {
@Mixin private FoDDelimiterMixin delimiterMixin; // Is automatically injected in resolver mixins
@Mixin private FoDReleaseByQualifiedNameOrIdResolverMixin.RequiredOption releaseResolver;

@Mixin private CommonOptionMixins.RequiredFile uploadFileMixin;

@Override
public final JsonNode getJsonNode(UnirestInstance unirest) {
var releaseDescriptor = releaseResolver.getReleaseDescriptor(unirest);
var releaseId = releaseDescriptor.getReleaseId();
HttpRequest<?> baseRequest = getBaseRequest(unirest, releaseId);
JsonNode response = FoDFileTransferHelper.upload(unirest, baseRequest, uploadFileMixin.getFile());
return releaseDescriptor.asObjectNode()
.put("fileType", getFileType())
.put("filename", uploadFileMixin.getFile().getName())
.put("fileId", response.get("fileId").intValue());
}

protected abstract String getFileType();

protected abstract HttpRequest<?> getBaseRequest(UnirestInstance unirest, String releaseId);

@Override
public final String getActionCommandResult() {
return "UPLOADED";
}

@Override
public final boolean isSingular() {
return true;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*******************************************************************************
* Copyright 2021, 2023 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*******************************************************************************/

package com.fortify.cli.fod._common.scan.cli.cmd;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fortify.cli.common.cli.mixin.CommonOptionMixins;
import com.fortify.cli.common.output.transform.IActionCommandResultSupplier;
import com.fortify.cli.fod._common.cli.mixin.FoDDelimiterMixin;
import com.fortify.cli.fod._common.output.cli.AbstractFoDJsonNodeOutputCommand;
import com.fortify.cli.fod._common.rest.FoDUrls;
import com.fortify.cli.fod._common.rest.helper.FoDFileTransferHelper;
import com.fortify.cli.fod._common.scan.cli.mixin.FoDEntitlementFrequencyTypeMixins;
import com.fortify.cli.fod._common.scan.helper.FoDScanType;
import com.fortify.cli.fod.release.cli.mixin.FoDReleaseByQualifiedNameOrIdResolverMixin;
import kong.unirest.HttpRequest;
import kong.unirest.HttpResponse;
import kong.unirest.UnirestInstance;
import lombok.Getter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;

public abstract class AbstractFoDScanSetupCommand extends AbstractFoDJsonNodeOutputCommand implements IActionCommandResultSupplier {
@Getter private static final ObjectMapper objectMapper = new ObjectMapper();

private static final Log LOG = LogFactory.getLog(AbstractFoDScanSetupCommand.class);


@Mixin protected FoDDelimiterMixin delimiterMixin; // Is automatically injected in resolver mixins
@Mixin protected FoDReleaseByQualifiedNameOrIdResolverMixin.RequiredOption releaseResolver;

@Option(names = {"--assessment-type"}, required = true)
protected String assessmentType; // Plain text name as custom assessment types can be created
@Mixin
protected FoDEntitlementFrequencyTypeMixins.RequiredOption entitlementFrequencyTypeMixin;
@Option(names = {"--entitlement-id"})
protected Integer entitlementId = 0;

@Mixin
protected CommonOptionMixins.OptionalFile uploadFileMixin;

// the File Id previously uploaded or uploaded using "uploadFileToUse" below
private int fileId = 0;

protected void setFileId(int fileId) {
this.fileId = fileId;
}

protected int uploadFileToUse(UnirestInstance unirest, String releaseId, FoDScanType scanType, String fileType) {
int fileIdToUse = 0;
HttpRequest<?> uploadFileRequest = null;
switch (scanType) {
case Dynamic:
// Only supporting DAST Automated file uploads from fcli for now
uploadFileRequest = getDastAutomatedUploadFileRequest(unirest, releaseId, fileType);
break;
case Static:
case Mobile:
// Neither Static or Mobile require any file uploads yet
break;
default:
}
JsonNode response = FoDFileTransferHelper.upload(unirest, uploadFileRequest, uploadFileMixin.getFile());
fileIdToUse = response.get("fileId").intValue(); fileId = fileIdToUse;
return fileIdToUse;
}

protected HttpRequest<?> getDastAutomatedUploadFileRequest(UnirestInstance unirest, String releaseId, String dastFileType) {
return unirest.patch(FoDUrls.DAST_AUTOMATED_SCANS + "/scan-setup/file-upload")
.routeParam("relId", releaseId)
.queryString("dastFileType", dastFileType);
}

@Override
public final JsonNode getJsonNode(UnirestInstance unirest) {
var releaseDescriptor = releaseResolver.getReleaseDescriptor(unirest);
var releaseId = releaseDescriptor.getReleaseId();
HttpRequest<?> request = getBaseRequest(unirest, releaseId);
HttpResponse<?> response = request.asString(); // successful invocation returns empty response
return releaseDescriptor.asObjectNode()
.put("scanType", getScanType())
.put("setupType", getSetupType())
.put("filename", (uploadFileMixin.getFile() != null ? uploadFileMixin.getFile().getName() : "N/A"))
.put("entitlementId", entitlementId)
.put("fileId", fileId);
}

protected abstract String getScanType();
protected abstract String getSetupType();

protected abstract HttpRequest<?> getBaseRequest(UnirestInstance unirest, String releaseId);

@Override
public final String getActionCommandResult() {
return "SETUP";
}

@Override
public final boolean isSingular() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import kong.unirest.UnirestInstance;
import picocli.CommandLine.Mixin;

@CommandGroup("*-scan")
@CommandGroup("*-scan-start")
public abstract class AbstractFoDScanStartCommand extends AbstractFoDJsonNodeOutputCommand implements IActionCommandResultSupplier {
@Mixin private FoDDelimiterMixin delimiterMixin; // Is automatically injected in resolver mixins
@Mixin private FoDReleaseByQualifiedNameOrIdResolverMixin.RequiredOption releaseResolver;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*******************************************************************************
* Copyright 2021, 2023 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*******************************************************************************/

package com.fortify.cli.fod._common.scan.cli.mixin;

import com.fortify.cli.fod._common.util.FoDEnums;
import lombok.Getter;
import picocli.CommandLine.Option;

public class FoDDastFileTypeMixins {
public static class RequiredOption {
@Option(names = {"--file-type", "--dast-file-type"}, required = true, descriptionKey = "fcli.fod.scan.dast-file-type")
@Getter private FoDEnums.DastAutomatedFileTypes dastFileType;
}
public static class OptionalOption {
@Option(names = {"--file-type", "--dast-file-type"}, required = false, descriptionKey = "fcli.fod.scan.dast-file-type")
@Getter private FoDEnums.DastAutomatedFileTypes dastFileType;
}
}
Loading

0 comments on commit db898ee

Please sign in to comment.