regscale-cli 6.21.2.0__py3-none-any.whl → 6.28.2.1__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.
Files changed (314) hide show
  1. regscale/_version.py +1 -1
  2. regscale/airflow/hierarchy.py +2 -2
  3. regscale/core/app/api.py +5 -2
  4. regscale/core/app/application.py +36 -6
  5. regscale/core/app/internal/control_editor.py +73 -21
  6. regscale/core/app/internal/evidence.py +727 -204
  7. regscale/core/app/internal/login.py +4 -2
  8. regscale/core/app/internal/model_editor.py +219 -64
  9. regscale/core/app/utils/app_utils.py +86 -12
  10. regscale/core/app/utils/catalog_utils/common.py +1 -1
  11. regscale/core/login.py +21 -4
  12. regscale/core/utils/async_graphql_client.py +363 -0
  13. regscale/core/utils/date.py +77 -1
  14. regscale/dev/cli.py +26 -0
  15. regscale/dev/code_gen.py +109 -24
  16. regscale/dev/version.py +72 -0
  17. regscale/integrations/commercial/__init__.py +30 -2
  18. regscale/integrations/commercial/aws/audit_manager_compliance.py +3908 -0
  19. regscale/integrations/commercial/aws/cli.py +3107 -54
  20. regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
  21. regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
  22. regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
  23. regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
  24. regscale/integrations/commercial/{amazon → aws}/common.py +71 -19
  25. regscale/integrations/commercial/aws/config_compliance.py +914 -0
  26. regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
  27. regscale/integrations/commercial/aws/control_compliance_analyzer.py +439 -0
  28. regscale/integrations/commercial/aws/evidence_generator.py +283 -0
  29. regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
  30. regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
  31. regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
  32. regscale/integrations/commercial/aws/iam_evidence.py +574 -0
  33. regscale/integrations/commercial/aws/inventory/__init__.py +338 -22
  34. regscale/integrations/commercial/aws/inventory/base.py +107 -5
  35. regscale/integrations/commercial/aws/inventory/resources/analytics.py +390 -0
  36. regscale/integrations/commercial/aws/inventory/resources/applications.py +234 -0
  37. regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
  38. regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
  39. regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
  40. regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
  41. regscale/integrations/commercial/aws/inventory/resources/compute.py +328 -9
  42. regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
  43. regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
  44. regscale/integrations/commercial/aws/inventory/resources/database.py +481 -31
  45. regscale/integrations/commercial/aws/inventory/resources/developer_tools.py +253 -0
  46. regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
  47. regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
  48. regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
  49. regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
  50. regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
  51. regscale/integrations/commercial/aws/inventory/resources/machine_learning.py +358 -0
  52. regscale/integrations/commercial/aws/inventory/resources/networking.py +390 -67
  53. regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
  54. regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
  55. regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
  56. regscale/integrations/commercial/aws/inventory/resources/storage.py +288 -29
  57. regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
  58. regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
  59. regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
  60. regscale/integrations/commercial/aws/kms_evidence.py +879 -0
  61. regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
  62. regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
  63. regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
  64. regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
  65. regscale/integrations/commercial/aws/org_evidence.py +666 -0
  66. regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
  67. regscale/integrations/commercial/aws/s3_evidence.py +632 -0
  68. regscale/integrations/commercial/aws/scanner.py +1072 -205
  69. regscale/integrations/commercial/aws/security_hub.py +319 -0
  70. regscale/integrations/commercial/aws/session_manager.py +282 -0
  71. regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
  72. regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
  73. regscale/integrations/commercial/jira.py +489 -153
  74. regscale/integrations/commercial/microsoft_defender/defender.py +326 -5
  75. regscale/integrations/commercial/microsoft_defender/defender_api.py +348 -14
  76. regscale/integrations/commercial/microsoft_defender/defender_constants.py +157 -0
  77. regscale/integrations/commercial/qualys/__init__.py +167 -68
  78. regscale/integrations/commercial/qualys/scanner.py +305 -39
  79. regscale/integrations/commercial/sarif/sairf_importer.py +432 -0
  80. regscale/integrations/commercial/sarif/sarif_converter.py +67 -0
  81. regscale/integrations/commercial/sicura/api.py +79 -42
  82. regscale/integrations/commercial/sicura/commands.py +8 -2
  83. regscale/integrations/commercial/sicura/scanner.py +83 -44
  84. regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
  85. regscale/integrations/commercial/synqly/assets.py +133 -16
  86. regscale/integrations/commercial/synqly/edr.py +2 -8
  87. regscale/integrations/commercial/synqly/query_builder.py +536 -0
  88. regscale/integrations/commercial/synqly/ticketing.py +27 -0
  89. regscale/integrations/commercial/synqly/vulnerabilities.py +165 -28
  90. regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
  91. regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
  92. regscale/integrations/commercial/tenablev2/commands.py +146 -5
  93. regscale/integrations/commercial/tenablev2/scanner.py +1 -3
  94. regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
  95. regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
  96. regscale/integrations/commercial/wizv2/click.py +191 -76
  97. regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
  98. regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
  99. regscale/integrations/commercial/wizv2/compliance_report.py +1592 -0
  100. regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
  101. regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +7 -3
  102. regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +92 -89
  103. regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
  104. regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
  105. regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +66 -9
  106. regscale/integrations/commercial/wizv2/file_cleanup.py +104 -0
  107. regscale/integrations/commercial/wizv2/issue.py +776 -28
  108. regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
  109. regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
  110. regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
  111. regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
  112. regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
  113. regscale/integrations/commercial/wizv2/reports.py +243 -0
  114. regscale/integrations/commercial/wizv2/sbom.py +1 -1
  115. regscale/integrations/commercial/wizv2/scanner.py +1031 -441
  116. regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
  117. regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
  118. regscale/integrations/commercial/wizv2/variables.py +89 -3
  119. regscale/integrations/compliance_integration.py +1036 -151
  120. regscale/integrations/control_matcher.py +432 -0
  121. regscale/integrations/due_date_handler.py +333 -0
  122. regscale/integrations/milestone_manager.py +291 -0
  123. regscale/integrations/public/__init__.py +14 -0
  124. regscale/integrations/public/cci_importer.py +834 -0
  125. regscale/integrations/public/csam/__init__.py +0 -0
  126. regscale/integrations/public/csam/csam.py +938 -0
  127. regscale/integrations/public/csam/csam_agency_defined.py +179 -0
  128. regscale/integrations/public/csam/csam_common.py +154 -0
  129. regscale/integrations/public/csam/csam_controls.py +432 -0
  130. regscale/integrations/public/csam/csam_poam.py +124 -0
  131. regscale/integrations/public/fedramp/click.py +77 -6
  132. regscale/integrations/public/fedramp/docx_parser.py +10 -1
  133. regscale/integrations/public/fedramp/fedramp_cis_crm.py +675 -289
  134. regscale/integrations/public/fedramp/fedramp_five.py +1 -1
  135. regscale/integrations/public/fedramp/poam/scanner.py +75 -7
  136. regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
  137. regscale/integrations/scanner_integration.py +1961 -430
  138. regscale/models/integration_models/CCI_List.xml +1 -0
  139. regscale/models/integration_models/aqua.py +2 -2
  140. regscale/models/integration_models/cisa_kev_data.json +805 -11
  141. regscale/models/integration_models/flat_file_importer/__init__.py +5 -8
  142. regscale/models/integration_models/nexpose.py +36 -10
  143. regscale/models/integration_models/qualys.py +3 -4
  144. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  145. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +87 -18
  146. regscale/models/integration_models/synqly_models/filter_parser.py +332 -0
  147. regscale/models/integration_models/synqly_models/ocsf_mapper.py +124 -25
  148. regscale/models/integration_models/synqly_models/synqly_model.py +89 -16
  149. regscale/models/locking.py +12 -8
  150. regscale/models/platform.py +4 -2
  151. regscale/models/regscale_models/__init__.py +7 -0
  152. regscale/models/regscale_models/assessment.py +2 -1
  153. regscale/models/regscale_models/catalog.py +1 -1
  154. regscale/models/regscale_models/compliance_settings.py +251 -1
  155. regscale/models/regscale_models/component.py +1 -0
  156. regscale/models/regscale_models/control_implementation.py +236 -41
  157. regscale/models/regscale_models/control_objective.py +74 -5
  158. regscale/models/regscale_models/file.py +2 -0
  159. regscale/models/regscale_models/form_field_value.py +5 -3
  160. regscale/models/regscale_models/inheritance.py +44 -0
  161. regscale/models/regscale_models/issue.py +301 -102
  162. regscale/models/regscale_models/milestone.py +33 -14
  163. regscale/models/regscale_models/organization.py +3 -0
  164. regscale/models/regscale_models/regscale_model.py +310 -73
  165. regscale/models/regscale_models/security_plan.py +4 -2
  166. regscale/models/regscale_models/vulnerability.py +3 -3
  167. regscale/regscale.py +25 -4
  168. regscale/templates/__init__.py +0 -0
  169. regscale/utils/threading/threadhandler.py +20 -15
  170. regscale/validation/record.py +23 -1
  171. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/METADATA +17 -33
  172. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/RECORD +310 -111
  173. tests/core/__init__.py +0 -0
  174. tests/core/utils/__init__.py +0 -0
  175. tests/core/utils/test_async_graphql_client.py +472 -0
  176. tests/fixtures/test_fixture.py +13 -8
  177. tests/regscale/core/test_login.py +171 -4
  178. tests/regscale/integrations/commercial/__init__.py +0 -0
  179. tests/regscale/integrations/commercial/aws/__init__.py +0 -0
  180. tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
  181. tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
  182. tests/regscale/integrations/commercial/aws/test_aws_analytics_collector.py +260 -0
  183. tests/regscale/integrations/commercial/aws/test_aws_applications_collector.py +242 -0
  184. tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
  185. tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
  186. tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
  187. tests/regscale/integrations/commercial/aws/test_aws_developer_tools_collector.py +203 -0
  188. tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
  189. tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
  190. tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
  191. tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
  192. tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
  193. tests/regscale/integrations/commercial/aws/test_aws_machine_learning_collector.py +237 -0
  194. tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
  195. tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
  196. tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
  197. tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
  198. tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
  199. tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
  200. tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
  201. tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
  202. tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
  203. tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
  204. tests/regscale/integrations/commercial/aws/test_control_compliance_analyzer.py +375 -0
  205. tests/regscale/integrations/commercial/aws/test_datetime_parsing.py +223 -0
  206. tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
  207. tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
  208. tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
  209. tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
  210. tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
  211. tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
  212. tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
  213. tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
  214. tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
  215. tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
  216. tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
  217. tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
  218. tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
  219. tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
  220. tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
  221. tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
  222. tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
  223. tests/regscale/integrations/commercial/conftest.py +28 -0
  224. tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
  225. tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
  226. tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
  227. tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
  228. tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
  229. tests/regscale/integrations/commercial/test_aws.py +3742 -0
  230. tests/regscale/integrations/commercial/test_burp.py +48 -0
  231. tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
  232. tests/regscale/integrations/commercial/test_dependabot.py +341 -0
  233. tests/regscale/integrations/commercial/test_gcp.py +1543 -0
  234. tests/regscale/integrations/commercial/test_gitlab.py +549 -0
  235. tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
  236. tests/regscale/integrations/commercial/test_jira.py +2204 -0
  237. tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
  238. tests/regscale/integrations/commercial/test_okta.py +1228 -0
  239. tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
  240. tests/regscale/integrations/commercial/test_sicura.py +349 -0
  241. tests/regscale/integrations/commercial/test_snow.py +423 -0
  242. tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
  243. tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
  244. tests/regscale/integrations/commercial/test_stig.py +33 -0
  245. tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
  246. tests/regscale/integrations/commercial/test_stigv2.py +406 -0
  247. tests/regscale/integrations/commercial/test_wiz.py +1365 -0
  248. tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
  249. tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
  250. tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
  251. tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
  252. tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
  253. tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
  254. tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
  255. tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
  256. tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
  257. tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
  258. tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
  259. tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
  260. tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
  261. tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
  262. tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
  263. tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
  264. tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
  265. tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
  266. tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
  267. tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
  268. tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
  269. tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
  270. tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
  271. tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
  272. tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
  273. tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
  274. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -0
  275. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
  276. tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
  277. tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
  278. tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
  279. tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
  280. tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1218 -0
  281. tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
  282. tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
  283. tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
  284. tests/regscale/integrations/public/__init__.py +0 -0
  285. tests/regscale/integrations/public/fedramp/__init__.py +1 -0
  286. tests/regscale/integrations/public/fedramp/test_gen_asset_list.py +150 -0
  287. tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
  288. tests/regscale/integrations/public/test_alienvault.py +220 -0
  289. tests/regscale/integrations/public/test_cci.py +1053 -0
  290. tests/regscale/integrations/public/test_cisa.py +1021 -0
  291. tests/regscale/integrations/public/test_emass.py +518 -0
  292. tests/regscale/integrations/public/test_fedramp.py +1152 -0
  293. tests/regscale/integrations/public/test_fedramp_cis_crm.py +3661 -0
  294. tests/regscale/integrations/public/test_file_uploads.py +506 -0
  295. tests/regscale/integrations/public/test_oscal.py +453 -0
  296. tests/regscale/integrations/test_compliance_status_mapping.py +406 -0
  297. tests/regscale/integrations/test_control_matcher.py +1421 -0
  298. tests/regscale/integrations/test_control_matching.py +155 -0
  299. tests/regscale/integrations/test_milestone_manager.py +408 -0
  300. tests/regscale/models/test_control_implementation.py +118 -3
  301. tests/regscale/models/test_form_field_value_integration.py +304 -0
  302. tests/regscale/models/test_issue.py +378 -1
  303. tests/regscale/models/test_module_integration.py +582 -0
  304. tests/regscale/models/test_tenable_integrations.py +811 -105
  305. regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3057
  306. regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +0 -7388
  307. regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +0 -9605
  308. regscale/integrations/public/fedramp/parts_mapper.py +0 -107
  309. /regscale/integrations/commercial/{amazon → sarif}/__init__.py +0 -0
  310. /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
  311. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/LICENSE +0 -0
  312. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/WHEEL +0 -0
  313. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/entry_points.txt +0 -0
  314. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,390 @@
