feat(preheat):add artifact filters for preheat policy

- add new selector based on vulnerability severity criteria
- add new selector based on signature(signed) criteria
- do change to the select factory method definition
- do changes to selector.Candidate model
- add preheat policy filter interface and default implementation
- add UT cases to cover new code

Signed-off-by: Steven Zou <szou@vmware.com>

misspelling
This commit is contained in:
Steven Zou 2020-06-30 00:59:01 +08:00
parent fb29a6ae87
commit d8e88ef5bc
12 changed files with 860 additions and 11 deletions

View File

@ -0,0 +1,35 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package preheat
import (
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/p2p/preheat/models/policy"
)
// Enforcer defines policy enforcement operations.
type Enforcer interface {
// Enforce the specified policy.
//
// Arguments:
// p *policy.Schema : the being enforced policy
// art ...*artifact.Artifact optional: the relevant artifact referred by the happening events
// that defined in the event-based policy p.
//
// Returns:
// - ID of the execution
// - non-nil error if any error occurred during the enforcement
Enforce(p *policy.Schema, art ...*artifact.Artifact) (int64, error)
}

View File

@ -18,9 +18,9 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
) )
const ( const (
@ -85,6 +85,12 @@ type Candidate struct {
CreationTime int64 `json:"create_time_second"` CreationTime int64 `json:"create_time_second"`
// Labels attached with the candidate // Labels attached with the candidate
Labels []string `json:"labels"` Labels []string `json:"labels"`
// Overall severity of the candidate
// Use severity code value here to avoid pkg dependency issue.
VulnerabilitySeverity uint `json:"vulnerability_severity"`
// Signatures of the above Tags
// This is not technical correct, just for keeping compatibilities with the original definition.
Signatures map[string]bool `json:"signatures"`
} }
// Hash code based on the candidate info for differentiation // Hash code based on the candidate info for differentiation

View File

@ -27,4 +27,6 @@ type Selector interface {
} }
// Factory is factory method to return a selector implementation // Factory is factory method to return a selector implementation
type Factory func(decoration string, pattern string, extras string) Selector // Pattern can be any type of data.
// TODO: 'extras' can also be an optional interface{} to accept more complicated data.
type Factory func(decoration string, pattern interface{}, extras string) Selector

View File

@ -16,6 +16,7 @@ package doublestar
import ( import (
"encoding/json" "encoding/json"
"github.com/bmatcuk/doublestar" "github.com/bmatcuk/doublestar"
iselector "github.com/goharbor/harbor/src/lib/selector" iselector "github.com/goharbor/harbor/src/lib/selector"
) )
@ -132,7 +133,7 @@ func (s *selector) tagSelectExclude(artifact *iselector.Candidate) (selected boo
} }
// New is factory method for doublestar selector // New is factory method for doublestar selector
func New(decoration string, pattern string, extras string) iselector.Selector { func New(decoration string, pattern interface{}, extras string) iselector.Selector {
untagged := true // default behavior for upgrade, active keep the untagged images untagged := true // default behavior for upgrade, active keep the untagged images
if decoration == Excludes { if decoration == Excludes {
untagged = false untagged = false
@ -145,9 +146,15 @@ func New(decoration string, pattern string, extras string) iselector.Selector {
untagged = extraObj.Untagged untagged = extraObj.Untagged
} }
} }
var p string
if pattern != nil {
p, _ = pattern.(string)
}
return &selector{ return &selector{
decoration: decoration, decoration: decoration,
pattern: pattern, pattern: p,
untagged: untagged, untagged: untagged,
} }
} }

View File

