diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/cmd/AbstractActionHelpCommand.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/cmd/AbstractActionHelpCommand.java index f3950fac51..10ec6ed2e8 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/cmd/AbstractActionHelpCommand.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/cmd/AbstractActionHelpCommand.java @@ -35,6 +35,7 @@ public final Integer call() { } private final String getActionHelp(Action action) { + var metadata = action.getMetadata(); var usage = action.getUsage(); return String.format( "\nAction: %s\n"+ @@ -42,7 +43,7 @@ private final String getActionHelp(Action action) { "\n%s\n"+ "\nAction options:\n"+ "%s", - action.getName(), usage.getHeader(), usage.getDescription(), ActionParameterHelper.getSupportedOptionsTable(action)); + metadata.getName(), usage.getHeader(), usage.getDescription(), ActionParameterHelper.getSupportedOptionsTable(action)); } protected abstract String getType(); diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/cmd/AbstractActionRunCommand.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/cmd/AbstractActionRunCommand.java index d00cea5923..29f11a2cdc 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/cmd/AbstractActionRunCommand.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/cmd/AbstractActionRunCommand.java @@ -66,7 +66,7 @@ public final Integer call() { private Callable run(ActionRunner actionRunner, IProgressWriterI18n progressWriter) { actionRunner.getSpelEvaluator().configure(context->configure(actionRunner, context)); - progressWriter.writeProgress("Executing action %s", actionRunner.getAction().getName()); + progressWriter.writeProgress("Executing action %s", actionRunner.getAction().getMetadata().getName()); return actionRunner.run(actionArgs); } diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/cmd/AbstractActionSignCommand.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/cmd/AbstractActionSignCommand.java index 85ffa3ef5e..6278c68564 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/cmd/AbstractActionSignCommand.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/cmd/AbstractActionSignCommand.java @@ -16,6 +16,7 @@ import java.nio.file.Files; import java.nio.file.Path; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,6 +25,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fortify.cli.common.cli.mixin.CommonOptionMixins; import com.fortify.cli.common.crypto.helper.SignatureHelper; +import com.fortify.cli.common.crypto.helper.SignatureHelper.SignatureMetadata; import com.fortify.cli.common.crypto.helper.impl.TextSigner; import com.fortify.cli.common.json.JsonHelper; import com.fortify.cli.common.output.cli.cmd.AbstractOutputCommand; @@ -47,6 +49,8 @@ public class AbstractActionSignCommand extends AbstractOutputCommand implements private Path signedActionFile; @Option(names = "--info", required=false, descriptionKey="fcli.action.sign.info") private Path extraInfoPath; + @Option(names = "--signer", required=false, descriptionKey="fcli.action.sign.signer") + private String signer; @Option(names="--with", required=true, descriptionKey="fcli.action.sign.with") private Path privateKeyPath; @Option(names="--pubout", required=false, descriptionKey="fcli.action.sign.pubout") @@ -59,16 +63,16 @@ public class AbstractActionSignCommand extends AbstractOutputCommand implements public JsonNode getJsonNode() { var keyPairCreated = createKeyPair(); deleteExistingOutputFile(); - ObjectNode extraInfo = createExtraInfo(); + var metadata = createMetadata(); var signer = SignatureHelper.textSigner(privateKeyPath, privateKeyPassword); - signer.signAndWrite(actionFileToSign, signedActionFile, extraInfo); + signer.signAndWrite(actionFileToSign, signedActionFile, metadata); writePublicKey(signer, !keyPairCreated); return JsonHelper.getObjectMapper().createObjectNode() .put("in", actionFileToSign.toString()) .put("out", signedActionFile.toString()) .put("publicKeyFingerprint", signer.publicKeyFingerprint()) - .set("extraInfo", extraInfo); + .set("metadata", JsonHelper.getObjectMapper().valueToTree(metadata)); } private void writePublicKey(TextSigner signer, boolean doWritePublicKey) { @@ -79,10 +83,26 @@ private void writePublicKey(TextSigner signer, boolean doWritePublicKey) { signer.writePublicKey(publicKeyPath); } } - } - - + + private final SignatureMetadata createMetadata() { + var extraInfo = createExtraInfo(); + var signer = getSigner(extraInfo); + return SignatureMetadata.builder() + .extraInfo(extraInfo) + .fcliVersion(FcliBuildPropertiesHelper.getFcliVersion()) + .signer(signer) + .build(); + } + + private final String getSigner(ObjectNode extraInfo) { + var signerNode = extraInfo.remove("signer"); + return StringUtils.isNotBlank(this.signer) + ? this.signer + : signerNode!=null + ? signerNode.asText() + : System.getProperty("user.name"); + } private final ObjectNode createExtraInfo() { ObjectNode extraInfo = objectMapper.createObjectNode(); @@ -93,7 +113,6 @@ private final ObjectNode createExtraInfo() { LOG.warn("WARN: Error parsing extra info file contents"); } } - extraInfo.put("fcli", FcliBuildPropertiesHelper.getFcliBuildInfo()); return extraInfo; } diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/mixin/ActionResolverMixin.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/mixin/ActionResolverMixin.java index 236d411074..cd7ca9ea57 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/mixin/ActionResolverMixin.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/mixin/ActionResolverMixin.java @@ -38,11 +38,11 @@ public ActionLoadResult load(String type, ActionValidationHandler actionValidati } public Action loadAction(String type, ActionValidationHandler actionValidationHandler) { - return load(type, actionValidationHandler).asAction(); + return load(type, actionValidationHandler).getAction(); } public String loadActionContents(String type, ActionValidationHandler actionValidationHandler) { - return load(type, actionValidationHandler).asText(); + return load(type, actionValidationHandler).getActionText(); } } diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionImportHelper.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionImportHelper.java index 0f6491b2ca..1d56440790 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionImportHelper.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionImportHelper.java @@ -33,8 +33,7 @@ import com.fortify.cli.common.action.helper.ActionLoaderHelper.ActionLoader; import com.fortify.cli.common.action.helper.ActionLoaderHelper.ActionSource; import com.fortify.cli.common.action.helper.ActionLoaderHelper.ActionValidationHandler; -import com.fortify.cli.common.action.model.Action; -import com.fortify.cli.common.action.model.Action.ActionProperties; +import com.fortify.cli.common.action.model.Action.ActionMetadata; import com.fortify.cli.common.json.JsonHelper; import com.fortify.cli.common.output.transform.IActionCommandResultSupplier; import com.fortify.cli.common.util.Break; @@ -82,29 +81,30 @@ private static final FileSystem createOutputZipFileSystem(String type) { } private static final Break importAction(FileSystem fs, ArrayNode result, ActionLoader loader, ZipInputStream zis, ZipEntry entry) { - var properties = ActionProperties.builder() + var metadata = ActionMetadata.builder() .custom(true).name(entry.getName()).build(); try { - result.add(importAction(fs, loader.load(zis, properties))); + result.add(importAction(fs, loader.load(zis, metadata))); } catch ( Exception e ) { - result.add(createErrorEntry(properties)); + result.add(createErrorEntry(metadata)); } return Break.FALSE; } - private static JsonNode createErrorEntry(ActionProperties properties) { + private static JsonNode createErrorEntry(ActionMetadata metadata) { return JsonHelper.getObjectMapper().createObjectNode() - .put("name", properties.getName()) + .put("name", metadata.getName()) .put(IActionCommandResultSupplier.actionFieldName, "ERROR"); } @SneakyThrows private static final ObjectNode importAction(FileSystem fs, ActionLoadResult actionLoadResult) { - var action = actionLoadResult.asAction(); // Validate action and allow for retrieving name - var contents = actionLoadResult.asRawText(); - var path = fs.getPath(getTargetFileName(action)); + var metadata = actionLoadResult.getMetadata(); + actionLoadResult.getAction(); // Validate action + var contents = actionLoadResult.getOriginalText(); + var path = fs.getPath(getTargetFileName(metadata)); Files.write(path, contents.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - var result = actionLoadResult.asJson(); + var result = actionLoadResult.getMetadataAsObjectNode(); return result.put("name", cleanActionName(path.getFileName().toString())); } @@ -112,8 +112,8 @@ private static final String cleanActionName(String name) { return name.replaceAll("\\.([^.]*)$", "[.$1]"); } - private static final String getTargetFileName(Action action) { - var path = action.getName(); // May be simple name, path or URL + private static final String getTargetFileName(ActionMetadata metadata) { + var path = metadata.getName(); // May be simple name, path or URL // TODO May be can use URI instead, to handle both URLs and local files? try { path = new URL(path).getPath(); diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionLoaderHelper.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionLoaderHelper.java index 23bd5550a8..3b88af789a 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionLoaderHelper.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionLoaderHelper.java @@ -40,14 +40,19 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fortify.cli.common.action.model.Action; -import com.fortify.cli.common.action.model.Action.ActionProperties; +import com.fortify.cli.common.action.model.Action.ActionMetadata; import com.fortify.cli.common.cli.mixin.CommonOptionMixins.RequireConfirmation.AbortedByUserException; import com.fortify.cli.common.crypto.helper.SignatureHelper; +import com.fortify.cli.common.crypto.helper.SignatureHelper.PublicKeyDescriptor; +import com.fortify.cli.common.crypto.helper.SignatureHelper.PublicKeySource; +import com.fortify.cli.common.crypto.helper.SignatureHelper.SignatureDescriptor; +import com.fortify.cli.common.crypto.helper.SignatureHelper.SignatureMetadata; import com.fortify.cli.common.crypto.helper.SignatureHelper.SignatureStatus; import com.fortify.cli.common.crypto.helper.SignatureHelper.SignatureValidator; import com.fortify.cli.common.crypto.helper.SignatureHelper.SignedTextDescriptor; import com.fortify.cli.common.crypto.helper.impl.SignedTextReader; import com.fortify.cli.common.util.Break; +import com.fortify.cli.common.util.FcliBuildPropertiesHelper; import com.fortify.cli.common.util.FcliDataHelper; import com.fortify.cli.common.util.FileUtils; import com.fortify.cli.common.util.ZipHelper; @@ -76,15 +81,15 @@ private static final Stream _streamAsJson(List sources Map result = new HashMap<>(); new ActionLoader(sources, actionValidationHandler) .processActions(loadResult->{ - result.putIfAbsent(loadResult.getProperties().getName(), loadResult.asJson()); + result.putIfAbsent(loadResult.getMetadata().getName(), loadResult.getMetadataAsObjectNode()); return Break.FALSE; }); return result.values().stream() .sorted((a,b)->a.get("name").asText().compareTo(b.get("name").asText())); } - public static final String getSignatureStatusMessage(ActionProperties properties, SignatureStatus signatureStatus) { - var name = properties.getName(); + public static final String getSignatureStatusMessage(ActionMetadata metadata, SignatureStatus signatureStatus) { + var name = metadata.getName(); switch (signatureStatus) { case INVALID_SIGNATURE: return "Signature for action "+name+" is invalid."; case NO_PUBLIC_KEY: return "No trusted public key found to verify "+name+" action signature."; @@ -123,9 +128,9 @@ public final void processActions(ActionLoadResultProcessor actionLoadResultProce private final ActionLoadResult loadFromFileOrUrl(String source) { try ( var is = createSourceInputStream(source, false) ) { if ( is!=null ) { - var properties = ActionProperties.builder() + var metadata = ActionMetadata.builder() .custom(true).name(source).build(); - return load(is, properties); + return load(is, metadata); } } catch (Exception e) { if ( e instanceof AbortedByUserException ) { throw (AbortedByUserException)e; } @@ -144,47 +149,47 @@ private final ActionLoadResult loadFromZips(String name) { } } - private final void processZipEntries(IZipEntryWithContextProcessor processor) { + private final void processZipEntries(IZipEntryWithContextProcessor processor) { for ( var source: sources ) { var _break = ZipHelper.processZipEntries(source.getInputStreamSupplier(), - processor, source.getActionProperties()); + processor, source.getMetadata()); if ( _break.doBreak() ) { break; } } } - private final IZipEntryWithContextProcessor zipEntryProcessor(ActionLoadResultProcessor loadResultProcessor) { - return (zis, ze, properties) -> loadResultProcessor.process(load(zis, ze, properties)); + private final IZipEntryWithContextProcessor zipEntryProcessor(ActionLoadResultProcessor loadResultProcessor) { + return (zis, ze, metadata) -> loadResultProcessor.process(load(zis, ze, metadata)); } - private final IZipEntryWithContextProcessor singleZipEntryProcessor(String name, Consumer loadResultConsumer) { - return (zis, ze, properties) -> processSingleZipEntry(zis, ze, name, loadResultConsumer, properties); + private final IZipEntryWithContextProcessor singleZipEntryProcessor(String name, Consumer loadResultConsumer) { + return (zis, ze, metadata) -> processSingleZipEntry(zis, ze, name, loadResultConsumer, metadata); } - private Break processSingleZipEntry(ZipInputStream zis, ZipEntry ze, String name, Consumer loadResultConsumer, ActionProperties properties) { + private Break processSingleZipEntry(ZipInputStream zis, ZipEntry ze, String name, Consumer loadResultConsumer, ActionMetadata metadata) { var fileName = name+".yaml"; if (ze.getName().equals(fileName)) { - loadResultConsumer.accept(load(zis, ze, properties)); + loadResultConsumer.accept(load(zis, ze, metadata)); return Break.TRUE; } return Break.FALSE; } - private final ActionLoadResult load(ZipInputStream zis, ZipEntry ze, ActionProperties properties) { - properties = properties.toBuilder().name(getActionName(ze.getName())).build(); - return load(zis, properties); + private final ActionLoadResult load(ZipInputStream zis, ZipEntry ze, ActionMetadata metadata) { + metadata = metadata.toBuilder().name(getActionName(ze.getName())).build(); + return load(zis, metadata); } - final ActionLoadResult load(InputStream is, ActionProperties properties) { - return new ActionLoadResult(actionValidationHandler, loadSignedTextDescriptor(properties, is), properties); + final ActionLoadResult load(InputStream is, ActionMetadata metadata) { + return new ActionLoadResult(actionValidationHandler, loadSignedTextDescriptor(metadata, is), metadata); } - private final SignedTextDescriptor loadSignedTextDescriptor(ActionProperties properties, InputStream is) { + private final SignedTextDescriptor loadSignedTextDescriptor(ActionMetadata metadata, InputStream is) { return signedTextReader.load(is, StandardCharsets.UTF_8, // TODO For now, we only evaluate/check signatures for custom actions, // until we've figured out how to sign internal actions during (or // potentially after) Gradle build. - properties.isCustom() - ? actionValidationHandler.getSignatureValidator(properties) + metadata.isCustom() + ? actionValidationHandler.getSignatureValidator(metadata) : null); } @@ -199,52 +204,120 @@ public static final class ActionLoadResult { private static final Pattern schemaPattern = Pattern.compile("(?m)(^\\$schema:\\s+(?\\S+)\\s*$)|(^#\\s+yaml-language-server:\\s+\\$schema=(?\\S+)\\s*$)"); private final ActionValidationHandler actionValidationHandler; private final SignedTextDescriptor signedTextDescriptor; - private final ActionProperties properties; + private final ActionMetadata metadata; - public final Action asAction() { + ActionLoadResult(ActionValidationHandler actionValidationHandler, SignedTextDescriptor signedTextDescriptor, ActionMetadata metadata) { + this.actionValidationHandler = actionValidationHandler; + this.signedTextDescriptor = signedTextDescriptor; + this.metadata = updateMetadata(metadata, signedTextDescriptor); + } + + /** + * @return Deserialized and initialized {@link Action} instance. + */ + public final Action getAction() { try { - var actionText = updateSchema(asText()); - var signatureStatus = signedTextDescriptor.getSignatureStatus(); + var actionText = updateSchema(getActionText()); var result = yamlObjectMapper.readValue(actionText, Action.class); - var properties = this.properties.toBuilder().signatureStatus(signatureStatus).build(); - result.postLoad(properties); + result.postLoad(metadata); return result; } catch ( Exception e ) { throw createException(e); } } - public final String asText() { - return signedTextDescriptor.getText(); + /** + * @return Textual action contents. + */ + public final String getActionText() { + return signedTextDescriptor.getPayload(); } - public final String asRawText() { - return signedTextDescriptor.getRawText(); + /** + * @return Original action file contents, including + * signature if available. + */ + public final String getOriginalText() { + return signedTextDescriptor.getOriginal(); } - public final ObjectNode asJson() { + public final ObjectNode getMetadataAsObjectNode() { try { - var payload = asText(); - var signatureStatus = signedTextDescriptor.getSignatureStatus(); - String name = properties.getName(); - boolean custom = properties.isCustom(); + + String name = metadata.getName(); + boolean custom = metadata.isCustom(); var customString = custom?"Yes":"No"; - // TODO see ActionLoader#loadSignedTextDescriptor; for internal actions - // we currently don't evaluate signatures until we implement functionality - // for signing these during or after build. - var signatureString = !custom || signatureStatus==SignatureStatus.VALID_SIGNATURE + var signatureStatus = metadata.getSignatureStatus(); + var signatureDescriptor = metadata.getSignatureDescriptor(); + var signatureMetadata = signatureDescriptor==null?null:signatureDescriptor.getMetadata(); + var publicKeyDescriptor = metadata.getPublicKeyDescriptor(); + + var signatureStatusString = signatureStatus==SignatureStatus.VALID_SIGNATURE ? "Valid" : "Invalid"; - return yamlObjectMapper.readValue(payload, ObjectNode.class) + var signer = StringUtils.defaultIfBlank(signatureMetadata==null + ? null : signatureMetadata.getSigner(), "N/A"); + var extraInfo = signatureMetadata==null + ? null : signatureMetadata.getExtraInfo(); + var verifiedBy = StringUtils.defaultIfBlank(publicKeyDescriptor==null + ? null : publicKeyDescriptor.getName(), "N/A"); + var publicKeyFingerprint = StringUtils.defaultIfBlank(publicKeyDescriptor==null + ? null : publicKeyDescriptor.getFingerprint(), "N/A"); + + var result = yamlObjectMapper.readValue(getActionText(), ObjectNode.class); + return result .put("name", name) .put("custom", custom) .put("customString", customString) .put("signatureStatus", signatureStatus.toString()) - .put("signatureString", signatureString); + .put("signatureString", signatureStatusString) + .put("signedBy", signer) + .put("verifiedBy", verifiedBy) + .put("publicKeyFingerprint", publicKeyFingerprint) + .put("info", String.format("Author: %s\nSigned by: %s\nPublic key: %s", result.get("author").asText(), signer, verifiedBy)) + .set("signatureExtraInfo", extraInfo); } catch ( Exception e ) { throw createException(e); } } + private static final ActionMetadata updateMetadata(ActionMetadata metadata, SignedTextDescriptor signedTextDescriptor) { + var custom = metadata.isCustom(); + return metadata.toBuilder() + .signatureDescriptor(getSignatureDescriptor(custom, signedTextDescriptor)) + .signatureStatus(getSignatureStatus(custom, signedTextDescriptor)) + .publicKeyDescriptor(getPublicKeyDescriptor(custom, signedTextDescriptor)) + .build(); + } + + private static SignatureDescriptor getSignatureDescriptor(boolean custom, SignedTextDescriptor signedTextDescriptor) { + return custom + ? signedTextDescriptor.getSignatureDescriptor() + : SignatureDescriptor.builder() + .signature("N/A") + .publicKeyFingerprint(SignatureHelper.fortifySignatureVerifier().publicKeyFingerPrint()) + .metadata(SignatureMetadata.builder() + .fcliVersion(FcliBuildPropertiesHelper.getFcliVersion()) + .signer("Fortify").build()).build(); + } + + private static SignatureStatus getSignatureStatus(boolean custom, SignedTextDescriptor signedTextDescriptor) { + return custom + ? signedTextDescriptor.getSignatureStatus() + : SignatureStatus.VALID_SIGNATURE; + } + + private static PublicKeyDescriptor getPublicKeyDescriptor(boolean custom, SignedTextDescriptor signedTextDescriptor) { + return custom + ? signedTextDescriptor.getPublicKeyDescriptor() + : PublicKeyDescriptor.builder() + .fingerprint(SignatureHelper.fortifySignatureVerifier().publicKeyFingerPrint()) + .name("Fortify") + .publicKey(SignatureHelper.FORTIFY_PUBLIC_KEY) + .source(PublicKeySource.INTERNAL) + .build(); + } + + private final String updateSchema(String actionText) { var result = actionText; var matcher = schemaPattern.matcher(actionText); @@ -267,7 +340,7 @@ private final String updateSchema(String actionText) { } var schemaVersion = ActionSchemaHelper.getSchemaVersion(schemaUri); if ( !ActionSchemaHelper.isSupportedSchemaVersion(schemaVersion) ) { - actionValidationHandler.onUnsupportedSchemaVersion(properties, schemaVersion); + actionValidationHandler.onUnsupportedSchemaVersion(metadata, schemaVersion); } return result; } @@ -287,7 +360,7 @@ private final RuntimeException createException(Exception e) { } private String getExceptionMessage(String detailMessage) { - var msg = "Error loading action "+properties.getName(); + var msg = "Error loading action "+metadata.getName(); if ( StringUtils.isNotBlank(detailMessage) ) { msg+=": "+detailMessage; } return msg; } @@ -296,7 +369,7 @@ private String getExceptionMessage(String detailMessage) { @Data @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public static final class ActionSource { private final Supplier inputStreamSupplier; - private final ActionProperties actionProperties; + private final ActionMetadata metadata; public static final List defaultActionSources(String type) { var result = new ArrayList(); @@ -328,19 +401,19 @@ public static final List externalActionSources(String source) { } private static final ActionSource external(String source) { - return new ActionSource(()->createSourceInputStream(source, true), ActionProperties.create(true)); + return new ActionSource(()->createSourceInputStream(source, true), ActionMetadata.create(true)); } private static final ActionSource imported(String type) { - return new ActionSource(customActionsInputStreamSupplier(type), ActionProperties.create(true)); + return new ActionSource(customActionsInputStreamSupplier(type), ActionMetadata.create(true)); } private static final ActionSource builtin(String type) { - return new ActionSource(builtinActionsInputStreamSupplier(type), ActionProperties.create(false)); + return new ActionSource(builtinActionsInputStreamSupplier(type), ActionMetadata.create(false)); } private static final ActionSource common(String type) { - return new ActionSource(commonActionsInputStreamSupplier(), ActionProperties.create(false)); + return new ActionSource(commonActionsInputStreamSupplier(), ActionMetadata.create(false)); } @SneakyThrows @@ -401,55 +474,55 @@ public static final class ActionValidationHandler { .onUnsupportedSchemaVersion(ActionInvalidSchemaVersionHandler.ignore) .build(); @Singular private final List extraPublicKeys; - @Singular private final Map> onSignatureStatuses; - @Builder.Default private final BiConsumer onSignatureStatusDefault = ActionInvalidSignatureHandler.prompt; - @Builder.Default private final BiConsumer onUnsupportedSchemaVersion = ActionInvalidSchemaVersionHandler.prompt; + @Singular private final Map> onSignatureStatuses; + @Builder.Default private final BiConsumer onSignatureStatusDefault = ActionInvalidSignatureHandler.prompt; + @Builder.Default private final BiConsumer onUnsupportedSchemaVersion = ActionInvalidSchemaVersionHandler.prompt; - public final SignatureValidator getSignatureValidator(ActionProperties properties) { - return new SignatureValidator(d->handleInvalidSignature(properties, d), extraPublicKeys.toArray(String[]::new)); + public final SignatureValidator getSignatureValidator(ActionMetadata metadata) { + return new SignatureValidator(d->handleInvalidSignature(metadata, d), extraPublicKeys.toArray(String[]::new)); } - public final void onUnsupportedSchemaVersion(ActionProperties properties, String schemaVersion) { - this.onUnsupportedSchemaVersion.accept(properties, schemaVersion); + public final void onUnsupportedSchemaVersion(ActionMetadata metadata, String schemaVersion) { + this.onUnsupportedSchemaVersion.accept(metadata, schemaVersion); } - private final void handleInvalidSignature(ActionProperties properties, SignedTextDescriptor signedTextDescriptor) { + private final void handleInvalidSignature(ActionMetadata metadata, SignedTextDescriptor signedTextDescriptor) { var consumer = onSignatureStatuses.get(signedTextDescriptor.getSignatureStatus()); if ( consumer==null ) { consumer = onSignatureStatusDefault; } - consumer.accept(properties, signedTextDescriptor); + consumer.accept(metadata, signedTextDescriptor); } @RequiredArgsConstructor - public static enum ActionInvalidSignatureHandler implements BiConsumer { + public static enum ActionInvalidSignatureHandler implements BiConsumer { ignore((p,d)->{}), warn((p,d)->_warn(signatureFailureMessage(p,d))), fail((p,d)->_throw(signatureFailureMessage(p,d))), prompt((p,d)->_prompt(signatureFailureMessage(p,d))); - private final BiConsumer onInvalidSignature; + private final BiConsumer onInvalidSignature; @Override - public void accept(ActionProperties properties, SignedTextDescriptor descriptor) { - onInvalidSignature.accept(properties, descriptor); + public void accept(ActionMetadata metadata, SignedTextDescriptor descriptor) { + onInvalidSignature.accept(metadata, descriptor); } - private static final String signatureFailureMessage(ActionProperties properties, SignedTextDescriptor descriptor) { - return getSignatureStatusMessage(properties, descriptor.getSignatureStatus()); + private static final String signatureFailureMessage(ActionMetadata metadata, SignedTextDescriptor descriptor) { + return getSignatureStatusMessage(metadata, descriptor.getSignatureStatus()); } } @RequiredArgsConstructor - public static enum ActionInvalidSchemaVersionHandler implements BiConsumer { + public static enum ActionInvalidSchemaVersionHandler implements BiConsumer { ignore((p,v)->{}), warn((p,v)->_warn(unsupportedSchemaMessage(p,v))), fail((p,v)->_throw(unsupportedSchemaMessage(p,v))), prompt((p,v)->_prompt(unsupportedSchemaMessage(p,v))); - private final BiConsumer onInvalidSchemaVersion; + private final BiConsumer onInvalidSchemaVersion; @Override - public void accept(ActionProperties properties, String schemaVersion) { - onInvalidSchemaVersion.accept(properties, schemaVersion); + public void accept(ActionMetadata metadata, String schemaVersion) { + onInvalidSchemaVersion.accept(metadata, schemaVersion); } - public static final String unsupportedSchemaMessage(ActionProperties properties, String unsupportedVersion) { - return String.format("Action "+properties.getName()+" uses unsupported schema version %s and may fail.", unsupportedVersion); + public static final String unsupportedSchemaMessage(ActionMetadata metadata, String unsupportedVersion) { + return String.format("Action "+metadata.getName()+" uses unsupported schema version %s and may fail.", unsupportedVersion); } } diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/model/Action.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/model/Action.java index 2fbabd3300..26671fa87f 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/model/Action.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/model/Action.java @@ -26,9 +26,10 @@ import com.fasterxml.jackson.annotation.JsonClassDescription; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonProperty.Access; import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.formkiq.graalvm.annotations.Reflectable; +import com.fortify.cli.common.crypto.helper.SignatureHelper.PublicKeyDescriptor; +import com.fortify.cli.common.crypto.helper.SignatureHelper.SignatureDescriptor; import com.fortify.cli.common.crypto.helper.SignatureHelper.SignatureStatus; import com.fortify.cli.common.util.StringUtils; @@ -56,18 +57,12 @@ @Data @JsonClassDescription("Fortify CLI action definition") public class Action implements IActionElement { - @JsonPropertyDescription("Read-only string: Action name, inferred from file name.") - @JsonProperty(access = Access.READ_ONLY) private String name; - - @JsonPropertyDescription("Read-only boolean: Whether this is a custom action, inferred from where the action is loaded from.") - @JsonProperty(access = Access.READ_ONLY) private boolean custom; - - @JsonPropertyDescription("Read-only object: Action signature verification status.") - @JsonProperty(access = Access.READ_ONLY) private SignatureStatus signatureStatus; - @JsonPropertyDescription("Required string unless `yaml-language-server` comment with schema location is provided: Schema location.") @JsonProperty(value = "$schema", required=false) public String schema; + @JsonPropertyDescription("Required string: Author of this action.") + @JsonProperty(required = true) private String author; + @JsonPropertyDescription("Required object: Action usage help.") @JsonProperty(required = true) private ActionUsage usage; @@ -86,6 +81,7 @@ public class Action implements IActionElement { @JsonPropertyDescription("Optional list: Value templates used to format data.") @JsonProperty(required = false) private List valueTemplates; + @JsonIgnore ActionMetadata metadata; /** Maps/Collections listing action elements. * These get filled by the {@link #visit(Action, Object)} method. */ @ToString.Exclude @JsonIgnore private final Map valueTemplatesByName = new HashMap<>(); @@ -114,9 +110,8 @@ public List getAllActionElements() { * methods, as these methods may utilize the collections. * @param signatureStatus */ - public final void postLoad(ActionProperties properties) { - this.name = properties.getName(); - this.custom = properties.isCustom(); + public final void postLoad(ActionMetadata metadata) { + this.metadata = metadata; initializeCollections(); allActionElements.forEach(elt->elt.postLoad(this)); } @@ -250,13 +245,15 @@ private Object getFieldValue(Object actionElement, Field field) { return field.get(actionElement); } - @Data @Builder(toBuilder = true) @AllArgsConstructor - public static final class ActionProperties { + @Reflectable @Data @Builder(toBuilder = true) @AllArgsConstructor + public static final class ActionMetadata { private final String name; private final boolean custom; + private final SignatureDescriptor signatureDescriptor; + private final PublicKeyDescriptor publicKeyDescriptor; @Builder.Default private final SignatureStatus signatureStatus = SignatureStatus.NOT_VERIFIED; - public static final ActionProperties create(boolean custom) { + public static final ActionMetadata create(boolean custom) { return builder().custom(custom).build(); } } diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/SignatureHelper.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/SignatureHelper.java index 096d41ac25..4faf9889d3 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/SignatureHelper.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/SignatureHelper.java @@ -3,6 +3,7 @@ import java.nio.file.Files; import java.nio.file.Path; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.node.ObjectNode; import com.formkiq.graalvm.annotations.Reflectable; import com.fortify.cli.common.crypto.helper.impl.KPGenerator; @@ -23,7 +24,7 @@ public class SignatureHelper { - private static final String FORTIFY_PUBLIC_KEY = + public static final String FORTIFY_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArij9U9yJVNc53oEMFWYp" + "NrXUG1UoRZseDh/p34q1uywD70RGKKWZvXIcUAZZwbZtCu4i0UzsrKRJeUwqanbc" + "woJvYanp6lc3DccXUN1w1Y0WOHOaBxiiK3B1TtEIH1cK/X+ZzazPG5nX7TSGh8Tp" @@ -140,25 +141,40 @@ public static final class SignatureDescriptor { private String signature; /** Public key fingerprint */ private String publicKeyFingerprint; - /** Additional information about the signature or action */ + /** Signature metadata */ + private SignatureMetadata metadata; + } + + @Reflectable @NoArgsConstructor + @Data @AllArgsConstructor @Builder + public static final class SignatureMetadata { + /** Fcli version */ + private String fcliVersion; + /** Free-format string describing who signed this file */ + private String signer; + /** Additional free-format information about the signed file, + * for example information about where the public key can + * be found, information about the signed file, ... */ private ObjectNode extraInfo; } @Reflectable @NoArgsConstructor @Data @AllArgsConstructor @Builder public static final class SignedTextDescriptor { - /** Raw text that was parsed, including signature if present */ - private String rawText; + /** Original text that was parsed, including signature if present */ + private String original; /** Text that was signed */ - private String text; + private String payload; /** Signature descriptor */ private SignatureDescriptor signatureDescriptor; /** Signature status */ private SignatureStatus signatureStatus; + /** Public key descriptor */ + private PublicKeyDescriptor publicKeyDescriptor; } @Reflectable @NoArgsConstructor - @Data @EqualsAndHashCode(callSuper = false) @AllArgsConstructor @Builder + @Data @EqualsAndHashCode(callSuper = false) @AllArgsConstructor @Builder(toBuilder = true) public static final class PublicKeyDescriptor extends JsonNodeHolder { /** Name for this public key */ private String name; @@ -166,5 +182,12 @@ public static final class PublicKeyDescriptor extends JsonNodeHolder { private String fingerprint; /** Actual public key */ private String publicKey; + /** Source where public key was loaded from */ + @JsonIgnore private PublicKeySource source; + } + + @Reflectable + public static enum PublicKeySource { + TRUSTSTORE, EXTERNAL, INTERNAL } } diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/impl/PublicKeyTrustStore.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/impl/PublicKeyTrustStore.java index e5f7bc2b18..930ed138d2 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/impl/PublicKeyTrustStore.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/impl/PublicKeyTrustStore.java @@ -19,6 +19,7 @@ import org.apache.commons.lang3.StringUtils; import com.fortify.cli.common.crypto.helper.SignatureHelper.PublicKeyDescriptor; +import com.fortify.cli.common.crypto.helper.SignatureHelper.PublicKeySource; import com.fortify.cli.common.util.FcliDataHelper; public final class PublicKeyTrustStore { @@ -28,7 +29,7 @@ private PublicKeyTrustStore() {} public PublicKeyDescriptor importKey(String publicKey, String name) { var fingerprint = new Verifier(publicKey).publicKeyFingerPrint(); if ( StringUtils.isBlank(name) ) { name=fingerprint; } - var descriptor = new PublicKeyDescriptor(name, fingerprint, publicKey); + var descriptor = new PublicKeyDescriptor(name, fingerprint, publicKey, PublicKeySource.EXTERNAL); FcliDataHelper.saveFile(publicKeyPath(fingerprint), descriptor, true); return descriptor; } @@ -43,6 +44,27 @@ public final PublicKeyDescriptor load(String nameOrFingerprint, boolean failIfNo throw new IllegalArgumentException("No public key found with name or fingerprint "+nameOrFingerprint); }); } + return result==null ? null : result.toBuilder().source(PublicKeySource.TRUSTSTORE).build(); + } + + public final PublicKeyDescriptor forFingerprint(String fingerprint, String... extraPublicKeys) { + PublicKeyDescriptor result = load(fingerprint, false); + // Try to locate public key for fingerprint from given extra public keys + if ( result==null && extraPublicKeys!=null ) { + for ( var extraPublicKey : extraPublicKeys ) { + if ( StringUtils.isNotBlank(extraPublicKey) ) { + var verifier = new Verifier(extraPublicKey); + if ( fingerprint.equals(verifier.publicKeyFingerPrint()) ) { + result = PublicKeyDescriptor.builder() + .fingerprint(fingerprint) + .name(null) + .publicKey(extraPublicKey) + .source(PublicKeySource.EXTERNAL) + .build(); + } + } + } + } return result; } diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/impl/SignedTextReader.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/impl/SignedTextReader.java index c3231be4be..ba71084b4f 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/impl/SignedTextReader.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/impl/SignedTextReader.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fortify.cli.common.crypto.helper.SignatureHelper.PublicKeyDescriptor; import com.fortify.cli.common.crypto.helper.SignatureHelper.SignatureDescriptor; import com.fortify.cli.common.crypto.helper.SignatureHelper.SignatureStatus; import com.fortify.cli.common.crypto.helper.SignatureHelper.SignatureValidator; @@ -72,27 +73,30 @@ public final SignedTextDescriptor load(String signedOrUnsignedText, boolean eval } } - private SignedTextDescriptor buildUnsignedDescriptor(String text) { + private SignedTextDescriptor buildUnsignedDescriptor(String payload) { return SignedTextDescriptor.builder() - .rawText(text) - .text(text) + .original(payload) + .payload(payload) .signatureStatus(SignatureStatus.NO_SIGNATURE) .build(); } - private SignedTextDescriptor buildSignedDescriptor(String rawText, String text, SignatureDescriptor signatureDescriptor, boolean evaluateSignatureStatus, String... extraPublicKeys) { + private SignedTextDescriptor buildSignedDescriptor(String original, String payload, SignatureDescriptor signatureDescriptor, boolean evaluateSignatureStatus, String... extraPublicKeys) { var signatureStatus = SignatureStatus.NOT_VERIFIED; + PublicKeyDescriptor publicKeyDescriptor = null; if ( evaluateSignatureStatus ) { var fingerprint = signatureDescriptor.getPublicKeyFingerprint(); var expectedSignature = signatureDescriptor.getSignature(); - signatureStatus = Verifier.forFingerprint(fingerprint, extraPublicKeys) - .verify(text, StandardCharsets.UTF_8, expectedSignature); + publicKeyDescriptor = PublicKeyTrustStore.INSTANCE.forFingerprint(fingerprint, extraPublicKeys); + signatureStatus = new Verifier(publicKeyDescriptor) + .verify(payload, StandardCharsets.UTF_8, expectedSignature); } return SignedTextDescriptor.builder() - .rawText(rawText) - .text(text) + .original(original) + .payload(payload) .signatureDescriptor(signatureDescriptor) .signatureStatus(signatureStatus) + .publicKeyDescriptor(publicKeyDescriptor) .build(); } diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/impl/TextSigner.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/impl/TextSigner.java index 821cdaf960..fa78e7ea8e 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/impl/TextSigner.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/impl/TextSigner.java @@ -22,10 +22,10 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; import com.fortify.cli.common.crypto.helper.SignatureHelper.SignatureDescriptor; +import com.fortify.cli.common.crypto.helper.SignatureHelper.SignatureMetadata; import lombok.SneakyThrows; @@ -41,18 +41,18 @@ public TextSigner(String pemOrBase64Key, char[] passPhrase) { } @SneakyThrows - public final void signAndWrite(Path payloadPath, Path outputPath, ObjectNode extraInfo) { - var content = sign(Files.readString(payloadPath), extraInfo); + public final void signAndWrite(Path payloadPath, Path outputPath, SignatureMetadata metadata) { + var content = sign(Files.readString(payloadPath), metadata); Files.writeString(outputPath, content, StandardOpenOption.CREATE_NEW); } @SneakyThrows - public final String sign(Path payloadPath, ObjectNode extraInfo) { - return sign(Files.readString(payloadPath), extraInfo); + public final String sign(Path payloadPath, SignatureMetadata metadata) { + return sign(Files.readString(payloadPath), metadata); } @SneakyThrows - public final String sign(String textToSign, ObjectNode extraInfo) { + public final String sign(String textToSign, SignatureMetadata metadata) { var payload = readBytes(textToSign); var fingerprint = signer.publicKeyFingerprint(); var signature = signer.sign(payload); @@ -60,7 +60,7 @@ public final String sign(String textToSign, ObjectNode extraInfo) { var signatureDescriptor = SignatureDescriptor.builder() .signature(signature) .publicKeyFingerprint(fingerprint) - .extraInfo(extraInfo) + .metadata(metadata) .build(); return generateOutput(payload, signatureDescriptor); } diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/impl/Verifier.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/impl/Verifier.java index 12400c8e7d..ca58101397 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/impl/Verifier.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/crypto/helper/impl/Verifier.java @@ -21,61 +21,30 @@ import java.security.spec.X509EncodedKeySpec; import java.util.Base64; -import org.apache.commons.lang3.StringUtils; - +import com.fortify.cli.common.crypto.helper.SignatureHelper.PublicKeyDescriptor; import com.fortify.cli.common.crypto.helper.SignatureHelper.SignatureStatus; import com.fortify.cli.common.crypto.helper.impl.InternalSignatureUtil.DataSignatureUpdater; import com.fortify.cli.common.crypto.helper.impl.InternalSignatureUtil.FileSignatureUpdater; import com.fortify.cli.common.crypto.helper.impl.InternalSignatureUtil.ISignatureUpdater; +import com.fortify.cli.common.util.StringUtils; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; -// TODO Refactor to make PublicKeyDescriptor available to callers, to allow -// callers to identify where the public key was loaded from, and the -// public key name and other properties if loaded from trust store: -// - Instead of publicKey, store publicKeyDescriptor -// - In PublicKeyDescriptor, make the parsed public key byte[] available -// to avoid having to do that in this and other classes -// - In PublicKeyDescriptor, add a loadedFrom enum field with TRUSTSTORE/EXTRAKEYS; -// in action loader we can convert this to something like 'public key loaded from -// trust store', or 'public key loaded from --pubkey option' -// - In PublicKeyDescriptor, allow name to be optional (probably already is) -// - In SignedTextDescriptor, add a new PublicKeyDescriptor field -// - In SignedTextReader::buildSignedDescriptor, get the public key descriptor from -// the verifier, and store it in SignedTextDescriptor. -// Ultimate goal is the ability to display public key information in fcli action -// outputs (action list/help command), for example for displaying something like -// "Certified by: " @RequiredArgsConstructor public final class Verifier { - // Based on comments above, change to 'private final PublicKeyDescriptor publicKeyDescriptor', - // and provide a getter method. private final byte[] publicKey; public Verifier(String pemOrBase64Key) { - this(InternalSignatureUtil.parseKey(pemOrBase64Key)); + this(StringUtils.isBlank(pemOrBase64Key) + ? null + : InternalSignatureUtil.parseKey(pemOrBase64Key)); } - // TODO Based on comments above, for public key loaded from extraPublicKeys, - // instantiate a new PublicKeyDescriptor instance and pass it to the constructor. - // For trusted public keys, simply pass the loaded descriptor to our constructor. - public static final Verifier forFingerprint(String fingerprint, String... extraPublicKeys) { - // Try to locate public key for fingerprint from given extra public keys - if ( extraPublicKeys!=null ) { - for ( var extraPublicKey : extraPublicKeys ) { - if ( StringUtils.isNotBlank(extraPublicKey) ) { - var verifier = new Verifier(extraPublicKey); - if ( fingerprint.equals(verifier.publicKeyFingerPrint()) ) { - return verifier; - } - } - } - } - // If not found in extra public keys, load from trusted public keys - var publicKeyDescriptor = PublicKeyTrustStore.INSTANCE.load(fingerprint, false); - var publicKey = publicKeyDescriptor==null ? null : publicKeyDescriptor.getPublicKey(); - return new Verifier(publicKey); + public Verifier(PublicKeyDescriptor publicKeyDescriptor) { + this(publicKeyDescriptor==null + ? null + : publicKeyDescriptor.getPublicKey()); } public final SignatureStatus verify(File file, String expectedSignature) { diff --git a/fcli-core/fcli-common/src/main/resources/com/fortify/cli/common/actions/zip/__sample__.yaml b/fcli-core/fcli-common/src/main/resources/com/fortify/cli/common/actions/zip/__sample__.yaml index 80435fe219..b8a308893a 100644 --- a/fcli-core/fcli-common/src/main/resources/com/fortify/cli/common/actions/zip/__sample__.yaml +++ b/fcli-core/fcli-common/src/main/resources/com/fortify/cli/common/actions/zip/__sample__.yaml @@ -22,6 +22,7 @@ $schema: https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev.js # by the 'list' and 'help' commands) and a more detailed description (shown by the # 'help' command). +author: Fortify usage: header: Sample Action description: | diff --git a/fcli-core/fcli-common/src/main/resources/com/fortify/cli/common/i18n/FortifyCLIMessages.properties b/fcli-core/fcli-common/src/main/resources/com/fortify/cli/common/i18n/FortifyCLIMessages.properties index e11cbf8d87..98b0575b0d 100644 --- a/fcli-core/fcli-common/src/main/resources/com/fortify/cli/common/i18n/FortifyCLIMessages.properties +++ b/fcli-core/fcli-common/src/main/resources/com/fortify/cli/common/i18n/FortifyCLIMessages.properties @@ -43,8 +43,12 @@ fcli.action.sign.out = Signed action output file. fcli.action.sign.with = PEM file containing private key used for signing. fcli.action.sign.password = Private key password. fcli.action.sign.info = YAML file containing informational properties to be added to signature \ - information. For example, this can be used to document by whom this action was signed, or \ - where the public key can be found. + metadata. For example, this can be used to document where the public key can be retrieved from, \ + or some extra information about the action being signed. +fcli.action.sign.signer = Free-format text string describing who signed this action, for example \ + a person, team or organization name. If not specified, signer will be taken from a property \ + named 'signer' in the file specified with the --info option if available, otherwise the current \ + user name will be used as the signer. fcli.action.sign.pubout = Public key output file. This option is required when generating a \ new key pair (if given private key doesn't exist), and may optionally be used for outputting \ the public key if an already existing private key is being used. diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/bitbucket-sast-report.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/bitbucket-sast-report.yaml index a78b8308e1..5265dc6c36 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/bitbucket-sast-report.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/bitbucket-sast-report.yaml @@ -1,5 +1,6 @@ # yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev.json +author: Fortify usage: header: Generate a BitBucket Code Insights report listing FoD SAST vulnerabilities. description: | diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/check-policy.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/check-policy.yaml index d7fb0e7c36..94f50a0a95 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/check-policy.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/check-policy.yaml @@ -1,5 +1,6 @@ # yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev.json +author: Fortify usage: header: (SAMPLE) Check security policy. description: | diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/github-pr-comment.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/github-pr-comment.yaml index 0ef3ee6d6e..32d340f2da 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/github-pr-comment.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/github-pr-comment.yaml @@ -5,6 +5,7 @@ # implement this, once FoD supports retrieving new/re-introduced/removed isses # for a particular scan id/PR number/commit id. +author: Fortify usage: header: (PREVIEW) Add GitHub Pull Request review comments. description: | diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/github-sast-report.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/github-sast-report.yaml index 1a9995d494..585b9862b0 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/github-sast-report.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/github-sast-report.yaml @@ -14,6 +14,7 @@ # when updating one, the other should also be updated. and ideally we should have functional tests # that compare the outputs of both actions. +author: Fortify usage: header: Generate a GitHub Code Scanning report listing FoD SAST vulnerabilities. description: | diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/gitlab-dast-report.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/gitlab-dast-report.yaml index b1125d0788..c3f469b9de 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/gitlab-dast-report.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/gitlab-dast-report.yaml @@ -1,5 +1,6 @@ # yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev.json +author: Fortify usage: header: Generate a GitLab DAST report listing FoD DAST vulnerabilities. description: | diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/gitlab-sast-report.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/gitlab-sast-report.yaml index 7650b70f03..26b530ca86 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/gitlab-sast-report.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/gitlab-sast-report.yaml @@ -1,5 +1,6 @@ # yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev.json +author: Fortify usage: header: Generate a GitLab SAST report listing FoD SAST vulnerabilities. description: | diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/release-summary.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/release-summary.yaml index 47e1e3eb1c..1be7ae9e36 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/release-summary.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/release-summary.yaml @@ -1,5 +1,6 @@ # yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev.json +author: Fortify usage: header: (PREVIEW) Generate release summary. description: | diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/sarif-sast-report.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/sarif-sast-report.yaml index 95dff3775e..529340c79d 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/sarif-sast-report.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/sarif-sast-report.yaml @@ -14,6 +14,7 @@ # when updating one, the other should also be updated. and ideally we should have functional tests # that compare the outputs of both actions. +author: Fortify usage: header: Generate SARIF report listing SSC SAST vulnerabilities. description: | diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/sonarqube-sast-report.yaml b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/sonarqube-sast-report.yaml index a2448873d8..8d6201c279 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/sonarqube-sast-report.yaml +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/sonarqube-sast-report.yaml @@ -1,5 +1,6 @@ # yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev.json +author: Fortify usage: header: Generate a SonarQube External Issues report listing FoD SAST vulnerabilities. description: | diff --git a/fcli-core/fcli-fod/src/test/java/com/fortify/cli/fod/_common/action/AbstractActionTest.java b/fcli-core/fcli-fod/src/test/java/com/fortify/cli/fod/_common/action/AbstractActionTest.java index 054665c342..e814c73042 100644 --- a/fcli-core/fcli-fod/src/test/java/com/fortify/cli/fod/_common/action/AbstractActionTest.java +++ b/fcli-core/fcli-fod/src/test/java/com/fortify/cli/fod/_common/action/AbstractActionTest.java @@ -25,7 +25,7 @@ import com.fortify.cli.common.action.helper.ActionLoaderHelper.ActionValidationHandler; import com.fortify.cli.common.action.helper.ActionLoaderHelper.ActionValidationHandler.ActionInvalidSchemaVersionHandler; import com.fortify.cli.common.action.helper.ActionLoaderHelper.ActionValidationHandler.ActionInvalidSignatureHandler; -import com.fortify.cli.common.action.model.Action.ActionProperties; +import com.fortify.cli.common.action.model.Action.ActionMetadata; import com.fortify.cli.common.crypto.helper.SignatureHelper.SignedTextDescriptor; // TODO Move this class to a common test utility module; currently @@ -43,7 +43,7 @@ public void testLoadAction(String name) { .build(); ActionLoaderHelper .load(ActionSource.builtinActionSources(getType()), name, actionValidationHandler) - .asAction(); + .getAction(); } catch ( Exception e ) { System.err.println(String.format("Error loading %s action %s:\n%s", getType(), name, e)); Assertions.fail(String.format("Error loading %s action %s", getType(), name), e); @@ -56,7 +56,7 @@ public final String[] getActions() { .toArray(String[]::new); } - private static final BiConsumer invalidSignatureHandler() { + private static final BiConsumer invalidSignatureHandler() { return "true".equalsIgnoreCase((System.getProperty("test.action.requireValidSignature"))) ? ActionInvalidSignatureHandler.fail : ActionInvalidSignatureHandler.warn; diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/appversion-summary.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/appversion-summary.yaml index 936a31b8fb..93d5914025 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/appversion-summary.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/appversion-summary.yaml @@ -1,5 +1,6 @@ # yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev.json +author: Fortify usage: header: (PREVIEW) Generate application version summary. description: | diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/bitbucket-sast-report.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/bitbucket-sast-report.yaml index ed90c76983..20695d15dc 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/bitbucket-sast-report.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/bitbucket-sast-report.yaml @@ -1,5 +1,6 @@ # yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev.json +author: Fortify usage: header: Generate a BitBucket Code Insights report listing SSC SAST vulnerabilities. description: | diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/check-policy.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/check-policy.yaml index 8b4ba72682..1ff8df421c 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/check-policy.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/check-policy.yaml @@ -1,5 +1,6 @@ # yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev.json +author: Fortify usage: header: (SAMPLE) Check security policy. description: | diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/github-pr-comment.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/github-pr-comment.yaml index 0038d9ce74..c4d9d9ed60 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/github-pr-comment.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/github-pr-comment.yaml @@ -4,6 +4,7 @@ # See corresponding .bak file for a better but incomplete (due to SSC limitations) # implementation based on artifact id. +author: Fortify usage: header: (PREVIEW) Add GitHub Pull Request review comments. description: | diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/github-pr-comment.yaml.bak b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/github-pr-comment.yaml.bak index e5ac36e19b..b4eec36cf1 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/github-pr-comment.yaml.bak +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/github-pr-comment.yaml.bak @@ -11,6 +11,7 @@ # state, rather than trying to identify issues for a particular scan. Based on the # '.bak' extension of this file, it won't be included in fcli artifacts. +author: Fortify usage: header: Generate GitHub Pull Request decoration. description: | diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/github-sast-report.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/github-sast-report.yaml index 60517d95a4..4bbec2055e 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/github-sast-report.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/github-sast-report.yaml @@ -14,6 +14,7 @@ # when updating one, the other should also be updated. and ideally we should have functional tests # that compare the outputs of both actions. +author: Fortify usage: header: Generate a GitHub Code Scanning report listing SSC SAST vulnerabilities. description: | diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/gitlab-dast-report.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/gitlab-dast-report.yaml index a3646f5116..634fb77bff 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/gitlab-dast-report.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/gitlab-dast-report.yaml @@ -1,5 +1,6 @@ # yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev.json +author: Fortify usage: header: Generate a GitLab DAST report listing SSC DAST vulnerabilities. description: | diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/gitlab-debricked-report.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/gitlab-debricked-report.yaml index 863174e574..2735b6048b 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/gitlab-debricked-report.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/gitlab-debricked-report.yaml @@ -1,5 +1,6 @@ # yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev.json +author: Fortify usage: header: Generate a GitLab Dependency Scanning report listing SSC Debricked vulnerabilities. description: | diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/gitlab-sast-report.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/gitlab-sast-report.yaml index 58aed21665..a90796e02f 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/gitlab-sast-report.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/gitlab-sast-report.yaml @@ -1,5 +1,6 @@ # yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev.json +author: Fortify usage: header: Generate a GitLab SAST report listing SSC SAST vulnerabilities. description: | diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/gitlab-sonatype-report.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/gitlab-sonatype-report.yaml index 8c5919be16..942aa5a605 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/gitlab-sonatype-report.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/gitlab-sonatype-report.yaml @@ -1,5 +1,6 @@ # yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev.json +author: Fortify usage: header: Generate a GitLab Dependency Scanning report listing SSC Sonatype vulnerabilities. description: | diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/sarif-sast-report.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/sarif-sast-report.yaml index 7be117d4eb..bf60642c70 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/sarif-sast-report.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/sarif-sast-report.yaml @@ -14,6 +14,7 @@ # when updating one, the other should also be updated. and ideally we should have functional tests # that compare the outputs of both actions. +author: Fortify usage: header: Generate SARIF report listing SSC SAST vulnerabilities. description: | diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/sonarqube-sast-report.yaml b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/sonarqube-sast-report.yaml index 97e9e61008..9cfbf4e878 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/sonarqube-sast-report.yaml +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/sonarqube-sast-report.yaml @@ -1,5 +1,6 @@ # yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev.json +author: Fortify usage: header: Generate a SonarQube External Issues report listing SSC SAST vulnerabilities. description: | diff --git a/fcli-core/fcli-ssc/src/test/java/com/fortify/cli/ssc/_common/action/AbstractActionTest.java b/fcli-core/fcli-ssc/src/test/java/com/fortify/cli/ssc/_common/action/AbstractActionTest.java index 3ee9cd0726..26c7ade76e 100644 --- a/fcli-core/fcli-ssc/src/test/java/com/fortify/cli/ssc/_common/action/AbstractActionTest.java +++ b/fcli-core/fcli-ssc/src/test/java/com/fortify/cli/ssc/_common/action/AbstractActionTest.java @@ -25,7 +25,7 @@ import com.fortify.cli.common.action.helper.ActionLoaderHelper.ActionValidationHandler; import com.fortify.cli.common.action.helper.ActionLoaderHelper.ActionValidationHandler.ActionInvalidSchemaVersionHandler; import com.fortify.cli.common.action.helper.ActionLoaderHelper.ActionValidationHandler.ActionInvalidSignatureHandler; -import com.fortify.cli.common.action.model.Action.ActionProperties; +import com.fortify.cli.common.action.model.Action.ActionMetadata; import com.fortify.cli.common.crypto.helper.SignatureHelper.SignedTextDescriptor; // TODO Move this class to a common test utility module; currently @@ -43,7 +43,7 @@ public void testLoadAction(String name) { .build(); ActionLoaderHelper .load(ActionSource.builtinActionSources(getType()), name, actionValidationHandler) - .asAction(); + .getAction(); } catch ( Exception e ) { System.err.println(String.format("Error loading %s action %s:\n%s", getType(), name, e)); Assertions.fail(String.format("Error loading %s action %s", getType(), name), e); @@ -56,7 +56,7 @@ public final String[] getActions() { .toArray(String[]::new); } - private static final BiConsumer invalidSignatureHandler() { + private static final BiConsumer invalidSignatureHandler() { return "true".equalsIgnoreCase((System.getProperty("test.action.requireValidSignature"))) ? ActionInvalidSignatureHandler.fail : ActionInvalidSignatureHandler.warn;