github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/artifact/vm/vm_test.go (about) 1 package vm_test 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "io/fs" 8 "os" 9 "path/filepath" 10 "strings" 11 "testing" 12 13 ebsfile "github.com/masahiro331/go-ebs-file" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 17 dio "github.com/aquasecurity/go-dep-parser/pkg/io" 18 "github.com/devseccon/trivy/pkg/fanal/analyzer" 19 "github.com/devseccon/trivy/pkg/fanal/artifact" 20 "github.com/devseccon/trivy/pkg/fanal/artifact/vm" 21 "github.com/devseccon/trivy/pkg/fanal/cache" 22 "github.com/devseccon/trivy/pkg/fanal/types" 23 "github.com/devseccon/trivy/pkg/fanal/walker" 24 "github.com/devseccon/trivy/pkg/misconf" 25 26 _ "github.com/devseccon/trivy/pkg/fanal/analyzer/os/alpine" 27 _ "github.com/devseccon/trivy/pkg/fanal/analyzer/pkg/apk" 28 ) 29 30 const ( 31 ebsPrefix = string(vm.TypeEBS) + ":" 32 filePrefix = string(vm.TypeFile) + ":" 33 ) 34 35 type mockWalker struct { 36 root string 37 } 38 39 func (m *mockWalker) Walk(_ *io.SectionReader, _ string, fn walker.WalkFunc) error { 40 return filepath.WalkDir(m.root, func(path string, d fs.DirEntry, err error) error { 41 if err != nil { 42 return err 43 } else if d.IsDir() { 44 return nil 45 } 46 47 info, err := d.Info() 48 if err != nil { 49 return err 50 } 51 opener := func() (dio.ReadSeekCloserAt, error) { 52 return os.Open(path) 53 } 54 relPath, err := filepath.Rel(m.root, path) 55 if err != nil { 56 return err 57 } 58 relPath = filepath.ToSlash(relPath) 59 return fn(relPath, info, opener) 60 }) 61 } 62 63 func TestNewArtifact(t *testing.T) { 64 tests := []struct { 65 name string 66 target string 67 wantErr assert.ErrorAssertionFunc 68 }{ 69 { 70 name: "happy path for file", 71 target: "testdata/mock.img", 72 wantErr: assert.NoError, 73 }, 74 { 75 name: "happy path for EBS", 76 target: "ebs:ebs-012345", 77 wantErr: assert.NoError, 78 }, 79 { 80 name: "sad path unsupported vm format", 81 target: "testdata/monolithicSparse.vmdk", 82 wantErr: func(t assert.TestingT, err error, args ...interface{}) bool { 83 return assert.ErrorContains(t, err, "unsupported type error") 84 }, 85 }, 86 { 87 name: "sad path file not found", 88 target: "testdata/no-file", 89 wantErr: func(t assert.TestingT, err error, args ...interface{}) bool { 90 return assert.ErrorContains(t, err, "file open error") 91 }, 92 }, 93 } 94 for _, tt := range tests { 95 t.Run(tt.name, func(t *testing.T) { 96 w := &mockWalker{root: "testdata"} 97 _, err := vm.NewArtifact(tt.target, nil, w, artifact.Option{}) 98 tt.wantErr(t, err, fmt.Sprintf("NewArtifact(%v, nil, nil)", tt.target)) 99 }) 100 } 101 } 102 103 func TestArtifact_Inspect(t *testing.T) { 104 tests := []struct { 105 name string 106 target string 107 rootDir string 108 artifactOpt artifact.Option 109 scannerOpt misconf.ScannerOption 110 disabledAnalyzers []analyzer.Type 111 disabledHandlers []types.HandlerType 112 missingBlobsExpectation cache.ArtifactCacheMissingBlobsExpectation 113 putBlobExpectation cache.ArtifactCachePutBlobExpectation 114 putArtifactExpectations []cache.ArtifactCachePutArtifactExpectation 115 want types.ArtifactReference 116 wantErr string 117 }{ 118 { 119 name: "happy path for raw image", 120 target: "testdata/mock.img", 121 rootDir: "testdata/alpine", 122 putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ 123 Args: cache.ArtifactCachePutBlobArgs{ 124 BlobID: "sha256:aeadb167e49ab2616738bc1d8b39f742968bef78baed984cf5801c678d6750ce", 125 BlobInfo: expectedBlobInfo, 126 }, 127 Returns: cache.ArtifactCachePutBlobReturns{}, 128 }, 129 putArtifactExpectations: []cache.ArtifactCachePutArtifactExpectation{ 130 { 131 Args: cache.ArtifactCachePutArtifactArgs{ 132 ArtifactID: "sha256:aeadb167e49ab2616738bc1d8b39f742968bef78baed984cf5801c678d6750ce", 133 ArtifactInfo: types.ArtifactInfo{ 134 SchemaVersion: types.ArtifactJSONSchemaVersion, 135 }, 136 }, 137 }, 138 }, 139 want: types.ArtifactReference{ 140 Name: "rawdata.img", 141 Type: types.ArtifactVM, 142 ID: "sha256:aeadb167e49ab2616738bc1d8b39f742968bef78baed984cf5801c678d6750ce", 143 BlobIDs: []string{ 144 "sha256:aeadb167e49ab2616738bc1d8b39f742968bef78baed984cf5801c678d6750ce", 145 }, 146 }, 147 }, 148 { 149 name: "happy path for ebs", 150 target: "ebs:ebs-012345", 151 rootDir: "testdata/alpine", 152 missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ 153 Args: cache.ArtifactCacheMissingBlobsArgs{ 154 ArtifactID: "sha256:c28da2df41e019b5d18459440178341ec05e9082b12b6f11afe73f0600bfe96a", 155 BlobIDs: []string{"sha256:c28da2df41e019b5d18459440178341ec05e9082b12b6f11afe73f0600bfe96a"}, 156 }, 157 }, 158 putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ 159 Args: cache.ArtifactCachePutBlobArgs{ 160 BlobID: "sha256:c28da2df41e019b5d18459440178341ec05e9082b12b6f11afe73f0600bfe96a", 161 BlobInfo: expectedBlobInfo, 162 }, 163 Returns: cache.ArtifactCachePutBlobReturns{}, 164 }, 165 putArtifactExpectations: []cache.ArtifactCachePutArtifactExpectation{ 166 { 167 Args: cache.ArtifactCachePutArtifactArgs{ 168 ArtifactID: "sha256:c28da2df41e019b5d18459440178341ec05e9082b12b6f11afe73f0600bfe96a", 169 ArtifactInfo: types.ArtifactInfo{ 170 SchemaVersion: types.ArtifactJSONSchemaVersion, 171 }, 172 }, 173 }, 174 }, 175 want: types.ArtifactReference{ 176 Name: "ebs-012345", 177 Type: types.ArtifactVM, 178 ID: "sha256:c28da2df41e019b5d18459440178341ec05e9082b12b6f11afe73f0600bfe96a", 179 BlobIDs: []string{ 180 "sha256:c28da2df41e019b5d18459440178341ec05e9082b12b6f11afe73f0600bfe96a", 181 }, 182 }, 183 }, 184 } 185 for _, tt := range tests { 186 t.Run(tt.name, func(t *testing.T) { 187 c := new(cache.MockArtifactCache) 188 c.ApplyPutBlobExpectation(tt.putBlobExpectation) 189 c.ApplyMissingBlobsExpectation(tt.missingBlobsExpectation) 190 c.ApplyPutArtifactExpectations(tt.putArtifactExpectations) 191 c.ApplyDeleteBlobsExpectation(cache.ArtifactCacheDeleteBlobsExpectation{ 192 Args: cache.ArtifactCacheDeleteBlobsArgs{BlobIDsAnything: true}, 193 }) 194 195 m := &mockWalker{root: tt.rootDir} 196 197 a, err := vm.NewArtifact(tt.target, c, m, tt.artifactOpt) 198 require.NoError(t, err) 199 200 if aa, ok := a.(*vm.EBS); ok { 201 ebs := ebsfile.NewMockEBS("testdata/mock.img", 1, 2) 202 aa.SetEBS(ebs) 203 } 204 205 got, err := a.Inspect(context.Background()) 206 defer a.Clean(got) 207 if tt.wantErr != "" { 208 require.Error(t, err) 209 assert.ErrorContains(t, err, tt.wantErr) 210 return 211 } 212 tt.want.Name = trimPrefix(tt.target) 213 require.NoError(t, err) 214 assert.Equal(t, tt.want, got) 215 }) 216 } 217 } 218 219 func trimPrefix(s string) string { 220 s = strings.TrimPrefix(s, ebsPrefix) 221 s = strings.TrimPrefix(s, filePrefix) 222 return s 223 } 224 225 var expectedBlobInfo = types.BlobInfo{ 226 SchemaVersion: types.BlobJSONSchemaVersion, 227 OS: types.OS{ 228 Family: "alpine", 229 Name: "3.17.5", 230 }, 231 PackageInfos: []types.PackageInfo{ 232 { 233 FilePath: "lib/apk/db/installed", 234 Packages: types.Packages{ 235 { 236 ID: "musl@1.2.3-r5", 237 Name: "musl", 238 Version: "1.2.3-r5", 239 SrcName: "musl", 240 SrcVersion: "1.2.3-r5", 241 Licenses: []string{"MIT"}, 242 Arch: "aarch64", 243 Digest: "sha1:742b0a26f327c6da60d42a02c3eb6189a58e468f", 244 InstalledFiles: []string{ 245 "lib/ld-musl-aarch64.so.1", 246 "lib/libc.musl-aarch64.so.1", 247 }, 248 }, 249 }, 250 }, 251 }, 252 }