Skip to content

Commit 6e3e090

Browse files
shyammukundkunaals
andauthored
fix: Cloudtrail Events Management handle missing request params (#1742)
### Summary > Describe your changes. - Added a check in each event types transform function that skips the event if the requestParameters field of the API data is null - Added unit tests for null requestParameters case ### Related issues or links > Include links to relevant issues or other pages. - #1740 --------- Signed-off-by: shyammukund <[email protected]> Co-authored-by: Kunaal Sikka <[email protected]>
1 parent 3351bfa commit 6e3e090

File tree

3 files changed

+125
-0
lines changed

3 files changed

+125
-0
lines changed

cartography/intel/aws/cloudtrail_management_events.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,13 @@ def transform_assume_role_events_to_role_assumptions(
223223

224224
cloudtrail_event = json.loads(event["CloudTrailEvent"])
225225

226+
# Skip events with null requestParameters since we can't extract roleArn
227+
if not cloudtrail_event.get("requestParameters"):
228+
logger.debug(
229+
f"Skipping CloudTrail AssumeRole event due to missing requestParameters. Event: {event.get('EventId', 'unknown')}"
230+
)
231+
continue
232+
226233
if cloudtrail_event.get("userIdentity", {}).get("arn"):
227234
source_principal = cloudtrail_event["userIdentity"]["arn"]
228235
destination_principal = cloudtrail_event["requestParameters"]["roleArn"]
@@ -298,6 +305,13 @@ def transform_saml_role_events_to_role_assumptions(
298305

299306
cloudtrail_event = json.loads(event["CloudTrailEvent"])
300307

308+
# Skip events with null requestParameters since we can't extract roleArn
309+
if not cloudtrail_event.get("requestParameters"):
310+
logger.debug(
311+
f"Skipping CloudTrail AssumeRoleWithSAML event due to missing requestParameters. Event: {event.get('EventId', 'unknown')}"
312+
)
313+
continue
314+
301315
response_elements = cloudtrail_event.get("responseElements", {})
302316
assumed_role_user = response_elements.get("assumedRoleUser", {})
303317

@@ -370,6 +384,13 @@ def transform_web_identity_role_events_to_role_assumptions(
370384

371385
cloudtrail_event = json.loads(event["CloudTrailEvent"])
372386

387+
# Skip events with null requestParameters since we can't extract roleArn
388+
if not cloudtrail_event.get("requestParameters"):
389+
logger.debug(
390+
f"Skipping CloudTrail AssumeRoleWithWebIdentity event due to missing requestParameters. Event: {event.get('EventId', 'unknown')}"
391+
)
392+
continue
393+
373394
user_identity = cloudtrail_event.get("userIdentity", {})
374395

375396
if user_identity.get("type") == "WebIdentityUser" and user_identity.get(

tests/data/aws/cloudtrail_management_events.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,3 +477,62 @@
477477
"CloudTrailEvent": '{"userIdentity": {"type": "SAMLUser", "principalId": "SAML:[email protected]", "userName": "[email protected]"}, "requestParameters": {"roleArn": "arn:aws:iam::987654321098:role/CrossAccountRole", "principalArn": "arn:aws:iam::123456789012:saml-provider/ExampleProvider"}, "responseElements": {"assumedRoleUser": {"arn": "arn:aws:sts::123456789012:assumed-role/SAMLRole/[email protected]"}}}',
478478
},
479479
]
480+
481+
# =============================================================================
482+
# EDGE CASE TEST MOCK DATA - ACCESS DENIED EVENTS
483+
# =============================================================================
484+
485+
# Mock CloudTrail AssumeRole events that failed with AccessDenied error
486+
# These events have null requestParameters which causes NoneType errors
487+
ACCESS_DENIED_ASSUME_ROLE_CLOUDTRAIL_EVENTS = [
488+
{
489+
"EventName": "AssumeRole",
490+
"EventTime": "2024-01-15T15:45:30.123000",
491+
"UserIdentity": {"arn": "arn:aws:iam::123456789012:user/john.doe"},
492+
"Resources": [],
493+
"ErrorCode": "AccessDenied",
494+
"ErrorMessage": "User: arn:aws:iam::123456789012:user/john.doe is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::123456789012:role/RestrictedRole",
495+
"CloudTrailEvent": '{"eventVersion": "1.08", "userIdentity": {"type": "IAMUser", "principalId": "AIDACKCEVSQ6C2EXAMPLE", "arn": "arn:aws:iam::123456789012:user/john.doe", "accountId": "123456789012", "userName": "john.doe"}, "eventTime": "2024-01-15T15:45:30Z", "eventSource": "sts.amazonaws.com", "eventName": "AssumeRole", "awsRegion": "us-east-1", "sourceIPAddress": "203.0.113.12", "userAgent": "aws-cli/2.0.0", "errorCode": "AccessDenied", "errorMessage": "User: arn:aws:iam::123456789012:user/john.doe is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::123456789012:role/RestrictedRole", "requestParameters": null, "responseElements": null, "requestID": "12345678-1234-1234-1234-123456789012", "eventID": "87654321-4321-4321-4321-210987654321", "readOnly": false, "eventType": "AwsApiCall", "apiVersion": "2011-06-15", "managementEvent": true, "recipientAccountId": "123456789012", "eventCategory": "Management", "tlsDetails": {"tlsVersion": "TLSv1.2", "cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256"}}',
496+
},
497+
{
498+
"EventName": "AssumeRole",
499+
"EventTime": "2024-01-15T16:20:45.789000",
500+
"UserIdentity": {"arn": "arn:aws:iam::123456789012:user/alice"},
501+
"Resources": [],
502+
"ErrorCode": "AccessDenied",
503+
"ErrorMessage": "User: arn:aws:iam::123456789012:user/alice is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::999999999999:role/CrossAccountRole",
504+
"CloudTrailEvent": '{"eventVersion": "1.08", "userIdentity": {"type": "IAMUser", "principalId": "AIDACKCEVSQ6C2ALICE", "arn": "arn:aws:iam::123456789012:user/alice", "accountId": "123456789012", "userName": "alice"}, "eventTime": "2024-01-15T16:20:45Z", "eventSource": "sts.amazonaws.com", "eventName": "AssumeRole", "awsRegion": "us-west-2", "sourceIPAddress": "203.0.113.45", "userAgent": "aws-sdk-java/1.12.0", "errorCode": "AccessDenied", "errorMessage": "User: arn:aws:iam::123456789012:user/alice is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::999999999999:role/CrossAccountRole", "requestParameters": null, "responseElements": null, "requestID": "abcdef12-ab34-cd56-ef78-abcdef123456", "eventID": "fedcba98-9876-5432-1098-fedcba987654", "readOnly": false, "eventType": "AwsApiCall", "apiVersion": "2011-06-15", "managementEvent": true, "recipientAccountId": "123456789012", "eventCategory": "Management", "tlsDetails": {"tlsVersion": "TLSv1.3", "cipherSuite": "TLS_AES_128_GCM_SHA256"}}',
505+
},
506+
]
507+
508+
# Mock CloudTrail AssumeRoleWithSAML events that failed with AccessDenied error
509+
ACCESS_DENIED_SAML_ASSUME_ROLE_CLOUDTRAIL_EVENTS = [
510+
{
511+
"EventName": "AssumeRoleWithSAML",
512+
"EventTime": "2024-01-15T17:30:15.456000",
513+
"UserIdentity": {
514+
"type": "SAMLUser",
515+
"principalId": "SAML:[email protected]",
516+
},
517+
"Resources": [],
518+
"ErrorCode": "AccessDenied",
519+
"ErrorMessage": "Not authorized to perform sts:AssumeRoleWithSAML",
520+
"CloudTrailEvent": '{"eventVersion": "1.08", "userIdentity": {"type": "SAMLUser", "principalId": "SAML:[email protected]", "userName": "[email protected]"}, "eventTime": "2024-01-15T17:30:15Z", "eventSource": "sts.amazonaws.com", "eventName": "AssumeRoleWithSAML", "awsRegion": "us-east-1", "sourceIPAddress": "203.0.113.78", "userAgent": "custom-saml-client/1.0", "errorCode": "AccessDenied", "errorMessage": "Not authorized to perform sts:AssumeRoleWithSAML", "requestParameters": null, "responseElements": {"assumedRoleUser": {"arn": "arn:aws:sts::123456789012:assumed-role/SAMLRole/[email protected]"}}, "requestID": "saml1234-5678-9abc-def0-123456789abc", "eventID": "saml9876-5432-1098-7654-321098765432", "readOnly": false, "eventType": "AwsApiCall", "apiVersion": "2011-06-15", "managementEvent": true, "recipientAccountId": "123456789012", "eventCategory": "Management"}',
521+
},
522+
]
523+
524+
# Mock CloudTrail AssumeRoleWithWebIdentity events that failed with AccessDenied error
525+
ACCESS_DENIED_WEB_IDENTITY_ASSUME_ROLE_CLOUDTRAIL_EVENTS = [
526+
{
527+
"EventName": "AssumeRoleWithWebIdentity",
528+
"EventTime": "2024-01-15T18:45:22.789000",
529+
"UserIdentity": {
530+
"type": "WebIdentityUser",
531+
"principalId": "repo:unauthorizedorg/forbidden-repo:ref:refs/heads/main",
532+
},
533+
"Resources": [],
534+
"ErrorCode": "AccessDenied",
535+
"ErrorMessage": "Not authorized to perform sts:AssumeRoleWithWebIdentity",
536+
"CloudTrailEvent": '{"eventVersion": "1.08", "userIdentity": {"type": "WebIdentityUser", "principalId": "repo:unauthorizedorg/forbidden-repo:ref:refs/heads/main", "identityProvider": "token.actions.githubusercontent.com", "userName": "repo:unauthorizedorg/forbidden-repo:ref:refs/heads/main"}, "eventTime": "2024-01-15T18:45:22Z", "eventSource": "sts.amazonaws.com", "eventName": "AssumeRoleWithWebIdentity", "awsRegion": "us-west-2", "sourceIPAddress": "140.82.112.3", "userAgent": "GitHub-Actions", "errorCode": "AccessDenied", "errorMessage": "Not authorized to perform sts:AssumeRoleWithWebIdentity", "requestParameters": null, "responseElements": {"assumedRoleUser": {"arn": "arn:aws:sts::123456789012:assumed-role/GitHubActionsRole/forbidden-repo"}}, "requestID": "github12-3456-789a-bcde-f0123456789a", "eventID": "github98-7654-321a-bcde-f0987654321a", "readOnly": false, "eventType": "AwsApiCall", "apiVersion": "2011-06-15", "managementEvent": true, "recipientAccountId": "123456789012", "eventCategory": "Management"}',
537+
},
538+
]

tests/unit/cartography/intel/aws/test_cloudtrail_management_events.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@
77
from cartography.intel.aws.cloudtrail_management_events import (
88
transform_web_identity_role_events_to_role_assumptions,
99
)
10+
from tests.data.aws.cloudtrail_management_events import (
11+
ACCESS_DENIED_ASSUME_ROLE_CLOUDTRAIL_EVENTS,
12+
)
13+
from tests.data.aws.cloudtrail_management_events import (
14+
ACCESS_DENIED_SAML_ASSUME_ROLE_CLOUDTRAIL_EVENTS,
15+
)
16+
from tests.data.aws.cloudtrail_management_events import (
17+
ACCESS_DENIED_WEB_IDENTITY_ASSUME_ROLE_CLOUDTRAIL_EVENTS,
18+
)
1019

1120
# Sample test data for AssumeRole events
1221
SAMPLE_ASSUME_ROLE_EVENT = {
@@ -111,3 +120,39 @@ def test_transform_single_github_web_identity_role_event():
111120
assert assumption["times_used"] == 1
112121
assert assumption["first_seen_in_time_window"] == "2024-01-15T12:15:30.789000"
113122
assert assumption["last_used"] == "2024-01-15T12:15:30.789000"
123+
124+
125+
def test_transform_assume_role_events_with_null_request_parameters():
126+
"""Test that AssumeRole events with null requestParameters are gracefully skipped."""
127+
# Arrange
128+
events = ACCESS_DENIED_ASSUME_ROLE_CLOUDTRAIL_EVENTS
129+
130+
# Act - This should no longer crash and should skip events with null requestParameters
131+
result = transform_assume_role_events_to_role_assumptions(events=events)
132+
133+
# Assert - Events with null requestParameters should be skipped, resulting in empty list
134+
assert len(result) == 0
135+
136+
137+
def test_transform_saml_role_events_with_null_request_parameters():
138+
"""Test that AssumeRoleWithSAML events with null requestParameters are gracefully skipped."""
139+
# Arrange
140+
events = ACCESS_DENIED_SAML_ASSUME_ROLE_CLOUDTRAIL_EVENTS
141+
142+
# Act - This should no longer crash and should skip events with null requestParameters
143+
result = transform_saml_role_events_to_role_assumptions(events=events)
144+
145+
# Assert - Events with null requestParameters should be skipped, resulting in empty list
146+
assert len(result) == 0
147+
148+
149+
def test_transform_web_identity_role_events_with_null_request_parameters():
150+
"""Test that AssumeRoleWithWebIdentity events with null requestParameters are gracefully skipped."""
151+
# Arrange
152+
events = ACCESS_DENIED_WEB_IDENTITY_ASSUME_ROLE_CLOUDTRAIL_EVENTS
153+
154+
# Act - This should no longer crash and should skip events with null requestParameters
155+
result = transform_web_identity_role_events_to_role_assumptions(events=events)
156+
157+
# Assert - Events with null requestParameters should be skipped, resulting in empty list
158+
assert len(result) == 0

0 commit comments

Comments
 (0)