Back to Checks
aws

CloudTrail trail S3 bucket has MFA delete enabled

cloudtrail_bucket_requires_mfa_delete
Service: cloudtrailSeverity: medium
OfficialBuilt-inVerified
Categories:identity-accessforensics-ready
Resource Type:AwsCloudTrailTrail

CloudTrail log buckets for actively logging trails are evaluated for MFA Delete on the associated S3 bucket. The assessment determines whether MFA Delete is configured on the in-account log bucket; if the bucket resides in another account, its configuration should be verified separately.

Risk

Without MFA Delete, stolen or over-privileged credentials can permanently delete log versions or change versioning, compromising log integrity and availability. This enables attacker cover-ups, hinders forensics, and weakens evidence for investigations.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks cloudtrail_bucket_requires_mfa_delete

Recommendation

Enable MFA Delete on the CloudTrail log bucket with versioning enabled. Enforce least privilege so only tightly controlled identities can delete or alter logs, and require MFA for such actions. Apply defense in depth using a dedicated logging account and log file integrity validation.

Remediation

CLI

aws s3api put-bucket-versioning --bucket <CLOUDTRAIL_BUCKET_NAME> --versioning-configuration Status=Enabled,MFADelete=Enabled --mfa "<MFA_SERIAL> <MFA_CODE>"

Other

1Sign in to the AWS Management Console as the root user with MFA enabled
2Open AWS CloudShell (from the top navigation bar)
3Run:
4```bash
5aws s3api put-bucket-versioning --bucket <CLOUDTRAIL_BUCKET_NAME> --versioning-configuration Status=Enabled,MFADelete=Enabled --mfa "<MFA_SERIAL> <MFA_CODE>"
6```

Check Code

from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudtrail.cloudtrail_client import (
    cloudtrail_client,
)
from prowler.providers.aws.services.s3.s3_client import s3_client


class cloudtrail_bucket_requires_mfa_delete(Check):
    def execute(self):
        findings = []
        if cloudtrail_client.trails is not None:
            for trail in cloudtrail_client.trails.values():
                if trail.is_logging:
                    trail_bucket_is_in_account = False
                    trail_bucket = trail.s3_bucket
                    report = Check_Report_AWS(metadata=self.metadata(), resource=trail)
                    report.region = trail.home_region
                    report.status = "FAIL"
                    report.status_extended = f"Trail {trail.name} bucket ({trail_bucket}) does not have MFA delete enabled."
                    for bucket in s3_client.buckets.values():
                        if trail_bucket == bucket.name:
                            trail_bucket_is_in_account = True
                            if bucket.mfa_delete:
                                report.status = "PASS"
                                report.status_extended = f"Trail {trail.name} bucket ({trail_bucket}) has MFA delete enabled."
                    # check if trail bucket is a cross account bucket
                    if not trail_bucket_is_in_account:
                        report.status = "MANUAL"
                        report.status_extended = f"Trail {trail.name} bucket ({trail_bucket}) is a cross-account bucket or out of Prowler's audit scope, please check it manually."

                    findings.append(report)

        return findings