AWS CloudTrail

You can integrate with AWS CloudTrail as a telemetry source with an S3 bucket and SNS. We use a CloudFormation template to provision the necessary resources and grant our worker role read-only access to ingest logs. This ensures your data remains in your control while allowing us to provide security insights.

There are two ways to integrate:

  • Existing Bucket & SNS - for users who already have CloudTrail configured with S3 and SNS.
  • Creating a New Trail & Bucket - for users who need to set up a new CloudTrail configuration from scratch.
📘

Prerequisites

  • Administrator access to the AWS Console

Additional prerequisites with an existing S3 bucket:

  • An existing CloudTrail configured that is sending data to S3
  • An SNS topic that publishes events when logs are delivered to the bucket
  • If you don't have an SNS topic set up, you can create one using the AWS-provided SNS topic template. Note: There will be additional steps in the CloudFormation outputs for adding KMS policy and S3 event notifications
CriteriaExisting BucketNew Trail
Current SetupCloudTrail + S3 + SNS already configuredNo existing CloudTrail
Resources CreatedSQS Queue, IAM RoleS3 Bucket, CloudTrail, SNS, SQS, KMS Key, IAM Role
Template Filecloud_formation_existing_bucket.yamlcloud_formation.yaml

Integrate an Existing Bucket

Use this option if you already have CloudTrail configured with an S3 bucket and SNS topic. This template creates only the resources needed to grant Nebulock read access to your existing infrastructure.

Copy the CloudFormation template below and save it as cloud_formation_existing_bucket.yaml on your computer.

AWSTemplateFormatVersion: '2010-09-09'
Description: Creates SQS queue, IAM role, and permissions for Nebulock to read CloudTrail logs from your existing S3 bucket and SNS topic

# --- Parameters ---
Parameters:
  S3BucketName:
    Type: String
    Description: Name of your existing S3 bucket where CloudTrail logs are stored.

  SNSTopicArn:
    Type: String
    Description: ARN of your existing SNS topic that receives S3 event notifications for CloudTrail logs.

  NebulockAccountId:
    Type: String
    Description: The AWS account ID of the Nebulock account.
    Default: "061039802247"

  ExternalId:
    Type: String
    Description: "Unique identifier used as an 'sts:ExternalId' to restrict unauthorized cross-account role assumption."

  KMSKeyArn:
    Type: String
    Description: "(Optional) ARN of the KMS key used to encrypt the S3 bucket and SNS/SQS. Leave blank if not KMS-encrypted."
    Default: ""

  UseKMSForSQS:
    Type: String
    Description: "Whether to encrypt the SQS queue with KMS. Set to false if SNS cannot send messages due to cross-account KMS permissions."
    Default: "true"
    AllowedValues:
      - "true"
      - "false"

# --- Conditions ---
Conditions:
  HasKMSKey: !Not [!Equals [!Ref KMSKeyArn, ""]]
  UseSQSEncryption: !And
    - !Condition HasKMSKey
    - !Equals [!Ref UseKMSForSQS, "true"]

