Skip to content

Commit

Permalink
feature: Jira integration - option to add custom fields to created is…
Browse files Browse the repository at this point in the history
…sues (#50)
  • Loading branch information
mlflr authored Nov 4, 2024
1 parent da5ebc1 commit d3f42f8
Show file tree
Hide file tree
Showing 9 changed files with 18 additions and 5 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ Since a lambda layer is used to provide the aws-lambda-powertools if you want to
| <a name="input_findings_manager_lambda_iam_role_name"></a> [findings\_manager\_lambda\_iam\_role\_name](#input\_findings\_manager\_lambda\_iam\_role\_name) | The name of the role which will be assumed by both Findings Manager Lambda functions | `string` | `"SecurityHubFindingsManagerLambda"` | no |
| <a name="input_findings_manager_trigger_lambda"></a> [findings\_manager\_trigger\_lambda](#input\_findings\_manager\_trigger\_lambda) | Findings Manager Lambda settings - Manage Security Hub findings in response to S3 file upload triggers | <pre>object({<br> name = optional(string, "securityhub-findings-manager-trigger")<br> log_level = optional(string, "INFO")<br> memory_size = optional(number, 256)<br> timeout = optional(number, 120)<br><br> security_group_egress_rules = optional(list(object({<br> cidr_ipv4 = optional(string)<br> cidr_ipv6 = optional(string)<br> description = string<br> from_port = optional(number, 0)<br> ip_protocol = optional(string, "-1")<br> prefix_list_id = optional(string)<br> referenced_security_group_id = optional(string)<br> to_port = optional(number, 0)<br> })), [])<br> })</pre> | `{}` | no |
| <a name="input_jira_eventbridge_iam_role_name"></a> [jira\_eventbridge\_iam\_role\_name](#input\_jira\_eventbridge\_iam\_role\_name) | The name of the role which will be assumed by EventBridge rules for Jira integration | `string` | `"SecurityHubFindingsManagerJiraEventBridge"` | no |
| <a name="input_jira_integration"></a> [jira\_integration](#input\_jira\_integration) | Findings Manager - Jira integration settings | <pre>object({<br> enabled = optional(bool, false)<br> autoclose_enabled = optional(bool, false)<br> autoclose_comment = optional(string, "Security Hub finding has been resolved. Autoclosing the issue.")<br> autoclose_transition_name = optional(string, "Close Issue")<br> credentials_secret_arn = string<br> exclude_account_ids = optional(list(string), [])<br> finding_severity_normalized_threshold = optional(number, 70)<br> issue_type = optional(string, "Security Advisory")<br> project_key = string<br><br> security_group_egress_rules = optional(list(object({<br> cidr_ipv4 = optional(string)<br> cidr_ipv6 = optional(string)<br> description = string<br> from_port = optional(number, 0)<br> ip_protocol = optional(string, "-1")<br> prefix_list_id = optional(string)<br> referenced_security_group_id = optional(string)<br> to_port = optional(number, 0)<br> })), [])<br><br> lambda_settings = optional(object({<br> name = optional(string, "securityhub-findings-manager-jira")<br> iam_role_name = optional(string, "SecurityHubFindingsManagerJiraLambda")<br> log_level = optional(string, "INFO")<br> memory_size = optional(number, 256)<br> timeout = optional(number, 60)<br> }), {<br> name = "securityhub-findings-manager-jira"<br> iam_role_name = "SecurityHubFindingsManagerJiraLambda"<br> log_level = "INFO"<br> memory_size = 256<br> timeout = 60<br> security_group_egress_rules = []<br> })<br><br> step_function_settings = optional(object({<br> log_level = optional(string, "ERROR")<br> retention = optional(number, 90)<br> }), {<br> log_level = "ERROR"<br> retention = 90<br> })<br><br> })</pre> | <pre>{<br> "credentials_secret_arn": null,<br> "enabled": false,<br> "project_key": null<br>}</pre> | no |
| <a name="input_jira_integration"></a> [jira\_integration](#input\_jira\_integration) | Findings Manager - Jira integration settings | <pre>object({<br> enabled = optional(bool, false)<br> autoclose_enabled = optional(bool, false)<br> autoclose_comment = optional(string, "Security Hub finding has been resolved. Autoclosing the issue.")<br> autoclose_transition_name = optional(string, "Close Issue")<br> credentials_secret_arn = string<br> exclude_account_ids = optional(list(string), [])<br> finding_severity_normalized_threshold = optional(number, 70)<br> issue_custom_fields = optional(map(string), {})<br> issue_type = optional(string, "Security Advisory")<br> project_key = string<br><br> security_group_egress_rules = optional(list(object({<br> cidr_ipv4 = optional(string)<br> cidr_ipv6 = optional(string)<br> description = string<br> from_port = optional(number, 0)<br> ip_protocol = optional(string, "-1")<br> prefix_list_id = optional(string)<br> referenced_security_group_id = optional(string)<br> to_port = optional(number, 0)<br> })), [])<br><br> lambda_settings = optional(object({<br> name = optional(string, "securityhub-findings-manager-jira")<br> iam_role_name = optional(string, "SecurityHubFindingsManagerJiraLambda")<br> log_level = optional(string, "INFO")<br> memory_size = optional(number, 256)<br> timeout = optional(number, 60)<br> }), {<br> name = "securityhub-findings-manager-jira"<br> iam_role_name = "SecurityHubFindingsManagerJiraLambda"<br> log_level = "INFO"<br> memory_size = 256<br> timeout = 60<br> security_group_egress_rules = []<br> })<br><br> step_function_settings = optional(object({<br> log_level = optional(string, "ERROR")<br> retention = optional(number, 90)<br> }), {<br> log_level = "ERROR"<br> retention = 90<br> })<br><br> })</pre> | <pre>{<br> "credentials_secret_arn": null,<br> "enabled": false,<br> "project_key": null<br>}</pre> | no |
| <a name="input_jira_step_function_iam_role_name"></a> [jira\_step\_function\_iam\_role\_name](#input\_jira\_step\_function\_iam\_role\_name) | The name of the role which will be assumed by AWS Step Function for Jira integration | `string` | `"SecurityHubFindingsManagerJiraStepFunction"` | no |
| <a name="input_lambda_runtime"></a> [lambda\_runtime](#input\_lambda\_runtime) | The version of Python to use for the Lambda functions | `string` | `"python3.12"` | no |
| <a name="input_rules_filepath"></a> [rules\_filepath](#input\_rules\_filepath) | Pathname to the file that stores the manager rules | `string` | `""` | no |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import os
import sys

import boto3
from aws_lambda_powertools import Logger
Expand All @@ -14,7 +15,7 @@
secretsmanager = boto3.client('secretsmanager')

REQUIRED_ENV_VARS = [
'EXCLUDE_ACCOUNT_FILTER', 'JIRA_ISSUE_TYPE', 'JIRA_PROJECT_KEY', 'JIRA_SECRET_ARN'
'EXCLUDE_ACCOUNT_FILTER', 'JIRA_ISSUE_CUSTOM_FIELDS', 'JIRA_ISSUE_TYPE', 'JIRA_PROJECT_KEY', 'JIRA_SECRET_ARN'
]
DEFAULT_JIRA_AUTOCLOSE_COMMENT = 'Security Hub finding has been resolved. Autoclosing the issue.'
DEFAULT_JIRA_AUTOCLOSE_TRANSITION = 'Done'
Expand All @@ -39,10 +40,19 @@ def lambda_handler(event: dict, context: LambdaContext):
'JIRA_AUTOCLOSE_COMMENT', DEFAULT_JIRA_AUTOCLOSE_COMMENT)
jira_autoclose_transition = os.getenv(
'JIRA_AUTOCLOSE_TRANSITION', DEFAULT_JIRA_AUTOCLOSE_TRANSITION)
jira_issue_custom_fields = os.environ['JIRA_ISSUE_CUSTOM_FIELDS']
jira_issue_type = os.environ['JIRA_ISSUE_TYPE']
jira_project_key = os.environ['JIRA_PROJECT_KEY']
jira_secret_arn = os.environ['JIRA_SECRET_ARN']

# Parse custom fields
try:
jira_issue_custom_fields = json.loads(jira_issue_custom_fields)
jira_issue_custom_fields = { k: {"value": v} for k, v in jira_issue_custom_fields.items() }
except json.JSONDecodeError as e:
logger.error(f"Failed to parse JSON for custom fields: {e}.")
sys.exit(1)

# Retrieve JIRA client
jira_secret = helpers.get_secret(secretsmanager, jira_secret_arn)
jira_client = helpers.get_jira_client(jira_secret)
Expand All @@ -66,7 +76,7 @@ def lambda_handler(event: dict, context: LambdaContext):
# and adds JIRA issue key to note (in JSON format)
try:
issue = helpers.create_jira_issue(
jira_client, jira_project_key, jira_issue_type, event_detail)
jira_client, jira_project_key, jira_issue_type, event_detail, jira_issue_custom_fields)
note = json.dumps({'jiraIssue': issue.key})
helpers.update_security_hub(
securityhub, finding["Id"], finding["ProductArn"], STATUS_NOTIFIED, note)
Expand Down
5 changes: 3 additions & 2 deletions files/lambda-artifacts/findings-manager-jira/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def get_secret(client: BaseClient, secret_arn: str) -> Dict[str, str]:
raise e


def create_jira_issue(jira_client: JIRA, project_key: str, issue_type: str, event: dict) -> Issue:
def create_jira_issue(jira_client: JIRA, project_key: str, issue_type: str, event: dict, custom_fields: dict) -> Issue:
"""
Create a JIRA issue based on a Security Hub event.
Expand All @@ -102,6 +102,7 @@ def create_jira_issue(jira_client: JIRA, project_key: str, issue_type: str, even
project_key (str): The key of the JIRA project.
issue_type (str): The type of the JIRA issue.
event (Dict): The Security Hub event data.
custom_fields (Dict): The custom fields to include in the JIRA issue.
Returns:
Issue: The created JIRA issue.
Expand Down Expand Up @@ -134,12 +135,12 @@ def create_jira_issue(jira_client: JIRA, project_key: str, issue_type: str, even
]

issue_dict = {
**custom_fields,
'project': {'key': project_key},
'issuetype': {'name': issue_type},
'summary': issue_title,
'description': issue_description,
'labels': issue_labels,
'customfield_11101': {'value': 'Vulnerability Management'}
}

try:
Expand Down
Binary file modified files/pkg/lambda_findings-manager-jira_python3.11.zip
Binary file not shown.
Binary file modified files/pkg/lambda_findings-manager-jira_python3.12.zip
Binary file not shown.
Binary file modified files/pkg/lambda_securityhub-findings-manager_python3.11.zip
Binary file not shown.
Binary file modified files/pkg/lambda_securityhub-findings-manager_python3.12.zip
Binary file not shown.
1 change: 1 addition & 0 deletions jira_lambda.tf
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ module "jira_lambda" {
EXCLUDE_ACCOUNT_FILTER = jsonencode(var.jira_integration.exclude_account_ids)
JIRA_AUTOCLOSE_COMMENT = var.jira_integration.autoclose_comment
JIRA_AUTOCLOSE_TRANSITION = var.jira_integration.autoclose_transition_name
JIRA_ISSUE_CUSTOM_FIELDS = jsonencode(var.jira_integration.issue_custom_fields)
JIRA_ISSUE_TYPE = var.jira_integration.issue_type
JIRA_PROJECT_KEY = var.jira_integration.project_key
JIRA_SECRET_ARN = var.jira_integration.credentials_secret_arn
Expand Down
1 change: 1 addition & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ variable "jira_integration" {
credentials_secret_arn = string
exclude_account_ids = optional(list(string), [])
finding_severity_normalized_threshold = optional(number, 70)
issue_custom_fields = optional(map(string), {})
issue_type = optional(string, "Security Advisory")
project_key = string

Expand Down

0 comments on commit d3f42f8

Please sign in to comment.