• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubescape / operator / 6717262989

01 Nov 2023 08:09AM UTC coverage: 22.338%. First build
6717262989

Pull #186

github

web-flow
Merge cfed8db72 into 9d530c589
Pull Request #186: Get namespace name from config

20 of 20 new or added lines in 3 files covered. (100.0%)

818 of 3662 relevant lines covered (22.34%)

1.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

7.41
/mainhandler/imageregistryhandler.go
1
package mainhandler
2

3
import (
4
        "bytes"
5
        "context"
6
        "encoding/base64"
7
        "encoding/json"
8
        "fmt"
9
        "net/http"
10
        "strings"
11

12
        "github.com/kubescape/go-logger"
13
        "github.com/kubescape/go-logger/helpers"
14
        "github.com/kubescape/operator/config"
15
        "github.com/kubescape/operator/utils"
16
        "github.com/mitchellh/mapstructure"
17

18
        regCommon "github.com/armosec/registryx/common"
19
        regInterfaces "github.com/armosec/registryx/interfaces"
20
        regFactory "github.com/armosec/registryx/registries"
21
        "github.com/kubescape/k8s-interface/cloudsupport"
22

23
        "github.com/armosec/armoapi-go/apis"
24
        "github.com/armosec/armoapi-go/armotypes"
25
        "github.com/docker/docker/api/types"
26
        "github.com/google/go-containerregistry/pkg/authn"
27
        "github.com/google/go-containerregistry/pkg/v1/remote"
28
        beClientV1 "github.com/kubescape/backend/pkg/client/v1"
29
        "github.com/kubescape/k8s-interface/k8sinterface"
30
        v1 "k8s.io/api/batch/v1"
31
        corev1 "k8s.io/api/core/v1"
32
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33
        "k8s.io/utils/strings/slices"
34
)
35

36
type AuthMethods string
37

38
const (
39
        registryScanConfigmap                   = "kubescape-registry-scan"
40
        registryNameField                       = "registryName"
41
        secretNameField                         = "secretName"
42
        imagesToScanLimit                       = 500
43
        registriesAuthFieldInSecret             = "registriesAuth"
44
        accessTokenAuth             AuthMethods = "accesstoken"
45
        registryCronjobTemplate                 = "registry-scan-cronjob-template"
46
        tagsPageSize                            = 1000
47
        registryScanDocumentation               = "https://hub.armosec.io/docs/registry-vulnerability-scan"
48
)
49

50
type registryScanConfig struct {
51
        Registry string   `json:"registry"`
52
        Depth    int      `json:"depth"`
53
        Include  []string `json:"include,omitempty"`
54
        Exclude  []string `json:"exclude,omitempty"`
55
}
56
type registryAuth struct {
57
        Registry      string `json:"registry,omitempty"`
58
        AuthMethod    string `json:"auth_method,omitempty"`
59
        Username      string `json:"username,omitempty"`
60
        Password      string `json:"password,omitempty"`
61
        RegistryToken string `json:"registryToken,omitempty"`
62

63
        Kind          regCommon.RegistryKind `json:"kind,omitempty"`
64
        SkipTLSVerify *bool                  `json:"skipTLSVerify,omitempty"`
65
        Insecure      *bool                  `json:"http,omitempty"`
66
}
67

68
type registry struct {
69
        hostname  string
70
        projectID string
71
}
72

73
type registryScan struct {
74
        k8sAPI         *k8sinterface.KubernetesApi
75
        registry       registry
76
        registryInfo   armotypes.RegistryInfo
77
        mapImageToTags map[string][]string
78
        config         config.IConfig
79
        sendReport     bool
80
}
81

82
type RepositoriesAndTagsParams struct {
83
        Repositories []armotypes.Repository `json:"repositories"`
84
        CustomerGUID string                 `json:"customerGUID"`
85
        RegistryName string                 `json:"registryName"`
86
        JobID        string                 `json:"jobID"`
87
}
88

89
type registryCreds struct {
90
        registryName string
91
        auth         *types.AuthConfig
92
}
93

94
func NewRegistryScanConfig(registryName string) *registryScanConfig {
×
95
        return &registryScanConfig{
×
96
                Registry: registryName,
×
97
                Depth:    1,
×
98
                Include:  make([]string, 0),
×
99
                Exclude:  make([]string, 0),
×
100
        }
×
101
}
×
102

103
func errorWithDocumentationRef(errorMessage string) error {
×
104
        return fmt.Errorf("%s. Please refer to the documentation %s", errorMessage, registryScanDocumentation)
×
105
}
×
106

107
func NewRegistryScan(config config.IConfig, k8sAPI *k8sinterface.KubernetesApi) registryScan {
1✔
108
        depth := new(int)
1✔
109
        isHTTPS := new(bool)
1✔
110
        skipTlsVerify := new(bool)
1✔
111
        *depth = 1
1✔
112
        *isHTTPS = true
1✔
113
        *skipTlsVerify = false
1✔
114
        return registryScan{
1✔
115
                registry:       registry{},
1✔
116
                mapImageToTags: make(map[string][]string),
1✔
117
                registryInfo: armotypes.RegistryInfo{
1✔
118
                        Depth:         depth,
1✔
119
                        IsHTTPS:       isHTTPS,
1✔
120
                        SkipTLSVerify: skipTlsVerify,
1✔
121
                },
1✔
122
                k8sAPI:     k8sAPI,
1✔
123
                config:     config,
1✔
124
                sendReport: config.EventReceiverURL() != "",
1✔
125
        }
1✔
126
}
1✔
127

128
func (rs *registryScan) makeRegistryInterface() (regInterfaces.IRegistry, error) {
×
129
        return regFactory.Factory(&authn.AuthConfig{Username: rs.registryInfo.AuthMethod.Username, Password: rs.registryInfo.AuthMethod.Password, RegistryToken: rs.registryInfo.RegistryToken}, rs.registry.hostname,
×
130
                regCommon.MakeRegistryOptions(false, !*rs.registryInfo.IsHTTPS, *rs.registryInfo.SkipTLSVerify, "", "", rs.registry.projectID, regCommon.RegistryKind(rs.registryInfo.Kind)))
×
131
}
×
132

133
func makeRegistryAuth(registryName string) registryAuth {
×
134
        kind, _ := regCommon.GetRegistryKind(strings.Split(registryName, "/")[0])
×
135
        falseInsecure := false
×
136
        falseSkipTLS := false
×
137
        return registryAuth{SkipTLSVerify: &falseSkipTLS, Insecure: &falseInsecure, Kind: kind}
×
138
}
×
139

140
func (rs *registryScan) isPrivate() bool {
×
141
        //TODO: support registry token
×
142
        return rs.registryInfo.AuthMethod.Password != ""
×
143
}
×
144

145
func (rs *registryScan) registryCredentials() *registryCreds {
×
146
        var regCreds *registryCreds
×
147
        if rs.isPrivate() {
×
148
                regCreds = &registryCreds{
×
149
                        auth:         rs.authConfig(),
×
150
                        registryName: rs.registry.hostname,
×
151
                }
×
152
        }
×
153
        return regCreds
×
154
}
155

156
func (rs *registryScan) authConfig() *types.AuthConfig {
×
157
        var authConfig *types.AuthConfig
×
158
        if rs.isPrivate() {
×
159
                authConfig = &types.AuthConfig{
×
160
                        Username: rs.registryInfo.AuthMethod.Username,
×
161
                        Password: rs.registryInfo.AuthMethod.Password,
×
162
                        Auth:     rs.registryInfo.AuthMethod.Type,
×
163
                        //TODO: add tokens support
×
164
                }
×
165
        }
×
166
        return authConfig
×
167
}
168

169
func (reg *registryAuth) initDefaultValues(ctx context.Context) error {
×
170
        switch reg.AuthMethod {
×
171
        case string(accessTokenAuth), "", "credentials":
×
172
                if reg.Password == "" || reg.Username == "" {
×
173
                        return errorWithDocumentationRef("auth_method accesstoken requirers username and password")
×
174
                }
×
175
        case "public":
×
176
                //do nothing
×
177
                break
×
178
        case "ips":
×
179
                // retrieve token and consider it as regular accesstoken auth
×
180
                authConfig, err := cloudsupport.GetCloudVendorRegistryCredentials(reg.Registry)
×
181
                if err != nil {
×
182
                        return fmt.Errorf("error getting credentials: %v", err)
×
183
                }
×
184

185
                if authConfigReg, ok := authConfig[reg.Registry]; ok {
×
186
                        reg.Username = authConfigReg.Username
×
187
                        reg.Password = authConfigReg.Password
×
188
                        reg.AuthMethod = string(accessTokenAuth)
×
189
                } else {
×
190
                        return fmt.Errorf("error getting credentials for: %v", reg.Registry)
×
191
                }
×
192
        case "identity_token", "registry_token":
×
193
                fallthrough
×
194
        default:
×
195
                return errorWithDocumentationRef(fmt.Sprintf("auth_method (%s) not supported", reg.AuthMethod))
×
196
        }
197
        if reg.Insecure == nil {
×
198
                falseBool := false
×
199
                reg.Insecure = &falseBool
×
200
        }
×
201
        if reg.SkipTLSVerify == nil {
×
202
                falseBool := false
×
203
                reg.SkipTLSVerify = &falseBool
×
204
        }
×
205
        var err error
×
206
        if reg.Kind != "" {
×
207
                if reg.Kind, err = regCommon.GetRegistryKind(string(reg.Kind)); err != nil {
×
208
                        //user defined unknown kind
×
209
                        logger.L().Ctx(ctx).Error("cannot validate registry kind", helpers.Error(err))
×
210
                        return err
×
211
                }
×
212
        } else {
×
213
                //try to get the kind from the reg name - if not found it will fallback to default kind
×
214
                reg.Kind, _ = regCommon.GetRegistryKind(strings.Split(reg.Registry, "/")[0])
×
215
        }
×
216
        return err
×
217
}
218

219
func (rs *registryScan) filterRepositories(ctx context.Context, repos []string) []string {
2✔
220
        if len(rs.registryInfo.Include) == 0 && len(rs.registryInfo.Exclude) == 0 {
2✔
221
                return repos
×
222
        }
×
223
        filteredRepos := []string{}
2✔
224
        for _, repo := range repos {
10✔
225
                // if rs.registry.projectID != "" {
8✔
226
                //         if !strings.Contains(repo, rs.registry.projectID+"/") {
8✔
227
                //                 continue
8✔
228
                //         }
8✔
229
                // }
8✔
230
                if len(rs.registryInfo.Include) != 0 {
12✔
231
                        if slices.Contains(rs.registryInfo.Include, strings.Replace(repo, rs.registry.projectID+"/", "", -1)) {
5✔
232
                                filteredRepos = append(filteredRepos, repo)
1✔
233
                                continue
1✔
234
                        }
235
                        if slices.Contains(rs.registryInfo.Include, repo) {
4✔
236
                                filteredRepos = append(filteredRepos, repo)
1✔
237
                                continue
1✔
238
                        }
239
                }
240
                if len(rs.registryInfo.Exclude) != 0 {
10✔
241
                        if !slices.Contains(rs.registryInfo.Exclude, strings.Replace(repo, rs.registry.projectID+"/", "", -1)) && !slices.Contains(rs.registryInfo.Exclude, repo) {
6✔
242
                                filteredRepos = append(filteredRepos, repo)
2✔
243
                        } else {
4✔
244
                                repoMsg := rs.registry.hostname + "/"
2✔
245
                                if rs.registry.projectID != "" {
4✔
246
                                        repoMsg += rs.registry.projectID + "/"
2✔
247
                                }
2✔
248
                                repoMsg += repo
2✔
249
                                logger.L().Ctx(ctx).Warning(fmt.Sprintf("image registry scan::%s was excluded", repoMsg)) // systest dependent
2✔
250
                        }
251

252
                }
253
        }
254
        return filteredRepos
2✔
255
}
256

257
func (registryScan *registryScan) createTriggerRequestSecret(k8sAPI *k8sinterface.KubernetesApi, name, registryName string) error {
×
258

×
259
        secret := corev1.Secret{}
×
260
        secret.Name = name
×
261
        secret.StringData = make(map[string]string)
×
262
        registryAuth := []registryAuth{
×
263
                {
×
264
                        Registry:   registryName,
×
265
                        Username:   registryScan.registryInfo.AuthMethod.Username,
×
266
                        Password:   registryScan.registryInfo.AuthMethod.Password,
×
267
                        AuthMethod: registryScan.registryInfo.AuthMethod.Type,
×
268
                },
×
269
        }
×
270
        registryAuthBytes, err := json.Marshal(registryAuth)
×
271
        if err != nil {
×
272
                return err
×
273
        }
×
274

275
        secret.StringData[registriesAuthFieldInSecret] = string(registryAuthBytes)
×
276
        if _, err := k8sAPI.KubernetesClient.CoreV1().Secrets(registryScan.config.Namespace()).Create(context.Background(), &secret, metav1.CreateOptions{}); err != nil {
×
277
                return err
×
278
        }
×
279
        registryScan.registryInfo.SecretName = name
×
280
        return nil
×
281
}
282

283
func (registryScan *registryScan) createTriggerRequestConfigMap(k8sAPI *k8sinterface.KubernetesApi, name, registryName string, webSocketScanCMD apis.Command) error {
×
284
        configMap := corev1.ConfigMap{}
×
285
        configMap.Name = name
×
286
        if configMap.Labels == nil {
×
287
                configMap.Labels = make(map[string]string)
×
288
        }
×
289
        configMap.Labels["app"] = name
×
290

×
291
        if configMap.Data == nil {
×
292
                configMap.Data = make(map[string]string)
×
293
        }
×
294

295
        // command is POST request to trigger websocket
296
        command, err := registryScan.getCommandForConfigMap()
×
297
        if err != nil {
×
298
                return err
×
299
        }
×
300

301
        // command will be mounted into cronjob by using this configmap
302
        configMap.Data[requestBodyFile] = string(command)
×
303

×
304
        if _, err := k8sAPI.KubernetesClient.CoreV1().ConfigMaps(registryScan.config.Namespace()).Create(context.Background(), &configMap, metav1.CreateOptions{}); err != nil {
×
305
                return err
×
306
        }
×
307
        return nil
×
308
}
309

310
func (registryScan *registryScan) getImagesForScanning(ctx context.Context, reporter beClientV1.IReportSender) error {
×
311
        logger.L().Info("getImagesForScanning: enumerating repoes...")
×
312
        errChan := make(chan error)
×
313
        repos, err := registryScan.enumerateRepos(ctx)
×
314
        if err != nil {
×
315
                reporter.SetDetails("enumerateRepos failed")
×
316
                return err
×
317
        }
×
318
        logger.L().Info(fmt.Sprintf("GetImagesForScanning: enumerating repos successfully, found %d repos", len(repos)))
×
319

×
320
        reposToTags := make(chan map[string][]string, len(repos))
×
321
        for _, repo := range repos {
×
322
                currentRepo := repo
×
323
                go registryScan.setImageToTagsMap(ctx, currentRepo, reporter, reposToTags)
×
324
        }
×
325
        for i := 0; i < len(repos); i++ {
×
326
                res := <-reposToTags
×
327
                for k, v := range res {
×
328
                        registryScan.mapImageToTags[k] = v
×
329
                }
×
330
        }
331

332
        if registryScan.isExceedScanLimit(imagesToScanLimit) {
×
333
                registryScan.filterScanLimit(imagesToScanLimit)
×
334
                errMsg := fmt.Sprintf("more than %d images provided. scanning only first %d images", imagesToScanLimit, imagesToScanLimit)
×
335
                if reporter != nil {
×
336
                        err := errorWithDocumentationRef(errMsg)
×
337
                        reporter.SendWarning(err.Error(), registryScan.sendReport, true)
×
338
                        if err := <-errChan; err != nil {
×
339
                                logger.L().Ctx(ctx).Error("setImageToTagsMap failed to send error report",
×
340
                                        helpers.String("registry", registryScan.registry.hostname), helpers.Error(err))
×
341
                        }
×
342
                }
343
                logger.L().Ctx(ctx).Warning("GetImagesForScanning: " + errMsg)
×
344
        }
345
        return nil
×
346
}
347

