AWS
Best Practices
Security
AWS CIS Benchmark in 2026: Automated Compliance with SQL-Based Policies
Somewhere in your security tooling, there's a compliance check written against CIS AWS Foundations Benchmark v1.2. It's probably still running. It's probably passing. And it's almost certainly not checking what you think it's checking.
The benchmark has gone through multiple major revisions since 2021. v3.0 (January 2024) added seven new controls including AWS Organizations management checks and account-level security defaults. AWS Security Hub now ships CIS v5.0 with 40 automated controls. The checks your team configured three years ago map to a different standard than what auditors reference today - and the version drift is subtle enough that most teams don't notice until a review.
What Is the CIS AWS Foundations Benchmark? #
The CIS AWS Foundations Benchmark is a set of prescriptive configuration recommendations for AWS accounts maintained by the Center for Internet Security. It maps to multiple compliance frameworks - SOC 2, HIPAA, PCI-DSS, NIST CSF - which is why most security teams treat it as the baseline to establish before any framework-specific work begins.
What Changed from v1.2 to v5.0? #
If your compliance checks were last updated in 2021, here's the short version of what you've missed:
v3.0 is the biggest gap for teams still running older checks. It added controls for AWS Organizations management accounts, account-level defaults for new regions, and tightened the hardware MFA requirement for root. If your environment uses AWS Organizations (and most multi-account setups do), v3+ has checks that specifically target that configuration - checks that simply didn't exist in v1.4.
A check that showed "PASS" in 2021 might map to a retired or updated control in the current standard. Not because your configuration changed, but because the test criteria did.
What Are the Six CIS Control Domains? #
CIS AWS checks cover six main areas. Most teams have IAM and logging covered reasonably well. Monitoring is where we consistently see gaps.
IAM #
Root account usage monitoring, MFA enforcement for all console users, access key rotation within 90 days, and password policy requirements. v3.0 tightened the root MFA requirement - hardware MFA is now required, not just a virtual authenticator app. If you set up root MFA years ago with Google Authenticator and called it done, that's worth revisiting. For SQL-based IAM security checks beyond the CIS baseline, see Continuous AWS IAM Security Best Practices and Investigating Toxic IAM and Access Combinations in AWS.
Logging #
CloudTrail enabled in all regions (not just your primary region), log file validation, S3 access logging on the CloudTrail storage bucket itself, and KMS encryption for log files. Two separate requirements catch a lot of teams. First: the S3 bucket that CloudTrail writes to needs its own access logging enabled - CloudTrail being on doesn't mean the bucket is covered. Second: CIS requires a customer-managed KMS key for encrypting CloudTrail logs, not just SSE-S3. If you enabled CloudTrail's S3 encryption using the default SSE-S3 option, that control fails.
Monitoring #
The 14 required events: unauthorized API calls, console sign-in without MFA, root account usage, IAM policy changes, CloudTrail configuration changes, failed console authentication, CMK disable or deletion, S3 bucket policy changes, AWS Config configuration changes, security group changes, network ACL changes, network gateway changes, route table changes, and VPC changes. See the full list in the AWS Security Hub docs.
Storage #
Block public access at the account level, versioning on S3 buckets used for logging, server-side encryption, MFA delete protection. The account-level block public access setting is a single configuration change - but it has to be set at the account level, not just on individual buckets. For a complete walkthrough of finding all exposed buckets with SQL, see How to Find All Publicly Accessible S3 Buckets in Your AWS Account.
Networking #
VPC Flow Logs enabled in all VPCs, default security group restricted to deny all traffic, NACL review. Default security groups are easy to miss when new VPCs are created, especially in accounts that provision infrastructure programmatically.
Data Protection (Added in v3.0) #
KMS key rotation enabled for all customer-managed keys. Operationally straightforward, but older CMKs in long-running accounts often have rotation disabled from before this requirement existed. For a deeper look at KMS access control and audit patterns, see AWS KMS Key Grants Explained: Access Control & Security Audit.
How Do You Actually Run These Checks? #
The honest answer is that there's no single right tool. The choice depends on what problem you're trying to solve. For a detailed breakdown of how AWS-native tooling compares to CloudQuery, see AWS Config vs. CloudQuery and CSPMs vs. CloudQuery.
AWS Security Hub is the fastest path. Enable the CIS standard in the console, and you get 40 controls running continuously without managing any infrastructure. The limitation is that you can't change the check logic. When a control fires on a resource you've intentionally exempted, you manage that through Security Hub's suppression system - not in code, not in version control, not with an expiration date.
Prowler is an open-source CLI tool with 300+ CIS and FSBP checks, and it's a solid choice for one-off scans or CI integration. Per-account execution means cross-account aggregation is a scripting problem you have to solve separately.
CloudQuery SQL Policies sync your AWS data into PostgreSQL (or Snowflake, BigQuery, ClickHouse, or whatever you already use), then evaluate CIS controls as SQL queries you own. There's real setup involved - you're operating a data sync pipeline. The payoff: check logic lives in SQL files in a repository, exceptions are auditable, and cross-account compliance is a single query.
How Do You Set Up CIS Checks with CloudQuery? #
Setting Up the Sync #
Create a
config.yml that pulls the tables needed for CIS checks:kind: source
spec:
name: aws
path: cloudquery/aws
version: 'v28.0.0' # check /hub/plugins/source/cloudquery/aws for latest
tables:
- aws_iam_users
- aws_iam_root_summary
- aws_iam_virtual_mfa_devices
- aws_iam_password_policies
- aws_cloudtrail_trails
- aws_s3_buckets
- aws_ec2_vpcs
- aws_ec2_security_groups
- aws_kms_keys
- aws_cloudwatch_alarms
- aws_guardduty_detectors
destinations:
- postgresql
---
kind: destination
spec:
name: postgresql
path: cloudquery/postgresql
version: 'v8.0.0' # check /hub/plugins/destination/cloudquery/postgresql for latest
spec:
connection_string: '${CQ_DSN}'
Run
cloudquery sync config.yml. The AWS source documentation covers authentication - the plugin uses the same credential chain as the AWS SDK, so existing profiles and environment variables work as-is.If you're using the CloudQuery Platform rather than the CLI, the sync runs as a managed pipeline - no cron jobs to configure or retry logic to write. You set the schedule and the Platform handles execution and monitoring.
Example CIS SQL Queries #
The full production-ready CIS policy set is in the CloudQuery Policies and the cloudquery/policies GitHub repository - you don't need to write these from scratch. The examples below show the pattern. Once your AWS data is synced, you can also build org-specific rules on top of the same tables: tag enforcement, naming conventions, cost controls. CIS becomes the baseline, not the ceiling - Mapping SOC 2 and CIS Controls to SQL for Continuous Compliance shows how the same pattern extends to full audit evidence generation.
Root account access key usage (CIS 1.1)
SELECT account_id
FROM aws_iam_root_summary
WHERE access_key_1_last_used_date > NOW() - INTERVAL '30 days'
OR access_key_2_last_used_date > NOW() - INTERVAL '30 days';
S3 buckets missing full public access block (CIS 2.1.5)
SELECT name, account_id, region
FROM aws_s3_buckets
WHERE block_public_acls IS NOT TRUE
OR block_public_policy IS NOT TRUE
OR ignore_public_acls IS NOT TRUE
OR restrict_public_buckets IS NOT TRUE;
CloudTrail not enabled in all regions (CIS 3.1)
SELECT account_id, name, home_region
FROM aws_cloudtrail_trails
WHERE is_multi_region_trail IS NOT TRUE
OR include_global_service_events IS NOT TRUE;
Customer-managed KMS keys without rotation enabled (CIS 3.8)
SELECT key_id, account_id, region
FROM aws_kms_keys
WHERE key_rotation_enabled IS NOT TRUE
AND key_manager = 'CUSTOMER';
Exception Management in Source Control #
Security Hub suppression is fine for a handful of one-off exceptions. It gets painful when you have 30 exceptions across 15 accounts and someone asks you to prove they were all reviewed in the last 90 days - because that information doesn't exist in a form you can query.
With SQL, you maintain an exceptions table. It lives in your database, it's in source control, and it's auditable:
-- Create your exceptions tracking table
CREATE TABLE cis_exceptions (
resource_id TEXT,
control_id TEXT,
reason TEXT,
approved_by TEXT,
expires_at TIMESTAMP
);
-- Log an approved exception
INSERT INTO cis_exceptions VALUES (
'arn:aws:s3:::my-public-assets-bucket',
'CIS-2.1.5',
'Public static asset bucket - engineering review on 2026-04-10',
'platform-security-team',
'2026-12-31'
);
-- CIS 2.1.5 with exceptions applied
SELECT b.name, b.account_id, b.region
FROM aws_s3_buckets b
LEFT JOIN cis_exceptions e
ON e.resource_id = b.arn
AND e.control_id = 'CIS-2.1.5'
AND (e.expires_at IS NULL OR e.expires_at > NOW())
WHERE (
b.block_public_acls IS NOT TRUE
OR b.block_public_policy IS NOT TRUE
OR b.ignore_public_acls IS NOT TRUE
OR b.restrict_public_buckets IS NOT TRUE
)
AND e.resource_id IS NULL;
Exceptions have owners, reasons, and expiration dates. They're in a git commit, not a UI click. When your next compliance review comes around, you can show exactly which exceptions were in place and when they expired.
Multi-Account Compliance in One Query #
If CloudQuery is syncing from multiple AWS accounts - configured via
account_ids in the AWS source spec - all accounts land in the same database. A compliance scorecard across your entire organization is one GROUP BY:SELECT
account_id,
COUNT(*) AS total_buckets,
COUNT(*) FILTER (
WHERE block_public_acls IS TRUE
AND block_public_policy IS TRUE
AND ignore_public_acls IS TRUE
AND restrict_public_buckets IS TRUE
) AS compliant_buckets,
ROUND(
100.0 * COUNT(*) FILTER (
WHERE block_public_acls IS TRUE
AND block_public_policy IS TRUE
AND ignore_public_acls IS TRUE
AND restrict_public_buckets IS TRUE
) / NULLIF(COUNT(*), 0), 1
) AS pct_compliant
FROM aws_s3_buckets
GROUP BY account_id
ORDER BY pct_compliant ASC;
Getting the equivalent from Security Hub requires the Organizations aggregator configuration. Prowler requires scripting per-account runs and merging output files. With CloudQuery, it's one query against one database. For the full AWS Organizations setup, see How to Deploy CloudQuery into an AWS Organization.
How Do You Stay Current as the Benchmark Evolves? #
When CIS releases a new version, your SQL policies are files in a repository. You update the query, open a pull request, and your team can see exactly what check logic changed. No waiting for a managed service to adopt the new standard version, no UI workflow to navigate.
One trade-off worth naming directly: CloudQuery checks run on your configured sync schedule, not continuously. For security-relevant tables, most teams sync every 30-60 minutes. That's a detection lag compared to Security Hub's continuous evaluation. For teams using CIS as a governance baseline rather than real-time threat detection, scheduled compliance is fine. If you need immediate alerting on configuration drift, pair CloudQuery with AWS Config or Security Hub for that layer - the two work well together.
The CloudQuery Platform's Automations can trigger notifications or remediation workflows when a SQL policy fires on the next sync. You define the trigger in SQL and point it at a webhook, Slack channel, or ticketing system. That closes most of the gap for scheduled use cases without building a separate alerting pipeline.
Key Takeaways #
- The CIS AWS Foundations Benchmark is now at v3.0+ with AWS Security Hub supporting v5.0 (40 controls). Checks written in 2021 are running against a different standard than auditors reference today.
- Monitoring (14 CloudWatch alarms) is the most commonly incomplete area. Check that you have both the metric filters AND the corresponding alarms in place - missing either causes the control to fail.
- AWS Security Hub is the right choice when you need immediate coverage and can work within its suppression model for exceptions.
- CloudQuery SQL Policies fit when you need exceptions in source control, cross-resource correlation, or a compliance view across many AWS accounts without per-account scripting.
- Exception management in code beats UI suppression for audits. An exceptions table with owners, reasons, and expiration dates is answerable in SQL; a suppression list in a console is not.
Moving Forward #
The full CloudQuery CIS policy set - covering all major control domains - is available in the CloudQuery Policies. If you're already running AWS Security Hub CIS checks and want to add the SQL layer for multi-account reporting, exception management in code, or custom policies beyond the standard, the two tools run well in parallel. The same approach works for AWS Foundational Security Best Practices and AWS PCI DSS if you're mapping to those frameworks as well.
See CloudQuery Policies in Action
See how CloudQuery's SQL-based Policies work for CIS compliance, multi-account reporting, and custom governance rules across your entire cloud estate. Or start with the platform documentation.
Frequently Asked Questions #
What Is the Latest Version of the CIS AWS Foundations Benchmark? #
The CIS AWS Foundations Benchmark v3.0 was released in January 2024. AWS Security Hub currently supports v5.0 of the benchmark (as of October 2025), which includes 40 automated controls. Check cisecurity.org for the most current published version.
What Is the Difference Between Level 1 and Level 2 CIS Controls? #
Level 1 controls are foundational configurations with minimal operational impact - enabling CloudTrail in all regions, rotating access keys within 90 days, blocking public S3 access at the account level. Level 2 controls are more restrictive and intended for environments with elevated risk. Some Level 2 requirements affect usability (hardware MFA for all console users, for example), so most teams implement Level 1 first and evaluate Level 2 against their specific risk profile.
How Does AWS Security Hub Run CIS Benchmark Checks? #
AWS Security Hub runs automated compliance checks against the CIS AWS Foundations Benchmark as a managed Security Standard. Enable it in the Security Hub console or via API, and it continuously evaluates your resources against 40 controls (v5.0 standard). The checks run automatically without you managing query logic. See the AWS Security Hub CIS documentation for setup details.
How Do I Run CIS Checks with SQL? #
Sync your AWS infrastructure data to PostgreSQL using CloudQuery (
cloudquery sync config.yml), then run SQL queries against the synced tables. CloudQuery's AWS source pulls IAM, S3, CloudTrail, EC2, KMS, and other service data into queryable tables. The CloudQuery Policies provides the full CIS control query set with exact schemas for each supported destination.What CIS Controls Were Added in v3.0? #
CIS v3.0 added seven new controls focused on AWS Organizations management, account-level security defaults, and stricter MFA requirements. Key additions: checks for security defaults in new AWS accounts created through Organizations, hardware MFA requirements for root accounts, and MITRE ATT&CK mappings for each control. Steampipe's v3.0 overview has a detailed breakdown of what changed from v2.0.
How Often Should I Run CIS Compliance Checks? #
AWS Security Hub runs continuously. CloudQuery-based checks run on your configured sync schedule - every 30-60 minutes for security-relevant tables is typical. For audit purposes, daily full syncs with policy evaluation are generally sufficient. If you need faster drift detection, schedule IAM, S3, and CloudTrail tables on shorter intervals.
How Do I Manage Exceptions to CIS Benchmark Controls? #
Maintain an exceptions table in your database with columns for
resource_id, control_id, reason, approved_by, and expires_at. Use a LEFT JOIN in your policy queries to exclude resources with active approvals. This keeps exceptions version-controlled, tied to specific owners, and time-bounded - and queryable when you need to prove they were reviewed.Can CloudQuery and AWS Security Hub Work Together? #
Yes, most teams use both. Security Hub provides continuous detection with AWS-native alerting. CloudQuery adds the SQL layer for exception management in source control, cross-account aggregation, multi-destination reporting, and custom policies beyond what the CIS standard covers. Security Hub catches things in real time; CloudQuery gives you the governance and analytics layer on top.