Skip to content

Commit 5d7149d

Browse files
knqyf263simar7
andauthored
feat(extractor): switch to layer ID of origin layer (fanal#93)
* feat(extractor): switch to layer ID of origin layer * integration: update golden file for vuln-image This file was updated during a COVID-19 crisis. Signed-off-by: Simarpreet Singh <[email protected]> * test(docker): sort applications * test(docker): fix order Co-authored-by: Simarpreet Singh <[email protected]>
1 parent c63e3aa commit 5d7149d

File tree

3 files changed

+284
-534
lines changed

3 files changed

+284
-534
lines changed

extractor/docker/docker.go

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"strings"
1111
"time"
1212

13+
godeptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
14+
1315
"github.com/aquasecurity/fanal/extractor/image/token/ecr"
1416
"github.com/aquasecurity/fanal/extractor/image/token/gcr"
1517
digest "github.com/opencontainers/go-digest"
@@ -84,6 +86,49 @@ func newDockerExtractor(ctx context.Context, imgRef image.Reference, transports
8486
}, cleanup, nil
8587
}
8688

89+
func containsPackage(e types.Package, s []types.Package) bool {
90+
for _, a := range s {
91+
if a.Name == e.Name && a.Version == e.Version && a.Release == e.Release {
92+
return true
93+
}
94+
}
95+
return false
96+
}
97+
98+
func containsLibrary(e godeptypes.Library, s []types.LibraryInfo) bool {
99+
for _, a := range s {
100+
if e.Name == a.Library.Name && e.Version == a.Library.Version {
101+
return true
102+
}
103+
}
104+
return false
105+
}
106+
107+
func lookupOriginLayerForPkg(pkg types.Package, layers []types.LayerInfo) digest.Digest {
108+
for _, layer := range layers {
109+
for _, info := range layer.PackageInfos {
110+
if containsPackage(pkg, info.Packages) {
111+
return layer.ID
112+
}
113+
}
114+
}
115+
return ""
116+
}
117+
118+
func lookupOriginLayerForLib(filePath string, lib godeptypes.Library, layers []types.LayerInfo) digest.Digest {
119+
for _, layer := range layers {
120+
for _, layerApp := range layer.Applications {
121+
if filePath != layerApp.FilePath {
122+
continue
123+
}
124+
if containsLibrary(lib, layerApp.Libraries) {
125+
return layer.ID
126+
}
127+
}
128+
}
129+
return ""
130+
}
131+
87132
func ApplyLayers(layers []types.LayerInfo) types.ImageDetail {
88133
sep := "/"
89134
nestedMap := nested.Nested{}
@@ -102,15 +147,9 @@ func ApplyLayers(layers []types.LayerInfo) types.ImageDetail {
102147
}
103148

104149
for _, pkgInfo := range layer.PackageInfos {
105-
for i := range pkgInfo.Packages {
106-
pkgInfo.Packages[i].LayerID = layer.ID
107-
}
108150
nestedMap.SetByString(pkgInfo.FilePath, sep, pkgInfo)
109151
}
110152
for _, app := range layer.Applications {
111-
for i := range app.Libraries {
112-
app.Libraries[i].LayerID = layer.ID
113-
}
114153
nestedMap.SetByString(app.FilePath, sep, app)
115154
}
116155
}
@@ -125,6 +164,18 @@ func ApplyLayers(layers []types.LayerInfo) types.ImageDetail {
125164
return nil
126165
})
127166

167+
for i, pkg := range mergedLayer.Packages {
168+
originLayerID := lookupOriginLayerForPkg(pkg, layers)
169+
mergedLayer.Packages[i].LayerID = originLayerID
170+
}
171+
172+
for _, app := range mergedLayer.Applications {
173+
for i, libInfo := range app.Libraries {
174+
originLayerID := lookupOriginLayerForLib(app.FilePath, libInfo.Library, layers)
175+
app.Libraries[i].LayerID = originLayerID
176+
}
177+
}
178+
128179
return mergedLayer
129180
}
130181

extractor/docker/docker_test.go

Lines changed: 162 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func TestApplyLayers(t *testing.T) {
8585
Version: "1.2.3",
8686
Release: "4.5.6",
8787
},
88-
{
88+
{ // added
8989
Name: "musl",
9090
Version: "1.2.4",
9191
Release: "4.5.7",
@@ -95,6 +95,27 @@ func TestApplyLayers(t *testing.T) {
9595
},
9696
WhiteoutFiles: []string{"app/composer.lock"},
9797
},
98+
{
99+
SchemaVersion: 1,
100+
ID: "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4",
101+
PackageInfos: []types.PackageInfo{
102+
{
103+
FilePath: "lib/apk/db/installed",
104+
Packages: []types.Package{
105+
{
106+
Name: "openssl",
107+
Version: "1.2.3",
108+
Release: "4.5.6",
109+
},
110+
{
111+
Name: "musl",
112+
Version: "1.2.4",
113+
Release: "4.5.8", // updated
114+
},
115+
},
116+
},
117+
},
118+
},
98119
},
99120
expectedImageDetail: types.ImageDetail{
100121
OS: &types.OS{
@@ -105,14 +126,14 @@ func TestApplyLayers(t *testing.T) {
105126
{
106127
Name: "musl",
107128
Version: "1.2.4",
108-
Release: "4.5.7",
109-
LayerID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7",
129+
Release: "4.5.8",
130+
LayerID: "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4",
110131
},
111132
{
112133
Name: "openssl",
113134
Version: "1.2.3",
114135
Release: "4.5.6",
115-
LayerID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7",
136+
LayerID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
116137
},
117138
},
118139
Applications: []types.Application{
@@ -132,6 +153,129 @@ func TestApplyLayers(t *testing.T) {
132153
},
133154
},
134155
},
156+
{
157+
name: "happy path with removed and updated lockfile",
158+
inputLayers: []types.LayerInfo{
159+
{
160+
SchemaVersion: 1,
161+
ID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
162+
OS: &types.OS{
163+
Family: "alpine",
164+
Name: "3.10",
165+
},
166+
Applications: []types.Application{
167+
{
168+
Type: "gem",
169+
FilePath: "app/Gemfile.lock",
170+
Libraries: []types.LibraryInfo{
171+
{
172+
Library: godeptypes.Library{
173+
Name: "rails",
174+
Version: "5.0.0",
175+
},
176+
},
177+
{
178+
Library: godeptypes.Library{
179+
Name: "rack",
180+
Version: "4.0.0",
181+
},
182+
},
183+
},
184+
},
185+
{
186+
Type: "composer",
187+
FilePath: "app/composer.lock",
188+
Libraries: []types.LibraryInfo{
189+
{
190+
Library: godeptypes.Library{
191+
Name: "phplibrary1",
192+
Version: "6.6.6",
193+
},
194+
},
195+
},
196+
},
197+
},
198+
},
199+
{
200+
SchemaVersion: 1,
201+
ID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7",
202+
Applications: []types.Application{
203+
{
204+
Type: "gem",
205+
FilePath: "app/Gemfile.lock",
206+
Libraries: []types.LibraryInfo{
207+
{
208+
Library: godeptypes.Library{
209+
Name: "rails",
210+
Version: "6.0.0",
211+
},
212+
},
213+
{
214+
Library: godeptypes.Library{
215+
Name: "rack",
216+
Version: "4.0.0",
217+
},
218+
},
219+
},
220+
},
221+
{
222+
Type: "composer",
223+
FilePath: "app/composer2.lock",
224+
Libraries: []types.LibraryInfo{
225+
{
226+
Library: godeptypes.Library{
227+
Name: "phplibrary1",
228+
Version: "6.6.6",
229+
},
230+
},
231+
},
232+
},
233+
},
234+
WhiteoutFiles: []string{"app/composer.lock"},
235+
},
236+
},
237+
expectedImageDetail: types.ImageDetail{
238+
OS: &types.OS{
239+
Family: "alpine",
240+
Name: "3.10",
241+
},
242+
Applications: []types.Application{
243+
{
244+
Type: "gem",
245+
FilePath: "app/Gemfile.lock",
246+
Libraries: []types.LibraryInfo{
247+
{
248+
Library: godeptypes.Library{
249+
Name: "rack",
250+
Version: "4.0.0",
251+
},
252+
LayerID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
253+
},
254+
{
255+
Library: godeptypes.Library{
256+
Name: "rails",
257+
Version: "6.0.0",
258+
},
259+
LayerID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7",
260+
},
261+
},
262+
},
263+
{
264+
Type: "composer",
265+
FilePath: "app/composer2.lock",
266+
Libraries: []types.LibraryInfo{
267+
{
268+
Library: godeptypes.Library{
269+
Name: "phplibrary1",
270+
Version: "6.6.6",
271+
},
272+
LayerID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7",
273+
},
274+
},
275+
},
276+
},
277+
},
278+
},
135279
{
136280
name: "happy path with status.d",
137281
inputLayers: []types.LayerInfo{
@@ -211,11 +355,21 @@ func TestApplyLayers(t *testing.T) {
211355
}
212356

213357
for _, tc := range testCases {
214-
gotImageDetail := ApplyLayers(tc.inputLayers)
215-
sort.Slice(gotImageDetail.Packages, func(i, j int) bool {
216-
return gotImageDetail.Packages[i].Name < gotImageDetail.Packages[j].Name
358+
t.Run(tc.name, func(t *testing.T) {
359+
gotImageDetail := ApplyLayers(tc.inputLayers)
360+
sort.Slice(gotImageDetail.Packages, func(i, j int) bool {
361+
return gotImageDetail.Packages[i].Name < gotImageDetail.Packages[j].Name
362+
})
363+
sort.Slice(gotImageDetail.Applications, func(i, j int) bool {
364+
return gotImageDetail.Applications[i].FilePath < gotImageDetail.Applications[j].FilePath
365+
})
366+
for _, app := range gotImageDetail.Applications {
367+
sort.Slice(app.Libraries, func(i, j int) bool {
368+
return app.Libraries[i].Library.Name < app.Libraries[j].Library.Name
369+
})
370+
}
371+
assert.Equal(t, tc.expectedImageDetail, gotImageDetail, tc.name)
217372
})
218-
assert.Equal(t, tc.expectedImageDetail, gotImageDetail, tc.name)
219373
}
220374
}
221375

0 commit comments

Comments
 (0)