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