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,504 @@
1
+ """
2
+ Module for Tenable vulnerability scanning integration.
3
+ """
4
+
5
+ import datetime
6
+ import json
7
+ import linecache
8
+ import logging
9
+ from pathlib import Path
10
+ from typing import Any, Dict, Iterator, List, Optional, Tuple
11
+
12
+ from tenable.errors import TioExportsError
13
+
14
+ from regscale.core.app.utils.app_utils import get_current_datetime
15
+ from regscale.core.utils.date import datetime_obj
16
+ from regscale.integrations.commercial.nessus.nessus_utils import get_min_cvss_score, validate_nessus_severity
17
+ from regscale.integrations.commercial.tenablev2.authenticate import gen_tio
18
+ from regscale.integrations.commercial.tenablev2.stig_parsers import parse_stig_output
19
+ from regscale.integrations.commercial.tenablev2.utils import get_last_pull_epoch
20
+ from regscale.integrations.commercial.tenablev2.variables import TenableVariables
21
+ from regscale.integrations.scanner_integration import IntegrationAsset, IntegrationFinding, ScannerIntegration
22
+ from regscale.integrations.variables import ScannerVariables
23
+ from regscale.models import regscale_models
24
+
25
+ logger = logging.getLogger("regscale")
26
+
27
+
28
+ class TenableIntegration(ScannerIntegration):
29
+ """Integration class for Tenable vulnerability scanning."""
30
+
31
+ title: str = "Tenable"
32
+ asset_identifier_field: str = "tenableId"
33
+ finding_severity_map: Dict[str, regscale_models.IssueSeverity] = {
34
+ "critical": regscale_models.IssueSeverity.Critical,
35
+ "high": regscale_models.IssueSeverity.High,
36
+ "medium": regscale_models.IssueSeverity.Moderate,
37
+ "low": regscale_models.IssueSeverity.Low,
38
+ }
39
+
40
+ def __init__(self, plan_id: int, tenant_id: int = 1, tags: Optional[List[str]] = None, **kwargs):
41
+ """
42
+ Initialize the TenableIntegration.
43
+
44
+ :param int plan_id: The ID of the security plan
45
+ :param int tenant_id: The ID of the tenant, defaults to 1
46
+ """
47
+ super().__init__(plan_id, tenant_id)
48
+ self.client = None
49
+ self.tags = tags or []
50
+
51
+ def authenticate(self) -> None:
52
+ """Authenticate to Tenable."""
53
+ self.client = gen_tio()
54
+
55
+ def fetch_assets(self, *args: Any, **kwargs: Any) -> Iterator[IntegrationAsset]:
56
+ """
57
+ Fetches Tenable assets using the Tenable.io API
58
+
59
+ :yields: Iterator[IntegrationAsset]
60
+ """
61
+ tags: List[Tuple[str, str]] = kwargs.get("tags", [])
62
+ # Create artifacts directory if not exist
63
+ Path.mkdir(Path("./artifacts"), exist_ok=True, parents=True)
64
+ current_datetime = datetime.datetime.now().strftime("%Y%m%d%H")
65
+ cache_file = Path("./artifacts") / Path(f"tenable_assets_{self.plan_id}_{current_datetime}.json")
66
+
67
+ if (
68
+ cache_file.exists()
69
+ and (datetime.datetime.now() - datetime.datetime.fromtimestamp(cache_file.stat().st_mtime)).days < 1
70
+ and not self.is_empty(cache_file)
71
+ ):
72
+ logger.info("Loading assets from cache...")
73
+ else:
74
+ self.authenticate()
75
+ logger.info("Fetching Tenable assets...")
76
+
77
+ if not self.client:
78
+ raise ValueError("Client not authenticated")
79
+
80
+ tenable_last_updated = self.get_last_update_time()
81
+ assets_iterator = (
82
+ self.client.exports.assets(updated_at=int(tenable_last_updated.timestamp()), tags=tags)
83
+ if tags
84
+ else self.client.exports.assets(updated_at=int(tenable_last_updated.timestamp()))
85
+ )
86
+ i = 0
87
+ with open(cache_file, "w") as f:
88
+ try:
89
+ for i, asset in enumerate(assets_iterator, 1):
90
+ f.write(json.dumps(asset) + "\n")
91
+ if i % 100 == 0:
92
+ logger.info(f"Fetched {i} assets")
93
+ except TioExportsError as e:
94
+ logger.error("Error fetching Tenable assets: %s", str(e))
95
+
96
+ logger.info(f"Total assets fetched: {i}")
97
+
98
+ # Count the number of lines in the file using linecache
99
+ self.num_assets_to_process = len(linecache.getlines(str(cache_file)))
100
+ logger.info(f"Total assets to process: {self.num_assets_to_process}")
101
+
102
+ # Process the assets
103
+ with open(cache_file, "r") as f:
104
+ for line in f:
105
+ asset = json.loads(line)
106
+ parsed_asset = self.parse_asset(asset)
107
+ yield parsed_asset
108
+
109
+ def get_last_update_time(self) -> datetime.datetime:
110
+ """
111
+ Get the last update time for Tenable assets.
112
+
113
+ :return: The last update time
114
+ :rtype: datetime.datetime
115
+ """
116
+ existing_assets: List[regscale_models.Asset] = regscale_models.Asset.get_all_by_parent(
117
+ parent_id=self.plan_id, parent_module="securityplans"
118
+ )
119
+ filtered_assets = [asset for asset in existing_assets if asset.tenableId and asset.dateLastUpdated]
120
+
121
+ if not filtered_assets:
122
+ return datetime.datetime.fromtimestamp(0)
123
+
124
+ return max(
125
+ datetime_obj(asset.dateLastUpdated) or datetime.datetime.fromtimestamp(0)
126
+ for asset in filtered_assets
127
+ if datetime_obj(asset.dateLastUpdated)
128
+ )
129
+
130
+ def parse_asset(self, node: Dict[str, Any]) -> IntegrationAsset:
131
+ """
132
+ Parses Tenable assets
133
+
134
+ :param Dict[str, Any] node: The Tenable asset to parse
135
+ :return: The parsed IntegrationAsset
136
+ :rtype: IntegrationAsset
137
+ """
138
+ system_types = node.get("system_types", [])
139
+ tenable_asset_type = system_types[0] if system_types else None
140
+ asset_type = self.map_tenable_to_regscale_asset_type(tenable_asset_type)
141
+
142
+ software_inventory = node.get("installed_software", [])
143
+ if software_inventory and isinstance(software_inventory[0], str):
144
+ software_inventory = [{"name": sw} for sw in software_inventory]
145
+
146
+ asset_id = node.get("id", "")
147
+ ipv4 = node.get("ipv4", "")
148
+ fqdn = node.get("fqdn", "")
149
+ if not asset_id:
150
+ asset_id = fqdn or ipv4
151
+
152
+ return IntegrationAsset(
153
+ name=self.get_asset_name(node),
154
+ external_id=node.get("uuid", ""),
155
+ identifier=asset_id,
156
+ asset_type=asset_type,
157
+ asset_owner_id=ScannerVariables.userId,
158
+ parent_id=self.plan_id,
159
+ parent_module=regscale_models.SecurityPlan.get_module_slug(),
160
+ asset_category=self.map_asset_category(tenable_asset_type),
161
+ date_last_updated=node.get("last_seen", ""),
162
+ status=self.map_tenable_status(node.get("terminated_at")),
163
+ ip_address=self.get_all_ip_addresses(node),
164
+ mac_address=self.get_all_mac_addresses(node),
165
+ fqdn=", ".join(node.get("fqdns", [])),
166
+ component_names=node.get("agent_names", []),
167
+ operating_system=", ".join(node.get("operating_systems", [])),
168
+ serial_number=node.get("bios_uuid", ""),
169
+ notes=self.generate_notes(node),
170
+ source_data=node,
171
+ software_inventory=software_inventory,
172
+ azure_identifier=node.get("azure_vm_id") or node.get("azure_resource_id", ""),
173
+ aws_identifier=node.get("aws_ec2_instance_id", ""),
174
+ google_identifier=node.get("gcp_instance_id", ""),
175
+ other_cloud_identifier=self.get_other_cloud_identifier(node),
176
+ )
177
+
178
+ @staticmethod
179
+ def get_asset_name(node: Dict[str, Any]) -> str:
180
+ """
181
+ Get the asset name from various possible sources
182
+
183
+ :param Dict[str, Any] node: The Tenable asset data
184
+ :return: The asset name
185
+ :rtype: str
186
+ """
187
+ for key in ["hostnames", "fqdns", "netbios_names", "ipv4s"]:
188
+ values = node.get(key, [])
189
+ if values and values[0]:
190
+ return values[0]
191
+ return "Unknown Asset"
192
+
193
+ @staticmethod
194
+ def _build_limited_string(items: set, char_limit: int = 450, tag: str = "Items") -> str:
195
+ """
196
+ Build a comma-separated string from items, respecting a character limit.
197
+
198
+ :param set items: Set of items to join
199
+ :param int char_limit: Maximum characters allowed (default: 450)
200
+ :return: Comma-separated string of items
201
+ :rtype: str
202
+ """
203
+ result = ""
204
+ for i, item in enumerate(items):
205
+ next_addition = item if i == 0 else ", " + item
206
+ if len(result) + len(next_addition) <= char_limit:
207
+ result += next_addition
208
+ else:
209
+ logger.warning("%s exceed character limit", tag)
210
+ break
211
+ return result
212
+
213
+ @staticmethod
214
+ def get_all_ip_addresses(ipv_node: Dict[str, Any]) -> str:
215
+ """
216
+ Get all IP addresses from both IPv4 and IPv6 nodes
217
+
218
+ :param Dict[str, Any] ipv_node: The IPv4 node
219
+ :return: Comma-separated string of IP addresses
220
+ :rtype: str
221
+ """
222
+ ip_addresses = set()
223
+ ip_addresses.update(ipv_node.get("ipv4s", []))
224
+ ip_addresses.update(ipv_node.get("ipv6s", []))
225
+ return TenableIntegration._build_limited_string(tag="IP Addresses", items=ip_addresses)
226
+
227
+ @staticmethod
228
+ def get_all_mac_addresses(node: Dict[str, Any]) -> str:
229
+ """
230
+ Get all MAC addresses from all network interfaces
231
+
232
+ :param Dict[str, Any] node: The Tenable asset data
233
+ :return: Comma-separated string of MAC addresses
234
+ :rtype: str
235
+ """
236
+ mac_addresses = set()
237
+ for interface in node.get("network_interfaces", []):
238
+ mac_addresses.update(interface.get("mac_addresses", []))
239
+ return TenableIntegration._build_limited_string(tag="MAC Addresses", items=mac_addresses)
240
+
241
+ @staticmethod
242
+ def generate_notes(node: Dict[str, Any]) -> str:
243
+ """
244
+ Generate notes from Tenable asset data
245
+
246
+ :param Dict[str, Any] node: The Tenable asset data
247
+ :return: Generated notes
248
+ :rtype: str
249
+ """
250
+ notes = []
251
+ if node.get("network_name"):
252
+ notes.append(f"Network: {node.get('network_name')}")
253
+ if node.get("acr_score") is not None:
254
+ notes.append(f"ACR Score: {node.get('acr_score')}")
255
+ if node.get("exposure_score") is not None:
256
+ notes.append(f"Exposure Score: {node.get('exposure_score')}")
257
+ if node.get("tags"):
258
+ tag_str = "; ".join([f"{tag.get('key', '')}: {tag.get('value', '')}" for tag in node.get("tags", [])])
259
+ notes.append(f"Tags: {tag_str}")
260
+ if node.get("sources"):
261
+ sources_str = "; ".join(
262
+ [
263
+ f"{source.get('name', '')} (First seen: {source.get('first_seen', '')}, Last seen: {source.get('last_seen', '')})"
264
+ for source in node.get("sources", [])
265
+ ]
266
+ )
267
+ notes.append(f"Sources: {sources_str}")
268
+ return "\n".join(notes)
269
+
270
+ @staticmethod
271
+ def get_other_cloud_identifier(node: Dict[str, Any]) -> Optional[str]:
272
+ """
273
+ Get other cloud identifier if present
274
+
275
+ :param Dict[str, Any] node: The Tenable asset data
276
+ :return: Other cloud identifier if present, None otherwise
277
+ :rtype: Optional[str]
278
+ """
279
+ if node.get("gcp_project_id"):
280
+ return f"GCP Project: {node.get('gcp_project_id')}"
281
+ if node.get("aws_vpc_id"):
282
+ return f"AWS VPC: {node.get('aws_vpc_id')}"
283
+ return None
284
+
285
+ @staticmethod
286
+ def map_asset_category(tenable_type: Optional[str]) -> str:
287
+ """
288
+ Map Tenable asset type to RegScale asset category.
289
+
290
+ :param Optional[str] tenable_type: The Tenable asset type
291
+ :return: Mapped asset category (either "Software" or "Hardware")
292
+ :rtype: regscale_models.AssetCategory
293
+ """
294
+ if not tenable_type:
295
+ return regscale_models.AssetCategory.Hardware # Default to Hardware if type is unknown
296
+
297
+ # List of types that are typically considered software
298
+ software_types = ["application"]
299
+
300
+ return (
301
+ regscale_models.AssetCategory.Software
302
+ if tenable_type.lower() in software_types
303
+ else regscale_models.AssetCategory.Hardware
304
+ )
305
+
306
+ @staticmethod
307
+ def map_tenable_status(terminated_at: Optional[str]) -> str:
308
+ """
309
+ Map Tenable status to IntegrationAsset status
310
+
311
+ :param Optional[str] terminated_at: The terminated_at value from Tenable
312
+ :return: Mapped status
313
+ :rtype: str
314
+ """
315
+ return "Off-Network" if terminated_at else "Active (On Network)"
316
+
317
+ @staticmethod
318
+ def map_tenable_to_regscale_asset_type(tenable_type: Optional[str]) -> regscale_models.AssetType:
319
+ """
320
+ Map Tenable asset type to RegScale AssetType enum.
321
+
322
+ :param Optional[str] tenable_type: The Tenable asset type
323
+ :return: Mapped RegScale AssetType
324
+ :rtype: regscale_models.AssetType
325
+ """
326
+ if not tenable_type:
327
+ return regscale_models.AssetType.Other
328
+
329
+ tenable_to_regscale_map = {
330
+ "general-purpose": regscale_models.AssetType.Desktop,
331
+ "laptop": regscale_models.AssetType.Laptop,
332
+ "server": regscale_models.AssetType.PhysicalServer,
333
+ "hypervisor": regscale_models.AssetType.VM,
334
+ "mobile": regscale_models.AssetType.Phone,
335
+ "network": regscale_models.AssetType.NetworkRouter,
336
+ "firewall": regscale_models.AssetType.Firewall,
337
+ "tablet": regscale_models.AssetType.Tablet,
338
+ "switch": regscale_models.AssetType.NetworkSwitch,
339
+ "appliance": regscale_models.AssetType.Appliance,
340
+ }
341
+
342
+ return tenable_to_regscale_map.get(tenable_type.lower(), regscale_models.AssetType.Other)
343
+
344
+ def fetch_findings(self, *args: Any, **kwargs: Any) -> Iterator[IntegrationFinding]:
345
+ """
346
+ Fetches Tenable findings using the Tenable.io API
347
+
348
+ :yields: Iterator[IntegrationFinding]
349
+ """
350
+ plan_id: int = int(kwargs.get("plan_id", self.plan_id))
351
+ tags: List[Tuple[str, str]] = kwargs.get("tags", [])
352
+ Path.mkdir(Path("./artifacts"), exist_ok=True, parents=True)
353
+ current_datetime = datetime.datetime.now().strftime("%Y%m%d%H")
354
+ cache_file = Path("./artifacts") / Path(f"tenable_findings_{self.plan_id}_{current_datetime}.json")
355
+
356
+ if (
357
+ cache_file.exists()
358
+ and (datetime.datetime.now() - datetime.datetime.fromtimestamp(cache_file.stat().st_mtime)).days < 1
359
+ and not self.is_empty(cache_file)
360
+ ):
361
+ logger.info("Loading findings from cache...")
362
+ else:
363
+ logger.info("Fetching findings from Tenable...")
364
+ minimum_severity = validate_nessus_severity(TenableVariables.tenableMinimumSeverityFilter)
365
+ cvss2_min = get_min_cvss_score(minimum_severity)
366
+ latest_scan: int = get_last_pull_epoch(regscale_ssp_id=plan_id)
367
+ logger.info("Latest scan: %s", datetime.datetime.fromtimestamp(latest_scan))
368
+ self.authenticate()
369
+ if not self.client:
370
+ raise ValueError("Client not authenticated")
371
+ with open(cache_file, "w", encoding="utf-8") as f:
372
+ findings_iterator = (
373
+ self.client.exports.vulns(
374
+ vpr_score={"gte": cvss2_min}, since=latest_scan, state=["OPEN", "REOPENED"], tags=tags
375
+ )
376
+ if tags
377
+ else self.client.exports.vulns(
378
+ vpr_score={"gte": cvss2_min}, since=latest_scan, state=["OPEN", "REOPENED"]
379
+ )
380
+ )
381
+ for i, line in enumerate(findings_iterator):
382
+ f.write(json.dumps(line) + "\n")
383
+ if i % 100 == 0:
384
+ logger.info(f"Fetched {i} vulnerabilities")
385
+
386
+ # Count the number of lines in the file using linecache
387
+ self.num_findings_to_process = len(linecache.getlines(str(cache_file)))
388
+ logger.info(f"Total findings to process: {self.num_findings_to_process}")
389
+ with open(cache_file, "r") as f:
390
+ for line in f:
391
+ parsed_asset = self.parse_finding(json.loads(line))
392
+ if parsed_asset:
393
+ yield parsed_asset
394
+
395
+ def parse_finding(self, vuln: Dict[str, Any]) -> Optional[IntegrationFinding]:
396
+ """
397
+ Parses a Tenable vulnerability into an IntegrationFinding object.
398
+
399
+ :param Dict[str, Any] vuln: The Tenable vulnerability to parse
400
+ :return: The parsed IntegrationFinding or None if parsing fails
401
+ :rtype: Optional[IntegrationFinding]
402
+ """
403
+ from regscale.core.app.application import Application
404
+
405
+ app = Application()
406
+
407
+ try:
408
+ # Extract relevant data from the vulnerability dict
409
+ asset = vuln.get("asset", {})
410
+ plugin = vuln.get("plugin", {})
411
+ plugin_output = vuln.get("output", "")
412
+ is_stig = "xccdf_mil.disa.stig_rule" in plugin_output
413
+ plugin_name = plugin.get("name", [])[0] if isinstance(plugin.get("name"), list) else plugin.get("name")
414
+
415
+ severity = vuln.get("severity", "info").lower()
416
+ severity_id = vuln.get("severity_id", 0)
417
+
418
+ # Determine if this is an informational item or a vulnerability
419
+ is_informational = severity == "info" or severity_id == 0
420
+
421
+ if is_informational and not is_stig:
422
+ logger.info(f"Ignoring Informational Vulnerability {plugin_name}")
423
+ return None
424
+
425
+ category = f"Tenable Vulnerability: {plugin.get('family', 'General')}"
426
+ issue_type = "Vulnerability"
427
+ severity_default = app.config.get("vulnerabilityMappingDefault", regscale_models.IssueSeverity.NotAssigned)
428
+ severity = self.finding_severity_map.get(severity, severity_default)
429
+ status = (
430
+ regscale_models.IssueStatus.Open if vuln.get("state") == "OPEN" else regscale_models.IssueStatus.Closed
431
+ )
432
+
433
+ # Mapping for severity strings to integers
434
+ severity_map = {"critical": 4, "high": 3, "medium": 2, "low": 1, "info": 0}
435
+ severity_int = severity_map.get(severity, 0)
436
+
437
+ identifier = plugin.get("cve", []) or [plugin.get("name", "")]
438
+ identifier = identifier[0] if isinstance(identifier, list) else identifier
439
+ asset_id = asset.get("uuid", "")
440
+ ipv4 = asset.get("ipv4", "")
441
+ fqdn = asset.get("fqdn", "")
442
+ if not asset_id:
443
+ asset_id = fqdn or ipv4
444
+
445
+ cve = plugin.get("cve", [])[0] if isinstance(plugin.get("cve"), list) else None
446
+
447
+ integration_finding = IntegrationFinding(
448
+ control_labels=[],
449
+ category=category,
450
+ title=f"{identifier}: {plugin.get('name', '')}",
451
+ issue_title=f"{identifier}: {plugin.get('name', '')}",
452
+ description=plugin.get("description", ""),
453
+ severity=severity,
454
+ status=status,
455
+ asset_identifier=asset_id,
456
+ external_id=str(plugin.get("id", "")),
457
+ first_seen=vuln.get("first_found", get_current_datetime()),
458
+ last_seen=vuln.get("last_found", get_current_datetime()),
459
+ remediation=plugin.get("solution", ""),
460
+ cvss_score=float(plugin.get("cvss3_base_score") or plugin.get("cvss_base_score") or 0),
461
+ cve=cve,
462
+ vulnerability_type=self.title,
463
+ plugin_id=str(plugin.get("id", "")),
464
+ plugin_name=plugin_name,
465
+ ip_address=asset.get("ipv4", ""),
466
+ dns=asset.get("fqdn", ""),
467
+ severity_int=severity_int,
468
+ issue_type=issue_type,
469
+ date_created=get_current_datetime(),
470
+ date_last_updated=get_current_datetime(),
471
+ gaps="",
472
+ observations=vuln.get("output", ""),
473
+ evidence=vuln.get("output", ""),
474
+ identified_risk=plugin.get("risk_factor", ""),
475
+ impact="",
476
+ recommendation_for_mitigation=plugin.get("solution", ""),
477
+ rule_id=str(plugin.get("id", "")),
478
+ rule_version=plugin.get("version", ""),
479
+ results=vuln.get("output", ""),
480
+ comments=None,
481
+ baseline="",
482
+ poam_comments=None,
483
+ vulnerable_asset=asset_id,
484
+ source_rule_id=str(plugin.get("id", "")),
485
+ )
486
+ if is_stig:
487
+ integration_finding = parse_stig_output(output=plugin_output, finding=integration_finding)
488
+ return integration_finding
489
+ except Exception as e:
490
+ logger.error("Error parsing Tenable finding: %s", str(e), exc_info=True)
491
+ return None
492
+
493
+ def is_empty(self, file_path: Path) -> bool:
494
+ """
495
+ Check if the file is empty.
496
+
497
+ :param Path file_path: The path to the file
498
+ :return: True if the file is empty, False otherwise
499
+ :rtype: bool
500
+ """
501
+ try:
502
+ return file_path.stat().st_size == 0
503
+ except FileNotFoundError:
504
+ return True
@@ -0,0 +1,140 @@
1
+ """Functions for parsing STIG output from Tenable"""
2
+
3
+ import logging
4
+ import re
5
+ from typing import Union
6
+
7
+ from regscale.core.app.utils.app_utils import get_current_datetime
8
+ from regscale.integrations.scanner_integration import IntegrationFinding, issue_due_date
9
+ from regscale.models import regscale_models
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def parse_stig_output(output: str, finding: IntegrationFinding) -> IntegrationFinding:
15
+ """
16
+ Parses STIG output and constructs a finding dictionary matching IntegrationFinding.
17
+
18
+ :param str output: The STIG output string to parse.
19
+ :param IntegrationFinding finding: The finding to update.
20
+ :return: An IntegrationFinding object containing the parsed finding information.
21
+ :rtype: IntegrationFinding
22
+ """
23
+
24
+ def _extract_field(pattern: str, text: str, flags: Union[int, re.RegexFlag] = 0, group: int = 1) -> str:
25
+ """
26
+ Extracts a field from a string using a regular expression
27
+
28
+ :param str pattern: The regular expression pattern to search for
29
+ :param str text: The string to search in
30
+ :param int flags: Optional regular expression flags, defaults to 0
31
+ :param int group: The group number to return from the match, defaults to 1
32
+ :return: The extracted field as a string. Empty string if no match was found
33
+ :rtype: str
34
+ """
35
+ match = re.search(pattern, text, flags)
36
+ return match.group(group).strip() if match else ""
37
+
38
+ # Extract fields
39
+ check_name_full = _extract_field(r"Check Name:\s*(.*?)\n", output, flags=re.DOTALL | re.MULTILINE)
40
+ check_name_parts = check_name_full.split(":", 1)
41
+ rule_id = check_name_parts[0].strip()
42
+ check_description = check_name_parts[1].strip() if len(check_name_parts) > 1 else ""
43
+
44
+ baseline = _extract_field(r"(.*?)\s+Target\s+(.*)", check_description, group=2)
45
+ if target_match := _extract_field(r"(.*?)\s+Target\s+(.*)", check_description):
46
+ check_description = check_description[: check_description.find(target_match) + len(target_match)].strip()
47
+
48
+ information = _extract_field(r"Information:\s*(.*?)\n", output, flags=re.DOTALL | re.MULTILINE)
49
+ vuln_discuss = _extract_field(r"VulnDiscussion='(.*?)'\s", output, flags=re.DOTALL | re.MULTILINE)
50
+ result = _extract_field(r"Result:\s*(.*?)(?:\n|$)", output, flags=re.IGNORECASE | re.DOTALL)
51
+ solution = _extract_field(r"Solution:\s*(.*?)\n\nReference Information:", output, flags=re.DOTALL | re.MULTILINE)
52
+
53
+ # Extract reference information
54
+ ref_info = _extract_field(r"Reference Information:\s*(.*)", output, flags=re.DOTALL | re.MULTILINE)
55
+ ref_dict = dict(item.split("|", 1) for item in ref_info.split(",") if "|" in item)
56
+
57
+ # Extract specific references
58
+ cci_ref = ref_dict.get("CCI", "CCI-000366")
59
+ severity = ref_dict.get("SEVERITY", "").lower()
60
+ oval_def = ref_dict.get("OVAL-DEF", "")
61
+ generated_date = ref_dict.get("GENERATED-DATE", "")
62
+ updated_date = ref_dict.get("UPDATED-DATE", "")
63
+ scan_date = ref_dict.get("SCAN-DATE", "")
64
+ rule_id_full = ref_dict.get("RULE-ID", "")
65
+ group_id = ref_dict.get("GROUP-ID", "")
66
+
67
+ vuln_num_match = re.search(r"SV-\d+r\d+_rule", rule_id)
68
+ vuln_num = vuln_num_match.group(0) if vuln_num_match else "unknown"
69
+
70
+ title = f"{vuln_num}: {check_description}"
71
+ issue_title = title
72
+
73
+ status_map = {
74
+ "PASSED": regscale_models.ChecklistStatus.PASS,
75
+ "FAILED": regscale_models.ChecklistStatus.FAIL,
76
+ "ERROR": regscale_models.ChecklistStatus.FAIL,
77
+ "NOT APPLICABLE": regscale_models.ChecklistStatus.NOT_APPLICABLE,
78
+ "NOT_APPLICABLE": regscale_models.ChecklistStatus.NOT_APPLICABLE,
79
+ }
80
+
81
+ result_key = result.upper().replace("_", " ").strip()
82
+ if result_key not in status_map:
83
+ logger.warning(f"Result '{result}' not found in status map")
84
+ status = regscale_models.ChecklistStatus.NOT_REVIEWED
85
+ else:
86
+ status = status_map[result_key]
87
+
88
+ # Map severity to IssueSeverity enum
89
+ priority = (severity or "").title()
90
+ severity_map = {
91
+ "critical": regscale_models.IssueSeverity.Critical,
92
+ "high": regscale_models.IssueSeverity.High,
93
+ "medium": regscale_models.IssueSeverity.Moderate,
94
+ "low": regscale_models.IssueSeverity.Low,
95
+ }
96
+ severity = severity_map.get(severity.lower(), regscale_models.IssueSeverity.NotAssigned)
97
+
98
+ results = (
99
+ f"Vulnerability Number: {vuln_num}, Severity: {severity.value}, "
100
+ f"Rule Title: {check_description}<br><br>"
101
+ f"Check Content: {information}<br><br>"
102
+ f"Vulnerability Discussion: {vuln_discuss}<br><br>"
103
+ f"Fix Text: {solution}<br><br>"
104
+ f"STIG Reference: {rule_id}"
105
+ )
106
+
107
+ current_datetime = get_current_datetime()
108
+ finding.title = title
109
+ finding.category = "STIG"
110
+ finding.plugin_id = cci_ref
111
+ finding.plugin_name = rule_id
112
+ finding.severity = severity
113
+ finding.description = f"{information}\n\nVulnerability Discussion: {vuln_discuss}\n\nSolution: {solution}"
114
+ finding.status = status
115
+ finding.priority = priority # Set priority based on severity
116
+ finding.first_seen = current_datetime
117
+ finding.last_seen = current_datetime
118
+ finding.issue_title = issue_title
119
+ finding.issue_type = "Risk"
120
+ finding.date_created = generated_date
121
+ finding.date_last_updated = updated_date
122
+ finding.due_date = issue_due_date(severity, generated_date)
123
+ finding.external_id = f"{cci_ref}:{vuln_num}:{finding.asset_identifier}"
124
+ finding.recommendation_for_mitigation = solution
125
+ finding.cci_ref = cci_ref
126
+ finding.rule_id = rule_id
127
+ finding.results = results
128
+ finding.baseline = baseline
129
+ finding.vulnerability_number = vuln_num
130
+ finding.oval_def = oval_def
131
+ finding.scan_date = scan_date
132
+ finding.rule_id_full = rule_id_full
133
+ finding.group_id = group_id
134
+
135
+ # Future values
136
+ # finding.comments = ""
137
+ # finding.poam_comments = ""
138
+ # finding.rule_version = ""
139
+
140
+ return finding