Skip to content

Commit 0bd4175

Browse files
rahul2393knqyf263
andauthored
Added skip_dir in image artifacts scan (fanal#128)
* Added skip_dir in image artifact scan * Updated walker as per suggestions * Fixed factory method * refactor(image): revert skipDirectories in artifact * feat: add InspectOption * test(walker): add tests for skipDirectories * test(walker): add tests for skipDirectories * test(fs): add tests * test(image): add tests * test(integration): fix * feat(main): add --skip-directories Co-authored-by: knqyf263 <[email protected]>
1 parent 80595dc commit 0bd4175

File tree

18 files changed

+262
-50
lines changed

18 files changed

+262
-50
lines changed

artifact/artifact.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import (
66
"github.com/aquasecurity/fanal/types"
77
)
88

9+
type InspectOption struct {
10+
SkipDirectories []string
11+
}
12+
913
type Artifact interface {
10-
Inspect(ctx context.Context) (reference types.ArtifactReference, err error)
14+
Inspect(ctx context.Context, option InspectOption) (reference types.ArtifactReference, err error)
1115
}

artifact/image/image.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func NewArtifact(img image.Image, c cache.ArtifactCache) artifact.Artifact {
3030
}
3131
}
3232

33-
func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) {
33+
func (a Artifact) Inspect(ctx context.Context, option artifact.InspectOption) (types.ArtifactReference, error) {
3434
imageID, err := a.image.ID()
3535
if err != nil {
3636
return types.ArtifactReference{}, xerrors.Errorf("unable to get the image ID: %w", err)
@@ -46,7 +46,7 @@ func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error)
4646
return types.ArtifactReference{}, xerrors.Errorf("unable to get missing layers: %w", err)
4747
}
4848

49-
if err := a.inspect(ctx, imageID, missingImage, missingLayers); err != nil {
49+
if err := a.inspect(ctx, imageID, missingImage, missingLayers, option); err != nil {
5050
return types.ArtifactReference{}, xerrors.Errorf("analyze error: %w", err)
5151
}
5252

@@ -58,14 +58,14 @@ func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error)
5858

5959
}
6060

61-
func (a Artifact) inspect(ctx context.Context, imageID string, missingImage bool, diffIDs []string) error {
61+
func (a Artifact) inspect(ctx context.Context, imageID string, missingImage bool, diffIDs []string, option artifact.InspectOption) error {
6262
done := make(chan struct{})
6363
errCh := make(chan error)
6464

6565
var osFound types.OS
6666
for _, d := range diffIDs {
6767
go func(diffID string) {
68-
layerInfo, err := a.inspectLayer(diffID)
68+
layerInfo, err := a.inspectLayer(diffID, option)
6969
if err != nil {
7070
errCh <- xerrors.Errorf("failed to analyze layer: %s : %w", diffID, err)
7171
return
@@ -101,14 +101,14 @@ func (a Artifact) inspect(ctx context.Context, imageID string, missingImage bool
101101

102102
}
103103

104-
func (a Artifact) inspectLayer(diffID string) (types.BlobInfo, error) {
104+
func (a Artifact) inspectLayer(diffID string, option artifact.InspectOption) (types.BlobInfo, error) {
105105
layerDigest, r, err := a.uncompressedLayer(diffID)
106106
if err != nil {
107107
return types.BlobInfo{}, xerrors.Errorf("unable to get uncompressed layer %s: %w", diffID, err)
108108
}
109109

110110
result := new(analyzer.AnalysisResult)
111-
opqDirs, whFiles, err := walker.WalkLayerTar(r, func(filePath string, info os.FileInfo, opener analyzer.Opener) error {
111+
opqDirs, whFiles, err := walker.WalkLayerTar(r, option.SkipDirectories, func(filePath string, info os.FileInfo, opener analyzer.Opener) error {
112112
r, err := analyzer.AnalyzeFile(filePath, info, opener)
113113
if err != nil {
114114
return err

artifact/image/image_test.go

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"testing"
77
"time"
88

9+
"github.com/aquasecurity/fanal/artifact"
10+
911
depTypes "github.com/aquasecurity/go-dep-parser/pkg/types"
1012
"golang.org/x/xerrors"
1113

@@ -15,6 +17,7 @@ import (
1517

1618
_ "github.com/aquasecurity/fanal/analyzer/command/apk"
1719
_ "github.com/aquasecurity/fanal/analyzer/library/composer"
20+
_ "github.com/aquasecurity/fanal/analyzer/library/pipenv"
1821
_ "github.com/aquasecurity/fanal/analyzer/os/alpine"
1922
_ "github.com/aquasecurity/fanal/analyzer/os/debian"
2023
_ "github.com/aquasecurity/fanal/analyzer/os/ubuntu"
@@ -29,7 +32,7 @@ import (
2932

3033
func TestArtifact_Inspect(t *testing.T) {
3134
type args struct {
32-
ctx context.Context
35+
inspectOption artifact.InspectOption
3336
}
3437
tests := []struct {
3538
name string
@@ -203,6 +206,132 @@ func TestArtifact_Inspect(t *testing.T) {
203206
},
204207
},
205208
},
209+
{
210+
name: "happy path: include a fake lock file",
211+
imagePath: "../../test/testdata/fake-lockfile.tar.gz",
212+
args: args{
213+
inspectOption: artifact.InspectOption{
214+
SkipDirectories: []string{"/dep/"},
215+
},
216+
},
217+
missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{
218+
Args: cache.ArtifactCacheMissingBlobsArgs{
219+
ArtifactID: "sha256:c4a9737d9fb3671d19691e767bd4ff04f1185e45c6256462af82127703a05904",
220+
BlobIDs: []string{
221+
"sha256:de1602ca36c9aa319d4bff5df192859c300d0c66975a1762d4564c145d0c5bd1",
222+
"sha256:1d3b68b6972f1721420b414849e7a08ed732fe05a89427296ffa3b89d885f372",
223+
"sha256:5f4707793820b85c490763fc4e0b344015825f86e3500357b0422e218db4f6a7",
224+
"sha256:f93dc29b4a9e546654f3d3c3e874fe21009a0862be3e1bea1ebd7dcc955e137a",
225+
},
226+
},
227+
Returns: cache.ArtifactCacheMissingBlobsReturns{
228+
MissingBlobIDs: []string{
229+
"sha256:de1602ca36c9aa319d4bff5df192859c300d0c66975a1762d4564c145d0c5bd1",
230+
"sha256:1d3b68b6972f1721420b414849e7a08ed732fe05a89427296ffa3b89d885f372",
231+
"sha256:5f4707793820b85c490763fc4e0b344015825f86e3500357b0422e218db4f6a7",
232+
"sha256:f93dc29b4a9e546654f3d3c3e874fe21009a0862be3e1bea1ebd7dcc955e137a",
233+
},
234+
},
235+
},
236+
putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{
237+
{
238+
Args: cache.ArtifactCachePutBlobArgs{
239+
BlobID: "sha256:de1602ca36c9aa319d4bff5df192859c300d0c66975a1762d4564c145d0c5bd1",
240+
BlobInfo: types.BlobInfo{
241+
SchemaVersion: 1,
242+
Digest: "",
243+
DiffID: "sha256:de1602ca36c9aa319d4bff5df192859c300d0c66975a1762d4564c145d0c5bd1",
244+
OS: &types.OS{Family: "debian", Name: "10.1"},
245+
PackageInfos: []types.PackageInfo{
246+
{
247+
FilePath: "var/lib/dpkg/status.d/base",
248+
Packages: []types.Package{
249+
{Name: "base-files", Version: "10.3+deb10u1", SrcName: "base-files", SrcVersion: "10.3+deb10u1"},
250+
},
251+
},
252+
{
253+
FilePath: "var/lib/dpkg/status.d/netbase",
254+
Packages: []types.Package{
255+
{Name: "netbase", Version: "5.6", SrcName: "netbase", SrcVersion: "5.6"},
256+
},
257+
},
258+
{
259+
FilePath: "var/lib/dpkg/status.d/tzdata",
260+
Packages: []types.Package{
261+
{Name: "tzdata", Version: "2019b-0+deb10u1", SrcName: "tzdata", SrcVersion: "2019b-0+deb10u1"},
262+
},
263+
},
264+
},
265+
},
266+
},
267+
},
268+
{
269+
Args: cache.ArtifactCachePutBlobArgs{
270+
BlobID: "sha256:1d3b68b6972f1721420b414849e7a08ed732fe05a89427296ffa3b89d885f372",
271+
BlobInfo: types.BlobInfo{
272+
SchemaVersion: 1,
273+
Digest: "",
274+
DiffID: "sha256:1d3b68b6972f1721420b414849e7a08ed732fe05a89427296ffa3b89d885f372",
275+
PackageInfos: []types.PackageInfo{
276+
{
277+
FilePath: "var/lib/dpkg/status.d/libc6",
278+
Packages: []types.Package{{Name: "libc6", Version: "2.28-10", SrcName: "glibc", SrcVersion: "2.28-10"}},
279+
},
280+
{
281+
FilePath: "var/lib/dpkg/status.d/libssl1",
282+
Packages: []types.Package{
283+
{Name: "libssl1.1", Version: "1.1.1d-0+deb10u1", SrcName: "openssl", SrcVersion: "1.1.1d-0+deb10u1"},
284+
},
285+
},
286+
{
287+
FilePath: "var/lib/dpkg/status.d/openssl",
288+
Packages: []types.Package{
289+
{Name: "openssl", Version: "1.1.1d-0+deb10u1", SrcName: "openssl", SrcVersion: "1.1.1d-0+deb10u1"},
290+
},
291+
},
292+
},
293+
},
294+
},
295+
},
296+
{
297+
Args: cache.ArtifactCachePutBlobArgs{
298+
BlobID: "sha256:5f4707793820b85c490763fc4e0b344015825f86e3500357b0422e218db4f6a7",
299+
BlobInfo: types.BlobInfo{
300+
SchemaVersion: 1,
301+
Digest: "",
302+
DiffID: "sha256:5f4707793820b85c490763fc4e0b344015825f86e3500357b0422e218db4f6a7",
303+
Applications: []types.Application{{Type: "pipenv", FilePath: "app/Pipfile.lock",
304+
Libraries: []types.LibraryInfo{
305+
{Library: depTypes.Library{Name: "pyyaml", Version: "5.3.1"}},
306+
},
307+
}},
308+
OpaqueDirs: []string{"app/"},
309+
},
310+
},
311+
},
312+
{
313+
Args: cache.ArtifactCachePutBlobArgs{
314+
BlobID: "sha256:f93dc29b4a9e546654f3d3c3e874fe21009a0862be3e1bea1ebd7dcc955e137a",
315+
BlobInfo: types.BlobInfo{
316+
SchemaVersion: 1,
317+
Digest: "",
318+
DiffID: "sha256:f93dc29b4a9e546654f3d3c3e874fe21009a0862be3e1bea1ebd7dcc955e137a",
319+
OpaqueDirs: []string{"dep/"},
320+
},
321+
},
322+
},
323+
},
324+
want: types.ArtifactReference{
325+
Name: "../../test/testdata/fake-lockfile.tar.gz",
326+
ID: "sha256:c4a9737d9fb3671d19691e767bd4ff04f1185e45c6256462af82127703a05904",
327+
BlobIDs: []string{
328+
"sha256:de1602ca36c9aa319d4bff5df192859c300d0c66975a1762d4564c145d0c5bd1",
329+
"sha256:1d3b68b6972f1721420b414849e7a08ed732fe05a89427296ffa3b89d885f372",
330+
"sha256:5f4707793820b85c490763fc4e0b344015825f86e3500357b0422e218db4f6a7",
331+
"sha256:f93dc29b4a9e546654f3d3c3e874fe21009a0862be3e1bea1ebd7dcc955e137a",
332+
},
333+
},
334+
},
206335
{
207336
name: "sad path, MissingBlobs returns an error",
208337
imagePath: "../../test/testdata/alpine-311.tar.gz",
@@ -363,7 +492,7 @@ func TestArtifact_Inspect(t *testing.T) {
363492
require.NoError(t, err)
364493

365494
a := image2.NewArtifact(img, mockCache)
366-
got, err := a.Inspect(context.Background())
495+
got, err := a.Inspect(context.Background(), tt.args.inspectOption)
367496
if tt.wantErr != "" {
368497
require.NotNil(t, err)
369498
assert.Contains(t, err.Error(), tt.wantErr, tt.name)

artifact/local/fs.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import (
1313
"golang.org/x/xerrors"
1414

1515
"github.com/aquasecurity/fanal/analyzer"
16-
_ "github.com/aquasecurity/fanal/analyzer/os/alpine"
17-
_ "github.com/aquasecurity/fanal/analyzer/pkg/apk"
1816
"github.com/aquasecurity/fanal/artifact"
1917
"github.com/aquasecurity/fanal/cache"
2018
"github.com/aquasecurity/fanal/types"
@@ -33,9 +31,9 @@ func NewArtifact(dir string, c cache.ArtifactCache) artifact.Artifact {
3331
}
3432
}
3533

36-
func (a Artifact) Inspect(_ context.Context) (types.ArtifactReference, error) {
34+
func (a Artifact) Inspect(_ context.Context, option artifact.InspectOption) (types.ArtifactReference, error) {
3735
var result analyzer.AnalysisResult
38-
err := walker.WalkDir(a.dir, func(filePath string, info os.FileInfo, opener analyzer.Opener) error {
36+
err := walker.WalkDir(a.dir, option.SkipDirectories, func(filePath string, info os.FileInfo, opener analyzer.Opener) error {
3937
filePath, err := filepath.Rel(a.dir, filePath)
4038
if err != nil {
4139
return err

artifact/local/fs_test.go

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@ import (
44
"errors"
55
"testing"
66

7-
"github.com/stretchr/testify/require"
8-
97
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
109

10+
"github.com/aquasecurity/fanal/analyzer/library"
11+
_ "github.com/aquasecurity/fanal/analyzer/library/bundler"
12+
_ "github.com/aquasecurity/fanal/analyzer/os/alpine"
13+
_ "github.com/aquasecurity/fanal/analyzer/pkg/apk"
14+
"github.com/aquasecurity/fanal/artifact"
1115
"github.com/aquasecurity/fanal/cache"
1216
"github.com/aquasecurity/fanal/types"
17+
godeptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
1318
)
1419

1520
func TestArtifact_Inspect(t *testing.T) {
@@ -30,10 +35,10 @@ func TestArtifact_Inspect(t *testing.T) {
3035
},
3136
putBlobExpectation: cache.ArtifactCachePutBlobExpectation{
3237
Args: cache.ArtifactCachePutBlobArgs{
33-
BlobID: "sha256:d2e3f5cd0886b85366bd784fbebed88cece36902f4c70722c42e5080e0d5ba17",
38+
BlobID: "sha256:5d883ef50a8d41f799cf1cf7d2a59cf65afd56e73909cc52ddd8893598ed2cb8",
3439
BlobInfo: types.BlobInfo{
3540
SchemaVersion: types.BlobJSONSchemaVersion,
36-
DiffID: "sha256:d2e3f5cd0886b85366bd784fbebed88cece36902f4c70722c42e5080e0d5ba17",
41+
DiffID: "sha256:5d883ef50a8d41f799cf1cf7d2a59cf65afd56e73909cc52ddd8893598ed2cb8",
3742
OS: &types.OS{
3843
Family: "alpine",
3944
Name: "3.11.6",
@@ -46,15 +51,25 @@ func TestArtifact_Inspect(t *testing.T) {
4651
},
4752
},
4853
},
54+
Applications: []types.Application{
55+
{
56+
Type: library.Bundler,
57+
FilePath: "Gemfile.lock",
58+
Libraries: []types.LibraryInfo{
59+
{Library: godeptypes.Library{Name: "dotenv", Version: "2.7.2"}},
60+
{Library: godeptypes.Library{Name: "rack", Version: "2.0.7"}},
61+
},
62+
},
63+
},
4964
},
5065
},
5166
Returns: cache.ArtifactCachePutBlobReturns{},
5267
},
5368
want: types.ArtifactReference{
5469
Name: "host",
55-
ID: "sha256:d2e3f5cd0886b85366bd784fbebed88cece36902f4c70722c42e5080e0d5ba17",
70+
ID: "sha256:5d883ef50a8d41f799cf1cf7d2a59cf65afd56e73909cc52ddd8893598ed2cb8",
5671
BlobIDs: []string{
57-
"sha256:d2e3f5cd0886b85366bd784fbebed88cece36902f4c70722c42e5080e0d5ba17",
72+
"sha256:5d883ef50a8d41f799cf1cf7d2a59cf65afd56e73909cc52ddd8893598ed2cb8",
5873
},
5974
},
6075
},
@@ -65,10 +80,10 @@ func TestArtifact_Inspect(t *testing.T) {
6580
},
6681
putBlobExpectation: cache.ArtifactCachePutBlobExpectation{
6782
Args: cache.ArtifactCachePutBlobArgs{
68-
BlobID: "sha256:d2e3f5cd0886b85366bd784fbebed88cece36902f4c70722c42e5080e0d5ba17",
83+
BlobID: "sha256:5d883ef50a8d41f799cf1cf7d2a59cf65afd56e73909cc52ddd8893598ed2cb8",
6984
BlobInfo: types.BlobInfo{
7085
SchemaVersion: types.BlobJSONSchemaVersion,
71-
DiffID: "sha256:d2e3f5cd0886b85366bd784fbebed88cece36902f4c70722c42e5080e0d5ba17",
86+
DiffID: "sha256:5d883ef50a8d41f799cf1cf7d2a59cf65afd56e73909cc52ddd8893598ed2cb8",
7287
OS: &types.OS{
7388
Family: "alpine",
7489
Name: "3.11.6",
@@ -81,6 +96,16 @@ func TestArtifact_Inspect(t *testing.T) {
8196
},
8297
},
8398
},
99+
Applications: []types.Application{
100+
{
101+
Type: library.Bundler,
102+
FilePath: "Gemfile.lock",
103+
Libraries: []types.LibraryInfo{
104+
{Library: godeptypes.Library{Name: "dotenv", Version: "2.7.2"}},
105+
{Library: godeptypes.Library{Name: "rack", Version: "2.0.7"}},
106+
},
107+
},
108+
},
84109
},
85110
},
86111
Returns: cache.ArtifactCachePutBlobReturns{
@@ -103,7 +128,7 @@ func TestArtifact_Inspect(t *testing.T) {
103128
c.ApplyPutBlobExpectation(tt.putBlobExpectation)
104129

105130
a := NewArtifact(tt.fields.dir, c)
106-
got, err := a.Inspect(nil)
131+
got, err := a.Inspect(nil, artifact.InspectOption{SkipDirectories: []string{"testdata/skipdir"}})
107132
if tt.wantErr != "" {
108133
require.NotNil(t, err)
109134
assert.Contains(t, err.Error(), tt.wantErr)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
GEM
2+
remote: https://rubygems.org/
3+
specs:
4+
dotenv (2.7.2)
5+
rack (2.0.7)
6+
7+
PLATFORMS
8+
ruby
9+
10+
DEPENDENCIES
11+
dotenv (~> 2.7)
12+
13+
BUNDLED WITH
14+
1.17.2
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
GEM
2+
remote: https://rubygems.org/
3+
specs:
4+
dotenv (2.7.2)
5+
thor (0.20.3)
6+
7+
PLATFORMS
8+
ruby
9+
10+
DEPENDENCIES
11+
dotenv (~> 2.7)
12+
13+
BUNDLED WITH
14+
1.17.2

0 commit comments

Comments
 (0)