// Copyright 2022 Amazon.com and its affiliates; all rights reserved.
// SPDX-License-Identifier: MIT No Attribution

terraform {
  required_providers {
    aws = {
      source                = "hashicorp/aws"
      configuration_aliases = [aws.primary, aws.secondary]
    }
  }
}

data "aws_caller_identity" "current" {}

locals {
  primary_bucket_name   = "${var.RESOURCE_PREFIX}-${var.BUCKET_NAME_PRIMARY_REGION}"
  secondary_bucket_name = "${var.RESOURCE_PREFIX}-${var.BUCKET_NAME_SECONDARY_REGION}"
}

data "aws_region" "primary" {

  provider = aws.primary
}

data "aws_region" "secondary" {

  provider = aws.secondary
}

# Create the S3 replication role trust policy

data "aws_iam_policy_document" "s3_assume_role_policy" {

  statement {
    actions = ["sts:AssumeRole"]
    effect  = "Allow"
    principals {
      type        = "Service"
      identifiers = ["s3.amazonaws.com"]
    }
  }
}

# Create the primary to secondary S3 replication IAM permissions policy

resource "aws_iam_policy" "s3_crr_primary_to_secondary-policy" {

  provider = aws.primary

  name = "${var.RESOURCE_PREFIX}-s3-crr-primary-to-secondary-policy"
  path = "/service-role/"
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "s3:ListBucket",
          "s3:GetReplicationConfiguration",
          "s3:GetObjectVersionForReplication",
          "s3:GetObjectVersionAcl",
          "s3:GetObjectVersionTagging",
          "s3:GetObjectRetention",
          "s3:GetObjectLegalHold",
        ]
        Effect = "Allow"
        Resource = [
          "arn:aws:s3:::${local.primary_bucket_name}",
          "arn:aws:s3:::${local.primary_bucket_name}/*",
        ]
      },
      {
        Action = [
          "s3:ReplicateObject",
          "s3:ReplicateDelete",
          "s3:ReplicateTags",
          "s3:GetObjectVersionTagging",
          "s3:ObjectOwnerOverrideToBucketOwner",
        ]
        Condition = {
          StringLikeIfExists = {
            "s3:x-amz-server-side-encryption" = [
              "aws:kms",
              "AES256",
            ]
          }
        }
        Effect = "Allow"
        Resource = [
          "arn:aws:s3:::${local.secondary_bucket_name}/*",
        ]
      },
      {
        Action = [
          "kms:Decrypt",
        ]
        Condition = {
          StringLike = {
            "kms:EncryptionContext:aws:s3:arn" = [
              "arn:aws:s3:::${local.primary_bucket_name}/*",
            ]
            "kms:ViaService" = "s3.${data.aws_region.primary.name}.amazonaws.com"
          }
        }
        Effect = "Allow"
        Resource = [
          var.PRIMARY_CMK_ARN,
        ]
      },
      {
        Action = [
          "kms:Encrypt",
        ]
        Condition = {
          StringLike = {
            "kms:EncryptionContext:aws:s3:arn" = [
              "arn:aws:s3:::${local.secondary_bucket_name}/*",
            ]
            "kms:ViaService" = [
              "s3.${data.aws_region.secondary.name}.amazonaws.com",
            ]
          }
        }
        Effect = "Allow"
        Resource = [
          var.SECONDARY_CMK_ARN,
        ]
      },
    ]
  })
}

# Create the secondary to primary S3 replication IAM permissions policy

resource "aws_iam_policy" "s3_crr_secondary_to_primary_policy" {

  provider = aws.secondary

  name = "${var.RESOURCE_PREFIX}-s3-crr-secondary-to-primary-policy"
  path = "/service-role/"
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "s3:ListBucket",
          "s3:GetReplicationConfiguration",
          "s3:GetObjectVersionForReplication",
          "s3:GetObjectVersionAcl",
          "s3:GetObjectVersionTagging",
          "s3:GetObjectRetention",
          "s3:GetObjectLegalHold",
        ]
        Effect = "Allow"
        Resource = [
          "arn:aws:s3:::${local.secondary_bucket_name}",
          "arn:aws:s3:::${local.secondary_bucket_name}/*",
        ]
      },
      {
        Action = [
          "s3:ReplicateObject",
          "s3:ReplicateDelete",
          "s3:ReplicateTags",
          "s3:GetObjectVersionTagging",
          "s3:ObjectOwnerOverrideToBucketOwner",
        ]
        Condition = {
          StringLikeIfExists = {
            "s3:x-amz-server-side-encryption" = [
              "aws:kms",
              "AES256",
            ]
          }
        }
        Effect = "Allow"
        Resource = [
          "arn:aws:s3:::${local.primary_bucket_name}/*",
        ]
      },
      {
        Action = [
          "kms:Decrypt",
        ]
        Condition = {
          StringLike = {
            "kms:EncryptionContext:aws:s3:arn" = [
              "arn:aws:s3:::${local.secondary_bucket_name}/*",
            ]
            "kms:ViaService" = "s3.${data.aws_region.secondary.name}.amazonaws.com"
          }
        }
        Effect = "Allow"
        Resource = [
          var.SECONDARY_CMK_ARN,
        ]
      },
      {
        Action = [
          "kms:Encrypt",
        ]
        Condition = {
          StringLike = {
            "kms:EncryptionContext:aws:s3:arn" = [
              "arn:aws:s3:::${local.primary_bucket_name}/*",
            ]
            "kms:ViaService" = [
              "s3.${data.aws_region.primary.name}.amazonaws.com",
            ]
          }
        }
        Effect = "Allow"
        Resource = [
          var.PRIMARY_CMK_ARN,
        ]
      },
    ]
  })
}

