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,353 @@
1
+ """
2
+ STIG Mapping Engine
3
+ """
4
+
5
+ import json
6
+ import logging
7
+ import os
8
+ from typing import Any, Dict, List, Optional
9
+
10
+ from regscale.models import SecurityPlan
11
+ from regscale.models.regscale_models import Asset, AssetMapping, Component
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class StigMappingEngine:
17
+ """
18
+ A class to map assets to STIGs based on defined rules
19
+ """
20
+
21
+ comparator_functions = {
22
+ "equals": lambda a, b: a == b,
23
+ "contains": lambda a, b: b in a,
24
+ "notcontains": lambda a, b: b not in a,
25
+ "startswith": lambda a, b: a.startswith(b),
26
+ "notin": lambda a, b: b not in a,
27
+ "endswith": lambda a, b: a.endswith(b),
28
+ "notstartswith": lambda a, b: not a.startswith(b),
29
+ "notendswith": lambda a, b: not a.endswith(b),
30
+ "gt": lambda a, b: a > b,
31
+ "lt": lambda a, b: a < b,
32
+ "gte": lambda a, b: a >= b,
33
+ "lte": lambda a, b: a <= b,
34
+ "ne": lambda a, b: a != b,
35
+ "in": lambda a, b: a in b,
36
+ "nin": lambda a, b: a not in b,
37
+ }
38
+
39
+ def __init__(self, json_file: str):
40
+ self._mapping_cache = None
41
+ self._component_cache = None
42
+ self.rules = self.load_rules(json_file)
43
+ logger.info(f"Loaded {len(self.rules)} rules from {json_file}")
44
+ # Preprocess rules for faster access
45
+ self.stig_to_rules = {}
46
+ for rule in self.rules:
47
+ stig_name = rule.get("stig")
48
+ if stig_name not in self.stig_to_rules:
49
+ self.stig_to_rules[stig_name] = []
50
+ self.stig_to_rules[stig_name].append(rule.get("comparators", []))
51
+
52
+ @staticmethod
53
+ def load_rules(json_file: str) -> List[Dict[str, str]]:
54
+ """
55
+ Load rules from a JSON file
56
+
57
+ :param str json_file: The path to the JSON file
58
+ :return: A list of rules
59
+ :rtype: List[Dict[str, str]]
60
+ """
61
+ if not os.path.exists(json_file):
62
+ logger.error(f"File not found: {json_file}")
63
+ return []
64
+ try:
65
+ with open(json_file, "r") as file:
66
+ data = json.load(file)
67
+ return data.get("rules", [])
68
+ except json.JSONDecodeError as e:
69
+ logger.error(f"JSON decoding error in file {json_file}: {e}")
70
+ except Exception as e:
71
+ logger.error(f"Error loading rules from {json_file}: {e}")
72
+ return []
73
+
74
+ def evaluate_match(self, item: Any, comparators: List[Dict[str, str]]) -> bool:
75
+ """
76
+ Evaluates a single item (Asset or software inventory item) against multiple comparator rules
77
+
78
+ :param Any item: The asset or software inventory item
79
+ :param List[Dict[str, str]] comparators: List of comparators
80
+ :return: True if item meets all 'and' comparators or at least one 'or' comparator, otherwise False
81
+ :rtype: bool
82
+ """
83
+ # Separate comparators by their logical operator
84
+ and_comparators = [comp for comp in comparators if comp.get("logical_operator", "and").lower() == "and"]
85
+ or_comparators = [comp for comp in comparators if comp.get("logical_operator", "and").lower() == "or"]
86
+
87
+ # Evaluate 'and' comparators
88
+ if not all(self.evaluate_single_comparator(item, comp) for comp in and_comparators):
89
+ return False
90
+
91
+ # Evaluate 'or' comparators
92
+ if or_comparators:
93
+ return any(self.evaluate_single_comparator(item, comp) for comp in or_comparators)
94
+
95
+ # If all 'and' comparators passed and there are no 'or' comparators
96
+ return True
97
+
98
+ @staticmethod
99
+ def get_item_value(item: Any, property_name: Any) -> Optional[Any]:
100
+ """
101
+ Fetches the property value from the item, or None if not found
102
+
103
+ :param Any item: The asset or software inventory item
104
+ :param Any property_name: The property name
105
+ :return: The property value
106
+ :rtype: Optional[Any]
107
+ """
108
+ if isinstance(item, dict):
109
+ return item.get(property_name)
110
+ return getattr(item, property_name, None)
111
+
112
+ def evaluate_single_comparator(self, item: Any, comparator: dict) -> bool:
113
+ """
114
+ Evaluates a single comparator against the item
115
+
116
+ :param Any item: The asset or software inventory item
117
+ :param Dict[str, str] comparator: The comparator
118
+ :return: True if the item satisfies the comparator, otherwise False
119
+ :rtype: bool
120
+ """
121
+ property_name = comparator.get("property")
122
+ item_value = self.get_item_value(item, property_name)
123
+
124
+ if item_value is None:
125
+ return False
126
+
127
+ operator = comparator.get("comparator")
128
+ value = comparator.get("value")
129
+ comparator_func = StigMappingEngine.comparator_functions.get(operator)
130
+
131
+ return comparator_func(item_value, value) if comparator_func else False
132
+
133
+ @staticmethod
134
+ def find_matching_stigs(items: List[Dict[str, Any]], rules: List[Dict[str, Any]]) -> List[str]:
135
+ """
136
+ Checks a list of items (software inventory or assets) to see which STIGs they match
137
+
138
+ :param List[Dict[str, Any]] items: List of items (e.g., software inventory dictionaries)
139
+ :param List[Dict[str, Any]] rules: List of STIG rules, each containing a "stig" name and comparators
140
+ :return: List of matched STIG names
141
+ :rtype: List[str]
142
+ """
143
+ matched_stigs = []
144
+
145
+ for rule in rules:
146
+ stig_name = rule.get("stig")
147
+ comparators = rule.get("comparators", [])
148
+
149
+ # Track satisfaction of each comparator across all items
150
+ comparator_match = {i: False for i in range(len(comparators))}
151
+
152
+ # Go through each comparator and attempt to satisfy it with any item
153
+ for i, comparator in enumerate(comparators):
154
+ property_name = comparator.get("property")
155
+ value = comparator.get("value")
156
+ operator = comparator.get("comparator")
157
+
158
+ # Check if any item satisfies this comparator
159
+ for item in items:
160
+ item_value = item.get(property_name)
161
+
162
+ # Retrieve the comparison function
163
+ comparator_func = StigMappingEngine.comparator_functions.get(operator)
164
+ if comparator_func and comparator_func(item_value, value):
165
+ comparator_match[i] = True
166
+ break # Move to the next comparator once a match is found
167
+
168
+ # Evaluate final match based on logical operators
169
+ if all(comparator_match.values()):
170
+ matched_stigs.append(stig_name)
171
+
172
+ return matched_stigs
173
+
174
+ @staticmethod
175
+ def asset_matches_comparators(asset: Asset, comparators: List[Dict[str, str]]) -> bool:
176
+ """
177
+ Determine if the asset matches the given comparators
178
+
179
+ :param Asset asset: An asset
180
+ :param List[Dict[str, str]] comparators: List of comparator dictionaries
181
+ :return: True if the asset matches the comparators, False otherwise
182
+ :rtype: bool
183
+ """
184
+ match_result = True
185
+
186
+ for comparator in comparators:
187
+ property_name = comparator.get("property")
188
+ if not hasattr(asset, property_name):
189
+ return False
190
+
191
+ operator = comparator.get("comparator")
192
+ comparator_func = StigMappingEngine.comparator_functions.get(operator)
193
+ if not comparator_func:
194
+ return False
195
+
196
+ value = comparator.get("value")
197
+ asset_value = getattr(asset, property_name)
198
+ comparison_result = comparator_func(asset_value, value)
199
+
200
+ logical_operator = comparator.get("logical_operator", "and").lower()
201
+
202
+ if logical_operator == "and":
203
+ match_result = match_result and comparison_result
204
+ if not match_result:
205
+ return False
206
+ elif logical_operator == "or":
207
+ match_result = match_result or comparison_result
208
+ else:
209
+ logger.warning(f"Unknown logical operator: {logical_operator}")
210
+ return False
211
+
212
+ return match_result
213
+
214
+ def match_asset_to_stigs(
215
+ self, asset: Asset, ssp_id: int, software_inventory: Optional[List] = None
216
+ ) -> List[Component]:
217
+ """
218
+ Match an asset to STIG components based on rules
219
+
220
+ :param Asset asset: An asset
221
+ :param int ssp_id: The security plan ID
222
+ :param Optional[List] software_inventory: A list of software inventory
223
+ :return: A list of matching components
224
+ :rtype: List[Component]
225
+ """
226
+ if software_inventory is None:
227
+ software_inventory = []
228
+ if not self.rules:
229
+ return []
230
+
231
+ matching_components = []
232
+
233
+ # Ensure component cache is initialized
234
+ if self._component_cache is None:
235
+ self._component_cache = self.get_component_dict(ssp_id)
236
+
237
+ for stig_name, comparators_list in self.stig_to_rules.items():
238
+ component = self._component_cache.get(stig_name)
239
+ if not component:
240
+ continue
241
+
242
+ for comparators in comparators_list:
243
+ if self.asset_matches_comparators(asset, comparators):
244
+ matching_components.append(component)
245
+ break # No need to check other comparators for this STIG
246
+
247
+ return matching_components
248
+
249
+ def map_stigs_to_assets(
250
+ self,
251
+ assets: List[Asset],
252
+ ssp_id: int,
253
+ ) -> List[AssetMapping]:
254
+ """
255
+ Map STIG components to assets based on rules
256
+
257
+ :param List[Asset] asset_list assets: A list of assets
258
+ :param List[Component] ssp_id: The security plan ID
259
+ :return: A list of asset mappings
260
+ :rtype: List[AssetMapping]
261
+ """
262
+ new_mappings = []
263
+
264
+ # Cache components to avoid redundant database queries
265
+ components = self.get_components(ssp_id)
266
+
267
+ # Build a mapping of existing mappings for quick lookup
268
+ existing_mappings = {}
269
+ for component in components:
270
+ mappings = AssetMapping.find_mappings(component_id=component.id)
271
+ existing_mappings[component.id] = {m.assetId for m in mappings}
272
+
273
+ for stig_name, comparators_list in self.stig_to_rules.items():
274
+ component = self._component_cache.get(stig_name)
275
+ if not component:
276
+ continue
277
+
278
+ component_existing_asset_ids = existing_mappings.get(component.id, set())
279
+
280
+ for asset in assets:
281
+ for comparators in comparators_list:
282
+ if self.asset_matches_comparators(asset, comparators):
283
+ if asset.id not in component_existing_asset_ids:
284
+ mapping = AssetMapping(assetId=asset.id, componentId=component.id)
285
+ new_mappings.append(mapping)
286
+ component_existing_asset_ids.add(asset.id)
287
+ logger.info(f"Mapping -> Asset ID: {asset.id}, Component ID: {component.id}")
288
+ else:
289
+ logger.info(
290
+ f"Existing mapping found for Asset ID: {asset.id}, Component ID: {component.id}"
291
+ )
292
+ break # No need to check other comparators for this asset and STIG
293
+
294
+ return new_mappings
295
+
296
+ def get_components(self, ssp_id: int) -> List[Component]:
297
+ """
298
+ Get all components for the given security plan
299
+
300
+ :param int ssp_id: The security plan ID
301
+ :return: A list of components
302
+ :rtype: List[Component]
303
+ """
304
+ if not hasattr(self, "_component_cache"):
305
+ components = Component.get_all_by_parent(parent_module=SecurityPlan.get_module_slug(), parent_id=ssp_id)
306
+ self._component_cache = {comp.title: comp for comp in components}
307
+ else:
308
+ components = self._component_cache.values()
309
+ return components
310
+
311
+ def get_component_dict(self, ssp_id: int) -> Dict[str, Component]:
312
+ """
313
+ Get a dictionary of components for the given security plan
314
+
315
+ :param int ssp_id: The security plan ID
316
+ :return: A dictionary of components
317
+ :rtype: Dict[str, Component]
318
+ """
319
+ if not hasattr(self, "_component_cache") or self._component_cache is None:
320
+ components = Component.get_all_by_parent(parent_module=SecurityPlan.get_module_slug(), parent_id=ssp_id)
321
+ self._component_cache = {comp.title: comp for comp in components}
322
+ return self._component_cache
323
+
324
+ def map_associated_stigs_to_asset(self, asset: Asset, ssp_id: int) -> List[AssetMapping]:
325
+ """
326
+ Map associated STIGs to an asset based on rules
327
+
328
+ :param Asset asset: An asset
329
+ :param int ssp_id: The security plan ID
330
+ :return: A list of asset mappings
331
+ :rtype: List[AssetMapping]
332
+ """
333
+ new_mappings = []
334
+ associated_components = self.match_asset_to_stigs(asset, ssp_id)
335
+
336
+ # Initialize or update the mapping cache
337
+ if not hasattr(self, "_mapping_cache") or self._mapping_cache is None:
338
+ mappings = AssetMapping.get_all_by_parent(parent_module=Asset.get_module_slug(), parent_id=asset.id)
339
+ self._mapping_cache = {m.componentId: m for m in mappings}
340
+
341
+ existing_component_ids = set(self._mapping_cache.keys())
342
+
343
+ for component in associated_components:
344
+ if component.id not in existing_component_ids:
345
+ mapping = AssetMapping(assetId=asset.id, componentId=component.id)
346
+ mapping.create()
347
+ new_mappings.append(mapping)
348
+ self._mapping_cache[component.id] = mapping
349
+ logger.debug(f"Created mapping for Asset ID: {asset.id}, Component ID: {component.id}")
350
+ else:
351
+ logger.debug(f"Mapping already exists for Asset ID: {asset.id}, Component ID: {component.id}")
352
+
353
+ return new_mappings
File without changes
@@ -0,0 +1,349 @@
1
+ """
2
+ This module is responsible for parsing STIG (Security Technical Implementation Guide) .ckl (Checklist) files.
3
+ It provides functionality to extract relevant information from .ckl files for further processing and analysis.
4
+ """
5
+
6
+ import io
7
+ import logging
8
+ from enum import Enum
9
+ from pathlib import Path
10
+ from typing import List, Dict, Optional, Any, Generator, Union
11
+
12
+ import lxml.etree as et
13
+ from lxml.etree import Element
14
+ from pydantic import BaseModel, ConfigDict
15
+
16
+ from regscale.core.utils import snakify
17
+
18
+ logger = logging.getLogger("regscale")
19
+
20
+
21
+ def stig_component_title(stig_title: str) -> str:
22
+ """
23
+ Extract the component title from the STIG title.
24
+
25
+ :param str stig_title: The STIG title.
26
+ :return: The component title.
27
+ :rtype: str
28
+ """
29
+ return stig_title.replace(" ", " ").strip() # Ensure no double spaces are present
30
+
31
+
32
+ def find_stig_files(directory: Path) -> List[Path]:
33
+ """
34
+ Finds all STIG (.ckl) files within the specified directory.
35
+
36
+ :param Path directory: Path to the directory where STIG files are located.
37
+ :return: A list of paths to STIG files.
38
+ :rtype: List[Path]
39
+ """
40
+ if str(directory).startswith("s3://"):
41
+ logger.info(f"Found S3 URI: {directory}")
42
+ return [directory]
43
+
44
+ if str(directory).endswith(".ckl"):
45
+ logger.info(f"Found CKL file: {directory}")
46
+ return [directory]
47
+
48
+ logger.info(f"Searching for STIG files in {directory}")
49
+ path = Path(directory)
50
+
51
+ if not path.is_dir():
52
+ logger.error(f"{directory} is not a valid directory")
53
+ return []
54
+
55
+ def is_hidden(filepath):
56
+ return any(part.startswith(".") for part in filepath.parts)
57
+
58
+ stig_files: list[Path] = [file for file in path.glob("**/*.ckl") if not is_hidden(file)]
59
+ logger.info(f"Found {len(stig_files)} STIG file(s)")
60
+ return stig_files
61
+
62
+
63
+ class Asset(BaseModel):
64
+ """Data model for Asset with optional attributes."""
65
+
66
+ model_config = ConfigDict(populate_by_name=True, alias_generator=snakify)
67
+
68
+ role: Optional[str] = None
69
+ asset_type: Optional[str] = None
70
+ host_name: Optional[str] = None
71
+ host_ip: Optional[str] = None
72
+ host_mac: Optional[str] = None
73
+ host_fqdn: Optional[str] = None
74
+ tech_area: Optional[str] = None
75
+ target_key: Optional[str] = None
76
+ web_or_database: Optional[bool] = None
77
+ web_db_site: Optional[str] = None
78
+ web_db_instance: Optional[str] = None
79
+
80
+
81
+ class STIGInfo(BaseModel):
82
+ """Data model for STIG Information with optional and required attributes."""
83
+
84
+ version: str
85
+ classification: str
86
+ customname: Optional[str] = None
87
+ stigid: str
88
+ description: Optional[str] = None
89
+ filename: Optional[str] = None
90
+ releaseinfo: str
91
+ title: str
92
+ uuid: str
93
+ notice: str
94
+ source: Optional[str] = None
95
+
96
+
97
+ class VulnStatus(Enum):
98
+ """Enumeration for STIG vulnerability status."""
99
+
100
+ OPEN = "OPEN"
101
+ NOT_A_FINDING = "NOT A FINDING"
102
+ NOT_APPLICABLE = "NOT APPLICABLE"
103
+ COMPLETED = "COMPLETED"
104
+
105
+
106
+ class Vuln(BaseModel):
107
+ """Data model for Vulnerability with optional and required attributes."""
108
+
109
+ vuln_num: str
110
+ severity: str
111
+ group_title: str
112
+ rule_id: str
113
+ rule_ver: str
114
+ rule_title: str
115
+ check_content: Optional[str] = None
116
+ fix_text: str
117
+ check_content_ref: Optional[str] = None
118
+ weight: str
119
+ stigref: Optional[str] = None
120
+ targetkey: Optional[str] = None
121
+ stig_uuid: str
122
+ vuln_discuss: Optional[str] = None
123
+ ia_controls: Optional[str] = None
124
+ class_: Optional[str] = None
125
+ cci_ref: list[str] = []
126
+ false_positives: Optional[str] = None
127
+ false_negatives: Optional[str] = None
128
+ documentable: Optional[str] = None
129
+ mitigations: Optional[str] = None
130
+ potential_impact: Optional[str] = None
131
+ third_party_tools: Optional[str] = None
132
+ mitigation_control: Optional[str] = None
133
+ responsibility: Optional[str] = None
134
+ security_override_guidance: Optional[str] = None
135
+ legacy_id: Optional[str] = None
136
+ status: Optional[str] = None
137
+ finding_details: Optional[str] = None
138
+ comments: Optional[str] = None
139
+ severity_override: Optional[str] = None
140
+ severity_justification: Optional[str] = None
141
+
142
+
143
+ class STIG(BaseModel):
144
+ """Data model for STIG including STIG information and vulnerabilities."""
145
+
146
+ baseline: str
147
+ stig_info: STIGInfo
148
+ vulns: List[Vuln] = []
149
+
150
+ @property
151
+ def component_title(self) -> str:
152
+ """
153
+ Extract the component title from the STIG title.
154
+
155
+ :return: The component title.
156
+ :rtype: str
157
+ """
158
+ return stig_component_title(self.stig_info.title)
159
+
160
+
161
+ class Checklist(BaseModel):
162
+ """Data model for Checklist including Asset and STIGs."""
163
+
164
+ assets: List[Asset] = []
165
+ stigs: List[STIG] = []
166
+
167
+
168
+ def parse_element_to_dict(
169
+ element: Element,
170
+ key_tag: Optional[str] = None,
171
+ value_tag: Optional[str] = None,
172
+ list_fields: Optional[List[str]] = None,
173
+ ) -> Dict[str, Any]:
174
+ """
175
+ This function parses an XML element into a dictionary. It can operate in two modes:
176
+ 1. If key_tag and value_tag are provided, it searches for these specific tags within each child element
177
+ to construct key-value pairs for the dictionary.
178
+ 2. If key_tag and value_tag are not provided, it converts all child elements of the given element
179
+ directly to a dictionary with child tag names as keys and their text content as values.
180
+
181
+ :param Element element: The XML element to parse.
182
+ :param Optional[str] key_tag: Optional; the tag name that contains the key for key-value parsing mode.
183
+ :param Optional[str] value_tag: Optional; the tag name that contains the value for key-value parsing mode.
184
+ :param Optional[List[str]] list_fields: Optional; list of fields that are expected to have lists back.
185
+ :return: A dictionary representation of the element.
186
+ :rtype: Dict[str, Any]
187
+ """
188
+ if list_fields is None:
189
+ list_fields = []
190
+
191
+ if key_tag and value_tag:
192
+ return _parse_key_value_pairs(element, key_tag, value_tag, list_fields)
193
+ else:
194
+ return _parse_direct_child_elements(element)
195
+
196
+
197
+ def _parse_key_value_pairs(element: Element, key_tag: str, value_tag: str, list_fields: List[str]) -> Dict[str, Any]:
198
+ """
199
+ Parse key-value pairs from XML element based on provided key and value tags.
200
+
201
+ :param Element element: The XML element to parse.
202
+ :param str key_tag: The tag name that contains the key for key-value parsing mode.
203
+ :param str value_tag: The tag name that contains the value for key-value parsing mode.
204
+ :param List[str] list_fields: List of fields that are expected to have lists back.
205
+ :return: A dictionary of parsed key-value pairs.
206
+ :rtype: Dict[str, Any]
207
+ """
208
+ parsed_data: dict[str, Any] = {}
209
+ for child in element.findall(f".//{key_tag}"):
210
+ key = child.text
211
+ value_element = child.find(f"../{value_tag}")
212
+ value = getattr(value_element, "text", None)
213
+ if key:
214
+ key = snakify(key)
215
+ if key in list_fields:
216
+ parsed_data.setdefault(key, []).append(value) if value else None
217
+ else:
218
+ parsed_data[key] = value
219
+ return parsed_data
220
+
221
+
222
+ def _parse_direct_child_elements(element: Element) -> Dict[str, Any]:
223
+ """
224
+ Parse direct child elements of an XML element into a dictionary.
225
+
226
+ :param Element element: The XML element to parse.
227
+ :return: A dictionary of direct child elements.
228
+ :rtype: Dict[str, Any]
229
+ """
230
+ result = {}
231
+ for child in element:
232
+ key = snakify(child.tag)
233
+ # Use empty string for None to ensure the field is included
234
+ value = child.text if child.text is not None else ""
235
+ result[key] = value
236
+ return result
237
+
238
+
239
+ def parse_checklist(file_path: Union[str, Path]) -> Checklist:
240
+ """
241
+ Main function to parse a checklist from an XML file or S3 object using lxml for parsing.
242
+
243
+ :param Union[str, Path] file_path: The path to the XML file or S3 object.
244
+ :raises ValueError: If the ASSET element is not found in the XML
245
+ :return: Checklist object
246
+ :rtype: Checklist
247
+ """
248
+ try:
249
+ import boto3
250
+
251
+ if isinstance(file_path, str) and file_path.startswith("s3://"):
252
+ # Handle S3 path
253
+ s3_parts = file_path[5:].split("/", 1)
254
+ bucket = s3_parts[0]
255
+ key = s3_parts[1]
256
+
257
+ s3 = boto3.client("s3")
258
+ response = s3.get_object(Bucket=bucket, Key=key)
259
+ content = response["Body"].read()
260
+
261
+ tree = et.parse(io.BytesIO(content))
262
+ root = tree.getroot()
263
+ file_name = key.split("/")[-1]
264
+ else:
265
+ # Handle local file path
266
+ xml_path = Path(file_path)
267
+ tree = et.parse(xml_path)
268
+ root = tree.getroot()
269
+ file_name = xml_path.name
270
+
271
+ # Parse the Assets
272
+ asset_elem = root.find("ASSET")
273
+ if asset_elem is None:
274
+ raise ValueError("ASSET element not found in XML.")
275
+ assets = [Asset(**parse_element_to_dict(asset_elem))]
276
+
277
+ stigs = [
278
+ STIG(
279
+ baseline=file_name.split(".")[0].split("-")[0].strip(),
280
+ stig_info=STIGInfo(**parse_element_to_dict(istig_elem, key_tag="SID_NAME", value_tag="SID_DATA")),
281
+ vulns=[
282
+ Vuln(
283
+ **parse_element_to_dict(
284
+ vuln_elem,
285
+ key_tag="VULN_ATTRIBUTE",
286
+ value_tag="ATTRIBUTE_DATA",
287
+ list_fields=[
288
+ "cci_ref",
289
+ ],
290
+ ),
291
+ status=vuln_elem.findtext("STATUS"),
292
+ finding_details=vuln_elem.findtext("FINDING_DETAILS"),
293
+ comments=vuln_elem.findtext("COMMENTS"),
294
+ severity_override=vuln_elem.findtext("SEVERITY_OVERRIDE"),
295
+ severity_justification=vuln_elem.findtext("SEVERITY_JUSTIFICATION"),
296
+ )
297
+ for vuln_elem in istig_elem.findall("VULN")
298
+ ],
299
+ )
300
+ for istig_elem in root.findall(".//iSTIG")
301
+ if istig_elem.find("STIG_INFO") is not None
302
+ ]
303
+
304
+ return Checklist(assets=assets, stigs=stigs)
305
+ except Exception as e:
306
+ logger.error(f"Error parsing {file_path}: {e}")
307
+ raise e
308
+
309
+
310
+ def get_components_from_checklist(
311
+ checklist: Checklist,
312
+ ) -> Generator[dict[str, str], None, None]:
313
+ """
314
+ Extract the component titles from the checklist using a generator expression.
315
+
316
+ :param Checklist checklist: The checklist object.
317
+ :return: A generator of component titles.
318
+ :rtype: Generator[dict[str, str], None, None]
319
+ """
320
+ return ({stig.stig_info.stigid: stig_component_title(stig.stig_info.title)} for stig in checklist.stigs)
321
+
322
+
323
+ def get_all_components_from_checklists(checklists: List[Checklist]) -> dict[str, str]:
324
+ """
325
+ Extract the component titles from a list of checklists using a generator expression.
326
+
327
+ :param List[Checklist] checklists: The list of checklist objects.
328
+ :return: A dictionary of unique component titles.
329
+ :rtype: dict[str, str]
330
+ """
331
+ components: dict[str, str] = {}
332
+ for checklist in checklists:
333
+ # Collect all components from the generator into a single dictionary
334
+ components.update({k: v for d in get_components_from_checklist(checklist) for k, v in d.items()})
335
+ return components
336
+
337
+
338
+ def get_all_assets_from_checklists(checklists: List[Checklist]) -> List[Asset]:
339
+ """
340
+ Extract the assets from a list of checklists.
341
+
342
+ :param List[Checklist] checklists: The list of checklist objects.
343
+ :return: A list of unique assets.
344
+ :rtype: List[Asset]
345
+ """
346
+ assets = set()
347
+ for checklist in checklists:
348
+ assets.update(checklist.assets)
349
+ return list(assets)