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,333 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Due Date Handler for Scanner Integrations"""
4
+
5
+ import logging
6
+ from datetime import datetime
7
+ from typing import Any, Dict, Optional, Union
8
+
9
+ from regscale.core.app.application import Application
10
+ from regscale.core.utils.date import get_day_increment
11
+ from regscale.integrations.public.cisa import pull_cisa_kev
12
+ from regscale.models import regscale_models
13
+ from regscale.utils.threading import ThreadSafeDict
14
+
15
+ logger = logging.getLogger("regscale")
16
+
17
+ # Date format constant for consistent datetime string formatting
18
+ DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
19
+
20
+
21
+ class DueDateHandler:
22
+ """
23
+ Handles due date calculations for scanner integrations based on:
24
+ 1. Init.yaml timeline configurations per integration
25
+ 2. KEV (Known Exploited Vulnerabilities) dates from CISA
26
+ 3. Default severity-based timelines
27
+ 4. Configurable past due date validation (noPastDueDates setting)
28
+
29
+ Configuration Options:
30
+ - Global setting: issues.noPastDueDates (default: true)
31
+ - Per-integration: issues.{integration_name}.noPastDueDates
32
+
33
+ When noPastDueDates=true (default):
34
+ - Due dates calculated in the past are automatically adjusted to future dates
35
+ - Prevents API validation errors for past due dates
36
+
37
+ When noPastDueDates=false:
38
+ - Original due dates are preserved, even if they're in the past
39
+ - Useful for historical data or when past dates are intentionally required
40
+ """
41
+
42
+ def __init__(self, integration_name: str, config: Optional[Dict[str, Any]] = None):
43
+ """
44
+ Initialize the DueDateHandler for a specific integration
45
+
46
+ :param str integration_name: Name of the integration (e.g., 'wiz', 'qualys', 'tenable')
47
+ :param Optional[Dict[str, Any]] config: Optional config override, uses Application config if None
48
+ """
49
+ self.integration_name = integration_name.lower()
50
+ self.config = config or Application().config
51
+ self._kev_data: Optional[ThreadSafeDict] = None
52
+
53
+ # Default due date timelines (days)
54
+ self.default_timelines = {
55
+ regscale_models.IssueSeverity.Critical: 30,
56
+ "critical": 30,
57
+ regscale_models.IssueSeverity.High: 60,
58
+ "high": 60,
59
+ regscale_models.IssueSeverity.Moderate: 120,
60
+ "moderate": 120,
61
+ regscale_models.IssueSeverity.Low: 364,
62
+ "low": 364,
63
+ regscale_models.IssueSeverity.NotAssigned: 364, # Default to Low severity timeline
64
+ "notassigned": 364,
65
+ }
66
+
67
+ # Load integration-specific timelines from config
68
+ self.integration_timelines = self._load_integration_timelines()
69
+
70
+ # Load noPastDueDates setting (defaults to True)
71
+ self.no_past_due_dates = self._load_no_past_due_dates_setting()
72
+
73
+ def _load_integration_timelines(self) -> Dict[regscale_models.IssueSeverity, int]:
74
+ """
75
+ Load timeline configurations for this integration from init.yaml
76
+ mv
77
+ :return: Dictionary mapping severity to days
78
+ :rtype: Dict[regscale_models.IssueSeverity, int]
79
+ """
80
+ timelines = self.default_timelines.copy()
81
+
82
+ issues_config = self.config.get("issues", {})
83
+ integration_config = issues_config.get(self.integration_name, {})
84
+
85
+ if integration_config:
86
+ logger.debug(f"Found timeline config for {self.integration_name}: {integration_config}")
87
+
88
+ # Map config keys to severity levels
89
+ severity_mapping = {
90
+ "critical": regscale_models.IssueSeverity.Critical,
91
+ "high": regscale_models.IssueSeverity.High,
92
+ "moderate": regscale_models.IssueSeverity.Moderate,
93
+ "medium": regscale_models.IssueSeverity.Moderate, # Some integrations use 'medium'
94
+ "low": regscale_models.IssueSeverity.Low,
95
+ "notassigned": regscale_models.IssueSeverity.NotAssigned, # Handle unassigned severities
96
+ }
97
+
98
+ for config_key, severity in severity_mapping.items():
99
+ if config_key in integration_config:
100
+ timelines[severity] = integration_config[config_key]
101
+
102
+ return timelines
103
+
104
+ def _load_no_past_due_dates_setting(self) -> bool:
105
+ """
106
+ Load noPastDueDates setting for this integration from init.yaml
107
+
108
+ Configuration hierarchy:
109
+ 1. Integration-specific setting: issues.{integration_name}.noPastDueDates
110
+ 2. Global setting: issues.noPastDueDates
111
+ 3. Default: True
112
+
113
+ :return: True if past due dates should be automatically adjusted to future dates
114
+ :rtype: bool
115
+ """
116
+ issues_config = self.config.get("issues", {})
117
+ integration_config = issues_config.get(self.integration_name, {})
118
+
119
+ # Check integration-specific setting first
120
+ if "noPastDueDates" in integration_config:
121
+ setting = integration_config["noPastDueDates"]
122
+ logger.debug(f"Using integration-specific noPastDueDates={setting} for {self.integration_name}")
123
+ return bool(setting)
124
+
125
+ # Fall back to global setting
126
+ if "noPastDueDates" in issues_config:
127
+ setting = issues_config["noPastDueDates"]
128
+ logger.debug(f"Using global noPastDueDates={setting} for {self.integration_name}")
129
+ return bool(setting)
130
+
131
+ # Default to True (prevent past due dates)
132
+ logger.debug(f"Using default noPastDueDates=True for {self.integration_name}")
133
+ return True
134
+
135
+ def _get_kev_data(self) -> ThreadSafeDict:
136
+ """
137
+ Get KEV data from CISA, using cache if available
138
+
139
+ :return: Thread-safe dictionary containing KEV data
140
+ :rtype: ThreadSafeDict
141
+ """
142
+ if self._kev_data is None:
143
+ try:
144
+ kev_data = pull_cisa_kev()
145
+ self._kev_data = ThreadSafeDict()
146
+ self._kev_data.update(kev_data)
147
+ logger.debug("Loaded KEV data from CISA")
148
+ except Exception as e:
149
+ logger.warning(f"Failed to load KEV data: {e}")
150
+ self._kev_data = ThreadSafeDict()
151
+
152
+ return self._kev_data
153
+
154
+ def _should_use_kev(self) -> bool:
155
+ """
156
+ Check if this integration should use KEV dates
157
+
158
+ :return: True if KEV should be used for this integration
159
+ :rtype: bool
160
+ """
161
+ issues_config = self.config.get("issues", {})
162
+ integration_config = issues_config.get(self.integration_name, {})
163
+ return integration_config.get("useKev", True) # Default to True if not specified
164
+
165
+ def _get_kev_due_date(self, cve: str) -> Optional[str]:
166
+ """
167
+ Get the KEV due date for a specific CVE
168
+
169
+ :param str cve: The CVE identifier
170
+ :return: KEV due date string if found, None otherwise
171
+ :rtype: Optional[str]
172
+ """
173
+ if not self._should_use_kev() or not cve:
174
+ return None
175
+
176
+ kev_data = self._get_kev_data()
177
+
178
+ # Find the KEV entry for this CVE
179
+ kev_entry = next(
180
+ (entry for entry in kev_data.get("vulnerabilities", []) if entry.get("cveID", "").upper() == cve.upper()),
181
+ None,
182
+ )
183
+
184
+ if kev_entry:
185
+ kev_due_date = kev_entry.get("dueDate")
186
+ if kev_due_date:
187
+ logger.debug(f"Found KEV due date for {cve}: {kev_due_date}")
188
+ return kev_due_date
189
+
190
+ return None
191
+
192
+ def calculate_due_date(
193
+ self,
194
+ severity: Union[regscale_models.IssueSeverity, str],
195
+ created_date: str,
196
+ cve: Optional[str] = None,
197
+ title: Optional[str] = None,
198
+ ) -> str:
199
+ """
200
+ Calculate the due date for an issue based on severity, KEV status, and integration config
201
+
202
+ :param Union[regscale_models.IssueSeverity, str] severity: The severity of the issue
203
+ :param str created_date: The creation date of the issue
204
+ :param Optional[str] cve: The CVE identifier (if applicable)
205
+ :param Optional[str] title: The title of the issue (for additional context)
206
+ :return: The calculated due date string
207
+ :rtype: str
208
+ """
209
+ # First, check if this CVE has a KEV due date
210
+ if cve:
211
+ kev_due_date = self._get_kev_due_date(cve)
212
+ if kev_due_date:
213
+ # Parse the KEV due date and created date
214
+ try:
215
+ from dateutil.parser import parse as date_parse
216
+
217
+ kev_date = date_parse(kev_due_date).date()
218
+ created_dt = date_parse(created_date).date()
219
+
220
+ # If KEV due date is after creation date, use KEV date but ensure it's not in the past
221
+ # If KEV due date is before creation date, add the difference to creation date
222
+ if kev_date >= created_dt:
223
+ # Ensure KEV due date is not in the past
224
+ kev_due_validated = self._ensure_future_due_date(
225
+ kev_due_date, 30
226
+ ) # Use 30 days as fallback for KEV
227
+ logger.debug(f"Using KEV due date {kev_due_validated} for CVE {cve}")
228
+ return kev_due_validated
229
+ else:
230
+ # KEV date has passed, calculate new due date from creation
231
+ days_diff = (created_dt - kev_date).days
232
+ # Give at least 30 days from creation for critical KEV items
233
+ adjusted_days = max(30, days_diff)
234
+ calculated_due_date_obj = get_day_increment(start=created_date, days=adjusted_days)
235
+ calculated_due_date = datetime.combine(calculated_due_date_obj, datetime.min.time()).strftime(
236
+ DATETIME_FORMAT
237
+ )
238
+ # Ensure the adjusted due date is not in the past
239
+ due_date = self._ensure_future_due_date(calculated_due_date, adjusted_days)
240
+ logger.debug(f"KEV date passed, using adjusted due date {due_date} for CVE {cve}")
241
+ return due_date
242
+
243
+ except Exception as e:
244
+ logger.warning(f"Failed to parse KEV due date {kev_due_date}: {e}")
245
+
246
+ # Fall back to severity-based timeline from integration config
247
+ days = self.integration_timelines.get(severity, self.default_timelines[severity])
248
+ calculated_due_date_obj = get_day_increment(start=created_date, days=days)
249
+ calculated_due_date = datetime.combine(calculated_due_date_obj, datetime.min.time()).strftime(DATETIME_FORMAT)
250
+
251
+ # Ensure due date is never in the past (allow yesterday for timezone differences)
252
+ due_date = self._ensure_future_due_date(calculated_due_date, days)
253
+
254
+ logger.debug(
255
+ f"Using {self.integration_name} timeline: {severity.name if isinstance(severity, regscale_models.IssueSeverity) else severity} = {days} days, due date = {due_date}"
256
+ )
257
+
258
+ return due_date
259
+
260
+ def _ensure_future_due_date(self, calculated_due_date: str, original_days: int) -> str:
261
+ """
262
+ Ensure the due date is not in the past. If it is, set it to today + original timeline days.
263
+ Behavior is controlled by the noPastDueDates configuration setting.
264
+
265
+ :param str calculated_due_date: The originally calculated due date
266
+ :param int original_days: The original number of days for this severity
267
+ :return: A due date that respects the noPastDueDates setting
268
+ :rtype: str
269
+ """
270
+ # If noPastDueDates is disabled, return the original date without validation
271
+ if not self.no_past_due_dates:
272
+ logger.debug(
273
+ f"noPastDueDates=False for {self.integration_name}, returning original due date: {calculated_due_date}"
274
+ )
275
+ return calculated_due_date
276
+
277
+ from dateutil.parser import parse as date_parse
278
+ from datetime import date
279
+
280
+ try:
281
+ calculated_date = date_parse(calculated_due_date).date()
282
+ today = date.today()
283
+
284
+ # Use > instead of >= to ensure due dates set to "today" are moved to tomorrow
285
+ # This prevents API validation errors when the time component (00:00:00) has already passed
286
+ if calculated_date > today:
287
+ return calculated_due_date
288
+ else:
289
+ # Due date is in the past or today, calculate new due date from today
290
+ # Use minimum 1 day to ensure it's always in the future
291
+ safe_days = max(1, original_days)
292
+ new_due_date_obj = get_day_increment(start=today, days=safe_days)
293
+ new_due_date = datetime.combine(new_due_date_obj, datetime.min.time()).strftime(DATETIME_FORMAT)
294
+ logger.debug(
295
+ f"Due date {calculated_due_date} was in the past or today for {self.integration_name}. "
296
+ f"Adjusted to {new_due_date} ({safe_days} days from today)."
297
+ )
298
+ return new_due_date
299
+
300
+ except Exception as e:
301
+ logger.warning(f"Failed to validate due date {calculated_due_date}: {e}")
302
+ # If we can't parse the date, return a safe fallback only if noPastDueDates is enabled
303
+ if self.no_past_due_dates:
304
+ safe_days = max(1, original_days)
305
+ fallback_due_date_obj = get_day_increment(start=date.today(), days=safe_days)
306
+ return datetime.combine(fallback_due_date_obj, datetime.min.time()).strftime(DATETIME_FORMAT)
307
+ else:
308
+ return calculated_due_date
309
+
310
+ def get_integration_config(self) -> Dict[str, Any]:
311
+ """
312
+ Get the full integration configuration from init.yaml
313
+
314
+ :return: Integration configuration dictionary
315
+ :rtype: Dict[str, Any]
316
+ """
317
+ issues_config = self.config.get("issues", {})
318
+ return issues_config.get(self.integration_name, {})
319
+
320
+ def get_timeline_info(self) -> Dict[str, Any]:
321
+ """
322
+ Get information about current timeline configuration
323
+
324
+ :return: Dictionary with timeline information
325
+ :rtype: Dict[str, Any]
326
+ """
327
+ return {
328
+ "integration_name": self.integration_name,
329
+ "use_kev": self._should_use_kev(),
330
+ "no_past_due_dates": self.no_past_due_dates,
331
+ "timelines": {severity.name: days for severity, days in self.integration_timelines.items()},
332
+ "config_source": "init.yaml",
333
+ }
@@ -0,0 +1,291 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Milestone Manager for Issue Tracking
5
+
6
+ Handles creation of milestones for issues based on status transitions (created, reopened, closed).
7
+ Also handles backfilling of missing milestones for existing issues.
8
+ """
9
+ import logging
10
+ from typing import TYPE_CHECKING, List, Optional
11
+
12
+ from regscale.core.app.utils.app_utils import get_current_datetime
13
+ from regscale.integrations.variables import ScannerVariables
14
+ from regscale.models import regscale_models
15
+
16
+ if TYPE_CHECKING:
17
+ from regscale.integrations.scanner_integration import IntegrationFinding
18
+
19
+ logger = logging.getLogger("regscale")
20
+
21
+
22
+ class MilestoneManager:
23
+ """
24
+ Manages milestone creation for issues based on status transitions.
25
+
26
+ Milestones are created when:
27
+ - A new issue is created
28
+ - An existing issue is reopened (Closed -> Open)
29
+ - An existing issue is closed (Open -> Closed)
30
+ """
31
+
32
+ def __init__(self, integration_title: str, assessor_id: str, scan_date: str):
33
+ """
34
+ Initialize the milestone manager.
35
+
36
+ :param str integration_title: Name of the integration (used in milestone titles)
37
+ :param str assessor_id: ID of the assessor/responsible person for milestones
38
+ :param str scan_date: Date of the scan (used for new issue milestones)
39
+ """
40
+ self.integration_title = integration_title
41
+ self.assessor_id = assessor_id
42
+ self.scan_date = scan_date
43
+
44
+ def create_milestones_for_issue(
45
+ self,
46
+ issue: regscale_models.Issue,
47
+ finding: Optional["IntegrationFinding"] = None,
48
+ existing_issue: Optional[regscale_models.Issue] = None,
49
+ ) -> None:
50
+ """
51
+ Create appropriate milestones for an issue based on status transitions.
52
+
53
+ :param regscale_models.Issue issue: The issue to create milestones for
54
+ :param Optional[IntegrationFinding] finding: The finding data (for logging/context)
55
+ :param Optional[regscale_models.Issue] existing_issue: Previous state of issue for comparison
56
+ """
57
+ if not self._should_create_milestones(issue):
58
+ return
59
+
60
+ if self._should_create_reopened_milestone(existing_issue, issue):
61
+ self._create_reopened_milestone(issue, finding)
62
+ elif self._should_create_closed_milestone(existing_issue, issue):
63
+ self._create_closed_milestone(issue, finding)
64
+ elif not existing_issue:
65
+ self._create_new_issue_milestone(issue, finding)
66
+ else:
67
+ logger.debug(
68
+ "No milestone created for issue %s (no status transition detected)",
69
+ issue.id,
70
+ )
71
+
72
+ def _should_create_milestones(self, issue: regscale_models.Issue) -> bool:
73
+ """
74
+ Check if milestones should be created for this issue.
75
+
76
+ :param regscale_models.Issue issue: The issue to check
77
+ :return: True if milestones should be created
78
+ :rtype: bool
79
+ """
80
+ if not ScannerVariables.useMilestones:
81
+ logger.debug("Milestone creation disabled (useMilestones=False)")
82
+ return False
83
+
84
+ if not issue.id:
85
+ logger.debug("Cannot create milestone - issue has no ID")
86
+ return False
87
+
88
+ return True
89
+
90
+ def _should_create_reopened_milestone(
91
+ self, existing_issue: Optional[regscale_models.Issue], issue: regscale_models.Issue
92
+ ) -> bool:
93
+ """
94
+ Check if a reopened milestone should be created.
95
+
96
+ :param Optional[regscale_models.Issue] existing_issue: The existing issue
97
+ :param regscale_models.Issue issue: The current issue
98
+ :return: True if reopened milestone should be created
99
+ :rtype: bool
100
+ """
101
+ return (
102
+ existing_issue is not None
103
+ and existing_issue.status == regscale_models.IssueStatus.Closed
104
+ and issue.status == regscale_models.IssueStatus.Open
105
+ )
106
+
107
+ def _should_create_closed_milestone(
108
+ self, existing_issue: Optional[regscale_models.Issue], issue: regscale_models.Issue
109
+ ) -> bool:
110
+ """
111
+ Check if a closed milestone should be created.
112
+
113
+ :param Optional[regscale_models.Issue] existing_issue: The existing issue
114
+ :param regscale_models.Issue issue: The current issue
115
+ :return: True if closed milestone should be created
116
+ :rtype: bool
117
+ """
118
+ return (
119
+ existing_issue is not None
120
+ and existing_issue.status == regscale_models.IssueStatus.Open
121
+ and issue.status == regscale_models.IssueStatus.Closed
122
+ )
123
+
124
+ def _create_reopened_milestone(
125
+ self,
126
+ issue: regscale_models.Issue,
127
+ finding: Optional["IntegrationFinding"] = None,
128
+ ) -> None:
129
+ """
130
+ Create a milestone for a reopened issue.
131
+
132
+ :param regscale_models.Issue issue: The issue being reopened
133
+ :param Optional[IntegrationFinding] finding: The finding data (for logging)
134
+ """
135
+ milestone_date = get_current_datetime()
136
+ self._create_milestone(
137
+ issue=issue,
138
+ title=f"Issue reopened from {self.integration_title} scan",
139
+ milestone_date=milestone_date,
140
+ milestone_type="reopened",
141
+ finding=finding,
142
+ )
143
+
144
+ def _create_closed_milestone(
145
+ self,
146
+ issue: regscale_models.Issue,
147
+ finding: Optional["IntegrationFinding"] = None,
148
+ ) -> None:
149
+ """
150
+ Create a milestone for a closed issue.
151
+
152
+ :param regscale_models.Issue issue: The issue being closed
153
+ :param Optional[IntegrationFinding] finding: The finding data (for logging)
154
+ """
155
+ milestone_date = issue.dateCompleted or get_current_datetime()
156
+ self._create_milestone(
157
+ issue=issue,
158
+ title=f"Issue closed from {self.integration_title} scan",
159
+ milestone_date=milestone_date,
160
+ milestone_type="closed",
161
+ finding=finding,
162
+ )
163
+
164
+ def _create_new_issue_milestone(
165
+ self,
166
+ issue: regscale_models.Issue,
167
+ finding: Optional["IntegrationFinding"] = None,
168
+ ) -> None:
169
+ """
170
+ Create a milestone for a newly created issue.
171
+
172
+ :param regscale_models.Issue issue: The newly created issue
173
+ :param Optional[IntegrationFinding] finding: The finding data (for logging)
174
+ """
175
+ self._create_milestone(
176
+ issue=issue,
177
+ title=f"Issue created from {self.integration_title} scan",
178
+ milestone_date=self.scan_date,
179
+ milestone_type="new",
180
+ finding=finding,
181
+ )
182
+
183
+ def _create_milestone(
184
+ self,
185
+ issue: regscale_models.Issue,
186
+ title: str,
187
+ milestone_date: str,
188
+ milestone_type: str,
189
+ finding: Optional["IntegrationFinding"] = None,
190
+ ) -> None:
191
+ """
192
+ Create a milestone with error handling.
193
+
194
+ :param regscale_models.Issue issue: The issue to create milestone for
195
+ :param str title: Title of the milestone
196
+ :param str milestone_date: Date for the milestone
197
+ :param str milestone_type: Type of milestone (for logging: new, reopened, closed)
198
+ :param Optional[IntegrationFinding] finding: The finding data (for logging)
199
+ """
200
+ try:
201
+ regscale_models.Milestone(
202
+ title=title,
203
+ milestoneDate=milestone_date,
204
+ dateCompleted=get_current_datetime(),
205
+ responsiblePersonId=self.assessor_id,
206
+ parentID=issue.id,
207
+ parentModule="issues",
208
+ ).create_or_update()
209
+
210
+ logger.debug(f"Created {milestone_type} milestone for issue {issue.id}")
211
+
212
+ except Exception as e:
213
+ logger.warning(f"Failed to create {milestone_type} milestone for issue {issue.id}: {e}")
214
+
215
+ def get_existing_milestones(self, issue: regscale_models.Issue) -> List[regscale_models.Milestone]:
216
+ """
217
+ Get all existing milestones for an issue.
218
+
219
+ :param regscale_models.Issue issue: The issue to check
220
+ :return: List of existing milestones
221
+ :rtype: List[regscale_models.Milestone]
222
+ """
223
+ if not issue.id:
224
+ return []
225
+
226
+ try:
227
+ milestones = regscale_models.Milestone.get_by_parent(parent_id=issue.id, parent_module="issues")
228
+ return milestones if milestones else []
229
+ except Exception as e:
230
+ logger.debug(f"Could not retrieve milestones for issue {issue.id}: {e}")
231
+ return []
232
+
233
+ def has_creation_milestone(self, issue: regscale_models.Issue) -> bool:
234
+ """
235
+ Check if an issue has a creation milestone.
236
+
237
+ :param regscale_models.Issue issue: The issue to check
238
+ :return: True if creation milestone exists
239
+ :rtype: bool
240
+ """
241
+ milestones = self.get_existing_milestones(issue)
242
+
243
+ # Check for creation milestone patterns
244
+ creation_patterns = [
245
+ "Issue created from",
246
+ "created from",
247
+ ]
248
+
249
+ for milestone in milestones:
250
+ milestone_title = milestone.title.lower() if milestone.title else ""
251
+ if any(pattern.lower() in milestone_title for pattern in creation_patterns):
252
+ logger.debug(f"Found existing creation milestone for issue {issue.id}: {milestone.title}")
253
+ return True
254
+
255
+ return False
256
+
257
+ def ensure_creation_milestone_exists(
258
+ self,
259
+ issue: regscale_models.Issue,
260
+ finding: Optional["IntegrationFinding"] = None,
261
+ ) -> None:
262
+ """
263
+ Ensure an issue has a creation milestone, backfilling if necessary.
264
+
265
+ This method checks if an issue has a creation milestone. If not, it creates one
266
+ based on the issue's dateCreated field to backfill missing milestones.
267
+
268
+ :param regscale_models.Issue issue: The issue to check
269
+ :param Optional[IntegrationFinding] finding: The finding data (for logging)
270
+ """
271
+ if not self._should_create_milestones(issue):
272
+ return
273
+
274
+ # Check if creation milestone already exists
275
+ if self.has_creation_milestone(issue):
276
+ logger.debug(f"Issue {issue.id} already has creation milestone, skipping backfill")
277
+ return
278
+
279
+ # Backfill missing creation milestone
280
+ logger.debug(f"Backfilling missing creation milestone for issue {issue.id}")
281
+
282
+ # Use issue's dateCreated if available, otherwise use scan_date
283
+ milestone_date = issue.dateCreated if issue.dateCreated else self.scan_date
284
+
285
+ self._create_milestone(
286
+ issue=issue,
287
+ title=f"Issue created from {self.integration_title} scan",
288
+ milestone_date=milestone_date,
289
+ milestone_type="backfilled",
290
+ finding=finding,
291
+ )
@@ -17,6 +17,7 @@ from regscale.core.lazy_group import LazyGroup
17
17
  "import_poam": "regscale.integrations.public.fedramp.click.import_fedramp_poam_template",
18
18
  "import_drf": "regscale.integrations.public.fedramp.click.import_drf",
19
19
  "import_cis_crm": "regscale.integrations.public.fedramp.click.import_ciscrm",
20
+ "export_poam_v5": "regscale.integrations.public.fedramp.click.export_poam_v5",
20
21
  },
21
22
  name="fedramp",
22
23
  )
@@ -25,6 +26,19 @@ def fedramp():
25
26
  pass
26
27
 
27
28
 
29
+ @click.group(
30
+ cls=LazyGroup,
31
+ lazy_subcommands={
32
+ "import_ssp": "regscale.integrations.public.csam.csam.import_ssp",
33
+ "import_poam": "regscale.integrations.public.csam.csam.import_poam",
34
+ },
35
+ name="csam",
36
+ )
37
+ def csam():
38
+ """[BETA] Integration with DoJ's CSAM GRC Tool."""
39
+ pass
40
+
41
+
28
42
  @click.group(
29
43
  cls=LazyGroup,
30
44
  lazy_subcommands={