348
func (registryScan *registryScan) setImageToTagsMap(ctx context.Context, repo string, sender beClientV1.IReportSender, c chan map[string][]string) error {
×
349
        logger.L().Info(fmt.Sprintf("Fetching repository %s tags", repo))
×
350
        iRegistry, err := registryScan.makeRegistryInterface()
×
351
        if err != nil {
×
352
                return err
×
353
        }
×
354

355
        firstPage := regCommon.MakePagination(tagsPageSize)
×
356
        latestTagFound := false
×
357
        tagsDepth := registryScan.registryInfo.Depth
×
358
        tags := []string{}
×
359
        options := []remote.Option{}
×
360
        if registryScan.isPrivate() {
×
361
                options = append(options, remote.WithAuth(registryScan.registryCredentials()))
×
362
        }
×
363
        if latestTags, err := iRegistry.GetLatestTags(repo, *tagsDepth, options...); err == nil {
×
364
                tags := []string{}
×
365
                for _, tag := range latestTags {
×
366
                        // filter out signature tags
×
367
                        if strings.HasSuffix(tag, ".sig") {
×
368
                                continue
×
369
                        }
370
                        tagsForDigest := strings.Split(tag, ",")
×
371
                        tagsForDigestLen := len(tagsForDigest)
×
372
                        if tagsForDigestLen == 1 {
×
373
                                tags = append(tags, tagsForDigest[0])
×
374
                        } else {
×
375
                                if tagsForDigestLen > *tagsDepth {
×
376
                                        tags = append(tags, tagsForDigest[:*tagsDepth]...)
×
377
                                        errMsg := fmt.Sprintf("image %s has %d tags. scanning only first %d tags - %s", repo, tagsForDigestLen, tagsDepth, strings.Join(tagsForDigest[:*tagsDepth], ","))
×
378
                                        if sender != nil {
×
379
                                                errChan := make(chan error)
×
380
                                                err := errorWithDocumentationRef(errMsg)
×
381
                                                sender.SendWarning(err.Error(), registryScan.sendReport, true)
×
382
                                                if err := <-errChan; err != nil {
×
383
                                                        logger.L().Ctx(ctx).Error("GetLatestTags failed to send error report",
×
384
                                                                helpers.String("registry", registryScan.registry.hostname), helpers.Error(err))
×
385
                                                }
×
386
                                        }
387
                                        logger.L().Ctx(ctx).Warning("GetImagesForScanning: " + errMsg)
×
388
                                } else {
×
389
                                        tags = append(tags, tagsForDigest...)
×
390
                                }
×
391
                        }
392
                }
393
                c <- map[string][]string{
×
394
                        registryScan.registry.hostname + "/" + repo: tags,
×
395
                }
×
396

397
        } else { //fallback to list images lexicographically
×
398
                logger.L().Ctx(ctx).Error("get latestTags failed, fetching lexicographical list of tags", helpers.String("repository", repo), helpers.Error(err))
×
399
                for tagsPage, nextPage, err := iRegistry.List(repo, firstPage, options...); ; tagsPage, nextPage, err = iRegistry.List(repo, *nextPage) {
×
400
                        if err != nil {
×
401
                                return err
×
402
                        }
×
403

404
                        if !latestTagFound {
×
405
                                latestTagFound = slices.Contains(tagsPage, "latest")
×
406
                        }
×
407
                        tags = updateTagsCandidates(tags, tagsPage, *tagsDepth, latestTagFound)
×
408

×
409
                        if *tagsDepth == 1 && latestTagFound {
×
410
                                break
×
411
                        }
412

413
                        if nextPage == nil {
×
414
                                break
×
415
                        }
416
                }
417
                c <- map[string][]string{
×
418
                        registryScan.registry.hostname + "/" + repo: tags,
×
419
                }
×
420
        }
421
        return nil
×
422
}
423

424
func updateTagsCandidates(tagsCandidates []string, tagsPage []string, tagsDepth int, latestTagFound bool) []string {
×
425
        prevCandidates := tagsCandidates
×
426
        tagsCandidates = []string{}
×
427
        lastIndexInPage := len(tagsPage) - 1
×
428
        for i := 0; len(tagsCandidates) < tagsDepth && i <= lastIndexInPage; i++ {
×
429
                if tagsPage[lastIndexInPage-i] == "latest" {
×
430
                        continue
×
431
                }
432
                tagsCandidates = append(tagsCandidates, tagsPage[lastIndexInPage-i])
×
433
        }
434

435
        for i := 0; i < len(prevCandidates) && len(tagsPage) < tagsDepth; i++ {
×
436
                if prevCandidates[i] == "latest" {
×
437
                        continue
×
438
                }
439
                tagsCandidates = append(tagsCandidates, prevCandidates[i])
×
440
        }
441

442
        if latestTagFound {
×
443
                tagsCandidates = append([]string{"latest"}, tagsCandidates...)
×
444
        }
×
445
        if len(tagsCandidates) > tagsDepth {
×
446
                tagsCandidates = tagsCandidates[:tagsDepth]
×
447
        }
×
448

449
        return tagsCandidates
×
450
}
451

452
func (registryScan *registryScan) isExceedScanLimit(limit int) bool {
×
453
        return registryScan.getNumOfImagesToScan() > limit
×
454
}
×
455

456
func (registryScan *registryScan) getNumOfImagesToScan() int {
×
457
        numOfImgs := 0
×
458
        for _, v := range registryScan.mapImageToTags {
×
459
                numOfImgs += len(v)
×
460
        }
×
461
        return numOfImgs
×
462
}
463

464
func (registryScan *registryScan) filterScanLimit(limit int) {
×
465
        filteredImages := make(map[string][]string)
×
466
        counter := 0
×
467
        for k := range registryScan.mapImageToTags {
×
468
                for _, img := range registryScan.mapImageToTags[k] {
×
469
                        if counter >= limit {
×
470
                                break
×
471
                        }
472
                        filteredImages[k] = append(filteredImages[k], img)
×
473
                        counter++
×
474
                }
475
        }
476
        registryScan.mapImageToTags = filteredImages
×
477
}
478

479
func (registryScan *registryScan) enumerateRepos(ctx context.Context) ([]string, error) {
×
480
        // token sometimes includes a lot of dots, so we need to remove them
×
481
        i := strings.Index(registryScan.registryInfo.AuthMethod.Password, ".....")
×
482
        if i != -1 {
×
483
                registryScan.registryInfo.AuthMethod.Password = registryScan.registryInfo.AuthMethod.Password[:i]
×
484
        }
×
485

486
        repos, err := registryScan.listReposInRegistry(ctx)
×
487
        if err != nil {
×
488
                return []string{}, err
×
489
        }
×
490
        return repos, nil
×
491

492
}
493

494
func (registryScan *registryScan) listReposInRegistry(ctx context.Context) ([]string, error) {
×
495
        iRegistry, err := registryScan.makeRegistryInterface()
×
496
        if err != nil {
×
497
                return nil, err
×
498
        }
×
499
        regCreds := registryScan.registryCredentials()
×
500
        var repos, pageRepos []string
×
501
        var nextPage *regCommon.PaginationOption
×
502

×
503
        firstPage := regCommon.MakePagination(iRegistry.GetMaxPageSize())
×
504
        catalogOpts := regCommon.CatalogOption{IsPublic: !registryScan.isPrivate(), Namespaces: registryScan.registry.projectID}
×
505
        for pageRepos, nextPage, err = iRegistry.Catalog(ctx, firstPage, catalogOpts, regCreds); ; pageRepos, nextPage, err = iRegistry.Catalog(ctx, *nextPage, catalogOpts, regCreds) {
×
506
                if err != nil {
×
507
                        return nil, err
×
508
                }
×
509
                if len(pageRepos) == 0 {
×
510
                        break
×
511
                }
512
                repos = append(repos, registryScan.filterRepositories(ctx, pageRepos)...)
×
513
                total2Include := len(registryScan.registryInfo.Include)
×
514
                if total2Include != 0 && total2Include == len(repos) {
×
515
                        break
×
516
                }
517
                if len(repos) >= imagesToScanLimit {
×
518
                        break
×
519
                }
520
                if nextPage == nil || nextPage.Cursor == "" {
×
521
                        break
×
522
                }
523
                logger.L().Info(fmt.Sprintf("Found %d repositories in registry %s, nextPage is %v\n", len(repos), registryScan.registry.hostname, nextPage))
×
524

525
        }
526
        return repos, nil
×
527
}
528

529
func (registryScan *registryScan) getCommandForConfigMap() (string, error) {
×
530
        scanRegistryCommand := apis.Command{}
×
531
        scanRegistryCommand.CommandName = apis.TypeScanRegistry
×
532

×
533
        scanRegistryCommand.Args = map[string]interface{}{}
×
534

×
535
        // credentials will not be in configmap
×
536
        registryScan.registryInfo.AuthMethod = armotypes.AuthMethod{
×
537
                Type: registryScan.registryInfo.AuthMethod.Type,
×
538
        }
×
539
        scanRegistryCommand.Args[armotypes.RegistryInfoArgKey] = registryScan.registryInfo
×
540

×
541
        scanRegistryCommands := apis.Commands{}
×
542
        scanRegistryCommands.Commands = append(scanRegistryCommands.Commands, scanRegistryCommand)
×
543

×
544
        scanV1Bytes, err := json.Marshal(scanRegistryCommands)
×
545
        if err != nil {
×
546
                return "", err
×
547
        }
×
548

549
        return string(scanV1Bytes), nil
×
550
}
551

