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:
- Query.ai Lambda assumes your IAM role using STS
- Uses temporary credentials to publish to your SNS topic
- 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-1FIFO topic (for ordered delivery):
aws sns create-topic \
--name security-detections.fifo \
--attributes FifoTopic=true \
--region us-east-1Or via AWS Console:
- Navigate to SNS → Topics
- Click Create topic
- Choose Standard or FIFO
- Enter name (e.g.,
security-detections) - Click Create topic
- Copy the Topic ARN
2. Generate External ID
Generate a secure random External ID:
# Generate a random 32-character External ID
openssl rand -hex 16Important: 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/QueryDetectionSNSPublish5. Get Role ARN
Get the ARN of the role you created:
aws iam get-role \
--role-name QueryDetectionAlertPublisher \
--query 'Role.Arn' \
--output textExample 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 IDseverity(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_HEREExpected: 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-1Expected: 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-1Confirm 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-1Lambda 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-1Filtering 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-1Filter 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-1Troubleshooting
| Error | Solution |
|---|---|
AccessDenied on AssumeRole | Verify trust policy includes Query.ai account, check External ID |
AccessDenied on Publish | Verify IAM policy grants sns:Publish on the topic ARN |
InvalidParameter | Check topic ARN format and region match |
NotFound | Verify topic exists in the specified region |
| Messages not appearing | Check subscriptions, verify filters, review CloudWatch logs |
| External ID mismatch | Ensure 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
- Use External ID: Always use External ID for role assumption
- Least Privilege: Grant only
sns:Publishpermission - Restrict Resources: Limit IAM policy to specific topic ARNs
- Enable CloudTrail: Log all SNS API calls
- Rotate External ID: Rotate if compromised
- 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
Updated 2 days ago