# inbox.camp bootstrap stack — creates the one cross-account role the engine assumes.
# Deploy in the CUSTOMER account. Revocation = delete this stack; access ends instantly.
#
# Source of truth lives here (engine/templates/); mirrored to the public `stack` repo on
# release. The inline policy grows one statement at a time as engine steps gain real
# implementations (see docs/infra-plan.md § reconciler) — that construction keeps it minimal.
#
# M3 will add the custom resource that POSTs the role ARN back to /api/connect; for
# tenant zero the aws_connections row is seeded by scripts/seed-tenant-zero.ts instead.

AWSTemplateFormatVersion: '2010-09-09'
Description: >-
  inbox.camp — cross-account role for the provisioning engine. Delete this stack at any
  time to revoke all inbox.camp access to this account.

Parameters:
  TenantId:
    Type: String
    Description: Your inbox.camp tenant id (32 hex chars, used as the role ExternalId).
    AllowedPattern: '[0-9a-f]{32}'
  ControlPlaneAccountId:
    Type: String
    Description: The inbox.camp control-plane AWS account allowed to assume this role.
    Default: '277707102570'
    AllowedPattern: '[0-9]{12}'

Resources:
  # Snapshot lifecycle (daily EBS snapshots of the mail server, retain 7). DLM assumes
  # this role; the engine only creates the policy that references it.
  DlmRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: inboxcamp-dlm
      Path: /inboxcamp/
      Description: Amazon Data Lifecycle Manager role for inbox.camp snapshot policies.
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: dlm.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSDataLifecycleManagerServiceRole

  EngineRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: inboxcamp-engine
      Description: Assumed by the inbox.camp control plane to manage email infrastructure in this account.
      MaxSessionDuration: 3600
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${ControlPlaneAccountId}:root'
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                sts:ExternalId: !Ref TenantId
      Policies:
        - PolicyName: inboxcamp-engine
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Sid: Probe
                Effect: Allow
                Action:
                  - ec2:DescribeRegions
                Resource: '*'
              # provision_network: dedicated VPC + public subnet + IGW + route + EIP.
              # TODO before publishing: tag-condition the mutating actions
              # (aws:RequestTag/inboxcamp:managed on creates, aws:ResourceTag on the rest —
              # needs per-resource-type statements because of multi-resource EC2 actions).
              - Sid: ProvisionNetwork
                Effect: Allow
                Action:
                  - ec2:CreateVpc
                  - ec2:ModifyVpcAttribute
                  - ec2:CreateSubnet
                  - ec2:CreateInternetGateway
                  - ec2:AttachInternetGateway
                  - ec2:CreateRouteTable
                  - ec2:CreateRoute
                  - ec2:AssociateRouteTable
                  - ec2:AllocateAddress
                  - ec2:CreateTags
                  - ec2:DescribeVpcs
                  - ec2:DescribeSubnets
                  - ec2:DescribeInternetGateways
                  - ec2:DescribeRouteTables
                  - ec2:DescribeAddresses
                  - ec2:DescribeAvailabilityZones
                Resource: '*'
              # launch_instance: t4g.small + security group + EIP association.
              - Sid: LaunchInstance
                Effect: Allow
                Action:
                  - ec2:RunInstances
                  - ec2:CreateSecurityGroup
                  - ec2:AuthorizeSecurityGroupIngress
                  - ec2:AuthorizeSecurityGroupEgress
                  - ec2:DescribeSecurityGroups
                  - ec2:DescribeInstances
                  - ec2:DescribeInstanceStatus
                  - ec2:DescribeImages
                  - ec2:AssociateAddress
                  - ec2:ModifyInstanceAttribute   # keep the root volume on termination
                Resource: '*'
              # restore (engine/restore.ts, scripts/restore.ts): relaunch the server from an
              # EBS snapshot — AMI from the snapshot, launch, verify over SSM, then either
              # terminate the copy (drill) or stop the old box and move the EIP (replace).
              - Sid: Restore
                Effect: Allow
                Action:
                  - ec2:DescribeSnapshots
                  - ec2:DescribeVolumes
                  - ec2:RegisterImage
                  - ec2:DeregisterImage
                  - ec2:DisassociateAddress
                Resource: '*'
              - Sid: RestoreStopTerminateManagedOnly
                Effect: Allow
                Action:
                  - ec2:StopInstances
                  - ec2:TerminateInstances
                Resource: 'arn:aws:ec2:*:*:instance/*'
                Condition:
                  StringEquals:
                    ec2:ResourceTag/inboxcamp:managed: 'true'
              - Sid: RestoreWaitForSsm
                Effect: Allow
                Action: ssm:DescribeInstanceInformation
                Resource: '*'
              - Sid: ResolveAl2023Ami
                Effect: Allow
                Action: ssm:GetParameter
                Resource: 'arn:aws:ssm:*::parameter/aws/service/ami-amazon-linux-latest/*'
              # Instance role/profile, confined to the /inboxcamp/ IAM path; the only
              # attachable managed policy is SSM core.
              - Sid: InstanceProfile
                Effect: Allow
                Action:
                  - iam:CreateRole
                  - iam:GetRole
                  - iam:PutRolePolicy
                  - iam:CreateInstanceProfile
                  - iam:GetInstanceProfile
                  - iam:AddRoleToInstanceProfile
                Resource:
                  - 'arn:aws:iam::*:role/inboxcamp/*'
                  - 'arn:aws:iam::*:instance-profile/inboxcamp/*'
              - Sid: AttachSsmCoreOnly
                Effect: Allow
                Action: iam:AttachRolePolicy
                Resource: 'arn:aws:iam::*:role/inboxcamp/*'
                Condition:
                  ArnEquals:
                    iam:PolicyARN: 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore'
              - Sid: PassInstanceRole
                Effect: Allow
                Action: iam:PassRole
                Resource: 'arn:aws:iam::*:role/inboxcamp/*'
                Condition:
                  StringEquals:
                    iam:PassedToService: ec2.amazonaws.com
              # provision_storage: the message-blob bucket.
              - Sid: ProvisionStorage
                Effect: Allow
                Action:
                  - s3:CreateBucket
                  - s3:ListBucket
                  - s3:PutBucketVersioning
                  - s3:GetBucketVersioning
                  - s3:PutBucketTagging
                  - s3:PutBucketPublicAccessBlock
                Resource: 'arn:aws:s3:::inboxcamp-*'
              # SSM Run Command against managed instances only — the sole management
              # channel (config apply, migration, upgrades).
              - Sid: RunCommandDocument
                Effect: Allow
                Action: ssm:SendCommand
                Resource: 'arn:aws:ssm:*::document/AWS-RunShellScript'
              - Sid: RunCommandInstances
                Effect: Allow
                Action: ssm:SendCommand
                Resource: 'arn:aws:ec2:*:*:instance/*'
                Condition:
                  StringEquals:
                    ssm:resourceTag/inboxcamp:managed: 'true'
              - Sid: ReadCommandResults
                Effect: Allow
                Action: ssm:GetCommandInvocation
                Resource: '*'
              # configure_dns: publish SPF/DKIM/SRV/autoconfig (never MX before
              # inbound_verified). TODO before publishing: scope to the tenant zones.
              - Sid: ConfigureDns
                Effect: Allow
                Action:
                  - route53:ListHostedZonesByName
                  - route53:GetHostedZone
                  - route53:ListResourceRecordSets
                  - route53:ChangeResourceRecordSets
                  - route53:GetChange
                Resource: '*'
              # setup_ses_relay: SES identities + a scoped SMTP user under /inboxcamp/.
              - Sid: SetupSesRelay
                Effect: Allow
                Action:
                  - ses:GetEmailIdentity
                  - ses:CreateEmailIdentity
                  - ses:GetAccount
                Resource: '*'
              # launch_instance: daily snapshot policy for the mail server volume.
              - Sid: Backups
                Effect: Allow
                Action:
                  - dlm:CreateLifecyclePolicy
                  - dlm:GetLifecyclePolicies
                  - dlm:GetLifecyclePolicy
                  - dlm:UpdateLifecyclePolicy
                  - dlm:DeleteLifecyclePolicy
                Resource: '*'
              - Sid: PassDlmRole
                Effect: Allow
                Action: iam:PassRole
                Resource: !GetAtt DlmRole.Arn
                Condition:
                  StringEquals:
                    iam:PassedToService: dlm.amazonaws.com
              # Secrets hand-off to the instance goes through THIS account's Parameter
              # Store (SecureString under /inboxcamp/<tenant>/), never through command text.
              - Sid: TenantParameters
                Effect: Allow
                Action:
                  - ssm:PutParameter
                  - ssm:DeleteParameter   # consumed one-time passwords (sync_config)
                Resource: !Sub 'arn:aws:ssm:*:${AWS::AccountId}:parameter/inboxcamp/${TenantId}/*'
              # Onboarding wizard: import the WorkMail org (domains, users, groups) and, at
              # migration time, set each user's password so the copy can log in — the
              # customer never types passwords into inbox.camp (docs/infra-plan.md § M3 plan §2).
              # TODO before publishing: scope to the organization ARN once known.
              - Sid: WorkMailImport
                Effect: Allow
                Action:
                  - workmail:ListOrganizations
                  - workmail:DescribeOrganization
                  - workmail:ListMailDomains
                  - workmail:ListUsers
                  - workmail:DescribeUser
                  - workmail:ListGroups
                  - workmail:ListGroupMembers
                  - workmail:ListAliases
                  - workmail:ResetPassword
                Resource: '*'
              - Sid: SesSmtpUser
                Effect: Allow
                Action:
                  - iam:CreateUser
                  - iam:GetUser
                  - iam:PutUserPolicy
                  - iam:CreateAccessKey
                  - iam:ListAccessKeys
                  - iam:DeleteAccessKey
                Resource: 'arn:aws:iam::*:user/inboxcamp/*'

Outputs:
  RoleArn:
    Description: Give this to inbox.camp (the connect flow reads it automatically).
    Value: !GetAtt EngineRole.Arn
