-
Notifications
You must be signed in to change notification settings - Fork 0
/
job.go
179 lines (163 loc) · 5.53 KB
/
job.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
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package scan
import (
"crypto/sha256"
"encoding/json"
"fmt"
"os"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/schema2"
"github.com/vmware/harbor/src/common"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/job"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/clair"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/jobservice/env"
"github.com/vmware/harbor/src/jobservice/job/impl/utils"
)
// ClairJob is the struct to scan Harbor's Image with Clair
type ClairJob struct {
registryURL string
secret string
tokenEndpoint string
clairEndpoint string
}
// MaxFails implements the interface in job/Interface
func (cj *ClairJob) MaxFails() uint {
return 1
}
// ShouldRetry implements the interface in job/Interface
func (cj *ClairJob) ShouldRetry() bool {
return false
}
// Validate implements the interface in job/Interface
func (cj *ClairJob) Validate(params map[string]interface{}) error {
return nil
}
// Run implements the interface in job/Interface
func (cj *ClairJob) Run(ctx env.JobContext, params map[string]interface{}) error {
logger := ctx.GetLogger()
if err := cj.init(ctx); err != nil {
logger.Errorf("Failed to initialize the job, error: %v", err)
return err
}
jobParms, err := transformParam(params)
if err != nil {
logger.Errorf("Failed to prepare parms for scan job, error: %v", err)
return err
}
repoClient, err := utils.NewRepositoryClientForJobservice(jobParms.Repository, cj.registryURL, cj.secret, cj.tokenEndpoint)
if err != nil {
logger.Errorf("Failed create repository client for repo: %s, error: %v", jobParms.Repository, err)
return err
}
_, _, payload, err := repoClient.PullManifest(jobParms.Tag, []string{schema2.MediaTypeManifest})
if err != nil {
logger.Errorf("Error pulling manifest for image %s:%s :%v", jobParms.Repository, jobParms.Tag, err)
return err
}
token, err := utils.GetTokenForRepo(jobParms.Repository, cj.secret, cj.tokenEndpoint)
if err != nil {
logger.Errorf("Failed to get token, error: %v", err)
return err
}
layers, err := prepareLayers(payload, cj.registryURL, jobParms.Repository, token)
if err != nil {
logger.Errorf("Failed to prepare layers, error: %v", err)
return err
}
loggerImpl, ok := logger.(*log.Logger)
if !ok {
loggerImpl = log.DefaultLogger()
}
clairClient := clair.NewClient(cj.clairEndpoint, loggerImpl)
for _, l := range layers {
logger.Infof("Scanning Layer: %s, path: %s", l.Name, l.Path)
if err := clairClient.ScanLayer(l); err != nil {
logger.Errorf("Failed to scan layer: %s, error: %v", l.Name, err)
return err
}
}
layerName := layers[len(layers)-1].Name
res, err := clairClient.GetResult(layerName)
if err != nil {
logger.Errorf("Failed to get result from Clair, error: %v", err)
return err
}
compOverview, sev := clair.TransformVuln(res)
err = dao.UpdateImgScanOverview(jobParms.Digest, layerName, sev, compOverview)
return err
}
func (cj *ClairJob) init(ctx env.JobContext) error {
errTpl := "Failed to get required property: %s"
if v, ok := ctx.Get(common.RegistryURL); ok && len(v.(string)) > 0 {
cj.registryURL = v.(string)
} else {
return fmt.Errorf(errTpl, common.RegistryURL)
}
if v := os.Getenv("JOBSERVICE_SECRET"); len(v) > 0 {
cj.secret = v
} else {
return fmt.Errorf(errTpl, "JOBSERVICE_SECRET")
}
if v, ok := ctx.Get(common.TokenServiceURL); ok && len(v.(string)) > 0 {
cj.tokenEndpoint = v.(string)
} else {
return fmt.Errorf(errTpl, common.TokenServiceURL)
}
if v, ok := ctx.Get(common.ClairURL); ok && len(v.(string)) > 0 {
cj.clairEndpoint = v.(string)
} else {
return fmt.Errorf(errTpl, common.ClairURL)
}
return nil
}
func transformParam(params map[string]interface{}) (*job.ScanJobParms, error) {
res := job.ScanJobParms{}
parmsBytes, err := json.Marshal(params)
if err != nil {
return nil, err
}
err = json.Unmarshal(parmsBytes, &res)
return &res, err
}
func prepareLayers(payload []byte, registryURL, repo, tk string) ([]models.ClairLayer, error) {
layers := []models.ClairLayer{}
manifest, _, err := distribution.UnmarshalManifest(schema2.MediaTypeManifest, payload)
if err != nil {
return layers, err
}
tokenHeader := map[string]string{"Connection": "close", "Authorization": fmt.Sprintf("Bearer %s", tk)}
// form the chain by using the digests of all parent layers in the image, such that if another image is built on top of this image the layer name can be re-used.
shaChain := ""
for _, d := range manifest.References() {
if d.MediaType == schema2.MediaTypeConfig {
continue
}
shaChain += string(d.Digest) + "-"
l := models.ClairLayer{
Name: fmt.Sprintf("%x", sha256.Sum256([]byte(shaChain))),
Headers: tokenHeader,
Format: "Docker",
Path: utils.BuildBlobURL(registryURL, repo, string(d.Digest)),
}
if len(layers) > 0 {
l.ParentName = layers[len(layers)-1].Name
}
layers = append(layers, l)
}
return layers, nil
}