data "aws_caller_identity" "current" {}

data "aws_canonical_user_id" "current" {}

data "aws_region" "current" {}

# Look up the logging bucket to ensure that it exists
data "aws_s3_bucket" "logging" {
  count  = var.access_logging_enabled ? 1 : 0
  bucket = local.logging_bucket
}

data "aws_iam_policy" "permissions_boundary_policy" {
  count = local.permissions_boundary_enabled ? 1 : 0
  name  = var.permissions_boundary_policy_name
}

module "aws_accounts" {
  source = "git@github.com:theorchard/terraform-aws-accounts-map.git//modules/read_accounts_map?ref=3.1.0"
}

locals {
  default_region_logging_bucket = [
    for name, properties in module.aws_accounts.accounts_map :
    properties["logging_bucket"] if properties["account_id"] == data.aws_caller_identity.current.account_id
  ]
  default_region = [
    for name, properties in module.aws_accounts.accounts_map :
    properties["default_region"] if properties["account_id"] == data.aws_caller_identity.current.account_id
  ]
  default_logging_bucket = (
    data.aws_region.current.name == local.default_region[0] ?
    local.default_region_logging_bucket[0] :
    "${local.default_region_logging_bucket[0]}-${data.aws_region.current.name}"
  )
  encryption_enabled = length(keys(var.apply_server_side_encryption_by_default)) > 0
  logging_bucket     = coalesce(var.custom_logging_bucket, local.default_logging_bucket)
  logging_prefix     = coalesce(var.custom_logging_prefix, "${data.aws_caller_identity.current.account_id}/${data.aws_region.current.name}/${local.bucket_name}/${local.bucket_name}-")
  config             = local.encryption_enabled ? [var.apply_server_side_encryption_by_default] : []
  bucket_name        = var.custom_bucket_name != "" ? var.custom_bucket_name : "${var.env}-${var.bucket_name}"
  all_lifecycle_rules = flatten([
    var.lifecycle_rules_abort_incomplete_multipart_upload_days,
    var.lifecycle_rules_options_current_version_expiration,
    var.lifecycle_rules_options_noncurrent_version_expiration,
    var.lifecycle_rules_options_current_version_transition,
    var.lifecycle_rules_options_noncurrent_version_transition,
    var.lifecycle_rules_expired_object_delete_markers_expiration,
  ])

  object_ownership = var.custom_object_ownership != "" ? var.custom_object_ownership : (
    length(var.acl_policy_grants) > 0 ? "BucketOwnerPreferred" : "BucketOwnerEnforced"
  )

  tags = merge(
    {
      s3-bucket-name     = local.bucket_name
      service_name       = var.bucket_name
      environment        = var.env
      application_family = var.application_family
      terraformed        = true
    },
    var.additional_tags
  )

  permissions_boundary_enabled = coalesce(var.permissions_boundary_enabled, var.env == "dev" ? true : false)
  permissions_boundary_arn     = local.permissions_boundary_enabled ? data.aws_iam_policy.permissions_boundary_policy[0].arn : null
}

# Create S3 bucket with server-side encryption enabled
resource "aws_s3_bucket" "bucket" {
  # checkov:skip=CKV_AWS_19:Server-Side Encryption is mandatory on the module level. This check fails because of the `dynamic` statement.
  # checkov:skip=CKV_AWS_145:Using KMS encryption is recommended, but AES256 is also sufficient.
  provider = aws
  bucket   = local.bucket_name

  tags = local.tags

  lifecycle {
    ignore_changes = [
      grant,
      cors_rule,
      lifecycle_rule,
      logging,
      replication_configuration,
      server_side_encryption_configuration,
      versioning
    ]
  }
}

# Create CORS configuration
resource "aws_s3_bucket_cors_configuration" "cors_configuration" {
  count = length(var.bucket_cors_rule) > 0 ? 1 : 0

  bucket = aws_s3_bucket.bucket.id

  dynamic "cors_rule" {
    for_each = var.bucket_cors_rule

    content {
      allowed_headers = cors_rule.value.allowed_headers
      allowed_methods = cors_rule.value.allowed_methods
      allowed_origins = cors_rule.value.allowed_origins
      expose_headers  = cors_rule.value.expose_headers
      max_age_seconds = cors_rule.value.max_age_seconds
    }
  }
}

