data "aws_caller_identity" "current" {}

data "aws_iam_role" "internal_read_roles" {
  count = length(var.internal_read_roles)
  name  = var.internal_read_roles[count.index]
}

data "aws_iam_role" "internal_upload_roles" {
  count = length(var.internal_upload_roles)
  name  = var.internal_upload_roles[count.index]
}

data "aws_iam_role" "internal_delete_roles" {
  count = length(var.internal_delete_roles)
  name  = var.internal_delete_roles[count.index]
}

data "aws_iam_user" "admins" {
  count     = length(var.admin_users)
  user_name = var.admin_users[count.index]
}

data "aws_iam_user" "debug_users" {
  count     = length(var.debug_users)
  user_name = var.debug_users[count.index]
}

data "aws_iam_role" "admin_roles" {
  count = length(var.admin_roles)
  name  = var.admin_roles[count.index]
}

locals {
  root_arn                  = "arn:aws:iam::${local.account_id}:root"
  admin_roles_ids           = formatlist("%s:*", data.aws_iam_role.admin_roles[*].unique_id)
  internal_read_roles_ids   = formatlist("%s:*", data.aws_iam_role.internal_read_roles[*].unique_id)
  internal_upload_roles_ids = formatlist("%s:*", data.aws_iam_role.internal_upload_roles[*].unique_id)
  internal_delete_roles_ids = formatlist("%s:*", data.aws_iam_role.internal_delete_roles[*].unique_id)
  external_read_enabled     = length(var.external_read_roles_arn) == 0 ? [] : ["Allow external read IAM roles"]
  external_upload_enabled   = length(var.external_upload_roles_arn) == 0 ? [] : ["Allow external upload IAM roles"]
  external_delete_enabled   = length(var.external_delete_roles_arn) == 0 ? [] : ["Allow external upload IAM roles"]
  external_ip_enabled       = length(var.external_read_ip_list) == 0 ? [] : ["Allow external ip statement"]
  bucket_arn                = "arn:aws:s3:::${var.bucket}"
  account_id                = data.aws_caller_identity.current.account_id
}

resource "aws_s3_bucket_policy" "main" {
  bucket = var.bucket
  policy = data.aws_iam_policy_document.main.json
}

