regscale-cli 6.21.2.0__py3-none-any.whl → 6.28.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. regscale/_version.py +1 -1
  2. regscale/airflow/hierarchy.py +2 -2
  3. regscale/core/app/api.py +5 -2
  4. regscale/core/app/application.py +36 -6
  5. regscale/core/app/internal/control_editor.py +73 -21
  6. regscale/core/app/internal/evidence.py +727 -204
  7. regscale/core/app/internal/login.py +4 -2
  8. regscale/core/app/internal/model_editor.py +219 -64
  9. regscale/core/app/utils/app_utils.py +86 -12
  10. regscale/core/app/utils/catalog_utils/common.py +1 -1
  11. regscale/core/login.py +21 -4
  12. regscale/core/utils/async_graphql_client.py +363 -0
  13. regscale/core/utils/date.py +77 -1
  14. regscale/dev/cli.py +26 -0
  15. regscale/dev/code_gen.py +109 -24
  16. regscale/dev/version.py +72 -0
  17. regscale/integrations/commercial/__init__.py +30 -2
  18. regscale/integrations/commercial/aws/audit_manager_compliance.py +3908 -0
  19. regscale/integrations/commercial/aws/cli.py +3107 -54
  20. regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
  21. regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
  22. regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
  23. regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
  24. regscale/integrations/commercial/{amazon → aws}/common.py +71 -19
  25. regscale/integrations/commercial/aws/config_compliance.py +914 -0
  26. regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
  27. regscale/integrations/commercial/aws/control_compliance_analyzer.py +439 -0
  28. regscale/integrations/commercial/aws/evidence_generator.py +283 -0
  29. regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
  30. regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
  31. regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
  32. regscale/integrations/commercial/aws/iam_evidence.py +574 -0
  33. regscale/integrations/commercial/aws/inventory/__init__.py +338 -22
  34. regscale/integrations/commercial/aws/inventory/base.py +107 -5
  35. regscale/integrations/commercial/aws/inventory/resources/analytics.py +390 -0
  36. regscale/integrations/commercial/aws/inventory/resources/applications.py +234 -0
  37. regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
  38. regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
  39. regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
  40. regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
  41. regscale/integrations/commercial/aws/inventory/resources/compute.py +328 -9
  42. regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
  43. regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
  44. regscale/integrations/commercial/aws/inventory/resources/database.py +481 -31
  45. regscale/integrations/commercial/aws/inventory/resources/developer_tools.py +253 -0
  46. regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
  47. regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
  48. regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
  49. regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
  50. regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
  51. regscale/integrations/commercial/aws/inventory/resources/machine_learning.py +358 -0
  52. regscale/integrations/commercial/aws/inventory/resources/networking.py +390 -67
  53. regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
  54. regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
  55. regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
  56. regscale/integrations/commercial/aws/inventory/resources/storage.py +288 -29
  57. regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
  58. regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
  59. regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
  60. regscale/integrations/commercial/aws/kms_evidence.py +879 -0
  61. regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
  62. regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
  63. regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
  64. regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
  65. regscale/integrations/commercial/aws/org_evidence.py +666 -0
  66. regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
  67. regscale/integrations/commercial/aws/s3_evidence.py +632 -0
  68. regscale/integrations/commercial/aws/scanner.py +1072 -205
  69. regscale/integrations/commercial/aws/security_hub.py +319 -0
  70. regscale/integrations/commercial/aws/session_manager.py +282 -0
  71. regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
  72. regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
  73. regscale/integrations/commercial/jira.py +489 -153
  74. regscale/integrations/commercial/microsoft_defender/defender.py +326 -5
  75. regscale/integrations/commercial/microsoft_defender/defender_api.py +348 -14
  76. regscale/integrations/commercial/microsoft_defender/defender_constants.py +157 -0
  77. regscale/integrations/commercial/qualys/__init__.py +167 -68
  78. regscale/integrations/commercial/qualys/scanner.py +305 -39
  79. regscale/integrations/commercial/sarif/sairf_importer.py +432 -0
  80. regscale/integrations/commercial/sarif/sarif_converter.py +67 -0
  81. regscale/integrations/commercial/sicura/api.py +79 -42
  82. regscale/integrations/commercial/sicura/commands.py +8 -2
  83. regscale/integrations/commercial/sicura/scanner.py +83 -44
  84. regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
  85. regscale/integrations/commercial/synqly/assets.py +133 -16
  86. regscale/integrations/commercial/synqly/edr.py +2 -8
  87. regscale/integrations/commercial/synqly/query_builder.py +536 -0
  88. regscale/integrations/commercial/synqly/ticketing.py +27 -0
  89. regscale/integrations/commercial/synqly/vulnerabilities.py +165 -28
  90. regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
  91. regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
  92. regscale/integrations/commercial/tenablev2/commands.py +146 -5
  93. regscale/integrations/commercial/tenablev2/scanner.py +1 -3
  94. regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
  95. regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
  96. regscale/integrations/commercial/wizv2/click.py +191 -76
  97. regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
  98. regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
  99. regscale/integrations/commercial/wizv2/compliance_report.py +1592 -0
  100. regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
  101. regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +7 -3
  102. regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +92 -89
  103. regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
  104. regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
  105. regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +66 -9
  106. regscale/integrations/commercial/wizv2/file_cleanup.py +104 -0
  107. regscale/integrations/commercial/wizv2/issue.py +776 -28
  108. regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
  109. regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
  110. regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
  111. regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
  112. regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
  113. regscale/integrations/commercial/wizv2/reports.py +243 -0
  114. regscale/integrations/commercial/wizv2/sbom.py +1 -1
  115. regscale/integrations/commercial/wizv2/scanner.py +1031 -441
  116. regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
  117. regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
  118. regscale/integrations/commercial/wizv2/variables.py +89 -3
  119. regscale/integrations/compliance_integration.py +1036 -151
  120. regscale/integrations/control_matcher.py +432 -0
  121. regscale/integrations/due_date_handler.py +333 -0
  122. regscale/integrations/milestone_manager.py +291 -0
  123. regscale/integrations/public/__init__.py +14 -0
  124. regscale/integrations/public/cci_importer.py +834 -0
  125. regscale/integrations/public/csam/__init__.py +0 -0
  126. regscale/integrations/public/csam/csam.py +938 -0
  127. regscale/integrations/public/csam/csam_agency_defined.py +179 -0
  128. regscale/integrations/public/csam/csam_common.py +154 -0
  129. regscale/integrations/public/csam/csam_controls.py +432 -0
  130. regscale/integrations/public/csam/csam_poam.py +124 -0
  131. regscale/integrations/public/fedramp/click.py +77 -6
  132. regscale/integrations/public/fedramp/docx_parser.py +10 -1
  133. regscale/integrations/public/fedramp/fedramp_cis_crm.py +675 -289
  134. regscale/integrations/public/fedramp/fedramp_five.py +1 -1
  135. regscale/integrations/public/fedramp/poam/scanner.py +75 -7
  136. regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
  137. regscale/integrations/scanner_integration.py +1961 -430
  138. regscale/models/integration_models/CCI_List.xml +1 -0
  139. regscale/models/integration_models/aqua.py +2 -2
  140. regscale/models/integration_models/cisa_kev_data.json +805 -11
  141. regscale/models/integration_models/flat_file_importer/__init__.py +5 -8
  142. regscale/models/integration_models/nexpose.py +36 -10
  143. regscale/models/integration_models/qualys.py +3 -4
  144. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  145. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +87 -18
  146. regscale/models/integration_models/synqly_models/filter_parser.py +332 -0
  147. regscale/models/integration_models/synqly_models/ocsf_mapper.py +124 -25
  148. regscale/models/integration_models/synqly_models/synqly_model.py +89 -16
  149. regscale/models/locking.py +12 -8
  150. regscale/models/platform.py +4 -2
  151. regscale/models/regscale_models/__init__.py +7 -0
  152. regscale/models/regscale_models/assessment.py +2 -1
  153. regscale/models/regscale_models/catalog.py +1 -1
  154. regscale/models/regscale_models/compliance_settings.py +251 -1
  155. regscale/models/regscale_models/component.py +1 -0
  156. regscale/models/regscale_models/control_implementation.py +236 -41
  157. regscale/models/regscale_models/control_objective.py +74 -5
  158. regscale/models/regscale_models/file.py +2 -0
  159. regscale/models/regscale_models/form_field_value.py +5 -3
  160. regscale/models/regscale_models/inheritance.py +44 -0
  161. regscale/models/regscale_models/issue.py +301 -102
  162. regscale/models/regscale_models/milestone.py +33 -14
  163. regscale/models/regscale_models/organization.py +3 -0
  164. regscale/models/regscale_models/regscale_model.py +310 -73
  165. regscale/models/regscale_models/security_plan.py +4 -2
  166. regscale/models/regscale_models/vulnerability.py +3 -3
  167. regscale/regscale.py +25 -4
  168. regscale/templates/__init__.py +0 -0
  169. regscale/utils/threading/threadhandler.py +20 -15
  170. regscale/validation/record.py +23 -1
  171. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/METADATA +17 -33
  172. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/RECORD +310 -111
  173. tests/core/__init__.py +0 -0
  174. tests/core/utils/__init__.py +0 -0
  175. tests/core/utils/test_async_graphql_client.py +472 -0
  176. tests/fixtures/test_fixture.py +13 -8
  177. tests/regscale/core/test_login.py +171 -4
  178. tests/regscale/integrations/commercial/__init__.py +0 -0
  179. tests/regscale/integrations/commercial/aws/__init__.py +0 -0
  180. tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
  181. tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
  182. tests/regscale/integrations/commercial/aws/test_aws_analytics_collector.py +260 -0
  183. tests/regscale/integrations/commercial/aws/test_aws_applications_collector.py +242 -0
  184. tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
  185. tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
  186. tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
  187. tests/regscale/integrations/commercial/aws/test_aws_developer_tools_collector.py +203 -0
  188. tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
  189. tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
  190. tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
  191. tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
  192. tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
  193. tests/regscale/integrations/commercial/aws/test_aws_machine_learning_collector.py +237 -0
  194. tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
  195. tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
  196. tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
  197. tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
  198. tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
  199. tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
  200. tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
  201. tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
  202. tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
  203. tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
  204. tests/regscale/integrations/commercial/aws/test_control_compliance_analyzer.py +375 -0
  205. tests/regscale/integrations/commercial/aws/test_datetime_parsing.py +223 -0
  206. tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
  207. tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
  208. tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
  209. tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
  210. tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
  211. tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
  212. tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
  213. tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
  214. tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
  215. tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
  216. tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
  217. tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
  218. tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
  219. tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
  220. tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
  221. tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
  222. tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
  223. tests/regscale/integrations/commercial/conftest.py +28 -0
  224. tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
  225. tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
  226. tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
  227. tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
  228. tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
  229. tests/regscale/integrations/commercial/test_aws.py +3742 -0
  230. tests/regscale/integrations/commercial/test_burp.py +48 -0
  231. tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
  232. tests/regscale/integrations/commercial/test_dependabot.py +341 -0
  233. tests/regscale/integrations/commercial/test_gcp.py +1543 -0
  234. tests/regscale/integrations/commercial/test_gitlab.py +549 -0
  235. tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
  236. tests/regscale/integrations/commercial/test_jira.py +2204 -0
  237. tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
  238. tests/regscale/integrations/commercial/test_okta.py +1228 -0
  239. tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
  240. tests/regscale/integrations/commercial/test_sicura.py +349 -0
  241. tests/regscale/integrations/commercial/test_snow.py +423 -0
  242. tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
  243. tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
  244. tests/regscale/integrations/commercial/test_stig.py +33 -0
  245. tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
  246. tests/regscale/integrations/commercial/test_stigv2.py +406 -0
  247. tests/regscale/integrations/commercial/test_wiz.py +1365 -0
  248. tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
  249. tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
  250. tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
  251. tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
  252. tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
  253. tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
  254. tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
  255. tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
  256. tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
  257. tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
  258. tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
  259. tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
  260. tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
  261. tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
  262. tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
  263. tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
  264. tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
  265. tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
  266. tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
  267. tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
  268. tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
  269. tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
  270. tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
  271. tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
  272. tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
  273. tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
  274. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -0
  275. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
  276. tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
  277. tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
  278. tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
  279. tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
  280. tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1218 -0
  281. tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
  282. tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
  283. tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
  284. tests/regscale/integrations/public/__init__.py +0 -0
  285. tests/regscale/integrations/public/fedramp/__init__.py +1 -0
  286. tests/regscale/integrations/public/fedramp/test_gen_asset_list.py +150 -0
  287. tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
  288. tests/regscale/integrations/public/test_alienvault.py +220 -0
  289. tests/regscale/integrations/public/test_cci.py +1053 -0
  290. tests/regscale/integrations/public/test_cisa.py +1021 -0
  291. tests/regscale/integrations/public/test_emass.py +518 -0
  292. tests/regscale/integrations/public/test_fedramp.py +1152 -0
  293. tests/regscale/integrations/public/test_fedramp_cis_crm.py +3661 -0
  294. tests/regscale/integrations/public/test_file_uploads.py +506 -0
  295. tests/regscale/integrations/public/test_oscal.py +453 -0
  296. tests/regscale/integrations/test_compliance_status_mapping.py +406 -0
  297. tests/regscale/integrations/test_control_matcher.py +1421 -0
  298. tests/regscale/integrations/test_control_matching.py +155 -0
  299. tests/regscale/integrations/test_milestone_manager.py +408 -0
  300. tests/regscale/models/test_control_implementation.py +118 -3
  301. tests/regscale/models/test_form_field_value_integration.py +304 -0
  302. tests/regscale/models/test_issue.py +378 -1
  303. tests/regscale/models/test_module_integration.py +582 -0
  304. tests/regscale/models/test_tenable_integrations.py +811 -105
  305. regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3057
  306. regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +0 -7388
  307. regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +0 -9605
  308. regscale/integrations/public/fedramp/parts_mapper.py +0 -107
  309. /regscale/integrations/commercial/{amazon → sarif}/__init__.py +0 -0
  310. /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
  311. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/LICENSE +0 -0
  312. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/WHEEL +0 -0
  313. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/entry_points.txt +0 -0
  314. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,490 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """AWS CloudWatch Logs Evidence Integration for RegScale Compliance."""