# Lifecycle configuration
resource "aws_s3_bucket_lifecycle_configuration" "lifecycle_configuration" {
  # checkov:skip=CKV_AWS_300:We define incomplete uploads cleanup as a dynamic rule, which Checkov isn't able to parse.

  count  = length(local.all_lifecycle_rules) > 0 ? 1 : 0
  bucket = aws_s3_bucket.bucket.id

  dynamic "rule" {
    for_each = var.lifecycle_rules_abort_incomplete_multipart_upload_days

    content {
      id     = sha1("abort-incomplete-multipart-upload-days-${jsonencode(rule.value)}")
      status = rule.value["enabled"] ? "Enabled" : "Disabled"
      filter {
        prefix = rule.value["prefix"]
      }
      abort_incomplete_multipart_upload {
        days_after_initiation = rule.value["days"]
      }
    }
  }

  dynamic "rule" {
    for_each = var.lifecycle_rules_options_current_version_expiration

    content {
      id     = sha1("current-version-expiration-${jsonencode(rule.value)}")
      status = rule.value["enabled"] ? "Enabled" : "Disabled"
      filter {
        prefix = rule.value["prefix"]
      }
      expiration {
        days = rule.value["days"]
      }
    }
  }

  dynamic "rule" {
    for_each = var.lifecycle_rules_options_noncurrent_version_expiration

    content {
      id     = sha1("noncurrent-version-expiration-${jsonencode(rule.value)}")
      status = rule.value["enabled"] ? "Enabled" : "Disabled"
      filter {
        prefix = rule.value["prefix"]
      }
      noncurrent_version_expiration {
        noncurrent_days = rule.value["days"]
      }
    }
  }

  dynamic "rule" {
    for_each = var.lifecycle_rules_options_current_version_transition

    content {
      id     = sha1("current-version-transition-${jsonencode(rule.value)}")
      status = rule.value["enabled"] ? "Enabled" : "Disabled"
      filter {
        prefix = rule.value["prefix"]
      }
      transition {
        days          = rule.value["days"]
        storage_class = rule.value["storage_class"]
      }
    }
  }

  dynamic "rule" {
    for_each = var.lifecycle_rules_options_noncurrent_version_transition

    content {
      id     = sha1("noncurrent-version-transition-${jsonencode(rule.value)}")
      status = rule.value["enabled"] ? "Enabled" : "Disabled"
      filter {
        prefix = rule.value["prefix"]
      }
      noncurrent_version_transition {
        noncurrent_days = rule.value["days"]
        storage_class   = rule.value["storage_class"]
      }
    }
  }

  dynamic "rule" {
    for_each = var.lifecycle_rules_expired_object_delete_markers_expiration

    content {
      id     = sha1("delete-expired-object-delete-marker-${jsonencode(rule.value)}")
      status = rule.value["enabled"] ? "Enabled" : "Disabled"
      filter {
        prefix = rule.value["prefix"]
      }
      expiration {
        expired_object_delete_marker = true
      }
    }
  }
}

# Accelerate_configuration
# S3 Transfer Acceleration is not supported for buckets with periods in the name
resource "aws_s3_bucket_accelerate_configuration" "acceleration_status" {
  count  = can(regex("\\.", local.bucket_name)) ? 0 : 1
  bucket = aws_s3_bucket.bucket.id
  status = var.acceleration_status
}

# ACL
resource "aws_s3_bucket_ownership_controls" "ownership_controls" {
  # checkov:skip=CKV2_AWS_65:This is false positive https://github.com/bridgecrewio/checkov/issues/5623
  bucket = aws_s3_bucket.bucket.id

  rule {
    object_ownership = local.object_ownership
  }
}

# ACL with grants
resource "aws_s3_bucket_acl" "acl_with_grant" {
  count  = length(var.acl_policy_grants) > 0 ? 1 : 0
  bucket = aws_s3_bucket.bucket.id
  access_control_policy {
    dynamic "grant" {
      for_each = var.acl_policy_grants
      content {
        permission = grant.value["permission"]
        grantee {
          id   = grant.value["id"]
          type = grant.value["type"]
        }
      }
    }
    owner {
      id = data.aws_canonical_user_id.current.id
    }
  }

  depends_on = [
    aws_s3_bucket_ownership_controls.ownership_controls
  ]
}

# Bucket versioning
resource "aws_s3_bucket_versioning" "versioning" {
  bucket = aws_s3_bucket.bucket.id
  versioning_configuration {
    status = "Enabled"
  }
}

