From f08850daee47c71109353b18c395f1eb92038628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Wed, 3 Sep 2025 10:46:09 +0200 Subject: [PATCH] feat(jira): add ui link and tenant info to ticket (#8607) --- prowler/CHANGELOG.md | 1 + prowler/lib/outputs/jira/jira.py | 113 +++++++++++++++++++++++++++- tests/lib/outputs/jira/jira_test.py | 20 ++++- 3 files changed, 130 insertions(+), 4 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 6307aa94b0..f34021c7da 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -39,6 +39,7 @@ All notable changes to the **Prowler SDK** are documented in this file. ### Added - Add more fields for the Jira ticket and handle custom fields errors [(#8601)](https://github.com/prowler-cloud/prowler/pull/8601) - Support labels on Jira tickets [(#8603)](https://github.com/prowler-cloud/prowler/pull/8603) +- Add finding url and tenant info inside Jira tickets [(#8607)](https://github.com/prowler-cloud/prowler/pull/8607) ### Changed diff --git a/prowler/lib/outputs/jira/jira.py b/prowler/lib/outputs/jira/jira.py index dfae32ebd4..6640390819 100644 --- a/prowler/lib/outputs/jira/jira.py +++ b/prowler/lib/outputs/jira/jira.py @@ -744,6 +744,8 @@ class Jira: remediation_code_other: str = None, resource_tags: dict = None, compliance: dict = None, + finding_url: str = None, + tenant_info: str = None, ) -> dict: table_rows = [ { @@ -1321,6 +1323,93 @@ class Jira: } ) + if finding_url: + table_rows.append( + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {"colwidth": [1]}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Finding URL", + "marks": [{"type": "strong"}], + } + ], + } + ], + }, + { + "type": "tableCell", + "attrs": {"colwidth": [3]}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": finding_url, + "marks": [ + { + "type": "link", + "attrs": {"href": finding_url}, + } + ], + } + ], + } + ], + }, + ], + } + ) + + if tenant_info: + table_rows.append( + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {"colwidth": [1]}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Tenant Info", + "marks": [{"type": "strong"}], + } + ], + } + ], + }, + { + "type": "tableCell", + "attrs": {"colwidth": [3]}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": tenant_info, + "marks": [{"type": "code"}], + } + ], + } + ], + }, + ], + } + ) + return { "type": "doc", "version": 1, @@ -1348,6 +1437,8 @@ class Jira: project_key: str = None, issue_type: str = None, issue_labels: list[str] = None, + finding_url: str = None, + tenant_info: str = None, ): """ Send the findings to Jira @@ -1357,6 +1448,8 @@ class Jira: - project_key: The project key - issue_type: The issue type - issue_labels: The issue labels + - finding_url: The finding URL + - tenant_info: The tenant info Raises: - JiraRefreshTokenError: Failed to refresh the access token @@ -1428,6 +1521,8 @@ class Jira: remediation_code_other=finding.metadata.Remediation.Code.Other, resource_tags=finding.resource_tags, compliance=finding.compliance, + finding_url=finding_url, + tenant_info=tenant_info, ) payload = { "fields": { @@ -1447,7 +1542,15 @@ class Jira: ) if response.status_code != 201: - response_json = response.json() + try: + response_json = response.json() + except (ValueError, requests.exceptions.JSONDecodeError): + response_error = f"Failed to send finding: {response.status_code} - {response.text}" + logger.warning(response_error) + raise JiraSendFindingsResponseError( + message=response_error, file=os.path.basename(__file__) + ) + # Check if the error is due to required custom fields if response.status_code == 400 and "errors" in response_json: errors = response_json.get("errors", {}) @@ -1475,7 +1578,13 @@ class Jira: message=response_error, file=os.path.basename(__file__) ) else: - logger.info(f"Finding sent successfully: {response.json()}") + try: + response_json = response.json() + logger.info(f"Finding sent successfully: {response_json}") + except (ValueError, requests.exceptions.JSONDecodeError): + logger.info( + f"Finding sent successfully: Status {response.status_code}" + ) except JiraRequiredCustomFieldsError as custom_fields_error: raise custom_fields_error except JiraRefreshTokenError as refresh_error: diff --git a/tests/lib/outputs/jira/jira_test.py b/tests/lib/outputs/jira/jira_test.py index ef9ea7be86..8b3b13259e 100644 --- a/tests/lib/outputs/jira/jira_test.py +++ b/tests/lib/outputs/jira/jira_test.py @@ -610,6 +610,8 @@ class TestJiraIntegration: project_key="TEST-1", issue_type="Bug", issue_labels=["scan-mocked", "whatever"], + finding_url="https://prowler-cloud-link/findings/12345", + tenant_info="Tenant Info", ) mock_post.assert_called_once() @@ -683,6 +685,8 @@ class TestJiraIntegration: "Remediation Terraform", "Remediation CLI", "Remediation Other", + "Finding URL", + "Tenant Info", ] actual_keys = [key for key, _ in row_texts] @@ -722,6 +726,8 @@ class TestJiraIntegration: assert "Owner=SecurityTeam" in row_dict["Resource Tags"] assert "CIS: 2.1.1, 2.1.2" in row_dict["Compliance"] assert "NIST: AC-3, AC-6" in row_dict["Compliance"] + assert "https://prowler-cloud-link/findings/12345" in row_dict["Finding URL"] + assert "Tenant Info" in row_dict["Tenant Info"] @patch.object(Jira, "get_access_token", return_value="valid_access_token") @patch.object( @@ -825,7 +831,12 @@ class TestJiraIntegration: with pytest.raises(JiraRequiredCustomFieldsError): self.jira_integration.send_findings( - findings=[finding], project_key="TEST-1", issue_type="Bug" + findings=[finding], + project_key="TEST-1", + issue_type="Bug", + issue_labels=["scan-mocked", "whatever"], + finding_url="https://prowler-cloud-link/findings/12345", + tenant_info="Tenant Info", ) @patch.object(Jira, "get_access_token", return_value="valid_access_token") @@ -882,7 +893,12 @@ class TestJiraIntegration: with pytest.raises(JiraCreateIssueError): self.jira_integration.send_findings( - findings=[finding], project_key="TEST-1", issue_type="Bug" + findings=[finding], + project_key="TEST-1", + issue_type="Bug", + issue_labels=["scan-mocked", "whatever"], + finding_url="https://prowler-cloud-link/findings/12345", + tenant_info="Tenant Info", ) @pytest.mark.parametrize(