# Create primary to secondary replication S3 role

resource "aws_iam_role" "primary_s3_crr_role" {

  provider = aws.primary

  name               = "${var.RESOURCE_PREFIX}-S3PrimaryCrrKmsRole"
  assume_role_policy = data.aws_iam_policy_document.s3_assume_role_policy.json
}

# Create secondary to primary replication S3 role

resource "aws_iam_role" "secondary_s3_crr_role" {

  provider = aws.secondary

  name               = "${var.RESOURCE_PREFIX}-S3SecondaryCrrKmsRole"
  assume_role_policy = data.aws_iam_policy_document.s3_assume_role_policy.json
}

# Assign policy to primary IAM role

resource "aws_iam_role_policy_attachment" "primary_iam_role" {

  provider = aws.primary

  policy_arn = aws_iam_policy.s3_crr_primary_to_secondary-policy.arn
  role       = aws_iam_role.primary_s3_crr_role.name
}

# Assign policy To secondary IAM role

resource "aws_iam_role_policy_attachment" "secondary_iam_role" {

  provider = aws.secondary

  policy_arn = aws_iam_policy.s3_crr_secondary_to_primary_policy.arn
  role       = aws_iam_role.secondary_s3_crr_role.name
}

# Create the primary bucket

resource "aws_s3_bucket" "primary" {

  provider = aws.primary

  bucket = local.primary_bucket_name
}

# Create the secondary bucket

resource "aws_s3_bucket" "secondary" {

  provider = aws.secondary

  bucket = local.secondary_bucket_name
}

# Block public access for primary bucket

