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,641 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Integrates CISA into RegScale"""
4
+
5
+ # standard python imports
6
+ import logging
7
+ import re
8
+ from concurrent.futures import ALL_COMPLETED, ThreadPoolExecutor, wait
9
+ from datetime import date, datetime
10
+ from typing import List, Optional, Tuple, Any, Dict
11
+ from urllib.error import URLError
12
+ from urllib.parse import urlparse
13
+
14
+ import click
15
+ import dateutil.parser as dparser
16
+ import requests
17
+ from bs4 import BeautifulSoup, Tag
18
+ from requests import exceptions
19
+ from rich.console import Console
20
+
21
+ from regscale.core.app.api import Api
22
+ from regscale.core.app.application import Application
23
+ from regscale.core.app.internal.login import is_valid
24
+ from regscale.models import Link, Threat
25
+ from regscale.core.app.utils.app_utils import error_and_exit
26
+
27
+ logger = logging.getLogger("regscale")
28
+ console = Console()
29
+ CISA_THREATS_URL = (
30
+ "https://www.cisa.gov/news-events/cybersecurity-advisories"
31
+ "?search_api_fulltext=&sort_by=field_release_date&f%5B0%5D="
32
+ "advisory_type%3A94&f%5B1%5D=release_date_year%3A"
33
+ )
34
+ CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
35
+ THREATS_SUFFIX = "/api/threats"
36
+ DEFAULT_STR = "See Link for details."
37
+
38
+
39
+ @click.group()
40
+ def cisa():
41
+ """Update CISA."""
42
+
43
+
44
+ @cisa.command(name="ingest_cisa_kev")
45
+ def ingest_cisa_kev():
46
+ """Update RegScale threats with the latest Known Exploited \
47
+ Vulnerabilities (KEV) feed from cisa.gov."""
48
+ data = pull_cisa_kev()
49
+ update_regscale(data)
50
+
51
+
52
+ @cisa.command(name="ingest_cisa_alerts")
53
+ @click.option(
54
+ "--year",
55
+ type=click.INT,
56
+ help="Enter the year to search for CISA alerts.",
57
+ default=date.today().year,
58
+ show_default=True,
59
+ required=True,
60
+ )
61
+ def ingest_cisa_alerts(year: int):
62
+ """Update RegScale threats with alerts from cisa.gov."""
63
+ alerts(year)
64
+
65
+
66
+ def update_regscale_links(threats: List[Threat]) -> None:
67
+ """
68
+ Create RegScale links from Threat Responses
69
+
70
+ :param List[Threat] threats: List of Responses from RegScale API
71
+ :rtype: None
72
+ """
73
+
74
+ # extract url from html string using regex
75
+ def extract_url(html: str) -> str:
76
+ """
77
+ Extract URL from HTML string
78
+
79
+ :param str html: HTML string
80
+ :return: URL
81
+ :rtype: str
82
+ """
83
+ url = re.findall(r"(?P<url>https?://[^\s]+)", html)
84
+ return url[0].replace('"', "") if url else None
85
+
86
+ links = []
87
+ for threat in threats:
88
+ url = extract_url(threat.description)
89
+ if threat.description:
90
+ link = Link(
91
+ parentID=threat.id,
92
+ parentModule="threats",
93
+ url=url,
94
+ title=threat.title,
95
+ )
96
+ links.append(link)
97
+ Link.batch_update(links)
98
+
99
+
100
+ def process_threats(threats: list[Threat], unique_threats: set[str], reg_threats: list[Threat]) -> Tuple[list, list]:
101
+ """
102
+ Process threats
103
+
104
+ :param list[Threat] threats: List of threats to process
105
+ :param set[str] unique_threats: Set of unique threat descriptions
106
+ :param list[Threat] reg_threats: List of RegScale threats
107
+ :return: Tuple of insert and update threats
108
+ :rtype: Tuple[list, list]
109
+ """
110
+ insert_threats = []
111
+ update_threats = []
112
+ for threat in threats:
113
+ if threat and threat.description in unique_threats:
114
+ old_dict = [reg.dict() for reg in reg_threats if reg.description == threat.description][0]
115
+ update_dict = threat.dict()
116
+ update_dict = merge_old(update_dict, old_dict)
117
+ update_threats.append(update_dict) # Update
118
+ else:
119
+ if threat:
120
+ insert_threats.append(threat.dict()) # Post
121
+ return insert_threats, update_threats
122
+
123
+
124
+ def alerts(year: int) -> None:
125
+ """
126
+ Return CISA alerts for the specified year
127
+
128
+ :param int year: A target year for CISA alerts
129
+ :rtype: None
130
+ """
131
+ app = Application()
132
+ update_threats = []
133
+ insert_threats = []
134
+ # Check to make sure we have a valid token
135
+ if not is_valid(app=app):
136
+ error_and_exit("Login Error: Invalid Credentials, please login for a new token.")
137
+
138
+ reg_threats = Threat.fetch_all_threats()
139
+ unique_threats = {reg.description for reg in reg_threats}
140
+ logger.info("Fetching CISA threats for %i...", year)
141
+ threats = parse_html(CISA_THREATS_URL + str(year), app=app)
142
+ logger.info("Found %i threats from CISA.", len(threats))
143
+
144
+ if len(threats) > 0:
145
+ logger.info("Processing %i threat(s)...", len(threats))
146
+ insert_threats, update_threats = process_threats(threats, unique_threats, reg_threats)
147
+
148
+ logging.getLogger("urllib3").propagate = False
149
+ if insert_threats:
150
+ logger.info("Inserting %i threats to RegScale...", len(insert_threats))
151
+ threats = Threat.bulk_insert(api=None, threats=insert_threats)
152
+ update_regscale_links(threats)
153
+ if update_threats:
154
+ update_regscale_threats(json_list=[Threat(**threat) for threat in update_threats])
155
+
156
+
157
+ def parse_html(page_url: str, app: Application) -> list:
158
+ """
159
+ Convert HTML from a given URL to a RegScale threat
160
+
161
+ :param str page_url: A URL to parse
162
+ :param Application app: Application object
163
+ :return: List of RegScale threats
164
+ :rtype: list
165
+ """
166
+ control = {"page": 0, "items": 999, "links": []}
167
+ while control["items"] > 0:
168
+ soup = gen_soup(page_url + f"&page={control['page']}")
169
+
170
+ articles = soup.find_all("article")
171
+ for article in articles:
172
+ try:
173
+ title = article.text.strip("\n").replace("\n", " ").split("|")[1].strip(" ").replace(" ", " ")
174
+ except IndexError:
175
+ continue
176
+ short_description = ""
177
+ article_soup = article.find_all("a", href=True)
178
+ link = ("https://www.cisa.gov" + article_soup[0]["href"]) if article_soup else None
179
+ if is_url(link):
180
+ logger.debug("Short Description: %s", short_description)
181
+ control["links"].append((link, short_description, title))
182
+ logger.info("Building RegScale threat from %s.", link)
183
+
184
+ control["items"] = len(articles)
185
+ control["page"] += 1
186
+ # check if max threads <= 20 to prevent IP ban from CISA
187
+ max_threads = min(app.config["maxThreads"], 20)
188
+ with ThreadPoolExecutor(max_workers=max_threads) as executor:
189
+ futures = []
190
+ for link in control["links"]:
191
+ logger.debug("Building RegScale threat from %s.", link[0])
192
+ futures.append(
193
+ executor.submit(
194
+ build_threat,
195
+ app=app,
196
+ detailed_link=link[0],
197
+ short_description=link[1],
198
+ title=link[2],
199
+ )
200
+ )
201
+ wait(futures, return_when=ALL_COMPLETED)
202
+ threats = [future.result() for future in futures if future.result()]
203
+ # Log errors
204
+ for error_ix in (ix for (ix, fut) in enumerate(futures) if not fut.result()):
205
+ logger.warning("Unable to fetch: %s", control["links"][error_ix][0])
206
+ return threats
207
+
208
+
209
+ def build_threat(app: Application, detailed_link: str, short_description: str, title: str) -> Threat:
210
+ """
211
+ Parse HTML from a given URL/link and build a RegScale threat.
212
+
213
+ :param Application app: Application object
214
+ :param str detailed_link: URL of the CISA threat
215
+ :param str short_description: Description of the threat
216
+ :param str title: Title for the threat
217
+ :return: RegScale threat class
218
+ :rtype: Threat
219
+ """
220
+ dat = parse_details(detailed_link)
221
+ threat = None
222
+ if dat:
223
+ date_created = dat[0]
224
+ vulnerability = dat[1]
225
+ mitigation = dat[2]
226
+ notes = dat[3]
227
+
228
+ threat = Threat(
229
+ uuid=Threat.xstr(None),
230
+ title=title,
231
+ threatType="Specific",
232
+ threatOwnerId=app.config["userId"],
233
+ dateIdentified=date_created,
234
+ targetType="Other",
235
+ source="Open Source",
236
+ description=short_description or f"""<p><a href="{detailed_link}" title="">{detailed_link}</a></p>""",
237
+ vulnerabilityAnalysis="".join(vulnerability),
238
+ mitigations="".join(mitigation),
239
+ notes="".join(notes),
240
+ dateCreated=date_created,
241
+ status="Initial Report/Notification",
242
+ )
243
+ return threat
244
+
245
+
246
+ def filter_elements(element: Tag) -> Optional[Tag]:
247
+ """
248
+ Filter elements
249
+
250
+ :param Tag element: A BeautifulSoup Tag
251
+ :return: The given tag if it is a Tag and has children
252
+ :rtype: Optional[Tag]
253
+ """
254
+ filter_lst = [
255
+ "c-figure__media",
256
+ "c-product-survey__text-area",
257
+ "l-full__footer",
258
+ "usa-navbar",
259
+ ]
260
+ found = False
261
+ if element.attrs.get("class"):
262
+ found = any(item in element.attrs["class"] for item in filter_lst)
263
+ if element.name in ("p", "li", "div", "table") and not found:
264
+ return element
265
+ return None
266
+
267
+
268
+ def process_params(
269
+ element: Tag, nav_string: str, vulnerability: list, mitigation: list, notes: list
270
+ ) -> Tuple[list, list, list]:
271
+ """
272
+ Process Parameters
273
+
274
+ :param Tag element: A BeautifulSoup Tag
275
+ :param str nav_string: A string to filter on
276
+ :param list vulnerability: A list of vulnerabilities
277
+ :param list mitigation: A list of mitigations
278
+ :param list notes: A list of notes
279
+ :return: Tuple[vulnerability, mitigation, notes]
280
+ :rtype: Tuple[list, list, list]
281
+ """
282
+ # Filter out UL, seems to be duplicated with li tag.
283
+ if filter_elements(element):
284
+ content = str(element)
285
+ if nav_string.lower() == "summary" and content not in notes:
286
+ notes.append(content)
287
+ if nav_string.lower() == "technical details" and content not in vulnerability:
288
+ vulnerability.append(content)
289
+ if nav_string.lower() == "mitigations" and content not in mitigation:
290
+ mitigation.append(content)
291
+ return vulnerability, mitigation, notes
292
+
293
+
294
+ def process_element(*args) -> Tuple[dict, str, str]:
295
+ """
296
+ Loop elements and determine last header, last_h3, and nav_string
297
+
298
+ :return: Tuple[last_header, last_h3, nav_string]
299
+ :rtype: Tuple[dict, str, str]
300
+ """
301
+ # Unpack tuple args
302
+ (
303
+ dat,
304
+ last_header,
305
+ last_h3,
306
+ nav_string,
307
+ div_list,
308
+ vulnerability,
309
+ mitigation,
310
+ notes,
311
+ ) = args[0]
312
+
313
+ last_header = {"type": dat.name, "title": dat.text} if re.match(r"^h[1-6]$", dat.name) else last_header
314
+ last_h3 = dat.text if dat.name == "h3" else last_h3
315
+ if last_header and isinstance(dat, Tag) and dat.text.lower() in div_list:
316
+ nav_string = dat.text.lower()
317
+ if last_h3 and nav_string and dat.text.lower().replace("\n", "") not in div_list and last_h3.lower() in div_list:
318
+ process_params(dat, nav_string, vulnerability, mitigation, notes)
319
+ return last_header, last_h3, nav_string
320
+
321
+
322
+ def parse_details(link: str) -> Optional[Tuple[str, list, list, list]]:
323
+ """
324
+ Parse the details of a given link
325
+
326
+ :param str link: A URL to parse
327
+ :return: A tuple of date created, vulnerability, mitigation, and notes
328
+ :rtype: Optional[Tuple[str, list, list, list]]
329
+ """
330
+ div_list = ["technical details", "mitigations", "summary", "executive summary"]
331
+ vulnerability = []
332
+ mitigation = []
333
+ notes = []
334
+ detailed_soup = gen_soup(link)
335
+ date_created = fuzzy_find_date(detailed_soup)
336
+ last_header = None
337
+ last_h3 = None
338
+ nav_string = ""
339
+
340
+ for ele in detailed_soup.find_all("div", {"class": "l-full__main"}):
341
+ for dat in ele.find_all():
342
+ args = (
343
+ dat,
344
+ last_header,
345
+ last_h3,
346
+ nav_string,
347
+ div_list,
348
+ vulnerability,
349
+ mitigation,
350
+ notes,
351
+ )
352
+ last_header, last_h3, nav_string = process_element(args)
353
+
354
+ if len(vulnerability) == 0:
355
+ vulnerability.append(DEFAULT_STR)
356
+ if len(notes) == 0:
357
+ notes.append(DEFAULT_STR)
358
+ if len(mitigation) == 0:
359
+ mitigation.append(DEFAULT_STR)
360
+ if date_created and vulnerability and mitigation and notes:
361
+ return date_created, unique(vulnerability), unique(mitigation), unique(notes)
362
+ return None
363
+
364
+
365
+ def fuzzy_find_date(detailed_soup: BeautifulSoup, location: int = 2, attempts: int = 0) -> str:
366
+ """
367
+ Perform a fuzzy find to pull a date from a bs4 object
368
+
369
+ :param BeautifulSoup detailed_soup: A BeautifulSoup object representing a webpage
370
+ :param int location: The location of the date in the webpage, defaults to 2
371
+ :param int attempts: Number of attempts to find a date, defaults to 0
372
+ :return: An ISO-formatted datetime string
373
+ :rtype: str
374
+ """
375
+
376
+ fuzzy_dt = None
377
+
378
+ try:
379
+ if matches := re.search(r"(Last Revised|Release Date)\s*(.*)", detailed_soup.text):
380
+ fuzzy_dt = dparser.parse(matches.group(2), fuzzy=True).isoformat()
381
+ return fuzzy_dt
382
+ fuzzy_dt = dparser.parse(
383
+ str(detailed_soup.find_all("div", {"class": "c-field__content"})[location].text)
384
+ .strip("\n")
385
+ .strip()
386
+ .split("|", maxsplit=1)[0]
387
+ .strip(),
388
+ fuzzy=True,
389
+ ).isoformat()
390
+ except dparser.ParserError as pex:
391
+ logger.error("Error Processing Alert date created: %s.", pex)
392
+ if not fuzzy_dt and attempts < 5:
393
+ fuzzy_dt = fuzzy_find_date(detailed_soup, location + 1, attempts + 1)
394
+ if not fuzzy_dt and attempts >= 5:
395
+ logger.error("Unable to find date created in CISA alert.")
396
+ return fuzzy_dt
397
+
398
+
399
+ def gen_soup(url: str) -> BeautifulSoup:
400
+ """
401
+ Generate a BeautifulSoup instance for the given URL
402
+
403
+ :param str url: URL string
404
+ :raises: URLError if URL is invalid
405
+ :rtype: BeautifulSoup
406
+ """
407
+ if isinstance(url, Tuple):
408
+ url = url[0]
409
+ if is_url(url):
410
+ req = Api().get(url)
411
+ req.raise_for_status()
412
+ content = req.content
413
+ return BeautifulSoup(content, "html.parser")
414
+ raise URLError("URL is invalid, exiting...")
415
+
416
+
417
+ def _load_from_package() -> dict:
418
+ """
419
+ Load the cisa_kev_data.json from the RegScale CLI package
420
+
421
+ :return: The cisa_kev_data.json
422
+ :rtype: dict
423
+ """
424
+ import json
425
+ import importlib.resources as pkg_resources
426
+
427
+ # check if the filepath exists before trying to open it
428
+ with pkg_resources.open_text("regscale.models.integration_models", "cisa_kev_data.json") as file:
429
+ data = json.load(file)
430
+ return data
431
+
432
+
433
+ def pull_cisa_kev() -> Dict[Any, Any]:
434
+ """
435
+ Pull the latest Known Exploited Vulnerabilities (KEV) data from CISA
436
+
437
+ :return: Dictionary of known vulnerabilities via API
438
+ :rtype: Dict[Any, Any]
439
+ """
440
+ # Use module-level variable for caching
441
+ if not hasattr(pull_cisa_kev, "_cached_data"):
442
+ app = Application()
443
+ api = Api()
444
+ config = app.config
445
+ result = []
446
+
447
+ # Get URL from config or use default
448
+ if "cisa_kev" in config:
449
+ cisa_url = config["cisaKev"]
450
+ else:
451
+ cisa_url = CISA_KEV_URL
452
+ config["cisaKev"] = cisa_url
453
+ app.save_config(config)
454
+
455
+ try:
456
+ response = api.get(url=cisa_url, headers={}, retry_login=False)
457
+ if not response:
458
+ logger.error("Failed to get response from %s\nUsing KEV data from RegScale CLI package.", cisa_url)
459
+ data = _load_from_package()
460
+ pull_cisa_kev._cached_data = data
461
+ return data
462
+ response.raise_for_status()
463
+ result = response.json()
464
+
465
+ # Cache the result
466
+ pull_cisa_kev._cached_data = result
467
+
468
+ except exceptions.RequestException as ex:
469
+ # Whoops it wasn't a 200
470
+ logger.error("Error retrieving CISA KEV data: %s.\nUsing KEV data from RegScale CLI package.", str(ex))
471
+ data = _load_from_package()
472
+ pull_cisa_kev._cached_data = data
473
+ return data
474
+
475
+ return pull_cisa_kev._cached_data
476
+
477
+
478
+ def convert_date_string(date_str: str) -> str:
479
+ """
480
+ Convert the given date string for use in RegScale
481
+
482
+ :param str date_str: date as a string
483
+ :return: RegScale accepted datetime string format
484
+ :rtype: str
485
+ """
486
+ fmt = "%Y-%m-%d"
487
+ result_dt = datetime.strptime(date_str, fmt) # 2022-11-03 to 2022-08-23T03:00:39.925Z
488
+ return f"{result_dt.isoformat()}.000Z"
489
+
490
+
491
+ def update_regscale(data: dict) -> None:
492
+ """
493
+ Update RegScale threats with the latest Known Exploited Vulnerabilities (KEV) data
494
+
495
+ :param dict data: Threat data from CISA
496
+ :rtype: None
497
+ """
498
+ app = Application()
499
+ api = Api()
500
+ reg_threats = Threat.fetch_all_threats()
501
+ unique_threats = {reg.description for reg in reg_threats}
502
+ matching_threats = [d for d in data["vulnerabilities"] if d["vulnerabilityName"] in unique_threats]
503
+ threats_inserted = []
504
+ threats_updated = []
505
+ new_threats = [dat for dat in data["vulnerabilities"] if dat not in matching_threats]
506
+ console.print(f"Found {len(new_threats)} new threats from CISA")
507
+ if [dat for dat in data["vulnerabilities"] if dat not in matching_threats]:
508
+ for rec in new_threats:
509
+ threat = Threat(
510
+ uuid=Threat.xstr(None),
511
+ title=rec["cveID"],
512
+ threatType="Specific",
513
+ threatOwnerId=app.config["userId"],
514
+ dateIdentified=convert_date_string(rec["dateAdded"]),
515
+ targetType="Other",
516
+ source="Open Source",
517
+ description=rec["vulnerabilityName"],
518
+ vulnerabilityAnalysis=rec["shortDescription"],
519
+ mitigations=rec["requiredAction"],
520
+ notes=rec["notes"].strip() + " Due Date: " + rec["dueDate"],
521
+ dateCreated=(datetime.now()).isoformat(),
522
+ status="Initial Report/Notification",
523
+ )
524
+ threats_inserted.append(threat.dict())
525
+ update_threats = [dat for dat in data["vulnerabilities"] if dat in matching_threats]
526
+ if len(matching_threats) > 0:
527
+ for rec in update_threats:
528
+ update_vuln = Threat(
529
+ uuid=Threat.xstr(None),
530
+ title=rec["cveID"],
531
+ threatType="Specific",
532
+ threatOwnerId=app.config["userId"],
533
+ dateIdentified=convert_date_string(rec["dateAdded"]),
534
+ targetType="Other",
535
+ description=rec["vulnerabilityName"],
536
+ vulnerabilityAnalysis=rec["shortDescription"],
537
+ mitigations=rec["requiredAction"],
538
+ dateCreated=convert_date_string(rec["dateAdded"]),
539
+ ).dict()
540
+ old_vuln = [threat.dict() for threat in reg_threats if threat.description == update_vuln["description"]][0]
541
+ update_vuln = merge_old(update_vuln=update_vuln, old_vuln=old_vuln)
542
+ if old_vuln:
543
+ threats_updated.append(update_vuln)
544
+ if len(threats_inserted) > 0:
545
+ logging.getLogger("urllib3").propagate = False
546
+ # Update Matching Threats
547
+ logger.info("Inserting %i threats to RegScale...", len(threats_inserted))
548
+ Threat.bulk_insert(api, threats_inserted)
549
+ update_regscale_threats(json_list=threats_updated)
550
+
551
+
552
+ def merge_old(update_vuln: dict, old_vuln: dict) -> dict:
553
+ """
554
+ Merge dictionaries of old and updated vulnerabilities
555
+
556
+ :param dict update_vuln: An updated vulnerability dictionary
557
+ :param dict old_vuln: An old vulnerability dictionary
558
+ :return: A merged vulnerability dictionary
559
+ :rtype: dict
560
+ """
561
+ update_vuln["id"] = old_vuln["id"]
562
+ update_vuln["uuid"] = old_vuln["uuid"]
563
+ update_vuln["status"] = old_vuln["status"]
564
+ update_vuln["source"] = old_vuln["source"]
565
+ update_vuln["threatType"] = old_vuln["threatType"]
566
+ update_vuln["threatOwnerId"] = old_vuln["threatOwnerId"]
567
+ update_vuln["notes"] = old_vuln["notes"]
568
+ update_vuln["targetType"] = old_vuln["targetType"]
569
+ update_vuln["dateCreated"] = old_vuln["dateCreated"]
570
+ update_vuln["isPublic"] = old_vuln["isPublic"]
571
+ update_vuln["investigated"] = old_vuln["investigated"]
572
+ if "investigationResults" in old_vuln.keys():
573
+ update_vuln["investigationResults"] = old_vuln["investigationResults"]
574
+ return update_vuln
575
+
576
+
577
+ def insert_or_upd_threat(threat: dict, app: Application, threat_id: int = None) -> requests.Response:
578
+ """
579
+ Insert or update the given threats in RegScale
580
+
581
+ :param dict threat: RegScale threat
582
+ :param Application app: Application object
583
+ :param int threat_id: RegScale ID of the threat, defaults to none
584
+ :return: An API response based on the PUT or POST action
585
+ :rtype: requests.Response
586
+ """
587
+ api = Api()
588
+ config = app.config
589
+ url_threats = config["domain"] + THREATS_SUFFIX
590
+ headers = {"Accept": "application/json", "Authorization": config["token"]}
591
+ return (
592
+ api.put(url=f"{url_threats}/{threat_id}", headers=headers, json=threat)
593
+ if threat_id
594
+ else api.post(url=url_threats, headers=headers, json=threat)
595
+ )
596
+
597
+
598
+ def update_regscale_threats(
599
+ json_list: Optional[list] = None,
600
+ ) -> None:
601
+ """
602
+ Update the given threats in RegScale via concurrent POST or PUT of multiple objects
603
+
604
+ :param Optional[list] json_list: list of threats to be updated, defaults to None
605
+ :rtype: None
606
+ """
607
+ if json_list and len(json_list) > 0:
608
+ logger.info("Updating %i threats to RegScale...", len(json_list))
609
+ Threat.bulk_update(None, json_list)
610
+
611
+
612
+ def unique(lst: List[str]) -> List[str]:
613
+ """
614
+ Make a list unique, but don't change the order
615
+
616
+ :param List[str] lst: List to make unique
617
+ :return: List with unique values
618
+ :rtype: List[str]
619
+ """
620
+ unique_list = []
621
+ seen = set()
622
+ for item in lst:
623
+ if item not in seen:
624
+ unique_list.append(item)
625
+ seen.add(item)
626
+ return unique_list
627
+
628
+
629
+ def is_url(url: str) -> bool:
630
+ """
631
+ Determines if the given string is a URL
632
+
633
+ :param str url: A candidate URL string
634
+ :return: Whether the given string is a valid URL
635
+ :rtype: bool
636
+ """
637
+ try:
638
+ result = urlparse(url)
639
+ return all([result.scheme, result.netloc])
640
+ except ValueError:
641
+ return False