fix #151
This commit is contained in:
@ -29,18 +29,20 @@ class JiraAPI(object):
|
|||||||
self.JIRA_RESOLUTION_FIXED = "Fixed"
|
self.JIRA_RESOLUTION_FIXED = "Fixed"
|
||||||
self.clean_obsolete = clean_obsolete
|
self.clean_obsolete = clean_obsolete
|
||||||
self.template_path = 'vulnwhisp/reporting/resources/ticket.tpl'
|
self.template_path = 'vulnwhisp/reporting/resources/ticket.tpl'
|
||||||
|
self.max_ips_ticket = 30
|
||||||
|
self.attachment_filename = "vulnerable_assets.txt"
|
||||||
if path:
|
if path:
|
||||||
self.download_tickets(path)
|
self.download_tickets(path)
|
||||||
else:
|
else:
|
||||||
self.logger.warn("No local path specified, skipping Jira ticket download.")
|
self.logger.warn("No local path specified, skipping Jira ticket download.")
|
||||||
|
|
||||||
def create_ticket(self, title, desc, project="IS", components=[], tags=[]):
|
def create_ticket(self, title, desc, project="IS", components=[], tags=[], attachment_contents = []):
|
||||||
labels = ['vulnerability_management']
|
labels = ['vulnerability_management']
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
labels.append(str(tag))
|
labels.append(str(tag))
|
||||||
|
|
||||||
self.logger.info("creating ticket for project {} title: {}".format(project, title[:20]))
|
self.logger.info("creating ticket for project {} title: {}".format(project, title[:20]))
|
||||||
self.logger.info("project {} has a component requirement: {}".format(project, components))
|
self.logger.debug("project {} has a component requirement: {}".format(project, components))
|
||||||
project_obj = self.jira.project(project)
|
project_obj = self.jira.project(project)
|
||||||
components_ticket = []
|
components_ticket = []
|
||||||
for component in components:
|
for component in components:
|
||||||
@ -62,6 +64,10 @@ class JiraAPI(object):
|
|||||||
components=components_ticket)
|
components=components_ticket)
|
||||||
|
|
||||||
self.logger.info("Ticket {} created successfully".format(new_issue))
|
self.logger.info("Ticket {} created successfully".format(new_issue))
|
||||||
|
|
||||||
|
if attachment_contents:
|
||||||
|
self.add_content_as_attachment(new_issue, attachment_contents)
|
||||||
|
|
||||||
return new_issue
|
return new_issue
|
||||||
|
|
||||||
#Basic JIRA Metrics
|
#Basic JIRA Metrics
|
||||||
@ -108,13 +114,18 @@ class JiraAPI(object):
|
|||||||
self.ticket_update_assets(vuln, ticketid, ticket_assets)
|
self.ticket_update_assets(vuln, ticketid, ticket_assets)
|
||||||
self.add_label(ticketid, vuln['risk'])
|
self.add_label(ticketid, vuln['risk'])
|
||||||
continue
|
continue
|
||||||
|
attachment_contents = []
|
||||||
|
# if assets >30, add as attachment
|
||||||
|
# create local text file with assets, attach it to ticket
|
||||||
|
if len(vuln['ips']) > self.max_ips_ticket:
|
||||||
|
attachment_contents = vuln['ips']
|
||||||
|
vuln['ips'] = ["Affected hosts ({assets}) exceed Jira's allowed character limit, added as an attachment.".format(assets = len(attachment_contents))]
|
||||||
try:
|
try:
|
||||||
tpl = template(self.template_path, vuln)
|
tpl = template(self.template_path, vuln)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error('Exception templating: {}'.format(str(e)))
|
self.logger.error('Exception templating: {}'.format(str(e)))
|
||||||
return 0
|
return 0
|
||||||
self.create_ticket(title=vuln['title'], desc=tpl, project=project, components=components, tags=[vuln['source'], vuln['scan_name'], 'vulnerability', vuln['risk']])
|
self.create_ticket(title=vuln['title'], desc=tpl, project=project, components=components, tags=[vuln['source'], vuln['scan_name'], 'vulnerability', vuln['risk']], attachment_contents = attachment_contents)
|
||||||
|
|
||||||
self.close_fixed_tickets(vulnerabilities)
|
self.close_fixed_tickets(vulnerabilities)
|
||||||
# we reinitialize so the next sync redoes the query with their specific variables
|
# we reinitialize so the next sync redoes the query with their specific variables
|
||||||
@ -159,12 +170,72 @@ class JiraAPI(object):
|
|||||||
try:
|
try:
|
||||||
affected_assets_section = ticket.raw.get('fields', {}).get('description').encode("ascii").split("{panel:title=Affected Assets}")[1].split("{panel}")[0]
|
affected_assets_section = ticket.raw.get('fields', {}).get('description').encode("ascii").split("{panel:title=Affected Assets}")[1].split("{panel}")[0]
|
||||||
assets = list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", affected_assets_section)))
|
assets = list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", affected_assets_section)))
|
||||||
except:
|
|
||||||
self.logger.error("Ticket IPs regex failed. Ticket ID: {}".format(ticketid))
|
if not assets:
|
||||||
|
#check if attachment, if so, get assets from attachment
|
||||||
|
affected_assets_section = self.check_ips_attachment(ticket)
|
||||||
|
if affected_assets_section:
|
||||||
|
assets = list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", affected_assets_section)))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error("Ticket IPs regex failed. Ticket ID: {}. Reason: {}".format(ticketid, e))
|
||||||
assets = []
|
assets = []
|
||||||
|
|
||||||
return ticketid, title, assets
|
return ticketid, title, assets
|
||||||
|
|
||||||
|
def check_ips_attachment(self, ticket):
|
||||||
|
affected_assets_section = []
|
||||||
|
try:
|
||||||
|
fields = self.jira.issue(ticket.key).raw.get('fields')
|
||||||
|
attachments = fields.get('attachment')
|
||||||
|
affected_assets_section = ""
|
||||||
|
#we will make sure we get the latest version of the file
|
||||||
|
latest = ''
|
||||||
|
attachment_id = ''
|
||||||
|
if attachments:
|
||||||
|
for item in attachments:
|
||||||
|
if item.get('filename') == self.attachment_filename:
|
||||||
|
if not latest:
|
||||||
|
latest = item.get('created')
|
||||||
|
attachment_id = item.get('id')
|
||||||
|
else:
|
||||||
|
if latest < item.get('created'):
|
||||||
|
latest = item.get('created')
|
||||||
|
attachment_id = item.get('id')
|
||||||
|
affected_assets_section = self.jira.attachment(attachment_id).get()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error("Failed to get assets from ticket attachment. Ticket ID: {}. Reason: {}".format(ticket, e))
|
||||||
|
|
||||||
|
return affected_assets_section
|
||||||
|
|
||||||
|
def clean_old_attachments(self, ticket):
|
||||||
|
fields = ticket.raw.get('fields')
|
||||||
|
attachments = fields.get('attachment')
|
||||||
|
if attachments:
|
||||||
|
for item in attachments:
|
||||||
|
if item.get('filename') == self.attachment_filename:
|
||||||
|
self.jira.delete_attachment(item.get('id'))
|
||||||
|
|
||||||
|
def add_content_as_attachment(self, issue, contents):
|
||||||
|
try:
|
||||||
|
#Create the file locally with the data
|
||||||
|
attachment_file = open(self.attachment_filename, "w")
|
||||||
|
attachment_file.write("\n".join(contents))
|
||||||
|
attachment_file.close()
|
||||||
|
#Push the created file to the ticket
|
||||||
|
attachment_file = open(self.attachment_filename, "rb")
|
||||||
|
self.jira.add_attachment(issue, attachment_file, self.attachment_filename)
|
||||||
|
attachment_file.close()
|
||||||
|
#remove the temp file
|
||||||
|
os.remove(self.attachment_filename)
|
||||||
|
self.logger.info("Added attachment successfully.")
|
||||||
|
except:
|
||||||
|
self.logger.error("Error while attaching file to ticket.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def get_ticket_reported_assets(self, ticket):
|
def get_ticket_reported_assets(self, ticket):
|
||||||
#[METRICS] return a list with all the affected assets for that vulnerability (including already resolved ones)
|
#[METRICS] return a list with all the affected assets for that vulnerability (including already resolved ones)
|
||||||
return list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b",str(self.jira.issue(ticket).raw))))
|
return list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b",str(self.jira.issue(ticket).raw))))
|
||||||
@ -193,12 +264,8 @@ class JiraAPI(object):
|
|||||||
|
|
||||||
if self.is_ticket_resolved(self.jira.issue(ticketid)):
|
if self.is_ticket_resolved(self.jira.issue(ticketid)):
|
||||||
self.reopen_ticket(ticketid)
|
self.reopen_ticket(ticketid)
|
||||||
try:
|
|
||||||
tpl = template(self.template_path, vuln)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error('Exception updating assets: {}'.format(str(e)))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
#First will do the comparison of assets
|
||||||
ticket_obj = self.jira.issue(ticketid)
|
ticket_obj = self.jira.issue(ticketid)
|
||||||
ticket_obj.update()
|
ticket_obj.update()
|
||||||
assets = list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", ",".join(vuln['ips']))))
|
assets = list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", ",".join(vuln['ips']))))
|
||||||
@ -211,21 +278,40 @@ class JiraAPI(object):
|
|||||||
for asset in difference:
|
for asset in difference:
|
||||||
if asset in assets:
|
if asset in assets:
|
||||||
if not added:
|
if not added:
|
||||||
added = 'The following assets *have been newly detected*:\n'
|
added = '\nThe following assets *have been newly detected*:\n'
|
||||||
added += '* {}\n'.format(asset)
|
added += '* {}\n'.format(asset)
|
||||||
elif asset in ticket_assets:
|
elif asset in ticket_assets:
|
||||||
if not removed:
|
if not removed:
|
||||||
removed= 'The following assets *have been resolved*:\n'
|
removed= '\nThe following assets *have been resolved*:\n'
|
||||||
removed += '* {}\n'.format(asset)
|
removed += '* {}\n'.format(asset)
|
||||||
|
|
||||||
comment = added + removed
|
comment = added + removed
|
||||||
|
|
||||||
|
#then will check if assets are too many that need to be added as an attachment
|
||||||
|
attachment_contents = []
|
||||||
|
if len(vuln['ips']) > self.max_ips_ticket:
|
||||||
|
attachment_contents = vuln['ips']
|
||||||
|
vuln['ips'] = ["Affected hosts ({assets}) exceed Jira's allowed character limit, added as an attachment.".format(assets = len(attachment_contents))]
|
||||||
|
|
||||||
|
#fill the ticket description template
|
||||||
try:
|
try:
|
||||||
|
tpl = template(self.template_path, vuln)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error('Exception updating assets: {}'.format(str(e)))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
#proceed checking if it requires adding as an attachment
|
||||||
|
try:
|
||||||
|
#update attachment with hosts and delete the old versions
|
||||||
|
if attachment_contents:
|
||||||
|
self.clean_old_attachments(ticket_obj)
|
||||||
|
self.add_content_as_attachment(ticket_obj, attachment_contents)
|
||||||
|
|
||||||
ticket_obj.update(description=tpl, comment=comment, fields={"labels":ticket_obj.fields.labels})
|
ticket_obj.update(description=tpl, comment=comment, fields={"labels":ticket_obj.fields.labels})
|
||||||
self.logger.info("Ticket {} updated successfully".format(ticketid))
|
self.logger.info("Ticket {} updated successfully".format(ticketid))
|
||||||
self.add_label(ticketid, 'updated')
|
self.add_label(ticketid, 'updated')
|
||||||
except:
|
except Exception as e:
|
||||||
self.logger.error("Error while trying up update ticket {}".format(ticketid))
|
self.logger.error("Error while trying up update ticket {ticketid}.\nReason: {e}".format(ticketid = ticketid, e=e))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def add_label(self, ticketid, label):
|
def add_label(self, ticketid, label):
|
||||||
|
Reference in New Issue
Block a user