Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

393 fix producer base path #395

Merged
merged 3 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions components/enrichers/custom-annotation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Custom Annotation Enricher

This enricher adds a set of custom annotations to every finding
Useful for development purposes and for tagging results
Can be used multiple times to add multiple annotations
northdpole marked this conversation as resolved.
Show resolved Hide resolved
81 changes: 81 additions & 0 deletions components/enrichers/custom-annotation/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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 (
"encoding/json"
"flag"
"log"
"log/slog"
"strings"

"github.com/go-errors/errors"

apiv1 "github.com/ocurity/dracon/api/proto/v1"
"github.com/ocurity/dracon/components/enrichers"
)

var (
defaultName = "custom-annotation"
annotations string
name string
)

func enrichIssue(i *apiv1.Issue, annotations string) (*apiv1.EnrichedIssue, error) {
enrichedIssue := apiv1.EnrichedIssue{}
annotationMap := map[string]string{}
if err := json.Unmarshal([]byte(annotations), &annotationMap); err != nil {
return nil, errors.Errorf("could not unmarshall annotation object to map[string]string, err: %w", err)
}
enrichedIssue = apiv1.EnrichedIssue{
RawIssue: i,
Annotations: annotationMap,
}
return &enrichedIssue, nil
}

func run(name, annotations string) error {
res, err := enrichers.LoadData()
if err != nil {
return err
}
if annotations == "" {
slog.Info("annotations is empty")
}
for _, r := range res {
slog.Info("processing results for ", slog.Any("scan", r.ScanInfo))
enrichedIssues := make([]*apiv1.EnrichedIssue, 0, len(res))
for _, i := range r.GetIssues() {
eI, err := enrichIssue(i, annotations)
if err != nil {
slog.Error(err.Error())
northdpole marked this conversation as resolved.
Show resolved Hide resolved
continue
}
enrichedIssues = append(enrichedIssues, eI)
}

err := enrichers.WriteData(&apiv1.EnrichedLaunchToolResponse{
OriginalResults: r,
Issues: enrichedIssues,
}, strings.ReplaceAll(name, " ", "-"))
if err != nil {
return err
northdpole marked this conversation as resolved.
Show resolved Hide resolved
}
}
return nil
}

func main() {
flag.StringVar(&annotations, "annotations", enrichers.LookupEnvOrString("ANNOTATIONS", ""), "what are the annotations this enricher will add to the issues")
flag.StringVar(&name, "annotation-name", enrichers.LookupEnvOrString("NAME", defaultName), "what is the name this enricher will masquerade as")

if err := enrichers.ParseFlags(); err != nil {
log.Fatal(err)
}
if err := run(name, annotations); err != nil {
log.Fatal(err)
}
}
140 changes: 140 additions & 0 deletions components/enrichers/custom-annotation/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package main

import (
"fmt"
"os"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/testing/protocmp"

draconv1 "github.com/ocurity/dracon/api/proto/v1"
"github.com/ocurity/dracon/components/enrichers"
)

func TestHandlesZeroFindings(t *testing.T) {
indir, outdir := enrichers.SetupIODirs(t)
mockLaunchToolResponses := enrichers.GetEmptyLaunchToolResponse(t)
for i, r := range mockLaunchToolResponses {
// Write sample enriched responses to indir
encodedProto, err := proto.Marshal(r)
require.NoError(t, err)
rwPermission600 := os.FileMode(0o600)
require.NoError(t, os.WriteFile(fmt.Sprintf("%s/input_%d_%s.tagged.pb", indir, i, r.ToolName), encodedProto, rwPermission600))
}

// Run the enricher
enrichers.SetReadPathForTests(indir)
enrichers.SetWritePathForTests(outdir)
require.NoError(t, run("foo", `{"foo":"bar"}`))

// Check there is something in our output directory
files, err := os.ReadDir(outdir)
require.NoError(t, err)
require.NotEmpty(t, files)
require.Len(t, files, 4)

// Check that both of them are EnrichedLaunchToolResponse
// and their Issue property is an empty list
for _, f := range files {
if strings.HasSuffix(f.Name(), ".raw.pb") {
continue
}

encodedProto, err := os.ReadFile(fmt.Sprintf("%s/%s", outdir, f.Name()))
require.NoError(t, err)
output := &draconv1.EnrichedLaunchToolResponse{}
require.NoError(t, proto.Unmarshal(encodedProto, output))
require.Empty(t, output.Issues)
}
}

func TestHandlesFindings(t *testing.T) {
indir, outdir := enrichers.SetupIODirs(t)
annotations = `{"foo":"bar","a":"b","1":"2"}`
name = "enricherName"

mockLaunchToolResponses := enrichers.GetLaunchToolResponse(t)
for i, r := range mockLaunchToolResponses {
// Write sample enriched responses to indir
encodedProto, err := proto.Marshal(r)
require.NoError(t, err)
rwPermission600 := os.FileMode(0o600)
require.NoError(t, os.WriteFile(fmt.Sprintf("%s/input_%d_%s.tagged.pb", indir, i, r.ToolName), encodedProto, rwPermission600))
}

// Run the enricher
enrichers.SetReadPathForTests(indir)
enrichers.SetWritePathForTests(outdir)
require.NoError(t, run(name, annotations))

// Check there is something in our output directory
files, err := os.ReadDir(outdir)
require.NoError(t, err)
require.NotEmpty(t, files)
require.Len(t, files, 4)

// Check that both of them are EnrichedLaunchToolResponse
// and their Issue property is not an empty list
expected := map[string]*draconv1.EnrichedLaunchToolResponse{
"tool1": {
OriginalResults: mockLaunchToolResponses[0],
Issues: []*draconv1.EnrichedIssue{
{
RawIssue: mockLaunchToolResponses[0].Issues[0],
Annotations: map[string]string{
"foo": "bar",
"a": "b",
"1": "2",
},
},
{
RawIssue: mockLaunchToolResponses[0].Issues[1],
Annotations: map[string]string{
"foo": "bar",
"a": "b",
"1": "2",
},
},
},
},
"tool2": {
OriginalResults: mockLaunchToolResponses[1],
Issues: []*draconv1.EnrichedIssue{
{
RawIssue: mockLaunchToolResponses[1].Issues[0],
Annotations: map[string]string{
"foo": "bar",
"a": "b",
"1": "2",
},
},
{
RawIssue: mockLaunchToolResponses[1].Issues[1],
Annotations: map[string]string{
"foo": "bar",
"a": "b",
"1": "2",
},
},
},
},
}
var actual draconv1.EnrichedLaunchToolResponse
for _, f := range files {
if strings.HasSuffix(f.Name(), ".raw.pb") {
continue
}
encodedProto, err := os.ReadFile(fmt.Sprintf("%s/%s", outdir, f.Name()))
require.NoError(t, err)

require.NoError(t, proto.Unmarshal(encodedProto, &actual))
if !proto.Equal(&actual, expected[actual.OriginalResults.ToolName]) {
require.True(t, proto.Equal(&actual, expected[actual.OriginalResults.ToolName]),
cmp.Diff(&actual, expected[actual.OriginalResults.ToolName], protocmp.Transform()))
}
}
}
35 changes: 35 additions & 0 deletions components/enrichers/custom-annotation/task.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: enricher-custom-annotation
labels:
v1.dracon.ocurity.com/component: enricher
spec:
description: Adds a set of custom annotations to all issues that pass through this
params:
- name: enricher-custom-annotation-base-annotation
type: string
default: ""
description: "a comma separated list of key:value pairs"
- name: enricher-custom-annotation-name
type: string
default: "custom-annotation"
description: "the name to masquerade as, useful when running multiple instances"
workspaces:
- name: output
description: The workspace where we can output results
steps:
- name: run-enricher
imagePullPolicy: IfNotPresent
image: '{{ default "ghcr.io/ocurity/dracon" .Values.image.registry }}/components/enrichers/custom-annotation:{{ .Chart.AppVersion }}'
command: ["/app/components/enrichers/custom-annotation/custom-annotation"]
env:
- name: READ_PATH
value: $(workspaces.output.path)/.dracon/producers
- name: WRITE_PATH
value: "$(workspaces.output.path)/.dracon/enrichers/custom-annotation"
- name: ANNOTATIONS
value: "$(params.enricher-custom-annotation-base-annotation)"
- name: NAME
value: "$(params.enricher-custom-annotation-name)"
76 changes: 76 additions & 0 deletions components/enrichers/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,79 @@ func GetEmptyLaunchToolResponse(_ *testing.T) []*draconv1.LaunchToolResponse {
},
}
}

// GetEmptyLaunchToolResponse returns a slice of LaunchToolResponse with no issues
func GetLaunchToolResponse(_ *testing.T) []*draconv1.LaunchToolResponse {
code := `this
is
some
code`
return []*draconv1.LaunchToolResponse{
{
ToolName: "tool1",
Issues: []*draconv1.Issue{
{
Target: "file:/a/b/c/d.php:1-2",
Type: "sometype",
Title: "this is a title",
Severity: draconv1.Severity_SEVERITY_CRITICAL,
Cvss: 1.0,
Confidence: draconv1.Confidence_CONFIDENCE_CRITICAL,
Description: "this is a handy dandy description",
Source: "this is a source",
Cve: "CVE-2020-123",
Uuid: "d9681ae9-223b-4df8-a422-7b29bb917a36",
Cwe: []int32{123},
ContextSegment: &code,
},
{
Target: "file:/a/b/c/d.go:2-3",
Type: "sometype1",
Title: "this is a title1",
Severity: draconv1.Severity_SEVERITY_CRITICAL,
Cvss: 1.0,
Confidence: draconv1.Confidence_CONFIDENCE_CRITICAL,
Description: "this is a handy dandy description1",
Source: "this is a source1",
Cve: "CVE-2020-124",
Uuid: "a9681ae9-223b-4df8-a422-7b29bb917a36",
Cwe: []int32{123},
ContextSegment: &code,
},
},
},
{
ToolName: "tool2",
Issues: []*draconv1.Issue{
{
Target: "file:/a/b/c/d.py:1-2",
Type: "sometype",
Title: "this is a title",
Severity: draconv1.Severity_SEVERITY_CRITICAL,
Cvss: 1.0,
Confidence: draconv1.Confidence_CONFIDENCE_CRITICAL,
Description: "this is a handy dandy description",
Source: "this is a source",
Cve: "CVE-2020-123",
Uuid: "q9681ae9-223b-4df8-a422-7b29bb917a36",
Cwe: []int32{123},
ContextSegment: &code,
},
{
Target: "file:/a/b/c/d.py:2-3",
Type: "sometype1",
Title: "this is a title1",
Severity: draconv1.Severity_SEVERITY_CRITICAL,
Cvss: 1.0,
Confidence: draconv1.Confidence_CONFIDENCE_CRITICAL,
Description: "this is a handy dandy description1",
Source: "this is a source1",
Cve: "CVE-2020-124",
Uuid: "w9681ae9-223b-4df8-a422-7b29bb917a36",
Cwe: []int32{123},
ContextSegment: &code,
},
},
},
}
}
10 changes: 5 additions & 5 deletions components/producers/producer.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ var (
)

const (
sourceDir = "/workspace/output"
SourceDir = "/workspace/output/source-code/"
)

var fileTargetPattern = regexp.MustCompile(`^(.*?:.*?):(.*)$`)
Expand Down Expand Up @@ -102,9 +102,9 @@ func WriteDraconOut(
source := getSource()
cleanIssues := []*draconapiv1.Issue{}
for _, iss := range issues {
iss.Description = strings.ReplaceAll(iss.Description, sourceDir, ".")
iss.Title = strings.ReplaceAll(iss.Title, sourceDir, ".")
iss.Target = strings.ReplaceAll(iss.Target, sourceDir, ".")
iss.Description = strings.ReplaceAll(iss.Description, SourceDir, "")
iss.Title = strings.ReplaceAll(iss.Title, SourceDir, "")
iss.Target = strings.ReplaceAll(iss.Target, SourceDir, "")
iss.Source = source
cleanIssues = append(cleanIssues, iss)
slog.Debug(fmt.Sprintf("found issue: %+v\n", iss))
Expand Down Expand Up @@ -135,7 +135,7 @@ func WriteDraconOut(
}

func getSource() string {
sourceMetaPath := filepath.Join(sourceDir, ".source.dracon")
sourceMetaPath := filepath.Join(SourceDir, ".source.dracon")
_, err := os.Stat(sourceMetaPath)
if os.IsNotExist(err) {
return "unknown"
Expand Down
Loading