provider "aws" {
  region = var.aws_region
}

# Terraform backends cannot contain interpolations
terraform {
  backend "s3" {
    bucket  = "orcd-terraform-state"
    key     = "prod/iam/shared/terraform.tfstate"
    region  = "us-east-1"
    encrypt = "true"
  }
}

# IAM group for everyone
resource "aws_iam_group" "generic_orchard_group" {
  name = "generic-orchard-${var.environment}-group"
}

# IAM group for engineers
resource "aws_iam_group" "generic_orchard_engineering_group" {
  name = "generic-orchard-${var.environment}-engineering-group"
}


resource "aws_iam_group_membership" "generic_orchard_engineering_group" {
  name  = "generic-orchard-${var.environment}-engineering-group-membership"
  group = aws_iam_group.generic_orchard_engineering_group.name
  users = [
    "aanand",
    "abaraz",
    "acikaj",
    "acortadellas",
    "adelarosa",
    "aduduskar",
    "aentwistle",
    "agraig",
    "ahakobyan",
    "ahendin",
    "ahorvath",
    "akanojiya",
    "akiss",
    "akleimonova",
    "akolomiiets",
    "akorosi",
    "akozlov",
    "akumar",
    "akurs",
    "alamech",
    "amccown",
    "amerkulov",
    "amontoya",
    "anavalu",
    "anevrekar",
    "anorton",
    "aoshomoji",
    "apavlov",
    "apred",
    "apryschepa",
    "aruhlov",
    "asampath",
    "asingh",
    "astorkey",
    "aurusov",
    "atatus",
    "avitale",
    "azekic",
    "bbabiy",
    "bburton",
    "bfleischer",
    "bhavin.shah",
    "buvarov",
    "bwilliams",
    "cajtony",
    "cbeesley",
    "cbudde",
    "chandirk",
    "ctang",
    "cvallecilla",
    "cxu",
    "dblair",
    "defanov",
    "dgale",
    "diakunchokov",
    "djadhav",
    "djimenez",
    "dkarnaukh",
    "dkaruppanasamy",
    "dklyukova",
    "dparmar",
    "dprotsyk",
    "dsalazar",
    "dslabozhanin",
    "dtyagi",
    "durukov",
    "efedorov",
    "ehenningsson",
    "emest",
    "etaranenko",
    "evlastos",
    "fasadullayev",
    "fsantos",
    "fumar",
    "gaguirre",
    "gdudich",
    "gdulai",
    "ggupta",
    "glados",
    "gmaroulis",
    "gneogrady",
    "gquintino",
    "gtheka",
    "hravariya",
    "iarroyo",
    "ibardocz",
    "iburilo",
    "ifinta",
    "ihalas",
    "imajczyk",
    "ingaku",
    "ipanteleyev",
    "ipape",
    "ipotyomkin",
    "ivladymyrov",
    "izsiros",
    "jbertran",
    "jbogoyavlenskaya",
    "jbrooks",
    "jchung",
    "jdiaz",
    "jfidlow",
    "jho",
    "jian",
    "jkiss",
    "jmarais",
    "jmlcoch",
    "jraysor",
    "jtraversa",
    "jviletto",
    "jzhang",
    "jzheng",
    "kgreenidge",
    "kjaisinghani",
    "kkavasi",
    "kshrayber",
    "kturner",
    "lnunez",
    "lseal",
    "lvona",
    "mbalan",
    "mcedaro",
    "mcesonis",
    "mchar",
    "mclark",
    "mdadhania",
    "menger",
    "mgonitzke",
    "mhirth",
    "mkhimka",
    "mklypalskyi",
    "mmoy",
    "mmuller",
    "mnikitin",
    "mpadsalgikar",
    "mpellet",
    "mrojas",
    "msolvag",
    "mstead",
    "mthomas",
    "mtrembickij",
    "mwalker",
    "mymac80",
    "nlichtenstein",
    "nlittmann",
    "nlovett",
    "npatel",
    "nsnyder",
    "nzameshaev",
    "oefanova",
    "olazurenko",
    "ooholiuk",
    "oshainoha",
    "otalaga",
    "owherry",
    "oyasinskaya",
    "oyatsuk",
    "ozhovnuvatyi",
    "pbaylas",
    "pborges",
    "pcharles",
    "pdumoulin",
    "piannone",
    "plavreshin",
    "pmaslekar",
    "pmolnar",
    "poygard",
    "prashant.t",
    "ratoui",
    "rbomberg",
    "rcarfora",
    "rgodwin",
    "rgupta",
    "rkhandeparkar",
    "rkordisch",
    "rlieberman",
    "rmayer",
    "rpapat",
    "rrahman",
    "rroy",
    "rshield",
    "rstiphout",
    "rtorres",
    "sarato",
    "sbugyi",
    "schenieryu",
    "sduberg",
    "sgiri",
    "sgolden",
    "shlee",
    "signatenko",
    "slaposhko",
    "smartin",
    "snanjans",
    "ssamarth",
    "ssavva",
    "sshinde",
    "ssudakov",
    "tallman",
    "tcalhoun",
    "tdenkinger",
    "tkenyeres",
    "tkharitonova",
    "tmartin",
    "tmartino",
    "tshagapov",
    "tsouza",
    "ttran",
    "tzseller",
    "vdutta",
    "vgollapudi",
    "vkalichonak",
    "vleung",
    "vmokal",
    "vsingh",
    "vsiranjeevi",
    "vsubramaniam",
    "vsvirskiy",
    "vtorres",
    "vzein",
    "vzhyvotkov",
    "wcheong",
    "xmeunier",
    "ydimitrov",
    "ymokliak",
    "ymuryi",
    "yostapyuk",
    "yzozulya",
    "zmarias",
  ]
}

data "aws_caller_identity" "current" {}

data "aws_s3_bucket" "buckets_to_read_access" {
  for_each = toset(keys(var.s3_buckets_to_read_access))
  bucket   = each.value
}

# Shared read policy for specific resources in this account
data "aws_iam_policy_document" "orchard_engineering_shared_rbac_read_policy" {
  statement {
    actions = [
      "s3:Get*",
    ]

    resources = [
      for bucket in data.aws_s3_bucket.buckets_to_read_access :
      format("%s/%s", bucket.arn, lookup(var.s3_buckets_to_read_access, bucket.id))
    ]
  }

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

    resources = [
      for bucket in data.aws_s3_bucket.buckets_to_read_access : bucket.arn
    ]
  }

  statement {
    actions = [
      "secretsmanager:GetSecretValue",
    ]

    resources = [
      "arn:aws:secretsmanager:*:*:secret:qa/cypress-configs/*",
      "arn:aws:secretsmanager:*:*:secret:qa/cypress-configs/",
      "arn:aws:secretsmanager:*:*:secret:qa/lambda-jwt-refresh/jwt_enabled_services*",
      "arn:aws:secretsmanager:*:*:secret:qa/lambda-jwt-refresh/jwt_token*",
    ]
  }

  statement {
    actions = [
      "pipes:ListPipes",
      "pipes:DescribePipe",
    ]

    resources = [
      "arn:aws:pipes:*:*:qa-*"
    ]
  }
  statement {
    sid = "AllowAccessMediaConvertListJobs"
    actions = [
      "mediaconvert:ListJobs",
    ]

    resources = [
      "*",
    ]
  }
}

# Shared write policy for specific resources in this account
data "aws_iam_policy_document" "orchard_engineering_shared_rbac_write_policy" {

  statement {
    actions = [
      "dynamodb:GetItem",
    ]

    resources = [
      "arn:aws:dynamodb:us-east-1:${data.aws_caller_identity.current.account_id}:table/qa-ows-request-authorization"
    ]

    condition {
      test     = "StringLike"
      variable = "dynamodb:LeadingKeys"

      values = [
        "alw:*",
      ]
    }
  }

  statement {
    actions = [
      "dynamodb:PutItem",
    ]

    resources = [
      "arn:aws:dynamodb:us-east-1:${data.aws_caller_identity.current.account_id}:table/qa-ows-request-authorization"
    ]

    condition {
      test     = "StringNotLike"
      variable = "dynamodb:LeadingKeys"

      values = [
        "alw:*",
      ]
    }
  }

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

    resources = [
      "arn:aws:s3:::elasticbeanstalk-*",
      "arn:aws:s3:::qa-orcdbucket"
    ]
  }

  statement {
    actions = [
      "s3:AbortMultipartUpload",
      "s3:DeleteObject",
      "s3:DeleteObject*",
      "s3:Get*",
      "s3:List*",
      "s3:PutObject",
      "s3:PutObject*",
      "s3:RestoreObject",
    ]

    resources = [
      "arn:aws:s3:::elasticbeanstalk-*/*",
      "arn:aws:s3:::qa-orcdbucket/*",
      "arn:aws:s3:::prod-orcd-cdn/images/*",
      "arn:aws:s3:::prod-orcd-cdn/web-icons/*",
      "arn:aws:s3:::qa-orcd-cdn/images/*",
      "arn:aws:s3:::qa-orcd-cdn/web-icons/*",
      "arn:aws:s3:::qa-orcd-cypress-tests/auth0/*"
    ]
  }
}

# Shared IAM policy for user-specific access
data "aws_iam_policy_document" "orchard_engineering_shared_iam_security_credentials_policy" {
  statement {
    actions = [
      "iam:*AccessKey*",
      "iam:*ServiceSpecificCredential*",
    ]

    resources = [
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/$${aws:username}",
    ]

  }
}

# IAM policy for everyone
data "aws_iam_policy_document" "orchard_everyone_shared_iam_access_policy" {

  statement {
    actions = [
      "iam:ChangePassword",
      "iam:GetUser",
      "iam:ListMFADevices",
      "iam:UpdateLoginProfile",
    ]

    resources = [
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/$${aws:username}",
    ]

  }

  # These actions do not support specific resources and are account-wide
  statement {
    actions = [
      "iam:GetAccountPasswordPolicy",
      "iam:ListVirtualMFADevices",
    ]

    resources = [
      "*",
    ]
  }

  statement {
    sid = "AllowPrimaryMfaRegistration"

    actions = [
      "iam:CreateVirtualMFADevice",
      "iam:EnableMFADevice",
      "iam:FinalizeSmsMfaRegistration",
      "iam:RequestSmsMfaRegistration",
      "iam:ResyncMFADevice",
    ]

    resources = [
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:mfa/$${aws:username}",
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/$${aws:username}",
    ]

  }

  statement {
    sid = "AllowSecondaryMfaRegistrationOnlyIfMfaAuthenticated"

    actions = [
      "iam:CreateVirtualMFADevice",
      "iam:EnableMFADevice",
      "iam:FinalizeSmsMfaRegistration",
      "iam:RequestSmsMfaRegistration",
      "iam:ResyncMFADevice",
    ]

    resources = [
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:mfa/$${aws:username}-*",
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/$${aws:username}",
    ]

    condition {
      test     = "Bool"
      variable = "aws:MultiFactorAuthPresent"
      values   = ["true"]
    }

  }

  statement {
    sid = "AllowMfaDeregistrationOnlyIfMfaAuthenticated"

    actions = [
      "iam:DeactivateMFADevice",
      "iam:DeleteVirtualMFADevice",
    ]

    resources = [
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:mfa/$${aws:username}",
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:mfa/$${aws:username}-*",
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/$${aws:username}",
    ]

    condition {
      test     = "Bool"
      variable = "aws:MultiFactorAuthPresent"
      values   = ["true"]
    }

  }

  statement {
    sid    = "BlockAnyAccessOtherThanAboveUnlessSignedInWithMFA"
    effect = "Deny"
    not_actions = [
      "iam:*",
    ]

    resources = [
      "*",
    ]

    condition {
      test     = "Bool"
      variable = "aws:MultiFactorAuthPresent"

      values = [
        "false",
      ]
    }

  }

  statement {
    sid = "AllowIAMWithMFA"
    actions = [
      "iam:CreateAccessKey",
      "iam:DeleteAccessKey",
      "iam:GetAccessKeyLastUsed",
      "iam:GetLoginProfile",
      "iam:GetUser",
      "iam:GetUserPolicy",
      "iam:ListAccessKeys",
      "iam:ListAttachedUserPolicies",
      "iam:ListGroupPolicies",
      "iam:ListGroupsForUser",
      "iam:ListMFADevices",
      "iam:ListServiceSpecificCredentials",
      "iam:ListSigningCertificates",
      "iam:ListSSHPublicKeys",
      "iam:ListUserPolicies",
      "iam:UpdateAccessKey",
    ]

    resources = [
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/$${aws:username}",
    ]

    condition {
      test     = "BoolIfExists"
      variable = "aws:MultiFactorAuthPresent"

      values = [
        "true",
      ]
    }
  }

  statement {
    sid = "AllowAccessCWSwitchRoleDashboard"
    actions = [
      "cloudwatch:GetDashboard",
    ]

    resources = [
      "arn:aws:cloudwatch::${data.aws_caller_identity.current.account_id}:dashboard/SwitchRole",
    ]
    condition {
      test     = "BoolIfExists"
      variable = "aws:MultiFactorAuthPresent"

      values = [
        "true",
      ]
    }
  }

  statement {
    sid = "AllowListCWDashboards"
    actions = [
      "cloudwatch:ListDashboards",
    ]

    resources = [
      "*",
    ]
    condition {
      test     = "BoolIfExists"
      variable = "aws:MultiFactorAuthPresent"

      values = [
        "true",
      ]
    }
  }
}

data "aws_iam_policy_document" "orchard_engineering_shared_role_access_policy" {
  statement {
    actions = [
      "sts:AssumeRole",
    ]

    resources = var.iam_roles_to_access
  }
}

# MFA enforcement policy for engineering group
# This policy enforces MFA for all non-IAM actions for the engineering group only
# The aws:MultiFactorAuthPresent key is only present when a request is made with temporary credentials,
# which includes all requests made through the console and requests where an AWS service calls another service on your behalf.
# Using BoolIfExists with "false" means the statement will evaluate to true (and thus deny access) in two cases:
# 1. When the key exists and is false (temporary credentials without MFA)
# 2. When the key doesn't exist at all (long-term credentials like access keys)
# This effectively enforces MFA for both console access and CLI operations using access keys.
# Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-multifactorauthpresent
#
# NOTE: This policy is intentionally only applied to the engineering group to avoid breaking access for business users
# who rely on access keys with tools like Cyberduck/Filezilla.
data "aws_iam_policy_document" "orchard_engineering_mfa_enforcement_policy" {
  statement {
    sid    = "BlockAnyAccessUnlessSignedInWithMFA"
    effect = "Deny"
    not_actions = [
      "iam:*",
    ]

    resources = [
      "*",
    ]

    condition {
      test     = "BoolIfExists"
      variable = "aws:MultiFactorAuthPresent"
      values   = ["false"]
    }
  }
}

# Policies for group engineering
module "shared_policies" {
  source = "git@github.com:theorchard/terraform-iam-policies.git//modules/shared?ref=1.22.0"

  environment                                 = var.environment
  break_glass_elevate_permissions_policy_name = "${var.environment}-break-glass-elevate-permissions-access-policy"
}

resource "aws_iam_policy" "shared_engineering_rbac_read_policy" {
  name   = "${var.environment}-orchard-engineering-shared-rbac-read-policy"
  policy = data.aws_iam_policy_document.orchard_engineering_shared_rbac_read_policy.json
}

resource "aws_iam_policy" "shared_engineering_rbac_write_policy" {
  name   = "${var.environment}-orchard-engineering-shared-rbac-write-policy"
  policy = data.aws_iam_policy_document.orchard_engineering_shared_rbac_write_policy.json
}

resource "aws_iam_policy" "shared_engineering_iam_security_credentials_policy" {
  name   = "${var.environment}-orchard-engineeering-shared-iam-security-credentials-policy"
  policy = data.aws_iam_policy_document.orchard_engineering_shared_iam_security_credentials_policy.json
}

resource "aws_iam_policy" "shared_engineering_role_access_policy" {
  name   = "${var.environment}-orchard-engineeering-shared-role-access-policy"
  policy = data.aws_iam_policy_document.orchard_engineering_shared_role_access_policy.json
}

resource "aws_iam_policy" "shared_engineering_mfa_enforcement_policy" {
  name   = "${var.environment}-orchard-engineering-mfa-enforcement-policy"
  policy = data.aws_iam_policy_document.orchard_engineering_mfa_enforcement_policy.json
}

resource "aws_iam_group_policy_attachment" "shared_engineering_global_read_policy_attachment" {
  group      = aws_iam_group.generic_orchard_engineering_group.name
  policy_arn = module.shared_policies.shared_engineering_read_policy_arn
}

resource "aws_iam_group_policy_attachment" "shared_engineering_global_iam_access_policy_attachment" {
  group      = aws_iam_group.generic_orchard_engineering_group.name
  policy_arn = module.shared_policies.shared_engineering_iam_access_policy_arn
}

resource "aws_iam_group_policy_attachment" "shared_engineering_rbac_read_policy_attachment" {
  group      = aws_iam_group.generic_orchard_engineering_group.name
  policy_arn = aws_iam_policy.shared_engineering_rbac_read_policy.arn
}

resource "aws_iam_group_policy_attachment" "shared_engineering_rbac_write_policy_attachment" {
  group      = aws_iam_group.generic_orchard_engineering_group.name
  policy_arn = aws_iam_policy.shared_engineering_rbac_write_policy.arn
}

resource "aws_iam_group_policy_attachment" "shared_engineering_iam_security_credentials_policy_attachment" {
  group      = aws_iam_group.generic_orchard_engineering_group.name
  policy_arn = aws_iam_policy.shared_engineering_iam_security_credentials_policy.arn
}

resource "aws_iam_group_policy_attachment" "shared_engineering_role_access_policy_attachment" {
  group      = aws_iam_group.generic_orchard_engineering_group.name
  policy_arn = aws_iam_policy.shared_engineering_role_access_policy.arn
}

resource "aws_iam_group_policy_attachment" "shared_engineering_mfa_enforcement_policy_attachment" {
  group      = aws_iam_group.generic_orchard_engineering_group.name
  policy_arn = aws_iam_policy.shared_engineering_mfa_enforcement_policy.arn
}

# Policies for everyone
resource "aws_iam_policy" "shared_everyone_iam_access_policy" {
  name   = "${var.environment}-orchard-everyone-shared-iam-access-policy"
  policy = data.aws_iam_policy_document.orchard_everyone_shared_iam_access_policy.json
}

resource "aws_iam_group_policy_attachment" "shared_everyone_iam_access_policy_attachment" {
  group      = aws_iam_group.generic_orchard_group.name
  policy_arn = aws_iam_policy.shared_everyone_iam_access_policy.arn
}

# Existing policies for everyone.
data "aws_iam_policy" "shared_everyone_existing_iam_policies" {
  for_each = toset(var.aws_managed_policy_attachments)
  name     = each.value
}

resource "aws_iam_group_policy_attachment" "shared_everyone_existing_policy_attachments" {
  for_each   = toset(var.aws_managed_policy_attachments)
  group      = aws_iam_group.generic_orchard_group.name
  policy_arn = data.aws_iam_policy.shared_everyone_existing_iam_policies[each.value].arn
}

module "ecs_infrastructure_role" {
  source = "git@github.com:theorchard/terraform-iam-policies.git//modules/ecs-infrastructure-role?ref=1.18.3"
}
