Conversation
There was a problem hiding this comment.
Pull request overview
This PR (Release/v2.5.0) implements three new automated compliance controls for Azure CIS v5.0.0, replacing previously manual checks, and includes a bug fix for file share soft delete validation.
Changes:
- Added automated query implementations for CIS v5.0.0 controls: storage account access key rotation (9.3.1.2), diagnostic settings for subscription activity logs (6.1.1.1), and user MFA enforcement (5.1.2)
- Fixed NULL handling in storage account file share soft delete validation query
- Updated Azure plugin minimum version requirement from 1.11.0 to 1.12.0
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| mod.pp | Updated Azure plugin minimum version to 1.12.0 |
| regulatory_compliance/storage.pp | Added storage account access keys periodic regeneration control and query; fixed NULL handling in file share soft delete query |
| regulatory_compliance/monitor.pp | Added diagnostic settings for subscription control and query |
| regulatory_compliance/activedirectory.pp | Added user MFA enabled control and query |
| cis_v500/section_9.pp | Updated control 9.3.1.2 to use new automated storage account keys query |
| cis_v500/section_6.pp | Updated control 6.1.1.1 to use new automated diagnostic settings query |
| cis_v500/section_5.pp | Updated control 5.1.2 to use new automated MFA query |
| all_controls/storage.pp | Added new storage account access keys control to benchmark |
| all_controls/monitor.pp | Added new diagnostic settings control to benchmark; reordered controls alphabetically |
| all_controls/activedirectory.pp | Added new user MFA control to benchmark; removed trailing comma |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
regulatory_compliance/storage.pp
Outdated
| azure_storage_account as sa, | ||
| jsonb_array_elements(sa.access_keys) as key | ||
| ) | ||
| select | ||
| saks.id as resource, | ||
| case | ||
| when saks.days_since_rotation > 90 then 'alarm' | ||
| else 'ok' | ||
| end as status, | ||
| format( | ||
| 'Access key %s for storage account %s was last rotated %s days ago (on %s).', | ||
| key_name, | ||
| storage_account_name, | ||
| floor(days_since_rotation)::int, | ||
| last_rotated | ||
| ) as reason |
There was a problem hiding this comment.
The query uses jsonb_array_elements(sa.access_keys) which will exclude storage accounts where access_keys is null or an empty array from the results. This means storage accounts without accessible keys won't be reported at all. Consider handling this case explicitly - either by checking for null/empty arrays first or by using a LEFT JOIN approach similar to the storage_account_uses_private_link query (line 440-478) which handles null arrays. Storage accounts without access keys should likely be reported with a different status (e.g., 'skip' or specific message).
| azure_storage_account as sa, | |
| jsonb_array_elements(sa.access_keys) as key | |
| ) | |
| select | |
| saks.id as resource, | |
| case | |
| when saks.days_since_rotation > 90 then 'alarm' | |
| else 'ok' | |
| end as status, | |
| format( | |
| 'Access key %s for storage account %s was last rotated %s days ago (on %s).', | |
| key_name, | |
| storage_account_name, | |
| floor(days_since_rotation)::int, | |
| last_rotated | |
| ) as reason | |
| azure_storage_account as sa | |
| left join lateral jsonb_array_elements(sa.access_keys) as key on true | |
| ) | |
| select | |
| saks.id as resource, | |
| case | |
| when saks.key_name is null then 'skip' | |
| when saks.days_since_rotation > 90 then 'alarm' | |
| else 'ok' | |
| end as status, | |
| case | |
| when saks.key_name is null then format( | |
| 'Storage account %s has no access keys available.', | |
| saks.storage_account_name | |
| ) | |
| else format( | |
| 'Access key %s for storage account %s was last rotated %s days ago (on %s).', | |
| saks.key_name, | |
| saks.storage_account_name, | |
| floor(saks.days_since_rotation)::int, | |
| saks.last_rotated | |
| ) | |
| end as reason |
regulatory_compliance/storage.pp
Outdated
| format( | ||
| 'Access key %s for storage account %s was last rotated %s days ago (on %s).', | ||
| key_name, | ||
| storage_account_name, | ||
| floor(days_since_rotation)::int, | ||
| last_rotated | ||
| ) as reason | ||
| ${replace(local.common_dimensions_qualifier_sql, "__QUALIFIER__", "saks.")} | ||
| ${replace(local.common_dimensions_qualifier_subscription_sql, "__QUALIFIER__", "sub.")} |
There was a problem hiding this comment.
The query is missing the tag_dimensions_qualifier_sql dimension that is consistently included in other storage account queries in this file. The CTE 'storage_account_key_status' includes the 'tags' field from azure_storage_account, so tag dimensions should be included in the output for consistency with other queries in this file. Compare with queries like 'storage_account_secure_transfer_required_enabled' (line 362) and 'storage_account_key_rotation_reminder_enabled' (line 1229) which include tag dimensions.
…dle null access keys (#362)
Checklist