From 8cbbf50d4d4b45b25591d81ef84f38c75a74fdc8 Mon Sep 17 00:00:00 2001 From: Spyros Date: Wed, 18 Oct 2023 22:25:27 +0100 Subject: [PATCH] Cdxgen (#45) * experimental cdxgen introduction * add params and image * add sbom printing capabilities to the stdou-json consumer * producer, example pipeline * add annotation caps to stdout-json consumer * lint * lint --- components/consumers/stdout-json/main.go | 51 ++++-- components/producers/cdxgen/BUILD | 32 ++++ components/producers/cdxgen/README.md | 17 ++ .../producers/cdxgen/kustomization.yaml | 169 ++++++++++++++++++ components/producers/cdxgen/main.go | 35 ++++ components/producers/cdxgen/task.yaml | 57 ++++++ examples/pipelines/cdxgen-project/BUILD | 17 ++ .../cdxgen-project/kustomization.yaml | 18 ++ .../pipelinerun/pipelinerun.yaml | 26 +++ third_party/docker/cyclonedx/cdxgen/BUILD | 8 + .../github.com/CycloneDX/cyclonedx-go/BUILD | 2 +- 11 files changed, 414 insertions(+), 18 deletions(-) create mode 100644 components/producers/cdxgen/BUILD create mode 100644 components/producers/cdxgen/README.md create mode 100644 components/producers/cdxgen/kustomization.yaml create mode 100644 components/producers/cdxgen/main.go create mode 100644 components/producers/cdxgen/task.yaml create mode 100644 examples/pipelines/cdxgen-project/BUILD create mode 100644 examples/pipelines/cdxgen-project/kustomization.yaml create mode 100644 examples/pipelines/cdxgen-project/pipelinerun/pipelinerun.yaml create mode 100644 third_party/docker/cyclonedx/cdxgen/BUILD diff --git a/components/consumers/stdout-json/main.go b/components/consumers/stdout-json/main.go index fe9e58583..c46139ebd 100644 --- a/components/consumers/stdout-json/main.go +++ b/components/consumers/stdout-json/main.go @@ -48,6 +48,12 @@ func main() { } func getRawIssue(scanStartTime time.Time, res *v1.LaunchToolResponse, iss *v1.Issue) ([]byte, error) { + var sbom map[string]interface{} + if iss.GetCycloneDXSBOM() != "" { + if err := json.Unmarshal([]byte(iss.GetCycloneDXSBOM()), &sbom); err != nil { + log.Fatalf("error unmarshaling cyclonedx sbom, err:%s", err) + } + } jBytes, err := json.Marshal(&draconDocument{ ScanStartTime: scanStartTime, ScanID: res.GetScanInfo().GetScanUuid(), @@ -64,6 +70,7 @@ func getRawIssue(scanStartTime time.Time, res *v1.LaunchToolResponse, iss *v1.Is Count: 1, FalsePositive: false, CVE: iss.GetCve(), + CycloneDXSBOM: sbom, }) if err != nil { return []byte{}, err @@ -72,6 +79,12 @@ func getRawIssue(scanStartTime time.Time, res *v1.LaunchToolResponse, iss *v1.Is } func getEnrichedIssue(scanStartTime time.Time, res *v1.EnrichedLaunchToolResponse, iss *v1.EnrichedIssue) ([]byte, error) { + var sbom map[string]interface{} + if iss.GetRawIssue().GetCycloneDXSBOM() != "" { + if err := json.Unmarshal([]byte(iss.GetRawIssue().GetCycloneDXSBOM()), &sbom); err != nil { + log.Fatalf("error unmarshaling cyclonedx sbom, err:%s", err) + } + } firstSeenTime := iss.GetFirstSeen().AsTime() jBytes, err := json.Marshal(&draconDocument{ ScanStartTime: scanStartTime, @@ -91,6 +104,8 @@ func getEnrichedIssue(scanStartTime time.Time, res *v1.EnrichedLaunchToolRespons SeverityText: enumtransformers.SeverityToText(iss.GetRawIssue().GetSeverity()), ConfidenceText: enumtransformers.ConfidenceToText(iss.GetRawIssue().GetConfidence()), CVE: iss.GetRawIssue().GetCve(), + CycloneDXSBOM: sbom, + Annotations: iss.GetAnnotations(), }) if err != nil { return []byte{}, err @@ -99,21 +114,23 @@ func getEnrichedIssue(scanStartTime time.Time, res *v1.EnrichedLaunchToolRespons } type draconDocument struct { - ScanStartTime time.Time `json:"scan_start_time"` - ScanID string `json:"scan_id"` - ToolName string `json:"tool_name"` - Source string `json:"source"` - Target string `json:"target"` - Type string `json:"type"` - Title string `json:"title"` - Severity v1.Severity `json:"severity"` - SeverityText string `json:"severity_text"` - CVSS float64 `json:"cvss"` - Confidence v1.Confidence `json:"confidence"` - ConfidenceText string `json:"confidence_text"` - Description string `json:"description"` - FirstFound time.Time `json:"first_found"` - Count uint64 `json:"count"` - FalsePositive bool `json:"false_positive"` - CVE string `json:"cve"` + ScanStartTime time.Time `json:"scan_start_time"` + ScanID string `json:"scan_id"` + ToolName string `json:"tool_name"` + Source string `json:"source"` + Target string `json:"target"` + Type string `json:"type"` + Title string `json:"title"` + Severity v1.Severity `json:"severity"` + SeverityText string `json:"severity_text"` + CVSS float64 `json:"cvss"` + Confidence v1.Confidence `json:"confidence"` + ConfidenceText string `json:"confidence_text"` + Description string `json:"description"` + FirstFound time.Time `json:"first_found"` + Count uint64 `json:"count"` + FalsePositive bool `json:"false_positive"` + CVE string `json:"cve"` + CycloneDXSBOM map[string]interface{} `json:"CycloneDX_SBOM"` + Annotations map[string]string `json:"annotations"` } diff --git a/components/producers/cdxgen/BUILD b/components/producers/cdxgen/BUILD new file mode 100644 index 000000000..dd248309f --- /dev/null +++ b/components/producers/cdxgen/BUILD @@ -0,0 +1,32 @@ +subinclude( + "//build/defs:buildkit", + "//build/defs:dracon", +) + +go_binary( + name = "cdxgen-parser", + srcs = [ + "main.go", + ], + deps = [ + "//api/proto/v1", + "//components/producers", + "//pkg/cyclonedx", + "//pkg/sarif", + ], +) + +buildkit_distroless_image( + name = "image", + srcs = [":cdxgen-parser"], +) + +dracon_component( + name = "cdxgen", + images = [ + ":image", + "//third_party/docker/cyclonedx/cdxgen", + ], + task = "task.yaml", + visibility = ["//examples/pipelines/..."], +) diff --git a/components/producers/cdxgen/README.md b/components/producers/cdxgen/README.md new file mode 100644 index 000000000..7b659379a --- /dev/null +++ b/components/producers/cdxgen/README.md @@ -0,0 +1,17 @@ +# Dracon CDXGEN Producer + +This producer runs [CycloneDX/cdxgen](https://github.com/CycloneDX/cdxgen) against the specified filesystem or image. +It then parses the results into the Dracon format and exits. + +## Testing without Dracon + +You can run this producer outside of dracon for development with + +``` bash +plz run //components/producers/cdxgen:cdxgen -- -in -out ./cdxgen.pb +``` +cdxgen can be run as a docker image by pulling `ghcr.io/cyclonedx/cdxgen` + +## SBOM mode + +The producer will output a `LaunchToolResponse` containing a single issue which will have its `CycloneDXSBOM` field populated with the output from cdxgen. \ No newline at end of file diff --git a/components/producers/cdxgen/kustomization.yaml b/components/producers/cdxgen/kustomization.yaml new file mode 100644 index 000000000..6dfcea00a --- /dev/null +++ b/components/producers/cdxgen/kustomization.yaml @@ -0,0 +1,169 @@ +# DO NOT EDIT. Code generated by: +# github.com/ocurity/dracon//build/tools/kustomize-component-generator. + +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +resources: + - task.yaml +patches: + # Add the Task to the Tekton Pipeline. + - patch: | + apiVersion: tekton.dev/v1beta1 + kind: Pipeline + metadata: + name: unused + spec: + workspaces: + - name: source-code-ws + tasks: + - name: producer-cdxgen + taskRef: + name: producer-cdxgen + workspaces: + - name: source-code-ws + workspace: source-code-ws + params: + - name: producer-cdxgen-flags + value: + - $(params.producer-cdxgen-flags) + - name: producer-cdxgen-fetch-license + value: $(params.producer-cdxgen-fetch-license) + - name: producer-cdxgen-github-token + value: $(params.producer-cdxgen-github-token) + - name: producer-cdxgen-astgen-ignore-file-pattern + value: $(params.producer-cdxgen-astgen-ignore-file-pattern) + - name: producer-cdxgen-astgen-ignore-dirs + value: $(params.producer-cdxgen-astgen-ignore-dirs) + params: + - name: producer-cdxgen-flags + type: array + default: [] + - name: producer-cdxgen-fetch-license + type: string + default: "false" + - name: producer-cdxgen-github-token + type: string + default: "" + - name: producer-cdxgen-astgen-ignore-file-pattern + type: string + default: "" + - name: producer-cdxgen-astgen-ignore-dirs + type: string + default: "" + target: + kind: Pipeline + # Add anchors to Task. + - patch: | + apiVersion: tekton.dev/v1beta1 + kind: Task + metadata: + name: producer-cdxgen + labels: + v1.dracon.ocurity.com/component: producer + spec: + params: + - name: anchors + type: array + description: A list of tasks that this task depends on using their anchors. + default: [] + results: + - name: anchor + description: An anchor to allow other tasks to depend on this task. + steps: + - name: anchor + image: docker.io/busybox:1.35.0 + script: echo "$(context.task.name)" > "$(results.anchor.path)" + target: + kind: Task + name: producer-cdxgen + # If we have a `source` task in the pipeline (added by a `source` component), + # depend on the completion of that source by referencing its anchor. + - patch: | + apiVersion: tekton.dev/v1beta1 + kind: Pipeline + metadata: + name: unused + spec: + tasks: + - name: producer-cdxgen + params: + - name: anchors + value: + - $(tasks.source.results.anchor) + target: + kind: Pipeline + annotationSelector: v1.dracon.ocurity.com/has-source=true + # If we have a producer-aggregator task in the pipeline (added by the + # producer-aggregator component), make it depend on the completion of this + # producer. + - patch: | + apiVersion: tekton.dev/v1beta1 + kind: Pipeline + metadata: + name: unused + spec: + tasks: + - name: producer-aggregator + params: + - name: anchors + value: + - $(tasks.producer-cdxgen.results.anchor) + target: + kind: Pipeline + annotationSelector: v1.dracon.ocurity.com/has-producer-aggregator=true + # Add scan information to Task. + - patch: | + apiVersion: tekton.dev/v1beta1 + kind: Task + metadata: + name: producer-cdxgen + labels: + v1.dracon.ocurity.com/component: producer + spec: + params: + - name: dracon_scan_id + type: string + - name: dracon_scan_start_time + type: string + steps: + - name: run-cdxgen + image: ghcr.io/cyclonedx/cdxgen:v9.8.10 + script: node /opt/cdxgen/bin/cdxgen.js -r -p -o /scratch/out.json $(workspaces.source-code-ws.path)/ --spec-version 1.4 + env: + - name: FETCH_LICENSE + value: $(params.producer-cdxgen-fetch-license) + - name: GITHUB_TOKEN + value: $(params.producer-cdxgen-github-token) + - name: ASTGEN_IGNORE_FILE_PATTERN + value: $(params.producer-cdxgen-astgen-ignore-file-pattern) + - name: ASTGEN_IGNORE_DIRS + value: $(params.producer-cdxgen-astgen-ignore-dirs) + - name: DRACON_SCAN_TIME + value: $(params.dracon_scan_start_time) + - name: DRACON_SCAN_ID + value: $(params.dracon_scan_id) + - name: produce-issues + image: ghcr.io/ocurity/dracon/components/producers/cdxgen/image:latest + env: + - name: DRACON_SCAN_TIME + value: $(params.dracon_scan_start_time) + - name: DRACON_SCAN_ID + value: $(params.dracon_scan_id) + target: + kind: Task + name: producer-cdxgen + - patch: | + apiVersion: tekton.dev/v1beta1 + kind: Pipeline + metadata: + name: unused + spec: + tasks: + - name: producer-cdxgen + params: + - name: dracon_scan_id + value: $(tasks.base.results.dracon-scan-id) + - name: dracon_scan_start_time + value: $(tasks.base.results.dracon-scan-start-time) + target: + kind: Pipeline diff --git a/components/producers/cdxgen/main.go b/components/producers/cdxgen/main.go new file mode 100644 index 000000000..eb96f1b9e --- /dev/null +++ b/components/producers/cdxgen/main.go @@ -0,0 +1,35 @@ +// Package main of the cdxgen producer parses the CycloneDX output of cdxgen and +// create a singular Dracon issue from it +package main + +import ( + "log" + + v1 "github.com/ocurity/dracon/api/proto/v1" + "github.com/ocurity/dracon/components/producers" + "github.com/ocurity/dracon/pkg/cyclonedx" +) + +func main() { + if err := producers.ParseFlags(); err != nil { + log.Fatal(err) + } + var results []*v1.Issue + inFile, err := producers.ReadInFile() + if err != nil { + log.Fatal("could not load file err:%s", err) + } + results, err = handleCycloneDX(inFile) + if err != nil { + log.Fatalf("could not parse cyclonedx document err:%s", err) + } + if err := producers.WriteDraconOut( + "cdxgen", results, + ); err != nil { + log.Fatal("could not write dracon out err:%s", err) + } +} + +func handleCycloneDX(inFile []byte) ([]*v1.Issue, error) { + return cyclonedx.ToDracon(inFile, "json") +} diff --git a/components/producers/cdxgen/task.yaml b/components/producers/cdxgen/task.yaml new file mode 100644 index 000000000..fa8e2dbf4 --- /dev/null +++ b/components/producers/cdxgen/task.yaml @@ -0,0 +1,57 @@ +--- +apiVersion: tekton.dev/v1beta1 +kind: Task +metadata: + name: producer-cdxgen + labels: + v1.dracon.ocurity.com/component: producer +spec: + params: + - name: producer-cdxgen-flags + type: array + default: [] + - name: producer-cdxgen-fetch-license + type: string + default: "false" + - name: producer-cdxgen-github-token + type: string + default: "" + - name: producer-cdxgen-astgen-ignore-file-pattern + type: string + default: "" + - name: producer-cdxgen-astgen-ignore-dirs + type: string + default: "" + volumes: + - name: scratch + emptyDir: {} + workspaces: + - name: source-code-ws + description: The workspace containing the source-code to scan. + steps: + - name: run-cdxgen + image: ghcr.io/cyclonedx/cdxgen:v9.8.10 + env: + - name: FETCH_LICENSE + value: $(params.producer-cdxgen-fetch-license) + - name: GITHUB_TOKEN + value: $(params.producer-cdxgen-github-token) + - name: ASTGEN_IGNORE_FILE_PATTERN + value: $(params.producer-cdxgen-astgen-ignore-file-pattern) + - name: ASTGEN_IGNORE_DIRS + value: $(params.producer-cdxgen-astgen-ignore-dirs) + script: node /opt/cdxgen/bin/cdxgen.js -r -p -o /scratch/out.json $(workspaces.source-code-ws.path)/ --spec-version 1.4 + volumeMounts: + - mountPath: /scratch + name: scratch + + - name: produce-issues + imagePullPolicy: IfNotPresent + image: ghcr.io/ocurity/dracon/components/producers/cdxgen/image:latest + command: ["app/components/producers/cdxgen/cdxgen-parser"] + args: + - "-in=/scratch/out.json" + - "-out=$(workspaces.source-code-ws.path)/.dracon/producers/cdxgen.pb" + volumeMounts: + - mountPath: /scratch + name: scratch diff --git a/examples/pipelines/cdxgen-project/BUILD b/examples/pipelines/cdxgen-project/BUILD new file mode 100644 index 000000000..4f9885a54 --- /dev/null +++ b/examples/pipelines/cdxgen-project/BUILD @@ -0,0 +1,17 @@ +subinclude("//build/defs:dracon") + +dracon_pipeline( + name = "cdxgen-project", + components = [ + "//components/base:k8s", + "//components/consumers/stdout-json:k8s", + "//components/enrichers/aggregator:k8s", + "//components/enrichers/policy:k8s", + "//components/producers/aggregator:k8s", + "//components/producers/cdxgen:k8s", + "//components/sources/git:k8s", + ], + kube_context = "//build/k8s/k3d:dracon", + kustomization_yaml = "kustomization.yaml", + pipelinerun = "pipelinerun/pipelinerun.yaml", +) diff --git a/examples/pipelines/cdxgen-project/kustomization.yaml b/examples/pipelines/cdxgen-project/kustomization.yaml new file mode 100644 index 000000000..34040a419 --- /dev/null +++ b/examples/pipelines/cdxgen-project/kustomization.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +nameSuffix: -cdxgen-project +namespace: dracon + +resources: + - ../../../components/base + +components: + - ../../../components/sources/git + + - ../../../components/producers/aggregator + - ../../../components/producers/cdxgen + - ../../../components/enrichers/aggregator + - ../../../components/enrichers/policy + - ../../../components/consumers/stdout-json diff --git a/examples/pipelines/cdxgen-project/pipelinerun/pipelinerun.yaml b/examples/pipelines/cdxgen-project/pipelinerun/pipelinerun.yaml new file mode 100644 index 000000000..81ab91e5d --- /dev/null +++ b/examples/pipelines/cdxgen-project/pipelinerun/pipelinerun.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + generateName: dracon-cdxgen-project- + namespace: dracon +spec: + pipelineRef: + name: dracon-cdxgen-project + params: + - name: repository_url + value: https://github.com/ocurity/e2e-monorepo.git + - name: b64-signature-key + # THIS IS AN EXAMPLE, PLEASE USE A PROPERLY SECURED SECRET KEY IN PRODUCTION + # Corresponding public key for verification is MOt7TFuLyGB9yRN5mcIeAPa6jKoFglkwEwGBTOVLeXI= + value: Lvbo+wAsW8Y4ENBA+lAikOwGTYAIXCQ49eRMEwClv94w63tMW4vIYH3JE3mZwh4A9rqMqgWCWTATAYFM5Ut5cg== + workspaces: + - name: source-code-ws + subPath: source-code + volumeClaimTemplate: + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/third_party/docker/cyclonedx/cdxgen/BUILD b/third_party/docker/cyclonedx/cdxgen/BUILD new file mode 100644 index 000000000..de18d4152 --- /dev/null +++ b/third_party/docker/cyclonedx/cdxgen/BUILD @@ -0,0 +1,8 @@ +subinclude("//build/defs:buildkit") + +buildkit_image_mirror( + name = "cdxgen", + digest = "sha256:3e3b983431338a55194e5c0e13b20812958bef8dbc7de60f905c386c054a65de", + repo = "ghcr.io/cyclonedx/cdxgen", + tags = ["v9.8.10"], +) diff --git a/third_party/go/github.com/CycloneDX/cyclonedx-go/BUILD b/third_party/go/github.com/CycloneDX/cyclonedx-go/BUILD index 3014f2048..3b4821c19 100644 --- a/third_party/go/github.com/CycloneDX/cyclonedx-go/BUILD +++ b/third_party/go/github.com/CycloneDX/cyclonedx-go/BUILD @@ -1,6 +1,6 @@ go_module( name = "cyclonedx-go", module = "github.com/CycloneDX/cyclonedx-go", - version = "v0.7.0", + version = "v0.7.2", visibility = ["PUBLIC"], )