AWSTemplateFormatVersion: "2010-09-09"
Description: IAM roles needed to orchestrate the tests in the Github Actions
Parameters:
  Repository:
    Type: String
    Description: "Fully qualified repository name, formatted as <organization>/<repository-name>"
  Branches:
    Type: String
    Description: "The restrictions on which branches have permission to reach out to the OIDC provider. This is useful for security hardening of your github actions as described in https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions"
  Regions:
    Type: CommaDelimitedList
    Default: "us-east-1,us-east-2,us-west-2,eu-west-1"
  PrometheusWorkspaceID:
    Type: String
    Description: "Prometheus workspace to forward cluster prometheus metrics to"
  DatabaseName:
    Type: String
    Description: "Timestream database to forward test metrics to"
  TableName:
    Type: String
    Description: "Timestream table to forward test metrics to"
  SweeperTableName:
    Type: String
    Description: "Timestream table to forward leaked resources to"
  ResourceCountTableName:
    Type: String
    Description: "Timestream table to count number of resources to"
Resources:
  GithubActionsPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: GithubActionsPolicy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - cloudformation:CreateChangeSet
              - cloudformation:CreateStack
              - cloudformation:DeleteStack
              - cloudformation:DescribeChangeSet
              - cloudformation:DescribeStackEvents
              - cloudformation:DescribeStackResources
              - cloudformation:ExecuteChangeSet
              - cloudformation:GetTemplate
              - cloudformation:GetTemplateSummary
            Resource:
              - !Sub "arn:${AWS::Partition}:cloudformation:*:${AWS::AccountId}:stack/iam-*"
              - !Sub "arn:${AWS::Partition}:cloudformation:*:${AWS::AccountId}:stack/eksctl-*"
            Condition:
              StringEquals:
                aws:RequestedRegion:
                  Ref: Regions
          - Effect: Allow
            Action:
             - cloudformation:ListStacks
             - cloudformation:DescribeStacks
            Resource: "*"
          - Effect: Allow
            Action: logs:PutRetentionPolicy
            Resource: !Sub "arn:aws:logs:*:${AWS::AccountId}:log-group:/aws/eks/*"
          - Effect: Allow
            Action: fis:CreateExperimentTemplate
            Resource: !Sub "arn:${AWS::Partition}:fis:*:${AWS::AccountId}:action/*"
            Condition:
              StringEquals:
                aws:RequestedRegion:
                  Ref: Regions
          - Effect: Allow
            Action:
              - fis:CreateExperimentTemplate
              - fis:DeleteExperimentTemplate
              - fis:StartExperiment
            Resource:
              - !Sub "arn:${AWS::Partition}:fis:*:${AWS::AccountId}:experiment-template/*"
            Condition:
              StringEquals:
                aws:RequestedRegion:
                  Ref: Regions
          - Effect: Allow
            Action:
              - fis:GetExperiment
              - fis:StartExperiment
            Resource:
              - !Sub "arn:${AWS::Partition}:fis:*:${AWS::AccountId}:experiment/*"
            Condition:
              StringEquals:
                aws:RequestedRegion:
                  Ref: Regions
          - Effect: Allow
            Action:
              - iam:CreateServiceLinkedRole
            Resource:
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/aws-service-role/eks-nodegroup.amazonaws.com/AWSServiceRoleForAmazonEKSNodegroup"
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/aws-service-role/spot.amazonaws.com/AWSServiceRoleForEC2Spot"
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/aws-service-role/elasticloadbalancing.amazonaws.com/AWSServiceRoleForElasticLoadBalancing"
          - Effect: Allow
            Action:
              - sqs:CreateQueue
              - sqs:AddPermission
              - sqs:TagQueue
              - sqs:SetQueueAttributes
              - sqs:GetQueueAttributes
              - sqs:GetQueueUrl
              - sqs:RemovePermission
              - sqs:DeleteQueue
              - sqs:UntagQueue
            Resource:
            - !Sub "arn:${AWS::Partition}:sqs:*:${AWS::AccountId}:*"
            Condition:
              StringEquals:
                aws:RequestedRegion:
                  Ref: Regions
          - Effect: Allow
            Action:
              - events:PutRule
              - events:DeleteRule
              - events:DescribeRule
              - events:PutTargets
              - events:RemoveTargets
              - events:EnableRule
            Resource: !Sub "arn:${AWS::Partition}:events:*:${AWS::AccountId}:rule/*"
            Condition:
              StringEquals:
                aws:RequestedRegion:
                  Ref: Regions
          - Effect: Allow
            Action: timestream:WriteRecords
            Resource:
              - !Sub "arn:${AWS::Partition}:timestream:${AWS::Region}:${AWS::AccountId}:database/${DatabaseName}/table/${TableName}"
              - !Sub "arn:${AWS::Partition}:timestream:${AWS::Region}:${AWS::AccountId}:database/${DatabaseName}/table/${SweeperTableName}"
              - !Sub "arn:${AWS::Partition}:timestream:${AWS::Region}:${AWS::AccountId}:database/${DatabaseName}/table/${ResourceCountTableName}"
          - Effect: Allow
            Action: timestream:DescribeEndpoints
            Resource: "*"
  GithubActionsIAMPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: GithubActionsIAMPolicy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - iam:AttachRolePolicy
              - iam:CreateRole
              - iam:TagRole
              - iam:DeleteRolePolicy
              - iam:DetachRolePolicy
              - iam:PutRolePolicy
            Resource:
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/KarpenterNodeRole-*"
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/eksctl-*"
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/karpenter-irsa-*"
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/prometheus-irsa-*"
            Condition:
              ArnEquals:
                "iam:PermissionsBoundary": !Ref GithubActionsPermissionsBoundary
          - Effect: Allow
            Action: iam:DeleteRole
            Resource:
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/KarpenterNodeRole-*"
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/eksctl-*"
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/karpenter-irsa-*"
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/prometheus-irsa-*"
          - Effect: Allow
            Action:
              - iam:GetRole
              - iam:GetRolePolicy
              - iam:ListAttachedRolePolicies
            Resource:
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/aws-service-role/eks-nodegroup.amazonaws.com/AWSServiceRoleForAmazonEKSNodegroup"
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/KarpenterNodeRole-*"
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/eksctl-*"
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/karpenter-irsa-*"
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/prometheus-irsa-*"
              - !GetAtt FISInterruptionRole.Arn
          - Effect: Allow
            Action: iam:PassRole
            Resource: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/eksctl-*"
          - Effect: Allow
            Action:
              - iam:ListInstanceProfiles
              - iam:ListInstanceProfileTags
            Resource:
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:instance-profile/*"
          - Effect: Allow
            Action:
              - iam:AddRoleToInstanceProfile
              - iam:CreateInstanceProfile
              - iam:TagInstanceProfile
              - iam:RemoveRoleFromInstanceProfile
              - iam:DeleteInstanceProfile
              - iam:GetInstanceProfile
            Resource:
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:instance-profile/KarpenterNodeInstanceProfile-*"
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/KarpenterNodeRole-*"
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:instance-profile/KarpenterNodeInstanceProfile-Drift-*"
          - Effect: Allow
            Action:
              - iam:CreateOpenIDConnectProvider
              - iam:DeleteOpenIDConnectProvider
              - iam:ListOpenIDConnectProviders
              - iam:ListOpenIDConnectProviderTags
              - iam:TagOpenIDConnectProvider
              - iam:GetOpenIDConnectProvider
              - iam:TagOpenIDConnectProvider
            Resource: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:oidc-provider/*"
          - Effect: Allow
            Action:
              - iam:CreatePolicy
              - iam:DeletePolicy
              - iam:GetPolicy
              - iam:CreatePolicyVersion
              - iam:DeletePolicyVersion
              - iam:ListPolicyVersions
            Resource: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/KarpenterControllerPolicy-*"
  GithubActionsEKSPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: GithubActionsEKSPolicy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - eks:CreateCluster
              - eks:CreateAddon
              - eks:CreateNodegroup
              - eks:CreatePodIdentityAssociation
              - eks:DeleteCluster
              - eks:ListFargateProfiles
              - eks:TagResource
              - eks:DescribeCluster
              - eks:ListClusters
            Resource: !Sub "arn:${AWS::Partition}:eks:*:${AWS::AccountId}:cluster/*"
            Condition:
              StringEquals:
                aws:RequestedRegion:
                  Ref: Regions
          - Effect: Allow
            Action:
              - eks:DeleteAddon
              - eks:DescribeAddon
            Resource:
              - !Sub "arn:${AWS::Partition}:eks:*:${AWS::AccountId}:addon/*"
            Condition:
              StringEquals:
                aws:RequestedRegion:
                  Ref: Regions
          - Effect: Allow
            Action:
              - eks:DeleteNodegroup
              - eks:DescribeNodegroup
              - eks:TagResource
            Resource: !Sub "arn:${AWS::Partition}:eks:*:${AWS::AccountId}:nodegroup/*"
            Condition:
              StringEquals:
                aws:RequestedRegion:
                  Ref: Regions
          - Effect: Allow
            Action:
              - eks:TagResource
            Resource: !Sub "arn:${AWS::Partition}:eks:*:${AWS::AccountId}:podidentityassociation/*"
            Condition:
              StringEquals:
                aws:RequestedRegion:
                  Ref: Regions
  # GithubActionsPermissionsBoundary includes all permissions needed for all designated roles provisioned by the GithubActions
  # CI task. This includes the cluster ServiceRoles that are generated by EKSCTL and all roles generated with IRSA to interface from the
  # cluster into AWS services through IAM.
  # The policies that are captured inside the GithubActionsPermissionsBoundary include:
  # - AmazonEC2ContainerRegistryReadOnly
  # - AmazonEKSWorkerNodePolicy
  # - AmazonSSMManagedInstanceCore
  # - AmazonEBSCSIDriverPolicy
  # - EBS CSI Driver Controller Policy (used by IRSA)
  # - Prometheus Controller Policy (used by IRSA)
  # - Karpenter Controller Policy (used by IRSA)
  # - VPC CNI Daemonset Policy (used by IRSA)
  GithubActionsPermissionsBoundary:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: GithubActionsPermissionsBoundary
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - ec2:*
              # Read-Only Permissions to pull ECR images needed by the NodeInstanceRole
              - ecr:GetAuthorizationToken
              - ecr:BatchCheckLayerAvailability
              - ecr:GetDownloadUrlForLayer
              - ecr:GetRepositoryPolicy
              - ecr:DescribeRepositories
              - ecr:ListImages
              - ecr:DescribeImages
              - ecr:BatchGetImage
              - ecr:GetLifecyclePolicy
              - ecr:GetLifecyclePolicyPreview
              - ecr:ListTagsForResource
              - ecr:DescribeImageScanFindings
              # EKS ServiceRole permissions needed for AutoScalingGroups
              - autoscaling:DescribeAutoScalingGroups
              - autoscaling:UpdateAutoScalingGroup
              # EKS ServiceRole permissions needed to handle LoadBalancer
              - elasticloadbalancing:*
              - kms:CreateGrant
              - kms:GenerateDataKeyWithoutPlaintext
              - kms:DescribeKey
              # SSM Permissions for AmazonSSMManagedInstanceCore policy applied to the NodeInstanceRole
              - ssm:*
              # SSM Permissions for AmazonSSMManagedInstanceCore policy applied to the NodeInstanceRole
              - ssmmessages:*
              # SSM Permissions for AmazonSSMManagedInstanceCore policy applied to the NodeInstanceRole
              - ec2messages:*
              - sqs:DeleteMessage
              - sqs:GetQueueAttributes
              - sqs:GetQueueUrl
              - sqs:SendMessage
              - sqs:ReceiveMessage
              - pricing:GetProducts
              - eks:DescribeCluster
              - eks-auth:AssumeRoleForPodIdentity
            Resource: "*"
          - Effect: Allow
            Action: iam:PassRole
            Resource:
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/karpenter-irsa-*"
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/prometheus-irsa-*"
              - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/KarpenterNodeRole-*"
              - !GetAtt FISInterruptionRole.Arn
          - Effect: Allow
            Action: iam:CreateInstanceProfile
            Resource: "*"
            Condition:
              StringLike:
                aws:RequestTag/karpenter.k8s.aws/ec2nodeclass: "*"
          - Effect: Allow
            Action: iam:TagInstanceProfile
            Resource: "*"
            Condition:
              StringLike:
                aws:RequestTag/karpenter.k8s.aws/ec2nodeclass: "*"
                aws:ResourceTag/karpenter.k8s.aws/ec2nodeclass: "*"
          - Effect: Allow
            Action:
              - iam:AddRoleToInstanceProfile
              - iam:RemoveRoleFromInstanceProfile
              - iam:DeleteInstanceProfile
            Resource: "*"
            Condition:
              StringLike:
                aws:ResourceTag/karpenter.k8s.aws/ec2nodeclass: "*"
          - Effect: Allow
            Action: iam:GetInstanceProfile
            Resource: "*"
          - Effect: Allow
            Action:
              - aps:RemoteWrite
              - aps:GetSeries
              - aps:GetLabels
              - aps:GetMetricMetadata
            Resource: !Sub "arn:${AWS::Partition}:aps:${AWS::Region}:${AWS::AccountId}:workspace/${PrometheusWorkspaceID}"
          # Deny ALL IMDSv1 instance launch
          - Effect: Deny
            Action:
              - ec2:RunInstances
            Resource: !Sub "arn:${AWS::Partition}:ec2:*:${AWS::AccountId}:instance/*"
            Condition:
              StringNotEquals:
                ec2:MetadataHttpTokens: required
          - Effect: Deny
            Action:
              - ec2:ModifyInstanceMetadataOptions
            Resource: !Sub "arn:${AWS::Partition}:ec2:*:${AWS::AccountId}:instance/*"
            Condition:
              StringEquals:
                ec2:Attribute: HttpTokens
              StringNotEquals:
                ec2:Attribute/HttpTokens: required
  GithubActionsRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GithubActionsRole
      ManagedPolicyArns:
        - !Ref GithubActionsPolicy
        - !Ref GithubActionsEKSPolicy
        - !Ref GithubActionsIAMPolicy
        - !Ref GithubActionsPermissionsBoundary
      MaxSessionDuration: 21600 # 6 hours is the max session for GHA
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Federated: !Sub "arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com"
            Action: "sts:AssumeRoleWithWebIdentity"
            Condition:
              StringEquals:
                token.actions.githubusercontent.com:aud: sts.amazonaws.com
              StringLike:
                token.actions.githubusercontent.com:sub: !Sub "repo:${Repository}:ref:${Branches}"
          - Effect: Allow
            Principal:
              AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
            Action: "sts:AssumeRole"
  FISInterruptionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: FISInterruptionRole
      ManagedPolicyArns:
        - !Ref FISInterruptionPolicy
      MaxSessionDuration: 3600
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: "fis.amazonaws.com"
            Action: "sts:AssumeRole"
  FISInterruptionPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: FISInterruptionPolicy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action: ec2:SendSpotInstanceInterruptions
            Resource:
              - !Sub "arn:${AWS::Partition}:ec2:*:${AWS::AccountId}:instance/*"
            Condition:
              StringEquals:
                aws:RequestedRegion:
                  Ref: Regions