4
+
5
+ import gzip
6
+ import json
7
+ import logging
8
+ import os
9
+ import tempfile
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime, timedelta
12
+ from pathlib import Path
13
+ from typing import Any, Dict, List, Optional
14
+
15
+ import boto3
16
+
17
+ from regscale.core.app.api import Api
18
+ from regscale.integrations.commercial.aws.cloudwatch_control_mappings import CloudWatchControlMapper
19
+ from regscale.integrations.commercial.aws.inventory.resources.cloudwatch import CloudWatchLogsCollector
20
+ from regscale.integrations.compliance_integration import ComplianceIntegration
21
+ from regscale.models.regscale_models.file import File
22
+
23
+ logger = logging.getLogger("regscale")
24
+
25
+
26
+ @dataclass
27
+ class CloudWatchEvidenceConfig:
28
+ """Configuration for AWS CloudWatch Logs evidence collection."""
29
+
30
+ plan_id: int
31
+ region: str = "us-east-1"
32
+ framework: str = "NIST800-53R5"
33
+ create_issues: bool = False
34
+ update_control_status: bool = True
35
+ create_poams: bool = False
36
+ parent_module: str = "securityplans"
37
+ account_id: Optional[str] = None
38
+ tags: Optional[Dict[str, str]] = None
39
+ log_group_prefix: Optional[str] = None
40
+ create_evidence: bool = False
41
+ create_ssp_attachment: bool = True
42
+ evidence_control_ids: Optional[List[str]] = None
43
+ force_refresh: bool = False
44
+ aws_profile: Optional[str] = None
45
+ aws_access_key_id: Optional[str] = None
46
+ aws_secret_access_key: Optional[str] = None
47
+ aws_session_token: Optional[str] = None
48
+
49
+
50
+ class CloudWatchComplianceItem:
51
+ """Represents CloudWatch Logs configuration for compliance assessment."""
52
+
53
+ def __init__(self, cloudwatch_data: Dict[str, Any]):
54
+ """
55
+ Initialize CloudWatch compliance item from configuration data.
56
+
57
+ :param Dict cloudwatch_data: CloudWatch Logs data from CloudWatchLogsCollector
58
+ """
59
+ self.log_groups = cloudwatch_data.get("LogGroups", [])
60
+ self.log_group_metrics = cloudwatch_data.get("LogGroupMetrics", {})
61
+ self.retention_policies = cloudwatch_data.get("RetentionPolicies", {})
62
+ self.raw_data = cloudwatch_data
63
+
64
+ def to_dict(self) -> Dict[str, Any]:
65
+ """Convert to dictionary representation."""
66
+ return self.raw_data
67
+
68
+
69
+ class AWSCloudWatchEvidenceIntegration(ComplianceIntegration):
70
+ """AWS CloudWatch Logs evidence integration for compliance data collection."""
71
+
72
+ def __init__(self, config: CloudWatchEvidenceConfig):
73
+ """
74
+ Initialize AWS CloudWatch Logs evidence integration.
75
+
76
+ :param CloudWatchEvidenceConfig config: Configuration object containing all parameters
77
+ """
78
+ super().__init__(
79
+ plan_id=config.plan_id,
80
+ framework=config.framework,
81
+ create_issues=config.create_issues,
82
+ update_control_status=config.update_control_status,
83
+ create_poams=config.create_poams,
84
+ parent_module=config.parent_module,
85
+ )
86
+
87
+ self.plan_id = config.plan_id
88
+ self.region = config.region
89
+ self.title = "AWS CloudWatch Logs"
90
+ self.account_id = config.account_id
91
+ self.tags = config.tags or {}
92
+ self.log_group_prefix = config.log_group_prefix
93
+ self.create_evidence = config.create_evidence
94
+ self.create_ssp_attachment = config.create_ssp_attachment
95
+ self.evidence_control_ids = config.evidence_control_ids or []
96
+ self.force_refresh = config.force_refresh
97
+
98
+ # Initialize control mapper
99
+ self.control_mapper = CloudWatchControlMapper(framework=config.framework)
100
+
101
+ # AWS credentials
102
+ self.aws_profile = config.aws_profile
103
+ self.aws_access_key_id = config.aws_access_key_id
104
+ self.aws_secret_access_key = config.aws_secret_access_key
105
+ self.aws_session_token = config.aws_session_token
106
+
107
+ # Initialize components
108
+ self.api = Api()
109
+ self.session = None
110
+ self.collector = None
111
+
112
+ # Cache configuration
113
+ self.cache_ttl_hours = 4
114
+ self.cache_dir = Path(tempfile.gettempdir()) / "regscale" / "aws_cloudwatch_cache"
115
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
116
+
117
+ # Data storage
118
+ self.raw_cloudwatch_data: Dict[str, Any] = {}
119
+ self.cloudwatch_item: Optional[CloudWatchComplianceItem] = None
120
+
121
+ def _get_cache_file_path(self) -> Path:
122
+ """Get cache file path for CloudWatch data."""
123
+ cache_key = f"{self.region}_{self.account_id or 'default'}"
124
+ return self.cache_dir / f"cloudwatch_logs_{cache_key}.json"
125
+
126
+ def _is_cache_valid(self) -> bool:
127
+ """Check if cache is valid and not expired."""
128
+ cache_file = self._get_cache_file_path()
129
+ if not cache_file.exists():
130
+ return False
131
+
132
+ cache_age = datetime.now() - datetime.fromtimestamp(cache_file.stat().st_mtime)
133
+ return cache_age < timedelta(hours=self.cache_ttl_hours)
134
+
135
+ def _save_cache(self, data: Dict[str, Any]) -> None:
136
+ """Save CloudWatch data to cache."""
137
+ cache_file = self._get_cache_file_path()
138
+ try:
139
+ with open(cache_file, "w", encoding="utf-8") as f:
140
+ json.dump(data, f, default=str)
141
+ logger.debug(f"Saved CloudWatch data to cache: {cache_file}")
142
+ except Exception as e:
143
+ logger.warning(f"Failed to save cache: {e}")
144
+
145
+ def _load_cached_data(self) -> Optional[Dict[str, Any]]:
146
+ """Load CloudWatch data from cache."""
147
+ cache_file = self._get_cache_file_path()
148
+ try:
149
+ with open(cache_file, encoding="utf-8") as f:
150
+ data = json.load(f)
151
+
152
+ # Validate cache format - must be a dict
153
+ if not isinstance(data, dict):
154
+ logger.warning("Invalid cache format detected (not a dict). Invalidating cache.")
155
+ return None
156
+
157
+ logger.info(f"Loaded CloudWatch data from cache (age: {self._get_cache_age_hours():.1f} hours)")
158
+ return data
159
+ except Exception as e:
160
+ logger.warning(f"Failed to load cache: {e}")
161
+ return None
162
+
163
+ def _get_cache_age_hours(self) -> float:
164
+ """Get cache age in hours."""
165
+ cache_file = self._get_cache_file_path()
166
+ if not cache_file.exists():
167
+ return float("inf")
168
+ cache_age = datetime.now() - datetime.fromtimestamp(cache_file.stat().st_mtime)
169
+ return cache_age.total_seconds() / 3600
170
+
171
+ def _initialize_aws_session(self) -> None:
172
+ """Initialize AWS session using provided credentials."""
173
+ if self.aws_access_key_id and self.aws_secret_access_key:
174
+ self.session = boto3.Session(
175
+ aws_access_key_id=self.aws_access_key_id,
176
+ aws_secret_access_key=self.aws_secret_access_key,
177
+ aws_session_token=self.aws_session_token,
178
+ region_name=self.region,
179
+ )
180
+ elif self.aws_profile:
181
+ self.session = boto3.Session(profile_name=self.aws_profile, region_name=self.region)
182
+ else:
183
+ self.session = boto3.Session(region_name=self.region)
184
+ logger.info(f"Initialized AWS session for region: {self.region}")
185
+
186
+ def fetch_compliance_data(self) -> Dict[str, Any]:
187
+ """
188
+ Fetch CloudWatch Logs configuration data from AWS.
189
+
190
+ :return: CloudWatch Logs configuration data
191
+ :rtype: Dict[str, Any]
192
+ """
193
+ # Check cache first
194
+ if not self.force_refresh and self._is_cache_valid():
195
+ cached_data = self._load_cached_data()
196
+ if cached_data:
197
+ return cached_data
198
+
199
+ # Fetch fresh data
200
+ return self._fetch_fresh_cloudwatch_data()
201
+
202
+ def _fetch_fresh_cloudwatch_data(self) -> Dict[str, Any]:
203
+ """
204
+ Fetch fresh CloudWatch Logs data from AWS API.
205
+
206
+ :return: CloudWatch Logs configuration data
207
+ :rtype: Dict[str, Any]
208
+ """
209
+ logger.info(f"Fetching CloudWatch Logs configurations from AWS region: {self.region}")
210
+
211
+ # Initialize AWS session
212
+ if not self.session:
213
+ self._initialize_aws_session()
214
+
215
+ # Create CloudWatch Logs collector
216
+ self.collector = CloudWatchLogsCollector(
217
+ session=self.session, region=self.region, account_id=self.account_id, tags=self.tags
218
+ )
219
+
220
+ # Collect CloudWatch data
221
+ self.raw_cloudwatch_data = self.collector.collect()
222
+
223
+ # Apply log group prefix filter if specified
224
+ if self.log_group_prefix:
225
+ original_count = len(self.raw_cloudwatch_data.get("LogGroups", []))
226
+ self.raw_cloudwatch_data["LogGroups"] = [
227
+ lg
228
+ for lg in self.raw_cloudwatch_data.get("LogGroups", [])
229
+ if lg.get("logGroupName", "").startswith(self.log_group_prefix)
230
+ ]
231
+ filtered_count = len(self.raw_cloudwatch_data["LogGroups"])
232
+ logger.info(
233
+ f"Applied log group prefix filter '{self.log_group_prefix}': {filtered_count}/{original_count} log groups match"
234
+ )
235
+
236
+ log_group_count = len(self.raw_cloudwatch_data.get("LogGroups", []))
237
+ logger.info(f"Collected {log_group_count} CloudWatch log group(s) from region {self.region}")
238
+
239
+ # Save to cache
240
+ self._save_cache(self.raw_cloudwatch_data)
241
+
242
+ return self.raw_cloudwatch_data
243
+
244
+ def sync_compliance_data(self) -> None:
245
+ """Sync CloudWatch Logs compliance data to RegScale."""
246
+ logger.info("Starting AWS CloudWatch Logs compliance data sync to RegScale")
247
+
248
+ # Fetch CloudWatch data
249
+ cloudwatch_data = self.fetch_compliance_data()
250
+ if not cloudwatch_data or not cloudwatch_data.get("LogGroups"):
251
+ logger.warning("No CloudWatch Logs data to sync")
252
+ return
253
+
254
+ # Convert to compliance item
255
+ self.cloudwatch_item = CloudWatchComplianceItem(cloudwatch_data)
256
+ logger.info(
257
+ f"Processing {len(self.cloudwatch_item.log_groups)} CloudWatch log group(s) for compliance assessment"
258
+ )
259
+
260
+ # Assess compliance
261
+ compliance_results = self._assess_compliance()
262
+
263
+ # Populate control dictionaries for assessment creation
264
+ if self.update_control_status:
265
+ self._populate_control_results(compliance_results["overall"])
266
+ # Create control assessments and update implementation statuses
267
+ self._process_control_assessments()
268
+
269
+ # Create evidence artifacts
270
+ if self.create_evidence or self.create_ssp_attachment:
271
+ self._create_evidence_artifacts(compliance_results)
272
+
273
+ logger.info("AWS CloudWatch Logs compliance sync completed successfully")
274
+
275
+ def create_compliance_item(self, raw_data: Dict[str, Any]):
276
+ """
277
+ Create a ComplianceItem from raw CloudWatch data.
278
+
279
+ :param Dict[str, Any] raw_data: Raw CloudWatch Logs data
280
+ :return: CloudWatchComplianceItem instance
281
+ :rtype: CloudWatchComplianceItem
282
+ """
283
+ return CloudWatchComplianceItem(raw_data)
284
+
285
+ def _assess_compliance(self) -> Dict[str, Any]:
286
+ """
287
+ Assess CloudWatch compliance against NIST controls.
288
+
289
+ :return: Compliance assessment results
290
+ :rtype: Dict[str, Any]
291
+ """
292
+ logger.info("Assessing CloudWatch Logs compliance against NIST 800-53 R5 controls")
293
+
294
+ # Assess overall compliance
295
+ overall_results = self.control_mapper.assess_cloudwatch_compliance(self.cloudwatch_item.to_dict())
296
+
297
+ # Log summary
298
+ passed_controls = [ctrl for ctrl, result in overall_results.items() if result == "PASS"]
299
+ failed_controls = [ctrl for ctrl, result in overall_results.items() if result == "FAIL"]
300
+
301
+ logger.info("CloudWatch Logs Compliance Assessment Summary:")
302
+ logger.info(f" Total Log Groups: {len(self.cloudwatch_item.log_groups)}")
303
+ logger.info(
304
+ f" Total Stored Bytes: {sum(m.get('StoredBytes', 0) for m in self.cloudwatch_item.log_group_metrics.values())}"
305
+ )
306
+ logger.info(f" Controls Passed: {len(passed_controls)} - {', '.join(passed_controls)}")
307
+ logger.info(f" Controls Failed: {len(failed_controls)} - {', '.join(failed_controls)}")
308
+
309
+ return {"overall": overall_results}
310
+
311
+ def _populate_control_results(self, control_results: Dict[str, str]) -> None:
312
+ """
313
+ Populate passing_controls and failing_controls dictionaries from assessment results.
314
+
315
+ This method converts the control-level assessment results into the format expected
316
+ by the base class _process_control_assessments() method.
317
+
318
+ :param Dict[str, str] control_results: Control assessment results (e.g., {"AC-2": "PASS", "AC-3": "FAIL"})
319
+ :return: None
320
+ :rtype: None
321
+ """
322
+ for control_id, result in control_results.items():
323
+ # Normalize control ID to lowercase for consistent lookup
324
+ control_key = control_id.lower()
325
+
326
+ # Create a simple compliance item placeholder for the base class
327
+ # The base class uses these to determine pass/fail status
328
+ if result in self.PASS_STATUSES:
329
+ self.passing_controls[control_key] = self.cloudwatch_item
330
+ elif result in self.FAIL_STATUSES:
331
+ self.failing_controls[control_key] = self.cloudwatch_item
332
+
333
+ logger.debug(
334
+ f"Populated control results: {len(self.passing_controls)} passing, {len(self.failing_controls)} failing"
335
+ )
336
+
337
+ def _create_evidence_artifacts(self, compliance_results: Dict[str, Any]) -> None:
338
+ """
339
+ Create evidence artifacts in RegScale.
340
+
341
+ :param Dict compliance_results: Compliance assessment results
342
+ """
343
+ logger.info("Creating CloudWatch Logs evidence artifacts in RegScale")
344
+
345
+ # Create comprehensive evidence file
346
+ evidence_file_path = self._create_evidence_file(compliance_results)
347
+
348
+ if self.create_ssp_attachment:
349
+ self._create_ssp_attachment_with_evidence(evidence_file_path)
350
+
351
+ # Clean up temporary file
352
+ if os.path.exists(evidence_file_path):
353
+ os.remove(evidence_file_path)
354
+ logger.debug(f"Cleaned up temporary evidence file: {evidence_file_path}")
355
+
356
+ def _create_evidence_file(self, compliance_results: Dict[str, Any]) -> str:
357
+ """
358
+ Create JSONL.GZ evidence file with CloudWatch Logs configuration data.
359
+
360
+ :param Dict compliance_results: Compliance assessment results
361
+ :return: Path to created evidence file
362
+ :rtype: str
363
+ """
364
+ evidence_file = self._get_evidence_file_path()
365
+
366
+ try:
367
+ with gzip.open(evidence_file, "wt", encoding="utf-8") as f:
368
+ self._write_metadata(f)
369
+ self._write_compliance_summary(f, compliance_results)
370
+ self._write_log_group_configurations(f)
371
+
372
+ logger.info(f"Created evidence file: {evidence_file}")
373
+ return evidence_file
374
+
375
+ except Exception as e:
376
+ logger.error(f"Failed to create evidence file: {e}", exc_info=True)
377
+ raise
378
+
379
+ def _get_evidence_file_path(self) -> str:
380
+ """Generate evidence file path with timestamp."""
381
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
382
+ return os.path.join(tempfile.gettempdir(), f"cloudwatch_evidence_{self.region}_{timestamp}.jsonl.gz")
383
+
384
+ def _write_metadata(self, file_handle) -> None:
385
+ """Write metadata record to evidence file."""
386
+ metadata = {
387
+ "type": "metadata",
388
+ "timestamp": datetime.now().isoformat(),
389
+ "region": self.region,
390
+ "account_id": self.account_id,
391
+ "log_group_count": len(self.cloudwatch_item.log_groups),
392
+ "compliance_framework": "NIST800-53R5",
393
+ }
394
+ file_handle.write(json.dumps(metadata) + "\n")
395
+
396
+ def _write_compliance_summary(self, file_handle, compliance_results: Dict[str, Any]) -> None:
397
+ """Write compliance summary to evidence file."""
398
+ summary = {"type": "compliance_summary", "results": compliance_results["overall"]}
399
+ file_handle.write(json.dumps(summary) + "\n")
400
+
401
+ def _write_log_group_configurations(self, file_handle) -> None:
402
+ """Write log group configuration records to evidence file."""
403
+ for log_group in self.cloudwatch_item.log_groups:
404
+ log_group_record = self._build_log_group_record(log_group)
405
+ file_handle.write(json.dumps(log_group_record, default=str) + "\n")
406
+
407
+ def _build_log_group_record(self, log_group: Dict[str, Any]) -> Dict[str, Any]:
408
+ """Build log group configuration record for evidence file."""
409
+ log_group_name = log_group.get("logGroupName", "")
410
+ metrics = self.cloudwatch_item.log_group_metrics.get(log_group_name, {})
411
+
412
+ return {
413
+ "type": "log_group_configuration",
414
+ "log_group_name": log_group_name,
415
+ "log_group_arn": log_group.get("arn"),
416
+ "creation_time": log_group.get("creationTime"),
417
+ "retention_days": log_group.get("retentionInDays"),
418
+ "stored_bytes": log_group.get("storedBytes", 0),
419
+ "kms_key_id": log_group.get("kmsKeyId"),
420
+ "metric_filter_count": metrics.get("MetricFilterCount", 0),
421
+ "subscription_filter_count": metrics.get("SubscriptionFilterCount", 0),
422
+ "metric_filters": log_group.get("MetricFilters", []),
423
+ "subscription_filters": log_group.get("SubscriptionFilters", []),
424
+ "tags": log_group.get("Tags", {}),
425
+ }
426
+
427
+ def _create_ssp_attachment_with_evidence(self, evidence_file_path: str) -> None:
428
+ """
429
+ Create SSP attachment with CloudWatch evidence.
430
+
431
+ :param str evidence_file_path: Path to evidence file
432
+ """
433
+ try:
434
+ date_str = datetime.now().strftime("%Y%m%d")
435
+ file_name_pattern = f"cloudwatch_evidence_{self.region}_{date_str}"
436
+
437
+ # Check if evidence for today already exists using base class method
438
+ if self.check_for_existing_evidence(file_name_pattern):
439
+ logger.info(
440
+ f"Evidence file for CloudWatch Logs in region {self.region} already exists for today. "
441
+ "Skipping upload to avoid duplicates."
442
+ )
443
+ return
444
+
445
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
446
+ file_name = f"cloudwatch_evidence_{self.region}_{timestamp}.jsonl.gz"
447
+
448
+ # Read the compressed file
449
+ with open(evidence_file_path, "rb") as f:
450
+ file_data = f.read()
451
+
452
+ # Upload file to RegScale
453
+ success = File.upload_file_to_regscale(
454
+ file_name=file_name,
455
+ parent_id=self.plan_id,
456
+ parent_module="securityplans",
457
+ api=self.api,
458
+ file_data=file_data,
459
+ tags="aws,cloudwatch,logs,monitoring,compliance,automated",
460
+ )
461
+
462
+ if success:
463
+ logger.info(f"Successfully uploaded CloudWatch Logs evidence file: {file_name}")
464
+ # Link to controls if specified
465
+ if self.evidence_control_ids:
466
+ # Note: SSP attachments don't have IDs returned by upload_file_to_regscale
467
+ # This would need to be implemented if attachment-to-control linking is required
468
+ pass
469
+ else:
470
+ logger.error("Failed to upload CloudWatch Logs evidence file")
471
+
472
+ except Exception as e:
473
+ logger.error(f"Failed to create SSP attachment: {e}", exc_info=True)
474
+
475
+ def _link_evidence_to_controls(self, evidence_id: int, is_attachment: bool = False) -> None:
476
+ """
477
+ Link evidence to specified control IDs.
478
+
479
+ :param int evidence_id: Evidence or attachment ID
480
+ :param bool is_attachment: True if linking attachment, False for evidence record
481
+ """
482
+ try:
483
+ for control_id in self.evidence_control_ids:
484
+ if is_attachment:
485
+ self.api.link_ssp_attachment_to_control(self.plan_id, evidence_id, control_id)
486
+ else:
487
+ self.api.link_evidence_to_control(evidence_id, control_id)
488
+ logger.info(f"Linked evidence {evidence_id} to control {control_id}")
489
+ except Exception as e:
490
+ logger.error(f"Failed to link evidence to controls: {e}", exc_info=True)
@@ -13,19 +13,51 @@ from regscale.core.app.utils.app_utils import create_logger
13
13
 
14
14
  logger = create_logger()
15
15
 
16
+ # Error message constants
17
+ ERROR_MSG_FETCHING_AWS_RESOURCES = "Unexpected error when fetching resources from AWS: %s"
16
18
 
17
- def check_finding_severity(comment: Optional[str]) -> str:
19
+
20
+ def check_finding_severity(finding: dict, comment: Optional[str] = None) -> str:
18
21
  """Check the severity of the finding
19
22
 
20
- :param Optional[str] comment: Comment from AWS Security Hub finding
21
- :return: Severity of the finding
23
+ This function extracts severity from AWS Security Hub findings by checking:
24
+ 1. finding["Severity"]["Label"] (primary source for all findings including Inspector CVEs)
25
+ 2. finding["FindingProviderFields"]["Severity"]["Label"] (alternative source)
26
+ 3. Comment text parsing as fallback (for legacy compatibility)
27
+
28
+ :param dict finding: AWS Security Hub finding
29
+ :param Optional[str] comment: Comment from AWS Security Hub finding (optional, for fallback)
30
+ :return: Severity of the finding (CRITICAL, HIGH, MEDIUM, LOW, INFORMATIONAL, or empty string)
22
31
  :rtype: str
23
32
  """
24
33
  result = ""
25
- match = re.search(r"(?<=Finding Severity: ).*", comment)
26
- if match:
27
- severity = match.group()
28
- result = severity # Output: "High"
34
+
35
+ # Primary: Check Severity.Label (available in all findings, including Inspector CVEs)
36
+ if "Severity" in finding and "Label" in finding["Severity"]:
37
+ result = finding["Severity"]["Label"]
38
+ logger.debug(f"Found severity from Severity.Label: {result}")
39
+ return result
40
+
41
+ # Secondary: Check FindingProviderFields.Severity.Label (alternative source)
42
+ if "FindingProviderFields" in finding:
43
+ provider_severity = finding.get("FindingProviderFields", {}).get("Severity", {}).get("Label", "")
44
+ if provider_severity:
45
+ result = provider_severity
46
+ logger.debug(f"Found severity from FindingProviderFields.Severity.Label: {result}")
47
+ return result
48
+
49
+ # Fallback: Parse from comment text (for legacy compatibility)
50
+ if comment:
51
+ match = re.search(r"(?<=Finding Severity: ).*", comment)
52
+ if match:
53
+ severity = match.group()
54
+ result = severity
55
+ logger.debug(f"Found severity from comment parsing: {result}")
56
+ return result
57
+
58
+ if not result:
59
+ logger.warning(f"No severity found for finding {finding.get('Id', 'unknown')}")
60
+
29
61
  return result
30
62
 
31
63
 
@@ -46,16 +78,16 @@ def get_due_date(earliest_date_performed: datetime, days: int) -> datetime:
46
78
  return due_date
47
79
 
48
80
 
49
- def determine_status_and_results(finding: Any) -> Tuple[str, Optional[str]]:
81
+ def determine_status_and_results(finding: Any) -> Tuple[str, str]:
50
82
  """
51
83
  Determine Status and Results
52
84
 
53
85
  :param Any finding: AWS Finding
54
86
  :return: Status and Results
55
- :rtype: Tuple[str, Optional[str]]
87
+ :rtype: Tuple[str, str]
56
88
  """
57
89
  status = "Pass"
58
- results = None
90
+ results = ""
59
91
  if "Compliance" in finding.keys():
60
92
  status = "Fail" if finding["Compliance"]["Status"] == "FAILED" else "Pass"
61
93
  results = ", ".join(finding.get("Compliance", {}).get("RelatedRequirements", [])) or "N/A"
@@ -71,6 +103,9 @@ def determine_status_and_results(finding: Any) -> Tuple[str, Optional[str]]:
71
103
  f"{finding.get('PatchSummary', {}).get('MissingCount', 0)} Missing Patch(s) of "
72
104
  "{finding.get('PatchSummary', {}).get('InstalledCount', 0)}"
73
105
  )
106
+ # Ensure results is always a string, not None
107
+ if not results:
108
+ results = "N/A"
74
109
  return status, results
75
110
 
76
111
 
@@ -94,10 +129,14 @@ def get_comments(finding: dict) -> str:
94
129
  return "No remediation recommendation available"
95
130
 
96
131
 
97
- def fetch_aws_findings(aws_client: BaseClient) -> list:
132
+ def fetch_aws_findings(
133
+ aws_client: BaseClient, minimum_severity: Optional[str] = None, posture_management_only: bool = False
134
+ ) -> list:
98
135
  """Fetch AWS Findings with optimized rate limiting and pagination
99
136
 
100
137
  :param BaseClient aws_client: AWS Security Hub Client
138
+ :param Optional[str] minimum_severity: Minimum severity to filter (CRITICAL, HIGH, MEDIUM, LOW, INFORMATIONAL)
139
+ :param bool posture_management_only: If True, only fetch posture management findings (compliance checks)
101
140
  :return: AWS Findings
102
141
  :rtype: list
103
142
  """
@@ -106,11 +145,10 @@ def fetch_aws_findings(aws_client: BaseClient) -> list:
106
145
  # Use optimized SecurityHubPuller for better performance
107
146
  from regscale.integrations.commercial.aws.security_hub import SecurityHubPuller
108
147
 
109
- # Extract credentials from the client to create SecurityHubPuller
110
- session = aws_client._client_config.__dict__.get("_user_provided_options", {})
111
- region = session.meta.region_name
148
+ # Extract region from the client
149
+ region = aws_client.meta.region_name
112
150
 
113
- # Create SecurityHubPuller with same credentials as the client
151
+ # Create SecurityHubPuller with same region as the client
114
152
  puller = SecurityHubPuller(region_name=region)
115
153
 
116
154
  # Use existing client instead of creating new one to maintain credentials
@@ -118,7 +156,21 @@ def fetch_aws_findings(aws_client: BaseClient) -> list:
118
156
 
119
157
  # Fetch all findings with optimized pagination and rate limiting
120
158
  logger.info("Using optimized SecurityHubPuller for findings retrieval...")
121
- findings = puller.get_all_findings_with_retries()
159
+
160
+ # Determine severity labels if minimum_severity is provided
161
+ severity_labels = None
162
+ if minimum_severity:
163
+ severity_labels = SecurityHubPuller.get_severity_filters_from_minimum(minimum_severity)
164
+ logger.info(f"Applying minimum severity filter '{minimum_severity}': {severity_labels}")
165
+
166
+ # Fetch findings based on type requested
167
+ if posture_management_only:
168
+ logger.info("Fetching posture management findings only (security standards compliance checks)")
169
+ findings = puller.get_posture_management_findings(severity_labels=severity_labels)
170
+ elif severity_labels:
171
+ findings = puller.get_findings_by_severity(severity_labels=severity_labels)
172
+ else:
173
+ findings = puller.get_all_findings_with_retries()
122
174
 
123
175
  logger.info(f"Successfully fetched {len(findings)} findings with rate limiting")
124
176
 
@@ -147,7 +199,7 @@ def fallback_fetch_aws_findings(aws_client: BaseClient) -> list:
147
199
  response = aws_client.get_findings()
148
200
  findings = response.get("Findings", [])
149
201
  except ClientError as cex:
150
- create_logger().error("Unexpected error when fetching resources from AWS: %s", cex)
202
+ create_logger().error(ERROR_MSG_FETCHING_AWS_RESOURCES, cex)
151
203
  return findings
152
204
 
153
205
 
@@ -163,7 +215,7 @@ def fetch_aws_findings_v2(aws_client: BaseClient) -> list:
163
215
  response = aws_client.get_findings_v2()
164
216
  findings = response.get("Findings", [])
165
217
  except ClientError as cex:
166
- create_logger().error("Unexpected error when fetching resources from AWS: %s", cex)
218
+ create_logger().error(ERROR_MSG_FETCHING_AWS_RESOURCES, cex)
167
219
  return findings
168
220
 
169
221
 
@@ -180,5 +232,5 @@ def fetch_aws_resources(aws_client: BaseClient) -> list:
180
232
  resources = response.get("Resources", [])
181
233
  logger.info(f"Fetched {len(resources)} resources from Security Hub")
182
234
  except ClientError as cex:
183
- create_logger().error("Unexpected error when fetching resources from AWS: %s", cex)
235
+ create_logger().error(ERROR_MSG_FETCHING_AWS_RESOURCES, cex)
184
236
  return resources