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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. regscale/_version.py +1 -1
  2. regscale/airflow/hierarchy.py +2 -2
  3. regscale/core/app/api.py +5 -2
  4. regscale/core/app/application.py +36 -6
  5. regscale/core/app/internal/control_editor.py +73 -21
  6. regscale/core/app/internal/evidence.py +727 -204
  7. regscale/core/app/internal/login.py +4 -2
  8. regscale/core/app/internal/model_editor.py +219 -64
  9. regscale/core/app/utils/app_utils.py +86 -12
  10. regscale/core/app/utils/catalog_utils/common.py +1 -1
  11. regscale/core/login.py +21 -4
  12. regscale/core/utils/async_graphql_client.py +363 -0
  13. regscale/core/utils/date.py +77 -1
  14. regscale/dev/cli.py +26 -0
  15. regscale/dev/code_gen.py +109 -24
  16. regscale/dev/version.py +72 -0
  17. regscale/integrations/commercial/__init__.py +30 -2
  18. regscale/integrations/commercial/aws/audit_manager_compliance.py +3908 -0
  19. regscale/integrations/commercial/aws/cli.py +3107 -54
  20. regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
  21. regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
  22. regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
  23. regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
  24. regscale/integrations/commercial/{amazon → aws}/common.py +71 -19
  25. regscale/integrations/commercial/aws/config_compliance.py +914 -0
  26. regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
  27. regscale/integrations/commercial/aws/control_compliance_analyzer.py +439 -0
  28. regscale/integrations/commercial/aws/evidence_generator.py +283 -0
  29. regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
  30. regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
  31. regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
  32. regscale/integrations/commercial/aws/iam_evidence.py +574 -0
  33. regscale/integrations/commercial/aws/inventory/__init__.py +338 -22
  34. regscale/integrations/commercial/aws/inventory/base.py +107 -5
  35. regscale/integrations/commercial/aws/inventory/resources/analytics.py +390 -0
  36. regscale/integrations/commercial/aws/inventory/resources/applications.py +234 -0
  37. regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
  38. regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
  39. regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
  40. regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
  41. regscale/integrations/commercial/aws/inventory/resources/compute.py +328 -9
  42. regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
  43. regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
  44. regscale/integrations/commercial/aws/inventory/resources/database.py +481 -31
  45. regscale/integrations/commercial/aws/inventory/resources/developer_tools.py +253 -0
  46. regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
  47. regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
  48. regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
  49. regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
  50. regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
  51. regscale/integrations/commercial/aws/inventory/resources/machine_learning.py +358 -0
  52. regscale/integrations/commercial/aws/inventory/resources/networking.py +390 -67
  53. regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
  54. regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
  55. regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
  56. regscale/integrations/commercial/aws/inventory/resources/storage.py +288 -29
  57. regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
  58. regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
  59. regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
  60. regscale/integrations/commercial/aws/kms_evidence.py +879 -0
  61. regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
  62. regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
  63. regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
  64. regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
  65. regscale/integrations/commercial/aws/org_evidence.py +666 -0
  66. regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
  67. regscale/integrations/commercial/aws/s3_evidence.py +632 -0
  68. regscale/integrations/commercial/aws/scanner.py +1072 -205
  69. regscale/integrations/commercial/aws/security_hub.py +319 -0
  70. regscale/integrations/commercial/aws/session_manager.py +282 -0
  71. regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
  72. regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
  73. regscale/integrations/commercial/jira.py +489 -153
  74. regscale/integrations/commercial/microsoft_defender/defender.py +326 -5
  75. regscale/integrations/commercial/microsoft_defender/defender_api.py +348 -14
  76. regscale/integrations/commercial/microsoft_defender/defender_constants.py +157 -0
  77. regscale/integrations/commercial/qualys/__init__.py +167 -68
  78. regscale/integrations/commercial/qualys/scanner.py +305 -39
  79. regscale/integrations/commercial/sarif/sairf_importer.py +432 -0
  80. regscale/integrations/commercial/sarif/sarif_converter.py +67 -0
  81. regscale/integrations/commercial/sicura/api.py +79 -42
  82. regscale/integrations/commercial/sicura/commands.py +8 -2
  83. regscale/integrations/commercial/sicura/scanner.py +83 -44
  84. regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
  85. regscale/integrations/commercial/synqly/assets.py +133 -16
  86. regscale/integrations/commercial/synqly/edr.py +2 -8
  87. regscale/integrations/commercial/synqly/query_builder.py +536 -0
  88. regscale/integrations/commercial/synqly/ticketing.py +27 -0
  89. regscale/integrations/commercial/synqly/vulnerabilities.py +165 -28
  90. regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
  91. regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
  92. regscale/integrations/commercial/tenablev2/commands.py +146 -5
  93. regscale/integrations/commercial/tenablev2/scanner.py +1 -3
  94. regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
  95. regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
  96. regscale/integrations/commercial/wizv2/click.py +191 -76
  97. regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
  98. regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
  99. regscale/integrations/commercial/wizv2/compliance_report.py +1592 -0
  100. regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
  101. regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +7 -3
  102. regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +92 -89
  103. regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
  104. regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
  105. regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +66 -9
  106. regscale/integrations/commercial/wizv2/file_cleanup.py +104 -0
  107. regscale/integrations/commercial/wizv2/issue.py +776 -28
  108. regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
  109. regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
  110. regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
  111. regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
  112. regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
  113. regscale/integrations/commercial/wizv2/reports.py +243 -0
  114. regscale/integrations/commercial/wizv2/sbom.py +1 -1
  115. regscale/integrations/commercial/wizv2/scanner.py +1031 -441
  116. regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
  117. regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
  118. regscale/integrations/commercial/wizv2/variables.py +89 -3
  119. regscale/integrations/compliance_integration.py +1036 -151
  120. regscale/integrations/control_matcher.py +432 -0
  121. regscale/integrations/due_date_handler.py +333 -0
  122. regscale/integrations/milestone_manager.py +291 -0
  123. regscale/integrations/public/__init__.py +14 -0
  124. regscale/integrations/public/cci_importer.py +834 -0
  125. regscale/integrations/public/csam/__init__.py +0 -0
  126. regscale/integrations/public/csam/csam.py +938 -0
  127. regscale/integrations/public/csam/csam_agency_defined.py +179 -0
  128. regscale/integrations/public/csam/csam_common.py +154 -0
  129. regscale/integrations/public/csam/csam_controls.py +432 -0
  130. regscale/integrations/public/csam/csam_poam.py +124 -0
  131. regscale/integrations/public/fedramp/click.py +77 -6
  132. regscale/integrations/public/fedramp/docx_parser.py +10 -1
  133. regscale/integrations/public/fedramp/fedramp_cis_crm.py +675 -289
  134. regscale/integrations/public/fedramp/fedramp_five.py +1 -1
  135. regscale/integrations/public/fedramp/poam/scanner.py +75 -7
  136. regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
  137. regscale/integrations/scanner_integration.py +1961 -430
  138. regscale/models/integration_models/CCI_List.xml +1 -0
  139. regscale/models/integration_models/aqua.py +2 -2
  140. regscale/models/integration_models/cisa_kev_data.json +805 -11
  141. regscale/models/integration_models/flat_file_importer/__init__.py +5 -8
  142. regscale/models/integration_models/nexpose.py +36 -10
  143. regscale/models/integration_models/qualys.py +3 -4
  144. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  145. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +87 -18
  146. regscale/models/integration_models/synqly_models/filter_parser.py +332 -0
  147. regscale/models/integration_models/synqly_models/ocsf_mapper.py +124 -25
  148. regscale/models/integration_models/synqly_models/synqly_model.py +89 -16
  149. regscale/models/locking.py +12 -8
  150. regscale/models/platform.py +4 -2
  151. regscale/models/regscale_models/__init__.py +7 -0
  152. regscale/models/regscale_models/assessment.py +2 -1
  153. regscale/models/regscale_models/catalog.py +1 -1
  154. regscale/models/regscale_models/compliance_settings.py +251 -1
  155. regscale/models/regscale_models/component.py +1 -0
  156. regscale/models/regscale_models/control_implementation.py +236 -41
  157. regscale/models/regscale_models/control_objective.py +74 -5
  158. regscale/models/regscale_models/file.py +2 -0
  159. regscale/models/regscale_models/form_field_value.py +5 -3
  160. regscale/models/regscale_models/inheritance.py +44 -0
  161. regscale/models/regscale_models/issue.py +301 -102
  162. regscale/models/regscale_models/milestone.py +33 -14
  163. regscale/models/regscale_models/organization.py +3 -0
  164. regscale/models/regscale_models/regscale_model.py +310 -73
  165. regscale/models/regscale_models/security_plan.py +4 -2
  166. regscale/models/regscale_models/vulnerability.py +3 -3
  167. regscale/regscale.py +25 -4
  168. regscale/templates/__init__.py +0 -0
  169. regscale/utils/threading/threadhandler.py +20 -15
  170. regscale/validation/record.py +23 -1
  171. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/METADATA +17 -33
  172. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/RECORD +310 -111
  173. tests/core/__init__.py +0 -0
  174. tests/core/utils/__init__.py +0 -0
  175. tests/core/utils/test_async_graphql_client.py +472 -0
  176. tests/fixtures/test_fixture.py +13 -8
  177. tests/regscale/core/test_login.py +171 -4
  178. tests/regscale/integrations/commercial/__init__.py +0 -0
  179. tests/regscale/integrations/commercial/aws/__init__.py +0 -0
  180. tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
  181. tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
  182. tests/regscale/integrations/commercial/aws/test_aws_analytics_collector.py +260 -0
  183. tests/regscale/integrations/commercial/aws/test_aws_applications_collector.py +242 -0
  184. tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
  185. tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
  186. tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
  187. tests/regscale/integrations/commercial/aws/test_aws_developer_tools_collector.py +203 -0
  188. tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
  189. tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
  190. tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
  191. tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
  192. tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
  193. tests/regscale/integrations/commercial/aws/test_aws_machine_learning_collector.py +237 -0
  194. tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
  195. tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
  196. tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
  197. tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
  198. tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
  199. tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
  200. tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
  201. tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
  202. tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
  203. tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
  204. tests/regscale/integrations/commercial/aws/test_control_compliance_analyzer.py +375 -0
  205. tests/regscale/integrations/commercial/aws/test_datetime_parsing.py +223 -0
  206. tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
  207. tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
  208. tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
  209. tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
  210. tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
  211. tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
  212. tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
  213. tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
  214. tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
  215. tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
  216. tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
  217. tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
  218. tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
  219. tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
  220. tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
  221. tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
  222. tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
  223. tests/regscale/integrations/commercial/conftest.py +28 -0
  224. tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
  225. tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
  226. tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
  227. tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
  228. tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
  229. tests/regscale/integrations/commercial/test_aws.py +3742 -0
  230. tests/regscale/integrations/commercial/test_burp.py +48 -0
  231. tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
  232. tests/regscale/integrations/commercial/test_dependabot.py +341 -0
  233. tests/regscale/integrations/commercial/test_gcp.py +1543 -0
  234. tests/regscale/integrations/commercial/test_gitlab.py +549 -0
  235. tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
  236. tests/regscale/integrations/commercial/test_jira.py +2204 -0
  237. tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
  238. tests/regscale/integrations/commercial/test_okta.py +1228 -0
  239. tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
  240. tests/regscale/integrations/commercial/test_sicura.py +349 -0
  241. tests/regscale/integrations/commercial/test_snow.py +423 -0
  242. tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
  243. tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
  244. tests/regscale/integrations/commercial/test_stig.py +33 -0
  245. tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
  246. tests/regscale/integrations/commercial/test_stigv2.py +406 -0
  247. tests/regscale/integrations/commercial/test_wiz.py +1365 -0
  248. tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
  249. tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
  250. tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
  251. tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
  252. tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
  253. tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
  254. tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
  255. tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
  256. tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
  257. tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
  258. tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
  259. tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
  260. tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
  261. tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
  262. tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
  263. tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
  264. tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
  265. tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
  266. tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
  267. tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
  268. tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
  269. tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
  270. tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
  271. tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
  272. tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
  273. tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
  274. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -0
  275. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
  276. tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
  277. tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
  278. tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
  279. tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
  280. tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1218 -0
  281. tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
  282. tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
  283. tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
  284. tests/regscale/integrations/public/__init__.py +0 -0
  285. tests/regscale/integrations/public/fedramp/__init__.py +1 -0
  286. tests/regscale/integrations/public/fedramp/test_gen_asset_list.py +150 -0
  287. tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
  288. tests/regscale/integrations/public/test_alienvault.py +220 -0
  289. tests/regscale/integrations/public/test_cci.py +1053 -0
  290. tests/regscale/integrations/public/test_cisa.py +1021 -0
  291. tests/regscale/integrations/public/test_emass.py +518 -0
  292. tests/regscale/integrations/public/test_fedramp.py +1152 -0
  293. tests/regscale/integrations/public/test_fedramp_cis_crm.py +3661 -0
  294. tests/regscale/integrations/public/test_file_uploads.py +506 -0
  295. tests/regscale/integrations/public/test_oscal.py +453 -0
  296. tests/regscale/integrations/test_compliance_status_mapping.py +406 -0
  297. tests/regscale/integrations/test_control_matcher.py +1421 -0
  298. tests/regscale/integrations/test_control_matching.py +155 -0
  299. tests/regscale/integrations/test_milestone_manager.py +408 -0
  300. tests/regscale/models/test_control_implementation.py +118 -3
  301. tests/regscale/models/test_form_field_value_integration.py +304 -0
  302. tests/regscale/models/test_issue.py +378 -1
  303. tests/regscale/models/test_module_integration.py +582 -0
  304. tests/regscale/models/test_tenable_integrations.py +811 -105
  305. regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3057
  306. regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +0 -7388
  307. regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +0 -9605
  308. regscale/integrations/public/fedramp/parts_mapper.py +0 -107
  309. /regscale/integrations/commercial/{amazon → sarif}/__init__.py +0 -0
  310. /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
  311. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/LICENSE +0 -0
  312. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/WHEEL +0 -0
  313. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/entry_points.txt +0 -0
  314. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1163 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Unit tests for AWS KMS Evidence Integration."""
4
+
5
+ import gzip
6
+ import json
7
+ import os
8
+ import time
9
+ from datetime import datetime, timedelta
10
+ from io import BytesIO
11
+ from unittest.mock import MagicMock, Mock, call, mock_open, patch
12
+
13
+ import pytest
14
+ from botocore.exceptions import ClientError
15
+
16
+ from regscale.integrations.commercial.aws.kms_evidence import (
17
+ AWSKMSEvidenceIntegration,
18
+ KMSComplianceItem,
19
+ CACHE_TTL_SECONDS,
20
+ KMS_CACHE_FILE,
21
+ )
22
+
23
+ PATH = "regscale.integrations.commercial.aws.kms_evidence"
24
+
25
+
26
+ class TestKMSComplianceItem:
27
+ """Test cases for KMSComplianceItem class."""
28
+
29
+ @patch(f"{PATH}.KMSControlMapper")
30
+ def test_init_with_complete_data(self, mock_mapper_class):
31
+ """Test initialization with complete KMS key data."""
32
+ mock_mapper = MagicMock()
33
+ mock_mapper.assess_key_compliance.return_value = {
34
+ "SC-12": "PASS",
35
+ "SC-13": "PASS",
36
+ "SC-28": "PASS",
37
+ }
38
+ mock_mapper.get_control_description.side_effect = lambda x: f"{x} description"
39
+ mock_mapper.framework = "NIST800-53R5"
40
+
41
+ key_data = {
42
+ "KeyId": "12345678-1234-1234-1234-123456789012",
43
+ "Arn": "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012",
44
+ "KeyState": "Enabled",
45
+ "RotationEnabled": True,
46
+ "KeyManager": "CUSTOMER",
47
+ "Description": "Test KMS Key",
48
+ "Tags": [{"TagKey": "Name", "TagValue": "TestKey"}],
49
+ }
50
+
51
+ item = KMSComplianceItem(key_data, mock_mapper)
52
+
53
+ assert item.resource_id == "12345678-1234-1234-1234-123456789012"
54
+ assert "TestKey" in item.resource_name
55
+ assert item.control_id in ["SC-12", "SC-13", "SC-28"]
56
+ assert item.compliance_result == "PASS"
57
+ assert item.severity is None
58
+ assert item.framework == "NIST800-53R5"
59
+
60
+ @patch(f"{PATH}.KMSControlMapper")
61
+ def test_init_with_minimal_data(self, mock_mapper_class):
62
+ """Test initialization with minimal KMS key data."""
63
+ mock_mapper = MagicMock()
64
+ mock_mapper.assess_key_compliance.return_value = {}
65
+ mock_mapper.get_control_description.return_value = "Test description"
66
+ mock_mapper.framework = "NIST800-53R5"
67
+
68
+ key_data = {}
69
+
70
+ item = KMSComplianceItem(key_data, mock_mapper)
71
+
72
+ assert item.resource_id == ""
73
+ assert "KMS Key" in item.resource_name
74
+ assert item.control_id == "SC-12"
75
+ assert item.compliance_result == "PASS"
76
+
77
+ @patch(f"{PATH}.KMSControlMapper")
78
+ def test_resource_name_with_tag(self, mock_mapper_class):
79
+ """Test resource name extraction with tags."""
80
+ mock_mapper = MagicMock()
81
+ mock_mapper.assess_key_compliance.return_value = {}
82
+ mock_mapper.framework = "NIST800-53R5"
83
+
84
+ key_data = {
85
+ "KeyId": "12345678-1234-1234-1234-123456789012",
86
+ "Tags": [{"TagKey": "Name", "TagValue": "ProductionKey"}],
87
+ }
88
+
89
+ item = KMSComplianceItem(key_data, mock_mapper)
90
+
91
+ assert "ProductionKey" in item.resource_name
92
+ assert "12345678" in item.resource_name
93
+
94
+ @patch(f"{PATH}.KMSControlMapper")
95
+ def test_resource_name_with_description(self, mock_mapper_class):
96
+ """Test resource name extraction with description."""
97
+ mock_mapper = MagicMock()
98
+ mock_mapper.assess_key_compliance.return_value = {}
99
+ mock_mapper.framework = "NIST800-53R5"
100
+
101
+ key_data = {
102
+ "KeyId": "12345678-1234-1234-1234-123456789012",
103
+ "Description": "Key for encrypting sensitive data in production environment",
104
+ }
105
+
106
+ item = KMSComplianceItem(key_data, mock_mapper)
107
+
108
+ assert "Key for encrypting sensitive data in production" in item.resource_name
109
+ assert "12345678" in item.resource_name
110
+
111
+ @patch(f"{PATH}.KMSControlMapper")
112
+ def test_control_id_first_failing_control(self, mock_mapper_class):
113
+ """Test control_id property returns first failing control."""
114
+ mock_mapper = MagicMock()
115
+ mock_mapper.assess_key_compliance.return_value = {
116
+ "SC-12": "PASS",
117
+ "SC-13": "FAIL",
118
+ "SC-28": "PASS",
119
+ }
120
+ mock_mapper.framework = "NIST800-53R5"
121
+
122
+ key_data = {"KeyId": "test-key"}
123
+
124
+ item = KMSComplianceItem(key_data, mock_mapper)
125
+
126
+ assert item.control_id == "SC-13"
127
+
128
+ @patch(f"{PATH}.KMSControlMapper")
129
+ def test_control_id_all_passing(self, mock_mapper_class):
130
+ """Test control_id property when all controls pass."""
131
+ mock_mapper = MagicMock()
132
+ mock_mapper.assess_key_compliance.return_value = {
133
+ "SC-12": "PASS",
134
+ "SC-13": "PASS",
135
+ "SC-28": "PASS",
136
+ }
137
+ mock_mapper.framework = "NIST800-53R5"
138
+
139
+ key_data = {"KeyId": "test-key"}
140
+
141
+ item = KMSComplianceItem(key_data, mock_mapper)
142
+
143
+ assert item.control_id in ["SC-12", "SC-13", "SC-28"]
144
+
145
+ @patch(f"{PATH}.KMSControlMapper")
146
+ def test_compliance_result_fail(self, mock_mapper_class):
147
+ """Test compliance_result property with failures."""
148
+ mock_mapper = MagicMock()
149
+ mock_mapper.assess_key_compliance.return_value = {
150
+ "SC-12": "PASS",
151
+ "SC-13": "FAIL",
152
+ "SC-28": "PASS",
153
+ }
154
+ mock_mapper.framework = "NIST800-53R5"
155
+
156
+ key_data = {"KeyId": "test-key"}
157
+
158
+ item = KMSComplianceItem(key_data, mock_mapper)
159
+
160
+ assert item.compliance_result == "FAIL"
161
+
162
+ @patch(f"{PATH}.KMSControlMapper")
163
+ def test_compliance_result_pass(self, mock_mapper_class):
164
+ """Test compliance_result property with all passing."""
165
+ mock_mapper = MagicMock()
166
+ mock_mapper.assess_key_compliance.return_value = {
167
+ "SC-12": "PASS",
168
+ "SC-13": "PASS",
169
+ "SC-28": "PASS",
170
+ }
171
+ mock_mapper.framework = "NIST800-53R5"
172
+
173
+ key_data = {"KeyId": "test-key"}
174
+
175
+ item = KMSComplianceItem(key_data, mock_mapper)
176
+
177
+ assert item.compliance_result == "PASS"
178
+
179
+ @patch(f"{PATH}.KMSControlMapper")
180
+ def test_severity_sc12_failure(self, mock_mapper_class):
181
+ """Test severity for SC-12 failure."""
182
+ mock_mapper = MagicMock()
183
+ mock_mapper.assess_key_compliance.return_value = {
184
+ "SC-12": "FAIL",
185
+ "SC-13": "PASS",
186
+ "SC-28": "PASS",
187
+ }
188
+ mock_mapper.framework = "NIST800-53R5"
189
+
190
+ key_data = {"KeyId": "test-key"}
191
+
192
+ item = KMSComplianceItem(key_data, mock_mapper)
193
+
194
+ assert item.severity == "HIGH"
195
+
196
+ @patch(f"{PATH}.KMSControlMapper")
197
+ def test_severity_sc13_failure(self, mock_mapper_class):
198
+ """Test severity for SC-13 failure."""
199
+ mock_mapper = MagicMock()
200
+ mock_mapper.assess_key_compliance.return_value = {
201
+ "SC-12": "PASS",
202
+ "SC-13": "FAIL",
203
+ "SC-28": "PASS",
204
+ }
205
+ mock_mapper.framework = "NIST800-53R5"
206
+
207
+ key_data = {"KeyId": "test-key"}
208
+
209
+ item = KMSComplianceItem(key_data, mock_mapper)
210
+
211
+ assert item.severity == "MEDIUM"
212
+
213
+ @patch(f"{PATH}.KMSControlMapper")
214
+ def test_severity_sc28_failure(self, mock_mapper_class):
215
+ """Test severity for SC-28 failure."""
216
+ mock_mapper = MagicMock()
217
+ mock_mapper.assess_key_compliance.return_value = {
218
+ "SC-12": "PASS",
219
+ "SC-13": "PASS",
220
+ "SC-28": "FAIL",
221
+ }
222
+ mock_mapper.framework = "NIST800-53R5"
223
+
224
+ key_data = {"KeyId": "test-key"}
225
+
226
+ item = KMSComplianceItem(key_data, mock_mapper)
227
+
228
+ assert item.severity == "MEDIUM"
229
+
230
+ @patch(f"{PATH}.KMSControlMapper")
231
+ def test_severity_pass(self, mock_mapper_class):
232
+ """Test severity when all controls pass."""
233
+ mock_mapper = MagicMock()
234
+ mock_mapper.assess_key_compliance.return_value = {
235
+ "SC-12": "PASS",
236
+ "SC-13": "PASS",
237
+ "SC-28": "PASS",
238
+ }
239
+ mock_mapper.framework = "NIST800-53R5"
240
+
241
+ key_data = {"KeyId": "test-key"}
242
+
243
+ item = KMSComplianceItem(key_data, mock_mapper)
244
+
245
+ assert item.severity is None
246
+
247
+ @patch(f"{PATH}.KMSControlMapper")
248
+ def test_description_property(self, mock_mapper_class):
249
+ """Test description property generates HTML."""
250
+ mock_mapper = MagicMock()
251
+ mock_mapper.assess_key_compliance.return_value = {
252
+ "SC-12": "FAIL",
253
+ "SC-13": "PASS",
254
+ }
255
+ mock_mapper.get_control_description.side_effect = lambda x: f"{x} description"
256
+ mock_mapper.framework = "NIST800-53R5"
257
+
258
+ key_data = {
259
+ "KeyId": "12345678-1234-1234-1234-123456789012",
260
+ "Arn": "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012",
261
+ "KeyState": "Enabled",
262
+ "RotationEnabled": False,
263
+ "KeyManager": "CUSTOMER",
264
+ "Description": "Test key",
265
+ }
266
+
267
+ item = KMSComplianceItem(key_data, mock_mapper)
268
+ description = item.description
269
+
270
+ assert "AWS KMS Key Compliance Assessment" in description
271
+ assert "12345678-1234-1234-1234-123456789012" in description
272
+ assert "Rotation Enabled" in description
273
+ assert "No" in description
274
+ assert "Control Compliance Results" in description
275
+ assert "SC-12" in description
276
+ assert "FAIL" in description
277
+ assert "Remediation Guidance" in description
278
+
279
+ @patch(f"{PATH}.KMSControlMapper")
280
+ def test_description_with_remediation_rotation(self, mock_mapper_class):
281
+ """Test description includes rotation remediation."""
282
+ mock_mapper = MagicMock()
283
+ mock_mapper.assess_key_compliance.return_value = {
284
+ "SC-12": "FAIL",
285
+ }
286
+ mock_mapper.get_control_description.return_value = "SC-12 description"
287
+ mock_mapper.framework = "NIST800-53R5"
288
+
289
+ key_data = {
290
+ "KeyId": "test-key",
291
+ "Arn": "arn:aws:kms:us-east-1:123456789012:key/test-key",
292
+ "KeyState": "Enabled",
293
+ "RotationEnabled": False,
294
+ "KeyManager": "CUSTOMER",
295
+ }
296
+
297
+ item = KMSComplianceItem(key_data, mock_mapper)
298
+ description = item.description
299
+
300
+ assert "Enable automatic key rotation" in description
301
+
302
+ @patch(f"{PATH}.KMSControlMapper")
303
+ def test_description_with_remediation_key_state(self, mock_mapper_class):
304
+ """Test description includes key state remediation."""
305
+ mock_mapper = MagicMock()
306
+ mock_mapper.assess_key_compliance.return_value = {
307
+ "SC-12": "FAIL",
308
+ }
309
+ mock_mapper.get_control_description.return_value = "SC-12 description"
310
+ mock_mapper.framework = "NIST800-53R5"
311
+
312
+ key_data = {
313
+ "KeyId": "test-key",
314
+ "Arn": "arn:aws:kms:us-east-1:123456789012:key/test-key",
315
+ "KeyState": "PendingDeletion",
316
+ "RotationEnabled": True,
317
+ "KeyManager": "CUSTOMER",
318
+ }
319
+
320
+ item = KMSComplianceItem(key_data, mock_mapper)
321
+ description = item.description
322
+
323
+ assert "Key is PendingDeletion" in description
324
+ assert "review key lifecycle" in description
325
+
326
+ @patch(f"{PATH}.KMSControlMapper")
327
+ def test_description_with_remediation_key_spec(self, mock_mapper_class):
328
+ """Test description includes key spec remediation."""
329
+ mock_mapper = MagicMock()
330
+ mock_mapper.assess_key_compliance.return_value = {
331
+ "SC-13": "FAIL",
332
+ }
333
+ mock_mapper.get_control_description.return_value = "SC-13 description"
334
+ mock_mapper.framework = "NIST800-53R5"
335
+
336
+ key_data = {
337
+ "KeyId": "test-key",
338
+ "Arn": "arn:aws:kms:us-east-1:123456789012:key/test-key",
339
+ "KeyState": "Enabled",
340
+ "RotationEnabled": True,
341
+ "KeyManager": "CUSTOMER",
342
+ "KeySpec": "RSA_1024",
343
+ }
344
+
345
+ item = KMSComplianceItem(key_data, mock_mapper)
346
+ description = item.description
347
+
348
+ assert "Review key specification" in description
349
+ assert "RSA_1024" in description
350
+ assert "FIPS-validated" in description
351
+
352
+ @patch(f"{PATH}.KMSControlMapper")
353
+ def test_extract_region_from_arn(self, mock_mapper_class):
354
+ """Test extracting region from ARN."""
355
+ result = KMSComplianceItem._extract_region_from_arn("arn:aws:kms:us-west-2:123456789012:key/test-key")
356
+ assert result == "us-west-2"
357
+
358
+ @patch(f"{PATH}.KMSControlMapper")
359
+ def test_extract_region_from_invalid_arn(self, mock_mapper_class):
360
+ """Test extracting region from invalid ARN."""
361
+ result = KMSComplianceItem._extract_region_from_arn("invalid-arn")
362
+ assert result == "unknown"
363
+
364
+ @patch(f"{PATH}.KMSControlMapper")
365
+ def test_extract_account_from_arn(self, mock_mapper_class):
366
+ """Test extracting account from ARN."""
367
+ result = KMSComplianceItem._extract_account_from_arn("arn:aws:kms:us-east-1:123456789012:key/test-key")
368
+ assert result == "123456789012"
369
+
370
+ @patch(f"{PATH}.KMSControlMapper")
371
+ def test_extract_account_from_invalid_arn(self, mock_mapper_class):
372
+ """Test extracting account from invalid ARN."""
373
+ result = KMSComplianceItem._extract_account_from_arn("invalid-arn")
374
+ assert result == "unknown"
375
+
376
+
377
+ class TestAWSKMSEvidenceIntegrationInit:
378
+ """Test cases for AWSKMSEvidenceIntegration initialization."""
379
+
380
+ @patch(f"{PATH}.KMSControlMapper")
381
+ @patch(f"{PATH}.boto3.Session")
382
+ def test_init_with_defaults(self, mock_session_class, mock_mapper_class):
383
+ """Test initialization with default parameters."""
384
+ mock_session = MagicMock()
385
+ mock_client = MagicMock()
386
+ mock_session.client.return_value = mock_client
387
+ mock_session_class.return_value = mock_session
388
+
389
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
390
+
391
+ assert integration.plan_id == 123
392
+ assert integration.region == "us-east-1"
393
+ assert integration.title == "AWS KMS"
394
+ assert integration.collect_evidence is False
395
+ assert integration.evidence_as_attachments is True
396
+ assert integration.evidence_control_ids is None
397
+ assert integration.evidence_frequency == 30
398
+ assert integration.force_refresh is False
399
+ assert integration.account_id is None
400
+ assert integration.tags == {}
401
+ assert integration.raw_kms_data == []
402
+
403
+ mock_session_class.assert_called_once()
404
+ mock_mapper_class.assert_called_once_with(framework="NIST800-53R5")
405
+
406
+ @patch(f"{PATH}.KMSControlMapper")
407
+ @patch(f"{PATH}.boto3.Session")
408
+ def test_init_with_explicit_credentials(self, mock_session_class, mock_mapper_class):
409
+ """Test initialization with explicit AWS credentials."""
410
+ mock_session = MagicMock()
411
+ mock_client = MagicMock()
412
+ mock_session.client.return_value = mock_client
413
+ mock_session_class.return_value = mock_session
414
+
415
+ integration = AWSKMSEvidenceIntegration(
416
+ plan_id=123,
417
+ region="us-west-2",
418
+ aws_access_key_id="AKIAIOSFODNN7EXAMPLE",
419
+ aws_secret_access_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
420
+ aws_session_token="session-token",
421
+ )
422
+
423
+ assert integration.region == "us-west-2"
424
+
425
+ mock_session_class.assert_called_once_with(
426
+ region_name="us-west-2",
427
+ aws_access_key_id="AKIAIOSFODNN7EXAMPLE",
428
+ aws_secret_access_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
429
+ aws_session_token="session-token",
430
+ )
431
+
432
+ @patch(f"{PATH}.KMSControlMapper")
433
+ @patch(f"{PATH}.boto3.Session")
434
+ def test_init_with_profile(self, mock_session_class, mock_mapper_class):
435
+ """Test initialization with AWS profile."""
436
+ mock_session = MagicMock()
437
+ mock_client = MagicMock()
438
+ mock_session.client.return_value = mock_client
439
+ mock_session_class.return_value = mock_session
440
+
441
+ AWSKMSEvidenceIntegration(plan_id=123, profile="test-profile")
442
+
443
+ mock_session_class.assert_called_once_with(profile_name="test-profile", region_name="us-east-1")
444
+
445
+ @patch(f"{PATH}.KMSControlMapper")
446
+ @patch(f"{PATH}.boto3.Session")
447
+ def test_init_with_all_options(self, mock_session_class, mock_mapper_class):
448
+ """Test initialization with all optional parameters."""
449
+ mock_session = MagicMock()
450
+ mock_client = MagicMock()
451
+ mock_session.client.return_value = mock_client
452
+ mock_session_class.return_value = mock_session
453
+
454
+ integration = AWSKMSEvidenceIntegration(
455
+ plan_id=456,
456
+ region="eu-west-1",
457
+ framework="ISO27001",
458
+ create_issues=False,
459
+ update_control_status=False,
460
+ create_poams=True,
461
+ parent_module="assessments",
462
+ collect_evidence=True,
463
+ evidence_as_attachments=False,
464
+ evidence_control_ids=["SC-12", "SC-13"],
465
+ evidence_frequency=60,
466
+ force_refresh=True,
467
+ account_id="123456789012",
468
+ tags={"Environment": "Production"},
469
+ )
470
+
471
+ assert integration.plan_id == 456
472
+ assert integration.region == "eu-west-1"
473
+ assert integration.framework == "ISO27001"
474
+ assert integration.create_issues is False
475
+ assert integration.update_control_status is False
476
+ assert integration.create_poams is True
477
+ assert integration.collect_evidence is True
478
+ assert integration.evidence_as_attachments is False
479
+ assert integration.evidence_control_ids == ["SC-12", "SC-13"]
480
+ assert integration.evidence_frequency == 60
481
+ assert integration.force_refresh is True
482
+ assert integration.account_id == "123456789012"
483
+ assert integration.tags == {"Environment": "Production"}
484
+
485
+ @patch(f"{PATH}.KMSControlMapper")
486
+ @patch(f"{PATH}.boto3.Session")
487
+ def test_init_client_creation_failure(self, mock_session_class, mock_mapper_class):
488
+ """Test initialization when client creation fails."""
489
+ mock_session = MagicMock()
490
+ mock_session.client.side_effect = Exception("Failed to create KMS client")
491
+ mock_session_class.return_value = mock_session
492
+
493
+ with pytest.raises(Exception) as exc_info:
494
+ AWSKMSEvidenceIntegration(plan_id=123)
495
+
496
+ assert "Failed to create KMS client" in str(exc_info.value)
497
+
498
+
499
+ class TestCacheManagement:
500
+ """Test cases for cache management methods."""
501
+
502
+ @patch(f"{PATH}.KMSControlMapper")
503
+ @patch(f"{PATH}.boto3.Session")
504
+ @patch(f"{PATH}.os.path.exists")
505
+ def test_is_cache_valid_no_file(self, mock_exists, mock_session_class, mock_mapper_class):
506
+ """Test cache validation when file does not exist."""
507
+ mock_exists.return_value = False
508
+ mock_session = MagicMock()
509
+ mock_session.client.return_value = MagicMock()
510
+ mock_session_class.return_value = mock_session
511
+
512
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
513
+
514
+ assert integration._is_cache_valid() is False
515
+
516
+ @patch(f"{PATH}.KMSControlMapper")
517
+ @patch(f"{PATH}.boto3.Session")
518
+ @patch(f"{PATH}.os.path.exists")
519
+ @patch(f"{PATH}.os.path.getmtime")
520
+ @patch(f"{PATH}.time.time")
521
+ def test_is_cache_valid_expired(self, mock_time, mock_getmtime, mock_exists, mock_session_class, mock_mapper_class):
522
+ """Test cache validation when cache is expired."""
523
+ mock_exists.return_value = True
524
+ mock_time.return_value = 1000000
525
+ mock_getmtime.return_value = 1000000 - CACHE_TTL_SECONDS - 100
526
+ mock_session = MagicMock()
527
+ mock_session.client.return_value = MagicMock()
528
+ mock_session_class.return_value = mock_session
529
+
530
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
531
+
532
+ assert integration._is_cache_valid() is False
533
+
534
+ @patch(f"{PATH}.KMSControlMapper")
535
+ @patch(f"{PATH}.boto3.Session")
536
+ @patch(f"{PATH}.os.path.exists")
537
+ @patch(f"{PATH}.os.path.getmtime")
538
+ @patch(f"{PATH}.time.time")
539
+ def test_is_cache_valid_fresh(self, mock_time, mock_getmtime, mock_exists, mock_session_class, mock_mapper_class):
540
+ """Test cache validation when cache is fresh."""
541
+ mock_exists.return_value = True
542
+ mock_time.return_value = 1000000
543
+ mock_getmtime.return_value = 1000000 - 1000
544
+ mock_session = MagicMock()
545
+ mock_session.client.return_value = MagicMock()
546
+ mock_session_class.return_value = mock_session
547
+
548
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
549
+
550
+ assert integration._is_cache_valid() is True
551
+
552
+ @patch(f"{PATH}.KMSControlMapper")
553
+ @patch(f"{PATH}.boto3.Session")
554
+ def test_load_cached_data_success(self, mock_session_class, mock_mapper_class):
555
+ """Test loading cached data successfully."""
556
+ mock_session = MagicMock()
557
+ mock_session.client.return_value = MagicMock()
558
+ mock_session_class.return_value = mock_session
559
+
560
+ test_data = [{"KeyId": "test-key", "KeyState": "Enabled"}]
561
+ mock_file = mock_open(read_data=json.dumps(test_data))
562
+
563
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
564
+
565
+ with patch("builtins.open", mock_file):
566
+ result = integration._load_cached_data()
567
+
568
+ assert result == test_data
569
+
570
+ @patch(f"{PATH}.KMSControlMapper")
571
+ @patch(f"{PATH}.boto3.Session")
572
+ def test_load_cached_data_json_error(self, mock_session_class, mock_mapper_class):
573
+ """Test loading cached data with JSON decode error."""
574
+ mock_session = MagicMock()
575
+ mock_session.client.return_value = MagicMock()
576
+ mock_session_class.return_value = mock_session
577
+
578
+ mock_file = mock_open(read_data="invalid json")
579
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
580
+
581
+ with patch("builtins.open", mock_file):
582
+ result = integration._load_cached_data()
583
+
584
+ assert result == []
585
+
586
+ @patch(f"{PATH}.KMSControlMapper")
587
+ @patch(f"{PATH}.boto3.Session")
588
+ def test_load_cached_data_io_error(self, mock_session_class, mock_mapper_class):
589
+ """Test loading cached data with IO error."""
590
+ mock_session = MagicMock()
591
+ mock_session.client.return_value = MagicMock()
592
+ mock_session_class.return_value = mock_session
593
+
594
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
595
+
596
+ with patch("builtins.open", side_effect=IOError("File not found")):
597
+ result = integration._load_cached_data()
598
+
599
+ assert result == []
600
+
601
+ @patch(f"{PATH}.KMSControlMapper")
602
+ @patch(f"{PATH}.boto3.Session")
603
+ @patch(f"{PATH}.os.makedirs")
604
+ def test_save_to_cache_success(self, mock_makedirs, mock_session_class, mock_mapper_class):
605
+ """Test saving data to cache successfully."""
606
+ mock_session = MagicMock()
607
+ mock_session.client.return_value = MagicMock()
608
+ mock_session_class.return_value = mock_session
609
+
610
+ test_data = [{"KeyId": "test-key"}]
611
+ mock_file = mock_open()
612
+
613
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
614
+
615
+ with patch("builtins.open", mock_file):
616
+ integration._save_to_cache(test_data)
617
+
618
+ mock_makedirs.assert_called_once()
619
+ mock_file.assert_called_once()
620
+
621
+ @patch(f"{PATH}.KMSControlMapper")
622
+ @patch(f"{PATH}.boto3.Session")
623
+ @patch(f"{PATH}.os.makedirs")
624
+ def test_save_to_cache_io_error(self, mock_makedirs, mock_session_class, mock_mapper_class):
625
+ """Test saving data to cache with IO error."""
626
+ mock_session = MagicMock()
627
+ mock_session.client.return_value = MagicMock()
628
+ mock_session_class.return_value = mock_session
629
+
630
+ test_data = [{"KeyId": "test-key"}]
631
+
632
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
633
+
634
+ with patch("builtins.open", side_effect=IOError("Permission denied")):
635
+ integration._save_to_cache(test_data)
636
+
637
+
638
+ class TestFetchKMSData:
639
+ """Test cases for fetching KMS data."""
640
+
641
+ @patch("regscale.integrations.commercial.aws.inventory.resources.kms.KMSCollector")
642
+ @patch(f"{PATH}.KMSControlMapper")
643
+ @patch(f"{PATH}.boto3.Session")
644
+ def test_fetch_fresh_kms_data(self, mock_session_class, mock_mapper_class, mock_collector_class):
645
+ """Test fetching fresh KMS data."""
646
+ mock_session = MagicMock()
647
+ mock_session.client.return_value = MagicMock()
648
+ mock_session_class.return_value = mock_session
649
+
650
+ test_data = [
651
+ {"KeyId": "key-1", "KeyState": "Enabled"},
652
+ {"KeyId": "key-2", "KeyState": "Enabled"},
653
+ ]
654
+
655
+ mock_collector = MagicMock()
656
+ mock_collector.collect.return_value = {"Keys": test_data}
657
+ mock_collector_class.return_value = mock_collector
658
+
659
+ integration = AWSKMSEvidenceIntegration(plan_id=123, region="us-east-1")
660
+
661
+ result = integration._fetch_fresh_kms_data()
662
+
663
+ assert len(result) == 2
664
+ assert result[0]["KeyId"] == "key-1"
665
+ mock_collector_class.assert_called_once()
666
+
667
+ @patch(f"{PATH}.KMSControlMapper")
668
+ @patch(f"{PATH}.boto3.Session")
669
+ def test_fetch_compliance_data_with_valid_cache(self, mock_session_class, mock_mapper_class):
670
+ """Test fetching compliance data when cache is valid."""
671
+ mock_session = MagicMock()
672
+ mock_session.client.return_value = MagicMock()
673
+ mock_session_class.return_value = mock_session
674
+
675
+ test_data = [{"KeyId": "cached-key"}]
676
+
677
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
678
+ integration._is_cache_valid = Mock(return_value=True)
679
+ integration._load_cached_data = Mock(return_value=test_data)
680
+
681
+ result = integration.fetch_compliance_data()
682
+
683
+ assert result == test_data
684
+ assert integration.raw_kms_data == test_data
685
+
686
+ @patch("regscale.integrations.commercial.aws.inventory.resources.kms.KMSCollector")
687
+ @patch(f"{PATH}.KMSControlMapper")
688
+ @patch(f"{PATH}.boto3.Session")
689
+ def test_fetch_compliance_data_force_refresh(self, mock_session_class, mock_mapper_class, mock_collector_class):
690
+ """Test fetching compliance data with force refresh."""
691
+ mock_session = MagicMock()
692
+ mock_session.client.return_value = MagicMock()
693
+ mock_session_class.return_value = mock_session
694
+
695
+ test_data = [{"KeyId": "fresh-key"}]
696
+
697
+ mock_collector = MagicMock()
698
+ mock_collector.collect.return_value = {"Keys": test_data}
699
+ mock_collector_class.return_value = mock_collector
700
+
701
+ integration = AWSKMSEvidenceIntegration(plan_id=123, force_refresh=True)
702
+ integration._save_to_cache = Mock()
703
+
704
+ result = integration.fetch_compliance_data()
705
+
706
+ assert result == test_data
707
+ integration._save_to_cache.assert_called_once_with(test_data)
708
+
709
+ @patch("regscale.integrations.commercial.aws.inventory.resources.kms.KMSCollector")
710
+ @patch(f"{PATH}.KMSControlMapper")
711
+ @patch(f"{PATH}.boto3.Session")
712
+ def test_fetch_compliance_data_client_error(self, mock_session_class, mock_mapper_class, mock_collector_class):
713
+ """Test fetching compliance data with ClientError."""
714
+ mock_session = MagicMock()
715
+ mock_session.client.return_value = MagicMock()
716
+ mock_session_class.return_value = mock_session
717
+
718
+ mock_collector = MagicMock()
719
+ mock_collector.collect.side_effect = ClientError(
720
+ {"Error": {"Code": "AccessDeniedException", "Message": "Access denied"}}, "ListKeys"
721
+ )
722
+ mock_collector_class.return_value = mock_collector
723
+
724
+ integration = AWSKMSEvidenceIntegration(plan_id=123, force_refresh=True)
725
+
726
+ result = integration.fetch_compliance_data()
727
+
728
+ assert result == []
729
+
730
+
731
+ class TestComplianceItem:
732
+ """Test cases for compliance item creation."""
733
+
734
+ @patch(f"{PATH}.KMSControlMapper")
735
+ @patch(f"{PATH}.boto3.Session")
736
+ def test_create_compliance_item(self, mock_session_class, mock_mapper_class):
737
+ """Test creating a compliance item from raw data."""
738
+ mock_session = MagicMock()
739
+ mock_session.client.return_value = MagicMock()
740
+ mock_session_class.return_value = mock_session
741
+
742
+ mock_mapper = MagicMock()
743
+ mock_mapper.assess_key_compliance.return_value = {"SC-12": "PASS"}
744
+ mock_mapper_class.return_value = mock_mapper
745
+
746
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
747
+
748
+ raw_data = {"KeyId": "test-key", "KeyState": "Enabled"}
749
+
750
+ result = integration.create_compliance_item(raw_data)
751
+
752
+ assert isinstance(result, KMSComplianceItem)
753
+ assert result.resource_id == "test-key"
754
+
755
+ @patch(f"{PATH}.KMSControlMapper")
756
+ @patch(f"{PATH}.boto3.Session")
757
+ def test_map_resource_type_to_asset_type(self, mock_session_class, mock_mapper_class):
758
+ """Test mapping resource type to asset type."""
759
+ mock_session = MagicMock()
760
+ mock_session.client.return_value = MagicMock()
761
+ mock_session_class.return_value = mock_session
762
+
763
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
764
+
765
+ mock_item = MagicMock()
766
+ result = integration._map_resource_type_to_asset_type(mock_item)
767
+
768
+ assert result == "AWS KMS Key"
769
+
770
+
771
+ class TestSyncCompliance:
772
+ """Test cases for sync_compliance method."""
773
+
774
+ @patch(f"{PATH}.KMSControlMapper")
775
+ @patch(f"{PATH}.boto3.Session")
776
+ def test_sync_compliance_without_evidence(self, mock_session_class, mock_mapper_class):
777
+ """Test sync_compliance without evidence collection."""
778
+ mock_session = MagicMock()
779
+ mock_session.client.return_value = MagicMock()
780
+ mock_session_class.return_value = mock_session
781
+
782
+ integration = AWSKMSEvidenceIntegration(plan_id=123, collect_evidence=False)
783
+
784
+ with patch.object(integration.__class__.__bases__[0], "sync_compliance") as mock_super_sync:
785
+ integration.sync_compliance()
786
+
787
+ mock_super_sync.assert_called_once()
788
+
789
+ @patch(f"{PATH}.KMSControlMapper")
790
+ @patch(f"{PATH}.boto3.Session")
791
+ def test_sync_compliance_with_evidence(self, mock_session_class, mock_mapper_class):
792
+ """Test sync_compliance with evidence collection."""
793
+ mock_session = MagicMock()
794
+ mock_session.client.return_value = MagicMock()
795
+ mock_session_class.return_value = mock_session
796
+
797
+ integration = AWSKMSEvidenceIntegration(plan_id=123, collect_evidence=True)
798
+ integration._collect_kms_evidence = Mock()
799
+
800
+ with patch.object(integration.__class__.__bases__[0], "sync_compliance"):
801
+ integration.sync_compliance()
802
+
803
+ integration._collect_kms_evidence.assert_called_once()
804
+
805
+
806
+ class TestEvidenceCollection:
807
+ """Test cases for evidence collection methods."""
808
+
809
+ @patch(f"{PATH}.KMSControlMapper")
810
+ @patch(f"{PATH}.boto3.Session")
811
+ @patch(f"{PATH}.get_current_datetime")
812
+ def test_collect_kms_evidence_as_attachments(self, mock_get_datetime, mock_session_class, mock_mapper_class):
813
+ """Test collecting evidence as SSP attachments."""
814
+ mock_session = MagicMock()
815
+ mock_session.client.return_value = MagicMock()
816
+ mock_session_class.return_value = mock_session
817
+
818
+ mock_get_datetime.return_value = "2023-12-01"
819
+
820
+ integration = AWSKMSEvidenceIntegration(plan_id=123, collect_evidence=True, evidence_as_attachments=True)
821
+
822
+ integration.raw_kms_data = [{"KeyId": "test-key"}]
823
+ integration._create_ssp_attachment = Mock()
824
+
825
+ integration._collect_kms_evidence()
826
+
827
+ integration._create_ssp_attachment.assert_called_once_with("2023-12-01")
828
+
829
+ @patch(f"{PATH}.KMSControlMapper")
830
+ @patch(f"{PATH}.boto3.Session")
831
+ @patch(f"{PATH}.get_current_datetime")
832
+ def test_collect_kms_evidence_as_records(self, mock_get_datetime, mock_session_class, mock_mapper_class):
833
+ """Test collecting evidence as evidence records."""
834
+ mock_session = MagicMock()
835
+ mock_session.client.return_value = MagicMock()
836
+ mock_session_class.return_value = mock_session
837
+
838
+ mock_get_datetime.return_value = "2023-12-01"
839
+
840
+ integration = AWSKMSEvidenceIntegration(plan_id=123, collect_evidence=True, evidence_as_attachments=False)
841
+
842
+ integration.raw_kms_data = [{"KeyId": "test-key"}]
843
+ integration._create_evidence_record = Mock()
844
+
845
+ integration._collect_kms_evidence()
846
+
847
+ integration._create_evidence_record.assert_called_once_with("2023-12-01")
848
+
849
+ @patch(f"{PATH}.KMSControlMapper")
850
+ @patch(f"{PATH}.boto3.Session")
851
+ def test_collect_kms_evidence_no_data(self, mock_session_class, mock_mapper_class):
852
+ """Test collecting evidence when no data is available."""
853
+ mock_session = MagicMock()
854
+ mock_session.client.return_value = MagicMock()
855
+ mock_session_class.return_value = mock_session
856
+
857
+ integration = AWSKMSEvidenceIntegration(plan_id=123, collect_evidence=True)
858
+
859
+ integration.raw_kms_data = []
860
+
861
+ integration._collect_kms_evidence()
862
+
863
+ @patch(f"{PATH}.KMSControlMapper")
864
+ @patch(f"{PATH}.boto3.Session")
865
+ @patch(f"{PATH}.Api")
866
+ @patch(f"{PATH}.File")
867
+ def test_create_ssp_attachment_success(
868
+ self, mock_file_class, mock_api_class, mock_session_class, mock_mapper_class
869
+ ):
870
+ """Test creating SSP attachment successfully."""
871
+ mock_session = MagicMock()
872
+ mock_session.client.return_value = MagicMock()
873
+ mock_session_class.return_value = mock_session
874
+
875
+ mock_mapper = MagicMock()
876
+ mock_mapper.assess_key_compliance.return_value = {"SC-12": "PASS"}
877
+ mock_mapper_class.return_value = mock_mapper
878
+
879
+ mock_api = MagicMock()
880
+ mock_api_class.return_value = mock_api
881
+
882
+ mock_file_class.upload_file_to_regscale.return_value = True
883
+
884
+ integration = AWSKMSEvidenceIntegration(plan_id=123, region="us-east-1", account_id="123456789012")
885
+ integration.raw_kms_data = [{"KeyId": "test-key"}]
886
+
887
+ integration._create_ssp_attachment("2023-12-01")
888
+
889
+ mock_file_class.upload_file_to_regscale.assert_called_once()
890
+ call_args = mock_file_class.upload_file_to_regscale.call_args[1]
891
+ assert call_args["parent_id"] == 123
892
+ assert call_args["parent_module"] == "securityplans"
893
+ assert "kms_evidence_123456789012_" in call_args["file_name"]
894
+ assert call_args["file_name"].endswith(".jsonl.gz")
895
+
896
+ @patch(f"{PATH}.KMSControlMapper")
897
+ @patch(f"{PATH}.boto3.Session")
898
+ @patch(f"{PATH}.Api")
899
+ @patch(f"{PATH}.File")
900
+ def test_create_ssp_attachment_failure(
901
+ self, mock_file_class, mock_api_class, mock_session_class, mock_mapper_class
902
+ ):
903
+ """Test creating SSP attachment with failure."""
904
+ mock_session = MagicMock()
905
+ mock_session.client.return_value = MagicMock()
906
+ mock_session_class.return_value = mock_session
907
+
908
+ mock_mapper = MagicMock()
909
+ mock_mapper.assess_key_compliance.return_value = {}
910
+ mock_mapper_class.return_value = mock_mapper
911
+
912
+ mock_api = MagicMock()
913
+ mock_api_class.return_value = mock_api
914
+
915
+ mock_file_class.upload_file_to_regscale.return_value = False
916
+
917
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
918
+ integration.raw_kms_data = [{"KeyId": "test-key"}]
919
+
920
+ integration._create_ssp_attachment("2023-12-01")
921
+
922
+ @patch(f"{PATH}.KMSControlMapper")
923
+ @patch(f"{PATH}.boto3.Session")
924
+ @patch(f"{PATH}.Api")
925
+ @patch(f"{PATH}.File")
926
+ def test_create_ssp_attachment_exception(
927
+ self, mock_file_class, mock_api_class, mock_session_class, mock_mapper_class
928
+ ):
929
+ """Test creating SSP attachment with exception."""
930
+ mock_session = MagicMock()
931
+ mock_session.client.return_value = MagicMock()
932
+ mock_session_class.return_value = mock_session
933
+
934
+ mock_mapper = MagicMock()
935
+ mock_mapper.assess_key_compliance.side_effect = Exception("Test error")
936
+ mock_mapper_class.return_value = mock_mapper
937
+
938
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
939
+ integration.raw_kms_data = [{"KeyId": "test-key"}]
940
+
941
+ integration._create_ssp_attachment("2023-12-01")
942
+
943
+ @patch(f"{PATH}.KMSControlMapper")
944
+ @patch(f"{PATH}.boto3.Session")
945
+ @patch(f"{PATH}.Evidence")
946
+ def test_create_evidence_record_success(self, mock_evidence_class, mock_session_class, mock_mapper_class):
947
+ """Test creating evidence record successfully."""
948
+ mock_session = MagicMock()
949
+ mock_session.client.return_value = MagicMock()
950
+ mock_session_class.return_value = mock_session
951
+
952
+ mock_mapper = MagicMock()
953
+ mock_mapper.assess_key_compliance.return_value = {"SC-12": "PASS"}
954
+ mock_mapper.get_control_description.return_value = "SC-12 description"
955
+ mock_mapper.get_mapped_controls.return_value = ["SC-12", "SC-13", "SC-28"]
956
+ mock_mapper_class.return_value = mock_mapper
957
+
958
+ mock_evidence = MagicMock()
959
+ mock_evidence.id = 999
960
+ mock_evidence_instance = MagicMock()
961
+ mock_evidence_instance.create.return_value = mock_evidence
962
+ mock_evidence_class.return_value = mock_evidence_instance
963
+
964
+ integration = AWSKMSEvidenceIntegration(plan_id=123, evidence_frequency=60)
965
+ integration.raw_kms_data = [{"KeyId": "test-key"}]
966
+ integration._upload_evidence_file = Mock()
967
+ integration._link_evidence_to_ssp = Mock()
968
+
969
+ integration._create_evidence_record("2023-12-01")
970
+
971
+ mock_evidence_instance.create.assert_called_once()
972
+ integration._upload_evidence_file.assert_called_once_with(999, "2023-12-01")
973
+ integration._link_evidence_to_ssp.assert_called_once_with(999)
974
+
975
+ @patch(f"{PATH}.KMSControlMapper")
976
+ @patch(f"{PATH}.boto3.Session")
977
+ @patch(f"{PATH}.Evidence")
978
+ def test_create_evidence_record_creation_failure(self, mock_evidence_class, mock_session_class, mock_mapper_class):
979
+ """Test creating evidence record when creation fails."""
980
+ mock_session = MagicMock()
981
+ mock_session.client.return_value = MagicMock()
982
+ mock_session_class.return_value = mock_session
983
+
984
+ mock_mapper = MagicMock()
985
+ mock_mapper.assess_key_compliance.return_value = {}
986
+ mock_mapper.get_mapped_controls.return_value = []
987
+ mock_mapper_class.return_value = mock_mapper
988
+
989
+ mock_evidence_instance = MagicMock()
990
+ mock_evidence_instance.create.return_value = None
991
+ mock_evidence_class.return_value = mock_evidence_instance
992
+
993
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
994
+ integration.raw_kms_data = []
995
+
996
+ integration._create_evidence_record("2023-12-01")
997
+
998
+ mock_evidence_instance.create.assert_called_once()
999
+
1000
+ @patch(f"{PATH}.KMSControlMapper")
1001
+ @patch(f"{PATH}.boto3.Session")
1002
+ @patch(f"{PATH}.Evidence")
1003
+ def test_create_evidence_record_exception(self, mock_evidence_class, mock_session_class, mock_mapper_class):
1004
+ """Test creating evidence record with exception."""
1005
+ mock_session = MagicMock()
1006
+ mock_session.client.return_value = MagicMock()
1007
+ mock_session_class.return_value = mock_session
1008
+
1009
+ mock_mapper = MagicMock()
1010
+ mock_mapper.assess_key_compliance.side_effect = Exception("Test error")
1011
+ mock_mapper_class.return_value = mock_mapper
1012
+
1013
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
1014
+ integration.raw_kms_data = []
1015
+
1016
+ integration._create_evidence_record("2023-12-01")
1017
+
1018
+ @patch(f"{PATH}.KMSControlMapper")
1019
+ @patch(f"{PATH}.boto3.Session")
1020
+ def test_build_evidence_description(self, mock_session_class, mock_mapper_class):
1021
+ """Test building evidence description."""
1022
+ mock_session = MagicMock()
1023
+ mock_session.client.return_value = MagicMock()
1024
+ mock_session_class.return_value = mock_session
1025
+
1026
+ mock_mapper = MagicMock()
1027
+ mock_mapper.assess_key_compliance.return_value = {"SC-12": "PASS", "SC-13": "FAIL", "SC-28": "PASS"}
1028
+ mock_mapper.get_control_description.side_effect = lambda x: f"{x} description"
1029
+ mock_mapper.get_mapped_controls.return_value = ["SC-12", "SC-13", "SC-28"]
1030
+ mock_mapper_class.return_value = mock_mapper
1031
+
1032
+ integration = AWSKMSEvidenceIntegration(
1033
+ plan_id=123, region="us-east-1", account_id="123456789012", tags={"Environment": "Production"}
1034
+ )
1035
+ integration.raw_kms_data = [
1036
+ {"KeyId": "key-1", "RotationEnabled": True, "KeyManager": "CUSTOMER"},
1037
+ {"KeyId": "key-2", "RotationEnabled": False, "KeyManager": "CUSTOMER"},
1038
+ ]
1039
+
1040
+ result = integration._build_evidence_description("2023-12-01")
1041
+
1042
+ assert "AWS KMS Evidence" in result
1043
+ assert "2023-12-01" in result
1044
+ assert "us-east-1" in result
1045
+ assert "123456789012" in result
1046
+ assert "Environment=Production" in result
1047
+ assert "Total Keys" in result
1048
+ assert "SC-12" in result
1049
+ assert "SC-13" in result
1050
+
1051
+ @patch(f"{PATH}.KMSControlMapper")
1052
+ @patch(f"{PATH}.boto3.Session")
1053
+ @patch(f"{PATH}.Api")
1054
+ @patch(f"{PATH}.File")
1055
+ def test_upload_evidence_file_success(self, mock_file_class, mock_api_class, mock_session_class, mock_mapper_class):
1056
+ """Test uploading evidence file successfully."""
1057
+ mock_session = MagicMock()
1058
+ mock_session.client.return_value = MagicMock()
1059
+ mock_session_class.return_value = mock_session
1060
+
1061
+ mock_mapper = MagicMock()
1062
+ mock_mapper.assess_key_compliance.return_value = {"SC-12": "PASS"}
1063
+ mock_mapper_class.return_value = mock_mapper
1064
+
1065
+ mock_api = MagicMock()
1066
+ mock_api_class.return_value = mock_api
1067
+
1068
+ mock_file_class.upload_file_to_regscale.return_value = True
1069
+
1070
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
1071
+ integration.raw_kms_data = [{"KeyId": "test-key"}]
1072
+
1073
+ integration._upload_evidence_file(999, "2023-12-01")
1074
+
1075
+ mock_file_class.upload_file_to_regscale.assert_called_once()
1076
+ call_args = mock_file_class.upload_file_to_regscale.call_args[1]
1077
+ assert call_args["parent_id"] == 999
1078
+ assert call_args["parent_module"] == "evidence"
1079
+
1080
+ @patch(f"{PATH}.KMSControlMapper")
1081
+ @patch(f"{PATH}.boto3.Session")
1082
+ @patch(f"{PATH}.Api")
1083
+ @patch(f"{PATH}.File")
1084
+ def test_upload_evidence_file_failure(self, mock_file_class, mock_api_class, mock_session_class, mock_mapper_class):
1085
+ """Test uploading evidence file with failure."""
1086
+ mock_session = MagicMock()
1087
+ mock_session.client.return_value = MagicMock()
1088
+ mock_session_class.return_value = mock_session
1089
+
1090
+ mock_mapper = MagicMock()
1091
+ mock_mapper.assess_key_compliance.return_value = {}
1092
+ mock_mapper_class.return_value = mock_mapper
1093
+
1094
+ mock_api = MagicMock()
1095
+ mock_api_class.return_value = mock_api
1096
+
1097
+ mock_file_class.upload_file_to_regscale.return_value = False
1098
+
1099
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
1100
+ integration.raw_kms_data = []
1101
+
1102
+ integration._upload_evidence_file(999, "2023-12-01")
1103
+
1104
+ @patch(f"{PATH}.KMSControlMapper")
1105
+ @patch(f"{PATH}.boto3.Session")
1106
+ @patch(f"{PATH}.Api")
1107
+ @patch(f"{PATH}.File")
1108
+ def test_upload_evidence_file_exception(
1109
+ self, mock_file_class, mock_api_class, mock_session_class, mock_mapper_class
1110
+ ):
1111
+ """Test uploading evidence file with exception."""
1112
+ mock_session = MagicMock()
1113
+ mock_session.client.return_value = MagicMock()
1114
+ mock_session_class.return_value = mock_session
1115
+
1116
+ mock_mapper = MagicMock()
1117
+ mock_mapper.assess_key_compliance.side_effect = Exception("Test error")
1118
+ mock_mapper_class.return_value = mock_mapper
1119
+
1120
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
1121
+ integration.raw_kms_data = []
1122
+
1123
+ integration._upload_evidence_file(999, "2023-12-01")
1124
+
1125
+ @patch(f"{PATH}.KMSControlMapper")
1126
+ @patch(f"{PATH}.boto3.Session")
1127
+ @patch(f"{PATH}.EvidenceMapping")
1128
+ def test_link_evidence_to_ssp_success(self, mock_mapping_class, mock_session_class, mock_mapper_class):
1129
+ """Test linking evidence to SSP successfully."""
1130
+ mock_session = MagicMock()
1131
+ mock_session.client.return_value = MagicMock()
1132
+ mock_session_class.return_value = mock_session
1133
+
1134
+ mock_mapping = MagicMock()
1135
+ mock_mapping_class.return_value = mock_mapping
1136
+
1137
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
1138
+
1139
+ integration._link_evidence_to_ssp(999)
1140
+
1141
+ mock_mapping_class.assert_called_once_with(evidenceID=999, mappedID=123, mappingType="securityplans")
1142
+ mock_mapping.create.assert_called_once()
1143
+
1144
+ @patch(f"{PATH}.KMSControlMapper")
1145
+ @patch(f"{PATH}.boto3.Session")
1146
+ @patch(f"{PATH}.EvidenceMapping")
1147
+ def test_link_evidence_to_ssp_failure(self, mock_mapping_class, mock_session_class, mock_mapper_class):
1148
+ """Test linking evidence to SSP with failure."""
1149
+ mock_session = MagicMock()
1150
+ mock_session.client.return_value = MagicMock()
1151
+ mock_session_class.return_value = mock_session
1152
+
1153
+ mock_mapping = MagicMock()
1154
+ mock_mapping.create.side_effect = Exception("Test error")
1155
+ mock_mapping_class.return_value = mock_mapping
1156
+
1157
+ integration = AWSKMSEvidenceIntegration(plan_id=123)
1158
+
1159
+ integration._link_evidence_to_ssp(999)
1160
+
1161
+
1162
+ if __name__ == "__main__":
1163
+ pytest.main([__file__, "-v"])