552
func (registryScan *registryScan) setCronJobTemplate(jobTemplateObj *v1.CronJob, name, schedule, jobID, registryName string) error {
×
553
        jobTemplateObj.Name = name
×
554
        if schedule == "" {
×
555
                return fmt.Errorf("schedule cannot be empty")
×
556
        }
×
557
        jobTemplateObj.Spec.Schedule = schedule
×
558

×
559
        // update volume name
×
560
        for i, v := range jobTemplateObj.Spec.JobTemplate.Spec.Template.Spec.Volumes {
×
561
                if v.Name == requestVolumeName {
×
562
                        if jobTemplateObj.Spec.JobTemplate.Spec.Template.Spec.Volumes[i].ConfigMap != nil {
×
563
                                jobTemplateObj.Spec.JobTemplate.Spec.Template.Spec.Volumes[i].ConfigMap.Name = name
×
564
                        }
×
565
                }
566
        }
567

568
        // add annotations
569
        if jobTemplateObj.Spec.JobTemplate.Spec.Template.Annotations == nil {
×
570
                jobTemplateObj.Spec.JobTemplate.Spec.Template.Annotations = make(map[string]string)
×
571
        }
×
572

573
        jobTemplateObj.Spec.JobTemplate.Spec.Template.Annotations[armotypes.CronJobTemplateAnnotationRegistryNameKey] = registryName
×
574

×
575
        // add annotations
×
576
        if jobTemplateObj.ObjectMeta.Labels == nil {
×
577
                jobTemplateObj.ObjectMeta.Labels = make(map[string]string)
×
578
        }
×
579
        jobTemplateObj.ObjectMeta.Labels["app"] = name
×
580

×
581
        return nil
×
582
}
583

584
func (registryScan *registryScan) parseRegistryFromCommand(sessionObj *utils.SessionObj) error {
×
585
        registryInfo, ok := sessionObj.Command.Args[armotypes.RegistryInfoArgKey].(map[string]interface{})
×
586
        if !ok {
×
587
                return fmt.Errorf("could not parse registry info")
×
588
        }
×
589

590
        if err := mapstructure.Decode(registryInfo, &registryScan.registryInfo); err != nil {
×
591
                return fmt.Errorf("could not decode registry info into registryInfo struct, reason: %w", err)
×
592
        }
×
593
        logRegistryInfoArgs(registryInfo)
×
594
        return nil
×
595
}
596

597
func logRegistryInfoArgs(registryInfo map[string]interface{}) {
×
598
        var logMsg string
×
599
        for k, v := range registryInfo {
×
600
                if k != "authMethod" {
×
601
                        logMsg += fmt.Sprintf("%v: %v ", k, v)
×
602
                }
×
603
        }
604
        logger.L().Info(fmt.Sprintf("registryInfo args: %v", logMsg))
×
605
}
606

607
func (registryScan *registryScan) setRegistryKind() {
×
608
        registryScan.registryInfo.RegistryProvider = registryScan.getRegistryProvider()
×
609
        registryScan.registryInfo.Kind = registryScan.registryInfo.RegistryProvider
×
610
}
×
611

612
func (registryScan *registryScan) getRegistryProvider() string {
×
613
        if strings.Contains(registryScan.registryInfo.RegistryName, ".dkr.ecr") {
×
614
                return "ecr"
×
615
        }
×
616
        if strings.Contains(registryScan.registryInfo.RegistryName, "gcr.io") {
×
617
                return "gcr"
×
618
        }
×
619
        if strings.Contains(registryScan.registryInfo.RegistryName, "quay.io") {
×
620
                return "quay.io"
×
621
        }
×
622
        return registryScan.registryInfo.RegistryProvider
×
623
}
624

625
// parse registry information from secret, configmap and command, giving priority to command
626
func (registryScan *registryScan) parseRegistry(ctx context.Context, sessionObj *utils.SessionObj) error {
×
627
        if err := registryScan.setRegistryAuthFromSecret(ctx); err != nil {
×
628
                logger.L().Info("parseRegistry: could not parse auth from secret, parsing from command", helpers.Error(err))
×
629
        } else {
×
630
                sessionObj.Reporter.SendDetails("secret loaded", registryScan.sendReport)
×
631
        }
×
632

633
        configMapMode, err := registryScan.getRegistryConfig(&registryScan.registryInfo)
×
634
        if err != nil {
×
635
                logger.L().Info("parseRegistry: could not get registry config", helpers.Error(err))
×
636
        }
×
637
        logger.L().Info(fmt.Sprintf("scanRegistries:registry(%s) %s configmap  successful", registryScan.registryInfo.RegistryName, configMapMode)) // systest dependent
×
638

×
639
        if e := registryScan.parseRegistryFromCommand(sessionObj); e != nil {
×
640
                return fmt.Errorf("get registry auth failed with err %w", e)
×
641
        }
×
642

643
        registryScan.setRegistryKind()
×
644
        if err != nil {
×
645
                return fmt.Errorf("parseRegistry: err %w", err)
×
646
        }
×
647

648
        registryScan.setHostnameAndProject()
×
649
        return nil
×
650
}
651

652
func (registryScan *registryScan) createTriggerRequestCronJob(k8sAPI *k8sinterface.KubernetesApi, name, registryName string, command apis.Command) error {
×
653

×
654
        // cronjob template is stored as configmap in cluster
×
655
        jobTemplateObj, err := getCronJobTemplate(k8sAPI, registryCronjobTemplate, registryScan.config.Namespace())
×
656
        if err != nil {
×
657
                return err
×
658
        }
×
659

660
        err = registryScan.setCronJobTemplate(jobTemplateObj, name, getCronTabSchedule(command), command.JobTracking.JobID, registryName)
×
661
        if err != nil {
×
662
                return err
×
663
        }
×
664

665
        // create cronJob
666
        if _, err := k8sAPI.KubernetesClient.BatchV1().CronJobs(registryScan.config.Namespace()).Create(context.Background(), jobTemplateObj, metav1.CreateOptions{}); err != nil {
×
667
                return err
×
668
        }
×
669
        return nil
×
670
}
671