# Bucket logging
resource "aws_s3_bucket_logging" "logging" {
  count         = var.access_logging_enabled ? 1 : 0
  bucket        = aws_s3_bucket.bucket.id
  target_bucket = data.aws_s3_bucket.logging[0].bucket
  target_prefix = local.logging_prefix
}

# Blocks all public access to the S3 bucket
resource "aws_s3_bucket_public_access_block" "public_access_block" {
  count  = var.block_public_access ? 1 : 0
  bucket = aws_s3_bucket.bucket.id

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

  depends_on = [aws_s3_bucket.bucket]
}

resource "aws_s3_bucket_server_side_encryption_configuration" "server_side_encryption" {
  bucket = aws_s3_bucket.bucket.id
  dynamic "rule" {
    for_each = local.config
    iterator = encryption

    content {
      apply_server_side_encryption_by_default {
        kms_master_key_id = length(aws_kms_key.kms_key) > 0 ? aws_kms_key.kms_key[0].arn : null
        sse_algorithm = lookup(encryption.value, "sse_algorithm",
        lookup(encryption.value, "kms_master_key_id", null) == null ? "AES256" : "aws:kms")
      }
      bucket_key_enabled = var.bucket_key_enabled
    }
  }
}

# Data resource for read-only policy to the S3 bucket
data "aws_iam_policy_document" "s3_read_only_policy" {
  statement {
    actions = [
      "s3:GetBucketLocation",
      "s3:GetObject",
      "s3:ListBucket",
    ]

    resources = [
      "${aws_s3_bucket.bucket.arn}/*",
    ]
  }

  statement {
    actions = [
      "s3:ListBucket",
    ]

    resources = [
      aws_s3_bucket.bucket.arn
    ]
  }

  statement {
    actions = [
      "s3:GetBucketLocation",
      "s3:ListAllMyBuckets",
    ]

    resources = [
      "*",
    ]
  }
}

# Creates policy for read-only access to the S3 bucket
resource "aws_iam_policy" "s3_read_only_policy" {
  count  = var.s3_read_only_policy ? 1 : 0
  name   = var.s3_custom_read_only_policy_name != "" ? var.s3_custom_read_only_policy_name : "S3-${var.env}-${var.bucket_name}-read-only"
  policy = data.aws_iam_policy_document.s3_read_only_policy.json
}

# Bucket resource policy.
resource "aws_s3_bucket_policy" "s3_bucket_resource_policy" {
  bucket = aws_s3_bucket.bucket.id
  policy = data.aws_iam_policy_document.s3_bucket_resource_policy.json
}