data "aws_iam_policy_document" "main" {
  version                 = "2012-10-17"
  policy_id               = "Resource policy for ${var.bucket}"
  source_policy_documents = var.encryption_in_transit ? [module.default_bucket_policy.document.json] : null

  statement {
    sid    = "Deny administrative operations"
    effect = "Deny"
    actions = sort([
      "s3:PutBucketNotification",
      "s3:PutBucketPublicAccessBlock",
      "s3:PutAnalyticsConfiguration",
      "s3:PutAccelerateConfiguration",
      "s3:PutMetricsConfiguration",
      "s3:PutReplicationConfiguration",
      "s3:PutBucketCORS",
      "s3:DeleteBucketPolicy",
      "s3:PutInventoryConfiguration",
      "s3:PutEncryptionConfiguration",
      "s3:ObjectOwnerOverrideToBucketOwner",
      "s3:DeleteBucketWebsite",
      "s3:PutBucketLogging",
      "s3:PutLifecycleConfiguration",
      "s3:PutBucketAcl",
      "s3:PutBucketPolicy",
      "s3:PutBucketObjectLockConfiguration",
      "s3:DeleteBucket"
    ])
    resources = sort([
      "${local.bucket_arn}/*",
      local.bucket_arn
    ])
    principals {
      type        = "AWS"
      identifiers = ["*"]
    }
    condition {
      test     = "StringNotLike"
      variable = "aws:UserId"
      values = sort(concat(
        data.aws_iam_user.admins[*].user_id,
        local.admin_roles_ids,
        [local.account_id]
      ))
    }
  }

  statement {
    sid     = "Deny delete object operation"
    effect  = "Deny"
    actions = ["s3:DeleteObject*"]
    resources = sort([
      "${local.bucket_arn}/*",
      local.bucket_arn
    ])
    principals {
      type        = "AWS"
      identifiers = ["*"]
    }
    condition {
      test     = "StringNotLike"
      variable = "aws:UserId"
      values = sort(concat(
        data.aws_iam_user.admins[*].user_id,
        data.aws_iam_user.debug_users[*].user_id,
        local.admin_roles_ids,
        local.internal_delete_roles_ids,
        var.external_delete_roles_ids,
        [local.account_id]
      ))
    }
  }

  statement {
    sid    = "Deny upload and tagging operations"
    effect = "Deny"
    actions = [
      "s3:PutObject*"
    ]
    resources = sort([
      "${local.bucket_arn}/*",
      local.bucket_arn
    ])
    principals {
      type        = "AWS"
      identifiers = ["*"]
    }
    condition {
      test     = "StringNotLike"
      variable = "aws:UserId"
      values = sort(concat(
        data.aws_iam_user.admins[*].user_id,
        local.admin_roles_ids,
        data.aws_iam_user.debug_users[*].user_id,
        local.internal_upload_roles_ids,
        var.external_upload_roles_ids,
        [local.account_id]
      ))
    }
  }

  statement {
    sid    = "Deny read operations"
    effect = "Deny"
    actions = [
      "s3:GetObject*"
    ]
    resources = sort([
      "${local.bucket_arn}/*",
      local.bucket_arn
    ])
    principals {
      type        = "AWS"
      identifiers = ["*"]
    }
    condition {
      test     = "StringNotLike"
      variable = "aws:UserId"
      values = sort(concat(
        data.aws_iam_user.admins[*].user_id,
        local.admin_roles_ids,
        data.aws_iam_user.debug_users[*].user_id,
        local.internal_read_roles_ids,
        var.external_read_roles_ids,
        [local.account_id]
      ))
    }
  }

  dynamic "statement" {
    for_each = var.denied_paths_for_roles
    content {
      effect    = "Deny"
      actions   = ["s3:*"]
      resources = sort(formatlist("${local.bucket_arn}/%s", statement.value.paths))
      principals {
        type        = "AWS"
        identifiers = sort(formatlist("arn:aws:iam::${local.account_id}:role/%s", statement.value.roles))
      }
    }
  }

  dynamic "statement" {
    for_each = var.denied_paths_for_external_roles
    content {
      effect    = "Deny"
      actions   = ["s3:*"]
      resources = sort(formatlist("${local.bucket_arn}/%s", statement.value.paths))
      principals {
        type        = "AWS"
        identifiers = sort(statement.value.role_arns)
      }
    }
  }

  dynamic "statement" {
    for_each = local.external_delete_enabled
    content {
      sid    = "Explicit allow for external delete"
      effect = "Allow"
      actions = [
        "s3:DeleteObject",
        "s3:DeleteObjectTagging",
        "s3:DeleteObjectVersion",
        "s3:DeleteObjectVersionTagging"
      ]
      resources = sort([
        "${local.bucket_arn}/*",
        local.bucket_arn
      ])
      principals {
        type        = "AWS"
        identifiers = sort(var.external_delete_roles_arn)
      }
    }
  }

  dynamic "statement" {
    for_each = local.external_read_enabled
    content {
      sid    = "Explicit allow for external read"
      effect = "Allow"
      actions = [
        "s3:GetObject",
        "s3:ListBucket",
        "s3:GetObjectTagging",
        "s3:GetObjectVersion",
        "s3:GetObjectVersionTagging"
      ]
      resources = sort([
        "${local.bucket_arn}/*",
        local.bucket_arn
      ])
      principals {
        type        = "AWS"
        identifiers = sort(var.external_read_roles_arn)
      }
    }
  }

  dynamic "statement" {
    for_each = local.external_upload_enabled
    content {
      sid    = "Explicit allow list uploads and put objects and version tagging"
      effect = "Allow"
      actions = sort([
        "s3:ListMultipartUploadParts",
        "s3:AbortMultipartUpload",
        "s3:ListBucketMultipartUploads",
        "s3:PutObjectVersionTagging",
        "s3:PutObjectTagging",
        "s3:PutObject",
        "s3:PutObjectAcl",
      ])
      resources = sort([
        "${local.bucket_arn}/*",
        local.bucket_arn
      ])
      principals {
        type        = "AWS"
        identifiers = sort(var.external_upload_roles_arn)
      }
    }
  }

  # list permissions for IP addresses
  dynamic "statement" {
    for_each = local.external_ip_enabled
    content {
      sid    = "Explicit allow ListBucket from IP list"
      effect = "Allow"
      actions = [
        "s3:ListBucket",
      ]
      resources = [
        local.bucket_arn
      ]
      principals {
        type        = "AWS"
        identifiers = ["*"]
      }
      condition {
        test     = "IpAddress"
        variable = "aws:SourceIp"
        values   = sort(var.external_read_ip_list[*].ipAddress)
      }
    }
  }

  # Read permissions for IP addresses
  dynamic "statement" {
    for_each = var.external_read_ip_list
    content {
      sid    = "Explicit allow for read for ${statement.value.ipAddress} to paths"
      effect = "Allow"
      actions = [
        "s3:GetObject",
        "s3:GetObjectTagging",
        "s3:GetObjectVersion",
        "s3:GetObjectVersionTagging"
      ]
      resources = sort(formatlist("${local.bucket_arn}/%s*", statement.value.paths))
      principals {
        type        = "AWS"
        identifiers = ["*"]
      }
      condition {
        test     = "IpAddress"
        variable = "aws:SourceIp"
        values   = [statement.value.ipAddress]
      }
    }
  }
}

module "default_bucket_policy" {
  source              = "../default_policy_template"
  template_bucket_arn = local.bucket_arn
}
