Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.org
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ connection details, choose which checks to run, and watch live progress.

| Directory | Description |
|----------------------+------------------------------------------------------------------------------|
| =applications/aws/= | AWS IAM users, password policy, and S3 bucket analysis |
| =applications/aws/= | AWS IAM users, account/root security, password policy, S3 public access, open security groups, CloudTrail, Config, SSO |
| =applications/github/= | GitHub admin enumeration, org security settings, webhooks, deploy keys, secret-scanning/Dependabot alerts, audit log, branch protections, commits |
| =applications/gitlab/= | GitLab group/project members, branch protections, approvals, pipelines, audit events |
| =databases/mongo/= | MongoDB admin enumeration |
Expand Down
19 changes: 17 additions & 2 deletions applications/aws/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
> **NOTE**: Authentication uses the standard AWS credential chain (environment
> variables, shared config/credentials, SSO profiles, instance roles). This tool
> never handles access keys directly. Read-only permissions are enough — IAM
> `Get*`/`List*`, S3 `s3:GetBucket*` + `s3:ListAllMyBuckets`, and for the SSO
> `Get*`/`List*`, S3 `s3:GetBucket*` + `s3:ListAllMyBuckets`, EC2
> `ec2:DescribeRegions`/`DescribeSecurityGroups`, `cloudtrail:DescribeTrails` +
> `GetTrailStatus`, `config:DescribeConfigurationRecorders*`, and for the SSO
> check `sso:List*`/`sso:Describe*`, `identitystore:Describe*`, and
> `organizations:ListAccounts`.
> `organizations:ListAccounts`. The SecurityAudit managed policy covers these.

---

Expand All @@ -21,6 +23,12 @@ export AWS_DEFAULT_REGION=us-east-1 # optional
export AWS_AUDIT_ACCOUNT=my-account # optional; only for the SSO check
```

If you authenticate with `aws login` / IAM Identity Center (SSO), those
credentials use the AWS Common Runtime provider, which needs the `crt` extra.
It is included via `botocore[crt]` in `requirements.txt`; if you installed
boto3 separately, run `pip install "botocore[crt]"`. Without it you'll see
`MissingDependencyException: ... requires an additional dependency`.

## Usage

```bash
Expand All @@ -42,10 +50,17 @@ Creates a directory: `<out>/aws_audit_<profile>_<YYYY-MM-DD>/`
|---|---|
| `iam_users.csv` | IAM users with MFA status, access-key count/age, console password, last use |
| `password_policy.csv` | Account IAM password policy (length, complexity, rotation, reuse) |
| `account_security.csv` | Account summary — root MFA, root access keys, and resource counts |
| `s3_public_access.csv` | Per-bucket Public Access Block, policy public status, and ACL public exposure |
| `open_security_groups.csv` | Security-group ingress rules open to `0.0.0.0/0` or `::/0`, across all regions |
| `cloudtrail.csv` | CloudTrail trails — logging status, multi-region, log-file validation |
| `config_recorders.csv` | AWS Config recording status per region (gaps are flagged) |
| `sso_assignments.csv` | IAM Identity Center permission-set assignments per account (Identity Center + Organizations) |
| `summary.txt` | Row counts per section |

`open_security_groups.csv` and `config_recorders.csv` scan every enabled region,
so they take longer on accounts with many regions.

Checks that aren't available (no password policy, no Identity Center instance,
missing permissions) are skipped with a warning; the rest still run.

Expand Down
10 changes: 9 additions & 1 deletion applications/aws/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from datetime import date

import config
from collectors import iam, s3, sso
from collectors import iam, monitoring, s3, security_groups, sso
from reporters import csv_reporter


Expand Down Expand Up @@ -86,7 +86,15 @@ def collect(label, fn, filename):

collect("IAM users", iam.iam_users, "iam_users.csv")
collect("Password policy", iam.password_policy, "password_policy.csv")
collect("Account security", iam.account_security, "account_security.csv")
collect("S3 public access", s3.s3_public_access, "s3_public_access.csv")
collect(
"Open security groups",
security_groups.security_groups,
"open_security_groups.csv",
)
collect("CloudTrail", monitoring.cloudtrail, "cloudtrail.csv")
collect("AWS Config recorders", monitoring.config_recorders, "config_recorders.csv")
collect("SSO assignments", sso.sso_assignments, "sso_assignments.csv")

print()
Expand Down
6 changes: 6 additions & 0 deletions applications/aws/collectors/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,9 @@ def account_id(cfg):
return cfg["session"].client("sts").get_caller_identity()["Account"]
except Exception:
return ""


