-
Notifications
You must be signed in to change notification settings - Fork 290
/
extract.go
260 lines (220 loc) · 6.36 KB
/
extract.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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
package provider
import (
"context"
"errors"
"io/fs"
"os"
"os/signal"
"path/filepath"
"sync"
"github.com/alexmullins/zip"
"github.com/Checkmarx/kics/pkg/kuberneter"
"github.com/Checkmarx/kics/pkg/model"
"github.com/Checkmarx/kics/pkg/terraformer"
"github.com/Checkmarx/kics/pkg/utils"
"github.com/rs/zerolog/log"
"github.com/hashicorp/go-getter"
)
const (
channelLength = 2
)
// ExtractedPath is a struct that contains the paths, temporary paths to remove
// and extraction map path of the sources
// Path is the slice of paths to scan
// ExtractionMap is a map that correlates the temporary path to the given path
// RemoveTmp is the slice containing temporary paths to be removed
type ExtractedPath struct {
Path []string
ExtractionMap map[string]model.ExtractedPathObject
}
type getterStruct struct {
ctx context.Context
cancel context.CancelFunc
mode getter.ClientMode
pwd string
opts []getter.ClientOption
destination string
source string
}
// GetTerraformerSources uses Terraformer to download runtime resources from AWS provider
// to terraform.
// After Downloaded files kics scan the files as normal local files
func GetTerraformerSources(source []string, destinationPath string) (ExtractedPath, error) {
extrStruct := ExtractedPath{
Path: []string{},
ExtractionMap: make(map[string]model.ExtractedPathObject),
}
for _, path := range source {
exportedPath, err := terraformer.Import(path, destinationPath)
if err != nil {
log.Error().Msgf("failed to import %s: %s", path, err)
}
extrStruct.ExtractionMap[exportedPath] = model.ExtractedPathObject{
Path: exportedPath,
LocalPath: true,
}
extrStruct.Path = append(extrStruct.Path, exportedPath)
}
return extrStruct, nil
}
// GetKuberneterSources uses Kubernetes API to download runtime resources
// After Downloaded files kics scan the files as normal local files
func GetKuberneterSources(ctx context.Context, source []string, destinationPath string) (ExtractedPath, error) {
extrStruct := ExtractedPath{
Path: []string{},
ExtractionMap: make(map[string]model.ExtractedPathObject),
}
for _, path := range source {
exportedPath, err := kuberneter.Import(ctx, path, destinationPath)
if err != nil {
log.Error().Msgf("failed to import %s: %s", path, err)
}
extrStruct.ExtractionMap[exportedPath] = model.ExtractedPathObject{
Path: exportedPath,
LocalPath: true,
}
extrStruct.Path = append(extrStruct.Path, exportedPath)
}
return extrStruct, nil
}
// GetSources goes through the source slice, and determines the of source type (ex: zip, git, local).
// It than extracts the files to be scanned. If the source given is not local, a temp dir
// will be created where the files will be stored.
func GetSources(source []string) (ExtractedPath, error) {
extrStruct := ExtractedPath{
Path: []string{},
ExtractionMap: make(map[string]model.ExtractedPathObject),
}
for _, path := range source {
destination := filepath.Join(os.TempDir(), "kics-extract-"+utils.NextRandom())
mode := getter.ClientModeAny
pwd, err := os.Getwd()
if err != nil {
log.Fatal().Msgf("Error getting wd: %s", err)
}
opts := []getter.ClientOption{}
opts = append(opts, getter.WithInsecure())
ctx, cancel := context.WithCancel(context.Background())
goGetter := getterStruct{
ctx: ctx,
cancel: cancel,
mode: mode,
pwd: pwd,
opts: opts,
destination: destination,
source: path,
}
getterDst, err := getPaths(&goGetter)
if err != nil {
if ignoreDamagedFiles(path) {
continue
}
log.Error().Msgf("%s", err)
return ExtractedPath{}, err
}
tempDst, local := checkSymLink(getterDst, path)
extrStruct.ExtractionMap[getterDst] = model.ExtractedPathObject{
Path: path,
LocalPath: local,
}
extrStruct.Path = append(extrStruct.Path, tempDst)
}
return extrStruct, nil
}
func getPaths(g *getterStruct) (string, error) {
if isEncrypted(g.source) {
err := errors.New("zip encrypted files are not supported")
log.Err(err)
return "", err
}
// Build the client
client := &getter.Client{
Ctx: g.ctx,
Src: g.source,
Dst: g.destination,
Pwd: g.pwd,
Mode: g.mode,
Options: g.opts,
}
wg := sync.WaitGroup{}
wg.Add(1)
errChan := make(chan error, channelLength)
go func() {
defer wg.Done()
defer g.cancel()
if err := client.Get(); err != nil {
errChan <- err
}
}()
c := make(chan os.Signal, channelLength)
signal.Notify(c, os.Interrupt)
select {
case <-c:
signal.Reset(os.Interrupt)
g.cancel()
wg.Wait()
case <-g.ctx.Done():
wg.Wait()
case err := <-errChan:
wg.Wait()
return "", err
}
return g.destination, nil
}
// check if the dst is a symbolic link
func checkSymLink(getterDst, pathFile string) (string, bool) {
var local bool
_, err := os.Stat(pathFile)
if err == nil { // check if file exist locally
local = true
}
info, err := os.Lstat(getterDst)
if err != nil {
log.Error().Msgf("failed lstat for %s: %v", getterDst, err)
}
fileInfo := getFileInfo(info, getterDst, pathFile)
if info.Mode()&os.ModeSymlink != 0 { // if it's a symbolic Link
path, err := os.Readlink(getterDst) // get location of symbolic Link
if err != nil {
log.Error().Msgf("failed Readlink for %s: %v", getterDst, err)
}
getterDst = path // change path to local path
} else if !fileInfo.IsDir() { // symbolic links are not created for single files
if local { // check if file exist locally
getterDst = pathFile
}
}
return getterDst, local
}
func getFileInfo(info fs.FileInfo, dst, pathFile string) fs.FileInfo {
var extension = filepath.Ext(pathFile)
var path string
if extension == "" {
path = filepath.Join(dst, filepath.Base(pathFile[0:len(pathFile)-len(extension)])) // for single file
} else {
path = filepath.Join(dst, filepath.Base(pathFile)) // for directories
}
fileInfo, err := os.Lstat(path)
if err != nil {
fileInfo = info
}
return fileInfo
}
func isEncrypted(sourceFile string) bool {
if filepath.Ext(sourceFile) != ".zip" {
return false
}
zipFile, err := zip.OpenReader(sourceFile)
if err != nil {
log.Error().Msgf("failed to open %s: %v", sourceFile, err)
return false
}
defer zipFile.Close()
for _, file := range zipFile.File {
if file.IsEncrypted() {
log.Error().Msgf("file %s is encrypted", sourceFile)
return true
}
}
return false
}