-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit b56a39c
Showing
22 changed files
with
650 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
root = true | ||
|
||
[*] | ||
indent_style = space | ||
indent_size = 2 | ||
end_of_line = lf | ||
charset = utf-8 | ||
trim_trailing_whitespace = true | ||
insert_final_newline = true | ||
|
||
[**/*.md] | ||
trim_trailing_whitespace = false | ||
|
||
[**/*.py] | ||
indent_size = 4 | ||
indent_style = space |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
*.swp | ||
package-lock.json | ||
__pycache__ | ||
.pytest_cache | ||
.venv | ||
*.egg-info | ||
|
||
# CDK asset staging directory | ||
.cdk.staging | ||
cdk.out |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
|
||
# Welcome to your CDK Python project! | ||
|
||
This is a blank project for Python development with CDK. | ||
|
||
The `cdk.json` file tells the CDK Toolkit how to execute your app. | ||
|
||
This project is set up like a standard Python project. The initialization | ||
process also creates a virtualenv within this project, stored under the `.venv` | ||
directory. To create the virtualenv it assumes that there is a `python3` | ||
(or `python` for Windows) executable in your path with access to the `venv` | ||
package. If for any reason the automatic creation of the virtualenv fails, | ||
you can create the virtualenv manually. | ||
|
||
To manually create a virtualenv on MacOS and Linux: | ||
|
||
``` | ||
$ python3 -m venv .venv | ||
``` | ||
|
||
After the init process completes and the virtualenv is created, you can use the following | ||
step to activate your virtualenv. | ||
|
||
``` | ||
$ source .venv/bin/activate | ||
``` | ||
|
||
If you are a Windows platform, you would activate the virtualenv like this: | ||
|
||
``` | ||
% .venv\Scripts\activate.bat | ||
``` | ||
|
||
Once the virtualenv is activated, you can install the required dependencies. | ||
|
||
``` | ||
$ pip install -r requirements.txt | ||
``` | ||
|
||
At this point you can now synthesize the CloudFormation template for this code. | ||
|
||
``` | ||
$ cdk synth | ||
``` | ||
|
||
To add additional dependencies, for example other CDK libraries, just add | ||
them to your `setup.py` file and rerun the `pip install -r requirements.txt` | ||
command. | ||
|
||
## Useful commands | ||
|
||
* `cdk ls` list all stacks in the app | ||
* `cdk synth` emits the synthesized CloudFormation template | ||
* `cdk deploy` deploy this stack to your default AWS account/region | ||
* `cdk diff` compare deployed stack with current state | ||
* `cdk docs` open CDK documentation | ||
|
||
Enjoy! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#!/usr/bin/env python3 | ||
import os | ||
|
||
import aws_cdk as cdk | ||
|
||
from database import AwsActivityDatabaseStack | ||
from notification import AwsActivityNotificationStack | ||
from login import AwsSignInActivityStack | ||
|
||
|
||
app = cdk.App() | ||
|
||
us_east_1 = cdk.Environment(region=os.environ["CDK_DEFAULT_REGION"], account=os.getenv('CDK_DEFAULT_ACCOUNT')) | ||
|
||
# database | ||
db_stack = AwsActivityDatabaseStack(app, 'aws-activity-db', env=us_east_1, | ||
description='Tracking AWS activities for security compliance' | ||
) | ||
|
||
# notification | ||
notification_stack = AwsActivityNotificationStack(app, 'aws-activity-notification', env=us_east_1, | ||
description='Notify AWS activities for security compliance' | ||
) | ||
|
||
# sign-in activity | ||
AwsSignInActivityStack(app, 'aws-sign-in-activity', env=us_east_1, | ||
dynamodb_table=db_stack.table, | ||
notification_topic=notification_stack.topic, | ||
description='Tracking AWS sign-in activities for security compliance' | ||
) | ||
|
||
app.synth() |
60 changes: 60 additions & 0 deletions
60
assets/lambda-functions/count-failed-sign-in-attempt/index.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import logging | ||
import time | ||
import boto3 | ||
import os | ||
from boto3.dynamodb.conditions import Key, Attr | ||
|
||
|
||
logging.basicConfig(level=logging.DEBUG) | ||
logger=logging.getLogger(__name__) | ||
|
||
def handler(event, context): | ||
logger.setLevel(logging.DEBUG) | ||
|
||
TABLE_NAME = os.getenv('DYNAMODB_TABLE_NAME') | ||
user_identity_index = { | ||
'name': 'UserIdentityIndex', | ||
'hash_key': 'userIdentity', | ||
'sort_key': 'timestamp' | ||
} | ||
|
||
|
||
user_identity = event['userIdentity'] | ||
|
||
# last 1 hour = now - 1h | ||
# seconds | ||
now = int(time.time()) | ||
last_one_hour = now - 1 * 60 * 60 | ||
|
||
# table = boto3.resource('dynamodb', region_name='us-east-1').Table(TABLE_NAME) | ||
table = boto3.resource('dynamodb').Table(TABLE_NAME) | ||
results = [] | ||
last_evaluated_key = None | ||
|
||
while True: | ||
if not last_evaluated_key: | ||
response = table.query( | ||
IndexName=user_identity_index['name'], | ||
KeyConditionExpression=Key(user_identity_index['hash_key']).eq(user_identity) & Key(user_identity_index['sort_key']).between(last_one_hour, now), | ||
FilterExpression=Attr('eventName').eq('ConsoleLogin') & Attr('detail.responseElements.ConsoleLogin').eq('Failure'), | ||
ScanIndexForward=False, | ||
) | ||
else: | ||
response = table.query( | ||
IndexName=user_identity_index['name'], | ||
KeyConditionExpression=Key(user_identity_index['hash_key']).eq(user_identity) & Key(user_identity_index['sort_key']).between(last_one_hour, now), | ||
FilterExpression=Attr('eventName').eq('ConsoleLogin') & Attr('detail.responseElements.ConsoleLogin').eq('Failure'), | ||
ScanIndexForward=False, | ||
ExclusiveStartKey=last_evaluated_key | ||
) | ||
results.extend(response['Items']) | ||
|
||
last_evaluated_key = response.get('LastEvaluatedKey') | ||
if not last_evaluated_key: | ||
break | ||
logger.info(len(results)) | ||
|
||
event['failedAttempts'] = len(results) | ||
logger.debug(event) | ||
|
||
return event |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import logging | ||
import requests | ||
import json | ||
from datetime import datetime | ||
|
||
|
||
logging.basicConfig(level=logging.DEBUG) | ||
logger=logging.getLogger(__name__) | ||
|
||
SLACK_CHANNELS = { | ||
'alarm-aws': 'https://hooks.slack.com/services/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' | ||
} | ||
|
||
def handler(event, context): | ||
logger.setLevel(logging.DEBUG) | ||
logger.debug(event) | ||
|
||
message = json.loads(event['Records'][0]['Sns']['Message']) | ||
message_attributes = event['Records'][0]['Sns']['MessageAttributes'] | ||
|
||
fields = [] | ||
|
||
text = 'See detail below' | ||
reason = message_attributes['reason']['Value'] | ||
if reason == 'ManyFailedSignInAttempt': | ||
text = 'There are many failed sign-in attempts in last one hour' | ||
fields.extend([ | ||
{ | ||
'title': 'User', | ||
"value": message['detail']['userIdentity']['userName'], | ||
'short': True | ||
}, | ||
{ | ||
'title': 'Failed attempt', | ||
"value": message['failedAttempts'], | ||
'short': True | ||
} | ||
]) | ||
elif reason == 'NoMFAUsed': | ||
text = 'Detected MFA not used' | ||
fields.extend([ | ||
{ | ||
'title': 'User', | ||
"value": message['detail']['userIdentity']['userName'], | ||
'short': True | ||
}, | ||
{ | ||
'title': 'Event name', | ||
"value": message['eventName'], | ||
'short': True | ||
} | ||
]) | ||
elif reason == 'RootActivity': | ||
text = 'Detected Root activity' | ||
fields.extend([ | ||
{ | ||
'title': 'User', | ||
"value": 'Root', | ||
'short': True | ||
}, | ||
{ | ||
'title': 'Event name', | ||
"value": message['eventName'], | ||
'short': True | ||
} | ||
]) | ||
|
||
fields.extend([ | ||
{ | ||
'title': 'IP address', | ||
"value": message['detail']['sourceIPAddress'], | ||
'short': True | ||
}, | ||
{ | ||
'title': 'Severity', | ||
"value": message_attributes['severity']['Value'], | ||
'short': True | ||
}, | ||
{ | ||
'title': 'Event ID', | ||
"value": message['id'], | ||
'short': True | ||
} | ||
]) | ||
|
||
send_slack( | ||
channel=message_attributes['channel']['Value'], | ||
title=text, | ||
fields=fields, | ||
severity=message_attributes['severity']['Value'] | ||
) | ||
|
||
def send_slack(channel: str, title: str, fields: list, severity='Medium'): | ||
color = '#36a64f' | ||
if severity == 'Medium': | ||
color = '#edaf2b' | ||
elif severity == 'High': | ||
color = '#cc5f00' | ||
elif severity == 'Critical': | ||
color = '#cc0000' | ||
|
||
payload = { | ||
'username': 'Cloud Guard', | ||
'attachments': [{ | ||
'title': title, | ||
'color': color, | ||
'fields': fields, | ||
'footer': datetime.now().strftime('%Y-%m-%d %H:%M:%S UTC'), | ||
'fallback': 'Required plain-text summary of the attachment.' | ||
}] | ||
} | ||
if severity.lower() == 'critical': | ||
payload['attachments'][0].update({'pretext': '<!here>'}) | ||
|
||
logger.info(payload) | ||
requests.post(SLACK_CHANNELS.get(channel), data=json.dumps(payload), headers={'Content-Type': 'application/json'}) | ||
logger.info('Send Slack notify successfully') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
requests==2.26.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import logging | ||
import time | ||
from dateutil import parser | ||
import boto3 | ||
import os | ||
import json | ||
|
||
|
||
logging.basicConfig(level=logging.DEBUG) | ||
logger=logging.getLogger(__name__) | ||
|
||
def handler(event, context): | ||
# logger.setLevel(logging.DEBUG) | ||
|
||
# 365 days | ||
ttl = int(time.time()) + 365 * 24 * 60 * 60 | ||
timestamp = int(parser.parse(event['time']).timestamp()) | ||
|
||
event_detail = event['detail'] | ||
event_name = event_detail['eventName'] | ||
user_identity_type = event_detail['userIdentity']['type'] | ||
|
||
user_identity = f'{user_identity_type}#Unknown' | ||
if user_identity_type == 'IAMUser': | ||
user_identity = f'{user_identity_type}-{event_detail["userIdentity"]["userName"]}' | ||
elif user_identity_type == 'Root': | ||
user_identity = f'{user_identity_type}#Root' | ||
elif user_identity == 'AssumedRole': | ||
user_identity = f'{user_identity_type}#{event_detail["userIdentity"]["sessionContext"]["sessionIssuer"]["userName"]}' | ||
|
||
event = {**event, 'eventName': event_name, 'userIdentity': user_identity, 'timestamp': timestamp, 'ttl': ttl} | ||
logger.debug(json.dumps(event)) | ||
|
||
# dynamodb = boto3.resource('dynamodb', region_name='us-east-1') | ||
dynamodb = boto3.resource('dynamodb') | ||
table = dynamodb.Table(os.getenv('DYNAMODB_TABLE_NAME')) | ||
table.put_item(Item=event) | ||
logger.info(f'Activity has been stored into database successfully') | ||
|
||
return event |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"app": "python3 app.py", | ||
"context": { | ||
"@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, | ||
"@aws-cdk/core:stackRelativeExports": true, | ||
"@aws-cdk/aws-rds:lowercaseDbIdentifier": true, | ||
"@aws-cdk/aws-lambda:recognizeVersionProps": true, | ||
"@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .infra import AwsActivityDatabaseStack |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
from aws_cdk import ( | ||
Stack, | ||
aws_dynamodb as dynamodb | ||
) | ||
from constructs import Construct | ||
|
||
|
||
class AwsActivityDatabaseStack(Stack): | ||
def __init__(self, scope: Construct, id: str, **kwargs) -> None: | ||
super().__init__(scope, id, **kwargs) | ||
|
||
# dynamodb | ||
self.table = dynamodb.Table(self, 'DynamoDB', | ||
table_name='aws-activity', | ||
partition_key=dynamodb.Attribute(name='id', type=dynamodb.AttributeType.STRING), | ||
sort_key=dynamodb.Attribute(name='timestamp', type=dynamodb.AttributeType.NUMBER), | ||
time_to_live_attribute='ttl', | ||
billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST | ||
) | ||
|
||
# DynamoDB GSI | ||
self.table.add_global_secondary_index( | ||
index_name='UserIdentityIndex', | ||
partition_key=dynamodb.Attribute(name='userIdentity', type=dynamodb.AttributeType.STRING), | ||
sort_key=dynamodb.Attribute(name='timestamp', type=dynamodb.AttributeType.NUMBER), | ||
non_key_attributes=['id', 'eventName', 'detail'], | ||
projection_type=dynamodb.ProjectionType.INCLUDE | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .infra import AwsSignInActivityStack |
Oops, something went wrong.