CloudFormation template for monitoring IAM user creation and access key activities with real-time alerts via email (SNS) and/or Slack.
This template sets up automated alerts for critical IAM events:
- CreateUser - New IAM user creation
- CreateAccessKey - Access key generation
- CreateLoginProfile - Console access enablement
Notifications are delivered via email (SNS) and/or Slack, enabling security teams to monitor unauthorized or unexpected IAM activity in real time.
- Security Monitoring - Detect unauthorized IAM user creation in real-time
- Compliance - Maintain audit trail for IAM changes
- Team Awareness - Keep security teams informed of IAM activities
- Multi-Account Deployment - Standardize alerting across AWS Organization
| File | Description |
|---|---|
iam-alerts-cloudformation.yaml |
Main CloudFormation template with EventBridge, SNS, and Lambda |
deploy.sh |
Interactive deployment script with validation |
parameters-example.json |
Sample parameters file for easy deployment |
Install the AWS CLI if you haven't already:
- macOS:
brew install awscli - Linux: Follow the official install guide
- Windows: Use the MSI installer from the link above
Verify it's installed:
aws --versionYou need valid AWS credentials with permission to create CloudFormation stacks, IAM roles, EventBridge rules, SNS topics, and Lambda functions.
Standard credentials (long-lived access keys):
aws configure
# Enter your AWS Access Key ID, Secret Access Key, region, and output formatTemporary credentials (AWS SSO, MFA, or assumed roles):
aws configure does not prompt for a session token. If you're using temporary credentials (e.g. copied from the AWS SSO console), paste all three lines individually:
aws configure set aws_access_key_id YOUR_ACCESS_KEY_ID
aws configure set aws_secret_access_key YOUR_SECRET_ACCESS_KEY
aws configure set aws_session_token YOUR_SESSION_TOKEN
aws configure set region us-east-1Alternatively, the deploy.sh script has a built-in credential setup step — you can paste the full credentials block (all three key=value lines at once) when prompted.
Verify your credentials are working:
aws sts get-caller-identityYou should see your account ID and IAM principal returned. If this fails, your credentials are invalid or expired.
This solution relies on AWS CloudTrail to capture IAM API calls. Ensure a trail exists in your account that logs management events:
aws cloudtrail describe-trails --query 'trailList[*].[name,IsMultiRegionTrail,HomeRegion]' --output tableIf no trail exists, create one in the AWS Console under CloudTrail > Create trail before deploying.
Required only if you want Slack notifications. See Setting Up Slack Webhooks below.
The easiest way to deploy is using the interactive script:
./deploy.shThe script will walk you through the following steps:
- AWS credentials — checks if you're already authenticated. If not, prompts you to paste your credentials block.
- CloudTrail check — verifies a multi-region trail is active.
- Stack name — defaults to
iam-activity-alerts, press Enter to accept. - AWS region — defaults to your currently configured region (e.g.
us-east-1). - Notification method — choose one:
1— Email only (enter your email address)2— Slack only (enter your webhook URL)3— Both email and Slack
- Excluded usernames — comma-separated list of IAM usernames that should NOT trigger alerts (e.g. automated service accounts). Leave empty to alert on all users.
- Alert rule name — defaults to
iam-user-creation-alert. - Confirmation — review the summary and type
yesto deploy.
The script waits for the stack to finish deploying and prints the stack outputs when complete.
Email notifications:
aws cloudformation create-stack \
--stack-name iam-activity-alerts \
--template-body file://iam-alerts-cloudformation.yaml \
--parameters \
ParameterKey=NotificationEmail,ParameterValue=security@example.com \
ParameterKey=ExcludedUserNames,ParameterValue="service-account1,service-account2" \
--capabilities CAPABILITY_IAMSlack notifications:
aws cloudformation create-stack \
--stack-name iam-activity-alerts \
--template-body file://iam-alerts-cloudformation.yaml \
--parameters \
ParameterKey=SlackWebhookUrl,ParameterValue=https://hooks.slack.com/services/YOUR/WEBHOOK/URL \
ParameterKey=ExcludedUserNames,ParameterValue="service-account1,service-account2" \
--capabilities CAPABILITY_IAMBoth email and Slack:
aws cloudformation create-stack \
--stack-name iam-activity-alerts \
--template-body file://iam-alerts-cloudformation.yaml \
--parameters \
ParameterKey=NotificationEmail,ParameterValue=security@example.com \
ParameterKey=SlackWebhookUrl,ParameterValue=https://hooks.slack.com/services/YOUR/WEBHOOK/URL \
ParameterKey=ExcludedUserNames,ParameterValue="service-account1,service-account2" \
--capabilities CAPABILITY_IAMCopy parameters-example.json, fill in your values, then:
aws cloudformation create-stack \
--stack-name iam-activity-alerts \
--template-body file://iam-alerts-cloudformation.yaml \
--parameters file://parameters.json \
--capabilities CAPABILITY_IAM| Parameter | Description | Default |
|---|---|---|
NotificationEmail |
Email for SNS alerts | (empty) |
SlackWebhookUrl |
Slack webhook URL | (empty) |
ExcludedUserNames |
Comma-separated usernames to exclude | (empty) |
AlertRuleName |
EventBridge rule name | iam-user-creation-alert |
- EventBridge Rule - Monitors CloudTrail for IAM events
- SNS Topic (optional) - Email notifications with formatted messages
- Lambda Function (optional) - Slack integration with color-coded alerts
- IAM Roles - Minimal permissions for Lambda execution
- Visit https://api.slack.com/apps and click "Create New App" > "From scratch"
- Under "Incoming Webhooks", toggle "Activate Incoming Webhooks" to ON
- Click "Add New Webhook to Workspace", select your channel (e.g.,
#security-alerts), and click "Allow" - Copy the webhook URL (format:
https://hooks.slack.com/services/T.../B.../xxx)
Test it:
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"IAM Alerts test message"}' \
https://hooks.slack.com/services/YOUR/WEBHOOK/URLAfter deploying with an email address, AWS sends a confirmation email from no-reply@sns.amazonaws.com. You must click the confirmation link within 3 days before alerts will be delivered.
Verify subscription status:
aws sns list-subscriptions-by-topic \
--topic-arn $(aws cloudformation describe-stacks \
--stack-name iam-activity-alerts \
--query 'Stacks[0].Outputs[?OutputKey==`SNSTopicArn`].OutputValue' \
--output text)Each alert includes:
- Event type and timestamp
- Target username
- AWS Account ID and region
- Principal who performed the action (ARN)
- Source IP address
# Create a test user (will trigger alert)
aws iam create-user --user-name test-alert-user
# Wait 1-2 minutes for the alert, then clean up
aws iam delete-user --user-name test-alert-userEnsure test-alert-user is not in your excluded users list.
Option A: Share via S3 presigned URL
# Upload to a private bucket
aws s3 cp iam-alerts-cloudformation.yaml s3://your-templates-bucket/
# Generate a time-limited presigned URL (valid 7 days)
aws s3 presign s3://your-templates-bucket/iam-alerts-cloudformation.yaml \
--expires-in 604800Option B: CloudFormation StackSets for organization-wide deployment — see AWS StackSets documentation.
Option C: Fork or clone this repository — all files are self-contained.
- Slack webhook URL: Marked
NoEchoto prevent display in the CloudFormation console and CLI output. However,NoEchodoes not prevent access vialambda:GetFunctionConfiguration— anyone with that permission can read the environment variable. For higher-security environments, store the webhook URL in SSM Parameter Store (SecureString) or AWS Secrets Manager and retrieve it at Lambda runtime instead. - Lambda has minimal IAM permissions (CloudWatch Logs only)
- SNS topic policy restricts publishing to EventBridge only
- No credentials stored or transmitted
- Check subscription status (see Email Subscription Confirmation above)
- Check spam/junk folder — add
no-reply@sns.amazonaws.comto contacts - Verify email address in stack parameters:
aws cloudformation describe-stacks \ --stack-name iam-activity-alerts \ --query 'Stacks[0].Parameters[?ParameterKey==`NotificationEmail`].ParameterValue' \ --output text
- Test the webhook directly:
curl -X POST -H 'Content-type: application/json' \ --data '{"text":"Test message"}' YOUR_WEBHOOK_URL
- Check Lambda logs:
aws logs tail /aws/lambda/iam-user-creation-alert-slack-notifier --follow
- Verify CloudTrail is enabled and logging management events:
aws cloudtrail get-trail-status --name YOUR_TRAIL_NAME
- Check EventBridge rule is enabled:
Look for
aws events describe-rule --name iam-user-creation-alert
"State": "ENABLED". - Verify targets:
aws events list-targets-by-rule --rule iam-user-creation-alert
Check the parameter value (case-sensitive):
aws cloudformation describe-stacks \
--stack-name iam-activity-alerts \
--query 'Stacks[0].Parameters[?ParameterKey==`ExcludedUserNames`].ParameterValue' \
--output textaws cloudformation update-stack \
--stack-name iam-activity-alerts \
--use-previous-template \
--parameters \
ParameterKey=ExcludedUserNames,ParameterValue="user1,user2,user3" \
ParameterKey=NotificationEmail,UsePreviousValue=true \
ParameterKey=SlackWebhookUrl,UsePreviousValue=true \
ParameterKey=AlertRuleName,UsePreviousValue=trueEdit the eventName list in iam-alerts-cloudformation.yaml:
eventName:
- CreateUser
- CreateAccessKey
- CreateLoginProfile
- DeleteUser # add as needed
- DeleteAccessKey # add as neededUse the interactive teardown script to remove all deployed resources:
./teardown.shThe script lists all stack resources before asking for confirmation, then waits for the deletion to complete.
Alternatively, delete directly with the AWS CLI:
aws cloudformation delete-stack --stack-name iam-activity-alerts --region us-east-1Expected: < $1/month for typical usage
- EventBridge: No charge for rules; minimal per-event cost
- Lambda (Slack): Free tier eligible
- SNS (email): First 1,000 emails/month free
- CloudWatch Logs: Minimal charges