module "default_tags" {
  source             = "git@github.com:theorchard/terraform-default-tags.git//?ref=2.0.0"
  environment        = var.environment
  application_family = var.application_family
  team_name          = "devops"
}

provider "aws" {
  region = var.aws_region

  default_tags {
    tags = module.default_tags.tags
  }
}

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

locals {
  permissions_boundary_policy_length = length(replace(replace(data.aws_iam_policy_document.permissions_boundary_policy.json, " ", ""), "\n", ""))
}

data "aws_caller_identity" "current" {}

data "aws_route53_zone" "dev_theorchard_io" {
  name = "dev.theorchard.io."
}

data "aws_iam_policy_document" "orchard_shared_read_policy" {
  # checkov:skip=CKV_AWS_109:This is DEV, no sensitive data is held and it is meant to be permissive.
  # checkov:skip=CKV_AWS_110:Same as above.
  # checkov:skip=CKV_AWS_111:Same as above.

  statement {
    actions = [
      "account:GetAccountInformation",
      "aws-portal:ViewBilling", # Added back since new actions start working only after the migration is completed on December 11, 2023
      "acm:Describe*",
      "acm:Get*",
      "acm:List*",
      "airflow:GetEnvironment",
      "airflow:ListEnvironments",
      "airflow:ListTagsForResource",
      "bedrock:Get*",
      "bedrock:List*",
      "billing:Get*",
      "billing:ListBillingViews",
      "ce:DescribeNotificationSubscription",
      "ce:DescribeReport",
      "ce:Get*",
      "ce:ListCostAllocationTags",
      "ce:ListSavingsPlansPurchaseRecommendationGeneration",
      "consolidatedbilling:GetAccountBillingRole",
      "consolidatedbilling:ListLinkedAccounts",
      "cur:GetClassic*",
      "cur:ValidateReportDestination",
      "datapipeline:Describe*",
      "datapipeline:Get*",
      "datapipeline:List*",
      "datapipeline:QueryObjects",
      "datapipeline:ValidatePipelineDefinition",
      "freetier:GetFreeTierAlertPreference",
      "freetier:GetFreeTierUsage",
      "glue:*",
      "guardduty:Get*",
      "guardduty:List*",
      "health:Describe*",
      "invoicing:GetInvoiceEmailDeliveryPreferences",
      "invoicing:GetInvoicePDF",
      "invoicing:ListInvoiceSummaries",
      "organizations:DescribeAccount",
      "organizations:ListAccounts",
      "organizations:ListParents",
      "payments:GetPayment*",
      "payments:ListPaymentPreferences",
      "route53:Get*",
      "route53:List*",
      "route53:Test*",
      "route53domains:Check*",
      "route53domains:Get*",
      "route53domains:List*",
      "route53domains:View*",
      "route53resolver:Get*",
      "route53resolver:List*",
      "ses:Describe*",
      "ses:Get*",
      "ses:List*",
      "shield:Describe*",
      "shield:Get*",
      "shield:List*",
      "support:*",
      "tag:GetResources",
      "tax:GetTaxInheritance",
      "tax:GetTaxRegistrationDocument",
      "tax:ListTaxRegistrations",
      "trustedadvisor:DescribeAccount*",
      "trustedadvisor:DescribeCheck*",
      "trustedadvisor:DescribeNotification*",
      "trustedadvisor:DescribeOrganization",
      "trustedadvisor:DescribeRisk*",
      "trustedadvisor:DownloadRisk",
      "trustedadvisor:RefreshCheck",
      "waf-regional:Get*",
      "waf-regional:List*",
      "waf:Get*",
      "waf:List*",
      "wafv2:CheckCapacity",
      "wafv2:Describe*",
      "wafv2:Get*",
      "wafv2:List*",
      "xray:Get*",
      "servicequotas:List*",
      "servicequotas:Get*"
    ]

    resources = [
      "*"
    ]

  }

  statement {
    actions = [
      "ram:GetResourceShares",
      "ram:List*",
      "ssm:GetParameter",
    ]

    resources = [
      "arn:aws:ram:${var.aws_region}:*:resource-share/*",
      "arn:aws:ssm:${var.aws_region}:*:parameter/shared/aws-account-ids/*",
    ]
  }
}

data "aws_iam_policy_document" "orchard_shared_write_policy" {
  # checkov:skip=CKV_AWS_107:This is DEV, no sensitive data is held and it is meant to be permissive.
  # checkov:skip=CKV_AWS_108:Same as above.
  # checkov:skip=CKV_AWS_109:Same as above.
  # checkov:skip=CKV_AWS_110:Same as above.
  # checkov:skip=CKV_AWS_111:Same as above.

  statement {
    actions = [
      "apigateway:*",
      "appsync:*",
      "autoscaling:*",
      "application-autoscaling:*",
      "athena:*",
      "batch:*",
      "cloudformation:*",
      "cloudfront:*",
      "cloudsearch:*",
      "cloudwatch:*",
      "dynamodb:*",
      "ec2:*",
      "ecr:*",
      "ecs:*",
      "elasticache:*",
      "elasticfilesystem:*",
      "elasticloadbalancing:*",
      "elasticmapreduce:*",
      "elastictranscoder:*",
      "elemental-inference:*",
      "emr-serverless:*",
      "es:*",
      "events:*",
      "firehose:*",
      "kafka:*",
      "kafkaconnect:*",
      "kafka-cluster:*",
      "kinesis:*",
      "kinesisanalytics:*",
      "kms:*",
      "lambda:*",
      "logs:*",
      "mediaconvert:*",
      "neptune-db:*",
      "rds:*",
      "s3:*",
      "s3files:*",
      "scheduler:*",
      "sdb:*",
      "secretsmanager:*",
      "serverlessrepo:",
      "ses:*",
      "sns:*",
      "sqs:*",
      "states:*",
      "swf:*",
      "timestream:*",
      "wafv2:*WebACL",
      "wafv2:*LoggingConfiguration",
      "wafv2:*TagResource",
      "xray:Put*",
      "sagemaker:*",
      "geo:*",
      "geo-places:*",
      "dsql:*",
    ]

    resources = [
      "*",
    ]
  }

  # Allow creation and deletion of CNAME records in dev.theorchard.io
  statement {
    effect = "Allow"
    actions = [
      "route53:ChangeResourceRecordSets",
    ]

    resources = [
      data.aws_route53_zone.dev_theorchard_io.arn,
    ]

    condition {
      test     = "ForAllValues:StringEquals"
      variable = "route53:ChangeResourceRecordSetsRecordTypes"
      values   = ["CNAME"]
    }

    condition {
      test     = "ForAllValues:StringLike"
      variable = "route53:ChangeResourceRecordSetsNormalizedRecordNames"
      values = [
        "dev-*"
      ]
    }
  }

  statement {
    effect  = "Allow"
    actions = ["airflow:CreateWebLoginToken"]
    resources = formatlist(
      "arn:aws:airflow:${var.aws_region}:${data.aws_caller_identity.current.account_id}:role/efed-airflow-2_4/%s",
      [
        "Public",
        "Op",
        "User",
        "Viewer",
        "Admin",
      ]
    )
  }

  statement {
    effect    = "Allow"
    actions   = ["airflow:UpdateEnvironment"]
    resources = ["arn:aws:airflow:${var.aws_region}:${data.aws_caller_identity.current.account_id}:environment/efed-airflow-2_4"]
  }

  // SYS-24365 - Bedrock Claude model access for engineering tools
  statement {
    effect = "Allow"
    actions = [
      "bedrock:InvokeModel",
      "bedrock:InvokeModelWithResponseStream",
    ]
    resources = [
      "arn:aws:bedrock:us-*::foundation-model/anthropic.claude-*",
      "arn:aws:bedrock:us-*:${data.aws_caller_identity.current.account_id}:inference-profile/us.anthropic.claude-*",
    ]
  }
}

data "aws_iam_policy_document" "orchard_shared_iam_access_policy" {
  # checkov:skip=CKV_AWS_109:This is DEV, no sensitive data is held and it is meant to be permissive.

  statement {
    actions = [
      "iam:GenerateCredentialReport",
      "iam:GenerateServiceLastAccessedDetails",
      "iam:Get*",
      "iam:List*",
      "iam:SimulateCustomPolicy",
      "iam:SimulatePrincipalPolicy",
      "iam:TagPolicy",
      "iam:TagRole",
      "iam:UntagPolicy",
      "iam:UntagRole"
    ]

    resources = [
      "*"
    ]

  }

  statement {
    actions = [
      "iam:PassRole"
    ]

    resources = [
      "arn:aws:iam::*:role/AWSBatchServiceRole",
      "arn:aws:iam::*:role/service-role/AWSBatchServiceRole",
      "arn:aws:iam::*:role/ecsInstanceRole",
      "arn:aws:iam::*:role/aws-ec2-spot-fleet-role",
      "arn:aws:iam::*:role/AWSBatchJobRole",
      "arn:aws:iam::*:role/service-role/AmazonSageMaker-ExecutionRole-20180430T180933",
      "arn:aws:iam::*:role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService",
      "arn:aws:iam::*:role/dev-*-task-role",
      "arn:aws:iam::*:role/dev-*-task-execution-role",
      "arn:aws:iam::*:role/EMR_DefaultRole",
      "arn:aws:iam::*:role/EMR_EC2_DefaultRole",
      "arn:aws:iam::*:role/StepFunctionsLambdaRole",
      "arn:aws:iam::*:role/dev-*-state-machine-role",
      "arn:aws:iam::*:role/dev-*-sfn-role",
      "arn:aws:iam::*:role/Lambda-dev-cucumbers",
      "arn:aws:iam::*:role/dev-lambda-timed-release-scheduling-role",
      "arn:aws:iam::*:role/dev-video-mediaconvert-role",
      "arn:aws:iam::*:role/dev-ows-podcast-browser-upload-role",
      "arn:aws:iam::*:role/dev-*-rds-monitoring-role",
      "arn:aws:iam::*:role/lambda-dev-*",
      "arn:aws:iam::*:role/S3-dev-*-replication-role",
      "arn:aws:iam::*:role/dev-flink-message-transformation-role",
      "arn:aws:iam::103233932089:role/dev-cloudformation-migration-assistant-role",
    ]

  }

  statement {
    actions = [
      "iam:CreateServiceLinkedRole"
    ]

    resources = [
      "*"
    ]

    condition {
      test     = "StringEquals"
      variable = "iam:AWSServiceName"

      values = [
        "autoscaling.amazonaws.com",
        "delivery.logs.amazonaws.com",
        "ec2scheduled.amazonaws.com",
        "elasticloadbalancing.amazonaws.com",
        "kafka.amazonaws.com",
        "spot.amazonaws.com",
        "spotfleet.amazonaws.com",
        "dsql.amazonaws.com",
      ]
    }

  }

  # Allow creation and deletion of managed policies based on Terraform module
  # naming conventions. For everything else, inline policies should be sufficient.
  statement {
    sid = "AllowPolicyManagement"

    actions = [
      "iam:CreatePolicy",
      "iam:CreatePolicyVersion",
      "iam:DeletePolicy",
      "iam:DeletePolicyVersion",
    ]

    resources = [
      "arn:aws:iam::*:policy/dev-*-rds-monitoring-policy", # terraform-rds
      "arn:aws:iam::*:policy/dev-*-run-task-policy",       # terraform-fargate
      "arn:aws:iam::*:policy/EFS-dev-*-policy",            # terraform-efs
      "arn:aws:iam::*:policy/Elasticsearch-dev-*-policy",  # terraform-elasticsearch
      "arn:aws:iam::*:policy/S3-*-replication-policy",     # terraform-s3
      "arn:aws:iam::*:policy/S3-dev-*-read-only",          # terraform-s3
      "arn:aws:iam::*:policy/SecretsManager-dev-*-policy", # terraform-fargate
      "arn:aws:iam::*:policy/SQS-dev-*-minimal-policy",    # terraform-sqs
    ]
  }

  statement {
    sid    = "DenyDeleteProtectedPolicies"
    effect = "Deny"
    actions = [
      "iam:DeletePolicy",
    ]

    resources = [
      "arn:aws:iam::*:policy/*-akhq-*",
      "arn:aws:iam::*:policy/*-break-glass-*",
      "arn:aws:iam::*:policy/*-chef-server-*",
      "arn:aws:iam::*:policy/*-code-push-server-*",
      "arn:aws:iam::*:policy/*-fivetran-proxy-*",
      "arn:aws:iam::*:policy/*-stripo-*",
      "arn:aws:iam::*:policy/SecretsManager-dev-datadog-agent-policy",
      "arn:aws:iam::*:policy/SecretsManager-dev-datadog-policy",
    ]
  }

  # Allow roles to be created and configured with policies, but only if
  # permissions boundary is set, thus preventing privilege escalation.
  statement {
    sid = "AllowRoleManagementWithPermissionsBoundary"

    actions = [
      "iam:AttachRolePolicy",
      "iam:CreateRole",
      "iam:DeleteRolePolicy",
      "iam:DetachRolePolicy",
      "iam:PutRolePermissionsBoundary",
      "iam:PutRolePolicy",
    ]

    resources = [
      "arn:aws:iam::*:role/dev-*-rds-monitoring-role", # terraform-rds
      "arn:aws:iam::*:role/dev-*-state-machine-role",
      "arn:aws:iam::*:role/dev-*-sfn-role",
      "arn:aws:iam::*:role/dev-*-task-execution-role", # terraform-fargate
      "arn:aws:iam::*:role/dev-*-task-run-role",       # terraform-fargate
      "arn:aws:iam::*:role/dev-*-task-role",           # terraform-fargate
      "arn:aws:iam::*:role/lambda-dev-*",              # terraform-lambda
      "arn:aws:iam::*:role/S3-dev-*-replication-role", # terraform-s3
    ]

    condition {
      test     = "StringEquals"
      variable = "iam:PermissionsBoundary"
      values   = [aws_iam_policy.permissions_boundary_policy.arn]
    }

  }

  statement {
    sid = "AllowDeletionOfRoles"

    actions = [
      "iam:DeleteRole",
    ]

    resources = [
      "arn:aws:iam::*:role/dev-*-rds-monitoring-role", # terraform-rds
      "arn:aws:iam::*:role/dev-*-state-machine-role",
      "arn:aws:iam::*:role/dev-*-sfn-role",
      "arn:aws:iam::*:role/dev-*-task-execution-role", # terraform-fargate
      "arn:aws:iam::*:role/dev-*-task-run-role",       # terraform-fargate
      "arn:aws:iam::*:role/dev-*-task-role",           # terraform-fargate
      "arn:aws:iam::*:role/lambda-dev-*",              # terraform-lambda
      "arn:aws:iam::*:role/S3-dev-*-replication-role", # terraform-s3
    ]
  }

  statement {
    sid    = "DenyChangesToProtectedRoles"
    effect = "Deny"
    actions = [
      "iam:AttachRolePolicy",
      "iam:DeleteRole",
      "iam:DeleteRolePolicy",
      "iam:DetachRolePolicy",
      "iam:PutRolePermissionsBoundary",
      "iam:PutRolePolicy",
    ]

    resources = [
      "arn:aws:iam::*:role/*-akhq-*",
      "arn:aws:iam::*:role/*-break-glass-*",
      "arn:aws:iam::*:role/*-chef-server-*",
      "arn:aws:iam::*:role/*-code-push-server-*",
      "arn:aws:iam::*:role/*-fivetran-proxy-*",
      "arn:aws:iam::*:role/*-stripo-*",
      "arn:aws:iam::*:role/dev-datadog-agent-*",
      "arn:aws:iam::*:role/lambda-dev-datadog-forwarder",
      "arn:aws:iam::*:role/lambda-dev-lambda-datadog-s3-event-forwarder",
      "arn:aws:iam::*:role/lambda-dev-lambda-ioc-*",
      "arn:aws:iam::*:role/lambda-dev-lambda-sanitise-rds-data",
    ]
  }
}

data "aws_iam_policy_document" "orchard_shared_iam_deny_policy" {

  statement {
    effect = "Deny"
    actions = [
      "s3:Create*",
      "s3:Delete*",
      "s3:Put*",
      "s3:Replicate*",
      "s3:Restore*",
      "s3:Update*",
    ]

    resources = [
      "arn:aws:s3:::aws-logs-${data.aws_caller_identity.current.account_id}-us-east-1/*",
      "arn:aws:s3:::aws-waf-logs-dev-tf-orcd-bucket/*",
      "arn:aws:s3:::chef-validator-keys/*",
      "arn:aws:s3:::dev-orcd-awsconfig/*",
      "arn:aws:s3:::dev-orcd-cloudfront-logs/*",
      "arn:aws:s3:::dev-orcd-cloudtrail-logs/*",
      "arn:aws:s3:::dev-orcd-guardduty-logs/*",
      "arn:aws:s3:::dev-orchard-elb-logs/*",
    ]

  }

  statement {
    effect = "Deny"
    actions = [
      "kms:CancelKeyDeletion",
      "kms:ConnectCustomKeyStore",
      "kms:Create*",
      "kms:Decrypt",
      "kms:Delete*",
      "kms:Disable*",
      "kms:DisconnectCustomKeyStore",
      "kms:EnableKey",
      "kms:EnableKeyRotation",
      "kms:Encrypt",
      "kms:Generate*",
      "kms:ImportKeyMaterial",
      "kms:ReEncryptFrom",
      "kms:ReEncryptTo",
      "kms:ReplicateKey",
      "kms:ScheduleKeyDeletion",
      "kms:Sign",
      "kms:SynchronizeMultiRegionKey",
      "kms:Update*",
      "kms:Verify",
    ]

    resources = [
      "arn:aws:kms:*:${data.aws_caller_identity.current.account_id}:key/33831a77-32d1-4629-a4ba-32c0f13327c2",
      "arn:aws:kms:*:${data.aws_caller_identity.current.account_id}:key/3843c879-ff39-4b02-963c-5c7dfb22977f",
      "arn:aws:kms:*:${data.aws_caller_identity.current.account_id}:alias/dev-aws-waf-logging-data-key",
      "arn:aws:kms:*:${data.aws_caller_identity.current.account_id}:alias/dev-guardduty-logs",
    ]

  }

  statement {
    sid    = "DenyDisassociateWebACLProtectedResources"
    effect = "Deny"
    actions = [
      "elasticloadbalancing:SetWebACL",
    ]

    resources = [
      "arn:aws:elasticloadbalancing:us-east-1:${data.aws_caller_identity.current.account_id}:loadbalancer/app/dev-akhq/*",
      "arn:aws:elasticloadbalancing:us-east-1:${data.aws_caller_identity.current.account_id}:loadbalancer/app/dev-break-glass/*",
      "arn:aws:elasticloadbalancing:us-east-1:${data.aws_caller_identity.current.account_id}:loadbalancer/app/dev-chef-server/*",
      "arn:aws:elasticloadbalancing:us-east-1:${data.aws_caller_identity.current.account_id}:loadbalancer/app/dev-code-push-server-public/*",
      "arn:aws:elasticloadbalancing:us-east-1:${data.aws_caller_identity.current.account_id}:loadbalancer/app/dev-datadog-agent/*",
    ]
  }

  statement {
    sid    = "DenyDeleteProtectedWebACLs"
    effect = "Deny"
    actions = [
      "wafv2:DeleteWebACL",
    ]

    resources = [
      "arn:aws:wafv2:us-east-1:${data.aws_caller_identity.current.account_id}:regional/webacl/dev-datadog-agent-waf-block/*",
      "arn:aws:wafv2:us-east-1:${data.aws_caller_identity.current.account_id}:regional/webacl/dev-orcd-waf-block/*",
      "arn:aws:wafv2:us-east-1:${data.aws_caller_identity.current.account_id}:regional/webacl/dev-orcd-waf-count-only/*"
    ]
  }
}

data "aws_iam_policy_document" "trusted_advisor_policy" {
  # checkov:skip=CKV_AWS_111:For better or worse, a widely permissive policy is the goal.

  statement {
    sid    = "AllowTrustedAdvisorAccess"
    effect = "Allow"
    actions = [
      "trustedadvisor:DescribeAccount*",
      "trustedadvisor:DescribeCheck*",
      "trustedadvisor:DescribeNotification*",
      "trustedadvisor:DescribeOrganization",
      "trustedadvisor:DescribeRisk*",
      "trustedadvisor:DownloadRisk",
      "trustedadvisor:RefreshCheck",
      "trustedadvisor:UpdateRiskStatus",
    ]

    resources = ["*"]
  }
}

resource "aws_iam_policy" "shared_iam_read_policy" {
  name   = "${var.environment}-orchard-shared-read-policy"
  policy = data.aws_iam_policy_document.orchard_shared_read_policy.json
}

resource "aws_iam_policy" "shared_iam_write_policy" {
  name   = "${var.environment}-orchard-shared-write-policy"
  policy = data.aws_iam_policy_document.orchard_shared_write_policy.json
}

resource "aws_iam_policy" "shared_iam_access_policy" {
  name   = "${var.environment}-orchard-shared-iam-access-policy"
  policy = data.aws_iam_policy_document.orchard_shared_iam_access_policy.json
}

resource "aws_iam_policy" "shared_iam_deny_policy" {
  name   = "${var.environment}-orchard-shared-iam-deny-policy"
  policy = data.aws_iam_policy_document.orchard_shared_iam_deny_policy.json
}

resource "aws_iam_policy" "trusted_advisor_policy" {
  name   = "${var.environment}-orchard-trusted-advisor-access-policy"
  policy = data.aws_iam_policy_document.trusted_advisor_policy.json
  tags = {
    terraformed = true
  }
}

# Create a permissions boundary to limit the permissions of any roles created
# by this role, and therefore prevent a privilege escalation vector.
data "aws_iam_policy_document" "permissions_boundary_policy" {
  source_policy_documents = [
    data.aws_iam_policy_document.orchard_shared_read_policy.json,
    data.aws_iam_policy_document.orchard_shared_write_policy.json,
    data.aws_iam_policy_document.orchard_shared_iam_deny_policy.json
  ]
}

resource "aws_iam_policy" "permissions_boundary_policy" {
  name   = "${var.environment}-permissions-boundary-policy"
  policy = data.aws_iam_policy_document.permissions_boundary_policy.json

  lifecycle {
    precondition {
      condition     = local.permissions_boundary_policy_length < 6144
      error_message = "Permissions boundary policy is more than 6144 symbols, current length is: ${local.permissions_boundary_policy_length}"
    }
  }
}

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

  role_name = var.role_name
  additional_policies_to_attach = [
    aws_iam_policy.shared_iam_access_policy.name,
    aws_iam_policy.shared_iam_deny_policy.name,
    aws_iam_policy.shared_iam_read_policy.name,
    aws_iam_policy.shared_iam_write_policy.name,
  ]
}

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