-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
fs.go
172 lines (144 loc) · 4.91 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
package local
import (
"context"
"crypto/sha256"
"encoding/json"
"os"
"path/filepath"
"strings"
"sync"
"github.com/opencontainers/go-digest"
"golang.org/x/sync/semaphore"
"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"
)
const (
parallel = 10
)
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,
FilePatterns: opt.FilePatterns,
DisabledAnalyzers: opt.DisabledAnalyzers,
SecretScannerOption: opt.SecretScannerOption,
})
if err != nil {
return nil, xerrors.Errorf("analyzer group error: %w", err)
}
return Artifact{
rootPath: filepath.Clean(rootPath),
cache: c,
walker: walker.NewFS(buildAbsPaths(rootPath, opt.SkipFiles), buildAbsPaths(rootPath, opt.SkipDirs)),
analyzer: a,
handlerManager: handlerManager,
artifactOption: opt,
}, nil
}
func buildAbsPaths(base string, paths []string) []string {
var absPaths []string
for _, path := range paths {
if filepath.IsAbs(path) {
absPaths = append(absPaths, path)
} else {
absPaths = append(absPaths, filepath.Join(base, path))
}
}
return absPaths
}
func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) {
var wg sync.WaitGroup
result := analyzer.NewAnalysisResult()
limit := semaphore.NewWeighted(parallel)
err := a.walker.Walk(a.rootPath, func(filePath string, info os.FileInfo, opener analyzer.Opener) error {
directory := a.rootPath
// When the directory is the same as the filePath, a file was given
// instead of a directory, rewrite the directory in this case.
if a.rootPath == filePath {
directory = filepath.Dir(a.rootPath)
}
// For exported rootfs (e.g. images/alpine/etc/alpine-release)
filePath, err := filepath.Rel(directory, filePath)
if err != nil {
return xerrors.Errorf("filepath rel (%s): %w", filePath, err)
}
opts := analyzer.AnalysisOptions{Offline: a.artifactOption.Offline}
if err = a.analyzer.AnalyzeFile(ctx, &wg, limit, result, directory, filePath, info, opener, nil, opts); err != nil {
return xerrors.Errorf("analyze file (%s): %w", filePath, err)
}
return nil
})
if err != nil {
return types.ArtifactReference{}, xerrors.Errorf("walk filesystem: %w", err)
}
// Wait for all the goroutine to finish.
wg.Wait()
// 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,
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 {
hostName = 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
}