Description: >
  Creates a comprehensive AWS environment for Tackle integration that enables AWS Marketplace and AWS Partner Central operations and event handling.
  The stack configures an IAM role with Marketplace permissions, an event processing pipeline, and encrypted resources to process Marketplace data.
  Must be deployed in us-east-1 region for AWS Marketplace compatibility.
# We're using AWS::LanguageExtensions to manage the JSON escaping for the TackleSendHeartSchedule SQS message payload. Some considerations:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-languageextension-transform.html#aws-languageextensions-considerations
Transform:
  - AWS::LanguageExtensions
Parameters:
  ExternalId:
    Type: String
    Description: A hash of a Tackle-internal identifier unique to your company
    ConstraintDescription: You must use the Tackle-provided external ID
  RegistrationToken:
    Type: String
    Description: The token used to complete the registration process
    MinLength: 10
    ConstraintDescription: You must use the Tackle-provided registration token
Rules:
  testDeploymentRegion:
    Assertions:
      - Assert:
          !Equals [us-east-1, !Ref AWS::Region]
        AssertionDescription: The Tackle stack must be deployed in the us-east-1 (N. Virginia) region to integrate with AWS Marketplace and Partner Central.
Resources:
  TackleRole:
    Type: AWS::IAM::Role
    Properties:
      Description: "IAM role that enables Tackle to perform AWS Marketplace and Partner Central operations"
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - 'arn:aws:iam::789840733359:role/cloud-connector-aws-vendo-TackleAssumingRole5450219-O5FwSqigW7e4'
            Action:
              - sts:AssumeRole
            Condition:
              StringEquals:
                sts:ExternalId: !Ref ExternalId
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/AWSMarketplaceMeteringFullAccess' # https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSMarketplaceMeteringFullAccess.html
        - 'arn:aws:iam::aws:policy/AWSMarketplaceGetEntitlements' # https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSMarketplaceGetEntitlements.html
        - 'arn:aws:iam::aws:policy/AWSMarketplaceManageSubscriptions' # https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSMarketplaceManageSubscriptions.html
        - 'arn:aws:iam::aws:policy/AWSMarketplaceSellerProductsFullAccess' # https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSMarketplaceSellerProductsFullAccess.html
        - 'arn:aws:iam::aws:policy/AWSPartnerCentralFullAccess' # https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSPartnerCentralFullAccess.html
        - 'arn:aws:iam::aws:policy/AWSMarketplaceSellerFullAccess' # https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSMarketplaceSellerFullAccess.html
  TackleIntrospectionPolicy:
    Type: AWS::IAM::Policy
    Metadata:
      Description: "IAM policy granting Tackle permission to inspect AWS resources for service health and configuration"
    Properties:
      PolicyName: TackleIntrospectionPolicy
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          # IAM policies
          - Effect: Allow
            Action:
              - iam:GetPolicyVersion
              - iam:GetPolicy
            Resource:
              - !Sub "arn:aws:iam::${AWS::AccountId}:policy/Tackle*"
          # IAM roles
          - Effect: Allow
            Action:
              - iam:ListAttachedRolePolicies
              - iam:ListRolePolicies
              - iam:GetRolePolicy
            Resource:
              - !GetAtt TackleRole.Arn
              - !GetAtt TackleAWSEventSinkChannelRole.Arn
              - !GetAtt TackleAWSEventPipelineRegistrationRole.Arn
              - !GetAtt TackleEventBridgeExecutionRole.Arn
          # Lambda functions
          - Effect: Allow
            Action:
              - lambda:GetFunction
              - lambda:GetFunctionConfiguration
            Resource:
              - !GetAtt TackleAWSEventPipelineRegistrationFunction.Arn
          # EventBridge rules
          - Effect: Allow
            Action:
              - events:DescribeRule
              - events:ListTargetsByRule
            Resource:
              - !GetAtt TackleAWSEventRule.Arn
          # EventBridge pipes
          - Effect: Allow
            Action:
              - pipes:DescribePipe
            Resource:
              - !GetAtt TackleAWSEventSinkChannel.Arn
            # SQS queues
          - Effect: Allow
            Action:
              - sqs:GetQueueAttributes
              - sqs:GetQueueUrl
            Resource:
              - !GetAtt TackleAWSEventQueue.Arn
              - !GetAtt TackleSDDSQueue.Arn
              - !GetAtt TackleMarketplaceNotificationsQueue.Arn
            # SNS topics
          - Effect: Allow
            Action:
              - sns:GetTopicAttributes
            Resource:
              - !Ref TackleSDDSTopic
            # S3 buckets
          - Effect: Allow
            Action:
              - s3:GetBucketAcl
              - s3:GetBucketLocation
              - s3:GetBucketPolicy
              - s3:GetBucketVersioning
            Resource:
              - !GetAtt TackleSDDSBucket.Arn
            # KMS key
          - Effect: Allow
            Action:
              - kms:DescribeKey
            Resource:
              - !GetAtt TackleKMSKey.Arn
      Roles:
        - !Ref TackleRole
  TackleCustomMeteringPolicy:
    Type: AWS::IAM::Policy
    Metadata:
      Description: "IAM policy allowing Tackle extended platform control for AWS Marketplace metering operations"
    Properties:
      PolicyName: TackleCustomMeteringPolicy
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - aws-marketplace:BatchMeterUsage
              - aws-marketplace:ResolveCustomer
            Resource: "*"
      Roles:
        - !Ref TackleRole
  TackleCustomMarketplacePortalPolicy:
    Type: AWS::IAM::Policy
    Metadata:
      Description: "IAM policy allowing Tackle access to Marketplace Management Portal and transaction event support"
    Properties:
      PolicyName: TackleCustomMarketplacePortalPolicy
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - aws-marketplace-management:viewSupport
              - aws-marketplace-management:viewReports
              - aws-marketplace-management:uploadFiles
              - aws-marketplace:SearchAgreements
              - aws-marketplace:DescribeAgreement
              - aws-marketplace:GetAgreementTerms
              - aws-marketplace:StartChangeSet
              - aws-marketplace:ListEntities
              - aws-marketplace:DescribeEntity
              - support:CreateCase
            Resource: "*"
      Roles:
        - !Ref TackleRole
  TackleAWSEventRule:
    Type: AWS::Events::Rule
    DependsOn:
      - TackleAWSEventQueuePolicy
      - TackleAWSEventSinkChannelRole # artificial
    Properties:
      Description: "EventBridge rule for Tackle that routes AWS Partner Central and Marketplace events to the SQS queue for processing"
      EventBusName: default
      EventPattern:
        source:
          - prefix: aws.partnercentral
          - aws.metering-marketplace
          - aws.marketplacecatalog
          - aws.agreement-marketplace
      State: ENABLED
      Targets:
        - Arn: !GetAtt TackleAWSEventQueue.Arn
          Id: aws-events-to-queue
          RoleArn: !GetAtt TackleEventBridgeExecutionRole.Arn
  TackleEventBridgeExecutionRole:
    Type: AWS::IAM::Role
    DependsOn:
      - TackleKMSKey
    Properties:
      Description: "IAM role allowing EventBridge to send messages to the event queue for Tackle"
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: TackleAllowSchedulerSQSKMSAccess
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - sqs:SendMessage
                Resource: !GetAtt TackleAWSEventQueue.Arn
              - Effect: Allow
                Action:
                  - kms:Decrypt
                  - kms:GenerateDataKey
                Resource: !GetAtt TackleKMSKey.Arn
  TackleSendHeartbeatSchedule:
    Type: AWS::Scheduler::Schedule
    DependsOn:
      - TackleAWSEventQueuePolicy
    Properties:
      Description: "EventBridge schedule that sends hourly heartbeat events for Tackle to validate pipeline health"
      FlexibleTimeWindow:
        Mode: "OFF"
      ScheduleExpression: "rate(1 hour)"
      State: ENABLED
      Target:
        Arn: 'arn:aws:scheduler:::aws-sdk:sqs:sendMessage'
        RoleArn: !GetAtt TackleSchedulerExecutionRole.Arn
        Input:
          Fn::ToJsonString:
            QueueUrl: !Ref TackleAWSEventQueue
            MessageBody:
              Fn::ToJsonString:
                source: "tackle.cloud-connector-aws"
                detail-type: "heartbeat@v1"
                id: "<aws.scheduler.execution-id>"
                time: "<aws.scheduler.scheduled-time>"
                arn: "<aws.scheduler.schedule-arn>"
                attempt: "<aws.scheduler.attempt-number>"
                region: !Ref AWS::Region
                account: !Ref AWS::AccountId
                detail:
                  stack_id: !Ref AWS::StackId
                  external_id: !Ref ExternalId
  TackleSchedulerExecutionRole:
    Type: AWS::IAM::Role
    DependsOn:
      - TackleKMSKey
    Properties:
      Description: "IAM role allowing EventBridge Scheduler to send messages to the event queue for Tackle"
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: scheduler.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: TackleAllowSchedulerSQSKMSAccess
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - sqs:SendMessage
                Resource: !GetAtt TackleAWSEventQueue.Arn
              - Effect: Allow
                Action:
                  - kms:Decrypt
                  - kms:GenerateDataKey
                Resource: !GetAtt TackleKMSKey.Arn
  TackleSchedulerControlPolicy:
    Type: AWS::IAM::Policy
    Metadata:
      Description: "IAM policy granting Tackle permissions to manage the heartbeat schedule"
    Properties:
      PolicyName: TackleSchedulerControlPolicy
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - scheduler:GetSchedule
              - scheduler:UpdateSchedule
            Resource: !GetAtt TackleSendHeartbeatSchedule.Arn
      Roles:
        - !Ref TackleRole
  TackleAWSEventQueue:
    Type: AWS::SQS::Queue
    Metadata:
      Description: "SQS queue that receives AWS events from EventBridge for processing"
    Properties:
      KmsMasterKeyId: !GetAtt TackleKMSKey.Arn
  TackleAWSEventQueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Metadata:
      Description: "SQS queue policy allowing EventBridge to send messages to the event queue for Tackle"
    Properties:
      PolicyDocument:
        Statement:
          - Action: SQS:SendMessage
            Effect: Allow
            Principal: { Service: events.amazonaws.com }
            Resource: !GetAtt TackleAWSEventQueue.Arn
          - Action: SQS:SendMessage
            Effect: Allow
            Principal: "*"
            Resource: !GetAtt TackleAWSEventQueue.Arn
            Condition:
              ArnEquals:
                aws:SourceArn: !Ref TackleSDDSTopic
          - Action: SQS:SendMessage
            Effect: Allow
            Principal: "*"
            Resource: !GetAtt TackleAWSEventQueue.Arn
            Condition:
              ArnEquals:
                aws:SourceArn: 'arn:aws:sns:us-east-1:287250355862:aws-mp-*' #  This account is owned by AWS, see https://docs.aws.amazon.com/marketplace/latest/userguide/saas-notification.html
      Queues:
        - !Ref TackleAWSEventQueue
  TackleAWSEventSinkChannel:
    DependsOn:
      - TackleAWSEventRule # artificial to inject delay
      - TackleAWSEventSinkChannelResourcePolicy # artificial to inject delay
      - TackleAWSEventPipelineRegistrationFunction # artificial to inject delay
      - TackleKMSKey
    Type: AWS::Pipes::Pipe
    Properties:
      Description: "EventBridge Pipe that forwards events from the SQS queue to Tackle's API destination"
      KmsKeyIdentifier: !GetAtt TackleKMSKey.Arn
      # Tackle will start transmitting your AWS events by starting the pipe once registration is complete.
      DesiredState: STOPPED
      RoleArn: !GetAtt TackleAWSEventSinkChannelRole.Arn
      Source: !GetAtt TackleAWSEventQueue.Arn
      Target: !GetAtt TackleAWSEventSink.Arn
  TackleAWSEventSinkChannelResourcePolicy:
    Type: AWS::IAM::Policy
    Metadata:
      Description: "IAM policy allowing the pipe to invoke API destinations"
    Properties:
      PolicyDocument:
        Statement:
          - Action: events:InvokeApiDestination
            Effect: Allow
            Resource: !GetAtt TackleAWSEventSink.Arn
          - Action: kms:Decrypt
            Effect: Allow
            Resource: !GetAtt TackleKMSKey.Arn
        Version: 2012-10-17
      PolicyName: TackleAWSEventSinkResourcePolicy
      Roles:
        - !Ref TackleAWSEventSinkChannelRole
  TackleAWSEventSinkChannelRole:
    Type: AWS::IAM::Role
    Properties:
      Description: "IAM role enabling the pipe to receive messages from the event queue for Tackle"
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: pipes.amazonaws.com
      Policies:
        - PolicyName: TackleAWSEventSinkChannelPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - sqs:DeleteMessage
                  - sqs:GetQueueAttributes
                  - sqs:ReceiveMessage
                Resource: !GetAtt TackleAWSEventQueue.Arn
              - Effect: Allow
                Action:
                  - kms:Decrypt
                Resource: !GetAtt TackleKMSKey.Arn
  TackleAWSEventSink:
    Type: AWS::Events::ApiDestination
    Properties:
      Description: "EventBridge API destination for forwarding events to Tackle's endpoint"
      ConnectionArn: !GetAtt TackleAWSEventApiKeyConnection.Arn
      HttpMethod: POST
      InvocationEndpoint: https://aws.events.tackle.io/
  TackleAWSEventApiKeyConnection:
    Type: AWS::Events::Connection
    Properties:
      Description: "EventBridge connection containing API key authentication for Tackle's API destination"
      AuthParameters:
        ApiKeyAuthParameters:
          ApiKeyName: Authorization
          ApiKeyValue: "-" # Replaced during registration
      AuthorizationType: API_KEY
      KmsKeyIdentifier: !GetAtt TackleKMSKey.Arn
  TackleAWSEventPipelineRegistration:
    Type: Custom::AWSEventPipelineRegistration
    DependsOn:
      - TackleAWSEventChannelAccessPolicy
      - TackleKMSKey
      - TackleApiKeyManagementPolicy
      - TackleSDDSPolicy
      - TackleAWSEventPipelineRegistrationLogGroup
    Properties:
      Description: "Custom resource that registers the event pipeline and configuration with Tackle"
      AccessRoleArn: !GetAtt TackleRole.Arn
      AWSEventPipeline: !GetAtt TackleAWSEventSinkChannel.Arn
      AWSEventApiKeyContainer: !GetAtt TackleAWSEventApiKeyConnection.Arn
      RegistrationToken: !Ref RegistrationToken
      RegistrationUrl: https://aws.events.tackle.io/api/sources
      StackArn: !Ref AWS::StackId
      TackleSDDSQueueArn: !GetAtt TackleSDDSQueue.Arn
      MarketplaceNotificationsQueueArn: !GetAtt TackleMarketplaceNotificationsQueue.Arn
      EventQueueArn: !GetAtt TackleAWSEventQueue.Arn
      EventPipeArn: !GetAtt TackleAWSEventSinkChannel.Arn
      TackleSDDSBucketArn: !GetAtt TackleSDDSBucket.Arn
      SDDSKeyArn: !GetAtt TackleKMSKey.Arn
      SDDSTopicArn: !Ref TackleSDDSTopic
      ServiceTimeout: 60
      ServiceToken: !GetAtt TackleAWSEventPipelineRegistrationFunction.Arn
      HeartbeatScheduleArn: !GetAtt TackleSendHeartbeatSchedule.Arn
      Features:
        - "assume-role-with-role"
        - "fix-2253"
        - "fix-2824"
        - "fix-2835"
        - "pc-events"
        - "sdds-events"
        - "fix-reseller"
        - "enable-key-rotation"
        - "seller-full"
  TackleAWSEventPipelineRegistrationFunction:
    Type: AWS::Lambda::Function
    Properties:
      Description: "Lambda function that handles the registration of the event pipeline with Tackle"
      Code:
        ZipFile: >
          let response = require("cfn-response");
          let pipeline = "Tackle AWS Event Pipeline";
          let contactTackle = "Contact Tackle Support: https://support.tackle.io/s/contactsupport";

          exports.handler = async (
            event,
            context
          ) => {
            try {
              if (! Object.keys(event.ResourceProperties) ) {
                console.log(
                  `Creation of ${pipeline} failed - ${contactTackle}`
                );
                await asyncSend(
                  event,
                  context,
                  response.FAILED,
                  {}
                );
              } else {
                let requestType = event.RequestType;
                console.log(
                  `${requestType} ${pipeline}`
                );
                if (requestType === "Create" || requestType === "Update") {
                  let accessRoleArn = event.ResourceProperties.AccessRoleArn;
                  let apiKeyContainerArn = event.ResourceProperties.AWSEventApiKeyContainer;
                  let apiKeySecretArn = event.ResourceProperties.AWSEventApiKeySecretArn;
                  let awsEventPipelineArn = event.ResourceProperties.AWSEventPipeline;
                  let registrationToken = event.ResourceProperties.RegistrationToken;
                  let registrationUrl = event.ResourceProperties.RegistrationUrl;
                  let stackArn = event.ResourceProperties.StackArn;
                  let TackleSDDSQueueArn = event.ResourceProperties.TackleSDDSQueueArn
                  let marketplaceNotificationsQueueArn = event.ResourceProperties.MarketplaceNotificationsQueueArn
                  let eventQueueArn = event.ResourceProperties.EventQueueArn;
                  let eventPipeArn = event.ResourceProperties.EventPipeArn;
                  let marketplaceTackleSDDSBucketArn = event.ResourceProperties.TackleSDDSBucketArn;
                  let marketplaceSDDSKeyArn = event.ResourceProperties.SDDSKeyArn;
                  let marketplaceSDDSTopicArn = event.ResourceProperties.SDDSTopicArn;
                  let heartbeatScheduleArn = event.ResourceProperties.HeartbeatScheduleArn;
                  let features = event.ResourceProperties.Features;

                  // Begin Registration
                  let registrationDetails = {
                    "accessRoleARN": accessRoleArn,
                    "connectionARN": apiKeyContainerArn,
                    "eventPipelineARN": awsEventPipelineArn,
                    "features": features,
                    "resourceARNs": {
                      "stackARN": stackArn,
                      "marketplaceSDDSQueueARN": TackleSDDSQueueArn,
                      "marketplaceSDDSKeyARN": marketplaceSDDSKeyArn,
                      "marketplaceSDDSTopicARN": marketplaceSDDSTopicArn,
                      "marketplaceSDDSBucketARN": marketplaceTackleSDDSBucketArn,
                      "marketplaceNotificationsQueueARN": marketplaceNotificationsQueueArn,
                      "eventQueueARN": eventQueueArn,
                      "eventPipeARN": eventPipeArn,
                      "heartbeatScheduleARN": heartbeatScheduleArn
                    }

                  };
                  console.log(
                    `Sending Registration details to ${registrationUrl}`
                  );
                  let registrationConfig = {
                    method: "POST",
                    headers: {
                      "Accept": "application/json",
                      "Content-Type": "application/json",
                      "Authorization": registrationToken
                    },
                    body: JSON.stringify(
                      registrationDetails
                    )
                  };
                  let results = await fetch(
                    registrationUrl,
                    registrationConfig
                  );

                  // Confirm Registration
                  if (results.ok) {
                    console.log(
                      `The Registration was sent successfully to Tackle`
                    );
                    await asyncSend(
                      event,
                      context,
                      response.SUCCESS,
                      {}
                    );
                  } else {
                    console.log(
                      `The Registration was not sent successfully to Tackle - ${contactTackle}`
                    );
                    await asyncSend(
                      event,
                      context,
                      response.FAILED,
                      {}
                    );
                  } // End Confirm Registration
                } else if (requestType === "Delete") {
                  console.log(
                    `${pipeline} has been deleted. ${contactTackle}`
                  );
                } // End Acting Based On Request Types

                await asyncSend(
                  event,
                  context,
                  response.SUCCESS,
                  {}
                );
              }  // End attempting to send Registration details
            } catch(error) {
              console.log(
                "Registration function failed"
              );
              console.log(
                error
              );

              await asyncSend(
                event,
                context,
                response.FAILED,
                {}
              );
            } // End try catch
          };  // End registration function

          function asyncSend(
            event,
            context,
            status,
            data
          ) {
            return new Promise( () => { response.send(event, context, status, data) });
          }
      Environment:
        Variables:
          AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1'
      Handler: index.handler
      Role: !GetAtt TackleAWSEventPipelineRegistrationRole.Arn
      Runtime: nodejs22.x
      Architectures:
        - arm64
      MemorySize: 128
      Timeout: 15
      LoggingConfig:
        LogGroup: !Ref TackleAWSEventPipelineRegistrationLogGroup
    DependsOn:
      - TackleAWSEventPipelineRegistrationRole
      - TackleAWSEventPipelineRegistrationLogGroup
  TackleAWSEventPipelineRegistrationLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      RetentionInDays: 90
  TackleAWSEventPipelineRegistrationRole:
    Type: AWS::IAM::Role
    Properties:
      Description: "IAM role allowing the Tackle registration Lambda function to execute"
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
        Version: 2012-10-17
      ManagedPolicyArns:
        - !Join
            - ''
            - - 'arn:'
              - !Ref AWS::Partition
              - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
  TackleAWSEventChannelAccessPolicy:
    Type: AWS::IAM::Policy
    Metadata:
      Description: "IAM policy granting Tackle permissions to start and stop the event pipe"
    Properties:
      PolicyName: TackleAWSEventChannelAccessPolicy
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - pipes:StartPipe
              - pipes:StopPipe
            Resource: !GetAtt TackleAWSEventSinkChannel.Arn
      Roles:
        - !Ref TackleRole
  TackleApiKeyManagementPolicy:
    Type: AWS::IAM::Policy
    Metadata:
      Description: "IAM policy allowing Tackle to manage the API key connection and its secrets"
    Properties:
      PolicyName: TackleApiKeyManagementPolicy
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - events:UpdateConnection
            Resource:
              - !GetAtt TackleAWSEventApiKeyConnection.ArnForPolicy
          - Effect: Allow
            Action:
              - secretsmanager:GetSecretValue
              - secretsmanager:PutSecretValue
              - secretsmanager:UpdateSecret
            Resource: !GetAtt TackleAWSEventApiKeyConnection.SecretArn
          - Effect: Allow
            Action:
              - kms:Encrypt
              - kms:GenerateDataKey*
            Resource: !GetAtt TackleKMSKey.Arn
      Roles:
        - !Ref TackleRole
  TackleKMSKey:
    Type: AWS::KMS::Key
    Properties:
      Description: "KMS key used for encrypting AWS Marketplace and Partner Central data in S3 buckets, SQS queues, and SNS topics for Tackle"
      Tags: # EventBridge requires a tag to be set for encryption https://docs.aws.amazon.com/eventbridge/latest/userguide/encryption-connections.html#encryption-connections-key-policy
        - Key: EventBridgeApiDestinations
          Value: "true"
      EnableKeyRotation: true
      KeyPolicy:
        Version: 2012-10-17
        Id: TackleKMSKeyPolicy
        Statement:
          - Sid: Owner Access
            Effect: Allow
            Action:
              - kms:*
            Principal:
              AWS:
                - !Sub 'arn:aws:iam::${AWS::AccountId}:root'
            Resource: "*"
          - Sid: Allow Service Access
            Effect: Allow
            Action:
              - kms:Decrypt
              - kms:DescribeKey
              - kms:Encrypt
              - kms:GenerateDataKey*
              - kms:ReEncrypt*
            Principal:
              Service:
              - reports.marketplace.amazonaws.com
              - pipes.amazonaws.com
              - sqs.amazonaws.com
              - sns.amazonaws.com
            Resource: "*"
          - Sid: Allow EventBridge Access
            Effect: Allow
            Action:
              - kms:Decrypt
              - kms:DescribeKey
              - kms:GenerateDataKey*
            Principal:
              Service: events.amazonaws.com
            Resource: "*"
            Condition:
              StringLike:
                kms:ViaService: "secretsmanager.*.amazonaws.com"
                kms:EncryptionContext:SecretARN:
                  - "arn:aws:secretsmanager:*:*:secret:events!connection/*"
          - Sid: Allow Tackle Role Access
            Effect: Allow
            Action:
              - kms:DescribeKey
            Principal:
              AWS:
                - !GetAtt TackleRole.Arn
            Resource: "*"
  TackleSDDSBucket:
    Type: AWS::S3::Bucket
    Metadata:
      Description: "S3 bucket for storing AWS Marketplace SDDS files with KMS encryption for Tackle"
    Properties:
      BucketEncryption:
        # Note on PublicAccessBlockConfiguration: public access blocks are the default for new buckets
        # as of April 2023. Some customers have SCPs that prevent explicitly setting PublicAccessBlockConfiguration,
        # so we need to omit it from the template. This does not compromise the security posture of the
        # bucket due to the default bucket settings.
        #
        # See https://aws.amazon.com/about-aws/whats-new/2022/12/amazon-s3-automatically-enable-block-public-access-disable-access-control-lists-buckets-april-2023/
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: aws:kms
              KMSMasterKeyID: !GetAtt TackleKMSKey.Arn
  TackleSDDSBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Metadata:
      Description: "S3 bucket policy controlling access to AWS Marketplace SDDS files and enforcing encryption for Tackle"
    Properties:
      Bucket:
        !Ref TackleSDDSBucket
      PolicyDocument:
        Statement:
          - Sid: AwsMarketplaceDataFeedsAccess
            Effect: Allow
            Action:
              - s3:ListBucket
              - s3:GetObject
              - s3:PutObject
              - s3:GetEncryptionConfiguration
              - s3:GetBucketAcl
              - s3:PutObjectAcl
            Resource:
              - !Sub "arn:aws:s3:::${TackleSDDSBucket}"
              - !Sub "arn:aws:s3:::${TackleSDDSBucket}/*"
            Principal:
              Service: reports.marketplace.amazonaws.com
          - Sid: DenyIncorrectEncryptionHeader
            Effect: Deny
            Principal: "*"
            Action: s3:PutObject
            Resource:
              !Sub "arn:aws:s3:::${TackleSDDSBucket}/*"
            Condition:
              StringNotEquals:
                s3:x-amz-server-side-encryption: aws:kms
          - Sid: DenyUnencryptedObjectUploads
            Effect: Deny
            Principal: "*"
            Action: s3:PutObject
            Resource:
              !Sub "arn:aws:s3:::${TackleSDDSBucket}/*"
            Condition:
              "Null":
                s3:x-amz-server-side-encryption: "true"
          - Sid: DenyInsecureRequestsToTackleSDDSBucketResources
            Effect: Deny
            Action: s3:*
            Principal: "*"
            Resource:
              - !Sub "arn:aws:s3:::${TackleSDDSBucket}"
              - !Sub "arn:aws:s3:::${TackleSDDSBucket}/*"
            Condition:
              Bool:
                aws:SecureTransport: false
  TackleSDDSTopic:
    Type: AWS::SNS::Topic
    Metadata:
      Description: "SNS topic for receiving AWS Marketplace SDDS notifications for Tackle"
    Properties:
      KmsMasterKeyId: !GetAtt TackleKMSKey.Arn
  TackleSDDSTopicPolicy:
    Type: AWS::SNS::TopicPolicy
    Metadata:
      Description: "SNS topic policy allowing AWS Marketplace to publish SDDS notifications for Tackle"
    Properties:
      PolicyDocument:
        Statement:
          - Sid: AwsMarketplaceDataFeedsAccess
            Effect: Allow
            Principal:
              Service: reports.marketplace.amazonaws.com
            Action: sns:Publish
            Resource: !Ref TackleSDDSTopic
      Topics:
        - !Ref TackleSDDSTopic
  TackleSDDSQueue:
    Type: AWS::SQS::Queue
    Metadata:
      Description: "SQS queue for processing AWS Marketplace SDDS notifications for Tackle"
    Properties:
      KmsMasterKeyId: !GetAtt TackleKMSKey.Arn
      SqsManagedSseEnabled: false # Explicitly disable SQS managed SSE since we are using KMS
  TackleSDDSSubscription:
    Type: AWS::SNS::Subscription
    Metadata:
      Description: "SNS subscription connecting the AWS Marketplace SDDS topic to the AWS Marketplace SDDS queue"
    Properties:
      TopicArn: !Ref TackleSDDSTopic
      Endpoint: !GetAtt TackleSDDSQueue.Arn
      Protocol: sqs
  TackleSDDSEventsSubscription:
    Type: AWS::SNS::Subscription
    Metadata:
      Description: "SNS subscription connecting the AWS Marketplace SDDS topic to the Tackle Events queue"
    Properties:
      TopicArn: !Ref TackleSDDSTopic
      Endpoint: !GetAtt TackleAWSEventQueue.Arn
      Protocol: sqs
  TackleSDDSQueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Metadata:
      Description: "SQS queue policy allowing the AWS Marketplace SDDS topic to send messages to the AWS Marketplace SDDS queue"
    Properties:
      PolicyDocument:
        Id: TackleSDDSQueuePolicy
        Version: 2012-10-17
        Statement:
        - Sid: AllowSNSSendMessage
          Effect: Allow
          Principal: "*"
          Action:
          - sqs:SendMessage
          Resource: !GetAtt TackleSDDSQueue.Arn
          Condition:
            ArnEquals:
              aws:SourceArn: !Ref TackleSDDSTopic
      Queues:
      - !Ref TackleSDDSQueue
  TackleSDDSPolicy:
    Type: AWS::IAM::Policy
    Metadata:
      Description: "IAM policy enabling Tackle to manage and process AWS Marketplace SDDS data"
    Properties:
      PolicyName: TackleSDDSPolicy
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - sqs:DeleteMessage
              - sqs:GetQueueUrl
              - sqs:ChangeMessageVisibility
              - sqs:ReceiveMessage
              - sqs:SendMessage
              - sqs:GetQueueAttributes
              - sqs:ListQueueTags
              - sqs:ListDeadLetterSourceQueues
            Resource: !GetAtt TackleSDDSQueue.Arn
          - Effect: Allow
            Action:
              - s3:GetObject
              - s3:ListBucket
            Resource:
              - !Sub "arn:aws:s3:::${TackleSDDSBucket}/*"
              - !Sub "arn:aws:s3:::${TackleSDDSBucket}"
          - Effect: Allow
            Action:
              - kms:Decrypt
            Resource:
              - !GetAtt TackleKMSKey.Arn
      Roles:
        - !Ref TackleRole
  TackleMarketplaceSNSPolicy:
    Type: AWS::IAM::Policy
    Metadata:
      Description: "IAM policy allowing Tackle to manage AWS Marketplace notifications"
    Properties:
      PolicyName: TackleSNSIAMPolicy
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - sns:Publish
              - sns:ListSubscriptionsByTopic
              - sns:Subscribe
              - sns:Unsubscribe
            Resource: !Ref TackleSDDSTopic
          - Effect: Allow
            Action:
              - sns:Subscribe
              - sns:Unsubscribe
              - sns:ListSubscriptionsByTopic
              - sns:ConfirmSubscription
              - sns:GetTopicAttributes
            Resource: 'arn:aws:sns:us-east-1:287250355862:aws-mp-*'
      Roles:
        - !Ref TackleRole
  TackleMarketplaceNotificationsQueue:
    Type: AWS::SQS::Queue
    Metadata:
      Description: "SQS queue for Tackle to process general AWS Marketplace notifications"
    Properties:
      KmsMasterKeyId: !GetAtt TackleKMSKey.Arn
      SqsManagedSseEnabled: false # Explicitly disable SQS managed SSE since we are using KMS
  TackleMarketplaceNotificationsQueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Metadata:
      Description: "SQS queue policy controlling access to the Marketplace notifications queue"
    Properties:
      PolicyDocument:
        Id: TackleMarketplaceNotificationsQueuePolicy
        Version: 2012-10-17
        Statement:
        - Sid: ChangeVisibilityDeleteReceiveMessage
          Effect: Allow
          Principal:
            AWS:
            - !Sub "arn:aws:iam::${AWS::AccountId}:role/${TackleRole}"
          Action:
          - sqs:ChangeMessageVisibility
          - sqs:DeleteMessage
          - sqs:ReceiveMessage
          Resource: !GetAtt TackleMarketplaceNotificationsQueue.Arn
        - Sid: AllowSNSSendMessage
          Effect: Allow
          Principal: "*"
          Action:
          - sqs:SendMessage
          Resource: !GetAtt TackleMarketplaceNotificationsQueue.Arn
          Condition:
            ArnEquals:
              aws:SourceArn: 'arn:aws:sns:us-east-1:287250355862:aws-mp-*' #  This account is owned by AWS, see https://docs.aws.amazon.com/marketplace/latest/userguide/saas-notification.html
      Queues:
      - !Ref TackleMarketplaceNotificationsQueue
