AWSTemplateFormatVersion: "2010-09-09"
Description: >-
  Ankra self-managed provisioning cross-account role for creating and managing
  k3s clusters on EC2. By default Ankra builds the cluster's own network - a
  VPC, subnets, an internet gateway, NAT gateways and route tables - and can
  instead adopt a VPC you already own. Ankra creates, tags and deletes only
  the objects it made (network, instances, security groups, an imported key
  pair, Elastic IPs, per-cluster IAM roles and instance profiles); in an
  adopted VPC it never modifies or deletes your VPC, subnets, gateways, DHCP
  options or existing route tables. Every write permission is scoped to
  resources carrying the ankra.cloud/managed=true tag, to IAM names prefixed
  ankra-k3s-, or to read-only lookups. This role is the EC2-only alternative
  to AnkraProvisioning for accounts that must not grant EKS access. Ankra
  assumes the role via STS with the external ID shown during setup; no
  long-lived credentials leave your account.

Parameters:
  ExternalId:
    Type: String
    Description: External ID provided by Ankra during setup. Do not change.
    AllowedPattern: "^ankra-[A-Za-z0-9_-]+$"
  AnkraPrincipalArn:
    Type: String
    Description: The Ankra AWS principal allowed to assume this role.
  RoleName:
    Type: String
    Default: AnkraSelfManagedProvisioning
    Description: Name of the IAM role to create.
  PermissionsBoundaryArn:
    Type: String
    Default: ""
    Description: Optional IAM permissions boundary ARN applied to this role.

Conditions:
  HasPermissionsBoundary: !Not [!Equals [!Ref PermissionsBoundaryArn, ""]]

Resources:
  AnkraSelfManagedProvisioningRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Ref RoleName
      Description: Write access for Ankra to provision and manage self-managed k3s clusters on EC2.
      MaxSessionDuration: 3600
      PermissionsBoundary: !If [HasPermissionsBoundary, !Ref PermissionsBoundaryArn, !Ref "AWS::NoValue"]
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Ref AnkraPrincipalArn
            Action: "sts:AssumeRole"
            Condition:
              StringEquals:
                "sts:ExternalId": !Ref ExternalId
      Policies:
        - PolicyName: AnkraSelfManagedProvisioning
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              # Read-only EC2 lookups: VPCs, subnets, route tables, DHCP
              # options, instance types and offerings, availability zones,
              # images, addresses, volumes and the instances Ankra runs.
              - Sid: Ec2Read
                Effect: Allow
                Action:
                  - "ec2:Describe*"
                Resource: "*"
              # Read-only pricing, quota and identity lookups behind the cost
              # catalog and the create preflight.
              - Sid: CatalogRead
                Effect: Allow
                Action:
                  - "pricing:GetProducts"
                  - "pricing:DescribeServices"
                  - "servicequotas:GetServiceQuota"
                  - "servicequotas:ListServiceQuotas"
                  - "sts:GetCallerIdentity"
                Resource: "*"
              # The Canonical Ubuntu AMI is resolved per region from the
              # public SSM parameter tree. GetParameters (plural) is the call
              # the SDK makes; DescribeParameters lists the available series.
              - Sid: UbuntuImageLookup
                Effect: Allow
                Action:
                  - "ssm:GetParameters"
                  - "ssm:GetParameter"
                  - "ssm:DescribeParameters"
                Resource:
                  - "arn:aws:ssm:*::parameter/aws/service/canonical/*"
              # Launch instances. The instance, its volumes and its network
              # interfaces must be created with the ankra.cloud/managed tag;
              # the subnet, security groups, key pair, image and instance
              # profile are referenced, not created, so they carry no tag
              # condition.
              - Sid: Ec2RunInstancesTagged
                Effect: Allow
                Action:
                  - "ec2:RunInstances"
                Resource:
                  - "arn:aws:ec2:*:*:instance/*"
                  - "arn:aws:ec2:*:*:volume/*"
                  - "arn:aws:ec2:*:*:network-interface/*"
                Condition:
                  StringEquals:
                    "aws:RequestTag/ankra.cloud/managed": "true"
              - Sid: Ec2RunInstancesReferences
                Effect: Allow
                Action:
                  - "ec2:RunInstances"
                Resource:
                  - "arn:aws:ec2:*:*:subnet/*"
                  - "arn:aws:ec2:*:*:security-group/*"
                  - "arn:aws:ec2:*:*:key-pair/*"
                  - "arn:aws:ec2:*::image/*"
              # Tag resources as they are created (RunInstances,
              # CreateSecurityGroup, AllocateAddress, CreateRouteTable,
              # ImportKeyPair) and re-tag resources Ankra already owns.
              - Sid: Ec2CreateTagsOnCreate
                Effect: Allow
                Action:
                  - "ec2:CreateTags"
                Resource: "*"
                Condition:
                  StringEquals:
                    "ec2:CreateAction":
                      - "RunInstances"
                      - "CreateSecurityGroup"
                      - "AllocateAddress"
                      - "CreateRouteTable"
                      - "ImportKeyPair"
                      - "CreateVpc"
                      - "CreateSubnet"
                      - "CreateInternetGateway"
                      - "CreateNatGateway"
              - Sid: Ec2CreateTagsOnOwned
                Effect: Allow
                Action:
                  - "ec2:CreateTags"
                  - "ec2:DeleteTags"
                Resource: "*"
                Condition:
                  StringEquals:
                    "aws:ResourceTag/ankra.cloud/managed": "true"
              # Lifecycle of instances Ankra created: stop/start for the
              # cluster power controls, reboot for node repair, termination
              # on teardown, source/dest check for the bastion NAT and the
              # IMDSv2 hardening.
              - Sid: Ec2ManageOwnedInstances
                Effect: Allow
                Action:
                  - "ec2:TerminateInstances"
                  - "ec2:StopInstances"
                  - "ec2:StartInstances"
                  - "ec2:RebootInstances"
                  - "ec2:ModifyInstanceAttribute"
                  - "ec2:ModifyInstanceMetadataOptions"
                  - "ec2:AssociateIamInstanceProfile"
                  - "ec2:ReplaceIamInstanceProfileAssociation"
                  - "ec2:DisassociateIamInstanceProfile"
                Resource:
                  - "arn:aws:ec2:*:*:instance/*"
                Condition:
                  StringEquals:
                    "aws:ResourceTag/ankra.cloud/managed": "true"
              # Ankra-created networks: the VPC, subnets, internet gateway and
              # NAT gateways a cluster is built inside when no VPC is adopted.
              # Every object is created with the managed tag and only
              # objects carrying it may be modified or deleted.
              - Sid: Ec2CreateNetworkTagged
                Effect: Allow
                Action:
                  - "ec2:CreateVpc"
                  - "ec2:CreateSubnet"
                  - "ec2:CreateInternetGateway"
                  - "ec2:CreateNatGateway"
                Resource:
                  - "arn:aws:ec2:*:*:vpc/*"
                  - "arn:aws:ec2:*:*:subnet/*"
                  - "arn:aws:ec2:*:*:internet-gateway/*"
                  - "arn:aws:ec2:*:*:natgateway/*"
                Condition:
                  StringEquals:
                    "aws:RequestTag/ankra.cloud/managed": "true"
              # CreateSubnet and CreateNatGateway also name the VPC, subnet and
              # Elastic IP they attach to; those are Ankra's own tagged objects.
              - Sid: Ec2CreateNetworkReferences
                Effect: Allow
                Action:
                  - "ec2:CreateSubnet"
                  - "ec2:CreateNatGateway"
                Resource:
                  - "arn:aws:ec2:*:*:vpc/*"
                  - "arn:aws:ec2:*:*:subnet/*"
                  - "arn:aws:ec2:*:*:elastic-ip/*"
                Condition:
                  StringEquals:
                    "aws:ResourceTag/ankra.cloud/managed": "true"
              - Sid: Ec2ManageOwnedNetwork
                Effect: Allow
                Action:
                  - "ec2:ModifyVpcAttribute"
                  - "ec2:DeleteVpc"
                  - "ec2:AttachInternetGateway"
                  - "ec2:DetachInternetGateway"
                  - "ec2:DeleteInternetGateway"
                  - "ec2:ModifySubnetAttribute"
                  - "ec2:DeleteSubnet"
                  - "ec2:DeleteNatGateway"
                Resource:
                  - "arn:aws:ec2:*:*:vpc/*"
                  - "arn:aws:ec2:*:*:subnet/*"
                  - "arn:aws:ec2:*:*:internet-gateway/*"
                  - "arn:aws:ec2:*:*:natgateway/*"
                Condition:
                  StringEquals:
                    "aws:ResourceTag/ankra.cloud/managed": "true"
              # Security groups: create inside your VPC with the managed tag,
              # then manage rules and delete only the groups Ankra tagged.
              - Sid: Ec2CreateSecurityGroup
                Effect: Allow
                Action:
                  - "ec2:CreateSecurityGroup"
                Resource:
                  - "arn:aws:ec2:*:*:vpc/*"
              - Sid: Ec2CreateSecurityGroupTagged
                Effect: Allow
                Action:
                  - "ec2:CreateSecurityGroup"
                Resource:
                  - "arn:aws:ec2:*:*:security-group/*"
                Condition:
                  StringEquals:
                    "aws:RequestTag/ankra.cloud/managed": "true"
              - Sid: Ec2ManageOwnedSecurityGroups
                Effect: Allow
                Action:
                  - "ec2:AuthorizeSecurityGroupIngress"
                  - "ec2:AuthorizeSecurityGroupEgress"
                  - "ec2:RevokeSecurityGroupIngress"
                  - "ec2:RevokeSecurityGroupEgress"
                  - "ec2:UpdateSecurityGroupRuleDescriptionsIngress"
                  - "ec2:DeleteSecurityGroup"
                Resource:
                  - "arn:aws:ec2:*:*:security-group/*"
                Condition:
                  StringEquals:
                    "aws:ResourceTag/ankra.cloud/managed": "true"
              # The Ankra SSH public key is imported as a key pair per cluster.
              - Sid: Ec2KeyPairs
                Effect: Allow
                Action:
                  - "ec2:ImportKeyPair"
                Resource:
                  - "arn:aws:ec2:*:*:key-pair/ankra-k3s-*"
                Condition:
                  StringEquals:
                    "aws:RequestTag/ankra.cloud/managed": "true"
              - Sid: Ec2DeleteOwnedKeyPairs
                Effect: Allow
                Action:
                  - "ec2:DeleteKeyPair"
                Resource:
                  - "arn:aws:ec2:*:*:key-pair/ankra-k3s-*"
                Condition:
                  StringEquals:
                    "aws:ResourceTag/ankra.cloud/managed": "true"
              # One Elastic IP per cluster for the bastion.
              - Sid: Ec2AllocateAddressTagged
                Effect: Allow
                Action:
                  - "ec2:AllocateAddress"
                Resource:
                  - "arn:aws:ec2:*:*:elastic-ip/*"
                Condition:
                  StringEquals:
                    "aws:RequestTag/ankra.cloud/managed": "true"
              - Sid: Ec2ManageOwnedAddresses
                Effect: Allow
                Action:
                  - "ec2:AssociateAddress"
                  - "ec2:DisassociateAddress"
                  - "ec2:ReleaseAddress"
                Resource:
                  - "arn:aws:ec2:*:*:elastic-ip/*"
                  - "arn:aws:ec2:*:*:instance/*"
                  - "arn:aws:ec2:*:*:network-interface/*"
                Condition:
                  StringEquals:
                    "aws:ResourceTag/ankra.cloud/managed": "true"
              # bastion_nat egress mode only: one Ankra-owned route table in
              # your VPC whose default route points at the bastion. Ankra
              # never edits a route table it did not create; the subnet
              # association it makes is recorded and restored on teardown.
              - Sid: Ec2CreateRouteTable
                Effect: Allow
                Action:
                  - "ec2:CreateRouteTable"
                Resource:
                  - "arn:aws:ec2:*:*:vpc/*"
              - Sid: Ec2CreateRouteTableTagged
                Effect: Allow
                Action:
                  - "ec2:CreateRouteTable"
                Resource:
                  - "arn:aws:ec2:*:*:route-table/*"
                Condition:
                  StringEquals:
                    "aws:RequestTag/ankra.cloud/managed": "true"
              - Sid: Ec2ManageOwnedRouteTables
                Effect: Allow
                Action:
                  - "ec2:CreateRoute"
                  - "ec2:ReplaceRoute"
                  - "ec2:DeleteRoute"
                  - "ec2:AssociateRouteTable"
                  - "ec2:ReplaceRouteTableAssociation"
                  - "ec2:DisassociateRouteTable"
                  - "ec2:DeleteRouteTable"
                Resource:
                  - "arn:aws:ec2:*:*:route-table/*"
                Condition:
                  StringEquals:
                    "aws:ResourceTag/ankra.cloud/managed": "true"
              # Associating the Ankra route table names the subnet as a
              # second resource; the subnet itself is yours and untagged.
              # ReplaceRouteTableAssociation and DisassociateRouteTable
              # address the association, which carries no tags.
              - Sid: Ec2RouteTableAssociationReferences
                Effect: Allow
                Action:
                  - "ec2:AssociateRouteTable"
                  - "ec2:ReplaceRouteTableAssociation"
                  - "ec2:DisassociateRouteTable"
                Resource:
                  - "arn:aws:ec2:*:*:subnet/*"
              # Teardown sweep of EBS volumes the CSI driver provisioned for
              # this cluster (the driver stamps them with the managed tag).
              - Sid: Ec2DeleteOwnedVolumes
                Effect: Allow
                Action:
                  - "ec2:DeleteVolume"
                Resource:
                  - "arn:aws:ec2:*:*:volume/*"
                Condition:
                  StringEquals:
                    "aws:ResourceTag/ankra.cloud/managed": "true"
              # Teardown sweep of load balancers the cloud controller manager
              # created for Services of type LoadBalancer. Ankra sets the
              # managed tag on them through the Service annotation the CCM
              # honours, so only Ankra-created balancers match.
              - Sid: ElbRead
                Effect: Allow
                Action:
                  - "elasticloadbalancing:Describe*"
                Resource: "*"
              - Sid: ElbDeleteOwned
                Effect: Allow
                Action:
                  - "elasticloadbalancing:DeleteLoadBalancer"
                  - "elasticloadbalancing:DeleteTargetGroup"
                  - "elasticloadbalancing:DeleteListener"
                Resource: "*"
                Condition:
                  StringEquals:
                    "aws:ResourceTag/ankra.cloud/managed": "true"
              # Lifecycle of the two IAM roles Ankra creates per cluster:
              # ankra-k3s-<cluster>-cp (control plane: cloud controller
              # manager + EBS CSI controller) and ankra-k3s-<cluster>-node.
              - Sid: IamForK3sRoles
                Effect: Allow
                Action:
                  - "iam:CreateRole"
                  - "iam:DeleteRole"
                  - "iam:PutRolePolicy"
                  - "iam:DeleteRolePolicy"
                  - "iam:AttachRolePolicy"
                  - "iam:DetachRolePolicy"
                  - "iam:GetRole"
                  - "iam:GetRolePolicy"
                  - "iam:ListRolePolicies"
                  - "iam:ListAttachedRolePolicies"
                  - "iam:ListInstanceProfilesForRole"
                  - "iam:TagRole"
                Resource:
                  - "arn:aws:iam::*:role/ankra-k3s-*"
              - Sid: IamForK3sInstanceProfiles
                Effect: Allow
                Action:
                  - "iam:CreateInstanceProfile"
                  - "iam:DeleteInstanceProfile"
                  - "iam:GetInstanceProfile"
                  - "iam:AddRoleToInstanceProfile"
                  - "iam:RemoveRoleFromInstanceProfile"
                  - "iam:TagInstanceProfile"
                Resource:
                  - "arn:aws:iam::*:instance-profile/ankra-k3s-*"
              # Pass the per-cluster roles to EC2 instances only.
              - Sid: IamPassRoleToEc2
                Effect: Allow
                Action:
                  - "iam:PassRole"
                Resource:
                  - "arn:aws:iam::*:role/ankra-k3s-*"
                Condition:
                  StringEquals:
                    "iam:PassedToService":
                      - "ec2.amazonaws.com"
              # Elastic Load Balancing creates its service-linked role on
              # first use in an account; the cloud controller manager calls
              # this with the control-plane instance role, but a fresh
              # account also hits it from the teardown sweep's Describe.
              - Sid: IamServiceLinkedRoles
                Effect: Allow
                Action:
                  - "iam:CreateServiceLinkedRole"
                Resource:
                  - "arn:aws:iam::*:role/aws-service-role/*"
                Condition:
                  StringEquals:
                    "iam:AWSServiceName":
                      - "elasticloadbalancing.amazonaws.com"
                      - "spot.amazonaws.com"

Outputs:
  RoleArn:
    Description: Paste this Role ARN back into Ankra to allow self-managed cluster provisioning.
    Value: !GetAtt AnkraSelfManagedProvisioningRole.Arn
  ExternalId:
    Description: The external ID this role is bound to.
    Value: !Ref ExternalId
