From 61c3f57a119de5478de3a1c387dc52a85ef72335 Mon Sep 17 00:00:00 2001 From: "huish@microsoft.com" Date: Fri, 8 Dec 2023 02:15:23 +0000 Subject: [PATCH 1/7] adding deny yaml --- .../samples/config_v1beta1_verifier_sbom_deny.yaml | 14 ++++++++++++++ test/bats/plugin-test.bats | 5 +++++ 2 files changed, 19 insertions(+) create mode 100644 config/samples/config_v1beta1_verifier_sbom_deny.yaml diff --git a/config/samples/config_v1beta1_verifier_sbom_deny.yaml b/config/samples/config_v1beta1_verifier_sbom_deny.yaml new file mode 100644 index 000000000..0106e40c8 --- /dev/null +++ b/config/samples/config_v1beta1_verifier_sbom_deny.yaml @@ -0,0 +1,14 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: Verifier +metadata: + name: verifier-sbom +spec: + name: sbom + artifactTypes: application/spdx+json + parameters: + disallowedLicenses: + - Zlib + disallowedPackages: + - name: musl-utils + version: 1.2.3-r4 + nestedReferences: application/vnd.cncf.notary.signature \ No newline at end of file diff --git a/test/bats/plugin-test.bats b/test/bats/plugin-test.bats index 3d07ab0c0..a366744af 100644 --- a/test/bats/plugin-test.bats +++ b/test/bats/plugin-test.bats @@ -137,6 +137,11 @@ SLEEP_TIME=1 assert_success sleep 5 + run kubectl apply -f ./config/samples/config_v1beta1_verifier_sbom_deny.yaml + sleep 5 + run kubectl run sbom --namespace default --image=registry:5000/sbom:v0 + assert_failure + run kubectl apply -f ./config/samples/config_v1beta1_verifier_sbom.yaml sleep 5 run kubectl run sbom --namespace default --image=registry:5000/sbom:v0 From dcad63ed38cc22a45508e25e54ef61789936dc78 Mon Sep 17 00:00:00 2001 From: "huish@microsoft.com" Date: Fri, 8 Dec 2023 02:18:55 +0000 Subject: [PATCH 2/7] consistent error handling --- plugins/verifier/sbom/sbom.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/verifier/sbom/sbom.go b/plugins/verifier/sbom/sbom.go index a396d6ce8..4bf121567 100644 --- a/plugins/verifier/sbom/sbom.go +++ b/plugins/verifier/sbom/sbom.go @@ -87,7 +87,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("Error fetching reference manifest for subject: %s reference descriptor: %v", subjectReference, referenceDescriptor.Descriptor), - }, err + }, nil } if len(referenceManifest.Blobs) == 0 { @@ -108,7 +108,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("Error fetching blob for subject: %s digest: %s", subjectReference, blobDesc.Digest), - }, err + }, nil } switch artifactType { @@ -201,7 +201,7 @@ func processSpdxJSONMediaType(name string, verifierType string, refBlob []byte, Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("SBOM failed to parse: %v", err), - }, err + }, nil } // iterate through all package info and check against the deny list From 8d58fbf0a65818bcbbf9ad480c97433e4d9de10f Mon Sep 17 00:00:00 2001 From: "huish@microsoft.com" Date: Fri, 8 Dec 2023 04:30:45 +0000 Subject: [PATCH 3/7] update error handling --- plugins/verifier/sbom/sbom.go | 12 ++++++------ plugins/verifier/sbom/sbom_test.go | 16 ++++++---------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/plugins/verifier/sbom/sbom.go b/plugins/verifier/sbom/sbom.go index 4bf121567..64e56471c 100644 --- a/plugins/verifier/sbom/sbom.go +++ b/plugins/verifier/sbom/sbom.go @@ -113,7 +113,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe switch artifactType { case SpdxJSONMediaType: - return processSpdxJSONMediaType(input.Name, verifierType, refBlob, input.DisallowedLicenses, input.DisallowedPackages) + return processSpdxJSONMediaType(input.Name, verifierType, refBlob, input.DisallowedLicenses, input.DisallowedPackages), nil default: return &verifier.VerifierResult{ Name: input.Name, @@ -159,7 +159,7 @@ func loadDisallowedPackagesMap(packages []utils.PackageInfo) (map[utils.PackageI } // parse through the spdx blob and returns the verifier result -func processSpdxJSONMediaType(name string, verifierType string, refBlob []byte, disallowedLicenses []string, disallowedPackages []utils.PackageInfo) (*verifier.VerifierResult, error) { +func processSpdxJSONMediaType(name string, verifierType string, refBlob []byte, disallowedLicenses []string, disallowedPackages []utils.PackageInfo) *verifier.VerifierResult { var err error var spdxDoc *v2_3.Document if spdxDoc, err = jsonLoader.Read(bytes.NewReader(refBlob)); spdxDoc != nil && err == nil { @@ -181,8 +181,8 @@ func processSpdxJSONMediaType(name string, verifierType string, refBlob []byte, Name: name, IsSuccess: false, Extensions: extensionData, - Message: "SBOM validation failed.", - }, err + Message: "SBOM validation failed. Please review extensions data for license and package violation found.", + } } } @@ -194,14 +194,14 @@ func processSpdxJSONMediaType(name string, verifierType string, refBlob []byte, CreationInfo: spdxDoc.CreationInfo, }, Message: "SBOM verification success. No license or package violation found.", - }, nil + } } return &verifier.VerifierResult{ Name: name, Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("SBOM failed to parse: %v", err), - }, nil + } } // iterate through all package info and check against the deny list diff --git a/plugins/verifier/sbom/sbom_test.go b/plugins/verifier/sbom/sbom_test.go index 0a5c45159..6b170ca25 100644 --- a/plugins/verifier/sbom/sbom_test.go +++ b/plugins/verifier/sbom/sbom_test.go @@ -17,6 +17,7 @@ package main import ( "os" "path/filepath" + "strings" "testing" "github.com/deislabs/ratify/plugins/verifier/sbom/utils" @@ -27,10 +28,7 @@ func TestProcessSPDXJsonMediaType(t *testing.T) { if err != nil { t.Fatalf("error reading %s", filepath.Join("testdata", "bom.json")) } - vr, err := processSpdxJSONMediaType("test", "", b, nil, nil) - if err != nil { - t.Fatalf("expected to process spdx json file: %s", filepath.Join("testdata", "bom.json")) - } + vr := processSpdxJSONMediaType("test", "", b, nil, nil) if !vr.IsSuccess { t.Fatalf("expected to successfully verify schema") } @@ -41,8 +39,9 @@ func TestProcessInvalidSPDXJsonMediaType(t *testing.T) { if err != nil { t.Fatalf("error reading %s", filepath.Join("testdata", "invalid-bom.json")) } - _, err = processSpdxJSONMediaType("test", "", b, nil, nil) - if err == nil { + report := processSpdxJSONMediaType("test", "", b, nil, nil) + + if !strings.Contains(report.Message, "SBOM failed to parse") { t.Fatalf("expected to have an error processing spdx json file: %s", filepath.Join("testdata", "bom.json")) } } @@ -151,10 +150,7 @@ func TestGetViolations(t *testing.T) { for _, tc := range cases { t.Run("test scenario", func(t *testing.T) { - report, err := processSpdxJSONMediaType("test", "", b, tc.disallowedLicenses, tc.disallowedPackages) - if err != nil { - t.Fatalf("unexpected error processing spdx json file: %s", filepath.Join("testdata", "bom.json")) - } + report := processSpdxJSONMediaType("test", "", b, tc.disallowedLicenses, tc.disallowedPackages) if len(tc.expectedPackageViolations) != 0 { extensionData := report.Extensions.(map[string]interface{}) From 8dfbcfa53d2bf4054323ce76ebd9110a082eaa88 Mon Sep 17 00:00:00 2001 From: "huish@microsoft.com" Date: Fri, 8 Dec 2023 05:28:31 +0000 Subject: [PATCH 4/7] improve license check --- plugins/verifier/sbom/sbom.go | 3 +- plugins/verifier/sbom/sbom_test.go | 20 ++++++++- plugins/verifier/sbom/utils/spdxutils.go | 25 +++++++++++ plugins/verifier/sbom/utils/spdxutils_test.go | 44 +++++++++++++++++++ test/bats/plugin-test.bats | 3 +- 5 files changed, 91 insertions(+), 4 deletions(-) diff --git a/plugins/verifier/sbom/sbom.go b/plugins/verifier/sbom/sbom.go index 64e56471c..ba8f617ce 100644 --- a/plugins/verifier/sbom/sbom.go +++ b/plugins/verifier/sbom/sbom.go @@ -213,8 +213,7 @@ func filterDisallowedPackages(packageLicenses []utils.PackageLicense, disallowed for _, packageInfo := range packageLicenses { // if license contains disallowed, add to violation for _, disallowed := range disallowedLicense { - license := packageInfo.License - if license != "" && strings.Contains(strings.ToLower(license), strings.ToLower(disallowed)) { + if utils.ContainsLicense(strings.ToLower(packageInfo.License), strings.ToLower(disallowed)) { violationLicense = append(violationLicense, packageInfo) } } diff --git a/plugins/verifier/sbom/sbom_test.go b/plugins/verifier/sbom/sbom_test.go index 6b170ca25..5ae3db3bd 100644 --- a/plugins/verifier/sbom/sbom_test.go +++ b/plugins/verifier/sbom/sbom_test.go @@ -97,6 +97,12 @@ func TestGetViolations(t *testing.T) { expectedPackageViolations []utils.PackageLicense enabled bool }{ + { + description: "disallowed packages with no version", + disallowedLicenses: []string{"MPL"}, + expectedLicenseViolations: nil, + expectedPackageViolations: nil, + }, { description: "disallowed packages with no version", disallowedPackages: []utils.PackageInfo{disallowedPackageNoVersion}, @@ -112,7 +118,6 @@ func TestGetViolations(t *testing.T) { }, { description: "invalid disallow package", - disallowedLicenses: []string{"BSD-3-Clause", "Zlib"}, disallowedPackages: []utils.PackageInfo{disallowedPackageNoName}, expectedLicenseViolations: []utils.PackageLicense{}, expectedPackageViolations: []utils.PackageLicense{}, @@ -152,6 +157,18 @@ func TestGetViolations(t *testing.T) { t.Run("test scenario", func(t *testing.T) { report := processSpdxJSONMediaType("test", "", b, tc.disallowedLicenses, tc.disallowedPackages) + if len(tc.expectedPackageViolations) != 0 || len(tc.expectedLicenseViolations) != 0 { + if report.IsSuccess { + t.Fatalf("Test %s failed. Expected IsSuccess: true, got: false", tc.description) + } + } + + if len(tc.expectedPackageViolations) == 0 && len(tc.expectedLicenseViolations) == 0 { + if !report.IsSuccess { + t.Fatalf("Test %s failed. Expected IsSuccess: false, got: true", tc.description) + } + } + if len(tc.expectedPackageViolations) != 0 { extensionData := report.Extensions.(map[string]interface{}) packageViolation := extensionData[PackageViolation].([]utils.PackageLicense) @@ -163,6 +180,7 @@ func TestGetViolations(t *testing.T) { licensesViolation := extensionData[LicenseViolation].([]utils.PackageLicense) AssertEquals(tc.expectedLicenseViolations, licensesViolation, tc.description, t) } + }) } } diff --git a/plugins/verifier/sbom/utils/spdxutils.go b/plugins/verifier/sbom/utils/spdxutils.go index a76b12da8..282f28c66 100644 --- a/plugins/verifier/sbom/utils/spdxutils.go +++ b/plugins/verifier/sbom/utils/spdxutils.go @@ -16,6 +16,8 @@ limitations under the License. package utils import ( + "strings" + "github.com/spdx/tools-golang/spdx" ) @@ -31,3 +33,26 @@ func GetPackageLicenses(doc spdx.Document) []PackageLicense { } return output } + +// returns true if the licenseExpression contains the disallowed license +// this implements a whole word match +func ContainsLicense(spdxLicenseExpression string, disallowed string) bool { + if len(spdxLicenseExpression) == 0 { + return false + } + + // if the licenseExpression is exactly the same as the disallowed license, return true + if spdxLicenseExpression == disallowed { + return true + } + + disallowed1 := disallowed + " " + disallowed2 := " " + disallowed + + // look for whole word match + if strings.Contains(spdxLicenseExpression, disallowed1) || strings.Contains(spdxLicenseExpression, disallowed2) { + return true + } + + return false +} diff --git a/plugins/verifier/sbom/utils/spdxutils_test.go b/plugins/verifier/sbom/utils/spdxutils_test.go index 897e70d3d..4692b39b4 100644 --- a/plugins/verifier/sbom/utils/spdxutils_test.go +++ b/plugins/verifier/sbom/utils/spdxutils_test.go @@ -33,3 +33,47 @@ func TestGetPackageLicenses(t *testing.T) { t.Fatalf("unexpected packages count, expected 16") } } + +func TestContainsLicense(t *testing.T) { + tests := []struct { + name string + spdxLicenseExpression string + disallowed string + expected bool + }{ + { + name: "exact match", + spdxLicenseExpression: "MIT", + disallowed: "MIT", + expected: true, + }, + { + name: "exact match with space", + spdxLicenseExpression: "MPL-2.0 AND LicenseRef-AND AND MIT", + disallowed: "MPL", + expected: false, + }, + { + name: "exact match with space", + spdxLicenseExpression: "MPL-2.0 AND LicenseRef-AND AND MIT", + disallowed: "MPL-2.0", + expected: true, + }, + { + name: "exact match with space", + spdxLicenseExpression: "MIT AND LicenseRef-AND AND MPL-2.0", + disallowed: "MPL-2.0", + expected: true, + }, + } + + for _, tt := range tests { + t.Run("test scenario", func(t *testing.T) { + + result := ContainsLicense(tt.spdxLicenseExpression, tt.disallowed) + if result != tt.expected { + t.Fatalf("expected %t, got %t", tt.expected, result) + } + }) + } +} diff --git a/test/bats/plugin-test.bats b/test/bats/plugin-test.bats index a366744af..c2c744939 100644 --- a/test/bats/plugin-test.bats +++ b/test/bats/plugin-test.bats @@ -143,7 +143,8 @@ SLEEP_TIME=1 assert_failure run kubectl apply -f ./config/samples/config_v1beta1_verifier_sbom.yaml - sleep 5 + # wait for the httpserver cache to be invalidated + sleep 15 run kubectl run sbom --namespace default --image=registry:5000/sbom:v0 assert_success From c1d25f863e57382c4154fc3da910d03028bfbf5f Mon Sep 17 00:00:00 2001 From: "huish@microsoft.com" Date: Fri, 8 Dec 2023 05:37:29 +0000 Subject: [PATCH 5/7] fix lint --- plugins/verifier/sbom/sbom.go | 4 ++-- plugins/verifier/sbom/sbom_test.go | 1 - plugins/verifier/sbom/utils/spdxutils_test.go | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/verifier/sbom/sbom.go b/plugins/verifier/sbom/sbom.go index ba8f617ce..5da724b41 100644 --- a/plugins/verifier/sbom/sbom.go +++ b/plugins/verifier/sbom/sbom.go @@ -86,7 +86,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe Name: input.Name, Type: verifierType, IsSuccess: false, - Message: fmt.Sprintf("Error fetching reference manifest for subject: %s reference descriptor: %v", subjectReference, referenceDescriptor.Descriptor), + Message: fmt.Sprintf("Error fetching reference manifest for subject: %s reference descriptor: %v, err: %v", subjectReference, referenceDescriptor.Descriptor, err), }, nil } @@ -107,7 +107,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe Name: input.Name, Type: verifierType, IsSuccess: false, - Message: fmt.Sprintf("Error fetching blob for subject: %s digest: %s", subjectReference, blobDesc.Digest), + Message: fmt.Sprintf("Error fetching blob for subject: %s digest: %s, err: %v", subjectReference, blobDesc.Digest, err), }, nil } diff --git a/plugins/verifier/sbom/sbom_test.go b/plugins/verifier/sbom/sbom_test.go index 5ae3db3bd..042c2c378 100644 --- a/plugins/verifier/sbom/sbom_test.go +++ b/plugins/verifier/sbom/sbom_test.go @@ -180,7 +180,6 @@ func TestGetViolations(t *testing.T) { licensesViolation := extensionData[LicenseViolation].([]utils.PackageLicense) AssertEquals(tc.expectedLicenseViolations, licensesViolation, tc.description, t) } - }) } } diff --git a/plugins/verifier/sbom/utils/spdxutils_test.go b/plugins/verifier/sbom/utils/spdxutils_test.go index 4692b39b4..48d37ac3a 100644 --- a/plugins/verifier/sbom/utils/spdxutils_test.go +++ b/plugins/verifier/sbom/utils/spdxutils_test.go @@ -69,7 +69,6 @@ func TestContainsLicense(t *testing.T) { for _, tt := range tests { t.Run("test scenario", func(t *testing.T) { - result := ContainsLicense(tt.spdxLicenseExpression, tt.disallowed) if result != tt.expected { t.Fatalf("expected %t, got %t", tt.expected, result) From 72771125a67286cd324542391a41539c013f7a18 Mon Sep 17 00:00:00 2001 From: "huish@microsoft.com" Date: Mon, 11 Dec 2023 00:13:42 +0000 Subject: [PATCH 6/7] add example to readme --- charts/ratify/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/ratify/README.md b/charts/ratify/README.md index 407577496..1795a6def 100644 --- a/charts/ratify/README.md +++ b/charts/ratify/README.md @@ -58,7 +58,7 @@ $ helm upgrade -n gatekeeper-system [RELEASE_NAME] ratify/ratify | sbom.enabled | Enables/disables installation of sbom verification configuration | `false` | | sbom.notaryProjectSignatureRequired | requires validation of sbom notation signature | `false` | | sbom.disallowedLicenses | list of disallowed licenses | [] | -| sbom.disallowedPackages | list of disallowed packages defined by package name and version | [] | +| sbom.disallowedPackages | list of disallowed packages defined by package name and version. For example: --set sbom.disallowedPackages[0].name="busybox" --set sbom.disallowedPackages[0].version="1.36.1-r0" | [] | | resources.limits.cpu | CPU limits of Ratify Deployment | `1000m` | | resources.limits.memory | Memory limits of Ratify Deployment | `512Mi` | | resources.requests.cpu | CPU request of Ratify Deployment | `600m` | From e26f5f9531bd22e6124e1ac91488c9ebbf0d6951 Mon Sep 17 00:00:00 2001 From: "huish@microsoft.com" Date: Mon, 11 Dec 2023 21:54:49 +0000 Subject: [PATCH 7/7] remove media type --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 119ba8967..3362a14e2 100644 --- a/Makefile +++ b/Makefile @@ -392,15 +392,15 @@ e2e-sbom-setup: ${GITHUB_WORKSPACE}/bin/oras attach \ --artifact-type application/spdx+json \ ${TEST_REGISTRY}/sbom:v0 \ - .staging/sbom/_manifest/spdx_2.2/manifest.spdx.json:application/spdx+json + .staging/sbom/_manifest/spdx_2.2/manifest.spdx.json ${GITHUB_WORKSPACE}/bin/oras attach \ --artifact-type application/spdx+json \ ${TEST_REGISTRY}/sbom:unsigned \ - .staging/sbom/_manifest/spdx_2.2/manifest.spdx.json:application/spdx+json + .staging/sbom/_manifest/spdx_2.2/manifest.spdx.json ${GITHUB_WORKSPACE}/bin/oras attach \ --artifact-type application/spdx+json \ ${TEST_REGISTRY}/all:v0 \ - .staging/sbom/_manifest/spdx_2.2/manifest.spdx.json:application/spdx+json + .staging/sbom/_manifest/spdx_2.2/manifest.spdx.json # Push Signature to sbom .staging/notation/notation sign -u ${TEST_REGISTRY_USERNAME} -p ${TEST_REGISTRY_PASSWORD} ${TEST_REGISTRY}/sbom@`oras discover -o json --artifact-type application/spdx+json ${TEST_REGISTRY}/sbom:v0 | jq -r ".manifests[0].digest"`