AWS CloudFormation

Getting started with CloudFormation

You can use this CloudFormation file to set up the IAM role, IAM policies, S3 bucket, and SNS topic to integrate with Scanner.

You can get the ScannerAwsAccountId and ScannerExternalId from your Scanner support engineer or from the Scanner UI.

The CloudFormation file is available here for copy-paste, or you can download it from this link:

cloudformation/2025-12/scanner.ymlarrow-up-right

After deploying the CloudFormation stack, see Setting up bucket notifications to enable EventBridge on your S3 buckets.

---
AWSTemplateFormatVersion: "2010-09-09"

Description: Scanner S3 Indexing Integration

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Scanner Auth Parameters
        Parameters:
          - ScannerAwsAccountId
          - ScannerExternalId
      - Label:
          default: S3 Bucket Parameters
        Parameters:
          - S3CollectRuleDestinationBuckets
          - S3IndexRuleSourceBuckets
          - S3CollectRuleDestinationBucketsKmsKeyArns
          - S3IndexRuleSourceBucketsKmsKeyArns
    ParameterLabels:
      ScannerAwsAccountId:
        default: Scanner AWS Account ID
      ScannerExternalId:
        default: Scanner External ID
      S3CollectRuleDestinationBuckets:
        default: S3 Collect Rule Destination Buckets
      S3IndexRuleSourceBuckets:
        default: S3 Index Rule Source Buckets
      S3CollectRuleDestinationBucketsKmsKeyArns:
        default: KMS Keys for Collect Rule Destination Buckets
      S3IndexRuleSourceBucketsKmsKeyArns:
        default: KMS Keys for Index Rule Source Buckets

Parameters:
  ScannerAwsAccountId:
    Description: Scanner provides its AWS Account ID to use here.
    Type: String
    MinLength: 12
    MaxLength: 12
    AllowedPattern: "^[0-9]{12}$"
    ConstraintDescription: Must be a valid 12-digit AWS account ID.
  ScannerExternalId:
    Description: Scanner provides an External ID to use here.
    Type: String
    MinLength: 1
    ConstraintDescription: External ID cannot be empty.
  S3CollectRuleDestinationBuckets:
    Description: Names of S3 buckets used as destinations for Collect Rules (i.e. Scanner will write your raw logs into). Comma-separated.
    Type: CommaDelimitedList
    Default: ""
  S3IndexRuleSourceBuckets:
    Description: Names of S3 buckets used as the source of Index Rules (i.e. from which the raw logs will be ingested to Scanner indexes and become queryable). Include the bucket in both if it is also a Collect Rule destination. Comma-separated.
    Type: CommaDelimitedList
    Default: ""
  S3CollectRuleDestinationBucketsKmsKeyArns:
    Description: ARNs of the KMS keys used to encrypt any of the Collect Rule destination buckets. Not needed if the buckets do not use KMS keys. Comma-separated.
    Type: CommaDelimitedList
    Default: ""
  S3IndexRuleSourceBucketsKmsKeyArns:
    Description: ARNs of the KMS keys used to encrypt any of the Index Rule source buckets. Not needed if the buckets do not use KMS keys. Comma-separated.
    Type: CommaDelimitedList
    Default: ""

Conditions:
  HasS3CollectRuleDestinationBuckets:
    !Not [!Equals [!Join ["", !Ref S3CollectRuleDestinationBuckets], ""]]
  HasS3IndexRuleSourceBuckets:
    !Not [!Equals [!Join ["", !Ref S3IndexRuleSourceBuckets], ""]]
  HasS3CollectRuleDestinationBucketsKmsKeyArns:
    !Not [
      !Equals [!Join ["", !Ref S3CollectRuleDestinationBucketsKmsKeyArns], ""],
    ]
  HasS3IndexRuleSourceBucketsKmsKeyArns:
    !Not [!Equals [!Join ["", !Ref S3IndexRuleSourceBucketsKmsKeyArns], ""]]

Resources:
  ScannerRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "scnr-ScannerRole"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                Ref: ScannerAwsAccountId
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                sts:ExternalId:
                  Ref: ScannerExternalId

  ScannerIndexFilesBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: "Retain"
    Properties:
      BucketName: !Sub
        - "scnr-index-files-${Suffix}"
        - Suffix: !Ref ScannerExternalId
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - BucketKeyEnabled: true
            ServerSideEncryptionByDefault:
              SSEAlgorithm: "aws:kms"
      LifecycleConfiguration:
        Rules:
          - Id: ExpireTagging
            Status: Enabled
            TagFilters:
              - Key: "Scnr-Lifecycle"
                Value: "expire"
            ExpirationInDays: 1
          - Id: AbortIncompleteMultiPartUploads
            Status: Enabled
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 1

  LogsBucketEventNotificationTopic:
    Type: AWS::SNS::Topic
    TopicName: "scnr-LogsBucketEventNotificationTopic"

  LogsBucketEventNotificationTopicPolicy:
    Type: AWS::SNS::TopicPolicy
    Properties:
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: s3.amazonaws.com
            Action: sns:Publish
            Resource: "*"
      Topics:
        - !Ref LogsBucketEventNotificationTopic

  LogsBucketEventNotificationTopicSubscription:
    Type: AWS::SNS::Subscription
    Properties:
      TopicArn: !Ref LogsBucketEventNotificationTopic
      Endpoint: !Sub
        - "arn:aws:sqs:${InstanceRegion}:${ScannerAWSAccountID}:scnr-S3ObjectCreatedNotificationsQueue"
        - InstanceRegion: !Ref "AWS::Region"
          ScannerAWSAccountID: !Ref ScannerAWSAccountID
      Protocol: "sqs"
      RawMessageDelivery: true

  ScannerRolePolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: scnr-ScannerRolePolicy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          # Basic S3 permissions for all buckets
          - Effect: Allow
            Action:
              - s3:GetBucketLocation
              - s3:GetBucketTagging
              - s3:ListAllMyBuckets
            Resource: "*"
          # Scanner will write the processed index files to the Index Files Bucket.
          # Delete permission is needed as Scanner compacts and expires files in
          # this bucket periodically.
          - Effect: Allow
            Action:
              - s3:DeleteObject
              - s3:DeleteObjectTagging
              - s3:DeleteObjectVersion
              - s3:DeleteObjectVersionTagging
              - s3:GetEncryptionConfiguration
              - s3:GetLifecycleConfiguration
              - s3:GetObject
              - s3:GetObjectTagging
              - s3:ListBucket
              - s3:PutObject
              - s3:PutObjectTagging
            Resource:
              - Fn::Sub: "arn:aws:s3:::scnr-index-files-${ScannerExternalId}"
              - Fn::Sub: "arn:aws:s3:::scnr-index-files-${ScannerExternalId}/*"
          # As destinations for Collect Rules, Scanner will write raw log files
          # into these buckets.
          - !If
            - HasS3CollectRuleDestinationBuckets
            - Effect: Allow
              Action:
                - s3:GetBucketNotification
                - s3:GetEncryptionConfiguration
                - s3:GetLifecycleConfiguration
                - s3:GetObject
                - s3:GetObjectTagging
                - s3:ListBucket
                - s3:PutObject
                - s3:PutObjectTagging
              Resource:
                Fn::Split:
                  - ","
                  - Fn::Join:
                      - ","
                      - - Fn::Sub:
                            - "arn:aws:s3:::${Buckets}"
                            - Buckets:
                                !Join [
                                  ",arn:aws:s3:::",
                                  !Ref S3CollectRuleDestinationBuckets,
                                ]
                        - Fn::Sub:
                            - "arn:aws:s3:::${Buckets}/*"
                            - Buckets:
                                !Join [
                                  "/*,arn:aws:s3:::",
                                  !Ref S3CollectRuleDestinationBuckets,
                                ]
            - !Ref "AWS::NoValue"
          # Scanner will test the integration by writing/deleting test files
          # into/from Collect Rule destination buckets.
          - !If
            - HasS3CollectRuleDestinationBuckets
            - Effect: Allow
              Action:
                - s3:DeleteObject
                - s3:DeleteObjectTagging
                - s3:DeleteObjectVersion
                - s3:DeleteObjectVersionTagging
              Resource:
                Fn::Split:
                  - ","
                  - Fn::Sub:
                      - "arn:aws:s3:::${Buckets}/*ScannerTestFile"
                      - Buckets:
                          !Join [
                            "/*ScannerTestFile,arn:aws:s3:::",
                            !Ref S3CollectRuleDestinationBuckets,
                          ]
            - !Ref "AWS::NoValue"
          # As sources for Index Rules, Scanner will read the raw log files from
          # these buckets.
          - !If
            - HasS3IndexRuleSourceBuckets
            - Effect: Allow
              Action:
                - s3:GetBucketNotification
                - s3:GetEncryptionConfiguration
                - s3:GetObject
                - s3:GetObjectTagging
                - s3:ListBucket
              Resource:
                Fn::Split:
                  - ","
                  - Fn::Join:
                      - ","
                      - - Fn::Sub:
                            - "arn:aws:s3:::${Buckets}"
                            - Buckets:
                                !Join [
                                  ",arn:aws:s3:::",
                                  !Ref S3IndexRuleSourceBuckets,
                                ]
                        - Fn::Sub:
                            - "arn:aws:s3:::${Buckets}/*"
                            - Buckets:
                                !Join [
                                  "/*,arn:aws:s3:::",
                                  !Ref S3IndexRuleSourceBuckets,
                                ]
            - !Ref "AWS::NoValue"
          # If a Collect Rule destination bucket is encrypted by a Customer
          # Managed KMS key, the Scanner Role will need permission to encrypt
          # with the key when writing raw log files into the bucket.
          - !If
            - HasS3CollectRuleDestinationBucketsKmsKeyArns
            - Effect: Allow
              Action:
                - kms:DescribeKey
                - kms:GenerateDataKey
              Resource: !Ref S3CollectRuleDestinationBucketsKmsKeyArns
            - !Ref "AWS::NoValue"
          # If an Index Rule source bucket is encrypted by a Customer Managed
          # KMS key, the Scanner Role will need permission to decrypt with the
          # key when reading/ingesting the raw log files from the bucket.
          - !If
            - HasS3IndexRuleSourceBucketsKmsKeyArns
            - Effect: Allow
              Action:
                - kms:Decrypt
                - kms:DescribeKey
              Resource: !Ref S3IndexRuleSourceBucketsKmsKeyArns
            - !Ref "AWS::NoValue"
      Roles:
        - Ref: ScannerRole

