AWSTemplateFormatVersion: "2010-09-09"
Description: >
  Main CloudFormation template to be placed in every account which need to be monitored.
  This template will capture information for one region, ensure it has been deployed in each region which you want to monitor.

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Agent Access
        Parameters:
          - StsAccountId
          - MainRegion
          - ExternalId
      - Label:
          default: Open Telemetry
        Parameters:
          - IncludeOpenTelemetryTracing
      - Label:
          default: Others
        Parameters:
          - PostFix
    ParameterLabels:
      StsAccountId:
        default: Account IDs
      MainRegion:
        default: Region
      ExternalId:
        default: External ID
      IncludeOpenTelemetryTracing:
        default: Tracing
      PostFix:
        default: Post fix

Parameters:
  StsAccountId:
    Type: String
    Description: Comma separated list of AWS account IDs where StackState agent and StackState platform run
    AllowedPattern: >-
      ^([0-9]{12},?)*$
    ConstraintDescription: Value must be a valid AWS account ID
  ExternalId:
    Type: String
    Description: Secret used when assuming the integration IAM role
    AllowedPattern: >-
      [\w+=,.@:\/-]{2,1224}
    ConstraintDescription: "The external ID provided is not valid. Allowed special characters: +=,.@:/-"
  MainRegion:
    Type: String
    Description: Primary AWS region, global resources will be deployed here
    AllowedPattern: >-
      ^[a-z]{2}-([a-z]*-){1,2}[0-9]$
    ConstraintDescription: Value must be a valid AWS region name
  IncludeOpenTelemetryTracing:
    Description: Enable OpenTelemetry Lambda Layer v1.0.0
    Default: false
    Type: String
    AllowedValues: [true, false]
  PostFix:
    Description: Value to append to all resource names when deploying the stack multiple times in the same account (optional)
    Default: ""
    Type: String
    AllowedPattern: >-
      ^[-\w+]{0,20}$
    ConstraintDescription: Value must only contain numbers, letter and hyphens

Conditions:
  IsMainRegion: !Equals [!Ref MainRegion, !Ref "AWS::Region"]
  ShouldIncludeOpenTelemetry:
    !Equals [true, !Ref IncludeOpenTelemetryTracing]

