diff --git a/sumologic-app-utils/Test/SampleTemplate.yaml b/sumologic-app-utils/Test/SampleTemplate.yaml index 858140c..e000292 100644 --- a/sumologic-app-utils/Test/SampleTemplate.yaml +++ b/sumologic-app-utils/Test/SampleTemplate.yaml @@ -48,7 +48,7 @@ Resources: Properties: Location: ApplicationId: arn:aws:serverlessrepo:us-east-1:956882708938:applications/sumologic-app-utils - SemanticVersion: 2.0.3 + SemanticVersion: 2.0.5 SumoLogicHelperPolicy: Type: AWS::IAM::Policy @@ -118,7 +118,7 @@ Resources: DependsOn: SumoLogicHelperPolicy Type: AWS::S3::BucketPolicy Properties: - Bucket: "sumologiclambdahelper-us-east-2" + Bucket: "cf-templates-1qpf3unpuo1hw-us-east-2" PolicyDocument: Statement: - Sid: AWSCloudTrailAclCheck @@ -127,14 +127,14 @@ Resources: Service: cloudtrail.amazonaws.com Action: s3:GetBucketAcl Resource: - - "arn:aws:s3:::sumologiclambdahelper-us-east-2" + - "arn:aws:s3:::cf-templates-1qpf3unpuo1hw-us-east-2" - Sid: AWSCloudTrailWrite Effect: Allow Principal: Service: cloudtrail.amazonaws.com Action: s3:PutObject Resource: - - "arn:aws:s3:::sumologiclambdahelper-us-east-2/*" + - "arn:aws:s3:::cf-templates-1qpf3unpuo1hw-us-east-2/*" Condition: StringEquals: s3:x-amz-acl: bucket-owner-full-control @@ -144,7 +144,7 @@ Resources: Service: cloudtrail.amazonaws.com Action: s3:ListBucket Resource: - - "arn:aws:s3:::sumologiclambdahelper-us-east-2" + - "arn:aws:s3:::cf-templates-1qpf3unpuo1hw-us-east-2" SumoCloudTrail: Type: Custom::AWSTrail @@ -153,7 +153,7 @@ Resources: ServiceToken: !GetAtt SumoAppUtils.Outputs.SumoAppUtilsFunction IsLogging: true IsMultiRegionTrail: false - S3BucketName: "sumologiclambdahelper-us-east-2" + S3BucketName: "cf-templates-1qpf3unpuo1hw-us-east-2" TrailName: "Aws-Observability-onemoreupdae" RemoveOnDeleteStack: !Ref Section1eRemoveSumoResourcesOnDeleteStack @@ -225,7 +225,7 @@ Resources: SumoAccessID: !Ref Section1bSumoAccessID SumoAccessKey: !Ref Section1cSumoAccessKey SumoDeployment: !Ref Section1aSumoDeployment - TargetBucketName: "sumologiclambdahelper-us-east-1" + TargetBucketName: "cf-templates-1qpf3unpuo1hw-us-east-1" PathExpression: "asdasd" DateFormat: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" DateLocatorRegex: '.*"updatedAt":"(.*)".*' @@ -266,10 +266,8 @@ Resources: Properties: ServiceToken: !GetAtt SumoAppUtils.Outputs.SumoAppUtilsFunction RemoveOnDeleteStack: !Ref Section1eRemoveSumoResourcesOnDeleteStack - ExplorerName: "Test EXPLORER" - MetadataKeys: - - "account" - - "region" + HierarchyName: "Test Explorer" + HierarchyLevel: {"entityType":"account","nextLevelsWithConditions":[],"nextLevel":{"entityType":"region","nextLevelsWithConditions":[],"nextLevel":{"entityType":"namespace","nextLevelsWithConditions":[]}}} SumoAccessID: !Ref Section1bSumoAccessID SumoAccessKey: !Ref Section1cSumoAccessKey SumoDeployment: !Ref Section1aSumoDeployment @@ -293,7 +291,7 @@ Resources: Properties: ServiceToken: !GetAtt SumoAppUtils.Outputs.SumoAppUtilsFunction Region: !Ref "AWS::Region" - SourceApiUrl: "https://api.sumologic.com/api/v1/collectors/170503459/sources/779514196" + SourceApiUrl: "https://api.us2.sumologic.com/api/v1/collectors/194268335/sources/1135631121" RemoveOnDeleteStack: !Ref Section1eRemoveSumoResourcesOnDeleteStack SumoAccessID: !Ref Section1bSumoAccessID SumoAccessKey: !Ref Section1cSumoAccessKey @@ -353,7 +351,7 @@ Resources: Properties: ServiceToken: !GetAtt SumoAppUtils.Outputs.SumoAppUtilsFunction AWSResource: "vpc" - BucketName: "sumologiclambdahelper-us-east-1" + BucketName: "cf-templates-1qpf3unpuo1hw-us-east-1" Filter: ".*" BucketPrefix: "djvsdvsbdjb" AccountID: !Ref "AWS::AccountId" diff --git a/sumologic-app-utils/deploy.sh b/sumologic-app-utils/deploy.sh index 31b9af3..b6c74ec 100644 --- a/sumologic-app-utils/deploy.sh +++ b/sumologic-app-utils/deploy.sh @@ -26,7 +26,7 @@ if [ ! -f sumo_app_utils.zip ]; then rm -r python fi -version="2.0.3" +version="2.0.5" aws s3 cp sumo_app_utils.zip s3://$SAM_S3_BUCKET/sumo_app_utils/v"$version"/sumo_app_utils.zip --region $AWS_REGION --acl public-read diff --git a/sumologic-app-utils/packaged_sumo_app_utils.yaml b/sumologic-app-utils/packaged_sumo_app_utils.yaml index 1c15533..e14981d 100644 --- a/sumologic-app-utils/packaged_sumo_app_utils.yaml +++ b/sumologic-app-utils/packaged_sumo_app_utils.yaml @@ -20,17 +20,17 @@ Metadata: - sumologic - serverless Name: sumologic-app-utils - SemanticVersion: 2.0.3 + SemanticVersion: 2.0.5 SourceCodeUrl: https://github.com/SumoLogic/sumologic-aws-lambda/tree/master/sumologic-app-utils SpdxLicenseId: Apache-2.0 - ReadmeUrl: s3://appdevstore/sumo_app_utils/v2.0.3/4d5a92c06a7fa9d956a900e51a1f6be4 + ReadmeUrl: s3://appdevstore/sumo_app_utils/v2.0.5/4d5a92c06a7fa9d956a900e51a1f6be4 Resources: SumoAppUtilsFunction: Type: AWS::Serverless::Function Properties: Handler: main.handler Runtime: python3.7 - CodeUri: s3://appdevstore/sumo_app_utils/v2.0.3/sumo_app_utils.zip + CodeUri: s3://appdevstore/sumo_app_utils/v2.0.5/sumo_app_utils.zip MemorySize: 128 Timeout: 300 Policies: diff --git a/sumologic-app-utils/src/sumologic.py b/sumologic-app-utils/src/sumologic.py index bdd59b7..164c9e3 100644 --- a/sumologic-app-utils/src/sumologic.py +++ b/sumologic-app-utils/src/sumologic.py @@ -1,7 +1,9 @@ import json import requests import time -import random +from random import uniform +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.util.retry import Retry try: import cookielib @@ -15,6 +17,9 @@ class SumoLogic(object): def __init__(self, accessId, accessKey, endpoint=None, cookieFile='cookies.txt'): self.session = requests.Session() + retries = Retry(total=3, backoff_factor=0.1, status_forcelist=[502, 503, 504, 429]) + self.session.mount('https://', HTTPAdapter(max_retries=retries)) + self.session.mount('http://', HTTPAdapter(max_retries=retries)) self.session.auth = (accessId, accessKey) self.session.headers = {'content-type': 'application/json', 'accept': 'application/json'} cj = cookielib.FileCookieJar(cookieFile) @@ -48,6 +53,7 @@ def get_versioned_endpoint(self, version): def delete(self, method, params=None, version=DEFAULT_VERSION): endpoint = self.get_versioned_endpoint(version) + time.sleep(uniform(2, 5)) r = self.session.delete(endpoint + method, params=params) if 400 <= r.status_code < 600: r.reason = r.text @@ -56,6 +62,7 @@ def delete(self, method, params=None, version=DEFAULT_VERSION): def get(self, method, params=None, version=DEFAULT_VERSION): endpoint = self.get_versioned_endpoint(version) + time.sleep(uniform(2, 5)) r = self.session.get(endpoint + method, params=params) if 400 <= r.status_code < 600: r.reason = r.text @@ -64,6 +71,7 @@ def get(self, method, params=None, version=DEFAULT_VERSION): def post(self, method, params, headers=None, version=DEFAULT_VERSION): endpoint = self.get_versioned_endpoint(version) + time.sleep(uniform(2, 5)) r = self.session.post(endpoint + method, data=json.dumps(params), headers=headers) if 400 <= r.status_code < 600: r.reason = r.text @@ -72,6 +80,7 @@ def post(self, method, params, headers=None, version=DEFAULT_VERSION): def put(self, method, params, headers=None, version=DEFAULT_VERSION): endpoint = self.get_versioned_endpoint(version) + time.sleep(uniform(2, 5)) r = self.session.put(endpoint + method, data=json.dumps(params), headers=headers) if 400 <= r.status_code < 600: r.reason = r.text @@ -205,6 +214,18 @@ def create_folder(self, name, description, parent_folder_id): def get_personal_folder(self): return self.get('/content/folders/personal', version='v2') + def get_folder_by_id(self, folder_id): + response = self.get('/content/folders/%s' % folder_id, version='v2') + return json.loads(response.text) + + def update_folder_by_id(self, folder_id, content): + response = self.put('/content/folders/%s' % folder_id, version='v2', params=content) + return json.loads(response.text) + + def copy_folder(self, folder_id, parent_folder_id): + return self.post('/content/%s/copy?destinationFolder=%s' % (folder_id, parent_folder_id), params={}, + version='v2') + def import_content(self, folder_id, content, is_overwrite="false"): return self.post('/content/folders/%s/import?overwrite=%s' % (folder_id, is_overwrite), params=content, version='v2') @@ -212,8 +233,10 @@ def import_content(self, folder_id, content, is_overwrite="false"): def check_import_status(self, folder_id, job_id): return self.get('/content/folders/%s/import/%s/status' % (folder_id, job_id), version='v2') + def check_copy_status(self, folder_id, job_id): + return self.get('/content/%s/copy/%s/status' % (folder_id, job_id), version='v2') + def install_app(self, app_id, content): - time.sleep(random.randint(1, 10)) return self.post('/apps/%s/install' % (app_id), params=content) def check_app_install_status(self, job_id): @@ -223,14 +246,17 @@ def get_apps(self): response = self.get('/apps') return json.loads(response.text) - def create_explorer_view(self, content): - return self.post('/topologies', params=content, version='v1alpha') + def create_hierarchy(self, content): + return self.post('/entities/hierarchies', params=content, version='v1') + + def delete_hierarchy(self, hierarchy_id): + return self.delete('/entities/hierarchies/%s' % hierarchy_id, version='v1') - def delete_explorer_view(self, explorer_id): - return self.delete('/topologies/%s' % explorer_id, version='v1alpha') + def update_hierarchy(self, hierarchy_id, content): + return self.put('/entities/hierarchies/%s' % hierarchy_id, params=content, version='v1') - def get_explorer_views(self): - response = self.get('/topologies', version='v1alpha') + def get_entity_hierarchies(self): + response = self.get('/entities/hierarchies', version='v1') return json.loads(response.text) def create_metric_rule(self, content): diff --git a/sumologic-app-utils/src/sumoresource.py b/sumologic-app-utils/src/sumoresource.py index 83a44ce..5b7387b 100644 --- a/sumologic-app-utils/src/sumoresource.py +++ b/sumologic-app-utils/src/sumoresource.py @@ -54,6 +54,7 @@ def is_enterprise_or_trial_account(self): | "2" as sev | "UserPermissions" as threatName | "Recon" as threatPurpose + | toint(sev) as sev | benchmark percentage as global_percent from guardduty on threatpurpose=threatPurpose, threatname=threatName, severity=sev, resource=targetresource''' response = self.sumologic_cli.search_job(search_query, fromTime=from_time, toTime=to_time) print("schedule job status: %s" % response) @@ -498,12 +499,15 @@ def _get_app_folder(self, appdata, parent_id): response = self.sumologic_cli.create_folder(appdata["name"], appdata["description"][:255], parent_id) folder_id = response.json()["id"] except Exception as e: - if hasattr(e, 'response') and e.response.json()['errors']: - msg = e.response.json()['errors'][0]['message'] - matched = re.search('(?<=ContentId\()\d+', msg) - if matched: - folder_id = matched[0] - else: + if hasattr(e, 'response') and e.response.json()["errors"]: + errors = e.response.json()["errors"] + for error in errors: + if error.get('code') == 'content:duplicate_content': + folder_details = self.sumologic_cli.get_folder_by_id(parent_id) + if "children" in folder_details: + for children in folder_details["children"]: + if "name" in children and children["name"] == appdata["name"]: + return children["id"] raise return folder_id @@ -534,6 +538,21 @@ def _wait_for_folder_creation(self, folder_id, job_id): print("job status: %s" % response.text) + def _wait_for_folder_copy(self, folder_id, job_id): + print("waiting for folder copy folder_id %s job_id %s" % (folder_id, job_id)) + waiting = True + while waiting: + response = self.sumologic_cli.check_copy_status(folder_id, job_id) + waiting = response.json()['status'] == "InProgress" + time.sleep(5) + + print("job status: %s" % response.text) + matched = re.search('id:\s*(.*?)\"', response.text) + copied_folder_id = None + if matched: + copied_folder_id = matched[1] + return copied_folder_id + def _wait_for_app_install(self, job_id): print("waiting for app installation job_id %s" % job_id) waiting = True @@ -620,12 +639,30 @@ def create(self, appname, source_params, appid=None, folder_name=None, *args, ** else: return self.create_by_import_api(appname, source_params, folder_name, *args, **kwargs) - def update(self, app_folder_id, appname, source_params, appid=None, folder_name=None, *args, **kwargs): + def update(self, app_folder_id, appname, source_params, appid=None, folder_name=None, retain_old_app=False, *args, + **kwargs): # Delete is called by CF itself on Old Resource if we create a new resource. So, no need to delete the resource here. # self.delete(app_folder_id, remove_on_delete_stack=True) - data, app_folder_id = self.create(appname, source_params, appid, folder_name) - print("updated app appFolderId: %s " % app_folder_id) - return data, app_folder_id + data, new_app_folder_id = self.create(appname, source_params, appid, folder_name) + print("updated app appFolderId: %s " % new_app_folder_id) + if retain_old_app: + # get the parent folder from new app folder. Create a OLD APPS folder in it. + # Copy the app_folder_id to OLD APPS. + new_folder_details = self.sumologic_cli.get_folder_by_id(new_app_folder_id) + parent_folder_id = new_folder_details["parentId"] + backup_folder_id = self._get_app_folder({"name": "BackUpOldApps", "description": "The folder contains back up of all the apps that are updated using CloudFormation template."}, + parent_folder_id) + # Starting Folder Copy + response = self.sumologic_cli.copy_folder(app_folder_id, backup_folder_id) + job_id = response.json()["id"] + print("Copy Completed parentFolderId: %s jobId: %s" % (backup_folder_id, job_id)) + copied_folder_id = self._wait_for_folder_copy(app_folder_id, job_id) + # Updating copied folder name with suffix BackUp. + copied_folder_details = self.sumologic_cli.get_folder_by_id(copied_folder_id) + copied_folder_details = {"name": copied_folder_details["name"].replace("(Copy)", "- BackUp_" + datetime.now().strftime("%H:%M:%S")), + "description": copied_folder_details["description"][:255]} + self.sumologic_cli.update_folder_by_id(copied_folder_id, copied_folder_details) + return data, new_app_folder_id def delete(self, app_folder_id, remove_on_delete_stack, *args, **kwargs): if remove_on_delete_stack: @@ -644,100 +681,87 @@ def extract_params(self, event): "appname": props.get("AppName"), "source_params": props.get("AppSources"), "folder_name": props.get("FolderName"), + "retain_old_app": props.get("RetainOldAppOnUpdate"), "app_folder_id": app_folder_id } class SumoLogicAWSExplorer(SumoResource): - def get_explorer_id(self, explorer_name): - explorer_views = self.sumologic_cli.get_explorer_views() - if explorer_views: - for explorer_view in explorer_views: - if explorer_name == explorer_view["name"]: - return explorer_view["id"] - raise Exception("Explorer View with name %s not found" % explorer_name) + def get_explorer_id(self, hierarchy_name): + hierarchies = self.sumologic_cli.get_entity_hierarchies() + if hierarchies and "data" in hierarchies: + for hierarchy in hierarchies["data"]: + if hierarchy_name == hierarchy["name"]: + return hierarchy["id"] + raise Exception("Hierarchy with name %s not found" % hierarchy_name) - def create_explorer_view(self, explorer_name, hierarchy): + def create_hierarchy(self, hierarchy_name, level, hierarchy_filter): content = { - "name": explorer_name, - "baseFilter": [], - "hierarchy": hierarchy + "name": hierarchy_name, + "filter": hierarchy_filter, + "level": level } try: - response = self.sumologic_cli.create_explorer_view(content) - job_id = response.json()["id"] - print("AWS EXPLORER - creation successful with ID %s" % job_id) - return {"EXPLORER_NAME": response.json()["name"]}, job_id + response = self.sumologic_cli.create_hierarchy(content) + hierarchy_id = response.json()["id"] + print("Hierarchy - creation successful with ID %s" % hierarchy_id) + return {"Hierarchy_Name": response.json()["name"]}, hierarchy_id except Exception as e: if hasattr(e, 'response') and e.response.json()["errors"]: errors = e.response.json()["errors"] for error in errors: - if error.get('code') == 'topology:duplicate': - print("AWS EXPLORER - Duplicate Exists for Name %s" % explorer_name) - # Get the explorer view ID from all explorer. - explorer_id = self.get_explorer_id(explorer_name) - return {"EXPLORER_NAME": explorer_name}, explorer_id + if error.get('code') == 'hierarchy:duplicate': + print("Hierarchy - Duplicate Exists for Name %s" % hierarchy_name) + # Get the hierarchy ID from all explorer. + hierarchy_id = self.get_explorer_id(hierarchy_name) + response = self.sumologic_cli.update_hierarchy(hierarchy_id, content) + hierarchy_id = response.json()["id"] + print("Hierarchy - update successful with ID %s" % hierarchy_id) + return {"Hierarchy_Name": hierarchy_name}, hierarchy_id raise e - def create(self, explorer_name, hierarchy, *args, **kwargs): - return self.create_explorer_view(explorer_name, hierarchy) + def create(self, hierarchy_name, level, hierarchy_filter, *args, **kwargs): + return self.create_hierarchy(hierarchy_name, level, hierarchy_filter) - # No Update API. So, Explorer view can be updated and deleted from the main stack where it was created. - # First have to delete the explorer and then create new. Handling delete again due to CF in delete method. - def update(self, old_explorer_name, explorer_id, explorer_name, hierarchy, *args, **kwargs): - self.delete(explorer_id, old_explorer_name, True) - data, explorer_id = self.create_explorer_view(explorer_name, hierarchy) - print("AWS EXPLORER - update successful with ID %s" % explorer_id) - return data, explorer_id + # Use the new update API. + def update(self, hierarchy_id, hierarchy_name, level, hierarchy_filter, *args, **kwargs): + data, hierarchy_id = self.create(hierarchy_name, level, hierarchy_filter) + print("Hierarchy - update successful with ID %s" % hierarchy_id) + return data, hierarchy_id - # Handling exception as the Explorer is already deleted. # handling exception during delete, as update can fail if the previous explorer, metric rule or field has # already been deleted. This is required in case of multiple installation of # CF template with same names for metric rule, explorer view or fields - def delete(self, explorer_id, explorer_name, remove_on_delete_stack, *args, **kwargs): + def delete(self, hierarchy_id, hierarchy_name, remove_on_delete_stack, *args, **kwargs): if remove_on_delete_stack: - try: - # Backward Compatibility for 2.0.2 Versions. - # If id is duplicate then get the id from explorer name and delete it. - if explorer_id == "Duplicate": - explorer_id = self.get_explorer_id(explorer_name) - response = self.sumologic_cli.delete_explorer_view(explorer_id) - print("AWS EXPLORER - Completed the AWS Explorer deletion for Name %s, response - %s" % ( - explorer_name, response.text)) - except Exception as e: - print("AWS EXPLORER - Exception while deleting the Explorer view %s," % e) + # Backward Compatibility for 2.0.2 Versions. + # If id is duplicate then get the id from explorer name and delete it. + if hierarchy_id == "Duplicate": + hierarchy_id = self.get_explorer_id(hierarchy_name) + response = self.sumologic_cli.delete_hierarchy(hierarchy_id) + print("Hierarchy - Completed the Hierarchy deletion for Name %s, response - %s" + % (hierarchy_name, response.text)) else: - print("AWS EXPLORER - Skipping the AWS Explorer deletion") + print("Hierarchy - Skipping the Hierarchy deletion.") def extract_params(self, event): props = event.get("ResourceProperties") - explorer_id = None + hierarchy_id = None if event.get('PhysicalResourceId'): - _, explorer_id = event['PhysicalResourceId'].split("/") - - # Get previous Explorer View Name - old_explorer_name = None - if "OldResourceProperties" in event and "ExplorerName" in event['OldResourceProperties']: - old_explorer_name = event["OldResourceProperties"]['ExplorerName'] - - hierarchy = [] - if "MetadataKeys" in props: - metadata_keys = props.get("MetadataKeys") - for value in metadata_keys: - hierarchy.append({"metadataKey": value}) + _, hierarchy_id = event['PhysicalResourceId'].split("/") return { - "explorer_name": props.get("ExplorerName"), - "explorer_id": explorer_id, - "hierarchy": hierarchy, - "old_explorer_name": old_explorer_name + "hierarchy_name": props.get("HierarchyName"), + "level": props.get("HierarchyLevel"), + "hierarchy_filter": props.get("HierarchyFilter"), + "hierarchy_id": hierarchy_id } class SumoLogicMetricRules(SumoResource): - def create_metric_rule(self, metric_rule_name, match_expression, variables): + def create_metric_rule(self, metric_rule_name, match_expression, variables, delete=True): variables_to_extract = [] if variables: for k, v in variables.items(): @@ -760,6 +784,9 @@ def create_metric_rule(self, metric_rule_name, match_expression, variables): if error.get('code') == 'metrics:rule_name_already_exists' \ or error.get('code') == 'metrics:rule_already_exists': print("METRIC RULES - Duplicate Exists for Name %s" % metric_rule_name) + if delete: + self.delete(metric_rule_name, metric_rule_name, True) + return self.create_metric_rule(metric_rule_name, match_expression, variables, False) return {"METRIC_RULES": metric_rule_name}, metric_rule_name raise e @@ -963,9 +990,12 @@ def update(self, fer_id, fer_name, fer_scope, fer_expression, fer_enabled, *args } try: fer_details = self.sumologic_cli.get_fer_by_id(fer_id) - - if "scope" in fer_details and fer_scope not in fer_details["scope"]: - content["scope"] = fer_details["scope"] + " or " + fer_scope + # Use existing or append the new scope to existing scope. + if "scope" in fer_details: + if fer_scope not in fer_details["scope"]: + content["scope"] = fer_details["scope"] + " or " + fer_scope + else: + content["scope"] = fer_details["scope"] response = self.sumologic_cli.update_field_extraction_rules(fer_id, content) job_id = response.json()["id"] @@ -1201,7 +1231,7 @@ def check_account(self): is_paid = "Yes" if not is_enterprise: all_apps = self.sumologic_cli.get_apps() - if "apps" in all_apps and len(all_apps['apps']) <= 2: + if "apps" in all_apps and len(all_apps['apps']) <= 5: is_paid = "No" return {"is_enterprise": "Yes" if is_enterprise else "No", "is_paid": is_paid}, is_enterprise diff --git a/sumologic-app-utils/sumo_app_utils.yaml b/sumologic-app-utils/sumo_app_utils.yaml index 07d4361..8b64dca 100644 --- a/sumologic-app-utils/sumo_app_utils.yaml +++ b/sumologic-app-utils/sumo_app_utils.yaml @@ -17,7 +17,7 @@ Metadata: - sumologic - serverless Name: sumologic-app-utils - SemanticVersion: 2.0.3 + SemanticVersion: 2.0.5 SourceCodeUrl: https://github.com/SumoLogic/sumologic-aws-lambda/tree/master/sumologic-app-utils SpdxLicenseId: Apache-2.0 ReadmeUrl: ./README.md @@ -29,7 +29,7 @@ Resources: Properties: Handler: main.handler Runtime: python3.7 - CodeUri: s3://appdevstore/sumo_app_utils/v2.0.3/sumo_app_utils.zip + CodeUri: s3://appdevstore/sumo_app_utils/v2.0.5/sumo_app_utils.zip MemorySize: 128 Timeout: 300 Policies: diff --git a/sumologic-app-utils/sumo_app_utils.zip b/sumologic-app-utils/sumo_app_utils.zip index cc66473..3fad88d 100644 Binary files a/sumologic-app-utils/sumo_app_utils.zip and b/sumologic-app-utils/sumo_app_utils.zip differ