Starting support for AWS SNS cloudtrail event
This commit is contained in:
parent
2a5a95ccaf
commit
7c06e91ea8
2 changed files with 210 additions and 1 deletions
|
|
@ -4,7 +4,7 @@ from typing import Any, List, Tuple
|
||||||
|
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
|
|
||||||
from ops_bot.common import COLOR_ALARM, COLOR_OK, COLOR_UNKNOWN
|
from ops_bot.common import COLOR_ALARM, COLOR_OK, COLOR_UNKNOWN, COLOR_WARNING
|
||||||
from ops_bot.config import RoutingKey
|
from ops_bot.config import RoutingKey
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -28,7 +28,12 @@ def handle_notification(payload: Any) -> List[Tuple[str, str]]:
|
||||||
|
|
||||||
def handle_json_notification(payload: Any, body: Any) -> List[Tuple[str, str]]:
|
def handle_json_notification(payload: Any, body: Any) -> List[Tuple[str, str]]:
|
||||||
if "AlarmName" not in body:
|
if "AlarmName" not in body:
|
||||||
|
payload_str = payload.get("Message")
|
||||||
msg = "Received unknown json payload type over AWS SNS"
|
msg = "Received unknown json payload type over AWS SNS"
|
||||||
|
msg += f"""\n<br/>
|
||||||
|
```json
|
||||||
|
{payload_str}
|
||||||
|
```"""
|
||||||
logging.info(msg)
|
logging.info(msg)
|
||||||
logging.info(payload.get("Message"))
|
logging.info(payload.get("Message"))
|
||||||
return [(msg, msg)]
|
return [(msg, msg)]
|
||||||
|
|
@ -56,6 +61,89 @@ def handle_json_notification(payload: Any, body: Any) -> List[Tuple[str, str]]:
|
||||||
return [(plain, formatted)]
|
return [(plain, formatted)]
|
||||||
|
|
||||||
|
|
||||||
|
def handle_cloudtrail_signin(payload: Any) -> List[Tuple[str, str]]:
|
||||||
|
region = payload["region"]
|
||||||
|
event_type = payload["detail"]["eventType"]
|
||||||
|
event_time = payload["detail"]["eventTime"]
|
||||||
|
|
||||||
|
account_id = None
|
||||||
|
if "accountId" in payload["detail"]["userIdentity"]:
|
||||||
|
account_id = payload["detail"]["userIdentity"]["accountId"]
|
||||||
|
else:
|
||||||
|
account_id = payload["detail"]["recipientAccountId"]
|
||||||
|
|
||||||
|
user_type = payload["detail"]["userIdentity"]["type"]
|
||||||
|
if user_type == "IAMUser":
|
||||||
|
user = payload["detail"]["userIdentity"]["userName"]
|
||||||
|
elif user_type == "Root":
|
||||||
|
user = "Root User"
|
||||||
|
elif user_type == "AssumedRole":
|
||||||
|
user = payload["detail"]["userIdentity"]["principalId"]
|
||||||
|
else:
|
||||||
|
user = "Unknown user"
|
||||||
|
|
||||||
|
mfa_used = "unknown"
|
||||||
|
if (
|
||||||
|
"additionalEventData" in payload["detail"]
|
||||||
|
and "MFAUsed" in payload["detail"]["additionalEventData"]
|
||||||
|
):
|
||||||
|
mfa_used = payload["detail"]["additionalEventData"]["MFAUsed"]
|
||||||
|
|
||||||
|
was_failure = False
|
||||||
|
if (
|
||||||
|
"responseElements" in payload["detail"]
|
||||||
|
and "ConsoleLogin" in payload["detail"]["responseElements"]
|
||||||
|
):
|
||||||
|
was_failure = payload["detail"]["responseElements"]["ConsoleLogin"] == "Failure"
|
||||||
|
|
||||||
|
error_message = None
|
||||||
|
if "errorMessage" in payload["detail"]:
|
||||||
|
error_message = payload["detail"]["errorMessage"]
|
||||||
|
|
||||||
|
if was_failure:
|
||||||
|
title = f"Failed AWS Console Sign attempt by user `{user}`."
|
||||||
|
color = COLOR_WARNING
|
||||||
|
else:
|
||||||
|
title = f"AWS Console Sign detected by user `{user}`."
|
||||||
|
color = COLOR_ALARM
|
||||||
|
|
||||||
|
formatted = [
|
||||||
|
x
|
||||||
|
for x in [
|
||||||
|
f"<font color={color}>**🚨 ALERT[{event_type}]** </font>: {title}",
|
||||||
|
f"- **Region**: {region}",
|
||||||
|
f"- **MFA Used**: {mfa_used}",
|
||||||
|
(f"- **Error Message**: {error_message}" if error_message else None),
|
||||||
|
f"- **Event Time**: {event_time}",
|
||||||
|
f"- **Account ID**: {account_id}",
|
||||||
|
]
|
||||||
|
if x is not None
|
||||||
|
]
|
||||||
|
|
||||||
|
plain = title
|
||||||
|
|
||||||
|
return [(plain, "<br/>".join(formatted))]
|
||||||
|
|
||||||
|
|
||||||
|
def handle_cloudtrail_generic(payload: Any) -> List[Tuple[str, str]]:
|
||||||
|
region = payload["region"]
|
||||||
|
event_type = payload["detail"]["eventType"]
|
||||||
|
event_time = payload["detail"]["eventTime"]
|
||||||
|
account_id = payload["detail"]["recipientAccountId"]
|
||||||
|
detail = payload["detail-type"]
|
||||||
|
|
||||||
|
plain = f"{detail}"
|
||||||
|
|
||||||
|
formatted = [
|
||||||
|
f"<font color={COLOR_UNKNOWN}>**⚠️CLOUDTRAIL EVENT[{event_type}]**</font>: {detail}",
|
||||||
|
f"**Region**: {region}",
|
||||||
|
f"**Event Time**: {event_time}",
|
||||||
|
f"**Account ID**: {account_id}",
|
||||||
|
]
|
||||||
|
|
||||||
|
return [(plain, "<br/>".join(formatted))]
|
||||||
|
|
||||||
|
|
||||||
async def parse_sns_event(
|
async def parse_sns_event(
|
||||||
route: RoutingKey,
|
route: RoutingKey,
|
||||||
payload: Any,
|
payload: Any,
|
||||||
|
|
@ -71,4 +159,11 @@ async def parse_sns_event(
|
||||||
return handle_json_notification(payload, body)
|
return handle_json_notification(payload, body)
|
||||||
except Exception:
|
except Exception:
|
||||||
return handle_notification(payload)
|
return handle_notification(payload)
|
||||||
|
elif "source" in payload:
|
||||||
|
source = payload["source"]
|
||||||
|
if source == "aws.signin":
|
||||||
|
return handle_cloudtrail_signin(payload)
|
||||||
|
else:
|
||||||
|
return handle_cloudtrail_generic(payload)
|
||||||
|
|
||||||
raise Exception("Unnown SNS payload type")
|
raise Exception("Unnown SNS payload type")
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,106 @@ sns_notification = """{
|
||||||
"UnsubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-west-2:123456789012:MyTopic:c9135db0-26c4-47ec-8998-413945fb5a96"
|
"UnsubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-west-2:123456789012:MyTopic:c9135db0-26c4-47ec-8998-413945fb5a96"
|
||||||
}"""
|
}"""
|
||||||
|
|
||||||
|
sns_signin = """
|
||||||
|
{
|
||||||
|
"version": "0",
|
||||||
|
"id": "000000-e441-44ce-22c1-00000000",
|
||||||
|
"detail-type": "AWS Console Sign In via CloudTrail",
|
||||||
|
"source": "aws.signin",
|
||||||
|
"account": "1234567890",
|
||||||
|
"time": "2025-01-31T14:03:15Z",
|
||||||
|
"region": "eu-north-1",
|
||||||
|
"resources": [],
|
||||||
|
"detail": {
|
||||||
|
"eventVersion": "1.09",
|
||||||
|
"userIdentity": {
|
||||||
|
"type": "IAMUser",
|
||||||
|
"principalId": "ABCDEFGHIJKLMNOPQRSTU",
|
||||||
|
"arn": "arn:aws:iam::1234567890:user/user@example.com",
|
||||||
|
"accountId": "1234567890",
|
||||||
|
"userName": "user@example.com"
|
||||||
|
},
|
||||||
|
"eventTime": "2025-01-31T14:03:15Z",
|
||||||
|
"eventSource": "signin.amazonaws.com",
|
||||||
|
"eventName": "ConsoleLogin",
|
||||||
|
"awsRegion": "eu-north-1",
|
||||||
|
"sourceIPAddress": "193.0.0.0.1",
|
||||||
|
"userAgent": "Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0",
|
||||||
|
"requestParameters": null,
|
||||||
|
"responseElements": {
|
||||||
|
"ConsoleLogin": "Success"
|
||||||
|
},
|
||||||
|
"additionalEventData": {
|
||||||
|
"LoginTo": "https://console.aws.amazon.com/console/home",
|
||||||
|
"MobileVersion": "No",
|
||||||
|
"MFAIdentifier": "arn:aws:iam::1234567890:u2f/user/user@example.com/user-omg-my-yubikey",
|
||||||
|
"MFAUsed": "Yes"
|
||||||
|
},
|
||||||
|
"eventID": "000000-1539-4d7f-b6cc-000000000",
|
||||||
|
"readOnly": false,
|
||||||
|
"eventType": "AwsConsoleSignIn",
|
||||||
|
"managementEvent": true,
|
||||||
|
"recipientAccountId": "1234567890",
|
||||||
|
"eventCategory": "Management",
|
||||||
|
"tlsDetails": {
|
||||||
|
"tlsVersion": "TLSv1.3",
|
||||||
|
"cipherSuite": "TLS_AES_128_GCM_SHA256",
|
||||||
|
"clientProvidedHostHeader": "eu-north-1.signin.aws.amazon.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
sns_signin_failure = """
|
||||||
|
{
|
||||||
|
"version": "0",
|
||||||
|
"id": "0000-a6cf-b920-6e14-000000",
|
||||||
|
"detail-type": "AWS Console Sign In via CloudTrail",
|
||||||
|
"source": "aws.signin",
|
||||||
|
"account": "1234567890",
|
||||||
|
"time": "2025-01-31T14:01:49Z",
|
||||||
|
"region": "eu-north-1",
|
||||||
|
"resources": [],
|
||||||
|
"detail": {
|
||||||
|
"eventVersion": "1.09",
|
||||||
|
"userIdentity": {
|
||||||
|
"type": "IAMUser",
|
||||||
|
"principalId": "AIDARWPFIVFS76W7ZBVBO",
|
||||||
|
"accountId": "1234567890",
|
||||||
|
"accessKeyId": "",
|
||||||
|
"userName": "user@example.com"
|
||||||
|
},
|
||||||
|
"eventTime": "2025-01-31T14:01:49Z",
|
||||||
|
"eventSource": "signin.amazonaws.com",
|
||||||
|
"eventName": "ConsoleLogin",
|
||||||
|
"awsRegion": "eu-north-1",
|
||||||
|
"sourceIPAddress": "193.0.0.0.1",
|
||||||
|
"userAgent": "Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0",
|
||||||
|
"errorMessage": "Failed authentication",
|
||||||
|
"requestParameters": null,
|
||||||
|
"responseElements": {
|
||||||
|
"ConsoleLogin": "Failure"
|
||||||
|
},
|
||||||
|
"additionalEventData": {
|
||||||
|
"LoginTo": "https://console.aws.amazon.com/console/home?",
|
||||||
|
"MobileVersion": "No",
|
||||||
|
"MFAUsed": "Yes"
|
||||||
|
},
|
||||||
|
"eventID": "00000-572b-4006-8d9f-00000",
|
||||||
|
"readOnly": false,
|
||||||
|
"eventType": "AwsConsoleSignIn",
|
||||||
|
"managementEvent": true,
|
||||||
|
"recipientAccountId": "1234567890",
|
||||||
|
"eventCategory": "Management",
|
||||||
|
"tlsDetails": {
|
||||||
|
"tlsVersion": "TLSv1.3",
|
||||||
|
"cipherSuite": "TLS_AES_128_GCM_SHA256",
|
||||||
|
"clientProvidedHostHeader": "eu-north-1.signin.aws.amazon.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
async def test_aws_sns_notification() -> None:
|
async def test_aws_sns_notification() -> None:
|
||||||
r = await aws.parse_sns_event(None, json.loads(sns_notification), None)
|
r = await aws.parse_sns_event(None, json.loads(sns_notification), None)
|
||||||
|
|
@ -67,3 +167,17 @@ async def test_aws_sns_unsubscribe() -> None:
|
||||||
print(r)
|
print(r)
|
||||||
expected = "You have chosen to deactivate subscription arn:aws:sns:us-west-2:123456789012:MyTopic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55.\nTo cancel this operation and restore the subscription, visit the SubscribeURL included in this message.\n\nhttps://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-west-2:123456789012:MyTopic&Token=2336412f37fb6..."
|
expected = "You have chosen to deactivate subscription arn:aws:sns:us-west-2:123456789012:MyTopic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55.\nTo cancel this operation and restore the subscription, visit the SubscribeURL included in this message.\n\nhttps://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-west-2:123456789012:MyTopic&Token=2336412f37fb6..."
|
||||||
assert r[0] == (expected, expected)
|
assert r[0] == (expected, expected)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_aws_sns_signin() -> None:
|
||||||
|
r = await aws.parse_sns_event(None, json.loads(sns_signin), None)
|
||||||
|
print(r)
|
||||||
|
expected = "<font color=#dc3545>**🚨 ALERT[AwsConsoleSignIn]** </font>: AWS Console Sign detected by user `user@example.com`.<br/>- **Region**: eu-north-1<br/>- **MFA Used**: Yes<br/>- **Event Time**: 2025-01-31T14:03:15Z<br/>- **Account ID**: 1234567890"
|
||||||
|
assert r[0][1] == expected
|
||||||
|
|
||||||
|
|
||||||
|
async def test_aws_sns_signin_failure() -> None:
|
||||||
|
r = await aws.parse_sns_event(None, json.loads(sns_signin_failure), None)
|
||||||
|
print(r)
|
||||||
|
expected = "<font color=#ffc107>**🚨 ALERT[AwsConsoleSignIn]** </font>: Failed AWS Console Sign attempt by user `user@example.com`.<br/>- **Region**: eu-north-1<br/>- **MFA Used**: Yes<br/>- **Error Message**: Failed authentication<br/>- **Event Time**: 2025-01-31T14:01:49Z<br/>- **Account ID**: 1234567890"
|
||||||
|
assert r[0][1] == expected
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue