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/enrichers/codeowners/BUILD b/components/enrichers/codeowners/BUILD new file mode 100644 index 000000000..841be2b22 --- /dev/null +++ b/components/enrichers/codeowners/BUILD @@ -0,0 +1,36 @@ +subinclude( + "//build/defs:dracon", + "//build/defs:buildkit", +) + +go_binary( + name = "codeowners", + srcs = [ + "main.go", + ], + static = True, + deps = [ + "//api/proto/v1", + "//pkg/putil", + "//third_party/go/github.com/hairyhenderson/go-codeowners", + "//third_party/go/github.com/package-url/packageurl-go", + "//third_party/go/google.golang.org/protobuf", + ], +) + +buildkit_distroless_image( + name = "image", + srcs = [":codeowners"], + visibility = [ + "//examples/...", + ], +) + +dracon_component( + name = "codeowners", + images = [ + ":image", + ], + task = "task.yaml", + visibility = ["//examples/pipelines/..."], +) diff --git a/components/enrichers/codeowners/README.md b/components/enrichers/codeowners/README.md new file mode 100644 index 000000000..e7655200b --- /dev/null +++ b/components/enrichers/codeowners/README.md @@ -0,0 +1,5 @@ +# CodeOwners Enricher + +This enricher scans the cloned source for [CODEOWNERS](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners) files, +For each finding, it adds the following annotation. +"Owner-:" diff --git a/components/enrichers/codeowners/kustomization.yaml b/components/enrichers/codeowners/kustomization.yaml new file mode 100644 index 000000000..b2dddae5d --- /dev/null +++ b/components/enrichers/codeowners/kustomization.yaml @@ -0,0 +1,93 @@ +# 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: enricher-codeowners + taskRef: + name: enricher-codeowners + workspaces: + - name: source-code-ws + workspace: source-code-ws + params: + - name: enricher-codeowners-annotation + value: $(params.enricher-codeowners-annotation) + params: + - name: enricher-codeowners-annotation + type: string + default: "" + target: + kind: Pipeline + # Add anchors to Task. + - patch: | + apiVersion: tekton.dev/v1beta1 + kind: Task + metadata: + name: enricher-codeowners + labels: + v1.dracon.ocurity.com/component: enricher + 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: enricher-codeowners + # If we have an producer-aggregator task in the pipeline (added by the + # producer-aggregator component), make the enricher depend on the completion of + # it. + - patch: | + apiVersion: tekton.dev/v1beta1 + kind: Pipeline + metadata: + name: unused + spec: + tasks: + - name: enricher-codeowners + params: + - name: anchors + value: + - $(tasks.producer-aggregator.results.anchor) + target: + kind: Pipeline + annotationSelector: v1.dracon.ocurity.com/has-producer-aggregator=true + # If we have a enricher-aggregator task in the pipeline (added by the + # enricher-aggregator component), make it depend on the completion of this + # enricher. + - patch: | + apiVersion: tekton.dev/v1beta1 + kind: Pipeline + metadata: + name: unused + spec: + tasks: + - name: enricher-aggregator + params: + - name: anchors + value: + - $(tasks.enricher-codeowners.results.anchor) + target: + kind: Pipeline + annotationSelector: v1.dracon.ocurity.com/has-enricher-aggregator=true diff --git a/components/enrichers/codeowners/main.go b/components/enrichers/codeowners/main.go new file mode 100644 index 000000000..d5de544a0 --- /dev/null +++ b/components/enrichers/codeowners/main.go @@ -0,0 +1,125 @@ +// Package main of the codeowners enricher +// handles enrichment of individual issues with +// the groups/usernames listed in the github repository +// CODEOWNERS files. +// Owners are matched against the "target" field of the issue +package main + +import ( + "flag" + "fmt" + "log" + "os" + "path/filepath" + "strings" + "time" + + owners "github.com/hairyhenderson/go-codeowners" + v1 "github.com/ocurity/dracon/api/proto/v1" + "github.com/ocurity/dracon/pkg/putil" +) + +const defaultAnnotation = "Owner" + +var ( + readPath string + writePath string + repoBasePath string + annotation string +) + +func lookupEnvOrString(key string, defaultVal string) string { + if val, ok := os.LookupEnv(key); ok { + return val + } + return defaultVal +} + +func enrichIssue(i *v1.Issue) (*v1.EnrichedIssue, error) { + enrichedIssue := v1.EnrichedIssue{} + annotations := map[string]string{} + targets := []string{} + if i.GetCycloneDXSBOM() != "" { + // shortcut, if there is a CycloneDX BOM then there is no target. + // we get the url from the repoURL parameter + targets = []string{"."} + } else { + target := strings.Split(i.GetTarget(), ":") + if len(target) > 1 { + targets = append(targets, target[0]) + } else { + targets = append(targets, i.GetTarget()) + } + } + for _, target := range targets { + path := filepath.Join(repoBasePath, target) + c, err := owners.FromFile(repoBasePath) + if err != nil { + log.Println("could not instantiate owners for path", path, "err", err) + continue + } + owners := c.Owners(path) + for _, owner := range owners { + annotations[fmt.Sprintf("Owner-%d", len(annotations))] = owner + } + } + + enrichedIssue = v1.EnrichedIssue{ + RawIssue: i, + Annotations: annotations, + } + enrichedIssue.Annotations = annotations + return &enrichedIssue, nil +} + +func run() { + res, err := putil.LoadTaggedToolResponse(readPath) + if err != nil { + log.Fatalf("could not load tool response from path %s , error:%v", readPath, err) + } + if annotation == "" { + annotation = defaultAnnotation + } + for _, r := range res { + enrichedIssues := []*v1.EnrichedIssue{} + for _, i := range r.GetIssues() { + eI, err := enrichIssue(i) + if err != nil { + log.Println(err) + continue + } + enrichedIssues = append(enrichedIssues, eI) + } + if len(enrichedIssues) > 0 { + if err := putil.WriteEnrichedResults(r, enrichedIssues, + filepath.Join(writePath, fmt.Sprintf("%s.depsdev.enriched.pb", r.GetToolName())), + ); err != nil { + log.Fatal(err) + } + } else { + log.Println("no enriched issues were created for", r.GetToolName()) + } + if len(r.GetIssues()) > 0 { + scanStartTime := r.GetScanInfo().GetScanStartTime().AsTime() + if err := putil.WriteResults( + r.GetToolName(), + r.GetIssues(), + filepath.Join(writePath, fmt.Sprintf("%s.raw.pb", r.GetToolName())), + r.GetScanInfo().GetScanUuid(), + scanStartTime.Format(time.RFC3339), + ); err != nil { + log.Fatalf("could not write results: %s", err) + } + } + + } +} + +func main() { + flag.StringVar(&readPath, "read_path", lookupEnvOrString("READ_PATH", ""), "where to find producer results") + flag.StringVar(&writePath, "write_path", lookupEnvOrString("WRITE_PATH", ""), "where to put enriched results") + flag.StringVar(&annotation, "annotation", lookupEnvOrString("ANNOTATION", defaultAnnotation), "what is the annotation this enricher will add to the issues, by default `Enriched Licenses`") + flag.StringVar(&repoBasePath, "repoBasePath", lookupEnvOrString("REPO_BASE_PATH", ""), `the base path of the repository, this is most likely an internally set variable`) + flag.Parse() + run() +} diff --git a/components/enrichers/codeowners/task.yaml b/components/enrichers/codeowners/task.yaml new file mode 100644 index 000000000..3e69c5b39 --- /dev/null +++ b/components/enrichers/codeowners/task.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: tekton.dev/v1beta1 +kind: Task +metadata: + name: enricher-codeowners + labels: + v1.dracon.ocurity.com/component: enricher +spec: + params: + - name: enricher-codeowners-annotation + type: string + default: "" + + workspaces: + - name: source-code-ws + description: The workspace containing the source-code to scan. + steps: + - name: run-enricher + imagePullPolicy: IfNotPresent + image: ghcr.io/ocurity/dracon/components/enrichers/codeowners/image:latest + command: ["app/components/enrichers/codeowners/codeowners"] + env: + - name: READ_PATH + value: $(workspaces.source-code-ws.path)/.dracon/producers + - name: WRITE_PATH + value: "$(workspaces.source-code-ws.path)/.dracon/enrichers/codeowners" + - name: REPO_BASE_PATH + value: "$(workspaces.source-code-ws.path)/" + - name: ANNOTATION + value: "$(params.enricher-codeowners-annotation)" \ No newline at end of file diff --git a/components/enrichers/depsdev/kustomization.yaml b/components/enrichers/depsdev/kustomization.yaml index c12ff0f68..e0e8d6207 100644 --- a/components/enrichers/depsdev/kustomization.yaml +++ b/components/enrichers/depsdev/kustomization.yaml @@ -25,12 +25,17 @@ patches: params: - name: enricher-depsdev-licenses-in-evidence value: $(params.enricher-depsdev-licenses-in-evidence) + - name: enricher-depsdev-scorecard-info + value: $(params.enricher-depsdev-scorecard-info) - name: enricher-depsdev-annotation value: $(params.enricher-depsdev-annotation) params: - name: enricher-depsdev-licenses-in-evidence type: string default: "false" + - name: enricher-depsdev-scorecard-info + type: string + default: "true" - name: enricher-depsdev-annotation type: string default: "" diff --git a/components/enrichers/depsdev/main.go b/components/enrichers/depsdev/main.go index 74341cece..01982471b 100644 --- a/components/enrichers/depsdev/main.go +++ b/components/enrichers/depsdev/main.go @@ -30,6 +30,7 @@ var ( annotation string ) +// Check is a deps.dev ScoreCardV2 check type Check struct { Name string `json:"name,omitempty"` Documentation struct { @@ -40,6 +41,7 @@ type Check struct { Reason string `json:"reason,omitempty"` Details []interface{} `json:"details,omitempty"` } +// ScorecardV2 is a deps.dev ScoreCardV2 result type ScorecardV2 struct { Date string `json:"date,omitempty"` Repo struct { @@ -54,6 +56,7 @@ type ScorecardV2 struct { Metadata []interface{} `json:"metadata,omitempty"` Score float64 `json:"score,omitempty"` } +// Project is a deps.dev project type Project struct { Type string `json:"type,omitempty"` Name string `json:"name,omitempty"` @@ -67,6 +70,8 @@ type Project struct { Link string `json:"link,omitempty"` ScorecardV2 ScorecardV2 `json:"scorecardV2,omitempty"` } + +// Version is a deps.dev version, main object in the response type Version struct { Version string `json:"version,omitempty"` SymbolicVersions []interface{} `json:"symbolicVersions,omitempty"` @@ -153,7 +158,6 @@ func addDepsDevLink(component cdx.Component) (cdx.Component, error) { return component, nil } - func addDepsDevInfo(component cdx.Component, annotations map[string]string) (cdx.Component, map[string]string, error) { var depsResp Response licenses := cdx.Licenses{} @@ -247,7 +251,6 @@ func enrichIssue(i *v1.Issue) (*v1.EnrichedIssue, error) { log.Println(err) continue } - // TODO(): enrich with vulnerability info whenever a consumer supports showing arbitrary properties in components } newComponents = append(newComponents, newComp) diff --git a/components/enrichers/depsdev/main_test.go b/components/enrichers/depsdev/main_test.go index 7aae60287..d92a4e0c5 100644 --- a/components/enrichers/depsdev/main_test.go +++ b/components/enrichers/depsdev/main_test.go @@ -84,6 +84,7 @@ func setup(t *testing.T) (string, *httptest.Server) { Projects: []Project{ { ScorecardV2: ScorecardV2{ + Date: "irrelevant", Score: 5.5, Check: []Check{ { @@ -105,11 +106,10 @@ func setup(t *testing.T) (string, *httptest.Server) { return dir, srv } - func TestParseIssuesDepsDevScoreCardInfoWritten(t *testing.T) { dir, srv := setup(t) defer srv.Close() - + scoreCardInfo = "true" // run enricher run() assert.FileExists(t, dir+"/depsdevSAT.depsdev.enriched.pb", "file was not created") @@ -119,33 +119,31 @@ func TestParseIssuesDepsDevScoreCardInfoWritten(t *testing.T) { assert.NoError(t, err, "could not read enriched file") res := v1.EnrichedLaunchToolResponse{} proto.Unmarshal(pbBytes, &res) - expectedExternalReferences := []cdx.ExternalReference{ - { - URL: "http://127.0.0.1:46679//go/p/cloud.google.com%2Fgo%2Fcompute/v/v1.14.0", - Type: "other", - }, { - URL: "http://127.0.0.1:46679//go/p/cloud.google.com%2Fgo%2Fcompute%2Fmetadata/v/v0.2.3", - Type: "other", - }, { - URL: "http://127.0.0.1:46679//go/p/github.com%2FAzure%2Fazure-pipeline-go/v/v0.2.3", - Type: "other", - }, + expectedProperties := []cdx.Property{ + {Name: "aquasecurity:trivy:PkgType", Value: "gomod"}, + {Name: "ScorecardScore", Value: "5.500000"}, + {Name: "ScorecardInfo", Value: "{\n\t\"date\": \"irrelevant\",\n\t\"repo\": {},\n\t\"scorecard\": {},\n\t\"check\": [\n\t\t{\n\t\t\t\"name\": \"foo\",\n\t\t\t\"documentation\": {},\n\t\t\t\"score\": 2,\n\t\t\t\"reason\": \"bar\"\n\t\t}\n\t],\n\t\"score\": 5.5\n}"}, + {Name: "aquasecurity:trivy:PkgType", Value: "gomod"}, + {Name: "ScorecardScore", Value: "5.500000"}, + {Name: "ScorecardInfo", Value: "{\n\t\"date\": \"irrelevant\",\n\t\"repo\": {},\n\t\"scorecard\": {},\n\t\"check\": [\n\t\t{\n\t\t\t\"name\": \"foo\",\n\t\t\t\"documentation\": {},\n\t\t\t\"score\": 2,\n\t\t\t\"reason\": \"bar\"\n\t\t}\n\t],\n\t\"score\": 5.5\n}"}, + {Name: "aquasecurity:trivy:PkgType", Value: "gomod"}, + {Name: "ScorecardScore", Value: "5.500000"}, + {Name: "ScorecardInfo", Value: "{\n\t\"date\": \"irrelevant\",\n\t\"repo\": {},\n\t\"scorecard\": {},\n\t\"check\": [\n\t\t{\n\t\t\t\"name\": \"foo\",\n\t\t\t\"documentation\": {},\n\t\t\t\"score\": 2,\n\t\t\t\"reason\": \"bar\"\n\t\t}\n\t],\n\t\"score\": 5.5\n}"}, } // ensure every component has a license attached to it for _, finding := range res.Issues { bom, err := cyclonedx.FromDracon(finding.RawIssue) assert.NoError(t, err, "Could not read enriched cyclone dx info") - externalReferences := []cdx.ExternalReference{} + properties := []cdx.Property{} for _, component := range *bom.Components { - externalReferences = append(externalReferences, *component.ExternalReferences...) + properties = append(properties, *component.Properties...) } - assert.Equal(t, externalReferences, expectedExternalReferences) + assert.Equal(t, properties, expectedProperties) } } - func TestParseIssuesDepsDevExternalReferenceLinksWritten(t *testing.T) { dir, srv := setup(t) defer srv.Close() @@ -185,7 +183,6 @@ func TestParseIssuesDepsDevExternalReferenceLinksWritten(t *testing.T) { } } - func TestParseIssuesLicensesWritten(t *testing.T) { dir, srv := setup(t) defer srv.Close() 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/go.mod b/go.mod index b80df1fc4..ace96c0f0 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( github.com/klauspost/asmfmt v1.3.2 // indirect github.com/klauspost/compress v1.15.9 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect - github.com/kr/text v0.2.0 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect diff --git a/go.sum b/go.sum index 33c375216..c755a3b82 100644 --- a/go.sum +++ b/go.sum @@ -712,6 +712,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= @@ -859,6 +860,7 @@ github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0 github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -905,6 +907,7 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 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"], ) diff --git a/third_party/go/github.com/hairyhenderson/go-codeowners/BUILD b/third_party/go/github.com/hairyhenderson/go-codeowners/BUILD new file mode 100644 index 000000000..9a41a2486 --- /dev/null +++ b/third_party/go/github.com/hairyhenderson/go-codeowners/BUILD @@ -0,0 +1,6 @@ +go_module( + name = "go-codeowners", + module = "github.com/hairyhenderson/go-codeowners", + version = "v0.4.0", + visibility = ["PUBLIC"], +)