From 818cdaa2f87a29aad1aa0386fd6783b8a3385f56 Mon Sep 17 00:00:00 2001 From: Michael Imamura Date: Fri, 20 Dec 2024 16:06:09 -0500 Subject: [PATCH] Add schemas and docs for SBOM, inventory, and configuration plugins (#397) * Document inventory and configuration findings. * Restrict type to values from API spec. * Handle non-array details. In the case of "inventory" plugins, the details is can be anything (i.e. not necessarily an array). * Add SBOM finding schema. * Add unit test for SBOM. --- backend/engine/plugins/README.md | 67 ++++++++++++++- .../utilities/plugin_runner/toolbox/lint.go | 3 + .../plugin_runner/toolbox/lint_test.go | 84 +++++++++++++++++++ .../schemas/configuration-finding.json | 26 ++++++ .../toolbox/schemas/plugin-results.json | 5 +- .../toolbox/schemas/sbom-finding.json | 55 ++++++++++++ .../toolbox/schemas/secrets-finding.json | 59 ++++++++----- .../schemas/static-analysis-finding.json | 49 ++++++----- .../toolbox/schemas/unknown-finding.json | 3 +- .../schemas/vulnerability-finding.json | 59 ++++++------- 10 files changed, 327 insertions(+), 83 deletions(-) create mode 100644 backend/utilities/plugin_runner/toolbox/schemas/configuration-finding.json create mode 100644 backend/utilities/plugin_runner/toolbox/schemas/sbom-finding.json diff --git a/backend/engine/plugins/README.md b/backend/engine/plugins/README.md index ed018214..5548d5f3 100644 --- a/backend/engine/plugins/README.md +++ b/backend/engine/plugins/README.md @@ -191,15 +191,76 @@ Example: ### SBOM -TODO: Add documentation here +SBOM ([software bill of materials](https://en.wikipedia.org/wiki/Software_supply_chain)) plugins gather an inventory of software components such as library dependencies. + +The `details` returned is a 2-element array. + +The first element is an array of SBOMs. These are not modified and are saved as-is for later retrieval. The specific format depends on the plugin, but should be a standard JSON format such as [CycloneDX](https://cyclonedx.org/) or [SPDX](https://spdx.dev/). This may be an empty array if no user-downloadable SBOMs are generated. + +The second element is an array of detected components, with the following fields: + +- `bom-ref`: Unique reference ID for this component. +- `type`: Component type (e.g. `jar`, `gomod`, etc.). This is tool-specific. For example, see the [list of types for Trivy](https://github.com/aquasecurity/trivy/blob/49f354085fdaf0f45f8f8f52c9a2a06fffbc2e63/pkg/fanal/analyzer/const.go). +- `name`: Component name, such as a package ID or filename. +- `version`: Component version. If not available or does not apply for this component type, must be `none`. +- `licenses`: Array of licenses: + - `id`: The [SPDX license identifier](https://spdx.org/licenses/). + - `name`: The license name. + +Full example: + +```jsonc +[ + [ + { /* SBOM for component 1... */ }, + { /* SBOM for component 2... */ } + ], + [ + { + "bom-ref": "pkg:golang/cloud.google.com/go/datastore@1.1.0", + "type": "gomod", + "name": "cloud.google.com/go/datastore", + "version": "1.1.0", + "licenses": [ + { + "id": "Apache-2.0", + "name": "Apache-2.0" + } + ] + } + ] +] +``` ### Inventory -TODO: Add documentation here +Inventory plugins generate statistics such as technologies used or declared dependencies, either for purely informational or audit purposes. + +These typically do not make judgements about the security impact of the data collected. + +The details payload for inventory plugins is specific to each plugin. ### Configuration -TODO: Add documentation here +Configuration plugins evaluate the security settings of a repository. The `details` returned is a list of objects. Each distinct issue should have its own entry in the list. + +The fields are: + +- `name`: The name of the issue. +- `description`: Long-form description of the issue. +- `severity`: The severity level of the issue: critical, high, medium, low, or negligible. + +Example: + +```json +[ + { + "name": "Branch Rule - Require Status Checks", + "description": "Requires that a branch rule is enabled that requires status checks on pull requests", + "severity": "medium" + } +] +``` ## Local Testing diff --git a/backend/utilities/plugin_runner/toolbox/lint.go b/backend/utilities/plugin_runner/toolbox/lint.go index 3cddfe17..e5bc10aa 100644 --- a/backend/utilities/plugin_runner/toolbox/lint.go +++ b/backend/utilities/plugin_runner/toolbox/lint.go @@ -34,7 +34,10 @@ func mustLoadSchema(id string) any { // Plugin type -> schema ID. var pluginTypeSchemaMap = map[string]string{ + "configuration": "configuration-finding", + "inventory": "unknown-finding", // Open-ended schema. "secrets": "secrets-finding", + "sbom": "sbom-finding", "static_analysis": "static-analysis-finding", "vulnerability": "vulnerability-finding", } diff --git a/backend/utilities/plugin_runner/toolbox/lint_test.go b/backend/utilities/plugin_runner/toolbox/lint_test.go index a130ce66..6008ca22 100644 --- a/backend/utilities/plugin_runner/toolbox/lint_test.go +++ b/backend/utilities/plugin_runner/toolbox/lint_test.go @@ -136,6 +136,90 @@ func TestValidSecrets(t *testing.T) { } } +func TestValidConfiguration(t *testing.T) { + actual := lint("configuration", []byte(`{ + "success": true, + "truncated": false, + "details": [{ + "name": "Branch Rule - Require Status Checks", + "description": "Requires that a branch rule is enabled that requires status checks on pull requests", + "severity": "medium" + }], + "errors": ["failed to scan"] + }`)) + if actual != nil { + t.Fatalf("expected no errors, got %v", actual) + } +} + +func TestValidInventory(t *testing.T) { + // The "details" payload for inventory plugins can be anything. + + actual := lint("inventory", []byte(`{ + "success": true, + "truncated": false, + "details": {"foo": {"bar": "baz"}}, + "errors": ["failed to scan"] + }`)) + if actual != nil { + t.Fatalf("expected no errors, got %v", actual) + } + + actual = lint("inventory", []byte(`{ + "success": true, + "truncated": false, + "details": [["foo", false]], + "errors": ["failed to scan"] + }`)) + if actual != nil { + t.Fatalf("expected no errors, got %v", actual) + } +} + +func TestValidSBOM(t *testing.T) { + actual := lint("sbom", []byte(`{ + "success": true, + "truncated": false, + "details": [ + [ + {"sbom": "foo"} + ], + [ + { + "bom-ref": "pkg:golang/cloud.google.com/go/datastore@1.1.0", + "type": "gomod", + "name": "cloud.google.com/go/datastore", + "version": "1.1.0", + "licenses": [ + { + "id": "Apache-2.0", + "name": "Apache-2.0" + } + ] + } + ] + ], + "errors": ["failed to scan"] + }`)) + if actual != nil { + t.Fatalf("expected no errors, got %v", actual) + } +} + +func TestSBOMMissingValue(t *testing.T) { + actual := lint("sbom", []byte(`{ + "success": true, + "truncated": false, + "details": [ + [] + ], + "errors": ["failed to scan"] + }`)) + if !containsValidationError(actual, "/details", "minItems: got 1, want 2") { + t.Fatalf("expected required error, got %v", actual) + } +} + func TestUnknownType(t *testing.T) { actual := lint("foo", []byte("{}")) if actual == nil || !strings.Contains(actual.Error(), "unknown plugin type") { diff --git a/backend/utilities/plugin_runner/toolbox/schemas/configuration-finding.json b/backend/utilities/plugin_runner/toolbox/schemas/configuration-finding.json new file mode 100644 index 00000000..cbd02b27 --- /dev/null +++ b/backend/utilities/plugin_runner/toolbox/schemas/configuration-finding.json @@ -0,0 +1,26 @@ +{ + "$id": "https://wbd.com/artemis/plugin/configuration-finding.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + + "title": "ConfigurationFinding", + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "description", + "severity" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "severity": { + "enum": ["critical", "high", "medium", "low", "negligible"] + } + } + } +} diff --git a/backend/utilities/plugin_runner/toolbox/schemas/plugin-results.json b/backend/utilities/plugin_runner/toolbox/schemas/plugin-results.json index 8f46f8aa..1dd9042f 100644 --- a/backend/utilities/plugin_runner/toolbox/schemas/plugin-results.json +++ b/backend/utilities/plugin_runner/toolbox/schemas/plugin-results.json @@ -17,10 +17,7 @@ "truncated": { "enum": [false] }, - "details": { - "type": "array", - "items": { "$ref": "finding.json" } - }, + "details": { "$ref": "finding.json" }, "errors": { "type": "array", "items": { "type": "string" } diff --git a/backend/utilities/plugin_runner/toolbox/schemas/sbom-finding.json b/backend/utilities/plugin_runner/toolbox/schemas/sbom-finding.json new file mode 100644 index 00000000..a8ee542a --- /dev/null +++ b/backend/utilities/plugin_runner/toolbox/schemas/sbom-finding.json @@ -0,0 +1,55 @@ +{ + "$id": "https://wbd.com/artemis/plugin/sbom-finding.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + + "title": "SBOMFinding", + "type": "array", + "items": false, + "prefixItems": [ + { + "type": "array", + "items": { "type": "object" } + }, + { + "type": "array", + "items": { "$ref": "#/$defs/component" } + } + ], + "minItems": 2, + "maxItems": 2, + + "$defs": { + "component": { + "type": "object", + "required": [ + "bom-ref", + "type", + "name", + "version", + "licenses" + ], + "properties": { + "bom-ref": { "type": "string" }, + "type": { "type": "string" }, + "name": { "type": "string" }, + "version": { "type": "string" }, + "licenses": { + "type": "array", + "items": { "$ref": "#/$defs/license" } + } + } + }, + + "license": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { "type": "string" }, + "name": { "type": "string" } + } + } + } +} diff --git a/backend/utilities/plugin_runner/toolbox/schemas/secrets-finding.json b/backend/utilities/plugin_runner/toolbox/schemas/secrets-finding.json index 702d8f0f..2d71e7b6 100644 --- a/backend/utilities/plugin_runner/toolbox/schemas/secrets-finding.json +++ b/backend/utilities/plugin_runner/toolbox/schemas/secrets-finding.json @@ -3,29 +3,42 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "SecretsFinding", - "type": "object", - "required": [ - "filename", - "line", - "commit", - "type", - "author" - ], - "properties": { - "filename": { - "type": "string" - }, - "line": { - "type": "integer" - }, - "commit": { - "type": "string" - }, - "type": { - "type": "string" - }, - "author": { - "type": "string" + "type": "array", + "items": { + "type": "object", + "required": [ + "filename", + "line", + "commit", + "type", + "author" + ], + "properties": { + "filename": { + "type": "string" + }, + "line": { + "type": "integer" + }, + "commit": { + "type": "string" + }, + "type": { + "enum": [ + "aws", + "ssh", + "mongo", + "postgres", + "redis", + "urlauth", + "google", + "slack", + "other" + ] + }, + "author": { + "type": "string" + } } } } diff --git a/backend/utilities/plugin_runner/toolbox/schemas/static-analysis-finding.json b/backend/utilities/plugin_runner/toolbox/schemas/static-analysis-finding.json index 355b57fb..ab939058 100644 --- a/backend/utilities/plugin_runner/toolbox/schemas/static-analysis-finding.json +++ b/backend/utilities/plugin_runner/toolbox/schemas/static-analysis-finding.json @@ -3,29 +3,32 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "StaticAnalysisFinding", - "type": "object", - "required": [ - "filename", - "line", - "message", - "severity", - "type" - ], - "properties": { - "filename": { - "type": "string" - }, - "line": { - "type": "integer" - }, - "message": { - "type": "string" - }, - "severity": { - "enum": ["critical", "high", "medium", "low", "negligible"] - }, - "type": { - "type": "string" + "type": "array", + "items": { + "type": "object", + "required": [ + "filename", + "line", + "message", + "severity", + "type" + ], + "properties": { + "filename": { + "type": "string" + }, + "line": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "severity": { + "enum": ["critical", "high", "medium", "low", "negligible"] + }, + "type": { + "type": "string" + } } } } diff --git a/backend/utilities/plugin_runner/toolbox/schemas/unknown-finding.json b/backend/utilities/plugin_runner/toolbox/schemas/unknown-finding.json index b7253023..2c0da43f 100644 --- a/backend/utilities/plugin_runner/toolbox/schemas/unknown-finding.json +++ b/backend/utilities/plugin_runner/toolbox/schemas/unknown-finding.json @@ -2,6 +2,5 @@ "$id": "https://wbd.com/artemis/plugin/unknown-finding.json", "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "UnknownFinding", - "type": "object" + "title": "UnknownFinding" } diff --git a/backend/utilities/plugin_runner/toolbox/schemas/vulnerability-finding.json b/backend/utilities/plugin_runner/toolbox/schemas/vulnerability-finding.json index a1849723..02b825b0 100644 --- a/backend/utilities/plugin_runner/toolbox/schemas/vulnerability-finding.json +++ b/backend/utilities/plugin_runner/toolbox/schemas/vulnerability-finding.json @@ -2,34 +2,37 @@ "$id": "https://wbd.com/artemis/plugin/vulnerability-finding.json", "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "StaticAnalysisFinding", - "type": "object", - "required": [ - "component", - "source", - "id", - "description", - "severity", - "remediation" - ], - "properties": { - "component": { - "type": "string" - }, - "source": { - "type": "string" - }, - "id": { - "type": "string" - }, - "description": { - "type": "string" - }, - "severity": { - "enum": ["critical", "high", "medium", "low", "negligible"] - }, - "remediation": { - "type": "string" + "title": "VulnerabilityFinding", + "type": "array", + "items": { + "type": "object", + "required": [ + "component", + "source", + "id", + "description", + "severity", + "remediation" + ], + "properties": { + "component": { + "type": "string" + }, + "source": { + "type": "string" + }, + "id": { + "type": "string" + }, + "description": { + "type": "string" + }, + "severity": { + "enum": ["critical", "high", "medium", "low", "negligible"] + }, + "remediation": { + "type": "string" + } } } }