From e88040f6c64cee2e4283df71348d7b17cdd79737 Mon Sep 17 00:00:00 2001 From: delarea Date: Tue, 31 Dec 2024 13:21:17 +0200 Subject: [PATCH] Add evidence to modules table --- .../utils/commandsummary/buildinfosummary.go | 55 ++++++++++-- .../commandsummary/buildinfosummary_test.go | 85 +++++++++++++++++-- .../utils/commandsummary/markdownConfig.go | 6 ++ artifactory/utils/commandsummary/utils.go | 12 +++ .../utils/commandsummary/utils_test.go | 5 ++ .../basic/multiarch-docker-image-evidence.md | 13 +++ .../multiarch-docker-image-evidence.md | 13 +++ 7 files changed, 176 insertions(+), 13 deletions(-) create mode 100644 artifactory/utils/testdata/command_summaries/basic/multiarch-docker-image-evidence.md create mode 100644 artifactory/utils/testdata/command_summaries/extended/multiarch-docker-image-evidence.md diff --git a/artifactory/utils/commandsummary/buildinfosummary.go b/artifactory/utils/commandsummary/buildinfosummary.go index 8071b7694..040a28099 100644 --- a/artifactory/utils/commandsummary/buildinfosummary.go +++ b/artifactory/utils/commandsummary/buildinfosummary.go @@ -129,14 +129,17 @@ func (bis *BuildInfoSummary) generateModulesMarkdown(modules ...buildInfo.Module if len(subModules) == 0 { continue } - if !scannableModuleType[subModules[0].Type] { + // Check if the artifacts inside the module contains evidences + evidenceExists := checkEvidence(subModules) + + if !scannableModuleType[subModules[0].Type] && !evidenceExists { tree, err := bis.generateModuleArtifactTree(rootModuleID, subModules) if err != nil { return "", err } modulesMarkdown.WriteString(tree) } else { - view, err := bis.generateModuleTableView(rootModuleID, subModules) + view, err := bis.generateModuleTableView(rootModuleID, subModules, evidenceExists) if err != nil { return "", err } @@ -146,6 +149,19 @@ func (bis *BuildInfoSummary) generateModulesMarkdown(modules ...buildInfo.Module return modulesMarkdown.String(), nil } +func checkEvidence(modules []buildInfo.Module) bool { + // TODO this has to be changed to SHA, as name can repeat + for _, module := range modules { + for _, artifact := range module.Artifacts { + _, exists := StaticMarkdownConfig.artifactsEvidencesMapping[artifact.Name] + if exists { + return true + } + } + } + return false +} + func (bis *BuildInfoSummary) generateModuleArtifactTree(rootModuleID string, nestedModules []buildInfo.Module) (string, error) { if len(nestedModules) == 0 { return "", nil @@ -170,17 +186,17 @@ func (bis *BuildInfoSummary) generateModuleArtifactTree(rootModuleID string, nes return markdownBuilder.String(), nil } -func (bis *BuildInfoSummary) generateModuleTableView(rootModuleID string, subModules []buildInfo.Module) (string, error) { +func (bis *BuildInfoSummary) generateModuleTableView(rootModuleID string, subModules []buildInfo.Module, evidenceExists bool) (string, error) { var markdownBuilder strings.Builder markdownBuilder.WriteString(generateModuleHeader(rootModuleID)) - markdownBuilder.WriteString(generateModuleTableHeader()) + markdownBuilder.WriteString(generateModuleTableHeader(evidenceExists)) isMultiModule := len(subModules) > 1 nestedModuleMarkdownTree, err := bis.generateTableModuleMarkdown(subModules, rootModuleID, isMultiModule) if err != nil { return "", err } scanResult := getScanResults(extractDockerImageTag(subModules)) - markdownBuilder.WriteString(generateTableRow(nestedModuleMarkdownTree, scanResult)) + markdownBuilder.WriteString(generateTableRow(nestedModuleMarkdownTree, scanResult, evidenceExists)) return markdownBuilder.String(), nil } @@ -351,14 +367,39 @@ func generateModuleHeader(parentModuleID string) string { return fmt.Sprintf("\n\n**%s**\n\n", parentModuleID) } -func generateModuleTableHeader() string { +func generateModuleTableHeader(evidenceExists bool) string { + if evidenceExists { + return "\n\n| Artifacts | Evidence created | Security Violations | Security Issues |\n|:------------|:---------------------|:------------------|:------------------|\n" + } return "\n\n| Artifacts | Security Violations | Security Issues |\n|:------------|:---------------------|:------------------|\n" } -func generateTableRow(nestedModuleMarkdownTree string, scanResult ScanResult) string { +func generateTableRow(nestedModuleMarkdownTree string, scanResult ScanResult, evidenceExists bool) string { + if evidenceExists { + return fmt.Sprintf(" %s | %s | %s | %s |\n", fitInsideMarkdownTable(nestedModuleMarkdownTree), getEvidenceLinkFromModule(nestedModuleMarkdownTree), appendSpacesToTableColumn(scanResult.GetViolations()), appendSpacesToTableColumn(scanResult.GetVulnerabilities())) + } return fmt.Sprintf(" %s | %s | %s |\n", fitInsideMarkdownTable(nestedModuleMarkdownTree), appendSpacesToTableColumn(scanResult.GetViolations()), appendSpacesToTableColumn(scanResult.GetVulnerabilities())) } +func getEvidenceLinkFromModule(moduleTree string) string { + for _, artifact := range StaticMarkdownConfig.artifactsEvidencesMapping { + if strings.Contains(moduleTree, artifact.Name) { + evidenceUrl, err := GenerateArtifactEvidenceUrl(path.Join(artifact.OriginalDeploymentRepo, artifact.Path)) + if err != nil { + log.Warn(err) + } + if StaticMarkdownConfig.IsExtendedSummary() { + return fmt.Sprintf("[Build-signature](%s)", evidenceUrl) + } + // TODO add evidence support link + return fmt.Sprintf("🔎 [Enable evidence support](%s)", "somelink") + } + } + // TODO handle no evidence + return fmt.Sprintf("[Learn more about evidence](%s)", "someLink") + +} + func fitInsideMarkdownTable(str string) string { return strings.ReplaceAll(str, "\n", "
") } diff --git a/artifactory/utils/commandsummary/buildinfosummary_test.go b/artifactory/utils/commandsummary/buildinfosummary_test.go index f445d5468..ca6171f65 100644 --- a/artifactory/utils/commandsummary/buildinfosummary_test.go +++ b/artifactory/utils/commandsummary/buildinfosummary_test.go @@ -14,12 +14,13 @@ import ( ) const ( - buildInfoTable = "build-info-table.md" - dockerImageModule = "docker-image-module.md" - genericModule = "generic-module.md" - mavenModule = "maven-module.md" - mavenNestedModule = "maven-nested-module.md" - dockerMultiArchModule = "multiarch-docker-image.md" + buildInfoTable = "build-info-table.md" + dockerImageModule = "docker-image-module.md" + genericModule = "generic-module.md" + mavenModule = "maven-module.md" + mavenNestedModule = "maven-nested-module.md" + dockerMultiArchModule = "multiarch-docker-image.md" + dockerMultiArchModuleEvidence = "multiarch-docker-image-evidence.md" ) type MockScanResult struct { @@ -500,6 +501,78 @@ func TestGroupModules(t *testing.T) { } } +func TestModuleWithEvidence(t *testing.T) { + buildInfoSummary, cleanUp := prepareBuildInfoTest() + defer func() { + cleanUp() + }() + StaticMarkdownConfig.artifactsEvidencesMapping = make(map[string]buildinfo.Artifact) + StaticMarkdownConfig.artifactsEvidencesMapping["list.manifest.json"] = buildinfo.Artifact{ + Path: "multiarch-image/sha256", + Name: "sha256", + } + var builds = []*buildinfo.BuildInfo{ + { + Name: "dockerx", + Number: "1", + Started: "2024-08-12T11:11:50.198+0300", + Modules: []buildinfo.Module{ + { + Properties: map[string]interface{}{ + "docker.image.tag": "ecosysjfrog.jfrog.io/docker-local/multiarch-image:1", + }, + Type: "docker", + Id: "multiarch-image:1", + Artifacts: []buildinfo.Artifact{ + { + Type: "json", + Checksum: buildinfo.Checksum{ + Sha1: "fa", + Sha256: "2217", + Md5: "ba0", + }, + Name: "list.manifest.json", + Path: "multiarch-image/1/list.manifest.json", + OriginalDeploymentRepo: "docker-local", + }, + }, + }, + { + Type: "docker", + Parent: "multiarch-image:1", + Id: "linux/amd64/multiarch-image:1", + Artifacts: []buildinfo.Artifact{ + { + Checksum: buildinfo.Checksum{ + Sha1: "32", + Sha256: "sha256:552c", + Md5: "f56", + }, + Name: "manifest.json", + Path: "multiarch-image/sha256", + OriginalDeploymentRepo: "docker-local", + }, + }, + }, + }, + }, + } + + t.Run("Extended Summary", func(t *testing.T) { + StaticMarkdownConfig.setExtendedSummary(true) + res, err := buildInfoSummary.buildInfoModules(builds) + assert.NoError(t, err) + testMarkdownOutput(t, getTestDataFile(t, dockerMultiArchModuleEvidence), res) + }) + t.Run("Basic Summary", func(t *testing.T) { + StaticMarkdownConfig.setExtendedSummary(false) + res, err := buildInfoSummary.buildInfoModules(builds) + assert.NoError(t, err) + testMarkdownOutput(t, getTestDataFile(t, dockerMultiArchModuleEvidence), res) + }) + +} + // Tests data files are location artifactory/commands/testdata/command_summary func getTestDataFile(t *testing.T, fileName string) string { var modulesPath string diff --git a/artifactory/utils/commandsummary/markdownConfig.go b/artifactory/utils/commandsummary/markdownConfig.go index d2d8a129b..19baca545 100644 --- a/artifactory/utils/commandsummary/markdownConfig.go +++ b/artifactory/utils/commandsummary/markdownConfig.go @@ -3,6 +3,7 @@ package commandsummary import ( "encoding/json" "fmt" + buildInfo "github.com/jfrog/build-info-go/entities" "net/http" "net/url" "strings" @@ -23,6 +24,8 @@ type MarkdownConfig struct { platformMajorVersion int // Static mapping of scan results to be used in the summary scanResultsMapping map[string]ScanResult + // Static mapping of artifacts evidences + artifactsEvidencesMapping map[string]buildInfo.Artifact } const extendedSummaryLandPage = "https://jfrog.com/help/access?xinfo:appid=csh-gen-gitbook" @@ -60,6 +63,9 @@ func (mg *MarkdownConfig) GetExtendedSummaryLangPage() string { func (mg *MarkdownConfig) SetScanResultsMapping(resultsMap map[string]ScanResult) { mg.scanResultsMapping = resultsMap } +func (mg *MarkdownConfig) SetArtifactsEvidencesMapping(artifactsEvidencesMapping map[string]buildInfo.Artifact) { + mg.artifactsEvidencesMapping = artifactsEvidencesMapping +} // Initializes the command summary values that effect Markdown generation func InitMarkdownGenerationValues(serverUrl string, platformMajorVersion int) (err error) { diff --git a/artifactory/utils/commandsummary/utils.go b/artifactory/utils/commandsummary/utils.go index f5034180c..828111bc4 100644 --- a/artifactory/utils/commandsummary/utils.go +++ b/artifactory/utils/commandsummary/utils.go @@ -12,6 +12,7 @@ import ( const ( artifactory7UiFormat = "%sui/repos/tree/General/%s?clearFilter=true" + artifactory7UiEvidenceFormat = "%sui/repos/tree/Evidence/%s?clearFilter=true" artifactory6UiFormat = "%sartifactory/webapp/#/artifacts/browse/tree/General/%s" artifactoryDockerPackagesUiFormat = "%s/ui/packages/docker:%s/sha256__%s" githubWorkflowEnv = "GITHUB_WORKFLOW" @@ -27,6 +28,17 @@ func GenerateArtifactUrl(pathInRt string, section summarySection) (url string, e return } +func GenerateArtifactEvidenceUrl(pathInRt string) (url string, err error) { + if StaticMarkdownConfig.GetPlatformMajorVersion() == 6 { + // todo handle not supported + url = fmt.Sprintf(artifactory6UiFormat, StaticMarkdownConfig.GetPlatformUrl(), pathInRt) + } else { + url = fmt.Sprintf(artifactory7UiEvidenceFormat, StaticMarkdownConfig.GetPlatformUrl(), pathInRt) + } + url, err = addGitHubTrackingToUrl(url, artifactsSection) + return +} + func WrapCollapsableMarkdown(title, markdown string, headerSize int) string { return fmt.Sprintf("\n\n\n
\n\n %s

\n\n%s\n\n
\n\n\n", headerSize, title, headerSize, markdown) } diff --git a/artifactory/utils/commandsummary/utils_test.go b/artifactory/utils/commandsummary/utils_test.go index 5473df4d0..6b0e50f42 100644 --- a/artifactory/utils/commandsummary/utils_test.go +++ b/artifactory/utils/commandsummary/utils_test.go @@ -13,6 +13,11 @@ const ( ) func TestGenerateArtifactUrl(t *testing.T) { + // Used to + _, cleanUp := prepareBuildInfoTest() + defer func() { + cleanUp() + }() cases := []struct { testName string projectKey string diff --git a/artifactory/utils/testdata/command_summaries/basic/multiarch-docker-image-evidence.md b/artifactory/utils/testdata/command_summaries/basic/multiarch-docker-image-evidence.md new file mode 100644 index 000000000..1a8697734 --- /dev/null +++ b/artifactory/utils/testdata/command_summaries/basic/multiarch-docker-image-evidence.md @@ -0,0 +1,13 @@ + + +

Published Modules

+ + + +**multiarch-image:1** + + + +| Artifacts | Evidence created | Security Violations | Security Issues | +| :------------ | :--------------------- | :------------------ | :------------------ | +| 🐸 Enable the linkage to Artifactory

linux/amd64/multiarch-image:1
📦 docker-local
└── 📁 multiarch-image
└── 📄 sha256

| 🔎 [Enable evidence support](somelink) | Not scanned | Not scanned | diff --git a/artifactory/utils/testdata/command_summaries/extended/multiarch-docker-image-evidence.md b/artifactory/utils/testdata/command_summaries/extended/multiarch-docker-image-evidence.md new file mode 100644 index 000000000..3c8294bef --- /dev/null +++ b/artifactory/utils/testdata/command_summaries/extended/multiarch-docker-image-evidence.md @@ -0,0 +1,13 @@ + + +

Published Modules

+ + + +**multiarch-image:1** + + + +| Artifacts | Evidence created | Security Violations | Security Issues | +| :------------ | :--------------------- | :------------------ | :------------------ | +|
linux/amd64/multiarch-image:1 (🐸 View)
📦 docker-local
└── 📁 multiarch-image
└── sha256

| [Build-signature](https://myplatform.com/ui/repos/tree/Evidence/multiarch-image/sha256?clearFilter=true&gh_job_id=JFrog+CLI+Core+Tests&gh_section=artifacts) | Not scanned | Not scanned |