From 3926bd7845e839380bb9daf11af4157ba7927d3f Mon Sep 17 00:00:00 2001 From: kadraman Date: Fri, 20 Sep 2024 08:37:35 +0100 Subject: [PATCH] feat: `fcli fod release create`: Improve handling of application attributes in --attrs option (fixes fortify#604) chore: working `fcli fod action run setup-release` --- .../cli/fod/_common/util/FoDEnums.java | 46 +++++++++++++++++++ .../app/attr/helper/FoDAttributeHelper.java | 33 +++++++++---- .../fod/app/cli/cmd/FoDAppCreateCommand.java | 4 +- .../fod/app/cli/cmd/FoDAppUpdateCommand.java | 6 ++- .../cli/cmd/FoDReleaseCreateCommand.java | 4 +- .../cli/cmd/FoDReleaseUpdateCommand.java | 6 ++- .../cli/fod/actions/zip/setup-release.yaml | 45 +++++++++++++++--- 7 files changed, 122 insertions(+), 22 deletions(-) diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/util/FoDEnums.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/util/FoDEnums.java index 8d044f9483..a3e0b084da 100644 --- a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/util/FoDEnums.java +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/_common/util/FoDEnums.java @@ -455,4 +455,50 @@ public enum ApiSchemeType { @JsonProperty("http,https") HTTPandHTTPs; } + + public enum AttributeTypes { + Application(1), + Vulnerability(2), + Microservice(3), + Release(4); + + private final int _val; + + AttributeTypes(int val) { + this._val = val; + } + + public int getValue() { + return this._val; + } + + public String toString() { + switch (this._val) { + case 1: + return "Application"; + case 2: + return "Vulnerability"; + case 3: + return "Microservice"; + case 4: + default: + return "Release"; + } + } + + public static AttributeTypes fromInt(int val) { + switch (val) { + case 1: + return Application; + case 2: + return Vulnerability; + case 3: + return Microservice; + case 4: + default: + return Release; + } + } + } + } diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/app/attr/helper/FoDAttributeHelper.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/app/attr/helper/FoDAttributeHelper.java index b7aeffd51d..fa75daeaf9 100644 --- a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/app/attr/helper/FoDAttributeHelper.java +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/app/attr/helper/FoDAttributeHelper.java @@ -19,6 +19,9 @@ import java.util.List; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -27,6 +30,7 @@ import com.fortify.cli.common.json.JsonHelper; import com.fortify.cli.fod._common.rest.FoDUrls; import com.fortify.cli.fod._common.rest.helper.FoDDataHelper; +import com.fortify.cli.fod._common.util.FoDEnums; import kong.unirest.GetRequest; import kong.unirest.UnirestInstance; @@ -34,6 +38,7 @@ import lombok.SneakyThrows; public class FoDAttributeHelper { + private static final Logger LOG = LoggerFactory.getLogger(FoDAttributeHelper.class); @Getter private static ObjectMapper objectMapper = new ObjectMapper(); public static final FoDAttributeDescriptor getAttributeDescriptor(UnirestInstance unirestInstance, String attrNameOrId, boolean failIfNotFound) { @@ -52,7 +57,8 @@ public static final FoDAttributeDescriptor getAttributeDescriptor(UnirestInstanc } @SneakyThrows - public static final Map getRequiredAttributesDefaultValues(UnirestInstance unirestInstance) { + public static final Map getRequiredAttributesDefaultValues(UnirestInstance unirestInstance, + FoDEnums.AttributeTypes attrType) { Map reqAttrs = new HashMap<>(); GetRequest request = unirestInstance.get(FoDUrls.ATTRIBUTES) .queryString("filters", "isRequired:true"); @@ -63,8 +69,8 @@ public static final Map getRequiredAttributesDefaultValues(Unire Iterator lookupIterator = lookupList.iterator(); while (lookupIterator.hasNext()) { FoDAttributeDescriptor currentLookup = lookupIterator.next(); - // currentLookup.getAttributeTypeId() == 1 is "Application", 4 is "Release" - filter above does not support querying on this yet! - if (currentLookup.getIsRequired() && (currentLookup.getAttributeTypeId() == 1 || currentLookup.getAttributeTypeId() == 4)) { + // currentLookup.getAttributeTypeId() == 1 if "Application", 4 if "Release" - filter above does not support querying on this yet! + if (currentLookup.getIsRequired() && (currentLookup.getAttributeTypeId() == attrType.getValue())) { switch (currentLookup.getAttributeDataType()) { case "Text": reqAttrs.put(currentLookup.getName(), "autofilled by fcli"); @@ -88,7 +94,7 @@ public static final Map getRequiredAttributesDefaultValues(Unire return reqAttrs; } - public static JsonNode mergeAttributesNode(UnirestInstance unirest, + public static JsonNode mergeAttributesNode(UnirestInstance unirest, FoDEnums.AttributeTypes attrType, ArrayList current, Map updates) { ArrayNode attrArray = objectMapper.createArrayNode(); @@ -111,7 +117,7 @@ public static JsonNode mergeAttributesNode(UnirestInstance unirest, return attrArray; } - public static JsonNode getAttributesNode(ArrayList attributes) { + public static JsonNode getAttributesNode(FoDEnums.AttributeTypes attrType, ArrayList attributes) { ArrayNode attrArray = objectMapper.createArrayNode(); if (attributes == null || attributes.isEmpty()) return attrArray; for (FoDAttributeDescriptor attr : attributes) { @@ -123,11 +129,12 @@ public static JsonNode getAttributesNode(ArrayList attri return attrArray; } - public static JsonNode getAttributesNode(UnirestInstance unirest, Map attributesMap, boolean autoReqdAttributes) { + public static JsonNode getAttributesNode(UnirestInstance unirest, FoDEnums.AttributeTypes attrType, + Map attributesMap, boolean autoReqdAttributes) { Map combinedAttributesMap = new LinkedHashMap<>(); if (autoReqdAttributes) { // find any required attributes - combinedAttributesMap.putAll(getRequiredAttributesDefaultValues(unirest)); + combinedAttributesMap.putAll(getRequiredAttributesDefaultValues(unirest, attrType)); } if ( attributesMap!=null && !attributesMap.isEmpty() ) { combinedAttributesMap.putAll(attributesMap); @@ -136,9 +143,15 @@ public static JsonNode getAttributesNode(UnirestInstance unirest, Map attr : combinedAttributesMap.entrySet()) { ObjectNode attrObj = getObjectMapper().createObjectNode(); FoDAttributeDescriptor attributeDescriptor = FoDAttributeHelper.getAttributeDescriptor(unirest, attr.getKey(), true); - attrObj.put("id", attributeDescriptor.getId()); - attrObj.put("value", attr.getValue()); - attrArray.add(attrObj); + // filter out any attributes that aren't valid for the entity we are working on, e.g. Application or Release + if (attributeDescriptor.getAttributeTypeId() == attrType.getValue()) { + attrObj.put("id", attributeDescriptor.getId()); + attrObj.put("value", attr.getValue()); + attrArray.add(attrObj); + } else { + LOG.debug("Skipping attribute '"+attributeDescriptor.getName()+"' as it is not a "+attrType.toString()+" attribute"); + + } } return attrArray; } diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/app/cli/cmd/FoDAppCreateCommand.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/app/cli/cmd/FoDAppCreateCommand.java index 2d3d5ec038..383d330eb0 100644 --- a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/app/cli/cmd/FoDAppCreateCommand.java +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/app/cli/cmd/FoDAppCreateCommand.java @@ -27,6 +27,7 @@ import com.fortify.cli.common.util.DisableTest; import com.fortify.cli.common.util.StringUtils; import com.fortify.cli.fod._common.output.cli.cmd.AbstractFoDJsonNodeOutputCommand; +import com.fortify.cli.fod._common.util.FoDEnums; import com.fortify.cli.fod.access_control.helper.FoDUserGroupHelper; import com.fortify.cli.fod.access_control.helper.FoDUserHelper; import com.fortify.cli.fod.app.attr.cli.mixin.FoDAttributeUpdateOptions; @@ -100,7 +101,8 @@ public JsonNode getJsonNode(UnirestInstance unirest) { .applicationType(appType.getAppType().getFoDValue()) .hasMicroservices(appType.getAppType().isMicroservice()) .microservices(FoDAppHelper.getMicroservicesNode(microservices)) - .attributes(FoDAttributeHelper.getAttributesNode(unirest, appAttrs.getAttributes(), autoRequiredAttrs)) + .attributes(FoDAttributeHelper.getAttributesNode(unirest, FoDEnums.AttributeTypes.Application, + appAttrs.getAttributes(), autoRequiredAttrs)) .userGroupIds(FoDUserGroupHelper.getUserGroupsNode(unirest, userGroups)).build(); return FoDAppHelper.createApp(unirest, appCreateRequest).asJsonNode(); diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/app/cli/cmd/FoDAppUpdateCommand.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/app/cli/cmd/FoDAppUpdateCommand.java index 8bf3064f97..816f75f49b 100644 --- a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/app/cli/cmd/FoDAppUpdateCommand.java +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/app/cli/cmd/FoDAppUpdateCommand.java @@ -25,6 +25,7 @@ import com.fortify.cli.common.util.DisableTest; import com.fortify.cli.common.util.StringUtils; import com.fortify.cli.fod._common.output.cli.cmd.AbstractFoDJsonNodeOutputCommand; +import com.fortify.cli.fod._common.util.FoDEnums; import com.fortify.cli.fod.app.attr.cli.mixin.FoDAttributeUpdateOptions; import com.fortify.cli.fod.app.attr.helper.FoDAttributeDescriptor; import com.fortify.cli.fod.app.attr.helper.FoDAttributeHelper; @@ -68,9 +69,10 @@ public JsonNode getJsonNode(UnirestInstance unirest) { Map attributeUpdates = appAttrsUpdate.getAttributes(); JsonNode jsonAttrs = objectMapper.createArrayNode(); if (attributeUpdates != null && !attributeUpdates.isEmpty()) { - jsonAttrs = FoDAttributeHelper.mergeAttributesNode(unirest, appAttrsCurrent, attributeUpdates); + jsonAttrs = FoDAttributeHelper.mergeAttributesNode(unirest, FoDEnums.AttributeTypes.Application, appAttrsCurrent, + attributeUpdates); } else { - jsonAttrs = FoDAttributeHelper.getAttributesNode(appAttrsCurrent); + jsonAttrs = FoDAttributeHelper.getAttributesNode(FoDEnums.AttributeTypes.Application, appAttrsCurrent); } String appEmailListNew = FoDAppHelper.getEmailList(notificationsUpdate); diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/cmd/FoDReleaseCreateCommand.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/cmd/FoDReleaseCreateCommand.java index c70650680c..74c1c6bc98 100644 --- a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/cmd/FoDReleaseCreateCommand.java +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/cmd/FoDReleaseCreateCommand.java @@ -20,6 +20,7 @@ import com.fortify.cli.common.util.StringUtils; import com.fortify.cli.fod._common.cli.mixin.FoDDelimiterMixin; import com.fortify.cli.fod._common.output.cli.cmd.AbstractFoDJsonNodeOutputCommand; +import com.fortify.cli.fod._common.util.FoDEnums; import com.fortify.cli.fod.app.attr.cli.mixin.FoDAttributeUpdateOptions; import com.fortify.cli.fod.app.attr.helper.FoDAttributeHelper; import com.fortify.cli.fod.app.cli.mixin.FoDSdlcStatusTypeOptions; @@ -79,7 +80,8 @@ public JsonNode getJsonNode(UnirestInstance unirest) { .releaseName(simpleReleaseName) .releaseDescription(description) .sdlcStatusType(sdlcStatus.getSdlcStatusType().name()) - .attributes(FoDAttributeHelper.getAttributesNode(unirest, relAttrs.getAttributes(), autoRequiredAttrs)); + .attributes(FoDAttributeHelper.getAttributesNode(unirest, FoDEnums.AttributeTypes.Release, + relAttrs.getAttributes(), autoRequiredAttrs)); if ( microserviceDescriptor!=null ) { requestBuilder = requestBuilder.microserviceId(Integer.valueOf(microserviceDescriptor.getMicroserviceId())); } diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/cmd/FoDReleaseUpdateCommand.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/cmd/FoDReleaseUpdateCommand.java index 8f427de022..f973e956ef 100644 --- a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/cmd/FoDReleaseUpdateCommand.java +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/cmd/FoDReleaseUpdateCommand.java @@ -24,6 +24,7 @@ import com.fortify.cli.common.util.StringUtils; import com.fortify.cli.fod._common.cli.mixin.FoDDelimiterMixin; import com.fortify.cli.fod._common.output.cli.cmd.AbstractFoDJsonNodeOutputCommand; +import com.fortify.cli.fod._common.util.FoDEnums; import com.fortify.cli.fod.app.attr.cli.mixin.FoDAttributeUpdateOptions; import com.fortify.cli.fod.app.attr.helper.FoDAttributeDescriptor; import com.fortify.cli.fod.app.attr.helper.FoDAttributeHelper; @@ -68,9 +69,10 @@ public JsonNode getJsonNode(UnirestInstance unirest) { Map attributeUpdates = appAttrsUpdate.getAttributes(); JsonNode jsonAttrs = objectMapper.createArrayNode(); if (attributeUpdates != null && !attributeUpdates.isEmpty()) { - jsonAttrs = FoDAttributeHelper.mergeAttributesNode(unirest, releaseAttrsCurrent, attributeUpdates); + jsonAttrs = FoDAttributeHelper.mergeAttributesNode(unirest, FoDEnums.AttributeTypes.Release, + releaseAttrsCurrent, attributeUpdates); } else { - jsonAttrs = FoDAttributeHelper.getAttributesNode(releaseAttrsCurrent); + jsonAttrs = FoDAttributeHelper.getAttributesNode(FoDEnums.AttributeTypes.Release, releaseAttrsCurrent); } FoDReleaseUpdateRequest appRelUpdateRequest = FoDReleaseUpdateRequest.builder() .releaseName(StringUtils.isNotBlank(releaseName) ? getUnqualifiedReleaseName(releaseName, releaseDescriptor) : releaseDescriptor.getReleaseName()) diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/setup-release.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/setup-release.yaml index 697a3ef5d2..8d1d812a2e 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/setup-release.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/setup-release.yaml @@ -34,7 +34,7 @@ parameters: cliAliases: t required: false type: array - description: "Optional comma-separated list of scan type(s) to set up; for now, only 'sast' is supported" + description: "Optional comma-separated list of scan type(s) to set up; for now, only 'sast' is supported" - group: rel_create_opts required: false name: copy-from @@ -49,32 +49,65 @@ parameters: name: sdlc-status cliAliases: status description: "See `fcli fod release create`" - + - group: sast_setup_opts + name: assessment-type + required: false + defaultValue: "Static Assessment" + description: "See `fcli fod sast-scan setup`" + - group: sast_setup_opts + required: false + name: use-aviator + description: "See `fcli fod sast-scan setup`" + - group: sast_setup_opts + required: false + name: oss + description: "See `fcli fod sast-scan setup`" + - name: release-attributes + required: false + type: array + cliAliases: rel-attrs + description: "Optional comma-separated list of attributes to set on the release" steps: - progress: "Creating FoD application release if non-existing (profile: ${parameters.profile})" + - if: ${parameters['release-attributes']!=null} + set: + - name: relAttrArgs + value: --attrs "${parameters['release-attributes']}" + - if: ${parameters['release-attributes']==null} + append: + - name: relAttrArgs + value: --auto-required-attrs - if: ${parameters.profile=="default"} set: - name: relCreateArgs - value: --skip-if-exists ${#action.copyParametersFromGroup("rel_create_opts")} + value: --skip-if-exists ${#action.copyParametersFromGroup("rel_create_opts")} ${relAttrArgs} # Custom actions can replace/repeat the above to define custom profiles. - if: ${relCreateArgs==null} throw: "Invalid profile: ${parameters.profile}" - fcli: - name: createRelease - args: fod release create ${parameters.release} ${relCreateArgs} + args: fod release create "${parameters.release}" ${relCreateArgs} ${relAttrArgs} - write: - to: stdout value: | Create application release ${parameters.release} (id ${createRelease[0].releaseId}): ${createRelease[0].__action__} - if: ${parameters["scan-types"].contains("sast")} steps: + - if: ${parameters.profile=="default"} + set: + - name: sastSetupArgs + value: --skip-if-exists --frequency "Subscription" --audit-preference Automated ${#action.copyParametersFromGroup("sast_setup_opts")} + - progress: "Configuring FoD application release ${parameters.release} for SAST scanning" - fcli: - name: setupSast - args: fod sast-scan setup --rel ${parameters.release} --skip-if-exists --assessment-type "Static Assessment" --frequency "Subscription" --audit-preference Automated + args: fod sast-scan setup --rel "${parameters.release}" ${sastSetupArgs} - write: - to: stdout value: | SAST scan setup status: ${setupSast[0].__action__} - + - if: ${parameters["scan-types"].contains("dast")} + steps: + - if: ${parameters.profile=="default"} + # Custom actions can replace the above to define DAST setup.