regscale-cli 6.16.0.0__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.

Potentially problematic release.


This version of regscale-cli might be problematic. Click here for more details.

Files changed (481) hide show
  1. regscale/__init__.py +1 -0
  2. regscale/airflow/__init__.py +9 -0
  3. regscale/airflow/azure/__init__.py +9 -0
  4. regscale/airflow/azure/cli.py +89 -0
  5. regscale/airflow/azure/upload_dags.py +116 -0
  6. regscale/airflow/click_dags.py +127 -0
  7. regscale/airflow/click_mixins.py +82 -0
  8. regscale/airflow/config.py +25 -0
  9. regscale/airflow/factories/__init__.py +0 -0
  10. regscale/airflow/factories/connections.py +58 -0
  11. regscale/airflow/factories/workflows.py +78 -0
  12. regscale/airflow/hierarchy.py +88 -0
  13. regscale/airflow/operators/__init__.py +0 -0
  14. regscale/airflow/operators/click.py +36 -0
  15. regscale/airflow/sensors/__init__.py +0 -0
  16. regscale/airflow/sensors/sql.py +107 -0
  17. regscale/airflow/sessions/__init__.py +0 -0
  18. regscale/airflow/sessions/sql/__init__.py +3 -0
  19. regscale/airflow/sessions/sql/queries.py +64 -0
  20. regscale/airflow/sessions/sql/sql_server_queries.py +248 -0
  21. regscale/airflow/tasks/__init__.py +0 -0
  22. regscale/airflow/tasks/branches.py +22 -0
  23. regscale/airflow/tasks/cli.py +116 -0
  24. regscale/airflow/tasks/click.py +73 -0
  25. regscale/airflow/tasks/debugging.py +9 -0
  26. regscale/airflow/tasks/groups.py +116 -0
  27. regscale/airflow/tasks/init.py +60 -0
  28. regscale/airflow/tasks/states.py +47 -0
  29. regscale/airflow/tasks/workflows.py +36 -0
  30. regscale/ansible/__init__.py +9 -0
  31. regscale/core/__init__.py +0 -0
  32. regscale/core/app/__init__.py +3 -0
  33. regscale/core/app/api.py +571 -0
  34. regscale/core/app/application.py +665 -0
  35. regscale/core/app/internal/__init__.py +136 -0
  36. regscale/core/app/internal/admin_actions.py +230 -0
  37. regscale/core/app/internal/assessments_editor.py +873 -0
  38. regscale/core/app/internal/catalog.py +316 -0
  39. regscale/core/app/internal/comparison.py +459 -0
  40. regscale/core/app/internal/control_editor.py +571 -0
  41. regscale/core/app/internal/encrypt.py +79 -0
  42. regscale/core/app/internal/evidence.py +1240 -0
  43. regscale/core/app/internal/file_uploads.py +151 -0
  44. regscale/core/app/internal/healthcheck.py +66 -0
  45. regscale/core/app/internal/login.py +305 -0
  46. regscale/core/app/internal/migrations.py +240 -0
  47. regscale/core/app/internal/model_editor.py +1701 -0
  48. regscale/core/app/internal/poam_editor.py +632 -0
  49. regscale/core/app/internal/workflow.py +105 -0
  50. regscale/core/app/logz.py +74 -0
  51. regscale/core/app/utils/XMLIR.py +258 -0
  52. regscale/core/app/utils/__init__.py +0 -0
  53. regscale/core/app/utils/api_handler.py +358 -0
  54. regscale/core/app/utils/app_utils.py +1110 -0
  55. regscale/core/app/utils/catalog_utils/__init__.py +0 -0
  56. regscale/core/app/utils/catalog_utils/common.py +91 -0
  57. regscale/core/app/utils/catalog_utils/compare_catalog.py +193 -0
  58. regscale/core/app/utils/catalog_utils/diagnostic_catalog.py +97 -0
  59. regscale/core/app/utils/catalog_utils/download_catalog.py +103 -0
  60. regscale/core/app/utils/catalog_utils/update_catalog.py +718 -0
  61. regscale/core/app/utils/catalog_utils/update_catalog_v2.py +1378 -0
  62. regscale/core/app/utils/catalog_utils/update_catalog_v3.py +1272 -0
  63. regscale/core/app/utils/catalog_utils/update_plans.py +334 -0
  64. regscale/core/app/utils/file_utils.py +238 -0
  65. regscale/core/app/utils/parser_utils.py +81 -0
  66. regscale/core/app/utils/pickle_file_handler.py +57 -0
  67. regscale/core/app/utils/regscale_utils.py +319 -0
  68. regscale/core/app/utils/report_utils.py +119 -0
  69. regscale/core/app/utils/variables.py +226 -0
  70. regscale/core/decorators.py +31 -0
  71. regscale/core/lazy_group.py +65 -0
  72. regscale/core/login.py +63 -0
  73. regscale/core/server/__init__.py +0 -0
  74. regscale/core/server/flask_api.py +473 -0
  75. regscale/core/server/helpers.py +373 -0
  76. regscale/core/server/rest.py +64 -0
  77. regscale/core/server/static/css/bootstrap.css +6030 -0
  78. regscale/core/server/static/css/bootstrap.min.css +6 -0
  79. regscale/core/server/static/css/main.css +176 -0
  80. regscale/core/server/static/images/regscale-cli.svg +49 -0
  81. regscale/core/server/static/images/regscale.svg +38 -0
  82. regscale/core/server/templates/base.html +74 -0
  83. regscale/core/server/templates/index.html +43 -0
  84. regscale/core/server/templates/login.html +28 -0
  85. regscale/core/server/templates/make_base64.html +22 -0
  86. regscale/core/server/templates/upload_STIG.html +109 -0
  87. regscale/core/server/templates/upload_STIG_result.html +26 -0
  88. regscale/core/server/templates/upload_ssp.html +144 -0
  89. regscale/core/server/templates/upload_ssp_result.html +128 -0
  90. regscale/core/static/__init__.py +0 -0
  91. regscale/core/static/regex.py +14 -0
  92. regscale/core/utils/__init__.py +117 -0
  93. regscale/core/utils/click_utils.py +13 -0
  94. regscale/core/utils/date.py +238 -0
  95. regscale/core/utils/graphql.py +254 -0
  96. regscale/core/utils/urls.py +23 -0
  97. regscale/dev/__init__.py +6 -0
  98. regscale/dev/analysis.py +454 -0
  99. regscale/dev/cli.py +235 -0
  100. regscale/dev/code_gen.py +492 -0
  101. regscale/dev/dirs.py +69 -0
  102. regscale/dev/docs.py +384 -0
  103. regscale/dev/monitoring.py +26 -0
  104. regscale/dev/profiling.py +216 -0
  105. regscale/exceptions/__init__.py +4 -0
  106. regscale/exceptions/license_exception.py +7 -0
  107. regscale/exceptions/validation_exception.py +9 -0
  108. regscale/integrations/__init__.py +1 -0
  109. regscale/integrations/commercial/__init__.py +486 -0
  110. regscale/integrations/commercial/ad.py +433 -0
  111. regscale/integrations/commercial/amazon/__init__.py +0 -0
  112. regscale/integrations/commercial/amazon/common.py +106 -0
  113. regscale/integrations/commercial/aqua/__init__.py +0 -0
  114. regscale/integrations/commercial/aqua/aqua.py +91 -0
  115. regscale/integrations/commercial/aws/__init__.py +6 -0
  116. regscale/integrations/commercial/aws/cli.py +322 -0
  117. regscale/integrations/commercial/aws/inventory/__init__.py +110 -0
  118. regscale/integrations/commercial/aws/inventory/base.py +64 -0
  119. regscale/integrations/commercial/aws/inventory/resources/__init__.py +19 -0
  120. regscale/integrations/commercial/aws/inventory/resources/compute.py +234 -0
  121. regscale/integrations/commercial/aws/inventory/resources/containers.py +113 -0
  122. regscale/integrations/commercial/aws/inventory/resources/database.py +101 -0
  123. regscale/integrations/commercial/aws/inventory/resources/integration.py +237 -0
  124. regscale/integrations/commercial/aws/inventory/resources/networking.py +253 -0
  125. regscale/integrations/commercial/aws/inventory/resources/security.py +240 -0
  126. regscale/integrations/commercial/aws/inventory/resources/storage.py +91 -0
  127. regscale/integrations/commercial/aws/scanner.py +823 -0
  128. regscale/integrations/commercial/azure/__init__.py +0 -0
  129. regscale/integrations/commercial/azure/common.py +32 -0
  130. regscale/integrations/commercial/azure/intune.py +488 -0
  131. regscale/integrations/commercial/azure/scanner.py +49 -0
  132. regscale/integrations/commercial/burp.py +78 -0
  133. regscale/integrations/commercial/cpe.py +144 -0
  134. regscale/integrations/commercial/crowdstrike.py +1117 -0
  135. regscale/integrations/commercial/defender.py +1511 -0
  136. regscale/integrations/commercial/dependabot.py +210 -0
  137. regscale/integrations/commercial/durosuite/__init__.py +0 -0
  138. regscale/integrations/commercial/durosuite/api.py +1546 -0
  139. regscale/integrations/commercial/durosuite/process_devices.py +101 -0
  140. regscale/integrations/commercial/durosuite/scanner.py +637 -0
  141. regscale/integrations/commercial/durosuite/variables.py +21 -0
  142. regscale/integrations/commercial/ecr.py +90 -0
  143. regscale/integrations/commercial/gcp/__init__.py +237 -0
  144. regscale/integrations/commercial/gcp/auth.py +96 -0
  145. regscale/integrations/commercial/gcp/control_tests.py +238 -0
  146. regscale/integrations/commercial/gcp/variables.py +18 -0
  147. regscale/integrations/commercial/gitlab.py +332 -0
  148. regscale/integrations/commercial/grype.py +165 -0
  149. regscale/integrations/commercial/ibm.py +90 -0
  150. regscale/integrations/commercial/import_all/__init__.py +0 -0
  151. regscale/integrations/commercial/import_all/import_all_cmd.py +467 -0
  152. regscale/integrations/commercial/import_all/scan_file_fingerprints.json +27 -0
  153. regscale/integrations/commercial/jira.py +1046 -0
  154. regscale/integrations/commercial/mappings/__init__.py +0 -0
  155. regscale/integrations/commercial/mappings/csf_controls.json +713 -0
  156. regscale/integrations/commercial/mappings/nist_800_53_r5_controls.json +1516 -0
  157. regscale/integrations/commercial/nessus/__init__.py +0 -0
  158. regscale/integrations/commercial/nessus/nessus_utils.py +429 -0
  159. regscale/integrations/commercial/nessus/scanner.py +416 -0
  160. regscale/integrations/commercial/nexpose.py +90 -0
  161. regscale/integrations/commercial/okta.py +798 -0
  162. regscale/integrations/commercial/opentext/__init__.py +0 -0
  163. regscale/integrations/commercial/opentext/click.py +99 -0
  164. regscale/integrations/commercial/opentext/scanner.py +143 -0
  165. regscale/integrations/commercial/prisma.py +91 -0
  166. regscale/integrations/commercial/qualys.py +1462 -0
  167. regscale/integrations/commercial/salesforce.py +980 -0
  168. regscale/integrations/commercial/sap/__init__.py +0 -0
  169. regscale/integrations/commercial/sap/click.py +31 -0
  170. regscale/integrations/commercial/sap/sysdig/__init__.py +0 -0
  171. regscale/integrations/commercial/sap/sysdig/click.py +57 -0
  172. regscale/integrations/commercial/sap/sysdig/sysdig_scanner.py +190 -0
  173. regscale/integrations/commercial/sap/tenable/__init__.py +0 -0
  174. regscale/integrations/commercial/sap/tenable/click.py +49 -0
  175. regscale/integrations/commercial/sap/tenable/scanner.py +196 -0
  176. regscale/integrations/commercial/servicenow.py +1756 -0
  177. regscale/integrations/commercial/sicura/__init__.py +0 -0
  178. regscale/integrations/commercial/sicura/api.py +855 -0
  179. regscale/integrations/commercial/sicura/commands.py +73 -0
  180. regscale/integrations/commercial/sicura/scanner.py +481 -0
  181. regscale/integrations/commercial/sicura/variables.py +16 -0
  182. regscale/integrations/commercial/snyk.py +90 -0
  183. regscale/integrations/commercial/sonarcloud.py +260 -0
  184. regscale/integrations/commercial/sqlserver.py +369 -0
  185. regscale/integrations/commercial/stig_mapper_integration/__init__.py +0 -0
  186. regscale/integrations/commercial/stig_mapper_integration/click_commands.py +38 -0
  187. regscale/integrations/commercial/stig_mapper_integration/mapping_engine.py +353 -0
  188. regscale/integrations/commercial/stigv2/__init__.py +0 -0
  189. regscale/integrations/commercial/stigv2/ckl_parser.py +349 -0
  190. regscale/integrations/commercial/stigv2/click_commands.py +95 -0
  191. regscale/integrations/commercial/stigv2/stig_integration.py +202 -0
  192. regscale/integrations/commercial/synqly/__init__.py +0 -0
  193. regscale/integrations/commercial/synqly/assets.py +46 -0
  194. regscale/integrations/commercial/synqly/ticketing.py +132 -0
  195. regscale/integrations/commercial/synqly/vulnerabilities.py +223 -0
  196. regscale/integrations/commercial/synqly_jira.py +840 -0
  197. regscale/integrations/commercial/tenablev2/__init__.py +0 -0
  198. regscale/integrations/commercial/tenablev2/authenticate.py +31 -0
  199. regscale/integrations/commercial/tenablev2/click.py +1584 -0
  200. regscale/integrations/commercial/tenablev2/scanner.py +504 -0
  201. regscale/integrations/commercial/tenablev2/stig_parsers.py +140 -0
  202. regscale/integrations/commercial/tenablev2/utils.py +78 -0
  203. regscale/integrations/commercial/tenablev2/variables.py +17 -0
  204. regscale/integrations/commercial/trivy.py +162 -0
  205. regscale/integrations/commercial/veracode.py +96 -0
  206. regscale/integrations/commercial/wizv2/WizDataMixin.py +97 -0
  207. regscale/integrations/commercial/wizv2/__init__.py +0 -0
  208. regscale/integrations/commercial/wizv2/click.py +429 -0
  209. regscale/integrations/commercial/wizv2/constants.py +1001 -0
  210. regscale/integrations/commercial/wizv2/issue.py +361 -0
  211. regscale/integrations/commercial/wizv2/models.py +112 -0
  212. regscale/integrations/commercial/wizv2/parsers.py +339 -0
  213. regscale/integrations/commercial/wizv2/sbom.py +115 -0
  214. regscale/integrations/commercial/wizv2/scanner.py +416 -0
  215. regscale/integrations/commercial/wizv2/utils.py +796 -0
  216. regscale/integrations/commercial/wizv2/variables.py +39 -0
  217. regscale/integrations/commercial/wizv2/wiz_auth.py +159 -0
  218. regscale/integrations/commercial/xray.py +91 -0
  219. regscale/integrations/integration/__init__.py +2 -0
  220. regscale/integrations/integration/integration.py +26 -0
  221. regscale/integrations/integration/inventory.py +17 -0
  222. regscale/integrations/integration/issue.py +100 -0
  223. regscale/integrations/integration_override.py +149 -0
  224. regscale/integrations/public/__init__.py +103 -0
  225. regscale/integrations/public/cisa.py +641 -0
  226. regscale/integrations/public/criticality_updater.py +70 -0
  227. regscale/integrations/public/emass.py +411 -0
  228. regscale/integrations/public/emass_slcm_import.py +697 -0
  229. regscale/integrations/public/fedramp/__init__.py +0 -0
  230. regscale/integrations/public/fedramp/appendix_parser.py +548 -0
  231. regscale/integrations/public/fedramp/click.py +479 -0
  232. regscale/integrations/public/fedramp/components.py +714 -0
  233. regscale/integrations/public/fedramp/docx_parser.py +259 -0
  234. regscale/integrations/public/fedramp/fedramp_cis_crm.py +1124 -0
  235. regscale/integrations/public/fedramp/fedramp_common.py +3181 -0
  236. regscale/integrations/public/fedramp/fedramp_docx.py +388 -0
  237. regscale/integrations/public/fedramp/fedramp_five.py +2343 -0
  238. regscale/integrations/public/fedramp/fedramp_traversal.py +138 -0
  239. regscale/integrations/public/fedramp/import_fedramp_r4_ssp.py +279 -0
  240. regscale/integrations/public/fedramp/import_workbook.py +495 -0
  241. regscale/integrations/public/fedramp/inventory_items.py +244 -0
  242. regscale/integrations/public/fedramp/mappings/__init__.py +0 -0
  243. regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +7388 -0
  244. regscale/integrations/public/fedramp/mappings/fedramp_r5_params.json +8636 -0
  245. regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +9605 -0
  246. regscale/integrations/public/fedramp/mappings/system_roles.py +34 -0
  247. regscale/integrations/public/fedramp/mappings/user.py +175 -0
  248. regscale/integrations/public/fedramp/mappings/values.py +141 -0
  249. regscale/integrations/public/fedramp/markdown_parser.py +150 -0
  250. regscale/integrations/public/fedramp/metadata.py +689 -0
  251. regscale/integrations/public/fedramp/models/__init__.py +59 -0
  252. regscale/integrations/public/fedramp/models/leveraged_auth_new.py +168 -0
  253. regscale/integrations/public/fedramp/models/poam_importer.py +522 -0
  254. regscale/integrations/public/fedramp/parts_mapper.py +107 -0
  255. regscale/integrations/public/fedramp/poam/__init__.py +0 -0
  256. regscale/integrations/public/fedramp/poam/scanner.py +851 -0
  257. regscale/integrations/public/fedramp/properties.py +201 -0
  258. regscale/integrations/public/fedramp/reporting.py +84 -0
  259. regscale/integrations/public/fedramp/resources.py +496 -0
  260. regscale/integrations/public/fedramp/rosetta.py +110 -0
  261. regscale/integrations/public/fedramp/ssp_logger.py +87 -0
  262. regscale/integrations/public/fedramp/system_characteristics.py +922 -0
  263. regscale/integrations/public/fedramp/system_control_implementations.py +582 -0
  264. regscale/integrations/public/fedramp/system_implementation.py +190 -0
  265. regscale/integrations/public/fedramp/xml_utils.py +87 -0
  266. regscale/integrations/public/nist_catalog.py +275 -0
  267. regscale/integrations/public/oscal.py +1946 -0
  268. regscale/integrations/public/otx.py +169 -0
  269. regscale/integrations/scanner_integration.py +2692 -0
  270. regscale/integrations/variables.py +25 -0
  271. regscale/models/__init__.py +7 -0
  272. regscale/models/app_models/__init__.py +5 -0
  273. regscale/models/app_models/catalog_compare.py +213 -0
  274. regscale/models/app_models/click.py +252 -0
  275. regscale/models/app_models/datetime_encoder.py +21 -0
  276. regscale/models/app_models/import_validater.py +321 -0
  277. regscale/models/app_models/mapping.py +260 -0
  278. regscale/models/app_models/pipeline.py +37 -0
  279. regscale/models/click_models.py +413 -0
  280. regscale/models/config.py +154 -0
  281. regscale/models/email_style.css +67 -0
  282. regscale/models/hierarchy.py +8 -0
  283. regscale/models/inspect_models.py +79 -0
  284. regscale/models/integration_models/__init__.py +0 -0
  285. regscale/models/integration_models/amazon_models/__init__.py +0 -0
  286. regscale/models/integration_models/amazon_models/inspector.py +262 -0
  287. regscale/models/integration_models/amazon_models/inspector_scan.py +206 -0
  288. regscale/models/integration_models/aqua.py +247 -0
  289. regscale/models/integration_models/azure_alerts.py +255 -0
  290. regscale/models/integration_models/base64.py +23 -0
  291. regscale/models/integration_models/burp.py +433 -0
  292. regscale/models/integration_models/burp_models.py +128 -0
  293. regscale/models/integration_models/cisa_kev_data.json +19333 -0
  294. regscale/models/integration_models/defender_data.py +93 -0
  295. regscale/models/integration_models/defenderimport.py +143 -0
  296. regscale/models/integration_models/drf.py +443 -0
  297. regscale/models/integration_models/ecr_models/__init__.py +0 -0
  298. regscale/models/integration_models/ecr_models/data.py +69 -0
  299. regscale/models/integration_models/ecr_models/ecr.py +239 -0
  300. regscale/models/integration_models/flat_file_importer.py +1079 -0
  301. regscale/models/integration_models/grype_import.py +247 -0
  302. regscale/models/integration_models/ibm.py +126 -0
  303. regscale/models/integration_models/implementation_results.py +85 -0
  304. regscale/models/integration_models/nexpose.py +140 -0
  305. regscale/models/integration_models/prisma.py +202 -0
  306. regscale/models/integration_models/qualys.py +720 -0
  307. regscale/models/integration_models/qualys_scanner.py +160 -0
  308. regscale/models/integration_models/sbom/__init__.py +0 -0
  309. regscale/models/integration_models/sbom/cyclone_dx.py +139 -0
  310. regscale/models/integration_models/send_reminders.py +620 -0
  311. regscale/models/integration_models/snyk.py +155 -0
  312. regscale/models/integration_models/synqly_models/__init__.py +0 -0
  313. regscale/models/integration_models/synqly_models/capabilities.json +1 -0
  314. regscale/models/integration_models/synqly_models/connector_types.py +22 -0
  315. regscale/models/integration_models/synqly_models/connectors/__init__.py +7 -0
  316. regscale/models/integration_models/synqly_models/connectors/assets.py +97 -0
  317. regscale/models/integration_models/synqly_models/connectors/ticketing.py +583 -0
  318. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +169 -0
  319. regscale/models/integration_models/synqly_models/ocsf_mapper.py +331 -0
  320. regscale/models/integration_models/synqly_models/param.py +72 -0
  321. regscale/models/integration_models/synqly_models/synqly_model.py +733 -0
  322. regscale/models/integration_models/synqly_models/tenants.py +39 -0
  323. regscale/models/integration_models/tenable_models/__init__.py +0 -0
  324. regscale/models/integration_models/tenable_models/integration.py +187 -0
  325. regscale/models/integration_models/tenable_models/models.py +513 -0
  326. regscale/models/integration_models/trivy_import.py +231 -0
  327. regscale/models/integration_models/veracode.py +217 -0
  328. regscale/models/integration_models/xray.py +135 -0
  329. regscale/models/locking.py +100 -0
  330. regscale/models/platform.py +110 -0
  331. regscale/models/regscale_models/__init__.py +67 -0
  332. regscale/models/regscale_models/assessment.py +570 -0
  333. regscale/models/regscale_models/assessment_plan.py +52 -0
  334. regscale/models/regscale_models/asset.py +567 -0
  335. regscale/models/regscale_models/asset_mapping.py +190 -0
  336. regscale/models/regscale_models/case.py +42 -0
  337. regscale/models/regscale_models/catalog.py +261 -0
  338. regscale/models/regscale_models/cci.py +46 -0
  339. regscale/models/regscale_models/change.py +167 -0
  340. regscale/models/regscale_models/checklist.py +372 -0
  341. regscale/models/regscale_models/comment.py +49 -0
  342. regscale/models/regscale_models/compliance_settings.py +112 -0
  343. regscale/models/regscale_models/component.py +412 -0
  344. regscale/models/regscale_models/component_mapping.py +65 -0
  345. regscale/models/regscale_models/control.py +38 -0
  346. regscale/models/regscale_models/control_implementation.py +1128 -0
  347. regscale/models/regscale_models/control_objective.py +261 -0
  348. regscale/models/regscale_models/control_parameter.py +100 -0
  349. regscale/models/regscale_models/control_test.py +34 -0
  350. regscale/models/regscale_models/control_test_plan.py +75 -0
  351. regscale/models/regscale_models/control_test_result.py +52 -0
  352. regscale/models/regscale_models/custom_field.py +245 -0
  353. regscale/models/regscale_models/data.py +109 -0
  354. regscale/models/regscale_models/data_center.py +40 -0
  355. regscale/models/regscale_models/deviation.py +203 -0
  356. regscale/models/regscale_models/email.py +97 -0
  357. regscale/models/regscale_models/evidence.py +47 -0
  358. regscale/models/regscale_models/evidence_mapping.py +40 -0
  359. regscale/models/regscale_models/facility.py +59 -0
  360. regscale/models/regscale_models/file.py +382 -0
  361. regscale/models/regscale_models/filetag.py +37 -0
  362. regscale/models/regscale_models/form_field_value.py +94 -0
  363. regscale/models/regscale_models/group.py +169 -0
  364. regscale/models/regscale_models/implementation_objective.py +335 -0
  365. regscale/models/regscale_models/implementation_option.py +275 -0
  366. regscale/models/regscale_models/implementation_role.py +33 -0
  367. regscale/models/regscale_models/incident.py +177 -0
  368. regscale/models/regscale_models/interconnection.py +43 -0
  369. regscale/models/regscale_models/issue.py +1176 -0
  370. regscale/models/regscale_models/leveraged_authorization.py +125 -0
  371. regscale/models/regscale_models/line_of_inquiry.py +52 -0
  372. regscale/models/regscale_models/link.py +205 -0
  373. regscale/models/regscale_models/meta_data.py +64 -0
  374. regscale/models/regscale_models/mixins/__init__.py +0 -0
  375. regscale/models/regscale_models/mixins/parent_cache.py +124 -0
  376. regscale/models/regscale_models/module.py +224 -0
  377. regscale/models/regscale_models/modules.py +191 -0
  378. regscale/models/regscale_models/objective.py +14 -0
  379. regscale/models/regscale_models/parameter.py +87 -0
  380. regscale/models/regscale_models/ports_protocol.py +81 -0
  381. regscale/models/regscale_models/privacy.py +89 -0
  382. regscale/models/regscale_models/profile.py +50 -0
  383. regscale/models/regscale_models/profile_link.py +68 -0
  384. regscale/models/regscale_models/profile_mapping.py +124 -0
  385. regscale/models/regscale_models/project.py +63 -0
  386. regscale/models/regscale_models/property.py +278 -0
  387. regscale/models/regscale_models/question.py +85 -0
  388. regscale/models/regscale_models/questionnaire.py +87 -0
  389. regscale/models/regscale_models/questionnaire_instance.py +177 -0
  390. regscale/models/regscale_models/rbac.py +132 -0
  391. regscale/models/regscale_models/reference.py +86 -0
  392. regscale/models/regscale_models/regscale_model.py +1643 -0
  393. regscale/models/regscale_models/requirement.py +29 -0
  394. regscale/models/regscale_models/risk.py +274 -0
  395. regscale/models/regscale_models/sbom.py +54 -0
  396. regscale/models/regscale_models/scan_history.py +436 -0
  397. regscale/models/regscale_models/search.py +53 -0
  398. regscale/models/regscale_models/security_control.py +132 -0
  399. regscale/models/regscale_models/security_plan.py +204 -0
  400. regscale/models/regscale_models/software_inventory.py +159 -0
  401. regscale/models/regscale_models/stake_holder.py +64 -0
  402. regscale/models/regscale_models/stig.py +647 -0
  403. regscale/models/regscale_models/supply_chain.py +152 -0
  404. regscale/models/regscale_models/system_role.py +188 -0
  405. regscale/models/regscale_models/system_role_external_assignment.py +40 -0
  406. regscale/models/regscale_models/tag.py +37 -0
  407. regscale/models/regscale_models/tag_mapping.py +19 -0
  408. regscale/models/regscale_models/task.py +133 -0
  409. regscale/models/regscale_models/threat.py +196 -0
  410. regscale/models/regscale_models/user.py +175 -0
  411. regscale/models/regscale_models/user_group.py +55 -0
  412. regscale/models/regscale_models/vulnerability.py +242 -0
  413. regscale/models/regscale_models/vulnerability_mapping.py +162 -0
  414. regscale/models/regscale_models/workflow.py +55 -0
  415. regscale/models/regscale_models/workflow_action.py +34 -0
  416. regscale/models/regscale_models/workflow_instance.py +269 -0
  417. regscale/models/regscale_models/workflow_instance_step.py +114 -0
  418. regscale/models/regscale_models/workflow_template.py +58 -0
  419. regscale/models/regscale_models/workflow_template_step.py +45 -0
  420. regscale/regscale.py +815 -0
  421. regscale/utils/__init__.py +7 -0
  422. regscale/utils/b64conversion.py +14 -0
  423. regscale/utils/click_utils.py +118 -0
  424. regscale/utils/decorators.py +48 -0
  425. regscale/utils/dict_utils.py +59 -0
  426. regscale/utils/files.py +79 -0
  427. regscale/utils/fxns.py +30 -0
  428. regscale/utils/graphql_client.py +113 -0
  429. regscale/utils/lists.py +16 -0
  430. regscale/utils/numbers.py +12 -0
  431. regscale/utils/shell.py +148 -0
  432. regscale/utils/string.py +121 -0
  433. regscale/utils/synqly_utils.py +165 -0
  434. regscale/utils/threading/__init__.py +8 -0
  435. regscale/utils/threading/threadhandler.py +131 -0
  436. regscale/utils/threading/threadsafe_counter.py +47 -0
  437. regscale/utils/threading/threadsafe_dict.py +242 -0
  438. regscale/utils/threading/threadsafe_list.py +83 -0
  439. regscale/utils/version.py +104 -0
  440. regscale/validation/__init__.py +0 -0
  441. regscale/validation/address.py +37 -0
  442. regscale/validation/record.py +48 -0
  443. regscale/visualization/__init__.py +5 -0
  444. regscale/visualization/click.py +34 -0
  445. regscale_cli-6.16.0.0.dist-info/LICENSE +21 -0
  446. regscale_cli-6.16.0.0.dist-info/METADATA +659 -0
  447. regscale_cli-6.16.0.0.dist-info/RECORD +481 -0
  448. regscale_cli-6.16.0.0.dist-info/WHEEL +5 -0
  449. regscale_cli-6.16.0.0.dist-info/entry_points.txt +6 -0
  450. regscale_cli-6.16.0.0.dist-info/top_level.txt +2 -0
  451. tests/fixtures/__init__.py +2 -0
  452. tests/fixtures/api.py +87 -0
  453. tests/fixtures/models.py +91 -0
  454. tests/fixtures/test_fixture.py +144 -0
  455. tests/mocks/__init__.py +0 -0
  456. tests/mocks/objects.py +3 -0
  457. tests/mocks/response.py +32 -0
  458. tests/mocks/xml.py +13 -0
  459. tests/regscale/__init__.py +0 -0
  460. tests/regscale/core/__init__.py +0 -0
  461. tests/regscale/core/test_api.py +232 -0
  462. tests/regscale/core/test_app.py +406 -0
  463. tests/regscale/core/test_login.py +37 -0
  464. tests/regscale/core/test_logz.py +66 -0
  465. tests/regscale/core/test_sbom_generator.py +87 -0
  466. tests/regscale/core/test_validation_utils.py +163 -0
  467. tests/regscale/core/test_version.py +78 -0
  468. tests/regscale/models/__init__.py +0 -0
  469. tests/regscale/models/test_asset.py +71 -0
  470. tests/regscale/models/test_config.py +26 -0
  471. tests/regscale/models/test_control_implementation.py +27 -0
  472. tests/regscale/models/test_import.py +97 -0
  473. tests/regscale/models/test_issue.py +36 -0
  474. tests/regscale/models/test_mapping.py +52 -0
  475. tests/regscale/models/test_platform.py +31 -0
  476. tests/regscale/models/test_regscale_model.py +346 -0
  477. tests/regscale/models/test_report.py +32 -0
  478. tests/regscale/models/test_tenable_integrations.py +118 -0
  479. tests/regscale/models/test_user_model.py +121 -0
  480. tests/regscale/test_about.py +19 -0
  481. tests/regscale/test_authorization.py +65 -0
@@ -0,0 +1,718 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Add functionality to upgrade application catalog information via API."""
4
+
5
+
6
+ # pylint: disable=line-too-long, global-statement, global-at-module-level, abstract-class-instantiated, too-many-lines
7
+
8
+ # Standard Imports
9
+ import contextlib
10
+ import operator
11
+ import sys
12
+ from typing import Optional, Tuple
13
+
14
+ import click # type: ignore
15
+ from requests import JSONDecodeError # type: ignore
16
+
17
+ from regscale.core.app.api import Api
18
+ from regscale.core.app.logz import create_logger
19
+ from regscale.core.app.utils.app_utils import create_progress_object, error_and_exit
20
+ from regscale.core.app.utils.catalog_utils.common import get_new_catalog
21
+ from regscale.models.app_models.catalog_compare import CatalogCompare
22
+
23
+ # create logger function to log to the console
24
+ logger = create_logger()
25
+ # create progress object
26
+ job_progress = create_progress_object()
27
+
28
+ DOWNLOAD_URL: str = ""
29
+ CAT_UUID: str = ""
30
+ SECURITY_CONTROL_ID_KEY: list = []
31
+
32
+
33
+ def display_menu() -> None:
34
+ """
35
+ Start the process of comparing two catalogs, one from the master catalog list
36
+ and one from the user's RegScale instance
37
+
38
+ :rtype: None
39
+ """
40
+ # set system environment variables
41
+ api = Api()
42
+ api.timeout = 180
43
+
44
+ # start menu build process
45
+ menu_counter: list = []
46
+ # import master catalog list
47
+ data = CatalogCompare.get_master_catalogs(api=api)
48
+ # sort master catalogue list
49
+ catalogues = data["catalogues"]
50
+ catalogues.sort(key=operator.itemgetter("id"))
51
+ for i, catalog in enumerate(catalogues):
52
+ # print each catalog in the master catalog list
53
+ print(f'{catalog["id"]}: {catalog["value"]}')
54
+ menu_counter.append(i)
55
+ # set status to False to run loop
56
+ status: bool = False
57
+ while not status:
58
+ # select catalog to run diagnostic
59
+ value = click.prompt(
60
+ "Please enter the number of the catalog you would like to run diagnostics on",
61
+ type=int,
62
+ )
63
+ # check if value exist that is selected
64
+ if value < min(menu_counter) or value > max(menu_counter):
65
+ print("That is not a valid selection, please try again")
66
+ else:
67
+ status = True
68
+ # choose catalog to run diagnostics on
69
+ for catalog in catalogues:
70
+ if catalog["id"] == value:
71
+ global CAT_UUID
72
+ CAT_UUID = catalog["metadata"]["uuid"]
73
+ if catalog["download"] is True:
74
+ if catalog["paid"] is False:
75
+ global DOWNLOAD_URL
76
+ DOWNLOAD_URL = catalog["link"]
77
+ if catalog["paid"] is True:
78
+ logger.warning("This is a paid catalog, please contact RegScale customer support.")
79
+ sys.exit()
80
+ break
81
+ compare_and_update_catalog_elements(api=api)
82
+
83
+
84
+ def compare_and_update_catalog_elements(api: Api) -> None:
85
+ """
86
+ Function to compare and update elements between catalogs
87
+
88
+ :param Api api: Api object
89
+ :rtype: None
90
+ """
91
+ new_catalog_elements = parse_new_catalog()
92
+ old_catalog_elements = parse_old_catalog(api=api)
93
+ update_security_controls(
94
+ new_security_controls=new_catalog_elements[0],
95
+ old_security_controls=old_catalog_elements[0],
96
+ api=api,
97
+ )
98
+ update_ccis(
99
+ new_ccis=new_catalog_elements[1],
100
+ old_ccis=old_catalog_elements[1],
101
+ api=api,
102
+ )
103
+ update_objectives(
104
+ new_objectives=new_catalog_elements[2],
105
+ old_objectives=old_catalog_elements[2],
106
+ api=api,
107
+ )
108
+ update_parameters(
109
+ new_parameters=new_catalog_elements[3],
110
+ old_parameters=old_catalog_elements[3],
111
+ api=api,
112
+ )
113
+ update_tests(
114
+ new_tests=new_catalog_elements[4],
115
+ old_tests=old_catalog_elements[4],
116
+ api=api,
117
+ )
118
+
119
+
120
+ def update_security_controls(new_security_controls: list[dict], old_security_controls: list[dict], api: Api) -> None:
121
+ """
122
+ Function to compare and update security controls
123
+
124
+ :param list[dict] new_security_controls: security controls from new catalog
125
+ :param list[dict] old_security_controls: security controls from old catalog
126
+ :param Api api: api object
127
+ :rtype: None
128
+ """
129
+ archived_list = []
130
+ updated_list = []
131
+ created_list = []
132
+ element_exists = False
133
+ for new_security_control in new_security_controls:
134
+ for old_security_control in old_security_controls:
135
+ if new_security_control.get("controlId") == old_security_control.get("controlId"):
136
+ key_dict = {
137
+ "old_sc_id": old_security_control["id"],
138
+ "new_sc_id": new_security_control["id"],
139
+ }
140
+ SECURITY_CONTROL_ID_KEY.append(key_dict)
141
+ element_exists = True
142
+ with contextlib.suppress(KeyError):
143
+ if new_security_control["archived"] is True:
144
+ old_security_control["archived"] = True
145
+ archived_list.append(old_security_control)
146
+ break
147
+ for key in old_security_control:
148
+ try:
149
+ if key not in [
150
+ "id",
151
+ "catalogueID",
152
+ "tenantsId",
153
+ "sortId",
154
+ "lastUpdatedById",
155
+ ]:
156
+ old_security_control[key] = new_security_control[key]
157
+ else:
158
+ continue
159
+ except KeyError:
160
+ old_security_control["archived"] = False
161
+ update = api.put(
162
+ url=api.config["domain"] + f"/api/SecurityControls/{old_security_control['id']}",
163
+ json=old_security_control,
164
+ )
165
+ update.raise_for_status()
166
+ if update.ok:
167
+ logger.info(
168
+ "Updated Security Control for Control ID: %i",
169
+ old_security_control["id"],
170
+ )
171
+ updated_list.append(old_security_control)
172
+ if element_exists is False:
173
+ try:
174
+ new_security_control["catalogueID"] = old_security_controls[0]["controls"]["catalogueID"]
175
+ except KeyError:
176
+ new_security_control["catalogueID"] = old_security_controls[0]["catalogueID"]
177
+ create = api.post(
178
+ url=api.config["domain"] + "/api/SecurityControls",
179
+ json=new_security_control,
180
+ )
181
+ create.raise_for_status()
182
+ if create.ok:
183
+ logger.info(
184
+ "Created Security Control for Control ID: %i",
185
+ new_security_control["id"],
186
+ )
187
+ created_list.append(new_security_control)
188
+ data_report(
189
+ data_name="SecurityControls",
190
+ archived=archived_list,
191
+ updated=updated_list,
192
+ created=created_list,
193
+ )
194
+
195
+
196
+ def update_ccis(new_ccis: list[dict], old_ccis: list[dict], api: Api) -> None:
197
+ """
198
+ Function to compare and update ccis
199
+
200
+ :param list[dict] new_ccis: ccis from new catalog
201
+ :param list[dict] old_ccis: ccis from old catalog
202
+ :param Api api: api object
203
+ :rtype: None
204
+ """
205
+ archived_list = []
206
+ updated_list = []
207
+ created_list = []
208
+ element_exists = False
209
+ for new_cci in new_ccis:
210
+ for old_cci in old_ccis:
211
+ for cci in old_cci:
212
+ if new_cci["name"] == cci["name"]:
213
+ element_exists = True
214
+ with contextlib.suppress(KeyError):
215
+ if new_cci["archived"] is True:
216
+ cci["archived"] = True
217
+ archived_list.append(cci)
218
+ break
219
+ for key in cci:
220
+ if key == "isPublic":
221
+ cci["isPublic"] = False
222
+ elif key not in ["id", "securityControlId"]:
223
+ cci[key] = new_cci[key]
224
+ else:
225
+ continue
226
+ key_set = next(
227
+ (item for item in SECURITY_CONTROL_ID_KEY if item["new_sc_id"] == new_cci["securityControlId"]),
228
+ None,
229
+ )
230
+ old_sc_id = key_set.get("old_sc_id")
231
+ cci["securityControlId"] = old_sc_id
232
+ update = api.put(
233
+ url=api.config["domain"] + f"/api/cci/{cci['id']}",
234
+ json=cci,
235
+ )
236
+ update.raise_for_status()
237
+ if update.ok:
238
+ logger.info(
239
+ "Updated CCI for CCI ID: %i",
240
+ cci["id"],
241
+ )
242
+ updated_list.append(cci)
243
+ if element_exists is False:
244
+ key_set = next(
245
+ (item for item in SECURITY_CONTROL_ID_KEY if item["new_sc_id"] == new_cci["securityControlId"]),
246
+ None,
247
+ )
248
+ old_sc_id = key_set.get("old_sc_id")
249
+ new_cci["securityControlId"] = old_sc_id
250
+ create = api.post(
251
+ url=api.config["domain"] + "/api/cci",
252
+ json=new_cci,
253
+ )
254
+ create.raise_for_status()
255
+ if create.ok:
256
+ logger.info(
257
+ "Created CCI for CCI ID: %i",
258
+ new_cci["id"],
259
+ )
260
+ created_list.append(new_cci)
261
+ data_report(
262
+ data_name="CCIs",
263
+ archived=archived_list,
264
+ updated=updated_list,
265
+ created=created_list,
266
+ )
267
+
268
+
269
+ def update_objectives(new_objectives: list[dict], old_objectives: list[dict], api: Api) -> None:
270
+ """
271
+ Function to compare and update objectives
272
+
273
+ :param list[dict] new_objectives: objectives from new catalog
274
+ :param list[dict] old_objectives: objectives from old catalog
275
+ :param Api api: Api object
276
+ :rtype: None
277
+ """
278
+ archived_list = []
279
+ updated_list = []
280
+ created_list = []
281
+ element_exists = False
282
+ for new_objective in new_objectives:
283
+ for old_objective in old_objectives:
284
+ if new_objective["name"] == old_objective["name"]:
285
+ element_exists = True
286
+ with contextlib.suppress(KeyError):
287
+ if new_objective["archived"] is True:
288
+ old_objective["archived"] = True
289
+ archived_list.append(old_objective)
290
+ break
291
+ for key in old_objective:
292
+ if key == "isPublic":
293
+ old_objective["isPublic"] = False
294
+ elif key not in ["id", "securityControlId", "tenantsId"]:
295
+ old_objective[key] = new_objective[key]
296
+ else:
297
+ continue
298
+ key_set = next(
299
+ (
300
+ item
301
+ for item in SECURITY_CONTROL_ID_KEY
302
+ if item["new_sc_id"] == new_objective["securityControlId"]
303
+ ),
304
+ None,
305
+ )
306
+ old_sc_id = key_set.get("old_sc_id")
307
+ old_objective["securityControlId"] = old_sc_id
308
+ if old_objective.get("tenantsId") is not None:
309
+ del old_objective["tenantsId"]
310
+ update = api.put(
311
+ url=api.config["domain"] + f"/api/controlObjectives/{old_objective['id']}",
312
+ json=old_objective,
313
+ )
314
+ update.raise_for_status()
315
+ if update.ok:
316
+ logger.info(
317
+ "Updated Objective for Objective ID: %i",
318
+ old_objective["id"],
319
+ )
320
+ updated_list.append(old_objective)
321
+ if element_exists is False:
322
+ key_set = next(
323
+ (item for item in SECURITY_CONTROL_ID_KEY if item["new_sc_id"] == new_objective["securityControlId"]),
324
+ None,
325
+ )
326
+ old_sc_id = key_set.get("old_sc_id")
327
+ new_objective["securityControlId"] = old_sc_id
328
+ create = api.post(
329
+ url=api.config["domain"] + "/api/controlObjectives",
330
+ json=new_objective,
331
+ )
332
+ create.raise_for_status()
333
+ if create.ok:
334
+ logger.info(
335
+ "Created Objective for Objective ID: %i",
336
+ new_objective["id"],
337
+ )
338
+ created_list.append(new_objective)
339
+ data_report(
340
+ data_name="Objectives",
341
+ archived=archived_list,
342
+ updated=updated_list,
343
+ created=created_list,
344
+ )
345
+
346
+
347
+ def parse_parameters(data: dict) -> dict:
348
+ """
349
+ Function to parse keys from the provided dictionary
350
+
351
+ :param dict data: parameters from catalog
352
+ :return: dictionary with parsed keys
353
+ :rtype: dict
354
+ """
355
+ for key in data:
356
+ if key == "isPublic":
357
+ data["isPublic"] = False
358
+ elif key == "default":
359
+ data["default"] = None
360
+ elif key == "dataType":
361
+ data["dataType"] = None
362
+ elif key != "id" or key != "securityControlId":
363
+ continue
364
+ return data
365
+
366
+
367
+ def update_parameters(new_parameters: list[dict], old_parameters: list[dict], api: Api) -> None:
368
+ """
369
+ Function to compare and update parameters
370
+
371
+ :param list[dict] new_parameters: parameters from new catalog
372
+ :param list[dict] old_parameters: parameters from old catalog
373
+ :param Api api: Api object
374
+ :rtype: None
375
+ """
376
+ archived_list = []
377
+ updated_list = []
378
+ created_list = []
379
+ element_exists = False
380
+ for new_parameter in new_parameters:
381
+ for old_parameter in old_parameters:
382
+ if new_parameter["parameterId"] == old_parameter["parameterId"]:
383
+ element_exists = True
384
+ with contextlib.suppress(KeyError):
385
+ if new_parameter["archived"] is True:
386
+ old_parameter["archived"] = True
387
+ archived_list.append(old_parameter)
388
+ break
389
+ old_parameter = parse_parameters(old_parameter)
390
+ key_set = next(
391
+ (
392
+ item
393
+ for item in SECURITY_CONTROL_ID_KEY
394
+ if item["new_sc_id"] == new_parameter["securityControlId"]
395
+ ),
396
+ None,
397
+ )
398
+ old_sc_id = key_set.get("old_sc_id")
399
+ old_parameter["securityControlId"] = old_sc_id
400
+ update = api.put(
401
+ url=api.config["domain"] + f"/api/controlParameters/{old_parameter['id']}",
402
+ json=old_parameter,
403
+ )
404
+ update.raise_for_status()
405
+ if update.ok:
406
+ logger.info(
407
+ "Updated Parameter for Parameter ID: %i",
408
+ old_parameter["id"],
409
+ )
410
+ updated_list.append(old_parameter)
411
+ if element_exists is False:
412
+ key_set = next(
413
+ (item for item in SECURITY_CONTROL_ID_KEY if item["new_sc_id"] == new_parameter["securityControlId"]),
414
+ None,
415
+ )
416
+ old_sc_id = key_set.get("old_sc_id")
417
+ new_parameter["securityControlId"] = old_sc_id
418
+ create = api.post(
419
+ url=api.config["domain"] + "/api/controlParameters",
420
+ json=new_parameter,
421
+ )
422
+ create.raise_for_status()
423
+ if create.ok:
424
+ logger.info(
425
+ "Created Parameter for Parameter ID: %i",
426
+ new_parameter["id"],
427
+ )
428
+ created_list.append(new_parameter)
429
+ data_report(
430
+ data_name="Parameters",
431
+ archived=archived_list,
432
+ updated=updated_list,
433
+ created=created_list,
434
+ )
435
+
436
+
437
+ def update_tests(new_tests: list[dict], old_tests: list[dict], api: Api) -> None:
438
+ """
439
+ Function to compare and update tests
440
+
441
+ :param list[dict] new_tests: tests from new catalog
442
+ :param list[dict] old_tests: tests from old catalog
443
+ :param Api api: API Object
444
+ :rtype: None
445
+ """
446
+ archived_list = []
447
+ updated_list = []
448
+ created_list = []
449
+ element_exists = False
450
+ for new_test in new_tests:
451
+ for old_test in old_tests:
452
+ if new_test["testId"] == old_test["testId"]:
453
+ element_exists = True
454
+ with contextlib.suppress(KeyError):
455
+ if new_test["archived"] is True:
456
+ old_test["archived"] = True
457
+ archived_list.append(old_test)
458
+ break
459
+ for key in old_test:
460
+ if key == "isPublic":
461
+ old_test["isPublic"] = False
462
+ elif key not in ["id", "securityControlId", "tenantsId"]:
463
+ old_test[key] = new_test[key]
464
+ else:
465
+ continue
466
+ key_set = next(
467
+ (item for item in SECURITY_CONTROL_ID_KEY if item["new_sc_id"] == new_test["securityControlId"]),
468
+ None,
469
+ )
470
+ old_sc_id = key_set.get("old_sc_id")
471
+ old_test["securityControlId"] = old_sc_id
472
+ update = api.put(
473
+ url=api.config["domain"] + f"/api/controlTestPlans/{old_test['id']}",
474
+ json=old_test,
475
+ )
476
+ update.raise_for_status()
477
+ if update.ok:
478
+ logger.info(
479
+ "Updated test for test ID: %i",
480
+ old_test["id"],
481
+ )
482
+ updated_list.append(old_test)
483
+ if element_exists is False:
484
+ key_set = next(
485
+ (item for item in SECURITY_CONTROL_ID_KEY if item["new_sc_id"] == new_test["securityControlId"]),
486
+ None,
487
+ )
488
+ old_sc_id = key_set.get("old_sc_id")
489
+ new_test["securityControlId"] = old_sc_id
490
+ create = api.post(
491
+ url=api.config["domain"] + "/api/controlTestPlans",
492
+ json=new_test,
493
+ )
494
+ create.raise_for_status()
495
+ if create.ok:
496
+ logger.info(
497
+ "Created test for test ID: %i",
498
+ new_test["id"],
499
+ )
500
+ created_list.append(new_test)
501
+ data_report(
502
+ data_name="Tests",
503
+ archived=archived_list,
504
+ updated=updated_list,
505
+ created=created_list,
506
+ )
507
+
508
+
509
+ def parse_new_catalog() -> Tuple[list, list, list, list, list]:
510
+ """
511
+ Function to parse elements from the new catalog
512
+
513
+ :return: Tuple containing lists of new catalog data elements
514
+ :rtype: Tuple[list, list, list, list, list]
515
+ """
516
+ with job_progress:
517
+ # add task for retrieving new catalog
518
+ retrieving_new_catalog = job_progress.add_task(
519
+ "[#f8b737]Retrieving selected catalog from RegScale.com/regulations.",
520
+ total=6,
521
+ )
522
+ # retrieve new catalog to run diagnostics on
523
+ new_catalog = get_new_catalog(url=DOWNLOAD_URL)
524
+ # update the task as complete
525
+ job_progress.update(retrieving_new_catalog, advance=1)
526
+ # retrieve new catalog security controls
527
+ new_security_controls = parse_dict_and_sublevel(
528
+ data=new_catalog,
529
+ key="catalogue",
530
+ sub_key="securityControls",
531
+ del_key="tenantsId",
532
+ )
533
+ # update the task as complete
534
+ job_progress.update(retrieving_new_catalog, advance=1)
535
+ # retrieve new catalog ccis
536
+ new_ccis = parse_dict_and_sublevel(data=new_catalog, key="catalogue", sub_key="ccis")
537
+ # update the task as complete
538
+ job_progress.update(retrieving_new_catalog, advance=1)
539
+ # retrieve new catalog objectives
540
+ new_objectives = parse_dict_and_sublevel(data=new_catalog, key="catalogue", sub_key="objectives")
541
+ # update the task as complete
542
+ job_progress.update(retrieving_new_catalog, advance=1)
543
+ # retrieve new catalog parameters
544
+ new_parameters = parse_dict_and_sublevel(data=new_catalog, key="catalogue", sub_key="parameters")
545
+ # update the task as complete
546
+ job_progress.update(retrieving_new_catalog, advance=1)
547
+ # retrieve new catalog tests
548
+ new_tests = parse_dict_and_sublevel(data=new_catalog, key="catalogue", sub_key="tests")
549
+ # update the task as complete
550
+ job_progress.update(retrieving_new_catalog, completed=6)
551
+ return new_security_controls, new_ccis, new_objectives, new_parameters, new_tests
552
+
553
+
554
+ def parse_old_catalog(api: Api) -> Tuple[list, list, list, list, list]:
555
+ """
556
+ Function to parse elements from the old catalog
557
+
558
+ :param Api api: RegScale API object
559
+ :return: Tuple containing lists of old catalog elements
560
+ :rtype: Tuple[list, list, list, list, list]
561
+ """
562
+ with job_progress:
563
+ # add task for retrieving old catalog
564
+ retrieving_old_catalog = job_progress.add_task(
565
+ "[#ef5d23]Retrieving selected catalog from RegScale application instance.",
566
+ total=5,
567
+ )
568
+ # retrieve old catalog security controls
569
+ security_controls = parse_controls(api=api)
570
+ old_security_controls = security_controls[0]
571
+ # update the task as complete
572
+ job_progress.update(retrieving_old_catalog, advance=1)
573
+ # retrive old catalog ccis
574
+ old_ccis = security_controls[4]
575
+ # update the task as complete
576
+ job_progress.update(retrieving_old_catalog, advance=1)
577
+ # retrieve old catalog objectives
578
+ old_objectives = security_controls[2]
579
+ # update the task as complete
580
+ job_progress.update(retrieving_old_catalog, advance=1)
581
+ # retrieve old catalog parameters
582
+ old_parameters = security_controls[1]
583
+ # update the task as complete
584
+ job_progress.update(retrieving_old_catalog, advance=1)
585
+ # retrieve old catalog tests
586
+ old_tests = security_controls[3]
587
+ # update the task as complete
588
+ job_progress.update(retrieving_old_catalog, advance=1)
589
+
590
+ return old_security_controls, old_ccis, old_objectives, old_parameters, old_tests
591
+
592
+
593
+ def parse_dict_and_sublevel(data: dict, key: str, sub_key: str, del_key: Optional[str] = None) -> list:
594
+ """
595
+ Function to parse a dictionary and retrieve data from the sublevel dictionary key
596
+
597
+ :param dict data: dictionary to parse
598
+ :param str key: key to be extracted from the dictionary
599
+ :param str sub_key: the sub-level key being looked for
600
+ :param Optional[str] del_key: the sub-level key to delete, defaults to None
601
+ :return: a list containing the parsed data elements
602
+ :rtype: list
603
+ """
604
+ parse_list = []
605
+ with contextlib.suppress(KeyError):
606
+ parse_list.extend(iter(data[key][sub_key]))
607
+ if del_key:
608
+ for parsed_item in parse_list:
609
+ del parsed_item[del_key]
610
+ return parse_list
611
+
612
+
613
+ def data_report(data_name: str, archived: list, updated: list, created: list) -> None:
614
+ """Creates output data report for changed data elements in the catalog
615
+
616
+ :param str data_name: catalog data element being updated
617
+ :param list archived: archived data elements
618
+ :param list updated: updated data elements
619
+ :param list created: created data elements
620
+ :rtype: None
621
+ """
622
+ import pandas as pd # Optimize import performance
623
+
624
+ archived_data = pd.DataFrame.from_dict(archived)
625
+ updated_data = pd.DataFrame.from_dict(updated)
626
+ created_data = pd.DataFrame.from_dict(created)
627
+ with pd.ExcelWriter(f"{data_name}.xlsx") as writer:
628
+ archived_data.to_excel(writer, sheet_name="Archived", index=False)
629
+ updated_data.to_excel(writer, sheet_name="Updated", index=False)
630
+ created_data.to_excel(writer, sheet_name="Created", index=False)
631
+
632
+
633
+ def get_old_security_controls(uuid_value: str, api: Api) -> list[dict]:
634
+ """
635
+ Function to retrieve the old catalog security controls from a RegScale instance via API & GraphQL
636
+
637
+ :param str uuid_value: UUID of the catalog to retrieve
638
+ :param Api api: RegScale API object
639
+ :return: a list containing security controls
640
+ :rtype: list[dict]
641
+ """
642
+ body = """
643
+ query {
644
+ catalogues(
645
+ skip: 0
646
+ take: 50
647
+ where: { uuid: { eq: "uuid_value" } }
648
+ ) {
649
+ items {
650
+ id
651
+ }
652
+ pageInfo {
653
+ hasNextPage
654
+ }
655
+ totalCount
656
+ }
657
+ }""".replace(
658
+ "uuid_value", uuid_value
659
+ )
660
+ try:
661
+ catalogue_id = api.graph(query=body)["catalogues"]["items"][0]["id"]
662
+ except (IndexError, KeyError):
663
+ error_and_exit(f"Catalog with UUID: {uuid_value} not found in RegScale instance.")
664
+ try:
665
+ old_security_controls = api.get(
666
+ url=api.config["domain"] + f"/api/SecurityControls/getAllByCatalogWithDetails/{catalogue_id}"
667
+ ).json()
668
+ if len(old_security_controls) == 0:
669
+ error_and_exit("This catalog does not currently exist in the RegScale application")
670
+ except JSONDecodeError as ex:
671
+ error_and_exit(f"Unable to retrieve control objectives from RegScale.\n{ex}")
672
+ except TimeoutError:
673
+ error_and_exit("The selected catalog is too large to update, please contact RegScale customer service.")
674
+ return old_security_controls
675
+
676
+
677
+ def parse_controls(
678
+ api: Api,
679
+ ) -> Tuple[list[dict], list[dict], list[dict], list[dict], list[dict]]:
680
+ """
681
+ Function to retrieve the old catalog security controls from a RegScale instance via API & GraphQL
682
+
683
+ :param Api api: RegScale API object
684
+ :return: a tuple containing a list for each catalog element
685
+ :rtype: Tuple[list[dict], list[dict], list[dict], list[dict], list[dict]]
686
+ """
687
+ old_security_controls = get_old_security_controls(uuid_value=CAT_UUID, api=api)
688
+ parsed_old_security_controls = []
689
+ for old_control in old_security_controls:
690
+ parsed_old_security_controls.append(old_control["control"])
691
+ old_parameters = []
692
+ for control in old_security_controls:
693
+ for parameter in control["parameters"]:
694
+ old_parameters.append(parameter)
695
+ old_objectives = []
696
+ for control in old_security_controls:
697
+ for objective in control["objectives"]:
698
+ old_objectives.append(objective)
699
+ old_tests = []
700
+ for control in old_security_controls:
701
+ for test in control["tests"]:
702
+ old_tests.append(test)
703
+ old_ccis = []
704
+ for control in old_security_controls:
705
+ id_number = control["control"]["id"]
706
+ try:
707
+ ccis = api.get(url=api.config["domain"] + f"/api/cci/getByControl/{id_number}").json()
708
+ if ccis:
709
+ old_ccis.append(ccis)
710
+ except JSONDecodeError as ex:
711
+ error_and_exit(f"Unable to retrieve control objectives from RegScale.\n{ex}")
712
+ return (
713
+ parsed_old_security_controls,
714
+ old_parameters,
715
+ old_objectives,
716
+ old_tests,
717
+ old_ccis,
718
+ )