mirror of
https://github.com/safedep/vet.git
synced 2025-12-10 00:22:08 -06:00
#13: Add exceptions package
This commit is contained in:
parent
d92561ca8f
commit
eadcd1a83d
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
88
pkg/exceptions/exceptions.go
Normal file
88
pkg/exceptions/exceptions.go
Normal file
@ -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)
|
||||
}
|
||||
62
pkg/exceptions/exceptions_test.go
Normal file
62
pkg/exceptions/exceptions_test.go
Normal file
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user