-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
fs.go
239 lines (206 loc) · 7.68 KB
/
fs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
package local
import (
"context"
"crypto/sha256"
"encoding/json"
"os"
"path"
"path/filepath"
"strings"
"sync"
"github.com/opencontainers/go-digest"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/artifact"
"github.com/aquasecurity/trivy/pkg/fanal/cache"
"github.com/aquasecurity/trivy/pkg/fanal/handler"
"github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/fanal/walker"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/semaphore"
)
type Artifact struct {
rootPath string
cache cache.ArtifactCache
walker walker.FS
analyzer analyzer.AnalyzerGroup
handlerManager handler.Manager
artifactOption artifact.Option
}
func NewArtifact(rootPath string, c cache.ArtifactCache, opt artifact.Option) (artifact.Artifact, error) {
handlerManager, err := handler.NewManager(opt)
if err != nil {
return nil, xerrors.Errorf("handler initialize error: %w", err)
}
a, err := analyzer.NewAnalyzerGroup(analyzer.AnalyzerOptions{
Group: opt.AnalyzerGroup,
Slow: opt.Slow,
FilePatterns: opt.FilePatterns,
DisabledAnalyzers: opt.DisabledAnalyzers,
MisconfScannerOption: opt.MisconfScannerOption,
SecretScannerOption: opt.SecretScannerOption,
LicenseScannerOption: opt.LicenseScannerOption,
})
if err != nil {
return nil, xerrors.Errorf("analyzer group error: %w", err)
}
return Artifact{
rootPath: filepath.ToSlash(filepath.Clean(rootPath)),
cache: c,
walker: walker.NewFS(buildPathsToSkip(rootPath, opt.SkipFiles), buildPathsToSkip(rootPath, opt.SkipDirs),
opt.Slow, opt.WalkOption.ErrorCallback),
analyzer: a,
handlerManager: handlerManager,
artifactOption: opt,
}, nil
}
// buildPathsToSkip builds correct patch for skipDirs and skipFiles
func buildPathsToSkip(base string, paths []string) []string {
var relativePaths []string
absBase, err := filepath.Abs(base)
if err != nil {
log.Logger.Warnf("Failed to get an absolute path of %s: %s", base, err)
return nil
}
for _, path := range paths {
// Supports three types of flag specification.
// All of them are converted into the relative path from the root directory.
// 1. Relative skip dirs/files from the root directory
// The specified dirs and files will be used as is.
// e.g. $ trivy fs --skip-dirs bar ./foo
// The skip dir from the root directory will be `bar/`.
// 2. Relative skip dirs/files from the working directory
// The specified dirs and files wll be converted to the relative path from the root directory.
// e.g. $ trivy fs --skip-dirs ./foo/bar ./foo
// The skip dir will be converted to `bar/`.
// 3. Absolute skip dirs/files
// The specified dirs and files wll be converted to the relative path from the root directory.
// e.g. $ trivy fs --skip-dirs /bar/foo/baz ./foo
// When the working directory is
// 3.1 /bar: the skip dir will be converted to `baz/`.
// 3.2 /hoge : the skip dir will be converted to `../../bar/foo/baz/`.
absSkipPath, err := filepath.Abs(path)
if err != nil {
log.Logger.Warnf("Failed to get an absolute path of %s: %s", base, err)
continue
}
rel, err := filepath.Rel(absBase, absSkipPath)
if err != nil {
log.Logger.Warnf("Failed to get a relative path from %s to %s: %s", base, path, err)
continue
}
var relPath string
switch {
case !filepath.IsAbs(path) && strings.HasPrefix(rel, ".."):
// #1: Use the path as is
relPath = path
case !filepath.IsAbs(path) && !strings.HasPrefix(rel, ".."):
// #2: Use the relative path from the root directory
relPath = rel
case filepath.IsAbs(path):
// #3: Use the relative path from the root directory
relPath = rel
}
relPath = filepath.ToSlash(relPath)
relativePaths = append(relativePaths, relPath)
}
return relativePaths
}
func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) {
var wg sync.WaitGroup
result := analyzer.NewAnalysisResult()
limit := semaphore.New(a.artifactOption.Slow)
opts := analyzer.AnalysisOptions{
Offline: a.artifactOption.Offline,
FileChecksum: a.artifactOption.FileChecksum,
}
// Prepare filesystem for post analysis
composite, err := a.analyzer.PostAnalyzerFS()
if err != nil {
return types.ArtifactReference{}, xerrors.Errorf("failed to prepare filesystem for post analysis: %w", err)
}
err = a.walker.Walk(a.rootPath, func(filePath string, info os.FileInfo, opener analyzer.Opener) error {
dir := a.rootPath
// When the directory is the same as the filePath, a file was given
// instead of a directory, rewrite the file path and directory in this case.
if filePath == "." {
dir, filePath = path.Split(a.rootPath)
}
if err = a.analyzer.AnalyzeFile(ctx, &wg, limit, result, dir, filePath, info, opener, nil, opts); err != nil {
return xerrors.Errorf("analyze file (%s): %w", filePath, err)
}
// Skip post analysis if the file is not required
analyzerTypes := a.analyzer.RequiredPostAnalyzers(filePath, info)
if len(analyzerTypes) == 0 {
return nil
}
// Build filesystem for post analysis
if err = composite.CreateLink(analyzerTypes, dir, filePath, filepath.Join(dir, filePath)); err != nil {
return xerrors.Errorf("failed to create link: %w", err)
}
return nil
})
if err != nil {
return types.ArtifactReference{}, xerrors.Errorf("walk filesystem: %w", err)
}
// Wait for all the goroutine to finish.
wg.Wait()
// Post-analysis
if err = a.analyzer.PostAnalyze(ctx, composite, result, opts); err != nil {
return types.ArtifactReference{}, xerrors.Errorf("post analysis error: %w", err)
}
// Sort the analysis result for consistent results
result.Sort()
blobInfo := types.BlobInfo{
SchemaVersion: types.BlobJSONSchemaVersion,
OS: result.OS,
Repository: result.Repository,
PackageInfos: result.PackageInfos,
Applications: result.Applications,
Misconfigurations: result.Misconfigurations,
Secrets: result.Secrets,
Licenses: result.Licenses,
CustomResources: result.CustomResources,
}
if err = a.handlerManager.PostHandle(ctx, result, &blobInfo); err != nil {
return types.ArtifactReference{}, xerrors.Errorf("failed to call hooks: %w", err)
}
cacheKey, err := a.calcCacheKey(blobInfo)
if err != nil {
return types.ArtifactReference{}, xerrors.Errorf("failed to calculate a cache key: %w", err)
}
if err = a.cache.PutBlob(cacheKey, blobInfo); err != nil {
return types.ArtifactReference{}, xerrors.Errorf("failed to store blob (%s) in cache: %w", cacheKey, err)
}
// get hostname
var hostName string
b, err := os.ReadFile(filepath.Join(a.rootPath, "etc", "hostname"))
if err == nil && string(b) != "" {
hostName = strings.TrimSpace(string(b))
} else {
// To slash for Windows
hostName = filepath.ToSlash(a.rootPath)
}
return types.ArtifactReference{
Name: hostName,
Type: types.ArtifactFilesystem,
ID: cacheKey, // use a cache key as pseudo artifact ID
BlobIDs: []string{cacheKey},
}, nil
}
func (a Artifact) Clean(reference types.ArtifactReference) error {
return a.cache.DeleteBlobs(reference.BlobIDs)
}
func (a Artifact) calcCacheKey(blobInfo types.BlobInfo) (string, error) {
// calculate hash of JSON and use it as pseudo artifactID and blobID
h := sha256.New()
if err := json.NewEncoder(h).Encode(blobInfo); err != nil {
return "", xerrors.Errorf("json error: %w", err)
}
d := digest.NewDigest(digest.SHA256, h)
cacheKey, err := cache.CalcKey(d.String(), a.analyzer.AnalyzerVersions(), a.handlerManager.Versions(), a.artifactOption)
if err != nil {
return "", xerrors.Errorf("cache key: %w", err)
}
return cacheKey, nil
}