Skip to content

Commit 153dfd8

Browse files
keroxpCopilot
andauthored
Use value ScanResult, add Summary & parseError (#108)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent be1e40e commit 153dfd8

13 files changed

Lines changed: 402 additions & 182 deletions

File tree

cli/cage/audit/aggregator.go

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,19 @@ import (
1212
type aggregater struct {
1313
cves map[string]CVE
1414
cveToContainers map[string]set.Set[string]
15-
summaries map[string][]*ScanResultSummary
15+
summaries map[string][]ScanResultSummary
1616
}
1717

1818
func NewAggregater() *aggregater {
1919
return &aggregater{
2020
cves: make(map[string]CVE),
2121
cveToContainers: make(map[string]set.Set[string]),
22-
summaries: make(map[string][]*ScanResultSummary)}
22+
summaries: make(map[string][]ScanResultSummary)}
2323
}
2424

25-
func (a *aggregater) Add(r *ScanResult) {
25+
func (a *aggregater) Add(r ScanResult) {
2626
container := r.ContainerName
27-
if r.Err != nil {
28-
a.summaries[container] = append(a.summaries[container], &ScanResultSummary{
29-
ContainerName: container,
30-
Status: "ERROR",
31-
})
32-
return
33-
} else if len(r.Cves) == 0 {
34-
a.summaries[container] = append(a.summaries[container], &ScanResultSummary{
35-
ContainerName: container,
36-
Status: "N/A",
37-
})
38-
return
39-
}
40-
summary := summaryScanResult(r)
27+
summary := r.Summary()
4128
a.summaries[container] = append(a.summaries[container], summary)
4229
for _, f := range r.Cves {
4330
if _, exists := a.cves[f.Name]; !exists {

cli/cage/audit/aggregator_test.go

Lines changed: 121 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -83,58 +83,131 @@ func TestNewAggregater(t *testing.T) {
8383
}
8484

8585
func TestAggregater_Add(t *testing.T) {
86-
tests := []struct {
87-
name string
88-
scanResult *ScanResult
89-
wantStatus string
90-
wantCVECount int
91-
}{
92-
{
93-
name: "add result with error",
94-
scanResult: &ScanResult{
95-
Err: assert.AnError,
86+
t.Run("adds single CVE from scan result", func(t *testing.T) {
87+
agg := NewAggregater()
88+
scanResult := ScanResult{
89+
ImageInfo: ImageInfo{ContainerName: "container1"},
90+
Cves: []CVE{
91+
{Name: "CVE-2021-1234", Severity: ecrtypes.FindingSeverityCritical},
9692
},
97-
wantStatus: "ERROR",
98-
wantCVECount: 0,
99-
},
100-
{
101-
name: "add result with nil findings",
102-
scanResult: &ScanResult{},
103-
wantStatus: "N/A",
104-
wantCVECount: 0,
105-
},
106-
{
107-
name: "add result with findings",
108-
scanResult: &ScanResult{
109-
ImageInfo: ImageInfo{
110-
ContainerName: "test-container",
111-
},
112-
Cves: []CVE{
113-
{
114-
Name: "CVE-2021-1234",
115-
Severity: ecrtypes.FindingSeverityCritical,
116-
},
117-
{
118-
Name: "CVE-2021-5678",
119-
Severity: ecrtypes.FindingSeverityHigh,
120-
},
121-
},
93+
}
94+
95+
agg.Add(scanResult)
96+
97+
assert.Equal(t, 1, len(agg.cves))
98+
assert.Equal(t, "CVE-2021-1234", agg.cves["CVE-2021-1234"].Name)
99+
assert.Contains(t, agg.cveToContainers["CVE-2021-1234"].Values(), "container1")
100+
})
101+
102+
t.Run("adds multiple CVEs from single scan result", func(t *testing.T) {
103+
agg := NewAggregater()
104+
scanResult := ScanResult{
105+
ImageInfo: ImageInfo{ContainerName: "container1"},
106+
Cves: []CVE{
107+
{Name: "CVE-2021-1111", Severity: ecrtypes.FindingSeverityCritical},
108+
{Name: "CVE-2021-2222", Severity: ecrtypes.FindingSeverityHigh},
109+
{Name: "CVE-2021-3333", Severity: ecrtypes.FindingSeverityMedium},
122110
},
123-
wantStatus: "VULNERABLE",
124-
wantCVECount: 2,
125-
},
126-
}
111+
}
127112

128-
for _, tt := range tests {
129-
t.Run(tt.name, func(t *testing.T) {
130-
agg := NewAggregater()
131-
agg.Add(tt.scanResult)
132-
assert.Equal(t, 1, len(agg.summaries))
133-
assert.Equal(t, tt.wantStatus, agg.summaries[tt.scanResult.ImageInfo.ContainerName][0].Status)
134-
assert.Equal(t, tt.wantCVECount, len(agg.cves))
135-
})
113+
agg.Add(scanResult)
136114

137-
}
115+
assert.Equal(t, 3, len(agg.cves))
116+
assert.NotNil(t, agg.cves["CVE-2021-1111"])
117+
assert.NotNil(t, agg.cves["CVE-2021-2222"])
118+
assert.NotNil(t, agg.cves["CVE-2021-3333"])
119+
})
120+
121+
t.Run("adds same CVE from multiple containers", func(t *testing.T) {
122+
agg := NewAggregater()
123+
scanResult1 := ScanResult{
124+
ImageInfo: ImageInfo{ContainerName: "container1"},
125+
Cves: []CVE{
126+
{Name: "CVE-2021-1234", Severity: ecrtypes.FindingSeverityCritical},
127+
},
128+
}
129+
scanResult2 := ScanResult{
130+
ImageInfo: ImageInfo{ContainerName: "container2"},
131+
Cves: []CVE{
132+
{Name: "CVE-2021-1234", Severity: ecrtypes.FindingSeverityCritical},
133+
},
134+
}
135+
136+
agg.Add(scanResult1)
137+
agg.Add(scanResult2)
138+
139+
assert.Equal(t, 1, len(agg.cves))
140+
containers := agg.cveToContainers["CVE-2021-1234"].Values()
141+
assert.Equal(t, 2, len(containers))
142+
assert.Contains(t, containers, "container1")
143+
assert.Contains(t, containers, "container2")
144+
})
145+
146+
t.Run("stores scan result summary", func(t *testing.T) {
147+
agg := NewAggregater()
148+
scanResult := ScanResult{
149+
ImageInfo: ImageInfo{ContainerName: "container1"},
150+
Cves: []CVE{},
151+
}
152+
153+
agg.Add(scanResult)
154+
155+
assert.Equal(t, 1, len(agg.summaries["container1"]))
156+
})
157+
158+
t.Run("appends multiple summaries for same container", func(t *testing.T) {
159+
agg := NewAggregater()
160+
scanResult1 := ScanResult{
161+
ImageInfo: ImageInfo{ContainerName: "container1"},
162+
Cves: []CVE{},
163+
}
164+
scanResult2 := ScanResult{
165+
ImageInfo: ImageInfo{ContainerName: "container1"},
166+
Cves: []CVE{},
167+
}
168+
169+
agg.Add(scanResult1)
170+
agg.Add(scanResult2)
171+
172+
assert.Equal(t, 2, len(agg.summaries["container1"]))
173+
})
174+
175+
t.Run("does not duplicate CVE when added multiple times from same container", func(t *testing.T) {
176+
agg := NewAggregater()
177+
scanResult1 := ScanResult{
178+
ImageInfo: ImageInfo{ContainerName: "container1"},
179+
Cves: []CVE{
180+
{Name: "CVE-2021-1234", Severity: ecrtypes.FindingSeverityCritical},
181+
},
182+
}
183+
scanResult2 := ScanResult{
184+
ImageInfo: ImageInfo{ContainerName: "container1"},
185+
Cves: []CVE{
186+
{Name: "CVE-2021-1234", Severity: ecrtypes.FindingSeverityCritical},
187+
},
188+
}
189+
190+
agg.Add(scanResult1)
191+
agg.Add(scanResult2)
192+
193+
assert.Equal(t, 1, len(agg.cves))
194+
containers := agg.cveToContainers["CVE-2021-1234"].Values()
195+
assert.Equal(t, 1, len(containers))
196+
assert.Contains(t, containers, "container1")
197+
})
198+
199+
t.Run("handles empty CVE list", func(t *testing.T) {
200+
agg := NewAggregater()
201+
scanResult := ScanResult{
202+
ImageInfo: ImageInfo{ContainerName: "container1"},
203+
Cves: []CVE{},
204+
}
205+
206+
agg.Add(scanResult)
207+
208+
assert.Equal(t, 0, len(agg.cves))
209+
assert.Equal(t, 1, len(agg.summaries["container1"]))
210+
})
138211
}
139212

140213
func TestAggregater_SummarizeTotal(t *testing.T) {

cli/cage/audit/command.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (a *command) Run(ctx context.Context) (err error) {
4444
return nil
4545
}
4646

47-
func (a *command) doScan(ctx context.Context) (results []*ScanResult, err error) {
47+
func (a *command) doScan(ctx context.Context) (results []ScanResult, err error) {
4848
l := a.di.Get(key.Printer).(logger.Printer)
4949
t := a.di.Get(key.Time).(types.Time)
5050
defer l.PrintErrf("\r")

cli/cage/audit/command_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func TestAuditCommandRun(t *testing.T) {
5252
b.Set(key.Time, test.NewNeverTimer())
5353
})
5454

55-
var results []*audit.ScanResult
55+
var results []audit.ScanResult
5656
gomock.InOrder(
5757
mockScanner.EXPECT().Scan(t.Context(), "cluster", "service").Return(results, nil),
5858
)
@@ -82,7 +82,7 @@ func TestAuditCommandRun(t *testing.T) {
8282
b.Set(key.Time, test.NewNeverTimer())
8383
})
8484

85-
var results []*audit.ScanResult
85+
var results []audit.ScanResult
8686
gomock.InOrder(
8787
mockScanner.EXPECT().Scan(t.Context(), "cluster", "service").Return(results, nil),
8888
)

cli/cage/audit/printer.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ type printer struct {
1919
}
2020

2121
type Printer interface {
22-
Print(result []*ScanResult)
23-
PrintJSON(metadata Target, scanResults []*ScanResult)
22+
Print(result []ScanResult)
23+
PrintJSON(metadata Target, scanResults []ScanResult)
2424
}
2525

2626
var _ Printer = (*printer)(nil)
@@ -33,7 +33,7 @@ func NewPrinter(di *di.D, noColor, logDetail bool) *printer {
3333
}
3434
}
3535

36-
func (p *printer) Print(scanResults []*ScanResult) {
36+
func (p *printer) Print(scanResults []ScanResult) {
3737
l := p.di.Get(key.Printer).(logger.Printer)
3838
containerMax, imageMax := MaxHeaderWidth(scanResults)
3939
// |container|status|critical|high|medium|low|info|image|
@@ -90,7 +90,7 @@ func (p *printer) Print(scanResults []*ScanResult) {
9090
)
9191
}
9292

93-
func (p *printer) PrintJSON(metadata Target, scanResults []*ScanResult) {
93+
func (p *printer) PrintJSON(metadata Target, scanResults []ScanResult) {
9494
l := p.di.Get(key.Printer).(logger.Printer)
9595
t := p.di.Get(key.Time).(types.Time)
9696
agg := NewAggregater()
@@ -135,7 +135,7 @@ func (i *ImageInfo) formatImageLabel() string {
135135
return fmt.Sprintf("%s/%s:%s", i.Registry, i.Repository, i.Tag)
136136
}
137137

138-
func MaxHeaderWidth(imageInfos []*ScanResult) (int, int) {
138+
func MaxHeaderWidth(imageInfos []ScanResult) (int, int) {
139139
containerMax := len("CONTAINER")
140140
imageMax := len("IMAGE")
141141
for _, info := range imageInfos {

cli/cage/audit/printer_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import (
1414
)
1515

1616
func makeScanResult(
17-
list ...ecrtypes.FindingSeverity) []*ScanResult {
18-
return []*ScanResult{
17+
list ...ecrtypes.FindingSeverity) []ScanResult {
18+
return []ScanResult{
1919
{
2020
ImageInfo: ImageInfo{
2121
ContainerName: "test-container",
@@ -344,7 +344,7 @@ func TestPrinter_PrintJSON(t *testing.T) {
344344
Cluster: "test-cluster",
345345
Service: "test-service",
346346
}
347-
result := []*ScanResult{}
347+
result := []ScanResult{}
348348

349349
printer.PrintJSON(metadata, result)
350350

cli/cage/audit/scanner.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package audit
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67

8+
smithy "github.com/aws/smithy-go"
79
"github.com/loilo-inc/canarycage/awsiface"
810
)
911

@@ -13,7 +15,7 @@ type scanner struct {
1315
}
1416

1517
type Scanner interface {
16-
Scan(ctx context.Context, cluster string, service string) ([]*ScanResult, error)
18+
Scan(ctx context.Context, cluster string, service string) ([]ScanResult, error)
1719
}
1820

1921
var _ Scanner = (*scanner)(nil)
@@ -26,37 +28,46 @@ func (s *scanner) Scan(
2628
ctx context.Context,
2729
cluster string,
2830
service string,
29-
) (results []*ScanResult, err error) {
31+
) (results []ScanResult, err error) {
3032
ecsTool := newEcsTool(s.ecs)
3133
ecrTool := newEcrTool(s.ecr)
3234
var imageInfos []ImageInfo
3335
if imageInfos, err = ecsTool.GetServiceImageInfos(ctx, cluster, service); err != nil {
3436
return nil, err
3537
}
36-
findingsList := make([]*ScanResult, len(imageInfos))
38+
findingsList := make([]ScanResult, len(imageInfos))
3739
for i, info := range imageInfos {
3840
if info.IsECRImage() {
3941
findingsList[i] = scanImage(ctx, ecrTool, info)
4042
} else {
41-
findingsList[i] = &ScanResult{ImageInfo: info, Err: ErrNonEcrImage}
43+
findingsList[i] = ScanResult{ImageInfo: info, Err: ErrNonEcrImage}
4244
}
4345
}
4446
return findingsList, nil
4547
}
4648

4749
var ErrNonEcrImage = fmt.Errorf("non-ECR image")
50+
var ErrScanNotFound = fmt.Errorf("scan not found")
4851

49-
func scanImage(ctx context.Context, ecrTool EcrTool, info ImageInfo) *ScanResult {
52+
func scanImage(ctx context.Context, ecrTool EcrTool, info ImageInfo) ScanResult {
5053
if imageID, err := ecrTool.GetActualImageIdentifier(ctx, &info); err != nil {
51-
return &ScanResult{ImageInfo: info, Err: err}
54+
return ScanResult{ImageInfo: info, Err: err}
5255
} else if findings, err := ecrTool.GetImageScanFindings(ctx, &info, imageID); err != nil {
53-
return &ScanResult{ImageInfo: info, Err: err}
56+
return ScanResult{ImageInfo: info, Err: parseError(err)}
5457
} else {
5558
var cves []CVE
5659
for _, f := range findings.Findings {
5760
cve := findingToCVE(f)
5861
cves = append(cves, cve)
5962
}
60-
return &ScanResult{ImageInfo: info, Cves: cves}
63+
return ScanResult{ImageInfo: info, Cves: cves}
6164
}
6265
}
66+
67+
func parseError(err error) error {
68+
var awserr smithy.APIError
69+
if errors.As(err, &awserr) && awserr.ErrorCode() == "ScanNotFoundException" {
70+
return ErrScanNotFound
71+
}
72+
return err
73+
}

0 commit comments

Comments
 (0)