Outputs:
  ScannerRoleARN:
    Description: The ARN of the new Scanner IAM Role
    Value:
      Fn::GetAtt:
        - ScannerRole
        - Arn

  ScannerIndexFilesBucketName:
    Description: The name of the new S3 bucket that will store Scanner index files
    Value: !Ref ScannerIndexFilesBucket

Permissions needed to launch the CloudFormation template

To launch (and rollback) the template successfully, your IAM role will need the following IAM permissions.

If your S3 buckets are in multiple regions

If the S3 buckets that you want to index are in multiple regions, edit the CloudFormation file to do the following:

  • Create one AWS::SNS::Topic per region.

  • Create an AWS::SNS::Subscription for each SNS topic, all pointing to the same SQS queue in your Scanner instance.

(Optional) If you want to use KMS encryption for your SNS notification topic

Setting up bucket notifications

To allow Scanner to index log files continuously, you will need to configure your S3 buckets to send "object created" notifications to an SNS topic created by CloudFormation earlier, which will forward the notifications to an SQS queue running in your Scanner instance. Setting up bucket notifications cannot be done directly in CloudFormation without the CloudFormation stack taking full ownership of the S3 bucket, which we don't necessarily want.

Here is how to set up bucket event notifications manually using the AWS console.

Navigate to S3 > (Click on your bucket) > Properties.

Scroll down to Event notifications.

Click Create event notification

Give it an Event name. Optionally provide a Prefix and Suffix to filter down to a specific set of files.

Select the checkbox next to All object create events - s3:ObjectCreated:*.

Scroll down to Destination. Select SNS topic. Under Specify SNS topic, select Choose from your SNS topics. Select the SNS topic created by Scanner's CloudFormation template earlier:

This SNS topic is already configured with a subscription to push bucket event notifications to Scanner's SQS queue.

Click Save changes.

Object creation notifications should now be sent to your Scanner instance.

What if I already have a conflicting destination for bucket event notifications?

You might be sending bucket notifications to a different destination already, like to the SQS queue of another vendor, to a different SNS topic, or to a Lambda function.

To resolve this situation, we recommend the following configuration:

  • Configure the bucket to push event notifications to an SNS topic.

  • Configure the SNS topic to relay messages to multiple destinations by creating subscriptions:

    • Create a topic subscription for Scanner

      • Protocol: SQS

      • Endpoint: ARN of your Scanner instance's SQS queue. Your contacts on the Scanner team can give this to you.

      • Enable raw message delivery.

    • Create topic subscriptions for your other vendors and destinations.

Last updated

Was this helpful?