├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CloudFormation ├── aws-backup-central-backup-account.yaml ├── aws-backup-member-account-iam-role.yaml ├── aws-backup-member-account.yaml └── aws-backup-org-policy.yaml ├── Images └── aws-backup-automation.jpg ├── LICENSE ├── Lambda └── OrgPolicyCustomResourceManager.zip └── README.md /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /CloudFormation/aws-backup-central-backup-account.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: This template creates the central backup KMS Key and Vault required for the automated centralized backup at scale in AWS Organizations using AWS Backup. 3 | It should be deployed in each member account. 4 | Metadata: 5 | AWS::CloudFormation::Interface: 6 | ParameterGroups: 7 | - 8 | Label: 9 | default: AWS Backup Configuration 10 | Parameters: 11 | - pCrossAccountBackupRole 12 | - pBackupKeyAlias 13 | - pCentralBackupVaultName 14 | - pOrganizationId 15 | - pTagKey1 16 | - pTagValue1 17 | ParameterLabels: 18 | pCrossAccountBackupRole: 19 | default: Enter an IAM Role Name 20 | pBackupKeyAlias: 21 | default: Backup KMS Key Alias Name 22 | pCentralBackupVaultName: 23 | default: Backup vault name (Case sensitive) 24 | pOrganizationId: 25 | default: Organization ID 26 | pTagKey1: 27 | default: Tag Key 28 | pTagValue1: 29 | default: Tag Value 30 | Parameters: 31 | pCrossAccountBackupRole: 32 | Type: String 33 | Description: This is the IAM role name for the cross-account backup role that carries out the backup activities. 34 | pBackupKeyAlias: 35 | Type: String 36 | Description: This is the name of the AWS Backup KMS key alias. 37 | pCentralBackupVaultName: 38 | Type: String 39 | Description: This is the name of the centralized account backup vault. 40 | AllowedPattern: ^[a-zA-Z0-9\-\_\.]{1,50}$ 41 | ConstraintDescription: Backup vault name is case sensitive. 42 | pOrganizationId: 43 | Type: String 44 | Description: This is the AWS Organization ID value. 45 | MinLength: 12 46 | MaxLength: 12 47 | AllowedPattern: '^o-[a-z0-9]{10,32}$' 48 | ConstraintDescription: > 49 | The Organization Id must be a 12 character string starting with o- and followed by 10 lower case 50 | alphanumeric characters 51 | pTagKey1: 52 | Type: String 53 | Description: This is the tag key to assign to resources. 54 | pTagValue1: 55 | Type: String 56 | Description: This is the tag value to assign to resources. 57 | Resources: 58 | rOrgAccountBackupRoleCentral: 59 | Type: "AWS::IAM::Role" 60 | Properties: 61 | Description: Allows AWS Backup to access AWS services 62 | AssumeRolePolicyDocument: 63 | Version: 2012-10-17 64 | Statement: 65 | - Effect: Allow 66 | Principal: 67 | Service: 68 | - backup.amazonaws.com 69 | Action: 70 | - "sts:AssumeRole" 71 | Path: / 72 | ManagedPolicyArns: 73 | - arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup 74 | - arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores 75 | RoleName: !Sub ${pCrossAccountBackupRole} 76 | Tags: 77 | - Key: !Ref pTagKey1 78 | Value: !Ref pTagValue1 79 | rCentralAccountBackupKey: 80 | Type: AWS::KMS::Key 81 | Metadata: 82 | cfn_nag: 83 | rules_to_suppress: 84 | - id: F76 85 | reason: The principal is restricted by the condition statement 86 | Properties: 87 | Description: "Backup Key" 88 | EnableKeyRotation: True 89 | KeyPolicy: 90 | Version: "2012-10-17" 91 | Id: !Sub ${pBackupKeyAlias} 92 | Statement: 93 | - 94 | Sid: "Enable IAM User Permissions" 95 | Effect: "Allow" 96 | Principal: 97 | AWS: 98 | - !Sub "arn:aws:iam::${AWS::AccountId}:root" 99 | Action: "kms:*" 100 | Resource: "*" 101 | 102 | - 103 | Sid: "Allow alias creation during setup" 104 | Effect: "Allow" 105 | Principal: 106 | AWS: "*" 107 | Action: "kms:CreateAlias" 108 | Resource: "*" 109 | Condition: 110 | StringEquals: 111 | "kms:CallerAccount": !Sub ${AWS::AccountId} 112 | "kms:ViaService": !Sub "cloudformation.${AWS::Region}.amazonaws.com" 113 | 114 | Tags: 115 | - Key: !Ref pTagKey1 116 | Value: !Ref pTagValue1 117 | rCentralAccountBackupKeyAlias: 118 | Type: AWS::KMS::Alias 119 | Properties: 120 | AliasName: !Sub alias/${pBackupKeyAlias} 121 | TargetKeyId: 122 | !Ref rCentralAccountBackupKey 123 | rCentralBackupVault: 124 | Type: AWS::Backup::BackupVault 125 | Properties: 126 | BackupVaultName: !Ref pCentralBackupVaultName 127 | EncryptionKeyArn: !GetAtt rCentralAccountBackupKey.Arn 128 | AccessPolicy: 129 | Version: "2012-10-17" 130 | Statement: 131 | - Sid: "Allow access to backup vault" 132 | Effect: Allow 133 | Action: backup:CopyIntoBackupVault 134 | Resource: "*" 135 | Principal: "*" 136 | Condition: 137 | StringEquals: 138 | aws:PrincipalOrgID: !Ref pOrganizationId 139 | Outputs: 140 | oCentralBackupVault: 141 | Value: !Ref rCentralBackupVault 142 | oOrgAccountBackupRoleCentral: 143 | Value: !Ref rOrgAccountBackupRoleCentral 144 | -------------------------------------------------------------------------------- /CloudFormation/aws-backup-member-account-iam-role.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: > 3 | This template creates the Backup IAM role required for the automated centralized backup at scale in AWS Organizations using AWS Backup. 4 | It should be deployed in each member account. 5 | Metadata: 6 | AWS::CloudFormation::Interface: 7 | ParameterGroups: 8 | - 9 | Label: 10 | default: AWS Backup Configuration 11 | Parameters: 12 | - pCrossAccountBackupRole 13 | - pTagKey1 14 | - pTagValue1 15 | ParameterLabels: 16 | pCrossAccountBackupRole: 17 | default: Enter an IAM Role Name 18 | pTagKey1: 19 | default: Enter a Tag Key 20 | pTagValue1: 21 | default: Enter a Tag Value 22 | Parameters: 23 | pCrossAccountBackupRole: 24 | Type: String 25 | Description: This is the IAM role name for the cross-account backup role that carries out the backup activities. 26 | pTagKey1: 27 | Type: String 28 | Description: This is the tag key to assign to resources. 29 | pTagValue1: 30 | Type: String 31 | Description: This is the tag value to assign to resources. 32 | Resources: 33 | rOrgAccountBackupRole: 34 | Type: "AWS::IAM::Role" 35 | Properties: 36 | Description: Allows AWS Backup to access AWS services 37 | AssumeRolePolicyDocument: 38 | Version: 2012-10-17 39 | Statement: 40 | - Effect: Allow 41 | Principal: 42 | Service: 43 | - backup.amazonaws.com 44 | Action: 45 | - "sts:AssumeRole" 46 | Path: / 47 | ManagedPolicyArns: 48 | - arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup 49 | - arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores 50 | RoleName: !Sub ${pCrossAccountBackupRole} 51 | Tags: 52 | - Key: !Ref pTagKey1 53 | Value: !Ref pTagValue1 54 | 55 | Outputs: 56 | oOrgAccountBackupRole: 57 | Value: !Ref rOrgAccountBackupRole 58 | 59 | -------------------------------------------------------------------------------- /CloudFormation/aws-backup-member-account.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: > 3 | This template creates the Backup KMS Key and Vault required for the automated centralized backup at scale in AWS Organizations using AWS Backup. 4 | It should be deployed in each member account. 5 | Metadata: 6 | AWS::CloudFormation::Interface: 7 | ParameterGroups: 8 | - 9 | Label: 10 | default: AWS Backup Configuration 11 | Parameters: 12 | - pCrossAccountBackupRole 13 | - pBackupKeyAlias 14 | - pMemberBackupVaultName 15 | - pOrganizationId 16 | - pTagKey1 17 | - pTagValue1 18 | ParameterLabels: 19 | pCrossAccountBackupRole: 20 | default: Enter the name of the Backup IAM Role 21 | pBackupKeyAlias: 22 | default: Name of the AWS Backup KMS key Alias 23 | pMemberBackupVaultName: 24 | default: Name of the AWS Backup vault (Case sensitive) 25 | pOrganizationId: 26 | default: Organization ID 27 | pTagKey1: 28 | default: Enter a Tag Key 29 | pTagValue1: 30 | default: Enter a Tag Value 31 | Parameters: 32 | pCrossAccountBackupRole: 33 | Type: String 34 | Description: This is the name for the cross-account backup role that carries out the backup activities. 35 | pBackupKeyAlias: 36 | Type: String 37 | Description: This is the name of the AWS Backup KMS key alias. 38 | pMemberBackupVaultName: 39 | Type: String 40 | Description: This is the name of the member account backup vaults. 41 | AllowedPattern: ^[a-zA-Z0-9\-\_\.]{1,50}$ 42 | ConstraintDescription: Backup vault name is case sensitive. Must contain from 2 to 50 alphanumeric and '-_' characters. 43 | pOrganizationId: 44 | Type: String 45 | Description: This is the AWS Organization ID value. 46 | MinLength: 12 47 | MaxLength: 12 48 | AllowedPattern: '^o-[a-z0-9]{10,32}$' 49 | ConstraintDescription: > 50 | The Organization Id must be a 12 character string starting with o- and followed by 10 lower case 51 | alphanumeric characters 52 | pTagKey1: 53 | Type: String 54 | Description: This is the tag key to assign to resources. 55 | pTagValue1: 56 | Type: String 57 | Description: This is the tag value to assign to resources. 58 | Resources: 59 | rMemberAccountBackupKey: 60 | Type: AWS::KMS::Key 61 | Metadata: 62 | cfn_nag: 63 | rules_to_suppress: 64 | - id: F76 65 | reason: The principal is restricted by the condition statement 66 | Properties: 67 | Description: Symmetric AWS CMK for Member Account Backup Vault Encryption 68 | EnableKeyRotation: True 69 | KeyPolicy: 70 | Version: "2012-10-17" 71 | Id: !Sub ${pBackupKeyAlias} 72 | Statement: 73 | - 74 | Sid: "Enable IAM User Permissions" 75 | Effect: "Allow" 76 | Principal: 77 | AWS: !Sub arn:aws:iam::${AWS::AccountId}:root 78 | Action: "kms:*" 79 | Resource: "*" 80 | - 81 | Sid: Allow use of the key by authorized Backup principal 82 | Effect: "Allow" 83 | Principal: 84 | AWS: !Sub arn:aws:iam::${AWS::AccountId}:role/${pCrossAccountBackupRole} 85 | Action: 86 | - kms:DescribeKey 87 | - kms:Encrypt 88 | - kms:Decrypt 89 | - kms:ReEncrypt* 90 | - kms:GenerateDataKey 91 | - kms:GenerateDataKeyWithoutPlaintext 92 | Resource: "*" 93 | Condition: 94 | StringEquals: 95 | "kms:ViaService": !Sub "backup.amazonaws.com" 96 | - 97 | Sid: Allow alias creation during setup 98 | Effect: "Allow" 99 | Principal: 100 | AWS: "*" 101 | Action: "kms:CreateAlias" 102 | Resource: "*" 103 | Condition: 104 | StringEquals: 105 | "kms:CallerAccount": !Sub ${AWS::AccountId} 106 | "kms:ViaService": !Sub "cloudformation.${AWS::Region}.amazonaws.com" 107 | Tags: 108 | - Key: !Ref pTagKey1 109 | Value: !Ref pTagValue1 110 | rMemberAccountBackupKeyAlias: 111 | Type: AWS::KMS::Alias 112 | Properties: 113 | AliasName: !Sub alias/${pBackupKeyAlias} 114 | TargetKeyId: 115 | !Ref rMemberAccountBackupKey 116 | rMemberAccountBackupVault: 117 | Type: AWS::Backup::BackupVault 118 | Properties: 119 | BackupVaultName: !Ref pMemberBackupVaultName 120 | EncryptionKeyArn: !GetAtt rMemberAccountBackupKey.Arn 121 | AccessPolicy: 122 | Version: "2012-10-17" 123 | Statement: 124 | - Sid: "Allow access to backup vault" 125 | Effect: Allow 126 | Action: backup:CopyIntoBackupVault 127 | Resource: "*" 128 | Principal: "*" 129 | Condition: 130 | StringEquals: 131 | aws:PrincipalOrgID: !Ref pOrganizationId 132 | Outputs: 133 | oMemberAccountBackupVault: 134 | Value: !Ref rMemberAccountBackupVault 135 | oMemberAccountKMSKey: 136 | Value: !Ref rMemberAccountBackupKey 137 | 138 | -------------------------------------------------------------------------------- /CloudFormation/aws-backup-org-policy.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: This template deploys Backup Policies required to manage backups at an organization level. 3 | 4 | Parameters: 5 | pOrgbackupAccounts: 6 | Description: A CSV list of the AWS accounts or OUs to attach backup policies. 7 | Type: CommaDelimitedList 8 | Default: "" 9 | pCrossAccountBackupRole: 10 | Description: This is the IAM role name for the cross-account backup role that carries out the backup activities. 11 | Type: String 12 | pBackupScheduler1: 13 | Description: The CRON or rate job to initiate backup jobs in sample backup policy 1. For example, cron(0 0/1 ? * * *). 14 | Type: String 15 | Default: "cron(0 0/1 ? * * *)" 16 | pBackupScheduler2: 17 | Description: The CRON or rate job to initiate backup jobs in sample backup policy 2. For example, cron(0 0/1 ? * * *). 18 | Type: String 19 | Default: "cron(0 0/1 ? * * *)" 20 | pMemberAccountBackupVault: 21 | AllowedPattern: ^[a-zA-Z0-9\-\_\.]{1,50}$ 22 | ConstraintDescription: The name of the member account Backup vaults. (Name is case sensitive). 23 | Type: String 24 | pCentralBackupVaultArn: 25 | Type: String 26 | Description: This is the ARN of the centralized account backup vault. 27 | pBackupTagKey1: 28 | Type: String 29 | Description: The tag key 1 to automatically assign AWS Backup supported resources to a backup plan across the member accounts. 30 | Default: 'project' 31 | pBackupTagValue1: 32 | Type: String 33 | Description: The tag value 1 to automatically assign AWS Backup supported resources to a backup plan across the member accounts. 34 | Default: 'aws-backup-demo' 35 | pBackupTagKey2: 36 | Type: String 37 | Description: The tag key 2 to automatically assign AWS Backup supported resources to a backup plan across the member accounts. 38 | Default: 'environment' 39 | pBackupTagValue2: 40 | Type: String 41 | Description: The tag value 2 to automatically assign AWS Backup supported resources to a backup plan across the member accounts. 42 | Default: 'aws-dev' 43 | pTagKey: 44 | Type: String 45 | Description: This is the tag key to assign to resources created by CloudFormation. 46 | Default: 'project' 47 | pTagValue: 48 | Type: String 49 | Description: This is the tag value to assign to resources created by CloudFormation. 50 | Default: 'aws-backup' 51 | 52 | pStackBinaryURL: 53 | Description: The URL for the StackBinary Zip File 54 | Type: String 55 | Default: 'https://awsstorageblogresources.s3.us-west-2.amazonaws.com/ioawssecbackupblog/OrgPolicyCustomResourceManager.zip' 56 | Resources: 57 | rLocalCacheBucket: 58 | Type: "AWS::S3::Bucket" 59 | DeletionPolicy: Delete 60 | UpdateReplacePolicy: Retain 61 | Properties: 62 | BucketEncryption: 63 | ServerSideEncryptionConfiguration: 64 | - ServerSideEncryptionByDefault: 65 | SSEAlgorithm: AES256 66 | PublicAccessBlockConfiguration: 67 | BlockPublicAcls: true 68 | BlockPublicPolicy: true 69 | IgnorePublicAcls: true 70 | RestrictPublicBuckets: true 71 | 72 | CleanupLocalCacheBucketOnDelete: 73 | Type: Custom::CleanupBucket 74 | Properties: 75 | ServiceToken: !GetAtt rGlobalCfnCodeReplicatorLambda.Arn 76 | S3BucketToCleanup: !Ref rLocalCacheBucket 77 | 78 | CopySolutionToLocalCacheBucket: 79 | Type: Custom::ReplicateSolutionBinaries 80 | Properties: 81 | ServiceToken: !GetAtt rGlobalCfnCodeReplicatorLambda.Arn 82 | SolutionDestinationBucket: !Ref rLocalCacheBucket 83 | SolutionURL: !Ref pStackBinaryURL 84 | 85 | rGlobalCfnCodeReplicatorLambda: 86 | Type: AWS::Lambda::Function 87 | Metadata: 88 | cfn_nag: 89 | rules_to_suppress: 90 | - id: W89 91 | reason: "NA" 92 | - id: W92 93 | reason: "NA" 94 | Properties: 95 | Code: 96 | ZipFile: |- 97 | #!/usr/bin/env python 98 | # -*- coding: utf-8 -*- 99 | import json 100 | import boto3 101 | import urllib3 102 | import os 103 | import shutil 104 | from urllib.parse import urlparse 105 | physical_resource_id = 'GlobalCfnCodeReplicator' 106 | def process_bucket_cleanup_request(bucket_name): 107 | print(f"process_bucket_cleanup_request starting for bucket_name : {bucket_name}") 108 | s3 = boto3.resource('s3') 109 | bucket_to_delete = s3.Bucket(bucket_name) 110 | response = bucket_to_delete.objects.all().delete() 111 | print(f"process_bucket_cleanup_request all object delete done. Response : {response}") 112 | 113 | def download_url(url, save_path): 114 | c = urllib3.PoolManager() 115 | with c.request('GET',url, preload_content=False) as resp, open(save_path, 'wb') as out_file: 116 | shutil.copyfileobj(resp, out_file) 117 | resp.release_conn() 118 | 119 | def lambda_handler(event, context): 120 | try: 121 | print(f'Handling event : {event}') 122 | request_type = event.get('RequestType') 123 | solution_url = event['ResourceProperties'].get('SolutionURL') 124 | solution_bucket = event['ResourceProperties'].get('SolutionDestinationBucket') 125 | response_data = { 126 | 'RequestType': request_type, 127 | 'SolutionURL' : solution_url, 128 | 'SolutionDestinationBucket' : solution_bucket 129 | } 130 | if request_type == 'Create' or request_type == 'Update': 131 | if solution_url: 132 | print(f'downloading file from : {solution_url}') 133 | a = urlparse(solution_url) 134 | original_file_name = os.path.basename(a.path) 135 | temp_file_name = '/tmp/'+original_file_name 136 | download_url(solution_url,temp_file_name) 137 | file_size = (os.stat(temp_file_name).st_size / 1024) 138 | print(f'Downloaded report to File : {temp_file_name} , Size : {file_size}') 139 | s3_client = boto3.client('s3') 140 | print(f"uploading payload to : {solution_bucket} at {original_file_name}") 141 | extraArgsForUpload = {'ACL':'bucket-owner-full-control', 'Tagging':'Source=StackBinaryURL'} 142 | s3_client.upload_file(Filename=temp_file_name, Bucket=solution_bucket, Key=original_file_name,ExtraArgs=extraArgsForUpload) 143 | elif request_type == 'Delete': 144 | solution_bucket = event['ResourceProperties'].get('S3BucketToCleanup') 145 | if solution_bucket: 146 | process_bucket_cleanup_request(solution_bucket) 147 | send(event, context, 'SUCCESS', response_data, physical_resource_id) 148 | except Exception as e: 149 | send(event, context, 'FAILED', response_data, physical_resource_id) 150 | def send(event, context, response_status, response_data, physical_resource_id, no_echo=False): 151 | http = urllib3.PoolManager() 152 | response_url = event['ResponseURL'] 153 | json_response_body = json.dumps({ 154 | 'Status': response_status, 155 | 'Reason': f'See the details in CloudWatch Log Stream: {context.log_stream_name}', 156 | 'PhysicalResourceId': physical_resource_id, 157 | 'StackId': event['StackId'], 158 | 'RequestId': event['RequestId'], 159 | 'LogicalResourceId': event['LogicalResourceId'], 160 | 'NoEcho': no_echo, 161 | 'Data': response_data 162 | }).encode('utf-8') 163 | headers = { 164 | 'content-type': '', 165 | 'content-length': str(len(json_response_body)) 166 | } 167 | try: 168 | http.request('PUT', response_url, 169 | body=json_response_body, headers=headers) 170 | except Exception as e: 171 | print(e) 172 | Description: Copy Solutions Binary to Local Cache Bucket 173 | Handler: index.lambda_handler 174 | Role : !GetAtt OrgPolicyCustomResourceManagerRole.Arn 175 | Runtime: python3.7 176 | Timeout: 300 177 | 178 | rOrgBackUpPolicy: 179 | Type: Custom::OrgPolicy 180 | Properties: 181 | PolicyPrefix: org-backup-policy 182 | PolicyType: BACKUP_POLICY 183 | PolicyTargets : !Ref pOrgbackupAccounts 184 | PolicyDescription: >- 185 | BackupPolicy for Daily Backup as per the resource selection criteria 186 | PolicyContents: |- 187 | [ 188 | { 189 | "plans": { 190 | "OrgDailyBackupPlan-1": { 191 | "regions": { 192 | "@@append":[ "us-east-1", "eu-central-1", "us-east-2", "us-west-2", "eu-west-1", "ap-southeast-1", "ap-southeast-2", "ca-central-1", "eu-north-1" ] }, 193 | "rules": { 194 | "OrgDailyBackupRule": { 195 | "schedule_expression": { 196 | "@@assign": "SCHEDULE_EXPRESSION_1" 197 | }, 198 | "start_backup_window_minutes": { 199 | "@@assign": "480" 200 | }, 201 | "complete_backup_window_minutes": { 202 | "@@assign": "720" 203 | }, 204 | "lifecycle": { 205 | "delete_after_days": { 206 | "@@assign": "5" 207 | } 208 | }, 209 | "target_backup_vault_name": { 210 | "@@assign": "VAULT_NAME" 211 | }, 212 | "recovery_point_tags": { 213 | "TAG_KEY": { 214 | "tag_key": { 215 | "@@assign": "TAG_KEY" 216 | }, 217 | "tag_value": { 218 | "@@assign": "TAG_VALUE" 219 | } 220 | } 221 | }, 222 | "copy_actions": { 223 | "CENTRAL_VAULT_ARN": { 224 | "target_backup_vault_arn": { 225 | "@@assign": "CENTRAL_VAULT_ARN" 226 | }, 227 | "lifecycle": { 228 | "move_to_cold_storage_after_days": { 229 | "@@assign": "30" 230 | }, 231 | "delete_after_days": { 232 | "@@assign": "365" 233 | } 234 | } 235 | } 236 | } 237 | } 238 | }, 239 | "backup_plan_tags": { 240 | "TAG_KEY": { 241 | "tag_key": { 242 | "@@assign": "TAG_KEY" 243 | }, 244 | "tag_value": { 245 | "@@assign": "TAG_VALUE" 246 | } 247 | } 248 | }, 249 | "selections": { 250 | "tags": { 251 | "OrgDailyBackupSelection": { 252 | "iam_role_arn": { 253 | "@@assign": "arn:aws:iam::$account:role/BACKUP_ROLE" 254 | }, 255 | "tag_key": { 256 | "@@assign": "TAG_KEY_1" 257 | }, 258 | "tag_value": { 259 | "@@assign": [ 260 | "TAG_VALUE_1" 261 | ] 262 | } 263 | } 264 | } 265 | } 266 | } 267 | } 268 | }, 269 | { 270 | "plans": { 271 | "OrgDailyBackupPlan-2": { 272 | "regions": { 273 | "@@append":[ "us-east-1", "eu-central-1", "us-east-2", "us-west-2", "eu-west-1", "ap-southeast-1", "ap-southeast-2", "ca-central-1", "eu-north-1" ] }, 274 | "rules": { 275 | "OrgDailyBackupRule": { 276 | "schedule_expression": { 277 | "@@assign": "SCHEDULE_EXPRESSION_2" 278 | }, 279 | "start_backup_window_minutes": { 280 | "@@assign": "480" 281 | }, 282 | "complete_backup_window_minutes": { 283 | "@@assign": "720" 284 | }, 285 | "lifecycle": { 286 | "delete_after_days": { 287 | "@@assign": "5" 288 | } 289 | }, 290 | "target_backup_vault_name": { 291 | "@@assign": "VAULT_NAME" 292 | }, 293 | "recovery_point_tags": { 294 | "TAG_KEY": { 295 | "tag_key": { 296 | "@@assign": "TAG_KEY" 297 | }, 298 | "tag_value": { 299 | "@@assign": "TAG_VALUE" 300 | } 301 | } 302 | }, 303 | "copy_actions": { 304 | "CENTRAL_VAULT_ARN": { 305 | "target_backup_vault_arn": { 306 | "@@assign": "CENTRAL_VAULT_ARN" 307 | }, 308 | "lifecycle": { 309 | "move_to_cold_storage_after_days": { 310 | "@@assign": "30" 311 | }, 312 | "delete_after_days": { 313 | "@@assign": "365" 314 | } 315 | } 316 | } 317 | } 318 | } 319 | }, 320 | "backup_plan_tags": { 321 | "TAG_KEY": { 322 | "tag_key": { 323 | "@@assign": "TAG_KEY" 324 | }, 325 | "tag_value": { 326 | "@@assign": "TAG_VALUE" 327 | } 328 | } 329 | }, 330 | "selections": { 331 | "tags": { 332 | "OrgDailyBackupSelection": { 333 | "iam_role_arn": { 334 | "@@assign": "arn:aws:iam::$account:role/BACKUP_ROLE" 335 | }, 336 | "tag_key": { 337 | "@@assign": "TAG_KEY_2" 338 | }, 339 | "tag_value": { 340 | "@@assign": [ 341 | "TAG_VALUE_2" 342 | ] 343 | } 344 | } 345 | } 346 | } 347 | } 348 | } 349 | } 350 | ] 351 | Variables: 352 | - BACKUP_ROLE: !Ref "pCrossAccountBackupRole" 353 | - VAULT_NAME: !Ref pMemberAccountBackupVault 354 | - TAG_KEY_1 : !Ref "pBackupTagKey1" 355 | - TAG_VALUE_1 : !Ref "pBackupTagValue1" 356 | - TAG_KEY_2 : !Ref "pBackupTagKey2" 357 | - TAG_VALUE_2 : !Ref "pBackupTagValue2" 358 | - TAG_KEY : !Ref "pTagKey" 359 | - TAG_VALUE : !Ref "pTagValue" 360 | - SCHEDULE_EXPRESSION_1 : !Ref "pBackupScheduler1" 361 | - SCHEDULE_EXPRESSION_2 : !Ref "pBackupScheduler2" 362 | - CENTRAL_VAULT_ARN : !Ref "pCentralBackupVaultArn" 363 | ServiceToken: !GetAtt OrgPolicyCustomResourceManager.Arn 364 | 365 | 366 | OrgPolicyCustomResourceManager: 367 | Type: AWS::Lambda::Function 368 | DependsOn: CopySolutionToLocalCacheBucket 369 | Properties: 370 | FunctionName: OrgPolicyCustomResourceManager 371 | Description: Lambda function to deploy CloudFormation custom resources 372 | to AWS Organizations. 373 | Handler: OrgPolicyCustomResourceManager.lambda_handler 374 | Code: 375 | S3Bucket: !Ref rLocalCacheBucket 376 | S3Key: OrgPolicyCustomResourceManager.zip 377 | Role: !GetAtt OrgPolicyCustomResourceManagerRole.Arn 378 | Runtime: python3.8 379 | MemorySize: 256 380 | Timeout: 300 381 | Tags: 382 | - Key: !Ref pTagKey 383 | Value: !Ref pTagValue 384 | 385 | OrgPolicyCustomResourceManagerRole: 386 | Type: 'AWS::IAM::Role' 387 | Metadata: 388 | cfn_nag: 389 | rules_to_suppress: 390 | - id: W11 391 | reason: IAM role should not allow * resource on its permissions policy 392 | - id: F3 393 | reason: IAM role should not allow * resource on its permissions policy 394 | Properties: 395 | AssumeRolePolicyDocument: 396 | Version: '2012-10-17' 397 | Statement: 398 | - Effect: Allow 399 | Principal: 400 | Service: 'lambda.amazonaws.com' 401 | Action: 402 | - 'sts:AssumeRole' 403 | Path: '/' 404 | ManagedPolicyArns: 405 | - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' 406 | Policies: 407 | - PolicyName: AssumeOrgRole 408 | PolicyDocument: 409 | Statement: 410 | - Effect: Allow 411 | Action: 412 | - sts:AssumeRole 413 | Resource: '*' 414 | - PolicyName: OrgPermissions 415 | PolicyDocument: 416 | Statement: 417 | - Effect: Allow 418 | Action: 419 | - organizations:CreatePolicy 420 | - organizations:DeletePolicy 421 | - organizations:AttachPolicy 422 | - organizations:DetachPolicy 423 | - organizations:ListPolicies 424 | - organizations:UpdatePolicy 425 | - organizations:DescribePolicy 426 | - organizations:ListTargetsForPolicy 427 | Resource: '*' 428 | - PolicyName: S3Permissions 429 | PolicyDocument: 430 | Statement: 431 | - Effect: Allow 432 | Action: 433 | - s3:* 434 | Resource: 435 | !Sub arn:aws:s3:::${rLocalCacheBucket}/* 436 | -------------------------------------------------------------------------------- /Images/aws-backup-automation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-backup-automation/703450876f5e7d9ffdae9312deb97a307c977d45/Images/aws-backup-automation.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /Lambda/OrgPolicyCustomResourceManager.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-backup-automation/703450876f5e7d9ffdae9312deb97a307c977d45/Lambda/OrgPolicyCustomResourceManager.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blog Post - [Automate centralized backup at scale across AWS services using AWS Backup](https://aws.amazon.com/blogs/storage/automate-centralized-backup-at-scale-across-aws-services-using-aws-backup/) 2 | 3 | This solution demonstrates how you can save time using AWS CloudFormation automation to centrally automate and scale the process of implementing AWS Backup policies, backup vaults, and cross-Region, cross-account replication across your multi-account AWS environment. Using this solution, you can easily manage AWS Backup with automation and implement a data protection strategy that mitigates the risk of data loss.. In this repository, you will find all the AWS CloudFormation templates and Python code that will build this solution in your AWS environment. 4 | 5 | 6 | ## Solution architecture and design 7 | 8 | 9 | ## ![](/Images/aws-backup-automation.jpg) 10 | 11 | The workflow and architecture of the solution works as follows: 12 | 13 | In the management account (the environment hosting your AWS Organizations): 14 | 15 | 1. Opt in to use the AWS Backup service and cross-account management features. See the blog post on how to [Automate centralized backup at scale across AWS services using AWS Backup](https://aws.amazon.com/blogs/storage/automate-centralized-backup-at-scale-across-aws-services-using-aws-backup/) for further details. 16 | 2. [aws-backup-member-account-iam-role.yaml](./CloudFormation/aws-backup-member-account-iam-role.yaml): This stackset implements the IAM roles assumed by the AWS Backup service for backup and restore jobs across the member accounts. 17 | 3. [aws-backup-member-account.yaml](./CloudFormation/aws-backup-member-account.yaml): This stackset implements the local backup vaults and vault AWS KMS encryption keys in the member accounts. We assume that the supported AWS Backup resources in the member accounts are tagged as indicated in the prerequisites section of the blog. 18 | 4. [aws-backup-central-backup-account.yaml](./CloudFormation/aws-backup-central-backup-account.yaml): This stackset implements the central backup vault, IAM role, vault AWS KMS encryption key, and vault access policy in the central backup account. 19 | 5. [aws-backup-org-policy.yaml](./CloudFormation/aws-backup-org-policy.yaml): This stack implements a Lambda function, Lambda IAM role, and centralized backup policies. The backup policies consist of tag-based and cross-account, cross-Region copy policies that are automatically attached to the root OU and inherited by all AWS Organizations accounts. 20 | ## Security 21 | 22 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 23 | 24 | ## License 25 | 26 | This library is licensed under the MIT-0 License. See the LICENSE file. 27 | 28 | --------------------------------------------------------------------------------