Resources:
  StsRole:
    Type: AWS::IAM::Role
    Condition: IsMainRegion
    Properties:
      RoleName: !Sub StackStateAwsIntegrationRole${PostFix}
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Split [",", !Ref StsAccountId]
            Action:
              - sts:AssumeRole
            Condition:
              StringEquals:
                sts:ExternalId: !Ref ExternalId
      Policies:
        - PolicyName: STSIntegration
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Sid: SelfAccess
                Effect: Allow
                Action:
                  - cloudtrail:LookupEvents
                  - iam:ListAccountAliases
                Resource: "*"
              - Sid: MetricsAccess
                Effect: Allow
                Action:
                  - cloudwatch:GetMetricData
                  - cloudwatch:ListMetrics
                Resource: "*"
              - Sid: EventsS3Access
                Effect: Allow
                Action:
                  - s3:DeleteObject
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:ListBucket
                Resource:
                  - !Sub ${StsLogsBucket.Arn}
                  - !Sub ${StsLogsBucket.Arn}/*
              - Sid: Ec2Access
                Effect: Allow
                Action:
                  - ec2:DescribeInstances
                  - ec2:DescribeInstanceTypes
                  - ec2:DescribeSecurityGroups
                  - ec2:DescribeSubnets
                  - ec2:DescribeVpcs
                  - ec2:DescribeVpnGateways
                  - ec2:DescribeNetworkInterfaces
                Resource: "*"
              - Sid: LoadBalancingAccess
                Effect: Allow
                Action:
                  - elasticloadbalancing:DescribeInstanceHealth
                  - elasticloadbalancing:DescribeListeners
                  - elasticloadbalancing:DescribeLoadBalancers
                  - elasticloadbalancing:DescribeTags
                  - elasticloadbalancing:DescribeTargetGroups
                  - elasticloadbalancing:DescribeTargetHealth
                Resource: "*"
              - Sid: AutoScalingAccess
                Effect: Allow
                Action:
                  - autoscaling:DescribeAutoScalingGroups
                Resource: "*"
              - Sid: RedshiftAccess
                Effect: Allow
                Action:
                  - redshift:DescribeClusters
                Resource: "*"
              - Sid: EcsAccess
                Effect: Allow
                Action:
                  - ecs:DescribeClusters
                  - ecs:DescribeContainerInstances
                  - ecs:DescribeServices
                  - ecs:DescribeTasks
                  - ecs:ListClusters
                  - ecs:ListContainerInstances
                  - ecs:ListServices
                  - ecs:ListTasks
                Resource: "*"
              - Sid: ServiceDiscoveryAccess
                Effect: Allow
                Action:
                  - servicediscovery:GetNamespace
                  - servicediscovery:GetService
                  - servicediscovery:ListInstances
                  - servicediscovery:ListServices
                Resource: "*"
              - Sid: FirehoseAccess
                Effect: Allow
                Action:
                  - firehose:DescribeDeliveryStream
                  - firehose:ListDeliveryStreams
                  - firehose:ListTagsForDeliveryStream
                Resource: "*"
              - Sid: S3Access
                Effect: Allow
                Action:
                  - s3:GetBucketNotification
                  - s3:GetBucketTagging
                  - s3:ListAllMyBuckets
                  - s3:ListBucket
                  - s3:GetBucketVersioning
                Resource: "*"
              - Sid: RdsAccess
                Effect: Allow
                Action:
                  - rds:DescribeDBClusters
                  - rds:DescribeDBInstances
                Resource: "*"
              - Sid: Route53Access
                Effect: Allow
                Action:
                  - route53:GetHostedZone
                  - route53:ListHostedZones
                  - route53:ListResourceRecordSets
                  - route53:ListTagsForResource
                  - route53domains:GetDomainDetail
                  - route53domains:ListDomains
                  - route53domains:ListTagsForDomain
                Resource: "*"
              - Sid: LambdaAccess
                Effect: Allow
                Action:
                  - lambda:GetFunction
                  - lambda:ListAliases
                  - lambda:ListEventSourceMappings
                  - lambda:ListFunctions
                  - lambda:ListTags
                Resource: "*"
              - Sid: SnsAccess
                Effect: Allow
                Action:
                  - sns:GetTopicAttributes
                  - sns:ListSubscriptionsByTopic
                  - sns:ListTagsForResource
                  - sns:ListTopics
                Resource: "*"
              - Sid: SqsAccess
                Effect: Allow
                Action:
                  - sqs:GetQueueAttributes
                  - sqs:ListQueues
                  - sqs:ListQueueTags
                Resource: "*"
              - Sid: DynamoDbAccess
                Effect: Allow
                Action:
                  - dynamodb:DescribeTable
                  - dynamodb:ListTables
                  - dynamodb:ListTagsOfResource
                Resource: "*"
              - Sid: KinesisAccess
                Effect: Allow
                Action:
                  - kinesis:DescribeStreamSummary
                  - kinesis:ListStreams
                  - kinesis:ListTagsForStream
                Resource: "*"
              - Sid: ApiGatewayAccess
                Effect: Allow
                Action:
                  - apigateway:GET
                Resource: "*"
              - Sid: CloudFormationAccess
                Effect: Allow
                Action:
                  - cloudformation:DescribeStackResources
                  - cloudformation:DescribeStacks
                Resource: "*"
              - Sid: StepFunctionsAccess
                Effect: Allow
                Action:
                  - states:DescribeStateMachine
                  - states:ListActivities
                  - states:ListStateMachines
                  - states:ListTagsForResource
                Resource: "*"

  StsKmsKey:
    Type: AWS::KMS::Key
    Condition: IsMainRegion
    Properties:
      Description: StackState Integration Key
      Enabled: true
      EnableKeyRotation: true
      KeyPolicy:
        Version: 2012-10-17
        Id: default
        Statement:
          - Sid: AllowKeyAdministration
            Effect: Allow
            Principal:
              AWS:
                - !Sub arn:aws:iam::${AWS::AccountId}:root
            Action:
              - kms:Create*
              - kms:Describe*
              - kms:Enable*
              - kms:List*
              - kms:Put*
              - kms:Update*
              - kms:Revoke*
              - kms:Disable*
              - kms:Get*
              - kms:Delete*
              - kms:TagResource
              - kms:UntagResource
              - kms:ScheduleKeyDeletion
              - kms:CancelKeyDeletion
            Resource: "*"
          - Sid: AllowS3Access
            Effect: Allow
            Principal:
              AWS: "*"
            Action:
              - kms:Decrypt
              - kms:DescribeKey
              - kms:Encrypt
              - kms:GenerateDataKey*
              - kms:ReEncrypt*
            Resource: "*"
            Condition:
              StringEquals:
                kms:ViaService: !Sub s3.${AWS::Region}.${AWS::URLSuffix}
          - Sid: AllowVpcFlowLogAccess
            Effect: Allow
            Principal:
              Service: !Sub delivery.logs.${AWS::URLSuffix}
            Action:
              - kms:Decrypt
              - kms:DescribeKey
              - kms:Encrypt
              - kms:GenerateDataKey*
              - kms:ReEncrypt*
            Resource: "*"
  StsKmsKeyAlias:
    Type: AWS::KMS::Alias
    Condition: IsMainRegion
    Properties:
      AliasName: !Sub alias/stackstate-integration${PostFix}
      TargetKeyId: !Ref StsKmsKey
  StsLogsBucket:
    Type: AWS::S3::Bucket
    Condition: IsMainRegion
    Properties:
      BucketName: !Sub stackstate-logs-${AWS::AccountId}${PostFix}
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: aws:kms
              KMSMasterKeyID: !GetAtt StsKmsKey.Arn
            BucketKeyEnabled: true
      VersioningConfiguration:
        Status: Enabled
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerPreferred
      LifecycleConfiguration:
        Rules:
          - Status: Enabled
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 1
            ExpirationInDays: 1
            NoncurrentVersionExpirationInDays: 1
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
    Metadata:
      cfn-lint:
        config:
          ignore_checks:
            - E3002 # New features BucketKeyEnabled, OwnershipControls not recognised
  StsLogsBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Condition: IsMainRegion
    Properties:
      Bucket: !Ref StsLogsBucket
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: AWSLogDeliveryWrite
            Effect: Allow
            Principal:
              Service: !Sub delivery.logs.${AWS::URLSuffix}
            Action: s3:PutObject
            Resource: !Sub ${StsLogsBucket.Arn}/AWSLogs/${AWS::AccountId}/*
            Condition:
              StringEquals:
                s3:x-amz-acl: bucket-owner-full-control
          - Sid: AWSLogDeliveryAclCheck
            Effect: Allow
            Principal:
              Service: !Sub delivery.logs.${AWS::URLSuffix}
            Action:
              - s3:GetBucketAcl
              - s3:ListBucket
              - s3:GetBucketVersioning
            Resource: !Sub ${StsLogsBucket.Arn}
          - Sid: DisableInsecureConnections
            Effect: Deny
            Principal: "*"
            Action: s3:*
            Resource:
              - !Sub ${StsLogsBucket.Arn}
              - !Sub ${StsLogsBucket.Arn}/*
            Condition:
              Bool:
                aws:SecureTransport: "false"
  StsFirehoseRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: !Sub firehose.${AWS::URLSuffix}
            Action: sts:AssumeRole
      Policies:
        - PolicyName: default
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - s3:AbortMultipartUpload
                  - s3:GetBucketLocation
                  - s3:GetObject
                  - s3:ListBucket
                  - s3:ListBucketMultipartUploads
                  - s3:PutObject
                  - s3:PutObjectAcl
                Resource:
                  - !Sub arn:${AWS::Partition}:s3:::stackstate-logs-${AWS::AccountId}${PostFix}/AWSLogs/${AWS::AccountId}/EventBridge/${AWS::Region}/*
                  - !Sub arn:${AWS::Partition}:s3:::stackstate-logs-${AWS::AccountId}${PostFix}
              - Effect: Allow
                Action:
                  - kms:Decrypt
                  - kms:GenerateDataKey
                Resource:
                  - !Sub arn:${AWS::Partition}:kms:${MainRegion}:${AWS::AccountId}:alias/stackstate-integration${PostFix}
                Condition:
                  StringEquals:
                    kms:ViaService: !Sub s3.${AWS::Region}.${AWS::URLSuffix}
                  ArnLike:
                    kms:EncryptionContext:aws:s3:arn: !Sub arn:${AWS::Partition}:s3:::stackstate-logs-${AWS::AccountId}${PostFix}/AWSLogs/${AWS::AccountId}/EventBridge/${AWS::Region}/*
  StsEventBridgeFirehose:
    Type: AWS::KinesisFirehose::DeliveryStream
    Properties:
      DeliveryStreamType: DirectPut
      ExtendedS3DestinationConfiguration:
        BucketARN: !If
          - IsMainRegion
          - !GetAtt StsLogsBucket.Arn
          - !Sub arn:${AWS::Partition}:s3:::stackstate-logs-${AWS::AccountId}${PostFix}
        RoleARN: !GetAtt StsFirehoseRole.Arn
        CompressionFormat: GZIP
        Prefix: !Sub AWSLogs/${AWS::AccountId}/EventBridge/${AWS::Region}/
        ErrorOutputPrefix: !Sub AWSLogs/${AWS::AccountId}/EventBridge/${AWS::Region}/
        BufferingHints:
          IntervalInSeconds: 60
          SizeInMBs: 8
        EncryptionConfiguration:
          KMSEncryptionConfig:
            AWSKMSKeyARN: !Sub arn:${AWS::Partition}:kms:${MainRegion}:${AWS::AccountId}:alias/stackstate-integration${PostFix}
  StsEventBridgeRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: !Sub events.${AWS::URLSuffix}
            Action: sts:AssumeRole
      Policies:
        - PolicyName: default
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - firehose:PutRecord
                  - firehose:PutRecordBatch
                Resource:
                  - !GetAtt StsEventBridgeFirehose.Arn
  StsEventBridgeRule:
    Type: AWS::Events::Rule
    Properties:
      EventPattern:
        source:
          - aws.apigateway
          - aws.application-autoscaling
          - aws.dynamodb
          - aws.ec2
          - aws.ecs
          - aws.elasticloadbalancing
          - aws.firehose
          - aws.kinesis
          - aws.lambda
          - aws.rds
          - aws.redshift
          - aws.s3
          - aws.sqs
          - aws.states
        detail-type:
          - "EC2 Instance State-change Notification"
          - "AWS API Call via CloudTrail"
      Targets:
        - Id: StsEventBridgeFirehose
          Arn: !GetAtt StsEventBridgeFirehose.Arn
          RoleArn: !GetAtt StsEventBridgeRole.Arn
  StsOpenTelemetryLayer:
    Type: AWS::Lambda::LayerVersion
    Condition: ShouldIncludeOpenTelemetry
    Properties:
      Description: v1.0.0 - StackState OpenTelemetry NodeJS Lambda Layer
      CompatibleRuntimes:
        - nodejs14.x
      Content:
        S3Bucket:
          !Join
          - '-'
          - - 'stackstate-integration-otel'
            - !Ref AWS::Region
        S3Key: otel-nodejs-1.0.0.zip
      LayerName: stackstate-otel-nodejs

Outputs:
  StackStateKmsKeyArn:
    Value: !If
      - IsMainRegion
      - !GetAtt StsKmsKey.Arn
      - !Sub arn:${AWS::Partition}:kms:${MainRegion}:${AWS::AccountId}:alias/stackstate-integration${PostFix}
    Export:
      Name: !Sub ${AWS::StackName}-StackStateKmsKeyArn
  StackStateLogsBucketArn:
    Value: !Sub arn:${AWS::Partition}:s3:::stackstate-logs-${AWS::AccountId}${PostFix}
    Export:
      Name: !Sub ${AWS::StackName}-StackStateLogsBucketArn
  StackStateIntegrationRole:
    Value: !Ref StsRole
    Export:
      Name: !Sub ${AWS::StackName}-StackStateIntegrationRole
