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/main.go b/components/enrichers/depsdev/main.go index 961022378..157e685ff 100644 --- a/components/enrichers/depsdev/main.go +++ b/components/enrichers/depsdev/main.go @@ -160,7 +160,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{} @@ -254,7 +253,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/go.mod b/go.mod index b4a35b206..ace96c0f0 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/golang-migrate/migrate/v4 v4.15.1 github.com/golang/protobuf v1.5.2 github.com/google/uuid v1.3.0 + github.com/hairyhenderson/go-codeowners v0.4.0 github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.5 github.com/owenrumney/go-sarif/v2 v2.1.2 diff --git a/go.sum b/go.sum index 23c0095b9..c755a3b82 100644 --- a/go.sum +++ b/go.sum @@ -589,6 +589,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hairyhenderson/go-codeowners v0.4.0 h1:Wx/tRXb07sCyHeC8mXfio710Iu35uAy5KYiBdLHdv4Q= +github.com/hairyhenderson/go-codeowners v0.4.0/go.mod h1:iJgZeCt+W/GzXo5uchFCqvVHZY2T4TAIpvuVlKVkLxc= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 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"], +)