diff --git a/Packs/AWS-GuardDuty/Integrations/AWSGuardDuty/AWSGuardDuty.py b/Packs/AWS-GuardDuty/Integrations/AWSGuardDuty/AWSGuardDuty.py index b7f747d32de0..e32b264601ec 100644 --- a/Packs/AWS-GuardDuty/Integrations/AWSGuardDuty/AWSGuardDuty.py +++ b/Packs/AWS-GuardDuty/Integrations/AWSGuardDuty/AWSGuardDuty.py @@ -3,13 +3,18 @@ import json from datetime import datetime, date from AWSApiModule import * # noqa: E402 -import urllib3.util -import boto3 from collections import defaultdict -from typing import Tuple +from typing import TYPE_CHECKING, Tuple + +# The following import are used only for type hints and autocomplete. +# It is not used at runtime, and not exist in the docker image. +if TYPE_CHECKING: + from mypy_boto3_guardduty import GuardDutyClient + from mypy_boto3_guardduty.type_defs import ( + FindingTypeDef, + ConditionTypeDef + ) -# Disable insecure warnings -urllib3.disable_warnings() ''' CONSTANTS ''' @@ -27,15 +32,15 @@ class DatetimeEncoder(json.JSONEncoder): def default(self, obj: Any): # pylint: disable=E0202 - if isinstance(obj, datetime): # type: ignore + if isinstance(obj, datetime): return obj.strftime('%Y-%m-%dT%H:%M:%S') - elif isinstance(obj, date): # type: ignore # pylint: disable=E0602 + elif isinstance(obj, date): # pylint: disable=E0602 return obj.strftime('%Y-%m-%d') # Let the base class default method raise the TypeError return json.JSONEncoder.default(self, obj) -def create_detector(client: boto3.client, args: dict) -> CommandResults: +def create_detector(client: "GuardDutyClient", args: dict) -> CommandResults: """ Creates a single Amazon GuardDuty detector. """ @@ -66,19 +71,19 @@ def create_detector(client: boto3.client, args: dict) -> CommandResults: outputs_key_field='DetectorId') -def delete_detector(client: boto3.client, args: dict): - response = client.delete_detector(DetectorId=args.get('detectorId')) +def delete_detector(client: "GuardDutyClient", args: dict): + response = client.delete_detector(DetectorId=args.get('detectorId', '')) if response == dict() or response.get('ResponseMetadata', {}).get('HTTPStatusCode') == 200: return f"The Detector {args.get('detectorId')} has been deleted" else: raise Exception(f"The Detector {args.get('detectorId')} failed to delete.") -def get_detector(client: boto3.client, args: dict) -> CommandResults: +def get_detector(client: "GuardDutyClient", args: dict) -> CommandResults: """ Retrieves an Amazon GuardDuty detector specified by the detectorId. """ - response = client.get_detector(DetectorId=args.get('detectorId')) + response = client.get_detector(DetectorId=args.get('detectorId', '')) data = ({ 'DetectorId': args.get('detectorId'), 'CreatedAt': response.get('CreatedAt'), @@ -105,7 +110,7 @@ def get_detector(client: boto3.client, args: dict) -> CommandResults: outputs_key_field='DetectorId') -def update_detector(client: boto3.client, args: dict) -> str: +def update_detector(client: "GuardDutyClient", args: dict) -> str: """ Updates the Amazon GuardDuty detector specified by the detectorId. """ @@ -132,7 +137,7 @@ def update_detector(client: boto3.client, args: dict) -> str: raise Exception(f"Detector {args.get('detectorId')} failed to update. Response was: {response}") -def list_detectors(client: boto3.client, args: dict) -> CommandResults: +def list_detectors(client: "GuardDutyClient", args: dict) -> CommandResults: limit, page_size, page = get_pagination_args(args) paginator = client.get_paginator('list_detectors') @@ -159,8 +164,8 @@ def list_detectors(client: boto3.client, args: dict) -> CommandResults: outputs_key_field='DetectorId') -def create_ip_set(client: boto3.client, args: dict): - kwargs = {'DetectorId': args.get('detectorId')} +def create_ip_set(client: "GuardDutyClient", args: dict): + kwargs: dict[str, Any] = {'DetectorId': args.get('detectorId')} if args.get('activate') is not None: kwargs.update({'Activate': True if args.get('activate') == 'True' else False}) if args.get('format') is not None: @@ -183,10 +188,10 @@ def create_ip_set(client: boto3.client, args: dict): outputs_key_field='IpSetId') -def delete_ip_set(client: boto3.client, args: dict): +def delete_ip_set(client: "GuardDutyClient", args: dict): response = client.delete_ip_set( - DetectorId=args.get('detectorId'), - IpSetId=args.get('ipSetId') + DetectorId=args.get('detectorId', ''), + IpSetId=args.get('ipSetId', '') ) if response == dict() or response.get('ResponseMetadata', {}).get('HTTPStatusCode') == 200: return f"The IPSet {args.get('ipSetId')} has been deleted from Detector {args.get('detectorId')}" @@ -195,8 +200,8 @@ def delete_ip_set(client: boto3.client, args: dict): raise Exception(f"Failed to delete ip set {args.get('ipSetId')} . Response was: {response}") -def update_ip_set(client: boto3.client, args: dict): - kwargs = { +def update_ip_set(client: "GuardDutyClient", args: dict): + kwargs: dict[str, Any] = { 'DetectorId': args.get('detectorId'), 'IpSetId': args.get('ipSetId') } @@ -216,9 +221,9 @@ def update_ip_set(client: boto3.client, args: dict): raise Exception(f"Failed updating ip set {args.get('ipSetId')} . Response was: {response}") -def get_ip_set(client: boto3.client, args: dict): - response = client.get_ip_set(DetectorId=args.get('detectorId'), - IpSetId=args.get('ipSetId')) +def get_ip_set(client: "GuardDutyClient", args: dict): + response = client.get_ip_set(DetectorId=args.get('detectorId', ''), + IpSetId=args.get('ipSetId', '')) data = ({'DetectorId': args.get('detectorId'), 'IpSetId': args.get('ipSetId'), 'Format': response['Format'], @@ -233,12 +238,12 @@ def get_ip_set(client: boto3.client, args: dict): outputs_key_field='IpSetId') -def list_ip_sets(client: boto3.client, args: dict) -> CommandResults: +def list_ip_sets(client: "GuardDutyClient", args: dict) -> CommandResults: limit, page_size, page = get_pagination_args(args) paginator = client.get_paginator('list_ip_sets') response_iterator = paginator.paginate( - DetectorId=args.get('detectorId'), + DetectorId=args.get('detectorId', ''), PaginationConfig={ 'MaxItems': limit, 'PageSize': page_size, @@ -261,8 +266,8 @@ def list_ip_sets(client: boto3.client, args: dict) -> CommandResults: outputs_key_field='IpSetId') -def create_threat_intel_set(client: boto3.client, args: dict): - kwargs = {'DetectorId': args.get('detectorId')} +def create_threat_intel_set(client: "GuardDutyClient", args: dict): + kwargs: dict[str, Any] = {'DetectorId': args.get('detectorId')} if args.get('activate') is not None: kwargs.update({'Activate': True if args.get('activate') == 'True' else False}) if args.get('format') is not None: @@ -286,10 +291,10 @@ def create_threat_intel_set(client: boto3.client, args: dict): outputs_key_field='ThreatIntelSetId') -def delete_threat_intel_set(client: boto3.client, args: dict): +def delete_threat_intel_set(client: "GuardDutyClient", args: dict): response = client.delete_threat_intel_set( - DetectorId=args.get('detectorId'), - ThreatIntelSetId=args.get('threatIntelSetId') + DetectorId=args.get('detectorId', ''), + ThreatIntelSetId=args.get('threatIntelSetId', '') ) if response == dict() or response.get('ResponseMetadata', {}).get('HTTPStatusCode') == 200: return f"The ThreatIntel Set {args.get('threatIntelSetId')} has been deleted from Detector {args.get('detectorId')}" @@ -297,10 +302,10 @@ def delete_threat_intel_set(client: boto3.client, args: dict): raise Exception(f"Failed to delete ThreatIntel set {args.get('threatIntelSetId')} . Response was: {response}") -def get_threat_intel_set(client: boto3.client, args: dict): +def get_threat_intel_set(client: "GuardDutyClient", args: dict): response = client.get_threat_intel_set( - DetectorId=args.get('detectorId'), - ThreatIntelSetId=args.get('threatIntelSetId') + DetectorId=args.get('detectorId', ''), + ThreatIntelSetId=args.get('threatIntelSetId', '') ) data = ({ 'DetectorId': args.get('detectorId'), @@ -318,12 +323,12 @@ def get_threat_intel_set(client: boto3.client, args: dict): outputs_key_field='ThreatIntelSetId') -def list_threat_intel_sets(client: boto3.client, args: dict) -> CommandResults: +def list_threat_intel_sets(client: "GuardDutyClient", args: dict) -> CommandResults: limit, page_size, page = get_pagination_args(args) paginator = client.get_paginator('list_threat_intel_sets') response_iterator = paginator.paginate( - DetectorId=args.get('detectorId'), + DetectorId=args.get('detectorId', ''), PaginationConfig={ 'MaxItems': limit, 'PageSize': page_size, @@ -346,8 +351,8 @@ def list_threat_intel_sets(client: boto3.client, args: dict) -> CommandResults: outputs_key_field='ThreatIntelSetId') -def update_threat_intel_set(client: boto3.client, args: dict): - kwargs = { +def update_threat_intel_set(client: "GuardDutyClient", args: dict): + kwargs: dict[str, Any] = { 'DetectorId': args.get('detectorId'), 'ThreatIntelSetId': args.get('threatIntelSetId') } @@ -393,12 +398,12 @@ def gd_severity_mapping(severity_list: List[str]): return gd_severity -def list_findings(client: boto3.client, args: dict) -> CommandResults: +def list_findings(client: "GuardDutyClient", args: dict) -> CommandResults: limit, page_size, page = get_pagination_args(args) paginator = client.get_paginator('list_findings') response_iterator = paginator.paginate( - DetectorId=args.get('detectorId'), + DetectorId=args.get('detectorId', ''), PaginationConfig={ 'MaxItems': limit, 'PageSize': page_size, @@ -420,7 +425,7 @@ def list_findings(client: boto3.client, args: dict) -> CommandResults: outputs_key_field='') -def get_pagination_args(args: dict) -> Tuple[Optional[int], Optional[int], Optional[int]]: +def get_pagination_args(args: dict) -> Tuple[int, int, Optional[int]]: """ Gets and validates pagination arguments. :param args: The command arguments (page, page_size or limit) @@ -428,24 +433,24 @@ def get_pagination_args(args: dict) -> Tuple[Optional[int], Optional[int], Optio """ # Automatic Pagination - limit = arg_to_number(args.get('limit', MAX_RESULTS_RESPONSE)) + limit = arg_to_number(args.get('limit')) or MAX_RESULTS_RESPONSE # Manual Pagination page = arg_to_number(args.get('page')) if page is not None and page <= 0: raise DemistoException('page argument must be greater than 0') - page_size = arg_to_number(args.get('page_size', MAX_RESULTS_RESPONSE)) - if not 0 < page_size <= MAX_RESULTS_RESPONSE: # type: ignore + page_size = arg_to_number(args.get('page_size')) or MAX_RESULTS_RESPONSE + if not 0 < page_size <= MAX_RESULTS_RESPONSE: raise DemistoException(f'page_size argument must be between 1 to {MAX_RESULTS_RESPONSE}') if page: - limit = page * page_size # type: ignore + limit = page * page_size return limit, page_size, page -def parse_finding(finding: dict) -> Dict: +def parse_finding(finding: "FindingTypeDef") -> Dict[str, Any]: """ Parse the finding data to output, context format :param finding: Contains information about the finding, @@ -477,11 +482,11 @@ def parse_finding(finding: dict) -> Dict: return parsed_finding -def get_findings(client: boto3.client, args: dict) -> dict: +def get_findings(client: "GuardDutyClient", args: dict) -> dict: return_raw_response = argToBoolean(args.get('returnRawResponse', 'false')) response = client.get_findings( - DetectorId=args.get('detectorId'), + DetectorId=args.get('detectorId', ''), FindingIds=argToList(args.get('findingIds'))) data = [] @@ -505,7 +510,7 @@ def get_findings(client: boto3.client, args: dict) -> dict: } -def parse_incident_from_finding(finding: dict): +def parse_incident_from_finding(finding: "FindingTypeDef") -> Dict[str, Any]: incident: dict = dict() incident['name'] = finding.get('Title') incident['details'] = finding.get('Description') @@ -524,13 +529,13 @@ def time_to_unix_epoch(date_time: datetime) -> int: return int(date_time.timestamp() * 1000) -def fetch_incidents(client: boto3.client, aws_gd_severity: List[str], last_run: dict, fetch_limit: int, +def fetch_incidents(client: "GuardDutyClient", aws_gd_severity: List[str], last_run: dict, fetch_limit: int, first_fetch_time: str, is_archive: bool) -> Tuple[Dict[str, Any], List[dict]]: """ This function will execute each interval (default is 1 minute). Args: - client (Client): boto3.client client + client (Client): "GuardDutyClient" client aws_gd_severity (List[str]): Guard Duty Severity level last_run (dict): {'latest_created_time' (string): The greatest incident created_time we fetched from last fetch, 'latest_updated_time' (string): The greatest incident updated_time we fetched from last fetch, @@ -552,7 +557,8 @@ def fetch_incidents(client: boto3.client, aws_gd_severity: List[str], last_run: # Handle first time fetch if latest_created_time is None: - latest_created_time = dateparser.parse(dateparser.parse(first_fetch_time).strftime(DATE_FORMAT)) # type: ignore + latest_created_time = dateparser.parse(dateparser.parse( + first_fetch_time).strftime(DATE_FORMAT)) # type: ignore[union-attr] else: latest_created_time = dateparser.parse(latest_created_time) @@ -562,7 +568,8 @@ def fetch_incidents(client: boto3.client, aws_gd_severity: List[str], last_run: created_time_to_ids = defaultdict(list) created_time_to_ids[latest_created_time] = last_incidents_ids - criterion_conditions = dict() # Represents the criteria to be used in the filter for querying findings. + # Represents the criteria to be used in the filter for querying findings. + criterion_conditions: Dict[str, "ConditionTypeDef"] = {} criterion_conditions['severity'] = {'Gte': gd_severity_mapping(aws_gd_severity)} if is_archive: demisto.debug('Fetching Amazon GuardDuty with Archive') @@ -631,8 +638,8 @@ def fetch_incidents(client: boto3.client, aws_gd_severity: List[str], last_run: return next_run, incidents -def create_sample_findings(client: boto3.client, args: dict): - kwargs = {'DetectorId': args.get('detectorId')} +def create_sample_findings(client: "GuardDutyClient", args: dict): + kwargs: dict[str, Any] = {'DetectorId': args.get('detectorId')} if args.get('findingTypes') is not None: kwargs.update({'FindingTypes': argToList(args.get('findingTypes'))}) @@ -644,8 +651,8 @@ def create_sample_findings(client: boto3.client, args: dict): raise Exception(f"Failed to generate findings. Response was: {response}") -def archive_findings(client: boto3.client, args: dict): - kwargs = {'DetectorId': args.get('detectorId')} +def archive_findings(client: "GuardDutyClient", args: dict): + kwargs: dict[str, Any] = {'DetectorId': args.get('detectorId')} if args.get('findingIds') is not None: kwargs.update({'FindingIds': argToList(args.get('findingIds'))}) @@ -657,7 +664,7 @@ def archive_findings(client: boto3.client, args: dict): raise Exception(f"Failed to archive findings. Response was: {response}") -def unarchive_findings(client: boto3.client, args: dict): +def unarchive_findings(client: "GuardDutyClient", args: dict): kwargs: dict = {'DetectorId': args.get('detectorId')} if args.get('findingIds') is not None: kwargs.update({'FindingIds': argToList(args.get('findingIds'))}) @@ -670,8 +677,8 @@ def unarchive_findings(client: boto3.client, args: dict): raise Exception(f"Failed to archive findings. Response was: {response}") -def update_findings_feedback(client: boto3.client, args: dict): - kwargs = {'DetectorId': args.get('detectorId')} +def update_findings_feedback(client: "GuardDutyClient", args: dict): + kwargs: dict[str, Any] = {'DetectorId': args.get('detectorId')} if args.get('findingIds') is not None: kwargs.update({'FindingIds': argToList(args.get('findingIds'))}) if args.get('comments') is not None: @@ -686,12 +693,12 @@ def update_findings_feedback(client: boto3.client, args: dict): raise Exception(f"Failed to send findings feedback. Response was: {response}") -def list_members(client: boto3.client, args: dict) -> CommandResults: +def list_members(client: "GuardDutyClient", args: dict) -> CommandResults: limit, page_size, page = get_pagination_args(args) paginator = client.get_paginator('list_members') response_iterator = paginator.paginate( - DetectorId=args.get('detectorId'), + DetectorId=args.get('detectorId', ''), PaginationConfig={ 'MaxItems': limit, 'PageSize': page_size, @@ -713,12 +720,12 @@ def list_members(client: boto3.client, args: dict) -> CommandResults: outputs_key_field='AccountId') -def get_members(client: boto3.client, args: dict): +def get_members(client: "GuardDutyClient", args: dict): accountId_list = [] - accountId_list.append(args.get('accountIds')) + accountId_list.append(args.get('accountIds', '')) response = client.get_members( - DetectorId=args.get('detectorId'), + DetectorId=args.get('detectorId', ''), AccountIds=accountId_list ) @@ -733,7 +740,7 @@ def get_members(client: boto3.client, args: dict): outputs_key_field='AccountId') -def connection_test(client: boto3.client): +def connection_test(client: "GuardDutyClient"): response = client.list_detectors() if response.get('DetectorIds'): return 'ok' @@ -769,10 +776,10 @@ def main(): # pragma: no cover timeout, retries, sts_endpoint_url=sts_endpoint_url, endpoint_url=endpoint_url) args = demisto.args() - client = aws_client.aws_session(service=SERVICE, region=args.get('region'), - role_arn=args.get('roleArn'), - role_session_name=args.get('roleSessionName'), - role_session_duration=args.get('roleSessionDuration')) + client: "GuardDutyClient" = aws_client.aws_session(service=SERVICE, region=args.get('region'), + role_arn=args.get('roleArn'), + role_session_name=args.get('roleSessionName'), + role_session_duration=args.get('roleSessionDuration')) # The command demisto.command() holds the command sent from the user. if demisto.command() == 'test-module': @@ -851,7 +858,7 @@ def main(): # pragma: no cover elif demisto.command() == 'fetch-incidents': next_run, incidents = fetch_incidents(client=client, aws_gd_severity=aws_gd_severity, last_run=demisto.getLastRun(), - fetch_limit=fetch_limit, # type: ignore + fetch_limit=fetch_limit, # type: ignore[arg-type] first_fetch_time=first_fetch_time, is_archive=is_archive) demisto.setLastRun(next_run) demisto.incidents(incidents) diff --git a/Packs/AWS-GuardDuty/Integrations/AWSGuardDuty/AWSGuardDuty.yml b/Packs/AWS-GuardDuty/Integrations/AWSGuardDuty/AWSGuardDuty.yml index ab1f732a94e0..d6c9bdfbc4d4 100644 --- a/Packs/AWS-GuardDuty/Integrations/AWSGuardDuty/AWSGuardDuty.yml +++ b/Packs/AWS-GuardDuty/Integrations/AWSGuardDuty/AWSGuardDuty.yml @@ -125,7 +125,7 @@ configuration: section: Connect advanced: true required: false -description: Amazon Web Services Guard Duty Service (gd) +description: Amazon Web Services Guard Duty Service (gd). display: AWS - GuardDuty name: AWS - GuardDuty script: @@ -315,7 +315,7 @@ script: - PROOF_POINT - FIRE_EYE required: true - - description: The URI of the file that contains the IPSet. For example (https://s3.us-west-2.amazonaws.com/my-bucket/my-object-key) + - description: The URI of the file that contains the IPSet. For example (https://s3.us-west-2.amazonaws.com/my-bucket/my-object-key). name: location - description: The user friendly name to identify the IPSet. This name is displayed in all findings that are triggered by activity that involves IP addresses included in this IPSet. name: name @@ -457,7 +457,7 @@ script: name: aws-gd-list-ip-sets outputs: - contextPath: AWS.GuardDuty.Detectors.IPSet.IpSetId - description: The unique identifier for an IP Set + description: The unique identifier for an IP Set. type: Unknown - arguments: - auto: PREDEFINED @@ -576,7 +576,7 @@ script: name: aws-gd-list-threatintel-sets outputs: - contextPath: AWS.GuardDuty.Detectors.ThreatIntelSet.ThreatIntelSetId - description: The unique identifier for an threat intel set + description: The unique identifier for an threat intel set. type: string - arguments: - description: The detectorID that specifies the GuardDuty service whose ThreatIntelSet you want to update. @@ -628,7 +628,7 @@ script: name: aws-gd-list-findings outputs: - contextPath: AWS.GuardDuty.Findings.FindingId - description: The unique identifier for the Finding + description: The unique identifier for the Finding. type: string - arguments: - description: The ID of the detector that specifies the GuardDuty service whose findings you want to retrieve. @@ -871,7 +871,7 @@ script: - contextPath: AWS.GuardDuty.Members.UpdatedAt description: The time a member was last updated. type: string - dockerimage: demisto/boto3py3:1.0.0.71685 + dockerimage: demisto/boto3py3:1.0.0.75596 isfetch: true runonce: false script: '-' diff --git a/Packs/AWS-GuardDuty/Integrations/AWSGuardDutyEventCollector/AWSGuardDutyEventCollector.py b/Packs/AWS-GuardDuty/Integrations/AWSGuardDutyEventCollector/AWSGuardDutyEventCollector.py index 1c4c07f23b5e..d5b47b39966a 100644 --- a/Packs/AWS-GuardDuty/Integrations/AWSGuardDutyEventCollector/AWSGuardDutyEventCollector.py +++ b/Packs/AWS-GuardDuty/Integrations/AWSGuardDutyEventCollector/AWSGuardDutyEventCollector.py @@ -2,15 +2,16 @@ from CommonServerPython import * # noqa: F401 from AWSApiModule import * # noqa: E402 -from typing import Tuple +from typing import TYPE_CHECKING, Tuple from datetime import datetime, date -import urllib3.util -import boto3 import json -# Disable insecure warnings -urllib3.disable_warnings() +# The following import are used only for type hints and autocomplete. +# It is not used at runtime, and not exist in the docker image. +if TYPE_CHECKING: + from mypy_boto3_guardduty import GuardDutyClient + CLIENT_SERVICE = 'guardduty' MAX_IDS_PER_REQ = 50 @@ -52,7 +53,7 @@ def convert_events_with_datetime_to_str(events: list) -> list: return output_events -def get_events(aws_client: boto3.client, collect_from: dict, collect_from_default: Optional[datetime], last_ids: dict, +def get_events(aws_client: "GuardDutyClient", collect_from: dict, collect_from_default: Optional[datetime], last_ids: dict, severity: str, limit: int = MAX_RESULTS, detectors_num: int = MAX_RESULTS, max_ids_per_req: int = MAX_IDS_PER_REQ) -> Tuple[list, dict, dict]: """Get events from AWSGuardDuty. @@ -91,7 +92,7 @@ def get_events(aws_client: boto3.client, collect_from: dict, collect_from_defaul response = aws_client.list_detectors(**list_detectors_args) detector_ids += response.get('DetectorIds', []) - next_token = response.get('NextToken') + next_token = response.get('NextToken', '') demisto.debug(f"AWSGuardDutyEventCollector - Found detector ids: {detector_ids}") @@ -124,7 +125,7 @@ def get_events(aws_client: boto3.client, collect_from: dict, collect_from_defaul list_finding_args.update({'NextToken': next_token}) list_findings = aws_client.list_findings(**list_finding_args) finding_ids += list_findings.get('FindingIds', []) - next_token = list_findings.get('NextToken') + next_token = list_findings.get('NextToken', '') # Handle duplicates and findings updated at the same time. if last_ids.get(detector_id) and last_ids.get(detector_id) in finding_ids: @@ -189,7 +190,7 @@ def main(): # pragma: no cover aws_role_policy, aws_access_key_id, aws_secret_access_key, verify_certificate, timeout, retries, sts_endpoint_url=sts_endpoint_url, endpoint_url=endpoint_url) - client = aws_client.aws_session(service=CLIENT_SERVICE, region=aws_default_region) + client: "GuardDutyClient" = aws_client.aws_session(service=CLIENT_SERVICE, region=aws_default_region) command = demisto.command() if command == 'test-module': diff --git a/Packs/AWS-GuardDuty/Integrations/AWSGuardDutyEventCollector/AWSGuardDutyEventCollector.yml b/Packs/AWS-GuardDuty/Integrations/AWSGuardDutyEventCollector/AWSGuardDutyEventCollector.yml index cdea6938301a..a50a8936d475 100644 --- a/Packs/AWS-GuardDuty/Integrations/AWSGuardDutyEventCollector/AWSGuardDutyEventCollector.yml +++ b/Packs/AWS-GuardDuty/Integrations/AWSGuardDutyEventCollector/AWSGuardDutyEventCollector.yml @@ -112,7 +112,7 @@ script: name: limit description: Manual command used to fetch events and display them. name: aws-gd-get-events - dockerimage: demisto/boto3py3:1.0.0.71685 + dockerimage: demisto/boto3py3:1.0.0.75596 isfetchevents: true subtype: python3 marketplaces: diff --git a/Packs/AWS-GuardDuty/ReleaseNotes/1_3_30.md b/Packs/AWS-GuardDuty/ReleaseNotes/1_3_30.md new file mode 100644 index 000000000000..1427e42b37c4 --- /dev/null +++ b/Packs/AWS-GuardDuty/ReleaseNotes/1_3_30.md @@ -0,0 +1,10 @@ + +#### Integrations + +##### AWS - GuardDuty Event Collector + +Updated the Docker image to: *demisto/boto3py3:1.0.0.75596*. + +##### AWS - GuardDuty + +Updated the Docker image to: *demisto/boto3py3:1.0.0.75596*. diff --git a/Packs/AWS-GuardDuty/pack_metadata.json b/Packs/AWS-GuardDuty/pack_metadata.json index 2f9757bdc3d0..609b12cea23c 100644 --- a/Packs/AWS-GuardDuty/pack_metadata.json +++ b/Packs/AWS-GuardDuty/pack_metadata.json @@ -2,7 +2,7 @@ "name": "AWS - GuardDuty", "description": "Amazon Web Services Guard Duty Service (gd)", "support": "xsoar", - "currentVersion": "1.3.29", + "currentVersion": "1.3.30", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "", diff --git a/Packs/AWS-SecurityHub/Integrations/AWSSecurityHubEventCollector/AWSSecurityHubEventCollector.py b/Packs/AWS-SecurityHub/Integrations/AWSSecurityHubEventCollector/AWSSecurityHubEventCollector.py index 4e1a9c810a4a..bc4a732b7ae8 100644 --- a/Packs/AWS-SecurityHub/Integrations/AWSSecurityHubEventCollector/AWSSecurityHubEventCollector.py +++ b/Packs/AWS-SecurityHub/Integrations/AWSSecurityHubEventCollector/AWSSecurityHubEventCollector.py @@ -1,15 +1,16 @@ import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 import datetime as dt -import urllib3 -from typing import Iterator +from typing import TYPE_CHECKING, Iterator, cast from AWSApiModule import * -import boto3 +# The following import are used only for type hints and autocomplete. +# It is not used at runtime, and not exist in the docker image. +if TYPE_CHECKING: + from mypy_boto3_securityhub import SecurityHubClient + from mypy_boto3_securityhub.type_defs import AwsSecurityFindingTypeDef -# Disable insecure warnings -urllib3.disable_warnings() VENDOR = 'AWS' PRODUCT = 'Security Hub' @@ -20,7 +21,7 @@ API_MAX_PAGE_SIZE = 100 # The API only allows a maximum of 100 results per request. Using more raises an error. -def generate_last_run(events: list[dict]) -> dict: +def generate_last_run(events: list["AwsSecurityFindingTypeDef"]) -> dict[str, Any]: """ Generate the last run object using events data. @@ -38,6 +39,8 @@ def generate_last_run(events: list[dict]) -> dict: ignore_list: list[str] = [] last_update_date = events[-1].get(TIME_FIELD) + # Since the "_time" key is added to each event, the event type changes from "AwsSecurityFindingTypeDef" to just dict + events = cast(list[dict[str, Any]], events) for event in events: event['_time'] = event[TIME_FIELD] @@ -50,14 +53,14 @@ def generate_last_run(events: list[dict]) -> dict: } -def get_events(client: boto3.client, start_time: dt.datetime | None = None, +def get_events(client: "SecurityHubClient", start_time: dt.datetime | None = None, end_time: dt.datetime | None = None, id_ignore_list: list[str] | None = None, - page_size: int = API_MAX_PAGE_SIZE, limit: int = 0) -> Iterator[list[dict]]: + page_size: int = API_MAX_PAGE_SIZE, limit: int = 0) -> Iterator[List["AwsSecurityFindingTypeDef"]]: """ Fetch events from AWS Security Hub. Args: - client (boto3.client): Boto3 client to use. + client (SecurityHubClient): Boto3 client to use. start_time (datetime | None, optional): Start time to fetch events from. Required if end_time is set. end_time (datetime | None, optional): Time to fetch events until. Defaults to current time. id_ignore_list (list[str] | None, optional): List of finding IDs to not include in the results. @@ -114,13 +117,14 @@ def get_events(client: boto3.client, start_time: dt.datetime | None = None, break -def fetch_events(client: boto3.client, last_run: dict, first_fetch_time: dt.datetime | None, - page_size: int = API_MAX_PAGE_SIZE, limit: int = 0) -> tuple[list[dict], dict, Exception | None]: +def fetch_events(client: "SecurityHubClient", last_run: dict, first_fetch_time: dt.datetime | None, + page_size: int = API_MAX_PAGE_SIZE, limit: int = 0 + ) -> tuple[list["AwsSecurityFindingTypeDef"], dict, Exception | None]: """ Fetch events from AWS Security Hub and send them to XSIAM. Args: - client (boto3.client): Boto3 client to use. + client (SecurityHubClient): Boto3 client to use. last_run (dict): Dict containing the last fetched event creation time. first_fetch_time (datetime | None, optional): In case of first fetch, fetch events from this datetime. page_size (int, optional): Number of results to fetch per request. Defaults to API_MAX_PAGE_SIZE. @@ -134,7 +138,7 @@ def fetch_events(client: boto3.client, last_run: dict, first_fetch_time: dt.date id_ignore_list: list = last_run.get('last_update_date_finding_ids', []) - events = [] + events: list["AwsSecurityFindingTypeDef"] = [] error = None try: @@ -161,13 +165,13 @@ def fetch_events(client: boto3.client, last_run: dict, first_fetch_time: dt.date return events, next_run, error -def get_events_command(client: boto3.client, should_push_events: bool, +def get_events_command(client: "SecurityHubClient", should_push_events: bool, page_size: int, limit: int = 0) -> CommandResults: """ Fetch events from AWS Security Hub. Args: - client (boto3.client): Boto3 client to use. + client (SecurityHubClient): Boto3 client to use. should_push_events (bool): Whether to push events to XSIAM. page_size (int, optional): Number of results to fetch per request. Defaults to API_MAX_PAGE_SIZE. limit (int, optional): Maximum number of events to fetch. Defaults to 0 (no limit). @@ -241,7 +245,7 @@ def main(): # pragma: no cover retries=retries, ) - client = aws_client.aws_session( + client: "SecurityHubClient" = aws_client.aws_session( service='securityhub', region=aws_default_region, role_arn=aws_role_arn, diff --git a/Packs/AWS-SecurityHub/Integrations/AWSSecurityHubEventCollector/AWSSecurityHubEventCollector.yml b/Packs/AWS-SecurityHub/Integrations/AWSSecurityHubEventCollector/AWSSecurityHubEventCollector.yml index 8b4428e25730..aff5931c94ab 100644 --- a/Packs/AWS-SecurityHub/Integrations/AWSSecurityHubEventCollector/AWSSecurityHubEventCollector.yml +++ b/Packs/AWS-SecurityHub/Integrations/AWSSecurityHubEventCollector/AWSSecurityHubEventCollector.yml @@ -116,7 +116,7 @@ script: name: limit description: Fetch events from AWS Security Hub. name: aws-securityhub-get-events - dockerimage: demisto/boto3py3:1.0.0.71373 + dockerimage: demisto/boto3py3:1.0.0.75596 isfetchevents: true script: '-' subtype: python3 diff --git a/Packs/AWS-SecurityHub/ReleaseNotes/1_3_12.md b/Packs/AWS-SecurityHub/ReleaseNotes/1_3_12.md new file mode 100644 index 000000000000..634718661aef --- /dev/null +++ b/Packs/AWS-SecurityHub/ReleaseNotes/1_3_12.md @@ -0,0 +1,6 @@ + +#### Integrations + +##### AWS Security Hub Event Collector + +Updated the Docker image to: *demisto/boto3py3:1.0.0.75596*. diff --git a/Packs/AWS-SecurityHub/pack_metadata.json b/Packs/AWS-SecurityHub/pack_metadata.json index 09487741ef80..7d99da4da92b 100644 --- a/Packs/AWS-SecurityHub/pack_metadata.json +++ b/Packs/AWS-SecurityHub/pack_metadata.json @@ -2,7 +2,7 @@ "name": "AWS - Security Hub", "description": "Amazon Web Services Security Hub Service.", "support": "xsoar", - "currentVersion": "1.3.11", + "currentVersion": "1.3.12", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "", diff --git a/Packs/AWS_WAF/Integrations/AWSWAF/AWSWAF.py b/Packs/AWS_WAF/Integrations/AWSWAF/AWSWAF.py index 8cffece7b4c8..84ebdaa9a824 100644 --- a/Packs/AWS_WAF/Integrations/AWSWAF/AWSWAF.py +++ b/Packs/AWS_WAF/Integrations/AWSWAF/AWSWAF.py @@ -2,13 +2,18 @@ from CommonServerPython import * # noqa # pylint: disable=unused-wildcard-import from CommonServerUserPython import * # noqa from AWSApiModule import * # noqa: E402 -from typing import Callable, Tuple -import urllib3.util -import boto3 -import urllib3 - -# Disable insecure warnings -urllib3.disable_warnings() +from typing import TYPE_CHECKING, Callable, Tuple, Any, List + +# The following import are used only for type hints and autocomplete. +# It is not used at runtime, and not exist in the docker image. +if TYPE_CHECKING: + from mypy_boto3_wafv2 import WAFV2Client + from mypy_boto3_wafv2.type_defs import ( + RegexTypeDef, + UpdateRuleGroupResponseTypeDef, + RuleOutputTypeDef, + VisibilityConfigTypeDef + ) ''' CONSTANTS ''' @@ -148,7 +153,7 @@ def get_tags_dict_from_args(tag_keys: list, tag_values: list) -> list: return [{'Key': k, 'Value': v} for k, v in zip(tag_keys, tag_values)] -def build_regex_pattern_object(regex_patterns: list) -> List[dict]: +def build_regex_pattern_object(regex_patterns: list) -> List["RegexTypeDef"]: """ Creates a list of dictionaries which represent a regex set object Args: @@ -360,7 +365,7 @@ def build_regex_match_statement(web_request_component: str, } -def build_new_rule_object(args: dict, rule_group_visibility_config: dict, +def build_new_rule_object(args: dict, rule_group_visibility_config: "VisibilityConfigTypeDef", build_rule_func: Callable[[dict], dict]) -> dict: """ Creates a country rule object that can be added to a rule group rules list @@ -425,7 +430,8 @@ def append_new_rule(rules: list, rule: dict) -> list: return updated_rules -def get_required_response_fields_from_rule_group(client: boto3.client, kwargs: dict) -> Tuple[list, dict, str]: +def get_required_response_fields_from_rule_group(client: "WAFV2Client", kwargs: dict + ) -> Tuple[List["RuleOutputTypeDef"], "VisibilityConfigTypeDef", str]: """ Gets all the fields from the response that are required for the update request Args: @@ -440,19 +446,20 @@ def get_required_response_fields_from_rule_group(client: boto3.client, kwargs: d rule_group = response.get('RuleGroup', {}) rules = rule_group.get('Rules', []) rule_group_visibility_config = rule_group.get('VisibilityConfig', {}) - lock_token = response.get('LockToken') + lock_token = response.get('LockToken', '') - return rules, rule_group_visibility_config, lock_token + return rules, rule_group_visibility_config, lock_token # type: ignore[return-value] '''CLIENT FUNCTIONS''' -def update_rule_group_rules(client: boto3.client, +def update_rule_group_rules(client: "WAFV2Client", kwargs: dict, lock_token: str, updated_rules: list, - rule_group_visibility_config: dict) -> dict: # pragma: no cover + rule_group_visibility_config: "VisibilityConfigTypeDef" + ) -> "UpdateRuleGroupResponseTypeDef": # pragma: no cover """ Updates rule group with new rules list""" kwargs |= {'LockToken': lock_token, 'Rules': updated_rules, @@ -465,17 +472,17 @@ def update_rule_group_rules(client: boto3.client, ''' COMMAND FUNCTIONS ''' -def connection_test(client: boto3.client) -> str: # pragma: no cover +def connection_test(client: "WAFV2Client") -> str: # pragma: no cover """ Command to test the connection to the API""" try: - client.list_ip_sets(Scope=SCOPE_MAP[DEFAULT_SCOPE]) + client.list_ip_sets(Scope=SCOPE_MAP[DEFAULT_SCOPE]) # type: ignore[arg-type] except Exception as e: raise DemistoException(f'Failed to execute test module. Error: {str(e)}') return 'ok' -def create_ip_set_command(client: boto3.client, args: dict) -> CommandResults: +def create_ip_set_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to create an IP set""" tag_keys = argToList(args.get('tag_key')) or [] tag_values = argToList(args.get('tag_value')) or [] @@ -503,7 +510,7 @@ def create_ip_set_command(client: boto3.client, args: dict) -> CommandResults: outputs_key_field='Id') -def get_ip_set_command(client: boto3.client, args: dict) -> CommandResults: +def get_ip_set_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to get a specific IP set""" kwargs = { 'Name': args.get('name', ''), @@ -524,7 +531,7 @@ def get_ip_set_command(client: boto3.client, args: dict) -> CommandResults: outputs_key_field='Id') -def update_ip_set_command(client: boto3.client, args: dict) -> CommandResults: +def update_ip_set_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to update a specific IP set""" kwargs = { 'Name': args.get('name', ''), @@ -538,7 +545,7 @@ def update_ip_set_command(client: boto3.client, args: dict) -> CommandResults: get_response = client.get_ip_set(**kwargs) lock_token = get_response.get('LockToken', '') - original_addresses = get_response.get('IPSet', {}).get('Addresses') + original_addresses = get_response.get('IPSet', {}).get('Addresses', []) if not overwrite: addresses_to_update.extend(original_addresses) @@ -555,9 +562,9 @@ def update_ip_set_command(client: boto3.client, args: dict) -> CommandResults: raw_response=response) -def list_ip_set_command(client: boto3.client, args: dict) -> CommandResults: +def list_ip_set_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to get a list of all IP sets""" - kwargs = { + kwargs: dict[str, Any] = { 'Scope': SCOPE_MAP[args.get('scope') or DEFAULT_SCOPE], 'Limit': arg_to_number(args.get('limit')) or 50 } @@ -580,7 +587,7 @@ def list_ip_set_command(client: boto3.client, args: dict) -> CommandResults: outputs_key_field='Id') -def delete_ip_set_command(client: boto3.client, args: dict) -> CommandResults: +def delete_ip_set_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to delete a specific IP set""" kwargs = { 'Name': args.get('name', ''), @@ -600,7 +607,7 @@ def delete_ip_set_command(client: boto3.client, args: dict) -> CommandResults: raw_response=response) -def create_regex_set_command(client: boto3.client, args: dict) -> CommandResults: +def create_regex_set_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to create a regex set""" tag_keys = argToList(args.get('tag_key')) or [] tag_values = argToList(args.get('tag_value')) or [] @@ -628,7 +635,7 @@ def create_regex_set_command(client: boto3.client, args: dict) -> CommandResults outputs_key_field='Id') -def get_regex_set_command(client: boto3.client, args: dict) -> CommandResults: +def get_regex_set_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to get a specific regex set""" kwargs = { 'Name': args.get('name', ''), @@ -649,7 +656,7 @@ def get_regex_set_command(client: boto3.client, args: dict) -> CommandResults: outputs_key_field='Id') -def update_regex_set_command(client: boto3.client, args: dict) -> CommandResults: +def update_regex_set_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to update a specific regex set""" kwargs = { 'Name': args.get('name', ''), @@ -663,7 +670,7 @@ def update_regex_set_command(client: boto3.client, args: dict) -> CommandResults get_response = client.get_regex_pattern_set(**kwargs) lock_token = get_response.get('LockToken', '') - original_patterns = get_response.get('RegexPatternSet', {}).get('RegularExpressionList') + original_patterns = get_response.get('RegexPatternSet', {}).get('RegularExpressionList', []) if not overwrite: patterns_to_update.extend(original_patterns) @@ -680,9 +687,9 @@ def update_regex_set_command(client: boto3.client, args: dict) -> CommandResults raw_response=response) -def list_regex_set_command(client: boto3.client, args: dict) -> CommandResults: +def list_regex_set_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to get a list of all regex sets""" - kwargs = { + kwargs: dict[str, Any] = { 'Scope': SCOPE_MAP[args.get('scope') or DEFAULT_SCOPE], 'Limit': arg_to_number(args.get('limit')) or 50 } @@ -705,7 +712,7 @@ def list_regex_set_command(client: boto3.client, args: dict) -> CommandResults: outputs_key_field='Id') -def delete_regex_set_command(client: boto3.client, args: dict) -> CommandResults: +def delete_regex_set_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to delete a specific regex set""" kwargs = { 'Name': args.get('name', ''), @@ -725,9 +732,9 @@ def delete_regex_set_command(client: boto3.client, args: dict) -> CommandResults raw_response=response) -def list_rule_group_command(client: boto3.client, args: dict) -> CommandResults: +def list_rule_group_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to get a list of all rule groups""" - kwargs = { + kwargs: dict[str, Any] = { 'Scope': SCOPE_MAP[args.get('scope') or DEFAULT_SCOPE], 'Limit': arg_to_number(args.get('limit')) or 50 } @@ -750,7 +757,7 @@ def list_rule_group_command(client: boto3.client, args: dict) -> CommandResults: outputs_key_field='Id') -def get_rule_group_command(client: boto3.client, args: dict) -> CommandResults: +def get_rule_group_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to get a specific rule group""" kwargs = { 'Name': args.get('name', ''), @@ -770,7 +777,7 @@ def get_rule_group_command(client: boto3.client, args: dict) -> CommandResults: outputs_key_field='Id') -def delete_rule_group_command(client: boto3.client, args: dict) -> CommandResults: +def delete_rule_group_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to delete a specific rule group""" kwargs = { 'Name': args.get('name', ''), @@ -790,7 +797,7 @@ def delete_rule_group_command(client: boto3.client, args: dict) -> CommandResult raw_response=response) -def create_rule_group_command(client: boto3.client, args: dict) -> CommandResults: +def create_rule_group_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to create a rule group""" tag_keys = argToList(args.get('tag_key')) or [] tag_values = argToList(args.get('tag_value')) or [] @@ -825,7 +832,7 @@ def create_rule_group_command(client: boto3.client, args: dict) -> CommandResult outputs_key_field='Id') -def create_ip_rule_command(client: boto3.client, args: dict) -> CommandResults: +def create_ip_rule_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to create an ip rule""" kwargs = get_required_args_for_get_rule_group(args) @@ -846,7 +853,7 @@ def create_ip_rule_command(client: boto3.client, args: dict) -> CommandResults: raw_response=response) -def create_country_rule_command(client: boto3.client, args: dict) -> CommandResults: +def create_country_rule_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to create a country rule""" kwargs = get_required_args_for_get_rule_group(args) @@ -867,7 +874,7 @@ def create_country_rule_command(client: boto3.client, args: dict) -> CommandResu raw_response=response) -def create_string_match_rule_command(client: boto3.client, args: dict) -> CommandResults: +def create_string_match_rule_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to create a string match rule""" kwargs = get_required_args_for_get_rule_group(args) @@ -888,7 +895,7 @@ def create_string_match_rule_command(client: boto3.client, args: dict) -> Comman raw_response=response) -def delete_rule_command(client: boto3.client, args: dict) -> CommandResults: +def delete_rule_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to delete a specific rule""" kwargs = get_required_args_for_get_rule_group(args) @@ -908,7 +915,7 @@ def delete_rule_command(client: boto3.client, args: dict) -> CommandResults: raw_response=response) -def add_ip_statement_command(client: boto3.client, args: dict) -> CommandResults: +def add_ip_statement_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to add an ip statement to a rule""" kwargs = get_required_args_for_get_rule_group(args) rules, rule_group_visibility_config, lock_token = get_required_response_fields_from_rule_group(client, kwargs) @@ -928,7 +935,7 @@ def add_ip_statement_command(client: boto3.client, args: dict) -> CommandResults raw_response=response) -def add_country_statement_command(client: boto3.client, args: dict) -> CommandResults: +def add_country_statement_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to add a country statement to a rule""" kwargs = get_required_args_for_get_rule_group(args) @@ -950,7 +957,7 @@ def add_country_statement_command(client: boto3.client, args: dict) -> CommandRe raw_response=response) -def add_string_match_statement_command(client: boto3.client, args: dict) -> CommandResults: +def add_string_match_statement_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to add a string match statement to a rule""" kwargs = get_required_args_for_get_rule_group(args) @@ -972,7 +979,7 @@ def add_string_match_statement_command(client: boto3.client, args: dict) -> Comm raw_response=response) -def add_json_statement_command(client: boto3.client, args: dict) -> CommandResults: +def add_json_statement_command(client: "WAFV2Client", args: dict) -> CommandResults: """ Command to add a json object represents a statement to a rule""" kwargs = get_required_args_for_get_rule_group(args) @@ -1059,7 +1066,7 @@ def main() -> None: # pragma: no cover retries=retries) args = demisto.args() command = demisto.command() - client = aws_client.aws_session(service=SERVICE, region=args.get('region')) + client: "WAFV2Client" = aws_client.aws_session(service=SERVICE, region=args.get('region')) if command == 'test-module': # This is the call made when pressing the integration test button. diff --git a/Packs/AWS_WAF/Integrations/AWSWAF/AWSWAF.yml b/Packs/AWS_WAF/Integrations/AWSWAF/AWSWAF.yml index 04bfeb1e4748..f99c689d24f3 100644 --- a/Packs/AWS_WAF/Integrations/AWSWAF/AWSWAF.yml +++ b/Packs/AWS_WAF/Integrations/AWSWAF/AWSWAF.yml @@ -86,7 +86,7 @@ configuration: section: Connect advanced: true required: false -description: Amazon Web Services Web Application Firewall (WAF) +description: Amazon Web Services Web Application Firewall (WAF). display: AWS-WAF name: AWS-WAF script: @@ -1412,7 +1412,7 @@ script: script: '-' type: python subtype: python3 - dockerimage: demisto/boto3py3:1.0.0.63655 + dockerimage: demisto/boto3py3:1.0.0.75596 fromversion: 6.5.0 tests: - No tests (auto formatted) diff --git a/Packs/AWS_WAF/ReleaseNotes/1_0_3.md b/Packs/AWS_WAF/ReleaseNotes/1_0_3.md new file mode 100644 index 000000000000..f94edbb17a95 --- /dev/null +++ b/Packs/AWS_WAF/ReleaseNotes/1_0_3.md @@ -0,0 +1,6 @@ + +#### Integrations + +##### AWS-WAF + +Updated the Docker image to: *demisto/boto3py3:1.0.0.75596*. diff --git a/Packs/AWS_WAF/pack_metadata.json b/Packs/AWS_WAF/pack_metadata.json index 05d63650e713..d32d3b573c33 100644 --- a/Packs/AWS_WAF/pack_metadata.json +++ b/Packs/AWS_WAF/pack_metadata.json @@ -2,7 +2,7 @@ "name": "AWS WAF", "description": "Amazon Web Services Web Application Firewall (WAF)", "support": "xsoar", - "currentVersion": "1.0.2", + "currentVersion": "1.0.3", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "",