Skip to content

Instantly share code, notes, and snippets.

@callum-p
Created November 26, 2019 21:51
Show Gist options
  • Select an option

  • Save callum-p/031029a38c1069e6d4c42c42be503111 to your computer and use it in GitHub Desktop.

Select an option

Save callum-p/031029a38c1069e6d4c42c42be503111 to your computer and use it in GitHub Desktop.

Revisions

  1. callum-p created this gist Nov 26, 2019.
    284 changes: 284 additions & 0 deletions root-logon-alert.yaml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,284 @@
    Description: Creates alerts for root logins

    Parameters:
    emailRecipients:
    Type: CommaDelimitedList
    Default: '[email protected],[email protected]'
    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': [
    '[email protected]'
    ],
    'topicArn': 'arn:aws:sns:ap-southeast-2:674647793584:'
    'test-snsTopic-V7OB2POYPQOW',
    'offline': True
    }
    }
    handler(event, None)