#13: Add exceptions package

This commit is contained in:
abhisek 2023-02-21 13:08:06 +05:30
parent d92561ca8f
commit eadcd1a83d
No known key found for this signature in database
GPG Key ID: CB92A4990C02A88F
5 changed files with 180 additions and 20 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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,

View 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)
}

View 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))
})
}
}