From e527ddf1c5b8e622df2e10b16bc00b3dde584385 Mon Sep 17 00:00:00 2001 From: Iakov Gan Date: Thu, 31 Oct 2024 07:50:03 +0100 Subject: [PATCH 1/6] add optional CUR bucket access scheduling --- ...r-aggregation-bucket-write-protection.yaml | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 cfn-templates/cur-aggregation-bucket-write-protection.yaml diff --git a/cfn-templates/cur-aggregation-bucket-write-protection.yaml b/cfn-templates/cur-aggregation-bucket-write-protection.yaml new file mode 100644 index 00000000..aa46dd1a --- /dev/null +++ b/cfn-templates/cur-aggregation-bucket-write-protection.yaml @@ -0,0 +1,164 @@ +AWSTemplateFormatVersion: '2010-09-10' +Description: 'CFN template for scheduled bucket policy management using EventBridge' + +Parameters: + ResourcePrefix: + Type: String + Description: Prefix for resource names. Must be the same as in the CUR Aggregation stack + + DisableCronSchedule: + Type: String + Description: 'Cron expression for disabling replication (UTC)' + Default: '0 1 * * ? *' + AllowedPattern: '^[0-9*,\-/\s?]+$' + ConstraintDescription: Must be a valid cron expression + + EnableCronSchedule: + Type: String + Description: 'Cron expression for enabling replication (UTC)' + Default: '0 3 * * ? *' + AllowedPattern: '^[0-9*,\-/\s?]+$' + ConstraintDescription: Must be a valid cron expression + +Resources: + BucketPolicyLambdaRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Policies: + - PolicyName: BucketPolicyAccess + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - s3:GetBucketPolicy + - s3:PutBucketPolicy + Resource: !Sub 'arn:aws:s3:::${ResourcePrefix}-${AWS::AccountId}-shared' + + BucketPolicyLambda: + Type: AWS::Lambda::Function + Properties: + Runtime: python3.9 + Handler: index.lambda_handler + Role: !GetAtt BucketPolicyLambdaRole.Arn + Code: + ZipFile: | + import os + import json + import boto3 + + def lambda_handler(event, context): + s3 = boto3.client('s3') + action = event.get('action', 'enable') # 'enable' or 'disable' + account_id = context.invoked_function_arn.split(':')[4] + bucket_name = f"{os.environ['RESOURCE_PREFIX']}-{account_id}-shared" + + try: + # Get current bucket policy + try: + policy = json.loads(s3.get_bucket_policy(Bucket=bucket_name)['Policy']) + except s3.exceptions.NoSuchBucketPolicy: + return { + 'statusCode': 404, + 'body': 'No bucket policy exists' + } + + # Find and modify the AllowReplicationWrite statement + found = False + for statement in policy['Statement']: + if statement.get('Sid') == 'AllowReplicationWrite': + statement['Effect'] = 'Allow' if action == 'enable' else 'Deny' + found = True + break + + if not found: + return { + 'statusCode': 404, + 'body': 'AllowReplicationWrite statement not found in policy' + } + + # Put updated policy + s3.put_bucket_policy( + Bucket=bucket_name, + Policy=json.dumps(policy) + ) + + return { + 'statusCode': 200, + 'body': f'Successfully {action}d AllowReplicationWrite statement' + } + + except Exception as e: + return { + 'statusCode': 500, + 'body': str(e) + } + Environment: + Variables: + RESOURCE_PREFIX: !Ref ResourcePrefix + Timeout: 30 + MemorySize: 128 + + DisableRuleSchedule: + Type: AWS::Events::Rule + Properties: + Description: !Sub "Schedule for disabling replication using cron: ${DisableCronSchedule}" + ScheduleExpression: !Sub "cron(${DisableCronSchedule})" + State: ENABLED + Targets: + - Arn: !GetAtt BucketPolicyLambda.Arn + Id: "DisableReplicationTarget" + Input: '{"action": "disable"}' + + EnableRuleSchedule: + Type: AWS::Events::Rule + Properties: + Description: !Sub "Schedule for enabling replication using cron: ${EnableCronSchedule}" + ScheduleExpression: !Sub "cron(${EnableCronSchedule})" + State: ENABLED + Targets: + - Arn: !GetAtt BucketPolicyLambda.Arn + Id: "EnableReplicationTarget" + Input: '{"action": "enable"}' + + LambdaPermissionDisable: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !Ref BucketPolicyLambda + Action: lambda:InvokeFunction + Principal: events.amazonaws.com + SourceArn: !GetAtt DisableRuleSchedule.Arn + + LambdaPermissionEnable: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !Ref BucketPolicyLambda + Action: lambda:InvokeFunction + Principal: events.amazonaws.com + SourceArn: !GetAtt EnableRuleSchedule.Arn + +Outputs: + LambdaFunctionArn: + Description: ARN of the created Lambda function + Value: !GetAtt BucketPolicyLambda.Arn + DisableScheduleRuleArn: + Description: ARN of the disable schedule rule + Value: !GetAtt DisableRuleSchedule.Arn + EnableScheduleRuleArn: + Description: ARN of the enable schedule rule + Value: !GetAtt EnableScheduleRuleArn + ConfiguredDisableSchedule: + Description: Configured cron schedule for disable action + Value: !Ref DisableCronSchedule + ConfiguredEnableSchedule: + Description: Configured cron schedule for enable action + Value: !Ref EnableCronSchedule From 6417bee8e09af6359a4d6700e3cd1a2e4119b8c8 Mon Sep 17 00:00:00 2001 From: Iakov Gan Date: Thu, 31 Oct 2024 07:51:14 +0100 Subject: [PATCH 2/6] add optional CUR bucket access scheduling --- ...cur-aggregation-bucket-write-protection.yaml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/cfn-templates/cur-aggregation-bucket-write-protection.yaml b/cfn-templates/cur-aggregation-bucket-write-protection.yaml index aa46dd1a..4f3d368f 100644 --- a/cfn-templates/cur-aggregation-bucket-write-protection.yaml +++ b/cfn-templates/cur-aggregation-bucket-write-protection.yaml @@ -145,20 +145,3 @@ Resources: Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt EnableRuleSchedule.Arn - -Outputs: - LambdaFunctionArn: - Description: ARN of the created Lambda function - Value: !GetAtt BucketPolicyLambda.Arn - DisableScheduleRuleArn: - Description: ARN of the disable schedule rule - Value: !GetAtt DisableRuleSchedule.Arn - EnableScheduleRuleArn: - Description: ARN of the enable schedule rule - Value: !GetAtt EnableScheduleRuleArn - ConfiguredDisableSchedule: - Description: Configured cron schedule for disable action - Value: !Ref DisableCronSchedule - ConfiguredEnableSchedule: - Description: Configured cron schedule for enable action - Value: !Ref EnableCronSchedule From 5e2f3f6d66707deb002a5baae21b765049b4b622 Mon Sep 17 00:00:00 2001 From: Iakov Gan Date: Thu, 31 Oct 2024 07:54:39 +0100 Subject: [PATCH 3/6] lint --- .../cur-aggregation-bucket-write-protection.yaml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cfn-templates/cur-aggregation-bucket-write-protection.yaml b/cfn-templates/cur-aggregation-bucket-write-protection.yaml index 4f3d368f..91aa9d09 100644 --- a/cfn-templates/cur-aggregation-bucket-write-protection.yaml +++ b/cfn-templates/cur-aggregation-bucket-write-protection.yaml @@ -1,4 +1,4 @@ -AWSTemplateFormatVersion: '2010-09-10' +AWSTemplateFormatVersion: '2010-09-09' Description: 'CFN template for scheduled bucket policy management using EventBridge' Parameters: @@ -107,7 +107,13 @@ Resources: RESOURCE_PREFIX: !Ref ResourcePrefix Timeout: 30 MemorySize: 128 - + Metadata: + cfn_nag: + rules_to_suppress: + - id: 'W89' + reason: "This Lambda does not require VPC" + - id: 'W92' + reason: "One Time execution. No need for ReservedConcurrentExecutions" DisableRuleSchedule: Type: AWS::Events::Rule Properties: From 91013dac615def4050ded4918229c237373b30e7 Mon Sep 17 00:00:00 2001 From: Iakov Gan Date: Thu, 31 Oct 2024 07:56:39 +0100 Subject: [PATCH 4/6] description --- cfn-templates/cur-aggregation-bucket-write-protection.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfn-templates/cur-aggregation-bucket-write-protection.yaml b/cfn-templates/cur-aggregation-bucket-write-protection.yaml index 91aa9d09..443a77ab 100644 --- a/cfn-templates/cur-aggregation-bucket-write-protection.yaml +++ b/cfn-templates/cur-aggregation-bucket-write-protection.yaml @@ -1,5 +1,5 @@ AWSTemplateFormatVersion: '2010-09-09' -Description: 'CFN template for scheduled bucket policy management using EventBridge' +Description: 'CID - Protecting CUR Bucket from replication Writes' Parameters: ResourcePrefix: From 9202825a6b23aa61c078754f26681741fed1a944 Mon Sep 17 00:00:00 2001 From: Iakov Gan Date: Thu, 31 Oct 2024 14:34:11 +0100 Subject: [PATCH 5/6] fix enablement funciton --- ...r-aggregation-bucket-write-protection.yaml | 56 ++++++++----------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/cfn-templates/cur-aggregation-bucket-write-protection.yaml b/cfn-templates/cur-aggregation-bucket-write-protection.yaml index 443a77ab..64c16bae 100644 --- a/cfn-templates/cur-aggregation-bucket-write-protection.yaml +++ b/cfn-templates/cur-aggregation-bucket-write-protection.yaml @@ -2,9 +2,15 @@ AWSTemplateFormatVersion: '2010-09-09' Description: 'CID - Protecting CUR Bucket from replication Writes' Parameters: - ResourcePrefix: + BucketName: Type: String - Description: Prefix for resource names. Must be the same as in the CUR Aggregation stack + Description: Target Bucket Name. You can use ACCOUNT_ID placeholder. + Default: 'cid-ACCOUNT_ID-shared' + + PolicySid: + Type: String + Description: 'Policy Sid to Disable/Enable' + Default: 'AllowReplicationWrite' DisableCronSchedule: Type: String @@ -20,6 +26,7 @@ Parameters: AllowedPattern: '^[0-9*,\-/\s?]+$' ConstraintDescription: Must be a valid cron expression + Resources: BucketPolicyLambdaRole: Type: AWS::IAM::Role @@ -42,7 +49,7 @@ Resources: Action: - s3:GetBucketPolicy - s3:PutBucketPolicy - Resource: !Sub 'arn:aws:s3:::${ResourcePrefix}-${AWS::AccountId}-shared' + Resource: !Sub 'arn:aws:s3:::${BucketName}' BucketPolicyLambda: Type: AWS::Lambda::Function @@ -56,45 +63,29 @@ Resources: import json import boto3 + POLICY_SID = os.environ['POLICY_SID'] + def lambda_handler(event, context): s3 = boto3.client('s3') action = event.get('action', 'enable') # 'enable' or 'disable' - account_id = context.invoked_function_arn.split(':')[4] - bucket_name = f"{os.environ['RESOURCE_PREFIX']}-{account_id}-shared" + bucket_name = os.environ['BUCKET_NAME'] try: - # Get current bucket policy - try: - policy = json.loads(s3.get_bucket_policy(Bucket=bucket_name)['Policy']) - except s3.exceptions.NoSuchBucketPolicy: - return { - 'statusCode': 404, - 'body': 'No bucket policy exists' - } - - # Find and modify the AllowReplicationWrite statement - found = False + policy = json.loads(s3.get_bucket_policy(Bucket=bucket_name)['Policy']) + + # Find and modify the policy statement for statement in policy['Statement']: - if statement.get('Sid') == 'AllowReplicationWrite': + if statement.get('Sid') == POLICY_SID: statement['Effect'] = 'Allow' if action == 'enable' else 'Deny' - found = True break + else: + raise Exception(f'{POLICY_SID} statement not found in policy') - if not found: - return { - 'statusCode': 404, - 'body': 'AllowReplicationWrite statement not found in policy' - } - - # Put updated policy - s3.put_bucket_policy( - Bucket=bucket_name, - Policy=json.dumps(policy) - ) + s3.put_bucket_policy(Bucket=bucket_name, Policy=json.dumps(policy)) return { 'statusCode': 200, - 'body': f'Successfully {action}d AllowReplicationWrite statement' + 'body': f'Successfully {action}d {POLICY_SID} statement' } except Exception as e: @@ -104,9 +95,10 @@ Resources: } Environment: Variables: - RESOURCE_PREFIX: !Ref ResourcePrefix - Timeout: 30 + BUCKET_NAME: !Ref BucketName + POLICY_SID: !Ref PolicySid MemorySize: 128 + Timeout: 60 Metadata: cfn_nag: rules_to_suppress: From b3f5d106c1338281841027c347c7f35f29470ac5 Mon Sep 17 00:00:00 2001 From: Iakov GAN <82834333+iakov-aws@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:19:15 +0100 Subject: [PATCH 6/6] Update cur-aggregation-bucket-write-protection.yaml --- cfn-templates/cur-aggregation-bucket-write-protection.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfn-templates/cur-aggregation-bucket-write-protection.yaml b/cfn-templates/cur-aggregation-bucket-write-protection.yaml index 64c16bae..176c2a6e 100644 --- a/cfn-templates/cur-aggregation-bucket-write-protection.yaml +++ b/cfn-templates/cur-aggregation-bucket-write-protection.yaml @@ -4,7 +4,7 @@ Description: 'CID - Protecting CUR Bucket from replication Writes' Parameters: BucketName: Type: String - Description: Target Bucket Name. You can use ACCOUNT_ID placeholder. + Description: Target Bucket Name. Replce ACCOUNT_ID with your Account Id. Default: 'cid-ACCOUNT_ID-shared' PolicySid: