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
@@ -3,8 +3,9 @@
3
3
  import json
4
4
  import logging
5
5
  import os
6
+ from dataclasses import dataclass
6
7
  from datetime import datetime
7
- from typing import Optional
8
+ from typing import Dict, Optional, Tuple, Any, List
8
9
 
9
10
  import click
10
11
 
@@ -13,6 +14,229 @@ from regscale.models.integration_models.flat_file_importer import FlatFileImport
13
14
  logger = logging.getLogger("regscale")
14
15
 
15
16
 
17
+ @dataclass
18
+ class ComplianceSyncConfig:
19
+ """Configuration for AWS Audit Manager compliance sync."""
20
+
21
+ region: str
22
+ regscale_id: int
23
+ framework: str = "NIST800-53R5"
24
+ custom_framework_name: Optional[str] = None
25
+ assessment_id: Optional[str] = None
26
+ create_issues: bool = True
27
+ update_control_status: bool = True
28
+ create_poams: bool = False
29
+ collect_evidence: bool = False
30
+ evidence_control_ids: Optional[List[str]] = None
31
+ evidence_frequency: int = 30
32
+ max_evidence_per_control: int = 100
33
+ use_assessment_evidence_folders: bool = True
34
+ force_refresh: bool = False
35
+ use_enhanced_analyzer: bool = False
36
+
37
+
38
+ # Evidence collection constants
39
+ EVIDENCE_MODE_SSP_ATTACHMENTS = "Evidence collection mode: SSP file attachments (default)"
40
+ EVIDENCE_MODE_INDIVIDUAL_RECORDS = "Evidence collection mode: Individual Evidence records"
41
+ DEFAULT_EVIDENCE_FREQUENCY_DAYS = 30 # Default evidence update frequency in days
42
+
43
+
44
+ @dataclass
45
+ class AWSCredentialConfig:
46
+ """AWS credential configuration."""
47
+
48
+ session_name: Optional[str] = None
49
+ profile: Optional[str] = None
50
+ aws_access_key_id: Optional[str] = None
51
+ aws_secret_access_key: Optional[str] = None
52
+ aws_session_token: Optional[str] = None
53
+ account_id: Optional[str] = None
54
+ tags: Optional[Dict[str, str]] = None
55
+
56
+
57
+ @dataclass
58
+ class FindingsSyncConfig:
59
+ """Configuration for AWS findings sync."""
60
+
61
+ region: str
62
+ regscale_id: int
63
+ credentials: AWSCredentialConfig
64
+ generate_evidence: bool = False
65
+ control_ids: Optional[str] = None
66
+ format: str = "native"
67
+ import_all_findings: bool = False
68
+ evidence_frequency: int = DEFAULT_EVIDENCE_FREQUENCY_DAYS
69
+
70
+
71
+ @dataclass
72
+ class ConfigComplianceConfig:
73
+ """Configuration for AWS Config compliance sync."""
74
+
75
+ region: str
76
+ regscale_id: int
77
+ framework: str = "NIST800-53R5"
78
+ conformance_pack_name: Optional[str] = None
79
+ create_issues: bool = True
80
+ update_control_status: bool = True
81
+ create_poams: bool = False
82
+ collect_evidence: bool = False
83
+ evidence_as_attachments: bool = True
84
+ evidence_as_records: bool = False
85
+ evidence_control_ids: Optional[List[str]] = None
86
+ evidence_frequency: int = 30
87
+ use_security_hub: bool = False
88
+ force_refresh: bool = False
89
+
90
+
91
+ @dataclass
92
+ class KMSConfig:
93
+ """Configuration for AWS KMS evidence collection."""
94
+
95
+ region: str
96
+ regscale_ssp_id: int
97
+ collect_kms_evidence: bool = True
98
+ kms_evidence_control_ids: Optional[List[str]] = None
99
+ kms_evidence_frequency: int = 30
100
+ kms_evidence_mode: str = "attachments"
101
+
102
+
103
+ @dataclass
104
+ class OrganizationConfig:
105
+ """Configuration for AWS Organizations evidence collection."""
106
+
107
+ region: str
108
+ regscale_ssp_id: int
109
+ collect_org_evidence: bool = True
110
+ org_evidence_control_ids: Optional[List[str]] = None
111
+ org_evidence_frequency: int = 30
112
+ org_evidence_mode: str = "attachments"
113
+
114
+
115
+ @dataclass
116
+ class IAMConfig:
117
+ """Configuration for AWS IAM evidence collection."""
118
+
119
+ region: str
120
+ regscale_ssp_id: int
121
+ collect_iam_evidence: bool = True
122
+ iam_evidence_control_ids: Optional[List[str]] = None
123
+ iam_evidence_frequency: int = 30
124
+ iam_evidence_mode: str = "attachments"
125
+
126
+
127
+ @dataclass
128
+ class GuardDutyConfig:
129
+ """Configuration for AWS GuardDuty evidence collection."""
130
+
131
+ region: str
132
+ regscale_ssp_id: int
133
+ collect_guardduty_evidence: bool = True
134
+ guardduty_evidence_control_ids: Optional[List[str]] = None
135
+ guardduty_evidence_frequency: int = 30
136
+ guardduty_evidence_mode: str = "attachments"
137
+
138
+
139
+ @dataclass
140
+ class S3Config:
141
+ """Configuration for AWS S3 evidence collection."""
142
+
143
+ region: str
144
+ regscale_ssp_id: int
145
+ collect_s3_evidence: bool = True
146
+ s3_evidence_control_ids: Optional[List[str]] = None
147
+ s3_evidence_frequency: int = 30
148
+ s3_evidence_mode: str = "attachments"
149
+
150
+
151
+ @dataclass
152
+ class CloudTrailConfig:
153
+ """Configuration for AWS CloudTrail evidence collection."""
154
+
155
+ region: str
156
+ regscale_ssp_id: int
157
+ collect_cloudtrail_evidence: bool = True
158
+ cloudtrail_evidence_control_ids: Optional[List[str]] = None
159
+ cloudtrail_evidence_frequency: int = 30
160
+ cloudtrail_evidence_mode: str = "attachments"
161
+
162
+
163
+ def parse_tags(tags_string: Optional[str]) -> Optional[Dict[str, str]]:
164
+ """
165
+ Parse tag string into dictionary.
166
+
167
+ Format: key1=value1,key2=value2
168
+
169
+ :param Optional[str] tags_string: Comma-separated key=value pairs
170
+ :return: Dictionary of tag key-value pairs or None if input is empty
171
+ :rtype: Optional[Dict[str, str]]
172
+ """
173
+ if not tags_string:
174
+ return None
175
+
176
+ tag_dict = {}
177
+ try:
178
+ for tag_pair in tags_string.split(","):
179
+ if "=" not in tag_pair:
180
+ logger.warning(f"Invalid tag format (missing '='): {tag_pair}")
181
+ continue
182
+ key, value = tag_pair.split("=", 1)
183
+ tag_dict[key.strip()] = value.strip()
184
+ return tag_dict if tag_dict else None
185
+ except (ValueError, AttributeError) as e:
186
+ logger.error(f"Error parsing tags: {e}")
187
+ raise click.ClickException("Invalid tag format. Expected format: key1=value1,key2=value2")
188
+
189
+
190
+ def resolve_aws_credentials(
191
+ session_name: Optional[str],
192
+ profile: Optional[str],
193
+ aws_access_key_id: Optional[str],
194
+ aws_secret_access_key: Optional[str],
195
+ aws_session_token: Optional[str],
196
+ region: Optional[str] = None,
197
+ ) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[str], Optional[str]]:
198
+ """
199
+ Resolve AWS credentials from session cache, profile, or explicit credentials.
200
+
201
+ Priority order:
202
+ 1. Cached session (if session_name provided)
203
+ 2. Explicit credentials (if provided)
204
+ 3. Profile (if provided)
205
+ 4. Environment variables / default credential chain
206
+
207
+ :param Optional[str] session_name: Name of cached session to use
208
+ :param Optional[str] profile: AWS profile name
209
+ :param Optional[str] aws_access_key_id: Explicit access key ID
210
+ :param Optional[str] aws_secret_access_key: Explicit secret access key
211
+ :param Optional[str] aws_session_token: Explicit session token
212
+ :param Optional[str] region: AWS region
213
+ :return: Tuple of (profile, access_key_id, secret_access_key, session_token, region)
214
+ :rtype: Tuple[Optional[str], Optional[str], Optional[str], Optional[str], Optional[str]]
215
+ """
216
+ # If session-name is provided, try to get credentials from cache
217
+ if session_name:
218
+ from .session_manager import AWSSessionManager
219
+
220
+ manager = AWSSessionManager()
221
+ cached_creds = manager.get_credentials_for_session(session_name)
222
+
223
+ if cached_creds:
224
+ cached_access_key, cached_secret_key, cached_session_token, cached_region = cached_creds
225
+ logger.info(f"Using cached AWS session: {session_name}")
226
+ return (
227
+ None, # Don't use profile when using cached session
228
+ cached_access_key,
229
+ cached_secret_key,
230
+ cached_session_token,
231
+ region or cached_region,
232
+ )
233
+ else:
234
+ logger.warning(f"Cached session '{session_name}' not found or expired. Falling back to other methods.")
235
+
236
+ # Otherwise, use provided credentials or profile
237
+ return (profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region)
238
+
239
+
16
240
  @click.group(name="aws")
17
241
  def awsv2():
18
242
  """AWS Integrations."""
@@ -33,33 +257,70 @@ def awsv2():
33
257
  help="RegScale will create and update assets as children of this record.",
34
258
  required=True,
35
259
  )
260
+ @click.option(
261
+ "--session-name",
262
+ type=str,
263
+ required=False,
264
+ help="Name of cached AWS session to use (from 'regscale aws auth login')",
265
+ )
266
+ @click.option(
267
+ "--profile",
268
+ type=str,
269
+ required=False,
270
+ help="AWS profile name from ~/.aws/credentials",
271
+ envvar="AWS_PROFILE",
272
+ )
36
273
  @click.option(
37
274
  "--aws_access_key_id",
38
275
  type=str,
39
276
  required=False,
40
- help="AWS access key ID",
277
+ help="AWS access key ID (overrides profile)",
41
278
  envvar="AWS_ACCESS_KEY_ID",
42
279
  )
43
280
  @click.option(
44
281
  "--aws_secret_access_key",
45
282
  type=str,
46
283
  required=False,
47
- help="AWS secret access key",
284
+ help="AWS secret access key (overrides profile)",
48
285
  default=os.getenv("AWS_SECRET_ACCESS_KEY"),
49
286
  )
50
287
  @click.option(
51
288
  "--aws_session_token",
52
289
  type=click.STRING,
53
290
  required=False,
54
- help="AWS Session ID",
291
+ help="AWS session token (overrides profile)",
55
292
  default=os.environ.get("AWS_SESSION_TOKEN"),
56
293
  )
294
+ @click.option(
295
+ "--account-id",
296
+ type=str,
297
+ required=False,
298
+ help="Filter resources by AWS account ID",
299
+ envvar="AWS_ACCOUNT_ID",
300
+ )
301
+ @click.option(
302
+ "--tags",
303
+ type=str,
304
+ required=False,
305
+ help="Filter resources by tags (format: key1=value1,key2=value2). All tags must match.",
306
+ )
307
+ @click.option(
308
+ "--force-refresh",
309
+ is_flag=True,
310
+ default=False,
311
+ help="Force refresh AWS inventory data, ignoring cached data even if it's still valid.",
312
+ )
57
313
  def sync_assets(
58
314
  region: str,
59
315
  regscale_id: int,
316
+ session_name: Optional[str] = None,
317
+ profile: Optional[str] = None,
60
318
  aws_access_key_id: Optional[str] = None,
61
319
  aws_secret_access_key: Optional[str] = None,
62
320
  aws_session_token: Optional[str] = None,
321
+ account_id: Optional[str] = None,
322
+ tags: Optional[str] = None,
323
+ force_refresh: bool = False,
63
324
  ) -> None:
64
325
  """
65
326
  Sync AWS resources to RegScale assets.
@@ -73,21 +334,60 @@ def sync_assets(
73
334
  - VPCs and networking resources
74
335
  - Container resources
75
336
  - And more...
337
+
338
+ Caching Behavior:
339
+ AWS inventory data is cached for 8 hours in artifacts/aws/inventory.json to improve performance.
340
+ The cache is shared across all SSPs for the same AWS account/region.
341
+ Use --force-refresh to bypass the cache and fetch fresh data from AWS.
342
+
343
+ Note: "Updated" assets in the output are resources that already exist in the target SSP
344
+ (from a previous sync). This is normal behavior when syncing to an SSP multiple times.
345
+
346
+ Filtering Options:
347
+ Use --account-id to filter resources by AWS account ID.
348
+ Use --tags to filter resources by tags (format: Environment=prod,Team=security).
349
+ Both filters use AND logic - all criteria must match.
350
+
351
+ Authentication methods (in priority order):
352
+ 1. Cached session: --session-name (from 'regscale aws auth login')
353
+ 2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
354
+ 3. AWS profile: --profile
355
+ 4. Environment variables or default AWS credential chain
76
356
  """
77
357
  try:
78
358
  logger.info("Starting AWS asset sync to RegScale...")
79
359
  from .scanner import AWSInventoryIntegration
80
360
 
81
- scanner = AWSInventoryIntegration(plan_id=regscale_id)
82
- scanner.sync_assets(
361
+ # Parse tags
362
+ tag_dict = parse_tags(tags)
363
+ if tag_dict:
364
+ logger.info(f"Filtering resources by tags: {tag_dict}")
365
+ if account_id:
366
+ logger.info(f"Filtering resources by account ID: {account_id}")
367
+
368
+ # Resolve credentials from session cache or other methods
369
+ profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region = resolve_aws_credentials(
370
+ session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
371
+ )
372
+
373
+ if force_refresh:
374
+ logger.info("Force refresh enabled - clearing cached inventory data")
375
+
376
+ AWSInventoryIntegration.sync_assets(
83
377
  plan_id=regscale_id,
84
378
  region=region,
379
+ profile=profile,
85
380
  aws_access_key_id=aws_access_key_id,
86
381
  aws_secret_access_key=aws_secret_access_key,
87
382
  aws_session_token=aws_session_token,
383
+ account_id=account_id,
384
+ tags=tag_dict,
385
+ force_refresh=force_refresh,
88
386
  )
89
387
  logger.info("AWS asset sync completed successfully.")
90
- except Exception as e:
388
+ except (
389
+ Exception
390
+ ) as e: # Broad catch appropriate for CLI command - may raise AWS SDK, network, or RegScale API errors
91
391
  logger.error(f"Error syncing AWS assets: {e}", exc_info=True)
92
392
  raise click.ClickException(str(e))
93
393
 
@@ -105,27 +405,53 @@ def inventory():
105
405
  default=os.getenv("AWS_REGION", "us-east-1"),
106
406
  help="AWS region to collect inventory from. Default is us-east-1.",
107
407
  )
408
+ @click.option(
409
+ "--session-name",
410
+ type=str,
411
+ required=False,
412
+ help="Name of cached AWS session to use (from 'regscale aws auth login')",
413
+ )
414
+ @click.option(
415
+ "--profile",
416
+ type=str,
417
+ required=False,
418
+ help="AWS profile name from ~/.aws/credentials",
419
+ envvar="AWS_PROFILE",
420
+ )
108
421
  @click.option(
109
422
  "--aws_access_key_id",
110
423
  type=str,
111
424
  required=False,
112
- help="AWS access key ID",
425
+ help="AWS access key ID (overrides profile)",
113
426
  envvar="AWS_ACCESS_KEY_ID",
114
427
  )
115
428
  @click.option(
116
429
  "--aws_secret_access_key",
117
430
  type=str,
118
431
  required=False,
119
- help="AWS secret access key",
432
+ help="AWS secret access key (overrides profile)",
120
433
  envvar="AWS_SECRET_ACCESS_KEY",
121
434
  )
122
435
  @click.option(
123
436
  "--aws_session_token",
124
437
  type=click.STRING,
125
438
  required=False,
126
- help="AWS Session ID",
439
+ help="AWS session token (overrides profile)",
127
440
  default=os.environ.get("AWS_SESSION_TOKEN"),
128
441
  )
442
+ @click.option(
443
+ "--account-id",
444
+ type=str,
445
+ required=False,
446
+ help="Filter resources by AWS account ID",
447
+ envvar="AWS_ACCOUNT_ID",
448
+ )
449
+ @click.option(
450
+ "--tags",
451
+ type=str,
452
+ required=False,
453
+ help="Filter resources by tags (format: key1=value1,key2=value2). All tags must match.",
454
+ )
129
455
  @click.option(
130
456
  "--output",
131
457
  type=click.Path(dir_okay=False, writable=True),
@@ -134,9 +460,13 @@ def inventory():
134
460
  )
135
461
  def collect_inventory(
136
462
  region: str,
463
+ session_name: Optional[str],
464
+ profile: Optional[str],
137
465
  aws_access_key_id: Optional[str],
138
466
  aws_secret_access_key: Optional[str],
139
467
  aws_session_token: Optional[str],
468
+ account_id: Optional[str],
469
+ tags: Optional[str],
140
470
  output: Optional[str],
141
471
  ) -> None:
142
472
  """
@@ -150,17 +480,44 @@ def collect_inventory(
150
480
  - And more...
151
481
 
152
482
  The inventory can be displayed to stdout or saved to a JSON file.
483
+
484
+ Filtering Options:
485
+ Use --account-id to filter resources by AWS account ID.
486
+ Use --tags to filter resources by tags (format: Environment=prod,Team=security).
487
+ Both filters use AND logic - all criteria must match.
488
+
489
+ Authentication methods (in priority order):
490
+ 1. Cached session: --session-name (from 'regscale aws auth login')
491
+ 2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
492
+ 3. AWS profile: --profile
493
+ 4. Environment variables or default AWS credential chain
153
494
  """
154
495
  try:
155
496
  from .inventory import collect_all_inventory
156
497
  from regscale.models import DateTimeEncoder
157
498
 
158
499
  logger.info("Collecting AWS inventory...")
500
+
501
+ # Parse tags
502
+ tag_dict = parse_tags(tags)
503
+ if tag_dict:
504
+ logger.info(f"Filtering resources by tags: {tag_dict}")
505
+ if account_id:
506
+ logger.info(f"Filtering resources by account ID: {account_id}")
507
+
508
+ # Resolve credentials from session cache or other methods
509
+ profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region = resolve_aws_credentials(
510
+ session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
511
+ )
512
+
159
513
  aws_inventory = collect_all_inventory(
160
514
  region=region,
515
+ profile=profile,
161
516
  aws_access_key_id=aws_access_key_id,
162
517
  aws_secret_access_key=aws_secret_access_key,
163
518
  aws_session_token=aws_session_token,
519
+ account_id=account_id,
520
+ tags=tag_dict,
164
521
  )
165
522
  logger.info(
166
523
  "AWS inventory collected successfully. Received %s resource(s).",
@@ -274,53 +631,268 @@ def import_aws_scans(
274
631
  help="RegScale will create and update findings as children of this record.",
275
632
  required=True,
276
633
  )
634
+ @click.option(
635
+ "--session-name",
636
+ type=str,
637
+ required=False,
638
+ help="Name of cached AWS session to use (from 'regscale aws auth login')",
639
+ )
640
+ @click.option(
641
+ "--profile",
642
+ type=str,
643
+ required=False,
644
+ help="AWS profile name from ~/.aws/credentials",
645
+ envvar="AWS_PROFILE",
646
+ )
277
647
  @click.option(
278
648
  "--aws_access_key_id",
279
649
  type=str,
280
650
  required=False,
281
- help="AWS access key ID",
651
+ help="AWS access key ID (overrides profile)",
282
652
  envvar="AWS_ACCESS_KEY_ID",
283
653
  )
284
654
  @click.option(
285
655
  "--aws_secret_access_key",
286
656
  type=str,
287
657
  required=False,
288
- help="AWS secret access key",
658
+ help="AWS secret access key (overrides profile)",
289
659
  default=os.getenv("AWS_SECRET_ACCESS_KEY"),
290
660
  )
291
661
  @click.option(
292
662
  "--aws_session_token",
293
663
  type=click.STRING,
294
664
  required=False,
295
- help="AWS Session ID",
665
+ help="AWS session token (overrides profile)",
296
666
  default=os.environ.get("AWS_SESSION_TOKEN"),
297
667
  )
668
+ @click.option(
669
+ "--generate-evidence",
670
+ is_flag=True,
671
+ default=False,
672
+ help="Generate evidence record for collected findings and link to SSP (uses --regscale_id)",
673
+ )
674
+ @click.option(
675
+ "--control-ids",
676
+ type=str,
677
+ default=None,
678
+ help="Comma-separated list of control IDs to link evidence (e.g., '123,456,789')",
679
+ )
680
+ @click.option(
681
+ "--format",
682
+ type=click.Choice(["native", "ocsf", "both"], case_sensitive=False),
683
+ default="native",
684
+ help="Output format for findings (native, ocsf, or both)",
685
+ )
686
+ @click.option(
687
+ "--account-id",
688
+ type=str,
689
+ required=False,
690
+ help="Filter findings by AWS account ID",
691
+ envvar="AWS_ACCOUNT_ID",
692
+ )
693
+ @click.option(
694
+ "--tags",
695
+ type=str,
696
+ required=False,
697
+ help="Filter findings by resource tags (format: key1=value1,key2=value2). All tags must match (AND logic).",
698
+ )
699
+ @click.option(
700
+ "--import-all-findings",
701
+ is_flag=True,
702
+ default=False,
703
+ help="Import all findings even if they are not associated with an asset in RegScale. By default, findings without matching assets are skipped.",
704
+ )
298
705
  def sync_findings(
299
706
  region: str,
300
707
  regscale_id: int,
708
+ session_name: Optional[str] = None,
709
+ profile: Optional[str] = None,
301
710
  aws_access_key_id: Optional[str] = None,
302
711
  aws_secret_access_key: Optional[str] = None,
303
712
  aws_session_token: Optional[str] = None,
713
+ generate_evidence: bool = False,
714
+ control_ids: Optional[str] = None,
715
+ format: str = "native",
716
+ account_id: Optional[str] = None,
717
+ tags: Optional[str] = None,
718
+ import_all_findings: bool = False,
304
719
  ) -> None:
305
720
  """
306
- Sync AWS Security Hub findings to RegScale.
721
+ Sync AWS Security Hub findings to RegScale with optional filtering.
307
722
 
308
723
  This command fetches findings from AWS Security Hub and creates/updates
309
- corresponding issues in RegScale.
724
+ corresponding issues in RegScale. Optionally generates evidence records
725
+ and supports OCSF (Open Cybersecurity Schema Framework) format export.
726
+
727
+ Filtering Options:
728
+ Use --account-id to filter findings by AWS account.
729
+ Use --tags to filter findings by resource tags (format: key1=value1,key2=value2).
730
+ Both filters use AND logic - all criteria must match.
731
+
732
+ Evidence Generation:
733
+ Use --generate-evidence to create evidence records for compliance documentation.
734
+ Evidence will be automatically linked to the SSP specified by --regscale_id.
735
+ Optionally link to specific controls with --control-ids.
736
+
737
+ OCSF Format:
738
+ Use --format ocsf to export findings in OCSF normalized format for cross-platform
739
+ security data sharing. Use --format both for dual-format export.
740
+
741
+ Authentication methods (in priority order):
742
+ 1. Cached session: --session-name (from 'regscale aws auth login')
743
+ 2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
744
+ 3. AWS profile: --profile
745
+ 4. Environment variables or default AWS credential chain
746
+ """
747
+ # Parse tags into dictionary
748
+ tag_dict = parse_tags(tags) if tags else None
749
+
750
+ # Create credential config
751
+ credentials = AWSCredentialConfig(
752
+ session_name=session_name,
753
+ profile=profile,
754
+ aws_access_key_id=aws_access_key_id,
755
+ aws_secret_access_key=aws_secret_access_key,
756
+ aws_session_token=aws_session_token,
757
+ account_id=account_id,
758
+ tags=tag_dict,
759
+ )
760
+
761
+ # Create config object
762
+ config = FindingsSyncConfig(
763
+ region=region,
764
+ regscale_id=regscale_id,
765
+ credentials=credentials,
766
+ generate_evidence=generate_evidence,
767
+ control_ids=control_ids,
768
+ format=format,
769
+ import_all_findings=import_all_findings,
770
+ )
771
+ _sync_findings_with_config(config)
772
+
773
+
774
+ def _sync_findings_with_config(config: FindingsSyncConfig) -> None:
775
+ """
776
+ Internal function to sync findings using configuration object.
777
+
778
+ :param FindingsSyncConfig config: Configuration for findings sync
310
779
  """
311
780
  try:
312
781
  logger.info("Starting AWS Security Hub findings sync to RegScale...")
313
782
  from .scanner import AWSInventoryIntegration
783
+ import boto3
784
+ from regscale.integrations.commercial.aws.common import fetch_aws_findings
314
785
 
315
- scanner = AWSInventoryIntegration(plan_id=regscale_id)
316
- findings_processed = scanner.sync_findings(
317
- plan_id=regscale_id,
318
- region=region,
319
- aws_access_key_id=aws_access_key_id,
320
- aws_secret_access_key=aws_secret_access_key,
321
- aws_session_token=aws_session_token,
786
+ # Resolve credentials from session cache or other methods
787
+ profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region = resolve_aws_credentials(
788
+ config.credentials.session_name,
789
+ config.credentials.profile,
790
+ config.credentials.aws_access_key_id,
791
+ config.credentials.aws_secret_access_key,
792
+ config.credentials.aws_session_token,
793
+ config.region,
322
794
  )
323
- logger.info(f"AWS Security Hub findings sync completed successfully. Processed {findings_processed} findings.")
795
+
796
+ # Debug logging
797
+ logger.debug(
798
+ f"Resolved credentials - profile: {profile}, region: {region}, has_keys: {bool(aws_access_key_id)}"
799
+ )
800
+
801
+ # Get tag dictionary and account ID from credentials
802
+ tag_dict = config.credentials.tags
803
+ if tag_dict:
804
+ logger.info(f"Filtering findings by tags: {tag_dict}")
805
+ if config.credentials.account_id:
806
+ logger.info(f"Filtering findings by account ID: {config.credentials.account_id}")
807
+
808
+ # Parse control IDs
809
+ control_id_list = None
810
+ if config.control_ids:
811
+ control_id_list = [int(cid.strip()) for cid in config.control_ids.split(",")]
812
+
813
+ # If evidence generation or OCSF format requested, use enhanced processing
814
+ if config.generate_evidence or config.format != "native":
815
+ # Create AWS session
816
+ if aws_access_key_id or aws_secret_access_key:
817
+ session = boto3.Session(
818
+ region_name=region,
819
+ aws_access_key_id=aws_access_key_id,
820
+ aws_secret_access_key=aws_secret_access_key,
821
+ aws_session_token=aws_session_token,
822
+ )
823
+ else:
824
+ session = boto3.Session(
825
+ profile_name=profile,
826
+ region_name=region,
827
+ )
828
+ client = session.client("securityhub")
829
+
830
+ logger.info("Fetching findings from AWS Security Hub...")
831
+ # Fetch raw findings with minimum severity from config
832
+ from regscale.core.app.application import Application
833
+
834
+ app = Application()
835
+ minimum_severity = app.config.get("issues", {}).get("amazon", {}).get("minimumSeverity")
836
+ raw_findings = fetch_aws_findings(aws_client=client, minimum_severity=minimum_severity)
837
+ logger.info(f"Fetched {len(raw_findings)} findings from AWS Security Hub")
838
+
839
+ # Process with evidence/OCSF support
840
+ scanner = AWSInventoryIntegration(
841
+ plan_id=config.regscale_id, import_all_findings=config.import_all_findings
842
+ )
843
+ scanner.authenticate(
844
+ aws_access_key_id=aws_access_key_id,
845
+ aws_secret_access_key=aws_secret_access_key,
846
+ region=region,
847
+ aws_session_token=aws_session_token,
848
+ profile=profile,
849
+ account_id=config.credentials.account_id,
850
+ tags=tag_dict,
851
+ )
852
+
853
+ # Don't set num_findings_to_process since many findings get filtered during parse_finding()
854
+ # Progress bar will show processed count without a misleading total
855
+
856
+ # Get asset map for linking findings to assets
857
+ logger.info("Loading asset map from RegScale...")
858
+ scanner.asset_map_by_identifier.update(scanner.get_asset_map())
859
+
860
+ logger.info("Processing findings with evidence/OCSF support...")
861
+ integration_findings, evidence = scanner.process_findings_with_evidence(
862
+ findings=raw_findings,
863
+ service_name="SecurityHub",
864
+ generate_evidence=config.generate_evidence,
865
+ ssp_id=config.regscale_id, # Use regscale_id as the SSP ID for evidence linking
866
+ control_ids=control_id_list,
867
+ ocsf_format=(config.format in ["ocsf", "both"]),
868
+ )
869
+
870
+ # Sync findings to RegScale with progress bar
871
+ logger.info("Syncing findings to RegScale...")
872
+ findings_processed = scanner.update_regscale_findings(integration_findings)
873
+
874
+ logger.info(
875
+ f"AWS Security Hub findings sync completed successfully. Processed {findings_processed} findings."
876
+ )
877
+ if evidence:
878
+ logger.info(f"Created evidence record: {evidence.id} - {evidence.title}")
879
+ else:
880
+ # Standard sync without evidence generation
881
+ findings_processed = AWSInventoryIntegration.sync_findings(
882
+ plan_id=config.regscale_id,
883
+ region=region,
884
+ profile=profile,
885
+ aws_access_key_id=aws_access_key_id,
886
+ aws_secret_access_key=aws_secret_access_key,
887
+ aws_session_token=aws_session_token,
888
+ account_id=config.credentials.account_id,
889
+ tags=tag_dict,
890
+ import_all_findings=config.import_all_findings,
891
+ )
892
+ logger.info(
893
+ f"AWS Security Hub findings sync completed successfully. Processed {findings_processed} findings."
894
+ )
895
+
324
896
  except Exception as e:
325
897
  logger.error(f"Error syncing AWS Security Hub findings: {e}", exc_info=True)
326
898
  raise click.ClickException(str(e))
@@ -340,33 +912,69 @@ def sync_findings(
340
912
  help="RegScale will create and update findings and assets as children of this record.",
341
913
  required=True,
342
914
  )
915
+ @click.option(
916
+ "--session-name",
917
+ type=str,
918
+ required=False,
919
+ help="Name of cached AWS session to use (from 'regscale aws auth login')",
920
+ )
921
+ @click.option(
922
+ "--profile",
923
+ type=str,
924
+ required=False,
925
+ help="AWS profile name from ~/.aws/credentials",
926
+ envvar="AWS_PROFILE",
927
+ )
343
928
  @click.option(
344
929
  "--aws_access_key_id",
345
930
  type=str,
346
931
  required=False,
347
- help="AWS access key ID",
932
+ help="AWS access key ID (overrides profile)",
348
933
  envvar="AWS_ACCESS_KEY_ID",
349
934
  )
350
935
  @click.option(
351
936
  "--aws_secret_access_key",
352
937
  type=str,
353
938
  required=False,
354
- help="AWS secret access key",
939
+ help="AWS secret access key (overrides profile)",
355
940
  default=os.getenv("AWS_SECRET_ACCESS_KEY"),
356
941
  )
357
942
  @click.option(
358
943
  "--aws_session_token",
359
944
  type=click.STRING,
360
945
  required=False,
361
- help="AWS Session ID",
946
+ help="AWS session token (overrides profile)",
362
947
  default=os.environ.get("AWS_SESSION_TOKEN"),
363
948
  )
949
+ @click.option(
950
+ "--account-id",
951
+ type=str,
952
+ required=False,
953
+ help="Filter resources by AWS account ID (from ARN)",
954
+ )
955
+ @click.option(
956
+ "--tags",
957
+ type=str,
958
+ required=False,
959
+ help="Filter resources by tags (format: key1=value1,key2=value2). All specified tags must match (AND logic).",
960
+ )
961
+ @click.option(
962
+ "--import-all-findings",
963
+ is_flag=True,
964
+ default=False,
965
+ help="Import all findings even if they are not associated with an asset in RegScale. By default, findings without matching assets are skipped.",
966
+ )
364
967
  def sync_findings_and_assets(
365
968
  region: str,
366
969
  regscale_id: int,
970
+ session_name: Optional[str] = None,
971
+ profile: Optional[str] = None,
367
972
  aws_access_key_id: Optional[str] = None,
368
973
  aws_secret_access_key: Optional[str] = None,
369
974
  aws_session_token: Optional[str] = None,
975
+ account_id: Optional[str] = None,
976
+ tags: Optional[str] = None,
977
+ import_all_findings: bool = False,
370
978
  ) -> None:
371
979
  """
372
980
  Sync AWS Security Hub findings and automatically discovered assets to RegScale.
@@ -375,18 +983,43 @@ def sync_findings_and_assets(
375
983
  issues in RegScale, and also creates assets for the resources referenced in the findings.
376
984
  This provides a comprehensive view by creating both the security findings and the
377
985
  underlying AWS resources they reference.
986
+
987
+ Filtering support:
988
+ - Account ID: Filter resources by AWS account ID (extracted from ARNs)
989
+ - Tags: Filter resources by tag key-value pairs (AND logic - all must match)
990
+
991
+ Authentication methods (in priority order):
992
+ 1. Cached session: --session-name (from 'regscale aws auth login')
993
+ 2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
994
+ 3. AWS profile: --profile
995
+ 4. Environment variables or default AWS credential chain
378
996
  """
379
997
  try:
380
998
  logger.info("Starting AWS Security Hub findings and assets sync to RegScale...")
381
999
  from .scanner import AWSInventoryIntegration
382
1000
 
383
- scanner = AWSInventoryIntegration(plan_id=regscale_id)
1001
+ # Parse tags if provided
1002
+ tag_dict = parse_tags(tags)
1003
+ if account_id:
1004
+ logger.info(f"Filtering resources by account ID: {account_id}")
1005
+ if tag_dict:
1006
+ logger.info(f"Filtering resources by tags: {tag_dict}")
1007
+
1008
+ # Resolve credentials from session cache or other methods
1009
+ profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region = resolve_aws_credentials(
1010
+ session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
1011
+ )
1012
+
1013
+ scanner = AWSInventoryIntegration(plan_id=regscale_id, import_all_findings=import_all_findings)
384
1014
  findings_processed, assets_processed = scanner.sync_findings_and_assets(
385
1015
  plan_id=regscale_id,
386
1016
  region=region,
1017
+ profile=profile,
387
1018
  aws_access_key_id=aws_access_key_id,
388
1019
  aws_secret_access_key=aws_secret_access_key,
389
1020
  aws_session_token=aws_session_token,
1021
+ account_id=account_id,
1022
+ tags=tag_dict,
390
1023
  )
391
1024
  logger.info(
392
1025
  f"AWS Security Hub sync completed successfully. "
@@ -403,6 +1036,121 @@ def findings():
403
1036
  pass
404
1037
 
405
1038
 
1039
+ def _create_aws_session(
1040
+ aws_access_key_id: Optional[str],
1041
+ aws_secret_access_key: Optional[str],
1042
+ aws_session_token: Optional[str],
1043
+ profile: Optional[str],
1044
+ region: str,
1045
+ ) -> "boto3.Session":
1046
+ """
1047
+ Create boto3 session with provided credentials.
1048
+
1049
+ :param Optional[str] aws_access_key_id: AWS access key ID
1050
+ :param Optional[str] aws_secret_access_key: AWS secret access key
1051
+ :param Optional[str] aws_session_token: AWS session token
1052
+ :param Optional[str] profile: AWS profile name
1053
+ :param str region: AWS region
1054
+ :return: Configured boto3 session
1055
+ :rtype: boto3.Session
1056
+ """
1057
+ import boto3
1058
+
1059
+ if aws_access_key_id or aws_secret_access_key:
1060
+ return boto3.Session(
1061
+ region_name=region,
1062
+ aws_access_key_id=aws_access_key_id,
1063
+ aws_secret_access_key=aws_secret_access_key,
1064
+ aws_session_token=aws_session_token,
1065
+ )
1066
+ return boto3.Session(profile_name=profile, region_name=region)
1067
+
1068
+
1069
+ def _finding_matches_account(finding: Dict[str, Any], account_id: str) -> bool:
1070
+ """
1071
+ Check if finding's resources match the account ID.
1072
+
1073
+ :param Dict[str, Any] finding: AWS Security Hub finding
1074
+ :param str account_id: AWS account ID to match
1075
+ :return: True if any resource matches the account ID
1076
+ :rtype: bool
1077
+ """
1078
+ resources = finding.get("Resources", [])
1079
+ for resource in resources:
1080
+ resource_id = resource.get("Id", "")
1081
+ if resource_id.startswith("arn:"):
1082
+ arn_parts = resource_id.split(":")
1083
+ if len(arn_parts) >= 5 and arn_parts[4] == account_id:
1084
+ return True
1085
+ return False
1086
+
1087
+
1088
+ def _finding_matches_tags(finding: Dict[str, Any], tag_dict: Dict[str, str]) -> bool:
1089
+ """
1090
+ Check if finding's resources match all specified tags.
1091
+
1092
+ :param Dict[str, Any] finding: AWS Security Hub finding
1093
+ :param Dict[str, str] tag_dict: Tags to match (all must match - AND logic)
1094
+ :return: True if any resource matches all tags
1095
+ :rtype: bool
1096
+ """
1097
+ resources = finding.get("Resources", [])
1098
+ for resource in resources:
1099
+ resource_tags = resource.get("Tags", {})
1100
+ if all(resource_tags.get(k) == v for k, v in tag_dict.items()):
1101
+ return True
1102
+ return False
1103
+
1104
+
1105
+ def _filter_findings(
1106
+ findings: List[Dict[str, Any]], account_id: Optional[str], tag_dict: Optional[Dict[str, str]]
1107
+ ) -> List[Dict[str, Any]]:
1108
+ """
1109
+ Apply account and tag filters to findings.
1110
+
1111
+ :param List[Dict[str, Any]] findings: List of findings to filter
1112
+ :param Optional[str] account_id: Account ID filter (if specified)
1113
+ :param Optional[Dict[str, str]] tag_dict: Tag filters (if specified)
1114
+ :return: Filtered list of findings
1115
+ :rtype: List[Dict[str, Any]]
1116
+ """
1117
+ if not account_id and not tag_dict:
1118
+ return findings
1119
+
1120
+ filtered = []
1121
+ for finding in findings:
1122
+ if account_id and not _finding_matches_account(finding, account_id):
1123
+ continue
1124
+ if tag_dict and not _finding_matches_tags(finding, tag_dict):
1125
+ continue
1126
+ filtered.append(finding)
1127
+
1128
+ return filtered
1129
+
1130
+
1131
+ def _write_findings_output(findings: List[Dict[str, Any]], output: Optional[str]) -> None:
1132
+ """
1133
+ Write findings to stdout or file.
1134
+
1135
+ :param List[Dict[str, Any]] findings: Findings to write
1136
+ :param Optional[str] output: Output path (None for default, '-' for stdout)
1137
+ :rtype: None
1138
+ """
1139
+ from regscale.models import DateTimeEncoder
1140
+
1141
+ # Default output path
1142
+ if output is None:
1143
+ output = os.path.join("artifacts", "aws", "findings.json")
1144
+
1145
+ if output == "-":
1146
+ click.echo(json.dumps(findings, indent=2, cls=DateTimeEncoder))
1147
+ else:
1148
+ os.makedirs(os.path.dirname(output), exist_ok=True)
1149
+ with open(output, "w", encoding="utf-8") as f:
1150
+ json.dump(findings, f, indent=2, cls=DateTimeEncoder)
1151
+ logger.info(f"Findings saved to {output}")
1152
+
1153
+
406
1154
  @findings.command(name="collect")
407
1155
  @click.option(
408
1156
  "--region",
@@ -410,27 +1158,52 @@ def findings():
410
1158
  default=os.getenv("AWS_REGION", "us-east-1"),
411
1159
  help="AWS region to collect findings from. Default is us-east-1.",
412
1160
  )
1161
+ @click.option(
1162
+ "--session-name",
1163
+ type=str,
1164
+ required=False,
1165
+ help="Name of cached AWS session to use (from 'regscale aws auth login')",
1166
+ )
1167
+ @click.option(
1168
+ "--profile",
1169
+ type=str,
1170
+ required=False,
1171
+ help="AWS profile name from ~/.aws/credentials",
1172
+ envvar="AWS_PROFILE",
1173
+ )
413
1174
  @click.option(
414
1175
  "--aws_access_key_id",
415
1176
  type=str,
416
1177
  required=False,
417
- help="AWS access key ID",
1178
+ help="AWS access key ID (overrides profile)",
418
1179
  envvar="AWS_ACCESS_KEY_ID",
419
1180
  )
420
1181
  @click.option(
421
1182
  "--aws_secret_access_key",
422
1183
  type=str,
423
1184
  required=False,
424
- help="AWS secret access key",
1185
+ help="AWS secret access key (overrides profile)",
425
1186
  envvar="AWS_SECRET_ACCESS_KEY",
426
1187
  )
427
1188
  @click.option(
428
1189
  "--aws_session_token",
429
1190
  type=click.STRING,
430
1191
  required=False,
431
- help="AWS Session ID",
1192
+ help="AWS session token (overrides profile)",
432
1193
  default=os.environ.get("AWS_SESSION_TOKEN"),
433
1194
  )
1195
+ @click.option(
1196
+ "--account-id",
1197
+ type=str,
1198
+ required=False,
1199
+ help="Filter findings by AWS account ID (from ARN)",
1200
+ )
1201
+ @click.option(
1202
+ "--tags",
1203
+ type=str,
1204
+ required=False,
1205
+ help="Filter findings by tags (format: key1=value1,key2=value2). All specified tags must match (AND logic).",
1206
+ )
434
1207
  @click.option(
435
1208
  "--output",
436
1209
  type=click.Path(dir_okay=False, writable=True),
@@ -439,9 +1212,13 @@ def findings():
439
1212
  )
440
1213
  def collect_findings(
441
1214
  region: str,
1215
+ session_name: Optional[str],
1216
+ profile: Optional[str],
442
1217
  aws_access_key_id: Optional[str],
443
1218
  aws_secret_access_key: Optional[str],
444
1219
  aws_session_token: Optional[str],
1220
+ account_id: Optional[str],
1221
+ tags: Optional[str],
445
1222
  output: Optional[str],
446
1223
  ) -> None:
447
1224
  """
@@ -453,44 +1230,2320 @@ def collect_findings(
453
1230
 
454
1231
  If no output file is specified, findings will be saved to artifacts/aws/findings.json
455
1232
  by default. Use --output - to display to stdout instead.
1233
+
1234
+ Filtering support:
1235
+ - Account ID: Filter findings by AWS account ID (extracted from ARNs)
1236
+ - Tags: Filter findings by tag key-value pairs (AND logic - all must match)
1237
+
1238
+ Authentication methods (in priority order):
1239
+ 1. Cached session: --session-name (from 'regscale aws auth login')
1240
+ 2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
1241
+ 3. AWS profile: --profile
1242
+ 4. Environment variables or default AWS credential chain
456
1243
  """
457
1244
  try:
458
- import boto3
459
- from regscale.integrations.commercial.amazon.common import fetch_aws_findings
460
- from regscale.models import DateTimeEncoder
1245
+ from regscale.integrations.commercial.aws.common import fetch_aws_findings
461
1246
 
462
1247
  logger.info("Collecting AWS Security Hub findings...")
463
1248
 
464
- # Create AWS session
465
- session = boto3.Session(
466
- region_name=region,
467
- aws_access_key_id=aws_access_key_id,
468
- aws_secret_access_key=aws_secret_access_key,
469
- aws_session_token=aws_session_token,
1249
+ # Parse tags and log filters
1250
+ tag_dict = parse_tags(tags)
1251
+ if account_id:
1252
+ logger.info(f"Filtering findings by account ID: {account_id}")
1253
+ if tag_dict:
1254
+ logger.info(f"Filtering findings by tags: {tag_dict}")
1255
+
1256
+ # Resolve credentials
1257
+ profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region = resolve_aws_credentials(
1258
+ session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
470
1259
  )
471
- client = session.client("securityhub")
472
1260
 
473
- # Fetch findings
1261
+ # Create session and fetch findings
1262
+ session = _create_aws_session(aws_access_key_id, aws_secret_access_key, aws_session_token, profile, region)
1263
+ client = session.client("securityhub")
474
1264
  findings = fetch_aws_findings(aws_client=client)
475
1265
 
1266
+ # Apply filtering
1267
+ original_count = len(findings)
1268
+ findings = _filter_findings(findings, account_id, tag_dict)
1269
+
1270
+ if account_id or tag_dict:
1271
+ logger.info(f"Filtered from {original_count} to {len(findings)} findings based on criteria")
1272
+
476
1273
  logger.info(f"AWS Security Hub findings collected successfully. Found {len(findings)} finding(s).")
477
1274
 
478
- # Default output path
479
- if output is None:
480
- output = os.path.join("artifacts", "aws", "findings.json")
1275
+ # Write output
1276
+ _write_findings_output(findings, output)
1277
+
1278
+ except Exception as e:
1279
+ logger.error(f"Error collecting AWS Security Hub findings: {e}", exc_info=True)
1280
+ raise click.ClickException(str(e))
1281
+
1282
+
1283
+ @awsv2.group()
1284
+ def auth():
1285
+ """AWS session token management commands."""
1286
+ pass
1287
+
1288
+
1289
+ @auth.command(name="login")
1290
+ @click.option(
1291
+ "--session-name",
1292
+ type=str,
1293
+ required=True,
1294
+ help="Name for this session (used to cache credentials)",
1295
+ )
1296
+ @click.option(
1297
+ "--profile",
1298
+ type=str,
1299
+ required=False,
1300
+ help="AWS profile name from ~/.aws/credentials",
1301
+ envvar="AWS_PROFILE",
1302
+ )
1303
+ @click.option(
1304
+ "--aws_access_key_id",
1305
+ type=str,
1306
+ required=False,
1307
+ help="AWS access key ID",
1308
+ envvar="AWS_ACCESS_KEY_ID",
1309
+ )
1310
+ @click.option(
1311
+ "--aws_secret_access_key",
1312
+ type=str,
1313
+ required=False,
1314
+ help="AWS secret access key",
1315
+ envvar="AWS_SECRET_ACCESS_KEY",
1316
+ )
1317
+ @click.option(
1318
+ "--mfa-serial",
1319
+ type=str,
1320
+ required=False,
1321
+ help="ARN of MFA device (e.g., arn:aws:iam::123456789012:mfa/username)",
1322
+ )
1323
+ @click.option(
1324
+ "--mfa-code",
1325
+ type=str,
1326
+ required=False,
1327
+ help="6-digit MFA code from authenticator app",
1328
+ )
1329
+ @click.option(
1330
+ "--role-arn",
1331
+ type=str,
1332
+ required=False,
1333
+ help="ARN of role to assume (e.g., arn:aws:iam::123456789012:role/MyRole)",
1334
+ )
1335
+ @click.option(
1336
+ "--role-session-name",
1337
+ type=str,
1338
+ required=False,
1339
+ help="Name for the assumed role session",
1340
+ )
1341
+ @click.option(
1342
+ "--duration",
1343
+ type=int,
1344
+ default=3600,
1345
+ help="Duration for session token in seconds (900-43200, default: 3600)",
1346
+ )
1347
+ @click.option(
1348
+ "--region",
1349
+ type=str,
1350
+ default=os.environ.get("AWS_REGION", "us-east-1"),
1351
+ help="AWS region to associate with this session",
1352
+ )
1353
+ def login(
1354
+ session_name: str,
1355
+ profile: Optional[str],
1356
+ aws_access_key_id: Optional[str],
1357
+ aws_secret_access_key: Optional[str],
1358
+ mfa_serial: Optional[str],
1359
+ mfa_code: Optional[str],
1360
+ role_arn: Optional[str],
1361
+ role_session_name: Optional[str],
1362
+ duration: int,
1363
+ region: str,
1364
+ ) -> None:
1365
+ """
1366
+ Generate and cache AWS session tokens.
1367
+
1368
+ This command generates temporary AWS credentials (session tokens) and caches them
1369
+ locally for use with subsequent AWS commands. Session tokens provide better security
1370
+ than long-term access keys and support MFA authentication.
1371
+
1372
+ Examples:
1373
+
1374
+ # Simple session from profile
1375
+ regscale aws auth login --session-name my-session --profile default
1376
+
1377
+ # Session with MFA
1378
+ regscale aws auth login --session-name my-session --profile default \\
1379
+ --mfa-serial arn:aws:iam::123456789012:mfa/username --mfa-code 123456
1380
+
1381
+ # Assume role with MFA
1382
+ regscale aws auth login --session-name cross-account --profile default \\
1383
+ --role-arn arn:aws:iam::987654321098:role/CrossAccountRole \\
1384
+ --mfa-serial arn:aws:iam::123456789012:mfa/username --mfa-code 123456
1385
+
1386
+ # Session with explicit credentials
1387
+ regscale aws auth login --session-name my-session \\
1388
+ --aws_access_key_id AKIAIOSFODNN7EXAMPLE \\
1389
+ --aws_secret_access_key wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
1390
+ """
1391
+ try:
1392
+ from .session_manager import AWSSessionManager
1393
+
1394
+ logger.info(f"Generating AWS session token: {session_name}")
1395
+
1396
+ manager = AWSSessionManager()
1397
+
1398
+ # Generate session token
1399
+ credentials = manager.get_session_token(
1400
+ profile=profile,
1401
+ aws_access_key_id=aws_access_key_id,
1402
+ aws_secret_access_key=aws_secret_access_key,
1403
+ mfa_serial=mfa_serial,
1404
+ mfa_code=mfa_code,
1405
+ role_arn=role_arn,
1406
+ role_session_name=role_session_name,
1407
+ duration_seconds=duration,
1408
+ )
1409
+
1410
+ # Cache the session
1411
+ manager.cache_session(session_name, credentials, region)
1412
+
1413
+ click.echo(click.style(f"\n✓ Session '{session_name}' created successfully!", fg="green", bold=True))
1414
+ click.echo(f"Region: {region}")
1415
+ click.echo(f"Expires: {credentials['expiration']}")
1416
+ click.echo(f"\nUse --session-name {session_name} with AWS commands to use these credentials.")
1417
+
1418
+ except Exception as e:
1419
+ logger.error(f"Failed to generate session token: {e}", exc_info=True)
1420
+ raise click.ClickException(str(e))
1421
+
1422
+
1423
+ @auth.command(name="logout")
1424
+ @click.option(
1425
+ "--session-name",
1426
+ type=str,
1427
+ required=True,
1428
+ help="Name of session to clear",
1429
+ )
1430
+ def logout(session_name: str) -> None:
1431
+ """
1432
+ Clear a cached AWS session.
1433
+
1434
+ This removes the cached session tokens for the specified session name.
1435
+
1436
+ Example:
1437
+ regscale aws auth logout --session-name my-session
1438
+ """
1439
+ try:
1440
+ from .session_manager import AWSSessionManager
1441
+
1442
+ manager = AWSSessionManager()
481
1443
 
482
- if output == "-":
483
- # Output to stdout
484
- click.echo(json.dumps(findings, indent=2, cls=DateTimeEncoder))
1444
+ if manager.clear_session(session_name):
1445
+ click.echo(click.style(f"✓ Session '{session_name}' cleared successfully!", fg="green"))
485
1446
  else:
486
- # Save to file
487
- # Ensure the artifacts directory exists
488
- os.makedirs(os.path.dirname(output), exist_ok=True)
1447
+ click.echo(click.style(f"Session '{session_name}' not found.", fg="yellow"))
1448
+
1449
+ except Exception as e:
1450
+ logger.error(f"Failed to clear session: {e}", exc_info=True)
1451
+ raise click.ClickException(str(e))
1452
+
1453
+
1454
+ @auth.command(name="logout-all")
1455
+ @click.confirmation_option(prompt="Are you sure you want to clear all cached sessions?")
1456
+ def logout_all() -> None:
1457
+ """
1458
+ Clear all cached AWS sessions.
489
1459
 
490
- with open(output, "w", encoding="utf-8") as f:
491
- json.dump(findings, f, indent=2, cls=DateTimeEncoder)
492
- logger.info(f"Findings saved to {output}")
1460
+ This removes all cached session tokens.
1461
+
1462
+ Example:
1463
+ regscale aws auth logout-all
1464
+ """
1465
+ try:
1466
+ from .session_manager import AWSSessionManager
1467
+
1468
+ manager = AWSSessionManager()
1469
+ count = manager.clear_all_sessions()
1470
+
1471
+ click.echo(click.style(f"✓ Cleared {count} session(s) successfully!", fg="green"))
493
1472
 
494
1473
  except Exception as e:
495
- logger.error(f"Error collecting AWS Security Hub findings: {e}", exc_info=True)
1474
+ logger.error(f"Failed to clear sessions: {e}", exc_info=True)
1475
+ raise click.ClickException(str(e))
1476
+
1477
+
1478
+ @auth.command(name="list")
1479
+ def list_sessions() -> None:
1480
+ """
1481
+ List all cached AWS sessions.
1482
+
1483
+ This shows all cached session tokens with their expiration status.
1484
+
1485
+ Example:
1486
+ regscale aws auth list
1487
+ """
1488
+ try:
1489
+ from .session_manager import AWSSessionManager
1490
+
1491
+ manager = AWSSessionManager()
1492
+ sessions = manager.list_sessions()
1493
+
1494
+ if not sessions:
1495
+ click.echo("No cached sessions found.")
1496
+ return
1497
+
1498
+ click.echo("\nCached AWS Sessions:")
1499
+ click.echo("=" * 80)
1500
+
1501
+ for session in sessions:
1502
+ status = click.style("EXPIRED", fg="red") if session["expired"] else click.style("ACTIVE", fg="green")
1503
+ click.echo(f"\nSession: {click.style(session['name'], bold=True)}")
1504
+ click.echo(f" Status: {status}")
1505
+ click.echo(f" Region: {session['region']}")
1506
+ click.echo(f" Expires: {session['expiration']}")
1507
+ click.echo(f" Cached At: {session['cached_at']}")
1508
+
1509
+ click.echo("\n" + "=" * 80)
1510
+
1511
+ except Exception as e:
1512
+ logger.error(f"Failed to list sessions: {e}", exc_info=True)
1513
+ raise click.ClickException(str(e))
1514
+
1515
+
1516
+ @awsv2.command(name="sync_compliance")
1517
+ @click.option(
1518
+ "--region",
1519
+ type=str,
1520
+ default=os.environ.get("AWS_REGION", "us-east-1"),
1521
+ help="AWS region to collect compliance data from",
1522
+ )
1523
+ @click.option(
1524
+ "--regscale_id",
1525
+ "--id",
1526
+ type=click.INT,
1527
+ help="RegScale will create and update compliance assessments as children of this record.",
1528
+ required=True,
1529
+ )
1530
+ @click.option(
1531
+ "--session-name",
1532
+ type=str,
1533
+ required=False,
1534
+ help="Name of cached AWS session to use (from 'regscale aws auth login')",
1535
+ )
1536
+ @click.option(
1537
+ "--profile",
1538
+ type=str,
1539
+ required=False,
1540
+ help="AWS profile name from ~/.aws/credentials",
1541
+ envvar="AWS_PROFILE",
1542
+ )
1543
+ @click.option(
1544
+ "--aws_access_key_id",
1545
+ type=str,
1546
+ required=False,
1547
+ help="AWS access key ID (overrides profile)",
1548
+ envvar="AWS_ACCESS_KEY_ID",
1549
+ )
1550
+ @click.option(
1551
+ "--aws_secret_access_key",
1552
+ type=str,
1553
+ required=False,
1554
+ help="AWS secret access key (overrides profile)",
1555
+ default=os.getenv("AWS_SECRET_ACCESS_KEY"),
1556
+ )
1557
+ @click.option(
1558
+ "--aws_session_token",
1559
+ type=click.STRING,
1560
+ required=False,
1561
+ help="AWS session token (overrides profile)",
1562
+ default=os.environ.get("AWS_SESSION_TOKEN"),
1563
+ )
1564
+ @click.option(
1565
+ "--account-id",
1566
+ type=str,
1567
+ required=False,
1568
+ help="Filter resources by AWS account ID (from ARN)",
1569
+ )
1570
+ @click.option(
1571
+ "--tags",
1572
+ type=str,
1573
+ required=False,
1574
+ help="Filter resources by tags (format: key1=value1,key2=value2). All specified tags must match (AND logic).",
1575
+ )
1576
+ @click.option(
1577
+ "--framework",
1578
+ type=click.Choice(
1579
+ ["NIST800-53R5", "SOC2", "PCI DSS", "HIPAA", "GDPR", "ISO27001", "CIS", "CUSTOM"],
1580
+ case_sensitive=False,
1581
+ ),
1582
+ default="NIST800-53R5",
1583
+ help="Compliance framework to sync. Only assessments matching this framework will be processed. "
1584
+ "Use CUSTOM for custom frameworks and provide --custom-framework-name. "
1585
+ "NOTE: Framework filtering is bypassed when --assessment-id is specified.",
1586
+ )
1587
+ @click.option(
1588
+ "--custom-framework-name",
1589
+ type=str,
1590
+ required=False,
1591
+ help="Custom framework name for CUSTOM framework types. Required when --framework=CUSTOM.",
1592
+ )
1593
+ @click.option(
1594
+ "--assessment-id",
1595
+ type=str,
1596
+ required=False,
1597
+ help="Specific AWS Audit Manager assessment ID to sync. When provided, framework filtering is bypassed "
1598
+ "and the specified assessment is used directly regardless of its framework type.",
1599
+ )
1600
+ @click.option(
1601
+ "--create-issues/--no-create-issues",
1602
+ default=True,
1603
+ help="Create issues for failed compliance controls (default: True)",
1604
+ )
1605
+ @click.option(
1606
+ "--update-control-status/--no-update-control-status",
1607
+ default=True,
1608
+ help="Update control implementation status based on compliance results (default: True)",
1609
+ )
1610
+ @click.option(
1611
+ "--create-poams",
1612
+ "-cp",
1613
+ is_flag=True,
1614
+ default=False,
1615
+ help="Mark created issues as POAMs (default: False)",
1616
+ )
1617
+ @click.option(
1618
+ "--collect-evidence/--no-collect-evidence",
1619
+ default=False,
1620
+ help="Collect and store evidence artifacts from AWS Audit Manager (default: False)",
1621
+ )
1622
+ @click.option(
1623
+ "--evidence-control-ids",
1624
+ type=str,
1625
+ required=False,
1626
+ help="Comma-separated list of control IDs to collect evidence for (e.g., 'AU-2,AU-3,AU-6'). "
1627
+ "If not specified, evidence is collected for all controls in the assessment.",
1628
+ )
1629
+ @click.option(
1630
+ "--evidence-frequency",
1631
+ type=int,
1632
+ default=30,
1633
+ help="Evidence update frequency in days (default: 30)",
1634
+ )
1635
+ @click.option(
1636
+ "--max-evidence-per-control",
1637
+ type=int,
1638
+ default=100,
1639
+ help="Maximum number of evidence items to collect per control (default: 100, max: 1000)",
1640
+ )
1641
+ @click.option(
1642
+ "--use-assessment-evidence-folders/--no-use-assessment-evidence-folders",
1643
+ default=True,
1644
+ help="Use assessment-level evidence collection (faster, automatic) vs control-level (slower, requires manual report). "
1645
+ "Assessment-level collects evidence from assessment folders directly. Control-level requires assessment report generation. "
1646
+ "(default: True - use assessment folders)",
1647
+ )
1648
+ @click.option(
1649
+ "--force-refresh",
1650
+ is_flag=True,
1651
+ default=False,
1652
+ help="Force refresh of compliance data by bypassing cache (default: False)",
1653
+ )
1654
+ @click.option(
1655
+ "--use-enhanced-analyzer/--no-enhanced-analyzer",
1656
+ default=True,
1657
+ help="Use enhanced ControlComplianceAnalyzer for evidence-based compliance determination (default: True)",
1658
+ )
1659
+ @click.pass_context
1660
+ def sync_compliance(ctx, **kwargs) -> None:
1661
+ """
1662
+ Sync AWS Audit Manager compliance assessments to RegScale.
1663
+
1664
+ This command fetches compliance assessment results from AWS Audit Manager and:
1665
+ - Creates control assessments in RegScale based on AWS assessment results
1666
+ - Creates issues for failed compliance controls (optional)
1667
+ - Updates control implementation status (optional)
1668
+ - Collects and stores evidence artifacts from AWS Audit Manager (optional)
1669
+ - Supports multiple compliance frameworks (NIST 800-53, SOC2, PCI DSS, etc.)
1670
+
1671
+ AWS Audit Manager provides automated evidence collection and compliance
1672
+ assessments against standard and custom frameworks. This integration brings
1673
+ those assessments into RegScale for unified compliance management.
1674
+
1675
+ Filtering support:
1676
+ - Account ID: Filter resources by AWS account ID (extracted from ARNs)
1677
+ - Tags: Filter resources by tag key-value pairs (AND logic - all must match)
1678
+
1679
+ Evidence Collection:
1680
+ Use --collect-evidence to retrieve and store evidence artifacts from AWS Audit Manager.
1681
+ Evidence is collected from evidence folders for each control and stored as JSONL
1682
+ attachments in RegScale Evidence records. Evidence includes CloudTrail events,
1683
+ AWS Config snapshots, and other automated evidence sources.
1684
+
1685
+ Authentication methods (in priority order):
1686
+ 1. Cached session: --session-name (from 'regscale aws auth login')
1687
+ 2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
1688
+ 3. AWS profile: --profile
1689
+ 4. Environment variables or default AWS credential chain
1690
+
1691
+ Examples:
1692
+ # Sync all assessments for a plan
1693
+ regscale aws sync_compliance --regscale_id 123
1694
+
1695
+ # Sync specific assessment (bypasses framework filtering)
1696
+ regscale aws sync_compliance --regscale_id 123 --assessment-id abc-123
1697
+
1698
+ # Sync custom framework assessment by ID
1699
+ regscale aws sync_compliance --regscale_id 123 --assessment-id abc-123 \\
1700
+ --framework CUSTOM --custom-framework-name "DOC Moderate Baseline"
1701
+ # NOTE: When --assessment-id is provided, the framework parameters are optional
1702
+
1703
+ # Sync without creating issues
1704
+ regscale aws sync_compliance --regscale_id 123 --no-create-issues
1705
+
1706
+ # Sync with evidence collection
1707
+ regscale aws sync_compliance --regscale_id 123 --collect-evidence
1708
+
1709
+ # Collect evidence only for specific controls
1710
+ regscale aws sync_compliance --regscale_id 123 --collect-evidence \\
1711
+ --evidence-control-ids AU-2,AU-3,AU-6,AC-2
1712
+ """
1713
+ try:
1714
+ # Extract parameters from kwargs
1715
+ region = kwargs.get("region", "us-east-1")
1716
+ regscale_id = kwargs.get("regscale_id")
1717
+ session_name = kwargs.get("session_name")
1718
+ profile = kwargs.get("profile")
1719
+ aws_access_key_id = kwargs.get("aws_access_key_id")
1720
+ aws_secret_access_key = kwargs.get("aws_secret_access_key")
1721
+ aws_session_token = kwargs.get("aws_session_token")
1722
+ account_id = kwargs.get("account_id")
1723
+ tags = kwargs.get("tags")
1724
+ framework = kwargs.get("framework", "NIST800-53R5")
1725
+ custom_framework_name = kwargs.get("custom_framework_name")
1726
+ assessment_id = kwargs.get("assessment_id")
1727
+ create_issues = kwargs.get("create_issues", True)
1728
+ update_control_status = kwargs.get("update_control_status", True)
1729
+ create_poams = kwargs.get("create_poams", False)
1730
+ collect_evidence = kwargs.get("collect_evidence", False)
1731
+ evidence_control_ids = kwargs.get("evidence_control_ids")
1732
+ evidence_frequency = kwargs.get("evidence_frequency", 30)
1733
+ max_evidence_per_control = kwargs.get("max_evidence_per_control", 100)
1734
+ use_assessment_evidence_folders = kwargs.get("use_assessment_evidence_folders", True)
1735
+ force_refresh = kwargs.get("force_refresh", False)
1736
+ use_enhanced_analyzer = kwargs.get("use_enhanced_analyzer", True)
1737
+
1738
+ # Parse evidence control IDs into list
1739
+ evidence_control_list = None
1740
+ if evidence_control_ids:
1741
+ evidence_control_list = [cid.strip() for cid in evidence_control_ids.split(",")]
1742
+
1743
+ # Build configuration objects
1744
+ sync_config = ComplianceSyncConfig(
1745
+ region=region,
1746
+ regscale_id=regscale_id,
1747
+ framework=framework,
1748
+ custom_framework_name=custom_framework_name,
1749
+ assessment_id=assessment_id,
1750
+ create_issues=create_issues,
1751
+ update_control_status=update_control_status,
1752
+ create_poams=create_poams,
1753
+ collect_evidence=collect_evidence,
1754
+ evidence_control_ids=evidence_control_list,
1755
+ evidence_frequency=evidence_frequency,
1756
+ max_evidence_per_control=max_evidence_per_control,
1757
+ use_assessment_evidence_folders=use_assessment_evidence_folders,
1758
+ force_refresh=force_refresh,
1759
+ use_enhanced_analyzer=use_enhanced_analyzer,
1760
+ )
1761
+
1762
+ credential_config = AWSCredentialConfig(
1763
+ session_name=session_name,
1764
+ profile=profile,
1765
+ aws_access_key_id=aws_access_key_id,
1766
+ aws_secret_access_key=aws_secret_access_key,
1767
+ aws_session_token=aws_session_token,
1768
+ account_id=account_id,
1769
+ tags=parse_tags(tags),
1770
+ )
1771
+
1772
+ # Delegate to helper function
1773
+ _execute_compliance_sync(sync_config, credential_config)
1774
+
1775
+ except Exception as e:
1776
+ logger.error(f"Error syncing AWS Audit Manager compliance: {e}", exc_info=True)
1777
+ raise click.ClickException(str(e))
1778
+
1779
+
1780
+ def _execute_compliance_sync(sync_config: ComplianceSyncConfig, credential_config: AWSCredentialConfig) -> None:
1781
+ """
1782
+ Execute the compliance sync with provided configurations.
1783
+
1784
+ :param ComplianceSyncConfig sync_config: Sync configuration
1785
+ :param AWSCredentialConfig credential_config: AWS credential configuration
1786
+ :return: None
1787
+ :rtype: None
1788
+ """
1789
+ from .audit_manager_compliance import AWSAuditManagerCompliance
1790
+
1791
+ logger.info("Starting AWS Audit Manager compliance sync to RegScale...")
1792
+
1793
+ # Log filtering information
1794
+ if credential_config.account_id:
1795
+ logger.info(f"Filtering resources by account ID: {credential_config.account_id}")
1796
+ if credential_config.tags:
1797
+ logger.info(f"Filtering resources by tags: {credential_config.tags}")
1798
+
1799
+ # Resolve AWS credentials
1800
+ profile, access_key, secret_key, session_token, region = resolve_aws_credentials(
1801
+ credential_config.session_name,
1802
+ credential_config.profile,
1803
+ credential_config.aws_access_key_id,
1804
+ credential_config.aws_secret_access_key,
1805
+ credential_config.aws_session_token,
1806
+ sync_config.region,
1807
+ )
1808
+
1809
+ # Log credential resolution results
1810
+ logger.info(
1811
+ f"Using AWS credentials - profile: {profile if profile else 'not set'}, "
1812
+ f"explicit credentials: {'yes' if access_key else 'no'}, region: {region}"
1813
+ )
1814
+
1815
+ # Log evidence collection request
1816
+ if sync_config.evidence_control_ids:
1817
+ logger.info(f"Evidence collection requested for controls: {sync_config.evidence_control_ids}")
1818
+
1819
+ # Create scanner and execute sync
1820
+ scanner = AWSAuditManagerCompliance(
1821
+ plan_id=sync_config.regscale_id,
1822
+ region=region,
1823
+ profile=profile,
1824
+ aws_access_key_id=access_key,
1825
+ aws_secret_access_key=secret_key,
1826
+ aws_session_token=session_token,
1827
+ framework=sync_config.framework,
1828
+ custom_framework_name=sync_config.custom_framework_name,
1829
+ assessment_id=sync_config.assessment_id,
1830
+ create_issues=sync_config.create_issues,
1831
+ update_control_status=sync_config.update_control_status,
1832
+ create_poams=sync_config.create_poams,
1833
+ collect_evidence=sync_config.collect_evidence,
1834
+ evidence_control_ids=sync_config.evidence_control_ids,
1835
+ evidence_frequency=sync_config.evidence_frequency,
1836
+ max_evidence_per_control=sync_config.max_evidence_per_control,
1837
+ use_assessment_evidence_folders=sync_config.use_assessment_evidence_folders,
1838
+ account_id=credential_config.account_id,
1839
+ tags=credential_config.tags,
1840
+ force_refresh=sync_config.force_refresh,
1841
+ use_enhanced_analyzer=sync_config.use_enhanced_analyzer,
1842
+ )
1843
+
1844
+ scanner.sync_compliance()
1845
+
1846
+ logger.info("AWS Audit Manager compliance sync completed successfully")
1847
+
1848
+
1849
+ @awsv2.command(name="sync_config_compliance")
1850
+ @click.option(
1851
+ "--region",
1852
+ type=str,
1853
+ default=os.environ.get("AWS_REGION", "us-east-1"),
1854
+ help="AWS region to collect compliance data from",
1855
+ )
1856
+ @click.option(
1857
+ "--regscale_id",
1858
+ "--id",
1859
+ type=click.INT,
1860
+ help="RegScale will create and update compliance assessments as children of this record.",
1861
+ required=True,
1862
+ )
1863
+ @click.option(
1864
+ "--session-name",
1865
+ type=str,
1866
+ required=False,
1867
+ help="Name of cached AWS session to use (from 'regscale aws auth login')",
1868
+ )
1869
+ @click.option(
1870
+ "--profile",
1871
+ type=str,
1872
+ required=False,
1873
+ help="AWS profile name from ~/.aws/credentials",
1874
+ envvar="AWS_PROFILE",
1875
+ )
1876
+ @click.option(
1877
+ "--aws_access_key_id",
1878
+ type=str,
1879
+ required=False,
1880
+ help="AWS access key ID (overrides profile)",
1881
+ envvar="AWS_ACCESS_KEY_ID",
1882
+ )
1883
+ @click.option(
1884
+ "--aws_secret_access_key",
1885
+ type=str,
1886
+ required=False,
1887
+ help="AWS secret access key (overrides profile)",
1888
+ default=os.getenv("AWS_SECRET_ACCESS_KEY"),
1889
+ )
1890
+ @click.option(
1891
+ "--aws_session_token",
1892
+ type=click.STRING,
1893
+ required=False,
1894
+ help="AWS session token (overrides profile)",
1895
+ default=os.environ.get("AWS_SESSION_TOKEN"),
1896
+ )
1897
+ @click.option(
1898
+ "--account-id",
1899
+ type=str,
1900
+ required=False,
1901
+ help="Filter Config rules by AWS account ID (from ARN)",
1902
+ )
1903
+ @click.option(
1904
+ "--tags",
1905
+ type=str,
1906
+ required=False,
1907
+ help="Filter Config rules by tags (format: key1=value1,key2=value2). All specified tags must match (AND logic).",
1908
+ )
1909
+ @click.option(
1910
+ "--framework",
1911
+ type=click.Choice(
1912
+ ["NIST800-53R5", "SOC2", "PCI DSS", "HIPAA", "GDPR", "ISO27001", "CIS"],
1913
+ case_sensitive=False,
1914
+ ),
1915
+ default="NIST800-53R5",
1916
+ help="Compliance framework to sync. Only rules matching this framework will be processed.",
1917
+ )
1918
+ @click.option(
1919
+ "--conformance-pack-name",
1920
+ type=str,
1921
+ required=False,
1922
+ help="Specific AWS Config conformance pack to sync (optional)",
1923
+ )
1924
+ @click.option(
1925
+ "--create-issues/--no-create-issues",
1926
+ default=True,
1927
+ help="Create issues for failed compliance controls (default: True)",
1928
+ )
1929
+ @click.option(
1930
+ "--update-control-status/--no-update-control-status",
1931
+ default=True,
1932
+ help="Update control implementation status based on compliance results (default: True)",
1933
+ )
1934
+ @click.option(
1935
+ "--create-poams",
1936
+ "-cp",
1937
+ is_flag=True,
1938
+ default=False,
1939
+ help="Mark created issues as POAMs (default: False)",
1940
+ )
1941
+ @click.option(
1942
+ "--collect-evidence/--no-collect-evidence",
1943
+ default=False,
1944
+ help="Collect and store evidence artifacts from AWS Config (default: False)",
1945
+ )
1946
+ @click.option(
1947
+ "--evidence-as-attachments/--no-evidence-as-attachments",
1948
+ default=True,
1949
+ help="Store evidence as SSP-level file attachments (default: True)",
1950
+ )
1951
+ @click.option(
1952
+ "--evidence-as-records",
1953
+ is_flag=True,
1954
+ default=False,
1955
+ help="Create individual Evidence records per control (like Audit Manager)",
1956
+ )
1957
+ @click.option(
1958
+ "--evidence-control-ids",
1959
+ type=str,
1960
+ required=False,
1961
+ help="Comma-separated list of control IDs to collect evidence for (e.g., 'AU-2,AU-3,AU-6'). "
1962
+ "If not specified, evidence is collected for all controls.",
1963
+ )
1964
+ @click.option(
1965
+ "--evidence-frequency",
1966
+ type=int,
1967
+ default=30,
1968
+ help="Evidence update frequency in days (default: 30)",
1969
+ )
1970
+ @click.option(
1971
+ "--use-security-hub/--no-use-security-hub",
1972
+ default=False,
1973
+ help="Include AWS Security Hub control findings (default: False)",
1974
+ )
1975
+ @click.option(
1976
+ "--force-refresh",
1977
+ is_flag=True,
1978
+ default=False,
1979
+ help="Force refresh of compliance data by bypassing cache (default: False)",
1980
+ )
1981
+ @click.pass_context
1982
+ def sync_config_compliance(ctx, **kwargs) -> None:
1983
+ """
1984
+ Sync AWS Config compliance assessments to RegScale (alternative to Audit Manager).
1985
+
1986
+ This command provides equivalent functionality to AWS Audit Manager using AWS Config
1987
+ and optionally Security Hub. Use this when Audit Manager is not available.
1988
+
1989
+ This command fetches compliance assessment results from AWS Config rules and:
1990
+ - Creates control assessments in RegScale based on Config rule evaluation results
1991
+ - Creates issues for failed compliance controls (optional)
1992
+ - Updates control implementation status (optional)
1993
+ - Collects evidence artifacts from Config evaluations (optional)
1994
+
1995
+ Evidence Collection Modes:
1996
+ - Default (--evidence-as-attachments): Creates consolidated evidence file attached to SSP
1997
+ - Optional (--evidence-as-records): Creates individual Evidence records per control
1998
+
1999
+ Examples:
2000
+ # Basic compliance sync (no evidence)
2001
+ regscale aws sync_config_compliance --regscale_id 123 --framework NIST800-53R5
2002
+
2003
+ # Filter by account ID
2004
+ regscale aws sync_config_compliance --regscale_id 123 --account-id 123456789012
2005
+
2006
+ # Filter by tags
2007
+ regscale aws sync_config_compliance --regscale_id 123 --tags Environment=Production,Owner=Security
2008
+
2009
+ # Combine account and tag filtering
2010
+ regscale aws sync_config_compliance --regscale_id 123 --account-id 123456789012 \\
2011
+ --tags Environment=Production
2012
+
2013
+ # With evidence as SSP attachments (default)
2014
+ regscale aws sync_config_compliance --regscale_id 123 --collect-evidence
2015
+
2016
+ # With evidence as individual records (Audit Manager style)
2017
+ regscale aws sync_config_compliance --regscale_id 123 --collect-evidence --evidence-as-records
2018
+
2019
+ # Evidence for specific controls only
2020
+ regscale aws sync_config_compliance --regscale_id 123 --collect-evidence \\
2021
+ --evidence-control-ids AC-2,AU-3,SI-2
2022
+
2023
+ # With Security Hub integration
2024
+ regscale aws sync_config_compliance --regscale_id 123 --use-security-hub
2025
+ """
2026
+ try:
2027
+ # Extract parameters from kwargs
2028
+ region = kwargs["region"]
2029
+ regscale_id = kwargs["regscale_id"]
2030
+ framework = kwargs["framework"]
2031
+ session_name = kwargs.get("session_name")
2032
+ profile = kwargs.get("profile")
2033
+ aws_access_key_id = kwargs.get("aws_access_key_id")
2034
+ aws_secret_access_key = kwargs.get("aws_secret_access_key")
2035
+ aws_session_token = kwargs.get("aws_session_token")
2036
+ account_id = kwargs.get("account_id")
2037
+ tags = kwargs.get("tags")
2038
+ conformance_pack_name = kwargs.get("conformance_pack_name")
2039
+ create_issues = kwargs.get("create_issues", True)
2040
+ update_control_status = kwargs.get("update_control_status", True)
2041
+ create_poams = kwargs.get("create_poams", False)
2042
+ collect_evidence = kwargs.get("collect_evidence", False)
2043
+ evidence_as_attachments = kwargs.get("evidence_as_attachments", True)
2044
+ evidence_as_records = kwargs.get("evidence_as_records", False)
2045
+ evidence_control_ids = kwargs.get("evidence_control_ids")
2046
+ evidence_frequency = kwargs.get("evidence_frequency", 30)
2047
+ use_security_hub = kwargs.get("use_security_hub", False)
2048
+ force_refresh = kwargs.get("force_refresh", False)
2049
+
2050
+ # Parse evidence control IDs
2051
+ evidence_control_list = None
2052
+ if evidence_control_ids:
2053
+ evidence_control_list = [cid.strip() for cid in evidence_control_ids.split(",")]
2054
+
2055
+ # Parse tags
2056
+ parsed_tags = parse_tags(tags) if tags else None
2057
+
2058
+ # Resolve AWS credentials
2059
+ resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
2060
+ session_name,
2061
+ profile,
2062
+ aws_access_key_id,
2063
+ aws_secret_access_key,
2064
+ aws_session_token,
2065
+ region,
2066
+ )
2067
+
2068
+ # Log credential resolution results
2069
+ logger.info(
2070
+ f"Using AWS credentials - profile: {resolved_profile if resolved_profile else 'not set'}, "
2071
+ f"explicit credentials: {'yes' if access_key else 'no'}, region: {resolved_region}"
2072
+ )
2073
+
2074
+ # Log filtering information
2075
+ if account_id:
2076
+ logger.info(f"Filtering Config rules by account ID: {account_id}")
2077
+ if parsed_tags:
2078
+ logger.info(f"Filtering Config rules by tags: {parsed_tags}")
2079
+
2080
+ # Log evidence collection request
2081
+ if collect_evidence:
2082
+ if evidence_as_records:
2083
+ logger.info(EVIDENCE_MODE_INDIVIDUAL_RECORDS)
2084
+ else:
2085
+ logger.info(EVIDENCE_MODE_SSP_ATTACHMENTS)
2086
+
2087
+ if evidence_control_list:
2088
+ logger.info(f"Evidence collection requested for controls: {evidence_control_list}")
2089
+
2090
+ # Import and create scanner
2091
+ from .config_compliance import AWSConfigCompliance
2092
+
2093
+ logger.info("Starting AWS Config compliance sync to RegScale...")
2094
+
2095
+ scanner = AWSConfigCompliance(
2096
+ plan_id=regscale_id,
2097
+ region=resolved_region,
2098
+ profile=resolved_profile,
2099
+ aws_access_key_id=access_key,
2100
+ aws_secret_access_key=secret_key,
2101
+ aws_session_token=session_token,
2102
+ account_id=account_id,
2103
+ tags=parsed_tags,
2104
+ framework=framework,
2105
+ conformance_pack_name=conformance_pack_name,
2106
+ create_issues=create_issues,
2107
+ update_control_status=update_control_status,
2108
+ create_poams=create_poams,
2109
+ collect_evidence=collect_evidence,
2110
+ evidence_as_attachments=evidence_as_attachments,
2111
+ evidence_as_records=evidence_as_records,
2112
+ evidence_control_ids=evidence_control_list,
2113
+ evidence_frequency=evidence_frequency,
2114
+ use_security_hub=use_security_hub,
2115
+ force_refresh=force_refresh,
2116
+ )
2117
+
2118
+ scanner.sync_compliance()
2119
+
2120
+ logger.info("AWS Config compliance sync completed successfully")
2121
+
2122
+ except Exception as e:
2123
+ logger.error(f"Error syncing AWS Config compliance: {e}", exc_info=True)
2124
+ raise click.ClickException(str(e))
2125
+
2126
+
2127
+ @awsv2.command(name="sync_kms")
2128
+ @click.option(
2129
+ "--region",
2130
+ type=str,
2131
+ default=os.environ.get("AWS_REGION", "us-east-1"),
2132
+ help="AWS region to collect KMS key data from",
2133
+ )
2134
+ @click.option(
2135
+ "--regscale_id",
2136
+ "--id",
2137
+ type=click.INT,
2138
+ help="RegScale SSP ID to create evidence and compliance assessments under.",
2139
+ required=True,
2140
+ )
2141
+ @click.option(
2142
+ "--session-name",
2143
+ type=str,
2144
+ required=False,
2145
+ help="Name of cached AWS session to use (from 'regscale aws auth login')",
2146
+ )
2147
+ @click.option(
2148
+ "--profile",
2149
+ type=str,
2150
+ required=False,
2151
+ help="AWS profile name from ~/.aws/credentials",
2152
+ envvar="AWS_PROFILE",
2153
+ )
2154
+ @click.option(
2155
+ "--aws_access_key_id",
2156
+ type=str,
2157
+ required=False,
2158
+ help="AWS access key ID (overrides profile)",
2159
+ envvar="AWS_ACCESS_KEY_ID",
2160
+ )
2161
+ @click.option(
2162
+ "--aws_secret_access_key",
2163
+ type=str,
2164
+ required=False,
2165
+ help="AWS secret access key (overrides profile)",
2166
+ default=os.getenv("AWS_SECRET_ACCESS_KEY"),
2167
+ )
2168
+ @click.option(
2169
+ "--aws_session_token",
2170
+ type=click.STRING,
2171
+ required=False,
2172
+ help="AWS session token (overrides profile)",
2173
+ default=os.environ.get("AWS_SESSION_TOKEN"),
2174
+ )
2175
+ @click.option(
2176
+ "--account-id",
2177
+ type=str,
2178
+ required=False,
2179
+ help="Filter KMS keys by AWS account ID (extracted from key ARN)",
2180
+ )
2181
+ @click.option(
2182
+ "--tags",
2183
+ type=str,
2184
+ required=False,
2185
+ help="Filter KMS keys by tags (format: key1=value1,key2=value2). All specified tags must match (AND logic).",
2186
+ )
2187
+ @click.option(
2188
+ "--framework",
2189
+ type=click.Choice(["NIST800-53R5", "ISO27001"], case_sensitive=False),
2190
+ default="NIST800-53R5",
2191
+ help="Compliance framework for KMS key assessments (default: NIST800-53R5)",
2192
+ )
2193
+ @click.option(
2194
+ "--create-issues/--no-create-issues",
2195
+ default=True,
2196
+ help="Create issues for non-compliant KMS keys (default: True)",
2197
+ )
2198
+ @click.option(
2199
+ "--update-control-status/--no-update-control-status",
2200
+ default=True,
2201
+ help="Update control implementation status based on KMS compliance results (default: True)",
2202
+ )
2203
+ @click.option(
2204
+ "--create-poams",
2205
+ "-cp",
2206
+ is_flag=True,
2207
+ default=False,
2208
+ help="Mark created issues as POAMs (default: False)",
2209
+ )
2210
+ @click.option(
2211
+ "--collect-evidence/--no-collect-evidence",
2212
+ default=False,
2213
+ help="Collect and store KMS key evidence artifacts (default: False)",
2214
+ )
2215
+ @click.option(
2216
+ "--evidence-as-attachments/--evidence-as-records",
2217
+ "evidence_as_attachments",
2218
+ default=True,
2219
+ help="Attach evidence files to SSP (default) vs create individual Evidence records",
2220
+ )
2221
+ @click.option(
2222
+ "--evidence-control-ids",
2223
+ type=str,
2224
+ required=False,
2225
+ help="Comma-separated list of control IDs to collect evidence for (e.g., 'SC-12,SC-13,SC-28')",
2226
+ )
2227
+ @click.option(
2228
+ "--evidence-frequency",
2229
+ type=int,
2230
+ default=30,
2231
+ help="Evidence update frequency in days (default: 30)",
2232
+ )
2233
+ @click.option(
2234
+ "--force-refresh",
2235
+ "-f",
2236
+ is_flag=True,
2237
+ default=False,
2238
+ help="Force refresh KMS data by bypassing cache (cache TTL: 4 hours)",
2239
+ )
2240
+ @click.pass_context
2241
+ def sync_kms(ctx, **kwargs) -> None:
2242
+ """
2243
+ Sync AWS KMS encryption key data to RegScale for compliance evidence and control assessments.
2244
+
2245
+ This command collects AWS KMS key metadata, rotation status, and policies, then:
2246
+ - Assesses each key against NIST 800-53 controls (SC-12, SC-13, SC-28)
2247
+ - Creates control assessments in RegScale with PASS/FAIL status
2248
+ - Creates issues for non-compliant keys (e.g., rotation disabled)
2249
+ - Optionally collects evidence artifacts for compliance documentation
2250
+
2251
+ KMS Compliance Checks:
2252
+ - SC-12 (Key Management): Key rotation enabled, proper lifecycle management
2253
+ - SC-13 (Cryptographic Protection): FIPS-validated algorithms, approved key specs
2254
+ - SC-28 (Data at Rest): Keys enabled and available for encryption
2255
+
2256
+ Filtering Options:
2257
+ Use --account-id to filter keys by AWS account (extracted from key ARN).
2258
+ Use --tags to filter keys by tags (format: key1=value1,key2=value2).
2259
+ Both filters use AND logic - all criteria must match.
2260
+
2261
+ Evidence Collection:
2262
+ Use --collect-evidence to create evidence artifacts for compliance documentation.
2263
+ By default, evidence is attached directly to the SSP as JSONL.GZ files.
2264
+ Use --evidence-as-records to create individual Evidence records instead.
2265
+ Optionally filter evidence by control IDs with --evidence-control-ids.
2266
+
2267
+ Caching:
2268
+ KMS data is cached for 4 hours to reduce API calls. Use --force-refresh to bypass cache.
2269
+
2270
+ Authentication methods (in priority order):
2271
+ 1. Cached session: --session-name (from 'regscale aws auth login')
2272
+ 2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
2273
+ 3. AWS profile: --profile
2274
+ 4. Environment variables or default AWS credential chain
2275
+
2276
+ Examples:
2277
+ # Basic compliance sync with evidence collection
2278
+ regscale aws sync_kms --regscale_id 123 --collect-evidence
2279
+
2280
+ # Filter by account and tags
2281
+ regscale aws sync_kms --regscale_id 123 --account-id 123456789012 \\
2282
+ --tags Environment=prod,Team=security
2283
+
2284
+ # Create individual evidence records (not SSP attachments)
2285
+ regscale aws sync_kms --regscale_id 123 --collect-evidence \\
2286
+ --evidence-as-records
2287
+
2288
+ # Force refresh to bypass cache
2289
+ regscale aws sync_kms --regscale_id 123 --force-refresh
2290
+ """
2291
+ try:
2292
+ # Extract parameters from kwargs
2293
+ region = kwargs["region"]
2294
+ regscale_id = kwargs["regscale_id"]
2295
+ session_name = kwargs.get("session_name")
2296
+ profile = kwargs.get("profile")
2297
+ aws_access_key_id = kwargs.get("aws_access_key_id")
2298
+ aws_secret_access_key = kwargs.get("aws_secret_access_key")
2299
+ aws_session_token = kwargs.get("aws_session_token")
2300
+ account_id = kwargs.get("account_id")
2301
+ tags = kwargs.get("tags")
2302
+ framework = kwargs.get("framework", "NIST800-53R5")
2303
+ create_issues = kwargs.get("create_issues", True)
2304
+ update_control_status = kwargs.get("update_control_status", True)
2305
+ create_poams = kwargs.get("create_poams", False)
2306
+ collect_evidence = kwargs.get("collect_evidence", False)
2307
+ evidence_as_attachments = kwargs.get("evidence_as_attachments", True)
2308
+ evidence_control_ids = kwargs.get("evidence_control_ids")
2309
+ evidence_frequency = kwargs.get("evidence_frequency", 30)
2310
+ force_refresh = kwargs.get("force_refresh", False)
2311
+
2312
+ logger.info("Starting AWS KMS compliance sync to RegScale...")
2313
+
2314
+ # Resolve AWS credentials
2315
+ resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
2316
+ session_name,
2317
+ profile,
2318
+ aws_access_key_id,
2319
+ aws_secret_access_key,
2320
+ aws_session_token,
2321
+ region,
2322
+ )
2323
+
2324
+ # Log credential resolution results
2325
+ logger.info(
2326
+ f"Using AWS credentials - profile: {resolved_profile if resolved_profile else 'not set'}, "
2327
+ f"explicit credentials: {'yes' if access_key else 'no'}, region: {resolved_region}"
2328
+ )
2329
+
2330
+ # Parse tags
2331
+ parsed_tags = parse_tags(tags)
2332
+
2333
+ # Log filtering information
2334
+ if account_id:
2335
+ logger.info(f"Filtering KMS keys by account ID: {account_id}")
2336
+ if parsed_tags:
2337
+ logger.info(f"Filtering KMS keys by tags: {parsed_tags}")
2338
+
2339
+ # Parse evidence control IDs
2340
+ evidence_control_list = None
2341
+ if evidence_control_ids:
2342
+ evidence_control_list = [cid.strip() for cid in evidence_control_ids.split(",")]
2343
+ logger.info(f"Evidence collection requested for controls: {evidence_control_list}")
2344
+
2345
+ # Log evidence collection mode
2346
+ if collect_evidence:
2347
+ if evidence_as_attachments:
2348
+ logger.info(EVIDENCE_MODE_SSP_ATTACHMENTS)
2349
+ else:
2350
+ logger.info(EVIDENCE_MODE_INDIVIDUAL_RECORDS)
2351
+
2352
+ # Import and create KMS evidence integration
2353
+ from .kms_evidence import AWSKMSEvidenceIntegration, KMSEvidenceConfig
2354
+
2355
+ # Create configuration object
2356
+ config = KMSEvidenceConfig(
2357
+ plan_id=regscale_id,
2358
+ region=resolved_region,
2359
+ framework=framework,
2360
+ create_issues=create_issues,
2361
+ update_control_status=update_control_status,
2362
+ create_poams=create_poams,
2363
+ collect_evidence=collect_evidence,
2364
+ evidence_as_attachments=evidence_as_attachments,
2365
+ evidence_control_ids=evidence_control_list,
2366
+ evidence_frequency=evidence_frequency,
2367
+ force_refresh=force_refresh,
2368
+ account_id=account_id,
2369
+ tags=parsed_tags,
2370
+ profile=resolved_profile,
2371
+ aws_access_key_id=access_key,
2372
+ aws_secret_access_key=secret_key,
2373
+ aws_session_token=session_token,
2374
+ )
2375
+
2376
+ scanner = AWSKMSEvidenceIntegration(config)
2377
+
2378
+ scanner.sync_compliance()
2379
+
2380
+ logger.info("AWS KMS compliance sync completed successfully")
2381
+
2382
+ except Exception as e:
2383
+ logger.error(f"Error syncing AWS KMS compliance: {e}", exc_info=True)
2384
+ raise click.ClickException(str(e))
2385
+
2386
+
2387
+ @awsv2.command(name="sync_org")
2388
+ @click.option(
2389
+ "--region",
2390
+ type=str,
2391
+ default=os.environ.get("AWS_REGION", "us-east-1"),
2392
+ help="AWS region for Organizations API access",
2393
+ )
2394
+ @click.option(
2395
+ "--regscale_id",
2396
+ "--id",
2397
+ type=click.INT,
2398
+ help="RegScale SSP ID to create evidence and compliance assessments under.",
2399
+ required=True,
2400
+ )
2401
+ @click.option(
2402
+ "--session-name",
2403
+ type=str,
2404
+ required=False,
2405
+ help="Name of cached AWS session to use (from 'regscale aws auth login')",
2406
+ )
2407
+ @click.option(
2408
+ "--profile",
2409
+ type=str,
2410
+ required=False,
2411
+ help="AWS profile name from ~/.aws/credentials",
2412
+ envvar="AWS_PROFILE",
2413
+ )
2414
+ @click.option(
2415
+ "--aws_access_key_id",
2416
+ type=str,
2417
+ required=False,
2418
+ help="AWS access key ID (overrides profile)",
2419
+ envvar="AWS_ACCESS_KEY_ID",
2420
+ )
2421
+ @click.option(
2422
+ "--aws_secret_access_key",
2423
+ type=str,
2424
+ required=False,
2425
+ help="AWS secret access key (overrides profile)",
2426
+ default=os.getenv("AWS_SECRET_ACCESS_KEY"),
2427
+ )
2428
+ @click.option(
2429
+ "--aws_session_token",
2430
+ type=click.STRING,
2431
+ required=False,
2432
+ help="AWS session token (overrides profile)",
2433
+ default=os.environ.get("AWS_SESSION_TOKEN"),
2434
+ )
2435
+ @click.option(
2436
+ "--framework",
2437
+ type=click.Choice(["NIST800-53R5", "ISO27001"], case_sensitive=False),
2438
+ default="NIST800-53R5",
2439
+ help="Compliance framework for Organizations assessments (default: NIST800-53R5)",
2440
+ )
2441
+ @click.option(
2442
+ "--create-issues/--no-create-issues",
2443
+ default=True,
2444
+ help="Create issues for non-compliant organization (default: True)",
2445
+ )
2446
+ @click.option(
2447
+ "--update-control-status/--no-update-control-status",
2448
+ default=True,
2449
+ help="Update control implementation status based on Organizations compliance (default: True)",
2450
+ )
2451
+ @click.option(
2452
+ "--create-poams",
2453
+ "-cp",
2454
+ is_flag=True,
2455
+ default=False,
2456
+ help="Mark created issues as POAMs (default: False)",
2457
+ )
2458
+ @click.option(
2459
+ "--collect-evidence/--no-collect-evidence",
2460
+ default=False,
2461
+ help="Collect and store Organizations evidence artifacts (default: False)",
2462
+ )
2463
+ @click.option(
2464
+ "--evidence-as-attachments/--evidence-as-records",
2465
+ "evidence_as_attachments",
2466
+ default=True,
2467
+ help="Attach evidence files to SSP (default) vs create individual Evidence records",
2468
+ )
2469
+ @click.option(
2470
+ "--evidence-control-ids",
2471
+ type=str,
2472
+ required=False,
2473
+ help="Comma-separated list of control IDs to collect evidence for (e.g., 'AC-1,PM-9,AC-2')",
2474
+ )
2475
+ @click.option(
2476
+ "--evidence-frequency",
2477
+ type=int,
2478
+ default=30,
2479
+ help="Evidence update frequency in days (default: 30)",
2480
+ )
2481
+ @click.option(
2482
+ "--force-refresh",
2483
+ "-f",
2484
+ is_flag=True,
2485
+ default=False,
2486
+ help="Force refresh Organizations data by bypassing cache (cache TTL: 4 hours)",
2487
+ )
2488
+ @click.pass_context
2489
+ def sync_org(ctx, **kwargs) -> None:
2490
+ """
2491
+ Sync AWS Organizations data to RegScale for governance and compliance evidence.
2492
+
2493
+ This command collects AWS Organizations structure, accounts, OUs, and SCPs, then:
2494
+ - Assesses organization against NIST 800-53 controls (AC-1, PM-9, AC-2, AC-6)
2495
+ - Creates control assessments in RegScale with PASS/FAIL status
2496
+ - Creates issues for non-compliant configurations
2497
+ - Optionally collects evidence artifacts for compliance documentation
2498
+
2499
+ Organizations Compliance Checks:
2500
+ - AC-1 (Access Control Policy): SCP enforcement, OU structure
2501
+ - PM-9 (Risk Management): Environment separation, restrictive SCPs
2502
+ - AC-2 (Account Management): Account status, contact information
2503
+ - AC-6 (Least Privilege): Restrictive SCPs, deny policies
2504
+
2505
+ Evidence Collection:
2506
+ Use --collect-evidence to create evidence artifacts for compliance documentation.
2507
+ By default, evidence is attached directly to the SSP as JSONL.GZ files.
2508
+ Use --evidence-as-records to create individual Evidence records instead.
2509
+
2510
+ Caching:
2511
+ Organizations data is cached for 4 hours. Use --force-refresh to bypass cache.
2512
+
2513
+ Authentication:
2514
+ 1. Cached session: --session-name (from 'regscale aws auth login')
2515
+ 2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
2516
+ 3. AWS profile: --profile
2517
+ 4. Environment variables or default AWS credential chain
2518
+
2519
+ Examples:
2520
+ # Basic compliance sync with evidence
2521
+ regscale aws sync_org --regscale_id 123 --collect-evidence
2522
+
2523
+ # Create individual evidence records
2524
+ regscale aws sync_org --regscale_id 123 --collect-evidence --evidence-as-records
2525
+
2526
+ # Force refresh with specific controls
2527
+ regscale aws sync_org --regscale_id 123 --collect-evidence \\
2528
+ --evidence-control-ids AC-1,PM-9 --force-refresh
2529
+ """
2530
+ try:
2531
+ # Extract parameters from kwargs
2532
+ region = kwargs["region"]
2533
+ regscale_id = kwargs["regscale_id"]
2534
+ session_name = kwargs.get("session_name")
2535
+ profile = kwargs.get("profile")
2536
+ aws_access_key_id = kwargs.get("aws_access_key_id")
2537
+ aws_secret_access_key = kwargs.get("aws_secret_access_key")
2538
+ aws_session_token = kwargs.get("aws_session_token")
2539
+ framework = kwargs.get("framework", "NIST800-53R5")
2540
+ create_issues = kwargs.get("create_issues", True)
2541
+ update_control_status = kwargs.get("update_control_status", True)
2542
+ create_poams = kwargs.get("create_poams", False)
2543
+ collect_evidence = kwargs.get("collect_evidence", False)
2544
+ evidence_as_attachments = kwargs.get("evidence_as_attachments", True)
2545
+ evidence_control_ids = kwargs.get("evidence_control_ids")
2546
+ evidence_frequency = kwargs.get("evidence_frequency", 30)
2547
+ force_refresh = kwargs.get("force_refresh", False)
2548
+
2549
+ logger.info("Starting AWS Organizations compliance sync to RegScale...")
2550
+
2551
+ # Resolve credentials
2552
+ resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
2553
+ session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
2554
+ )
2555
+
2556
+ logger.info(
2557
+ f"Using AWS credentials - profile: {resolved_profile if resolved_profile else 'not set'}, "
2558
+ f"explicit credentials: {'yes' if access_key else 'no'}, region: {resolved_region}"
2559
+ )
2560
+
2561
+ # Parse evidence control IDs
2562
+ evidence_control_list = None
2563
+ if evidence_control_ids:
2564
+ evidence_control_list = [cid.strip() for cid in evidence_control_ids.split(",")]
2565
+ logger.info(f"Evidence collection requested for controls: {evidence_control_list}")
2566
+
2567
+ # Log evidence collection mode
2568
+ if collect_evidence:
2569
+ if evidence_as_attachments:
2570
+ logger.info(EVIDENCE_MODE_SSP_ATTACHMENTS)
2571
+ else:
2572
+ logger.info(EVIDENCE_MODE_INDIVIDUAL_RECORDS)
2573
+
2574
+ # Import and create Organizations evidence integration
2575
+ from .org_evidence import AWSOrganizationsEvidenceIntegration
2576
+
2577
+ scanner = AWSOrganizationsEvidenceIntegration(
2578
+ plan_id=regscale_id,
2579
+ region=resolved_region,
2580
+ framework=framework,
2581
+ create_issues=create_issues,
2582
+ update_control_status=update_control_status,
2583
+ create_poams=create_poams,
2584
+ collect_evidence=collect_evidence,
2585
+ evidence_as_attachments=evidence_as_attachments,
2586
+ evidence_control_ids=evidence_control_list,
2587
+ evidence_frequency=evidence_frequency,
2588
+ force_refresh=force_refresh,
2589
+ profile=resolved_profile,
2590
+ aws_access_key_id=access_key,
2591
+ aws_secret_access_key=secret_key,
2592
+ aws_session_token=session_token,
2593
+ )
2594
+
2595
+ scanner.sync_compliance()
2596
+
2597
+ logger.info("AWS Organizations compliance sync completed successfully")
2598
+
2599
+ except Exception as e:
2600
+ logger.error(f"Error syncing AWS Organizations compliance: {e}", exc_info=True)
2601
+ raise click.ClickException(str(e))
2602
+
2603
+
2604
+ @awsv2.command(name="sync_iam")
2605
+ @click.option(
2606
+ "--region",
2607
+ type=str,
2608
+ default=os.environ.get("AWS_REGION", "us-east-1"),
2609
+ help="AWS region for IAM API access",
2610
+ )
2611
+ @click.option(
2612
+ "--regscale_id",
2613
+ "--id",
2614
+ type=click.INT,
2615
+ help="RegScale SSP ID to create evidence and compliance assessments under.",
2616
+ required=True,
2617
+ )
2618
+ @click.option(
2619
+ "--session-name",
2620
+ type=str,
2621
+ required=False,
2622
+ help="Name of cached AWS session to use (from 'regscale aws auth login')",
2623
+ )
2624
+ @click.option(
2625
+ "--profile",
2626
+ type=str,
2627
+ required=False,
2628
+ help="AWS profile name from ~/.aws/credentials",
2629
+ envvar="AWS_PROFILE",
2630
+ )
2631
+ @click.option(
2632
+ "--aws_access_key_id",
2633
+ type=str,
2634
+ required=False,
2635
+ help="AWS access key ID (overrides profile)",
2636
+ envvar="AWS_ACCESS_KEY_ID",
2637
+ )
2638
+ @click.option(
2639
+ "--aws_secret_access_key",
2640
+ type=str,
2641
+ required=False,
2642
+ help="AWS secret access key (overrides profile)",
2643
+ default=os.getenv("AWS_SECRET_ACCESS_KEY"),
2644
+ )
2645
+ @click.option(
2646
+ "--aws_session_token",
2647
+ type=click.STRING,
2648
+ required=False,
2649
+ help="AWS session token (overrides profile)",
2650
+ default=os.environ.get("AWS_SESSION_TOKEN"),
2651
+ )
2652
+ @click.option(
2653
+ "--framework",
2654
+ type=click.Choice(["NIST800-53R5", "ISO27001"], case_sensitive=False),
2655
+ default="NIST800-53R5",
2656
+ help="Compliance framework for IAM assessments (default: NIST800-53R5)",
2657
+ )
2658
+ @click.option(
2659
+ "--create-issues/--no-create-issues",
2660
+ default=True,
2661
+ help="Create issues for non-compliant IAM configurations (default: True)",
2662
+ )
2663
+ @click.option(
2664
+ "--update-control-status/--no-update-control-status",
2665
+ default=True,
2666
+ help="Update control implementation status based on IAM compliance (default: True)",
2667
+ )
2668
+ @click.option(
2669
+ "--create-poams",
2670
+ "-cp",
2671
+ is_flag=True,
2672
+ default=False,
2673
+ help="Mark created issues as POAMs (default: False)",
2674
+ )
2675
+ @click.option(
2676
+ "--collect-evidence/--no-collect-evidence",
2677
+ default=False,
2678
+ help="Collect and store IAM evidence artifacts (default: False)",
2679
+ )
2680
+ @click.option(
2681
+ "--evidence-as-attachments/--evidence-as-records",
2682
+ "evidence_as_attachments",
2683
+ default=True,
2684
+ help="Attach evidence files to SSP (default) vs create individual Evidence records",
2685
+ )
2686
+ @click.option(
2687
+ "--evidence-control-ids",
2688
+ type=str,
2689
+ required=False,
2690
+ help="Comma-separated list of control IDs to collect evidence for (e.g., 'AC-2,IA-2,AC-6')",
2691
+ )
2692
+ @click.option(
2693
+ "--evidence-frequency",
2694
+ type=int,
2695
+ default=30,
2696
+ help="Evidence update frequency in days (default: 30)",
2697
+ )
2698
+ @click.option(
2699
+ "--force-refresh",
2700
+ "-f",
2701
+ is_flag=True,
2702
+ default=False,
2703
+ help="Force refresh IAM data by bypassing cache (cache TTL: 4 hours)",
2704
+ )
2705
+ @click.pass_context
2706
+ def sync_iam(ctx, **kwargs) -> None:
2707
+ """
2708
+ Sync AWS IAM data to RegScale for access control and authentication evidence.
2709
+
2710
+ This command collects AWS IAM users, groups, roles, and policies, then:
2711
+ - Assesses IAM against NIST 800-53 controls (AC-2, AC-6, IA-2, IA-5, AC-3)
2712
+ - Creates control assessments in RegScale with PASS/FAIL status
2713
+ - Creates issues for non-compliant configurations
2714
+ - Optionally collects evidence artifacts for compliance documentation
2715
+
2716
+ IAM Compliance Checks:
2717
+ - AC-2 (Account Management): MFA enforcement, root account security
2718
+ - AC-6 (Least Privilege): No users with AdministratorAccess
2719
+ - IA-2 (Authentication): Strong password policy, MFA required
2720
+ - IA-5 (Authenticator Management): Access key rotation, unused credentials
2721
+ - AC-3 (Access Enforcement): Restrictive role trust policies
2722
+
2723
+ Evidence Collection:
2724
+ Use --collect-evidence to create evidence artifacts for compliance documentation.
2725
+ By default, evidence is attached directly to the SSP as JSONL.GZ files.
2726
+ Use --evidence-as-records to create individual Evidence records instead.
2727
+
2728
+ Caching:
2729
+ IAM data is cached for 4 hours. Use --force-refresh to bypass cache.
2730
+
2731
+ Authentication:
2732
+ 1. Cached session: --session-name (from 'regscale aws auth login')
2733
+ 2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
2734
+ 3. AWS profile: --profile
2735
+ 4. Environment variables or default AWS credential chain
2736
+
2737
+ Examples:
2738
+ # Basic compliance sync with evidence
2739
+ regscale aws sync_iam --regscale_id 123 --collect-evidence
2740
+
2741
+ # Create individual evidence records
2742
+ regscale aws sync_iam --regscale_id 123 --collect-evidence --evidence-as-records
2743
+
2744
+ # Force refresh with specific controls
2745
+ regscale aws sync_iam --regscale_id 123 --collect-evidence \\
2746
+ --evidence-control-ids AC-2,IA-2,AC-6 --force-refresh
2747
+ """
2748
+ try:
2749
+ # Extract parameters from kwargs
2750
+ region = kwargs["region"]
2751
+ regscale_id = kwargs["regscale_id"]
2752
+ session_name = kwargs.get("session_name")
2753
+ profile = kwargs.get("profile")
2754
+ aws_access_key_id = kwargs.get("aws_access_key_id")
2755
+ aws_secret_access_key = kwargs.get("aws_secret_access_key")
2756
+ aws_session_token = kwargs.get("aws_session_token")
2757
+ framework = kwargs.get("framework", "NIST800-53R5")
2758
+ create_issues = kwargs.get("create_issues", True)
2759
+ update_control_status = kwargs.get("update_control_status", True)
2760
+ create_poams = kwargs.get("create_poams", False)
2761
+ collect_evidence = kwargs.get("collect_evidence", False)
2762
+ evidence_as_attachments = kwargs.get("evidence_as_attachments", True)
2763
+ evidence_control_ids = kwargs.get("evidence_control_ids")
2764
+ evidence_frequency = kwargs.get("evidence_frequency", 30)
2765
+ force_refresh = kwargs.get("force_refresh", False)
2766
+
2767
+ logger.info("Starting AWS IAM compliance sync to RegScale...")
2768
+
2769
+ # Resolve credentials
2770
+ resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
2771
+ session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
2772
+ )
2773
+
2774
+ logger.info(
2775
+ f"Using AWS credentials - profile: {resolved_profile if resolved_profile else 'not set'}, "
2776
+ f"explicit credentials: {'yes' if access_key else 'no'}, region: {resolved_region}"
2777
+ )
2778
+
2779
+ # Parse evidence control IDs
2780
+ evidence_control_list = None
2781
+ if evidence_control_ids:
2782
+ evidence_control_list = [cid.strip() for cid in evidence_control_ids.split(",")]
2783
+ logger.info(f"Evidence collection requested for controls: {evidence_control_list}")
2784
+
2785
+ # Log evidence collection mode
2786
+ if collect_evidence:
2787
+ if evidence_as_attachments:
2788
+ logger.info(EVIDENCE_MODE_SSP_ATTACHMENTS)
2789
+ else:
2790
+ logger.info(EVIDENCE_MODE_INDIVIDUAL_RECORDS)
2791
+
2792
+ # Import and create IAM evidence integration
2793
+ from .iam_evidence import AWSIAMEvidenceIntegration
2794
+
2795
+ scanner = AWSIAMEvidenceIntegration(
2796
+ plan_id=regscale_id,
2797
+ region=resolved_region,
2798
+ framework=framework,
2799
+ create_issues=create_issues,
2800
+ update_control_status=update_control_status,
2801
+ create_poams=create_poams,
2802
+ collect_evidence=collect_evidence,
2803
+ evidence_as_attachments=evidence_as_attachments,
2804
+ evidence_control_ids=evidence_control_list,
2805
+ evidence_frequency=evidence_frequency,
2806
+ force_refresh=force_refresh,
2807
+ profile=resolved_profile,
2808
+ aws_access_key_id=access_key,
2809
+ aws_secret_access_key=secret_key,
2810
+ aws_session_token=session_token,
2811
+ )
2812
+
2813
+ scanner.sync_compliance()
2814
+
2815
+ logger.info("AWS IAM compliance sync completed successfully")
2816
+
2817
+ except Exception as e:
2818
+ logger.error(f"Error syncing AWS IAM compliance: {e}", exc_info=True)
2819
+ raise click.ClickException(str(e))
2820
+
2821
+
2822
+ @awsv2.command(name="sync_guardduty")
2823
+ @click.option(
2824
+ "--region",
2825
+ type=str,
2826
+ default=os.environ.get("AWS_REGION", "us-east-1"),
2827
+ help="AWS region for GuardDuty API access",
2828
+ )
2829
+ @click.option(
2830
+ "--regscale_id",
2831
+ "--id",
2832
+ type=click.INT,
2833
+ help="RegScale SSP ID to create findings and evidence under.",
2834
+ required=True,
2835
+ )
2836
+ @click.option(
2837
+ "--session-name",
2838
+ type=str,
2839
+ required=False,
2840
+ help="Name of cached AWS session to use (from 'regscale aws auth login')",
2841
+ )
2842
+ @click.option(
2843
+ "--profile",
2844
+ type=str,
2845
+ required=False,
2846
+ help="AWS profile name from ~/.aws/credentials",
2847
+ envvar="AWS_PROFILE",
2848
+ )
2849
+ @click.option(
2850
+ "--aws_access_key_id",
2851
+ type=str,
2852
+ required=False,
2853
+ help="AWS access key ID (overrides profile)",
2854
+ envvar="AWS_ACCESS_KEY_ID",
2855
+ )
2856
+ @click.option(
2857
+ "--aws_secret_access_key",
2858
+ type=str,
2859
+ required=False,
2860
+ help="AWS secret access key (overrides profile)",
2861
+ default=os.getenv("AWS_SECRET_ACCESS_KEY"),
2862
+ )
2863
+ @click.option(
2864
+ "--aws_session_token",
2865
+ type=click.STRING,
2866
+ required=False,
2867
+ help="AWS session token (overrides profile)",
2868
+ default=os.environ.get("AWS_SESSION_TOKEN"),
2869
+ )
2870
+ @click.option(
2871
+ "--account-id",
2872
+ type=str,
2873
+ required=False,
2874
+ help="Filter GuardDuty findings by AWS account ID",
2875
+ )
2876
+ @click.option(
2877
+ "--tags",
2878
+ type=str,
2879
+ required=False,
2880
+ help="Filter GuardDuty detectors by tags (format: key1=value1,key2=value2)",
2881
+ )
2882
+ @click.option(
2883
+ "--framework",
2884
+ type=click.Choice(["NIST800-53R5"], case_sensitive=False),
2885
+ default="NIST800-53R5",
2886
+ help="Compliance framework for GuardDuty assessments (default: NIST800-53R5)",
2887
+ )
2888
+ @click.option(
2889
+ "--create-issues/--no-create-issues",
2890
+ default=False,
2891
+ help="Create issues for GuardDuty findings without CVEs (default: False)",
2892
+ )
2893
+ @click.option(
2894
+ "--create-vulnerabilities/--no-create-vulnerabilities",
2895
+ default=False,
2896
+ help="Create vulnerabilities for GuardDuty findings with CVEs (default: False)",
2897
+ )
2898
+ @click.option(
2899
+ "--update-control-status/--no-update-control-status",
2900
+ default=True,
2901
+ help="Update control implementation status based on findings (default: True)",
2902
+ )
2903
+ @click.option(
2904
+ "--create-poams/--no-create-poams",
2905
+ default=False,
2906
+ help="Create POA&Ms for failed controls (default: False)",
2907
+ )
2908
+ @click.option(
2909
+ "--collect-evidence/--no-collect-evidence",
2910
+ default=True,
2911
+ help="Collect and store GuardDuty evidence artifacts (default: True)",
2912
+ )
2913
+ @click.option(
2914
+ "--evidence-as-attachments/--evidence-as-records",
2915
+ "evidence_as_attachments",
2916
+ default=True,
2917
+ help="Attach evidence files to SSP (default) vs create individual Evidence records",
2918
+ )
2919
+ @click.option(
2920
+ "--evidence-control-ids",
2921
+ type=str,
2922
+ required=False,
2923
+ help="Comma-separated list of control IDs to collect evidence for (e.g., 'SI-4,IR-4,IR-5')",
2924
+ )
2925
+ @click.option(
2926
+ "--evidence-frequency",
2927
+ type=int,
2928
+ default=30,
2929
+ help="Evidence update frequency in days (default: 30)",
2930
+ )
2931
+ @click.option(
2932
+ "--force-refresh",
2933
+ "-f",
2934
+ is_flag=True,
2935
+ default=False,
2936
+ help="Force refresh GuardDuty data by bypassing cache (cache TTL: 4 hours)",
2937
+ )
2938
+ @click.pass_context
2939
+ def sync_guardduty(ctx, **kwargs) -> None:
2940
+ """
2941
+ Sync AWS GuardDuty threat detection data to RegScale as compliance evidence.
2942
+
2943
+ Collects GuardDuty detector configurations and findings for compliance assessment
2944
+ against NIST 800-53 R5 controls. By default, creates evidence attachments only
2945
+ (no issues or vulnerabilities).
2946
+
2947
+ GuardDuty Compliance Checks:
2948
+ - SI-4 (System Monitoring): Detector enabled and configured
2949
+ - IR-4 (Incident Handling): Finding detection and alerting
2950
+ - IR-5 (Incident Monitoring): Continuous threat monitoring
2951
+ - SI-3 (Malicious Code Protection): Malware detection capabilities
2952
+ - RA-5 (Vulnerability Monitoring): Threat intelligence integration
2953
+
2954
+ Examples:
2955
+ # Basic evidence collection (default)
2956
+ regscale aws sync_guardduty --regscale_id 123
2957
+
2958
+ # Collect evidence for specific controls
2959
+ regscale aws sync_guardduty --regscale_id 123 --evidence-control-ids SI-4,IR-4,IR-5
2960
+
2961
+ # Filter by AWS account
2962
+ regscale aws sync_guardduty --regscale_id 123 --account-id 123456789012
2963
+
2964
+ # Create issues/vulnerabilities from findings (non-default)
2965
+ regscale aws sync_guardduty --regscale_id 123 --create-issues --create-vulnerabilities --no-collect-evidence
2966
+ """
2967
+ try:
2968
+ # Extract parameters from kwargs
2969
+ region = kwargs["region"]
2970
+ regscale_id = kwargs["regscale_id"]
2971
+ session_name = kwargs.get("session_name")
2972
+ profile = kwargs.get("profile")
2973
+ aws_access_key_id = kwargs.get("aws_access_key_id")
2974
+ aws_secret_access_key = kwargs.get("aws_secret_access_key")
2975
+ aws_session_token = kwargs.get("aws_session_token")
2976
+ account_id = kwargs.get("account_id")
2977
+ tags = kwargs.get("tags")
2978
+ framework = kwargs.get("framework", "NIST800-53R5")
2979
+ create_issues = kwargs.get("create_issues", False)
2980
+ create_vulnerabilities = kwargs.get("create_vulnerabilities", False)
2981
+ update_control_status = kwargs.get("update_control_status", True)
2982
+ create_poams = kwargs.get("create_poams", False)
2983
+ collect_evidence = kwargs.get("collect_evidence", True)
2984
+ evidence_as_attachments = kwargs.get("evidence_as_attachments", True)
2985
+ evidence_control_ids = kwargs.get("evidence_control_ids")
2986
+ evidence_frequency = kwargs.get("evidence_frequency", 30)
2987
+ force_refresh = kwargs.get("force_refresh", False)
2988
+
2989
+ logger.info("Starting AWS GuardDuty findings sync to RegScale...")
2990
+
2991
+ resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
2992
+ session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
2993
+ )
2994
+
2995
+ logger.info(
2996
+ f"Using AWS credentials - profile: {resolved_profile if resolved_profile else 'not set'}, "
2997
+ f"explicit credentials: {'yes' if access_key else 'no'}, region: {resolved_region}"
2998
+ )
2999
+
3000
+ parsed_tags = parse_tags(tags)
3001
+
3002
+ if account_id:
3003
+ logger.info(f"Filtering GuardDuty findings by account ID: {account_id}")
3004
+ if parsed_tags:
3005
+ logger.info(f"Filtering GuardDuty detectors by tags: {parsed_tags}")
3006
+
3007
+ evidence_control_list = None
3008
+ if evidence_control_ids:
3009
+ evidence_control_list = [cid.strip() for cid in evidence_control_ids.split(",")]
3010
+ logger.info(f"Evidence collection requested for controls: {evidence_control_list}")
3011
+
3012
+ if collect_evidence:
3013
+ if evidence_as_attachments:
3014
+ logger.info(EVIDENCE_MODE_SSP_ATTACHMENTS)
3015
+ else:
3016
+ logger.info(EVIDENCE_MODE_INDIVIDUAL_RECORDS)
3017
+
3018
+ from .guardduty_evidence import AWSGuardDutyEvidenceIntegration, GuardDutyEvidenceConfig
3019
+
3020
+ # Create configuration object
3021
+ config = GuardDutyEvidenceConfig(
3022
+ plan_id=regscale_id,
3023
+ region=resolved_region,
3024
+ framework=framework,
3025
+ create_issues=create_issues,
3026
+ create_vulnerabilities=create_vulnerabilities,
3027
+ update_control_status=update_control_status,
3028
+ create_poams=create_poams,
3029
+ collect_evidence=collect_evidence,
3030
+ evidence_as_attachments=evidence_as_attachments,
3031
+ evidence_control_ids=evidence_control_list,
3032
+ evidence_frequency=evidence_frequency,
3033
+ force_refresh=force_refresh,
3034
+ account_id=account_id,
3035
+ tags=parsed_tags,
3036
+ profile=resolved_profile,
3037
+ aws_access_key_id=access_key,
3038
+ aws_secret_access_key=secret_key,
3039
+ aws_session_token=session_token,
3040
+ )
3041
+
3042
+ scanner = AWSGuardDutyEvidenceIntegration(config)
3043
+
3044
+ scanner.sync_findings()
3045
+
3046
+ logger.info("AWS GuardDuty findings sync completed successfully")
3047
+
3048
+ except Exception as e:
3049
+ logger.error(f"Error syncing AWS GuardDuty findings: {e}", exc_info=True)
3050
+ raise click.ClickException(str(e))
3051
+
3052
+
3053
+ @awsv2.command(name="sync_s3")
3054
+ @click.option("--region", default="us-east-1", help="AWS region to collect S3 buckets from")
3055
+ @click.option("--regscale-id", type=int, required=True, help="RegScale SSP plan ID")
3056
+ @click.option("--account-id", help="AWS account ID to filter resources")
3057
+ @click.option("--tags", help="Resource tags for filtering (format: key1=value1,key2=value2)")
3058
+ @click.option("--bucket-name-filter", help="Filter buckets by name prefix/pattern")
3059
+ @click.option("--create-evidence", is_flag=True, default=False, help="Create evidence records in RegScale")
3060
+ @click.option(
3061
+ "--create-ssp-attachment",
3062
+ is_flag=True,
3063
+ default=True,
3064
+ help="Create SSP attachment with evidence (default: True)",
3065
+ )
3066
+ @click.option(
3067
+ "--evidence-control-ids",
3068
+ help="Comma-separated control IDs to link evidence (e.g., SC-13,SC-28,AC-3)",
3069
+ )
3070
+ @click.option("--force-refresh", is_flag=True, default=False, help="Force refresh cached data")
3071
+ @click.option("--session-name", help="Custom session name for this operation")
3072
+ @click.option("--profile", help="AWS profile name to use for authentication")
3073
+ @click.option("--aws-access-key-id", help="AWS access key ID")
3074
+ @click.option("--aws-secret-access-key", help="AWS secret access key")
3075
+ @click.option("--aws-session-token", help="AWS session token")
3076
+ @click.pass_context
3077
+ def sync_s3(ctx, **kwargs):
3078
+ """
3079
+ Sync AWS S3 storage configurations to RegScale as compliance evidence.
3080
+
3081
+ Collects S3 bucket configurations including encryption, versioning, logging,
3082
+ access controls, and public access settings for compliance assessment against
3083
+ NIST 800-53 R5 controls (SC-13, SC-28, AC-3, AC-6, AU-2, AU-9, CP-9).
3084
+
3085
+ Examples:
3086
+
3087
+ # Sync all S3 buckets in us-east-1
3088
+ regscale aws sync_s3 --region us-east-1 --regscale-id 123
3089
+
3090
+ # Filter by bucket name prefix
3091
+ regscale aws sync_s3 --region us-east-1 --regscale-id 123 --bucket-name-filter prod-
3092
+
3093
+ # Filter by tags
3094
+ regscale aws sync_s3 --region us-east-1 --regscale-id 123 --tags Environment=Production,Compliance=Required
3095
+
3096
+ # Create evidence and link to controls
3097
+ regscale aws sync_s3 --region us-east-1 --regscale-id 123 --create-evidence \\
3098
+ --evidence-control-ids SC-13,SC-28,AC-3
3099
+
3100
+ # Use specific AWS profile
3101
+ regscale aws sync_s3 --region us-west-2 --regscale-id 456 --profile production
3102
+
3103
+ # Force refresh cached data
3104
+ regscale aws sync_s3 --region us-east-1 --regscale-id 123 --force-refresh
3105
+ """
3106
+ from regscale.integrations.commercial.aws.s3_evidence import AWSS3EvidenceIntegration
3107
+
3108
+ try:
3109
+ # Extract parameters from kwargs
3110
+ region = kwargs["region"]
3111
+ regscale_id = kwargs["regscale_id"]
3112
+ account_id = kwargs.get("account_id")
3113
+ tags = kwargs.get("tags")
3114
+ bucket_name_filter = kwargs.get("bucket_name_filter")
3115
+ create_evidence = kwargs.get("create_evidence", False)
3116
+ create_ssp_attachment = kwargs.get("create_ssp_attachment", True)
3117
+ evidence_control_ids = kwargs.get("evidence_control_ids")
3118
+ force_refresh = kwargs.get("force_refresh", False)
3119
+ session_name = kwargs.get("session_name")
3120
+ profile = kwargs.get("profile")
3121
+ aws_access_key_id = kwargs.get("aws_access_key_id")
3122
+ aws_secret_access_key = kwargs.get("aws_secret_access_key")
3123
+ aws_session_token = kwargs.get("aws_session_token")
3124
+
3125
+ logger.info("Starting AWS S3 storage configuration sync to RegScale...")
3126
+
3127
+ resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
3128
+ session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
3129
+ )
3130
+
3131
+ logger.info(
3132
+ f"Using AWS credentials - profile: {resolved_profile if resolved_profile else 'not set'}, "
3133
+ f"explicit credentials: {'yes' if access_key else 'no'}, region: {resolved_region}"
3134
+ )
3135
+
3136
+ parsed_tags = parse_tags(tags)
3137
+
3138
+ if account_id:
3139
+ logger.info(f"Filtering S3 buckets by account ID: {account_id}")
3140
+
3141
+ if parsed_tags:
3142
+ logger.info(f"Filtering S3 buckets by tags: {parsed_tags}")
3143
+
3144
+ if bucket_name_filter:
3145
+ logger.info(f"Filtering S3 buckets by name pattern: {bucket_name_filter}")
3146
+
3147
+ control_ids = None
3148
+ if evidence_control_ids:
3149
+ control_ids = [ctrl.strip() for ctrl in evidence_control_ids.split(",")]
3150
+ logger.info(f"Evidence collection requested for controls: {control_ids}")
3151
+
3152
+ if create_evidence or create_ssp_attachment:
3153
+ if create_ssp_attachment:
3154
+ logger.info(EVIDENCE_MODE_SSP_ATTACHMENTS)
3155
+ if create_evidence:
3156
+ logger.info(EVIDENCE_MODE_INDIVIDUAL_RECORDS)
3157
+
3158
+ from .s3_evidence import AWSS3EvidenceIntegration, S3EvidenceConfig
3159
+
3160
+ # Create configuration object
3161
+ config = S3EvidenceConfig(
3162
+ plan_id=regscale_id,
3163
+ region=resolved_region,
3164
+ account_id=account_id,
3165
+ tags=parsed_tags,
3166
+ bucket_name_filter=bucket_name_filter,
3167
+ create_evidence=create_evidence,
3168
+ create_ssp_attachment=create_ssp_attachment,
3169
+ evidence_control_ids=control_ids,
3170
+ force_refresh=force_refresh,
3171
+ aws_profile=resolved_profile,
3172
+ aws_access_key_id=access_key,
3173
+ aws_secret_access_key=secret_key,
3174
+ aws_session_token=session_token,
3175
+ )
3176
+
3177
+ scanner = AWSS3EvidenceIntegration(config)
3178
+
3179
+ scanner.sync_compliance_data()
3180
+
3181
+ logger.info("AWS S3 evidence sync completed successfully")
3182
+
3183
+ except Exception as e:
3184
+ logger.error(f"Error syncing AWS S3 evidence: {e}", exc_info=True)
3185
+ raise click.ClickException(str(e))
3186
+
3187
+
3188
+ @awsv2.command(name="sync_cloudtrail")
3189
+ @click.option("--region", default="us-east-1", help="AWS region to collect CloudTrail trails from")
3190
+ @click.option("--regscale-id", type=int, required=True, help="RegScale SSP plan ID")
3191
+ @click.option("--account-id", help="AWS account ID to filter resources")
3192
+ @click.option("--tags", help="Resource tags for filtering (format: key1=value1,key2=value2)")
3193
+ @click.option("--trail-name-filter", help="Filter trails by name prefix/pattern")
3194
+ @click.option("--create-evidence", is_flag=True, default=False, help="Create evidence records in RegScale")
3195
+ @click.option(
3196
+ "--create-ssp-attachment",
3197
+ is_flag=True,
3198
+ default=True,
3199
+ help="Create SSP attachment with evidence (default: True)",
3200
+ )
3201
+ @click.option(
3202
+ "--evidence-control-ids",
3203
+ help="Comma-separated control IDs to link evidence (e.g., AU-2,AU-3,AU-6)",
3204
+ )
3205
+ @click.option("--force-refresh", is_flag=True, default=False, help="Force refresh cached data")
3206
+ @click.option("--session-name", help="Custom session name for this operation")
3207
+ @click.option("--profile", help="AWS profile name to use for authentication")
3208
+ @click.option("--aws-access-key-id", help="AWS access key ID")
3209
+ @click.option("--aws-secret-access-key", help="AWS secret access key")
3210
+ @click.option("--aws-session-token", help="AWS session token")
3211
+ @click.pass_context
3212
+ def sync_cloudtrail(ctx, **kwargs):
3213
+ """
3214
+ Sync AWS CloudTrail audit logging configurations to RegScale as compliance evidence.
3215
+
3216
+ Collects CloudTrail trail configurations including logging status, multi-region settings,
3217
+ log file validation, encryption, CloudWatch Logs integration, and SNS notifications for
3218
+ compliance assessment against NIST 800-53 R5 controls (AU-2, AU-3, AU-6, AU-9, AU-11, AU-12, SI-4).
3219
+
3220
+ Examples:
3221
+
3222
+ # Sync all CloudTrail trails in us-east-1
3223
+ regscale aws sync_cloudtrail --region us-east-1 --regscale-id 123
3224
+
3225
+ # Filter by trail name prefix
3226
+ regscale aws sync_cloudtrail --region us-east-1 --regscale-id 123 --trail-name-filter prod-
3227
+
3228
+ # Filter by tags
3229
+ regscale aws sync_cloudtrail --region us-east-1 --regscale-id 123 --tags Environment=Production
3230
+
3231
+ # Create evidence and link to controls
3232
+ regscale aws sync_cloudtrail --region us-east-1 --regscale-id 123 --create-evidence \\
3233
+ --evidence-control-ids AU-2,AU-3,AU-6,AU-9
3234
+
3235
+ # Use specific AWS profile
3236
+ regscale aws sync_cloudtrail --region us-west-2 --regscale-id 456 --profile production
3237
+
3238
+ # Force refresh cached data
3239
+ regscale aws sync_cloudtrail --region us-east-1 --regscale-id 123 --force-refresh
3240
+ """
3241
+ from regscale.integrations.commercial.aws.cloudtrail_evidence import AWSCloudTrailEvidenceIntegration
3242
+
3243
+ try:
3244
+ # Extract parameters from kwargs
3245
+ region = kwargs["region"]
3246
+ regscale_id = kwargs["regscale_id"]
3247
+ account_id = kwargs.get("account_id")
3248
+ tags = kwargs.get("tags")
3249
+ trail_name_filter = kwargs.get("trail_name_filter")
3250
+ create_evidence = kwargs.get("create_evidence", False)
3251
+ create_ssp_attachment = kwargs.get("create_ssp_attachment", True)
3252
+ evidence_control_ids = kwargs.get("evidence_control_ids")
3253
+ force_refresh = kwargs.get("force_refresh", False)
3254
+ session_name = kwargs.get("session_name")
3255
+ profile = kwargs.get("profile")
3256
+ aws_access_key_id = kwargs.get("aws_access_key_id")
3257
+ aws_secret_access_key = kwargs.get("aws_secret_access_key")
3258
+ aws_session_token = kwargs.get("aws_session_token")
3259
+
3260
+ logger.info("Starting AWS CloudTrail audit logging sync to RegScale...")
3261
+
3262
+ resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
3263
+ session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
3264
+ )
3265
+
3266
+ logger.info(
3267
+ f"Using AWS credentials - profile: {resolved_profile if resolved_profile else 'not set'}, "
3268
+ f"explicit credentials: {'yes' if access_key else 'no'}, region: {resolved_region}"
3269
+ )
3270
+
3271
+ parsed_tags = parse_tags(tags)
3272
+
3273
+ if account_id:
3274
+ logger.info(f"Filtering CloudTrail trails by account ID: {account_id}")
3275
+
3276
+ if parsed_tags:
3277
+ logger.info(f"Filtering CloudTrail trails by tags: {parsed_tags}")
3278
+
3279
+ if trail_name_filter:
3280
+ logger.info(f"Filtering CloudTrail trails by name pattern: {trail_name_filter}")
3281
+
3282
+ control_ids = None
3283
+ if evidence_control_ids:
3284
+ control_ids = [ctrl.strip() for ctrl in evidence_control_ids.split(",")]
3285
+ logger.info(f"Evidence collection requested for controls: {control_ids}")
3286
+
3287
+ if create_evidence or create_ssp_attachment:
3288
+ if create_ssp_attachment:
3289
+ logger.info(EVIDENCE_MODE_SSP_ATTACHMENTS)
3290
+ if create_evidence:
3291
+ logger.info(EVIDENCE_MODE_INDIVIDUAL_RECORDS)
3292
+
3293
+ from .cloudtrail_evidence import AWSCloudTrailEvidenceIntegration, CloudTrailEvidenceConfig
3294
+
3295
+ # Create configuration object
3296
+ config = CloudTrailEvidenceConfig(
3297
+ plan_id=regscale_id,
3298
+ region=resolved_region,
3299
+ account_id=account_id,
3300
+ tags=parsed_tags,
3301
+ trail_name_filter=trail_name_filter,
3302
+ create_evidence=create_evidence,
3303
+ create_ssp_attachment=create_ssp_attachment,
3304
+ evidence_control_ids=control_ids,
3305
+ force_refresh=force_refresh,
3306
+ aws_profile=resolved_profile,
3307
+ aws_access_key_id=access_key,
3308
+ aws_secret_access_key=secret_key,
3309
+ aws_session_token=session_token,
3310
+ )
3311
+
3312
+ scanner = AWSCloudTrailEvidenceIntegration(config)
3313
+
3314
+ scanner.sync_compliance_data()
3315
+
3316
+ logger.info("AWS CloudTrail evidence sync completed successfully")
3317
+
3318
+ except Exception as e:
3319
+ logger.error(f"Error syncing AWS CloudTrail evidence: {e}", exc_info=True)
3320
+ raise click.ClickException(str(e))
3321
+
3322
+
3323
+ @awsv2.command(name="sync_ssm")
3324
+ @click.option("--region", default="us-east-1", help="AWS region to sync from")
3325
+ @click.option("--regscale-id", type=int, required=True, help="RegScale SSP ID to attach evidence")
3326
+ @click.option("--account-id", help="Optional AWS account ID to filter resources")
3327
+ @click.option("--tags", help='Optional tags to filter resources (format: "Key1=Value1,Key2=Value2")')
3328
+ @click.option("--create-evidence", is_flag=True, default=False, help="Create evidence records (default: False)")
3329
+ @click.option("--create-ssp-attachment", is_flag=True, default=True, help="Create SSP attachments (default: True)")
3330
+ @click.option("--evidence-control-ids", help='Control IDs for evidence (comma-separated, e.g., "CM-2,CM-6,SI-2")')
3331
+ @click.option("--force-refresh", is_flag=True, default=False, help="Force cache refresh")
3332
+ @click.option("--session-name", help="AWS session name for role assumption")
3333
+ @click.option("--profile", help="AWS profile name to use")
3334
+ @click.option("--aws-access-key-id", help="AWS access key ID")
3335
+ @click.option("--aws-secret-access-key", help="AWS secret access key")
3336
+ @click.option("--aws-session-token", help="AWS session token")
3337
+ @click.pass_context
3338
+ def sync_ssm(ctx, **kwargs):
3339
+ """Sync AWS Systems Manager configurations to RegScale as compliance evidence.
3340
+
3341
+ This command collects SSM configuration data including managed instances, patch baselines,
3342
+ parameters, documents, maintenance windows, and compliance status for NIST 800-53 R5
3343
+ controls (CM-2, CM-6, SI-2, CM-3, CM-8).
3344
+
3345
+ Examples:
3346
+ # Sync SSM evidence with default settings
3347
+ regscale commercial aws sync_ssm --regscale-id 123
3348
+
3349
+ # Sync with specific AWS profile
3350
+ regscale commercial aws sync_ssm --regscale-id 123 --profile prod-account
3351
+
3352
+ # Force refresh cache and filter by tags
3353
+ regscale commercial aws sync_ssm --regscale-id 123 --force-refresh --tags "Environment=Production"
3354
+
3355
+ # Sync for specific account
3356
+ regscale commercial aws sync_ssm --regscale-id 123 --account-id 123456789012
3357
+ """
3358
+ from regscale.integrations.commercial.aws.ssm_evidence import AWSSSMEvidenceIntegration
3359
+
3360
+ try:
3361
+ # Extract parameters from kwargs
3362
+ region = kwargs["region"]
3363
+ regscale_id = kwargs["regscale_id"]
3364
+ account_id = kwargs.get("account_id")
3365
+ tags = kwargs.get("tags")
3366
+ create_evidence = kwargs.get("create_evidence", False)
3367
+ create_ssp_attachment = kwargs.get("create_ssp_attachment", True)
3368
+ evidence_control_ids = kwargs.get("evidence_control_ids")
3369
+ force_refresh = kwargs.get("force_refresh", False)
3370
+ session_name = kwargs.get("session_name")
3371
+ profile = kwargs.get("profile")
3372
+ aws_access_key_id = kwargs.get("aws_access_key_id")
3373
+ aws_secret_access_key = kwargs.get("aws_secret_access_key")
3374
+ aws_session_token = kwargs.get("aws_session_token")
3375
+
3376
+ # Resolve AWS credentials
3377
+ resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
3378
+ session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
3379
+ )
3380
+
3381
+ # Parse tags if provided
3382
+ parsed_tags = parse_tags(tags)
3383
+
3384
+ # Parse control IDs if provided
3385
+ control_ids = [ctrl.strip() for ctrl in evidence_control_ids.split(",")] if evidence_control_ids else None
3386
+
3387
+ logger.info(f"Starting AWS Systems Manager evidence sync for region: {resolved_region}")
3388
+
3389
+ from .ssm_evidence import AWSSSMEvidenceIntegration, SSMEvidenceConfig
3390
+
3391
+ # Create configuration object
3392
+ config = SSMEvidenceConfig(
3393
+ plan_id=regscale_id,
3394
+ region=resolved_region,
3395
+ account_id=account_id,
3396
+ tags=parsed_tags,
3397
+ create_evidence=create_evidence,
3398
+ create_ssp_attachment=create_ssp_attachment,
3399
+ evidence_control_ids=control_ids,
3400
+ force_refresh=force_refresh,
3401
+ aws_profile=resolved_profile,
3402
+ aws_access_key_id=access_key,
3403
+ aws_secret_access_key=secret_key,
3404
+ aws_session_token=session_token,
3405
+ )
3406
+
3407
+ scanner = AWSSSMEvidenceIntegration(config)
3408
+
3409
+ scanner.sync_compliance_data()
3410
+
3411
+ logger.info("AWS Systems Manager evidence sync completed successfully")
3412
+
3413
+ except Exception as e:
3414
+ logger.error(f"Error syncing AWS Systems Manager evidence: {e}", exc_info=True)
3415
+ raise click.ClickException(str(e))
3416
+
3417
+
3418
+ @awsv2.command(name="sync_cloudwatch")
3419
+ @click.option("--region", default="us-east-1", help="AWS region to collect CloudWatch Logs from")
3420
+ @click.option("--regscale-id", type=int, required=True, help="RegScale SSP plan ID")
3421
+ @click.option("--account-id", help="AWS account ID to filter resources")
3422
+ @click.option("--tags", help="Resource tags for filtering (format: key1=value1,key2=value2)")
3423
+ @click.option("--log-group-prefix", help="Filter log groups by name prefix")
3424
+ @click.option("--create-evidence", is_flag=True, default=False, help="Create evidence records in RegScale")
3425
+ @click.option(
3426
+ "--create-ssp-attachment",
3427
+ is_flag=True,
3428
+ default=True,
3429
+ help="Create SSP attachment with evidence (default: True)",
3430
+ )
3431
+ @click.option(
3432
+ "--evidence-control-ids",
3433
+ help="Comma-separated control IDs to link evidence (e.g., AU-2,AU-3,AU-6)",
3434
+ )
3435
+ @click.option("--force-refresh", is_flag=True, default=False, help="Force refresh cached data")
3436
+ @click.option("--session-name", help="Custom session name for this operation")
3437
+ @click.option("--profile", help="AWS profile name to use for authentication")
3438
+ @click.option("--aws-access-key-id", help="AWS access key ID")
3439
+ @click.option("--aws-secret-access-key", help="AWS secret access key")
3440
+ @click.option("--aws-session-token", help="AWS session token")
3441
+ @click.pass_context
3442
+ def sync_cloudwatch(ctx, **kwargs):
3443
+ """
3444
+ Sync AWS CloudWatch Logs configurations to RegScale as compliance evidence.
3445
+
3446
+ Collects CloudWatch log group configurations including retention policies, encryption status,
3447
+ metric filters, subscription filters, and storage metrics for compliance assessment against
3448
+ NIST 800-53 R5 controls (AU-2, AU-3, AU-6, AU-9, AU-11, AU-12, SI-4).
3449
+
3450
+ Examples:
3451
+
3452
+ # Sync all CloudWatch log groups in us-east-1
3453
+ regscale aws sync_cloudwatch --region us-east-1 --regscale-id 123
3454
+
3455
+ # Filter by log group name prefix
3456
+ regscale aws sync_cloudwatch --region us-east-1 --regscale-id 123 --log-group-prefix /aws/lambda/
3457
+
3458
+ # Filter by tags
3459
+ regscale aws sync_cloudwatch --region us-east-1 --regscale-id 123 --tags Environment=Production
3460
+
3461
+ # Create evidence and link to controls
3462
+ regscale aws sync_cloudwatch --region us-east-1 --regscale-id 123 --create-evidence --evidence-control-ids AU-2,AU-3,AU-6,AU-9
3463
+
3464
+ # Use specific AWS profile
3465
+ regscale aws sync_cloudwatch --region us-west-2 --regscale-id 456 --profile production
3466
+
3467
+ # Force refresh cached data
3468
+ regscale aws sync_cloudwatch --region us-east-1 --regscale-id 123 --force-refresh
3469
+ """
3470
+ from regscale.integrations.commercial.aws.cloudwatch_evidence import AWSCloudWatchEvidenceIntegration
3471
+
3472
+ try:
3473
+ # Extract parameters from kwargs
3474
+ region = kwargs["region"]
3475
+ regscale_id = kwargs["regscale_id"]
3476
+ account_id = kwargs.get("account_id")
3477
+ tags = kwargs.get("tags")
3478
+ log_group_prefix = kwargs.get("log_group_prefix")
3479
+ create_evidence = kwargs.get("create_evidence", False)
3480
+ create_ssp_attachment = kwargs.get("create_ssp_attachment", True)
3481
+ evidence_control_ids = kwargs.get("evidence_control_ids")
3482
+ force_refresh = kwargs.get("force_refresh", False)
3483
+ session_name = kwargs.get("session_name")
3484
+ profile = kwargs.get("profile")
3485
+ aws_access_key_id = kwargs.get("aws_access_key_id")
3486
+ aws_secret_access_key = kwargs.get("aws_secret_access_key")
3487
+ aws_session_token = kwargs.get("aws_session_token")
3488
+
3489
+ logger.info("Starting AWS CloudWatch Logs sync to RegScale...")
3490
+
3491
+ resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
3492
+ session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
3493
+ )
3494
+
3495
+ logger.info(
3496
+ f"Using AWS credentials - profile: {resolved_profile if resolved_profile else 'not set'}, "
3497
+ f"explicit credentials: {'yes' if access_key else 'no'}, region: {resolved_region}"
3498
+ )
3499
+
3500
+ parsed_tags = parse_tags(tags)
3501
+
3502
+ if account_id:
3503
+ logger.info(f"Filtering CloudWatch log groups by account ID: {account_id}")
3504
+
3505
+ if parsed_tags:
3506
+ logger.info(f"Filtering CloudWatch log groups by tags: {parsed_tags}")
3507
+
3508
+ if log_group_prefix:
3509
+ logger.info(f"Filtering CloudWatch log groups by name prefix: {log_group_prefix}")
3510
+
3511
+ control_ids = None
3512
+ if evidence_control_ids:
3513
+ control_ids = [ctrl.strip() for ctrl in evidence_control_ids.split(",")]
3514
+ logger.info(f"Evidence collection requested for controls: {control_ids}")
3515
+
3516
+ if create_evidence or create_ssp_attachment:
3517
+ if create_ssp_attachment:
3518
+ logger.info(EVIDENCE_MODE_SSP_ATTACHMENTS)
3519
+ if create_evidence:
3520
+ logger.info(EVIDENCE_MODE_INDIVIDUAL_RECORDS)
3521
+
3522
+ from .cloudwatch_evidence import AWSCloudWatchEvidenceIntegration, CloudWatchEvidenceConfig
3523
+
3524
+ # Create configuration object
3525
+ config = CloudWatchEvidenceConfig(
3526
+ plan_id=regscale_id,
3527
+ region=resolved_region,
3528
+ account_id=account_id,
3529
+ tags=parsed_tags,
3530
+ log_group_prefix=log_group_prefix,
3531
+ create_evidence=create_evidence,
3532
+ create_ssp_attachment=create_ssp_attachment,
3533
+ evidence_control_ids=control_ids,
3534
+ force_refresh=force_refresh,
3535
+ aws_profile=resolved_profile,
3536
+ aws_access_key_id=access_key,
3537
+ aws_secret_access_key=secret_key,
3538
+ aws_session_token=session_token,
3539
+ )
3540
+
3541
+ scanner = AWSCloudWatchEvidenceIntegration(config)
3542
+
3543
+ scanner.sync_compliance_data()
3544
+
3545
+ logger.info("AWS CloudWatch Logs evidence sync completed successfully")
3546
+
3547
+ except Exception as e:
3548
+ logger.error(f"Error syncing AWS CloudWatch Logs evidence: {e}", exc_info=True)
496
3549
  raise click.ClickException(str(e))