1
+ """AWS analytics resource collectors."""
2
+
3
+ import logging
4
+ from typing import Dict, List, Any, Optional
5
+
6
+ from ..base import BaseCollector
7
+
8
+ logger = logging.getLogger("regscale")
9
+
10
+
11
+ class AnalyticsCollector(BaseCollector):
12
+ """Collector for AWS analytics resources with filtering support."""
13
+
14
+ def __init__(
15
+ self,
16
+ session: Any,
17
+ region: str,
18
+ account_id: Optional[str] = None,
19
+ tags: Optional[Dict[str, str]] = None,
20
+ enabled_services: Optional[Dict[str, bool]] = None,
21
+ ):
22
+ """
23
+ Initialize analytics collector with filtering support.
24
+
25
+ :param session: AWS session to use for API calls
26
+ :param str region: AWS region to collect from
27
+ :param str account_id: Optional AWS account ID to filter resources
28
+ :param dict tags: Optional tag filters (AND logic)
29
+ :param dict enabled_services: Optional dict of service names to boolean flags for enabling/disabling collection
30
+ """
31
+ super().__init__(session, region, account_id, tags)
32
+ self.enabled_services = enabled_services or {}
33
+
34
+ def get_emr_clusters(self) -> List[Dict[str, Any]]:
35
+ """
36
+ Get information about EMR clusters.
37
+
38
+ :return: List of EMR cluster information
39
+ :rtype: List[Dict[str, Any]]
40
+ """
41
+ clusters = []
42
+ try:
43
+ emr_client = self._get_client("emr")
44
+ paginator = emr_client.get_paginator("list_clusters")
45
+
46
+ for page in paginator.paginate(ClusterStates=["STARTING", "BOOTSTRAPPING", "RUNNING", "WAITING"]):
47
+ for cluster_summary in page.get("Clusters", []):
48
+ cluster_id = cluster_summary.get("Id")
49
+
50
+ try:
51
+ cluster_detail = emr_client.describe_cluster(ClusterId=cluster_id)
52
+ cluster = cluster_detail.get("Cluster", {})
53
+ cluster_arn = cluster.get("ClusterArn", "")
54
+
55
+ if not self._matches_account(cluster_arn):
56
+ continue
57
+
58
+ if not self._matches_tags(cluster.get("Tags", [])):
59
+ continue
60
+
61
+ clusters.append(
62
+ {
63
+ "Region": self.region,
64
+ "ClusterId": cluster_id,
65
+ "ClusterArn": cluster_arn,
66
+ "Name": cluster.get("Name"),
67
+ "Status": cluster.get("Status", {}).get("State"),
68
+ "NormalizedInstanceHours": cluster.get("NormalizedInstanceHours"),
69
+ "MasterPublicDnsName": cluster.get("MasterPublicDnsName"),
70
+ "ReleaseLabel": cluster.get("ReleaseLabel"),
71
+ "AutoTerminate": cluster.get("AutoTerminate"),
72
+ "Tags": cluster.get("Tags", []),
73
+ }
74
+ )
75
+ except Exception as detail_error:
76
+ logger.debug("Error getting EMR cluster details for %s: %s", cluster_id, detail_error)
77
+ continue
78
+
79
+ except Exception as e:
80
+ self._handle_error(e, "EMR clusters")
81
+ return clusters
82
+
83
+ def get_kinesis_streams(self) -> List[Dict[str, Any]]:
84
+ """
85
+ Get information about Kinesis Data Streams.
86
+
87
+ :return: List of Kinesis stream information
88
+ :rtype: List[Dict[str, Any]]
89
+ """
90
+ streams = []
91
+ try:
92
+ kinesis_client = self._get_client("kinesis")
93
+ paginator = kinesis_client.get_paginator("list_streams")
94
+
95
+ for page in paginator.paginate():
96
+ for stream_name in page.get("StreamNames", []):
97
+ try:
98
+ stream_detail = kinesis_client.describe_stream(StreamName=stream_name)
99
+ stream = stream_detail.get("StreamDescription", {})
100
+ stream_arn = stream.get("StreamARN", "")
101
+
102
+ if not self._matches_account(stream_arn):
103
+ continue
104
+
105
+ tags_response = kinesis_client.list_tags_for_stream(StreamName=stream_name)
106
+ stream_tags = tags_response.get("Tags", [])
107
+
108
+ if not self._matches_tags(stream_tags):
109
+ continue
110
+
111
+ streams.append(
112
+ {
113
+ "Region": self.region,
114
+ "StreamName": stream_name,
115
+ "StreamARN": stream_arn,
116
+ "StreamStatus": stream.get("StreamStatus"),
117
+ "RetentionPeriodHours": stream.get("RetentionPeriodHours"),
118
+ "StreamCreationTimestamp": stream.get("StreamCreationTimestamp"),
119
+ "EnhancedMonitoring": stream.get("EnhancedMonitoring", []),
120
+ "EncryptionType": stream.get("EncryptionType"),
121
+ "Tags": stream_tags,
122
+ }
123
+ )
124
+ except Exception as stream_error:
125
+ logger.debug("Error getting Kinesis stream details for %s: %s", stream_name, stream_error)
126
+ continue
127
+
128
+ except Exception as e:
129
+ self._handle_error(e, "Kinesis Data Streams")
130
+ return streams
131
+
132
+ def _process_firehose_stream(self, firehose_client: Any, stream_name: str) -> Optional[Dict[str, Any]]:
133
+ """
134
+ Process a single Firehose delivery stream.
135
+
136
+ :param firehose_client: Firehose client
137
+ :param str stream_name: Stream name
138
+ :return: Stream information or None if filtered out
139
+ :rtype: Optional[Dict[str, Any]]
140
+ """
141
+ try:
142
+ stream_detail = firehose_client.describe_delivery_stream(DeliveryStreamName=stream_name)
143
+ stream = stream_detail.get("DeliveryStreamDescription", {})
144
+ stream_arn = stream.get("DeliveryStreamARN", "")
145
+
146
+ if not self._matches_account(stream_arn):
147
+ return None
148
+
149
+ tags_response = firehose_client.list_tags_for_delivery_stream(DeliveryStreamName=stream_name)
150
+ stream_tags = tags_response.get("Tags", [])
151
+
152
+ if not self._matches_tags(stream_tags):
153
+ return None
154
+
155
+ return {
156
+ "Region": self.region,
157
+ "DeliveryStreamName": stream_name,
158
+ "DeliveryStreamARN": stream_arn,
159
+ "DeliveryStreamStatus": stream.get("DeliveryStreamStatus"),
160
+ "DeliveryStreamType": stream.get("DeliveryStreamType"),
161
+ "VersionId": stream.get("VersionId"),
162
+ "CreateTimestamp": stream.get("CreateTimestamp"),
163
+ "Tags": stream_tags,
164
+ }
165
+ except Exception as stream_error:
166
+ logger.debug("Error getting Firehose stream details for %s: %s", stream_name, stream_error)
167
+ return None
168
+
169
+ def get_kinesis_firehose_streams(self) -> List[Dict[str, Any]]:
170
+ """
171
+ Get information about Kinesis Firehose delivery streams.
172
+
173
+ :return: List of Kinesis Firehose stream information
174
+ :rtype: List[Dict[str, Any]]
175
+ """
176
+ streams = []
177
+ try:
178
+ firehose_client = self._get_client("firehose")
179
+ has_more = True
180
+ exclusive_start_stream_name = None
181
+
182
+ while has_more:
183
+ if exclusive_start_stream_name:
184
+ response = firehose_client.list_delivery_streams(
185
+ ExclusiveStartDeliveryStreamName=exclusive_start_stream_name
186
+ )
187
+ else:
188
+ response = firehose_client.list_delivery_streams()
189
+
190
+ for stream_name in response.get("DeliveryStreamNames", []):
191
+ stream_info = self._process_firehose_stream(firehose_client, stream_name)
192
+ if stream_info:
193
+ streams.append(stream_info)
194
+
195
+ has_more = response.get("HasMoreDeliveryStreams", False)
196
+ if has_more and response.get("DeliveryStreamNames"):
197
+ exclusive_start_stream_name = response["DeliveryStreamNames"][-1]
198
+ else:
199
+ has_more = False
200
+
201
+ except Exception as e:
202
+ self._handle_error(e, "Kinesis Firehose delivery streams")
203
+ return streams
204
+
205
+ def get_glue_databases(self) -> List[Dict[str, Any]]:
206
+ """
207
+ Get information about AWS Glue databases.
208
+
209
+ :return: List of Glue database information
210
+ :rtype: List[Dict[str, Any]]
211
+ """
212
+ databases = []
213
+ try:
214
+ glue_client = self._get_client("glue")
215
+ paginator = glue_client.get_paginator("get_databases")
216
+
217
+ for page in paginator.paginate():
218
+ for database in page.get("DatabaseList", []):
219
+ database_name = database.get("Name")
220
+ database_arn = f"arn:aws:glue:{self.region}:{self.account_id or '*'}:database/{database_name}"
221
+
222
+ if not self._matches_account(database_arn):
223
+ continue
224
+
225
+ try:
226
+ tags_response = glue_client.get_tags(ResourceArn=database_arn)
227
+ database_tags = tags_response.get("Tags", {})
228
+
229
+ if not self._matches_tags(database_tags):
230
+ continue
231
+
232
+ databases.append(
233
+ {
234
+ "Region": self.region,
235
+ "DatabaseName": database_name,
236
+ "Description": database.get("Description"),
237
+ "LocationUri": database.get("LocationUri"),
238
+ "CreateTime": database.get("CreateTime"),
239
+ "Tags": database_tags,
240
+ }
241
+ )
242
+ except Exception as tag_error:
243
+ logger.debug("Error getting Glue database tags for %s: %s", database_name, tag_error)
244
+ continue
245
+
246
+ except Exception as e:
247
+ self._handle_error(e, "Glue databases")
248
+ return databases
249
+
250
+ def _process_athena_workgroup(self, athena_client: Any, wg_name: str) -> Optional[Dict[str, Any]]:
251
+ """
252
+ Process a single Athena workgroup.
253
+
254
+ :param athena_client: Athena client
255
+ :param str wg_name: Workgroup name
256
+ :return: Workgroup information or None if filtered out
257
+ :rtype: Optional[Dict[str, Any]]
258
+ """
259
+ try:
260
+ wg_detail = athena_client.get_work_group(WorkGroup=wg_name)
261
+ workgroup = wg_detail.get("WorkGroup", {})
262
+
263
+ wg_arn = f"arn:aws:athena:{self.region}:{self.account_id or '*'}:workgroup/{wg_name}"
264
+
265
+ if not self._matches_account(wg_arn):
266
+ return None
267
+
268
+ tags_response = athena_client.list_tags_for_resource(ResourceARN=wg_arn)
269
+ wg_tags = tags_response.get("Tags", [])
270
+
271
+ if not self._matches_tags(wg_tags):
272
+ return None
273
+
274
+ return {
275
+ "Region": self.region,
276
+ "WorkGroupName": wg_name,
277
+ "State": workgroup.get("State"),
278
+ "Description": workgroup.get("Description"),
279
+ "CreationTime": workgroup.get("CreationTime"),
280
+ "Tags": wg_tags,
281
+ }
282
+ except Exception as wg_error:
283
+ logger.debug("Error getting Athena workgroup details for %s: %s", wg_name, wg_error)
284
+ return None
285
+
286
+ def get_athena_workgroups(self) -> List[Dict[str, Any]]:
287
+ """
288
+ Get information about Athena workgroups.
289
+
290
+ :return: List of Athena workgroup information
291
+ :rtype: List[Dict[str, Any]]
292
+ """
293
+ workgroups = []
294
+ try:
295
+ athena_client = self._get_client("athena")
296
+ next_token = None
297
+
298
+ while True:
299
+ if next_token:
300
+ response = athena_client.list_work_groups(NextToken=next_token)
301
+ else:
302
+ response = athena_client.list_work_groups()
303
+
304
+ for wg_summary in response.get("WorkGroups", []):
305
+ wg_name = wg_summary.get("Name")
306
+ wg_info = self._process_athena_workgroup(athena_client, wg_name)
307
+ if wg_info:
308
+ workgroups.append(wg_info)
309
+
310
+ next_token = response.get("NextToken")
311
+ if not next_token:
312
+ break
313
+
314
+ except Exception as e:
315
+ self._handle_error(e, "Athena workgroups")
316
+ return workgroups
317
+
318
+ def get_msk_clusters(self) -> List[Dict[str, Any]]:
319
+ """
320
+ Get information about MSK (Managed Streaming for Kafka) clusters.
321
+
322
+ :return: List of MSK cluster information
323
+ :rtype: List[Dict[str, Any]]
324
+ """
325
+ clusters = []
326
+ try:
327
+ msk_client = self._get_client("kafka")
328
+ paginator = msk_client.get_paginator("list_clusters")
329
+
330
+ for page in paginator.paginate():
331
+ for cluster_info in page.get("ClusterInfoList", []):
332
+ cluster_arn = cluster_info.get("ClusterArn", "")
333
+
334
+ if not self._matches_account(cluster_arn):
335
+ continue
336
+
337
+ if not self._matches_tags(cluster_info.get("Tags", {})):
338
+ continue
339
+
340
+ clusters.append(
341
+ {
342
+ "Region": self.region,
343
+ "ClusterName": cluster_info.get("ClusterName"),
344
+ "ClusterArn": cluster_arn,
345
+ "State": cluster_info.get("State"),
346
+ "ClusterType": cluster_info.get("ClusterType"),
347
+ "CurrentVersion": cluster_info.get("CurrentVersion"),
348
+ "CreationTime": cluster_info.get("CreationTime"),
349
+ "NumberOfBrokerNodes": cluster_info.get("NumberOfBrokerNodes"),
350
+ "Tags": cluster_info.get("Tags", {}),
351
+ }
352
+ )
353
+ except Exception as e:
354
+ self._handle_error(e, "MSK clusters")
355
+ return clusters
356
+
357
+ def collect(self) -> Dict[str, Any]:
358
+ """
359
+ Collect analytics resources based on enabled_services configuration.
360
+
361
+ :return: Dictionary containing enabled analytics resource information
362
+ :rtype: Dict[str, Any]
363
+ """
364
+ result = {}
365
+
366
+ # EMR Clusters
367
+ if self.enabled_services.get("emr", True):
368
+ result["EMRClusters"] = self.get_emr_clusters()
369
+
370
+ # Kinesis Data Streams
371
+ if self.enabled_services.get("kinesis_streams", True):
372
+ result["KinesisStreams"] = self.get_kinesis_streams()
373
+
374
+ # Kinesis Firehose
375
+ if self.enabled_services.get("kinesis_firehose", True):
376
+ result["KinesisFirehoseStreams"] = self.get_kinesis_firehose_streams()
377
+
378
+ # Glue Databases
379
+ if self.enabled_services.get("glue", True):
380
+ result["GlueDatabases"] = self.get_glue_databases()
381
+
382
+ # Athena Workgroups
383
+ if self.enabled_services.get("athena", True):
384
+ result["AthenaWorkgroups"] = self.get_athena_workgroups()
385
+
386
+ # MSK Clusters
387
+ if self.enabled_services.get("msk", True):
388
+ result["MSKClusters"] = self.get_msk_clusters()
389
+
390
+ return result
@@ -0,0 +1,234 @@
1
+ """AWS application service resource collectors."""
2
+
3
+ import logging
4
+ from typing import Dict, List, Any, Optional
5
+
6
+ from ..base import BaseCollector
7
+
8
+ logger = logging.getLogger("regscale")
9
+
10
+
11
+ class ApplicationCollector(BaseCollector):
12
+ """Collector for AWS application service resources with filtering support."""
13
+
14
+ def __init__(
15
+ self,
16
+ session: Any,
17
+ region: str,
18
+ account_id: Optional[str] = None,
19
+ tags: Optional[Dict[str, str]] = None,
20
+ enabled_services: Optional[Dict[str, bool]] = None,
21
+ ):
22
+ """
23
+ Initialize application collector with filtering support.
24
+
25
+ :param session: AWS session to use for API calls
26
+ :param str region: AWS region to collect from
27
+ :param str account_id: Optional AWS account ID to filter resources
28
+ :param dict tags: Optional tag filters (AND logic)
29
+ :param dict enabled_services: Optional dict of service names to boolean flags for enabling/disabling collection
30
+ """
31
+ super().__init__(session, region, account_id, tags)
32
+ self.enabled_services = enabled_services or {}
33
+
34
+ def get_step_functions_state_machines(self) -> List[Dict[str, Any]]:
35
+ """
36
+ Get information about Step Functions state machines.
37
+
38
+ :return: List of Step Functions state machine information
39
+ :rtype: List[Dict[str, Any]]
40
+ """
41
+ state_machines = []
42
+ try:
43
+ sfn_client = self._get_client("stepfunctions")
44
+ paginator = sfn_client.get_paginator("list_state_machines")
45
+
46
+ for page in paginator.paginate():
47
+ for sm_item in page.get("stateMachines", []):
48
+ sm_arn = sm_item.get("stateMachineArn", "")
49
+
50
+ if not self._matches_account(sm_arn):
51
+ continue
52
+
53
+ try:
54
+ sm_detail = sfn_client.describe_state_machine(stateMachineArn=sm_arn)
55
+ tags_response = sfn_client.list_tags_for_resource(resourceArn=sm_arn)
56
+ sm_tags = tags_response.get("tags", [])
57
+
58
+ if not self._matches_tags(sm_tags):
59
+ continue
60
+
61
+ state_machines.append(
62
+ {
63
+ "Region": self.region,
64
+ "StateMachineName": sm_item.get("name"),
65
+ "StateMachineArn": sm_arn,
66
+ "Type": sm_item.get("type"),
67
+ "Status": sm_detail.get("status"),
68
+ "RoleArn": sm_detail.get("roleArn"),
69
+ "CreationDate": sm_item.get("creationDate"),
70
+ "Tags": sm_tags,
71
+ }
72
+ )
73
+ except Exception as sm_error:
74
+ logger.debug("Error getting Step Functions state machine details for %s: %s", sm_arn, sm_error)
75
+ continue
76
+
77
+ except Exception as e:
78
+ self._handle_error(e, "Step Functions state machines")
79
+ return state_machines
80
+
81
+ def get_appsync_apis(self) -> List[Dict[str, Any]]:
82
+ """
83
+ Get information about AppSync GraphQL APIs.
84
+
85
+ :return: List of AppSync API information
86
+ :rtype: List[Dict[str, Any]]
87
+ """
88
+ apis = []
89
+ try:
90
+ appsync_client = self._get_client("appsync")
91
+ paginator = appsync_client.get_paginator("list_graphql_apis")
92
+
93
+ for page in paginator.paginate():
94
+ for api in page.get("graphqlApis", []):
95
+ api_arn = api.get("arn", "")
96
+
97
+ if not self._matches_account(api_arn):
98
+ continue
99
+
100
+ if not self._matches_tags(api.get("tags", {})):
101
+ continue
102
+
103
+ apis.append(
104
+ {
105
+ "Region": self.region,
106
+ "ApiId": api.get("apiId"),
107
+ "ApiArn": api_arn,
108
+ "Name": api.get("name"),
109
+ "AuthenticationType": api.get("authenticationType"),
110
+ "Uris": api.get("uris"),
111
+ "Tags": api.get("tags", {}),
112
+ }
113
+ )
114
+ except Exception as e:
115
+ self._handle_error(e, "AppSync GraphQL APIs")
116
+ return apis
117
+
118
+ def get_workspaces(self) -> List[Dict[str, Any]]:
119
+ """
120
+ Get information about WorkSpaces virtual desktops.
121
+
122
+ :return: List of WorkSpaces information
123
+ :rtype: List[Dict[str, Any]]
124
+ """
125
+ workspaces = []
126
+ try:
127
+ workspaces_client = self._get_client("workspaces")
128
+ paginator = workspaces_client.get_paginator("describe_workspaces")
129
+
130
+ for page in paginator.paginate():
131
+ for workspace in page.get("Workspaces", []):
132
+ workspace_id = workspace.get("WorkspaceId")
133
+
134
+ try:
135
+ tags_response = workspaces_client.describe_tags(ResourceId=workspace_id)
136
+ workspace_tags = tags_response.get("TagList", [])
137
+
138
+ if not self._matches_tags(workspace_tags):
139
+ continue
140
+
141
+ workspaces.append(
142
+ {
143
+ "Region": self.region,
144
+ "WorkspaceId": workspace_id,
145
+ "DirectoryId": workspace.get("DirectoryId"),
146
+ "UserName": workspace.get("UserName"),
147
+ "IpAddress": workspace.get("IpAddress"),
148
+ "State": workspace.get("State"),
149
+ "BundleId": workspace.get("BundleId"),
150
+ "SubnetId": workspace.get("SubnetId"),
151
+ "ComputerName": workspace.get("ComputerName"),
152
+ "Tags": workspace_tags,
153
+ }
154
+ )
155
+ except Exception as tag_error:
156
+ logger.debug("Error getting WorkSpaces tags for %s: %s", workspace_id, tag_error)
157
+ continue
158
+
159
+ except Exception as e:
160
+ self._handle_error(e, "WorkSpaces")
161
+ return workspaces
162
+
163
+ def get_iot_things(self) -> List[Dict[str, Any]]:
164
+ """
165
+ Get information about IoT Core things.
166
+
167
+ :return: List of IoT thing information
168
+ :rtype: List[Dict[str, Any]]
169
+ """
170
+ things = []
171
+ try:
172
+ iot_client = self._get_client("iot")
173
+ paginator = iot_client.get_paginator("list_things")
174
+
175
+ for page in paginator.paginate():
176
+ for thing in page.get("things", []):
177
+ thing_name = thing.get("thingName")
178
+ thing_arn = thing.get("thingArn", "")
179
+
180
+ if not self._matches_account(thing_arn):
181
+ continue
182
+
183
+ try:
184
+ tags_response = iot_client.list_tags_for_resource(resourceArn=thing_arn)
185
+ thing_tags = tags_response.get("tags", [])
186
+
187
+ if not self._matches_tags(thing_tags):
188
+ continue
189
+
190
+ things.append(
191
+ {
192
+ "Region": self.region,
193
+ "ThingName": thing_name,
194
+ "ThingArn": thing_arn,
195
+ "ThingTypeName": thing.get("thingTypeName"),
196
+ "Attributes": thing.get("attributes", {}),
197
+ "Version": thing.get("version"),
198
+ "Tags": thing_tags,
199
+ }
200
+ )
201
+ except Exception as tag_error:
202
+ logger.debug("Error getting IoT thing tags for %s: %s", thing_name, tag_error)
203
+ continue
204
+
205
+ except Exception as e:
206
+ self._handle_error(e, "IoT things")
207
+ return things
208
+
209
+ def collect(self) -> Dict[str, Any]:
210
+ """
211
+ Collect application service resources based on enabled_services configuration.
212
+
213
+ :return: Dictionary containing enabled application resource information
214
+ :rtype: Dict[str, Any]
215
+ """
216
+ result = {}
217
+
218
+ # Step Functions State Machines
219
+ if self.enabled_services.get("step_functions", True):
220
+ result["StepFunctionsStateMachines"] = self.get_step_functions_state_machines()
221
+
222
+ # AppSync GraphQL APIs
223
+ if self.enabled_services.get("appsync", True):
224
+ result["AppSyncAPIs"] = self.get_appsync_apis()
225
+
226
+ # WorkSpaces
227
+ if self.enabled_services.get("workspaces", True):
228
+ result["WorkSpaces"] = self.get_workspaces()
229
+
230
+ # IoT Things
231
+ if self.enabled_services.get("iot", True):
232
+ result["IoTThings"] = self.get_iot_things()
233
+
234
+ return result