# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

AWSTemplateFormatVersion: "2010-09-09"
Description: AWS CloudFormation Template for WorkDocs Backup/Restore to S3.
Parameters:
  NotificationEmail:
    Description: "Email address to notify status of backup/restore"
    Type: String
  ArchiveFolderId:
    Description: "WorkDocs Archive folder ID"
    Type: String
  RestoreFolderId:
    Description: "WorkDocs Restore folder ID"
    Type: String
  S3StorageClass:
    Description: "Storage class for archive"
    Type: String
    Default: "GLACIER_IR"
    AllowedValues:
      - "STANDARD"
      - "STANDARD_IA"
      - "ONEZONE_IA"
      - "GLACIER_IR"
      - "GLACIER"
      - "DEEP_ARCHIVE"
  EventSchedule:
    Description: Schedule (cron expression) when to run backup & restore. Default is 8 am UTC everyday.
    Type: String
    Default: "0 8 * * ? *"
  ExpirationInDays:
    Description: Retention period (in days) for archived data
    Type: Number
    Default: 180
  CodeS3Bucket:
    Description: Bucket name where the Lambda code is stored
    Type: String
    Default: awsstorageblogresources
  CodeS3PathPrefix:
    Description: Prefix path where the Lambda code is stored
    Type: String
    Default: workdocstos3archive
Resources:
  S3ArchiveBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    UpdateReplacePolicy : Retain
    Properties: 
      BucketName: !Sub wdbr-archive-${AWS::AccountId}
      BucketEncryption: 
        ServerSideEncryptionConfiguration: 
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      LifecycleConfiguration:
        Rules: 
          - Id: ExpireRule
            Status: Enabled
            ExpirationInDays: !Ref ExpirationInDays
      NotificationConfiguration:
        EventBridgeConfiguration:
          EventBridgeEnabled: true
  DeadLetterQueue: 
    Type: AWS::SQS::Queue
    Properties: 
      FifoQueue: True
      QueueName: "WDBRDeadLetterQueue.fifo"
      KmsMasterKeyId: "alias/aws/sqs"
  BackupQueue: 
    Type: AWS::SQS::Queue
    Properties: 
      FifoQueue: True
      QueueName: "WDBRBackupQueue.fifo"
      KmsMasterKeyId: "alias/aws/sqs"
      VisibilityTimeout: 600
      RedrivePolicy: 
        deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn
        maxReceiveCount: 5
  RestoreQueue: 
    Type: AWS::SQS::Queue
    Properties: 
      FifoQueue: True
      QueueName: "WDBRRestoreQueue.fifo"
      KmsMasterKeyId: "alias/aws/sqs"
      VisibilityTimeout: 600
      RedrivePolicy: 
        deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn
        maxReceiveCount: 5
  RestoreDynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties: 
      TableName: "WDBRRestoreTable"
      BillingMode: "PAY_PER_REQUEST"
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true
      AttributeDefinitions: 
        - 
          AttributeName: "Path"
          AttributeType: "S"
        - 
          AttributeName: "RunId"
          AttributeType: "S"
      KeySchema: 
        - 
          AttributeName: "Path"
          KeyType: "HASH"
      GlobalSecondaryIndexes: 
        - 
          IndexName: "RunId-Path-index"
          KeySchema: 
            - 
              AttributeName: "RunId"
              KeyType: "HASH"
            - 
              AttributeName: "Path"
              KeyType: "RANGE"
          Projection:
            ProjectionType: "INCLUDE"
            NonKeyAttributes:
              - Type
  IndexDynamoDBTable: 
    Type: AWS::DynamoDB::Table
    Properties: 
      TableName: "WDBRIndexTable"
      BillingMode: "PAY_PER_REQUEST"
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true
      AttributeDefinitions: 
        - 
          AttributeName: "FileName"
          AttributeType: "S"
        - 
          AttributeName: "PathName"
          AttributeType: "S"
        - 
          AttributeName: "RunId"
          AttributeType: "S"
      KeySchema: 
        - 
          AttributeName: "FileName"
          KeyType: "HASH"
        - 
          AttributeName: "PathName"
          KeyType: "RANGE"
      GlobalSecondaryIndexes: 
        - 
          IndexName: "RunId-PathName-index"
          KeySchema: 
            - 
              AttributeName: "RunId"
              KeyType: "HASH"
            - 
              AttributeName: "PathName"
              KeyType: "RANGE"
          Projection:
            ProjectionType: "ALL"
  NotificationTopic:
    Type: AWS::SNS::Topic
    Properties: 
      TopicName: "WDBRNotificationTopic"
      DisplayName: "WorkDocs Backup-Restore"
      Subscription:
        - Endpoint:
            Ref: NotificationEmail
          Protocol: email
  LambdaLayer:
    Type: AWS::Lambda::LayerVersion
    Properties:
      CompatibleRuntimes:
        - nodejs12.x
        - nodejs14.x
        - nodejs16.x
      Content:
        S3Bucket: !Ref CodeS3Bucket
        S3Key: !Sub '${CodeS3PathPrefix}/wdbr-lambda-layer.zip'
      Description: node-fetch + exceljs
      LayerName: wdbr-lambda-layer
      LicenseInfo: MIT
  BackupCheckFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: WDBRBackupCheckFunction
      Description: Checks if there are any files to backup.
      Runtime: nodejs16.x
      MemorySize: 512
      Role: !GetAtt WDBRLambdaRole.Arn
      Handler: WorkDocsBackupCheck.handler
      Code:
        S3Bucket: !Ref CodeS3Bucket
        S3Key: !Sub '${CodeS3PathPrefix}/wdbr-code.zip'
      Timeout: 300
      TracingConfig:
        Mode: Active
      Layers:
        - !Ref LambdaLayer
      Environment:
        Variables:
          S3_FOLDER_NAME: Archive
          SNS_ARN: !Ref NotificationTopic
          SQS_QUEUE_URL: !Ref BackupQueue
          WORKDOCS_FOLDER_ID: !Ref ArchiveFolderId
  BackupScheduledRule: 
    Type: AWS::Events::Rule
    Properties: 
      Name: WDBRBackupCheckRule
      Description: "ScheduledRule for Backup Check"
      ScheduleExpression: !Sub 'cron(${EventSchedule})'
      State: "ENABLED"
      Targets: 
        - 
          Arn: !GetAtt BackupCheckFunction.Arn
          Id: "TargetFunctionV1"
  PermissionForBackupEventsToInvokeLambda: 
    Type: AWS::Lambda::Permission
    Properties: 
      FunctionName: !Ref BackupCheckFunction
      Action: "lambda:InvokeFunction"
      Principal: "events.amazonaws.com"
      SourceArn: !GetAtt BackupScheduledRule.Arn
  BackupWorkerFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: WDBRBackupWorkerFunction
      Description: Backup files to S3
      Runtime: nodejs16.x
      MemorySize: 1024
      Role: !GetAtt WDBRLambdaRole.Arn
      Handler: WorkDocsBackupWorker.handler
      Code:
        S3Bucket: !Ref CodeS3Bucket
        S3Key: !Sub '${CodeS3PathPrefix}/wdbr-code.zip'
      Timeout: 600
      TracingConfig:
        Mode: Active
      Layers:
        - !Ref LambdaLayer
      Environment:
        Variables:
          DDB_TABLE_INDEX_NAME: "RunId-PathName-index"
          DDB_TABLE_NAME: "WDBRIndexTable"
          S3_BUCKET_NAME: !Ref S3ArchiveBucket
          S3_STORAGE_CLASS: !Ref S3StorageClass
          SNS_ARN: !Ref NotificationTopic
          WORKDOCS_FOLDER_ID: !Ref ArchiveFolderId
  BackupWorkerEvents:
    Type: AWS::Lambda::EventSourceMapping
    Properties:
      BatchSize: 1
      EventSourceArn: !GetAtt BackupQueue.Arn
      FunctionName: !GetAtt BackupWorkerFunction.Arn
  RestoreCheckFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: WDBRRestoreCheckFunction
      Description: Checks if there are any files to restore.
      Runtime: nodejs16.x
      MemorySize: 512
      Role: !GetAtt WDBRLambdaRole.Arn
      Handler: WorkDocsRestoreCheck.handler
      Code:
        S3Bucket: !Ref CodeS3Bucket
        S3Key: !Sub '${CodeS3PathPrefix}/wdbr-code.zip'
      Timeout: 300
      TracingConfig:
        Mode: Active
      Layers:
        - !Ref LambdaLayer
      Environment:
        Variables:
          SNS_ARN: !Ref NotificationTopic
          SQS_QUEUE_URL: !Ref RestoreQueue
          DDB_TABLE_NAME: "WDBRRestoreTable"
          WORKDOCS_RESTORE_FOLDER_ID: !Ref RestoreFolderId
  RestoreScheduledRule: 
    Type: AWS::Events::Rule
    Properties: 
      Name: WDBRRestoreCheckRule
      Description: "Scheduled Rule for Restore Check"
      ScheduleExpression: !Sub 'cron(${EventSchedule})'
      State: "ENABLED"
      Targets: 
        - 
          Arn: !GetAtt RestoreCheckFunction.Arn
          Id: "TargetFunctionV1"
  PermissionForRestoreEventsToInvokeLambda: 
    Type: AWS::Lambda::Permission
    Properties: 
      FunctionName: !Ref RestoreCheckFunction
      Action: "lambda:InvokeFunction"
      Principal: "events.amazonaws.com"
      SourceArn: !GetAtt RestoreScheduledRule.Arn
  RestoreObjectRule: 
    Type: AWS::Events::Rule
    Properties: 
      Name: WDBRRestoreObjectRule
      Description: "Scheduled Rule for Restore Check"
      EventPattern:
        source:
          - 'aws.s3'
        detail-type:
          - 'Object Restore Completed'
        detail:
          bucket:
            name:
              - !Ref S3ArchiveBucket
      State: "ENABLED"
      Targets: 
        - 
          Arn: !GetAtt RestoreWorkerFunction.Arn
          Id: "TargetFunctionV1"
  RestoreWorkerFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: WDBRRestoreWorkerFunction
      Description: Restore files from S3
      Runtime: nodejs16.x
      MemorySize: 1024
      Role: !GetAtt WDBRLambdaRole.Arn
      Handler: WorkDocsRestoreWorker.handler
      Code:
        S3Bucket: !Ref CodeS3Bucket
        S3Key: !Sub '${CodeS3PathPrefix}/wdbr-code.zip'
      Timeout: 600
      TracingConfig:
        Mode: Active
      Layers:
        - !Ref LambdaLayer
      Environment:
        Variables:
          S3_BUCKET_NAME: !Ref S3ArchiveBucket
          SNS_ARN: !Ref NotificationTopic
          DDB_TABLE_NAME: "WDBRRestoreTable"
          DDB_TABLE_INDEX_NAME: "RunId-Path-index"
  PermissionForRestoreObjectEventsToInvokeLambda: 
    Type: AWS::Lambda::Permission
    Properties: 
      FunctionName: !Ref RestoreWorkerFunction
      Action: "lambda:InvokeFunction"
      Principal: "events.amazonaws.com"
      SourceArn: !GetAtt RestoreObjectRule.Arn
  RestoreWorkerEvents:
    Type: AWS::Lambda::EventSourceMapping
    Properties:
      BatchSize: 1
      EventSourceArn: !GetAtt RestoreQueue.Arn
      FunctionName: !GetAtt RestoreWorkerFunction.Arn
  WDBRLambdaRole: 
    Type: "AWS::IAM::Role"
    Properties: 
      RoleName: "WDBRLambdaRole"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - 
            Effect: "Allow"
            Principal: 
              Service: 
                - "lambda.amazonaws.com"
            Action: 
              - "sts:AssumeRole"
      Path: "/"
  WDBRLambdaPolicy: 
    Type: "AWS::IAM::Policy"
    Properties: 
      PolicyName: "WDBRLambdaPolicy"
      PolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: "Allow"
            Action: 
              - "sqs:DeleteMessage"
              - "sqs:ReceiveMessage"
              - "sqs:ListQueues"
              - "sqs:ChangeMessageVisibility"
              - "sqs:SendMessage"
              - "sqs:GetQueueAttributes"
            Resource: !GetAtt BackupQueue.Arn
          - Effect: "Allow"
            Action: 
              - "sqs:DeleteMessage"
              - "sqs:ReceiveMessage"
              - "sqs:ListQueues"
              - "sqs:ChangeMessageVisibility"
              - "sqs:SendMessage"
              - "sqs:GetQueueAttributes"
            Resource: !GetAtt RestoreQueue.Arn
          - Effect: "Allow"
            Action: 
              - "sns:Publish"
            Resource: !Ref NotificationTopic
          - Effect: "Allow"
            Action: 
              - "dynamodb:DeleteItem"
              - "dynamodb:GetItem"
              - "dynamodb:BatchGetItem"
              - "dynamodb:PutItem"
              - "dynamodb:Scan"
              - "dynamodb:Query"
              - "dynamodb:GetRecords"
            Resource: 
              - !GetAtt IndexDynamoDBTable.Arn
              - !GetAtt RestoreDynamoDBTable.Arn
          - Effect: "Allow"
            Action: 
              - "dynamodb:Query"
            Resource: 
              - !Sub ${IndexDynamoDBTable.Arn}/index/RunId-PathName-index
              - !Sub ${RestoreDynamoDBTable.Arn}/index/RunId-Path-index
          - Effect: "Allow"
            Action:
              - "s3:ListBucket"
            Resource: !GetAtt S3ArchiveBucket.Arn
          - Effect: "Allow"
            Action:
              - "s3:*Object"
            Resource: !Sub ${S3ArchiveBucket.Arn}/*
          - Effect: "Allow"
            Action:
              - "workdocs:GetDocumentPath"
              - "workdocs:UpdateFolder"
              - "workdocs:RegisterDirectory"
              - "workdocs:DownloadDocumentVersion"
              - "workdocs:DeleteFolderContents"
              - "workdocs:DescribeRootFolders"
              - "workdocs:UpdateDocument"
              - "workdocs:UpdateInstanceAlias"
              - "workdocs:CreateFolder"
              - "workdocs:GetFolder"
              - "workdocs:InitiateDocumentVersionUpload"
              - "workdocs:DescribeDocumentVersions"
              - "workdocs:DescribeFolderContents"
              - "workdocs:AbortDocumentVersionUpload"
              - "workdocs:GetDocumentVersion"
              - "workdocs:DeleteFolder"
              - "workdocs:DescribeUsers"
              - "workdocs:GetDocument"
              - "workdocs:DeleteDocument"
              - "workdocs:UpdateDocumentVersion"
              - "workdocs:GetFolderPath"
            Resource: "*"
      Roles: 
        - Ref: "WDBRLambdaRole"
