From d92561ca8fb71b22ef94d2babfab1602bf25ebe6 Mon Sep 17 00:00:00 2001 From: abhisek Date: Tue, 21 Feb 2023 11:18:40 +0530 Subject: [PATCH 1/9] #13: Add spec for exceptions management --- Makefile | 11 +- api/exceptions_spec.proto | 17 ++ docs/exceptions.md | 80 +++++++ gen/exceptionsapi/exceptions_spec.pb.go | 266 ++++++++++++++++++++++++ samples/exceptions.yml | 13 ++ 5 files changed, 386 insertions(+), 1 deletion(-) create mode 100644 api/exceptions_spec.proto create mode 100644 docs/exceptions.md create mode 100644 gen/exceptionsapi/exceptions_spec.pb.go create mode 100644 samples/exceptions.yml diff --git a/Makefile b/Makefile index 1666ba8..5f300e5 100644 --- a/Makefile +++ b/Makefile @@ -25,9 +25,18 @@ protoc-codegen: --go_out=./gen/filtersuite \ --go_opt=paths=source_relative \ ./api/filter_suite_spec.proto + protoc -I ./api \ + --go_out=./gen/exceptionsapi \ + --go_opt=paths=source_relative \ + ./api/exceptions_spec.proto setup: - mkdir -p out gen/insightapi gen/controlplane gen/filterinput gen/filtersuite + mkdir -p out \ + gen/insightapi \ + gen/controlplane \ + gen/filterinput \ + gen/filtersuite \ + gen/exceptionsapi GO_CFLAGS=-X main.commit=$(GITCOMMIT) -X main.version=$(VERSION) GO_LDFLAGS=-ldflags "-w $(GO_CFLAGS)" diff --git a/api/exceptions_spec.proto b/api/exceptions_spec.proto new file mode 100644 index 0000000..d4985c3 --- /dev/null +++ b/api/exceptions_spec.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option go_package = "github.com/safedep/vet/gen/exceptionsapi"; + +message Exception { + string ecosystem = 1; + string name = 2; + string version = 3; + string expires = 4; + string pattern = 5; // To be used for special cases +} + +message ExceptionSuite { + string name = 1; + string description = 2; + repeated Exception exceptions = 3; +} diff --git a/docs/exceptions.md b/docs/exceptions.md new file mode 100644 index 0000000..4349d58 --- /dev/null +++ b/docs/exceptions.md @@ -0,0 +1,80 @@ +# Exceptions Management + +Any security scanning tool may produce + +1. False positive +2. Issues that are acceptable for a period of time +3. Issues that are ignored permanently + +To support exceptions, we introduce the exception model defined in [exception +spec](../api/exception_spec.proto) + +## Use-case + +> As a user of `vet` tool, I want to add all existing packages or package +> versions as `exceptions` to make the scanner and filter analyser to ignore +> them while reporting issues so that I can deploy `vet` as a security gate to +> prevent introducing new packages with security issues + +This workflow will allow users to + +1. Accept the current issues as backlog to be mitigated over time +2. Deploy `vet` as a security gate in CI to prevent introducing new issues + +### Security Risks + +Exceptions management should handle the potential security risk of ignoring an +asset and its future issues. To mitigate this risk, we will ensure that issues +can be ignored till an acceptable time window and not permanently. + +## Workflow + +### Generate Exceptions File + +Run a scan and dump raw data to a directory + +```bash +vet scan -D /path/to/repo --json-dump-dir /path/to/dump +``` + +Use `vet query` to generate exceptions for all existing packages + +```bash +vet query --from /path/to/dump \ + --exceptions-generate /path/to/exceptions.yml \ + --exceptions-filter 'true' \ # Optional filter for packages to add + --exceptions-till '2023-12-12' +``` + +> `--exceptions-till` is parsed as `YYYY-mm-dd` and will generate a timestamp +> of `00:00:00` in UTC timezone for the date in RFC3339 format + +### Customize Exceptions File + +The generated exceptions file will add all packages, matching optional filter, +into the `exceptions.yml` file. This file should be reviewed and customised as +required before using it. + +### Use Exceptions to Ignore Specific Packages + +An exceptions file can be passed as a global flag to `vet`. It will be used for +various commands such as `scan` or `query`. + +```bash +./vet --exceptions /path/to/exceptions.yml scan -D /path/to/repo +``` + +## Behaviour + +* All exceptions rules are applied only on a `Package` +* All comparisons will be case-insensitive +* Only `version` can have a value of `*` matching any version +* Exceptions are globally managed and will be shared across packages +* Exempted packages will be ignored by all analysers and reporters + +Anti-patterns that will NOT be implemented + +* Exceptions will not be implemented for manifests because they will cause + false negatives +* Exceptions will not be created without an expiry to avoid future false + negatives on the package diff --git a/gen/exceptionsapi/exceptions_spec.pb.go b/gen/exceptionsapi/exceptions_spec.pb.go new file mode 100644 index 0000000..ce300e9 --- /dev/null +++ b/gen/exceptionsapi/exceptions_spec.pb.go @@ -0,0 +1,266 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.0 +// protoc v3.18.0 +// source: exceptions_spec.proto + +package exceptionsapi + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Exception struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ecosystem string `protobuf:"bytes,1,opt,name=ecosystem,proto3" json:"ecosystem,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` + Expires string `protobuf:"bytes,4,opt,name=expires,proto3" json:"expires,omitempty"` + Pattern string `protobuf:"bytes,5,opt,name=pattern,proto3" json:"pattern,omitempty"` // To be used for special cases +} + +func (x *Exception) Reset() { + *x = Exception{} + if protoimpl.UnsafeEnabled { + mi := &file_exceptions_spec_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Exception) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Exception) ProtoMessage() {} + +func (x *Exception) ProtoReflect() protoreflect.Message { + mi := &file_exceptions_spec_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Exception.ProtoReflect.Descriptor instead. +func (*Exception) Descriptor() ([]byte, []int) { + return file_exceptions_spec_proto_rawDescGZIP(), []int{0} +} + +func (x *Exception) GetEcosystem() string { + if x != nil { + return x.Ecosystem + } + return "" +} + +func (x *Exception) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Exception) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *Exception) GetExpires() string { + if x != nil { + return x.Expires + } + return "" +} + +func (x *Exception) GetPattern() string { + if x != nil { + return x.Pattern + } + return "" +} + +type ExceptionSuite struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + Exceptions []*Exception `protobuf:"bytes,3,rep,name=exceptions,proto3" json:"exceptions,omitempty"` +} + +func (x *ExceptionSuite) Reset() { + *x = ExceptionSuite{} + if protoimpl.UnsafeEnabled { + mi := &file_exceptions_spec_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExceptionSuite) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExceptionSuite) ProtoMessage() {} + +func (x *ExceptionSuite) ProtoReflect() protoreflect.Message { + mi := &file_exceptions_spec_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExceptionSuite.ProtoReflect.Descriptor instead. +func (*ExceptionSuite) Descriptor() ([]byte, []int) { + return file_exceptions_spec_proto_rawDescGZIP(), []int{1} +} + +func (x *ExceptionSuite) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ExceptionSuite) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *ExceptionSuite) GetExceptions() []*Exception { + if x != nil { + return x.Exceptions + } + return nil +} + +var File_exceptions_spec_proto protoreflect.FileDescriptor + +var file_exceptions_spec_proto_rawDesc = []byte{ + 0x0a, 0x15, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x73, 0x70, 0x65, + 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8b, 0x01, 0x0a, 0x09, 0x45, 0x78, 0x63, 0x65, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x63, 0x6f, 0x73, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x63, 0x6f, 0x73, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, + 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, + 0x74, 0x74, 0x65, 0x72, 0x6e, 0x22, 0x72, 0x0a, 0x0e, 0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, + 0x0a, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x0a, 0x2e, 0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x65, + 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x2a, 0x5a, 0x28, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x61, 0x66, 0x65, 0x64, 0x65, 0x70, 0x2f, + 0x76, 0x65, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_exceptions_spec_proto_rawDescOnce sync.Once + file_exceptions_spec_proto_rawDescData = file_exceptions_spec_proto_rawDesc +) + +func file_exceptions_spec_proto_rawDescGZIP() []byte { + file_exceptions_spec_proto_rawDescOnce.Do(func() { + file_exceptions_spec_proto_rawDescData = protoimpl.X.CompressGZIP(file_exceptions_spec_proto_rawDescData) + }) + return file_exceptions_spec_proto_rawDescData +} + +var file_exceptions_spec_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_exceptions_spec_proto_goTypes = []interface{}{ + (*Exception)(nil), // 0: Exception + (*ExceptionSuite)(nil), // 1: ExceptionSuite +} +var file_exceptions_spec_proto_depIdxs = []int32{ + 0, // 0: ExceptionSuite.exceptions:type_name -> Exception + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_exceptions_spec_proto_init() } +func file_exceptions_spec_proto_init() { + if File_exceptions_spec_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_exceptions_spec_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Exception); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_exceptions_spec_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExceptionSuite); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_exceptions_spec_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_exceptions_spec_proto_goTypes, + DependencyIndexes: file_exceptions_spec_proto_depIdxs, + MessageInfos: file_exceptions_spec_proto_msgTypes, + }.Build() + File_exceptions_spec_proto = out.File + file_exceptions_spec_proto_rawDesc = nil + file_exceptions_spec_proto_goTypes = nil + file_exceptions_spec_proto_depIdxs = nil +} diff --git a/samples/exceptions.yml b/samples/exceptions.yml new file mode 100644 index 0000000..f4eb04f --- /dev/null +++ b/samples/exceptions.yml @@ -0,0 +1,13 @@ +name: Sample Exceptions File +description: | + This is a sample exceptions file +exceptions: + - ecosystem: maven + name: com.example.mypackage:mylib1 + version: '1.2.3' + expires: 2009-11-10T23:00:00Z + - ecosystem: maven + name: com.example.mypackage:mylib2 + version: '*' + expires: 2009-11-10T23:00:00Z + From eadcd1a83da38a77b64f8b99b569e852818e4833 Mon Sep 17 00:00:00 2001 From: abhisek Date: Tue, 21 Feb 2023 13:08:06 +0530 Subject: [PATCH 2/9] #13: Add exceptions package --- api/exceptions_spec.proto | 11 ++-- docs/exceptions.md | 6 +- gen/exceptionsapi/exceptions_spec.pb.go | 33 ++++++---- pkg/exceptions/exceptions.go | 88 +++++++++++++++++++++++++ pkg/exceptions/exceptions_test.go | 62 +++++++++++++++++ 5 files changed, 180 insertions(+), 20 deletions(-) create mode 100644 pkg/exceptions/exceptions.go create mode 100644 pkg/exceptions/exceptions_test.go diff --git a/api/exceptions_spec.proto b/api/exceptions_spec.proto index d4985c3..6ea9c70 100644 --- a/api/exceptions_spec.proto +++ b/api/exceptions_spec.proto @@ -3,11 +3,12 @@ syntax = "proto3"; option go_package = "github.com/safedep/vet/gen/exceptionsapi"; message Exception { - string ecosystem = 1; - string name = 2; - string version = 3; - string expires = 4; - string pattern = 5; // To be used for special cases + string id = 1; + string ecosystem = 2; + string name = 3; + string version = 4; + string expires = 5; + string pattern = 6; // To be used for special cases } message ExceptionSuite { diff --git a/docs/exceptions.md b/docs/exceptions.md index 4349d58..0140be0 100644 --- a/docs/exceptions.md +++ b/docs/exceptions.md @@ -7,7 +7,7 @@ Any security scanning tool may produce 3. Issues that are ignored permanently To support exceptions, we introduce the exception model defined in [exception -spec](../api/exception_spec.proto) +spec](../api/exceptions_spec.proto) ## Use-case @@ -23,8 +23,8 @@ This workflow will allow users to ### Security Risks -Exceptions management should handle the potential security risk of ignoring an -asset and its future issues. To mitigate this risk, we will ensure that issues +Exceptions management should handle the potential security risk of ignoring a +package and its future issues. To mitigate this risk, we will ensure that issues can be ignored till an acceptable time window and not permanently. ## Workflow diff --git a/gen/exceptionsapi/exceptions_spec.pb.go b/gen/exceptionsapi/exceptions_spec.pb.go index ce300e9..2ed281c 100644 --- a/gen/exceptionsapi/exceptions_spec.pb.go +++ b/gen/exceptionsapi/exceptions_spec.pb.go @@ -25,11 +25,12 @@ type Exception struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Ecosystem string `protobuf:"bytes,1,opt,name=ecosystem,proto3" json:"ecosystem,omitempty"` - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` - Expires string `protobuf:"bytes,4,opt,name=expires,proto3" json:"expires,omitempty"` - Pattern string `protobuf:"bytes,5,opt,name=pattern,proto3" json:"pattern,omitempty"` // To be used for special cases + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Ecosystem string `protobuf:"bytes,2,opt,name=ecosystem,proto3" json:"ecosystem,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"` + Expires string `protobuf:"bytes,5,opt,name=expires,proto3" json:"expires,omitempty"` + Pattern string `protobuf:"bytes,6,opt,name=pattern,proto3" json:"pattern,omitempty"` // To be used for special cases } func (x *Exception) Reset() { @@ -64,6 +65,13 @@ func (*Exception) Descriptor() ([]byte, []int) { return file_exceptions_spec_proto_rawDescGZIP(), []int{0} } +func (x *Exception) GetId() string { + if x != nil { + return x.Id + } + return "" +} + func (x *Exception) GetEcosystem() string { if x != nil { return x.Ecosystem @@ -166,15 +174,16 @@ var File_exceptions_spec_proto protoreflect.FileDescriptor var file_exceptions_spec_proto_rawDesc = []byte{ 0x0a, 0x15, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x73, 0x70, 0x65, - 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8b, 0x01, 0x0a, 0x09, 0x45, 0x78, 0x63, 0x65, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x63, 0x6f, 0x73, 0x79, 0x73, 0x74, - 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x63, 0x6f, 0x73, 0x79, 0x73, - 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9b, 0x01, 0x0a, 0x09, 0x45, 0x78, 0x63, 0x65, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x63, 0x6f, 0x73, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x63, 0x6f, 0x73, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, - 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, + 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x22, 0x72, 0x0a, 0x0e, 0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, diff --git a/pkg/exceptions/exceptions.go b/pkg/exceptions/exceptions.go new file mode 100644 index 0000000..c62661a --- /dev/null +++ b/pkg/exceptions/exceptions.go @@ -0,0 +1,88 @@ +package exceptions + +import ( + "fmt" + "hash/fnv" + "io" + "strconv" + "strings" + "sync" + "time" + + "github.com/safedep/vet/gen/exceptionsapi" +) + +// Represents an exception rule as per spec with additional details +type exceptionRule struct { + spec *exceptionsapi.Exception + expiry time.Time +} + +// In-memory store of exceptions to be used for package hash and exception ID +// based lookup for fast matching and avoid duplicates +type exceptionStore struct { + m sync.Mutex + rules map[string]map[string]*exceptionRule +} + +// Represents an exceptions loader interface to support loading exceptions +// from multiple sources +type exceptionsLoader interface { + // Read an exception rule, return io.EOF on done + Read() (*exceptionRule, error) +} + +// Global exceptions store +var globalExceptions *exceptionStore + +// Initialize the global exceptions cache +func init() { + initStore() +} + +func initStore() { + globalExceptions = &exceptionStore{ + rules: make(map[string]map[string]*exceptionRule), + } +} + +func Load(loader exceptionsLoader) error { + globalExceptions.m.Lock() + defer globalExceptions.m.Unlock() + + for { + rule, err := loader.Read() + if err != nil { + if err == io.EOF { + break + } else { + return err + } + } + + h := pkgHash(rule.spec.GetEcosystem(), rule.spec.GetName()) + if _, ok := globalExceptions.rules[h]; ok { + if _, ok = globalExceptions.rules[h][rule.spec.GetId()]; ok { + continue + } + } else { + globalExceptions.rules[h] = make(map[string]*exceptionRule) + } + + if rule.expiry.UTC().Before(time.Now().UTC()) { + continue + } + + globalExceptions.rules[h][rule.spec.GetId()] = rule + } + + return nil +} + +func pkgHash(ecosystem, name string) string { + h := fnv.New64a() + h.Write([]byte(fmt.Sprintf("%s/%s", + strings.ToLower(ecosystem), strings.ToLower(name)))) + + return strconv.FormatUint(h.Sum64(), 16) +} diff --git a/pkg/exceptions/exceptions_test.go b/pkg/exceptions/exceptions_test.go new file mode 100644 index 0000000..3c1c184 --- /dev/null +++ b/pkg/exceptions/exceptions_test.go @@ -0,0 +1,62 @@ +package exceptions + +import ( + "io" + "testing" + "time" + + "github.com/safedep/vet/gen/exceptionsapi" + "github.com/stretchr/testify/assert" +) + +type exceptionsLoaderMocker struct { + rules []exceptionRule + idx int +} + +func (m *exceptionsLoaderMocker) Read() (*exceptionRule, error) { + if m.idx >= len(m.rules) { + return nil, io.EOF + } + + r := m.rules[m.idx] + m.idx += 1 + + return &r, nil +} + +func TestLoad(t *testing.T) { + cases := []struct { + name string + rules []exceptionRule + pCount int // Package count + }{ + { + "Load a rule", + []exceptionRule{ + { + spec: &exceptionsapi.Exception{ + Id: "a", + Ecosystem: "maven", + Name: "p1", + }, + expiry: time.Now().Add(1 * time.Hour), + }, + }, + 1, + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + // Reset the store + initStore() + + Load(&exceptionsLoaderMocker{ + rules: test.rules, + }) + + assert.Equal(t, test.pCount, len(globalExceptions.rules)) + }) + } +} From 0c41bdd5b078dee0e6b357d2f9890a810996ed77 Mon Sep 17 00:00:00 2001 From: abhisek Date: Tue, 21 Feb 2023 18:00:20 +0530 Subject: [PATCH 3/9] Add exception matching logic --- docs/exceptions.md | 3 +- pkg/exceptions/exceptions.go | 62 +++++++++- pkg/exceptions/exceptions_test.go | 190 ++++++++++++++++++++++++++++++ 3 files changed, 249 insertions(+), 6 deletions(-) diff --git a/docs/exceptions.md b/docs/exceptions.md index 0140be0..96c444f 100644 --- a/docs/exceptions.md +++ b/docs/exceptions.md @@ -67,10 +67,11 @@ various commands such as `scan` or `query`. ## Behaviour * All exceptions rules are applied only on a `Package` -* All comparisons will be case-insensitive +* All comparisons will be case-insensitive except version * Only `version` can have a value of `*` matching any version * Exceptions are globally managed and will be shared across packages * Exempted packages will be ignored by all analysers and reporters +* First match policy for exceptions matching Anti-patterns that will NOT be implemented diff --git a/pkg/exceptions/exceptions.go b/pkg/exceptions/exceptions.go index c62661a..76b7436 100644 --- a/pkg/exceptions/exceptions.go +++ b/pkg/exceptions/exceptions.go @@ -10,6 +10,11 @@ import ( "time" "github.com/safedep/vet/gen/exceptionsapi" + "github.com/safedep/vet/pkg/models" +) + +var ( + jitter = 5 * time.Second ) // Represents an exception rule as per spec with additional details @@ -21,7 +26,7 @@ type exceptionRule struct { // In-memory store of exceptions to be used for package hash and exception ID // based lookup for fast matching and avoid duplicates type exceptionStore struct { - m sync.Mutex + m sync.RWMutex rules map[string]map[string]*exceptionRule } @@ -32,6 +37,12 @@ type exceptionsLoader interface { Read() (*exceptionRule, error) } +// Represents an exception match result +type exceptionMatchResult struct { + pkg *models.Package + rule *exceptionRule +} + // Global exceptions store var globalExceptions *exceptionStore @@ -60,6 +71,10 @@ func Load(loader exceptionsLoader) error { } } + if rule.expiry.Before(time.Now().Add(jitter)) { + continue + } + h := pkgHash(rule.spec.GetEcosystem(), rule.spec.GetName()) if _, ok := globalExceptions.rules[h]; ok { if _, ok = globalExceptions.rules[h][rule.spec.GetId()]; ok { @@ -69,16 +84,53 @@ func Load(loader exceptionsLoader) error { globalExceptions.rules[h] = make(map[string]*exceptionRule) } - if rule.expiry.UTC().Before(time.Now().UTC()) { - continue - } - globalExceptions.rules[h][rule.spec.GetId()] = rule } return nil } +func Apply(pkg *models.Package) (*exceptionMatchResult, error) { + return globalExceptions.Match(pkg) +} + +func (s *exceptionStore) Match(pkg *models.Package) (*exceptionMatchResult, error) { + result := exceptionMatchResult{} + + s.m.RLock() + defer s.m.RUnlock() + + h := pkgHash(string(pkg.PackageDetails.Ecosystem), pkg.PackageDetails.Name) + if _, ok := s.rules[h]; !ok { + return &result, nil + } + + for _, rule := range s.rules[h] { + if rule.matchByPattern(pkg) || rule.matchByVersion(pkg) { + result.pkg = pkg + result.rule = rule + + return &result, nil + } + } + + return &result, nil +} + +func (r *exceptionRule) matchByPattern(pkg *models.Package) bool { + return false +} + +func (r *exceptionRule) matchByVersion(pkg *models.Package) bool { + return strings.EqualFold(string(pkg.PackageDetails.Ecosystem), r.spec.GetEcosystem()) && + strings.EqualFold(pkg.PackageDetails.Name, r.spec.GetName()) && + ((r.spec.GetVersion() == "*") || (r.spec.GetVersion() == pkg.PackageDetails.Version)) +} + +func (r *exceptionMatchResult) Matched() bool { + return (r == nil) || ((r.pkg != nil) && (r.rule != nil)) +} + func pkgHash(ecosystem, name string) string { h := fnv.New64a() h.Write([]byte(fmt.Sprintf("%s/%s", diff --git a/pkg/exceptions/exceptions_test.go b/pkg/exceptions/exceptions_test.go index 3c1c184..7758f94 100644 --- a/pkg/exceptions/exceptions_test.go +++ b/pkg/exceptions/exceptions_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/safedep/vet/gen/exceptionsapi" + "github.com/safedep/vet/pkg/models" "github.com/stretchr/testify/assert" ) @@ -45,6 +46,64 @@ func TestLoad(t *testing.T) { }, 1, }, + { + "Load two rule for same package", + []exceptionRule{ + { + spec: &exceptionsapi.Exception{ + Id: "a", + Ecosystem: "maven", + Name: "p1", + }, + expiry: time.Now().Add(1 * time.Hour), + }, + { + spec: &exceptionsapi.Exception{ + Id: "b", + Ecosystem: "maven", + Name: "p1", + }, + expiry: time.Now().Add(1 * time.Hour), + }, + }, + 1, + }, + { + "Load two rule for two package", + []exceptionRule{ + { + spec: &exceptionsapi.Exception{ + Id: "a", + Ecosystem: "maven", + Name: "p1", + }, + expiry: time.Now().Add(1 * time.Hour), + }, + { + spec: &exceptionsapi.Exception{ + Id: "b", + Ecosystem: "maven", + Name: "p2", + }, + expiry: time.Now().Add(1 * time.Hour), + }, + }, + 2, + }, + { + "Load an expired rule", + []exceptionRule{ + { + spec: &exceptionsapi.Exception{ + Id: "a", + Ecosystem: "maven", + Name: "p1", + }, + expiry: time.Now().Add(-1 * time.Hour), + }, + }, + 0, + }, } for _, test := range cases { @@ -60,3 +119,134 @@ func TestLoad(t *testing.T) { }) } } + +func TestApply(t *testing.T) { + cases := []struct { + name string + rules []exceptionRule + ecosystem string + pkgName string + version string + match bool + errMsg string + }{ + { + "Match by version", + []exceptionRule{ + { + spec: &exceptionsapi.Exception{ + Id: "a", + Ecosystem: "maven", + Name: "p1", + Version: "v1", + }, + expiry: time.Now().Add(1 * time.Hour), + }, + }, + "maven", + "p1", + "v1", + true, + "", + }, + { + "Match any version", + []exceptionRule{ + { + spec: &exceptionsapi.Exception{ + Id: "a", + Ecosystem: "maven", + Name: "p1", + Version: "*", + }, + expiry: time.Now().Add(1 * time.Hour), + }, + }, + "maven", + "p1", + "v-anything", + true, + "", + }, + { + "No Match without a version", + []exceptionRule{ + { + spec: &exceptionsapi.Exception{ + Id: "a", + Ecosystem: "maven", + Name: "p1", + Version: "", + }, + expiry: time.Now().Add(1 * time.Hour), + }, + }, + "maven", + "p1", + "v-anything", + false, + "", + }, + { + "Match with case-insensitive", + []exceptionRule{ + { + spec: &exceptionsapi.Exception{ + Id: "a", + Ecosystem: "MAVEN", + Name: "P1", + Version: "v1", + }, + expiry: time.Now().Add(1 * time.Hour), + }, + }, + "maven", + "p1", + "v1", + true, + "", + }, + { + "No match with case-insensitive version", + []exceptionRule{ + { + spec: &exceptionsapi.Exception{ + Id: "a", + Ecosystem: "MAVEN", + Name: "P1", + Version: "V1", + }, + expiry: time.Now().Add(1 * time.Hour), + }, + }, + "maven", + "p1", + "v1", + false, + "", + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + initStore() + + Load(&exceptionsLoaderMocker{ + rules: test.rules, + }) + + pd := models.NewPackageDetail(test.ecosystem, test.pkgName, test.version) + res, err := Apply(&models.Package{ + PackageDetails: pd, + }) + + if test.errMsg != "" { + assert.NotNil(t, err) + assert.ErrorContains(t, err, test.errMsg) + } else { + assert.Nil(t, err) + assert.Equal(t, test.match, res.Matched()) + } + }) + } +} From 0c99a6988a065c9fc550c2c3e2c52dcfc6f26016 Mon Sep 17 00:00:00 2001 From: abhisek Date: Tue, 21 Feb 2023 18:57:11 +0530 Subject: [PATCH 4/9] Add file based exceptions loader --- pkg/exceptions/exceptions_loader_file.go | 60 +++++++++++++ pkg/exceptions/exceptions_loader_file_test.go | 86 +++++++++++++++++++ pkg/exceptions/fixtures/1_valid.yml | 17 ++++ pkg/exceptions/fixtures/2_invalid.yml | 2 + 4 files changed, 165 insertions(+) create mode 100644 pkg/exceptions/exceptions_loader_file.go create mode 100644 pkg/exceptions/exceptions_loader_file_test.go create mode 100644 pkg/exceptions/fixtures/1_valid.yml create mode 100644 pkg/exceptions/fixtures/2_invalid.yml diff --git a/pkg/exceptions/exceptions_loader_file.go b/pkg/exceptions/exceptions_loader_file.go new file mode 100644 index 0000000..5b30f4d --- /dev/null +++ b/pkg/exceptions/exceptions_loader_file.go @@ -0,0 +1,60 @@ +package exceptions + +import ( + "io" + "os" + "sync" + "time" + + "github.com/safedep/dry/utils" + "github.com/safedep/vet/gen/exceptionsapi" +) + +type exceptionsFileLoader struct { + m sync.Mutex + idx int + suite *exceptionsapi.ExceptionSuite +} + +func NewExceptionsFileLoader(path string) (exceptionsLoader, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + + return newExceptionsFileLoaderUsingReader(file) +} + +func newExceptionsFileLoaderUsingReader(reader io.Reader) (exceptionsLoader, error) { + var suite exceptionsapi.ExceptionSuite + err := utils.FromYamlToPb(reader, &suite) + if err != nil { + return nil, err + } + + return &exceptionsFileLoader{ + suite: &suite, + }, nil +} + +func (f *exceptionsFileLoader) Read() (*exceptionRule, error) { + f.m.Lock() + defer f.m.Unlock() + + if f.idx >= len(f.suite.Exceptions) { + return nil, io.EOF + } + + cx := f.suite.Exceptions[f.idx] + f.idx += 1 + + expiry, err := time.Parse(time.RFC3339, cx.Expires) + if err != nil { + return nil, err + } + + return &exceptionRule{ + spec: cx, + expiry: expiry, + }, nil +} diff --git a/pkg/exceptions/exceptions_loader_file_test.go b/pkg/exceptions/exceptions_loader_file_test.go new file mode 100644 index 0000000..e307d55 --- /dev/null +++ b/pkg/exceptions/exceptions_loader_file_test.go @@ -0,0 +1,86 @@ +package exceptions + +import ( + "testing" + + "github.com/safedep/vet/pkg/models" + "github.com/stretchr/testify/assert" +) + +func TestNewExceptionsFileLoader(t *testing.T) { + cases := []struct { + name string + file string + ecosystem string + pkgName string + version string + match bool + errMsg string + }{ + { + "Load a file and match", + "fixtures/1_valid.yml", + "maven", + "p1", + "v1", + true, + "", + }, + { + "Match any version", + "fixtures/1_valid.yml", + "maven", + "p2", + "v5-anything", + true, + "", + }, + { + "Does not match expired exceptions", + "fixtures/1_valid.yml", + "maven", + "p3", + "v5-anything", + false, + "", + }, + { + "Error loading file that does not exist", + "fixtures/1_does_not_exists.yml", + "", + "", + "", + false, + "no such file or directory", + }, + { + "Error loading invalid YAML file", + "fixtures/2_invalid.yml", + "", + "", + "", + false, + "unknown field", + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + initStore() + + loader, err := NewExceptionsFileLoader(test.file) + if test.errMsg != "" { + assert.NotNil(t, err) + assert.ErrorContains(t, err, test.errMsg) + return + } + + Load(loader) + + pd := models.NewPackageDetail(test.ecosystem, test.pkgName, test.version) + res, _ := Apply(&models.Package{PackageDetails: pd}) + + assert.Equal(t, test.match, res.Matched()) + }) + } +} diff --git a/pkg/exceptions/fixtures/1_valid.yml b/pkg/exceptions/fixtures/1_valid.yml new file mode 100644 index 0000000..ce8a866 --- /dev/null +++ b/pkg/exceptions/fixtures/1_valid.yml @@ -0,0 +1,17 @@ +name: Exceptions File for Fixture +description: | + Test exceptions file +exceptions: + - ecosystem: maven + name: p1 + version: v1 + expires: 2050-11-10T23:00:00Z + - ecosystem: maven + name: p2 + version: '*' + expires: 2050-11-10T23:00:00Z + - ecosystem: maven + name: p3 + version: '*' + expires: 2020-11-10T23:00:00Z + diff --git a/pkg/exceptions/fixtures/2_invalid.yml b/pkg/exceptions/fixtures/2_invalid.yml new file mode 100644 index 0000000..cc5f4c5 --- /dev/null +++ b/pkg/exceptions/fixtures/2_invalid.yml @@ -0,0 +1,2 @@ +A: 1 +B: 2 From 4882e4681517c5e57ac85fcc5a0378e70aba83b7 Mon Sep 17 00:00:00 2001 From: abhisek Date: Tue, 21 Feb 2023 19:42:44 +0530 Subject: [PATCH 5/9] Add exceptions loader in main --- main.go | 24 +++++++++++++++++++++--- pkg/common/logger/logger.go | 4 ++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 2027fe8..fef0b0b 100644 --- a/main.go +++ b/main.go @@ -9,13 +9,15 @@ import ( "github.com/safedep/dry/utils" "github.com/safedep/vet/internal/ui" "github.com/safedep/vet/pkg/common/logger" + "github.com/safedep/vet/pkg/exceptions" "github.com/spf13/cobra" ) var ( - verbose bool - debug bool - logFile string + verbose bool + debug bool + logFile string + globalExceptionsFile string ) var banner string = ` @@ -50,6 +52,7 @@ func main() { cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Show verbose logs") cmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Show debug logs") cmd.PersistentFlags().StringVarP(&logFile, "log", "l", "", "Write command logs to file") + cmd.PersistentFlags().StringVarP(&globalExceptionsFile, "exceptions", "e", "", "Load exceptions from file") cmd.AddCommand(newAuthCommand()) cmd.AddCommand(newScanCommand()) @@ -58,6 +61,7 @@ func main() { cobra.OnInitialize(func() { printBanner() + loadExceptions() logger.SetLogLevel(verbose, debug) }) @@ -66,6 +70,20 @@ func main() { } } +func loadExceptions() { + if globalExceptionsFile != "" { + loader, err := exceptions.NewExceptionsFileLoader(globalExceptionsFile) + if err != nil { + logger.Fatalf("Exceptions loader: %v", err) + } + + err = exceptions.Load(loader) + if err != nil { + logger.Fatalf("Exceptions loader: %v", err) + } + } +} + func printBanner() { bRet, err := strconv.ParseBool(os.Getenv("VET_DISABLE_BANNER")) if (err != nil) || (!bRet) { diff --git a/pkg/common/logger/logger.go b/pkg/common/logger/logger.go index aedbd51..0ec5394 100644 --- a/pkg/common/logger/logger.go +++ b/pkg/common/logger/logger.go @@ -51,6 +51,10 @@ func Debugf(msg string, args ...any) { logrus.Debugf(msg, args...) } +func Fatalf(msg string, args ...any) { + logrus.Fatalf(msg, args...) +} + func LoggerWith(key string, value any) *logrus.Entry { return logrus.WithFields(logrus.Fields{ key: value, From 46bd7e2d13e4722e4e82bd1a1285a2e26c873e3d Mon Sep 17 00:00:00 2001 From: abhisek Date: Wed, 22 Feb 2023 15:01:31 +0530 Subject: [PATCH 6/9] Add reader to read packages with exceptions --- pkg/analyzer/cel_filter.go | 11 +++++++---- pkg/analyzer/cel_filter_suite.go | 9 ++++++--- pkg/exceptions/utils.go | 31 +++++++++++++++++++++++++++++++ pkg/models/models.go | 6 ++++++ pkg/readers/pm.go | 18 ++++++++++++++++++ pkg/readers/reader.go | 16 ++++++++++++++++ pkg/reporter/console.go | 6 ++++-- pkg/reporter/summary.go | 6 ++++-- pkg/scanner/scanner.go | 6 ++++-- 9 files changed, 96 insertions(+), 13 deletions(-) create mode 100644 pkg/exceptions/utils.go create mode 100644 pkg/readers/pm.go create mode 100644 pkg/readers/reader.go diff --git a/pkg/analyzer/cel_filter.go b/pkg/analyzer/cel_filter.go index 42db95e..6126016 100644 --- a/pkg/analyzer/cel_filter.go +++ b/pkg/analyzer/cel_filter.go @@ -9,6 +9,7 @@ import ( "github.com/safedep/vet/pkg/analyzer/filter" "github.com/safedep/vet/pkg/common/logger" "github.com/safedep/vet/pkg/models" + "github.com/safedep/vet/pkg/readers" ) type celFilterAnalyzer struct { @@ -48,7 +49,7 @@ func (f *celFilterAnalyzer) Analyze(manifest *models.PackageManifest, logger.Infof("CEL filtering manifest: %s", manifest.Path) f.stat.IncScannedManifest() - for _, pkg := range manifest.Packages { + readers.NewManifestModelReader(manifest).EnumPackages(func(pkg *models.Package) error { f.stat.IncEvaluatedPackage() res, err := f.evaluator.EvalPackage(pkg) @@ -59,19 +60,21 @@ func (f *celFilterAnalyzer) Analyze(manifest *models.PackageManifest, pkg.PackageDetails.Name, pkg.PackageDetails.Version, err) - continue + return nil } if res.Matched() { // Avoid duplicates added to the table if _, ok := f.packages[pkg.Id()]; ok { - continue + return nil } f.stat.IncMatchedPackage() f.packages[pkg.Id()] = pkg } - } + + return nil + }) return f.notifyCaller(manifest, handler) } diff --git a/pkg/analyzer/cel_filter_suite.go b/pkg/analyzer/cel_filter_suite.go index 13153c8..158347b 100644 --- a/pkg/analyzer/cel_filter_suite.go +++ b/pkg/analyzer/cel_filter_suite.go @@ -10,6 +10,7 @@ import ( "github.com/safedep/vet/pkg/analyzer/filter" "github.com/safedep/vet/pkg/common/logger" "github.com/safedep/vet/pkg/models" + "github.com/safedep/vet/pkg/readers" ) type celFilterMatchedPackage struct { @@ -62,7 +63,7 @@ func (f *celFilterSuiteAnalyzer) Analyze(manifest *models.PackageManifest, logger.Infof("CEL Filter Suite: Analyzing manifest: %s", manifest.Path) f.stat.IncScannedManifest() - for _, pkg := range manifest.Packages { + readers.NewManifestModelReader(manifest).EnumPackages(func(pkg *models.Package) error { f.stat.IncEvaluatedPackage() res, err := f.evaluator.EvalPackage(pkg) @@ -73,13 +74,15 @@ func (f *celFilterSuiteAnalyzer) Analyze(manifest *models.PackageManifest, pkg.PackageDetails.Name, pkg.PackageDetails.Version, err) - continue + return nil } if res.Matched() { f.queueMatchedPkg(pkg, res.GetMatchedFilter().Name()) } - } + + return nil + }) if f.failOnMatch && (len(f.matchedPackages) > 0) { handler(&AnalyzerEvent{ diff --git a/pkg/exceptions/utils.go b/pkg/exceptions/utils.go new file mode 100644 index 0000000..903ed88 --- /dev/null +++ b/pkg/exceptions/utils.go @@ -0,0 +1,31 @@ +package exceptions + +import ( + "github.com/safedep/vet/pkg/common/logger" + "github.com/safedep/vet/pkg/models" +) + +// AllowedPackages iterates over packages in the manifest and call handler +// only for packages not in the exempted by exception rules +func AllowedPackages(manifest *models.PackageManifest, + handler func(pkg *models.Package) error) error { + for _, pkg := range manifest.Packages { + res, err := Apply(pkg) + if err != nil { + logger.Errorf("Failed to evaluate exception for %s: %v", + pkg.ShortName(), err) + continue + } + + if res.Matched() { + continue + } + + err = handler(pkg) + if err != nil { + return err + } + } + + return nil +} diff --git a/pkg/models/models.go b/pkg/models/models.go index 83a171a..17dbddf 100644 --- a/pkg/models/models.go +++ b/pkg/models/models.go @@ -75,6 +75,12 @@ func (p *Package) Id() string { return strconv.FormatUint(h.Sum64(), 16) } +func (p *Package) ShortName() string { + return fmt.Sprintf("pkg:%s/%s@%s", + strings.ToLower(string(p.Ecosystem)), + strings.ToLower(p.Name), p.Version) +} + func NewPackageDetail(e, n, v string) lockfile.PackageDetails { return lockfile.PackageDetails{ Ecosystem: lockfile.Ecosystem(e), diff --git a/pkg/readers/pm.go b/pkg/readers/pm.go new file mode 100644 index 0000000..3737b3e --- /dev/null +++ b/pkg/readers/pm.go @@ -0,0 +1,18 @@ +package readers + +import ( + "github.com/safedep/vet/pkg/exceptions" + "github.com/safedep/vet/pkg/models" +) + +type packageManifestReader struct { + manifest *models.PackageManifest +} + +func NewManifestModelReader(manifest *models.PackageManifest) PackageReader { + return &packageManifestReader{manifest: manifest} +} + +func (r *packageManifestReader) EnumPackages(handler func(pkg *models.Package) error) error { + return exceptions.AllowedPackages(r.manifest, handler) +} diff --git a/pkg/readers/reader.go b/pkg/readers/reader.go new file mode 100644 index 0000000..925c200 --- /dev/null +++ b/pkg/readers/reader.go @@ -0,0 +1,16 @@ +package readers + +import "github.com/safedep/vet/pkg/models" + +// Contract for implementing package manifest readers such as lockfile parser, +// SBOM parser etc. Reader should stop enumeration and return error if handler +// returns an error +type PackageManifestReader interface { + EnumManifests(func(*models.PackageManifest) error) error +} + +// Contract for implementing a package reader. Enumerator should fail and return +// error if handler fails +type PackageReader interface { + EnumPackages(func(*models.Package) error) error +} diff --git a/pkg/reporter/console.go b/pkg/reporter/console.go index 9793704..e67466d 100644 --- a/pkg/reporter/console.go +++ b/pkg/reporter/console.go @@ -11,6 +11,7 @@ import ( "github.com/safedep/vet/pkg/analyzer" "github.com/safedep/vet/pkg/models" "github.com/safedep/vet/pkg/policy" + "github.com/safedep/vet/pkg/readers" ) type consoleReporter struct{} @@ -29,9 +30,10 @@ func (r *consoleReporter) AddManifest(manifest *models.PackageManifest) { tbl.SetStyle(table.StyleLight) tbl.AppendHeader(table.Row{"Package", "Attribute", "Summary"}) - for _, pkg := range manifest.Packages { + readers.NewManifestModelReader(manifest).EnumPackages(func(pkg *models.Package) error { r.report(tbl, pkg) - } + return nil + }) fmt.Print(text.Bold.Sprint("Manifest: ", text.FgBlue.Sprint(manifest.Path))) fmt.Print("\n") diff --git a/pkg/reporter/summary.go b/pkg/reporter/summary.go index e24487b..09b3933 100644 --- a/pkg/reporter/summary.go +++ b/pkg/reporter/summary.go @@ -14,6 +14,7 @@ import ( "github.com/safedep/vet/pkg/analyzer" "github.com/safedep/vet/pkg/models" "github.com/safedep/vet/pkg/policy" + "github.com/safedep/vet/pkg/readers" ) const ( @@ -72,13 +73,14 @@ func (r *summaryReporter) Name() string { } func (r *summaryReporter) AddManifest(manifest *models.PackageManifest) { - for _, pkg := range manifest.Packages { + readers.NewManifestModelReader(manifest).EnumPackages(func(pkg *models.Package) error { r.processForVulns(pkg) r.processForPopularity(pkg) r.processForVersionDrift(pkg) r.summary.packages += 1 - } + return nil + }) r.summary.manifests += 1 } diff --git a/pkg/scanner/scanner.go b/pkg/scanner/scanner.go index b86e7c8..5751060 100644 --- a/pkg/scanner/scanner.go +++ b/pkg/scanner/scanner.go @@ -7,6 +7,7 @@ import ( "github.com/safedep/vet/pkg/common/logger" "github.com/safedep/vet/pkg/common/utils" "github.com/safedep/vet/pkg/models" + "github.com/safedep/vet/pkg/readers" "github.com/safedep/vet/pkg/reporter" ) @@ -217,9 +218,10 @@ func (s *packageManifestScanner) enrichManifest(manifest *models.PackageManifest q.Start() - for _, pkg := range manifest.Packages { + readers.NewManifestModelReader(manifest).EnumPackages(func(pkg *models.Package) error { q.Add(pkg) - } + return nil + }) q.Wait() q.Stop() From 4b16c05ff944b532dd4f4b33cc332eb55ebe1567 Mon Sep 17 00:00:00 2001 From: abhisek Date: Wed, 22 Feb 2023 19:06:48 +0530 Subject: [PATCH 7/9] Add exceptions generate analyzer --- go.mod | 2 +- go.sum | 2 + pkg/analyzer/exceptions_generator.go | 109 +++++++++++++++++++++++++++ query.go | 28 +++++++ 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 pkg/analyzer/exceptions_generator.go diff --git a/go.mod b/go.mod index 7084709..25f459a 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/cel-go v0.13.0 github.com/google/osv-scanner v1.1.0 github.com/jedib0t/go-pretty/v6 v6.4.4 - github.com/safedep/dry v0.0.0-20230218045153-1a93b0397b55 + github.com/safedep/dry v0.0.0-20230222132026-c8b6cb976849 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/stretchr/testify v1.8.1 diff --git a/go.sum b/go.sum index a1dc880..afeb37d 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/safedep/dry v0.0.0-20230218045153-1a93b0397b55 h1:OBzggSWzjyEa7YaXp2DvpKDe1wYXtOEcFXQfDqkK7PI= github.com/safedep/dry v0.0.0-20230218045153-1a93b0397b55/go.mod h1:odFOtG1l46k23IaCY6kdNkkLW8L+NT+EUVYYVphP59I= +github.com/safedep/dry v0.0.0-20230222132026-c8b6cb976849 h1:5nO9ht1jn7XHFyNFRhUneDZbKmwh4kRr0w/EoWuOQQA= +github.com/safedep/dry v0.0.0-20230222132026-c8b6cb976849/go.mod h1:odFOtG1l46k23IaCY6kdNkkLW8L+NT+EUVYYVphP59I= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= diff --git a/pkg/analyzer/exceptions_generator.go b/pkg/analyzer/exceptions_generator.go new file mode 100644 index 0000000..0b7ae0c --- /dev/null +++ b/pkg/analyzer/exceptions_generator.go @@ -0,0 +1,109 @@ +package analyzer + +import ( + "io" + "os" + "time" + + "github.com/safedep/dry/utils" + "github.com/safedep/vet/gen/exceptionsapi" + "github.com/safedep/vet/pkg/analyzer/filter" + "github.com/safedep/vet/pkg/models" + "github.com/safedep/vet/pkg/readers" +) + +type exceptionsGenerator struct { + writer io.WriteCloser + filterEvaluator filter.Evaluator + expires time.Time + pkgCache map[string]*models.Package +} + +type ExceptionsGeneratorConfig struct { + Path string + Filter string + ExpiresOn string +} + +func NewExceptionsGenerator(config ExceptionsGeneratorConfig) (Analyzer, error) { + fd, err := os.Create(config.Path) + if err != nil { + return nil, err + } + + expiresOn, err := time.Parse("2006-01-02", config.ExpiresOn) + if err != nil { + return nil, err + } + + filterEvaluator, err := filter.NewEvaluator("exceptions-generator", true) + if err != nil { + return nil, err + } + + if utils.IsEmptyString(config.Filter) { + config.Filter = "true" + } + + err = filterEvaluator.AddFilter("exceptions-filter", config.Filter) + if err != nil { + return nil, err + } + + return &exceptionsGenerator{ + writer: fd, + filterEvaluator: filterEvaluator, + expires: expiresOn, + pkgCache: make(map[string]*models.Package), + }, nil +} + +func (f *exceptionsGenerator) Name() string { + return "Exceptions Generator" +} + +func (f *exceptionsGenerator) Analyze(manifest *models.PackageManifest, + handler AnalyzerEventHandler) error { + readers.NewManifestModelReader(manifest).EnumPackages(func(pkg *models.Package) error { + res, err := f.filterEvaluator.EvalPackage(pkg) + if err != nil { + return err + } + + if !res.Matched() { + return nil + } + + f.pkgCache[pkg.Id()] = pkg + return nil + }) + + return nil +} + +func (f *exceptionsGenerator) Finish() error { + defer f.writer.Close() + + suite := exceptionsapi.ExceptionSuite{ + Name: "Auto Generated Exceptions", + Description: "Exceptions file auto-generated using vet", + Exceptions: make([]*exceptionsapi.Exception, 0), + } + + for _, pkg := range f.pkgCache { + suite.Exceptions = append(suite.Exceptions, &exceptionsapi.Exception{ + Id: utils.NewUniqueId(), + Ecosystem: string(pkg.Ecosystem), + Name: pkg.Name, + Version: pkg.Version, + Expires: f.expires.Format(time.RFC3339), + }) + } + + err := utils.FromPbToYaml(f.writer, &suite) + if err != nil { + return err + } + + return nil +} diff --git a/query.go b/query.go index e8dd6ea..d795c24 100644 --- a/query.go +++ b/query.go @@ -1,6 +1,8 @@ package main import ( + "time" + "github.com/safedep/dry/utils" "github.com/safedep/vet/pkg/analyzer" "github.com/safedep/vet/pkg/reporter" @@ -16,6 +18,11 @@ var ( queryEnableConsoleReport bool queryEnableSummaryReport bool queryMarkdownReportPath string + queryExceptionsFile string + queryExceptionsTill string + queryExceptionsFilter string + + queryDefaultExceptionExpiry = time.Now().Add(90 * 24 * time.Hour) ) func newQueryCommand() *cobra.Command { @@ -36,6 +43,13 @@ func newQueryCommand() *cobra.Command { "Filter packages using CEL Filter Suite from file") cmd.Flags().BoolVarP(&queryFilterFailOnMatch, "filter-fail", "", false, "Fail the command if filter matches any package (for security gate)") + cmd.Flags().StringVarP(&queryExceptionsFile, "exceptions-generate", "", "", + "Generate exception records to file (YAML)") + cmd.Flags().StringVarP(&queryExceptionsTill, "exceptions-till", "", + queryDefaultExceptionExpiry.Format("2006-01-02"), + "Generated exceptions are valid till") + cmd.Flags().StringVarP(&queryExceptionsFilter, "exceptions-filter", "", "", + "Generate exception records for packages matching filter") cmd.Flags().BoolVarP(&queryEnableConsoleReport, "report-console", "", false, "Minimal summary of package manifest") cmd.Flags().BoolVarP(&queryEnableSummaryReport, "report-summary", "", false, @@ -74,6 +88,20 @@ func internalStartQuery() error { analyzers = append(analyzers, task) } + if !utils.IsEmptyString(queryExceptionsFile) { + task, err := analyzer.NewExceptionsGenerator(analyzer.ExceptionsGeneratorConfig{ + Path: queryExceptionsFile, + ExpiresOn: queryExceptionsTill, + Filter: queryExceptionsFilter, + }) + + if err != nil { + return err + } + + analyzers = append(analyzers, task) + } + if queryEnableConsoleReport { rp, err := reporter.NewConsoleReporter() if err != nil { From 2ecc52ef008402560d7c0934025253f031f2673b Mon Sep 17 00:00:00 2001 From: abhisek Date: Thu, 23 Feb 2023 09:59:04 +0530 Subject: [PATCH 8/9] Print excepions statement in summary report --- pkg/exceptions/exceptions.go | 8 ++++++++ pkg/reporter/summary.go | 13 ++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/pkg/exceptions/exceptions.go b/pkg/exceptions/exceptions.go index 76b7436..034f6c3 100644 --- a/pkg/exceptions/exceptions.go +++ b/pkg/exceptions/exceptions.go @@ -94,6 +94,14 @@ func Apply(pkg *models.Package) (*exceptionMatchResult, error) { return globalExceptions.Match(pkg) } +func ActiveCount() int { + return globalExceptions.ActiveCount() +} + +func (s *exceptionStore) ActiveCount() int { + return len(s.rules) +} + func (s *exceptionStore) Match(pkg *models.Package) (*exceptionMatchResult, error) { result := exceptionMatchResult{} diff --git a/pkg/reporter/summary.go b/pkg/reporter/summary.go index 09b3933..6fb71f7 100644 --- a/pkg/reporter/summary.go +++ b/pkg/reporter/summary.go @@ -12,6 +12,7 @@ import ( "github.com/safedep/dry/utils" "github.com/safedep/vet/gen/insightapi" "github.com/safedep/vet/pkg/analyzer" + "github.com/safedep/vet/pkg/exceptions" "github.com/safedep/vet/pkg/models" "github.com/safedep/vet/pkg/policy" "github.com/safedep/vet/pkg/readers" @@ -198,9 +199,14 @@ func (r *summaryReporter) Finish() error { r.renderRemediationAdvice() fmt.Println() + if exceptions.ActiveCount() > 0 { + fmt.Println(text.Faint.Sprint(summaryListPrependText, r.exceptionsCountStatement())) + fmt.Println() + } + fmt.Println("Run with `vet --filter=\"...\"` for custom filters to identify risky libraries") - fmt.Println() fmt.Println("For more details", text.Bold.Sprint("https://github.com/safedep/vet")) + fmt.Println() return nil } @@ -298,3 +304,8 @@ func (r *summaryReporter) majorVersionDriftStatement() string { return fmt.Sprintf("%d libraries are out of date with major version drift in direct dependencies", r.summary.metrics.drifts) } + +func (r *summaryReporter) exceptionsCountStatement() string { + return fmt.Sprintf("%d libraries are exempted from analysis through exception rules", + exceptions.ActiveCount()) +} From ca1fbbcee2453a511e79c09cd95a674436291953 Mon Sep 17 00:00:00 2001 From: abhisek Date: Thu, 23 Feb 2023 10:35:21 +0530 Subject: [PATCH 9/9] Update docs --- README.md | 32 +++++++++++++++++++++++++++ docs/exceptions.md | 3 +++ docs/images/vet-summary-demo.png | Bin 0 -> 84209 bytes pkg/analyzer/exceptions_generator.go | 6 +++++ pkg/exceptions/utils.go | 2 ++ 5 files changed, 43 insertions(+) create mode 100644 docs/images/vet-summary-demo.png diff --git a/README.md b/README.md index 7ae6ab4..e3a4b8e 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,17 @@ source dependencies and evaluate them against organizational policies. ## TL;DR +Scan a repository for OSS dependency risks with auto-detection of package +manifests + +```bash +vet scan -D /path/to/repo +``` + +![vet Summary Demo](docs/images/vet-summary-demo.png) + +## Getting Started + > Ensure `$(go env GOPATH)/bin` is in your `$PATH` Install using `go get` @@ -57,6 +68,12 @@ vet scan --lockfiles /path/to/requirements.txt vet scan --lockfiles /path/to/package-lock.json ``` +or scan a supported package manifest with a non-standard name + +```bash +vet scan --lockfiles /path/to/gradle-compileOnly.lock --lockfile-as gradle.lockfile +``` + > Use `vet scan parsers` to list supported package manifest parsers The default scan uses an opinionated [Summary Reporter](#) which presents @@ -98,6 +115,21 @@ vet scan -D /path/to/dir --filter-suite /path/to/suite.yml --filter-fail Read more about filter suites in [filtering guide](docs/filtering.md) +## Exceptions Management + +Exception rules can be generated using the `query` workflow to temporarily +ignore (or snooze) existing issues when using `vet` for the first time. This +helps in establishing security gating to prevent introduction of new security +issues while existing issues are being remediated. + +Use exception rules during scan to ignore specific packages + +```bash +vet scan -D /path/to/repo -e /path/to/exceptions.yml +``` + +For more information, refer to [exceptions guide](docs/exceptions.md) + ## FAQ ### How do I disable the stupid banner? diff --git a/docs/exceptions.md b/docs/exceptions.md index 96c444f..40649a8 100644 --- a/docs/exceptions.md +++ b/docs/exceptions.md @@ -64,6 +64,9 @@ various commands such as `scan` or `query`. ./vet --exceptions /path/to/exceptions.yml scan -D /path/to/repo ``` +> **Note:** Do not pass this flag while generating exceptions list in query +> workflow to avoid incorrect exception list generation + ## Behaviour * All exceptions rules are applied only on a `Package` diff --git a/docs/images/vet-summary-demo.png b/docs/images/vet-summary-demo.png new file mode 100644 index 0000000000000000000000000000000000000000..26f195f3a90655fda22d9034c71059f9f793aa27 GIT binary patch literal 84209 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 z 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 A#sB~S literal 0 HcmV?d00001 diff --git a/pkg/analyzer/exceptions_generator.go b/pkg/analyzer/exceptions_generator.go index 0b7ae0c..2bcee73 100644 --- a/pkg/analyzer/exceptions_generator.go +++ b/pkg/analyzer/exceptions_generator.go @@ -8,6 +8,7 @@ import ( "github.com/safedep/dry/utils" "github.com/safedep/vet/gen/exceptionsapi" "github.com/safedep/vet/pkg/analyzer/filter" + "github.com/safedep/vet/pkg/common/logger" "github.com/safedep/vet/pkg/models" "github.com/safedep/vet/pkg/readers" ) @@ -50,6 +51,9 @@ func NewExceptionsGenerator(config ExceptionsGeneratorConfig) (Analyzer, error) return nil, err } + logger.Infof("Initialized exceptions generator with filter: '%s' expiry: %s", + config.Filter, expiresOn.Format(time.RFC3339)) + return &exceptionsGenerator{ writer: fd, filterEvaluator: filterEvaluator, @@ -91,6 +95,8 @@ func (f *exceptionsGenerator) Finish() error { } for _, pkg := range f.pkgCache { + logger.Infof("Adding %s to exceptions list", pkg.ShortName()) + suite.Exceptions = append(suite.Exceptions, &exceptionsapi.Exception{ Id: utils.NewUniqueId(), Ecosystem: string(pkg.Ecosystem), diff --git a/pkg/exceptions/utils.go b/pkg/exceptions/utils.go index 903ed88..5d18b10 100644 --- a/pkg/exceptions/utils.go +++ b/pkg/exceptions/utils.go @@ -18,6 +18,8 @@ func AllowedPackages(manifest *models.PackageManifest, } if res.Matched() { + logger.Debugf("Ignoring package:%s due to exception rule", + pkg.ShortName()) continue }