AWSTemplateFormatVersion: "2010-09-09"
Description: >-
  CMDash integration read role (ROADMAP-V102 M6). Creates ONE IAM role that
  CMDash's integration principal can assume only with the External ID CMDash
  issued for your connection. It can read the account-level settings the CMDash
  AWS connector checks and, if you choose, Security Hub findings. Nothing else:
  no write action, no wildcard action, no S3 object read, no secrets read, and
  no AWS managed policy (not SecurityAudit, not ReadOnlyAccess). Delete the
  stack to cut CMDash off.

# Long-form intrinsics only (Ref, Fn::GetAtt, ...), no short "!" tags: the
# CMDash policy spec (apps/api/test/cfn-integration-role.spec.ts) parses this
# file with a plain YAML parser and compares its action list with
# apps/api/src/integration/aws/required-actions.ts exactly.
#
# A byte-identical copy is served at /cfn/cmdash-integration-role.yaml by the
# CMDash site (apps/portal/public/cfn/); a drift test keeps the two equal.

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "From the CMDash connect form"
        Parameters:
          - CmdashPrincipalArn
          - ExternalId
      - Label:
          default: "What CMDash may read"
        Parameters:
          - IncludeConfigChecks
          - IncludeSecurityHub
    ParameterLabels:
      CmdashPrincipalArn:
        default: "CMDash principal to trust"
      ExternalId:
        default: "External ID"
      IncludeConfigChecks:
        default: "Allow the account configuration checks (Amazon Web Services connector)"
      IncludeSecurityHub:
        default: "Allow reading Security Hub findings (AWS Security Hub connector)"

Parameters:
  CmdashPrincipalArn:
    Type: String
    # No default on purpose: the CMDash connect form passes the principal of
    # the CMDash deployment you are connecting (SaaS or your own self-hosted
    # install) in the launch link, and shows it for a manual launch.
    AllowedPattern: "^arn:(aws|aws-us-gov):iam::[0-9]{12}:role/[A-Za-z0-9+=,.@_/-]{1,512}$"
    ConstraintDescription: >-
      Must be the full IAM role ARN shown as "CMDash principal to trust" on the
      CMDash connect form, e.g. arn:aws:iam::111122223333:role/cmdash-integration-principal.
    Description: The CMDash role that may assume this role. Copy it from the CMDash connect form.
  ExternalId:
    Type: String
    # Exactly what CMDash issues (a random UUID); see isValidExternalId in
    # apps/api/src/integration/aws/assume-role.ts.
    AllowedPattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
    ConstraintDescription: >-
      Must be the External ID shown on the CMDash connect form (36 characters,
      for example 0f8e2a4c-5b1d-4e3f-9a7b-6c2d1e0f3a4b).
    Description: The External ID CMDash issued for this connection. Copy it from the CMDash connect form.
  IncludeConfigChecks:
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "true"
    Description: Grant the read actions the Amazon Web Services connector uses (IAM, S3 account settings, CloudTrail, EBS, KMS, AWS Backup).
  IncludeSecurityHub:
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "false"
    Description: Grant securityhub:GetFindings for the AWS Security Hub connector.

Rules:
  SomethingToRead:
    Assertions:
      - Assert:
          Fn::Or:
            - Fn::Equals:
                - Ref: IncludeConfigChecks
                - "true"
            - Fn::Equals:
                - Ref: IncludeSecurityHub
                - "true"
        AssertDescription: Choose at least one of the configuration checks or Security Hub; a role that can read nothing is of no use to CMDash.

Conditions:
  WantConfigChecks:
    Fn::Equals:
      - Ref: IncludeConfigChecks
      - "true"
  WantSecurityHub:
    Fn::Equals:
      - Ref: IncludeSecurityHub
      - "true"

Resources:
  CmdashIntegrationRole:
    Type: AWS::IAM::Role
    Properties:
      Description: Read-only role for CMDash. Assumable only by the CMDash principal, and only with this connection's External ID.
      MaxSessionDuration: 3600
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: CmdashWithExternalIdOnly
            Effect: Allow
            Principal:
              AWS:
                Ref: CmdashPrincipalArn
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                sts:ExternalId:
                  Ref: ExternalId
      Tags:
        - Key: managed-by
          Value: cmdash-integration-role

  CmdashConfigReadPolicy:
    Type: AWS::IAM::Policy
    Condition: WantConfigChecks
    Properties:
      Roles:
        - Ref: CmdashIntegrationRole
      PolicyName: cmdash-config-read
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          # Every action here is an account-level or list call that AWS does not
          # scope to a resource ARN, so Resource must be "*"; the ACTION list is
          # what is narrowed, to exactly the calls the connector makes.
          - Sid: CmdashConfigRead
            Effect: Allow
            Action:
              - iam:GetAccountSummary
              - iam:GetAccountPasswordPolicy
              - s3:GetAccountPublicAccessBlock
              - cloudtrail:DescribeTrails
              - ec2:GetEbsEncryptionByDefault
              - kms:ListKeys
              - kms:DescribeKey
              - kms:GetKeyRotationStatus
            Resource: "*"
          # v10.2.2 (#845): AWS Backup posture for the Backups claim. Read-only: no
          # recovery point, vault-access, backup, restore or delete action.
          # ListBackupPlans, ListBackupVaults and ListProtectedResources are list
          # calls AWS cannot scope to one resource of a plan or vault, so Resource
          # must be "*" for these three (the ACTION list is what is narrowed).
          - Sid: CmdashBackupList
            Effect: Allow
            Action:
              - backup:ListBackupPlans
              - backup:ListBackupVaults
              - backup:ListProtectedResources
            Resource: "*"
          # GetBackupPlan and ListBackupSelections do take a resource: scoped to
          # backup plans in this account, in this partition, any region.
          - Sid: CmdashBackupPlanRead
            Effect: Allow
            Action:
              - backup:GetBackupPlan
              - backup:ListBackupSelections
            Resource:
              Fn::Sub: "arn:${AWS::Partition}:backup:*:${AWS::AccountId}:backup-plan:*"

  CmdashSecurityHubReadPolicy:
    Type: AWS::IAM::Policy
    Condition: WantSecurityHub
    Properties:
      Roles:
        - Ref: CmdashIntegrationRole
      PolicyName: cmdash-securityhub-read
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: CmdashSecurityHubRead
            Effect: Allow
            Action:
              - securityhub:GetFindings
            Resource: "*"

Outputs:
  RoleArn:
    Description: Paste this into the "IAM role ARN" field of the CMDash connect form.
    Value:
      Fn::GetAtt:
        - CmdashIntegrationRole
        - Arn
