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,93 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Dataclass for a Microsoft Defender recommendations or alerts"""
4
+
5
+ # standard python imports
6
+ from dataclasses import dataclass
7
+ from typing import Optional
8
+
9
+ from pydantic import BaseModel, Field, field_validator, ConfigDict
10
+
11
+
12
+ @dataclass
13
+ class DefenderData(BaseModel):
14
+ """DefenderData Model"""
15
+
16
+ _model_config = ConfigDict(arbitrary_types_allowed=True)
17
+
18
+ id: str # Required
19
+ data: dict # Required
20
+ system: str # Required
21
+ object: str # Required
22
+ analyzed: Optional[str] = Field(default=False) # type: ignore
23
+ created: Optional[str] = Field(default=False) # type: ignore
24
+ integration_field: Optional[str] = None
25
+ init_key: Optional[str] = None
26
+
27
+ def __init__(self, *args, **data):
28
+ super().__init__(*args, **data)
29
+ self.integration_field = self.get_integration_field(self.system, self.object)
30
+ self.init_key = self.get_init_key()
31
+
32
+ @field_validator("system")
33
+ def validate_system(cls, v: str) -> str:
34
+ """
35
+ Validates the riskAdjustment field.
36
+
37
+ :param str v: The value to validate
38
+ :raise ValueError: If the value is not valid
39
+
40
+ :return: The validated values
41
+ :rtype: str
42
+
43
+ """
44
+ allowed_values = ["365", "cloud"]
45
+ if v not in allowed_values:
46
+ raise ValueError(f"system must be one of {allowed_values}")
47
+ return v
48
+
49
+ @field_validator("object")
50
+ def validate_object(cls, v: str) -> str:
51
+ """
52
+ Validates the riskAdjustment field.
53
+
54
+ :param str v: The value to validate
55
+ :raise ValueError: If the value is not valid
56
+
57
+ :return: The validated values
58
+ :rtype: str
59
+
60
+ """
61
+ allowed_values = ["alerts", "recommendations"]
62
+ if v not in allowed_values:
63
+ raise ValueError(f"object must be one of {allowed_values}")
64
+ return v
65
+
66
+ @staticmethod
67
+ def get_integration_field(system: str, object: str) -> str:
68
+ """
69
+ Get the integration field for the provided system and object
70
+
71
+ :return: The integration field for the provided system and object
72
+ :rtype: str
73
+ """
74
+ issue_integration_field = {
75
+ "365_alerts": "defenderAlertId",
76
+ "365_recommendations": "defenderId",
77
+ "cloud_alerts": "defenderCloudId",
78
+ "cloud_recommendations": "manualDetectionId",
79
+ }
80
+ return issue_integration_field.get(f"{system}_{object}", "pluginId")
81
+
82
+ def get_init_key(self) -> str:
83
+ """
84
+ Get the init.yaml key for the system and object
85
+
86
+ :return: The init.yaml key for the system and object
87
+ :rtype: str
88
+ """
89
+ init_mapping = {
90
+ "365": "defender365",
91
+ "cloud": "defenderCloud",
92
+ }
93
+ return init_mapping.get(self.system, "defender")
@@ -0,0 +1,143 @@
1
+ """
2
+ Integration model to import data from Defender .csv export
3
+ """
4
+
5
+ import json
6
+ from typing import Optional
7
+
8
+ from regscale.core.app.application import Application
9
+ from regscale.core.app.logz import create_logger
10
+ from regscale.core.app.utils.app_utils import get_current_datetime, is_valid_fqdn
11
+ from regscale.core.utils.date import datetime_obj, datetime_str
12
+ from regscale.models import Asset, ImportValidater, Vulnerability
13
+ from regscale.models.integration_models.flat_file_importer import FlatFileImporter
14
+
15
+
16
+ class DefenderImport(FlatFileImporter):
17
+ def __init__(self, **kwargs):
18
+ self.name = kwargs.get("name")
19
+ self.vuln_title = "SUBASSESSMENTNAME"
20
+ self.vuln_id = "SUBASSESSMENTID"
21
+ logger = create_logger()
22
+ self.fmt = "%Y-%m-%d"
23
+ self.dt_format = "%Y-%m-%d %H:%M:%S"
24
+ self.required_headers = [
25
+ "SEVERITY",
26
+ self.vuln_title,
27
+ self.vuln_id,
28
+ ]
29
+ self.mapping_file = kwargs.get("mappings_path")
30
+ self.disable_mapping = kwargs.get("disable_mapping")
31
+ self.validater = ImportValidater(
32
+ self.required_headers, kwargs.get("file_path"), self.mapping_file, self.disable_mapping
33
+ )
34
+ self.headers = self.validater.parsed_headers
35
+ self.mapping = self.validater.mapping
36
+
37
+ super().__init__(
38
+ logger=logger,
39
+ app=Application(),
40
+ headers=self.headers,
41
+ asset_func=self.create_asset,
42
+ vuln_func=self.create_vuln,
43
+ extra_headers_allowed=True,
44
+ **kwargs,
45
+ )
46
+
47
+ def determine_first_seen(self, dat: dict) -> str:
48
+ """
49
+ Determine the first seen date of the vulnerability
50
+
51
+ :param dict dat: Data row from CSV file
52
+ :return: The first seen date as a string
53
+ :rtype: str
54
+ """
55
+ # Remove the 'Z' at the end
56
+ iso_string = self.mapping.get_value(dat, "TIMEGENERATED", "").rstrip("Z")
57
+
58
+ # Convert to datetime object
59
+ dt_object = datetime_obj(iso_string)
60
+
61
+ return datetime_str(dt_object, self.dt_format)
62
+
63
+ def create_asset(self, dat: Optional[dict] = None) -> Asset:
64
+ """
65
+ Create an asset from a row in the Snyk file
66
+
67
+ :param Optional[dict] dat: Data row from CSV file, defaults to None
68
+ :return: RegScale Asset object
69
+ :rtype: Asset
70
+ """
71
+ additional_data = json.loads(self.mapping.get_value(dat, "ADDITIONALDATA", {}))
72
+ os = Asset.find_os(additional_data.get("imageDetails", {}).get("osDetails", ""))
73
+ name = additional_data.get("repositoryName", "")
74
+ valid_name = is_valid_fqdn(name)
75
+ return Asset(
76
+ **{
77
+ "id": 0,
78
+ "name": name,
79
+ "ipAddress": "0.0.0.0",
80
+ "isPublic": True,
81
+ "status": "Active (On Network)",
82
+ "assetCategory": "Software",
83
+ "bLatestScan": True,
84
+ "bAuthenticatedScan": True,
85
+ "scanningTool": self.name,
86
+ "assetOwnerId": self.config["userId"],
87
+ "assetType": "Other",
88
+ "fqdn": name if valid_name else None,
89
+ "systemAdministratorId": self.config["userId"],
90
+ "parentId": self.attributes.parent_id,
91
+ "parentModule": self.attributes.parent_module,
92
+ "operatingSystem": os,
93
+ }
94
+ )
95
+
96
+ def create_vuln(self, dat: Optional[dict] = None, **kwargs: dict) -> Optional[Vulnerability]:
97
+ """
98
+ Create a vulnerability from a row in the Snyk csv file
99
+
100
+ :param Optional[dict] dat: Data row from CSV file, defaults to None
101
+ :param dict **kwargs: Additional keyword arguments
102
+ :return: RegScale Vulnerability object or None
103
+ :rtype: Optional[Vulnerability]
104
+ """
105
+ regscale_vuln = None
106
+ severity = self.mapping.get_value(dat, "SEVERITY", "").lower()
107
+ additional_data = json.loads(self.mapping.get_value(dat, "ADDITIONALDATA", {}))
108
+ hostname = additional_data.get("repositoryName", "")
109
+ description = self.mapping.get_value(dat, self.vuln_title)
110
+ solution = self.mapping.get_value(dat, self.vuln_id)
111
+ config = self.attributes.app.config
112
+ asset_match = [asset for asset in self.data["assets"] if asset.name == hostname]
113
+ asset = asset_match[0] if asset_match else None
114
+ cves = [cve.get("title", "") for cve in additional_data.get("cve", [])]
115
+ cvss_v3_score = float(additional_data.get("cvssV30Score", 0))
116
+ if dat and asset_match:
117
+ regscale_vuln = Vulnerability(
118
+ id=0,
119
+ scanId=0, # set later
120
+ parentId=asset.id,
121
+ parentModule="assets",
122
+ ipAddress="0.0.0.0", # No ip address available
123
+ lastSeen=get_current_datetime(),
124
+ firstSeen=self.determine_first_seen(dat),
125
+ daysOpen=None,
126
+ dns=hostname,
127
+ mitigated=None,
128
+ operatingSystem=None,
129
+ severity=severity,
130
+ plugInName=description,
131
+ cve=", ".join(cves) if cves else self.mapping.get_value(dat, self.vuln_title),
132
+ vprScore=None,
133
+ cvsSv3BaseScore=cvss_v3_score,
134
+ tenantsId=0,
135
+ title=f"{description} on asset {asset.name}",
136
+ description=description,
137
+ plugInText=self.mapping.get_value(dat, self.vuln_title),
138
+ createdById=config["userId"],
139
+ lastUpdatedById=config["userId"],
140
+ dateCreated=get_current_datetime(),
141
+ extra_data={"solution": solution},
142
+ )
143
+ return regscale_vuln
@@ -0,0 +1,443 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """A class to import DRF forms"""
4
+ import re
5
+ from datetime import datetime
6
+ from typing import Any, Optional, Union
7
+
8
+ from openpyxl import Workbook, load_workbook, worksheet
9
+ from pathlib import Path
10
+ from rich.progress import Progress, track
11
+
12
+ from regscale.core.app.logz import create_logger
13
+ from regscale.core.app.utils.app_utils import create_progress_object
14
+ from regscale.integrations.integration.issue import IntegrationIssue
15
+ from regscale.models.regscale_models.deviation import Deviation
16
+ from regscale.models.regscale_models.issue import Issue
17
+ from regscale.models.regscale_models.property import Property
18
+
19
+ DR_SUBMISSION_DATE = "DR Submission Date"
20
+ CVSS_BASE_SCORE = "CVSS Base Score"
21
+ ADJUSTED_CVSS_SCORE = "Adjusted CVSS Score"
22
+ DR_NUMBER = "DR Number"
23
+
24
+
25
+ class DRF(IntegrationIssue):
26
+ """
27
+ Deviation Request Form class
28
+ """
29
+
30
+ def __init__(self, **kwargs):
31
+ super().__init__(**kwargs)
32
+ self.properties = []
33
+ logger = create_logger()
34
+ self.logger = logger
35
+ file_path: Union[str, None] = kwargs.get("file_path", None)
36
+ if not file_path:
37
+ raise ValueError("File path is required")
38
+ self.file_path = Path(file_path)
39
+ self.id_other_identifier_map = self.get_id_map()
40
+ self.module = kwargs.get("module", "securityplans")
41
+ self.module_id = kwargs.get("module_id", 0)
42
+
43
+ self.metadata = {}
44
+ self.report = []
45
+ self.errors = []
46
+ self.existing_deviations = Deviation.get_existing_deviations_by_ssp(
47
+ self.module_id,
48
+ issue_ids=[poam.get("id") for poam in self.id_other_identifier_map.values()],
49
+ poam_map=self.id_other_identifier_map,
50
+ )
51
+ self.drf_data = []
52
+ self.missed_drf_data = []
53
+ self.import_drf()
54
+ self.logger.info("Saving DR Identifiers to Issue properties.. ")
55
+ create_properties = [
56
+ prop
57
+ for prop in self.properties
58
+ if prop.value.upper()
59
+ not in {
60
+ val.get("dr_number").upper() for val in self.id_other_identifier_map.values() if val.get("dr_number")
61
+ }
62
+ ]
63
+ update_properties = self.get_update_properties()
64
+ if create_properties:
65
+ Property.batch_create(create_properties)
66
+ if update_properties:
67
+ Property.batch_update(update_properties)
68
+ self.export_report()
69
+
70
+ def pull(self) -> tuple[Workbook, list[str]]:
71
+ """
72
+ Pull data from Excel Workbook
73
+
74
+ :return: The workbook and the sheets
75
+ :rtype: tuple[Workbook, list[str]]
76
+ """
77
+ workbook = load_workbook(filename=self.file_path, data_only=True, read_only=True)
78
+ sheets = workbook.sheetnames
79
+ pattern = "DR Sheet"
80
+
81
+ return workbook, [item for item in sheets if re.search(pattern, item)]
82
+
83
+ def gen_metadata(self) -> dict:
84
+ """
85
+ Generate metadata from the DRF sheet including the system information and POC information, example:
86
+ {
87
+ "CSP Name": "CSP Name",
88
+ "System Name": "System Name",
89
+ "Impact Level": "Impact Level",
90
+ "DR Submission Date": "DR Submission Date",
91
+ "Name": "Name",
92
+ "Title": "Title",
93
+ "Phone": "Phone",
94
+ "Email": "Email"
95
+ }
96
+ :return: The metadata as a dictionary example:
97
+ :rtype: dict
98
+ """
99
+ import pandas as pd # Optimize import performance
100
+ import copy
101
+
102
+ instructions_df = pd.read_excel(str(self.file_path), sheet_name="DR Sheet", nrows=20)
103
+ plan_df = copy.deepcopy(instructions_df)
104
+ csp_df = copy.deepcopy(instructions_df)
105
+
106
+ found_items = 0
107
+ attempts = 0
108
+ for index in range(len(instructions_df)):
109
+ attempts += 1
110
+ row_headers = [str(val).strip().lower() for val in instructions_df.iloc[index].values]
111
+ if found_items == 2 or attempts > 10:
112
+ break
113
+ elif "csp name" in row_headers:
114
+ plan_df.columns = plan_df.iloc[index]
115
+ plan_df = plan_df[index + 1 :]
116
+ plan_df = plan_df[["CSP Name", "System Name", "Impact Level", "DR Submission Date"]]
117
+ found_items += 1
118
+ elif "name" in row_headers:
119
+ csp_df.columns = csp_df.iloc[index]
120
+ csp_df = csp_df[index + 1 :]
121
+ csp_df = csp_df[["Name", "Title", "Phone", "Email"]]
122
+ found_items += 1
123
+ try:
124
+ data = plan_df.to_dict(orient="records")[0] | csp_df.to_dict(orient="records")[0]
125
+ except (IndexError, TypeError, AttributeError, KeyError):
126
+ data = {}
127
+ return data
128
+
129
+ def import_drf(self) -> Workbook:
130
+ """
131
+ Import the DRF
132
+
133
+ :rtype: Workbook
134
+ :return: The workbook
135
+ """
136
+ workbook, drf_sheets = self.pull()
137
+ with create_progress_object() as progress:
138
+ for sheet in drf_sheets:
139
+ ws = workbook[sheet]
140
+ self.metadata = self.gen_metadata()
141
+ self.parse_sheet_and_generate_deviations(ws, sheet, progress)
142
+ self.save_deviations(progress)
143
+ return workbook
144
+
145
+ def parse_sheet_and_generate_deviations(self, ws: worksheet, sheet: str, progress: Progress):
146
+ """
147
+ Parse the sheet and generate deviations
148
+
149
+ :param worksheet ws: The worksheet
150
+ :param str sheet: The sheet name
151
+ :param Progress progress: The progress object
152
+ """
153
+ parsing_drfs = progress.add_task(f"[#ef5d23]Parsing '{sheet}' sheet for DRFs...", total=ws.max_row)
154
+ columns = []
155
+ found_columns = False
156
+ row_number = 0
157
+ min_row = 0
158
+ for row_number in range(ws.max_row + 1):
159
+ try:
160
+ columns = [(cell.value.replace("\n", " ")).strip() for cell in ws[row_number] if cell.value]
161
+ except (AttributeError, IndexError, TypeError):
162
+ continue
163
+ if DR_NUMBER in columns:
164
+ found_columns = True
165
+ break
166
+ if not found_columns:
167
+ self.logger.error("Unable to find DR Number column in sheet %s... Skipping sheet.", sheet)
168
+ return
169
+ for index, row in enumerate(ws.iter_rows(min_row=row_number + 1, max_row=ws.max_row, values_only=True)):
170
+ if row[0] and isinstance(row[0], str) and row[0].upper().startswith("DR-"):
171
+ drf = self.gen_drf_from_row(columns=columns, row=row, index=index + min_row, sheet=sheet)
172
+ if drf:
173
+ self.drf_data.append(drf)
174
+ else:
175
+ self.missed_drf_data.append(row)
176
+ progress.update(parsing_drfs, advance=1)
177
+ self.logger.info("Found %s Deviations ready to create or update", len(self.drf_data))
178
+
179
+ def save_deviations(self, progress: Progress) -> None:
180
+ """
181
+ Save the deviations to RegScale
182
+
183
+ :param Progress progress: The progress object
184
+ :rtype: None
185
+ """
186
+ saving_drfs = progress.add_task(
187
+ "[#D9F837]Creating or Updating Deviations in RegScale...", total=len(self.drf_data)
188
+ )
189
+ for dev in self.drf_data:
190
+ if dev.extra_data["dr_number"] in {ex.extra_data["dr_number"] for ex in self.existing_deviations}:
191
+ dev.id = [
192
+ item.id
193
+ for item in self.existing_deviations
194
+ if item.extra_data["dr_number"].upper() == dev.extra_data["dr_number"].upper()
195
+ ].pop()
196
+ dev.save()
197
+ else:
198
+ dev.create()
199
+ progress.update(saving_drfs, advance=1)
200
+
201
+ def get_property(self, dr_number: str, matching_poam_id: int):
202
+ """
203
+ Get the property and update the list
204
+ """
205
+ prop = Property(
206
+ name=DR_NUMBER,
207
+ key="dr_number",
208
+ value=dr_number,
209
+ parentId=matching_poam_id,
210
+ parentModule="issues",
211
+ label="Deviation Request Number",
212
+ isPublic=True,
213
+ )
214
+ self.properties.append(prop)
215
+
216
+ def parse_cvs_score(self, score: Optional[str] = None) -> float:
217
+ """
218
+ Function to parse the CVSS Base Score from a string from an excel workbook
219
+
220
+ :param Optional[str] score: The score to parse
221
+ :return: The parsed score or 0.0
222
+ :rtype: float
223
+ """
224
+ import math
225
+
226
+ if not score or (isinstance(score, float) and math.isnan(score)):
227
+ return 0.0
228
+ if score.isnumeric():
229
+ return float(score)
230
+ if score.isdigit():
231
+ return float(score)
232
+ if score and isinstance(score, str):
233
+ try:
234
+ return float(score)
235
+ except ValueError:
236
+ self.logger.error("Unable to parse base score: %s", score)
237
+ return 0.0
238
+ return 0.0
239
+
240
+ def gen_drf_from_row(self, columns: list[str], row: tuple, index: int, sheet: str) -> Optional[Deviation]:
241
+ """
242
+ Generate a Deviation from a row
243
+
244
+ :param list[str] columns: The columns
245
+ :param tuple row: The row
246
+ :param int index: The index
247
+ :param str sheet: The sheet
248
+
249
+ :return: The Deviation or None
250
+ :rtype: Optional[Deviation]
251
+ """
252
+
253
+ def get_val(index_str: str, default_val: Optional[Any] = None) -> Optional[Any]:
254
+ """
255
+ Get the value from the row
256
+
257
+ :param str index_str: The index string
258
+ :param Optional[Any] default_val: The default value to use if nothing is found
259
+ :return: The value or None
260
+ :rtype: Optional[Any]
261
+ """
262
+ index_str = (str(index_str)).strip() if index_str and isinstance(index_str, str) else ""
263
+ try:
264
+ if (dat := row[columns.index(index_str)]) is not None:
265
+ return str(dat)
266
+ except ValueError:
267
+ self.logger.error("Unable to find column %s in sheet %s", index_str, sheet)
268
+ except TypeError:
269
+ self.logger.error("Type Error: %s, %s", index, sheet)
270
+ return str(default_val) if default_val else None
271
+
272
+ # Unique Ident Coalfire
273
+ dr_number = get_val(DR_NUMBER).upper().strip() if get_val(DR_NUMBER) else ""
274
+ poam_id = get_val("POA&M ID").upper().strip() if get_val("POA&M ID") else ""
275
+
276
+ matching_poam = self.id_other_identifier_map.get(poam_id)
277
+ if not matching_poam:
278
+ self.report.append({"dr_number": dr_number, "poam_id": poam_id, "status": "Unmatched"})
279
+ return
280
+ matching_poam_id = matching_poam.get("id")
281
+
282
+ self.get_property(dr_number=dr_number, matching_poam_id=matching_poam_id)
283
+
284
+ deviation_type = Deviation.mapping().get(get_val("Type of DR"))
285
+ justification = get_val("Justification")
286
+ if deviation_type == "Risk Adjustment (RA)" and not justification:
287
+ justification = "Unknown Justification"
288
+ requested_risk_rating = (
289
+ get_val("Requested Risk Rating/Impact") if get_val("Requested Risk Rating/Impact") else "Low"
290
+ )
291
+ self.report.append({"dr_number": dr_number, "poam_id": poam_id, "status": "Matched"})
292
+ if requested_risk_rating and requested_risk_rating.lower() not in ["low", "moderate", "high"]:
293
+ self.logger.error("A valid Requested Risk Rating is required for %s", dr_number)
294
+ self.errors.append(
295
+ {"dr_number": dr_number, "error": f"The Requested Risk Rating {requested_risk_rating} is invalid"}
296
+ )
297
+ return
298
+ if not deviation_type:
299
+ self.logger.error("Unable to find deviation type for %s", dr_number)
300
+ self.errors.append({"dr_number": dr_number, "error": "Deviation Type not found"})
301
+ return None
302
+ if deviation_type == "Risk Adjustment (RA)" and not justification:
303
+ self.logger.error("Justification is required for RA Deviation %s", dr_number)
304
+ self.errors.append({"dr_number": dr_number, "error": f"Justification is required for {deviation_type}"})
305
+ return None
306
+ from regscale.core.utils.date import datetime_str
307
+
308
+ return Deviation(
309
+ id=0,
310
+ otherIdentifier=poam_id,
311
+ extra_data={"dr_number": dr_number},
312
+ baseScore=self.parse_cvs_score(get_val(CVSS_BASE_SCORE)),
313
+ environmentalScore=(
314
+ float(get_val(ADJUSTED_CVSS_SCORE))
315
+ if get_val(ADJUSTED_CVSS_SCORE) and str(get_val(ADJUSTED_CVSS_SCORE)).isnumeric()
316
+ else None
317
+ ),
318
+ parentIssueId=matching_poam_id,
319
+ isPublic=True,
320
+ deviationType=deviation_type,
321
+ requestedImpactRating=requested_risk_rating,
322
+ dateSubmitted=(
323
+ get_val(DR_SUBMISSION_DATE).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
324
+ if isinstance(get_val(DR_SUBMISSION_DATE), datetime)
325
+ else datetime_str(get_val(DR_SUBMISSION_DATE))
326
+ ), # Format must be 2024-08-15T19:00:26.372Z
327
+ rationale=get_val("DR Rationale", ""),
328
+ evidenceDescription=(get_val("Evidence Description")),
329
+ operationalImpacts=(
330
+ get_val("Operational Impact Statement") if get_val("Operational Impact Statement") else "N/A"
331
+ ),
332
+ riskJustification=justification,
333
+ tmpExploitCodeMaturity=None,
334
+ tmpRemediationLevel=get_val("Remediation Level"),
335
+ tmpReportConfidence=None,
336
+ envConfidentiality=get_val("Impact Metrics: Confidentiality"),
337
+ envIntegrity=get_val("Impact Metrics: Integrity"),
338
+ envAvailability=get_val("Impact Metrics: Availability"),
339
+ envAttackVector=get_val("Attack Vector"),
340
+ envAttackComplexity=get_val("Attack Complexity"),
341
+ envPrivilegesRequired=get_val("Privileges Required"),
342
+ envUserInteraction=get_val("User Interaction"),
343
+ envScope=None,
344
+ envModConfidentiality=None,
345
+ envModIntegrity=None,
346
+ envModAvailability=None,
347
+ vulnerabilityId=get_val("Vulnerability Name"),
348
+ envAttackVectorExplanation=get_val("Attack Vector Explanation"),
349
+ envAttackComplexityExplanation=get_val("Attack Complexity Explanation"),
350
+ envPrivilegesRequiredExplanation=get_val("Privileges Required Explanation"),
351
+ envUserInteractionExplanation=get_val("User Interaction Explanation"),
352
+ envConfidentialityExplanation=get_val("Impact Metrics: Confidentiality Explanation"),
353
+ envIntegrityExplanation=get_val("Impact Metrics: Integrity Explanation"),
354
+ envAvailabilityExplanation=get_val("Impact Metrics: Availability Explanation"),
355
+ tmpExploitCodeMaturityExplanation=None,
356
+ tmpRemediationLevelExplanation=get_val("Remediation Level Explanation"),
357
+ tmpReportConfidenceExplanation=None,
358
+ baseSeverity=get_val("Initial Risk Rating"),
359
+ temporalSeverity=None,
360
+ environmentalSeverity=None,
361
+ finalVectorString=None,
362
+ overallRiskReductionExplanation=get_val("List of Risk Reduction"),
363
+ evidenceAttachments=get_val("List of Evidence Attachments")
364
+ or get_val("List of Operational Requirement Attachments"),
365
+ )
366
+
367
+ def get_id_map(self) -> dict:
368
+ """
369
+ Get the ID map
370
+
371
+ :return: The ID map
372
+ :rtype: dict
373
+ """
374
+ self.logger.info(f"Fetching all issues for {self.module} #{self.module_id}...")
375
+ id_map = {}
376
+ all_issues = Issue.get_all_by_parent(parent_id=self.module_id, parent_module=self.module)
377
+ self.logger.info("Fetched %s issue(s) from RegScale.", len(all_issues))
378
+
379
+ for issue in track(all_issues, description="Building id-otherIdentifier lookup..."):
380
+ if issue.otherIdentifier:
381
+ ident = issue.otherIdentifier.upper()
382
+ id_map[ident] = {"id": issue.id, "dr_number": None, "prop_id": None}
383
+
384
+ properties = Property.get_all_by_parent(parent_id=issue.id, parent_module="issues")
385
+ dr_number_property = next((prop for prop in properties if prop.key == "dr_number"), None)
386
+ if dr_number_property:
387
+ id_map[ident]["dr_number"] = dr_number_property.value.upper()
388
+ id_map[ident]["prop_id"] = dr_number_property.id
389
+ self.logger.info(
390
+ "Constructed a map of %s issues with POAM id (otherIdentifier) and a nested dictionary of dr_number, issue_key, and property_key",
391
+ len(id_map),
392
+ )
393
+ return id_map
394
+
395
+ def export_report(self):
396
+ """Save a Report of missing POAMs to a file"""
397
+ timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
398
+ report_file = self.file_path.parent / f"DRF-Import-Report_{timestamp}.csv"
399
+ with open(report_file, "w") as file:
400
+ file.write("DR Number,POAM ID,Status,Errors\n")
401
+ for item in self.report:
402
+ item["errors"] = []
403
+ errors = [er for er in self.errors if er["dr_number"] == item["dr_number"]]
404
+ if errors:
405
+ for error in errors:
406
+ item["status"] = "Error"
407
+ item["errors"].append(error)
408
+ if item["status"] in ["Unmatched", "Error"]:
409
+ file.write(f"{item['dr_number']},{item['poam_id']},{item['status']},{item['errors']}\n")
410
+ self.logger.info("Mismatched POAM Report saved to %s", report_file)
411
+
412
+ def get_update_properties(self) -> list[Property]:
413
+ """
414
+ Get the properties to update
415
+
416
+ :return: The properties to update
417
+ :rtype: list[Property]
418
+ """
419
+ # Filter properties that have a corresponding 'dr_number' and 'prop_id' in 'id_other_identifier_map'
420
+ props = [
421
+ prop
422
+ for prop in self.properties
423
+ if prop.value
424
+ in {
425
+ val.get("dr_number")
426
+ for val in self.id_other_identifier_map.values()
427
+ if val.get("dr_number") and val.get("prop_id")
428
+ }
429
+ ]
430
+
431
+ # Iterate over the filtered properties
432
+ for prop in props:
433
+ # Find the matching value in 'id_other_identifier_map' where 'dr_number' equals the property value
434
+ match = next(
435
+ (val for val in self.id_other_identifier_map.values() if val.get("dr_number") == prop.value), None
436
+ )
437
+
438
+ # If a match is found, update the property id
439
+ if match:
440
+ prop.id = match.get("prop_id")
441
+
442
+ # Return the list of properties that have an id
443
+ return [prop for prop in props if prop.id]