From 287e485b3a355e9b5c5c6537464ea7c9f09f2d75 Mon Sep 17 00:00:00 2001 From: Juncheng Zhu <74894646+junczhu@users.noreply.github.com> Date: Thu, 7 Dec 2023 13:05:07 +0800 Subject: [PATCH] feat: Enable multi verifier name (#1187) --- .../notation-issuer-validation/template.yaml | 4 +- library/notation-validation/template.yml | 2 +- pkg/controllers/verifier_controller.go | 19 ++--- pkg/executor/core/executor.go | 2 + pkg/executor/core/executor_test.go | 4 + pkg/executor/core/testtypes.go | 6 +- pkg/verifier/api.go | 4 + pkg/verifier/factory/factory.go | 24 ++++-- pkg/verifier/factory/factory_test.go | 18 ++-- pkg/verifier/notation/notation.go | 32 ++++++-- pkg/verifier/notation/notation_test.go | 22 +++-- pkg/verifier/plugin/plugin.go | 16 +++- pkg/verifier/plugin/plugin_test.go | 1 + pkg/verifier/types/types.go | 3 + plugins/verifier/cosign/cosign.go | 35 ++++---- .../verifier/licensechecker/licensechecker.go | 9 +- plugins/verifier/sample/sample.go | 6 ++ plugins/verifier/sbom/sbom.go | 15 +++- plugins/verifier/sbom/sbom_test.go | 6 +- .../schemavalidator/schema_validator.go | 10 ++- .../vulnerability_report.go | 37 +++++++-- .../vulnerability_report_test.go | 6 +- test/bats/cli-test.bats | 15 ++++ test/bats/helpers.bash | 24 ++++++ .../config_external_verifier_with_type.json | 45 ++++++++++ .../config_multiple_notation_verifiers.json | 82 +++++++++++++++++++ .../config_notation_verifier_with_type.json | 51 ++++++++++++ ...config_rego_policy_notation_root_cert.json | 2 +- 28 files changed, 424 insertions(+), 76 deletions(-) create mode 100644 test/bats/tests/config/config_external_verifier_with_type.json create mode 100644 test/bats/tests/config/config_multiple_notation_verifiers.json create mode 100644 test/bats/tests/config/config_notation_verifier_with_type.json diff --git a/library/notation-issuer-validation/template.yaml b/library/notation-issuer-validation/template.yaml index b5dc4238b..44caf1cb7 100644 --- a/library/notation-issuer-validation/template.yaml +++ b/library/notation-issuer-validation/template.yaml @@ -57,7 +57,7 @@ spec: general_violation[{"result": result}] { subject_results := remote_data.responses[_] subject_result := subject_results[1] - notation_results := [res | subject_result.verifierReports[i].name == "notation"; res := subject_result.verifierReports[i]] + notation_results := [res | subject_result.verifierReports[i].type == "notation"; res := subject_result.verifierReports[i]] issuer_results := [res | notation_results[i].extensions.Issuer == input.parameters.issuer; res := notation_results[i]] count(issuer_results) == 0 result := sprintf("Subject %s has no signatures for certificate with Issuer: %s", [subject_results[0], input.parameters.issuer]) @@ -67,7 +67,7 @@ spec: general_violation[{"result": result}] { subject_results := remote_data.responses[_] subject_result := subject_results[1] - notation_results := [res | subject_result.verifierReports[i].name == "notation"; res := subject_result.verifierReports[i]] + notation_results := [res | subject_result.verifierReports[i].type == "notation"; res := subject_result.verifierReports[i]] notation_result := notation_results[_] notation_result.isSuccess == false notation_result.extensions.Issuer == input.parameters.issuer diff --git a/library/notation-validation/template.yml b/library/notation-validation/template.yml index 04f312215..19ca1d8fe 100644 --- a/library/notation-validation/template.yml +++ b/library/notation-validation/template.yml @@ -38,7 +38,7 @@ spec: general_violation[{"result": result}] { subject_results := remote_data.responses[_] subject_result := subject_results[1] - notation_results := [res | subject_result.verifierReports[i].name == "notation"; res := subject_result.verifierReports[i]] + notation_results := [res | subject_result.verifierReports[i].type == "notation"; res := subject_result.verifierReports[i]] successful_result := [ notation_result | notation_result := notation_results[_]; notation_result["isSuccess"] == true] successful_result == [] result = sprintf("signature verification failed for all signatures associated with %s", [subject_results[0]]) diff --git a/pkg/controllers/verifier_controller.go b/pkg/controllers/verifier_controller.go index c6525dba1..f8f6ff829 100644 --- a/pkg/controllers/verifier_controller.go +++ b/pkg/controllers/verifier_controller.go @@ -97,13 +97,11 @@ func (r *VerifierReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // creates a verifier reference from CRD spec and add store to map func verifierAddOrReplace(spec configv1beta1.VerifierSpec, objectName string, namespace string) error { - verifierConfig, err := specToVerifierConfig(spec) - + verifierConfig, err := specToVerifierConfig(spec, objectName) if err != nil { logrus.Error(err, "unable to convert crd specification to verifier config") return fmt.Errorf("unable to convert crd specification to verifier config, err: %w", err) } - // verifier factory only support a single version of configuration today // when we support multi version verifier CRD, we will also pass in the corresponding config version so factory can create different version of the object verifierConfigVersion := "1.0.0" // TODO: move default values to defaulting webhook in the future #413 @@ -111,14 +109,15 @@ func verifierAddOrReplace(spec configv1beta1.VerifierSpec, objectName string, na spec.Address = config.GetDefaultPluginPath() logrus.Infof("Address was empty, setting to default path: %v", spec.Address) } - verifierReference, err := vf.CreateVerifierFromConfig(verifierConfig, verifierConfigVersion, []string{spec.Address}, namespace) - if err != nil || verifierReference == nil { + referenceVerifier, err := vf.CreateVerifierFromConfig(verifierConfig, verifierConfigVersion, []string{spec.Address}, namespace) + + if err != nil || referenceVerifier == nil { logrus.Error(err, "unable to create verifier from verifier config") return err } - VerifierMap[objectName] = verifierReference - logrus.Infof("verifier '%v' added to verifier map", verifierReference.Name()) + VerifierMap[objectName] = referenceVerifier + logrus.Infof("verifier '%v' added to verifier map", referenceVerifier.Name()) return nil } @@ -129,7 +128,7 @@ func verifierRemove(objectName string) { } // returns a verifier reference from spec -func specToVerifierConfig(verifierSpec configv1beta1.VerifierSpec) (vc.VerifierConfig, error) { +func specToVerifierConfig(verifierSpec configv1beta1.VerifierSpec, verifierName string) (vc.VerifierConfig, error) { verifierConfig := vc.VerifierConfig{} if string(verifierSpec.Parameters.Raw) != "" { @@ -138,8 +137,8 @@ func specToVerifierConfig(verifierSpec configv1beta1.VerifierSpec) (vc.VerifierC return vc.VerifierConfig{}, err } } - - verifierConfig[types.Name] = verifierSpec.Name + verifierConfig[types.Name] = verifierName + verifierConfig[types.Type] = verifierSpec.Name verifierConfig[types.ArtifactTypes] = verifierSpec.ArtifactTypes if verifierSpec.Source != nil { verifierConfig[types.Source] = verifierSpec.Source diff --git a/pkg/executor/core/executor.go b/pkg/executor/core/executor.go index 116b439c8..74a071d26 100644 --- a/pkg/executor/core/executor.go +++ b/pkg/executor/core/executor.go @@ -177,6 +177,7 @@ func (executor Executor) verifyReferenceForJSONPolicy(ctx context.Context, subje verifyResult = vr.VerifierResult{ IsSuccess: false, Name: verifier.Name(), + Type: verifier.Type(), Message: errors.ErrorCodeVerifyReferenceFailure.NewError(errors.Verifier, verifier.Name(), errors.EmptyLink, err, nil, errors.HideStackTrace).Error()} } @@ -225,6 +226,7 @@ func (executor Executor) verifyReferenceForRegoPolicy(ctx context.Context, subje verifierReport = vt.VerifierResult{ IsSuccess: false, Name: verifier.Name(), + Type: verifier.Type(), Message: errors.ErrorCodeVerifyReferenceFailure.NewError(errors.Verifier, verifier.Name(), errors.EmptyLink, err, nil, errors.HideStackTrace).Error()} } else { verifierReport = vt.NewVerifierResult(verifierResult) diff --git a/pkg/executor/core/executor_test.go b/pkg/executor/core/executor_test.go index 0e66c8943..36edaff76 100644 --- a/pkg/executor/core/executor_test.go +++ b/pkg/executor/core/executor_test.go @@ -131,6 +131,10 @@ type mockVerifier struct { } func (v *mockVerifier) Name() string { + return "verifier-mockVerifier" +} + +func (v *mockVerifier) Type() string { return "mockVerifier" } diff --git a/pkg/executor/core/testtypes.go b/pkg/executor/core/testtypes.go index d5f26d99a..fd28a410d 100644 --- a/pkg/executor/core/testtypes.go +++ b/pkg/executor/core/testtypes.go @@ -31,7 +31,11 @@ type TestVerifier struct { } func (s *TestVerifier) Name() string { - return "test-verifier" + return "verifier-testVerifier" +} + +func (s *TestVerifier) Type() string { + return "testVerifier" } func (s *TestVerifier) CanVerify(_ context.Context, referenceDescriptor ocispecs.ReferenceDescriptor) bool { diff --git a/pkg/verifier/api.go b/pkg/verifier/api.go index 5224c2963..00f505de6 100644 --- a/pkg/verifier/api.go +++ b/pkg/verifier/api.go @@ -28,6 +28,7 @@ type VerifierResult struct { //nolint:revive // ignore linter to have unique typ Subject string `json:"subject,omitempty"` IsSuccess bool `json:"isSuccess"` Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` Message string `json:"message,omitempty"` Extensions interface{} `json:"extensions,omitempty"` NestedResults []VerifierResult `json:"nestedResults,omitempty"` @@ -40,6 +41,9 @@ type ReferenceVerifier interface { // Name returns the name of the verifier Name() string + // Type returns the type name of the verifier + Type() string + // CanVerify returns if the verifier can verify the given reference CanVerify(ctx context.Context, referenceDescriptor ocispecs.ReferenceDescriptor) bool diff --git a/pkg/verifier/factory/factory.go b/pkg/verifier/factory/factory.go index d97bedb8a..19e1403cc 100644 --- a/pkg/verifier/factory/factory.go +++ b/pkg/verifier/factory/factory.go @@ -52,14 +52,20 @@ func Register(name string, factory VerifierFactory) { // returns a single verifier from a verifierConfig // namespace is only applicable in K8s environment, namespace is appended to the certstore of the truststore so it is uniquely identifiable in a cluster env func CreateVerifierFromConfig(verifierConfig config.VerifierConfig, configVersion string, pluginBinDir []string, namespace string) (verifier.ReferenceVerifier, error) { - verifierName, ok := verifierConfig[types.Name] - if !ok { + // in cli mode both `type` and `name`` are read from config, if `type` is not specified, `name` is used as `type` + var verifierTypeStr string + if value, ok := verifierConfig[types.Name]; ok { + verifierTypeStr = value.(string) + } else { return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to find verifier name in the verifier config with key %s", "name")) } - verifierNameStr := fmt.Sprintf("%s", verifierName) - if strings.ContainsRune(verifierNameStr, os.PathSeparator) { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("invalid plugin name for a verifier: %s", verifierNameStr)) + if value, ok := verifierConfig[types.Type]; ok { + verifierTypeStr = value.(string) + } + + if strings.ContainsRune(verifierTypeStr, os.PathSeparator) { + return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("invalid plugin name for a verifier: %s", verifierTypeStr)) } // if source is specified, download the plugin @@ -70,18 +76,18 @@ func CreateVerifierFromConfig(verifierConfig config.VerifierConfig, configVersio return nil, re.ErrorCodeConfigInvalid.NewError(re.Verifier, "", re.EmptyLink, err, "failed to parse plugin source", re.HideStackTrace) } - targetPath := path.Join(pluginBinDir[0], verifierNameStr) + targetPath := path.Join(pluginBinDir[0], verifierTypeStr) err = pluginCommon.DownloadPlugin(source, targetPath) if err != nil { return nil, re.ErrorCodeDownloadPluginFailure.NewError(re.Verifier, "", re.EmptyLink, err, "failed to download plugin", re.HideStackTrace) } - logrus.Infof("downloaded verifier plugin %s from %s to %s", verifierNameStr, source.Artifact, targetPath) + logrus.Infof("downloaded verifier plugin %s from %s to %s", verifierTypeStr, source.Artifact, targetPath) } else { - logrus.Warnf("%s was specified for verifier plugin %s, but dynamic plugins are currently disabled", types.Source, verifierNameStr) + logrus.Warnf("%s was specified for verifier plugin type %s, but dynamic plugins are currently disabled", types.Source, verifierTypeStr) } } - verifierFactory, ok := builtInVerifiers[verifierNameStr] + verifierFactory, ok := builtInVerifiers[verifierTypeStr] if ok { return verifierFactory.Create(configVersion, verifierConfig, pluginBinDir[0], namespace) } diff --git a/pkg/verifier/factory/factory_test.go b/pkg/verifier/factory/factory_test.go index 6058a0a2f..9632f3e77 100644 --- a/pkg/verifier/factory/factory_test.go +++ b/pkg/verifier/factory/factory_test.go @@ -35,6 +35,10 @@ type TestVerifier struct { type TestVerifierFactory struct{} func (s *TestVerifier) Name() string { + return "test-verifier-0" +} + +func (s *TestVerifier) Type() string { return "test-verifier" } @@ -63,7 +67,8 @@ func TestCreateVerifiersFromConfig_BuiltInVerifiers_ReturnsExpected(t *testing.T } verifierConfig := map[string]interface{}{ - "name": "test-verifier", + "name": "test-verifier-0", + "type": "test-verifier", } verifiersConfig := config.VerifiersConfig{ Verifiers: []config.VerifierConfig{verifierConfig}, @@ -79,8 +84,8 @@ func TestCreateVerifiersFromConfig_BuiltInVerifiers_ReturnsExpected(t *testing.T t.Fatalf("expected to have %d verifiers, actual count %d", 1, len(verifiers)) } - if verifiers[0].Name() != "test-verifier" { - t.Fatalf("expected to create test verifier") + if nameResult := verifiers[0].Name(); nameResult != "test-verifier-0" { + t.Fatalf("expected to create test-verifier-0 for test case but got %v", nameResult) } if _, ok := verifiers[0].(*plugin.VerifierPlugin); ok { @@ -98,7 +103,8 @@ func TestCreateVerifiersFromConfig_BuiltInVerifiers_ReturnsExpected(t *testing.T func TestCreateVerifiersFromConfig_PluginVerifiers_ReturnsExpected(t *testing.T) { verifierConfig := map[string]interface{}{ - "name": "plugin-verifier", + "name": "plugin-verifier-0", + "type": "plugin-verifier", } verifiersConfig := config.VerifiersConfig{ Verifiers: []config.VerifierConfig{verifierConfig}, @@ -114,8 +120,8 @@ func TestCreateVerifiersFromConfig_PluginVerifiers_ReturnsExpected(t *testing.T) t.Fatalf("expected to have %d verifiers, actual count %d", 1, len(verifiers)) } - if verifiers[0].Name() != "plugin-verifier" { - t.Fatalf("expected to create plugin verifier") + if verifiers[0].Name() != "plugin-verifier-0" { + t.Fatalf("expected to create plugin-verifier-0") } if _, ok := verifiers[0].(*plugin.VerifierPlugin); !ok { diff --git a/pkg/verifier/notation/notation.go b/pkg/verifier/notation/notation.go index 4f2e808d2..7ac4b6f9a 100644 --- a/pkg/verifier/notation/notation.go +++ b/pkg/verifier/notation/notation.go @@ -34,6 +34,7 @@ import ( "github.com/deislabs/ratify/pkg/verifier" "github.com/deislabs/ratify/pkg/verifier/config" "github.com/deislabs/ratify/pkg/verifier/factory" + "github.com/deislabs/ratify/pkg/verifier/types" "github.com/notaryproject/notation-go/log" _ "github.com/notaryproject/notation-core-go/signature/cose" // register COSE signature @@ -45,7 +46,7 @@ import ( ) const ( - verifierName = "notation" + verifierType = "notation" defaultCertPath = "ratify-certs/notation/truststore" ) @@ -63,6 +64,8 @@ type NotationPluginVerifierConfig struct { //nolint:revive // ignore linter to h } type notationPluginVerifier struct { + name string + verifierType string artifactTypes []string notationVerifier *notation.Verifier } @@ -70,30 +73,41 @@ type notationPluginVerifier struct { type notationPluginVerifierFactory struct{} func init() { - factory.Register(verifierName, ¬ationPluginVerifierFactory{}) + factory.Register(verifierType, ¬ationPluginVerifierFactory{}) } func (f *notationPluginVerifierFactory) Create(_ string, verifierConfig config.VerifierConfig, pluginDirectory string, namespace string) (verifier.ReferenceVerifier, error) { logger.GetLogger(context.Background(), logOpt).Debugf("creating notation with config %v, namespace '%v'", verifierConfig, namespace) + verifierName := fmt.Sprintf("%s", verifierConfig[types.Name]) + verifierTypeStr := "" + if _, ok := verifierConfig[types.Type]; ok { + verifierTypeStr = fmt.Sprintf("%s", verifierConfig[types.Type]) + } conf, err := parseVerifierConfig(verifierConfig, namespace) if err != nil { return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName) } - verfiyService, err := getVerifierService(conf, pluginDirectory) + verifyService, err := getVerifierService(conf, pluginDirectory) if err != nil { return nil, re.ErrorCodePluginInitFailure.WithComponentType(re.Verifier).WithPluginName(verifierName).WithError(err) } artifactTypes := strings.Split(conf.ArtifactTypes, ",") return ¬ationPluginVerifier{ + name: verifierName, + verifierType: verifierTypeStr, artifactTypes: artifactTypes, - notationVerifier: &verfiyService, + notationVerifier: &verifyService, }, nil } func (v *notationPluginVerifier) Name() string { - return verifierName + return v.name +} + +func (v *notationPluginVerifier) Type() string { + return v.verifierType } func (v *notationPluginVerifier) CanVerify(_ context.Context, referenceDescriptor ocispecs.ReferenceDescriptor) bool { @@ -122,7 +136,7 @@ func (v *notationPluginVerifier) Verify(ctx context.Context, } if len(referenceManifest.Blobs) == 0 { - return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeSignatureNotFound.NewError(re.Verifier, verifierName, re.EmptyLink, nil, fmt.Sprintf("no signature content found for referrer: %s@%s", subjectReference.Path, referenceDescriptor.Digest.String()), re.HideStackTrace) + return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeSignatureNotFound.NewError(re.Verifier, v.name, re.EmptyLink, nil, fmt.Sprintf("no signature content found for referrer: %s@%s", subjectReference.Path, referenceDescriptor.Digest.String()), re.HideStackTrace) } for _, blobDesc := range referenceManifest.Blobs { @@ -136,7 +150,7 @@ func (v *notationPluginVerifier) Verify(ctx context.Context, subjectRef := fmt.Sprintf("%s@%s", subjectReference.Path, subjectReference.Digest.String()) outcome, err := v.verifySignature(ctx, subjectRef, blobDesc.MediaType, subjectDesc.Descriptor, refBlob) if err != nil { - return verifier.VerifierResult{IsSuccess: false, Extensions: extensions}, re.ErrorCodeVerifyPluginFailure.NewError(re.Verifier, verifierName, re.NotationTsgLink, err, "failed to verify signature of digest", re.HideStackTrace) + return verifier.VerifierResult{IsSuccess: false, Extensions: extensions}, re.ErrorCodeVerifyPluginFailure.NewError(re.Verifier, v.name, re.NotationTsgLink, err, "failed to verify signature of digest", re.HideStackTrace) } // Note: notation verifier already validates certificate chain is not empty. @@ -146,7 +160,8 @@ func (v *notationPluginVerifier) Verify(ctx context.Context, } return verifier.VerifierResult{ - Name: verifierName, + Name: v.name, + Type: v.verifierType, IsSuccess: true, Message: "signature verification success", Extensions: extensions, @@ -173,6 +188,7 @@ func (v *notationPluginVerifier) verifySignature(ctx context.Context, subjectRef } func parseVerifierConfig(verifierConfig config.VerifierConfig, namespace string) (*NotationPluginVerifierConfig, error) { + verifierName := verifierConfig[types.Name].(string) conf := &NotationPluginVerifierConfig{} verifierConfigBytes, err := json.Marshal(verifierConfig) diff --git a/pkg/verifier/notation/notation_test.go b/pkg/verifier/notation/notation_test.go index 8feb097ff..e8f155fd2 100644 --- a/pkg/verifier/notation/notation_test.go +++ b/pkg/verifier/notation/notation_test.go @@ -143,11 +143,19 @@ func (s mockStore) GetSubjectDescriptor(_ context.Context, _ common.Reference) ( } func TestName(t *testing.T) { - v := ¬ationPluginVerifier{} - name := v.Name() + verifierConfig := map[string]interface{}{ + "name": "notation-verifier-0", + "type": "notation", + "trustPolicyDoc": testTrustPolicy, + } - if name != "notation" { - t.Fatalf("expect name: notation, got: %s", name) + f := ¬ationPluginVerifierFactory{} + verifier, err := f.Create(testVersion, verifierConfig, "", "") + if err != nil { + t.Fatalf("failed create notation verifier got error = %v", err) + } + if name := verifier.Name(); name != "notation-verifier-0" { + t.Fatalf("expect name: notation-verifier-0, got: %s", name) } } @@ -205,7 +213,8 @@ func TestParseVerifierConfig(t *testing.T) { { name: "failed unmarshalling to notation config", configMap: map[string]interface{}{ - "name": []string{test}, + "name": test, + "verificationCerts": test, }, expectErr: true, expect: nil, @@ -294,7 +303,8 @@ func TestCreate(t *testing.T) { { name: "failed parsing verifier config", configMap: map[string]interface{}{ - "name": []string{test}, + "name": test, + "trustPolicyDoc": 1, }, expectErr: true, }, diff --git a/pkg/verifier/plugin/plugin.go b/pkg/verifier/plugin/plugin.go index 3ecaf1821..2d51ebe50 100644 --- a/pkg/verifier/plugin/plugin.go +++ b/pkg/verifier/plugin/plugin.go @@ -36,6 +36,7 @@ import ( // VerifierPlugin describes a verifier that is implemented by invoking the plugins type VerifierPlugin struct { name string + verifierType string artifactTypes []string nestedReferences []string version string @@ -50,6 +51,10 @@ func NewVerifier(version string, verifierConfig config.VerifierConfig, pluginPat if !ok { return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("failed to find verifier name in the verifier config with key: %s", types.Name)) } + verifierType := "" + if _, ok := verifierConfig[types.Type]; ok { + verifierType = fmt.Sprintf("%s", verifierConfig[types.Type]) + } var nestedReferences []string if vs, ok := verifierConfig[types.NestedReferences]; ok { @@ -67,6 +72,7 @@ func NewVerifier(version string, verifierConfig config.VerifierConfig, pluginPat return &VerifierPlugin{ name: fmt.Sprintf("%s", verifierName), + verifierType: verifierType, version: version, path: pluginPaths, rawConfig: verifierConfig, @@ -89,6 +95,10 @@ func (vp *VerifierPlugin) Name() string { return vp.name } +func (vp *VerifierPlugin) Type() string { + return vp.verifierType +} + func (vp *VerifierPlugin) Verify(ctx context.Context, subjectReference common.Reference, referenceDescriptor ocispecs.ReferenceDescriptor, @@ -107,7 +117,11 @@ func (vp *VerifierPlugin) verifyReference( subjectReference common.Reference, referenceDescriptor ocispecs.ReferenceDescriptor, referrerStoreConfig *rc.StoreConfig) (*verifier.VerifierResult, error) { - pluginPath, err := vp.executor.FindInPaths(vp.name, vp.path) + verifierTypeStr := vp.name + if vp.verifierType != "" { + verifierTypeStr = vp.verifierType + } + pluginPath, err := vp.executor.FindInPaths(verifierTypeStr, vp.path) if err != nil { return nil, re.ErrorCodePluginNotFound.NewError(re.Verifier, vp.name, re.EmptyLink, err, nil, re.HideStackTrace) } diff --git a/pkg/verifier/plugin/plugin_test.go b/pkg/verifier/plugin/plugin_test.go index 222f4cb76..dd6c2faf6 100644 --- a/pkg/verifier/plugin/plugin_test.go +++ b/pkg/verifier/plugin/plugin_test.go @@ -46,6 +46,7 @@ func (e *TestExecutor) FindInPaths(plugin string, paths []string) (string, error func TestNewVerifier_Expected(t *testing.T) { verifierConfig := map[string]interface{}{ "name": "test-verifier", + "type": "test-verifier", "artifactTypes": "test1,test2", "nestedReferences": "ref1,ref2", } diff --git a/pkg/verifier/types/types.go b/pkg/verifier/types/types.go index 65fe199e0..df9890efb 100644 --- a/pkg/verifier/types/types.go +++ b/pkg/verifier/types/types.go @@ -26,6 +26,7 @@ const ( SpecVersion string = "0.1.0" Version string = "version" Name string = "name" + Type string = "type" ArtifactTypes string = "artifactTypes" NestedReferences string = "nestedReferences" Source string = "source" @@ -49,6 +50,7 @@ type VerifierResult struct { IsSuccess bool `json:"isSuccess"` Message string `json:"message"` Name string `json:"name"` + Type string `json:"type,omitempty"` Extensions interface{} `json:"extensions"` } @@ -62,6 +64,7 @@ func GetVerifierResult(result []byte) (*verifier.VerifierResult, error) { IsSuccess: vResult.IsSuccess, Message: vResult.Message, Name: vResult.Name, + Type: vResult.Type, Extensions: vResult.Extensions, }, nil } diff --git a/plugins/verifier/cosign/cosign.go b/plugins/verifier/cosign/cosign.go index d7c995b85..a70a0b2c6 100644 --- a/plugins/verifier/cosign/cosign.go +++ b/plugins/verifier/cosign/cosign.go @@ -47,6 +47,7 @@ import ( type PluginConfig struct { Name string `json:"name"` + Type string `json:"type"` KeyRef string `json:"key"` RekorURL string `json:"rekorURL"` // config specific to the plugin @@ -95,6 +96,10 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if err != nil { return nil, err } + verifierType := "" + if input.Config.Type != "" { + verifierType = input.Config.Type + } keyRef := input.Config.KeyRef rekorURL := input.Config.RekorURL cosignOpts := &cosign.CheckOpts{ @@ -106,33 +111,33 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if keyRef != "" { ecdsaVerifier, err = loadPublicKey(ctx, keyRef) if err != nil { - return errorToVerifyResult(input.Config.Name, fmt.Errorf("failed to load public key: %w", err)), nil + return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to load public key: %w", err)), nil } cosignOpts.SigVerifier = ecdsaVerifier } else { roots, err = fulcio.GetRoots() if err != nil { - return errorToVerifyResult(input.Config.Name, fmt.Errorf("failed to get fulcio roots: %w", err)), nil + return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to get fulcio roots: %w", err)), nil } cosignOpts.RootCerts = roots if cosignOpts.RootCerts == nil { - return errorToVerifyResult(input.Config.Name, fmt.Errorf("failed to initialize root certificates")), nil + return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to initialize root certificates")), nil } } if rekorURL != "" { cosignOpts.RekorClient, err = rekor.NewClient(rekorURL) if err != nil { - return errorToVerifyResult(input.Config.Name, fmt.Errorf("failed to create Rekor client from URL %s: %w", rekorURL, err)), nil + return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to create Rekor client from URL %s: %w", rekorURL, err)), nil } cosignOpts.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx) if err != nil { - return errorToVerifyResult(input.Config.Name, fmt.Errorf("failed to set Certificate Transparency Log public keys: %w", err)), nil + return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to set Certificate Transparency Log public keys: %w", err)), nil } // Fetches the Rekor public keys from the Rekor server cosignOpts.RekorPubKeys, err = cosign.GetRekorPubs(ctx) if err != nil { - return errorToVerifyResult(input.Config.Name, fmt.Errorf("failed to set Rekor public keys: %w", err)), nil + return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to set Rekor public keys: %w", err)), nil } } else { // if no rekor url is provided, turn off transparency log verification and ignore SCTs @@ -142,17 +147,17 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe referenceManifest, err := referrerStore.GetReferenceManifest(ctx, subjectReference, referenceDescriptor) if err != nil { - return errorToVerifyResult(input.Config.Name, fmt.Errorf("failed to get reference manifest: %w", err)), nil + return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to get reference manifest: %w", err)), nil } // manifest must be an OCI Image if referenceManifest.MediaType != imgspec.MediaTypeImageManifest { - return errorToVerifyResult(input.Config.Name, fmt.Errorf("reference manifest is not an image")), nil + return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("reference manifest is not an image")), nil } subjectDesc, err := referrerStore.GetSubjectDescriptor(ctx, subjectReference) if err != nil { - return errorToVerifyResult(input.Config.Name, fmt.Errorf("failed to create subject hash: %w", err)), nil + return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to create subject hash: %w", err)), nil } subjectDescHash := v1.Hash{ Algorithm: subjectDesc.Digest.Algorithm().String(), @@ -164,15 +169,15 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe for _, blob := range referenceManifest.Blobs { blobBytes, err := referrerStore.GetBlobContent(ctx, subjectReference, blob.Digest) if err != nil { - return errorToVerifyResult(input.Config.Name, fmt.Errorf("failed to get blob content: %w", err)), nil + return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to get blob content: %w", err)), nil } staticOpts, err := staticLayerOpts(blob) if err != nil { - return errorToVerifyResult(input.Config.Name, fmt.Errorf("failed to parse static signature opts: %w", err)), nil + return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to parse static signature opts: %w", err)), nil } sig, err := static.NewSignature(blobBytes, blob.Annotations[static.SignatureAnnotationKey], staticOpts...) if err != nil { - return errorToVerifyResult(input.Config.Name, fmt.Errorf("failed to generate static signature: %w", err)), nil + return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to generate static signature: %w", err)), nil } // The verification will return an error if the signature is not valid. bundleVerified, err := cosign.VerifyImageSignature(ctx, sig, subjectDescHash, cosignOpts) @@ -193,13 +198,14 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if len(signatures) > 0 { return &verifier.VerifierResult{ Name: input.Config.Name, + Type: verifierType, IsSuccess: true, Message: "cosign verification success. valid signatures found", Extensions: Extension{SignatureExtension: sigExtensions}, }, nil } - errorResult := errorToVerifyResult(input.Config.Name, fmt.Errorf("no valid signatures found")) + errorResult := errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("no valid signatures found")) errorResult.Extensions = Extension{SignatureExtension: sigExtensions} return errorResult, nil } @@ -238,10 +244,11 @@ func staticLayerOpts(desc imgspec.Descriptor) ([]static.Option, error) { return options, nil } -func errorToVerifyResult(name string, err error) *verifier.VerifierResult { +func errorToVerifyResult(name string, verifierType string, err error) *verifier.VerifierResult { return &verifier.VerifierResult{ IsSuccess: false, Name: name, + Type: verifierType, Message: errors.Wrap(err, "cosign verification failed").Error(), } } diff --git a/plugins/verifier/licensechecker/licensechecker.go b/plugins/verifier/licensechecker/licensechecker.go index 3419d41fd..ec7c1dcb9 100644 --- a/plugins/verifier/licensechecker/licensechecker.go +++ b/plugins/verifier/licensechecker/licensechecker.go @@ -32,6 +32,7 @@ import ( type PluginConfig struct { Name string `json:"name"` + Type string `json:"type"` AllowedLicenses []string `json:"allowedLicenses"` } @@ -58,7 +59,10 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, desc if err != nil { return nil, err } - + verifierType := "" + if input.Type != "" { + verifierType = input.Type + } allowedLicenses := utils.LoadAllowedLicenses(input.AllowedLicenses) ctx := context.Background() @@ -70,6 +74,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, desc if len(referenceManifest.Blobs) == 0 { return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("License Check FAILED: no blobs found for referrer %s@%s", subjectReference.Path, descriptor.Digest.String()), }, nil @@ -92,6 +97,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, desc if len(disallowedLicenses) > 0 { return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("License Check: FAILED. %s", disallowedLicenses), }, nil @@ -100,6 +106,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, desc return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: true, Message: "License Check: SUCCESS. All packages have allowed licenses", }, nil diff --git a/plugins/verifier/sample/sample.go b/plugins/verifier/sample/sample.go index 27a14b816..7426b1998 100644 --- a/plugins/verifier/sample/sample.go +++ b/plugins/verifier/sample/sample.go @@ -28,6 +28,7 @@ import ( type PluginConfig struct { Name string `json:"name"` + Type string `json:"type"` // config specific to the plugin } @@ -54,9 +55,14 @@ func VerifyReference(args *skel.CmdArgs, _ common.Reference, referenceDescriptor if err != nil { return nil, err } + verifierType := "" + if input.Type != "" { + verifierType = input.Type + } return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: referenceDescriptor.Size > 0, Message: "Sample verification success", }, nil diff --git a/plugins/verifier/sbom/sbom.go b/plugins/verifier/sbom/sbom.go index 7011b1d9b..a396d6ce8 100644 --- a/plugins/verifier/sbom/sbom.go +++ b/plugins/verifier/sbom/sbom.go @@ -39,6 +39,7 @@ import ( // PluginConfig describes the configuration of the sbom verifier type PluginConfig struct { Name string `json:"name"` + Type string `json:"type"` DisallowedLicenses []string `json:"disallowedLicenses,omitempty"` DisallowedPackages []utils.PackageInfo `json:"disallowedPackages,omitempty"` } @@ -73,12 +74,17 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if err != nil { return nil, err } + verifierType := "" + if input.Type != "" { + verifierType = input.Type + } ctx := context.Background() referenceManifest, err := referrerStore.GetReferenceManifest(ctx, subjectReference, referenceDescriptor) if err != nil { return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("Error fetching reference manifest for subject: %s reference descriptor: %v", subjectReference, referenceDescriptor.Descriptor), }, err @@ -99,6 +105,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if err != nil { return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("Error fetching blob for subject: %s digest: %s", subjectReference, blobDesc.Digest), }, err @@ -106,10 +113,11 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe switch artifactType { case SpdxJSONMediaType: - return processSpdxJSONMediaType(input.Name, refBlob, input.DisallowedLicenses, input.DisallowedPackages) + return processSpdxJSONMediaType(input.Name, verifierType, refBlob, input.DisallowedLicenses, input.DisallowedPackages) default: return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("Unsupported artifactType: %s", artifactType), }, nil @@ -118,6 +126,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: true, Message: "SBOM verification success. No license or package violation found.", }, nil @@ -150,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, 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, error) { var err error var spdxDoc *v2_3.Document if spdxDoc, err = jsonLoader.Read(bytes.NewReader(refBlob)); spdxDoc != nil && err == nil { @@ -179,6 +188,7 @@ func processSpdxJSONMediaType(name string, refBlob []byte, disallowedLicenses [] return &verifier.VerifierResult{ Name: name, + Type: verifierType, IsSuccess: true, Extensions: map[string]interface{}{ CreationInfo: spdxDoc.CreationInfo, @@ -188,6 +198,7 @@ func processSpdxJSONMediaType(name string, refBlob []byte, disallowedLicenses [] } return &verifier.VerifierResult{ Name: name, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("SBOM failed to parse: %v", err), }, err diff --git a/plugins/verifier/sbom/sbom_test.go b/plugins/verifier/sbom/sbom_test.go index ffd978515..0a5c45159 100644 --- a/plugins/verifier/sbom/sbom_test.go +++ b/plugins/verifier/sbom/sbom_test.go @@ -27,7 +27,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) + vr, err := processSpdxJSONMediaType("test", "", b, nil, nil) if err != nil { t.Fatalf("expected to process spdx json file: %s", filepath.Join("testdata", "bom.json")) } @@ -41,7 +41,7 @@ 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) + _, err = processSpdxJSONMediaType("test", "", b, nil, nil) if err == nil { t.Fatalf("expected to have an error processing spdx json file: %s", filepath.Join("testdata", "bom.json")) } @@ -151,7 +151,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) + 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")) } diff --git a/plugins/verifier/schemavalidator/schema_validator.go b/plugins/verifier/schemavalidator/schema_validator.go index ba242dfae..2dd5fec02 100644 --- a/plugins/verifier/schemavalidator/schema_validator.go +++ b/plugins/verifier/schemavalidator/schema_validator.go @@ -31,6 +31,7 @@ import ( type PluginConfig struct { Name string `json:"name"` + Type string `json:"type"` Schemas map[string]string `json:"schemas"` } @@ -54,11 +55,13 @@ func parseInput(stdin []byte) (*PluginConfig, error) { func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, referenceDescriptor ocispecs.ReferenceDescriptor, referrerStore referrerstore.ReferrerStore) (*verifier.VerifierResult, error) { input, err := parseInput(args.StdinData) - if err != nil { return nil, err } - + verifierType := "" + if input.Type != "" { + verifierType = input.Type + } schemaMap := input.Schemas ctx := context.Background() @@ -70,6 +73,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if len(referenceManifest.Blobs) == 0 { return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("schema validation failed: no blobs found for referrer %s@%s", subjectReference.Path, referenceDescriptor.Digest.String()), }, nil @@ -85,6 +89,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if err != nil { return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("schema validation failed for digest:[%s],media type:[%s],parse errors:[%v]", blobDesc.Digest, blobDesc.MediaType, err.Error()), }, nil @@ -93,6 +98,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: true, Message: "schema validation passed for configured media types", }, nil diff --git a/plugins/verifier/vulnerabilityreport/vulnerability_report.go b/plugins/verifier/vulnerabilityreport/vulnerability_report.go index 60d0cd8c1..a3eb495c0 100644 --- a/plugins/verifier/vulnerabilityreport/vulnerability_report.go +++ b/plugins/verifier/vulnerabilityreport/vulnerability_report.go @@ -50,6 +50,7 @@ const ( type PluginConfig struct { Name string `json:"name"` + Type string `json:"type"` SchemaURL string `json:"schemaURL,omitempty"` CreatedAnnotationName string `json:"createdAnnotationName,omitempty"` MaximumAge string `json:"maximumAge,omitempty"` @@ -81,6 +82,10 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if err != nil { return nil, err } + verifierType := "" + if input.Type != "" { + verifierType = input.Type + } // extract created timestamp from descriptor annotations if input.CreatedAnnotationName == "" { @@ -90,6 +95,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if err != nil { return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("vulnerability report validation failed: error extracting create timestamp annotation:[%v]", err.Error()), }, nil @@ -101,6 +107,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if err != nil { return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("vulnerability report validation failed: error validating maximum age:[%v]", err.Error()), Extensions: map[string]interface{}{ @@ -111,6 +118,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if !ok { return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("vulnerability report validation failed: report is older than maximum age:[%s]", input.MaximumAge), Extensions: map[string]interface{}{ @@ -126,6 +134,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if err != nil { return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("error fetching reference manifest for subject: %s reference descriptor: %v: [%v]", subjectReference, referenceDescriptor.Descriptor, err.Error()), Extensions: map[string]interface{}{ @@ -137,6 +146,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if len(referenceManifest.Blobs) == 0 { return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("vulnerability report validation failed: no layers found in manifest for referrer %s@%s", subjectReference.Path, referenceDescriptor.Digest.String()), Extensions: map[string]interface{}{ @@ -150,6 +160,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if err != nil { return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("error fetching blob for subject:[%s] digest:[%s]: [%v]", subjectReference, blobDesc.Digest, err.Error()), Extensions: map[string]interface{}{ @@ -162,6 +173,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if input.Passthrough { return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: true, Message: "vulnerability report validation skipped", Extensions: map[string]interface{}{ @@ -176,6 +188,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if err := verifyJSONSchema(referenceDescriptor.ArtifactType, refBlob, input.SchemaURL); err != nil { return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("vulnerability report validation failed: schema validation failed for digest:[%s],artifact type:[%s],parse errors:[%v]", blobDesc.Digest, referenceDescriptor.ArtifactType, err.Error()), Extensions: map[string]interface{}{ @@ -185,11 +198,12 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe } if referenceDescriptor.ArtifactType == SarifArtifactType { - return processSarifReport(input, input.Name, refBlob, createdTime) + return processSarifReport(input, input.Name, verifierType, refBlob, createdTime) } return &verifier.VerifierResult{ Name: input.Name, + Type: verifierType, IsSuccess: true, Message: "vulnerability report validation succeeded", Extensions: map[string]interface{}{ @@ -217,11 +231,12 @@ func verifyJSONSchema(artifactType string, refBlob []byte, schemaURL string) err } // processSarifReport processes the sarif report running individual validations as configured -func processSarifReport(input *PluginConfig, verifierName string, blob []byte, createdTime time.Time) (*verifier.VerifierResult, error) { +func processSarifReport(input *PluginConfig, verifierName string, verifierType string, blob []byte, createdTime time.Time) (*verifier.VerifierResult, error) { sarifReport, err := sarif.FromBytes(blob) if err != nil { return &verifier.VerifierResult{ Name: verifierName, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("vulnerability report validation failed: error parsing sarif report:[%v]", err.Error()), Extensions: map[string]interface{}{ @@ -233,6 +248,7 @@ func processSarifReport(input *PluginConfig, verifierName string, blob []byte, c if len(sarifReport.Runs) < 1 { return &verifier.VerifierResult{ Name: verifierName, + Type: verifierType, IsSuccess: false, Message: "vulnerability report validation failed: no runs found in sarif report", Extensions: map[string]interface{}{ @@ -242,7 +258,7 @@ func processSarifReport(input *PluginConfig, verifierName string, blob []byte, c } scannerName := strings.ToLower(sarifReport.Runs[0].Tool.Driver.Name) if len(input.DenylistCVEs) > 0 { - verifierReport, err := verifyDenyListCVEs(input.Name, scannerName, sarifReport, input.DenylistCVEs, createdTime) + verifierReport, err := verifyDenyListCVEs(input.Name, verifierType, scannerName, sarifReport, input.DenylistCVEs, createdTime) if err != nil { return nil, err } @@ -251,7 +267,7 @@ func processSarifReport(input *PluginConfig, verifierName string, blob []byte, c } } if len(input.DisallowedSeverities) > 0 { - verifierReport, err := verifyDisallowedSeverities(input.Name, scannerName, sarifReport, input.DisallowedSeverities, createdTime) + verifierReport, err := verifyDisallowedSeverities(input.Name, verifierType, scannerName, sarifReport, input.DisallowedSeverities, createdTime) if err != nil { return nil, err } @@ -262,6 +278,7 @@ func processSarifReport(input *PluginConfig, verifierName string, blob []byte, c return &verifier.VerifierResult{ Name: verifierName, + Type: verifierType, IsSuccess: true, Message: "vulnerability report validation succeeded", Extensions: map[string]interface{}{ @@ -272,7 +289,7 @@ func processSarifReport(input *PluginConfig, verifierName string, blob []byte, c } // verifyDenyListCVEs verifies that the report does not contain any deny-listed CVEs -func verifyDenyListCVEs(verifierName string, scannerName string, sarifReport *sarif.Report, denylistCVEs []string, createdTime time.Time) (*verifier.VerifierResult, error) { +func verifyDenyListCVEs(verifierName string, verifierType string, scannerName string, sarifReport *sarif.Report, denylistCVEs []string, createdTime time.Time) (*verifier.VerifierResult, error) { denylistCVESet := make(map[string]bool) denylistViolations := []string{} @@ -286,6 +303,7 @@ func verifyDenyListCVEs(verifierName string, scannerName string, sarifReport *sa if result.RuleID == nil || *result.RuleID == "" { return &verifier.VerifierResult{ Name: verifierName, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("vulnerability report validation failed: rule id not found for result:[%v]", result), Extensions: map[string]interface{}{ @@ -310,6 +328,7 @@ func verifyDenyListCVEs(verifierName string, scannerName string, sarifReport *sa if len(denylistViolations) > 0 { return &verifier.VerifierResult{ Name: verifierName, + Type: verifierType, IsSuccess: false, Extensions: map[string]interface{}{ "scanner": scannerName, @@ -322,6 +341,7 @@ func verifyDenyListCVEs(verifierName string, scannerName string, sarifReport *sa return &verifier.VerifierResult{ Name: verifierName, + Type: verifierType, IsSuccess: true, Message: "vulnerability report validation succeeded", Extensions: map[string]interface{}{ @@ -332,7 +352,7 @@ func verifyDenyListCVEs(verifierName string, scannerName string, sarifReport *sa } // verifyDisallowedSeverities verifies that the report does not contain any disallowed severity levels -func verifyDisallowedSeverities(verifierName string, scannerName string, sarifReport *sarif.Report, disallowedSeverities []string, createdTime time.Time) (*verifier.VerifierResult, error) { +func verifyDisallowedSeverities(verifierName string, verifierType string, scannerName string, sarifReport *sarif.Report, disallowedSeverities []string, createdTime time.Time) (*verifier.VerifierResult, error) { ruleMap := make(map[string]*sarif.ReportingDescriptor) violatingRules := make([]sarif.ReportingDescriptor, 0) // create a map of rule id to rule for easy lookup @@ -344,6 +364,7 @@ func verifyDisallowedSeverities(verifierName string, scannerName string, sarifRe if result.RuleID == nil || *result.RuleID == "" { return &verifier.VerifierResult{ Name: verifierName, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("vulnerability report validation failed: rule id not found for result:[%v]", result), Extensions: map[string]interface{}{ @@ -356,6 +377,7 @@ func verifyDisallowedSeverities(verifierName string, scannerName string, sarifRe if !ok { return &verifier.VerifierResult{ Name: verifierName, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("vulnerability report validation failed: rule not found for result:[%v]", result), Extensions: map[string]interface{}{ @@ -368,6 +390,7 @@ func verifyDisallowedSeverities(verifierName string, scannerName string, sarifRe if err != nil { return &verifier.VerifierResult{ Name: verifierName, + Type: verifierType, IsSuccess: false, Message: fmt.Sprintf("vulnerability report validation failed: error extracting severity:[%v]", err.Error()), Extensions: map[string]interface{}{ @@ -387,6 +410,7 @@ func verifyDisallowedSeverities(verifierName string, scannerName string, sarifRe if len(violatingRules) > 0 { return &verifier.VerifierResult{ Name: verifierName, + Type: verifierType, IsSuccess: false, Extensions: map[string]interface{}{ "scanner": scannerName, @@ -398,6 +422,7 @@ func verifyDisallowedSeverities(verifierName string, scannerName string, sarifRe } return &verifier.VerifierResult{ Name: verifierName, + Type: verifierType, IsSuccess: true, Message: "vulnerability report validation succeeded", Extensions: map[string]interface{}{ diff --git a/plugins/verifier/vulnerabilityreport/vulnerability_report_test.go b/plugins/verifier/vulnerabilityreport/vulnerability_report_test.go index d06c5af91..1e2adff47 100644 --- a/plugins/verifier/vulnerabilityreport/vulnerability_report_test.go +++ b/plugins/verifier/vulnerabilityreport/vulnerability_report_test.go @@ -398,7 +398,7 @@ func TestProcessSarifReport(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - verifierReport, err := processSarifReport(&tt.args.input, "sample_verifier", []byte(tt.args.blobContent), time.Now()) + verifierReport, err := processSarifReport(&tt.args.input, "sample_verifier", "", []byte(tt.args.blobContent), time.Now()) if err != nil && err.Error() != tt.want.err.Error() { t.Errorf("processSarifReport() error = %v, wantErr %v", err, tt.want.err) return @@ -521,7 +521,7 @@ func TestVerifyDenyListCVEs(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - verifierReport, err := verifyDenyListCVEs("test_verifier", TrivyScannerName, &tt.args.sarifReport, tt.args.denyListCVEs, time.Now()) + verifierReport, err := verifyDenyListCVEs("test_verifier", "", TrivyScannerName, &tt.args.sarifReport, tt.args.denyListCVEs, time.Now()) if err != nil && err.Error() != tt.want.err.Error() { t.Errorf("verifyDenyListCVEs() error = %v, wantErr %v", err, tt.want.err) return @@ -729,7 +729,7 @@ func TestVerifyDisallowedSeverities(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - verifierReport, err := verifyDisallowedSeverities("test_verifier", TrivyScannerName, &tt.args.sarifReport, tt.args.disallowedSeverities, time.Now()) + verifierReport, err := verifyDisallowedSeverities("test_verifier", "", TrivyScannerName, &tt.args.sarifReport, tt.args.disallowedSeverities, time.Now()) if err != nil && err.Error() != tt.want.err.Error() { t.Errorf("verifyDisallowedSeverities() error = %v, wantErr %v", err, tt.want.err) return diff --git a/test/bats/cli-test.bats b/test/bats/cli-test.bats index 1213caf2f..1960eac56 100644 --- a/test/bats/cli-test.bats +++ b/test/bats/cli-test.bats @@ -31,6 +31,16 @@ load helpers assert_cmd_verify_failure } +@test "notation verifier with type test" { + run bin/ratify verify -c $RATIFY_DIR/config_notation_verifier_with_type.json -s $TEST_REGISTRY/notation:leafSigned + assert_cmd_verify_success_with_type +} + +@test "multiple notation verifiers test" { + run bin/ratify verify -c $RATIFY_DIR/config_multiple_notation_verifiers.json -s $TEST_REGISTRY/notation:leafSigned + assert_cmd_multi_verifier_success +} + @test "notation verifier leaf cert with rego policy" { run bin/ratify verify -c $RATIFY_DIR/config_rego_policy_notation_root_cert.json -s $TEST_REGISTRY/notation:leafSigned assert_cmd_verify_success @@ -59,6 +69,11 @@ load helpers assert_cmd_verify_failure } +@test "licensechecker verifier with type test" { + run bin/ratify verify -c $RATIFY_DIR/config_external_verifier_with_type.json -s $TEST_REGISTRY/licensechecker:v0 + assert_cmd_verify_success_with_type +} + @test "sbom verifier test" { # run with deny license config should fail run bin/ratify verify -c $RATIFY_DIR/sbom_denylist_config_licensematch.json -s $TEST_REGISTRY/sbom:v0 diff --git a/test/bats/helpers.bash b/test/bats/helpers.bash index d685a2ee1..d8c872686 100644 --- a/test/bats/helpers.bash +++ b/test/bats/helpers.bash @@ -41,6 +41,30 @@ assert_cmd_verify_success() { fi } +assert_cmd_multi_verifier_success() { + if [[ "$status" != 0 ]]; then + return 1 + fi + if [[ "$output" == *'{ "isSuccess": true, "verifierReports"'* ]]; then + echo $output + return 1 + fi +} + +assert_cmd_verify_success_with_type() { + if [[ "$status" != 0 ]]; then + return 1 + fi + if [[ "$output" == *'"isSuccess": false,'* ]]; then + echo $output + return 1 + fi + if [[ "$output" != *'"type":'* ]]; then + echo $output + return 1 + fi +} + assert_cmd_cosign_keyless_verify_bundle_success() { if [[ "$status" != 0 ]]; then return 1 diff --git a/test/bats/tests/config/config_external_verifier_with_type.json b/test/bats/tests/config/config_external_verifier_with_type.json new file mode 100644 index 000000000..fc86027a8 --- /dev/null +++ b/test/bats/tests/config/config_external_verifier_with_type.json @@ -0,0 +1,45 @@ +{ + "store": { + "version": "1.0.0", + "plugins": [ + { + "name": "oras", + "useHttp": true + } + ] + }, + "policy": { + "version": "1.0.0", + "plugin": { + "name": "configPolicy", + "artifactVerificationPolicies": { + "application/vnd.ratify.spdx.v0": "all" + } + } + }, + "verifier": { + "version": "1.0.0", + "plugins": [ + { + "name": "verifier-licensechecker", + "type": "licensechecker", + "artifactTypes": "application/vnd.ratify.spdx.v0", + "allowedLicenses": [ + "GPL-2.0-only", + "MIT", + "OpenSSL", + "BSD-2-Clause AND BSD-3-Clause", + "Zlib", + "MPL-2.0 AND MIT", + "ISC", + "Apache-2.0", + "MIT AND BSD-2-Clause AND GPL-2.0-or-later", + "MIT AND LicenseRef-AND AND BSD-2-Clause AND LicenseRef-AND AND GPL-2.0-or-later", + "MPL-2.0 AND LicenseRef-AND AND MIT", + "BSD-2-Clause AND LicenseRef-AND AND BSD-3-Clause", + "NONE" + ] + } + ] + } +} \ No newline at end of file diff --git a/test/bats/tests/config/config_multiple_notation_verifiers.json b/test/bats/tests/config/config_multiple_notation_verifiers.json new file mode 100644 index 000000000..43ac3326a --- /dev/null +++ b/test/bats/tests/config/config_multiple_notation_verifiers.json @@ -0,0 +1,82 @@ +{ + "executor": {}, + "store": { + "version": "1.0.0", + "plugins": [ + { + "name": "oras", + "cosignEnabled": true, + "useHttp": true + } + ] + }, + "policy": { + "version": "1.0.0", + "plugin": { + "name": "regoPolicy", + "policyPath": "", + "policy": "package ratify.policy\ndefault valid := false\nvalid {\n succeeded_verify(input)\n}\nsucceeded_verify(reports) {\n [path, value] := walk(reports)\n value == true\n path[count(path) - 1] == \"isSuccess\"\n}" + } + }, + "verifier": { + "version": "1.0.0", + "plugins": [ + { + "name": "verifier-notation-root", + "type": "notation", + "artifactTypes": "application/vnd.cncf.notary.signature", + "verificationCerts": [ + "~/.config/notation/truststore/x509/ca/leaf-test/root.crt" + ], + "trustPolicyDoc": { + "version": "1.0", + "trustPolicies": [ + { + "name": "default", + "registryScopes": [ + "*" + ], + "signatureVerification": { + "level": "strict" + }, + "trustStores": [ + "ca:certs" + ], + "trustedIdentities": [ + "*" + ] + } + ] + } + }, + { + "name": "verifier-notation-leaf", + "type": "notation", + "artifactTypes": "application/vnd.cncf.notary.signature", + "verificationCerts": [ + "~/.config/notation/truststore/x509/ca/leaf-test/leaf.crt" + ], + "trustPolicyDoc": { + "version": "1.0", + "trustPolicies": [ + { + "name": "default", + "registryScopes": [ + "*" + ], + "signatureVerification": { + "level": "strict" + }, + "trustStores": [ + "ca:certs" + ], + "trustedIdentities": [ + "*" + ] + } + ] + } + } + ] + } +} \ No newline at end of file diff --git a/test/bats/tests/config/config_notation_verifier_with_type.json b/test/bats/tests/config/config_notation_verifier_with_type.json new file mode 100644 index 000000000..f7dea47b3 --- /dev/null +++ b/test/bats/tests/config/config_notation_verifier_with_type.json @@ -0,0 +1,51 @@ +{ + "store": { + "version": "1.0.0", + "plugins": [ + { + "name": "oras", + "cosignEnabled": true, + "useHttp": true + } + ] + }, + "policy": { + "version": "1.0.0", + "plugin": { + "name": "configPolicy" + } + }, + "verifier": { + "version": "1.0.0", + "plugins": [ + { + "name": "verifier-notation-root", + "type": "notation", + "artifactTypes": "application/vnd.cncf.notary.signature", + "verificationCerts": [ + "~/.config/notation/truststore/x509/ca/leaf-test/root.crt" + ], + "trustPolicyDoc": { + "version": "1.0", + "trustPolicies": [ + { + "name": "default", + "registryScopes": [ + "*" + ], + "signatureVerification": { + "level": "strict" + }, + "trustStores": [ + "ca:certs" + ], + "trustedIdentities": [ + "*" + ] + } + ] + } + } + ] + } +} \ No newline at end of file diff --git a/test/bats/tests/config/config_rego_policy_notation_root_cert.json b/test/bats/tests/config/config_rego_policy_notation_root_cert.json index 57757c27d..eaf786090 100644 --- a/test/bats/tests/config/config_rego_policy_notation_root_cert.json +++ b/test/bats/tests/config/config_rego_policy_notation_root_cert.json @@ -15,7 +15,7 @@ "plugin": { "name": "regoPolicy", "policyPath": "", - "policy": "package ratify.policy\ndefault valid := false\nvalid {\n not failed_verify(input)\n}\nfailed_verify(reports) {\n [path, value] := walk(reports)\n value == false\n path[count(path) - 1] == \"isSuccess\"\n}" + "policy": "package ratify.policy\ndefault valid := false\nvalid {\n not failed_verify(input)\n}\nfailed_verify(reports) {\n [path, value] := walk(reports)\n value == false\n path[count(path) - 1] == \"isSuccess\"\n}" } }, "verifier": {