Amazon SNS Destination Setup

Send detection alerts to Amazon SNS topics for flexible message routing using cross-account IAM role assumption.

Amazon SNS Destination Setup

Send detection alerts to Amazon SNS topics for flexible message routing using cross-account IAM role assumption.

Prerequisites

  • AWS account with SNS topic created
  • IAM role with SNS publish permissions
  • External ID for secure role assumption

Architecture Overview

The alert handler uses cross-account IAM role assumption to securely publish messages to your SNS topic:

  1. Query.ai Lambda assumes your IAM role using STS
  2. Uses temporary credentials to publish to your SNS topic
  3. Credentials automatically expire after 15 minutes

This approach provides:

  • No long-lived credentials to manage
  • Secure with External ID validation
  • Full audit trail in CloudTrail
  • Works across AWS accounts

Setup Steps

1. Create SNS Topic

Create a standard or FIFO topic in your AWS account:

Standard topic:

aws sns create-topic \
  --name security-detections \
  --region us-east-1

FIFO topic (for ordered delivery):

aws sns create-topic \
  --name security-detections.fifo \
  --attributes FifoTopic=true \
  --region us-east-1

Or via AWS Console:

  1. Navigate to SNSTopics
  2. Click Create topic
  3. Choose Standard or FIFO
  4. Enter name (e.g., security-detections)
  5. Click Create topic
  6. Copy the Topic ARN

2. Generate External ID

Generate a secure random External ID:

# Generate a random 32-character External ID
openssl rand -hex 16

Important: Save this External ID - you'll need it for both the IAM role and the destination configuration.

3. Create IAM Role

Create an IAM role that Query.ai can assume.

Trust Policy (trust-policy.json):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::QUERY_AWS_ACCOUNT_ID:role/detection-outcome-handler-role"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "YOUR_EXTERNAL_ID_HERE"
        }
      }
    }
  ]
}

Note: Replace QUERY_AWS_ACCOUNT_ID with the AWS account ID provided by Query.ai support.

Create the role:

aws iam create-role \
  --role-name QueryDetectionAlertPublisher \
  --assume-role-policy-document file://trust-policy.json \
  --description "Allows Query.ai to publish detection alerts to SNS"

4. Create IAM Policy

Create a policy that grants SNS publish permissions.

Policy (sns-publish-policy.json):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "sns:Publish"
      ],
      "Resource": "arn:aws:sns:us-east-1:YOUR_ACCOUNT_ID:security-detections"
    }
  ]
}

Create and attach the policy:

# Create policy
aws iam create-policy \
  --policy-name QueryDetectionSNSPublish \
  --policy-document file://sns-publish-policy.json

# Attach to role
aws iam attach-role-policy \
  --role-name QueryDetectionAlertPublisher \
  --policy-arn arn:aws:iam::YOUR_ACCOUNT_ID:policy/QueryDetectionSNSPublish

5. Get Role ARN

Get the ARN of the role you created:

aws iam get-role \
  --role-name QueryDetectionAlertPublisher \
  --query 'Role.Arn' \
  --output text

Example output: arn:aws:iam::123456789012:role/QueryDetectionAlertPublisher

6. Configure in Query.ai

Contact your Query.ai administrator to configure the Amazon SNS destination with:

Required Configuration:

  • Role ARN
  • External ID (stored securely)
  • AWS region
  • Topic ARN

Optional Configuration:

  • Subject line template
  • Timeout in seconds (default: 30)

Message Format

Message Body (JSON)

The message body contains all detection fields:

{
  "detection_id": 123,
  "detection_name": "Suspicious Login Attempts",
  "description": "Multiple failed login attempts detected",
  "severity": "HIGH",
  "outcome": "MATCHED",
  "match_count": 5,
  "replay_link": "https://app.query.ai/replay/123",
  "ran_at": "2025-01-15T10:00:00Z",
  "range_start": "2025-01-15T09:00:00Z",
  "range_end": "2025-01-15T10:00:00Z",
  "run_id": "run-456",
  "run_type": "SCHEDULED",
  "errors": [],
  "match_operator": "GREATER_THAN",
  "match_threshold": 0,
  "match_eagerness": "EXHAUSTIVE",
  "match_exhaustiveness": "COMPLETED",
  "search_id": "search-abc-123",
  "trace_id": "1-abc-def"
}

Message Attributes

Messages include attributes for filtering:

  • detection_id (String) - Detection configuration ID
  • severity (String) - Detection severity (CRITICAL, HIGH, MEDIUM, LOW)
  • outcome (String) - Detection outcome (MATCHED, NOT_MATCHED, ERROR)
  • run_type (String) - Type of run (SCHEDULED, MANUAL)

Subject Line (Optional)

For email subscriptions, configure a subject line with placeholders:

  • {detection_name} - Name of the detection
  • {severity} - Severity level
  • {outcome} - Detection outcome

Example: "[{severity}] {detection_name}""[HIGH] Suspicious Login Attempts"

Subject is truncated to 100 characters (SNS limit).

Testing

Test Role Assumption

Test that Query.ai can assume your role:

# From Query.ai AWS account
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/QueryDetectionAlertPublisher \
  --role-session-name test-session \
  --external-id YOUR_EXTERNAL_ID_HERE

Expected: JSON response with temporary credentials

Test SNS Publish

Test publishing to your topic:

aws sns publish \
  --topic-arn arn:aws:sns:us-east-1:123456789012:security-detections \
  --message '{"test": "message from Query.ai"}' \
  --subject "Test Alert" \
  --message-attributes '{"severity":{"DataType":"String","StringValue":"HIGH"}}' \
  --region us-east-1

Expected: JSON response with MessageId

Subscribing to Messages

Email Subscription

aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:123456789012:security-detections \
  --protocol email \
  --notification-endpoint [email protected] \
  --region us-east-1

Confirm the subscription via email.

SQS Subscription

# Create SQS queue
aws sqs create-queue \
  --queue-name security-detections-queue \
  --region us-east-1

# Subscribe to SNS
aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:123456789012:security-detections \
  --protocol sqs \
  --notification-endpoint arn:aws:sqs:us-east-1:123456789012:security-detections-queue \
  --region us-east-1

Lambda Subscription

aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:123456789012:security-detections \
  --protocol lambda \
  --notification-endpoint arn:aws:lambda:us-east-1:123456789012:function:process-detection \
  --region us-east-1

Filtering Messages

Filter by Severity

Create a subscription that only receives HIGH and CRITICAL alerts:

aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:123456789012:security-detections \
  --protocol email \
  --notification-endpoint [email protected] \
  --attributes '{"FilterPolicy":"{\"severity\":[\"HIGH\",\"CRITICAL\"]}"}' \
  --region us-east-1

Filter by Outcome

Only receive MATCHED detections:

aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:123456789012:security-detections \
  --protocol sqs \
  --notification-endpoint arn:aws:sqs:us-east-1:123456789012:matched-detections \
  --attributes '{"FilterPolicy":"{\"outcome\":[\"MATCHED\"]}"}' \
  --region us-east-1

Troubleshooting

ErrorSolution
AccessDenied on AssumeRoleVerify trust policy includes Query.ai account, check External ID
AccessDenied on PublishVerify IAM policy grants sns:Publish on the topic ARN
InvalidParameterCheck topic ARN format and region match
NotFoundVerify topic exists in the specified region
Messages not appearingCheck subscriptions, verify filters, review CloudWatch logs
External ID mismatchEnsure External ID in trust policy matches configuration

Configuration Options

Required

role_arn

  • IAM Role ARN to assume for SNS access
  • Format: arn:aws:iam::123456789012:role/RoleName

external_id (secret)

  • External ID for secure role assumption
  • Stored securely in AWS Secrets Manager
  • Must match External ID in IAM role trust policy

region_name

  • AWS region where the SNS topic exists
  • Format: String (e.g., "us-east-1")

topic_arn

  • SNS Topic ARN to publish messages to
  • Format: arn:aws:sns:region:account-id:topic-name

Optional

subject

  • Subject line for email endpoints
  • Supports placeholders: {detection_name}, {severity}, {outcome}
  • Maximum 100 characters
  • Only used for email subscriptions

timeout

  • Request timeout in seconds
  • Default: 30
  • Maximum: 300 (5 minutes)

Security Best Practices

  1. Use External ID: Always use External ID for role assumption
  2. Least Privilege: Grant only sns:Publish permission
  3. Restrict Resources: Limit IAM policy to specific topic ARNs
  4. Enable CloudTrail: Log all SNS API calls
  5. Rotate External ID: Rotate if compromised
  6. Monitor Usage: Review CloudTrail logs for role assumption

External ID Best Practices

  • Generate cryptographically random External IDs (32+ characters)
  • Never reuse External IDs across different integrations
  • Store External ID securely in Secrets Manager
  • Rotate External ID if compromised
  • Document External ID in secure location

Resources