data "aws_iam_policy_document" "s3_bucket_resource_policy" {
  override_policy_documents = var.bucket_policy_overrides

  statement {
    sid    = "DenyRequestsWithoutSSL"
    effect = "Deny"
    principals {
      type        = "*"
      identifiers = ["*"]
    }
    actions = ["s3:*"]
    resources = [
      aws_s3_bucket.bucket.arn,
      "${aws_s3_bucket.bucket.arn}/*",
    ]
    condition {
      test     = "Bool"
      variable = "aws:SecureTransport"
      values   = ["false"]
    }
  }

  dynamic "statement" {
    for_each = var.enable_guardduty_malware_protection ? [true] : []
    content {
      sid    = "DenyGetObjectIfMalwareFound"
      effect = "Deny"
      principals {
        type        = "*"
        identifiers = ["*"]
      }
      actions   = ["s3:GetObject"]
      resources = ["${aws_s3_bucket.bucket.arn}/*"]
      condition {
        test     = "StringEquals"
        variable = "s3:ExistingObjectTag/GuardDutyMalwareScanStatus"
        values   = ["THREATS_FOUND"]
      }
      condition {
        test     = "StringNotLike"
        variable = "aws:PrincipalArn"
        values   = ["arn:aws:iam::086679231553:role/guardduty-malware-response-role-shared"]
      }
    }
  }

  dynamic "statement" {
    for_each = var.enable_guardduty_malware_protection ? [true] : []
    content {
      sid    = "AllowMalwareRemediationRole"
      effect = "Allow"
      principals {
        type        = "AWS"
        identifiers = ["arn:aws:iam::086679231553:role/guardduty-malware-response-role-shared"]
      }
      actions = [
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:GetObjectTagging"
      ]
      resources = ["${aws_s3_bucket.bucket.arn}/*"]
    }
  }

  dynamic "statement" {
    for_each = var.enable_guardduty_malware_protection ? [true] : []
    content {
      sid    = "AllowMalwareRemediationRoleList"
      effect = "Allow"
      principals {
        type        = "AWS"
        identifiers = ["arn:aws:iam::086679231553:role/guardduty-malware-response-role-shared"]
      }
      actions   = ["s3:ListBucket"]
      resources = ["${aws_s3_bucket.bucket.arn}"]
    }
  }

  dynamic "statement" {
    for_each = var.kms_key_required ? [true] : []

    content {
      sid    = "DenyIncorrectEncryptionHeader"
      effect = "Deny"
      principals {
        type        = "*"
        identifiers = ["*"]
      }
      actions   = ["s3:PutObject"]
      resources = ["${aws_s3_bucket.bucket.arn}/*"]
      condition {
        test     = "StringNotEquals"
        variable = "s3:x-amz-server-side-encryption"
        values   = ["aws:kms"]
      }
    }
  }

  dynamic "statement" {
    for_each = var.kms_key_required ? [true] : []

    content {
      sid    = "RequireKMSEncryption"
      effect = "Deny"
      principals {
        type        = "*"
        identifiers = ["*"]
      }
      actions   = ["s3:PutObject"]
      resources = ["${aws_s3_bucket.bucket.arn}/*"]
      condition {
        test     = "StringNotLikeIfExists"
        variable = "s3:x-amz-server-side-encryption-aws-kms-key-id"
        values   = [aws_kms_key.kms_key[0].arn]
      }
    }
  }

  # Replication Destination policy
  dynamic "statement" {
    for_each = var.apply_replication_destination_configuration
    content {
      effect = "Allow"
      actions = [
        "s3:ReplicateObject",
        "s3:ReplicateDelete",
        "s3:ReplicateTags",
        "s3:PutBucketVersioning",
        "s3:ObjectOwnerOverrideToBucketOwner",
        "s3:List*",
        "s3:GetBucketVersioning"
      ]
      resources = [
        resource.aws_s3_bucket.bucket.arn,
        "${resource.aws_s3_bucket.bucket.arn}/*",
      ]
      principals {
        type = "AWS"
        identifiers = [
          statement.value.replication_source_iam_role_arn
        ]
      }
    }
  }

  dynamic "statement" {
    for_each = var.replication_helper_allowed ? [true] : []
    content {
      effect = "Allow"
      actions = [
        "s3:PutObject"
      ]
      resources = [
        "${aws_s3_bucket.bucket.arn}/*",
      ]
      principals {
        type = "AWS"
        identifiers = [
          var.replication_helper_arn
        ]
      }
    }
  }
}

resource "aws_s3control_storage_lens_configuration" "s3_storage_lens_dashboard" {
  count = var.storage_lens_configuration.enabled ? 1 : 0

  config_id = aws_s3_bucket.bucket.id

  storage_lens_configuration {
    enabled = true

    include {
      buckets = [aws_s3_bucket.bucket.arn]
    }

    account_level {
      activity_metrics {
        enabled = var.storage_lens_configuration.activity_metrics_enabled
      }
      advanced_cost_optimization_metrics {
        enabled = var.storage_lens_configuration.advanced_cost_optimization_metrics_enabled
      }
      advanced_data_protection_metrics {
        enabled = var.storage_lens_configuration.advanced_data_protection_metrics_enabled
      }
      detailed_status_code_metrics {
        enabled = var.storage_lens_configuration.detailed_status_code_metrics_enabled
      }

      bucket_level {
        activity_metrics {
          enabled = var.storage_lens_configuration.activity_metrics_enabled
        }
        advanced_cost_optimization_metrics {
          enabled = var.storage_lens_configuration.advanced_cost_optimization_metrics_enabled
        }
        advanced_data_protection_metrics {
          enabled = var.storage_lens_configuration.advanced_data_protection_metrics_enabled
        }
        detailed_status_code_metrics {
          enabled = var.storage_lens_configuration.detailed_status_code_metrics_enabled
        }
        prefix_level {
          storage_metrics {
            enabled = var.storage_lens_configuration.prefix_level_storage_metrics_enabled
            selection_criteria {
              delimiter                    = "/"
              max_depth                    = var.storage_lens_configuration.prefix_level_max_depth
              min_storage_bytes_percentage = var.storage_lens_configuration.prefix_level_min_storage_bytes_percentage
            }
          }
        }
      }
    }

    data_export {
      cloud_watch_metrics {
        enabled = var.storage_lens_configuration.cloudwatch_metrics_export_enabled
      }
    }
  }

  tags = local.tags
}