resource "aws_s3_bucket_public_access_block" "public_access_block_primary" {

  provider = aws.primary

  bucket = aws_s3_bucket.primary.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# Block public access for secondary bucket

resource "aws_s3_bucket_public_access_block" "public_access_block_secondary" {

  provider = aws.secondary

  bucket = aws_s3_bucket.secondary.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# Enable primary bucket versioning

resource "aws_s3_bucket_versioning" "primary" {

  provider = aws.primary

  bucket = aws_s3_bucket.primary.id
  versioning_configuration {
    status = "Enabled"
  }
}

# Enable secondary bucket versioning

resource "aws_s3_bucket_versioning" "secondary" {

  provider = aws.secondary

  bucket = aws_s3_bucket.secondary.id
  versioning_configuration {
    status = "Enabled"
  }
}

# Enable default encryption for primary bucket

resource "aws_s3_bucket_server_side_encryption_configuration" "primary" {

  provider = aws.primary

  bucket = aws_s3_bucket.primary.bucket

  rule {
    bucket_key_enabled = false
    apply_server_side_encryption_by_default {
      kms_master_key_id = var.PRIMARY_CMK_ARN
      sse_algorithm     = "aws:kms"
    }
  }
}

# Enable default encryption for secondary bucket

resource "aws_s3_bucket_server_side_encryption_configuration" "secondary" {

  provider = aws.secondary

  bucket = aws_s3_bucket.secondary.bucket

  rule {
    bucket_key_enabled = false
    apply_server_side_encryption_by_default {
      kms_master_key_id = var.SECONDARY_CMK_ARN
      sse_algorithm     = "aws:kms"
    }
  }
}

# Configure primary to secondary cross region replication

resource "aws_s3_bucket_replication_configuration" "primary_to_secondary_repl" {

  provider = aws.primary

  role   = aws_iam_role.primary_s3_crr_role.arn
  bucket = aws_s3_bucket.primary.id

  rule {
    id = "AllToSecondary"

    status = "Enabled"

    destination {
      bucket = aws_s3_bucket.secondary.arn
      encryption_configuration {
        replica_kms_key_id = var.SECONDARY_CMK_ARN
      }
    }

    source_selection_criteria {

      sse_kms_encrypted_objects {
        status = "Enabled"
      }
    }
  }

  # Must have bucket versioning enabled first
  depends_on = [
    aws_s3_bucket_versioning.primary,
    aws_s3_bucket_versioning.secondary
  ]
}

# Configure secondary to primary cross region replication

resource "aws_s3_bucket_replication_configuration" "secondary_to_primary_repl" {

  provider = aws.secondary

  role   = aws_iam_role.secondary_s3_crr_role.arn
  bucket = aws_s3_bucket.secondary.id

  rule {
    id = "AllToPrimary"

    status = "Enabled"

    destination {
      bucket = aws_s3_bucket.primary.arn
      encryption_configuration {
        replica_kms_key_id = var.PRIMARY_CMK_ARN
      }
    }

    source_selection_criteria {

      sse_kms_encrypted_objects {
        status = "Enabled"
      }
    }
  }

  # Must have bucket versioning enabled first
  depends_on = [
    aws_s3_bucket_versioning.primary,
    aws_s3_bucket_versioning.secondary
  ]
}

# Create primary log bucket

resource "aws_s3_bucket" "log_bucket_primary" {

  provider = aws.primary

  bucket = "${local.primary_bucket_name}-log"

  #checkov:skip=CKV_AWS_144: "Ensure that S3 bucket has cross-region replication enabled"
}

# Create secondary log bucket

resource "aws_s3_bucket" "log_bucket_secondary" {

  provider = aws.secondary

  bucket = "${local.secondary_bucket_name}-log"

  #checkov:skip=CKV_AWS_144: "Ensure that S3 bucket has cross-region replication enabled"
}

# Create a bucket policy for primary log bucket

resource "aws_s3_bucket_policy" "log_bucket_primary" {

  provider = aws.primary

  bucket = aws_s3_bucket.log_bucket_primary.id
  policy = data.aws_iam_policy_document.log_bucket_primary.json
}

data "aws_iam_policy_document" "log_bucket_primary" {
  statement {
    principals {
      type        = "Service"
      identifiers = ["logging.s3.amazonaws.com"]
    }
    actions = [
      "s3:PutObject",
    ]
    resources = [
      "${aws_s3_bucket.log_bucket_primary.arn}/*",
    ]
    condition {
      test     = "StringEquals"
      variable = "aws:SourceAccount"

      values = [
        data.aws_caller_identity.current.account_id
      ]
    }
    condition {
      test     = "ArnLike"
      variable = "aws:SourceArn"

      values = [
        aws_s3_bucket.primary.arn
      ]
    }
  }
}

# Create a bucket policy for secondary log bucket

resource "aws_s3_bucket_policy" "log_bucket_secondary" {

  provider = aws.secondary

  bucket = aws_s3_bucket.log_bucket_secondary.id
  policy = data.aws_iam_policy_document.log_bucket_secondary.json
}

data "aws_iam_policy_document" "log_bucket_secondary" {
  statement {
    principals {
      type        = "Service"
      identifiers = ["logging.s3.amazonaws.com"]
    }
    actions = [
      "s3:PutObject",
    ]
    resources = [
      "${aws_s3_bucket.log_bucket_secondary.arn}/*",
    ]
    condition {
      test     = "StringEquals"
      variable = "aws:SourceAccount"

      values = [
        data.aws_caller_identity.current.account_id
      ]
    }
    condition {
      test     = "ArnLike"
      variable = "aws:SourceArn"

      values = [
        aws_s3_bucket.secondary.arn
      ]
    }
  }
}

# Block public access for primary log bucket

resource "aws_s3_bucket_public_access_block" "public_access_block_log_primary" {

  provider = aws.primary

  bucket = aws_s3_bucket.log_bucket_primary.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# Block public access for secondary log bucket

resource "aws_s3_bucket_public_access_block" "public_access_block_log_secondary" {

  provider = aws.secondary

  bucket = aws_s3_bucket.log_bucket_secondary.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# Create bucket logging for primary bucket

resource "aws_s3_bucket_logging" "log_bucket_logging_primary" {

  provider = aws.primary

  bucket = aws_s3_bucket.primary.id

  target_bucket = aws_s3_bucket.log_bucket_primary.id
  target_prefix = "log/"
}

# Create bucket logging for secondary bucket

resource "aws_s3_bucket_logging" "log_bucket_logging_secondary" {

  provider = aws.secondary

  bucket = aws_s3_bucket.secondary.id

  target_bucket = aws_s3_bucket.log_bucket_secondary.id
  target_prefix = "log/"
}

# Enable default encryption for primary log bucket

resource "aws_s3_bucket_server_side_encryption_configuration" "log_bucket_primary" {

  provider = aws.primary

  bucket = aws_s3_bucket.log_bucket_primary.bucket

  # Log buckets cannot use KMS with CMK and can only use AES-256 (SSE-S3)
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

# Enable default encryption for secondary log bucket

resource "aws_s3_bucket_server_side_encryption_configuration" "log_bucket_secondary" {

  provider = aws.secondary

  bucket = aws_s3_bucket.log_bucket_secondary.bucket

  # Log buckets cannot use KMS with CMK and can only use AES-256 (SSE-S3)
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}