From ce067c43e1a59a53648c230e3c2f87a7fe73f33c Mon Sep 17 00:00:00 2001 From: sg Date: Wed, 9 Oct 2024 14:05:38 +0100 Subject: [PATCH 1/2] close feature 405, modelscan component --- .../producers/modelscan/examples/input.json | 59 ++++++++++ .../producers/modelscan/examples/modelscan.pb | 6 + components/producers/modelscan/main.go | 110 ++++++++++++++++++ components/producers/modelscan/main_test.go | 109 +++++++++++++++++ components/producers/modelscan/task.yaml | 64 ++++++++++ 5 files changed, 348 insertions(+) create mode 100644 components/producers/modelscan/examples/input.json create mode 100644 components/producers/modelscan/examples/modelscan.pb create mode 100644 components/producers/modelscan/main.go create mode 100644 components/producers/modelscan/main_test.go create mode 100644 components/producers/modelscan/task.yaml diff --git a/components/producers/modelscan/examples/input.json b/components/producers/modelscan/examples/input.json new file mode 100644 index 000000000..b1ee9a456 --- /dev/null +++ b/components/producers/modelscan/examples/input.json @@ -0,0 +1,59 @@ +{ + "modelscan_version": "0.5.0", + "timestamp": "2024-01-25T17:56:00.855056", + "input_path": "/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl", + "total_issues": 1, + "summary": { + "total_issues_by_severity": { + "LOW": 0, + "MEDIUM": 0, + "HIGH": 0, + "CRITICAL": 1 + } + }, + "issues_by_severity": { + "CRITICAL": [ + { + "description": "Use of unsafe operator 'system' from module 'posix'", + "operator": "system", + "module": "posix", + "source": "/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl", + "scanner": "modelscan.scanners.PickleUnsafeOpScan" + } + ], + "MEDIUM": [ + { + "description": "Use of unsafe operator 'system' from module 'posix'", + "operator": "system", + "module": "posix", + "source": "/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl", + "scanner": "modelscan.scanners.PickleUnsafeOpScan" + } + ], + "HIGH": [ + { + "description": "Use of unsafe operator 'system' from module 'posix'", + "operator": "system", + "module": "posix", + "source": "/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl", + "scanner": "modelscan.scanners.PickleUnsafeOpScan" + } + ], + "LOW": [ + { + "description": "Use of unsafe operator 'system' from module 'posix'", + "operator": "system", + "module": "posix", + "source": "/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl", + "scanner": "modelscan.scanners.PickleUnsafeOpScan" + } + ] + }, + "errors": [], + "scanned": { + "total_scanned": 4, + "scanned_files": [ + "/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl" + ] + } +} diff --git a/components/producers/modelscan/examples/modelscan.pb b/components/producers/modelscan/examples/modelscan.pb new file mode 100644 index 000000000..73dbec886 --- /dev/null +++ b/components/producers/modelscan/examples/modelscan.pb @@ -0,0 +1,6 @@ + + ¢Í™¸í–ž²gosecë +O/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl%modelscan.scanners.PickleUnsafeOpScan3Use of unsafe operator 'system' from module 'posix':3Use of unsafe operator 'system' from module 'posix'Bunknownë +O/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl%modelscan.scanners.PickleUnsafeOpScan3Use of unsafe operator 'system' from module 'posix':3Use of unsafe operator 'system' from module 'posix'Bunknownë +O/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl%modelscan.scanners.PickleUnsafeOpScan3Use of unsafe operator 'system' from module 'posix':3Use of unsafe operator 'system' from module 'posix'Bunknownë +O/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl%modelscan.scanners.PickleUnsafeOpScan3Use of unsafe operator 'system' from module 'posix':3Use of unsafe operator 'system' from module 'posix'Bunknown \ No newline at end of file diff --git a/components/producers/modelscan/main.go b/components/producers/modelscan/main.go new file mode 100644 index 000000000..a08833210 --- /dev/null +++ b/components/producers/modelscan/main.go @@ -0,0 +1,110 @@ +package main + +import ( + "encoding/json" + "log" + "log/slog" + + v1 "github.com/ocurity/dracon/api/proto/v1" + + "github.com/ocurity/dracon/components/producers" +) + +func main() { + if err := producers.ParseFlags(); err != nil { + log.Fatal(err) + } + + inFile, err := producers.ReadInFile() + if err != nil { + log.Fatal(err) + } + + var results ModelScanOut + if err := json.Unmarshal(inFile, &results); err != nil { + log.Fatal(err) + } + + issues, err := parseIssues(&results) + if err != nil { + log.Fatal(err) + } + if err := producers.WriteDraconOut( + "modelscan", + issues, + ); err != nil { + log.Fatal(err) + } +} + +func parseIssues(out *ModelScanOut) ([]*v1.Issue, error) { + issues := make([]*v1.Issue, 0, len(out.Issues)) + slog.Info("found Critical issues", slog.Int("numCrit", out.Summary.TotalIssuesBySeverity.Critical)) + slog.Info("found High issues", slog.Int("numCrit", out.Summary.TotalIssuesBySeverity.High)) + slog.Info("found Medium issues", slog.Int("numCrit", out.Summary.TotalIssuesBySeverity.Medium)) + slog.Info("found Low issues", slog.Int("numCrit", out.Summary.TotalIssuesBySeverity.Low)) + for _, issue := range out.Issues { + issues = append(issues, + &v1.Issue{ + Target: "file:///" + issue.Source, + Type: issue.Scanner, + Description: issue.Description, + Title: issue.Description, + Severity: modelScanSeverityToDracon(issue.Severity), + Confidence: v1.Confidence_CONFIDENCE_UNSPECIFIED, + }) + } + return issues, nil +} + +func modelScanSeverityToDracon(severity string) v1.Severity { + switch severity { + case "CRITICAL": + return v1.Severity_SEVERITY_CRITICAL + case "HIGH": + return v1.Severity_SEVERITY_HIGH + case "MEDIUM": + return v1.Severity_SEVERITY_MEDIUM + case "LOW": + return v1.Severity_SEVERITY_LOW + default: + return v1.Severity_SEVERITY_UNSPECIFIED + } +} + +type ModelScanOut struct { + Summary ModelScanSummary `json:"summary,omitempty"` + Issues []ModelScanIssue `json:"issues,omitempty"` + Errors []any `json:"errors,omitempty"` +} + +type ModelScanIssue struct { + Description string `json:"description,omitempty"` + Operator string `json:"operator,omitempty"` + Module string `json:"module,omitempty"` + Source string `json:"source,omitempty"` + Scanner string `json:"scanner,omitempty"` + Severity string `json:"severity,omitempty"` +} + +type ModelScanSummary struct { + TotalIssuesBySeverity TotalIssuesBySeverity `json:"total_issues_by_severity,omitempty"` + TotalIssues int `json:"total_issues,omitempty"` + InputPath string `json:"input_path,omitempty"` + AbsolutePath string `json:"absolute_path,omitempty"` + ModelscanVersion string `json:"modelscan_version,omitempty"` + Timestamp string `json:"timestamp,omitempty"` + Scanned Scanned `json:"scanned,omitempty"` +} + +type TotalIssuesBySeverity struct { + Low int `json:"LOW,omitempty"` + Medium int `json:"MEDIUM,omitempty"` + High int `json:"HIGH,omitempty"` + Critical int `json:"CRITICAL,omitempty"` +} + +type Scanned struct { + TotalScanned int `json:"total_scanned,omitempty"` + ScannedFiles []string `json:"scanned_files,omitempty"` +} diff --git a/components/producers/modelscan/main_test.go b/components/producers/modelscan/main_test.go new file mode 100644 index 000000000..e3920f70a --- /dev/null +++ b/components/producers/modelscan/main_test.go @@ -0,0 +1,109 @@ +package main + +import ( + "encoding/json" + "testing" + + v1 "github.com/ocurity/dracon/api/proto/v1" + + "github.com/stretchr/testify/require" +) + +func TestParseIssues(t *testing.T) { + var results ModelScanOut + err := json.Unmarshal([]byte(modelScanOut), &results) + require.NoError(t, err) + + issues, err := parseIssues(&results) + require.NoError(t, err) + expectedIssue := []*v1.Issue{ + + { + Target: "/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl", + Type: "modelscan.scanners.PickleUnsafeOpScan", + Title: "Use of unsafe operator 'system' from module 'posix'", + Description: "Use of unsafe operator 'system' from module 'posix'", + }, + { + Target: "/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl", + Type: "modelscan.scanners.PickleUnsafeOpScan", + Title: "Use of unsafe operator 'system' from module 'posix'", + Description: "Use of unsafe operator 'system' from module 'posix'", + }, + { + Target: "/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl", + Type: "modelscan.scanners.PickleUnsafeOpScan", + Title: "Use of unsafe operator 'system' from module 'posix'", + Description: "Use of unsafe operator 'system' from module 'posix'", + }, + { + Target: "/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl", + Type: "modelscan.scanners.PickleUnsafeOpScan", + Title: "Use of unsafe operator 'system' from module 'posix'", + Description: "Use of unsafe operator 'system' from module 'posix'", + }, + } + + require.Equal(t, expectedIssue, issues) +} + +const modelScanOut = `{ + "modelscan_version": "0.5.0", + "timestamp": "2024-01-25T17:56:00.855056", + "input_path": "/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl", + "total_issues": 4, + "summary": { + "total_issues_by_severity": { + "LOW": 1, + "MEDIUM": 1, + "HIGH": 1, + "CRITICAL": 1 + } + }, + "issues_by_severity": { + "CRITICAL": [ + { + "description": "Use of unsafe operator 'system' from module 'posix'", + "operator": "system", + "module": "posix", + "source": "/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl", + "scanner": "modelscan.scanners.PickleUnsafeOpScan" + } + ], + "MEDIUM": [ + { + "description": "Use of unsafe operator 'system' from module 'posix'", + "operator": "system", + "module": "posix", + "source": "/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl", + "scanner": "modelscan.scanners.PickleUnsafeOpScan" + } + ], + "HIGH": [ + { + "description": "Use of unsafe operator 'system' from module 'posix'", + "operator": "system", + "module": "posix", + "source": "/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl", + "scanner": "modelscan.scanners.PickleUnsafeOpScan" + } + ], + "LOW": [ + { + "description": "Use of unsafe operator 'system' from module 'posix'", + "operator": "system", + "module": "posix", + "source": "/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl", + "scanner": "modelscan.scanners.PickleUnsafeOpScan" + } + ] + }, + "errors": [], + "scanned": { + "total_scanned": 4, + "scanned_files": [ + "/Users/mehrinkiani/Documents/modelscan/notebooks/XGBoostModels/unsafe_model.pkl" + ] + } +} +` diff --git a/components/producers/modelscan/task.yaml b/components/producers/modelscan/task.yaml new file mode 100644 index 000000000..1dc384445 --- /dev/null +++ b/components/producers/modelscan/task.yaml @@ -0,0 +1,64 @@ +--- +apiVersion: tekton.dev/v1beta1 +kind: Task +metadata: + name: producer-modelscan + labels: + v1.dracon.ocurity.com/component: producer + v1.dracon.ocurity.com/test-type: sast + v1.dracon.ocurity.com/language: python +spec: + description: Analyse Go source code to look for security issues. + params: + - name: producer-modelscan-relative-path-to-model + type: string + volumes: + - name: scratch + emptyDir: {} + workspaces: + - name: output + description: The workspace containing the source-code to scan. + steps: + - name: run-modelscan + image: python:3.11-alpine + imagePullPolicy: Always + script: | + set -x + set +e + + pip install 'modelscan' + modelscan --path "$(workspaces.output.path)/source-code/$(params.producer-modelscan-relative-path-to-model)" --reporting-format json --output-file /scratch/out.json + exitCode=$? + + if [[ $exitCode -eq 1 ]]; then + echo "ModelScan found vulnerabilities" + exit 0 + elif [[ $exitCode -eq 2 ]]; then + echo "ModelScan failed, error while scanning" + exit $exitCode + elif [[ $exitCode -eq 3 ]]; then + echo "ModelScan did not find any supported files while scanning" + exit $exitCode + elif [[ $exitCode -eq 4 ]]; then + echo "ModelScan encountered an error whle parsing CLI variables, the task definition has a bug" + exit $exitCode + elif [[ $exitCode -eq 0 ]]; then + echo "ModelScan did not find any vulnerabilities" + exit $exitCode + else + echo "Received unexpected exit code, exiting" + exit $exitCode + fi + volumeMounts: + - mountPath: /scratch + name: scratch + - name: produce-issues + imagePullPolicy: Always + image: '{{ default "ghcr.io/ocurity/dracon" .Values.image.registry }}/components/producers/modelscan:{{ .Chart.AppVersion }}' + command: ["/app/components/producers/modelscan/modelscan-parser"] + args: + - "-in=/scratch/out.json" + - "-out=$(workspaces.output.path)/.dracon/producers/modelscan.pb" + volumeMounts: + - mountPath: /scratch + name: scratch From 931ff7d8da03a77459517a0335dec15803b76e7d Mon Sep 17 00:00:00 2001 From: sg Date: Wed, 9 Oct 2024 14:27:20 +0100 Subject: [PATCH 2/2] model scan example workflow --- .../kustomization.yaml | 12 ++++++++++ .../machine-learning-project/pipelinerun.yaml | 22 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 examples/pipelines/machine-learning-project/kustomization.yaml create mode 100644 examples/pipelines/machine-learning-project/pipelinerun.yaml diff --git a/examples/pipelines/machine-learning-project/kustomization.yaml b/examples/pipelines/machine-learning-project/kustomization.yaml new file mode 100644 index 000000000..509311b43 --- /dev/null +++ b/examples/pipelines/machine-learning-project/kustomization.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +nameSuffix: -machine-learning-project +components: + - pkg:helm/dracon-oss-components/base + - pkg:helm/dracon-oss-components/git-clone + - pkg:helm/dracon-oss-components/producer-modelscan + - pkg:helm/dracon-oss-components/producer-aggregator + - pkg:helm/dracon-oss-components/enricher-codeowners + - pkg:helm/dracon-oss-components/enricher-aggregator + - pkg:helm/dracon-oss-components/consumer-stdout-json diff --git a/examples/pipelines/machine-learning-project/pipelinerun.yaml b/examples/pipelines/machine-learning-project/pipelinerun.yaml new file mode 100644 index 000000000..e0620a65b --- /dev/null +++ b/examples/pipelines/machine-learning-project/pipelinerun.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + generateName: dracon-machine-learning-project- +spec: + pipelineRef: + name: dracon-machine-learning-project + params: + - name: git-clone-url + value: https://github.com/ocurity/e2e-monorepo.git + - name: producer-modelscan-relative-path-to-model + value: "vulnerable-ml-models/unsafe_xgboost_model.pkl" + workspaces: + - name: output + volumeClaimTemplate: + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi