data "aws_caller_identity" "current" {}

data "aws_iam_policy_document" "tag_based_access_policy_part_1" {

  # API gateway
  statement {
    actions = [
      "apigateway:DELETE",
      "apigateway:GET",
      "apigateway:PATCH",
      "apigateway:POST",
      "apigateway:PUT",
    ]

    resources = [
      "arn:aws:apigateway:*::*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # Athena
  statement {
    actions = [
      "athena:BatchGet*",
      "athena:Get*",
      "athena:List*",
      "athena:*QueryExecution",
      "athena:UpdatePreparedStatement",
    ]

    resources = [
      "arn:aws:athena:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # Batch
  statement {
    actions = [
      "batch:Describe*",
      "batch:*Job",
      "batch:*JobDefinition",
      "batch:Update*",
    ]

    resources = [
      "arn:aws:batch:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # Cloudfront
  statement {
    actions = [
      "cloudfront:CreateInvalidation",
      "cloudfront:CreateMonitoringSubscription",
      "cloudfront:Get*",
      "cloudfront:UpdateDistribution",
      "cloudfront:UpdateStreamingDistribution",
    ]

    resources = [
      "arn:aws:cloudfront:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # Cloudwatch
  statement {
    actions = [
      "cloudwatch:Describe*",
      "cloudwatch:Disable*",
      "cloudwatch:Enable*",
      "cloudwatch:Get*",
      "cloudwatch:SetAlarmState",
    ]

    resources = [
      "arn:aws:cloudwatch:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # Aurora DSQL
  statement {
    actions = [
      "dsql:DbConnect",
      "dsql:DbConnectAdmin",
      "dsql:UpdateCluster",
    ]

    resources = [
      "arn:aws:dsql:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # EC2 (EBS uses this namespace/partition too)
  statement {
    actions = [
      "ebs:StartSnapshot",
      "ec2:AssociateAddress",
      "ec2:AttachNetworkInterface",
      "ec2:AttachVolume",
      "ec2:DetachNetworkInterface",
      "ec2:DetachVolume",
      "ec2:DisassociateAddress",
      "ec2:ModifyInstance*",
      "ec2:ModifyVolume*",
      "ec2:MonitorInstances",
      "ec2:RebootInstances",
      "ec2:ReportInstanceStatus",
      "ec2:StartInstances",
      "ec2:StopInstances",
    ]

    resources = [
      "arn:aws:ec2:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "ec2:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # EC2 Autoscaling
  statement {
    actions = [
      "autoscaling:Describe*",
      "autoscaling:*Instances",
      "autoscaling:*LoadBalancerTargetGroups",
      "autoscaling:*LoadBalancers",
      "autoscaling:Set*",
      "autoscaling:UpdateAutoScalingGroup",
    ]

    resources = [
      "arn:aws:autoscaling:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "autoscaling:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # ECS
  statement {
    actions = [
      "ecs:Describe*",
      "ecs:*TaskDefinition",
      "ecs:*Task",
      "ecs:Update*",
    ]

    resources = [
      "arn:aws:ecs:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "ecs:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # Elasticache
  statement {
    actions = [
      "elasticache:*Snapshot",
      "elasticache:Decrease*",
      "elasticache:Increase*",
      "elasticache:ModifyCacheCluster",
      "elasticache:ModifyReplicationGroup*",
      "elasticache:RebootCacheCluster",
    ]

    resources = [
      "arn:aws:elasticache:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # Elasticache parameter group permission. This is a special case where parameter groups are not tagged with application_family
  # but engineers need to modify the parameter group settings for the cache cluster that is tagged with the application_family.
  statement {
    actions = [
      "elasticache:ModifyReplicationGroup",
    ]

    resources = [
      "arn:aws:elasticache:*:${data.aws_caller_identity.current.account_id}:parametergroup:*"
    ]
  }

  # EMR
  statement {
    actions = [
      "elasticmapreduce:AddInstance*",
      "elasticmapreduce:AddJobFlowSteps",
      "elasticmapreduce:CancelSteps*",
      "elasticmapreduce:Describe*",
      "elasticmapreduce:Get*",
      "elasticmapreduce:List*",
      "elasticmapreduce:Modify*",
      "elasticmapreduce:RunJobFlow",
      "elasticmapreduce:TerminateJobFlows",
    ]

    resources = [
      "arn:aws:elasticmapreduce:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # EventBridge (FKA Cloudwatch Events)
  statement {
    actions = [
      "events:Describe*",
      "events:DisableRule",
      "events:EnableRule",
    ]

    resources = [
      "arn:aws:events:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # Kendra
  statement {
    actions = [
      "kendra:Describe*",
      "kendra:Get*",
      "kendra:List*",
      "kendra:*Document*",
      "kendra:Query",
    ]

    resources = [
      "arn:aws:kendra:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # Kinesis Data Analytics
  statement {
    actions = [
      "kinesisanalytics:Add*",
      "kinesisanalytics:Describe*",
      "kinesisanalytics:List*",
      "kinesisanalytics:RollbackApplication",
      "kinesisanalytics:StopApplication",
      "kinesisanalytics:StartApplication",
      "kinesisanalytics:UpdateApplication*",
    ]

    resources = [
      "arn:aws:kinesisanalytics:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # KMS
  statement {
    actions = [
      "kms:Decrypt",
      "kms:Encrypt",
      "kms:GenerateDataKey*",
      "kms:DescribeKey",
    ]

    resources = [
      "arn:aws:kms:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # Logs
  statement {
    actions = [
      "logs:CreateExportTask",
      "logs:Describe*",
      "logs:FilterLogEvents",
    ]

    resources = [
      "arn:aws:logs:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }
}

data "aws_iam_policy_document" "tag_based_access_policy_part_2" {

  # Mediaconvert
  statement {
    actions = [
      "mediaconvert:*Job",
      "mediaconvert:Get*",
      "mediaconvert:List*",
    ]

    resources = [
      "arn:aws:mediaconvert:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # MSK
  statement {
    actions = [
      "kafka:Describe*",
      "kafka:Get*",
      "kafka:List*",
      "kafka:RebootBroker",
      "kafka:Update*",
      "kafka-cluster:Describe*",
      "kafka-cluster:Connect",
    ]

    resources = [
      "arn:aws:kafka:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # MWAA (Airflow)
  statement {
    actions = [
      "airflow:Describe*",
      "airflow:Get*",
      "airflow:List*",
      "airflow:Update*",
    ]

    resources = [
      "arn:aws:airflow:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # Opensearch (ES)
  statement {
    actions = [
      "es:Cancel*Update",
      "es:Describe*",
      "es:ESHttp*",
      "es:Get*",
      "es:List*",
      "es:Start*Update",
      "es:Update*",
      "es:Upgrade*",
      "es:StartDomainMaintenance",
    ]

    resources = [
      "arn:aws:es:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # RDS
  statement {
    actions = [
      "rds:AddTagsToResource",
      "rds:BacktrackDBCluster",
      "rds:CreateDBInstanceReadReplica",
      "rds:*BlueGreenDeployment*",
      "rds:*Snapshot*",
      "rds:Describe*",
      "rds:Download*",
      "rds:ModifyCurrentDBClusterCapacity",
      "rds:ModifyDBCluster",
      "rds:ModifyDBInstance",
      "rds:ModifyGlobalCluster",
      "rds:PromoteReadReplica*",
      "rds:RebootDBInstance",
      "rds:RestoreDB*ToPointInTime",
    ]

    resources = [
      "arn:aws:rds:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # Secrets Manager
  statement {
    actions = [
      "secretsmanager:CancelRotateSecret",
      "secretsmanager:Describe*",
      "secretsmanager:Get*",
      "secretsmanager:List*",
      "secretsmanager:PutSecretValue",
      "secretsmanager:RestoreSecret",
      "secretsmanager:RotateSecret",
      "secretsmanager:UpdateSecret",
      "secretsmanager:UpdateSecretVersionStage",
    ]

    resources = [
      "arn:aws:secretsmanager:*:${data.aws_caller_identity.current.account_id}:secret:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "secretsmanager:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # SNS
  statement {
    actions = [
      "sns:CheckIfPhoneNumberIsOptedOut",
      "sns:ConfirmSubscription",
      "sns:Get*",
      "sns:List*",
      "sns:OptInPhoneNumber",
      "sns:Publish",
      "sns:SetSubscriptionAttributes",
      "sns:Subscribe",
      "sns:Unsubscribe",
    ]

    resources = [
      "arn:aws:sns:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # Step Functions
  statement {
    actions = [
      "states:Describe*",
      "states:Get*",
      "states:List*",
      "states:Send*",
      "states:Start*",
      "states:StopExecution",
      "states:UpdateStateMachine",
      "states:RedriveExecution",
    ]

    resources = [
      "arn:aws:states:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # SWF
  statement {
    actions = [
      "swf:*WorkflowExecution",
      "swf:Count*",
      "swf:Describe*",
      "swf:Get*",
      "swf:List*",
      "swf:Poll*",
      "swf:Record*",
      "swf:Request*",
      "swf:Respond*",
      "swf:ScheduleActivityTask",
      "swf:Start*",
    ]

    resources = [
      "arn:aws:swf:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # SQS
  statement {
    actions = [
      "sqs:GetQueueAttributes",
      "sqs:ReceiveMessage",
      "sqs:SendMessage*",
    ]

    resources = [
      "arn:aws:sqs:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  # X-Ray
  statement {
    actions = [
      "xray:*",
    ]

    resources = [
      "arn:aws:xray:*:${data.aws_caller_identity.current.account_id}:*"
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"

      values = [
        var.application_family
      ]
    }
  }

  statement {
    actions = [
      "xray:PutEncryptionConfig",
    ]
    effect = "Deny"

    resources = [
      "*"
    ]
  }

  # Lambda
  statement {
    actions = [
      "lambda:Get*",
      "lambda:Invoke*",
      "lambda:List*",
      "lambda:PublishVersion",
      "lambda:UpdateEventSourceMapping",
      "lambda:UpdateFunctionCode",
      "lambda:UpdateFunctionConfiguration",
      "lambda:PutFunctionConcurrency",
      "lambda:PutProvisionedConcurrencyConfig",
    ]
    resources = [
      "arn:aws:lambda:*:${data.aws_caller_identity.current.account_id}:*"
    ]
    condition {
      test     = "StringEquals"
      variable = "aws:ResourceTag/application_family"
      values = [
        var.application_family
      ]
    }
  }
}

resource "aws_iam_policy" "tag_based_access_policy" {
  for_each = {
    1 = data.aws_iam_policy_document.tag_based_access_policy_part_1.json,
    2 = data.aws_iam_policy_document.tag_based_access_policy_part_2.json,
  }
  name   = "ABAC-${var.application_family}-tag-access-policy-part-${each.key}"
  policy = each.value

  tags = {
    access_type = "abac",
  }
}
