Skip to content

Instantly share code, notes, and snippets.

@callum-p
Created November 26, 2019 21:51
Show Gist options
  • Save callum-p/031029a38c1069e6d4c42c42be503111 to your computer and use it in GitHub Desktop.
Save callum-p/031029a38c1069e6d4c42c42be503111 to your computer and use it in GitHub Desktop.
CloudFormation Root Logon Alert
Description: Creates alerts for root logins
Parameters:
emailRecipients:
Type: CommaDelimitedList
Default: 'xx.xx@xx.co,xx.xx@xx.co'
slackChannel:
Type: String
Default: '#security-task-force'
slackToken:
Type: String
NoEcho: true
Mappings:
accounts:
'542423016982':
account: dev
'657252080847':
account: prod
'999262362450':
account: uat
'914027852804':
account: sharedservices
'715767712827':
account: experimental
'608847787792':
account: backups
'557740671058':
account: users
'989529009797':
account: org root
'556013944167':
account: corporate
Resources:
snsTopic:
Type: AWS::SNS::Topic
Properties:
DisplayName: Root Login Email Alerts
lambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: root
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: arn:aws:logs:*:*:*
- Effect: Allow
Action:
- sns:ListSubscriptionsByTopic
- sns:Subscribe
Resource: !Ref snsTopic
rootLoginRule:
Type: AWS::Events::Rule
Properties:
EventPattern: |
{
"detail-type": [
"AWS Console Sign In via CloudTrail"
],
"detail": {
"userIdentity": {
"type": [
"Root"
]
}
}
}
State: ENABLED
Targets:
- Arn: !GetAtt slackNotifyFunction.Arn
Id: slackNotify
- Arn: !Ref snsTopic
Id: emailNotify
slackNotifyFunction:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Role: !GetAtt lambdaExecutionRole.Arn
Runtime: python3.6
Timeout: '300'
Environment:
Variables:
SLACK_TOKEN: !Ref slackToken
SLACK_CHANNEL: !Ref slackChannel
ACCOUNT_ALIAS:
Fn::FindInMap:
- accounts
- !Ref 'AWS::AccountId'
- account
Code:
ZipFile: !Sub |
#!/usr/bin/env python3
import urllib.request
import os
import json
def handler(event, context):
slackToken = os.getenv('SLACK_TOKEN')
slackChannel = os.getenv('SLACK_CHANNEL')
url = 'https://slack.com/api/chat.postMessage'
account = os.getenv('ACCOUNT_ALIAS')
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {slackToken}'
}
values = {
'as_user': False,
'username': 'Root Login Alerts',
'channel': slackChannel,
'attachments': [{
'color': 'danger',
'text': f'Root login for account {account} detected: ' +
'```' +
json.dumps(event, indent=4, sort_keys=True) +
'```'
}]
}
req = urllib.request.Request(
url,
data=json.dumps(values).encode('utf8'),
headers=headers)
with urllib.request.urlopen(req) as response:
print(response.read())
print(response.getcode())
if __name__ == '__main__':
event = {
"version": "0",
"id": "dd5d691d-a125-848c-ff9b-1debadd8c5fd",
"detail-type": "AWS Console Sign In via CloudTrail",
"source": "aws.signin",
"account": "674647793584",
"time": "2019-02-26T00:04:19Z",
"region": "us-east-1",
"resources": [],
"detail": {
"eventVersion": "1.05",
"userIdentity": {
"type": "IAMUser",
"principalId": "xx",
"arn": "arn:aws:iam::674647793584:user/xx.xx",
"accountId": "674647793584",
"userName": "xx.xx"
},
"eventTime": "2019-02-26T00:04:19Z",
"eventSource": "signin.amazonaws.com",
"eventName": "ConsoleLogin",
"awsRegion": "us-east-1",
"sourceIPAddress": "xx.xx.xx.xx",
"userAgent": "Mozilla/5.0 (X11; Linux x86_64; rv:65.0) "
"Gecko/20100101 Firefox/65.0",
"requestParameters": None,
"responseElements": {
"ConsoleLogin": "Success"
},
"additionalEventData": {
"LoginTo": "https://console.aws.amazon.com/console/home?"
"state=hashArgs%23&isauthcode=true",
"MobileVersion": "No",
"MFAUsed": "No"
},
"eventID": "98d1f04e-944e-47fb-a224-580089106881",
"eventType": "AwsConsoleSignIn"
}
}
handler(event, None)
slackNotifyPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
Principal: events.amazonaws.com
SourceArn: !GetAtt rootLoginRule.Arn
FunctionName: !Ref slackNotifyFunction
snsTopicPolicy:
Type: AWS::SNS::TopicPolicy
Properties:
PolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: events.amazonaws.com
Action: sns:Publish
Resource: '*'
Topics:
- !Ref snsTopic
susbcribeResource:
Type: Custom::SnsSubscriber
Properties:
ServiceToken: !GetAtt subscribeFunction.Arn
emailRecipients: !Ref emailRecipients
topicArn: !Ref snsTopic
subscribeFunction:
Type: AWS::Lambda::Function
Properties:
Runtime: python3.6
Timeout: '300'
Handler: index.handler
Role: !GetAtt lambdaExecutionRole.Arn
Code:
ZipFile: |
#!/usr/bin/env python3
import boto3
import cfnresponse
sns = boto3.client('sns')
def create_subscription(topic_arn, email_address):
sns.subscribe(
TopicArn=topic_arn,
Protocol='email',
Endpoint=email_address)
def get_subscriptions(topic_arn):
subscriptions = []
args = {'TopicArn': topic_arn}
while True:
response = sns.list_subscriptions_by_topic(**args)
for sub in response['Subscriptions']:
if sub['Protocol'] == 'email':
subscriptions.append(sub['Endpoint'])
if 'NextToken' in response:
args['NextToken'] = response['NextToken']
else:
break
return subscriptions
def handler(event, context):
if event['RequestType'] in ['Create', 'Update']:
topic_arn = event['ResourceProperties']['topicArn']
subs = get_subscriptions(topic_arn)
for recipient in event['ResourceProperties']['emailRecipients']:
if recipient not in subs:
create_subscription(topic_arn, recipient)
if 'offline' not in event['ResourceProperties']:
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
if __name__ == '__main__':
event = {
'RequestType': 'Create',
'ResourceProperties': {
'emailRecipients': [
'xx.xx@xx.co'
],
'topicArn': 'arn:aws:sns:ap-southeast-2:674647793584:'
'test-snsTopic-V7OB2POYPQOW',
'offline': True
}
}
handler(event, None)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment