blob: 5dff8c0dd2e3609392bc0d8503ab1b8da4aec948 [file] [log] [blame]
Ivan Udovichenko894fd8a2020-04-13 17:24:50 +03001#!groovy
2
3import groovy.json.JsonSlurper
4
5def callREST (String uri, String auth,
6 String method = 'GET', String message = null) {
7 String authEnc = auth.bytes.encodeBase64()
8 def req = new URL(uri).openConnection()
9 req.setRequestMethod(method)
10 req.setRequestProperty('Content-Type', 'application/json')
11 req.setRequestProperty('Authorization', "Basic ${authEnc}")
12 if (message) {
13 req.setDoOutput(true)
14 req.getOutputStream().write(message.getBytes('UTF-8'))
15 }
16 Integer responseCode = req.getResponseCode()
17 String responseText = ''
18 if (responseCode == 200 || responseCode == 201) {
19 responseText = req.getInputStream().getText()
20 }
21 req = null
22 return [ 'responseCode': responseCode, 'responseText': responseText ]
23}
24
25def getTeam (String image = '') {
26 def team_assignee = ''
27 switch(image) {
28 case ~/^(tungsten|tungsten-operator)\/.*$/:
29 team_assignee = 'OpenContrail'
30 break
31 case ~/^bm\/.*$/:
32 team_assignee = 'BM/OS (KaaS BM)'
33 break
34 case ~/^openstack\/.*$/:
35 team_assignee = 'OpenStack hardening'
36 break
37 case ~/^stacklight\/.*$/:
38 team_assignee = 'Stacklight LMA'
39 break
40 case ~/^ceph\/.*$/:
41 team_assignee = 'Storage'
42 break
43 case ~/^iam\/.*$/:
44 team_assignee = 'KaaS'
45 break
46 case ~/^lcm\/.*$/:
47 team_assignee = 'Kubernetes'
48 break
49 default:
50 team_assignee = 'Release Engineering'
51 break
52 }
53
54 return team_assignee
55}
56
57def updateDictionary (String jira_issue_key, Map dict, String uri, String auth, String jira_user_id) {
58 def response = callREST("${uri}/${jira_issue_key}", auth)
59 if ( response['responseCode'] == 200 ) {
60 def issueJSON = new JsonSlurper().parseText(response["responseText"])
61 if (issueJSON.containsKey('fields')) {
62 if (!dict.containsKey(jira_issue_key)) {
63 dict[jira_issue_key] = [
64 summary : '',
65 description: '',
66 comments: []
67 ]
68 }
69 if (issueJSON['fields'].containsKey('summary')){
70 dict[jira_issue_key].summary = issueJSON['fields']['summary']
71 }
72 if (issueJSON['fields'].containsKey('description')) {
73 dict[jira_issue_key].description = issueJSON['fields']['description']
74 }
75 if (issueJSON['fields'].containsKey('comment') && issueJSON['fields']['comment']['comments']) {
76 issueJSON['fields']['comment']['comments'].each {
77 if (it.containsKey('author') && it['author'].containsKey('accountId') && it['author']['accountId'] == jira_user_id) {
78 dict[jira_issue_key]['comments'].add(it['body'])
79 }
80 }
81 }
82 }
83 }
84 return dict
85}
86
87def cacheLookUp(Map dict, String image_short_name, String image_full_name = '', String cve_id = '' ) {
88 def found_key = ['','']
89 if (!found_key[0] && dict && image_short_name) {
90 dict.each { issue_key_name ->
91 if (!found_key[0]) {
92 def s = dict[issue_key_name.key]['summary'] =~ /\b${image_short_name}\b/
93 if (s) {
94 if (image_full_name) {
95 def d = dict[issue_key_name.key]['description'] =~ /(?m)\b${image_full_name}\b/
96
97 if (d) {
98 found_key = [issue_key_name.key,'']
99 } else {
100 if (dict[issue_key_name.key]['comments']) {
101 def comment_match = false
102 dict[issue_key_name.key]['comments'].each{ comment ->
103 if (!comment_match) {
104 def c = comment =~ /(?m)\b${image_full_name}\b/
105 if (c) {
106 comment_match = true
107 }
108 }
109 }
110 if (!comment_match) {
111 found_key = [issue_key_name.key,'na']
112 } else {
113 found_key = [issue_key_name.key,'']
114 }
115 } else {
116 found_key = [issue_key_name.key,'na']
117 }
118 }
119 }
120 }
121 }
122 }
123 }
124 return found_key
125}
126
127def reportJiraTickets(String pathToResultsJSON, String jiraCredentialsID, String jiraUserID) {
128
129 def dict = [:]
130
131 def cred = common.getCredentialsById(jiraCredentialsID)
132 def auth = "${cred.username}:${cred.password}"
133 def uri = "${cred.description}/rest/api/2/issue"
134
135 def search_api_url = "${cred.description}/rest/api/2/search"
136
137 def search_json = """
138{
139 "jql": "reporter = ${jiraUserID} and (labels = cve and labels = security) and (status = 'To Do' or status = 'For Triage' or status = Open or status = 'In Progress')"
140}
141"""
142
143 def response = callREST("${search_api_url}", auth, 'POST', search_json)
144
145 def InputJSON = new JsonSlurper().parseText(response["responseText"])
146
147 InputJSON['issues'].each {
148 dict[it['key']] = [
149 summary : '',
150 description: '',
151 comments: []
152 ]
153 }
154
155 InputJSON['issues'].each { jira_issue ->
156 dict = updateDictionary(jira_issue['key'], dict, uri, auth, jiraUserID)
157 sleep(1000)
158 }
159
160 def reportFile = new File(pathToResultsJSON)
161 def reportJSON = new JsonSlurper().parseText(reportFile.text)
162 def imageDict = [:]
163 def cves = []
164 reportJSON.each{
165 image ->
166 if ("${image.value}".contains('issues')) { return }
167 image.value.each{
168 pkg ->
169 cves = []
170 pkg.value.each{
171 cve ->
172 if (cve[2] && (cve[1].contains('High') || cve[1].contains('Critical'))) {
173 if (!imageDict.containsKey("${image.key}")) {
174 imageDict.put(image.key, [:])
175 }
176 if (!imageDict[image.key].containsKey(pkg.key)) {
177 imageDict[image.key].put(pkg.key, [])
178 }
179 cves.add("${cve[0]} (${cve[2]})")
180 }
181 }
182 if (cves) {
183 imageDict[image.key] = [
184 "${pkg.key}": cves
185 ]
186 }
187 }
188 }
189
190 def jira_summary = ''
191 def jira_description = ''
192 imageDict.each{
193 image ->
194 def image_key = image.key.replaceAll(/(^[a-z0-9-.]+.mirantis.(net|com)\/|:.*$)/, '')
195 // Temporary exclude tungsten images
196 if (image_key.startsWith('tungsten/') || image_key.startsWith('tungsten-operator/')) { return }
197 jira_summary = "[${image_key}] Found CVEs in Docker image"
198 jira_description = "{noformat}${image.key}\\n"
199 image.value.each{
200 pkg ->
201 jira_description += " * ${pkg.key}\\n"
202 pkg.value.each{
203 cve ->
204 jira_description += " - ${cve}\\n"
205 }
206 }
207 jira_description += "{noformat}"
208
209 def team_assignee = getTeam(image_key)
210
211 def post_issue_json = """
212{
213 "fields": {
214 "project": {
215 "key": "PRODX"
216 },
217 "summary": "${jira_summary}",
218 "description": "${jira_description}",
219 "issuetype": {
220 "name": "Bug"
221 },
222 "labels": [
223 "security",
224 "cve"
225 ],
226 "customfield_19000": {
227 "value": "${team_assignee}"
228 },
229 "versions": [
230 {
231 "name": "Backlog"
232 }
233 ]
234 }
235}
236"""
237 def post_comment_json = """
238{
239 "body": "${jira_description}"
240}
241"""
242 def jira_key = cacheLookUp(dict, image_key, image.key)
243 if (jira_key[0] && jira_key[1] == 'na') {
244 def post_comment_response = callREST("${uri}/${jira_key[0]}/comment", auth, 'POST', post_comment_json)
245 if ( post_comment_response['responseCode'] == 201 ) {
246 def issueCommentJSON = new JsonSlurper().parseText(post_comment_response["responseText"])
247 } else {
248 print "\nComment to ${jira_key[0]} Jira issue was not posted"
249 }
250 } else if (!jira_key[0]) {
251 def post_issue_response = callREST("${uri}/", auth, 'POST', post_issue_json)
252 if (post_issue_response['responseCode'] == 201) {
253 def issueJSON = new JsonSlurper().parseText(post_issue_response["responseText"])
254 dict = updateDictionary(issueJSON['key'], dict, uri, auth, jiraUserID)
255 } else {
256 print "\n${image.key} CVE issues were not published\n"
257 }
258 } else {
259 print "\n\nNothing to process for for ${image_key} and ${image.key}"
260 }
261 sleep(10000)
262 }
263}