672
func (registryScan *registryScan) setRegistryAuthFromSecret(ctx context.Context) error {
×
673
        var secretName string
×
674

×
675
        if registryScan.registryInfo.SecretName != "" {
×
676
                // in newer versions the secret is sent as part of the command
×
677
                secretName = registryScan.registryInfo.SecretName
×
678
        } else {
×
679
                // in older versions the secret is stored in the cluster (backward compatibility)
×
680
                secretName = armotypes.RegistryScanSecretName
×
681
        }
×
682

683
        // find secret in cluster
684
        secrets, err := getRegistryScanSecrets(registryScan.k8sAPI, registryScan.config.Namespace(), secretName)
×
685
        if err != nil || len(secrets) == 0 {
×
686
                return err
×
687
        }
×
688

689
        secret := secrets[0]
×
690

×
691
        registriesAuth, err := parseRegistryAuthSecret(secret)
×
692
        if err != nil {
×
693
                return err
×
694
        }
×
695

696
        //try to find an auth with the same registry name from the request
697
        for _, auth := range registriesAuth {
×
698
                if auth.Registry == registryScan.registryInfo.RegistryName {
×
699
                        if err := auth.initDefaultValues(ctx); err != nil {
×
700
                                return err
×
701
                        }
×
702
                        registryScan.setRegistryInfoFromAuth(auth, &registryScan.registryInfo)
×
703
                        return nil
×
704
                }
705
        }
706
        //couldn't find auth with the full, check if there is an auth for the registry without the project name
707
        regAndProject := strings.Split(registryScan.registryInfo.RegistryName, "/")
×
708
        if len(regAndProject) > 1 {
×
709
                for _, auth := range registriesAuth {
×
710
                        if auth.Registry == regAndProject[0] {
×
711
                                if err := auth.initDefaultValues(ctx); err != nil {
×
712
                                        return err
×
713
                                }
×
714
                                registryScan.setRegistryInfoFromAuth(auth, &registryScan.registryInfo)
×
715
                                return nil
×
716
                        }
717
                }
718

719
        }
720

721
        //no auth found for registry return a default one
722
        auth := makeRegistryAuth(registryScan.registryInfo.RegistryName)
×
723
        registryScan.setRegistryInfoFromAuth(auth, &registryScan.registryInfo)
×
724
        return nil
×
725
}
726

727
func parseRegistryAuthSecret(secret k8sinterface.IWorkload) ([]registryAuth, error) {
×
728
        secretData := secret.GetData()
×
729
        var registriesAuth []registryAuth
×
730
        registriesAuthStr, ok := secretData[registriesAuthFieldInSecret].(string)
×
731
        if !ok {
×
732
                return nil, fmt.Errorf("error parsing Secret: %s field must be a string", registriesAuthFieldInSecret)
×
733
        }
×
734
        data, err := base64.StdEncoding.DecodeString(registriesAuthStr)
×
735
        if err != nil {
×
736
                return nil, fmt.Errorf("error parsing Secret: %s", err.Error())
×
737
        }
×
738
        registriesAuthStr = strings.Replace(string(data), "\n", "", -1)
×
739

×
740
        if e := json.Unmarshal([]byte(registriesAuthStr), &registriesAuth); e != nil {
×
741
                return nil, fmt.Errorf("error parsing Secret: %s", e.Error())
×
742
        }
×
743

744
        return registriesAuth, nil
×
745
}
746

747
func (registryScan *registryScan) setRegistryInfoFromAuth(auth registryAuth, registryInfo *armotypes.RegistryInfo) {
×
748
        registryInfo.AuthMethod.Type = auth.AuthMethod
×
749
        registryInfo.AuthMethod.Username = auth.Username
×
750
        registryInfo.AuthMethod.Password = auth.Password
×
751
        registryInfo.SkipTLSVerify = auth.SkipTLSVerify
×
752
        *registryInfo.IsHTTPS = !*auth.Insecure
×
753
        registryInfo.Kind = string(auth.Kind)
×
754
}
×
755

756
func (registryScan *registryScan) getRegistryConfig(registryInfo *armotypes.RegistryInfo) (string, error) {
×
757
        configMap, err := registryScan.k8sAPI.GetWorkload(registryScan.config.Namespace(), "ConfigMap", registryScanConfigmap)
×
758
        // in case of an error or missing configmap, fallback to the deprecated namespace
×
759
        if err != nil || configMap == nil {
×
760
                configMap, err = registryScan.k8sAPI.GetWorkload(armotypes.ArmoSystemNamespace, "ConfigMap", registryScanConfigmap)
×
761
        }
×
762

763
        if err != nil {
×
764
                // if configmap not found, it means we will use all images and default depth
×
765
                if strings.Contains(err.Error(), fmt.Sprintf("reason: configmaps \"%v\" not found", registryScanConfigmap)) {
×
766
                        logger.L().Info(fmt.Sprintf("configmap: %s does not exists, using default values", registryScanConfigmap))
×
767
                        return string(cmDefaultMode), nil
×
768
                } else {
×
769
                        return string(cmDefaultMode), err
×
770
                }
×
771
        }
772
        configData := configMap.GetData()
×
773
        var registriesConfigs []registryScanConfig
×
774
        registriesConfigStr, ok := configData["registries"].(string)
×
775
        if !ok {
×
776
                return string(cmDefaultMode), fmt.Errorf("error parsing %v confgimap: registries field not found", registryScanConfigmap)
×
777
        }
×
778
        registriesConfigStr = strings.Replace(registriesConfigStr, "\n", "", -1)
×
779
        err = json.Unmarshal([]byte(registriesConfigStr), &registriesConfigs)
×
780
        if err != nil {
×
781
                return string(cmDefaultMode), fmt.Errorf("error parsing ConfigMap: %s", err.Error())
×
782
        }
×
783
        for _, config := range registriesConfigs {
×
784
                if config.Registry == registryInfo.RegistryName {
×
785
                        registryScan.setRegistryInfoFromConfigMap(registryInfo, config)
×
786
                        return string(cmLoadedMode), nil
×
787
                }
×
788
        }
789
        registryConfig := NewRegistryScanConfig(registryInfo.RegistryName)
×
790
        registryScan.setRegistryInfoFromConfigMap(registryInfo, *registryConfig)
×
791
        return string(cmDefaultMode), nil
×
792

793
}
794

