Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add compliance policy for empty name and version #3257

Merged
merged 3 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions cmd/syft/internal/options/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type Catalog struct {
Scope string `yaml:"scope" json:"scope" mapstructure:"scope"`
Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
Relationships relationshipsConfig `yaml:"relationships" json:"relationships" mapstructure:"relationships"`
Compliance complianceConfig `yaml:"compliance" json:"compliance" mapstructure:"compliance"`
Enrich []string `yaml:"enrich" json:"enrich" mapstructure:"enrich"`

// ecosystem-specific cataloger configuration
Expand All @@ -62,6 +63,7 @@ var _ interface {

func DefaultCatalog() Catalog {
return Catalog{
Compliance: defaultComplianceConfig(),
Scope: source.SquashedScope.String(),
Package: defaultPackageConfig(),
LinuxKernel: defaultLinuxKernelConfig(),
Expand All @@ -79,6 +81,7 @@ func (cfg Catalog) ToSBOMConfig(id clio.Identification) *syft.CreateSBOMConfig {
WithTool(id.Name, id.Version).
WithParallelism(cfg.Parallelism).
WithRelationshipsConfig(cfg.ToRelationshipsConfig()).
WithComplianceConfig(cfg.ToComplianceConfig()).
WithSearchConfig(cfg.ToSearchConfig()).
WithPackagesConfig(cfg.ToPackagesConfig()).
WithFilesConfig(cfg.ToFilesConfig()).
Expand All @@ -104,6 +107,13 @@ func (cfg Catalog) ToRelationshipsConfig() cataloging.RelationshipsConfig {
}
}

func (cfg Catalog) ToComplianceConfig() cataloging.ComplianceConfig {
return cataloging.ComplianceConfig{
MissingName: cfg.Compliance.MissingName,
MissingVersion: cfg.Compliance.MissingVersion,
}
}

func (cfg Catalog) ToFilesConfig() filecataloging.Config {
hashers, err := intFile.Hashers(cfg.File.Metadata.Digests...)
if err != nil {
Expand Down
35 changes: 35 additions & 0 deletions cmd/syft/internal/options/compliance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package options

import (
"github.com/anchore/fangs"
"github.com/anchore/syft/syft/cataloging"
)

var (
_ fangs.FieldDescriber = (*complianceConfig)(nil)
_ fangs.PostLoader = (*complianceConfig)(nil)
)

type complianceConfig struct {
MissingName cataloging.ComplianceAction `mapstructure:"missing-name" json:"missing-name" yaml:"missing-name"`
MissingVersion cataloging.ComplianceAction `mapstructure:"missing-version" json:"missing-version" yaml:"missing-version"`
}

func defaultComplianceConfig() complianceConfig {
def := cataloging.DefaultComplianceConfig()
return complianceConfig{
MissingName: def.MissingName,
MissingVersion: def.MissingVersion,
}
}

func (r *complianceConfig) DescribeFields(descriptions fangs.FieldDescriptionSet) {
descriptions.Add(&r.MissingName, "action to take when a package is missing a name")
descriptions.Add(&r.MissingVersion, "action to take when a package is missing a version")
}

func (r *complianceConfig) PostLoad() error {
r.MissingName = r.MissingName.Parse()
r.MissingVersion = r.MissingVersion.Parse()
return nil
}
56 changes: 46 additions & 10 deletions internal/relationship/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,16 @@ type Index struct {

// NewIndex returns a new relationship Index
func NewIndex(relationships ...artifact.Relationship) *Index {
out := Index{}
out := Index{
fromID: make(map[artifact.ID]*mappedRelationships),
toID: make(map[artifact.ID]*mappedRelationships),
}
out.Add(relationships...)
return &out
}

// Add adds all the given relationships to the index, without adding duplicates
func (i *Index) Add(relationships ...artifact.Relationship) {
if i.fromID == nil {
i.fromID = map[artifact.ID]*mappedRelationships{}
}
if i.toID == nil {
i.toID = map[artifact.ID]*mappedRelationships{}
}

// store appropriate indexes for stable ordering to minimize ID() calls
for _, r := range relationships {
// prevent duplicates
Expand Down Expand Up @@ -68,6 +64,39 @@ func (i *Index) Add(relationships ...artifact.Relationship) {
}
}

func (i *Index) Remove(id artifact.ID) {
delete(i.fromID, id)
delete(i.toID, id)

for idx := 0; idx < len(i.all); {
if i.all[idx].from == id || i.all[idx].to == id {
i.all = append(i.all[:idx], i.all[idx+1:]...)
} else {
idx++
}
}
}

func (i *Index) Replace(ogID artifact.ID, replacement artifact.Identifiable) {
for _, mapped := range fromMappedByID(i.fromID, ogID) {
i.Add(artifact.Relationship{
From: replacement,
To: mapped.relationship.To,
Type: mapped.relationship.Type,
})
}

for _, mapped := range fromMappedByID(i.toID, ogID) {
i.Add(artifact.Relationship{
From: mapped.relationship.From,
To: replacement,
Type: mapped.relationship.Type,
})
}

i.Remove(ogID)
}

// From returns all relationships from the given identifiable, with specified types
func (i *Index) From(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship {
return toSortedSlice(fromMapped(i.fromID, identifiable), types)
Expand Down Expand Up @@ -110,10 +139,17 @@ func (i *Index) All(types ...artifact.RelationshipType) []artifact.Relationship
}

func fromMapped(idMap map[artifact.ID]*mappedRelationships, identifiable artifact.Identifiable) []*sortableRelationship {
if identifiable == nil || idMap == nil {
if identifiable == nil {
return nil
}
return fromMappedByID(idMap, identifiable.ID())
}

func fromMappedByID(idMap map[artifact.ID]*mappedRelationships, id artifact.ID) []*sortableRelationship {
if idMap == nil {
return nil
}
mapped := idMap[identifiable.ID()]
mapped := idMap[id]
if mapped == nil {
return nil
}
Expand Down
177 changes: 177 additions & 0 deletions internal/relationship/index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package relationship
import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/anchore/syft/syft/artifact"
Expand Down Expand Up @@ -231,3 +232,179 @@ func (i id) ID() artifact.ID {
func slice[T any](values ...T) []T {
return values
}

func TestRemove(t *testing.T) {
p1 := pkg.Package{Name: "pkg-1"}
p2 := pkg.Package{Name: "pkg-2"}
p3 := pkg.Package{Name: "pkg-3"}
c1 := file.Coordinates{RealPath: "/coords/1"}
c2 := file.Coordinates{RealPath: "/coords/2"}
c3 := file.Coordinates{RealPath: "/coords/3"}
c4 := file.Coordinates{RealPath: "/coords/4"}

for _, p := range []*pkg.Package{&p1, &p2, &p3} {
p.SetID()
}

r1 := artifact.Relationship{
From: p1,
To: p2,
Type: artifact.DependencyOfRelationship,
}
r2 := artifact.Relationship{
From: p1,
To: p3,
Type: artifact.DependencyOfRelationship,
}
r3 := artifact.Relationship{
From: p1,
To: c1,
Type: artifact.ContainsRelationship,
}
r4 := artifact.Relationship{
From: p2,
To: c2,
Type: artifact.ContainsRelationship,
}
r5 := artifact.Relationship{
From: p3,
To: c1,
Type: artifact.ContainsRelationship,
}
r6 := artifact.Relationship{
From: p3,
To: c2,
Type: artifact.ContainsRelationship,
}
r7 := artifact.Relationship{
From: c1,
To: c3,
Type: artifact.ContainsRelationship,
}
r8 := artifact.Relationship{
From: c3,
To: c4,
Type: artifact.ContainsRelationship,
}

index := NewIndex(r1, r2, r3, r4, r5, r6, r7, r8)

assert.Equal(t, 8, len(index.All()))

// removal of p1 should remove r1, r2, and r3
index.Remove(p1.ID())
remaining := index.All()

assert.Equal(t, 5, len(remaining))
assert.NotContains(t, remaining, r1)
assert.NotContains(t, remaining, r2)
assert.NotContains(t, remaining, r3)

assert.Empty(t, index.From(p1))
assert.Empty(t, index.To(p1))

// removal of c1 should remove r5 and r7
index.Remove(c1.ID())
remaining = index.All()

// r8 remains since c3->c4 should still exist
assert.Equal(t, 3, len(remaining))
assert.NotContains(t, remaining, r5)
assert.NotContains(t, remaining, r7)
assert.Contains(t, remaining, r8)

assert.Empty(t, index.From(c1))
assert.Empty(t, index.To(c1))

// removal of c3 should remove r8
index.Remove(c3.ID())
remaining = index.All()

assert.Equal(t, 2, len(remaining))
assert.Contains(t, remaining, r4)
assert.Contains(t, remaining, r6)

assert.Empty(t, index.From(c3))
assert.Empty(t, index.To(c3))
}

func TestReplace(t *testing.T) {
p1 := pkg.Package{Name: "pkg-1"}
p2 := pkg.Package{Name: "pkg-2"}
p3 := pkg.Package{Name: "pkg-3"}
p4 := pkg.Package{Name: "pkg-4"}

for _, p := range []*pkg.Package{&p1, &p2, &p3, &p4} {
p.SetID()
}

r1 := artifact.Relationship{
From: p1,
To: p2,
Type: artifact.DependencyOfRelationship,
}
r2 := artifact.Relationship{
From: p3,
To: p1,
Type: artifact.DependencyOfRelationship,
}
r3 := artifact.Relationship{
From: p2,
To: p3,
Type: artifact.ContainsRelationship,
}

index := NewIndex(r1, r2, r3)

// replace p1 with p4 in the relationships
index.Replace(p1.ID(), &p4)

expectedRels := []artifact.Relationship{
{
From: p4, // replaced
To: p2,
Type: artifact.DependencyOfRelationship,
},
{
From: p3,
To: p4, // replaced
Type: artifact.DependencyOfRelationship,
},
{
From: p2,
To: p3,
Type: artifact.ContainsRelationship,
},
}

compareRelationships(t, expectedRels, index.All())
}

func compareRelationships(t testing.TB, expected, actual []artifact.Relationship) {
assert.Equal(t, len(expected), len(actual), "number of relationships should match")
for _, e := range expected {
found := false
for _, a := range actual {
if a.From.ID() == e.From.ID() && a.To.ID() == e.To.ID() && a.Type == e.Type {
found = true
break
}
}
assert.True(t, found, "expected relationship not found: %+v", e)
}
}

func TestReplace_NoExistingRelations(t *testing.T) {
p1 := pkg.Package{Name: "pkg-1"}
p2 := pkg.Package{Name: "pkg-2"}

p1.SetID()
p2.SetID()

index := NewIndex()

index.Replace(p1.ID(), &p2)

allRels := index.All()
assert.Len(t, allRels, 0)
}
Loading
Loading