audit-labs/audit-tools

A collection of scripts, queries, and other goodies you can use in an audit.

clone: git clone https://gitbay.org/audit-labs/audit-tools.git

v1.0.0: applications/aws/collectors/monitoring.py · raw

 1"""
 2Collect audit-logging posture: CloudTrail trails and AWS Config recorders.
 3"""
 4
 5import sys
 6
 7from botocore.exceptions import ClientError
 8
 9from .api import enabled_regions
10
11
12def cloudtrail(cfg):
13    """
14    One row per CloudTrail trail: whether it is logging, multi-region, and has
15    log-file validation. An empty result means no trails are configured.
16    """
17    region = cfg.get("region") or "us-east-1"
18    ct = cfg["session"].client("cloudtrail", region_name=region)
19    rows = []
20    for trail in ct.describe_trails(includeShadowTrails=False).get("trailList", []):
21        try:
22            status = ct.get_trail_status(Name=trail["TrailARN"])
23        except ClientError:
24            status = {}
25        rows.append(
26            {
27                "name": trail.get("Name", ""),
28                "home_region": trail.get("HomeRegion", ""),
29                "multi_region": trail.get("IsMultiRegionTrail"),
30                "log_file_validation": trail.get("LogFileValidationEnabled"),
31                "is_logging": status.get("IsLogging"),
32                "s3_bucket": trail.get("S3BucketName", ""),
33            }
34        )
35    return rows
36
37
38def config_recorders(cfg):
39    """
40    One row per region: whether AWS Config is recording. Regions with no
41    recorder are reported so gaps are visible.
42    """
43    session = cfg["session"]
44    rows = []
45    for region in enabled_regions(cfg):
46        try:
47            cc = session.client("config", region_name=region)
48            recorders = cc.describe_configuration_recorders().get(
49                "ConfigurationRecorders", []
50            )
51            statuses = {
52                s["name"]: s
53                for s in cc.describe_configuration_recorder_status().get(
54                    "ConfigurationRecordersStatus", []
55                )
56            }
57        except ClientError as e:
58            print(f"  Skipping {region}: config returned {e}", file=sys.stderr)
59            continue
60
61        if not recorders:
62            rows.append(
63                {
64                    "region": region,
65                    "recorder": "(none)",
66                    "recording": False,
67                    "last_status": "",
68                }
69            )
70            continue
71        for r in recorders:
72            st = statuses.get(r["name"], {})
73            rows.append(
74                {
75                    "region": region,
76                    "recorder": r["name"],
77                    "recording": st.get("recording"),
78                    "last_status": st.get("lastStatus", ""),
79                }
80            )
81    return rows