Skip to content

Commit 52891af

Browse files
authored
fix(aws): correct GuardDuty finding date fields (#2459)
### Type of change <!-- Mark the relevant option with an "x" --> - [x] Bug fix (non-breaking change that fixes an issue) - [x] Documentation update ### Summary This PR fixes GuardDuty finding timestamp ingestion so Cartography reads first/last seen from the correct GuardDuty API path. Changes included: - Read `CreatedAt` and `UpdatedAt` from top-level finding payload into `createdat`/`updatedat`. - Read `Service.EventFirstSeen` and `Service.EventLastSeen` into snake_case node fields: `eventfirstseen` and `eventlastseen`. - Keep backward-compatible fallback to top-level `EventFirstSeen`/`EventLastSeen` if present. - Update GuardDuty unit/integration test fixtures and assertions. - Update AWS schema docs for `GuardDutyFinding::Risk` fields. ### Checklist #### General - [x] I have read the [contributing guidelines](https://cartography-cncf.github.io/cartography/dev/developer-guide.html). - [ ] The linter passes locally (`make lint`). - [x] I have added/updated tests that prove my fix is effective or my feature works. #### Proof of functionality <!-- Provide at least one of the following to help reviewers verify your changes: --> - [ ] Screenshot showing the graph before and after changes. - [x] New or updated unit/integration tests. #### If you are adding or modifying a synced entity - [x] Included Cartography sync logs from a real environment demonstrating successful synchronization of the new/modified entity. Logs should show: - The sync job starting and completing without errors - The number of nodes/relationships created or updated - Example: ``` INFO:cartography.intel.aws.guardduty:Loading <N> GuardDuty findings for region us-east-1 into graph. INFO:cartography.util:Syncing GuardDuty findings completed. ``` #### If you are changing a node or relationship - [x] Updated the [schema documentation](https://github.com/cartography-cncf/cartography/tree/master/docs/root/modules). - [ ] Updated the [schema README](https://github.com/cartography-cncf/cartography/blob/master/docs/schema/README.md). #### If you are implementing a new intel module - [ ] Used the NodeSchema [data model](https://cartography-cncf.github.io/cartography/dev/writing-intel-modules.html#defining-a-node). ### Notes for reviewers - Scope is intentionally limited to GuardDuty findings field mapping and docs/tests alignment. --------- Signed-off-by: Kunaal Sikka <kunaal@subimage.io>
1 parent f07b891 commit 52891af

6 files changed

Lines changed: 101 additions & 2 deletions

File tree

cartography/intel/aws/guardduty.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ def transform_findings(findings: list[dict[str, Any]]) -> list[dict[str, Any]]:
168168
"""Transform GuardDuty findings from API response to schema format."""
169169
transformed: list[dict[str, Any]] = []
170170
for f in findings:
171+
service = f.get("Service", {})
171172
item: dict[str, Any] = {
172173
"id": f["Id"],
173174
"arn": f.get("Arn"),
@@ -176,8 +177,16 @@ def transform_findings(findings: list[dict[str, Any]]) -> list[dict[str, Any]]:
176177
"title": f.get("Title"),
177178
"description": f.get("Description"),
178179
"confidence": f.get("Confidence"),
179-
"eventfirstseen": f.get("EventFirstSeen"),
180-
"eventlastseen": f.get("EventLastSeen"),
180+
"createdat": f.get("CreatedAt"),
181+
"updatedat": f.get("UpdatedAt"),
182+
"eventfirstseen": service.get(
183+
"EventFirstSeen",
184+
f.get("EventFirstSeen"),
185+
),
186+
"eventlastseen": service.get(
187+
"EventLastSeen",
188+
f.get("EventLastSeen"),
189+
),
181190
"accountid": f.get("AccountId"),
182191
"region": f.get("Region"),
183192
"detectorid": f.get("DetectorId"),

cartography/models/aws/guardduty/findings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ class GuardDutyFindingNodeProperties(CartographyNodeProperties):
2121
type: PropertyRef = PropertyRef("type")
2222
severity: PropertyRef = PropertyRef("severity")
2323
confidence: PropertyRef = PropertyRef("confidence")
24+
createdat: PropertyRef = PropertyRef("createdat")
25+
updatedat: PropertyRef = PropertyRef("updatedat")
2426
eventfirstseen: PropertyRef = PropertyRef("eventfirstseen")
2527
eventlastseen: PropertyRef = PropertyRef("eventlastseen")
2628
accountid: PropertyRef = PropertyRef("accountid")

docs/root/modules/aws/schema.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@ Representation of an AWS [GuardDuty Finding](https://docs.aws.amazon.com/guarddu
241241
| confidence | The confidence level that GuardDuty has in the accuracy of the finding |
242242
| title | A short description of the finding |
243243
| description | A more detailed description of the finding |
244+
| createdat | Timestamp when GuardDuty created the finding |
245+
| updatedat | Timestamp when GuardDuty last updated the finding |
244246
| eventfirstseen | Timestamp when the activity that prompted GuardDuty to generate this finding was first observed |
245247
| eventlastseen | Timestamp when the activity that prompted GuardDuty to generate this finding was last observed |
246248
| accountid | The ID of the AWS account in which the finding was generated |

tests/data/aws/guardduty.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,8 @@
316316
"confidence": 7.5,
317317
"title": "EC2 instance is communicating with a malicious IP address",
318318
"description": "EC2 instance i-99999999 is communicating with a malicious IP address 198.51.100.1.",
319+
"createdat": datetime(2023, 1, 15, 10, 30, 0),
320+
"updatedat": datetime(2023, 1, 15, 10, 45, 0),
319321
"eventfirstseen": datetime(2023, 1, 15, 10, 30, 0),
320322
"eventlastseen": datetime(2023, 1, 15, 10, 45, 0),
321323
"accountid": "123456789012",
@@ -334,6 +336,8 @@
334336
"confidence": 8.0,
335337
"title": "S3 bucket is being enumerated from an unusual location",
336338
"description": "S3 bucket test-bucket is being enumerated from an unusual location.",
339+
"createdat": datetime(2023, 1, 16, 14, 20, 0),
340+
"updatedat": datetime(2023, 1, 16, 14, 35, 0),
337341
"eventfirstseen": datetime(2023, 1, 16, 14, 20, 0),
338342
"eventlastseen": datetime(2023, 1, 16, 14, 35, 0),
339343
"accountid": "123456789012",
@@ -352,6 +356,8 @@
352356
"confidence": 6.0,
353357
"title": "IAM user is making anomalous API calls",
354358
"description": "IAM user GeneratedFindingUserName is making anomalous API calls.",
359+
"createdat": datetime(2023, 1, 17, 9, 15, 0),
360+
"updatedat": datetime(2023, 1, 17, 9, 30, 0),
355361
"eventfirstseen": datetime(2023, 1, 17, 9, 15, 0),
356362
"eventlastseen": datetime(2023, 1, 17, 9, 30, 0),
357363
"accountid": "123456789012",

tests/integration/cartography/intel/aws/test_guardduty.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,44 @@ def test_sync_guardduty_findings(
130130
# Note: S3Bucket finding with severity 5.0 excluded by HIGH threshold
131131
}
132132

133+
# Assert - Check that finding date fields were populated from the expected API paths
134+
finding_dates = neo4j_session.run(
135+
"""
136+
MATCH (f:GuardDutyFinding)
137+
RETURN
138+
f.id AS id,
139+
toString(f.createdat) AS createdat,
140+
toString(f.updatedat) AS updatedat,
141+
toString(f.eventfirstseen) AS eventfirstseen,
142+
toString(f.eventlastseen) AS eventlastseen
143+
""",
144+
).data()
145+
assert {
146+
(
147+
row["id"],
148+
row["createdat"],
149+
row["updatedat"],
150+
row["eventfirstseen"],
151+
row["eventlastseen"],
152+
)
153+
for row in finding_dates
154+
} == {
155+
(
156+
"74b1234567890abcdef1234567890abcdef",
157+
"2023-01-15T10:30:00",
158+
"2023-01-15T10:45:00",
159+
"2023-01-15T10:30:00",
160+
"2023-01-15T10:45:00",
161+
),
162+
(
163+
"96d3456789012cdef3456789012cdef01",
164+
"2023-01-17T09:15:00",
165+
"2023-01-17T09:30:00",
166+
"2023-01-17T09:15:00",
167+
"2023-01-17T09:30:00",
168+
),
169+
}
170+
133171
# Assert - Check that GuardDuty detectors are connected to the AWSAccount
134172
assert check_rels(
135173
neo4j_session,

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from datetime import datetime
2+
13
from cartography.intel.aws.guardduty import transform_findings
24
from tests.data.aws.guardduty import EXPECTED_TRANSFORM_RESULTS
35
from tests.data.aws.guardduty import GET_FINDINGS
@@ -25,3 +27,43 @@ def test_transform_findings():
2527
# Expected IAM AccessKey finding
2628
expected_iam_finding = EXPECTED_TRANSFORM_RESULTS[2]
2729
assert transformed[2] == expected_iam_finding
30+
31+
32+
def test_transform_findings_prefers_service_event_fields():
33+
findings = [
34+
{
35+
"Id": "finding-prefers-service",
36+
"CreatedAt": datetime(2024, 1, 1, 0, 0, 0),
37+
"UpdatedAt": datetime(2024, 1, 1, 1, 0, 0),
38+
"EventFirstSeen": datetime(2024, 1, 1, 2, 0, 0),
39+
"EventLastSeen": datetime(2024, 1, 1, 3, 0, 0),
40+
"Service": {
41+
"EventFirstSeen": datetime(2024, 1, 1, 4, 0, 0),
42+
"EventLastSeen": datetime(2024, 1, 1, 5, 0, 0),
43+
},
44+
"Resource": {"ResourceType": "AccessKey"},
45+
}
46+
]
47+
48+
transformed = transform_findings(findings)
49+
50+
assert transformed[0]["eventfirstseen"] == datetime(2024, 1, 1, 4, 0, 0)
51+
assert transformed[0]["eventlastseen"] == datetime(2024, 1, 1, 5, 0, 0)
52+
53+
54+
def test_transform_findings_falls_back_to_top_level_event_fields():
55+
findings = [
56+
{
57+
"Id": "finding-fallback-top-level",
58+
"CreatedAt": datetime(2024, 2, 1, 0, 0, 0),
59+
"UpdatedAt": datetime(2024, 2, 1, 1, 0, 0),
60+
"EventFirstSeen": datetime(2024, 2, 1, 2, 0, 0),
61+
"EventLastSeen": datetime(2024, 2, 1, 3, 0, 0),
62+
"Resource": {"ResourceType": "AccessKey"},
63+
}
64+
]
65+
66+
transformed = transform_findings(findings)
67+
68+
assert transformed[0]["eventfirstseen"] == datetime(2024, 2, 1, 2, 0, 0)
69+
assert transformed[0]["eventlastseen"] == datetime(2024, 2, 1, 3, 0, 0)

0 commit comments

Comments
 (0)