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,665 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Application Configuration"""
4
+
5
+ import contextlib
6
+ import inspect
7
+ import json
8
+ import os
9
+ import platform
10
+ import sys
11
+ from threading import Lock
12
+ from typing import Any, Optional, Union
13
+ from urllib.parse import urljoin
14
+
15
+ import requests
16
+ import yaml
17
+ from pydantic import Field
18
+ from requests import Response
19
+ from yaml.scanner import ScannerError
20
+
21
+ from regscale.core.app.internal.encrypt import IOA21H98
22
+ from regscale.core.app.logz import create_logger
23
+ from regscale.utils.threading.threadhandler import ThreadManager
24
+
25
+ DEFAULT_CLIENT = "<myClientIdGoesHere>"
26
+ DEFAULT_SECRET = "<mySecretGoesHere>"
27
+ DEFAULT_POPULATED = "<createdProgrammatically>"
28
+ DEFAULT_TENANT = "<myTenantIdGoesHere>"
29
+
30
+
31
+ class Singleton(type):
32
+ """
33
+ Singleton class to prevent multiple instances of Application
34
+ """
35
+
36
+ _instances = {}
37
+
38
+ def __call__(cls, *args, **kwargs):
39
+ if cls not in cls._instances or kwargs.get("config"):
40
+ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
41
+ return cls._instances[cls]
42
+
43
+
44
+ class Application(metaclass=Singleton):
45
+ """
46
+ RegScale CLI configuration class
47
+
48
+ :param Optional[dict] config: Configuration dictionary to use instead of init.yaml, defaults to None
49
+ :param bool local_config: Whether to use the local config file, defaults to True
50
+ """
51
+
52
+ config: dict = Field(default_factory=dict)
53
+ _config_lock = Lock()
54
+
55
+ def __init__(
56
+ self,
57
+ config: Optional[dict] = None,
58
+ local_config: bool = True,
59
+ ):
60
+ self.config_file = os.getenv("REGSCALE_CONFIG_FILE", "init.yaml")
61
+ self.api_handler = None
62
+ template = {
63
+ "stigBatchSize": 100,
64
+ "adAccessToken": DEFAULT_POPULATED,
65
+ "adAuthUrl": "https://login.microsoftonline.com/",
66
+ "adClientId": DEFAULT_CLIENT,
67
+ "adClientSecret": DEFAULT_SECRET,
68
+ "adGraphUrl": "https://graph.microsoft.com/.default",
69
+ "adTenantId": DEFAULT_TENANT,
70
+ "assessmentDays": 10,
71
+ "azure365AccessToken": DEFAULT_POPULATED,
72
+ "azure365ClientId": DEFAULT_CLIENT,
73
+ "azure365Secret": DEFAULT_SECRET,
74
+ "azure365TenantId": DEFAULT_TENANT,
75
+ "azureCloudAccessToken": DEFAULT_POPULATED,
76
+ "azureCloudClientId": DEFAULT_CLIENT,
77
+ "azureCloudSecret": DEFAULT_SECRET,
78
+ "azureCloudTenantId": DEFAULT_TENANT,
79
+ "azureCloudSubscriptionId": "<mySubscriptionIdGoesHere>",
80
+ "cisaKev": "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json",
81
+ "crowdstrikeClientId": DEFAULT_CLIENT,
82
+ "crowdstrikeClientSecret": DEFAULT_SECRET,
83
+ "crowdstrikeBaseUrl": "<crowdstrikeApiUrl>",
84
+ "dependabotId": "<myGithubUserIdGoesHere>",
85
+ "dependabotOwner": "<myGithubRepoOwnerGoesHere>",
86
+ "dependabotRepo": "<myGithubRepoNameGoesHere>",
87
+ "dependabotToken": "<myGithubPersonalAccessTokenGoesHere>",
88
+ "domain": "https://regscale.yourcompany.com/",
89
+ "evidenceFolder": "./evidence",
90
+ "passScore": 80,
91
+ "failScore": 30,
92
+ "gcpCredentials": "<path/to/credentials.json>",
93
+ "gcpOrganizationId": "<000000000000>",
94
+ "gcpProjectId": "<000000000000>",
95
+ "gcpScanType": "<organization | project>",
96
+ "githubDomain": "api.github.com",
97
+ "issues": {
98
+ "aqua": {
99
+ "critical": 30,
100
+ "high": 30,
101
+ "moderate": 90,
102
+ "low": 180,
103
+ "status": "Open",
104
+ "minimumSeverity": "low",
105
+ "useKev": True,
106
+ },
107
+ "amazon": {
108
+ "high": 30,
109
+ "low": 365,
110
+ "moderate": 90,
111
+ "status": "Open",
112
+ "minimumSeverity": "low",
113
+ "useKev": True,
114
+ },
115
+ "defender365": {
116
+ "high": 30,
117
+ "low": 365,
118
+ "moderate": 90,
119
+ "status": "Open",
120
+ },
121
+ "defenderCloud": {
122
+ "high": 30,
123
+ "low": 365,
124
+ "moderate": 90,
125
+ "status": "Open",
126
+ },
127
+ "defenderFile": {
128
+ "high": 30,
129
+ "low": 365,
130
+ "moderate": 90,
131
+ "status": "Open",
132
+ "useKev": True,
133
+ },
134
+ "ecr": {
135
+ "critical": 30,
136
+ "high": 30,
137
+ "moderate": 90,
138
+ "low": 180,
139
+ "status": "Open",
140
+ "minimumSeverity": "low",
141
+ "useKev": True,
142
+ },
143
+ "jira": {
144
+ "highest": 7,
145
+ "high": 30,
146
+ "medium": 90,
147
+ "low": 180,
148
+ "lowest": 365,
149
+ "status": "Open",
150
+ },
151
+ "qualys": {
152
+ "high": 30,
153
+ "moderate": 90,
154
+ "low": 365,
155
+ "status": "Open",
156
+ "useKev": True,
157
+ },
158
+ "salesforce": {
159
+ "critical": 7,
160
+ "high": 30,
161
+ "medium": 90,
162
+ "low": 365,
163
+ "status": "Open",
164
+ },
165
+ "snyk": {
166
+ "critical": 30,
167
+ "high": 30,
168
+ "moderate": 90,
169
+ "low": 180,
170
+ "status": "Open",
171
+ "minimumSeverity": "low",
172
+ "useKev": True, # Override the issue due date with the KEV date
173
+ },
174
+ "nexpose": {
175
+ "critical": 30,
176
+ "high": 30,
177
+ "moderate": 90,
178
+ "low": 180,
179
+ "status": "Open",
180
+ "minimumSeverity": "low",
181
+ "useKev": True, # Override the issue due date with the KEV date
182
+ },
183
+ "prisma": {
184
+ "critical": 30,
185
+ "high": 30,
186
+ "moderate": 90,
187
+ "low": 180,
188
+ "status": "Open",
189
+ "minimumSeverity": "low",
190
+ "useKev": True, # Override the issue due date with the KEV date
191
+ },
192
+ "tenable": {
193
+ "critical": 30,
194
+ "high": 30,
195
+ "moderate": 90,
196
+ "low": 180,
197
+ "status": "Open",
198
+ "useKev": False, # Override the issue due date with the KEV date
199
+ },
200
+ "wiz": {
201
+ "critical": 30,
202
+ "high": 90,
203
+ "low": 365,
204
+ "medium": 90,
205
+ "status": "Open",
206
+ },
207
+ "xray": {
208
+ "critical": 30,
209
+ "high": 30,
210
+ "moderate": 90,
211
+ "low": 180,
212
+ "status": "Open",
213
+ "minimumSeverity": "low",
214
+ "useKev": True,
215
+ },
216
+ "veracode": {
217
+ "critical": 30,
218
+ "high": 30,
219
+ "moderate": 90,
220
+ "low": 180,
221
+ "status": "Open",
222
+ "minimumSeverity": "low",
223
+ "useKev": False,
224
+ },
225
+ },
226
+ "jiraApiToken": "<jiraAPIToken>",
227
+ "jiraUrl": "<myJiraUrl>",
228
+ "jiraUserName": "<jiraUserName>",
229
+ "maxThreads": 1000,
230
+ "nistCpeApiKey": "<myNistCpeApiKey>",
231
+ "oktaApiToken": "Can be a SSWS token from Okta or created programmatically",
232
+ "oktaClientId": "<oktaClientIdGoesHere>",
233
+ "oktaUrl": "<oktaUrlGoesHere>",
234
+ "oscalLocation": "/opt/OSCAL",
235
+ "pwshPath": "/opt/microsoft/powershell/7/pwsh",
236
+ "qualysUrl": "https://yourcompany.qualys.com/api/2.0/fo/scan/",
237
+ "qualysUserName": "<qualysUserName>",
238
+ "qualysPassword": "<qualysPassword>",
239
+ "sicuraUrl": "<mySicuraUrl>",
240
+ "sicuraToken": "<mySicuraToken>",
241
+ "salesforceUserName": "<salesforceUserName>",
242
+ "salesforcePassword": "<salesforcePassword>",
243
+ "salesforceToken": "<salesforceSecurityToken>",
244
+ "snowPassword": "<snowPassword>",
245
+ "snowUrl": "<mySnowUrl>",
246
+ "snowUserName": "<snowUserName>",
247
+ "sonarToken": "<mySonarToken>",
248
+ "tenableAccessKey": "<tenableAccessKeyGoesHere>",
249
+ "tenableSecretKey": "<tenableSecretKeyGoesHere>",
250
+ "tenableUrl": "https://sc.tenalab.online",
251
+ "tenableMinimumSeverityFilter": "low",
252
+ "token": DEFAULT_POPULATED,
253
+ "userId": "enter RegScale user id here",
254
+ "otx": "enter AlienVault API key here",
255
+ "wizAccessToken": DEFAULT_POPULATED,
256
+ "wizAuthUrl": "https://auth.wiz.io/oauth/token",
257
+ "wizExcludes": "My things to exclude here",
258
+ "wizScope": "<filled out programmatically after authenticating to Wiz>",
259
+ "wizUrl": "<my Wiz URL goes here>",
260
+ "wizReportAge": 15,
261
+ "wizLastInventoryPull": "<wizLastInventoryPull>",
262
+ "wizInventoryFilterBy": "<wizInventoryFilterBy>",
263
+ "wizIssueFilterBy": "<wizIssueFilterBy>",
264
+ "wizFullPullLimitHours": 8,
265
+ "wizStigMapperFile": os.path.join(
266
+ os.getcwd(), os.makedirs("artifacts", exist_ok=True) or "artifacts/stig_mapper_rules.json"
267
+ ), # could blow up on missing artifacts folder
268
+ "timeout": 60,
269
+ "tenableGroupByPlugin": False,
270
+ "findingFromMapping": {
271
+ "aqua": {
272
+ "remediation": "default",
273
+ "title": "default",
274
+ "description": "default",
275
+ },
276
+ "tenable_sc": {
277
+ "remediation": "default",
278
+ "title": "default",
279
+ "description": "default",
280
+ },
281
+ },
282
+ }
283
+ logger = create_logger()
284
+ if os.environ.get("LOGLEVEL", "INFO").upper() == "DEBUG":
285
+ stack = inspect.stack()
286
+ logger.debug("*" * 80)
287
+ logger.debug(f"Initializing Application from {stack[1].filename}")
288
+ logger.debug("*" * 80)
289
+ logger.debug(f"Initializing in directory: {os.getcwd()}")
290
+ self.template = template
291
+ self.templated = False
292
+ self.logger = logger
293
+ self.local_config = local_config
294
+ self.running_in_airflow = os.getenv("REGSCALE_AIRFLOW") == "true"
295
+ if isinstance(config, str):
296
+ config = self._read_config_from_str(config)
297
+ self.config = self._gen_config(config)
298
+ self.os = platform.system()
299
+ self.input_host = ""
300
+ self.thread_manager = ThreadManager(self.config.get("maxThreads", 100))
301
+ logger.debug("Finished Initializing Application")
302
+ logger.debug("*" * 80)
303
+
304
+ def __getitem__(self, key: Any) -> Any:
305
+ """
306
+ Get an item
307
+
308
+ :param Any key: key to retrieve
309
+ :return: value of provided key
310
+ :rtype: Any
311
+ """
312
+ return self.config.__getitem__(key)
313
+
314
+ def __setitem__(self, key: Any, value: Any) -> None:
315
+ """
316
+ Set an item
317
+
318
+ :param Any key: Key to set the provided value
319
+ :param Any value: Value to set the provided key
320
+ :rtype: None
321
+ """
322
+ self.config.__setitem__(key, value)
323
+
324
+ def __delitem__(self, key: Any) -> None:
325
+ """
326
+ Delete an item
327
+
328
+ :param Any key: Key desired to delete
329
+ :rtype: None
330
+ """
331
+ self.config.__delitem__(key)
332
+
333
+ def __iter__(self):
334
+ """
335
+ Return iterator
336
+ """
337
+ return self.config.__iter__()
338
+
339
+ def __len__(self) -> int:
340
+ """
341
+ Get the length of the config
342
+
343
+ :return: # of items in config
344
+ :rtype: int
345
+ """
346
+ return len(self.config) if self.config is not None else 0
347
+
348
+ def __contains__(self, x: str) -> bool:
349
+ """
350
+ Check config if it contains string
351
+
352
+ :param str x: String to check if it exists in the config
353
+ :return: Whether the provided string exists in the config
354
+ :rtype: bool
355
+ """
356
+ return self.config.__contains__(x)
357
+
358
+ def _read_config_from_str(self, config: str) -> dict:
359
+ """
360
+ Tries to convert the provided config string to a dictionary, and if it fails, try to use the
361
+ string as a file path, and if that fails it will return an empty dictionary
362
+
363
+ :param str config: String to try and convert to a dictionary before trying to use as a file path
364
+ :return: Dictionary of provided string or file, or an empty dictionary
365
+ :rtype: dict
366
+ """
367
+ try:
368
+ return json.loads(config)
369
+ except json.JSONDecodeError:
370
+ self.config_file = config
371
+ try:
372
+ config = self._get_conf()
373
+ return config
374
+ except Exception as ex:
375
+ self.logger.debug(f"Unable to load config from file: {ex}")
376
+ return {}
377
+
378
+ def _fetch_config_from_regscale(self, config: Optional[dict] = None) -> dict:
379
+ """
380
+ Fetch config from RegScale via API
381
+
382
+ :param Optional[dict] config: configuration dictionary, defaults to None
383
+ :return: Combined config from RegScale and the provided config
384
+ :rtype: dict
385
+ """
386
+ if config is None:
387
+ config = {}
388
+ self.logger.debug(f"Provided config in _fetch_config_from_regscale is: {type(config)}")
389
+ token = config.get("token") or os.getenv("REGSCALE_TOKEN")
390
+ domain = config.get("domain") or os.getenv("REGSCALE_DOMAIN")
391
+ if domain is None or "http" not in domain or domain == self.template["domain"]:
392
+ domain = self.retrieve_domain()[:-1] if self.retrieve_domain().endswith("/") else self.retrieve_domain()
393
+ self.logger.debug(f"domain: {domain}, token: {token}")
394
+ if domain is not None and token is not None:
395
+ self.logger.info(f"Fetching config from {domain}...")
396
+ try:
397
+ response = requests.get(
398
+ url=urljoin(domain, "/api/tenants/getDetailedCliConfig"),
399
+ headers={
400
+ "Content-Type": "application/json",
401
+ "Accept": "application/json",
402
+ "Authorization": token,
403
+ },
404
+ )
405
+ self.logger.debug(f"status_code: {response.status_code} text: {response.text}")
406
+ res_data = response.json()
407
+ if config := res_data.get("cliConfig"):
408
+ parsed_dict = yaml.safe_load(config)
409
+ self.logger.debug(f"parsed_dict: {parsed_dict}")
410
+ parsed_dict["token"] = token
411
+ parsed_dict["domain"] = domain
412
+ from regscale.core.app.internal.login import parse_user_id_from_jwt
413
+
414
+ parsed_dict["userId"] = res_data.get("userId") or parse_user_id_from_jwt(self, token)
415
+ self.logger.debug(f"Updated domain, token and userId: {parsed_dict}")
416
+ self.logger.info("Successfully fetched config from RegScale.")
417
+ # fill in any missing keys with the template
418
+ return {**self.template, **parsed_dict}
419
+ except Exception as ex:
420
+ self.logger.error("Unable to fetch config from RegScale.\n%s", ex)
421
+ return {}
422
+
423
+ def _load_config_from_click_context(self) -> Optional[dict]:
424
+ """
425
+ Load configuration from Click context
426
+
427
+ :return: Configuration dictionary
428
+ :rtype: Optional[dict]
429
+ """
430
+ try:
431
+ import click
432
+
433
+ ctx = click.get_current_context()
434
+ if ctx and ctx.obj and "CONFIG" in ctx.obj:
435
+ self.logger.debug("Found config in Click context...")
436
+ if click_config := ctx.obj["CONFIG"]:
437
+ self.logger.debug("Using config from Click context")
438
+ config = self.verify_config(template=self.template, config=click_config)
439
+ self.save_config(config)
440
+ ctx.obj["CONFIG"] = config
441
+ return config
442
+ except (RuntimeError, ImportError):
443
+ # RuntimeError is raised when there's no active Click context
444
+ # ImportError is raised if Click is not available
445
+ pass
446
+ return None
447
+
448
+ def _gen_config(self, config: Optional[Union[dict, str]] = None) -> dict:
449
+ """
450
+ Generate the Application config from file or environment
451
+
452
+ :param Optional[Union[dict, str]] config: Configuration dictionary, defaults to None
453
+ :raises: TypeError if unable to generate config file
454
+ :return: configuration as a dictionary
455
+ :rtype: dict
456
+ """
457
+ # Check for Click context first
458
+ if click_config := self._load_config_from_click_context():
459
+ self.logger.debug("Successfully retrieved config from Click context.")
460
+ return click_config
461
+
462
+ if self.running_in_airflow:
463
+ if airflow_config := self._get_airflow_config(config):
464
+ self.logger.debug("Successfully retrieved config from Airflow.")
465
+ return airflow_config
466
+ try:
467
+ if config and self.local_config:
468
+ self.logger.debug(f"Config provided as :\n{type(config)}")
469
+ file_config = config
470
+ elif not self.local_config:
471
+ file_config = {}
472
+ else:
473
+ file_config = self._get_conf() or {}
474
+ # Merge
475
+ env = self._get_env()
476
+ if self.templated is False:
477
+ self.logger.debug(f"Starting with {self.config_file}:{len(file_config)} and merging environment.")
478
+ config = {**file_config, **env}
479
+ else:
480
+ self.logger.debug(
481
+ f"Starting with config from environment and merging {self.config_file}:{len(file_config)}."
482
+ )
483
+ config = {**env, **file_config}
484
+ except ScannerError:
485
+ config = self.template
486
+ except TypeError:
487
+ self.logger.error(f"ERROR: {self.config_file} has been encrypted! Please decrypt it before proceeding.\n")
488
+ IOA21H98(self.config_file)
489
+ sys.exit()
490
+ if config is not None:
491
+ # verify keys aren't null and the values are the expected data type
492
+ config = self.verify_config(template=self.template, config=config)
493
+ self.save_config(config)
494
+ # Return config
495
+ return config
496
+
497
+ def _get_airflow_config(self, config: Optional[Union[dict, str]] = None) -> Optional[dict]:
498
+ if config:
499
+ self.logger.debug(f"Received config from Airflow as: {type(config)}")
500
+ # check to see if config is a string because airflow can pass a string instead of a dict
501
+ try:
502
+ config = json.loads(config.replace("'", '"')) if isinstance(config, str) else config
503
+ except json.JSONDecodeError:
504
+ return None
505
+ if isinstance(config, dict):
506
+ config = self._fetch_config_from_regscale(config=config)
507
+ if isinstance(config, str):
508
+ config = self._read_config_from_str(config)
509
+ return config
510
+ elif os.getenv("REGSCALE_TOKEN") and os.getenv("REGSCALE_DOMAIN"):
511
+ self.logger.debug("No config provided, fetching from RegScale via api.")
512
+ return self._fetch_config_from_regscale(
513
+ config={
514
+ "token": os.getenv("REGSCALE_TOKEN"),
515
+ "domain": os.getenv("REGSCALE_DOMAIN"),
516
+ }
517
+ )
518
+ return config or None
519
+
520
+ def _get_env(self) -> dict:
521
+ """
522
+ return dict of RegScale keys from system
523
+
524
+ :return: Application config
525
+ :rtype: dict
526
+ """
527
+ all_keys = self.template.keys()
528
+ sys_keys = [key for key in os.environ if key in all_keys]
529
+ # Update Template
530
+ dat = {}
531
+ try:
532
+ dat = self.template.copy()
533
+ for k in sys_keys:
534
+ dat[k] = os.environ[k]
535
+ except KeyError as ex:
536
+ self.logger.error("Key Error!!: %s", ex)
537
+ self.logger.debug("dat: %s", dat)
538
+ self.templated = dat == self.template
539
+ return dat
540
+
541
+ def _get_conf(self) -> dict:
542
+ """
543
+ Get configuration from init.yaml if exists
544
+
545
+ :return: Application config
546
+ :rtype: dict
547
+ """
548
+ config = None
549
+ # load the config from YAML
550
+ with self._config_lock: # Acquire the lock
551
+ try:
552
+ with open(self.config_file, encoding="utf-8") as stream:
553
+ self.logger.debug(f"Loading {self.config_file}")
554
+ config = yaml.safe_load(stream)
555
+ except FileNotFoundError as ex:
556
+ self.logger.debug(
557
+ "%s!\n This RegScale CLI application will create the %s file in the current working directory.",
558
+ ex,
559
+ )
560
+ finally:
561
+ self.logger.debug("_get_conf: %s, %s", config, type(config))
562
+ return config
563
+
564
+ def save_config(self, conf: dict) -> None:
565
+ """
566
+ Save Configuration to init.yaml
567
+
568
+ :param dict conf: Application configuration
569
+ :rtype: None
570
+ """
571
+ self.config = conf
572
+ if self.api_handler is not None:
573
+ self.api_handler.config = conf
574
+ self.api_handler.domain = conf.get("domain") or self.retrieve_domain()
575
+ if self.running_in_airflow:
576
+ self.logger.debug(
577
+ f"Updated config and not saving to {self.config_file} because CLI is running in an Airflow container."
578
+ )
579
+ return None
580
+ try:
581
+ self.logger.debug(f"Saving config to {self.config_file}.")
582
+ with self._config_lock:
583
+ with open(self.config_file, "w", encoding="utf-8") as file:
584
+ yaml.dump(conf, file)
585
+ except OSError:
586
+ self.logger.error(f"Could not save config to {self.config_file}.")
587
+
588
+ # Has to be Any class to prevent circular imports
589
+ def get_regscale_license(self, api: Any) -> Optional[Response]:
590
+ """
591
+ Get RegScale license of provided application via provided API object
592
+
593
+ :param Any api: API object
594
+ :return: API response, if successful or None
595
+ :rtype: Optional[Response]
596
+ """
597
+ config = self.config or api.config
598
+ if config is None and self.running_in_airflow:
599
+ config = self._get_airflow_config()
600
+ elif config is None:
601
+ config = self._gen_config()
602
+ domain = config.get("domain") or self.retrieve_domain()
603
+ if domain.endswith("/"):
604
+ domain = domain[:-1]
605
+ with contextlib.suppress(requests.RequestException):
606
+ return api.get(
607
+ url=f"{domain}/api/config/getLicense".lower(),
608
+ )
609
+ return None
610
+
611
+ def load_config(self) -> dict:
612
+ """
613
+ Load Configuration file: init.yaml
614
+
615
+ :return: Dict of config
616
+ :rtype: dict
617
+ """
618
+ try:
619
+ with self._config_lock:
620
+ with open(self.config_file, "r", encoding="utf-8") as stream:
621
+ return yaml.safe_load(stream)
622
+ except FileNotFoundError:
623
+ return {}
624
+
625
+ def retrieve_domain(self) -> str:
626
+ """
627
+ Retrieve the domain from the OS environment if it exists
628
+
629
+ :return: The domain
630
+ :rtype: str
631
+ """
632
+ self.logger.debug("Unable to determine domain, using retrieve_domain()...")
633
+ # REGSCALE_DOMAIN is the default host
634
+ for envar in ["REGSCALE_DOMAIN", "PLATFORM_HOST", "domain"]:
635
+ if host := os.environ.get(envar):
636
+ if host.startswith("http"):
637
+ self.logger.debug(f"Found {envar}={host} in environment.")
638
+ return host
639
+ return "https://regscale.yourcompany.com/"
640
+
641
+ def verify_config(self, template: dict, config: dict) -> dict:
642
+ """
643
+ Verify keys and value types in init.yaml while retaining keys in config that are not present in template
644
+
645
+ :param dict template: Default template configuration
646
+ :param dict config: Dictionary to compare against template
647
+ :return: validated and/or updated config
648
+ :rtype: dict
649
+ """
650
+ updated_config = config.copy() # Start with a copy of the original config
651
+
652
+ # Update or add template keys in config
653
+ for key, template_value in template.items():
654
+ config_value = config.get(key)
655
+
656
+ # If key missing or value type mismatch, use template value
657
+ if config_value is None or config_value == "" or not isinstance(config_value, type(template_value)):
658
+ updated_config[key] = template_value
659
+ # If value is a dict, recurse
660
+ elif isinstance(template_value, dict):
661
+ updated_config[key] = self.verify_config(template_value, config.get(key, {}))
662
+ # Else, retain the config value
663
+ else:
664
+ updated_config[key] = config_value
665
+ return updated_config