@ -15,8 +15,9 @@
package label package label
import ( import (
iselector "github.com/goharbor/harbor/src/lib/selector"
"strings" "strings"
iselector "github.com/goharbor/harbor/src/lib/selector"
) )
const ( const (
@ -48,11 +49,15 @@ func (s *selector) Select(artifacts []*iselector.Candidate) (selected []*iselect
return selected, nil return selected, nil
} }
// New is factory method for list selector // New is factory method for label selector
func New(decoration string, pattern string, extras string) iselector.Selector { func New(decoration string, pattern interface{}, extras string) iselector.Selector {
labels := make([]string, 0) labels := make([]string, 0)
if len(pattern) > 0 {
labels = append(labels, strings.Split(pattern, ",")...) if pattern != nil {
labelText, ok := pattern.(string)
if ok && len(labelText) > 0 {
labels = append(labels, strings.Split(labelText, ",")...)
}
} }
return &selector{ return &selector{

View File

@ -0,0 +1,95 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package severity
import (
sl "github.com/goharbor/harbor/src/lib/selector"
)
const (
// Kind of this selector
Kind = "severity"
// Gte decoration: Severity of candidate should be greater than or equal to the expected severity.
Gte = "gte"
// Gt decoration: Severity of candidate should be greater than the expected severity.
Gt = "gt"
// Equal decoration: Severity of candidate should be equal to the expected severity.
Equal = "equal"
// Lte decoration: Severity of candidate should be less than or equal to the expected severity.
Lte = "lte"
// Lt decoration: Severity of candidate should be less than the expected severity.
Lt = "lt"
)
// selector filters the candidates by comparing the vulnerability severity
type selector struct {
// Pre defined pattern decorations
// "gte", "gt", "equal", "lte" or "lt"
decoration string
// expected severity value
severity uint
}
// Select candidates by comparing the vulnerability severity of the candidate
func (s *selector) Select(artifacts []*sl.Candidate) (selected []*sl.Candidate, err error) {
for _, a := range artifacts {
matched := false
switch s.decoration {
case Gte:
if a.VulnerabilitySeverity >= s.severity {
matched = true
}
case Gt:
if a.VulnerabilitySeverity > s.severity {
matched = true
}
case Equal:
if a.VulnerabilitySeverity == s.severity {
matched = true
}
case Lte:
if a.VulnerabilitySeverity <= s.severity {
matched = true
}
case Lt:
if a.VulnerabilitySeverity < s.severity {
matched = true
}
default:
break
}
if matched {
selected = append(selected, a)
}
}
return selected, nil
}
// New is factory method for vulnerability severity selector
func New(decoration string, pattern interface{}, extras string) sl.Selector {
var sev int
if pattern != nil {
sev, _ = pattern.(int)
}
return &selector{
decoration: decoration,
severity: (uint)(sev),
}
}

View File

@ -0,0 +1,130 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package severity
import (
"testing"
"github.com/stretchr/testify/require"
sl "github.com/goharbor/harbor/src/lib/selector"
"github.com/stretchr/testify/suite"
)
// SeveritySelectorTestSuite is a test suite of testing severity selector
type SeveritySelectorTestSuite struct {
suite.Suite
candidates []*sl.Candidate
}
// TestSeveritySelector is an entry method of running SeveritySelectorTestSuite
func TestSeveritySelector(t *testing.T) {
suite.Run(t, &SeveritySelectorTestSuite{})
}
// SetupSuite prepares the env of running SeveritySelectorTestSuite.
func (suite *SeveritySelectorTestSuite) SetupSuite() {
suite.candidates = []*sl.Candidate{
{
Namespace: "test",
NamespaceID: 1,
Repository: "busybox",
Kind: "image",
Digest: "sha256@fake",
Tags: []string{
"latest",
"1.0",
},
VulnerabilitySeverity: 3, // medium
}, {
Namespace: "test",
NamespaceID: 1,
Repository: "core",
Kind: "image",
Digest: "sha256@fake",
Tags: []string{
"latest",
"1.1",
},
VulnerabilitySeverity: 4, // high
}, {
Namespace: "test",
NamespaceID: 1,
Repository: "portal",
Kind: "image",
Digest: "sha256@fake",
Tags: []string{
"latest",
"1.2",
},
VulnerabilitySeverity: 5, // critical
},
}
}
// TestGte test >=
func (suite *SeveritySelectorTestSuite) TestGte() {
s := New(Gte, 3, "")
l, err := s.Select(suite.candidates)
require.NoError(suite.T(), err, "filter candidates by vulnerability severity")
suite.Equal(3, len(l), "number of matched candidates")
}
// TestGte test >
func (suite *SeveritySelectorTestSuite) TestGt() {
s := New(Gt, 3, "")
l, err := s.Select(suite.candidates)
require.NoError(suite.T(), err, "filter candidates by vulnerability severity")
require.Equal(suite.T(), 2, len(l), "number of matched candidates")
suite.Condition(func() (success bool) {
for _, a := range l {
if a.VulnerabilitySeverity <= 3 {
return false
}
}
return true
}, "severity checking of matched candidates")
}
// TestGte test =
func (suite *SeveritySelectorTestSuite) TestEqual() {
s := New(Equal, 3, "")
l, err := s.Select(suite.candidates)
require.NoError(suite.T(), err, "filter candidates by vulnerability severity")
require.Equal(suite.T(), 1, len(l), "number of matched candidates")
suite.Equal("busybox", l[0].Repository, "repository comparison of matched candidate")
}
// TestGte test <=
func (suite *SeveritySelectorTestSuite) TestLte() {
s := New(Lte, 4, "")
l, err := s.Select(suite.candidates)
require.NoError(suite.T(), err, "filter candidates by vulnerability severity")
require.Equal(suite.T(), 2, len(l), "number of matched candidates")
suite.Equal("busybox", l[0].Repository, "repository comparison of matched candidate")
suite.Equal("core", l[1].Repository, "repository comparison of matched candidate")
}
// TestGte test <
func (suite *SeveritySelectorTestSuite) TestLt() {
s := New(Lt, 5, "")
l, err := s.Select(suite.candidates)
require.NoError(suite.T(), err, "filter candidates by vulnerability severity")
require.Equal(suite.T(), 2, len(l), "number of matched candidates")
suite.Equal("busybox", l[0].Repository, "repository comparison of matched candidate")
suite.Equal("core", l[1].Repository, "repository comparison of matched candidate")
}

View File

@ -0,0 +1,77 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package signature
import (
sl "github.com/goharbor/harbor/src/lib/selector"
)
const (
// Kind of this selector
Kind = "signature"
// Any tag of the artifact candidate is signed
Any = "any"
// All the tags of the artifact candidate are signed
All = "all"
)
// selector filters the candidates by signing status (signature)
type selector struct {
// Pre defined pattern decorations
// "any" or "all"
decoration string
// expected status of signing
expected bool
}
// Select candidates by the signing status of the candidate
func (s *selector) Select(artifacts []*sl.Candidate) (selected []*sl.Candidate, err error) {
for _, a := range artifacts {
matched := 0
for _, t := range a.Tags {
if a.Signatures[t] == s.expected {
matched++
if s.decoration == Any {
break
}
} else {
if s.decoration == All {
break
}
}
}
if (s.decoration == Any && matched > 0) ||
(s.decoration == All && matched == len(a.Tags)) {
selected = append(selected, a)
}
}
return selected, nil
}
// New is factory method for signature selector
func New(decoration string, pattern interface{}, extras string) sl.Selector {
var e bool
if pattern != nil {
e, _ = pattern.(bool)
}
return &selector{
decoration: decoration,
expected: e,
}
}

View File

@ -0,0 +1,122 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package signature
import (
"testing"
sl "github.com/goharbor/harbor/src/lib/selector"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
// SignatureSelectorTestSuite is a test suite for testing the signature selector
type SignatureSelectorTestSuite struct {
suite.Suite
candidates []*sl.Candidate
}
// TestSignatureSelector is the entry method of running SignatureSelectorTestSuite
func TestSignatureSelector(t *testing.T) {
suite.Run(t, &SignatureSelectorTestSuite{})
}
// SetupSuite prepares the env for running SeveritySelectorTestSuite
func (suite *SignatureSelectorTestSuite) SetupSuite() {
suite.candidates = []*sl.Candidate{
{
Namespace: "test",
NamespaceID: 1,
Repository: "busybox",
Kind: "image",
Digest: "sha256@fake",
Tags: []string{
"latest",
"1.0",
},
Signatures: map[string]bool{
"latest": false,
"1.0": true,
},
}, {
Namespace: "test",
NamespaceID: 1,
Repository: "core",
Kind: "image",
Digest: "sha256@fake",
Tags: []string{
"latest",
"1.1",
},
Signatures: map[string]bool{
"latest": false,
"1.1": false,
},
}, {
Namespace: "test",
NamespaceID: 1,
Repository: "portal",
Kind: "image",
Digest: "sha256@fake",
Tags: []string{
"latest",
"1.2",
},
Signatures: map[string]bool{
"latest": true,
"1.2": true,
},
},
}
}
// TestAnySigned tests the 'any' decoration with expected=true
func (suite *SignatureSelectorTestSuite) TestAnySigned() {
s := New(Any, true, "")
l, err := s.Select(suite.candidates)
require.NoError(suite.T(), err, "filter candidates by signature")
suite.Equal(2, len(l), "number of matched candidates")
suite.Equal("busybox", l[0].Repository)
suite.Equal("portal", l[1].Repository)
}
// TestAnyUnSigned tests the 'any' decoration with expected=false
func (suite *SignatureSelectorTestSuite) TestAnyUnSigned() {
s := New(Any, false, "")
l, err := s.Select(suite.candidates)
require.NoError(suite.T(), err, "filter candidates by signature")
suite.Equal(2, len(l), "number of matched candidates")
suite.Equal("busybox", l[0].Repository)
suite.Equal("core", l[1].Repository)
}
// TestAllSigned tests the 'all' decoration with expected=true
func (suite *SignatureSelectorTestSuite) TestAllSigned() {
s := New(All, true, "")
l, err := s.Select(suite.candidates)
require.NoError(suite.T(), err, "filter candidates by signature")
suite.Equal(1, len(l), "number of matched candidates")
suite.Equal("portal", l[0].Repository)
}
// TestAllUnSigned tests the 'all' decoration with expected=false
func (suite *SignatureSelectorTestSuite) TestAllUnSigned() {
s := New(All, false, "")
l, err := s.Select(suite.candidates)
require.NoError(suite.T(), err, "filter candidates by signature")
suite.Equal(1, len(l), "number of matched candidates")
suite.Equal("core", l[0].Repository)
}

View File

@ -27,8 +27,8 @@ const (
// Repository : type=Repository value=name text (double star pattern used) // Repository : type=Repository value=name text (double star pattern used)
// Tag: type=Tag value=tag text (double star pattern used) // Tag: type=Tag value=tag text (double star pattern used)
// Signature: type=Signature value=bool (True/False) // Signature: type=Signature value=bool (True/False)
// Vulnerability: type=Vulnerability value=Severity (expected bar) // Vulnerability: type=Vulnerability value=Severity (int) (expected bar)
// Label: type=Label value=label array // Label: type=Label value=label array (with format: lb1,lb2,lb3)
// FilterTypeRepository represents the repository filter type // FilterTypeRepository represents the repository filter type
FilterTypeRepository FilterType = "repository" FilterTypeRepository FilterType = "repository"

View File

@ -0,0 +1,181 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package policy
import (
"sort"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/selector"
"github.com/goharbor/harbor/src/lib/selector/selectors/doublestar"
"github.com/goharbor/harbor/src/lib/selector/selectors/label"
"github.com/goharbor/harbor/src/lib/selector/selectors/severity"
"github.com/goharbor/harbor/src/lib/selector/selectors/signature"
"github.com/goharbor/harbor/src/pkg/p2p/preheat/models/policy"
)
// Filter defines the filter operations of the preheat policy.
type Filter interface {
// Build filter from the given policy schema
BuildFrom(pl *policy.Schema) Filter
// Filter the inputting candidates and return the matched ones.
Filter(candidates []*selector.Candidate) ([]*selector.Candidate, error)
}
type defaultFilter struct {
// all kinds of underlying selectors
selectors []selector.Selector
// keep internal error
error error
}
// NewFilter constructs a filter
func NewFilter() Filter {
return &defaultFilter{}
}
// Filter candidates
func (df *defaultFilter) Filter(candidates []*selector.Candidate) ([]*selector.Candidate, error) {
if len(df.selectors) == 0 {
return nil, errors.New("no underlying filters")
}
if df.error != nil {
// Internal error occurred
return nil, df.error
}
var (
// At the beginning
filtered = candidates
err error
)
// Do filters
for _, sl := range df.selectors {
filtered, err = sl.Select(filtered)
if err != nil {
return nil, errors.Wrap(err, "do filter error")
}
if len(filtered) == 0 {
// Return earlier
return filtered, nil
}
}
// Final filtered ones
return filtered, nil
}
// BuildFrom builds filter from the given policy schema
func (df *defaultFilter) BuildFrom(pl *policy.Schema) Filter {
if pl != nil && len(pl.Filters) > 0 {
filters := make([]*policy.Filter, 0)
// Copy filters and sort the filter list
for _, fl := range pl.Filters {
filters = append(filters, fl)
}
// Sort
sort.SliceStable(filters, func(i, j int) bool {
return filterOrder(filters[i].Type) < filterOrder(filters[j].Type)
})
// Build executable selector based on the filter
if df.selectors == nil || len(df.selectors) > 0 {
// make or reset
df.selectors = make([]selector.Selector, 0)
}
for _, fl := range filters {
sl, err := buildFilter(fl)
if err != nil {
df.error = errors.Wrap(err, "build filter error")
// Return earlier
return df
}
df.selectors = append(df.selectors, sl)
}
}
return df
}
// Assign the filter with different order weight and then do filters with fixed order.
func filterOrder(t policy.FilterType) uint {
switch t {
case policy.FilterTypeRepository:
return 0
case policy.FilterTypeTag:
return 1
case policy.FilterTypeLabel:
return 2
case policy.FilterTypeSignature:
return 3
case policy.FilterTypeVulnerability:
return 4
default:
return 5
}
}
// buildFilter constructs the selector with the given filter object.
// The filter function leverages the selector lib.
func buildFilter(f *policy.Filter) (selector.Selector, error) {
if f == nil {
return nil, errors.New("nil policy filter object")
}
// Value should not be nil as all the following filters need pattern data,
// even the pattern is empty string or zero int (not nil object).
if f.Value == nil {
return nil, errors.Errorf("pattern value is missing for filter: %s", f.Type)
}
// Check value type
switch f.Type {
case policy.FilterTypeRepository,
policy.FilterTypeTag,
policy.FilterTypeLabel:
if _, ok := f.Value.(string); !ok {
return nil, errors.Errorf("invalid string pattern format for filter: %s", f.Type)
}
case policy.FilterTypeSignature:
if _, ok := f.Value.(bool); !ok {
return nil, errors.Errorf("invalid boolean pattern format for filter: %s", f.Type)
}
case policy.FilterTypeVulnerability:
if _, ok := f.Value.(int); !ok {
return nil, errors.Errorf("invalid integer pattern format for filter: %s", f.Type)
}
}
// Build selectors
switch f.Type {
case policy.FilterTypeRepository:
return doublestar.New(doublestar.RepoMatches, f.Value, ""), nil
case policy.FilterTypeTag:
return doublestar.New(doublestar.Matches, f.Value, ""), nil
case policy.FilterTypeLabel:
return label.New(label.With, f.Value, ""), nil
case policy.FilterTypeSignature:
return signature.New(signature.All, f.Value, ""), nil
case policy.FilterTypeVulnerability:
return severity.New(severity.Lt, f.Value, ""), nil
default:
return nil, errors.Errorf("unknown filter type: %s", f.Type)
}
}

View File

@ -0,0 +1,189 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package policy
import (
"testing"
"github.com/goharbor/harbor/src/lib/selector"
"github.com/goharbor/harbor/src/pkg/p2p/preheat/models/policy"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
// FilterTestSuite is a test suite of testing policy filter
type FilterTestSuite struct {
suite.Suite
candidates []*selector.Candidate
}
// TestFilter is an entry method of running FilterTestSuite
func TestFilter(t *testing.T) {
suite.Run(t, &FilterTestSuite{})
}
// SetupSuite prepares env for running FilterTestSuite
func (suite *FilterTestSuite) SetupSuite() {
suite.candidates = []*selector.Candidate{
{
NamespaceID: 1,
Namespace: "test",
Kind: "image",
Repository: "sub/busybox",
Tags: []string{"prod"},
Digest: "sha256@fake",
Labels: []string{"prod_ready", "approved"},
Signatures: map[string]bool{"prod": true},
VulnerabilitySeverity: 3, // medium
},
{
NamespaceID: 1,
Namespace: "test",
Kind: "image",
Repository: "sub/busybox",
Tags: []string{"qa"},
Digest: "sha256@fake2",
Labels: []string{"prod_ready", "approved"},
Signatures: map[string]bool{"qa": true},
VulnerabilitySeverity: 3, // medium
},
{
NamespaceID: 1,
Namespace: "test",
Kind: "image",
Repository: "portal",
Tags: []string{"prod"},
Digest: "sha256@fake3",
Labels: []string{"prod_ready", "approved"},
Signatures: map[string]bool{"prod": true},
VulnerabilitySeverity: 3, // medium
},
{
NamespaceID: 1,
Namespace: "test",
Kind: "image",
Repository: "sub/busybox",
Tags: []string{"prod2"},
Digest: "sha256@fake4",
Labels: []string{"prod_ready", "approved"},
Signatures: map[string]bool{"prod2": true},
VulnerabilitySeverity: 5, // critical
},
{
NamespaceID: 1,
Namespace: "test",
Kind: "image",
Repository: "sub/busybox",
Tags: []string{"prod3"},
Digest: "sha256@fake5",
Labels: []string{"prod_ready", "approved"},
Signatures: map[string]bool{"prod3": false},
VulnerabilitySeverity: 3, // medium
},
{
NamespaceID: 1,
Namespace: "test",
Kind: "image",
Repository: "sub/busybox",
Tags: []string{"prod4"},
Digest: "sha256@fake6",
Labels: []string{"prod_ready"},
Signatures: map[string]bool{"prod4": true},
VulnerabilitySeverity: 3, // medium
},
{
NamespaceID: 1,
Namespace: "test",
Kind: "image",
Repository: "portal",
Tags: []string{"qa"},
Digest: "sha256@fake7",
Labels: []string{"staged"},
Signatures: map[string]bool{"qa": false},
VulnerabilitySeverity: 4, // high
},
}
}
// TestInvalidFilters tests the invalid filters
func (suite *FilterTestSuite) TestInvalidFilters() {
p1 := &policy.Schema{
Filters: []*policy.Filter{
{
Type: policy.FilterTypeRepository,
Value: 100,
},
},
}
fl := NewFilter()
_, err := fl.BuildFrom(p1).Filter(suite.candidates)
suite.Errorf(err, "invalid filter: %s", policy.FilterTypeRepository)
p2 := &policy.Schema{
Filters: []*policy.Filter{
{
Type: policy.FilterTypeSignature,
Value: "true",
},
},
}
_, err = fl.BuildFrom(p2).Filter(suite.candidates)
suite.Errorf(err, "invalid filter: %s", policy.FilterTypeSignature)
p3 := &policy.Schema{
Filters: []*policy.Filter{
{
Type: policy.FilterTypeVulnerability,
Value: "3",
},
},
}
_, err = fl.BuildFrom(p3).Filter(suite.candidates)
suite.Errorf(err, "invalid filter: %s", policy.FilterTypeVulnerability)
}
// TestFilters test all the supported filters with candidates
func (suite *FilterTestSuite) TestFilters() {
p := &policy.Schema{
Filters: []*policy.Filter{
{
Type: policy.FilterTypeRepository,
Value: "sub/**",
},
{
Type: policy.FilterTypeTag,
Value: "prod*",
},
{
Type: policy.FilterTypeLabel,
Value: "prod_ready,approved",
},
{
Type: policy.FilterTypeSignature,
Value: true, // signed
},
{
Type: policy.FilterTypeVulnerability,
Value: 4, // < high
},
},
}
res, err := NewFilter().BuildFrom(p).Filter(suite.candidates)
require.NoError(suite.T(), err, "do filters")
require.Equal(suite.T(), 1, len(res), "number of matched candidates")
suite.Equal("sha256@fake", res[0].Digest, "digest of matched candidate")
}