def enabled_regions(cfg):
"""Return the region names enabled for the account (for region-scoped checks)."""
ec2 = cfg["session"].client("ec2", region_name=cfg.get("region") or "us-east-1")
return [r["RegionName"] for r in ec2.describe_regions().get("Regions", [])]
26 changes: 25 additions & 1 deletion applications/aws/collectors/iam.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""
Collect IAM user hygiene and the account password policy.
Collect IAM user hygiene, the account password policy, and account-level
security summary (root MFA, root access keys).
"""

import sys
Expand All @@ -8,6 +9,29 @@
from botocore.exceptions import ClientError


def account_security(cfg):
"""
One row of account-level security signals from the IAM account summary:
whether the root user has MFA and access keys, plus resource counts.
"""
iam = cfg["session"].client("iam")
s = iam.get_account_summary()["SummaryMap"]
return [
{
"root_mfa_enabled": bool(s.get("AccountMFAEnabled", 0)),
"root_access_keys_present": bool(s.get("AccountAccessKeysPresent", 0)),
"root_signing_certs_present": bool(
s.get("AccountSigningCertificatesPresent", 0)
),
"mfa_devices": s.get("MFADevices", 0),
"users": s.get("Users", 0),
"groups": s.get("Groups", 0),
"roles": s.get("Roles", 0),
"policies": s.get("Policies", 0),
}
]


def iam_users(cfg):
"""
One row per IAM user: MFA status, access-key count and oldest key age,
Expand Down
81 changes: 81 additions & 0 deletions applications/aws/collectors/monitoring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""
Collect audit-logging posture: CloudTrail trails and AWS Config recorders.
"""

import sys

from botocore.exceptions import ClientError

from .api import enabled_regions


def cloudtrail(cfg):
"""
One row per CloudTrail trail: whether it is logging, multi-region, and has
log-file validation. An empty result means no trails are configured.
"""
region = cfg.get("region") or "us-east-1"
ct = cfg["session"].client("cloudtrail", region_name=region)
rows = []
for trail in ct.describe_trails(includeShadowTrails=False).get("trailList", []):
try:
status = ct.get_trail_status(Name=trail["TrailARN"])
except ClientError:
status = {}
rows.append(
{
"name": trail.get("Name", ""),
"home_region": trail.get("HomeRegion", ""),
"multi_region": trail.get("IsMultiRegionTrail"),
"log_file_validation": trail.get("LogFileValidationEnabled"),
"is_logging": status.get("IsLogging"),
"s3_bucket": trail.get("S3BucketName", ""),
}
)
return rows


def config_recorders(cfg):
"""
One row per region: whether AWS Config is recording. Regions with no
recorder are reported so gaps are visible.
"""
session = cfg["session"]
rows = []
for region in enabled_regions(cfg):
try:
cc = session.client("config", region_name=region)
recorders = cc.describe_configuration_recorders().get(
"ConfigurationRecorders", []
)
statuses = {
s["name"]: s
for s in cc.describe_configuration_recorder_status().get(
"ConfigurationRecordersStatus", []
)
}
except ClientError as e:
print(f" Skipping {region}: config returned {e}", file=sys.stderr)
continue

if not recorders:
rows.append(
{
"region": region,
"recorder": "(none)",
"recording": False,
"last_status": "",
}
)
continue
for r in recorders:
st = statuses.get(r["name"], {})
rows.append(
{
"region": region,
"recorder": r["name"],
"recording": st.get("recording"),
"last_status": st.get("lastStatus", ""),
}
)
return rows
63 changes: 63 additions & 0 deletions applications/aws/collectors/security_groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
Collect security-group ingress rules open to the internet, across all regions.

Only rules allowing 0.0.0.0/0 or ::/0 are reported — one row per open rule.
"""

import sys

from botocore.exceptions import ClientError

from .api import enabled_regions

OPEN_V4 = "0.0.0.0/0"
OPEN_V6 = "::/0"


def security_groups(cfg):
session = cfg["session"]
rows = []
for region in enabled_regions(cfg):
try:
ec2 = session.client("ec2", region_name=region)
groups = _all_groups(ec2)
except ClientError as e:
print(f" Skipping {region}: ec2 returned {e}", file=sys.stderr)
continue
for sg in groups:
rows.extend(_open_rules(region, sg))
return rows


def _all_groups(ec2):
groups = []
for page in ec2.get_paginator("describe_security_groups").paginate():
groups.extend(page.get("SecurityGroups", []))
return groups


def _open_rules(region, sg):
rows = []
for perm in sg.get("IpPermissions", []):
open_to = [
r["CidrIp"] for r in perm.get("IpRanges", []) if r.get("CidrIp") == OPEN_V4
]
open_to += [
r["CidrIpv6"]
for r in perm.get("Ipv6Ranges", [])
if r.get("CidrIpv6") == OPEN_V6
]
if not open_to:
continue
rows.append(
{
"region": region,
"group_id": sg.get("GroupId", ""),
"group_name": sg.get("GroupName", ""),
"protocol": perm.get("IpProtocol", ""),
"from_port": perm.get("FromPort", "all"),
"to_port": perm.get("ToPort", "all"),
"open_to": ", ".join(open_to),
}
)
return rows
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ PyYAML
pytest
requests
boto3
botocore[crt]
textual
dash
plotly
Expand Down
30 changes: 29 additions & 1 deletion tui/aws_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@
if _REPO_ROOT not in sys.path:
sys.path.insert(0, _REPO_ROOT)

from applications.aws.collectors import api, iam, s3, sso
from applications.aws.collectors import (
api,
iam,
monitoring,
s3,
security_groups,
sso,
)
from applications.aws.reporters import csv_reporter

# --- Check registry ---------------------------------------------------------
Expand All @@ -34,12 +41,33 @@
iam.password_policy,
"password_policy.csv",
),
Check(
"account_security",
"Account security (root MFA)",
iam.account_security,
"account_security.csv",
),
Check(
"s3_public_access",
"S3 public access",
s3.s3_public_access,
"s3_public_access.csv",
),
Check(
"security_groups",
"Open security groups",
security_groups.security_groups,
"open_security_groups.csv",
note="scans all regions",
),
Check("cloudtrail", "CloudTrail", monitoring.cloudtrail, "cloudtrail.csv"),
Check(
"config_recorders",
"AWS Config recorders",
monitoring.config_recorders,
"config_recorders.csv",
note="scans all regions",
),
Check(
"sso_assignments",
"SSO assignments",
Expand Down
Loading
Loading