AWSTemplateFormatVersion: "2010-09-09"
Description: >-
  GitHub Backup to Your S3 - storage in YOUR account. Creates (or reuses) an S3 bucket and a role our service can
  assume with your External ID. The role can only read/write/delete objects under the backup prefix of this one bucket.

Parameters:
  ExternalId:
    Type: String
    MinLength: 8
    Description: External ID shown on your Storage page.
  TrustedAccountId:
    Type: String
    AllowedPattern: "[0-9]{12}"
    Description: AWS account of the backup service (pre-filled).
  ExistingBucketName:
    Type: String
    Default: ""
    Description: Leave empty to create a new encrypted, private bucket. Or give the name of a bucket you already have (same region).
  Prefix:
    Type: String
    Default: devops-backup
    AllowedPattern: "[a-zA-Z0-9!_.*'()/-]+"
    Description: Folder inside the bucket for backups.
  KmsKeyArn:
    Type: String
    Default: ""
    Description: Optional customer-managed KMS key for encryption. Empty = S3-managed keys (SSE-S3).

Conditions:
  NewBucket: !Equals [!Ref ExistingBucketName, ""]
  UseKms: !Not [!Equals [!Ref KmsKeyArn, ""]]

Resources:
  BackupBucket:
    Type: AWS::S3::Bucket
    Condition: NewBucket
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: !If [UseKms, "aws:kms", "AES256"]
              KMSMasterKeyID: !If [UseKms, !Ref KmsKeyArn, !Ref "AWS::NoValue"]
            BucketKeyEnabled: !If [UseKms, true, !Ref "AWS::NoValue"]
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      OwnershipControls:
        Rules: [{ObjectOwnership: BucketOwnerEnforced}]
      LifecycleConfiguration:
        Rules:
          - Id: abort-incomplete-uploads
            Status: Enabled
            AbortIncompleteMultipartUpload: {DaysAfterInitiation: 7}

  BackupRole:
    Type: AWS::IAM::Role
    Properties:
      Description: GitHub Backup to Your S3 - write backups to one bucket prefix
      MaxSessionDuration: 3600
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal: {AWS: !Sub "arn:${AWS::Partition}:iam::${TrustedAccountId}:root"}
            Action: sts:AssumeRole
            Condition: {StringEquals: {"sts:ExternalId": !Ref ExternalId}}
      Policies:
        - PolicyName: backup-prefix-only
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: [s3:PutObject, s3:GetObject, s3:DeleteObject, s3:AbortMultipartUpload]
                Resource: !Sub
                  - "arn:${AWS::Partition}:s3:::${B}/${Prefix}/*"
                  - B: !If [NewBucket, !Ref BackupBucket, !Ref ExistingBucketName]
              - Effect: Allow
                Action: s3:ListBucket
                Resource: !Sub
                  - "arn:${AWS::Partition}:s3:::${B}"
                  - B: !If [NewBucket, !Ref BackupBucket, !Ref ExistingBucketName]
                Condition: {StringLike: {"s3:prefix": [!Sub "${Prefix}/*", !Ref Prefix]}}
              - !If
                - UseKms
                - Effect: Allow
                  Action: [kms:Encrypt, kms:Decrypt, kms:GenerateDataKey]
                  Resource: !Ref KmsKeyArn
                - !Ref "AWS::NoValue"

Outputs:
  RoleArn:
    Description: Paste into the Storage page.
    Value: !GetAtt BackupRole.Arn
  BucketName:
    Description: Paste into the Storage page.
    Value: !If [NewBucket, !Ref BackupBucket, !Ref ExistingBucketName]
  Region:
    Value: !Ref "AWS::Region"