# --- Resources ---
Resources:
  # 1. SQS Queue for Processing S3 Events (subscribes to existing SNS topic)
  CloudTrailLogSQSQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: 'nebulock-cloudtrail-sqs'
      VisibilityTimeout: 300
      MessageRetentionPeriod: 1209600  # 14 days
      ReceiveMessageWaitTimeSeconds: 20  # Enable long polling
      KmsMasterKeyId: !If [UseSQSEncryption, !Ref KMSKeyArn, !Ref AWS::NoValue]

  # 2. SQS Queue Policy (allow SNS to send messages)
  CloudTrailLogSQSQueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Properties:
      Queues:
        - !Ref CloudTrailLogSQSQueue
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: AllowSNSToSendMessages
            Effect: Allow
            Principal:
              Service: sns.amazonaws.com
            Action: 'sqs:SendMessage'
            Resource: !GetAtt CloudTrailLogSQSQueue.Arn
            Condition:
              ArnEquals:
                'aws:SourceArn': !Ref SNSTopicArn

  # 3. Subscribe SQS to existing SNS topic (fan-out pattern)
  CloudTrailLogSQSSubscription:
    Type: AWS::SNS::Subscription
    Properties:
      Protocol: sqs
      TopicArn: !Ref SNSTopicArn
      Endpoint: !GetAtt CloudTrailLogSQSQueue.Arn
      RawMessageDelivery: true

  # 4. IAM Role for Nebulock Read Access
  NebulockReadRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "Nebulock-Read-Access-Role"
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          # Allow worker role to assume with ExternalId
          - Sid: AllowWorkerAssumeRole
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${NebulockAccountId}:role/eks-pod-identity-intake-worker-app-role'
            Action: "sts:AssumeRole"
            Condition:
              StringEquals:
                "sts:ExternalId": !Ref ExternalId
          # Allow worker role to tag session (no ExternalId condition)
          - Sid: AllowWorkerTagSession
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${NebulockAccountId}:role/eks-pod-identity-intake-worker-app-role'
            Action: "sts:TagSession"
          # Allow orchestrator role to assume with ExternalId
          - Sid: AllowOrchestratorAssumeRole
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${NebulockAccountId}:role/eks-pod-identity-intake-orchestrator-app-role'
            Action: "sts:AssumeRole"
            Condition:
              StringEquals:
                "sts:ExternalId": !Ref ExternalId
          # Allow orchestrator role to tag session (no ExternalId condition)
          - Sid: AllowOrchestratorTagSession
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${NebulockAccountId}:role/eks-pod-identity-intake-orchestrator-app-role'
            Action: "sts:TagSession"
      Policies:
        # S3 Read Access for Worker
        - PolicyName: S3ReadAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 's3:GetObject'
                  - 's3:ListBucket'
                Resource:
                  - !Sub "arn:aws:s3:::${S3BucketName}"
                  - !Sub "arn:aws:s3:::${S3BucketName}/*"
        # SQS Read Access for Orchestrator
        - PolicyName: SQSReadAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'sqs:ReceiveMessage'
                  - 'sqs:DeleteMessage'
                  - 'sqs:GetQueueAttributes'
                  - 'sqs:GetQueueUrl'
                Resource:
                  - !GetAtt CloudTrailLogSQSQueue.Arn
        # KMS Decrypt Access for S3 (always added if KMSKeyArn provided, regardless of SQS encryption)
        - !If
          - HasKMSKey
          - PolicyName: KMSDecryptAccess
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - 'kms:Decrypt'
                    - 'kms:DescribeKey'
                  Resource: !Ref KMSKeyArn
          - !Ref AWS::NoValue

# --- Outputs ---
Outputs:
  CloudTrailSQSQueueUrl:
    Description: URL of the SQS queue for CloudTrail log notifications.
    Value: !Ref CloudTrailLogSQSQueue
  NebulockReadRoleArn:
    Description: ARN of the IAM Role that Nebulock will assume to read CloudTrail logs.
    Value: !GetAtt NebulockReadRole.Arn
  ExternalId:
    Description: Unique identifier used as an 'sts:ExternalId' to restrict unauthorized cross-account role assumption.
    Value: !Ref ExternalId

Log in to your AWS Management Console.

Navigate to the CloudFormation service.

Click Create stack > With new resources.

Select Upload a template file, choose the cloud_formation_existing_bucket.yaml file, and click Next.

Configure the stack parameters:

  1. Stack Name: Assign any name you prefer for this deployment (e.g., Nebulock-CloudTrail-Ingest).

  2. External ID: Provide a unique string of your choice; this acts as a shared secret to secure the cross-account connection.

  3. S3 Bucket Name: Enter the name of the existing S3 bucket where your CloudTrail logs are stored.

  4. SNS Topic ARN: Provide the ARN of the SNS topic that is currently receiving S3 event notifications for your log bucket.

  5. KMS Key ARN (Crucial): * If your logs are encrypted, retrieve the ARN from your CloudTrail console under Trail > General details > KMS key ID.

  6. Leave this blank if your bucket uses default SSE-S3 encryption.

  7. Nebulock Account ID: Leave this as the default value provided in the template.

  8. KMS for SQS: Only provide the KMS ARN if your SNS topic is already using that key. This ensures SNS has the necessary permissions to deliver encrypted messages into your new SQS queue.

In Configure stack options, no changes are needed. Click Next and then Create.

Monitor the Events tab to ensure the stack is created correctly.

Once the stack shows CREATE_COMPLETE, navigate to the Outputs tab to find the values needed for the Nebulock integration.

Go to your Nebulock platform and navigate to Integrations > Available Integrations, select "CloudTrail" and paste the values you gathered in the previous step.

Create a New Trail & Bucket

Use this option if you do not have an existing CloudTrail configuration. This template creates a complete setup including a new trail, S3 bucket, SNS/SQS queues, and KMS encryption.

Copy the CloudFormation template below and save it as cloud_formation.yaml on your computer.

AWSTemplateFormatVersion: '2010-09-09'
Description: Centralized CloudTrail Log Collection with Cross-Account Read Access

# --- Parameters ---
Parameters:
  BucketName:
    Type: String
    Description: Name of the S3 bucket to store CloudTrail logs (must be globally unique).
    Default: "cloudtrail-logs-centralized-yourcompanyname"

  TrailName:
    Type: String
    Description: The name of the CloudTrail trail to create.
    Default: "Nebulock-Integration-Trail"

  NebulockAccountId:
    Type: String
    Description: The AWS account ID of the Nebulock account.
    Default: "061039802247"

  ExternalId:
    Type: String
    Description: "Unique identifier used as an 'sts:ExternalId' to restrict unauthorized cross-account role assumption."

# --- Resources ---
Resources:
  # 1. KMS Key for S3 Encryption
  CloudTrailKMSKey:
    Type: AWS::KMS::Key
    Properties:
      Description: KMS key for encrypting CloudTrail logs in S3
      EnableKeyRotation: true
      KeyPolicy:
        Version: '2012-10-17'
        Statement:
          # Allow current account to manage the key
          - Sid: Enable IAM User Permissions
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
            Action: 'kms:*'
            Resource: '*'
          # Allow CloudTrail to encrypt logs
          - Sid: Allow CloudTrail to encrypt logs
            Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action:
              - 'kms:GenerateDataKey*'
              - 'kms:Decrypt'
            Resource: '*'
            Condition:
              StringLike:
                'kms:EncryptionContext:aws:cloudtrail:arn': !Sub 'arn:aws:cloudtrail:*:${AWS::AccountId}:trail/*'
          # Allow S3 to use the key for bucket operations
          - Sid: Allow S3 to use the key
            Effect: Allow
            Principal:
              Service: s3.amazonaws.com
            Action:
              - 'kms:Decrypt'
              - 'kms:GenerateDataKey'
            Resource: '*'
          # Allow SNS to use the key for encrypting messages to SQS
          - Sid: Allow SNS to use the key
            Effect: Allow
            Principal:
              Service: sns.amazonaws.com
            Action:
              - 'kms:Decrypt'
              - 'kms:GenerateDataKey*'
            Resource: '*'
          # Allow Nebulock worker role to decrypt S3 objects
          - Sid: Allow Nebulock Worker to decrypt S3 objects
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${NebulockAccountId}:role/eks-pod-identity-intake-worker-app-role'
            Action:
              - 'kms:Decrypt'
              - 'kms:DescribeKey'
            Resource: '*'
          # Allow Nebulock orchestrator role to decrypt SQS messages
          - Sid: Allow Nebulock Orchestrator to decrypt SQS messages
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${NebulockAccountId}:role/eks-pod-identity-intake-orchestrator-app-role'
            Action:
              - 'kms:Decrypt'
              - 'kms:DescribeKey'
            Resource: '*'

  # 2. SNS Topic for S3 Event Notifications
  CloudTrailLogSNSTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: !Sub '${TrailName}-s3-events'
      DisplayName: CloudTrail S3 Event Notifications
      KmsMasterKeyId: !GetAtt CloudTrailKMSKey.Arn

  CloudTrailLogSNSTopicPolicy:
    Type: AWS::SNS::TopicPolicy
    Properties:
      Topics:
        - !Ref CloudTrailLogSNSTopic
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: AllowS3ToPublish
            Effect: Allow
            Principal:
              Service: s3.amazonaws.com
            Action: 'sns:Publish'
            Resource: !Ref CloudTrailLogSNSTopic
            Condition:
              ArnLike:
                'aws:SourceArn': !Sub 'arn:aws:s3:::${BucketName}'

  # 3. SQS Queue for Processing S3 Events
  CloudTrailLogSQSQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub '${TrailName}-log-queue'
      VisibilityTimeout: 300
      MessageRetentionPeriod: 1209600  # 14 days
      ReceiveMessageWaitTimeSeconds: 20  # Enable long polling
      KmsMasterKeyId: !GetAtt CloudTrailKMSKey.Arn

  CloudTrailLogSQSQueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Properties:
      Queues:
        - !Ref CloudTrailLogSQSQueue
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: AllowSNSToSendMessages
            Effect: Allow
            Principal:
              Service: sns.amazonaws.com
            Action: 'sqs:SendMessage'
            Resource: !GetAtt CloudTrailLogSQSQueue.Arn
            Condition:
              ArnEquals:
                'aws:SourceArn': !Ref CloudTrailLogSNSTopic

  # 4. Subscribe SQS to SNS
  CloudTrailLogSQSSubscription:
    Type: AWS::SNS::Subscription
    Properties:
      Protocol: sqs
      TopicArn: !Ref CloudTrailLogSNSTopic
      Endpoint: !GetAtt CloudTrailLogSQSQueue.Arn
      RawMessageDelivery: true

  # 5. S3 Bucket for CloudTrail Logs
  CloudTrailLogBucket:
    Type: AWS::S3::Bucket
    DependsOn:
      - CloudTrailLogSNSTopicPolicy
    Properties:
      BucketName: !Ref BucketName
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: aws:kms
              KMSMasterKeyID: !GetAtt CloudTrailKMSKey.Arn
            BucketKeyEnabled: true
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerEnforced
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      NotificationConfiguration:
        TopicConfigurations:
          - Event: 's3:ObjectCreated:*'
            Topic: !Ref CloudTrailLogSNSTopic

  # 6. S3 Bucket Policy
  # Grants CloudTrail permission to write.
  CloudTrailLogBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref CloudTrailLogBucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          # Statement 1: CloudTrail Write Permission
          - Sid: AWSCloudTrailWrite
            Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: 's3:PutObject'
            Resource: !Sub 'arn:aws:s3:::${BucketName}/AWSLogs/${AWS::AccountId}/*'
            Condition:
              StringEquals:
                's3:x-amz-acl': 'bucket-owner-full-control'

          # Statement 2: CloudTrail Get ACL Permission
          - Sid: AWSCloudTrailGet
            Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: 's3:GetBucketAcl'
            Resource: !Sub 'arn:aws:s3:::${BucketName}'

  # 7. IAM Role for Nebulock Read Access
  NebulockReadRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "Nebulock-Read-Access-Role"
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          # Allow worker role to assume with ExternalId
          - Sid: AllowWorkerAssumeRole
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${NebulockAccountId}:role/eks-pod-identity-intake-worker-app-role'
            Action: "sts:AssumeRole"
            Condition:
              StringEquals:
                "sts:ExternalId": !Ref ExternalId
          # Allow worker role to tag session (no ExternalId condition)
          - Sid: AllowWorkerTagSession
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${NebulockAccountId}:role/eks-pod-identity-intake-worker-app-role'
            Action: "sts:TagSession"
          # Allow orchestrator role to assume with ExternalId
          - Sid: AllowOrchestratorAssumeRole
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${NebulockAccountId}:role/eks-pod-identity-intake-orchestrator-app-role'
            Action: "sts:AssumeRole"
            Condition:
              StringEquals:
                "sts:ExternalId": !Ref ExternalId
          # Allow orchestrator role to tag session (no ExternalId condition)
          - Sid: AllowOrchestratorTagSession
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${NebulockAccountId}:role/eks-pod-identity-intake-orchestrator-app-role'
            Action: "sts:TagSession"
      Policies:
        # S3 Read Access for Worker
        - PolicyName: S3ReadAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 's3:GetObject'
                  - 's3:ListBucket'
                Resource:
                  - !GetAtt CloudTrailLogBucket.Arn
                  - !Sub "${CloudTrailLogBucket.Arn}/*"
        # SQS Read Access for Orchestrator
        - PolicyName: SQSReadAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'sqs:ReceiveMessage'
                  - 'sqs:DeleteMessage'
                  - 'sqs:GetQueueAttributes'
                  - 'sqs:GetQueueUrl'
                Resource:
                  - !GetAtt CloudTrailLogSQSQueue.Arn
        # KMS Decrypt Access for both roles
        - PolicyName: KMSDecryptAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'kms:Decrypt'
                  - 'kms:DescribeKey'
                Resource:
                  - !GetAtt CloudTrailKMSKey.Arn

  # 8. CloudTrail Trail
  MyCloudTrail:
    Type: AWS::CloudTrail::Trail
    DependsOn:
      - CloudTrailLogBucketPolicy
    Properties:
      S3BucketName: !Ref CloudTrailLogBucket
      IsLogging: true
      IsOrganizationTrail: false
      IsMultiRegionTrail: true
      IncludeGlobalServiceEvents: true
      EnableLogFileValidation: true
      TrailName: !Ref TrailName
      KMSKeyId: !GetAtt CloudTrailKMSKey.Arn

# --- Outputs ---
Outputs:
  CloudTrailSQSQueueUrl:
    Description: URL of the SQS queue for CloudTrail log notifications.
    Value: !Ref CloudTrailLogSQSQueue
  NebulockReadRoleArn:
    Description: ARN of the IAM Role that Nebulock will assume to read CloudTrail logs.
    Value: !GetAtt NebulockReadRole.Arn
  ExternalId:
    Description: Unique identifier used as an 'sts:ExternalId' to restrict unauthorized cross-account role assumption.
    Value: !Ref ExternalId

Log in to your AWS Management Console.

Navigate to the CloudFormation service.

Click Create stack > With new resources.

Select Upload a template file, choose the cloud_formation.yaml file, and click Next.

Configure the stack parameters.

  1. Name the stack whatever you like

  2. Change the name of the bucket parameter to something unique

  3. Leave NebulockAccountID as default

  4. Create some unique External ID

  5. Can keep the TrailName as default or use your own naming convention

After submitting, ensure the stack was created correctly.

All the values you need for the Nebulock integration can be found under outputs.

Go to your Nebulock platform and navigate to Integrations > Available Integrations, select "CloudTrail" and paste the values you gathered in the previous step.