795
func (registryScan *registryScan) setRegistryInfoFromConfigMap(registryInfo *armotypes.RegistryInfo, registryConfig registryScanConfig) {
×
796
        // default is one
×
797
        if registryConfig.Depth != 0 {
×
798
                *registryInfo.Depth = registryConfig.Depth
×
799
        }
×
800
        registryInfo.Include = registryConfig.Include
×
801
        registryInfo.Exclude = registryConfig.Exclude
×
802
}
803

804
func getRegistryScanSecrets(k8sAPI *k8sinterface.KubernetesApi, namespace, secretName string) ([]k8sinterface.IWorkload, error) {
×
805
        if secretName != "" {
×
806
                secret, err := k8sAPI.GetWorkload(namespace, "Secret", secretName)
×
807
                if err == nil && secret != nil {
×
808
                        return []k8sinterface.IWorkload{secret}, err
×
809
                }
×
810
        }
811

812
        // when secret name is not provided, we will try to find all secrets starting with kubescape-registry-scan
813
        registryScanSecrets := []k8sinterface.IWorkload{}
×
814
        all, err := k8sAPI.ListWorkloads2(namespace, "Secret")
×
815
        if err == nil {
×
816
                for _, secret := range all {
×
817
                        if strings.HasPrefix(secret.GetName(), armotypes.RegistryScanSecretName) {
×
818
                                registryScanSecrets = append(registryScanSecrets, secret)
×
819
                        }
×
820
                }
821
        }
822

823
        return registryScanSecrets, err
×
824
}
825

826
func (registryScan *registryScan) setHostnameAndProject() {
×
827
        regAndProject := strings.Split(registryScan.registryInfo.RegistryName, "/")
×
828
        hostname := regAndProject[0]
×
829
        project := ""
×
830
        if len(regAndProject) > 1 {
×
831
                project = regAndProject[1]
×
832
        }
×
833
        registryScan.registry = registry{hostname: hostname, projectID: project}
×
834
}
835

836
func (registryScan *registryScan) SendRepositoriesAndTags(params RepositoriesAndTagsParams) error {
×
837
        reqBody, err := json.Marshal(params.Repositories)
×
838
        if err != nil {
×
839
                return fmt.Errorf("in 'sendReport' failed to json.Marshal, reason: %v", err)
×
840
        }
×
841

842
        url, err := beClientV1.GetRegistryRepositoriesUrl(registryScan.config.EventReceiverURL(), params.CustomerGUID, params.RegistryName, params.JobID)
×
843
        if err != nil {
×
844
                return err
×
845
        }
×
846

847
        bodyReader := bytes.NewReader(reqBody)
×
848
        req, err := http.NewRequest(http.MethodPost, url.String(), bodyReader)
×
849
        if err != nil {
×
850
                return fmt.Errorf("in 'SendRepositoriesAndTags' failed to create request, reason: %v", err)
×
851
        }
×
852
        for k, v := range utils.GetRequestHeaders(registryScan.config.AccessKey()) {
×
853
                req.Header.Set(k, v)
×
854
        }
×
855

856
        _, err = http.DefaultClient.Do(req)
×
857

×
858
        if err != nil {
×
859
                return fmt.Errorf("in 'SendRepositoriesAndTags' failed to send request, reason: %v", err)
×
860
        }
×
861
        return nil
×
862
}
863

864
func (registryScan *registryScan) validateRegistryScanInformation() error {
×
865
        if registryScan.registryInfo.RegistryName == "" {
×
866
                return fmt.Errorf("registry name is missing")
×
867
        }
×
868

869
        if registryScan.isPrivate() {
×
870
                if registryScan.registryInfo.AuthMethod.Password == "" || registryScan.registryInfo.AuthMethod.Username == "" {
×
871
                        return fmt.Errorf("username or password is missing")
×
872
                }
×
873
        }
874

875
        if len(registryScan.registryInfo.Exclude) > 0 && len(registryScan.registryInfo.Include) > 0 {
×
876
                return fmt.Errorf("cannot have both exclude and include lists")
×
877
        }
×
878

879
        return nil
×
880
}
881

882
func (registryScan *registryScan) setSecretName(secretName string) {
×
883
        registryScan.registryInfo.SecretName = secretName
×
884
}
×
885

886
func (registryScan *registryScan) setRegistryName(registryName string) {
×
887
        registryScan.registryInfo.RegistryName = registryName
×
888
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc