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  }