GRC Engineering in Practice: Detecting NIST Non-Compliance with the AWS CLI

GRC Engineering · AWS CLI · NIST 800-53 · Compliance as Code · Cloud Security · Continuous Compliance · DevSecOps · Audit Automation

IA-2(1): MFA for privileged accounts

Multi-factor authentication for accounts is one of the most-cited audit findings. List users without an MFA device:

for user in $(aws iam list-users --query 'Users[].UserName' --output text); do

 mfa=$(aws iam list-mfa-devices --user-name "$user" \

    --query 'MFADevices' --output text)

 [ -z "$mfa" ] && echo "NON-COMPLIANT (IA-2): $user has no MFA"

done

Also check the root account — a single command that should return 1:

aws iam get-account-summary --query 'SummaryMap.AccountMFAEnabled'

AU-2 / AU-12: Audit logging

NIST requires event logging across the environment. In AWS terms: CloudTrail must exist, be multi-region, and be actively logging.

aws cloudtrail describe-trails \

 --query 'trailList[].{Name:Name,MultiRegion:IsMultiRegionTrail}'

aws cloudtrail get-trail-status --name main-trail \

 --query 'IsLogging'

An empty trail list or IsLogging: false is a direct AU-2 failure worth escalating the same day.

SC-28: Protection of information at rest

Unencrypted storage violates SC-28. Sweep S3 buckets for missing default encryption:

for b in $(aws s3api list-buckets --query 'Buckets[].Name' --output text); do

 aws s3api get-bucket-encryption --bucket "$b" >/dev/null 2>&1 \

  || echo "NON-COMPLIANT (SC-28): $b has no default encryption"

done

Repeat the pattern for EBS (aws ec2 get-ebs-encryption-by-default) and RDS (aws rds describe-db-instances --query 'DBInstances[?StorageEncrypted==\false`].DBInstanceIdentifier'`).

AC-3 / SC-7: Boundary protection

Security groups exposing management ports to the world are the cloud's unlocked front door:

aws ec2 describe-security-groups \

 --filters Name=ip-permission.cidr,Values=0.0.0.0/0 \

      Name=ip-permission.from-port,Values=22 \

 --query 'SecurityGroups[].{ID:GroupId,Name:GroupName}'

Run the same query for 3389 (RDP) and 3306/5432 (databases). Anything returned is an SC-7 finding. Pair it with the S3 public access block check (aws s3api get-public-access-block --bucket <name>) to cover data-plane exposure.

CM-8: System component inventory

You cannot protect assets you have not inventoried. A dated export of running instances is CM-8 evidence in one line:

aws ec2 describe-instances \

 --query 'Reservations[].Instances[].{ID:InstanceId,Type:InstanceType,State:State.Name}' \

 --output json > inventory-$(date +%F).json

AC-2: Account management — stale credentials

Dormant accounts with active keys violate AC-2's periodic-review requirement. Generate the credential report and flag keys unused for 90+ days:

aws iam generate-credential-report

aws iam get-credential-report --query 'Content' --output text | base64 -d

The CSV output drops straight into your evidence repository — parse access_key_1_last_used_date and you have a repeatable access review instead of a quarterly scramble.

From scripts to a program

Individual commands prove the concept; the engineering discipline comes from how you run them. Wrap the checks in a single script that emits JSON with a control ID, resource, status, and timestamp. Run it on a schedule (cron, CI pipeline, or Lambda). Version-control it, so your checks themselves have change history — auditors love that. Fail the pipeline on new criticals, and route findings into your ticketing system so remediation is tracked, not just detected.

When the ad-hoc scripts start straining, graduate to AWS Config conformance packs (there is a prebuilt one for NIST 800-53 Rev. 5) and Security Hub's NIST standard, which run these evaluations continuously. But do not skip the CLI stage: writing the checks yourself forces you to understand exactly what each NIST control means in your environment — and that understanding is the real difference between compliance theatre and GRC engineering. The auditor asks, "How do you know?" Your answer becomes: "Here's the code, here's the schedule, here's every result since January."

Back to all articles

Features · Integrations · Pricing · Frameworks · About · Blog