Skip to content

Conversation

@MrCloudSec
Copy link
Member

@MrCloudSec MrCloudSec commented Oct 30, 2025

Context

Add CloudTrail timeline to AWS findings as an API-only programmatic utility that provides timeline context showing who created/modified resources and when.

Description

Introduces CloudTrail timeline capability that queries AWS CloudTrail Event History to add lifecycle context to security findings.

Key Features:

  • 141 event types across 20 AWS services: EC2, S3, RDS, Lambda, IAM, VPC, ELB, KMS, Secrets Manager, EBS, EKS, ECR, DynamoDB, SNS, SQS, CloudWatch, EventBridge, Backup, ACM, ECS
  • API-first design: Returns list[dict] for direct REST consumption
  • Session-based: Requires boto3.Session for flexible credential management
  • Regional support: Automatic CloudTrail client creation per region
  • Configurable: 90-day default lookback period (CloudTrail retention limit)
  • Test coverage: 42 tests (28 unit + 14 moto integration tests)

API Usage:

from prowler.providers.aws.lib.cloudtrail_timeline.cloudtrail_timeline import (
    CloudTrailTimeline,
)

timeline = CloudTrailTimeline(session=session, lookback_days=90)

timeline_events = timeline. get_resource_timeline(
    resource_id="sg-0abc123",
    resource_arn="arn:aws:ec2:us-east-1:123456789012:security-group/sg-0abc123",
    region="us-east-1"
)
# Returns: list[dict] with timestamp, event_type, principal, message, event_details

Checklist

API

  • Verify if API specs need to be regenerated.
  • Check if version updates are required (e.g., specs, Poetry, etc.).
  • Ensure new entries are added to CHANGELOG.md, if applicable.

License

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@github-actions github-actions bot added documentation provider/aws Issues/PRs related with the AWS provider output/csv Issues/PRs related with the CSV output format output/ocsf Issues/PRs related with the OCSF output format output/html Issues/PRs related with the HTML output format labels Oct 30, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Oct 30, 2025

⚠️ Changes detected in the following folders without a corresponding update to the CHANGELOG.md:

  • prowler

Please add an entry to the corresponding CHANGELOG.md file to maintain a clear history of changes.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 30, 2025

Conflict Markers Resolved

All conflict markers have been successfully resolved in this pull request.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 30, 2025

🔒 Container Security Scan

Image: prowler:4b38cae
Last scan: 2025-11-17 14:59:46 UTC

📊 Vulnerability Summary

Severity Count
🔴 Critical 2
Total 2

2 package(s) affected

⚠️ Action Required

Critical severity vulnerabilities detected. These should be addressed before merging:

  • Review the detailed scan results
  • Update affected packages to patched versions
  • Consider using a different base image if updates are unavailable

📋 Resources:

@codecov
Copy link

codecov bot commented Oct 30, 2025

Codecov Report

❌ Patch coverage is 82.97735% with 263 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.12%. Comparing base (0f43789) to head (8e5b28a).
⚠️ Report is 6 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff             @@
##           master    #9101       +/-   ##
===========================================
+ Coverage   58.94%   90.12%   +31.18%     
===========================================
  Files           8      837      +829     
  Lines         397    25077    +24680     
===========================================
+ Hits          234    22601    +22367     
- Misses        163     2476     +2313     
Flag Coverage Δ
prowler-py3.10-aws 90.05% <82.97%> (?)
prowler-py3.10-kubernetes ?
prowler-py3.11-aws 90.08% <82.97%> (?)
prowler-py3.11-kubernetes ?
prowler-py3.12-aws 90.11% <82.97%> (?)
prowler-py3.12-kubernetes ?
prowler-py3.9-aws 90.06% <82.97%> (?)
prowler-py3.9-kubernetes ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
prowler 90.12% <82.97%> (+31.18%) ⬆️
api ∅ <ø> (∅)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@MrCloudSec MrCloudSec marked this pull request as ready for review November 3, 2025 21:35
@MrCloudSec MrCloudSec requested review from a team as code owners November 3, 2025 21:35
)


def _enrich_findings_post_scan(findings: list, output_options: Any) -> dict:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this name? Is this included in a class?

from types import SimpleNamespace
from typing import Generator

from alive_progress import alive_bar
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't add terminal UI things to the SDK. This is a CLI responsibility.

Comment on lines 46 to 48
_enrich_findings: bool = False
_enrichment_config: dict = None
_enricher = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about adding this to the Scan SDK. We need to think about it to have it as something external, executed afterwards.

We need to talk about the whole logic in this class. We need to move it somewhere else.

RESOURCE_MODIFIED = "resource_modified"
RESOURCE_DELETED = "resource_deleted"

# EC2 Instance events
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not to add classes for each service?

f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)

def lookup_events_for_resource(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we move the logic out of the Scan, maybe we'll need to create a new class for this part. We can't re-init the CloudTrail class. To me this would fit best in the enricher class.

output_filename: str
only_logs: bool
unix_timestamp: bool
enrich_findings: bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to find a way not to add more data to this object. The idea is to get rid of it as it makes some things dependant in between.

@github-actions github-actions bot removed documentation output/ocsf Issues/PRs related with the OCSF output format output/html Issues/PRs related with the HTML output format labels Nov 6, 2025
@github-actions github-actions bot removed the output/csv Issues/PRs related with the CSV output format label Nov 6, 2025
@MrCloudSec MrCloudSec requested a review from jfagoagas November 6, 2025 20:20
- `codepipeline_project_repo_private` check for AWS provider [(#5915)](https://github.com/prowler-cloud/prowler/pull/5915)
- `cloudstorage_bucket_versioning_enabled` check for GCP provider [(#9014)](https://github.com/prowler-cloud/prowler/pull/9014)
- `cloudstorage_bucket_soft_delete_enabled` check for GCP provider [(#9028)](https://github.com/prowler-cloud/prowler/pull/9028)
- CloudTrail enrichment for AWS provider [(#9101)](https://github.com/prowler-cloud/prowler/pull/9101)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd not add this until the feature is implemented as for now is only an SDK thing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Copy link
Member

@puchy22 puchy22 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As general comment the current AWS-specific design would require complete duplication for Azure (Activity Log) and GCP (Audit Logs). I've recommended an abstraction layer with provider-agnostic base classes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the best place to put the code? You are not really using nothing for the service and it is not expected to be used by checks or services, for me it should be at provider level like other integrations like S3 or SecurityHub in prowler/providers/aws/lib

)
return None

def _create_enrichment(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that this method is never used

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed this one when refactoring.


assert "1234abcd-12ab-34cd-56ef-1234567890ab" in message
assert "30" in message
assert "⚠️" in message or "DELETION SCHEDULED" in message
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if we should add this kind of emojis in the tests

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed!

@@ -0,0 +1,1269 @@
"""CloudTrail enrichment service for Prowler findings.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am concerned about the sustainability of this file. I think it should be split into different files for each service and we should try to use another more sustainable way of adding new events that does not involve modifying a file with 1200 lines of code

@@ -0,0 +1,1882 @@
"""Event message formatters for CloudTrail events.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as with cloudtrail_enricher.py, it is not sustainable having a class that is 1800 lines of code. It's so difficult to know what is the class exactly doing. Is there any way to divide it into other parts to make it more sustainable?



# Union type for all event types
TimelineEventType = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are generating this union at module level and then only using it in the type hint of the TimelineEvent class.
Besides, this won't be a type as such, but rather a union. It seems like a strange way to do it, especially since it will only be used in one place.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100%, changed!

)


@dataclass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why using dataclass instead of using pydantic BaseModel?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, good catch.

last_modified_at: datetime | None = None
related_resources: list[dict[str, str]] = field(default_factory=list)

def to_dict(self) -> dict[str, Any]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, good catch!

if not self.created_at:
return None

from datetime import timezone
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this here and not in the top of the file?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, I did it inside the function because we only need to import it if we use this function.

if not self.last_modified_at:
return None

from datetime import timezone
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@MrCloudSec MrCloudSec added the no-changelog Skip including change in changelog/release notes label Nov 7, 2025
@MrCloudSec MrCloudSec requested a review from jfagoagas November 7, 2025 16:54
@github-actions
Copy link
Contributor

🔒 Container Security Scan

Image: prowler-api:79ea883
Last scan: 2025-11-14 23:46:33 UTC

📊 Vulnerability Summary

Severity Count
🔴 Critical 4
Total 4

3 package(s) affected

⚠️ Action Required

Critical severity vulnerabilities detected. These should be addressed before merging:

  • Review the detailed scan results
  • Update affected packages to patched versions
  • Consider using a different base image if updates are unavailable

📋 Resources:

Copy link
Contributor

@AdriiiPRodri AdriiiPRodri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to add dedicated timeline serializers and hook them into the viewset so the timeline action follows our current response schema, because right now the endpoint isn’t aligned with it.

You also need to clean up the timeline action: validate filter[lookback_days], use the existing provider session initialization, remove the ad-hoc JSON body handling, and return errors through the usual DRF validation flow. Make sure the endpoint is documented with extend_schema, and if there are constants involved, they should live in the view as class-level attributes. Remove the global query-parameter override since it’s no longer needed.

Finally, add the required unittests: provider validation, lookback validation, and the happy path using mocked AWS components

Comment on lines 2513 to 2516
from api.utils import initialize_prowler_provider
from prowler.providers.aws.lib.cloudtrail_timeline.cloudtrail_timeline import (
CloudTrailTimeline,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we importing this here? I mean, this code runs on every call to this endpoint. This should be at the top of the file

url_name="timeline",
url_path=r"timeline(?:/(?P<lookback_days>[0-9]+))?",
)
def timeline(self, request, pk=None, lookback_days=None):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MrCloudSec based on your tests, how long can this take?

@AdriiiPRodri @vicferpoy @josemazo @rakan-pro I'm not sure about having a synchronous endpoint to retrieve this data. From a design standpoint I think we'd have an async endpoint to enqueue a task to fetch that data. Then, another endpoint to get the status/result. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't currently know how long it takes us to retrieve that data, but if it can block, then I completely agree with you, this should be a task, and we should have an endpoint to fetch the results

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It takes one second or less to retrieve the timelin for each resource.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if the timeline is more than 90 days? Since this depends on CloudTrail and the user's events, we need to make this async.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the API side or in the SDK?

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/api no-changelog Skip including change in changelog/release notes provider/aws Issues/PRs related with the AWS provider

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants