diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/scan/helper/dast/FoDScanDastAutomatedHelper.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/scan/helper/dast/FoDScanDastAutomatedHelper.java index 731017cb6e2..e1b16c32489 100644 --- a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/scan/helper/dast/FoDScanDastAutomatedHelper.java +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/scan/helper/dast/FoDScanDastAutomatedHelper.java @@ -80,7 +80,11 @@ public static final FoDScanDescriptor handleInProgressScan(UnirestInstance unire .queryString("fields", "scanId,scanType,analysisStatusType") .asObject(JsonNode.class).getBody(); JsonNode itemsNode = response.path("items"); - if (!itemsNode.isArray() || itemsNode.isEmpty()) continue; + if (!itemsNode.isArray() || itemsNode.isEmpty()) { + // No scans exist for this release yet; nothing to handle. + progressWriter.writeProgress("Status: No previous scans found"); + return null; + } boolean foundActive = false; for (JsonNode node : itemsNode) { @@ -107,10 +111,13 @@ public static final FoDScanDescriptor handleInProgressScan(UnirestInstance unire throw new FcliSimpleException("Unable to start Dynamic scan after " + maxAttempts + " attempts. Please check the UI and try again."); } - public static final FoDScanDescriptor startScan(UnirestInstance unirest, FoDReleaseDescriptor releaseDescriptor) { - JsonNode response = unirest.post(FoDUrls.DAST_AUTOMATED_SCANS + "/start-scan") - .routeParam("relId", releaseDescriptor.getReleaseId()) - .asObject(JsonNode.class).getBody(); + public static final FoDScanDescriptor startScan(UnirestInstance unirest, String networkName, FoDReleaseDescriptor releaseDescriptor) { + var request = unirest.post(FoDUrls.DAST_AUTOMATED_SCANS + "/start-scan") + .routeParam("relId", releaseDescriptor.getReleaseId()); + if (networkName != null && !networkName.isBlank()) { + request = request.queryString("networkName", networkName); + } + JsonNode response = request.asObject(JsonNode.class).getBody(); FoDStartScanResponse startScanResponse = JsonHelper.treeToValue(response, FoDStartScanResponse.class); if (startScanResponse == null || startScanResponse.getScanId() <= 0) { throw new FcliSimpleException("Unable to retrieve scan id from response when starting Dynamic scan."); @@ -151,7 +158,7 @@ private static FoDScanDescriptor handleActiveScan( Thread.sleep(waitMillis); break; case DoNotStartScan: - progressWriter.writeProgress("Status: A scan is running %s, no new scan will be started", scanId); + progressWriter.writeProgress("Status: A scan with id %s is %s, no new scan will be started", scanId, status); JsonNode scan = objectMapper.createObjectNode() .put("scanId", scanId) .put("scanType", FoDScanType.Dynamic.name()) diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/dast_scan/cli/cmd/FoDDastAutomatedScanStartCommand.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/dast_scan/cli/cmd/FoDDastAutomatedScanStartCommand.java index c68709a4f1e..6701c7d0b4f 100644 --- a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/dast_scan/cli/cmd/FoDDastAutomatedScanStartCommand.java +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/dast_scan/cli/cmd/FoDDastAutomatedScanStartCommand.java @@ -12,8 +12,10 @@ */ package com.fortify.cli.fod.dast_scan.cli.cmd; +import com.fortify.cli.common.exception.FcliSimpleException; import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; import com.fortify.cli.common.progress.cli.mixin.ProgressWriterFactoryMixin; +import com.fortify.cli.common.rest.unirest.UnexpectedHttpResponseException; import com.fortify.cli.fod._common.scan.cli.cmd.AbstractFoDScanStartCommand; import com.fortify.cli.fod._common.scan.cli.mixin.FoDInProgressScanActionTypeMixins; import com.fortify.cli.fod._common.scan.helper.FoDScanDescriptor; @@ -36,6 +38,8 @@ public class FoDDastAutomatedScanStartCommand extends AbstractFoDScanStartComman private Integer waitInterval; @Option(names="--max-attempts", descriptionKey = "fcli.fod.scan.max-attempts", defaultValue = "30", required = false) private Integer maxAttempts; + @Option(names = {"--vpn"}, descriptionKey = "fcli.fod.dast-scan.start.vpn") + private String fodConnectNetwork; @Mixin private ProgressWriterFactoryMixin progressWriterFactory; @@ -48,13 +52,19 @@ protected FoDScanDescriptor startScan(UnirestInstance unirest, FoDReleaseDescrip try (var progressWriter = progressWriterFactory.create()) { // get current setup to ensure the scan has been configured - FoDScanDastAutomatedHelper.getSetupDescriptor(unirest, relId); + var setup = FoDScanDastAutomatedHelper.getSetupDescriptor(unirest, relId); + if (setup == null) { + throw new FcliSimpleException("DAST Automated scan is not configured for release '" + + releaseDescriptor.getReleaseName() + + "'. Please run one of the 'fod dast-scan setup-xxx' commands to configure the scan before starting."); + } - // check if scan is already in progress + // Handle any in-progress or active scans according to configured action. FoDScanDescriptor scan = FoDScanDastAutomatedHelper.handleInProgressScan(unirest, releaseDescriptor, inProgressScanActionType.getInProgressScanActionType(), progressWriter, maxAttempts, waitInterval); + // If the action was to not start a new scan, return current in-progress descriptor. if (scan != null && scan.getAnalysisStatusType().equals("In_Progress")) { if (inProgressScanActionType.getInProgressScanActionType() == FoDEnums.InProgressScanActionType.DoNotStartScan) { scanAction = "NOT_STARTED_SCAN_IN_PROGRESS"; @@ -62,7 +72,22 @@ protected FoDScanDescriptor startScan(UnirestInstance unirest, FoDReleaseDescrip } } - return FoDScanDastAutomatedHelper.startScan(unirest, releaseDescriptor); + try { + return FoDScanDastAutomatedHelper.startScan(unirest, fodConnectNetwork, releaseDescriptor); + } catch (UnexpectedHttpResponseException e) { + // If FoD reports active scan race, resolve and return that scan instead of failing. + if (e.getStatus() == 422 && (e.getMessage().contains("dynamic scan is currently in progress") + || e.getMessage().contains("errorCode: -10"))) { + FoDScanDescriptor running = FoDScanDastAutomatedHelper.handleInProgressScan(unirest, releaseDescriptor, + FoDEnums.InProgressScanActionType.DoNotStartScan, progressWriter, maxAttempts, + waitInterval); + if (running != null) { + scanAction = "NOT_STARTED_SCAN_IN_PROGRESS"; + return running; + } + } + throw e; + } } } diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/microservice/cli/cmd/FoDMicroserviceCreateCommand.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/microservice/cli/cmd/FoDMicroserviceCreateCommand.java index 1a6f78c5023..14d526380d8 100644 --- a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/microservice/cli/cmd/FoDMicroserviceCreateCommand.java +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/microservice/cli/cmd/FoDMicroserviceCreateCommand.java @@ -14,6 +14,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fortify.cli.common.cli.mixin.CommonOptionMixins; +import com.fortify.cli.common.exception.FcliSimpleException; import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; import com.fortify.cli.common.output.transform.IActionCommandResultSupplier; import com.fortify.cli.fod._common.cli.mixin.FoDDelimiterMixin; @@ -52,6 +53,10 @@ public JsonNode getJsonNode(UnirestInstance unirest) { } FoDAppDescriptor appDescriptor = qualifiedMicroserviceNameResolver.getAppDescriptor(unirest, true); FoDQualifiedMicroserviceNameDescriptor qualifiedMicroserviceNameDescriptor = qualifiedMicroserviceNameResolver.getQualifiedMicroserviceNameDescriptor(); + if (!appDescriptor.isHasMicroservices()) { + throw new FcliSimpleException("Cannot create microservice for non-microservice application " + + appDescriptor.getApplicationName()); + } FoDMicroserviceUpdateRequest msCreateRequest = FoDMicroserviceUpdateRequest.builder() .microserviceName(qualifiedMicroserviceNameDescriptor.getMicroserviceName()) .attributes(new FoDAttributeDefinitionHelper(unirest).buildAttributesNode(FoDEnums.AttributeTypes.Microservice, diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties index 62dcf5b6c6d..249c0230a9a 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties @@ -637,6 +637,7 @@ fcli.fod.dast-scan.start.usage.description.0 = This command is intended for DAST fcli.fod.dast-scan.start.usage.description.1 = The scan will need to have been previously setup using the FoD UI or one of the \ 'fod dast-scan setup-xxx' commands. fcli.fod.dast-scan.start.validate-entitlement = Validate if an entitlement has been set and is still valid. +fcli.fod.dast-scan.start.vpn = Fortify Connect network name to use for site-to-site VPN. fcli.fod.dast-scan.start-legacy.usage.header = (LEGACY) Start a new DAST scan. fcli.fod.dast-scan.start-legacy.usage.description.0 = This command is not fully implemented and is intended for legacy DAST scanning (not DAST Automated). \ It can only be used for starting a configured Dynamic scan and does not support file uploads (i.e. API definitions \