From 74f2534eddabaf0b118fdbd51dbddd4ecedc9d21 Mon Sep 17 00:00:00 2001 From: "Pablo E. Colazurdo" Date: Wed, 7 Jul 2021 18:23:38 +0000 Subject: [PATCH 01/18] Added organization template support --- rdk/rdk.py | 624 +++++++++++++++++++++++ rdk/template/configRuleOrganization.json | 262 ++++++++++ 2 files changed, 886 insertions(+) create mode 100644 rdk/template/configRuleOrganization.json diff --git a/rdk/rdk.py b/rdk/rdk.py index 239da4c9..250963d8 100644 --- a/rdk/rdk.py +++ b/rdk/rdk.py @@ -322,6 +322,33 @@ def get_deployment_parser(ForceArgument=False, Command="deploy"): if ForceArgument: parser.add_argument("--force", required=False, action='store_true', help='[optional] Remove selected Rules from account without prompting for confirmation.') return parser + +def get_deployment_organization_parser(ForceArgument=False, Command="deploy-organization"): + direction = "to" + if Command=="undeploy": + direction = "from" + + parser = argparse.ArgumentParser( + prog='rdk '+Command, + description="Used to " + Command + " the Config Rule " + direction + " the target Organization." + ) + parser.add_argument('rulename', metavar='', nargs='*', help='Rule name(s) to deploy. Rule(s) will be pushed to AWS.') + parser.add_argument('--all','-a', action='store_true', help="All rules in the working directory will be deployed.") + parser.add_argument('-s','--rulesets', required=False, help='comma-delimited list of RuleSet names') + parser.add_argument('-f','--functions-only', action='store_true', required=False, help="[optional] Only deploy Lambda functions. Useful for cross-account deployments.") + parser.add_argument('--stack-name', required=False, help="[optional] CloudFormation Stack name for use with --functions-only option. If omitted, \"RDK-Config-Rule-Functions\" will be used." ) + parser.add_argument('--custom-code-bucket', required=False, help="[optional] Provide the custom code S3 bucket name, which is not created with rdk init, for generated cloudformation template storage.") + parser.add_argument('--rdklib-layer-arn', required=False, help="[optional] Lambda Layer ARN that contains the desired rdklib. Note that Lambda Layers are region-specific.") + parser.add_argument('--lambda-role-arn', required=False, help="[optional] Assign existing iam role to lambda functions. If omitted, \"rdkLambdaRole\" will be created.") + parser.add_argument('--lambda-layers', required=False, help="[optional] Comma-separated list of Lambda Layer ARNs to deploy with your Lambda function(s).") + parser.add_argument('--lambda-subnets', required=False, help="[optional] Comma-separated list of Subnets to deploy your Lambda function(s).") + parser.add_argument('--lambda-security-groups', required=False, help="[optional] Comma-separated list of Security Groups to deploy with your Lambda function(s).") + parser.add_argument('--lambda-timeout', required=False, default=60, help="[optional] Timeout (in seconds) for the lambda function", type=str) + parser.add_argument('--boundary-policy-arn', required=False, help="[optional] Boundary Policy ARN that will be added to \"rdkLambdaRole\".") + + if ForceArgument: + parser.add_argument("--force", required=False, action='store_true', help='[optional] Remove selected Rules from account without prompting for confirmation.') + return parser def get_export_parser(ForceArgument=False, Command="export"): @@ -1470,6 +1497,565 @@ def deploy(self): return 0 + def deploy_organization(self): + self.__parse_deploy_organization_args() + + #get the rule names + rule_names = self.__get_rule_list_for_command() + + #run the deploy code + print ("Running Organization deploy!") + + #create custom session based on whatever credentials are available to us + my_session = self.__get_boto_session() + + #get accountID + identity_details = self.__get_caller_identity_details(my_session) + account_id = identity_details['account_id'] + partition = identity_details['partition'] + + if self.args.custom_code_bucket: + code_bucket_name = self.args.custom_code_bucket + else: + code_bucket_name = code_bucket_prefix + account_id + "-" + my_session.region_name + + #If we're only deploying the Lambda functions (and role + permissions), branch here. Someday the "main" execution path should use the same generated CFN templates for single-account deployment. + if self.args.functions_only: + print ("We don't handle Function Only deployment for Organizations") + return 1 + # #Generate the template + # function_template = self.__create_function_cloudformation_template() + + # #Generate CFN parameter json + # cfn_params = [ + # { + # 'ParameterKey': 'SourceBucket', + # 'ParameterValue': code_bucket_name, + # } + # ] + + # #Write template to S3 + # my_s3_client = my_session.client('s3') + # my_s3_client.put_object( + # Body=bytes(function_template.encode('utf-8')), + # Bucket=code_bucket_name, + # Key=self.args.stack_name + ".json" + # ) + + # #Package code and push to S3 + # s3_code_objects = {} + # for rule_name in rule_names: + # rule_params, cfn_tags = self.__get_rule_parameters(rule_name) + # if 'SourceIdentifier' in rule_params: + # print("Skipping code packaging for Managed Rule.") + # else: + # s3_dst = self.__upload_function_code(rule_name, rule_params, account_id, my_session, code_bucket_name) + # s3_code_objects[rule_name] = s3_dst + + # my_cfn = my_session.client('cloudformation') + + # # Generate the template_url regardless of region using the s3 sdk + # config = my_s3_client._client_config + # config.signature_version = botocore.UNSIGNED + # template_url = boto3.client('s3', config=config).generate_presigned_url('get_object', ExpiresIn=0, Params={'Bucket': code_bucket_name, 'Key': self.args.stack_name + ".json"}) + + # # Check if stack exists. If it does, update it. If it doesn't, create it. + + # try: + # my_stack = my_cfn.describe_stacks(StackName=self.args.stack_name) + + # #If we've gotten here, stack exists and we should update it. + # print ("Updating CloudFormation Stack for Lambda functions.") + # try: + + # cfn_args = { + # 'StackName': self.args.stack_name, + # 'TemplateURL': template_url, + # 'Parameters': cfn_params, + # 'Capabilities': [ 'CAPABILITY_IAM' ] + # } + + # # If no tags key is specified, or if the tags dict is empty + # if cfn_tags is not None: + # cfn_args['Tags'] = cfn_tags + + # response = my_cfn.update_stack(**cfn_args) + + # #wait for changes to propagate. + # self.__wait_for_cfn_stack(my_cfn, self.args.stack_name) + # except ClientError as e: + # if e.response['Error']['Code'] == 'ValidationError': + # if 'No updates are to be performed.' in str(e): + # #No changes made to Config rule definition, so CloudFormation won't do anything. + # print("No changes to Config Rule configurations.") + # else: + # #Something unexpected has gone wrong. Emit an error and bail. + # print(e) + # return 1 + # else: + # raise + + # #Push lambda code to functions. + # for rule_name in rule_names: + # rule_params, cfn_tags = self.__get_rule_parameters(rule_name) + # my_lambda_arn = self.__get_lambda_arn_for_rule(rule_name, partition, my_session.region_name, account_id, rule_params) + # if 'SourceIdentifier' in rule_params: + # print("Skipping Lambda upload for Managed Rule.") + # continue + + # print("Publishing Lambda code...") + # my_lambda_client = my_session.client('lambda') + # my_lambda_client.update_function_code( + # FunctionName=my_lambda_arn, + # S3Bucket=code_bucket_name, + # S3Key=s3_code_objects[rule_name], + # Publish=True + # ) + # print("Lambda code updated.") + # except ClientError as e: + # #If we're in the exception, the stack does not exist and we should create it. + # print ("Creating CloudFormation Stack for Lambda Functions.") + + # cfn_args = { + # 'StackName': self.args.stack_name, + # 'TemplateURL': template_url, + # 'Parameters': cfn_params, + # 'Capabilities': ['CAPABILITY_IAM'] + # } + + # # If no tags key is specified, or if the tags dict is empty + # if cfn_tags is not None: + # cfn_args['Tags'] = cfn_tags + + # response = my_cfn.create_stack(**cfn_args) + + # #wait for changes to propagate. + # self.__wait_for_cfn_stack(my_cfn, self.args.stack_name) + + # #We're done! Return with great success. + # sys.exit(0) + + #If we're deploying both the functions and the Config rules, run the following process: + for rule_name in rule_names: + rule_params, cfn_tags = self.__get_rule_parameters(rule_name) + + #create CFN Parameters common for Managed and Custom + source_events = "NONE" + if 'SourceEvents' in rule_params: + source_events = rule_params['SourceEvents'] + + source_periodic = "NONE" + if 'SourcePeriodic' in rule_params: + source_periodic = rule_params['SourcePeriodic'] + + combined_input_parameters = {} + if 'InputParameters' in rule_params: + combined_input_parameters.update(json.loads(rule_params['InputParameters'])) + + if 'OptionalParameters' in rule_params: + #Remove empty parameters + keys_to_delete = [] + optional_parameters_json = json.loads(rule_params['OptionalParameters']) + for key, value in optional_parameters_json.items(): + if not value: + keys_to_delete.append(key) + for key in keys_to_delete: + del optional_parameters_json[key] + combined_input_parameters.update(optional_parameters_json) + + if 'SourceIdentifier' in rule_params: + print("Found Managed Rule.") + #create CFN Parameters for Managed Rules + + try: + rule_description = rule_params["Description"] + except KeyError: + rule_description = rule_name + my_params = [ + { + 'ParameterKey': 'RuleName', + 'ParameterValue': rule_name, + }, + { + 'ParameterKey': 'RuleLambdaName', + 'ParameterValue': self.__get_lambda_name(rule_name, rule_params), + }, + { + 'ParameterKey': 'Description', + 'ParameterValue': rule_description, + }, + { + 'ParameterKey': 'SourceEvents', + 'ParameterValue': source_events, + }, + { + 'ParameterKey': 'SourcePeriodic', + 'ParameterValue': source_periodic, + }, + { + 'ParameterKey': 'SourceInputParameters', + 'ParameterValue': json.dumps(combined_input_parameters), + }, + { + 'ParameterKey': 'SourceIdentifier', + 'ParameterValue': rule_params['SourceIdentifier'] + }] + my_cfn = my_session.client('cloudformation') + if "Remediation" in rule_params: + print("Organization Rules with Remediation are not handled at the moment") + return 1 + # print('Build The CFN Template with Remediation Settings') + # cfn_body = os.path.join(path.dirname(__file__), 'template', "configManagedRuleWithRemediation.json") + # template_body = open(cfn_body, "r").read() + # json_body = json.loads(template_body) + # remediation = self.__create_remediation_cloudformation_block(rule_params["Remediation"]) + # json_body["Resources"]["Remediation"] = remediation + + # if "SSMAutomation" in rule_params: + # #Reference the SSM Automation Role Created, if IAM is created + # print('Building SSM Automation Section') + # ssm_automation = self.__create_automation_cloudformation_block(rule_params['SSMAutomation'], self.__get_alphanumeric_rule_name(rule_name)) + # json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+'RemediationAction')] = ssm_automation + # if "IAM" in rule_params['SSMAutomation']: + # print('Lets Build IAM Role and Policy') + # #TODO Check For IAM Settings + # json_body["Resources"]['Remediation']['Properties']['Parameters']['AutomationAssumeRole']['StaticValue']['Values'] = [{"Fn::GetAtt":[self.__get_alphanumeric_rule_name(rule_name+"Role"), "Arn"]}] + + # ssm_iam_role, ssm_iam_policy = self.__create_automation_iam_cloudformation_block(rule_params['SSMAutomation'], self.__get_alphanumeric_rule_name(rule_name)) + # json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+'Role')] = ssm_iam_role + # json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+'Policy')] = ssm_iam_policy + + # print('Build Supporting SSM Resources') + # resource_depends_on = ['rdkConfigRule', self.__get_alphanumeric_rule_name(rule_name+"RemediationAction")] + # #Builds SSM Document Before Config RUle + # json_body["Resources"]["Remediation"]['DependsOn'] = resource_depends_on + # json_body["Resources"]["Remediation"]['Properties']['TargetId'] = {'Ref': self.__get_alphanumeric_rule_name(rule_name+"RemediationAction")} + + # try: + # my_stack_name = self.__get_stack_name_from_rule_name(rule_name) + # my_stack = my_cfn.describe_stacks(StackName=my_stack_name) + # #If we've gotten here, stack exists and we should update it. + # print ("Updating CloudFormation Stack for " + rule_name) + # try: + # cfn_args = { + # 'StackName': my_stack_name, + # 'TemplateBody': json.dumps(json_body), + # 'Parameters': my_params, + # 'Capabilities': ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'] + # } + + # # If no tags key is specified, or if the tags dict is empty + # if cfn_tags is not None: + # cfn_args['Tags'] = cfn_tags + + # response = my_cfn.update_stack(**cfn_args) + # except ClientError as e: + # if e.response['Error']['Code'] == 'ValidationError': + # if 'No updates are to be performed.' in str(e): + # #No changes made to Config rule definition, so CloudFormation won't do anything. + # print("No changes to Config Rule.") + # else: + # #Something unexpected has gone wrong. Emit an error and bail. + # print(e) + # return 1 + # else: + # raise + # except ClientError as e: + # #If we're in the exception, the stack does not exist and we should create it. + # print ("Creating CloudFormation Stack for " + rule_name) + + # if "Remediation" in rule_params: + # cfn_args = { + # 'StackName': my_stack_name, + # 'TemplateBody': json.dumps(json_body), + # 'Parameters': my_params, + # 'Capabilities': ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'] + # } + + + # else: + # cfn_args = { + # 'StackName': my_stack_name, + # 'TemplateBody': open(cfn_body, "r").read(), + # 'Parameters': my_params + # } + + # if cfn_tags is not None: + # cfn_args['Tags'] = cfn_tags + + # response = my_cfn.create_stack(**cfn_args) + + # #wait for changes to propagate. + # self.__wait_for_cfn_stack(my_cfn, my_stack_name) + # continue + + else: + #deploy config rule + cfn_body = os.path.join(path.dirname(__file__), 'template', "configRuleOrganization.json") + + try: + my_stack_name = self.__get_stack_name_from_rule_name(rule_name) + my_stack = my_cfn.describe_stacks(StackName=my_stack_name) + #If we've gotten here, stack exists and we should update it. + print ("Updating CloudFormation Stack for " + rule_name) + try: + cfn_args = { + 'StackName': my_stack_name, + 'TemplateBody': open(cfn_body, "r").read(), + 'Parameters': my_params + } + + # If no tags key is specified, or if the tags dict is empty + if cfn_tags is not None: + cfn_args['Tags'] = cfn_tags + + response = my_cfn.update_stack(**cfn_args) + except ClientError as e: + if e.response['Error']['Code'] == 'ValidationError': + if 'No updates are to be performed.' in str(e): + #No changes made to Config rule definition, so CloudFormation won't do anything. + print("No changes to Config Rule.") + else: + #Something unexpected has gone wrong. Emit an error and bail. + print(e) + return 1 + else: + raise + except ClientError as e: + #If we're in the exception, the stack does not exist and we should create it. + print ("Creating CloudFormation Stack for " + rule_name) + cfn_args = { + 'StackName': my_stack_name, + 'TemplateBody': open(cfn_body, "r").read(), + 'Parameters': my_params + } + + if cfn_tags is not None: + cfn_args['Tags'] = cfn_tags + + response = my_cfn.create_stack(**cfn_args) + + #wait for changes to propagate. + self.__wait_for_cfn_stack(my_cfn, my_stack_name) + + #Cloudformation is not supporting tagging config rule currently. + if cfn_tags is not None and len(cfn_tags) > 0: + self.__tag_config_rule(rule_name, cfn_tags, my_session) + + continue + + print("Found Custom Rule.") + + s3_src = "" + s3_dst = self.__upload_function_code(rule_name, rule_params, account_id, my_session, code_bucket_name) + + #create CFN Parameters for Custom Rules + lambdaRoleArn = "" + if self.args.lambda_role_arn: + print ("Existing IAM Role provided: " + self.args.lambda_role_arn) + lambdaRoleArn = self.args.lambda_role_arn + + if self.args.boundary_policy_arn: + print ("Boundary Policy provided: " + self.args.boundary_policy_arn) + boundaryPolicyArn = self.args.boundary_policy_arn + else: + boundaryPolicyArn = "" + + try: + rule_description = rule_params["Description"] + except KeyError: + rule_description = rule_name + + my_params = [ + { + 'ParameterKey': 'RuleName', + 'ParameterValue': rule_name, + }, + { + 'ParameterKey': 'RuleLambdaName', + 'ParameterValue': self.__get_lambda_name(rule_name, rule_params), + }, + { + 'ParameterKey': 'Description', + 'ParameterValue': rule_description, + }, + { + 'ParameterKey': 'LambdaRoleArn', + 'ParameterValue': lambdaRoleArn, + }, + { + 'ParameterKey': 'BoundaryPolicyArn', + 'ParameterValue': boundaryPolicyArn, + }, + { + 'ParameterKey': 'SourceBucket', + 'ParameterValue': code_bucket_name, + }, + { + 'ParameterKey': 'SourcePath', + 'ParameterValue': s3_dst, + }, + { + 'ParameterKey': 'SourceRuntime', + 'ParameterValue': self.__get_runtime_string(rule_params), + }, + { + 'ParameterKey': 'SourceEvents', + 'ParameterValue': source_events, + }, + { + 'ParameterKey': 'SourcePeriodic', + 'ParameterValue': source_periodic, + }, + { + 'ParameterKey': 'SourceInputParameters', + 'ParameterValue': json.dumps(combined_input_parameters), + }, + { + 'ParameterKey': 'SourceHandler', + 'ParameterValue': self.__get_handler(rule_name, rule_params) + + }, + { + 'ParameterKey': 'Timeout', + 'ParameterValue': str(self.args.lambda_timeout) + }] + layers = [] + if 'SourceRuntime' in rule_params: + if rule_params['SourceRuntime'] in ['python3.6-lib', 'python3.7-lib', 'python3.8-lib']: + if self.args.rdklib_layer_arn: + layers.append(self.args.rdklib_layer_arn) + else: + rdk_lib_version = RDKLIB_LAYER_VERSION[my_session.region_name] + rdklib_arn = RDKLIB_ARN_STRING.format(region=my_session.region_name, version=rdk_lib_version) + layers.append(rdklib_arn) + + + if self.args.lambda_layers: + additional_layers = self.args.lambda_layers.split(',') + layers.extend(additional_layers) + + if layers: + my_params.append({ + 'ParameterKey': 'Layers', + 'ParameterValue': ",".join(layers) + }) + + if self.args.lambda_security_groups and self.args.lambda_subnets: + my_params.append({ + 'ParameterKey': 'SecurityGroupIds', + 'ParameterValue': self.args.lambda_security_groups + },{ + 'ParameterKey': 'SubnetIds', + 'ParameterValue': self.args.lambda_subnets + }) + + #create json of CFN template + cfn_body = os.path.join(path.dirname(__file__), 'template', "configRule.json") + template_body = open(cfn_body, "r").read() + json_body = json.loads(template_body) + + remediation = "" + if "Remediation" in rule_params: + remediation = self.__create_remediation_cloudformation_block(rule_params["Remediation"]) + json_body["Resources"]["Remediation"] = remediation + + if "SSMAutomation" in rule_params: + ##AWS needs to build the SSM before the Config Rule + resource_depends_on = ['rdkConfigRule', self.__get_alphanumeric_rule_name(rule_name+"RemediationAction")] + remediation["DependsOn"] = resource_depends_on + #Add JSON Reference to SSM Document { "Ref" : "MyEC2Instance" } + remediation['Properties']['TargetId'] = {"Ref" : self.__get_alphanumeric_rule_name(rule_name+"RemediationAction") } + + if "SSMAutomation" in rule_params: + print('Building SSM Automation Section') + + ssm_automation = self.__create_automation_cloudformation_block(rule_params['SSMAutomation'], rule_name) + json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+"RemediationAction")] = ssm_automation + if "IAM" in rule_params['SSMAutomation']: + print('Lets Build IAM Role and Policy') + #TODO Check For IAM Settings + json_body["Resources"]['Remediation']['Properties']['Parameters']['AutomationAssumeRole']['StaticValue']['Values'] = [{"Fn::GetAtt":[self.__get_alphanumeric_rule_name(rule_name+"Role"), "Arn"]}] + + ssm_iam_role, ssm_iam_policy = self.__create_automation_iam_cloudformation_block(rule_params['SSMAutomation'], rule_name) + json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+'Role')] = ssm_iam_role + json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+'Policy')] = ssm_iam_policy + + #debugging + #print(json.dumps(json_body, indent=2)) + + #deploy config rule + my_cfn = my_session.client('cloudformation') + try: + my_stack_name = self.__get_stack_name_from_rule_name(rule_name) + my_stack = my_cfn.describe_stacks(StackName=my_stack_name) + #If we've gotten here, stack exists and we should update it. + print ("Updating CloudFormation Stack for " + rule_name) + try: + cfn_args = { + 'StackName': my_stack_name, + 'TemplateBody': json.dumps(json_body), + 'Parameters': my_params, + 'Capabilities': ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'] + } + + # If no tags key is specified, or if the tags dict is empty + if cfn_tags is not None: + cfn_args['Tags'] = cfn_tags + + response = my_cfn.update_stack(**cfn_args) + except ClientError as e: + if e.response['Error']['Code'] == 'ValidationError': + + if 'No updates are to be performed.' in str(e): + #No changes made to Config rule definition, so CloudFormation won't do anything. + print("No changes to Config Rule.") + else: + #Something unexpected has gone wrong. Emit an error and bail. + print('Validation Error on CFN') + print(json.dumps(cfn_args)) + print(e) + return 1 + else: + raise + + my_lambda_arn = self.__get_lambda_arn_for_stack(my_stack_name) + + print("Publishing Lambda code...") + my_lambda_client = my_session.client('lambda') + my_lambda_client.update_function_code( + FunctionName=my_lambda_arn, + S3Bucket=code_bucket_name, + S3Key=s3_dst, + Publish=True + ) + print("Lambda code updated.") + except ClientError as e: + #If we're in the exception, the stack does not exist and we should create it. + print ("Creating CloudFormation Stack for " + rule_name) + cfn_args = { + 'StackName': my_stack_name, + 'TemplateBody': json.dumps(json_body), + 'Parameters': my_params, + 'Capabilities': ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'] + } + + if cfn_tags is not None: + cfn_args['Tags'] = cfn_tags + + response = my_cfn.create_stack(**cfn_args) + + #wait for changes to propagate. + self.__wait_for_cfn_stack(my_cfn, my_stack_name) + + #Cloudformation is not supporting tagging config rule currently. + if cfn_tags is not None and len(cfn_tags) > 0: + self.__tag_config_rule(rule_name, cfn_tags, my_session) + + print('Config deploy complete.') + + return 0 + def export(self): self.__parse_export_args() @@ -2434,6 +3020,44 @@ def __parse_deploy_args(self, ForceArgument=False): if self.args.rulesets: self.args.rulesets = self.args.rulesets.split(',') + + def __parse_deploy_organization_args(self, ForceArgument=False): + + self.args = get_deployment_organization_parser(ForceArgument).parse_args(self.args.command_args, self.args) + + ### Validate inputs ### + if self.args.stack_name and not self.args.functions_only: + print("--stack-name can only be specified when using the --functions-only feature.") + sys.exit(1) + + #Make sure we're not exceeding Layer limits + if self.args.lambda_layers: + layer_count = len(self.args.lambda_layers.split(",")) + if layer_count > 5: + print("You may only specify 5 Lambda Layers.") + sys.exit(1) + if self.args.rdklib_layer_arn and layer_count > 4: + print("Because you have selected a 'lib' runtime You may only specify 4 additional Lambda Layers.") + sys.exit(1) + + #RDKLib version and RDKLib Layer ARN are mutually exclusive. + if "rdk_lib_version" in self.args and "rdklib_layer_arn" in self.args: + print("Specify EITHER an RDK Lib version to use the official release OR a specific Layer ARN to use a custom implementation.") + sys.exit(1) + + #Check rule names to make sure none are too long. This is needed to catch Rules created before length constraint was added. + if self.args.rulename: + for name in self.args.rulename: + if len(name) > 128: + print("Error: Found Rule with name over 128 characters: {} \n Recreate the Rule with a shorter name.".format(name)) + sys.exit(1) + + if self.args.functions_only and not self.args.stack_name: + self.args.stack_name = "RDK-Config-Rule-Functions" + + if self.args.rulesets: + self.args.rulesets = self.args.rulesets.split(',') + def __parse_export_args(self, ForceArgument=False): diff --git a/rdk/template/configRuleOrganization.json b/rdk/template/configRuleOrganization.json new file mode 100644 index 00000000..23498105 --- /dev/null +++ b/rdk/template/configRuleOrganization.json @@ -0,0 +1,262 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "AWS CloudFormation template to create custom AWS Config rules. You will be billed for the AWS resources used if you create a stack from this template.", + + "Parameters": { + "RuleName": { + "Description": "Name of the Rule", + "Type": "String", + "MinLength": "1", + "MaxLength": "128" + }, + "Description": { + "Description": "Description of the Rule", + "Type": "String", + "MinLength": "1", + "MaxLength": "255" + }, + "RuleLambdaName": { + "Description": "Name of the Rule's Lambda function", + "Type": "String", + "MinLength": "1", + "MaxLength": "64" + }, + "LambdaRoleArn": { + "Description": "ARN of the existing IAM role that you want to attach to the lambda function.", + "Type": "String", + "Default": "" + }, + "BoundaryPolicyArn": { + "Description": "ARN of a Boundary Policy, will be used only if LambdaRoleArn is NOT set.", + "Type": "String", + "Default": "" + }, + "SourceBucket": { + "Description": "Name of the S3 bucket that you have stored the rule zip files in.", + "Type": "String", + "MinLength": "1", + "MaxLength": "255" + }, + "SourcePath": { + "Description": "Location in the S3 bucket where you have stored the rule zip files.", + "Type": "String", + "MinLength": "1", + "MaxLength": "255" + }, + "SourceEvents": { + "Description": "Event Type", + "Type": "CommaDelimitedList", + "Default": "NONE" + }, + "SourceRuntime": { + "Description": "Runtime Language", + "Type": "String", + "MinLength": "1", + "MaxLength": "255" + }, + "SourcePeriodic": { + "Description": "Execution Frequency", + "Type": "String", + "MinLength": "1", + "MaxLength": "255", + "Default": "NONE" + }, + "SourceInputParameters": { + "Description": "Input Parameters", + "Type": "String", + "Default": "{}" + }, + "SourceHandler":{ + "Description": "Lambda Function Handler", + "Type": "String" + }, + "Layers": { + "Description": "Comma-separated list of Lambda layers to be included with Lambda Function deployment", + "Type": "String", + "Default": "" + }, + "SecurityGroupIds": { + "Description": "Comma-separated list of Security Group Ids for Lambda Function deployment", + "Type": "String", + "Default": "" + }, + "SubnetIds": { + "Description": "Comma-separated list of Subnet Ids for Lambda Function deployment", + "Type": "String", + "Default": "" + }, + "Timeout":{ + "Description": "Lambda Function timeout", + "Type": "String", + "Default": 60 + } + }, + "Conditions": { + "CreateNewLambdaRole" : { "Fn::Equals" : [{ "Ref": "LambdaRoleArn" }, ""]}, + "UseBoundaryPolicyInRole" : {"Fn::Not":[{ "Fn::Equals" : [{ "Ref": "BoundaryPolicyArn" }, ""]}]}, + "EventTriggered" : {"Fn::Not": [{ "Fn::Equals" : [{"Fn::Join": [",", { "Ref": "SourceEvents" }]}, "NONE"]}]}, + "PeriodicTriggered" : { "Fn::Not": [{"Fn::Equals" : [{ "Ref": "SourcePeriodic" }, "NONE"]}]}, + "UseAdditionalLayers": {"Fn::Not": [{"Fn::Equals": [{"Ref": "Layers"}, ""]}]}, + "UseVpcConfig": { + "Fn::And": [ + {"Fn::Not": [{"Fn::Equals": [{"Ref": "SecurityGroupIds"}, ""]}]}, + {"Fn::Not": [{"Fn::Equals": [{"Ref": "SubnetIds"}, ""]}]} + ] + } + }, + "Resources": { + "rdkRuleCodeLambda": { + "Type": "AWS::Lambda::Function", + "Properties": { + "FunctionName": { "Ref": "RuleLambdaName" }, + "Code": { + "S3Bucket": { "Ref": "SourceBucket" }, + "S3Key": { "Fn::Join" : [ "", [ { "Ref": "RuleName" }, "/", { "Ref": "RuleName" }, ".zip"]]} + }, + "Description": "Create a new AWS lambda function for rule code", + "Handler": { "Ref": "SourceHandler"}, + "MemorySize": "256", + "Role": { + "Fn::If": [ "CreateNewLambdaRole", + { "Fn::GetAtt": [ "rdkLambdaRole", "Arn" ]}, + { "Ref": "LambdaRoleArn" } + ] + }, + "Runtime": { "Ref": "SourceRuntime"}, + "Timeout": { "Ref": "Timeout"}, + "Layers": + {"Fn::If": + [ "UseAdditionalLayers", + { "Fn::Split": [",", {"Ref": "Layers"}]}, + { "Ref": "AWS::NoValue"} + ] + }, + "VpcConfig": + {"Fn::If": + [ "UseVpcConfig", + { + "SecurityGroupIds": {"Fn::Split": [",", {"Ref": "SecurityGroupIds"}]}, + "SubnetIds": {"Fn::Split": [",", {"Ref": "SubnetIds"}]} + }, + { "Ref": "AWS::NoValue"} + ] + } + } + }, + "ConfigPermissionToCallrdkRuleCodeLambda": { + "Type": "AWS::Lambda::Permission", + "DependsOn": "rdkRuleCodeLambda", + "Properties":{ + "FunctionName": { "Fn::GetAtt": [ "rdkRuleCodeLambda", "Arn" ] } , + "Action": "lambda:InvokeFunction", + "Principal": "config.amazonaws.com" + } + }, + "rdkConfigRule": { + "Type": "AWS::Config::OrganizationConfigRule", + "DependsOn": [ + "ConfigPermissionToCallrdkRuleCodeLambda" + ], + "Properties": { + "OrganizationConfigRuleName": { "Ref": "RuleName" }, + "OrganizationCustomRuleMetadata": { + "Description": { "Ref": "Description" }, + "InputParameters": { "Ref": "SourceInputParameters" }, + "LambdaFunctionArn": { "Fn::GetAtt": [ "rdkRuleCodeLambda", "Arn" ] }, + "ResourceTypesScope": { "Ref": "SourceEvents" } + { "Fn::If": [ "PeriodicTriggered", + { + "MaximumExecutionFrequency": { "Ref": "SourcePeriodic" } + }, + { "Ref": "AWS::NoValue"} ] + } + }, + } + } + }, + "rdkLambdaRole": { + "Condition": "CreateNewLambdaRole", + "Type": "AWS::IAM::Role", + "Properties": { + "Path": "/rdk/", + "PermissionsBoundary": {"Fn::If": [ "UseBoundaryPolicyInRole", + { "Ref": "BoundaryPolicyArn" }, + { "Ref": "AWS::NoValue" } + ] + }, + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ { + "Sid": "AllowLambdaAssumeRole", + "Effect": "Allow", + "Principal": { "Service": "lambda.amazonaws.com" }, + "Action": "sts:AssumeRole" + } ] + }, + "Policies": [ { + "PolicyName": "ConfigRulePolicy", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "1", + "Action": [ + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": { "Fn::Sub": "arn:${AWS::Partition}:s3:::${SourceBucket}/${SourcePath}" } + }, + { + "Sid": "2", + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Sid": "3", + "Action": [ + "config:PutEvaluations" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Sid": "4", + "Action": [ + "iam:List*", + "iam:Describe*", + "iam:Get*" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Sid": "5", + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Resource": "*" + } + ] + } + } ], + "ManagedPolicyArns": [ + { "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/ReadOnlyAccess" } + ] + } + } + }, + + "Outputs": { + "RuleCodeLambda": { + "Description": "ARN for the Rule Code lambda", + "Value": { "Fn::GetAtt": [ "rdkRuleCodeLambda", "Arn" ] } + } + } +} From 0583f5be93e13a43aabd793e535d1a4501a2eacb Mon Sep 17 00:00:00 2001 From: "Pablo E. Colazurdo" Date: Wed, 7 Jul 2021 20:45:17 +0000 Subject: [PATCH 02/18] deploy-organization initial working model --- rdk/__init__.py | 2 +- rdk/rdk.py | 8 +++++--- rdk/template/configRuleOrganization.json | 11 +++-------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/rdk/__init__.py b/rdk/__init__.py index 14c7ba8a..a9ce7c4f 100644 --- a/rdk/__init__.py +++ b/rdk/__init__.py @@ -6,4 +6,4 @@ # # or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -MY_VERSION = "0.8.0" +MY_VERSION = "0.8.0-pab" diff --git a/rdk/rdk.py b/rdk/rdk.py index 250963d8..bc78873f 100644 --- a/rdk/rdk.py +++ b/rdk/rdk.py @@ -222,7 +222,7 @@ def get_command_parser(): parser.add_argument('-r','--region',help='Select the region to run the command in.') #parser.add_argument('--verbose','-v', action='count') #Removed for now from command choices: 'test-remote', 'status' - parser.add_argument('command', metavar='', help='Command to run. Refer to the usage instructions for each command for more details', choices=['clean', 'create', 'create-rule-template', 'deploy', 'init', 'logs', 'modify', 'rulesets', 'sample-ci', 'test-local', 'undeploy', 'export']) + parser.add_argument('command', metavar='', help='Command to run. Refer to the usage instructions for each command for more details', choices=['clean', 'create', 'create-rule-template', 'deploy', 'deploy-organization', 'init', 'logs', 'modify', 'rulesets', 'sample-ci', 'test-local', 'undeploy', 'undeploy-organization', 'export']) parser.add_argument('command_args', metavar='', nargs=argparse.REMAINDER, help="Run `rdk --help` to see command-specific arguments.") parser.add_argument('-v','--version', help='Display the version of this tool', action="version", version='%(prog)s '+MY_VERSION) @@ -1833,6 +1833,7 @@ def deploy_organization(self): if cfn_tags is not None: cfn_args['Tags'] = cfn_tags + print(json.dumps(cfn_args, indent=4)) response = my_cfn.create_stack(**cfn_args) #wait for changes to propagate. @@ -1951,7 +1952,7 @@ def deploy_organization(self): }) #create json of CFN template - cfn_body = os.path.join(path.dirname(__file__), 'template', "configRule.json") + cfn_body = os.path.join(path.dirname(__file__), 'template', "configRuleOrganization.json") template_body = open(cfn_body, "r").read() json_body = json.loads(template_body) @@ -2042,7 +2043,8 @@ def deploy_organization(self): if cfn_tags is not None: cfn_args['Tags'] = cfn_tags - + + print(json.dumps(cfn_args, indent=4)) response = my_cfn.create_stack(**cfn_args) #wait for changes to propagate. diff --git a/rdk/template/configRuleOrganization.json b/rdk/template/configRuleOrganization.json index 23498105..e2c6c34a 100644 --- a/rdk/template/configRuleOrganization.json +++ b/rdk/template/configRuleOrganization.json @@ -163,14 +163,9 @@ "Description": { "Ref": "Description" }, "InputParameters": { "Ref": "SourceInputParameters" }, "LambdaFunctionArn": { "Fn::GetAtt": [ "rdkRuleCodeLambda", "Arn" ] }, - "ResourceTypesScope": { "Ref": "SourceEvents" } - { "Fn::If": [ "PeriodicTriggered", - { - "MaximumExecutionFrequency": { "Ref": "SourcePeriodic" } - }, - { "Ref": "AWS::NoValue"} ] - } - }, + "ResourceTypesScope": { "Ref": "SourceEvents" }, + "OrganizationConfigRuleTriggerTypes": [ "ScheduledNotification" ], + "MaximumExecutionFrequency": { "Ref": "SourcePeriodic" } } } }, From 825fc25c0383fe2f8ba8fd3c1314aad62cb65664 Mon Sep 17 00:00:00 2001 From: Chau Date: Wed, 7 Jul 2021 16:58:56 -0400 Subject: [PATCH 03/18] Add config opscenter integration example --- .../.DS_Store | Bin 0 -> 6148 bytes .../AWS_Config_and_OpsCenter.pdf | Bin 0 -> 121438 bytes .../AWS_Config_and_OpsCenter.xml | 1 + .../README.md | 36 ++++++++++ .../build.sh | 9 +++ .../cleanup.sh | 5 ++ .../opsitem-role.yaml | 43 ++++++++++++ .../s3EncryptedConfigRule.yaml | 62 ++++++++++++++++++ .../rdk-workshop}/WorkshopSetup.yaml | 0 .../rdk-workshop}/instructions.md | 0 10 files changed, 156 insertions(+) create mode 100644 add-ons/config-opscenter-integration-example/.DS_Store create mode 100644 add-ons/config-opscenter-integration-example/AWS_Config_and_OpsCenter.pdf create mode 100644 add-ons/config-opscenter-integration-example/AWS_Config_and_OpsCenter.xml create mode 100644 add-ons/config-opscenter-integration-example/README.md create mode 100755 add-ons/config-opscenter-integration-example/build.sh create mode 100755 add-ons/config-opscenter-integration-example/cleanup.sh create mode 100644 add-ons/config-opscenter-integration-example/opsitem-role.yaml create mode 100644 add-ons/config-opscenter-integration-example/s3EncryptedConfigRule.yaml rename {rdk-workshop => add-ons/rdk-workshop}/WorkshopSetup.yaml (100%) rename {rdk-workshop => add-ons/rdk-workshop}/instructions.md (100%) diff --git a/add-ons/config-opscenter-integration-example/.DS_Store b/add-ons/config-opscenter-integration-example/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..44117d1f172b4c27e8c7b27de3ffd49cac053d4e GIT binary patch literal 6148 zcmeHKJx{|h5PdF{XkjTELJS#Mh=dqf($WqL2&o+yOWU+iDg98P5^P!c3;YLm{uw_2 z-q}{wB<;e05W17>XJ78lch6BA12BzcdTkB3{#9S z#(lwB6@QTd*}G%Zaf=SRSl_=cdfdyH+8fOA$Y(>&pV%)DQWIlzmCOeHWY%X4d4cL| zZ(mk8`dQp(OpxT_e&pg-(8rSRSi&4aX7-S?zo7leS{{2C&~|9+Su*oYKJPi71HQ>p z_1IQC!6~br*(f1i7^#q@dCf(rqKy_gY2um~(j(61X+CovM_limwo~T2;3{*wcAVLj zHY=A}wq#N2I4*{ECsOCt@N4r%r#rAxXIL_oShxY|KDp>Y?@3Ib=2fE5JtI9Qoco7EwDR zJqH^*0G*tTo0+Ah9t((>74V2oPS3#1+QG)&ln)?f?O>Q~(+PHdb9WkS^CF zdVuhgCzeJJjPzy999TdcT+AGt;2UM`tIEkT0xZoei~y2G1{OA8K~YmX8!ICg9yVrH zW+0G@otcdd2vE^8*0VGFej`0Hi=v&4p`(Ej*b)^BGd&it!2l~BHt>aO3(ah-#q=DE z0CZx!?5yl;tX!;I?5sfWx4W#YwBR4GoSco}pG5=&kd3Sjul(lt;lF_%fDM^NL=M0r zZ)0brXZb@2^g~Eg_)6$TR8|zgip(M_ef|5vRlA%l0Eo<@A`Eb_b2R$BF3umiR9&AK z0az6E92|`7tif*Ie7SCA2kX*vurqT3XtA>~b8z#3Sb4xVvI3ZX{F8%~l^Mj&&B*}( zv9mIRKY`ldXPfF7npv9w!2K!VXl>vCM(gUkC@VmVnT>-Bz|6)C1TeD$Il!`_Y`fQ;QPLF@&6?>+?>o{UT^~0{{I5aRpSdXeR20}cO`wST(1iy?4w%!%ys3dJ5^>z zIPu$n4yo+UBd8&7!AG_;v>e4Jd^g6Ej=5)5-JfT8*tdQ?q-q;s*68P=1d}iu+j^*3sT*}PI zMB}%(weG^mi7wcYi`CY~^lvZHl+p3k!V`zn1^C6*06bO)^`WG)8_aj!t1PQ_CB?+L)LcVHho zxFD5zkp%WlXP9oBZSfv+Hf`5e=r$+`Ty(rwxR{bi98C?WwOtZ&DKVo#{8UN|FF1mg zTOja--JVA!+Bo~d?QP!2-ggaMR@E&rHY6~kt6SW5cZ?0JPs~bq<+)l6R_z5#$821; zDg9RuM)@?;*OIitHT4AT?kW^VR$6$RtKv=E^UtM(^_*IhpDY%uN*_EI%01)D^>%CA zs>Dht%MUB`$lKzQ_I|N-@m2MrZim)foluKcv>n%b|4i_F)5iO}JHv*|yRvh+(QE(A z_H1?GTbrP_=jFcZHtU7y7RPJey7CEA6n3w!UfP02RT~zB6a_u^Ev4BJx(DU#98mOO37q}+>`eLjWg;z94mk?tHE8we)(xq~WMgmzo-q^_Icbv5EjYhfZG1}wvky?jxhIGMLpxP1 zOavZ{O`sN?54V`Rpn?X%IxZO0Izarr;&3=LvnR0V0d}{{Gde(+`5e18Ttu%qL)uHb zUgmvxL^dtPQy190wWqqeeQ(nB`IIM(KCkGwC4bLiLQ3~%=vzTC0i1{dI}nmc5~R&J=y&%K`^T_m@=c5yC)k}eSdLW zfP{G1iD8nwKqnnA)rR$%#u9C5L)e_skjWk0i5I)p-oi{u-Vp!X`bR~ce(f0oDU#rhBauUbLaqChUNt$u={er4+HDPO~bs&%H<&oxV-s0auIK!^qz!eS{HN=dCq zf((Ak8D{(r{l);LRF5N!;?(SxJM|6@r%U>3`IJIw`eW_urURvqN7>JN5y6w*sJR^p28}UQsjyPJC4|8o?(_6ynnz|mv})d@+i=T;9PP2%)NFdM45QUtbJ8AIsuD zuj+AS;GKc$dXB>p2&G4&nDs^nVf|SM5ToT%t@_hAyK^TUtcR*3Tdr$lA{}2*Db@?S za2?ipk?^g`0^9@(jE=a~bDb1#CwB#pwk#mKb9+I&fzDewmxA|x4u`mgk4JY&eTNx$ zzs5(*X&?Bs0E-Dxpc@67Qvvc}j$UeWnBF!qEz&BBLF<0$PA3Epv5Gi95Od395%a>H zEcXo)RT@nSYfH*!;YG^xn?J$+Hk z3LSP*H3B0Pp9RNyRrx2+^|5If~Z>8)5HLxyAJhM#R@<~ z7wG1y8rbD$hg!X^i+iI{vTKUehYF^%Vn(o|&}D72eD2iY?LJ1SlL-XWh+gUHCcYh8 zx~rJ5$~9s1hspJAxtXXGXNsbW>5_BuiA6)Arj%$=-JfxRjfKiJY@2z9ovZ|eMGASJ zl$-Md^W`?Eh=cHGw{{2c+>tt!9&)3BTFS%5eDm~R5fo90Uq_qE97iNR%jqd;F=^gK>8u4n?a);VO^lW}PI6rdClvwJBsCQOTTh!jz=D6sF z;cW{mj*~9An~1Kf_C#7qFs0ry1g(HYXZV%CV|9B8%7mEIv5aAl5e5fc&T^If$xkr3T z4VcR;8}tTeqCU!UG$oeWy;Cph<$b1L$`Y@Y%7B6XDiSj@i@MXnlYuYq!YBoL8sbbR zQA%H>DJXZQ%{IkY3hXUjGCNP?*P&>XFjL;Ot-wssSqx_er9H0b38|+qxHCGP5MbRB z5g3cIAhY{)(=K+n_q~>%6{F7>j11OgZHyN-bL)WGK+bB$q&Gqy!;t=SxX}D~&#Kfh zm2T2CG0OQ{#Gf6ck?eKmxuFpF5^|G688}UfnRhfiRYwtu;4L?6lolCB?r7TZQW znDaQQA#dE$mKf@((RZ86CG(ezOsD5;J!Y?fGzs3`iXP0&LbC#_+9%;i_ApIUp33wSVba^CcLXhEwx zH<{i}1icev*qZ=#OR z%=82}h1*p}8tc=uvFDydUdX)J4pThwsc8g;F{RZ;ZH%B4qm5g0QS6S6XRvTg1&_*$ zf&oco+unOA8jTqIhTx2n(tPVv*2-Cpi22FuEso3a++j`tB3tW zo1}K8qUe%T|LzdYX?4&m#pfb<@@(SK<0DiRN?c;^H$rl-v0bot@|@}8q(DY+oNj3=k1T%&o%XaV zFYIlH=8WIML%oLGvq5z4O>GUt`bIe}{5@LvSyw*EEQi`xo@5~IkA%)WLm|rv=$$%! zZhV9VY2R2%2RlwI`nlGku=dU-YG%ClS;)S1+Q_5Kcn0+(+j!kW7SJ9P!-8hZUw+OR zsmxnFn>=hlz!PmdKeDW#WYGaAc+yNj{snnwP|K3427M|h>x+miKw~PSvJj} zi8cj}YAp=noTvmgYTajAU!xJV^`EunV9;Fba$j7)ZfbT};1nkq6WPd0ztlM!8Mexy zR;ck(J6thqE?iKnYRgLKy;UZgN1)f0Rz$SmZyv-r*%t-B{#<;| zlpwBDw3qT*_7=aZH^GI5K*k*JZ+qV41QN!T!` zw8lo^+b~IlRPO~6d%wmEplFi9r>@2uhWb#_KXx|@Sz={is%CxPRAs|~*pG|%DFmA! zCrHKF-3Pe>3)SP@Q7#p9Jq&s@zhwPqiv4FqNYtd37J!F>~mwR{{sxaaoyC;H>P{ zUBY6OLm1VEmAtK}wvGWB-W*czW;s9B!3&$Bkazdj61H25B0F+T5A}!0hK^0+#Y^ID z(M=1=zsIOo&L35gfq(vSI@($)u}rJ4;iavJ42II$y9?ohZO34Y02dtl_Smz*4**I( zd8-)RC+Y^-6FBaUMlm(;B(2r&3-RudYm!ty73UPzkW@CPn_20rD;0{@uk~Lwh86D| zq?EUIHhL=gRL5ACzU*@T9usw6*V0kTii1oT z8r5B`P|OECFHfwIoqINltnt_TW50}6C3f9aiNRL1I{b9I5qnzgfm{TJYD|&-*exNv z_50liFvgbYm6tUGFDD}rhG8!J?}jnnUAqiUQ7a-Oqln24`(Qr*AQ@h$)*8v;`52*k zkSlI=w3542aD#3t(g5-4Xm{=^)?0#(DfqW;>B!;rB${Jn%alJX+O5qg>Bh(Q)JIkS?2MJ{O(sSIWPyjhMOAN2#VOsSFYxm8eSA-$J}iOevg2 z^U0V;zLj`c$JQ!->MIA)M;F!SX;P{U53;*i*t#>NE5~RC8K!qL0jap0RVm6Hj)_Ps z)owe(l*p~7z3m{AWgM|;={*w}wmVf>uk=iV@VNv~K|9ZgMgmpJ^A8M;d#XS4yKaK| z+0WW*n8!TV7iML#q4xU{XoZqGH2b1A>Rc>h13Q$fV3bdLUm>Q>kOm-WnIU$D6&gRf zZ7!F-C!$POs$cGdEq?!Dfc=P5rc~JotRIciNza65fA&uFaD`<@7Htc9Pr1I4p8|Kb zbFuinHxU;MQzJApVfzn`wMpRi{9}y`OAsE_(1_6RJz2grx1_XcDKu@xg~l{eG?{i@cVkEK?km z(yHEr4-@SMU2&~Y1XS2t!?lkt(f1AP`fXad9Lz#^NNQZ;WB3~Hh|a6M;(35xTScfZ z-~Ql4HhOh7k62&i*|0IYo1!D73t0$$0WUYhHu0p#gMC}m&&0<_fseU!F7#@Nk{^vJ zF5WLdp|b2is~zL!a>j71;SHlSspEbYPmgHQf9i)U9@iv4spZhavv(!>pX>)oNNT`VE=WSSU@H zOiCu_@5WY1MWM19w&v>Y_-R&89v&AFxv1L7fTaa~(4sZ%uOj z{?xM4&e~hr@*&=q2Z39!+KfaF>t!k%NGo28>Mhne7fX`h3#Maz@*!{8JnN~%QY6cl z_{hKS|9~Ui%AOyQ4?R!G`k1!g%kvvvwM*SNk?BzYPM~X4>oarIIP|Zz#gfnxs*kL1 zVO9)pK2m0ncpHQowxKli@`Y*Nm)vx8IjS1{YGjnM%{qz2lI6~1yRy4vm7fXC*gpE) zCOjAV%9vLke4Ez$K7Heh=7a;@>IovgeLOCl(BP5{{<@K5-1;b23-LeOdZt)%TTyQveOlmn6vULDN45<^ z8jr_>98jt*D|E{lGdDa3!#w^vkApWB2^!O&$Bn*#X?wtHh7tl7T&* z0pi#)D31veP_&<5q~LXn8wP%MZ3|jHwL*ught0rKkuPg=C~6FH4?bq@vbRv2lbht( zLuMhpt<+1?Og7Py)YHjIXt)|f4P)?0;`BtMwhyiIY0*ayaLf&@!$RYJt2S$GCrZr9 z;k@BRJ;9zcg{UO@AeM`WVpLQWG~fCb?;)Z5Il7<7)o?0nRrGe;BE*Fe|MV<4G66Xl&w@XjyBvhyNfhj?0pcdF+@ z^AxOn>*&u#l!-`@HcPIq|c+aqFU$aeefhnDC~Z=>r` ze=LYR;Pz^qpZd7TKdar@$%BHR+Bw+StTZq~>SpiEF`w|xzjX8KlbPPbd<{1X$)!l)B@8ZxJge(SL7A zbuNG^KMg~=!IRuXzjJWO)jS98f-dsD#tL`(=1Lkj{o1JR&KfqwSr>W9z+Oy8T2XlF z6wtO=Zeo(mR_nI^XfhkyQExxO!(?|{ymJavJ+3+abLEf0+Ac(m!yNn(yQE%)Ca!Ls zuw~{TJH0V19=i?SJLs6VCOa0q+Km=PEcEVRvdz>&(SzmJcyXzkHJi z?dfHJ_R^I;Z{s{L63-5fkuR9J| zAnE+vv-(H5tnI1!KDrWg!~3M9YjbL~Oow4xVLJjTg&(nwlqhr@={*`puP&W@S-Y34 z+W4}yl5F=3R}%i%PxOT5&b0S)I{O7usvHe zoFit+kK2<{*e3Tm!D<{jtVr@3OCvATu&vk6T#HG@rsMAQ=%zSNOxt`x{nk@U)@!qI z=2o+NkzHwSE1r=z;RUffvD{<38&al=!mNv_ZCUuh=xhE7hXpH?SQ;mm+(<5YJ!JX89eE#a zDWel4N&Atonf=+Q!u_L%ZkM9OAdy9(?A;(?&k0VrF04kUiDEm%84iPiltsKC+ibqc zH{_VyFt_INd{)*z50)f8_Iz8oj=H4kw9%du->rxI7HUPVcBKMF^fJs}Z2N4*l7VvA z1Et`RygiPinQ8sTOaG>o`e}PTcX+@-olMKUXqoxYla;6hDSmt(J6;ef!AM3`GOod*X{C)M3Hv zfTp_vdMV{~Xpd{oK9gT22Vy=N{#tBC__m0})8e7iVk_Ga|LqE+888wr^o+d7O{{jP@eF96Zuf_Cr@x z7z|*1)HFtOHi&#qM|zS4qHVw@um3=ym26_1RsH16V>}Ut3g{GZ#;0*kkFyCy9PNak zZgOD{zV7Tgyj!5G!x4ngW*}PFi_k0{1Y77N@bj)%Jq7W-C;*DceQnMJ-QNu{+hJ>eoW8>B1f~SiWfV>j|E1`0%OLNZU*= zy?~&?1x~J_=y76%hspiwi>A=Cb-Q}Im!~{uuRVqqNoJX|3F1Uv{w(6(Wsa1m#uNR5^Y zCfTjNw<*oF)^4z@wx`BjC3Vze&KwKAoo5y82oQ>ue}PYUj9!QwkqTa8#5dqAj5{$i+c z2eHaS73$mN64nz}4quLUD~mwSiCM`)tKF~Y43c^bWs(_I$2GSL%!tc7WCYK<6nims zc4&J?Z94lhOjtK@S7g`2AL!T8rO@nk2kFpK2jEF8m$oLqV$e`4J6Z{-2~k448^h6+ z#vExFtiUlCE@D?}p(Ok!x(w|h8B@dg3oe#no;i42-q17PN|)cL+WfT7hIZx(O#7Vy z>BhspGnGj_oeZ*Un^=mcuvQT`bu!Qa(|nr+xHOGZiq0{0&D9fkX+##$jZjIe5d3^Z zOippAwlQ>(snN)s@0?aj*Rbt}G=Eu{kxF0$S@M(2Ny|^ZMlr&>^8&7YG;@j<4fiFx z2xDN~^G)mN+wFGicEbGJsCWlZsYS>i2_`cG&|S+z&cpadpc8Owq@G7_JJkA3G>5Ms z%od+JMS=SF*WXPB2qOa0lhM!>wga`BJd#ab9Q$nS4xQLpVxXx%HS-XJGssW&{qiMt zNh0p$v+S=Zp%a&}QDJLg7&A!fE3CVvkrK}YjTmkN;c z0fjPON`dTCwP%kpsZ2zjs=^|-G9z3X$k)qle78TloNpd0pUlqPkOqn`Rvf8K<2m{! z-1>G?VUNj{8@1E z(N77a%jI(-Q;UKo5n(D;6zVnTCCzq7L_Jq;i8Z;9k34uV>NtP&koSqUABAU@XXt1a zx35N+VF;gpLLCmZs-*kg!1JM7{OZChs=IAS1kpB+FIexqiK|%$a`dXcc|mdRM&4Y` z7&c}6Oou{BS+eU1)x#u}pj9sIn(#u~l}!U_V&qoUubZWLwJ77r$Y_>PP97iqXmwi2 zaa8+eZqp|!1h`Odr?Q!7BbH|!aC`{1a0|dD7dM5H?n+iHi|3~$)ibuq5Y1O6tK?i) zVX15@)gHLu*ldzvGa=S!*F)G1TQQng4ICeC2oDD*=5(Rdf$t)=G$Fv zN`+i8Mv*MBBnXtb*w+Y>XcA zyYk&tRx?yQjk_|0%)3Fmq2wm9-vnS69HA9O0?4K`aqvH-$;6aKH}YuVCn~{f zyEqDW%N5bUu+(Q7@hkzI2?u(+wwSsquovlyG}WWSt=lGJZ{d*H3<=P~g}I&dLkDUC$g@?#Q@Y=O)X6EoY$1C7&Ob|b?bzv#*BQSClfoOjuOr*?lpe0%?BzQ( zCrG*?qu%vOb<$a!Aj@~2vgi8|hmoX(dsoL`qH$zY@hPWJD4N~%W&d>E#1jt!Dn#U$ zPLM_%=9$U97RarKHRLIM(74~CN#T~SbyRNPlmR?-^xmK|G8r)sa>juBywRSmwdV*E z#KJFHU|_h0r2G8a4$&PA69H#=#KpG09m_Y0~K>-7mET#rJkR8J^VeI@6!$!@p)5mMW3%M?HuV;Jn*w=v#7%ljLH#sYJ7q z0zhZCb<^FmNLsYhm1aHleW+U&I7Z~AGPN{VK2Wh{ zR9L<=bAdI4=c#(u9)cd*H9VP7cQ~89s`RFHjT4#nfwrnpS_GR0wUufVaJ8Mdl4@pQ z3zBS$3l9i)ZM>XMpH3>9E{O0I#;&(=RJrD}qw}g7iye~6 z=n*sblCVyxvjcRSwS0vqmZVc}?UVO8c?XfZ@jS|tj<9CV(sx?Zn!J`-zaTc$7#P<; zcUP?F&n>|F6sWaqNVlC{x8x(v~i^?(a7{+cAMeJp=%gFx$A)|~ zM62pS{9-{zmLW+;wzB!OC$)cr25Xa`F|HfTVdbl3)-{uG$-T^sG^Q)l>hB@A$z*aN5H8pw{ohTKx0$~BSvYhJ z*puJ3ZEleD05|AR#+GM!c#vk*52fS}xwz`c+oUtaegrq69YhFiBVKz+d4CO%>3p^dA#w4nOhD(X+O2$sW2M~zdQ>#EohFuaS%AcB8vS)ThTNuHQmps4SMeyT9&z zrTRu%?`1Yi3O+DhC2oM(oOb03UFLHUZZ#!mXLH}nm98xp6r%*PD*rVBz* z_|g-*;?P1F3JtU`9LPje##^R*vR^TI8Fd$^Xggp@I*~5cWQN+#jT-VGg{HyiRR4qk zT5h&0mNw`$?uYEI80U2;M;*M#{jb!WE-*d;Mz>jH8H19FsLpQL%lB5=SyI=1bPRY9 z^{k!8*x}pcZJ$L7`b;E^xm@X}<`$x&r1hnX74gq7kEIM8g2)>_xx8|cd5noWw_}vW z4FdJjr^KrAF2|SbiR6!<$;k}utuTwxL}N2yN|_&ErM)p^n`2eRAQHQ?WGPX((bdJ7 zzH~O=)0sA`w;gy2Fe9wb$GZ&(jAl_S39!NQ@eU+wE5 zT&+PC|M#lJ$$f~jb`v{UQSGm3rd5Tx`_u0q>U1XIeY zWZAIww~^0{estY$8*m2pNX7VKFRW0)18uqA9>4HMV^9{G@x0egWApAYU)?j{?SBUXj^r5egKl|AJZ^*fAlrV-{> zpH<}q1sE{(eaRK-hR$WfrPS^N1S=cDdYueMr$uP7NrVmQF|rdw@x=+{%nG|c?};6e z!UV*1gy@d?AC=m$`s8eZBn6y#X>3mYJ67pE)e+55x9em#w?7_HS#)0#MQB!29(ZMh z1o(M-79@zS?Qp%9rg#xy<;5ADjQ6GDJS})C;DDuTjptUPHL`pCF0Zsv`n)pL=pM%8 zH-xI452k|3z1^Ql2E05zag`sAnR@4;Yk2C_HhSyTlFglw8F~b5%=teT41e>u4%2i3 zhU_pl7BSKiYS2p|p)u`S)y(C7B)ido0r8{l+noff2&9_)X}Zxv=+9``UgnuZti>m+ z%t{3KbsAOH_W9d(P4|~$391;_p?5S1kodzc7jTc+$$ogqPZf4Y6o!zDe(@EmL%Q|a zr*>Oe*qQ9Car`>$9R9pkBJOWl+EK;cNCs8!6Gu4>`Gx56>lXlIJOipE89fYZAQ%;k z$QaV|EX^FNu9Mp!b)P^I&H=a?aatoN)+!a|oUHL=!$sIHjoG|rU%8n^UNRTfrr0C$ zO>M~u^Yl;P4PHpa+q&KGukkfwvO$kMi&u0Bkd zKPW%KB|EX;HnKzr*fGQORp)BXL>fH~q0Uf@W-N;qo1u16(tM&!LAuB7TY^5*s`e{DYwq)6 z?Izn~75UtVSYLvFV}R-rhNVYSdgD;j&1+l?v#VY$F4XQ*THd~s#aPL#=)9K{afe)GC@ zI03Ug`Z=G7u0|H}$p!^Ibrtse#QC|`qO;B-uD1p?wC81070DC$Oj?rZsq2 zrwBfg=v*yH#9#F3?Z*?Bg0&rmL*SY>B$KF@{q?VLyO4 z`$n!QR!KsHhaqdhE+~rF&({d+i{0oZ5+y_G9 z!5=otVT(52tEmeRH$6S0z0;_|4dc`MsDRJOQv!<~A)d=Ufw90d(IS5F(Zar8m~>F4 z7G_G&zIRC94CO==!nzPAlRGx9Y$tBANQi|NVt9YbyvmSG$aR{=yJ z6O(QS(w0px4))zfH|=C`LZ9EVTM!+$(@207i`z<770)(p6=#qdDCrPzcQcS?dR0jh(=exx<(6X`u z-X$wMsD!e&!vKNRm2>{psFxe8^UYA3c=(--1qOrzaAFCI>Se=NqNLsdePu_M3{0ue#BrU5lxetg z)?dLhz>~!X3~(XUe-0kMox2Z1)vuLJq;K=Eg0A(5VEUjMIx{idNfztq_N#<#O>)@J ztug~%l}aOV=Guf?u33fc&P?3n^ea+bL|bW$NB4_Wd3Uo_D-Z%>#?2!u;4${2Hgt%z zVf2pM_-=bDJ;PJQLfDI@oS1NnEtsgjYxseJP8)tR5*pXFm!wHgdz(11L+%P#dsNAmou+t&D?<5{ zC~tw<0cL zD}H9gp12m7m?CY$FU5L+6&f}>x`Zg2ou^GqzaHgF+woJq;Ya-(KK9>=-?sVpaJL$LT<4&WYrFGTg5B$k)a22<6 z-Z3-Yrx6Lvn8?=5_uUQ90`8+kjydovP*2bcp6ac#2*&O9SCjO3ebs_lbw7(}(jpPd z(Px$>9*W3YyZvr#P3&8dL8ymPV?W#Hre2)YhI{^H1 zeb?0^^h9jH1$S2hTs-UmPB!-IvNmwhqqT!QxZ>yLMip?;n}LI(p51jTxSr_d zD*#-`cw@qM(60aeubM3rBm1jDIY+RG;A%k`v#WZ!Ya6t|Cj0|7LjG$joxYKm6TQO31n~WmoT!u-fE8S;^hW`3UEiMt*aZZ@ zaDY|OS%V9qSg-y7fk1i)!>^wAlf(ZqT+Gbr+T^Q(EwF`Pli9B^{Y5Ps2c%kv`mW#N z@sHZ?9ebtr2Gf6A%gqC+_DcLOtOXa!{U2+u4gbYjZf;1mS5*6x+Mj#xJC*;d_c;Em zbHJr&zt{!*$*y1OPX8~R^L_kXp?E!Neo@QzQ=dbW#{RK$zK_K#wbwrXq841>3qhqT z@js#Xz0V;#=i2aJ)v`jU<@hfu{nR-eKM3{5kpud#&f)w`=YZHD?fOeDvj5=XwJXZM zxXT8DPW`hn z^*ih#LE`vJs&RY||5qUWo@#8I5ZGUf{{{9OKjQbbCBJql&rjC=WsHTu{>JcMv4r1mdEC?xj351~AeOacB+`5yR#FMq_I=Qr4MLWbCDMStq@?;wF|=>Gxf+S1=( z580*H;(w#|M{vDy=~wI_vo-K9BP0a&H-`VJ_9tuqizz?x9{7VVf5e^*JlXo?xtx&W z?OM~{pmZH+uBS@C-&2Y0CzSpxg#Oc~KZ56tPyeDg8$nP31WrJ#-RTx z8-soj=Fi}RC$j%`2gnIIj;}TS4fsE@?X{`DGM^pN9pHb((0{=H5j}5w`nR*7% z{L2^#3I1=hEr=B|+k*a!DnBtF^n);e2LE@91TILFt~LDy_@E!@_S)3nfDcL0Yw^EP z`y+hb`1C9Ikm(lmzo*+9!+%u^Is8HYMU|hy|3R2PgAZOM{QJ4!SL{F}M$lh!33MH6 zuEWl?slNgLC$;|-MZYs2^do*=Tk?xq5bICl^ncH{H-`Tk1Q07^z6HN8=YN>%aQ-07 zpTYm#3XBVqOV^tIf})&167IFBzn}zUhl~%`;(w#|NBq3;=~t8>Qwit)o^Wpr|5Yty z!sYxgs{F)!&L4#NGx*>o*uS3(? z9nK%|^TwxNQTk~>{bi7ZNVuH8Pq=K5L6Yl#O1NA<2=iy~f49EnhNLLhe=+~35%<^6 z{oUG*8?tw}{-=@t*U$akx`rDvykBej3$k(jm|$FYjWXaGDe%X2^7}VOfb2g#_ z5B!EcIwNZ)HI=K^9Rk>XWStvdetFW*q2!+k^?m;waI=m6e?G3^0NiZf0&X@6|If$u zlK?lnD1e&X_(3AkD00B)w+fSZXQ;ARQ}xJib9o3IS{{eby?e!=xiSiU(RQMCh? zO0md0T3x>*kn5TT5XTlNxZ+=OfeYxqu8h*t16 z)<4XJYz5o!oyd@_V0*vw8ln}vBK?QvkgZqvUT1Ph-@tSDe+B`hEZDN|V*;WTJnQ*q zh(NZ2E&D!VAX~5c;uk)EQ`z?f1Ze{}d;xAk&`+&b_})ZlNWEa5v0pDCAZ2;L9)Azx z5VGJi7XbS&sPlkv_#W#aWv|B2FX-@qefypeAZ4$5^E$u&WW&`M`o&`&ux~f}yFbZ- zefyqTAoX62p?~{!wW;(^{`nz$wSxr!{@b^!JrKb6Bn2r8_U(H6Mj65e5G&ZX@0koz z7VO*28viF*uy5bf9i;3P`);B!Wa|}o{@rij_q&2yfANhC?A!Om384YJSODBi_#th$ z3X$KlE2QjIeE4_2fxw4&-&62U8m>5b-EWYA8DiCNb7FlRs+A!`uJhM#67^NcSGKVM zXFM6eO;iSFyX%AVt1I&0{~{n`T}=Yr&dXZjP`1G4^{lhIk~zWbfwf zpKRc0$HoeN)9)YO5DEfu{q%;=&3YskgZ4PqhmPe=n$`8)dlVlk>$^soHK(ez-Y$w) zQp;3QvA$;f5Y6<#sb(tPDqg5%yh%tSCJ^p@3n9ESS6B*v3AqCe)l+}q^KfG+5d|8( z6llgqdn4ze+dbd*zHL)~`_`~MHQ3--v^H79E0bnlWQd~YugG)H^ghqMBE@eq8Sik& zCt<7jXqPxRUYEZt%JjgF)vmB~h&bx&P*D^>{*s~}hB8l-?}U#@(dUOoNVBldrXm+a zO5VZLUzRE>hKG@6>a!Q7SeBA1V{G%V)))GVGv*83=pIIecZbMlYW|^dh#^&lE>5dE zX=T*@b@Pb0Z$^e5Myi?e=n%4DC;6m zYbP%~$^a}yG~BMY*!JHp&)+oqviF4S4o_~4y&+qgIln7nz(4G=pDo{;|1D1=C8^WMP?uVnke^z3OAO3>PfhH8@Pzo4yr+<*v#Q>JI z?wt=N!ZXzIhLDQg4)2VWNN6A)7j; zMtbPKm2FMYn2i9+=5;_jh3^Odx;>a>J{&sT2uEKm`SKODY)>T}2u1v>i+ z#w~HV_`67eEuaw}RZ>IWpDSO>&8*(>;@?FKoq!`3^T z!$cWSUPw=R{ow~zcO>ql-H!Eu<@eL^Inctd!d3SN^dXFQhi{JnY4%2?n){Xu(px#i^B-On8g!W0gw zsOMzBDSm35(utLZBRfTA!J#*WZsLjd!q+vZC{v zj09%7;aNoHrvcus1^CP%A0}?!2RqKYL&vO_!2m=7a$mNI;INPsa(-cA8XcMdL;f-8 zqW2u{*FU6u;4}Tyzi^;^V6l2e;BBlE9r?H83P!g?atSI#_1$cFO@I?b{-!!I{{y zB0~2=j9bJ|MF{6?Hw!Y8G?g^-&i1^&JQQ~T#s40I_1QD=j<5I#NoGs@J(Md@JIA_D^J2>JAJUZ3 z3P>zt$!nMh!pIyMwA_m3Mj&gOftB|pJ5Wbi1>L8)&v2jkzV0p3YEfQm*wr_N=1+a? zcLQ+Z{45@35*jZrJ!mS*N-{OB^a)p}Oi7`JhRVE~2&@8jC+CD?RTN~sD^u5?EGR1~ zp<}uGB>`tYw3he@E-G(06>IcFZbaJr$WmJIYvaQ15vjObrGnse)e3txRJ8~dTlIE6 z6w`SXHaQ=j6CT+in!Hswnx!yzMTPuFmI$SU#T^=QekDF0YRG=n4-#v>{HQry5<= z*aM54Y7rv(SCR|Gl+(|r)bB?5^L;7)Oc*0yLQ!Z`m1D5(xW>DPGR8%iWVQY>?~0s+t1`skXNaI`10F}Eepky$N3ttT_QZPU`1m3 znZw6hif2dAhZD__1LRN0o8L@OH^qvkEy;7l*(4zLP4&!NjvHTm%$U8ilYU9E==ToW zqj~uLpjYBj5w|+N7_$9l9%+d6Yp&7TrcVOc6nUz;s<$S)XW(s02aP-mUA7Sr;i&nK zwhBQ5rG$aczCphbl1`-s!i?9ShaZSxPWA9p{QMmP z%-{VM&(rWZyV7&s0u@&NV?g#>gOcOuquHC%hlsczN+P*LMXQhAvB z?>oHCtYqnR{mjnRmpkY9e=$~@UfrS0Y7G$4yF-=LeVHVto>Rk9i`0ObMOiWV zKwYxvdS3Nz{w1~VFp;k!XsHa9WZ}8H-*j!G&-7_yz_c&Lt%Z2kA(z+TanIFzMV17i z{>MYNqIK>lkeKHDVF-8nWk6ztg7p4EqTZ3F&5VPsNF;^X!~iNA6-R#Hp76-}_#uuK zW0z!mT}4CfN@v=9eyNyZ= zqo1rJ`)K#~!5tghMrY?>kz?1(Mc=8f+1F&x4`3BKuEK;F{1)n~fktKFB20h43(?(S zG5g!|##k@#HdfXr)%w!R^@-mXW#wTNp7}))w@DTU#weX_i$XV{Crk}}ehP&@xQ-e= z#}cjbi$@VNYK8ThX`z&i<*VrANiPpNBf$&7yPa_$W65HqV8hYK5H+5?Z_$yF=Hk2; zZy}WhlRk9A!_Iksz1YA3x_e!L*bC0hytIe-9<4RxDtgo&#RR+k60+~Tv0K!dTw_+1 zH;`+cVB41GAh3^n`8*86)>ACg84()qNY{ob!*ycxiS`GYHO?dCM;4G4MA`OmHi>#x zS@7vFx&vZfHrX1se_`bIwK*J0T}zI`=t^?PYb}nYOsD(IlaPufhhlIXLFcDH^;gm@ zyfeVS^kfnb|EcEf4%GB^Fx=ZV+_vc`tbV^9AZ?;BjKrmQtvG#rI`utDF*Lv)lk*L<)v7{}%1!JEX<7QJNjdxWoNk|yyQ zImyGDfT`&`wf7ViWpxetWo>HtM}2K-j`a=Sdggv|_QTzbSCSi%VKtygXMi2Bg%qp< z*msh)&O9FT$ji4a z1qK_=#);@DC-pVm%xK*W%LMf?_ywa~cn6~Bae6Tad}0`@_*b`fx}FS{V4{OT3~TJO zkOo}gdkNRPx3$PO&t_x;VZm?cp{vU`~<;U}h?sIpxi)|*;XzrTP^h}s|0wwQ-g%HYB(8^}b+oBoY-EmoVf@W-%*VhP`P9OI4YSEef#afW)WOP z*HyGb^ej9r?3x&B%|MgNh!3l&t!XMo5!haYFl+a7h4671$H>u@K^g`&DWcpC2|BVY z`W(lqc7PE<+z27U0i(DQZQWunUbChrek$5k5ieX6H-Zfn`@*h=2qQJ>7Y%ZTWR!fA z?Dx4b8s3t(A|b4v(0o)qQn69DVO&Q?Fi~DYz)rGvjWi3saj@a~kkbthRqDh-k`Zta+Y@C^6jcWs+daGZg7~<{|XG zz}%@-{)CF~RbjB~l#$RbVE3*@MPIc)KB(rSUV7{X(24!aj}qi3Al0MCn*O{q(r}H5 z+{cExva*B57&pZVJ>s8$5YmE0_N~ zHJADI@KpsY>>~QhSAinbQkIUi@%WI) zr|_Cx*3H&168fBIv&Z=ZN3NOcH)vB6Cc}=!f~dWQx!UL+c^XsXEYjd z`gP9I1S$6O;;qk2#RUe_R(!X=jeoxcOIEJ|zCB!7>2D!U?vZ<6fC?rzNfkbiy>}Y3 zXSog#cnNuAxqQhM;hbYzAmN;R+@f0z6)^H9ylVH_M!(kuHMW=(9!}jVE?bZK8=RZJ zyBn>e#FGx)K^>R`=ahaH{pi~Wv3wrYtVXfX!Y?nc3Z-f*Tk`N|bbp!nWPmZ|14cMK zF$g}YE_^$uqReLV*mrJD`&I)KOoVK2kv!h9t44_Lf*w(kPt4l>mV$|D<%2{Hc+pztBWdWYi#Y%9NeC=$!8YllRY{#%V=n9;}W!_ z7j67ZmP2XpP-#hIXBVd86=3Kn?ugW&ZcfiOcacmQaPSLC-Fz;bXc4@G`MxVb863M7 z)yEQ><91vU_%g#=lJk&h9()VmYko$q{${MZc5XO4*hOBthXBcUgS7a~6#XGVXPC}@SMxG;DVVb(*ep8~W1QD%+;ckS!!i>eK1 zA3axabrUM@R4?=w+7^*6Rao^ZUb|T@uzqg%QTk(N#*TGV-+b zeB)R#;c<$CXF#lPZLn);PRHh8LEGSh2te+Wdcl7j>y?TM@@PAMH;XREy-O)vRf0{) z=JfmR$CGqb*%_(yi`mHm|MFV=`j$Y*xR4K^6WjdR_2@GqM_s3^E_Ess?s?Fynq|`2D-b^U@ zq^anPo*deRbVwCzHH!+#7i-nptc_Gn5HW}MR5_*~dyL%ZnWB3f_N#i0rkfOE*=cIi z`VxnV*Kr-UD942AKv;Q$F0qJ3Y_}p*Z++GzykcEfh@SgIf36;cfo%+Qwv7l-7Ty^X zkYFaFD8W=!VugIF&(+pA)@KHdKqsN6~U=1t~6z^zGeH`GwiA>FCz z^&1p|`VWZgUD0}vEjVbV)cuw%9q>uzlC*qAJ%gXcMUR>eQ>hY@n&Ndv2H@s4V~tD+ zQlj?YOb>Fu8G$XC|U{ zzbIdZ{-z{u0qt6i(ak5%H&s(^^_&=rbz#3h-L=xP?R0zE71?7Ekz(|}X95sBGdXpp zt{d>OtbW`Xl!9?^%qo3*`yR#W7>ZqhDoQ z)>7zL%P{9%@vd?@vQ>15=o{Hv*BipKoz~5n7i za&}R(qbJ`jXSDaWD;<>(vlbNy{RCQ+YwDDLV6ST{_g={$*$`ax7xnkl*OYgyY$`0` zo3qD0N*RhYPvTJ(ZSalhir}B-zAB@8h+PoSH@5LJdj9h0#|LZKK5^PsaWq)fF5o|P ztuoYSD00BNdhKv)Pg?ESxoW83iPng%hJTCX7^*(hS=JzGAf#rz=+Y=rsYY)z-!O>Z zD5j@4)3{<|nN_G$eqiBrWN-V;h1fQ#4qu(?OAEZvlzEL`J884}*6|X?7D@OC2=Wk? z#h#Nu`TGM(kkPw!@yA|f8wLg$^<0jo5sy#e9PCZK;v0F6gKLf}A3ISe2&w9$anRxF z=7)IX5Rk4Y>`cWRq_rF(^Wd8IQN$?Rm)RB5{Oz^j6K1gtJ1gt-6jjrP7YS{5M&H(W z(DZvc;ib4PHK(pQJ_(%N58Q`b3b52?`rdggCOixzv#&0$NX93Oyf?+}Oig8Hc7C}k zwql~83jHMTP+??BYqfr9?*O^_I=ccEGFE)KX>ayNVJP?5CcQwrVAjBxRuZ~zv;{FBOb#9n+)ZFTP^1+|$TZzq36Lr|y8VPiU;avR!twWSL$!d&vu(A2o9;gQv<6 zui>MDU8ReZrTtk_9zCDJQEdeX7iAq2bn1kPkra9XyLUN~W*sQmSHM$p~g!h1aNjaef|$Wk$9uXWb9O%Fj>dp-^8i|(MRmjtA6pvLt7L7LwY)u7^#HN6=yI{Ah%Z+@sCQI8Ea1toU>mefo2VOo zgN0GX6uq;u!dd+TgeFZJF1_NP3b&4U&B(uYKl!J~ufO}D$;GpPGcFFOPe-?`V;a5* zMx+cTN-1${IXUO98|SMa#Er5~mB^{}cBp8z8}`+_WEz*?u>2m?>mF-Fa(dXsF0OqMzDY%8vXSKd1_b%Y}}{TgXawmzO%8Smoq zKrvtSY0&Q;3mBR^_ZR&QckL_EvEI-nF-3--9vWY3A&f+y(rqURf0mDJeO8dx<{2#B zi0{7ZIuptxYoM@ zYTgD&RVDEQZ(cJxqpaUjQrc#v#Y0j%IeA8gXkW<#uNVQ&#n?72_y=h(u}p66#DbC# z`(^-CTd^7u3N1BL2L4B96>WXhR@N|Tm&M6RK63#i}SUy1v`_BTqkH>ev}rM zY~h}ze$kcokw+FoWAZ_~$cZ0%#rANlTJBhU!zKytz3z%9WL{M_BPs|kk{y@aCEGuw0R4EwhA5Qjdu z=WJiHXV0nCWoUk`Ag!*NO6O+fnL8k@QN?-HI1)7 zR=3vBLKcP|qXP|+?Ki!CQ)ApW<$7PTN4(k;TB^Rz3Phe^oZ3|`RCe#HVS4~&acp_~ zF`FRF5jgNA2!@UcBBTC^iUyM}R`X1c?d8Itsd5$g$~J!B`Q^ci4vZpt594f23s-Uz z5i_a5ENmln6AtsP+Ip#03*Or#!nmvs%aj7U>MAXXnJcHjTpG`mM8>F z;o=HHO;*tPl2s=bb_}YhrK%TA^@`C)Qwo~`+?Mq05IvV0jGD>Kv!XXM7_8O zQ1?tZeqfT;7q&5VwYd5YP3WVK+r+aeg}*UUK1*VIShr9bK#3P2FI{#7r@e^p%16~1 zy)j>O@Fp4Gbzef9sa$!a!V;&JyGqJXp*g7godSs=><85YT)oPhz@k2zhSby#t$F1;f%0v5#!sk(k zF87}Ma@qwlUd3$d>W%T5>qKQVkB<#hz1-Z}X3;GVli>kL0VOSU0s$YU2NLi-hk#p8 zprw^5>Y}`2;+j0{=w;SM{1sq{9$mo*-mK?GlFwY-s&gMbIk~ukqixDdSO{{o;|8ur zKIcETefF2Un>WLw>j-ON*kU<6zb-_fVKg;;>bq*R5}t zTMB*p7L!{_G&9{+Lv4~rSx!$vD9-|T5fcV&UDS-;)`4Jd{&7r0pB6hR(6bCZDp^B6 z{6|bUFJmf8oOnSr=OH_d;p~KF3evf8D6yE>zDe4=%u=v^L##bS2$#HZEO}M!LIPQL z>!V)8VUTMy3-Vwb*>*P^nm`le3c?(;)t@)vYCOXL(53*qjJu*uEo@S1Aj6+^>+*j6 zq2$Xw`CfmeAIG0gBE+!*fsxtK;p3RrXV?U=Bs#=f!kG8xG>OQFtrmzGFe0k7>5ik- zfRqD>3MrB#NHs;f7c0#t3}z%;4LlYM>!SU`Dv! z)$ZjqH*@+ejgcUW+e?!nJmbY&gB;UIVC}cTuV!yQds}QDB+hz-ow_VfhL#65b%FLb zi`+PPp|yG6Ot<8Cm8^`B7yG}tWGJBH%*lM9w}UTI^N~!{aD{Mf#)Vd23p0VLMf~1= zd}i4U@~mL#qI&Zv)3OcztZdpIRjLk8*V?H6qlt0u(`>RFDwR4hq1*}5jcG8g3+Dk* znC3G4lbNr_cO`4o< zgqW=UKXWx1Mt-2RpsR(5F49z1?Xc<`I=1|XS=Ab1SyOs1)hVWka&(wPU-#YI#D(5= zMPB})S-oXNp0JT#ovG?X^ZG-!-^r4zT`4_2UT|;1n(Er6t;ghUV%h$o*^B^$bUR*r z(Bj+|1iLiLNE4{=JUeGyZ+EE=9d)N~4CU};Z0=loDxY?w^i4nWNir|YYki65uvL+S>yz z>4rqk5K9447y|QxmahSZtzSZ@rp)ClYX`8(D=SUe8LcD$E z&^rh1^!vI(5)2V}x)Y0noiSg;d5$;kq~pkkRnNy&N0>EReX8*}AU=qL#Bj?=BEOp-&)VPO59@8F{u=54PmX^C~C&gWA6^^2O}#P5;9Xrcz(!gc9&_>GI)9}ih93FMNCk?=D;8Rg>C(G{PyJ7&aDJ)BAVZB z?XHVoO+?XP)R-TD>>OHa-t@2Vj}oh|Ip()7UIk z^4wSGcfza6^=UDaFLx}WQwL!p2}L<2X1R&Asrrs%+W{;JPfP4sO_9p%rOmPlf`uUg zAW3Nw?#s($V=-9VtQg6jH;Tu&oq%r$%Oc+pm@}^L-(uQw@Q~DrF3;6NwohWLF6r&IjNmMLYrHw_e{rhYxT=$~Txm#ZDT`xdsY&C7C1S1^f|Y&_?-l8pGM(}WG`{18MVhkh2i zdjPd;ftBmXo2aRuN%(n+e)l3F2pIHCyMA2>KWcU1=kF8(x-!3PRkCNKGyZCZ5w1)f zx1vNy^p`_?0Y-<6$aQ~&ja3?l$&%~)IkS~F&ZXL>`MeUx#xHT(Z(wqiQ)MawnGnUn(PN|=BM*nZfcC#b*=_d`Kv zM7S@WR?)qq=5%Q_XyBS0FMpfG$aIJ<*G#W`1;gpGlmS5#zP_PBH)i#Di6}?X)VvTe zetmXQZX(_9g2`DZw7pMCc|lLgKl0kjt}c_^rBqe1#rA)AjERIH{HdnwwO2R;yIDXS zBPGqce~zW;1)Bg987=@5z3{c;NQ*^lG?{gbz_-OyK#3PlqLQ-%o)_bUTLph>e_H1a zg6D2<_|;yC`tgf3LC}eoD9o6K1Np(D(e92pW@uToBlpFFhtyVlZ2kUoxfCz8;|%t)AzI%kzhxyL#V zK+l|$o}$b&%z=7w+#%Rbk@ItS4^&UFA;gfQJi15l8@2s+wEDEHkGn9+ZLnb5`y3e{ zEh|MXp9b62IIdvj!EP>&V!I(+q;qB4-i~fExpZDsiy^LJZ{lS8@#jGi%F@R6s z(`R`v47KwPOoZo-E!u$Q0ywH%aw7%{^#tEL-=rwlql}?ppoqEQR5ES;&u_r3OI( zw@f_Ti!@%qE?v>s*s%3?>z#VtyW>avaLh{lPVRT4?6MazWPOHjNM?Q6Bu zY9p4Nk<2nq^BXvvEHu=ZZ*n5EFFEy=9DXpdHY`td+st9CKxxdPqQ_kjPi0{Ko5>Uh zKfZ?2uf5~Q+Cq&VA{={OV@r=;Pv5!Ot(%de(p09)@UyLDaaUip8+Hw+t|b^nDm-Y2 zD!zS9Kz~hKsALkCVaqemD&A^V_9`bjD%AHb)<9 zy@uZ`m*RdmBdR>rEXba?`dcxLHvgvZYmlv_mG|7tiTd~+jn&rdE?a@A!{A$#A6stF z)-=Dh#t+6uVtR#=CvI4Lt&6#qYgU>}(D`7ST)4Aq@Af#cH642M*xDV`OTYPhyxn0F zIhm$vFGxErgf16%RvKp>>@aPf+)Pc=*eyaZFx3PMCe#WD%CAoaubnmU-%CGucou)^ zG8V!jCE`$~<#a9dV{9~2F67VDJqc7KRhF75TkV&l`IX~ZA}IIU;@9SNj1Ay^KS}LK z|KfyDt`OF9sG}z%_FCHDGTpRtk=S+Yw;;D~H^a_pvtsM)lc%MVx4YAT?U6R(&~A~G z!{wn0Gnc?t%>LkUE@RMAvgpu^O)vKB5c{A31+J2>7KvLemb<0resA9JOwwmz{E-Pu zCZj^%VW+Q3jMkp|_0WyJeDN{-luuvr;?_GXCfm5`6K@W7Y^aEhkZgiWTgG8N+uukX zAEbGIV6~KQbHtTjFO3)!=1w3YYGC4<5p7>QYiM;NYh=l&!FXNZr*MJ;*G1x!R39*E zjnBmMA?w*?-ehkpaB8iB-a|rrD56g}r4X~B|kk2Kdz53)@ z%wj%jA;zX}LNAD`vCMQjU{mGW&-H5Vi{Zl$=iplevsXkqFcN4Jotz)&cKR%zdMXcH zJ>%8evl6&TJf6=DUme%jFSFNYj^8=nxX_!j-4}G}7E{`e(LDChb-T%)rD3cLRUK^BSYS-mNr#1hr1x64BvOUVH+aDzrlG!4J^Q zP-|v4Rag94PB3WA|kiUmP&UKHvSrkR+2R0>h$LG@SngQ(thTWssQU zoIY#8f|<;XiExMndt7Ci2fLYAK#<}s{a%jeypydCT`+v~R>94TLS z%??_|1$Hh7JHHpoysw?<+@BvSrxj0I^ZXrX?!fOwEl@HDB?I3}WlYl;tF2?;Wjd;3t*nhFuP zBJ_c2NrU-Yn!bzziv^QXGe?t|JKB#B^L1N!8nOsw#jk?}PdAw}S^8=>%0~^^t#g4K zZ_mJr){oDsIU|B)Xp&jW@Xb;g(Z0#85pERQlklI+XXQzsFdp(ZQZg@oB>v15DQw~; zNP-UNCz98r3D;(XO}z2%+t+cTJLH%=X@bL5sy9y=>Pm_Y?+xr@0f(w-~%dV`)LZgMNXdn0atu9_-{LrRpx5 zLJLF$%p&(lmQt6#qZN&bo@I{ar8fz1kj1Ux#LzXSe^lcUIT&vdv#ZIIuU*Uj_%iaz za{*ry;~2T@yValuD#K=J%gk<$mv>excD;uaYeOc{jm_5jaD$K4Tn@@0M>cZYq@aVV z9)1YDW;i(wal_3+-$?FAbLMQEf-gReK~xX2$Jo}0UK>-M$uN6jIuSUi*g#JI!BCz9 z_B&X?f-gVPYRxvfF<@+KPD!fXXw-t6om@YvB)r(Kl`X@Zz3$C0h61`m9Gu{8O{d74 zICtw14%-l~B?0pEbmA+kt)^6lsm>~L1d;9_al z&ivjmdV*Hwx3C&aoqHuQe+f%A9z4UgYgEh93Y%@H!@GkdN*gmJKAtmMq*Jtj zw6%h&)Wyvs&%U?J;aqQT?mH6SU1u(QA0PWVK~%3~>BX6#J|234n&({-V5L%NNJ4f( zMnrm|)LI<-WHKv-Biqc+i+9-;EME#ykg(TREqmV@L0N}$g3{v!dsnzh?PVm6)b)isG%cP?cCd6tiU4q>Nli%%h6LkEszdfneewFa8 z^->m>(nT~ED;{vp{A6=1jynP!OM5_AL%rweJJ?V*&Gw-3frmVj zf4l6LL6N}g;y8SR4Q;8jStz{tGn;{H_^LxBR=!jux+9j945hY5Y9qTwWHRj)Mm0XI z&~fsXSzs#!huw(vq9*GCbtgaX<;uo5YH_xdfer`NQ&$?dDjB6>%rL+~{Ny)TZ;y0n z?T^Ukfg8dn^ZcZ!+{ISh=0C5GaEvB@EDAV#>@~nf#$;hku^!J$jRiqWx}A*#iWUyCn0y9sB&ffI03fYYeg*;5QI? z&CfsWFdz=I^-IXwpJ=uaN3^VpA7|@FhU*s1b8<+dtQ*vY-RwRy5}@*RlpV{MP!L?g z?}~c1ubH1eyV!V|v8JGfx!JB*up$Y&+xA!?qe-))ikqfPJJ<56DJ=r*`pGg-i`#}s ze$G`{wzC~zz|(Hw3xKE1ZUxw9`wD_02F_IZVQjA>Xi7Y2INBs^nE7^fbQIvWN_Cu! z9Gi$>r_+MMfC+14vI8kXqh-HSmj%grh-atS8ts7s_UDrpMY!L+Kh3l@aI$4Z4~b)i zq>a;e^H&jF7kXuguVYtp)W9Say>ETaq-=Rp`#UCEhS{W6pmQ*g+T=R@;fXAoc29l? ze+&6CUN`Ydkr6Q|#C+Mso2JnW-6zvX-(_&uYbF1uZmcBXS*ps>m;BwDQO9$I`^?Q} z)0IH}Z*qk9#jz-(BL=H&Z*C-tn9}`Ka9=KZ$3D%p2-+QK>3;8H@Ql}1NXxEooB1}Z z(TuzG%Yk=MNO0F^&tnR z`Ck_b0l!4z(~-vY`g(rQvzA?<1*&^MC`U{K94uzO9eaHdoL_Q!(GuRnY_yBs{-1qvqo9UbP_aC4@s zc!$1U-K~^&7VCpa*yGY1`yTv9m|LCd8`0}{Po3JYiJKI z>==XmJE?82yvNSr!x@sVmhVY}l0ojhmR>i?(x3WzR`VSPYOy9Q@W=A5dPA9551YdF zo=nJL*4$>#S66#=XiDRj@3r5jgEIZ*Y@!wV__J#DN%`viKPF-*G1_vmdCU+r>3ZH7 zvdyy&FQ3kRfhoXlTsr$I!Q{q((apl0c8%d-PqKO_STqe%}5fKX)={U40oTcHVbB7M53ij13rl+ME@9`YA&6 z)gcF95pvmycwY^;71(=Scl9b8Q9JuBi;tkW-OHt7NoSO=bVg#zs?TrV#fKakslW#?^vuyEDTp5B^@8(`$>!>wdQjkIU8b)omilYg?zs?e#aa zjvIj7;{C}oWp}3HiJff`9!%j+FI*c!zWF=t8<1%K;Rn#k-YV<# z&ESx7(U`qRtJrmMj#l-0Zi!V^zzccx!>lb6NBN|7L78=7%3;9M_FfMEZOo-e)#9S@ z=c`}QU$qsZpBp^}+ffOc{cgk003JcyBk@9Y?-gG1{q{7y#vf10LcAJwe}Z-P>H$iZ zS-WLk+;i5Q&$YKsJ{w_86X^tTeqh9EyXkuHbk{Lob$}kuJvb#T#hLevvioNEdhaD# z*5F3~oz8boqZKWIhts(2W+lOk9AzQbp#9*iG!D-tAA!(f!=+>Q&b#SG0QpH#=EFXp zw2|;O_@FsSxQ4@HIaZnTE5^clJo;#cvz+lIP(V|{QQ->(T^ zb_261N;W?04G`PlBW9&kq8~skl?cEYr zJ5^@Wl-^kF({MPZ^Fh&HB;z($;@_Twlf(A36$i}Lo%=f|&DN96*0uXPhQO9Ka7653 zwA^@cU?aj!U1@ItDeo02Z_Mr2CGLo1)Fm0trnT1f7|w3A)|V=*FLcEaAuYh>u>;W62P}oK)Y(X`hnV+>82emGLa* zmhX&vN5p(j1M|_1GDN0s9fv(h_XSXnWeTdqwy&VaRZC8UK_cQ7R_5bcId&H2sy=%` zUG}-HPl1oL@7?H}L=ANi<`resZN9%(gzj`@uqFZX!16x{r|wIw5Dg+q+{rH>JqGUM zhACf}E)t>J3GeHfi{3mmac!N#<}c#Kr`N=jYDcYu4d znxsqCr%81pZ_`@x&lsw({ghox_XYgIcJ(>Ko~v#N>W-FkFQBIjr^W$`L>OdPCkrZ+ zE|RwUu6n{BqC~HS)gw#fov>o*$?$#f+9mrkD6dyg(&W!of--Lv1l>A^D@q8iZ=NI? z))10Nun0>RF(Vo>Ldx4wc1rKRNRWOcJAqbhH>S1=eOFtm{zis<(gGnBq3CO=1viJ~ z7=o2heX!=>R(+JNBqK)jfLV^i4PQ3ulq*I1G~DMUxVqr+%8Ncwpj9ED zKIL+k)RBt$(}(yY3E36gLnqG@Ofj!XsDES$v?^dnIr*UWX6jA` zm1UFBZTeP}l${26{c$snm3D}g=c^2*nDPHq_R-CJ@K1o`} zbvZNWbYP>_5pot|Tn~7T)fX9E=x}@gJ^#YS>{`mUx9M}&9L;*bvZC<}0CygcKG#IP zW^o&Svtc3k3nSBtzY<*Fh{)1rmhzNT%PhpUP{q*oqn@E;!c*1U(BdV*N?;y<&GkKt zXjCcIU zR4V#yeE;s`XeyZ*prqOyk_(Gnusd8ko0E^oVdD%|-|phu}m) zAdd?rGI|2BICM(;o$GYizCG)t{FEe=F$yw(Y8e&0h}umo|KvnVU(`ewG6wDp_xQjK=?!QIw+zx+kP{+A$({Dfg`f@fa|Dw@x4a7cx|kcBjpNyqi+i%2McJmT1w1p@GKNS{!I z5V^Xgt3Qsf>|;W8!$w9wlRjaCx0Q!Mh-=a*^h|ez<%|A&h8;vB?!k zKKK#d|BVO?cpJhN81OX*3PJ!Ofa(r47BdiwHAWbi#!6RX&ZBO4&zwi;4t0bq1VreC za^aaquldFb(-Hd%0qkie02GqI1a?ZW0R1G$fiz}_SGyD+?{^$Sag(k+alDn*Mc1Lh zhC5kMFM%DzAm3qMDB+LK3_#3yMCfP2I|^VoMF0laen%eaUTNiidPp^}vU$_65J1AH*Y+wBy zOlr=KrcSTe!PMG~f*XZJ)Xn_WlJcrdcy+7)DfefC{m+KFyQQPntH7O?nU|NFhmG%5 zrEcmXV`*z`OZOuiUtQ{;VUQ7LXIkA6<-I>@~IhfgAg$GQW z?7YnE?Ch_G3^pECR%SN7*BSk6>^yADoV=VIysu`FKZRZ=cU!Vk@cwH){QsH_{>Lfs zSNs0I693amihp&(tK?kF)XnlwW&hS#bq_OlAD7oIcy+V?+5N|z&Hmb+*IDn*(y|hY zrY^6te2dqX+q(Pw3#+`U)GqZ8?@bMV_QF-XUcI~UCgNK=yfJJV2Y)G34&tK$++xF{S+K_LNFOwNYVbzi*j4$6$Bw9t%k zn*{Q(+)kBjhdkwNZG~7p+vejGbHn)LQsB^2Z$$jtgveP5rX8r65xS>fdq5EC%;(N6 z8`6?Riz#k?&qgOdI5Vk|)seAum^+9nNPW<*4r*wvn{Rc3b#h*K20{(iGnUd9|J^4%ID|Mc~}5r{V6-3DplN@8d3_^c3>)52VRi}k?5-Av5!_P2p);oW!6 z6Je^LM^}XNtHz)qyxM};GRPxLoZOGZzwaCWzqgG)m+*fLls_Znbu|Bn@$|Y}wX?;a z%m4q58YY?lzI#}hzK-SBG4^T-dcAO7#Z<4CfQGZClkIDfSHZ-;j3obSeE%;y)PLI^ zxVc%mUPtFYT^+BZi0{>j_CMFhaTcQYo5j}K$+WcjGD>6(Q)?@1u|TcPz>m4>STJ}R zwm2nT@gI@U22<&QCJKne#OBdkJ7%=5E`Y=%-U-F)+d>|J*Ch<8R#&N%GW(_OpX-OY z?~Q3U9}J-F3Z~3;~&AiSx0& z?L*v|wYczyeq4e@51yMR*;}PITYbph{)=sju)Ft)wT1B_^r4?$rxUuGgy&QmMHJhe z1H|Nw?Qgw|AAaHwKZJrWolc1Cem~30N=O^|O}m3d zXFLi6_9CPR!jmIS?lzJ#(kY;n6?W)tk zaX+Ip|8CqZ&2m30ugj0yUPXlg2<+?57xqH2YvWs7=odM=n|Bdi>(4>8+|y`2t=IQm z=;MB*jx}f+_o$qLW8;2<(t=Q_bud8-@l>19Id&)l7sI54*>(h2ignFf%X5-K^Osm26+o+1DOMd0^^{`VgCZ_>2=KD*B>P2 z7|KN)5(ZcI%@$O|-P$9Pue2@(LqV=5_XuCLT|gwYywZ#l*wJqN`RKJa;*85!(cFLH zzo;+D5H$&}^zoi60CC8k_xXba$*vvAt}_X<49TFY0XD|3kQQ!|3|ABXp`bWeaztSa z011Q_>x~2SNpw1^o?rXhW0{a?pec0qhV9@J~?UzXNq(aiB7E z4va8700r^}Ob7ZK^b`jx!{?v~;{oU)8sJip`oDu&U~!N#Yz~4jIzX?|a>6z6zcz>< zoM2nfpS?M#{>+e8&|dIA1w?-`h!1ElDCa*1WyBl|VNw7;#2tJ9n(ZkL`3vM=`U^wu zKzkwoIds44hZ*aE@8Wg&3eS3o%8E%~!ps6RFbce6wCRO@X)qzcfONtWxB_38|IpSB zC^sMIiNw6k)L_`XVVNH2fP_B}VORhH1Px3G;s9C)PD16pf=4khY7YxsH`YIe_uyzC z3rsiC<9`kS8i)co4aCu79Xtt_gDi~uH^7H5f^|XlKhUvRKQ<=8{z<70@e3e zhfE^mpbHZN{sI^f5-=ag4Y(IL37rEgj0pG(FhdN$tsu9az2Hgs98_Tfz+Zq2A_(>Y zT>|%lCSh|Bg)sqt0bvLLd%)kSn=pKR)76fCouD5`H z2ajgp=pGh??zjIC0LTy=Ff~XXm=eeWZ4A=|_ZN7?0m}nfpu6GzApqzgYT!bUd{0U+ z3%oH(*PFk~VvA`N5bfNtX_#mIa zzd@rtDIqL~#u!~BfWH7d1OcfkPu0@M&Wa59LYM?1J0&KS812k`IU z@e_EohXt{lt_j%z_ZLl# zqDcmjJIE1c1+IzEf#a7|&A9iORfiYHdOP9S8d?`2fCFL$9tUOg@Ps%cY@l_)15hFO zU?z}0usP5fdIRQffCpfJXn`w1`aR9T&hQ&3U4H>|05OCc>b45A*=M4LbXGV2tT61i1z+hWta=fbxfhpo4e<{~??qZeVti z14JPH;3v>S&uz$GfE-E$${*?x3IYv=1t9`41OEaWNdA2hs0i+$c@qcJL52&WLb>FKfs5Fr#pcDzcDZR4+>Ag!w>Am+76#=D_P&U1H z0@6F7Yd|^ysiB14dke{fefGWgoO}1#h--wW?xC>3gMVGkQ9aNgX3z(+gn|ApmauzmA96GgBu z(!Ten*#Gom!TST^ZV=A-KLnb3|Lg-42PL+!cNa)_?%$5U`U@+F={$YG`xD~P-w3qH z{@EueIjVJG?*WkO+`j{X`xjPFQY|}H5c#?Bg7-(ngMTN25%@s@=W`3*|6ZJZKwt$C zo{KH~o%n>n4dOa)T{!#qV$q~qeE+I_4+~0&`tM@T)U_RFCzx;=ho***-$G)r59WyPvrBvCj$GK@_lwiauDh9aH2k&_(>0iWL6^bnZ%S7q z<=4)*ukjFqcr(0_b@(^g5(Hi1__={yHw#0{5p?z9=SFqmxVEbibPeL?rgzz_y_y!n zGZerx4g-c@p%3qD&)r@3zte`k6R>;tR}0qg&9kUhto>#jGXcWi_OM>z;S3Amn28Yn zc7~<4@Bt%s=gRlawf7w~=FYLio$G^%CE8<&hu4HsnBZZ||5Dz+CX~cT4`ZGjOZ=6j zbbWY9^9dc6g`ryq{zJM9O53aj4quC>?TObMh_46I40_T`d((Kd5w0~7IyMtt&WE|r zhI!5581G>LY0j_OX|RFMQ1AA`I{r@F1>Q#y?aQ^({Cj~QL{qF=0@HxtL z|9c0`zZaQzfcH?3_L*kK}e zpX&U{d04x|eKg%Z;YZ*@l=K44JO8DN z0uTLe|JLFP_-FSXnrJ`f!_8QvU!b{*d3Y|@F7ZDt(>uVss0aJx?SCUA9-}q(V?N!C z#oPs&2N>paxsJcEmJh=t7Pvk^AEIBL|6B2TKj!1jNHkxd!NNQ{|K2Wvjs6SEbw4Kf zW-Kl*(BNPM&%bv_5TO6X>RnuZ78YavT-+%gYuV-gKUkyq2&BBB<9F(gM%jhTTV9pR zxwH|T7v91w@yR{9T_WlRv!KW zzVdY1=MnniDe8#fXOfc58pHXT(rtq^PRYqzant7Aym4q-(qbl8mxO8WY%^Z^tq29S zMP7JQ_}vOTOaO)ZKDP(-R-iAqR~IeUffSdfq3EP+%s=Euv|bj5bRGEM()52RvoSjB zK+;Ro5VYR^P7+U0Q>_@1@3)E=#JiCTlU0lvbpIKU9CP&hIf zAO{wWKu!UM=!8No$YGU$?{Ux7-0s1?0+`~`l-(Y~iUEajqDe?!Kp|K(6ZrsO9orI% zlmS?S(q!EDVM2g4P@0^ZIP3v{9Axyy?KLbyfs@Yn5iB3T#o$W;vjil8jU?R|VEqc^ zFB?;klpuSBa{9&$Bq^W*;7bZCRWOlu!-7QtDj0lUz*GPN;8`g*Hkc!z9y}}Q#so74 zc!Ou9-MCKXc&Ay4}L%B7z1Epk%Zs?-<)maQD8h$i5U5H z=voAZNM=@>+_+%c@U5~On@SXuJ^YS-Z^)QoJ^DsJaJORJB?PIaj&Tt^;Q?Xf5210W zhcGqNO5KlKpbQhC@Q@BDGvBpyC+K(Nnmig0hRCanGfhM$4Zr|ubiR}@0#KJcLE&kO zkVf?7OR=Fq1+V$M23Wu+i*PKJa4ZA}z;^5xy)41m3+>H}c}6bw-7P-06~=|V-D|yI7D>@?U}CoR>?e0 zH(fk6DqMrAao6h6K{;5WlSsZRgoVgo02kDTGKIh5>ThSmh%*&F7pf1W6WZ`RNv>plf*ofPSpxe zts7K1TFvbiOoAcmKXjQbB1o!?S$oJ>dtS1B7q4NYwN?2|Q$_!V{K}FCvC4ae8f7{E zjS-_Un{r#;NnR(39#+a*s{VkRvgbeHr0n6O9AJoA1@ky{cc znaRhQPe=Y4n;=_WC~tVkU(?6IcSCpr@Bbuah;qO>>OISU5fCR^886!=pKI_(`Ad=B zG%t>8?1#7=vY$76fVV1%a>_6&AYHaHMfTKy-oG@?d7Qa!?1!Em$(AjNy&ZCfuVlwI zp3N>^$v$4hF5b{yPQDG8QwF-$g$0#bROnQ#u}*rk2WxqAaeru5Z%VhCa@_WaS)flNGne5p&jY-H>z#jbtEi6pFEe2^0 zpkcbW4xTXf3ft7c_W5EZH3fiTGB(=*rBUJ7yxNBfwT=q*#2~w6 zR>(oIN2^K-4dVa~conR0H03em9q=HszqjEzKc^PT8r1q4CXvF6P)> zKqpO6GZ6IV@|uElRz4J9ykG(^VS$(Kf>ALFXjtC)2;j;d)XL8?&&@*ZK6>Kj#t}C` z0D!zuG#Jxnq1J>^7snG8lc<$y+iMn2QVkiq?a=RewLS{=7odqlhP~@7F>BDoCk9BZl1YDIt(SuR#;p%gQJNkD*s7)G zi8+H#8x*F;7;On-^28iLr{5H&ml^-4`Og4iGD^*_a8Q=#SD7c~06L{rn9jY)ziDg_ zIxSO}W}~%r{*dQ)3W5ISo2q<}DutG$yDC#h75p=?t$NJ!n#^rl|H8c94+INI44d|z zdQn@pdP}*wXpwqLkvb|%Js?}XRWCb0GTX=W20CDIEL4|y{-%>ZK-}i<&(nmfk@jVh zOw*TWh!>BYkJ|=p@``&>W(r5CrS}%9E^8GY{4Z@SRLLy?g<9eeEp(PvK-t*&q%CSo zduYs7Os4RJB>m7leKSxPj^mMHDm=iIzM7om|wW+#W&T93e^+E^a(8w1GSOjpkl2ch?bbv?VfI|K8#17 z+HJ+{&i&$4FPc>^T5mgn>vK45)By3$f z6J9J{IyO(5KEdQs!n75S*!cg7h(QGx55+(P6X2@mXC7*62Ean)Yfanv&Sw!2Z4}UfK0{PHc+~7D02N+A1__pFl#J`I$a!`JLdOQb&Ek5lC)kk zX;9cYAsiSghj|7&PNutF28@~bq;Gx|h7)^UZhuwvZ}yzNf{d9sr;GFCx}#LLB!wZE zn`6k+5|baoR`rA~eN$Q( z9_BGUQ$A)*?Rn{tpn3ubd@rUzdQl?fe@|xwB_5++aTJ(2c@RV@$-Pof*;7H;1Eie7 ziP{d9JyOWM$Qub5wGp@FHRVm=qKxv6>TQ-?HqJdLAMu&65w+*d;G-O(jIxgEt(INZ z$UP`>z5d*r`wuzdGj1bl$4kve8ABN*5Vd)83*>Rk5z~58EUdB1*w{Ip9+2Ad?h`vc zr3CAqUe39E{$C*`&E2E4DFmVcB%ekUttk}vD6O0rhgWFNE2$nNDEStpN!w^Joghq8Qr()v5b zit*Y6c4wUrX88=#0?lHty*01L6;7m?;NM9oO&>>bfj2D~;h8_bo)n&)&0*kbr7sO@ zN7iJuN6t;Dwekiod13zCOCP3)kyaoHwL^OV@FQ}P=|>53P^MQcbkE%DGA-g|;z*;v z0-6#AWw_V^V4{(VpsV5?oxQQaC`_$dkCi?7reh$Ai#RZ)g@2?_q2w+z>Vol*2y{lG z86{7b`HmQHl;L=oOnIRU^Z(4UHq3kj93w?Ja6H@oGidK+R$_fQ331i2z&(glu|0hF_7Ctg&hn>K3(?ssmZH33ZC7 zOVqD5voEuyP9#D$10VOWJ%($MpbhVkK$%H0{~(5$Ncz10fSlwo=`p;JB>ek-L0R#*$MiAA5j&iTBs}I{=-VUd!;tjJ zk(5+0#}BdGpgp9{Os!so&Q?cdtBV)>6UC#bn;ACc@=vG}Ce(BcBy?=WwDM10Cu~Rb zWlH4v+*F7sOmt}*Nbtj`lPzLYs@51GnBQyA=A)M>3R~8UQ_|qep}Z48@C1&^DB@ON zCJ1_l7*8q`wiFm4OvyDF79cTxTQOMP2>?8yq+(#oZQJ?-05k`+3fu@JbWVCphH-C? z3HWZ*FHQm7m3Q(IJQ1O6z;&DVXK*;lcwv9*_9gvm#eZfo?Krz=im&8t?c|-fu63Ur zd>tB@$7Px#VA|nu8J&iLSIfbd(+c)0W43z(9@AG#KW{#N-!Kc&|NFZJ~DgM8Jk6*$@p`FBCMD|Xd-S$6;#qcsG{wun?%r|7by5R?q3W>Sx zryQbHW~2TOQ18`m_=h$g$r8?xJcyC>{4IF%12!a`qQ=W=csg0q-Wt0{@6T8r^7H08 zmDN-v>8TOC8Ho+g!3&4pQX)f9lV~+;kRn;j8f>S74auj_ZssoXvQ*!>d1Do^cXYUm z>a5fq2eJ%eC2MJe?If|`pKe|N3hh$fA~FkgjvraIg259@cm`~*Z6MuQ262+LFN5u5 z{$sz6Ls`=qlC@&NcK+D#Q@rVBv*Gy%!FImb@I$;3V+!pV?xI~M^|`sM=~~HJ``aUc z*Ws0fP-x@w6p`Hw5$xbo{%1Ay9a)}tgC`!~l|ZPp4F!rOY}I#uX5E~lKLy^jlKHR+ z<$t6NSy3EMu?LH%=8p#G*tV7Mn(Cxfutsqu*nMY(hu_Qs!+JrLfQnK?%Q9ecKoT0T z%p#l~-nz^DSBJQJo>un)%fU-2sQlBYc5*N(jX}M??e}9+(3|j zaGw9W^3DBNgCOsg!Q5L^|KMk##to4rx2Wdd5?yu+LFp}cg&NT}y+UpoEEoxT9Cd1H zQ-hJpeXf;zjZL}YLJ33%_r^%^d52)y*rJ@Hdh2_aQ|;_1;Vrx=K*|A$C``s%LAx?)n* zVq9>}8@+TSXrL97%XcCeZoSN$5p}EnH2!IGE)hL3zHFl+7LC$)evvD9DCdfdS-Oo6 z!GhCjG;$Y`&oA}_4-t>2m_n{4E~rA5Btq=XY6>7Aj1~-2%p0VQ_P^l8=%ip+r`!{^ z8x@xg8Gu=b7=r9`bOQj^nK)9Ib&?mNvg8%-yx(O-NQ7}wFu zRE5R}g~nJ|?Gwr^MhGHGon1)k%3Ib(G-}v~GW&p6YT5R40PNufi0F`k9;qbz<1{9A zhbH?#NNU;rvjgVgMFG*F3OzDa_J=^!FzOkdDYw+oyU(j#f41N?m9=S$RCjdvY;Qk~ zP_0_Ifna)TH>-7*z#!9O*u)D5q9@Ee>8W16m35+c6WG)#@nmBO97usx|>Kr$LI!+HEL6tW_6@s7&NKjQlFbzd!8GwWBR0vA=glsU0 z!KnIRyCZ^<0g>5SmVo4Xq&H@0r_5EKCW*LrrFi+U#WlGc3d>wsz<~RK_Q02zp({+w ze-t#ouFDKDmxA^vuY>cQ&4Za0;NpchwJArLVUK&V&pVV!^7;08s~dwe$|R&&)^4u; zklZx;5!+zole@94GIc*l^K&%EzsQcTPrs@7#a!3IFt^iu-RPO4${SQBPnIm51H@=7_EXkf(@&(5d~5-siJl1C>-0K(i|J^0zc} zS3Q#CfUA>JoU<+$Zc40|m-K~1zyfs6nRfmB;KgfJlcy3NIdQtX&eOR}z`!RaOP)fw z6$->6J%{T({H~0&SXL}BYb*MDe?9BYj%@ZFCl&5-VCCH_grm5U))Fa z^hHkd9ug~cXZ`x<9v~Uuyj@&rx`47>(xG5pu6C zV(H*jjca}bx2|yAquGjs5}!(Kho0u^x0G%Lk#W{Y!f`5dk>1^4r?Gt_Qa|tXb+WTB zT1#o?u~$0wwYGI;X%cGj3yoLZNj>&U^_oAXxaFL5+ltXr*J_R*y{xt$)Z{+io1`67 zB5mcC)`c-nF-ZU&$NnTF$7uZh zPu>W!kzft?`dXhTWjN2FmK~yp9J5)eKGZ*Oyl}iAYm<{{Sjz8aWs8@! ze2B2_W?9+VMIY#-Z?g00a%ix956TUa57fMdkmbYew`w8N-@Ft>oKvj~ZAvTQGnWqE z9o1&qEUprFChG)5pHusbary;}FK>CN`G;S}2WqRz87fmSh_i}^Xn0HQ1dB=|`-~e( z;7k3_S_NJ;Bc1}E4t*usc#>P<$!8rc_Od)?rwfkCJOAA;>Bo~ZHbUlz@7!>uk|bqH zf8sU8_LHV-f9Rd@A3^(7WrRL@5)*4JkcZyKfz3m zP_n?wgsQD&h9Xmlgyw81o6M)jzwCb-j$L2@2j9bDytul!^zrwd7IR1`kzeeKVYhiz zG2qZpFc8Vk8eWV1AuBrUGlPXcsVu7f>VR+ZC-zzYoJJ}4^6ZGp%l23NoEI%aE zalbdV3d-}{>1mNaYAWNZ=((2mjq_ZsrP{s9-@THXJ-c>6Y}_|n4Xa&Ap0o%~213lV zW?X`#sa4M+Pm*c(-X{5ATj`&_Wolz|qpGhBS?u+bLDzoy(SovHjPu=H8!tlj7T8;^ z)L!^%r4Kf}o#aby=5*WPBT9C6)ch&vEv?OAKMLyi-W`ZATkXy~auRhfSj zn|(!bl;>nT_LkT^^YRecRb$+_L%U!8OCo27g!89JK8BL4O5cV%#_n0`iNDMgqHyRE zv{M8jRn6t6DYM4jr9``Rh|wG&ZiHW6c2~s9=Ck$qj!Qm?%T>-W@=Ux@iKV0}wwTRP>K1a86=tIhKM%DG9IjAaTz zHqQY&(HG|U4X2_lFYYJd68>~uqMon4v&QmGHdM%gfLs7S^??;tXgt|UTMzL-m*>Iy zw>|@ph_@zNEr~h;Cv(jMo}Ndp8G>7)DOaLho`TZf&nbjwt=k_tEy)~|KegejE~{%f zosMoPo~%0D9Nu`AJ(K`9@h6xpE+?o^^r_Epn46&HpI?1bBlM177ceKR#4z|#YaE{t z0eYD}nL$OJA?z?vV|eJva{jUG0H+-H?9uSY$(3mCGcA#q=~{Ef+mRkQ`kuO(jOKTs zJRiy<;-T|h#r0<{lm@AryNivOEb0tzCb6_2IWbYpwZG5ZX;H-HVkBXC7`f{Dq71M# zjK_Izk-dh0Y_4nSCV8+COg-3|OFih1p&pb=Ju0K-JYVo@aFHS;zdoVnY-SA-ej9SMzlVlLB4DjKs6U*wB82dh_#L{Ug(g-h_JB!F~;w zs(uZt(S8l5*ZvIzj^(v}4d^-Q!4yB0+4(M!BIzE9mVQ0u^}_^UYro#y{7UHvN@thT z^oVftFjQpYuvcXBuyzlzvNgO~tD7WnP3Yx&^qJH7V#|2m&p)X?`vS6ivpfQhr|q`%Ot-xHZA)6 zhdy7kH0eD5Kz51Z=cKSXI+=#TNm6S)*|*9+M+LYQovqmM&!uH;+XwLCl3jVagr5?F zu+l${2oBoBm;kS2W{!X+9>%3UTV1->_!RX!Pv>Qb<~!wD(%@4CKWt{$_k5|{Tf;@C zTLk>~#!h|HG-B)vMazp~Y?DOG`?uyZlQPfH(U&jw%x)%sQ~z9;L>}a;)U1-cwDW@W zzH9gZ8R_S>?F{!U(9%wD{7hHBVXg1wc}S@2xsN?{-IiOQmPyH{)WX)Tz*GUJhY?dX zybDp^MpHL7&L1fgnx-Jq&Z@up#8ISZq$I}?#b;0xhB){yzpr}`Z43URN?61-{$MHLe2OYI?%nse-)Z%}>WpsCr;ipd8L}R@jh&RbWamHc% zsp}qaQhe=5Y$cxsL3p(|E)n|Rvq_S;ejHIo22tBoXbHBZoDT(^yR_IEWs_K^kZn1& zldzhIg!WF``+NbHRz<8a=YVDG@s^u6f}UASe4!UYhBKhzo{~+MJ(xVgLtn_&p(Ohet6PB;t+lnc1;(@6rp4F03SVIa%Frki!b)ll4@$Y3i2g zRWb8>|LHAMaq0V4x(rZac!Vee;MrNa=2qfRH2N(?VBm3TE>v3<$esyqo7mSgjS>=B zgFM?wbsy|)5rdzi45cR`ODV51252TlRJ$YOs)a?FrqWN77sVIBQt8<|h5@P9(}hsV zEglgwul=$w$sYcit5sGfzSVCd@Q3eq$bWqX(@jV2L+nclxtCgjPH63Z>Py1JR( z9`WVTRd3DqIZH|6i)B(8ub1MlJd)SG>JxpHH86{kkHA%vZ{a+NX%V2EcOm>rIGL|r4T#QyzE62K5DKc)HWKnsvH!MPKKYsX0m!HZ$NhEn3>xwRB zib2hLZ4R7Ji5L2cm`ZMA<TnqHG2+%KEZS|`}54!EviMOB^ z`p}aJ2YxQom=8S$ZIB<{nRS!=+s#AHdwF7a5%4xgf7qGf-H&uHrBdJHp5dljRHGqz zCAQ&Vb56m0l~=x(VHa#BC7xu2glaS~zxk^dc^9euz85Q;=UC-<_>1KH(FyUioFmQA z`KQYVI|xV2?t$@1^K`uf;?T!9(D*8O$C_FeVo;$oWhZKPZwBrK>6us>Ssi=2ZeGhe zD*#w97aziI+dQ&cNS%kCT^MJ@#MD1^n&NEMGmCvnO+xya?j8Qmq?b7WnHpOES#X@TR68hoBHF>hnHiLi{Mn#z@dWH!@dE-(=y&C zT@#dz^-i5-MCo^N!>*kAbu}w}mD+LrvF01Cjkiy>dRRq~5{vD`R1^KMY-~SF zH|!~Qz9LL}TGBskW}Oz<%SK;lF5YgSsvGpm(@9D<&_neCm0%4gu&Y7|1{$E05n z6<8bCG`!pOB;!b5U~+0(&uIO6mL-h}Zxad^43 z@H=YOQb%#)wqqbdK8`a`+{Ti#@Z&G;K|6`-m&LwuHs4?oT zd{xzo@+XgIwA*6iM<7+8QlB$0!2c*G+>r_=tR{gkKYT5-T9tpIZyeF5o_ul|8bgf^ z@a$tE%s@Ts^+5gVpAIm>7gT<2Y&5{ixj!qik|!FqF6}oZde4D~q3+X%_wF&XVzw2F zkkF!Rury;BnJ?!f{ig=cE4eK!Z@ysbKV;bP{5~`wFz}(LiqH?ky?8u{izjMT)K;P zZEo~vUGEm3{)399i)bF=XOphn-1w^^yxqIc>yLNs!#N-0J;BXGDb_;+CfYoE+V)7T zR8BRVLVb8Si^|eV!}{biFN2+dmdC!fEy6M{8cNNtdrj+(d3TwGJYHLSjgEirEjSw0 z>xC6h6q0Ml@ucKJWVtRn_87igmVJ2lVr+nd%H5d6gJV<8+8$=-sa_F(#Ww?E~|O@?=5Xa>(qHX^U2k_bd>kun&}_4 zUMUt1eF>852^odtfKr~ui|Sn<_glr7Mm^opk1|6X^|>2d`q!6DUtH%xm5LuPDM1W(`TV0CstOu6fc}n{zV}hNp7q`+Uxx`aQp3bGXmuJy(8_Ghj zMf7tF4$e*JYT$Kh;p%b8q?TGa2?iK`prfKq?0!0t9pN{B|AMKPolH1ya6-dEfPMP> z<>?{~oUM0*cc@#P`bqAknm?dg?t)YHevGiFO1XM#w;eySw>2`iB@o^AvXH~C#>CKA zy=FfnSHPQ@$$l&`P1NUzWroSQt+AlnZsabi2^oEwTgAvG z%@Qo1@;a$4pK?cAyP%%ye~>WQBdH!C!!m!5JxH;Nei5SF-LC)Q3z=9PS9dN(s>A0C z?;-=!$A={O?;r1Obat2KxKcUEXmLMXV$F@-;E5Zp{2$6?%p zd~I%%b)zz?Eq&9RkRTJun^ke5Q&;-NBCI%WCLs=RSD!+x8`QDa?OJ6fdbZigPRt^! zLVvwocaDmc{rUBY&07$uJ-CBgaWJ7)+)bUl3w{M!onL(p z@nyBmCL+qP{kXVDG`Ug`qwK-*d&;s#0jcU2{H^ay#WRP4E;!LF6MJUGD7T>u$C=aJ zxq|K}wB7OAIXWTj9u5&_;VkNsJfq3y0LH{+5>R4!TvB#9WD2_+`3}stct^7P*)TxK zkcBa#XXv30S+2jfX7hPK435NwPBC7mwvO^4@At5>r!or`88yt)k{$i4VW1 zL2Rz3a$&cuQ?HlmK@ykgT4ttX8nMo6kYDe%_`g&>_`+-bGRSl-zmVFb&1s@+q|!Q( zz42g&OG(K0xR84BU`vl9mB)Hp5q^kh5feW3zeqG{>{eD&RD4Ki`Yb84ei`XabB!`` zN2AueV~2gK``GGCd=8qEwCfr0sINk*rY1sN+s-%cOai(lNzWfOn%KBm>V7dUJC3D( z*YFA~IzQZ!)1zUO|7~j&LqCYUwG=?VoRCW4Z1GlaO+s;QC6p@p+tt@cU@b_vw3~~? z0>p;n*Y4f9d%!}y~H@tIze zv65G3o$m}2EqZ*z-hBS~D4AT-(Yen5&9BxFxi8Sndi(>;&zasHZ+=(n=1nFe6Zf3< z2M4;7F@@CH#!7y-ZUt&g z%>~R~ncX%fHEm=mHD&>BX<0icfO(8)oP2Z}5g)yz`#>Hg{}jqjtdyxRrmt?S{$4vwLTLFUAJ_jK|CU0=4}ows@xNTTBifi)N>vN?8@z zj!6FK)#f_*cDz4MUFYw=EO!Jd;po)!3pKp1eix9HBQQI0{(#M(H}*%%h1BXmBsu%Z znUi~=hAv7}2u{JC-Z7|MOjd#=0EJkz7#DYQX>FM{yB?i$1!c`zUT2c%CMMp^q%6A6ELSednvD;GX6z!1E8g zFB?WV79AQUQn>6FMK~q!Dyi+DkC6KbRTCtA#(wtawFvo?1e5J%X-%632Wb*#h(t(`>9svud8scn$Mi!JRE&nt?8ck6q@(H zHF{r7?Wfs|v3C^Ab-*x#T!f1|%f2`j&Bizt2XdeH8+Syh6A2REhZlP0`q?C!)n7}e zUbUH0cdkWy>_5$@pQ~?U_x(iRwQkF2*fU!to3|4@+M(6%GS{)iZoWU5G;~g1zxCnN zQ1~sm+)1Uh#DG)v{i}4map+OM*7?OXbi)i5v?p#=zgg0G1eC`S>=*G#A94&t2MPN; z;^NwBHy)V89hz!!NOY0IUfx3;l7Ja6P&-N4b8UO`XH>v9UljJlLr?wbU0t^poHrI; zmj@f8>VdxDA24z+*e}Fhw1m{xdp=acaZVfQnz4FB6G`y#V-WDl(xx8acn0B83TyYK zk-mCI6@ZQ)@V0n`9^~R8ItD_z@($z}Hy4(Jh0KjL4YLT3tFL?vI&rdtD}EQ9!Vs9O zG0pqyES%FzDF>asu0c$adl`Hq@P2>W-t@KQVIK6F`}o?w!VM@o3>n)IJ~t44i}jOi z9rt>>EhBNiWBti<6tJ`ll4vM=AK^u9DiJAAPXOk4A$oHP-b{a-C^22fUGM#-I|uWn zP8(($9?b+ni3AgOs#Q*dgEE5t(FrBxdA(`q!O?u_EDeL_7Kz`CFA8-ECvM0{&O0%odQMO}k~^BWEqP)+Ha)?*)9%LhDNcZkHfSnYR) zBCD^V0r%F%>W04ZV6Gh-kGN`>EKy!7=`Hii;er=!YsF+b_5G*PQb6eBLDB>lStNqY zPJ%SK!4rcZo*n9_JUg9a-B|iCN9Xl6#&8IF)Uf$Pq+^9y@69V}gzOrj?9xgLxDIu+ z1;yY_TBrEy1{iqIKHrdfo8K^CPt*I;{^3New-$AObV!| ztf;`v&3QE8GdX?AIg8z9m0&Qz*EDg`Jb~YsLTRirP`*m7=MF8+pMzI*z(X^m%1;^f z{Gp|R?Qp(@&0TL#!Un%4qrh_t7sqwa6Yr)=fktfp2L4)sx&}(a$!1CsL2RQa zLnC{tD`WfAgRv;A_oen6yN)i99{;8ERO2lJLudQnmKjTlR9*;PPBx{4`WX+{3pAxP z&+cs2rc8(~*!9u}x9-Z;c{dvPLF(!px?dyh96f6J8x5L`AlJd=6F0p>QjHv=f}M0+ zI$b>tzVmIao2+d*bgR{u58K>`*531@}lLz@3rMC-T8M@Ro3gZ|XM?uQV zYs)!NyKj8dcNi7wTu!I#)iAJ`gv7j?`w))MkI*+{SO^D94Y*IS3B=u75ds z+IV?${-L=DIAXJn*0?;VMNcBg<>P)1{sf;?7+w}Gj)5^&dih6_trUE`25jufW`3`x zVXf3@<5d3B25hQSq#@PBwN~aYcr~0`zH(L1y$z2?7clLZG zyJFcyao`FFiTfEThTQOgAb#2o(N7n!ye?wml`@zGK-D84MNBe5SP29(t zxRmyrP3|+oTm6SPkNfNM*g0*7%KQ5vLnc)LN9ro?ud9|?wrdy~kDA;Ka1$-`@YwA) z9XQzD&S!$Oc!o6)S{Z2w-IVGUp6p7_Y~hOh+yLp7>2Qt*Q;(=9`)5K^?6z=Aaj)!I zBd|9OSWCj$`VDdHSo^9=?JucPn{h_jgj4tv;Jl=Dcsf12wX1pwI!Ru7YvoEm?I8w+ zh_U#pvXY7>-ohH!jYRZ#UU<)(O6>!fQs{&ysW-Ad$#Rku$*E?Fcsri(>usg4w270B zx6FGTRWC1FRMY4A!>W#^yu^%64$E54*DDWnY-Q$7PPPn`>{*{0Xr(0T#p`M_93yMs zPUXzgY1(g7N7K@^(o9B^(-TF0W$J2+3%-SmH{>;mS{Id+R70!ynF?FXjaBrWP7Y@~ zKIODz3Ij=yO~7=Hd3I-tEimrM!|((C6;HrR=88LqKE& z$BI%Z?q(v=Nk2{Nm$6a3!#9rl)~Xur!d9MwoN8w!FU!5&rOlGY+8vz-Q*&P#rXD;n zq^xF3B&n&OVy!4KNi>{?kHj0L*E_h_DjJXereP^{vz87jJU+a0`!Z)>MQ3FvzgXyz zqtFSTrTy09>UwsknHE=HO=ndn4bS%`azM{_-iFqyQpPIolfOC2cx|ob43MTrL9QWi z$FIc+d|PDJ&Q7U%;NeOA@pSbW&GD(X8A)w|P#1UKKpj0(EpJ2LjnTo*lHK{s@afIZ zo3l$PE7DUp3nXmfZiZeOx(kqhN3L2=!p|(2$XAX9$m$c;1Q_a9SG*=5kTPY5ej0YC zzVgjI4x99(c*KOk*!Xm+0VqvfL!T!W6rc9XSZ6B1Breuqbau|v&@{mSWNe}_7oXMu zu>K}o&kdw~=c}WtsuAR@q+((k6lCq??k;1i)Pb?xna*Wk>I?8JxSAI^ekIsdMzAy- z>>lQ8L?Ce8G&k_7YakcoahaS4-hI=q;N1kC*_cdsavFuOb>0!q5&W5YS8qfITiu)b zlOA<!b_x{;pOWNvvA8_p^7<4?>PQ=4sR#$sU-0*K-OD0)*#M^opb1f&R*DbX15 zl7g#0ReYzX#uud=?oKCt$kI{3H^|TSQ0^HsfG_-W8e3tXy1kw;UBc@2M`ak9#w#iX zrJF}V_gD;me~G;#ukdnONQg(ivYZj$??w4IV>ek|kyPH0CrEYhCCAt3(V`tz+2XM` zYjN+e-@K&I#u1WyYxXHrmvRA*j;C0YUJ+?lW%w~KmXPrv2hJ(Q_HIlubybkDKej_x zk+x!I_#4QMm-ne%ZA5FBnKVCpO>ncnejG7E z`%W_5isY-=xydw=9sn7Pe@w(lKVaGC!^YdlmFcg?J+98dhnQL;O^K=+ex zVxng+(aZR`;llwR6V4}-j8jfJt|7KB1m4D3g#(mTBdTJ_k#(9s?{9&kz~4xR5{&uE z_{;zZJDdlS3DwFK>Xgh59ouzEo(w5@({vqA*~<~VVC~#Au(1+XA1lkl7ajccO{oiP zRu#$a3B763+!voH1?)R|gS}5e(RO^UtNeK|;e|>0? z-=OQNBVU)LQSLn_y4!A)X-ni8vU2HO*Smj@eL*b2MIXfdV!piHq1c%9=_bhv;GwpL z>s|i$?5rXkQ9qH`K1rHQZ^Qv$Aq5u3cU@*Lr1)(bIf>n%w2bL5Gq;rn2$+^F!YO`8 zmduB|Pp?USN--$-O1GLGH}B1pe7|qF8ySl3l0phzW5=|mR+n!oUfB@a3a=mPYHJ9l zX_fu>P&*w@-}Ul4=pO3~i{o#ae3Py{W+I&TRl>8HNp_w*DDvP|=tEixzf|Wa%Jl-# zKFZCVa>bhJW+~8L1f&pwJwoXp2Bc6UM}?k;T67x8N3>ECZ-xS7w~zzj992Fov~2p_ zoq5tODK<&ZG&r(^KZO=6YdOdc2fHO?n7@bTShCSoXQlc)7S;`mJX4GuCiaMqNl@vB zN63~u40%YSUFe|%7^}f1ZAt%$x6KU~pla0ARlpx_F-gi^u%D_o;hQ^>&XJ#_@R z_~FX!2G0bN)%`QfA0)7L%9oGu&?RtD)nyTfQ|L&S_qlTChBU!iC}b?N)kEo}Li-xN z#sAWIsAmJW;|&S@5yAiUk|4KST4PwSx+3Jl_l3S>sQs%cO{QK3zs2#i(bqPwH6IZ@ zE=jzP=NqpL->7;@#YQfbM2ET-d_% z2XQB@ZRmYb*2TW|C+9|FvV~@1M1Tj7clRMu_}|w6dK|?CI-E~F89=D$;Nwqg8-GU9 z9>s09$H5CejwSa0R>@?G6()0yA7?pgkgV|Se(`Y5Q<5)tg<>u%3jnlRUxbxjbmlP| zRNgZg$;%4I<1GKC-8;S`Uu2biRrBcG&VtgMJ^8`BecD9C!2$X9e-FCY#w?g~FZ$RL{m7`7zsX+#j)oG_dFgC{?Qm zO9j^a+D_DG6LHMKB~h=)@FtOZ+U=rYFl^^(nL&4uGO|Xe^*BejyB;w0d!jG7PkiTnMHZgMCALz-zDdsuo(s#?i9bLo>@_EDzZRe#U^{OW=e>6ke+Y$2{%P2Tp6~D&pYyHl)<~i zLFhT2wMqw`@JmWIO1g;n$Jlv|K8?>`2J4IqteO?RUog;NHNeWDW*GBgVBouW!er%Z z^}*hE!oy9y+|4sgeN{MGawcz(vH!Wb>`}RTLMJwiM1`KpA|?KLzP<(nV4j($4pdfa z#69%-;yHyyazXKH2KxIfDiz@gLcMllwPb0E5mjlA58NHza^?q9(l1SEL$tG>{o+H$ zvL}4TGbM(|mfIR;=QKyGxbWUl0`Nd+3#-$gM3g!V%I13NZ)jNL(Skl}#Z!HC14|D4 zAKvZ*p344@AOBs73Q1NGvdNacPR7B>IHZ!DP$VPbSjR~!E96kJWwwx!?HnV!?7fb? zIUMU8&iEhQ^LuySzTf}v@%#TDz2bGfug|&8=r~4oJ+**ZT`l%zp|bbeif=J$p1h== zi;5QzW$1(Oh?idFd*;05VolU2cF(EUZneCyp zl-N0gVjfddgEMav3vwlFoi2-nKG)d(_QLGJZm(78rIs-C2@0 z`uL3N`Qmy*;nCpwQ*Y1uC)HMhUVHW?r1gc~Zn&)jg!`&ka61dJN%!1Pp!jH**!Ago zJHu5uaRF+@_~hrG*(LS-3}#q8Ub)?S`;_{VW+nZX*v~-4l-XMm@0k4z=cgvstve&q zEl)gC;`)%^_o!XnR$PGZikjqlm`J@|A@kbT@W^ul)btu2UmQcPW-#hfZmcaL)XLqnnP_# zXPt3ER)u?+rHGqzmYG}6HMM1jSmW0rl&pSS9)TSFn|#g*jvw^klaL$B3qE!e*Fl#O z=1&w^GhF&|CeL8LuIv2qhJhw?s?3xM@%xNA9UG5F3rbO^~!f_N$||z!-^7 zS1sO&GF?#B0Vx+2-3+O*v^pUa@#Nh%H}{hful%;v4MiP9sc*#epcD;O1$tZBaDnJk}+|d_owQD7m$Ap+_j=rBiu)($I6D!25+`m*XC>C1dhJ;H8sZ z0!l+DzcEXH_o4ye4rJ^((vHl&uiuW!-Z@+=9e}VJjM&f7$_lXE5KeSeEEd@;5>M)q z{iL|dmY=(j+#^ruRG~tbZw!5p@_$^lHAOwOy%}iPy8A%sk$HDLE33} zHoXf&w>U>bmp3+*z%oeOG5NhYh^qS>#{A0{&T4;+spq+g<9Vp{dX~nbjw)iGxcGk8 z7bQ*w|INa-o1DZVSe2jS*1Y$;F>$SMo>F^lCkfF8#cgks2-BV7a{(;GMCVFYLX$~; z6&h;2KL#5oA*Njcu%@EnHl?D{#+$^p_kBwxo@`#oDnn}}2n6c1iz^bh+=}Sgzv+x8 z4wb4VKPNp}9yV`Jew#(&{B zhaL7s!tKY~ge&t;T=zoHm7Hl)5jwN!d!>e=b|$1pKmbiG;_H=^H2QdOhwpjvi(Czw zZ(>pUl*9|dRO6AN2HtnYY4A4gunB#}@i$dc>-#OYC-!yFev-25B7sw2%9Nps5H7E~ z?p@3AD&p(CGuwgtw&q7sA&w&^9i#hqNfn?qY6^DZ?{L^)_5Y>ftl zjXY@|D%!QXEq6y7{4mj$jU}#Hx>C{YK11oE7UNyR!H_3a3qs$l6a&0R&&4&>Pa990 zH;SJ}dgIeKsGA1#KBahfd~FS7Vwl&{(|e)sE%5oIM8VySm5#eP&-MMj5BfcS@J(sd zPaCT;=oTef4gIRjaBiY@>Rj1kHML1zwflDDjg!v<;>Ow`JI18aT!QY3DFknL^%6T0 zlI*K2eeB-Adt(y7{FP>9>>z+|$J$71%}=)G(kZ1VRN?Jh72*bFZ(G1>5jmZ>08gF` zlbP||_J(P^QYE%h`VvkW`NTQcpA&US1OIZI`Kmzd&?V@#B&OgspI&31W_!}5d=*k6 z3l)k_QeN)`+uW-Tgzh7+bIF`@uh@Si^?MX1A>oO~SzKhbDW+70(jbj0qLwak-i@uxYam{_et*B-?F-jKe87j7yd>Ec^=}^qr5wEntxB1F|H^++vUmrbI zXA<;we!;0mMsTM-1b2Wnj+gFxo0b#tLrU-dFsb~w~#spQ?xPAX59wg3< zXOh^$1<;H}kWakH5nES1VHH~k4Z%-*3~8)ADY3;s5_LNZ;y7BrjpWdhKFy&vLy=SK z_^3By!st;i;K|!y@+sSFbv93Gs;=`$Y;6!YCxE;-rgM47{j=NvtjxK*dJG$R-z9E< zH!Ys|)hgSB=TnsC%;w+(-8*%*6v{fz4e3DC=E)B2XuGKSm=ox2ntLyI{9H7!pXYz6 z(^g)%s^aSHqRm89?MSSNa&GG9uA&$@;y?K6+=t^2m=l3-Y7Iqii!^e7v$;?Q5(#xX zJx$O2Sy`TnL6TnYHc(F;o_y;h=+e0l?BC9{-?X{$>6G%D(7u;d&rEg38;5nqYZXsG z3VfdK-cfn#TJK^$+q7e|e>C6$poDXhV(`dQx;s?#s!!R~u@YYACV2+wpSm`=Xk!Jv z7$=oJE1f|wR5DB=>s6`n!McIrN+&x zQ?;<>;~m`i&%0_*InU?an1g=vW1SSJLaAYe&@m^F3sBZc*d|eU-3K;FfINjw6Ub*j znuRi@rSB5yu}~|{LT}Y#F6F%wJ@dN}JXLk2M<;jpJ1wUxJ0V>1O{FcnN1n><%jRSD zORy!!@T#MwOrSf6i?`-RrpGh%sm!9WQueI+J+F(&nNb>wRdjvN z#AkK6l8eP79!Vi+x`nWXI(b;Tr@9F4x({F=_EPHe^*+SMXrc_l=_s)Pj;{aZ1=K3NmsM zWGYAcvaHwv^&)*yQc{MjugEFLD*UIKbD(Hk`-`ff@K5*o*L(WsJ-_(lp}F=~<>T)U zz#ksS|Nn5#W%-ML&^Z^S<;j}J?*t7Q#F4&e{)dl4zN-hn{tDobFZx#@M^2KwTvCE; z?kJFFcic;)?Z&ec2XoaW!>ZdTnsZaY)1 zZ-?;98>Qs3pITvwj%`e4>bKH~zcWAXcm@g4CV?JDyF?dK1a9Nx7@Hu}lY z$&l-W4wI)Zt&}U@6@Dnsn*;>NpTbyirBqjyz5ifZ`3Zb>%GG^+Z@gjExjz(B-~Tu)`)u&iGjEp*Rvqu1@>~;$d-L5F^Xa)4t`G{pcq)yctS)^OhMnPdG1k!U zNq_{C#?-mSYnMypSnq$lZ(t6t3K_6I4)}Zod~C{40d=r+xC<;1II}{Htp6cTMCcUc?`;PN>sRZ?D+J$(rLWb@J^` z#F{TPbv>ro6m$|e{hv_DPYlUVgP$0Zp9ViMBtH#)Vn}`({KSy_eSrUEouY=0d$y5GPH5 z1on{f-;@+CEZ0QvW6q-iC;q)OQ>oK5T)4pcg2JBW*)`5EA9+R(AHg|{VeDnb5?2Sd z$XxlziLw{dJ{R7!k#YIpJ7RP|h5APqy5tQTKOd354SrsmLPG(d!Aq>y*UUa`8x=M7hKP%i8=~6bx8Tii1}%N zcUaG;jiLmw9#VcXVtyX{cM#^>x@}F=5dbCrkaBC?wl?ZGfEj;Ckzb##iJ}0U#vf9= z*QaZvr~n-JL&~vrh8plMfFt-rism{)E%+FK9)C#TU5}{&9|fGmA5!erV`{;afb;l6 z3gfzQ4fqJ)IR21weciYgd>p`pKcq;n_tbzX0JQjjFFzYPe^++!kJfu?!BhbD!wSv% z<(iIP02KH`ir)I=+KyuY2K*sKWWA`S<0ya{e@J<-UR2vb31Gt?QZB4})N~vHP~rc* z{A>gLe}V;Btb5dU90#!A4=Kv)n>8I206P33#eaRXwu1`5i9e(qU8k-Q{{{IA{*dy2 zi}m`y0301+Q~j~)ck^f>xW5MWN$r9OMG55t+CkROGPj*-`XA>WF{dbDejqw%_W28T z#mwJJRJD>h)$$)Gq<@f){zf7FEFb-yLiz_q=s%sIfRk9hiMYqsM-MAP*sO`TC)TF| z{=NKzM)WtHDB%2^m-Zm}GfE;9mE^zX|J+t1_D9jc7pzdppCQR{?Kr68QBSc*vNtqE z;{Qt0KPRUjLL#d}TwadqE8R~Av&ehy#PNR>(+`2Zp7x02aWRb?yri$)=jlM`k zd!^_?LC3+y-^8m^#eTKR%9jy9$Qlc&|=BuHa@QwYZQ?9es99E7Y>G4|YY_+ccUH~VFpmp#QvaAs^k$Q~8ixsH-htafs!AU&-dSZ?1 zQRHu>j!pJ&icwAt;5boe9UMp|IE*J!kCGU%|1kR9E&2m6`iCMqQTiBlG=LiW@5Nh> zuHe6TL}aSyze__kuK!gU8mUG9-4OZ%6Z*5%SB3nm4>TY5u!eH=Dedq{wqgcFJC@c# zrWX`m4_LOE#bS$`&Yd1CwCn^{8usVe%ir$sdRq^9TV;9AttNZ*iNWwQ#nnC5)3N2$ zzK$$QUzTmTyy|7wp1TZ3RIKhyJ%*0b5{lWjM^xyj9?ZBbl#RWcUaOYBEdbHzSk+R2 zWTaFYb&Pd-R(AD|A?uF#l~e7x&<$5ol?2c|SmRizQgPVi!20^#hU{K(T~n*vy&}1y zHop?MHX2ld_F<-4L31p0s4O5=K9l@9i7E~xD^xefFp^~z6~2Q8?(e1({JMzMDOZanqso>{vY&qTbXjBPW+h^aXiQups5c`F5 zdn8I0t*x+cR7!9_2gDX|ZVyM5tWlBKn}Bt25Se%BnMkE1Nn>+r0klNNe;QHMaQ;HJ zZH}xTR%$tq!Oq~-)~Wuz1lMpLC5tOu>xY#?Ri9twAML|bAI{&vpToSK-zcA^LnWTy zw4TEx9?oBp9)Uw8p5L^dLnWSHk)E7GC7$1qoegQR{d z)YWdxvq{ga@N9@UvH#20m1-g`^euHKaNY+RAr^2dsHMQRmqW_x7{=r+xx ziOUnIM@Tf-do=(G;`#NC$7BQOKS%TbcjNP2*2m;U%k7B7O z#2+JnIjkJQ-TblZELL|yJP>(|`0s@y&cuQeb{=oP9#?DqZ^fj>`Uvbeo@xECQfqx2 z#)QAIZt_p1LB1;Np*Pd?`iFd)7xkxqO>EDR{a$hcCC35_6>p~=L?Tbg(28os_#mvP zKq;l)#sEdb{L_Q;wC}XT<4!fA$@m;vCAecwcd4({pfs z`AyJ3*UmEe8f;SYZ8@y8_wzFd16{;r2a;zoNpToPv5E}bL0#E*O(V#464@L`w8bRK z|8bn?K+-QJ@ejioR*|7QD9wG>bb?$L@qZcvmPdGkb^zf&lGxL?IX$AXxn6aT^!;Ad z-RKqma`BcH-kg^!Tnevdq~SfF_KW45E%Cg5pf#{NbITC#{L3U=ca9buFWtrR$1{Pv zbmX6Ewmjpdv(3v0l z%cYhc9Wfp<*mGGY1%wfS--}TWo^gi@>*VqJh_!6$q<}F;;Vkf~keN$xGNz;FPTTT@ zcOcpVOjdPtb*!$s^R%?-xN!R@vp85kc9bs*@F3^3v#gf=E$ zhe!v9>0?}Vh~jXVE{2Milrn>g#&flhG%(t{uu=1p!EqEnj=LpJXDtmg2*7f;0Jw3k zI*4&|FAtxN82{Y3(fu#^aJ-9adA?dcEX7K|vOmsqhzU;GxxCz6ne=(QaAzsip`I3w z)Fjjsdpkg6riT*^g8fB-VD}@VW#szYM;7rx21T2eY2e@PTq)K{` zc6PZ>@MsXS)}O< z2$uMjE_ikW!&*%&QkAy$=s?+fmkLMVcyCZZ6)zG@ei&P#Uan|c@Zm(^q!_yo$TBbn z5HmVtzQzX1M`~cgc>&rO7M+0j8SxnWsqa*ULGZb7X;3zfr-d$VhKxnzbgQ{bv~cli zzZB8ohjM|ebt3GN?buoj;1pz4Ma1zFi?n2-_!c4YSKPT< z>;Sw&bfAFOyik#rj_6pE=$TzmYmp8)h52yu^Xoq%2q!;5F}#1z%3EGPK_PMOzX#3L z5}@1ar}H0i(rNu$%yn81iIPsMk`AVi54Qxzoc=!w4IJ}Z!nSg_3Ga1am%3Al4RCEc zB+lnLzh72#b$7Fsqg}pqVGiq%al}fT*3MNP@|e)4gSwyTD$`xcI_qQaEsi6Fd-SWN z;#4Et8CmyzQ!|`eD&j|Wc&k$%J>#}~tHUDHZOMvbX2o%{?*9(x7?@r&FwH3V5FDeI zniG?s{U zOKI0Z;s%76ds}w9{+eIW3e2-d|ClgQPjWD?9{rqz4#n^HlKqAL%!6nHM65xHPL9RF zTLIKn0rSil(H;%^OEb%hGfqo( zBc83Ri;TicjJnH=N=uC9%Tzk5gXaA@FsDqUO!olmqDRDLC1aw~by&nosMek}4 z@N#nD1Y4SP0-oD-M}>K8kATL3QhnfS1m0Y=7AM}G1bC*!r%V}f(DD^{KR54e3E5fE zX^n6yZp`W0E^!FBCCw?40A~JvuML3OU>N5~RB^Y6(j%q<_ZqqP9lHFB98}GuVWJqY z2qst*gA(b^XI?Z8--RI|!mk2W6Ux#xLy{#jhY6oJ8$NqJ=2tA#gbU zNcc)QkoXw5)6czsKf1c0dHj}LstATo1VbfK2w)x(2*+!OZ{-8`hPh#F+^}!@18v{S zHO-V>7JCUg>KpUGba9b>V=o-xSz>@U~lW(P)N#bXZa91c;CQwMw?z%kddn?nrbwEF|j`SJtK zxhd{QW^T+rXR6r3`=|JFXLZfm;;MFoc9KBk|1?eu2d@*v>ll@gc>w8hCUuI2i#GM4!Ajk6vChwajs$H0Wpyqn%|7NHiHtvpc!$HFuW3wn9L2c zj;=PIi4tyw2)4QkVg{ISL1B0xkQl^GQa3`F7*(Y;qfBPd!i8~RD_;&)BgqFbVRSuM zJ-)9&e4ma5b#WLaPl19ObJV7L(l#7BvCRF%4frA!ceqfXxf8=An$kX<*99yt8HZbxM9KM<+nTc$Vd1ks9%o8 zj8oD<d*lV^8aN9H8IA#uKe*BLgP(%X|x{BGpL`6d|M<0 zNiHk=K6EYQ+h~XwPxG(dkZvjFxYy~M^e|+mo|kOqs4X*4=7P^PfmGEnAUHTi52qD` ziJ*kuYfP)Iqg?frTJYngT+NbofYzsN(;nQ#z4j0_RD;Aq<#Ec@dC3JO1blzY8SK#TaY?b8zWB-UE(R%hKbXtZ2oEd&!MO2GgcF8=rc-`yG{+ zr8zuVqZH@C4$4cv)(Gw7#tegLqnwR*J>ES+u=g>lJ$}oV`S_ixXZhhd`(6S7?$RxI z7HC>~ns9Z7)A1jZqr(paDQRF#;f}}#m_{=XXfj7 z2lMAfy_rG9&veAw(dl)QTv}5rgkTHV_&4brSq}cDvEoI7B)jc)0JLJ2XseVPYLpOx zkV_B!0Pr;cC89@oQ=8W*qbR`_wbNHNb`zVmbjFQ! z6u5!1_53<>DkFB6JqzREyC;mly(K3GoVS@E($|rmR0LG8`B-{-F&8Xo{muws$U`#D zz-Kh>zZJ6TK1fO@IfA52BO(Y6xY&XK6}pCDAYmA27^XZ7QyYe<4BOK+QoVi<;x!|p zp%D>mgaAh)(*;x0v93p6lG2+2Ml-2`7;Q!zPZ*vaxT70gea)_eamWO?b0Zph!_Jy% zmW6SM6S$)t4K-#&3x+`$8>qupveNOf+%UapKz1`H`GsDHH9rh5-T+Gs#GeEbqq#{s z(Xh;Bz_l4pp)fRK11xNc9*AQCZX~698yKl-L<71u0)+QZi4;6ewLmh^4j@T~&iGKe`> zz_i%ve%BrxxC~?-Vhi7@J}9JB4m0sWZx~>RI=nwzq>z(2mxa8}uFE(2VE3;Z(bXl* zR_5fXQPqF$_+wSMBP=L!N=VMqRW3KG(l0-1Hy~oistg1vb94<%>?e*%L&kasT+D>g zx_+*E-Qz5fuGH6jmgb;Tf%o}*WHe_qI(;-U-6A5r{DZ?M!365CemGNv46vLH=wu(( zh?qi0#<;8yKeX{k;c)z9_{I?(3-Zg+BhxwE<3cfcsejqnF^G?Kk`*d_aIlBGAsz7h zGV%tQ&dIDc=3kAFa&(Oam3+^vHu`;Ivm+CC556kvh>ihO)Mi$L`N>R<5X7c?Tp(sg zHGW3;4cW)}ZG5oCFIA5$>io8syo;CcmyJyM-h-t$MgB!VjJpCqPW{Kx+GRk<_#|PT zF&DUt9QhzEq=a#O%n%&#=YM_>r&hN5bQ0+7f2M6K2}ti%rPu zK>V{u^$Jb+aq0ZaCKwfca)$lL*US@h$N3;D&p>`rdW8UfT-bl+Isfb{y;j}$*l>xK zIO#t_)@617Gr|coHf1WOfqbsS z^tbDoDP4A4{g(OVatvPhw{0O4vcq-vnpJOR)jN=nR*bt5KhF9fv*~(fx|bbAzh_#! z0+nBi>F?HSwTzFgBtP!<-Bx@tJD1{P%`Xd;-n}N)oN4hARDLnWU5$VCiC*iCgji}R zAr`xCvm}tyL;l=XZ;Ef;xt2#>6%8sUqfb9>hfpaQ&*;b;O#zjw#JKzMuUbktqMv}u zm1Ep}_-FIofAhM1G0{f&AF?hDUC9S3CwS zALXCz*Kf^Dj9r!%g515f_ARrThJQ6n(osC(_hpbGy*-!LAk_D62gH48Yng<6*6$_} zjd5D#wh`_PtV5g;D-l{dT0DLk(a^5u6bC!7bfEz2BFo287~XXL+6IbFdHdF=bqC3p zIIxLKA(0j>Due`PvXhb%-Su2ryUK*_&1PSCl}Pq-W`AKE5)Ah@Oulm^c_2e5I9)J! zDjUtVXKWGG8e8YAhe5Xt6j|J%_-exQgr)YV37H1{z2vwWCOB8ySN0 zd02F1H@4r=e_0tJl#^}TP9`36dhk6s^m{{t7A>c9vS#(4yI7x-{T#7Xfi6c_Ry#s& zHiQXnoq3@&{YR$AT+ZGVxazN54p71EAj^wk{mV?VRZJsFWX6WENH}bf={p#>Kh4yT z6gHwEnVn8}AC?yfMCE*0*e_?EtrFb}$Rl4c+9a!NiP8+cB!wm-^xfTXe5n1b8ieIqdd9gf@VBM#aihOQFr|!S zD#vUSBW|jCDF&7RB>FJtzQ5O%;LD6FGM(wTd(eIx{P`}LlnQiOGA+(d0qzKfuNW~8 zWr!4N|JdFDF|X!yq+>6JuRxfGT!DL_0)l{FMl;Dci6TSBIbtPMYX_XMk+Fel%faP$ z;Rke5oiaOQSW&D)6cHrI2^`sFHg18rzU=pBBzHszi ziTZ7UOV^$y6%Ni`49f{>G^UEkIn4@YmrhY^e*NgB=;B55ZncukphqyOMdE4JRCZ}q zC7oT!OXsDFBfV-R?}Hu~Q+i*}vhwypspK8O4Bv8=+}qg`jyk$rPNe&goSadX_z$#mT6Z?SkE?hj#^y=qDR z4&5lP7g&CcWmerXmfAC#G{Fs>=O(Oj6Uc9u(h0g^tup0;12gFsG2uI|<;?+=f^z8e z(WkK7SsjLb6s8Um^}P z0acVks;$sdsE6|h<_kz(v^CNeViXc50c1f!12_=53$R)kfD|N%x z@5a*+E9S)#eQvc5=Fe)DWG5#DUEpzZpu-sg^uA|2;EIpuu=bh(_9?i(@xHObkkcS) z!!qEO5?PXZ#g((q z-@Z8jep>etbcfP~ZDtcAFf0o78;H+?p_eqlnc)H0Uf|T{vZol=$(;|3h1X?UWqodO z+yLG$^0h+U4PUK_3TUqJJTKfB%5+D-cLMk#5EVv>M&5jAR@R?gJ$|Ee+nQTPZ_B#e zw9I9-Y_-N2FuVmXZo8mhW)QD zD(^NSmg1gct9yANOk$EI#UT+6VFcaGgKYZ;Ec= zI2VuolW#FG4->g_S{2JRRe6w4M}&zLc&kbEcfT;by>kPe@b<%vnBh#sM_I>EGl27x zoA1Z!R%utAS9w->#7FZf>KT)0}e+Q&SC zaP%8NbRkg0B*MTB=~1Q_=`WlQr7MQ>Q-o{1yAuO!W;LR?0KxpU1mwt98;vKWycKJTaMqj-5MT6Rf; zS^7Hfne4aPbilgXnY_E6=Gl{1uj++Fg|9L&urr;f=Qw+T@!Z6tLFbC>%=Op&QKoid z29}S^!TKC>c}TE9ULC`YDCL5Ml4m)MTLB%;PSGUin`j90WU}X0d-@uLS;aqxru2EJ z4Was`W1;7M>Q#8IQeokz3-kL|;liIrcJcEugn26bJeNGA^2n{MhqQeC&gIRQs#UV9 zZ6J%}lauVGqlK>o5~@{!*Lk1#A4f4~e3X^`I$66L**$Wyg)gggIz|_t@0im4Az`Ga zwN+R5?AY2V<=~v`xarG#esS0(p`Bv`yaOxc#=IX`aP1%FUZMIwTypscL=`QxA3lF}r>`UoGvaOz=JWTo zFzdD)8=kp_G7f)jR!RX6k74r?w|v^p)YF!#D|l5M-$?h51I)*8{9UPnxieVP8guRShpz`hF`tp#w6iyoqRK3|As|b*r_VvT^s$4G8>QuIC;}>el$S71ubSz*?NVZ zsS*OwZCM-1UmH&@8sIoui zBHY#-qdyL-2&PgUt`9OC2_$lG;8~U?>zWoPxuW{&H2~GY!Ef6&peo9ESbnhf*3x7& zd389)>ZH*-=V)YbI7wYadGDcb*-U|N*;bFAH_ej8((+^h$xlT&&x9_D)T5$2*~O6( zGHkHEyPkg2papY+x3;U}m@7mC@@QpJ^?X2@J8E&wkF+SB5{>0QZ%<(8MoceWGAhL~x z@T{V0>0w3HUO|QbMh?V>cByJ`VD<}%rLt-*S}fSZY@MazOW4i#Gk1J$Slx(u_;DMx zif;VfX%+nwp+v>*diz{khI=Z`;=H(YQX zveL3xIFljSb{T!*-VXcQX}9Mn`YF*1s^{m2>D(fG=_wEIpwe`583W9;^q_*2cfE$-JNFG!A1l+JWls^{;i;=*2fcit-WGFh(d@Q` zTgr*>ugRBdQv^IiEA{Zr>o$}1QkN^yPyqOQ+-9xW3<=&mAcH_@7u=2^D!>ZwMlB5Lt0Y_sSdlD8>)B%mKRuDhS_ z6_ByH-QM0#@~EDg5N}8L@1@QZ6>jCi)X)~SuS{PEGjLC7LZi!hrfy}ndQhIXDRU?= zxaV?RT|GoVT2*z@8M2?Z#!-ewACbfmoA-%#b6QpDQ{G&!te@*-XKl>=&T(!{(|mtI z?4pz)STUVGIU_JWp5UL0!43zStCd~#(spnWE&*#6YuM{--&}oHa#Dk#4zZiq?&13G zyj11118%W>b=P$)1Y~YhaIf*!d9ET=MVoNndEsOfem71jRfF2ah7qMf{e&zOUW=WQ zc%-6pIn(fn0gpuU`A<)RUVRof(x5o2E|a7EHTdaxw3@XWC$uxk2C?J69-z6)#O?(f zY_>s!hcyDrZAa-+7ahL4+R!t z?{Qv>q5d_ym&~n__0A= z<2mm!-j6eO-$SZ+1el*W`R2(=k21WPIQdOx%2*jvr?vbjZoA#pD_{^;hrH@JJ5BQT0nR;LMn#Ld&-%Fv zIlPmT?4!fU7>lG|d3P?gyY)%`x-jpnNeOy%Y3OLW;9F_? z1=*a}6E9nB88*C{*cGO!*LIE*5o~kFDF6!(RDkDU^d>ZJ_nYN*cvPF=sH^MSs%iB> zTO+GjY*TvJSp_3wuk&f!(Hp}`-OPM3#S3X)Clm@T_9O9#o#Zq$71dIpug?47ye%nD zi}x>TwqDi-25B^YQ21b}pe(}`cs0B?^VF%x-j((@gM-H`OUI&f!qt!Ji#7)UZhhG2 z;upg&ife^p7rZRygeDOm5S%168?J!q@!h_P;*w)bQ4J>|UWg{iLs#cDo}ioAI)vXJ z4RXUIT^l|z;cqt2z z4j#1H%4KVxa6!236>W8{zkXOm@yOfbNe+2EYc#z_DYC{wot_L3`?Cd(DJ3Bw$0Mif(t+Kp{u;Di14IKlHvzJs(<>CFdpKpG@vhQ&3+e-=2hao%{ zL4172fphmyYFW4#R<(J@_2M=7(5YHJ++4?u1#4_|R~cC21k!GBBPN-gA;F* z&u6)@aK3pXz`doJ+P(R)I^{jDKZ6kW=v!Y#?@V(|=BL!C;;_g^PJ4@Sr#-9seTp|d ztZ}2!CU%K>c>8(R09?R)6v@2t>2Sc#BnpD)!`1m&PBo!hPZpvecGzH+*qu<{MpRnKoJi4Gu0D;Nle4_$f)vD_7F%Ph5Ks3N>hBu=U+@D7Vo)%OI8XKBc$@ zyY-@UOnaH5+A9W7w#z;$QSdyX3Y8mvisufsAEVKvzw+I=xn`V{>+Rad#n+Kb9h>&b ztEV**3c(Lj2hRz{KZsv~Nt&`%?p|wKuV{Z-xitJG+g18TM~bb7gIcsH=P}CV+Q@`D z&twhsebD#dc4pS(z$NvyC;R64vjU|Le4%$6-#9>eKMx_5UM70Om0j?XHVR~JFHq!0o`ERj#f|reX z8^7AOV I~b2Rs>OFR@D{nPHP&de`Lwjiv~PEnzetJ6qk$*u};DIL7qjpi2KX0vLZ-=M;K7u~kg+8jl^5g8yP z0$KAb1>ZOgU0&91+wn>v*w4d~RCC|AV5(`2Pn+_z=eKlhZtXIE^PBvb4A`^WZ|>*m zYr}j>T0v+mTklVp`|aX`Ic7G=XPF-9oOD~FWaQJSp3|?Cy}E1raSitp@Pj;s4=H~^!Q$4GCisDyp12OKN$Jt**Iosn(cGbkf4Ir7JYpf z+}7E#q;}FQ&9H=P@(8q86`D?=Tzm3iHv>B-8)O_>jO)oJe8yp4ge;_-4w~8kTZ+n9 zRoIVw4s%7NChpTsmqON7M=PDHSe^ZF(^{UuG$skhPD z5JC-diJt#?n9Wo$Flc|axyJ2QbY?OB;iT@-*UyzsB}dmDF}st(!&k$^vBH}0zEJ8p zLgG5@_vQIe8pvQ0$PsEAv(`N35ZU%9v%K(0?8l1^Z)^uYXF6zWA)SOy@|?X4=YN_- z8_B6U)rOI;*aiCNZLz*YqhPCVud(^iiEh(sqI$no#Cdke-H@~;Xh=aP(%vA{8~tW# zBuYarPpEGdq>puvnb2HwiJl1a%-DZ8FfHycRWp}^-FGUZd1Z4}O#Q_c&X+=>;)0hp z=0rR?af`mon?aSE(Ew<6KLLG&=ix+};Q3)Gdz%JHh=!H1=Nb(TAM$2Gba{u?lgg@y zu$vV(V$$IIBqAhWycb86R;mqx!<^NNeub&b(9y*F|H^qn|Xjjl8t0l<6Vg zeRvEKCtk?2=Q?(InLOe7dfXhXqxXUq93E?y5T@gHJiDP$pnOfIiDgujP4&E7JwHlV z1YcZs`*8@w_lfOMW1z!z@B0wVGju2E14lUNk8Pg>0W}|2K9=|UAkOUuyQ(>XHt)(& z;xcM4u-Uvf%<uAN#vt<6~~ z+(N&w#4z*l0(eQsmuoSZcdSR?TZ~;e9gUaf)9h#E-49MW3YkV0W3j!(=(7BF^utEf zF1Q9gx@-Evr9N7PWCK%OcOg_OYVj`1e$Rf;ojs!erl)!R-TH#t7{|(D;v4uSk;$vC zo(o^>kG}Nej=HhEj)VIXHt>+!{0j|I@Y87nJJoYYqQP3+5L&3*9dSzmHPEN3vLI^* znP9rat{OZrPZ91vlq)v5sZ83L2#lWOBn^8_U=i^OU8xYg@5u#h9Wv1l70<{^$TJQ!PeDdI6_ zHA>rc#EQszYUmj}cwe_&FpOTEy7;XQ3TfIuRO(#lCaH@+sSODg@-UJVuME2nM1dKd?z zz(wrWkQWO|*Hq(P1`6?pAAR->W0x6*E#6X+nxT&Bog%E$otf10S=dxxL~V}frzT)% zj~;pfp{*LryaemIC z$~s%@2HnS#i^dk8-CGE*X@zC&8kLBg%D0nG=*ODEDS$YEq^3IXPI6Aldntf)r^k3 z(WSq1L-EeTyVSL&w;#EEcsW_-u@h+4qyBI=RD;Iz^;y$SkBGE3ZpnPcPr9X-_BTqB zK-LxRD}!03SsM@MVWiKSw&!vAJF@4!wg;c^Z~`Ek(ADa8YtMjAALOR1-CUD|3{ci0 zXQ$n?f=ZtbcwzkJO!eAI$c;qb9A0 z;g`_U{t_dMsAcKBj(&DOa@9{AN9+Tiwu&6uE&HdWD`IK=u`{os8;f%h<0eb4(txEYQ7YA3OOZVE!l6BTggX0eevmQ-iH?#Ll4#-$*5kp z#6Muun2o<9CBU&=tX#$xvE3t{OqsNWgF|3In6TBa&bM&#DZe9DNy8&ApLH=j&1R1A zuAx3iecO$Q8&_8;X9qmPi{JIY%YMzxt-i@6i)c}YecBPuG+~yH!dg8e(j1B7*PT_p zp^n~AQ8)Sdx=*X3wN)Qa>cbkGte**R4|3~Bp#|@DPk($BEWyYC+1md&EoJ&)!2NWG zP9eBCcqe@&0C=V)@~ym!CU7&t9GdqAVkRNQJZ`IwuVLr!JN~w2rv*OMIkX>4LQC*jC;|-u8NiNTlHtu<+H!*@IQ?ST>q4MY#@w3KrNAPt=0l(GpMOO z=pJ$M-)TGnMCX4kGb_Gg#9Y;W&_V#g1qtcSBrrxv`euE+-$zGvGvSIysj^@AvuiQe zM%3=v0FEQ$G~i!NW*3gXWtqX#X1TnI*ESso&P?Iw$LDV_gx^m2Z|@yw<1v8oVCDXM z%Y9#7e7nv#Nt0Pq0fWjIS*~~y&GORHkTZ>5Tv^dMTt95TX_8(aP;@Tv#EomcHru*8 zb5-QE-kl@?acK@|@#$tmzpgoFTqEo3cM+JA*>&?YE-udbmFNN?p(G?0;pIiMzS*9B z##N^~fnYvr{hNDtHjZGhMC^$MD?6E|iiGOj6R;(cfqfBOSkOs3Fnwq?6{pcm)W7w$ zU9y$ls^To@iFeCO)`A`F9TxZ#){j(MvxoL*swPQ#TxO{%U!t%^vqz`ARwW#0=hIwc z$rp4BrQ2#uW-S|vTOPT^YB=3Hwv^Ad-td3#K7UZ{RiBm>VQ#+WwU|O7Sjw)gY}xLU z@TgWq{zRB~!sDJw`tWAjyQzaasJ%^Vn}MZ~k2WY4x&1(ANksE3v&g-;ovzbzbs7e@ zuS4B*r)w`Vk}0K^&LOee5uYP1?V=<(7_~>-6NqhUr`|fl3v2D?6J7D}_EedtXy#tR z<-cH3*k1I!z4DR>u!21t|FYbR$FJHg`PKQ}wbHpvQ%=#>o^B1K(vn2C zA9n`5gKFLi3B?HYOQwbLQfHM$U}0i;rqWVgh+1Mt#m7JVaCh1=;uL^Berqn=A*gDTKFNUy|9LDf@x>#g zRSzK^UKn^cnU);Ol6b6PlYVwIK-2Sj{Q#W>5$^LyTz}MN&Mg(;GdF3->4GcejB?*bSW+dl0=RP7dmsDh!~_uRviENZ22cWs!7H; zzXJ~+1AG5P5QTIg=gfjc`1~$%n%P5c6WuJjzH`3rUcHkn+_zb~r1h!er$hY@d2`Od zrwbG^*iW|u58JBuwrB?JQn<@^#Ign_{hO(se9DXJd?=-JuofKd9fB>rqjw8fZW#Db z)cR7CACBqz*x5E!_cm3R&eSu{)ZcKz2r%$T{ohfu@COB zUsJMMBkXH5)#SiEbHuFYF;jGsJX56Lq-RGSE9^8X?&P6YTw8ym<(?@0m|wBW*nDZH z)qJ8|Q`E)=pzw}fLph0%s!Nr=pWRs5?wEc#Y>a1r+%Bx#w%TFFQ1ZQRSPNJ+#b-kw zzHV5H+hyqFpIqDAY1H!BXtsXRsKqw3wt0F=vwCutjO^=3#>uN2+zy7D4kaI*5^IS8 zQ}?6x!;@z^nP)l`oEG@cc#eh9?d(h2hoBC|TPFH?J0|*DCgGZ;zsU!Gbu~W{f-Z`RvdyGsajUOl75q6qdPoB|>F+I>dC2=|}TACTvt2TAt zP9r~@ss2Fq0ewSX>ZbVx@BP`2l2nsYW5osK{ppqNzB*g5H3xD;N8<8l!}pK-D?@75 zZ(q%~yk}>~F&b5L56v$JOs`}}4 zjPDyX`kwWydxT;1riu`ja23Dgd!(!7FX=J?BVWqT-B64vF6S-mw<|5auhUWnTHX}1tRpw)jKWJb;G%x#{$=w)wVMjn zzRD8~_80eC>KhwvV4uO1@5j|?b%ywC4k|NT>PFJL1gnG$`eXkYTsNh9sn2*!*lsNPfrE!-@>1^J=e+O}_EB?v zwZaF&0rOR2iN#$prp6Dxy<-T}--A+`@4Ml>>8BahG!e3ez72<5CAaLbwrZcBc#k#S zXP?nj`G(6%_rZ3F$B3%o_^9v=p3L{42itEm(Ck#k$O!3-w8wOHgmb)?KZK-F)`@YC zJU1#(&(e7yUD{arcsP30VPU*xY3U5;pNaQk>QiQ`Vr)}`MNWKD?p%cn8-cR3A15Lq z*@vmPb}_;p0_LJbub@jA+cEJosI)0RKpVeqNEf`TL?pV6OMu1`m*Pl*n`Fu!m%DRn zh}&eRYINe#G<;;pHog-}?IhZ42lTxNWJI1JKmwSEid+bb$%!JaN_YD%J zXazKQ*@b54#rm&b`_dm3NvEJx^pHbAF4tGJGPTCCrdPt)@SX9!o}&97wNCCC77B9a zC9zbwN8=9KDi?jo;NY_HH5>M!7~uIz;0ZNPVc{ajrec27R|PpmsVvNvXA0$NixGwm zvmz>QJF`9C@R3>J8aAg#d5u_NX{1HR*jR&;zw|$*8yUt;A*#$jXrgif4PL3BCHtpFE!GA>N-oj2_jRTlc~bZCWJD+;eZ9yOI`FH47xkI+84L+u z9-;nzXTma7-8gPCkKWFvbAjLYRaKjYd)U4H{+FqrSwb!FPQ7T^bJ36?F`vx5o==UyrvAZ@R518jr4=ieNvKbJt8)~=yQg_4 zSEJg~K@UISHV^VJZ88)?AEZ3Oe*SpCIir}Eo2zrU!~vn7=4iez)ZA-jFr;~U*Lq!fa-Xn2QL1qA+^vz-lc80>9N?L>wJcIH(^Iwcj2^gQ}H{R zS7fVaAwd&w6zb*3tM9#Z(MjR*yV|p4%S6qIbvM}BYlWLCT_4M^`b$z~2p87O9o$de zz?4x8R*k6}QaD_yme|psIz{h$e3g6Ly}ydDRr6VeGZ*6K${q2TGI!MJHg2v?wBqi~ zlwO#H+Cv?a8i$t|UpS^Lfh?=Z_e+y+$n)5U1)D&7r|q#w!B;zqAw}6K8<;$VgVAp% zjmt84?ef(`a_irIAzV+IQoX&$wDN(nCS^B@i`yW|$bn&eQ$01Dyo<@C=9?z=%e&ueHSCf@qLG|pD8SKND*$^v^@+o}lQO0HpC|40I zvMII5;t^`lld7|3(oFm^BB#CM5$$aSd43$B|MF9xXi1`E?;{KT>^_UK=Oo55YuQ5b zoJl0EDm)_LxKpi`EJ|8v_Tux_je_)?3=^wMB=e1;D{fE3bNOSQlmv-AanOmVCnC-2wVWo2TkF5f{+>t#$>=en8=Mi+M|YhxgnHD+^qf%v4cfBq@R+ zxaxj=ukyZP0S0%GuTL}oMoVK|Jd^7-o0wJn2ff^vB*g_B_J*6(89U3C zl&Ka5OmKz9JRBn%4YrCc_t){Qeve;j6>C$h9Gd1EfFTdEd&?e4g} zPls_UzRH&4Fgt7ClaKG2Gnwyo^S?PWb!4S@ zgI0IfK?EtBXns6mGp%21IQUJA(%G4N`!k+WSeAF$h8rxV^H0O_)2yll4h56VY_qk| zlYP?q#P)Di8>X2XOdub3_&E%w52(^c->uu@#fe3JAz`(9(`rOm%*08UV5nOW`?*>v z-P%J*LLH_pyIDxGkS$a2;!(bKOs7k3CSe|nIB$agXxZ_bzPk@?<&3!?P0xoqi>a1v zNFF_GWL*s}u-T;N-FLJ9h+i+Ai@4|8ox;St`re`I^Xoj-!4HlpVO4Sql?i-&${ObQ z?UfHoLft}RH`z=E?po%qzIG`H(_+X9*$Oe^VX2ps2rhbgxAK1O2?j^7`cxlT?}T>= zOF_*r8tG!Ang$c+x#3_=N1gjC=KBSMD$7yU0XbyPq$Wr!!)}PhuP)+K1;|xDi+suyI{frxcYNG+I6g}2sHiACl`ANDJb2gxG6{BgmA3c7hn+4ytp4tV z3NSUXkO6@|6u*4kMNoTwfBAh_bhJ+F85e zrZKCJxghqVkt!PO${!M}N#4xA1TpZATL2AoCB@nk9h+xfdXi;5d|P737R8+jSy9tw za`QEP$j;w4J~DD-+~G{ZS3ZTNXFaFNaw{Go8YTX`Q4gxkZW){DnJ3*tdXfDayCS6E zhMh5msYgJXiER>dav{u6gp+{a9@D8&v4_*0$IXr-_R|Srlp$?+`1ZM9gJ{LMJ+oEG z)gnbys?uK;e&l8HS;HOgJ}7$Ez+OCEKjO-{`YuPw?Pd7&dO^!HpzY#F-|u1#fMt1sa^HxQu6+=jh0F4d){ zPHXLX4>b8?t)eNF)!azfS;SsH=oz|O(%FOYikM7*PWSL>Z|XPcW%#1;?K$lmQ^oqE zm_3>|%f~pqr5}m8GYD{}mb5-E=*22Gd^eyNwS@&35s11=i6SgLrw74F?FEnLje9D+ z?Pk<1<}n!s$rT1)24}=i4hxbVt2}9N5;exE4#CeKmdPGFx4kvjw`$9wJWkCtz{@VU z%smkum-bG*N@|?*)73Ki_M;cI6^$H^@k`T$lC>VZ(S8?RXsYF*v-B-Jt4iF%0;aUdsIekt53Wtt{@wR-d1$0mb9&3 z%NJG~s;imXbNEVupGP%UpER`XORk<6g`46^1-V&@e)Vi2n4$O8QNyBugc&TWVVOQ~pvAyz_6#BAr9SQX>V@}`zwKGx%E4ax z;hD(x#Svc1ogF#gs}qtBDh_EcZGQ-36K$mTC;q&=0psD1H zuSWU7x>Rk((fa}-ptI)&eAMYIU`xLI+wm2b_Kh6^6E7WP=f(_0%X#&*jDx2H*!zPk zmJ2)$w|*@rRpczSc`m#YJJ9#o?yCI&=PVNBQhnOZyM2L(+i^rH_{{glHE!&+NbD|6 zShc7El^bE@En;cnoWlNVP3d8bV7=#AJ8R%(j7}hD;KD@tjOS{a zL6~0y5Z>)o_IMU}5o#y5kzprt*3rLF&_4^Cjbv0EdjOCPaLbptebjeExRypSE-B>! ziQs7nIDs{p`E0)UR)5e^&i3VquHNc6bitr&JS+EPTw2-0;z8ErS%tx?sSi7^23Gh3 zMg1-&TFhFOufQ5ML^d00&KLc%CaX_-%1LL%FCYS)X7_30Xy->FBjsw113G!7$!0v< z(=W;lM0lIvy<1ZTdLJB!vc2w_`kh;zuaXj7JlZ0U#E3#31aAZcAr~meo4l9Wv3#!` zb()zReQP!=ZCTGT-vX>zee;O$==^jneTKRaSl7Gvye||hAFl7|0KDhyMi;WADAh`v z_99~dKpG*YS!3Bu5j}8XlC`uryBrYns-hVr|sYQaNN4_S|H!f@!3clYI;=KwaX=G_!` z=#0^yv@X|5affDRzk4lq%H5~cDg#7{WSa+)2#SVF;(lz621f(Cyd67-{i%Q=1!>0^-AOCsNLbNC& z;EDV6I%KsrC81(qn$kaDTD2=dpp6}>*!?m>1C5oqIH4h-A&B60w^gfx1&;SS%DRQ_ zlqc@2f?L=ot7RdMqdmkjM*vemob>ZXfIU9rZpMDw2_MY)z;Y8~DC=?Wtwra9$a}qy z;79p~yCa*0LtO>i(bB?83V^Wf)@vi@Mk$Ibn_0FB$0cS8+oCj0HU~*x=2l}^XKoZg z3yBnsjE)o>>^ojfHPDDVi>8HpI(YKelLJN4(oU4!eMexJ=)k~-928Ag{CG~93(1|R zA7)cqjo%%3IZ4`dJ#9?mb~?|~ZBbWXC6RH^rbOdyQYkEAmg8sduFf_@8~Z$+jdn0r z2!C&c=<}=HO&4~A$cs2W5^)GAkrKMt+f(g|+=F}DgZANwg_YPWseqGDlaV7VGeRz~ zPwSD9k$aYl!R=GI=3NOG4<$aQU?{S5vM}^^%V|ln*=pQ-5M`Ux&S3fm%UFRo8r*yuKScKjD za1x!rwNX<(UBl8h9Ju{%KQ6Wch%}5)qy3i7y@tJ$#ZTC&xVxORSF?u{+xt}9f6yXt#Y&M0-7+mJBYK(g_&+66=U*;{G_(XSUZDe9 z@KkPQ;97z#KQZFlB?!e2pzVY|Qj?*?-pEl4VS84ViAkNU7UI@cx53)x18s`ur7Rsq z=WKKstf%c^LJ3KODro31LO#Q-eOG6(DpNE|k}+$EoN8Q45)fwenBy5+lCrZ9{N~xK ztSjSpW`umT@r;Se!a$5SOMxKLB;|M5DwyB!9m5MhT>XZwQ$c|miKlsBZeOXD0iL#6 zJH615dx;r{r*N79!up1RFy%8WIDQY6?mle`xcTuMidvwtB|_2I7NDqe!j>NyO?cYE z!z!y@M$OJPiZ<(jOCQO5%p7{#dn~opT5r5Oh&6kFHRo=tSDUrT7$A5@rmq(@e|`XU zv}PeglZbNJ$RIPBmzYSj0D}D^p+`ZS7Sf;OZpfl9%CccRz$I^6kEa2*-F-{LA=B3%Pbj31lcL^D7xOq~c@~{%G2B*j zme5CY_L@g_Gd1ctSz*h!X0NZyHQ#n$NP`UC%SnHom?M=V74JkijgLqfcMKay=0 za_3x}lC70#mU5R^l#unhGea?njTp(%kZTq=5P8Vo$n8#XWp3-yixg*GNjx8ReDiCk zxlP(18$O~fqJR;BIgPOLP>>)7FG5 z5;4rBe&oFqAaq9hHjwd*^U8bav#f{wB(bgaeCTHJT@2>&tc(ls8nMxIH$^$o1BDXa zGg^g?CfrMKqj!q6Hx;0qAekV^jLuHL%YMQV60Dctjv3Kre6#MlEzfgf%yVO!x|=6; zOs?1PBckj>1D@g#om()x<6@Xlx^V)LcOBG_21f)WJ;OOPyc<@c*b^V~3=L7KbQKoc zO}L7LbuSG;ZcDjdy<(mkOZm`sbFHG69w=~xzlb7|!p^EI%7Y=1R$a{jk4HtWTwrs^ zZx;S6o`~`P$&CC-p8Tl={iX-~;3T1%{}&<&CF%$W2?+{|{ar{B;o%n*y8TNbNlb{3 zM_5!qNbK)GlAsWeC?B7QC<;j8zs(~g%Fie8Hz4V|q4HN#Mc|(f_P0a)^N{Jk-zNVI zB>lyL`TyQVG7?5vNFt(Qe8Q;1;G_S;Dl(xL5dOQ2^e3V8XZ2S(Nkl{h#Ui0@uD@>P z`$qqyjQ)U^{sJ=n_2c;d(*JYYe~?Cht~H?-MmbJ@ZWv`D@m+rYe{@XfMTF@^#89Rd zKhJGp5it>ZQNF*yPolT!MFswxPcJG&Z}g|Mbh))ZZi~OnLiPW_RT4z)`G0bi#Qv)2 zn1BZyL;3JKcYJUG4RTirp4O1a7QNtb`3i{HLZiR!kVpJFp3eI;?88Q$JoE_M-moWpOE-*!jr_ril@6rumrcq?LN6&?!^|Mdz1(^S@*U-MEstQVdV;&~}C`Cq-s4T4scJ;6Dio$s@}s ziq{_8A0z!X21pbhf zgaiZyQ73|5%1V3!sF!TeyScksK61eI&OpgZ#>V3>j|2Ogym0k`?vUL+?#DrsjeaYO zeogs+C3qFRL;+jtD$Y|bNRfW(XoG_g9=SvctrHjPM&nEg?Nr0WCmZ$Id&26%6EVU@ z8TKSbyBAMdYv3I&8PgWXofRacHO%M9vfoOE+knqH<#lZw;vhiUJKpkV8sdfLCswjb ztaK(u2D4McN9D7_rb`f9m@#zb@cm zdR(FUGb^1vkgTecodJSVLtf62^y7w--HLPXtfZhl#XJ%U3I;mbtxv+e`w()nAcrdh zMpJ1U>HSjx5ZZu2NY;2bwxrd4Q{s7mu-vPOF`;)AOA%v%VIVD>rMmuCohN=b$hZ9W z46GM=%1&aM=m>YXWzTY>YQ2Prz6`_!%!ylM1g?8EY&4`<7Op+G74Tr)x4E_;@L|b4 zLAh;@UjM!(UhI-tA_NXrUPEnpf!9Ox1AWwsI7bY_HAkCZ&-kuA61P+7lvrPA85Ox3 z#jyHjhWCK*_%I6p5d}rxswy5#AtmI3cyO^E44A|Hw z#1%KNCn+ijuqVlbn-#xx?vccbID-IPEswm75EuOZZUxl5tt%w%>Q9Xw z!%FpC)EkcaXT}wN8crAY`Bo~|$J1iG&{kiMnYKOt*z!4VJna74*efqW-9ER8EoT$B z4Tg(Zx6@QTBAn#=tt}Rcv0+Qte|trUX7(&e}Hh|WV`JD1a=h!X`p4HOJDt){b6f-X)F0aSAw&!1L*!N5YUy- zETRBu_%90G-9o5N+pnCj|HgY!!LLFog3PW2p#6K%HYLmTiOP6224m$uPJZ+C4d+{9 zFxir&FwudJEHv+|@Vw?kgBLhrSDYVm^L{ckeOBFgeMNcP8FRi>Y^`K#hKyaP%RYK9;1i{gKvIHWOtcFBH_%Sb{04I#q=-GFobodx@$ zK@Iwe1^c0a4f=@%`=LPr`iTYmp+N-ti3R(i0Ra8Tg8k5FzVcrUuv%hNg@RE}nb@pIz_ zh#PLWTMe~q8xJYJYW9Btc`nd)iGol<0tOUt1$hOI-3q+&53OI5G0DHNGQY%J^nL?h zeo3{A{>Hg{r&|6nTmHG=hF3$!L(K33{=I1a{{uDj--)2+YamiM%kI5hxpXK{_*SeP z`V?jj%KQ1s^6uV4lmQg%fIfv^LuoJY^+}_bz)S<>&UEy#Bi1Z_^AAD~dh?~N^DBJ^ zxB1f6`IWwd)qH8|{7T;;Y`%1Jej)3$5PlbI%73NY%%U}8fUx0&yR=Y=RA{conJ?k5hR*@m#KHaKGibCc8NjbG)VSZ8+v@0kvBhb%ln=;}pkF($mlU z>xoa@LwB%d37V-vB5=puX{g`e4&6Zm_W8CZM%&vEb$#w>O-5eNX6-t~u`w*K4* z-NBwEY6gIS@POTO=xN*Wj}4+A01A!_K+%zKv|S7+cI!_Kr~iQhA_@Uu0&xPapV7l* zc0-}qZ9g}H0q8)?0GzYy@LRib(1=!|9~*yDg!a7{mv}7s$&%Bie{U0a!r%fSYH0@JGA9HD)mbNa6ROB0nHPf9s&|l~5=E z6G;!pZUf?6$m|k@{6ottA0iSuiyt8H50cOy+R(pCLVqGdrO?`tS=<0l_%DixRv_lZ ztzDvE6#60u1)w9Z!+&mhOLEr)&te5o!yTZtzZc$Z!n;$gPJa=JXwN)%r`nu;=?`I? z5$;O-i45)h6GTLMroJoDwu9O7??v3wEzjWDs{s^n6R6j}71L0!&{={25%@2Ph&CYp z#iL!K(7>B#?N9&~k`Mk5Eh*@6&@4s(5&Yi^?-vvm6lbQper-EfTYfCgRcYSI_&o|e zLS210jBp~tZF9d9O`?X#*8Fgayry`j_uN_ zz8&#uD$a9TQQ|l^^E7AUD7XAHFdX9ucI99i?cn%|{W;p#Zy19<*n=w=yN86W+jK!Q zl0gB0pvR&?ZDLoZeXjTecbN#k&SDH^Ve>a1T>$PPiC!bIhmfJG zNSOm9{Yl{UbKw8fzIA}SeiDdt4t#n6kVO*ZAR`8ma;wNc+X^&4w^t;edeSM(5-ort zhC16?n%ruMkoLci33+`1)^UN z@5TlFLkqPA2hIqM3;I=od%*_Jg~o*de^Fpv+=QD$1D&1rt0Gsu2SGMRE0l46Xy!2nPP5Ah-~Ok3cm-fxjp)F7V+3(19S}FABU1 zF8F)sKnU;`1@;9E+zmPq4E#m$7f9$2QRs)Fs~HQI%hfQ&916M) zAG$D8Z?gJjt<`eH98$WlS3YF*8(OQOiaF+VVKcsD^-!&h6|L10#T;xvhZtRGvoF5* z?(N;U;6VH{b0`1!q zqa8IFD@y!_M-uX)^>6ym2TT(j9Aj(bDKdmiUq#+KK(d?!lAgCjUd-M_ zI=%iM+hG^8vPh>KWbGi*XcftQfV^=Mh~eQ+kzz%8-ZMQ<>vk~))EkVsVQgFrE?*Q{A)0d?sGT zx)+Ni;M7E86<12g5|cr^v9c>UWR$6#zAk*x5WJxj5VV-gRLxRdtnu5HN{= zzuk2e_?<>_m4Iw0ZPV8!F7kjIl(%oyr7j*a9VdL#fL(!5E739eJX?%mB1&wGT*OwM zQG?w8*MiUZA?5LB94I>J%o04yglJ$w1}*L+p2ab>$is+~FNiPgAPwbhFfoE?arXiJ?Tjvg7-b7H?BC$B&6g!In z0Z_WYgov>85=zgyF_~pCX#{NVGX=U;O3D?iWaX?Vr}jNGIl_bl?nzAXBP4ZLde>C} zLlNmrfoYu)=Y;^&MU}xwA{4_E0Q0Jqr#pQn{c=qbbt!iIK2pbHd9WD+qFnhxygFvH zlBvNyOU0EQGRHKfM0iUqX>#8zajCl{XT^t0d0-0SR0&i@xk;WdjM)Sx&I8}0g)9ov z+Q^l5mb)8I)$H2)1NK&wmM<>IAt(>ZjtNN%F=IlgG6e=NqLim<2&~-O#6N7&De;UJ z+#)|7H;*3X9`n(2$Hw=SdxGqW!3Qa?Rm4t$+Lm6oqbh8H8L~41eYY(Gfb#dVnbdr`6XSSPReW0g>mb`%N0DZM!!={6YpA=affRbzsNc2u=lAo7^YJop~U7|S}co?8DkRn3BiW>g5-e@;gV6)Fgoe@LlS%-DN{ z=A=JUqr_;Y!f2GI#dEn#aLP~M9_~)D$pZ*gT2djNe5Z{{6>8KO&F(S!mnJlib_?4s zPT5#+3c(HqVM0QcTU1L&g3rl?p63farxki$TFuA=$mA-{Ytb8?o`h9fxEe5so#+3 zuUw6zT<4%%eNDN}CD|6lVd5lFN+eN=Em6uUQEJuT*R-%Ka*LYOwfiZ`PRcxp$=uA% z981p}LoLfQ&qyT@?VhmN%{;TMJdLfqfz3Rp%|+yda@GU{`92Y8#p?fCR`hF+do7S# z#&)+(omfvDSWj(O16$RPzNudXWp0*cZWd>5qBf06V&)l!1Omge66jf};Ryq5&e- zj9Rjf=V_NI=Nl)g)UhOQCaPRiscabJdu!x-Yvy}v<$LR*!t7tJ>7bC4R-;nKmF)UJ zVC;hK0Ya)0xszZX7)NvDw-^$aLYoJmsk)%aYoYl9#2#IyK@muUp#LfH$i?h$Z$n*k z343BtSC$q-g-vArvSoWn2-~5t7@$R6ojJZ`CEKtl6lO=*ld3$K%WM;uk~jH&aguj* zl6h+mtlN;PTb!zE_kD>fP(eXSR%+VZrrO+QsK923Cf!&HZ*Uck@zkYgRC_8{yDL|N zl&fu&{nD7WY!a(h?hVt4DEmz^ZOtWA6`%&f%6{LzulZvzy3V}yC~rH4ZG5?3Mu z=SkUqXOvKK(m+~X6FT?^OR2{y4tcfb)B+_YRTAN@S%sa6{4EDfvKJ|`7ny%gv+R-j ztw7@(P=qXkAneQ|>`W%?Og`)kJM0WE4Az9(frpDYz08Da*k-ltMf3033%{Iyd=Vd5 zqH!IYUk}vRmu8vU6oVUw_z5IO!8FGy* z>vCSRzO=s+%>akq8Q3e9x4{`Vtl+zC`VD@;O@F zFU(_dBX49y$$e6_RL5WL`_U0(N(;OmvAXSkVjrx2>f?T*7OQ?plDW<-KC0?IX;(U8 zYL_Y9o{PK+Ie2;N+y`vK+o+DXn+b~)_dmK@&@?T+bv|ZOUaOw z0lxJljy6tGGVCCCIa;!TT22Avjg)+W;`>c z_ur0K6}$Jat|jg(7j^5Aqwu8X%S1k(55;$gd}x2ilM;|c3CMzkfI%gob=50DLk+4@ z)|jzGg$?=K!`DNgaqDI~!C9fS9jZR7nC15h`wwyvB}1SwYiT<{fKXZ-6$($1w*9T_ zQ;b>8RoK_cMZ6sXjj#n4CP@UbS6fC>=Wg@Uj+P(oIb!Ogr`{NX?>ggMrlUFboM%pvimnRX@?x*05UGAo17 z!CoSwO#N9-mjk>XTrnoA$mpMzgBe_ct1T+9K(MilE3mq0U2d{& zuC`0p-hE59$tgPPl|2eif_%r5<_1I(V&WSM;$83JPT(ifzdW{2?EBLFL}i9i?RJC$ zD$;eGLJw@SGF|&Ud#+8fZp1@Qp2EU7mhkjoK)oJdQ6nI2o;l_+*HB4@X>crMF(EL( zo9RraZ!|B|b3j?L2;(qV8}5=9ITddhEC+WhAhx**0>cYp96aSF%U24 zj3Vre^m~ZI&c0_eRrW~bmfxFrf4%Nz4wRl$S&oYWlq!L^6M(SIblJVw?+KAT(#|*0^zh3kF+n?tJaZ%s2?&(j3#d8#ckhe}5HdQ1x*XlHN_Xjpatjl}O%xWBcR6k^pm>`lEjk=tHT6a1AZIIYh zgUV%V51fgbUPLibXlha5oAAeAG5ef=@Oc4^dlTyVU0!NsaT)=!N2a1mUZU1c!J)QD zLc<0^Dm(1a9;?BbbjqsZj)c9T$~-&HwR5JJ>b#1SBnfS{+FZ>tg`Dc5L1QgzZZ=(2 zed{vzqz0|G`fmom8NlB!(6ewhlNnULYQJ9 zRKDji7NQah@fr)^inXPOng^DtyAryYEYtk9?Ianb5sJzY9cs_r+4rYVq4#?CWT@A37%J3(PEe1`QLtX8#md5aV zu2Q1ZBsK~QO`h&-<9L}ml~7k1tfdhAq7F*kD1o>7k2Om_Wg92R)G>s*%3tmrwdbTM z0k&EzO~--kZ?9zPq(YB)ZdQ4v@N%Wfh--8p=3c^+z-v}S*+m#Omzbc{WNq^wFstrtXzh_|hsLS_7@ z@lWhiWU~7BSKmM2C^bkPD^}Ui%Rej{KGSCRD$y>x&wQE-ZqkCxytQ!Bt>opHAWhzk z{QgVSq#JHaM_$foKDqWZ2VX%cqGWQn*JeArH9w_3}l$qPlA(sV3R{h3Cp4pcV(h19hab}X+ARE?~AEPkBhD!Lb_XSwv z4eHx68`kh;mWM9$<}%$YBbQMeMTPCn@MRRnE)zbAN_PZm17Fy}QNuB|CnMItFojcI z!O6fJt?0s`0F0Q&qA@_E%)VkyM#IOz+2rL~6gjtmxF;qQ<7!G(3XB=c>I~Efb5&s} zdi3IL_@mcU;gP>7O4fn-o!=FunHD86$1vA#xJw_%FQ)eLh?v$els9CCK?ZEtWtxP> zIllF>hrVI1Gq_U{MRjZ_Mu}{Mvr3wL5>T%BxzIU~ty9-Jut;{iS3RqsdSu0ID`~&x z`#X$U9rbB-F%OBQ?kyCgbW)&=)T06UgA?~qb{Mn%teNC|h; zm<+WqT}G|93a1T|m zw2DVYx6bq4T}n#v$O31=XxmAS31ISOmdd{3QcJ4FhFSh$Iat~N^((1hiyrEiROHTe z7!fm~0F0cW9|e)2YJWPp;R^-j?~;<(1C*pRld82iC7<9#DqmqkW@2}I6nScCc$!dk z#AvgnI6N7ZS52;L#|a)&XDZE9GNewZ0x*M*((kJGBROU>PMfL|?Ik+uJ$qIr3#F|K z>K+%IWM{62dcvl+$c!e~Q{L;?KPvdzuHO7Q^Ra+LpNRw#s9w}E!R}e1!)38zjML{K zF}kDBol28)F2N|6rC==`GYF!}K?us$SUk0d;7ovtE14PdP8F4o;|9-Yau8y&^?^_A zM{p+I5Lez~v`AO5_K%L|rp!6ylihgq`$rh^GFF)?u!#tNtP`Sg!d`Plg?h`QcF5F= zdX&|nDs@`nP=&gqUii6(gKl}1R+DzFZFb=B-gg$2vpy~-i)iEfda1Sk94 z`y&b!=`oK*s5=~mpHJIc_&K1m7&WzeG66qH;C0MBR}-Ebu=vZn=S}uxeiPQf#263; zH3CZ*Mqs~)_qCg%CZQZPO+y$~YJX5W`Tbwjj6EuOy!KVi4!Ri`y{){p$l&62`B2MWrw8n z+B!F#4-;KW$H;-Ll4}pL;z?N+*GOI0pQ??|_GkT$DFyQX=e+jY$<6Lqpc8rM()nrU zb>wU@YkYksM`5lzofUL7xhx;4o=eJo}`ROzol)O!i{FmfV~@O&ZGC`o&qe2 z-$lt*7$-$33~^2Fea^mlgJQbWZ|9}2 zySM7zQ=2_>VXI9e(dMlaDMO(n^O53@PE~fBv(qnW4TKl3TDmLOdn?nBtaOZgn-cJ;ZU%#fxM}2rcPV{UbGI5@=$~tA0M8^B* zT{O)UrWnRC^{29m-p6m0$cdsT7EPT*=!Ha5~aiXSJuv% zk2uM584bIiQ@c`Pq`1+a>R3c-OV+D`^c9L`>FnC?^W5FwdDhjzyk{_P_+o3 zex=KwMBC#w>8P{2Ditjk-Q8snyh`kM8=V)LhKzt$pVg7PJ}Jal=QL+GN3u75(mog- zO?+Yuk*UBoix?^xP~vnyoqhz;yR#=U8a`8Mr_A-?90cx@;qWNs$lm}uxzsT#X@;QO8{GYhshpi zbVxiJzZwhVF(|ovsZzeCw`&n~3q9~M3Mhoive7+nAt`yvNv;w)oNuqwTxPFRQ2c(B zp<3Oa%B;N5sc7fzjx~!z(T%(%yRiA}vHZ;TaiOjHqmm(G4o|Pb0mIL2^uH<@uaGwwbD#ePO8YHF!vsj^!wbw?1Jv0;a>T|Zn;KRqj zC;v}%XB8Dk*fi)E0YV7DLT~~Bf-}G{44R<9gF6J5!6i5ZO>lP$Hn_W6aF@Xu+}#~^ z^6!5>3E%F;p1s)ZJBsS6E_v(i=`+)hQp9Wk`&sM0TBf0)elW~jG~<9z!E9A z!^;HPX+k3m{kD0}%dUr2X1sqh*WBILvZ2zz0%NBDWohZ;hQfW8ljw7AczNjA-aDv% zSr~id&PQs&B#n{RZaN|_^uo|w5UzT?n_W>=k^R%t6U+!sBf%J(KX0C%`Nkayx&&73 zLgtvtq}}wNYfkYO^}Ti1@3=0CbUU*RVMVwO_lB zSGP}UHGkgX`s3+$9#95Dr<+<~#X5X|&~zrHjvYD6x#cvBbPy~RL*DaRvk)6Wr9V}h z=0E5?^`D*5#st??2~b$i4(5c_oJv-S_nKJSK)Hx29A08XO)T)Nam;A!;vm;2kjKB8 z?S738(Kz2y8lP3%hFH)+G}ezo8i1(1kgk_Fo?}KSO@e z(O$urb4QkuiTf>4aY{qYMr|+I$U84++qxrRhA3>?$!M4J+AQQ}{?E$W?QtDTA;S-ts9fAw<+=ctu$9B>3xs5=H5iZ zjq8o3>fPO=&l6liIB7{$yFq&(zmK|d8hr6`?ROpW^)IVb{md)Vd8Vpx`|yEBl{BTr zcW0IU=kdeQtnliJZR_My6m*+YZ8Jp%&8BZn^H1v0HTUtf=kL)x#NS$-8%B`kq&WO0 z+B`>Ftlh31+8@N_zK7p_I&ZFqGrmvjw{u0dpeTRTRDZv66E~Tofx?F8m z)c(|7FyCgAcu_|6I56@DWWTFq&R#Hh7isa`7%=a!d=vK?s4&HU- zuu6s$PM*)4b>Z3hLE5u*SzFfJa%leAS!Lp86|1$06)v-@7HvVzEf%A?8G~lj zg`4=Hv$%7YU_!*u+V83A2yVCN7l}`^vgexI)%`7J-Hh+=Hm@}xrs|CpNPMA)AN&(9 z@;(+m37%Elax+o$)~$8!BgTBimNyQB+5j@=6J+cKdqhGG>aRr{cvZOud0KPaGccvD zIVkEEbb}6BF-R9}%N(?gQxSs2oe!vo# z*E(bLv7OYlOG%30NP(1RC2ybf2xxR?avoZAOM&iq+lhbV8vX z{?cel8r7UaP1slwx5RhD9Hr%L_V8Pg(SWAz#&q1KW6}EM?Vo+YHS;uYlc`@T zQ7q}SUl4vcMD>%4xTVl^zqP~jR*DLCojbuE4f%}&(C`kp#UJ2-Dz)bw6-7_5+F51rLm4)IMXLRORM={L7)$Q*(n{ZoF%zPow)lqmPx9c!nsCJ7JtYHU53^H2p$mUZ8| z(RAOdmOg*aguep08n(D|ItdzHZ{ne9yq8pYZ|E+0b-Pwdbmt?lf3=a9uI1!>f1 zeFsj-myCdY{fq&r&}28w(T2A42(%l-TQx6LM)DuSNw!Yj_ex+Sq*R${=VRfO9bEeK zv}R0&J$8E&0pxZPw!9#&;iDj*G*C`6v(ofuXs4SCPbMKT@{4nIHJDu51S|^4rO654 zG2Dqes&f2BJBo~rm9nU#9#cElbaNMDs0r9yC4Id^5?ftoWv}tn=g?ZZLgjc3x&Ji_ zo9sZs((89uwm)JGFl;|}z65Al{bW`v7I+RmXalxoXw^D9nRPyk$TN@#7+ zgyMOrZ+sY?SeFV_gj<_13kQm&hBfZ{m?jNMFPyj7nTnc?AhA63158pKDED958aoe{ za?hc2Y8EvyIn72(GkpE-E+_m?pm97|aZl6rzEitBQ}Ik+k;mW>tHw)*#QtJb7rcEo3RHQV4$!${b` zb#=C}vFn4`eqDL9QUYgP;eyB(;| z;oebe7!dm=5V#&s^l90Kd! zRx4Ux69#b=*=y9F%()6^xeHOfkQJd}hY*l8!;W4<~GdOZTGu)O7M^YP?Gn z4YZkT{yWVl4}3Vj$9ODr&DgfwR^HL^R^Din@ojWxLI;S#4eKTXrAX;k3;m0ts$_7S z+<;_RzXYoq{U}kTWk8l$yi&%h?ZxMd<5(Kim2#S}Md%l4i-0DJ6CAa28hR&#L`qIJ zHrgvlFl1To8S8JJPmSLOGd((RWo~lMJFy9R_8Cd+LQIl>4oT#S0z@MW*ncHIy@6gK zT^Sqfcj1p33ZvE)3yIFHVVe43X?|*y8R-O(_j&w~xX?y#xwyVq{PEM(LZ}wm@!QVM zuJtW#iCYvT`w(|!C==pQ`<#GoWJmVyt!dRrf_CNS)f$l8I?_eMwasYQ=nq=#Cn+WL z()6SQ^zLRSDf(S}Vr>w`VY!5&1U>KQc^QRq72C@my1cbjCT;>Hf<0=A3>8`y2%Cuj zJXm_q{x(8hfu2Ak`|g?2@7Fehi4MH!4w_$6D~8fLRJThet~*Y)D+z}ubhKjkqO=vV z`hOm}PT4#kOE3s7@o{=S$({9EWob!b(PXx~8VrCIKeL!ON@oRRZ{yvOZd6g)&v|k2)(-qs(XNfT@B7@8jlx4K*7u?f!<|X%N9@w}F1B$3CS`h7 z=htzkwh#UoYg^7v=)SBRlv8{|I^X(!`b%|utbIOQP-9TrAuZUz_ak*LqtwyPx~Jj< zK0J|f6}NxPI~?%MjfO%j$=;xsT6iAO%e{$t7_RJ0M;B{@4EDipD!r;iVGZkZ8B8zT zu5YlBhBrHNo0K%Kv_`00^=3CRW0Ey&_LwwXYOP{ekj*<8Uv6q3z6X;=-sTr)j9&Rn zExlqRhmf8ye>{h`XN>IKjhTUXXjPVUprP{5m;RWe{9XckpO2}D(N!W|LVFc!PZi^B zCHbpVJu69+hB5DK=H#B7f8&V>tJmq2rdzB3MZLFqJ7)7mV4lGyLnFs^M>Eb^-_otW z2DIO>Hj-nMq|UJz*58X#b@N#uqCYoF#M1JufpDOZ&(=ZpX1GJh&D^us2TH_jiO~sqw)-=-!;7TlfJ$@mVXFORy zT-tf!c~yI51{>95Q7d?Zp)i%1_@$6GH~h`QVrpEUvZBW0w}8C%j2x=6AFyW%tEi;1 zKxfsyZ6te)Ay$v&lkNSTX&P9A`M4?W$E5w^5bU zjPJii83)XZyN%mIYJ1z8t+xe4^h%MVz1#V8X5@|n_(%|+=x-cOrbeaXG+oZ4UF^!& zHNCJJ-OC9ZW`0$Wg2qI0nY7$4U$qS8_QobXG2g z!xKb5VYAX)@Faww0s$#|2`YmIeFYj#1;4?px7NoXaRYZ~y2ISmdYg`unsD;y(ja|G zjZt@B{gyvk)Q|msCASe7r;F)^^d7_7@Mzw%%yYv;C%kD&97PK|;icm>rG9^dS8O84 zui}yEp&uce{bGOlJYqxTBP|&M)`26j0Pm*T|>8_s$$R8nX?wZ zu2m9Vu&cODv!Zjr>+;*r!bkYqG)-lr$wP%|MVE82-#V}2PYI_(T-*sZ8k$WieU$6X z9&4sX4-Jt~I*Co%hHU&AD&DluK&C&-ohN4I5GsDN3WPmXY|-)ykN!H#dbyBn_{%;r z)a^n$>G{)wtEEol)F)$eecvPfAkTu{JVHxtJ|+b9%+Dtk?~F<(a@ws*<}`6#Y0VS5 zDm}l6aQrA$^UdVc&pxH$H2P|HIHl!N4d@CL|C({O`DH}!3_-yN`LLZ9K;WewhlR3; zXOxPfb9GbE)Em7xowEub=l%nQG=WwFr`8GU-+i%I1b|}!SB5H+bLj-;gvC)Pnw)av z8%k)pF4Xwqoq%h)H^I(c1kuF8#&GrMzT(IixV4Vkb21{9It}J~l+|O3FvpZT3G?@E z!5ut2Ov@g~e88)l=9Bx?BoaorBKxBx_@NRSqJ1(&{{q zy_}~E_62S*MH>nV<;n0K!(1efE3Rl7N$yDC!-^AZC+Fq9l2KM}fID)wuUo`%0h?8) zjhpMLv+d%VjpwDkwxajvN)eE)cOR`T#R`4O_7#UzBB= zoYO9ae(6$emMc`&o)mEEU*&LdMe_!8|0f13Kh#GMnJF1p-!Bb z^#`QHQ$3P$V@qWWyESEEzPvH8yMm_s%e*(2_4Ebe$|Xx4vvCKshJB2)Zfi^!6BP_% zJxtwQ3B;VRH~W1&hh2p8wb?fWHZ%@9h$nJ6uclLPm|T{)wT`0fSHc57o2jZ6o1C4V zV0hIkW`c#ET|SqTL06EOPkKW@a8{UZ*Xq?&MZ2qCm7c4`#f{uZM<$pWR@)9H0m6dZZZ8^U$8t1f!8_V>@Z5T9qB6+L zBnGUh)8v)#wW1+?HTH$-uO$GC;4LSR%`)2Y^X>D#eCA817iA$?sPS~av9LUUkqxkR z%+S$GbX3P*MV*iB2^L%J7o59RQEnRF?Hg-=;SwAQcgzmQ{mXg4c0O6CFXhfnZ(`TF zIW4A*TF8C2POyt=KvRq&ul??6tq3jx_jogzHWg*qdrmX|(xyO)DSOU+dmrJ5<;`(r=w zlouIqu5d1lD@6%b_(+JBYIbm_|A0xI(xnV4W-KdTM!R6^psEqGWxP5%vP!&rmEkf? zM4)=~?ek&7zWcpq_VrdZH`?UzchqZR58XwjBgGQ^8>O9*(#62flvE!TfSA}{*%LNa zEOnFF{V)a+qJ1qm%et5F0xUSFQJyOnq-?#5Sd4d3+G0728Xkxd1{odhC(F#hFIj|* zT=Lz%J8P2jb1xffSkENh1Tr(#6j1ffzz?fj;W&dR()ZsDaUiPkbmu^g=WU}_V6H1^ z_si}5z~tyQK9bwjjSV$xs zpMP#0t;s%p@xy%y9O01S|z9pQfeunJBy?fimrrcK)q$TuH?ojPr zM2E4X4&)k0UW2eP#>ZtTO2DfRIb|d@L z@=CE=uPj#?r%Ls+l@H?6AXD@)U?g|T0cc_N~ci zCK3{Sw}YL|Fc?>$blS8^A*eT8r}%S;!4Ma$uQ)$pEi?LR22GNIo&Vrp5BF?sWJ=YD?a<3 z8v}bPJe;O_|9&@VY58*AxM*UflV?Z}V{y^E=nu0zHa{LFpv=R&LUbRIKy`EX>nylw zXkB1t+b<=zIG2|s*{JA8_pp2 zO{1bIY~q8fP@5ZRb1KXYlcLMzMz_$P_hWwNhC?~*;1yn)xDx~LxaZdsasJ4eD5snDV)?g_`bWJ9*T5RHN6eGv?662?wr$PVrcH|R8y|M z7u-dE6?=qS1!|sKxwhctEj+t&sGHf|UG_Rtt#|dQ+W(Qb>~75(5SkX%Srm}eSq`5 z1%g#J!M9^&6aT?=zj0IHaMu0&Mz!ffCje!;#*&{g_M!o$lkqaw@_vqiy9xdLJkG*j zqp;J8-qvc59gcH-;PPggAh5I27@K|alu%^Iy~@nJQL;~Fg5FK+qGz6NdMmFriT6Y%?*)@gYVuDnGFQLGcTXM=bO2DH`D z)jcE$;R(8Xe6|N{wj=zDcM|RbmtSPNYmW-o_KrNJSMv4ga@o)FHze?ly2QH~wx=hR zs%VNbE9;Ck#l^-g?f5la6?9Zo&N})s9W@$Uk&yhn^b=S{t*x)}j!Q>WOMPEVfK0^_ zlt$+OMTPk}+1a$IRm^+^6-GijG79=40yYW|Q{$zMj#kO1HQ??z{eFEFajrUN6YDZH zRy0*L8-nmFH4{*!d2UX=rcAVQf>un_xW)|FUcMV(C<&jDkdZ*B+g9N@ix`ktJp}HOUbwm z`!6ll2HEL96^%-Is-S?Y@RUK{4mjnYr=kWC70{Fxl`~u%8NqEwN16(7`?xA=qMF*RRCQ8jIFXO)(3CDdaFj5yQ3$5`&C$F2VX5lKMg_QAjkBxPGXvnJTxvy-H)S$nh~4W8PbW4@4xN#U^8;hM47L zOH4IjzW%>U$HVu$B^?F)b}P(ih=cTvZDJH7Pb*f=`k2i6HO4~|R7>+K@+;U=tH_#Y ztBM-wtTZ$YE^n@GlE*r3exv*NHAJn>@u0VZy`mV9DpW2nD=6boCEF>xZ~fx*6@1X6 zDu`iIw_dAe#*(X!4M_c$#y&v>Fn#entcV0UWKJ8}ZO-k|o)MP3QJpdn)tFKwtYS?G z_m$$r%SaebIh1i1veiXMB4(P;NJLLuIKU{rBLVsF)_)LRiiVD0YeOqcr9P+D`RlJr z@>EVxYL3w*H2ktRFTgp5@)TOoC7Co6Iuog!#0)CR&ovaV(y>$3*E#IS#5Ha!XtVRn z1f%lMVI}m4<&BI3^(T8@`(nN3`F-kIb&Po+YKX3u4Zon!4(toU`0-Qw1 zGI3fvATv>1;#8eqQAl4zUzP`^Gs`YFFhBRt--}+Vgty!TRaDTG{#aXIVMmXqq>Nl! z$%u*`$N9!)0E1TMi{}&?C#flh#myvEl^7R8Y2Rh($yvy1IvUt~laiB@vu~YUD;(?D zsZ4gnt)YCndg-H&B-tL18!5;e$RUP#bYSVWWC&9uMUOV2cz$GBlk-@PwM z+uOrX*arzJklYb9Hr}PN5M@-2*H9sBa*^wSCMFKl##grP9QEbjL03BBO#(kz>zL@m zJASW}=BBO&UFIYq5khohFmS&yNt@I5=SgV9$0UH%Ri@bZ_?jv>%jf{?Ow>g=R@nfC zEG{co@lO_?WE{7@xJsHS$|)|@`<~TK_U+{NC5|`t?92v9f_GH}z;lv*lyO2ij{1J$ z$7(it(FW`0JzG+e410>kMf0(0@fu&M>Y1I5;79(Yk}!RR-CjuBAdrH35a{|&n5OTu}?&6*j}^0 z3JLW#vp`@hdOY-$O5clXs#WTBhrjet#t`40aGHQum`>BVHUqui@RN9FAQRd9_&)nD zoRZjw23Zo3`SS`Kr-|swlVvB#8HhlQo056G@nzxd#^rs`&FDQ}{Gm}sT!{f2>lAjc zj;tA!PSQs_T*jlj??tuQ8>6Z$ol2TA^S*F>rUc6f{dmIeOZjLMHbeU7qzbh@vW-++ zDk>)pg)0GJXI&Y4T=*T8v4s{Yv=jQ2=I>DhUe$@5I*<^iv-Q%C%ON?6F=hd{^~YxH z6tz#EZOd>6XHh#k2`=bJ7HY?d<`hn;8VU{tP^>zs<3z~x9`4c?0jcGxU-#2$swg5U zb*-A{W zmgi1OViu_S`Kd}M>}@&pF&m|4i6hxz?F*JVnL=^2Q58T$Njj2k7K#;8R*GHMHS#UL zrhgQ+%n<@nc!gmg-Y#6eqh*3OFGrTjV(<_y$Z@&k*4v??t= zPX;n=7-2~#)wP)!i4vM7)bj7duvcc^EB##GI{N?=8i+L|q#9?A_2rZ^Vpm|?#Cty{ zevpipwW;b=FhxlzGfJbm)k9BNOazuY(#apnv-LqEI;G)C)))SawNRr4#AP4GVD|0E~-9H|H_ zatONLwpqMa86RviEONWWU%`&+%Qnm&v!fWZ?;s)qJIH0F1Q!WfrhV_v;O1Y+m z0@3fE)8T9lvquyG#hq*i$e1I>mw8kzBniVd9qC5)R0t&FF_oztquYAQ2V;kAQPaa! z^a<$bd`y!=bxRd@Y1E@4RrsP0v|*SnN-zy7uV7b8X!Xw+UWxm_+R$j2J z|FTRQQ+S{fMAYBxAS^KPM43)p!I+9aWW_1NSU#ce^95egx`beByKM;K*Sbash}s7ZJ|oYutAeYzEmO=%U2O2l#1iMIbL z4Km)KK5bZcDa^WMBvU5rP?=#Pq~_lDDide5ri1O1l_31LjID3*z+5&|q-8BI6I zFc4vt5bUgy=>@^{>KQr&=Xl77;pQFs=an810O>FbLIdqp@JQ*dBcJyU1o7vIiUxIhxH^6e^yxX>vO@4 zK#^F(XWSL^Ct7A--xapPx{G#e+=41GC@bW(!n!ek{OqS*^`tq7Zlf7a6KG$PcTtrZ zH%`cv9{uLMM#*oQgSGwj&X%!V^pZs&T$o}{9B3+>Hj}Gi92D${3#@wrnJ$L2u7EW!k4OQe08OC%EbX0<|h2J+MlP$UaF5Q zbM#uzzUP&ssv9j$XXC++2uchPO5{v_efc&Yk5e_+=*;wqoSYz=l2mye>J!2W2!Ig* z8x2UUqqBZX%y5 zC@6Qy?=n5ct?d4dS&b>uR+@-7jPsBv!QUU4s_Qr&yV!1M4Y!rsYrm-{gv7n&F@c#x z$`Sp#GZik+rx(EL8<32rikt_3@B!J z7nF}0cS9(TiTazbvF`?R-#bLF(2fX8Rc+x{uo|%7jSaBq(rWYWd61(mDvb+ z(m3mrm|x2-l)NU=xadE=uh%Y8m)cpwGoEo`FEWdSfnIbgOT;Y+6XygS%4eyK-G=37 zt@U(M)o*cHnK$N&(yA??^^4{h;t19@l*k{}vcZSVeDgR;nT1RAqJ8Xe)@^vQ2JGx| ze$s`RBwNYpQeJqyXVR2Vlq0l^q3bPV&~pVZF>|^SAf;u@|{* z6FyiXcHZ3iWjN8k1uTI&d2a`7H)N%t6!ks*cqp=-S;dr_qqm&4jGcx@qELLA-qH+A zV)1>U{(Mh4PDr4r4mZuP4x1?K$k>;F#>P6v5UD-zf-=eSvVNu`=@m~D#jJOGg&3u0 z>*TY6yeRUxcl1Br$);Pwj|x7kvpT>$&3J`P)!zTVHU_M4{%q3o6BAvS6R~jgZieP7%uv+|P@Wq7iq{ttALxiK6X4K7Qfl ze9S)6BB4w*oxH72!Oue1b`@K77)ZNBpB}Ftr2wvst#B$)l(JW#dcu_GiPjr;_F)-h z*sIZ$IJWa-u+&kL3HrjUkOC_QK!YEYg{?T|YlaWsot!F6T)8xAlhuol(BKaYEe$ua z{}4~CtskdT9@+0OEF(AYm0XBQ)<0vFATIHDo+E;j6L`=d%m21_oQAOS@>ohB^CdY{ z6!DV=c%E%^<+?TI%ZYj%)R{e14~9upNy{3Pq%H)}z7N+5I-c0udT;*HpzjdN(2_GX z(iuQC$IRXMc*e6)9;yz~W{cMZsD#?QAhF3E#uUbcM!tBXuTe#YZCWw3UF>L9yD1@L zCl?Pzzi8_IGBZl4x>6nBhVAM$lb3{N!PUxlk&;PV$5*%Axag|n|9e|0C+t{r4B37= zu88NLKgQjcC(xDc4C!D2sMWMt!m~WV*K`WLtgXE{lJ>8nzr1S#pQjMH>W^ZqOTp$x zdr}-N2Zi_?_a+w=PUWf^FK1RK8t)FYw+capANQ;48jgeW$j_7K7GtP8FqaOOUTjU8 z37O9678P8Qy7p_p&hIZg%2Zq?w&!n6&2z4Q50YGLVvri$h&f&bNUI-=@ey4u{YINN zNKVzfKi5k7slmzObStp!vurr%a_kj1bv*A5Z^2+9y;-;>eX^y?7a?#e-n1d zzHGp7-F~yz;5A=Itg$)UxK>m-2X;C2oyYE6FA(SZezRp@Z?v!-R4`&zMDJ|waExqT zZFADmpvCDbTc~w^olv^kAVNBOxUoyN^0K&pH^YyT+mh}YQ;OZ~S5dWlQ-#h37wJt_hlR+2!bBfg#Mg@)G>oR3sF57$6b#>(eHW))5%H-Q z&Fb^1(_Pk(+s?L-_jOE3k4~Ut&Udvenq1C)CmL4^P38`#3QX0E#muKApu_F#HgRff zy~~1Kiw5WUy5y_zq!KMgUVD{S(U0C+l6=IwE?sF?abNq09@)G@dVM}`{ry;K5CgAy z9CPJ83ORMNBt@6;3eMz{w69O!?w!*=GF)LhdiLvH;@GixQ<`mo?FbzDIHBdO@_l6% zQZc$79x(6j7x;1b(BM7##!3njx)iyz=c8hgH=&(_4gHRLQQVJftu}NhZk5)vqZZz* zytBl!#rRnDOyV8aN4(nIp*b{-1v5^RIaIyQlIEe`q8Vt6LbBMYAC+q^!Vh{fQx8CI zLOlj&@!X78^Rhz4o9mh}LdAm{&=N%$gh)Q_A+w`Uq&$Iyku0=0>D?cqR~)b#aiYWz z%{`e5Z)fl%;;!Vxa>H_DQYQSk08sLRJzhCHfLvU+@^#>SSy8^&v_pG*aq0uJv{-6A zkZU{lk;bT7q)z3s9XRRo^|=Ee|9~uN3L8i+A zXD4u^lc5qzqG>ej)C1w8BTLf@k;P#g$y}NlBP07v~$v~v*6Uz|1Q21|Qw_7;+O>G6? z$YKRywcR!uUNZ5`#=@!B*jGJEJ8$v zZp#%q;k@Pl(LB_SX5o6zdMkdpgtRhb$hd{= z-HH6h`0HcGlM65`9s4#DhWT2VP2KyfYNN(5K`* zWftnb_Ufxmy{seKJoKUg;Lg}eJjM30$g`YmQWZItSfrKIJGJTCtl#2y2N`dXo-TZt z#TP8R(^cwN%5I^&_0S?iu~>Tj*3-@Nwgvz8mE&vGH(T~ZKLl>~_^aI?M?R{4-Ls$* zBJwDjyq8StIUK$1tuG6Tq5!G~GYS)i2k;C3g0sB!PIAH^$bItuPC2U zH9sq-dW7Uiw=r1jOi_)gYT=3HNjc4K#PA8#1Hq(VoNb~$9z>;uf_ZBd_ zAZ5w5lJtuY<6`a$|5v<7G6dMSO@8OG^HR&HC z2L(_)saqlrvPT!#xVO-L@zov0G>;CgvwrbhnEA!WcZAVw|7#66q*Zs4g$Qu{SCel$ z{)bNoXdm7kXf4DX%qAl>`QAK_B$GvI^1)XXFbTB~-Xin9VK{Qjo(gt(d8F~V$sovbR7bR0q-VocIBQQhJ)}OaeThXg!jj})lOZYn z{vx!@=tZgbTC=RYus*Z5i4loOWh&>G@`pa+vAuJh)K^=Np4knvjDfyjY-J4_XIrF6 zb}`sKKhiKFJd(0qFMaI}-s89b1P|Kttw)}F>c~`!cf_;76MCL{O*S6@M|0$> zB|Un4@@PdmEyde~^va$LXVMdn@$oD7qfsm)Br38&UtJXEFWDR*V&og-0u2EMFg8w%mP87WXZsqQ}5@=gw>Caj-L(+xLGYAb!p*Wyg)&3U_OA^lrcRyXu?)Q6?zD&0RI83{5K@z zpP-6=5@P=UhT{CeU;HZ$1N5JQeF(+*lav3ap*RQ(4jCKUzoK9e6q)}8vF5*+H07r6K(KZGNv8Rq@S1=A{SQix2HF1+r3P^t_z!UC55xw5aIN+a zlFff$*Kqu^QvW+r2`lrzaY{78{*7Ie`B4q^-LQES$4*Gc*&{Z9#(%SGkP_@h$UmLf z+)SnHY{>JC3p@XW&yo?I3>l{r1-JE!dZTGpU?x0^jCOl>)!SG>{$k01fak1Y^SKPp zqhV(U;)aB;3OS3O801cNFOt}mlwFhKoBI*$nw?>XpF%30t&H+c=DXrE6oT*7CTQy+ zd<9->Ov}2ZD5!_Nb6uYnjnw=Kfl4?+L{g~j;6&M9GWT#BMGekN7-tXMcZWl2$r`#M z7)kQ85*2}+r|7{-vz3lC6vW-mjdfy#mCb9~LyUWnY{t#2W!pb7hV64){O+IBaLf2w z$-yyHp-V9p=0!ihi-$aGL(*KIeQmqiU&E|CO(kiYoXPH>RZYB;AKyfJ5uWh);WPmJ zM?}{DWo*r#MfexC=1;Z%Wt}=$>l?gaCIf?CF#qEqV+R6Rfn)|`|BwL?tNYJEX8zwY z02?a@!qUIVn3-8XfWKs{U?76R^tT=>^TTslIS@ddf3wF91pkL~+1U|6{hJ;$GXmuG zmkiAEP{s^kVMTC!{`Fh{0L;SncWlf6R>0qJ01$k!zx}eXu>UFIzuIGFVdh}@+a3!u zi1qJyS(w>@fBRrzW_u7D0EmE){pAAyWPacSzy^Nc1Hc9X{uM7X00?||E)Y@72R>LI zTp!|Ez~BdZ|G_U10bYBc2V(!bz5w9E`~lcm0S~T)orU#*J$3+MDF1uz0qp-FFYN5# zhxXVX)|VZ~^3Wa|^WU`ufc}8t{`L$0gM|CHJ^-+TnIGB%ArNDK>4De~__@Djtca2Q zw+unEdmsa_Ka_E>{k;|dp#RVZAOu|PfjtC8?{66g3(JGpH~_2! zG7dzM9{2!0oG%<~ED!R`@o;VcIKT*o+h4ij;P`W^_2>BKyyD*T#?7{}BE3f|625TKIne1}Uj? literal 0 HcmV?d00001 diff --git a/add-ons/config-opscenter-integration-example/AWS_Config_and_OpsCenter.xml b/add-ons/config-opscenter-integration-example/AWS_Config_and_OpsCenter.xml new file mode 100644 index 00000000..3ca11922 --- /dev/null +++ b/add-ons/config-opscenter-integration-example/AWS_Config_and_OpsCenter.xml @@ -0,0 +1 @@ +7Vptc6I8FP01flwnhBfxo1q32+nL9qnddfeTEyEiUyBMCL7sr38SCAqEbq2LrdNpnbHkJsTknMO9N1c7+ijcXFIUL2+Ji4MOBO6mo190IOxrPf4uDNvcYBYGj/pubtL2hon/B0sjkNbUd3FSGcgICZgfV40OiSLssIoNUUrW1WELElQ/NUYeVgwTBwWqdeq7bJlbbRPs7d+w7y2LT9aA7AlRMVgakiVyybpk0scdfUQJYflVuBnhQGBX4JLf9/WZ3t3CKI7YITeA/4Lpj+uHX3Q2C4ez+6fNzU/ni5HPskJBKjcsF8u2BQKUpJGLxSSgow/XS5/hSYwc0bvmlHPbkoUBb2n8Ul2UXOcKU4Y3JZNc5CUmIWZ0y4fIXqMn17AtlCABXO/x10w7ty1L2Ot9ORBJzr3d3HtY+IVE5hUomeeHErTByyjpQEUJAngilKzzQ6l/diD1FJAG0wk3TLYJw2HCr25RxJ0KVbAjKQv8CI92jk5AuCARG5GA0GyMzl9fxWKGHkWuj/d9EYn4NMOEUfKESzcssj8xkR8ETRO5KFnuCBPA+9w33qA5Du5J4jOfRLwv9F1XLHM3YBD4nuiYE8ZIyDuQNAR4waosN4lA7EqGAq3Y5UQiIe5J+HA/8nhLF60likVPuPFECOqidWJ0Y0rc1GFXjljgMKb5RXVMkmM+CyXib+a+jAbJ2adSnP2M4riQFr73MWXm8DVlhJ6H0Jwc6nZcmnaUvHqnio19VV4h+sMx4AoLSOpOEXOWH1NlZ+XMHAH2OgO7pQTjSKGdyo8VJ4KS0r7HySh/0k+hr5qKhiNNNy1Fd3LwQdLaKaguLUbiF7xXWUWwpiLxkSiJ840u/I1YxzAmvphlvOKTJYXUmsRVi4IzEie7BbSgI1Or6agIdCUd9Rqy+cLWvo40RUdYgPSpoeM1lAE4y9/nKMlubkE7Oqzl77bxztqBinZGJIwDH0Xc5UNA0wAnn0I6XkgCwBlsRz27k91rPY91KvXoinoShlgqTn3OEkUeVqSDI3cgqliCiAAlie8ceEZmiHqY/W0xciB2KwUwFc0SWmZDvC9sFAeI+atq2awJQvkJ90ISpXRDr4YJWFQPiikSklIHy7vKZa76RLZdZb2ekOTIKBNljO62/Q8kqyU1h2LEhHvg+coVj7VvyTI4K5b1vl1jWTuOZV0HXQABAKYG+yYEenVaHXatXs/mnTYEVl1Kp1bAAeXCEuN1n60dyP2ZUGoYVS+rVGEPpVSZCLwta2r58uHHzVhUSm4Hd4PL8e347lHhkYei2hGwMSCXo7c0KfG1HoZ359CmQ2W1kNpCsOz1K+CbTTV3qIoInqqsoKmF0gdOwcXV4PHq+93H5cHQe2dGhFo/5GGMGx4xUiPZM4le/sUXqKemL9EV1FJWhaN/zWlrWbUJxKshNw0TB+Eu31oa4W6a8FOyx5mP23jurKrXg0DrmgrlTf67EEr7jKslPbzBTpqlMCjlAKMMbgge0mhOyNOr8xm88dkvoYEuMGXzd9YsWhcbqZCssS017jH1+TYFe39/5I5NjfhGsoD14iPxXgG3B2GhkCJSGqCwvDboWmZNftbb5kpQLcaUFfZ5kD72IF1CsZ0yHqgmCLqlBia76WutFuLS9PftlHkrO0onP7889K8naXrd8J08xQkJVpVzlhWIrGBO+ZXHMiAsFApSonki/s3FDnfO7ahTmeI+jvFF75vCm0aVW3hsCm/2axPxU5qhGz0bWsC2+qalt+RceHP/w5l8+P7XR/r4fw== \ No newline at end of file diff --git a/add-ons/config-opscenter-integration-example/README.md b/add-ons/config-opscenter-integration-example/README.md new file mode 100644 index 00000000..6f6b0348 --- /dev/null +++ b/add-ons/config-opscenter-integration-example/README.md @@ -0,0 +1,36 @@ +### AWS Config and OpsCenter integration ### + +This is an example showing how we can create a CloudWatch event to monitoring +a change of compliance status and create an OpsItem in OpsCenter. + +# Scenario + +User wants to leverage OpsCenter to have a central location where operations engineers and IT professionals +can view, investigate, and resolve operational work items (OpsItems) related to AWS resources. User also wants +to create OpsItem automatically on non-compliant resouces found by AWS Config. In addition, OpsCenter provides +action action to trigger a runbook. Engineers/professionals can easily trigger the remediation process with this +feature. + + +# Example Walkthrough + +pre-requisite: + aws account, + awscli, + IAM role permission to create config rules, cloudwatch event and opsitem with cloudformation + +1. execute "sh build.sh" + - create an IAM role and a managed config rule that checks if server side encryption enabled for a S3 bucket + +2. [Optional] Create a non-encrypted S3 bucket if you do not have one + +3. Go to AWS Config > Rules > my-config-rule-S3BucketServerSideEncryptionEnabled in Console + - click action button and select re-evaluate + +4. Once the evaluation is done, go to AWS Systems Manager > OpsCenter in the console and user will see OpsItems created + - User can get the details for the non-compliant resources, suggested runbook for remediation + - User can execute the runbook to resolve the issue. + - Please check the doc for more information on OpsCenter + https://docs.aws.amazon.com/systems-manager/latest/userguide/OpsCenter.html + +5. execute "sh cleanup.sh" diff --git a/add-ons/config-opscenter-integration-example/build.sh b/add-ons/config-opscenter-integration-example/build.sh new file mode 100755 index 00000000..bc25224c --- /dev/null +++ b/add-ons/config-opscenter-integration-example/build.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + + +aws cloudformation deploy --stack-name my-opsitem-role \ +--template-file opsitem-role.yaml \ +--capabilities CAPABILITY_IAM + +aws cloudformation deploy --stack-name my-config-rule \ +--template-file s3EncryptedConfigRule.yaml \ No newline at end of file diff --git a/add-ons/config-opscenter-integration-example/cleanup.sh b/add-ons/config-opscenter-integration-example/cleanup.sh new file mode 100755 index 00000000..7bb8e958 --- /dev/null +++ b/add-ons/config-opscenter-integration-example/cleanup.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + + +aws cloudformation delete-stack --stack-name my-opsitem-role +aws cloudformation delete-stack --stack-name my-config-rule \ No newline at end of file diff --git a/add-ons/config-opscenter-integration-example/opsitem-role.yaml b/add-ons/config-opscenter-integration-example/opsitem-role.yaml new file mode 100644 index 00000000..7e0e7b75 --- /dev/null +++ b/add-ons/config-opscenter-integration-example/opsitem-role.yaml @@ -0,0 +1,43 @@ +# Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with +# the License. A copy of the License is located at +# http://aws.amazon.com/apache2.0/ +# or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and +# limitations under the License. + +AWSTemplateFormatVersion: '2010-09-09' +Description: Role to create OpsItem with CloudWatch event + +Resources: + + OpsItemEventRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - events.amazonaws.com + Action: + - sts:AssumeRole + Path: / + Policies: + - PolicyName: create-opsitem-event + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - ssm:CreateOpsItem + Resource: "*" + + +Outputs: + OpsItemEventRoleArn: + Value: !GetAtt OpsItemEventRole.Arn + Description: 'Role to create OpsItem with CloudWatch event' + Export: + Name: "OpsItemEventRoleArn" \ No newline at end of file diff --git a/add-ons/config-opscenter-integration-example/s3EncryptedConfigRule.yaml b/add-ons/config-opscenter-integration-example/s3EncryptedConfigRule.yaml new file mode 100644 index 00000000..548710c5 --- /dev/null +++ b/add-ons/config-opscenter-integration-example/s3EncryptedConfigRule.yaml @@ -0,0 +1,62 @@ +AWSTemplateFormatVersion: 2010-09-09 + +Parameters: + CloudWatchEventIAMRole: + Type: String + Description: The IAM role that grants CloudWatchEvent access to create OpsItems + Default: opscenter-role + +Resources: + S3BucketServerSideEncryptionEnabled: + Type: AWS::Config::ConfigRule + Properties: + Scope: + ComplianceResourceTypes: + - "AWS::S3::Bucket" + Source: + Owner: AWS + SourceIdentifier: "S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED" + + OpsItemGenForS3BucketServerSideEncryptionEnabled: + Type: 'AWS::Events::Rule' + Properties: + Description: "CloudWatch Rule which creates Ops Items for CloudTrail Compliance Events" + EventPattern: + source: + - aws.config + detail-type: + - 'Config Rules Compliance Change' + detail: + configRuleName: + - !Ref S3BucketServerSideEncryptionEnabled + newEvaluationResult: + complianceType: + - NON_COMPLIANT + State: "ENABLED" + Targets: + - Arn: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:opsitem + Id: SSM-OpsItem + RoleArn: !ImportValue OpsItemEventRoleArn + InputTransformer: + InputTemplate: + Fn::Sub: + '{ "title": "CloudTrail CloudWatch Logs Compliance Failure", + "description": "CloudWatch Event Rule was triggered for Config Compliance Rule Failure.", + "source": "Config Compliance", + "priority": "2", + "severity": "1", + "notifications": [{ "arn": "arn:${AWS::Partition}:sns:${AWS::Region}:${AWS::AccountId}:OpsCenterEventNotificationTopic"}], + "operationalData": { + "/aws/dedup": {"type": "SearchableString","value": "{\"dedupString\":\"SSMOpsItems-S3-Encrypted-enabled-failed\"}"}, + "/aws/automations": { "value": "[ { \"automationType\": \"AWS:SSM:Automation\", \"automationId\": \"AWS-EnableS3BucketEncryption\" } ]" }, + "/aws/resources": {"value": "[{\"arn\":\"arn:aws:s3:::\"}]","type": "SearchableString"}, + "configRuleName": {"type": "SearchableString","value": }, + "resourceType": {"type": "SearchableString","value": }, + "resourceId": {"type": "SearchableString","value": } + } + }' + InputPathsMap: + resourceType: "$.detail.resourceType" + resourceId: "$.detail.resourceId" + configRuleName: "$.detail.configRuleName" + diff --git a/rdk-workshop/WorkshopSetup.yaml b/add-ons/rdk-workshop/WorkshopSetup.yaml similarity index 100% rename from rdk-workshop/WorkshopSetup.yaml rename to add-ons/rdk-workshop/WorkshopSetup.yaml diff --git a/rdk-workshop/instructions.md b/add-ons/rdk-workshop/instructions.md similarity index 100% rename from rdk-workshop/instructions.md rename to add-ons/rdk-workshop/instructions.md From a941f8a19108dccf7f32764ff39319df99f53655 Mon Sep 17 00:00:00 2001 From: rickychau2780 Date: Wed, 7 Jul 2021 17:19:18 -0400 Subject: [PATCH 04/18] add license --- .../s3EncryptedConfigRule.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/add-ons/config-opscenter-integration-example/s3EncryptedConfigRule.yaml b/add-ons/config-opscenter-integration-example/s3EncryptedConfigRule.yaml index 548710c5..d2e89121 100644 --- a/add-ons/config-opscenter-integration-example/s3EncryptedConfigRule.yaml +++ b/add-ons/config-opscenter-integration-example/s3EncryptedConfigRule.yaml @@ -1,3 +1,11 @@ +# Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with +# the License. A copy of the License is located at +# http://aws.amazon.com/apache2.0/ +# or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and +# limitations under the License. + AWSTemplateFormatVersion: 2010-09-09 Parameters: From ef324a019dbaf392c7bbe0777d208454518a248a Mon Sep 17 00:00:00 2001 From: rickychau2780 Date: Wed, 7 Jul 2021 17:22:42 -0400 Subject: [PATCH 05/18] Add opsCenter integration example --- .../config-opscenter-integration-example/.DS_Store | Bin .../AWS_Config_and_OpsCenter.pdf | Bin .../AWS_Config_and_OpsCenter.xml | 0 .../config-opscenter-integration-example/README.md | 0 .../config-opscenter-integration-example/build.sh | 0 .../config-opscenter-integration-example/cleanup.sh | 0 .../opsitem-role.yaml | 0 .../s3EncryptedConfigRule.yaml | 0 .../WorkshopSetup.yaml | 0 .../rdk-workshop => rdk-workshop}/instructions.md | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename {add-ons => integration}/config-opscenter-integration-example/.DS_Store (100%) rename {add-ons => integration}/config-opscenter-integration-example/AWS_Config_and_OpsCenter.pdf (100%) rename {add-ons => integration}/config-opscenter-integration-example/AWS_Config_and_OpsCenter.xml (100%) rename {add-ons => integration}/config-opscenter-integration-example/README.md (100%) rename {add-ons => integration}/config-opscenter-integration-example/build.sh (100%) rename {add-ons => integration}/config-opscenter-integration-example/cleanup.sh (100%) rename {add-ons => integration}/config-opscenter-integration-example/opsitem-role.yaml (100%) rename {add-ons => integration}/config-opscenter-integration-example/s3EncryptedConfigRule.yaml (100%) rename {add-ons/rdk-workshop => rdk-workshop}/WorkshopSetup.yaml (100%) rename {add-ons/rdk-workshop => rdk-workshop}/instructions.md (100%) diff --git a/add-ons/config-opscenter-integration-example/.DS_Store b/integration/config-opscenter-integration-example/.DS_Store similarity index 100% rename from add-ons/config-opscenter-integration-example/.DS_Store rename to integration/config-opscenter-integration-example/.DS_Store diff --git a/add-ons/config-opscenter-integration-example/AWS_Config_and_OpsCenter.pdf b/integration/config-opscenter-integration-example/AWS_Config_and_OpsCenter.pdf similarity index 100% rename from add-ons/config-opscenter-integration-example/AWS_Config_and_OpsCenter.pdf rename to integration/config-opscenter-integration-example/AWS_Config_and_OpsCenter.pdf diff --git a/add-ons/config-opscenter-integration-example/AWS_Config_and_OpsCenter.xml b/integration/config-opscenter-integration-example/AWS_Config_and_OpsCenter.xml similarity index 100% rename from add-ons/config-opscenter-integration-example/AWS_Config_and_OpsCenter.xml rename to integration/config-opscenter-integration-example/AWS_Config_and_OpsCenter.xml diff --git a/add-ons/config-opscenter-integration-example/README.md b/integration/config-opscenter-integration-example/README.md similarity index 100% rename from add-ons/config-opscenter-integration-example/README.md rename to integration/config-opscenter-integration-example/README.md diff --git a/add-ons/config-opscenter-integration-example/build.sh b/integration/config-opscenter-integration-example/build.sh similarity index 100% rename from add-ons/config-opscenter-integration-example/build.sh rename to integration/config-opscenter-integration-example/build.sh diff --git a/add-ons/config-opscenter-integration-example/cleanup.sh b/integration/config-opscenter-integration-example/cleanup.sh similarity index 100% rename from add-ons/config-opscenter-integration-example/cleanup.sh rename to integration/config-opscenter-integration-example/cleanup.sh diff --git a/add-ons/config-opscenter-integration-example/opsitem-role.yaml b/integration/config-opscenter-integration-example/opsitem-role.yaml similarity index 100% rename from add-ons/config-opscenter-integration-example/opsitem-role.yaml rename to integration/config-opscenter-integration-example/opsitem-role.yaml diff --git a/add-ons/config-opscenter-integration-example/s3EncryptedConfigRule.yaml b/integration/config-opscenter-integration-example/s3EncryptedConfigRule.yaml similarity index 100% rename from add-ons/config-opscenter-integration-example/s3EncryptedConfigRule.yaml rename to integration/config-opscenter-integration-example/s3EncryptedConfigRule.yaml diff --git a/add-ons/rdk-workshop/WorkshopSetup.yaml b/rdk-workshop/WorkshopSetup.yaml similarity index 100% rename from add-ons/rdk-workshop/WorkshopSetup.yaml rename to rdk-workshop/WorkshopSetup.yaml diff --git a/add-ons/rdk-workshop/instructions.md b/rdk-workshop/instructions.md similarity index 100% rename from add-ons/rdk-workshop/instructions.md rename to rdk-workshop/instructions.md From 20f798340cf8c77a1cf47a35c1c2822304f0d833 Mon Sep 17 00:00:00 2001 From: Shreejesh MV Date: Wed, 14 Jul 2021 17:57:35 +1000 Subject: [PATCH 06/18] Add [RuleLamdaName] parameter to configManagedRule.json and configManagedRuleWithRemediation.json --- rdk/template/configManagedRule.json | 6 ++++++ rdk/template/configManagedRuleWithRemediation.json | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/rdk/template/configManagedRule.json b/rdk/template/configManagedRule.json index d574987c..f85e01eb 100644 --- a/rdk/template/configManagedRule.json +++ b/rdk/template/configManagedRule.json @@ -15,6 +15,12 @@ "MinLength": "1", "MaxLength": "255" }, + "RuleLambdaName": { + "Description": "Name of the Rule's Lambda function", + "Type": "String", + "MinLength": "1", + "MaxLength": "64" + }, "SourceEvents": { "Description": "Event Type", "Type": "CommaDelimitedList", diff --git a/rdk/template/configManagedRuleWithRemediation.json b/rdk/template/configManagedRuleWithRemediation.json index 7dfa867f..2595622e 100644 --- a/rdk/template/configManagedRuleWithRemediation.json +++ b/rdk/template/configManagedRuleWithRemediation.json @@ -14,6 +14,12 @@ "MinLength": "1", "MaxLength": "255" }, + "RuleLambdaName": { + "Description": "Name of the Rule's Lambda function", + "Type": "String", + "MinLength": "1", + "MaxLength": "64" + }, "SourceEvents": { "Description": "Event Type", "Type": "CommaDelimitedList", From 5fe0275493d5de13b19e4c4c15c80de82bb9f957 Mon Sep 17 00:00:00 2001 From: "Pablo E. Colazurdo" Date: Wed, 14 Jul 2021 13:11:37 +0000 Subject: [PATCH 07/18] Added support for undeploy-organization. It doesn't do anything different than undeploy, but it may be used for extensibility in the future --- rdk/__init__.py | 2 +- rdk/rdk.py | 58 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/rdk/__init__.py b/rdk/__init__.py index a9ce7c4f..15334884 100644 --- a/rdk/__init__.py +++ b/rdk/__init__.py @@ -6,4 +6,4 @@ # # or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -MY_VERSION = "0.8.0-pab" +MY_VERSION = "0.8.1-pab" diff --git a/rdk/rdk.py b/rdk/rdk.py index bc78873f..c88de721 100644 --- a/rdk/rdk.py +++ b/rdk/rdk.py @@ -292,6 +292,9 @@ def get_rule_parser(is_required, command): def get_undeploy_parser(): return get_deployment_parser(ForceArgument=True, Command="undeploy") + +def get_undeploy_organization_parser(): + return get_deployment_organization_parser(ForceArgument=True, Command="undeploy") def get_deploy_parser(): return get_deployment_parser() @@ -942,6 +945,59 @@ def undeploy(self): print("Rule removal complete, but local files have been preserved.") print("To re-deploy, use the 'deploy' command.") + def undeploy_organization(self): + self.__parse_deploy_args(ForceArgument=True) + + if not self.args.force: + confirmation = False + while not confirmation: + my_input = input("Delete specified Rules and Lambda Functions from your Organization? (y/N): ") + if my_input.lower() == "y": + confirmation = True + if my_input.lower() == "n" or my_input == "": + sys.exit(0) + + #get the rule names + rule_names = self.__get_rule_list_for_command() + + print("Running Organization un-deploy!") + + #create custom session based on whatever credentials are available to us. + my_session = self.__get_boto_session() + + #Collect a list of all of the CloudFormation templates that we delete. We'll need it at the end to make sure everything worked. + deleted_stacks = [] + + cfn_client = my_session.client('cloudformation') + + if self.args.functions_only: + try: + cfn_client.delete_stack(StackName=self.args.stack_name) + deleted_stacks.append(self.args.stack_name) + except ClientError as ce: + print("Client Error encountered attempting to delete CloudFormation stack for Lambda Functions: " + str(ce)) + except Exception as e: + print("Exception encountered attempting to delete CloudFormation stack for Lambda Functions: " + str(e)) + + return + + for rule_name in rule_names: + try: + cfn_client.delete_stack(StackName=self.__get_stack_name_from_rule_name(rule_name)) + deleted_stacks.append(self.__get_stack_name_from_rule_name(rule_name)) + except ClientError as ce: + print("Client Error encountered attempting to delete CloudFormation stack for Rule: " + str(ce)) + except Exception as e: + print("Exception encountered attempting to delete CloudFormation stack for Rule: " + str(e)) + + print("Rule removal initiated. Waiting for Stack Deletion to complete.") + + for stack_name in deleted_stacks: + self.__wait_for_cfn_stack(cfn_client, stack_name) + + print("Rule removal complete, but local files have been preserved.") + print("To re-deploy, use the 'deploy-organization' command.") + def deploy(self): self.__parse_deploy_args() @@ -1833,7 +1889,6 @@ def deploy_organization(self): if cfn_tags is not None: cfn_args['Tags'] = cfn_tags - print(json.dumps(cfn_args, indent=4)) response = my_cfn.create_stack(**cfn_args) #wait for changes to propagate. @@ -2044,7 +2099,6 @@ def deploy_organization(self): if cfn_tags is not None: cfn_args['Tags'] = cfn_tags - print(json.dumps(cfn_args, indent=4)) response = my_cfn.create_stack(**cfn_args) #wait for changes to propagate. From 4d15e41584d8c68b0568794703f92e1ff02b4269 Mon Sep 17 00:00:00 2001 From: "Pablo E. Colazurdo" Date: Wed, 14 Jul 2021 15:18:58 +0000 Subject: [PATCH 08/18] Added support for ConfigurationItemChangeNotification as supported OrganizationConfigRuleTriggerTypes --- rdk/__init__.py | 2 +- rdk/rdk.py | 199 +---------------------- rdk/template/configRuleOrganization.json | 10 +- 3 files changed, 11 insertions(+), 200 deletions(-) diff --git a/rdk/__init__.py b/rdk/__init__.py index 15334884..a3ddc1c5 100644 --- a/rdk/__init__.py +++ b/rdk/__init__.py @@ -6,4 +6,4 @@ # # or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -MY_VERSION = "0.8.1-pab" +MY_VERSION = "0.8.1" diff --git a/rdk/rdk.py b/rdk/rdk.py index c88de721..abee3f74 100644 --- a/rdk/rdk.py +++ b/rdk/rdk.py @@ -1578,118 +1578,7 @@ def deploy_organization(self): #If we're only deploying the Lambda functions (and role + permissions), branch here. Someday the "main" execution path should use the same generated CFN templates for single-account deployment. if self.args.functions_only: print ("We don't handle Function Only deployment for Organizations") - return 1 - # #Generate the template - # function_template = self.__create_function_cloudformation_template() - - # #Generate CFN parameter json - # cfn_params = [ - # { - # 'ParameterKey': 'SourceBucket', - # 'ParameterValue': code_bucket_name, - # } - # ] - - # #Write template to S3 - # my_s3_client = my_session.client('s3') - # my_s3_client.put_object( - # Body=bytes(function_template.encode('utf-8')), - # Bucket=code_bucket_name, - # Key=self.args.stack_name + ".json" - # ) - - # #Package code and push to S3 - # s3_code_objects = {} - # for rule_name in rule_names: - # rule_params, cfn_tags = self.__get_rule_parameters(rule_name) - # if 'SourceIdentifier' in rule_params: - # print("Skipping code packaging for Managed Rule.") - # else: - # s3_dst = self.__upload_function_code(rule_name, rule_params, account_id, my_session, code_bucket_name) - # s3_code_objects[rule_name] = s3_dst - - # my_cfn = my_session.client('cloudformation') - - # # Generate the template_url regardless of region using the s3 sdk - # config = my_s3_client._client_config - # config.signature_version = botocore.UNSIGNED - # template_url = boto3.client('s3', config=config).generate_presigned_url('get_object', ExpiresIn=0, Params={'Bucket': code_bucket_name, 'Key': self.args.stack_name + ".json"}) - - # # Check if stack exists. If it does, update it. If it doesn't, create it. - - # try: - # my_stack = my_cfn.describe_stacks(StackName=self.args.stack_name) - - # #If we've gotten here, stack exists and we should update it. - # print ("Updating CloudFormation Stack for Lambda functions.") - # try: - - # cfn_args = { - # 'StackName': self.args.stack_name, - # 'TemplateURL': template_url, - # 'Parameters': cfn_params, - # 'Capabilities': [ 'CAPABILITY_IAM' ] - # } - - # # If no tags key is specified, or if the tags dict is empty - # if cfn_tags is not None: - # cfn_args['Tags'] = cfn_tags - - # response = my_cfn.update_stack(**cfn_args) - - # #wait for changes to propagate. - # self.__wait_for_cfn_stack(my_cfn, self.args.stack_name) - # except ClientError as e: - # if e.response['Error']['Code'] == 'ValidationError': - # if 'No updates are to be performed.' in str(e): - # #No changes made to Config rule definition, so CloudFormation won't do anything. - # print("No changes to Config Rule configurations.") - # else: - # #Something unexpected has gone wrong. Emit an error and bail. - # print(e) - # return 1 - # else: - # raise - - # #Push lambda code to functions. - # for rule_name in rule_names: - # rule_params, cfn_tags = self.__get_rule_parameters(rule_name) - # my_lambda_arn = self.__get_lambda_arn_for_rule(rule_name, partition, my_session.region_name, account_id, rule_params) - # if 'SourceIdentifier' in rule_params: - # print("Skipping Lambda upload for Managed Rule.") - # continue - - # print("Publishing Lambda code...") - # my_lambda_client = my_session.client('lambda') - # my_lambda_client.update_function_code( - # FunctionName=my_lambda_arn, - # S3Bucket=code_bucket_name, - # S3Key=s3_code_objects[rule_name], - # Publish=True - # ) - # print("Lambda code updated.") - # except ClientError as e: - # #If we're in the exception, the stack does not exist and we should create it. - # print ("Creating CloudFormation Stack for Lambda Functions.") - - # cfn_args = { - # 'StackName': self.args.stack_name, - # 'TemplateURL': template_url, - # 'Parameters': cfn_params, - # 'Capabilities': ['CAPABILITY_IAM'] - # } - - # # If no tags key is specified, or if the tags dict is empty - # if cfn_tags is not None: - # cfn_args['Tags'] = cfn_tags - - # response = my_cfn.create_stack(**cfn_args) - - # #wait for changes to propagate. - # self.__wait_for_cfn_stack(my_cfn, self.args.stack_name) - - # #We're done! Return with great success. - # sys.exit(0) + sys.exit(1) #If we're deploying both the functions and the Config rules, run the following process: for rule_name in rule_names: @@ -1759,91 +1648,7 @@ def deploy_organization(self): my_cfn = my_session.client('cloudformation') if "Remediation" in rule_params: print("Organization Rules with Remediation are not handled at the moment") - return 1 - # print('Build The CFN Template with Remediation Settings') - # cfn_body = os.path.join(path.dirname(__file__), 'template', "configManagedRuleWithRemediation.json") - # template_body = open(cfn_body, "r").read() - # json_body = json.loads(template_body) - # remediation = self.__create_remediation_cloudformation_block(rule_params["Remediation"]) - # json_body["Resources"]["Remediation"] = remediation - - # if "SSMAutomation" in rule_params: - # #Reference the SSM Automation Role Created, if IAM is created - # print('Building SSM Automation Section') - # ssm_automation = self.__create_automation_cloudformation_block(rule_params['SSMAutomation'], self.__get_alphanumeric_rule_name(rule_name)) - # json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+'RemediationAction')] = ssm_automation - # if "IAM" in rule_params['SSMAutomation']: - # print('Lets Build IAM Role and Policy') - # #TODO Check For IAM Settings - # json_body["Resources"]['Remediation']['Properties']['Parameters']['AutomationAssumeRole']['StaticValue']['Values'] = [{"Fn::GetAtt":[self.__get_alphanumeric_rule_name(rule_name+"Role"), "Arn"]}] - - # ssm_iam_role, ssm_iam_policy = self.__create_automation_iam_cloudformation_block(rule_params['SSMAutomation'], self.__get_alphanumeric_rule_name(rule_name)) - # json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+'Role')] = ssm_iam_role - # json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+'Policy')] = ssm_iam_policy - - # print('Build Supporting SSM Resources') - # resource_depends_on = ['rdkConfigRule', self.__get_alphanumeric_rule_name(rule_name+"RemediationAction")] - # #Builds SSM Document Before Config RUle - # json_body["Resources"]["Remediation"]['DependsOn'] = resource_depends_on - # json_body["Resources"]["Remediation"]['Properties']['TargetId'] = {'Ref': self.__get_alphanumeric_rule_name(rule_name+"RemediationAction")} - - # try: - # my_stack_name = self.__get_stack_name_from_rule_name(rule_name) - # my_stack = my_cfn.describe_stacks(StackName=my_stack_name) - # #If we've gotten here, stack exists and we should update it. - # print ("Updating CloudFormation Stack for " + rule_name) - # try: - # cfn_args = { - # 'StackName': my_stack_name, - # 'TemplateBody': json.dumps(json_body), - # 'Parameters': my_params, - # 'Capabilities': ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'] - # } - - # # If no tags key is specified, or if the tags dict is empty - # if cfn_tags is not None: - # cfn_args['Tags'] = cfn_tags - - # response = my_cfn.update_stack(**cfn_args) - # except ClientError as e: - # if e.response['Error']['Code'] == 'ValidationError': - # if 'No updates are to be performed.' in str(e): - # #No changes made to Config rule definition, so CloudFormation won't do anything. - # print("No changes to Config Rule.") - # else: - # #Something unexpected has gone wrong. Emit an error and bail. - # print(e) - # return 1 - # else: - # raise - # except ClientError as e: - # #If we're in the exception, the stack does not exist and we should create it. - # print ("Creating CloudFormation Stack for " + rule_name) - - # if "Remediation" in rule_params: - # cfn_args = { - # 'StackName': my_stack_name, - # 'TemplateBody': json.dumps(json_body), - # 'Parameters': my_params, - # 'Capabilities': ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'] - # } - - - # else: - # cfn_args = { - # 'StackName': my_stack_name, - # 'TemplateBody': open(cfn_body, "r").read(), - # 'Parameters': my_params - # } - - # if cfn_tags is not None: - # cfn_args['Tags'] = cfn_tags - - # response = my_cfn.create_stack(**cfn_args) - - # #wait for changes to propagate. - # self.__wait_for_cfn_stack(my_cfn, my_stack_name) - # continue + sys.exit(1) else: #deploy config rule diff --git a/rdk/template/configRuleOrganization.json b/rdk/template/configRuleOrganization.json index e2c6c34a..6629331b 100644 --- a/rdk/template/configRuleOrganization.json +++ b/rdk/template/configRuleOrganization.json @@ -164,8 +164,14 @@ "InputParameters": { "Ref": "SourceInputParameters" }, "LambdaFunctionArn": { "Fn::GetAtt": [ "rdkRuleCodeLambda", "Arn" ] }, "ResourceTypesScope": { "Ref": "SourceEvents" }, - "OrganizationConfigRuleTriggerTypes": [ "ScheduledNotification" ], - "MaximumExecutionFrequency": { "Ref": "SourcePeriodic" } + "OrganizationConfigRuleTriggerTypes": [ {"Fn::If": [ + "PeriodicTriggered", + "ScheduledNotification" , + "ConfigurationItemChangeNotification" ]} ], + "MaximumExecutionFrequency": {"Fn::If": [ + "PeriodicTriggered", + { "Ref": "SourcePeriodic" }, + { "Ref": "AWS::NoValue"}]} } } }, From 44b3026de5252d6b1295e95f22ed6f668cf01d05 Mon Sep 17 00:00:00 2001 From: Sandeep Batchu <65614605+batchus@users.noreply.github.com> Date: Thu, 19 Aug 2021 11:07:38 -0500 Subject: [PATCH 09/18] Create template for Org Managed Rule deployment Created a template for Org Managed Config rule deployment --- .../configManagedRuleOrganization.json | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 rdk/template/configManagedRuleOrganization.json diff --git a/rdk/template/configManagedRuleOrganization.json b/rdk/template/configManagedRuleOrganization.json new file mode 100644 index 00000000..f55fa0df --- /dev/null +++ b/rdk/template/configManagedRuleOrganization.json @@ -0,0 +1,78 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "AWS CloudFormation template to create Managed AWS Config rules in an Organization. You will be billed for the AWS resources used if you create a stack from this template.", + + "Parameters": { + "RuleName": { + "Description": "Name of the Rule", + "Type": "String", + "MinLength": "1", + "MaxLength": "128" + }, + "Description": { + "Description": "Description of the Rule", + "Type": "String", + "MinLength": "1", + "MaxLength": "255" + }, + "RuleLambdaName": { + "Description": "Name of the Rule's Lambda function", + "Type": "String", + "MinLength": "1", + "MaxLength": "64" + }, + "SourceEvents": { + "Description": "Event Type", + "Type": "CommaDelimitedList", + "Default": "NONE" + }, + "SourcePeriodic": { + "Description": "Execution Frequency", + "Type": "String", + "MinLength": "1", + "MaxLength": "255", + "Default": "NONE" + }, + "SourceIdentifier": { + "Description": "Source Identifier of Managed Rule", + "Type": "String", + "MinLength": "1", + "MaxLength": "255" + }, + "SourceInputParameters": { + "Description": "Input Parameters", + "Type": "String", + "Default": "{}" + }, + }, + "Conditions": { + "EventTriggered" : {"Fn::Not": [{ "Fn::Equals" : [{"Fn::Join": [",", { "Ref": "SourceEvents" }]}, "NONE"]}]}, + "PeriodicTriggered" : { "Fn::Not": [{"Fn::Equals" : [{ "Ref": "SourcePeriodic" }, "NONE"]}]} + }, + "Resources": { + "rdkConfigRule": { + "Type": "AWS::Config::OrganizationConfigRule", + "Properties": { + "ConfigRuleName": { "Ref": "RuleName" }, + "Description": { "Ref": "Description" }, + "Scope": { + "Fn::If": [ "EventTriggered", + { "ComplianceResourceTypes": { "Ref": "SourceEvents" } }, + { "Ref": "AWS::NoValue" } + ] + }, + "MaximumExecutionFrequency": { + "Fn::If": [ "PeriodicTriggered", + { "Ref": "SourcePeriodic" }, + { "Ref": "AWS::NoValue" } + ] + }, + "Source": { + "Owner": "AWS", + "SourceIdentifier": { "Ref": "SourceIdentifier" }, + }, + "InputParameters": { "Ref": "SourceInputParameters" } + } + } + } +} From 4fc5d58c56b0af0b11ff8b727b008dd5d77fb1ca Mon Sep 17 00:00:00 2001 From: Sandeep Batchu <65614605+batchus@users.noreply.github.com> Date: Thu, 19 Aug 2021 11:10:16 -0500 Subject: [PATCH 10/18] Bug fix for Org Managed Rule deployment Reference template changed for Managed Org rule deployment --- rdk/rdk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rdk/rdk.py b/rdk/rdk.py index abee3f74..f1b54ed0 100644 --- a/rdk/rdk.py +++ b/rdk/rdk.py @@ -1652,7 +1652,7 @@ def deploy_organization(self): else: #deploy config rule - cfn_body = os.path.join(path.dirname(__file__), 'template', "configRuleOrganization.json") + cfn_body = os.path.join(path.dirname(__file__), 'template', "configManagedRuleOrganization.json") try: my_stack_name = self.__get_stack_name_from_rule_name(rule_name) From 9381e2f1b25bf02a23997db07b7c2411774e4c82 Mon Sep 17 00:00:00 2001 From: Sandeep Batchu <65614605+batchus@users.noreply.github.com> Date: Thu, 19 Aug 2021 11:30:49 -0500 Subject: [PATCH 11/18] parameter name change --- rdk/template/configManagedRuleOrganization.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rdk/template/configManagedRuleOrganization.json b/rdk/template/configManagedRuleOrganization.json index f55fa0df..9c517dfb 100644 --- a/rdk/template/configManagedRuleOrganization.json +++ b/rdk/template/configManagedRuleOrganization.json @@ -53,7 +53,7 @@ "rdkConfigRule": { "Type": "AWS::Config::OrganizationConfigRule", "Properties": { - "ConfigRuleName": { "Ref": "RuleName" }, + "OrganizationConfigRuleName": { "Ref": "RuleName" }, "Description": { "Ref": "Description" }, "Scope": { "Fn::If": [ "EventTriggered", From 4465a40549b70736f594280d816244cdc23b5ca2 Mon Sep 17 00:00:00 2001 From: Sandeep Batchu <65614605+batchus@users.noreply.github.com> Date: Thu, 19 Aug 2021 13:57:04 -0500 Subject: [PATCH 12/18] Removing Lambda reference for managed rule Removing Lambda reference for managed org rule --- rdk/rdk.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rdk/rdk.py b/rdk/rdk.py index f1b54ed0..80dd6eb2 100644 --- a/rdk/rdk.py +++ b/rdk/rdk.py @@ -1621,10 +1621,6 @@ def deploy_organization(self): 'ParameterKey': 'RuleName', 'ParameterValue': rule_name, }, - { - 'ParameterKey': 'RuleLambdaName', - 'ParameterValue': self.__get_lambda_name(rule_name, rule_params), - }, { 'ParameterKey': 'Description', 'ParameterValue': rule_description, From 32cb463fad1273f516b58fff73919fa6b3cc7fed Mon Sep 17 00:00:00 2001 From: Sandeep Batchu <65614605+batchus@users.noreply.github.com> Date: Thu, 19 Aug 2021 14:01:22 -0500 Subject: [PATCH 13/18] Updated the template to reflect the doc Removed Lambda parameter --- .../configManagedRuleOrganization.json | 36 ++++++------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/rdk/template/configManagedRuleOrganization.json b/rdk/template/configManagedRuleOrganization.json index 9c517dfb..9ce3d10a 100644 --- a/rdk/template/configManagedRuleOrganization.json +++ b/rdk/template/configManagedRuleOrganization.json @@ -1,6 +1,6 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "AWS CloudFormation template to create Managed AWS Config rules in an Organization. You will be billed for the AWS resources used if you create a stack from this template.", + "Description": "AWS CloudFormation template to create Managed AWS Config rules. You will be billed for the AWS resources used if you create a stack from this template.", "Parameters": { "RuleName": { @@ -15,12 +15,6 @@ "MinLength": "1", "MaxLength": "255" }, - "RuleLambdaName": { - "Description": "Name of the Rule's Lambda function", - "Type": "String", - "MinLength": "1", - "MaxLength": "64" - }, "SourceEvents": { "Description": "Event Type", "Type": "CommaDelimitedList", @@ -54,25 +48,17 @@ "Type": "AWS::Config::OrganizationConfigRule", "Properties": { "OrganizationConfigRuleName": { "Ref": "RuleName" }, - "Description": { "Ref": "Description" }, - "Scope": { - "Fn::If": [ "EventTriggered", - { "ComplianceResourceTypes": { "Ref": "SourceEvents" } }, - { "Ref": "AWS::NoValue" } - ] - }, - "MaximumExecutionFrequency": { - "Fn::If": [ "PeriodicTriggered", + "OrganizationCustomRuleMetadata": { + "Description": { "Ref": "Description" }, + "RuleIdentifier": { "Ref": "SourceIdentifier" }, + "InputParameters": { "Ref": "SourceInputParameters" }, + "ResourceTypesScope": { "Ref": "SourceEvents" }, + "MaximumExecutionFrequency": {"Fn::If": [ + "PeriodicTriggered", { "Ref": "SourcePeriodic" }, - { "Ref": "AWS::NoValue" } - ] - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": { "Ref": "SourceIdentifier" }, - }, - "InputParameters": { "Ref": "SourceInputParameters" } + { "Ref": "AWS::NoValue"}]} + } } - } + }, } } From 06aba41e274945d1c5b2b292e67c4f81d900ead4 Mon Sep 17 00:00:00 2001 From: Sandeep Batchu <65614605+batchus@users.noreply.github.com> Date: Thu, 19 Aug 2021 14:10:00 -0500 Subject: [PATCH 14/18] Update custom to manage parameter property --- rdk/template/configManagedRuleOrganization.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rdk/template/configManagedRuleOrganization.json b/rdk/template/configManagedRuleOrganization.json index 9ce3d10a..fd4557a4 100644 --- a/rdk/template/configManagedRuleOrganization.json +++ b/rdk/template/configManagedRuleOrganization.json @@ -48,7 +48,7 @@ "Type": "AWS::Config::OrganizationConfigRule", "Properties": { "OrganizationConfigRuleName": { "Ref": "RuleName" }, - "OrganizationCustomRuleMetadata": { + "OrganizationManagedRuleMetadata": { "Description": { "Ref": "Description" }, "RuleIdentifier": { "Ref": "SourceIdentifier" }, "InputParameters": { "Ref": "SourceInputParameters" }, From 08dc42cbf10ff8921376390ab04e47fa0306169a Mon Sep 17 00:00:00 2001 From: Sandeep Batchu <65614605+batchus@users.noreply.github.com> Date: Thu, 19 Aug 2021 14:12:40 -0500 Subject: [PATCH 15/18] Update REadMe for Org Config Rule notes Add Org Config Rule deployment instructions --- README.rst | 140 ++++++++++------------------------------------------- 1 file changed, 25 insertions(+), 115 deletions(-) diff --git a/README.rst b/README.rst index 84e8dd9d..a6f9deb3 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ rdk === Rule Development Kit -We are greatly appreciated feedback and bug reports at rdk-maintainers@amazon.com! You may also create an issue on this repo. +We are greatly appreciated feedback and bug reports at rdk-maintainers@amazon.com! You may also create an issue on this repo. The RDK is designed to support a "Compliance-as-Code" workflow that is intuitive and productive. It abstracts away much of the undifferentiated heavy lifting associated with deploying AWS Config rules backed by custom lambda functions, and provides a streamlined develop-deploy-monitor iterative process. @@ -160,6 +160,29 @@ Once you have completed your compliance validation code and set your Rule's conf The exact output will vary depending on Lambda runtime. You can use the --all flag to deploy all of the rules in your working directory. +Deploy Organization Rule +------------------------ +You can also deploy the Rule to your AWS Orgnization using the ``deploy-organization`` command. +For successful evaluation of custom rules in child accounts, please make sure you do one of the following: + +1. Set ASSUME_ROLE_MODE in Lambda code to True, to get the lambda to assume the Role attached on the Config Service and confirm that the role trusts the master account where the Lambda function is going to be deployed. +2. Set ASSUME_ROLE_MODE in Lambda code to True, to get the lambda to assume a custom role and define an optional parameter with key as ExecutionRoleName and set the value to your custom role name; confirm that the role trusts the master account of the organization where the Lambda function will be deployed. + +:: + + $ rdk deploy-organization MyRule + Running deploy! + Zipping MyRule + Uploading MyRule + Creating CloudFormation Stack for MyRule + Waiting for CloudFormation stack operation to complete... + ... + Waiting for CloudFormation stack operation to complete... + Config deploy complete. + +The exact output will vary depending on Lambda runtime. You can use the --all flag to deploy all of the rules in your working directory. +This command uses 'PutOrganizationConfigRule' API for the rule deployment. If a new account joins an organization, the rule is deployed to that account. When an account leaves an organization, the rule is removed. Deployment of existing organizational AWS Config Rules will only be retried for 7 hours after an account is added to your organization if a recorder is not available. You are expected to create a recorder if one doesn't exist within 7 hours of adding an account to your organization. + View Logs For Deployed Rule --------------------------- Once the Rule has been deployed to AWS you can get the CloudWatch logs associated with your lambda function using the ``logs`` command. @@ -188,117 +211,4 @@ Advanced Features ================= Cross-Account Deployments ------------------------- -Features have been added to the RDK to facilitate the cross-account deployment pattern that enterprise customers have standardized on for custom Config Rules. A cross-account architecture is one in which the Lambda functions are deployed to a single central "Compliance" account (which may be the same as a central "Security" account), and the Config Rules are deployed to any number of "Satellite" accounts that are used by other teams or departments. This gives the compliance team confidence that their Rule logic cannot be tampered with and makes it much easier for them to modify rule logic without having to go through a complex deployment process to potentially hundreds of AWS accounts. The cross-account pattern uses two advanced RDK features - functions-only deployments and the `create-rule-template` command. - -**Function-Only Deployment** - -By using the `-f` or `--functions-only` flag on the `deploy` command the RDK will deploy only the necessary Lambda Functions, Lambda Execution Role, and Lambda Permissions to the account specified by the execution credentials. It accomplishes this by batching up all of the Lambda function CloudFormation snippets for the selected Rule(s) into a single dynamically generated template and deploy that CloudFormation template. One consequence of this is that subsequent deployments that specify a different set of Rules for the same stack name will update that CloudFormation stack, and any Rules that were included in the first deployment but not in the second will be removed. You can use the `--stack-name` parameter to override the default CloudFormation stack name if you need to manage different subsets of your Lambda Functions independently. The intended usage is to deploy the functions for all of the Config rules in the Security/Compliance account, which can be done simply by using `rdk deploy -f --all` from your working directory. - -**`create-rule-template` command** - -This command generates a CloudFormation template that defines the AWS Config rules themselves, along with the Config Role, Config data bucket, Configuration Recorder, and Delivery channel necessary for the Config rules to work in a satellite account. You must specify the file name for the generated template using the `--output-file` or `o` command line flags. The generated template takes a single parameter of the AccountID of the central compliance account that contains the Lambda functions that will back your custom Config Rules. The generated template can be deployed in the desired satellite accounts through any of the means that you can deploy any other CloudFormation template, including the console, the CLI, as a CodePipeline task, or using StackSets. The `create-rule-template` command takes all of the standard arguments for selecting Rules to include in the generated template, including lists of individual Rule names, an `--all` flag, or using the RuleSets feature described below. - -:: - - $ rdk create-rule-template -o remote-rule-template.json --all - Generating CloudFormation template! - CloudFormation template written to remote-rule-template.json - - -Disable the supported resource types check ------------------------------------------- -It is now possible to define a resource type that is not yet supported by rdk. To disable the supported resource check use the optional flag '--skip-supported-resource-check' during the create command. - -:: - - $ rdk create MyRule --runtime python3.8 --resource-types AWS::New::ResourceType --skip-supported-resource-check - 'AWS::New::ResourceType' not found in list of accepted resource types. - Skip-Supported-Resource-Check Flag set (--skip-supported-resource-check), ignoring missing resource type error. - Running create! - Local Rule files created. - -Custom Lambda Function Name ---------------------------- -As of version 0.7.14, instead of defaulting the lambda function names to 'RDK-Rule-Function-' it is possible to customize the name for lambda function to any 64 characters string as per lambda naming standrds using the optional '--custom-lambda-name' flag while performing rdk create. This opens up new features like : - -1. Longer config rule name. -2. Custom lambda function naming as per personal or enterprise standards. - -:: - - $ rdk create MyLongerRuleName --runtime python3.8 --resource-types AWS::EC2::Instance --custom-lambda-name custom-prefix-for-MyLongerRuleName - Running create! - Local Rule files created. - -The above example would create files with config rule name as 'MyLongerRuleName' and lambda function with the name 'custom-prefix-for-MyLongerRuleName' instead of 'RDK-Rule-Function-MyLongerRuleName' - -RuleSets --------- -New as of version 0.3.11, it is possible to add RuleSet tags to rules that can be used to deploy and test groups of rules together. Rules can belong to multiple RuleSets, and RuleSet membership is stored only in the parameters.json metadata. The `deploy`, `create-rule-template`, and `test-local` commands are RuleSet-aware such that a RuleSet can be passed in as the target instead of `--all` or a specific named Rule. - -A comma-delimited list of RuleSets can be added to a Rule when you create it (using the `--rulesets` flag), as part of a `modify` command, or using new `ruleset` subcommands to add or remove individual rules from a RuleSet. - -Running `rdk rulesets list` will display a list of the RuleSets currently defined across all of the Rules in the working directory - -:: - - rdk-dev $ rdk rulesets list - RuleSets: AnotherRuleSet MyNewSet - -Naming a specific RuleSet will list all of the Rules that are part of that RuleSet. - -:: - - rdk-dev $ rdk rulesets list AnotherRuleSet - Rules in AnotherRuleSet : RSTest - -Rules can be added to or removed from RuleSets using the `add` and `remove` subcommands: - -:: - - rdk-dev $ rdk rulesets add MyNewSet RSTest - RSTest added to RuleSet MyNewSet - - rdk-dev $ rdk rulesets remove AnotherRuleSet RSTest - RSTest removed from RuleSet AnotherRuleSet - -RuleSets are a convenient way to maintain a single repository of Config Rules that may need to have subsets of them deployed to different environments. For example your development environment may contain some of the Rules that you run in Production but not all of them; RuleSets gives you a way to identify and selectively deploy the appropriate Rules to each environment. - -Managed Rules -------------- -The RDK is able to deploy AWS Managed Rules. - -To do so, create a rule using "rdk create" and provide a valid SourceIdentifier via the --source-identifier CLI option. The list of Managed Rules can be found here: https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html, and note that the Identifier can be obtained by replacing the dashes with underscores and using all capitals (for example, the "guardduty-enabled-centralized" rule has the SourceIdentifier "GUARDDUTY_ENABLED_CENTRALIZED"). Just like custom Rules you will need to specify source events and/or a maximum evaluation frequency, and also pass in any Rule parameters. The resulting Rule directory will contain only the parameters.json file, but using `rdk deploy` or `rdk create-rule-template` can be used to deploy the Managed Rule like any other Custom Rule. - -Contributing -============ - -email us at rdk-maintainers@amazon.com if you have any questions. We are happy to help and discuss. - -Authors -======= - -* **Michael Borchert** - *Python version* -* **Jonathan Rault** - *Design, testing, feedback* -* **Greg Kim and Chris Gutierrez** - *Initial work and CI definitions* -* **Henry Huang** - *Original CFN templates and other code* -* **Ricky Chau** - *current maintainer* -* **Santosh Kumar** - *current maintainer* -* **Jose Obando** - *current maintainer* -* **Sandeep Batchu** - *current maintainer* - -License -======= - -This project is licensed under the Apache 2.0 License - -Acknowledgments -=============== - -* the boto3 team makes all of this magic possible. - - -Link -==== - -* to view example of rules built with the RDK: https://github.com/awslabs/aws-config-rules/tree/master/python +Features have been added to the RDK to facilitate... From ccdaaba36842f507788b1d5ccd7759949b51fe69 Mon Sep 17 00:00:00 2001 From: Sandeep Batchu <65614605+batchus@users.noreply.github.com> Date: Thu, 19 Aug 2021 14:17:18 -0500 Subject: [PATCH 16/18] Added notes for Org Rule deployment --- README.rst | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index a6f9deb3..30c5baec 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ rdk === Rule Development Kit -We are greatly appreciated feedback and bug reports at rdk-maintainers@amazon.com! You may also create an issue on this repo. +We are greatly appreciated feedback and bug reports at rdk-maintainers@amazon.com! You may also create an issue on this repo. The RDK is designed to support a "Compliance-as-Code" workflow that is intuitive and productive. It abstracts away much of the undifferentiated heavy lifting associated with deploying AWS Config rules backed by custom lambda functions, and provides a streamlined develop-deploy-monitor iterative process. @@ -211,4 +211,117 @@ Advanced Features ================= Cross-Account Deployments ------------------------- -Features have been added to the RDK to facilitate... +Features have been added to the RDK to facilitate the cross-account deployment pattern that enterprise customers have standardized on for custom Config Rules. A cross-account architecture is one in which the Lambda functions are deployed to a single central "Compliance" account (which may be the same as a central "Security" account), and the Config Rules are deployed to any number of "Satellite" accounts that are used by other teams or departments. This gives the compliance team confidence that their Rule logic cannot be tampered with and makes it much easier for them to modify rule logic without having to go through a complex deployment process to potentially hundreds of AWS accounts. The cross-account pattern uses two advanced RDK features - functions-only deployments and the `create-rule-template` command. + +**Function-Only Deployment** + +By using the `-f` or `--functions-only` flag on the `deploy` command the RDK will deploy only the necessary Lambda Functions, Lambda Execution Role, and Lambda Permissions to the account specified by the execution credentials. It accomplishes this by batching up all of the Lambda function CloudFormation snippets for the selected Rule(s) into a single dynamically generated template and deploy that CloudFormation template. One consequence of this is that subsequent deployments that specify a different set of Rules for the same stack name will update that CloudFormation stack, and any Rules that were included in the first deployment but not in the second will be removed. You can use the `--stack-name` parameter to override the default CloudFormation stack name if you need to manage different subsets of your Lambda Functions independently. The intended usage is to deploy the functions for all of the Config rules in the Security/Compliance account, which can be done simply by using `rdk deploy -f --all` from your working directory. + +**`create-rule-template` command** + +This command generates a CloudFormation template that defines the AWS Config rules themselves, along with the Config Role, Config data bucket, Configuration Recorder, and Delivery channel necessary for the Config rules to work in a satellite account. You must specify the file name for the generated template using the `--output-file` or `o` command line flags. The generated template takes a single parameter of the AccountID of the central compliance account that contains the Lambda functions that will back your custom Config Rules. The generated template can be deployed in the desired satellite accounts through any of the means that you can deploy any other CloudFormation template, including the console, the CLI, as a CodePipeline task, or using StackSets. The `create-rule-template` command takes all of the standard arguments for selecting Rules to include in the generated template, including lists of individual Rule names, an `--all` flag, or using the RuleSets feature described below. + +:: + + $ rdk create-rule-template -o remote-rule-template.json --all + Generating CloudFormation template! + CloudFormation template written to remote-rule-template.json + + +Disable the supported resource types check +------------------------------------------ +It is now possible to define a resource type that is not yet supported by rdk. To disable the supported resource check use the optional flag '--skip-supported-resource-check' during the create command. + +:: + + $ rdk create MyRule --runtime python3.8 --resource-types AWS::New::ResourceType --skip-supported-resource-check + 'AWS::New::ResourceType' not found in list of accepted resource types. + Skip-Supported-Resource-Check Flag set (--skip-supported-resource-check), ignoring missing resource type error. + Running create! + Local Rule files created. + +Custom Lambda Function Name +--------------------------- +As of version 0.7.14, instead of defaulting the lambda function names to 'RDK-Rule-Function-' it is possible to customize the name for lambda function to any 64 characters string as per lambda naming standrds using the optional '--custom-lambda-name' flag while performing rdk create. This opens up new features like : + +1. Longer config rule name. +2. Custom lambda function naming as per personal or enterprise standards. + +:: + + $ rdk create MyLongerRuleName --runtime python3.8 --resource-types AWS::EC2::Instance --custom-lambda-name custom-prefix-for-MyLongerRuleName + Running create! + Local Rule files created. + +The above example would create files with config rule name as 'MyLongerRuleName' and lambda function with the name 'custom-prefix-for-MyLongerRuleName' instead of 'RDK-Rule-Function-MyLongerRuleName' + +RuleSets +-------- +New as of version 0.3.11, it is possible to add RuleSet tags to rules that can be used to deploy and test groups of rules together. Rules can belong to multiple RuleSets, and RuleSet membership is stored only in the parameters.json metadata. The `deploy`, `create-rule-template`, and `test-local` commands are RuleSet-aware such that a RuleSet can be passed in as the target instead of `--all` or a specific named Rule. + +A comma-delimited list of RuleSets can be added to a Rule when you create it (using the `--rulesets` flag), as part of a `modify` command, or using new `ruleset` subcommands to add or remove individual rules from a RuleSet. + +Running `rdk rulesets list` will display a list of the RuleSets currently defined across all of the Rules in the working directory + +:: + + rdk-dev $ rdk rulesets list + RuleSets: AnotherRuleSet MyNewSet + +Naming a specific RuleSet will list all of the Rules that are part of that RuleSet. + +:: + + rdk-dev $ rdk rulesets list AnotherRuleSet + Rules in AnotherRuleSet : RSTest + +Rules can be added to or removed from RuleSets using the `add` and `remove` subcommands: + +:: + + rdk-dev $ rdk rulesets add MyNewSet RSTest + RSTest added to RuleSet MyNewSet + + rdk-dev $ rdk rulesets remove AnotherRuleSet RSTest + RSTest removed from RuleSet AnotherRuleSet + +RuleSets are a convenient way to maintain a single repository of Config Rules that may need to have subsets of them deployed to different environments. For example your development environment may contain some of the Rules that you run in Production but not all of them; RuleSets gives you a way to identify and selectively deploy the appropriate Rules to each environment. + +Managed Rules +------------- +The RDK is able to deploy AWS Managed Rules. + +To do so, create a rule using "rdk create" and provide a valid SourceIdentifier via the --source-identifier CLI option. The list of Managed Rules can be found here: https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html, and note that the Identifier can be obtained by replacing the dashes with underscores and using all capitals (for example, the "guardduty-enabled-centralized" rule has the SourceIdentifier "GUARDDUTY_ENABLED_CENTRALIZED"). Just like custom Rules you will need to specify source events and/or a maximum evaluation frequency, and also pass in any Rule parameters. The resulting Rule directory will contain only the parameters.json file, but using `rdk deploy` or `rdk create-rule-template` can be used to deploy the Managed Rule like any other Custom Rule. + +Contributing +============ + +email us at rdk-maintainers@amazon.com if you have any questions. We are happy to help and discuss. + +Authors +======= + +* **Michael Borchert** - *Python version* +* **Jonathan Rault** - *Design, testing, feedback* +* **Greg Kim and Chris Gutierrez** - *Initial work and CI definitions* +* **Henry Huang** - *Original CFN templates and other code* +* **Ricky Chau** - *current maintainer* +* **Santosh Kumar** - *current maintainer* +* **Jose Obando** - *current maintainer* +* **Sandeep Batchu** - *current maintainer* + +License +======= + +This project is licensed under the Apache 2.0 License + +Acknowledgments +=============== + +* the boto3 team makes all of this magic possible. + + +Link +==== + +* to view example of rules built with the RDK: https://github.com/awslabs/aws-config-rules/tree/master/python From 53dc915dc267f64e41eb95be786478f2fe4d2a64 Mon Sep 17 00:00:00 2001 From: rickychau2780 Date: Thu, 19 Aug 2021 16:31:10 -0400 Subject: [PATCH 17/18] fixed organization config managed rules and config managed rules --- rdk/rdk.py | 115 +++++++----------- rdk/template/configManagedRule.json | 6 - .../configManagedRuleOrganization.json | 15 +-- .../configManagedRuleWithRemediation.json | 6 - 4 files changed, 51 insertions(+), 91 deletions(-) diff --git a/rdk/rdk.py b/rdk/rdk.py index 80dd6eb2..142caaae 100644 --- a/rdk/rdk.py +++ b/rdk/rdk.py @@ -1175,10 +1175,6 @@ def deploy(self): 'ParameterKey': 'RuleName', 'ParameterValue': rule_name, }, - { - 'ParameterKey': 'RuleLambdaName', - 'ParameterValue': self.__get_lambda_name(rule_name, rule_params), - }, { 'ParameterKey': 'Description', 'ParameterValue': rule_description, @@ -1442,7 +1438,8 @@ def deploy(self): my_params.append({ 'ParameterKey': 'SecurityGroupIds', 'ParameterValue': self.args.lambda_security_groups - },{ + }) + my_params.append({ 'ParameterKey': 'SubnetIds', 'ParameterValue': self.args.lambda_subnets }) @@ -1586,6 +1583,9 @@ def deploy_organization(self): #create CFN Parameters common for Managed and Custom source_events = "NONE" + if "Remediation" in rule_params: + print(f"WARNING: Organization Rules with Remediation is not supported at the moment. {rule_name} will be deployed without auto-remediation.") + if 'SourceEvents' in rule_params: source_events = rule_params['SourceEvents'] @@ -1642,62 +1642,58 @@ def deploy_organization(self): 'ParameterValue': rule_params['SourceIdentifier'] }] my_cfn = my_session.client('cloudformation') - if "Remediation" in rule_params: - print("Organization Rules with Remediation are not handled at the moment") - sys.exit(1) - else: #deploy config rule - cfn_body = os.path.join(path.dirname(__file__), 'template', "configManagedRuleOrganization.json") + cfn_body = os.path.join(path.dirname(__file__), 'template', "configManagedRuleOrganization.json") + try: + my_stack_name = self.__get_stack_name_from_rule_name(rule_name) + my_stack = my_cfn.describe_stacks(StackName=my_stack_name) + #If we've gotten here, stack exists and we should update it. + print ("Updating CloudFormation Stack for " + rule_name) try: - my_stack_name = self.__get_stack_name_from_rule_name(rule_name) - my_stack = my_cfn.describe_stacks(StackName=my_stack_name) - #If we've gotten here, stack exists and we should update it. - print ("Updating CloudFormation Stack for " + rule_name) - try: - cfn_args = { - 'StackName': my_stack_name, - 'TemplateBody': open(cfn_body, "r").read(), - 'Parameters': my_params - } - - # If no tags key is specified, or if the tags dict is empty - if cfn_tags is not None: - cfn_args['Tags'] = cfn_tags - - response = my_cfn.update_stack(**cfn_args) - except ClientError as e: - if e.response['Error']['Code'] == 'ValidationError': - if 'No updates are to be performed.' in str(e): - #No changes made to Config rule definition, so CloudFormation won't do anything. - print("No changes to Config Rule.") - else: - #Something unexpected has gone wrong. Emit an error and bail. - print(e) - return 1 - else: - raise - except ClientError as e: - #If we're in the exception, the stack does not exist and we should create it. - print ("Creating CloudFormation Stack for " + rule_name) cfn_args = { 'StackName': my_stack_name, 'TemplateBody': open(cfn_body, "r").read(), 'Parameters': my_params } + # If no tags key is specified, or if the tags dict is empty if cfn_tags is not None: cfn_args['Tags'] = cfn_tags - response = my_cfn.create_stack(**cfn_args) + response = my_cfn.update_stack(**cfn_args) + except ClientError as e: + if e.response['Error']['Code'] == 'ValidationError': + if 'No updates are to be performed.' in str(e): + #No changes made to Config rule definition, so CloudFormation won't do anything. + print("No changes to Config Rule.") + else: + #Something unexpected has gone wrong. Emit an error and bail. + print(e) + return 1 + else: + raise + except ClientError as e: + #If we're in the exception, the stack does not exist and we should create it. + print ("Creating CloudFormation Stack for " + rule_name) + cfn_args = { + 'StackName': my_stack_name, + 'TemplateBody': open(cfn_body, "r").read(), + 'Parameters': my_params + } - #wait for changes to propagate. - self.__wait_for_cfn_stack(my_cfn, my_stack_name) + if cfn_tags is not None: + cfn_args['Tags'] = cfn_tags + + response = my_cfn.create_stack(**cfn_args) + + #wait for changes to propagate. + self.__wait_for_cfn_stack(my_cfn, my_stack_name) #Cloudformation is not supporting tagging config rule currently. if cfn_tags is not None and len(cfn_tags) > 0: - self.__tag_config_rule(rule_name, cfn_tags, my_session) + print("WARNING: Tagging is not supported for organization config rules. Only the cloudformation template will be tagged.") continue @@ -1802,7 +1798,8 @@ def deploy_organization(self): my_params.append({ 'ParameterKey': 'SecurityGroupIds', 'ParameterValue': self.args.lambda_security_groups - },{ + }) + my_params.append({ 'ParameterKey': 'SubnetIds', 'ParameterValue': self.args.lambda_subnets }) @@ -1812,32 +1809,6 @@ def deploy_organization(self): template_body = open(cfn_body, "r").read() json_body = json.loads(template_body) - remediation = "" - if "Remediation" in rule_params: - remediation = self.__create_remediation_cloudformation_block(rule_params["Remediation"]) - json_body["Resources"]["Remediation"] = remediation - - if "SSMAutomation" in rule_params: - ##AWS needs to build the SSM before the Config Rule - resource_depends_on = ['rdkConfigRule', self.__get_alphanumeric_rule_name(rule_name+"RemediationAction")] - remediation["DependsOn"] = resource_depends_on - #Add JSON Reference to SSM Document { "Ref" : "MyEC2Instance" } - remediation['Properties']['TargetId'] = {"Ref" : self.__get_alphanumeric_rule_name(rule_name+"RemediationAction") } - - if "SSMAutomation" in rule_params: - print('Building SSM Automation Section') - - ssm_automation = self.__create_automation_cloudformation_block(rule_params['SSMAutomation'], rule_name) - json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+"RemediationAction")] = ssm_automation - if "IAM" in rule_params['SSMAutomation']: - print('Lets Build IAM Role and Policy') - #TODO Check For IAM Settings - json_body["Resources"]['Remediation']['Properties']['Parameters']['AutomationAssumeRole']['StaticValue']['Values'] = [{"Fn::GetAtt":[self.__get_alphanumeric_rule_name(rule_name+"Role"), "Arn"]}] - - ssm_iam_role, ssm_iam_policy = self.__create_automation_iam_cloudformation_block(rule_params['SSMAutomation'], rule_name) - json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+'Role')] = ssm_iam_role - json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+'Policy')] = ssm_iam_policy - #debugging #print(json.dumps(json_body, indent=2)) @@ -1907,7 +1878,7 @@ def deploy_organization(self): #Cloudformation is not supporting tagging config rule currently. if cfn_tags is not None and len(cfn_tags) > 0: - self.__tag_config_rule(rule_name, cfn_tags, my_session) + print("WARNING: Tagging is not supported for organization config rules. Only the cloudformation template will be tagged.") print('Config deploy complete.') diff --git a/rdk/template/configManagedRule.json b/rdk/template/configManagedRule.json index f85e01eb..d574987c 100644 --- a/rdk/template/configManagedRule.json +++ b/rdk/template/configManagedRule.json @@ -15,12 +15,6 @@ "MinLength": "1", "MaxLength": "255" }, - "RuleLambdaName": { - "Description": "Name of the Rule's Lambda function", - "Type": "String", - "MinLength": "1", - "MaxLength": "64" - }, "SourceEvents": { "Description": "Event Type", "Type": "CommaDelimitedList", diff --git a/rdk/template/configManagedRuleOrganization.json b/rdk/template/configManagedRuleOrganization.json index fd4557a4..c34e5775 100644 --- a/rdk/template/configManagedRuleOrganization.json +++ b/rdk/template/configManagedRuleOrganization.json @@ -40,7 +40,6 @@ }, }, "Conditions": { - "EventTriggered" : {"Fn::Not": [{ "Fn::Equals" : [{"Fn::Join": [",", { "Ref": "SourceEvents" }]}, "NONE"]}]}, "PeriodicTriggered" : { "Fn::Not": [{"Fn::Equals" : [{ "Ref": "SourcePeriodic" }, "NONE"]}]} }, "Resources": { @@ -50,15 +49,17 @@ "OrganizationConfigRuleName": { "Ref": "RuleName" }, "OrganizationManagedRuleMetadata": { "Description": { "Ref": "Description" }, - "RuleIdentifier": { "Ref": "SourceIdentifier" }, + "RuleIdentifier" : { "Ref": "SourceIdentifier" }, "InputParameters": { "Ref": "SourceInputParameters" }, "ResourceTypesScope": { "Ref": "SourceEvents" }, - "MaximumExecutionFrequency": {"Fn::If": [ - "PeriodicTriggered", - { "Ref": "SourcePeriodic" }, - { "Ref": "AWS::NoValue"}]} + "MaximumExecutionFrequency": { + "Fn::If": [ "PeriodicTriggered", + { "Ref": "SourcePeriodic" }, + { "Ref": "AWS::NoValue" } + ] + } } } - }, + } } } diff --git a/rdk/template/configManagedRuleWithRemediation.json b/rdk/template/configManagedRuleWithRemediation.json index 2595622e..7dfa867f 100644 --- a/rdk/template/configManagedRuleWithRemediation.json +++ b/rdk/template/configManagedRuleWithRemediation.json @@ -14,12 +14,6 @@ "MinLength": "1", "MaxLength": "255" }, - "RuleLambdaName": { - "Description": "Name of the Rule's Lambda function", - "Type": "String", - "MinLength": "1", - "MaxLength": "64" - }, "SourceEvents": { "Description": "Event Type", "Type": "CommaDelimitedList", From fd5ebe03ecc7dd558b45fdf91e5d8f5522821eee Mon Sep 17 00:00:00 2001 From: rickychau2780 Date: Fri, 20 Aug 2021 14:32:12 -0400 Subject: [PATCH 18/18] update default rdklib layer version --- rdk/rdk.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rdk/rdk.py b/rdk/rdk.py index 142caaae..656c3e21 100644 --- a/rdk/rdk.py +++ b/rdk/rdk.py @@ -48,7 +48,8 @@ test_ci_filename = 'test_ci.json' event_template_filename = 'test_event_template.json' -RDKLIB_LAYER_VERSION={'ap-southeast-1':'26', 'ap-south-1':'4', 'us-east-2':'4', 'us-east-1':'4', 'us-west-1':'3', 'us-west-2':'3', 'ap-northeast-2':'4', 'ap-southeast-2':'4', 'ap-northeast-1':'4', 'ca-central-1':'4', 'eu-central-1':'4', 'eu-west-1':'4', 'eu-west-2':'3', 'eu-west-3':'4', 'eu-north-1':'4', 'sa-east-1':'4', 'cn-north-1': '1', 'cn-northwest-1': '1'} +RDKLIB_LAYER_VERSION={'ap-southeast-1':'28', 'ap-south-1':'5', 'us-east-2':'5', 'us-east-1':'5', 'us-west-1':'4', 'us-west-2':'4', 'ap-northeast-2':'5', 'ap-southeast-2':'5', 'ap-northeast-1':'5', 'ca-central-1':'5', 'eu-central-1':'5', 'eu-west-1':'5', 'eu-west-2':'4', 'eu-west-3':'5', 'eu-north-1':'5', 'sa-east-1':'5', 'cn-north-1': '1', 'cn-northwest-1': '1'} + RDKLIB_ARN_STRING = "arn:aws:lambda:{region}:711761543063:layer:rdklib-layer:{version}" #this need to be update whenever config service supports more resource types : https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html