howler-api 3.4.0.dev929__tar.gz → 3.4.0.dev947__tar.gz

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 (203) hide show
  1. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/PKG-INFO +2 -2
  2. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/hit.py +3 -11
  3. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/tool.py +1 -1
  4. howler_api-3.4.0.dev947/howler/cronjobs/action_queue_worker.py +82 -0
  5. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/helper/discover.py +4 -1
  6. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/helper.py +1 -1
  7. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/config.py +31 -0
  8. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/security/utils.py +2 -2
  9. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/services/action_service.py +95 -0
  10. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/services/hit_service.py +5 -5
  11. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/pyproject.toml +2 -2
  12. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/README.md +0 -0
  13. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/__init__.py +0 -0
  14. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/actions/__init__.py +0 -0
  15. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/actions/add_label.py +0 -0
  16. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/actions/add_to_bundle.py +0 -0
  17. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/actions/change_field.py +0 -0
  18. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/actions/demote.py +0 -0
  19. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/actions/example_plugin.py +0 -0
  20. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/actions/prioritization.py +0 -0
  21. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/actions/promote.py +0 -0
  22. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/actions/remove_from_bundle.py +0 -0
  23. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/actions/remove_label.py +0 -0
  24. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/actions/transition.py +0 -0
  25. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/__init__.py +0 -0
  26. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/base.py +0 -0
  27. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/socket.py +0 -0
  28. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/__init__.py +0 -0
  29. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/action.py +0 -0
  30. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/analytic.py +0 -0
  31. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/auth.py +0 -0
  32. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/clue.py +0 -0
  33. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/configs.py +0 -0
  34. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/dossier.py +0 -0
  35. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/help.py +0 -0
  36. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/notebook.py +0 -0
  37. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/overview.py +0 -0
  38. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/search.py +0 -0
  39. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/template.py +0 -0
  40. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/user.py +0 -0
  41. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/utils/__init__.py +0 -0
  42. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/utils/etag.py +0 -0
  43. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/api/v1/view.py +0 -0
  44. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/app.py +0 -0
  45. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/common/README.md +0 -0
  46. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/common/__init__.py +0 -0
  47. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/common/classification.py +0 -0
  48. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/common/classification.yml +0 -0
  49. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/common/exceptions.py +0 -0
  50. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/common/loader.py +0 -0
  51. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/common/logging/__init__.py +0 -0
  52. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/common/logging/audit.py +0 -0
  53. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/common/logging/format.py +0 -0
  54. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/common/net.py +0 -0
  55. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/common/net_static.py +0 -0
  56. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/common/random_user.py +0 -0
  57. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/common/swagger.py +0 -0
  58. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/config.py +0 -0
  59. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/cronjobs/__init__.py +0 -0
  60. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/cronjobs/retention.py +0 -0
  61. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/cronjobs/rules.py +0 -0
  62. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/cronjobs/view_cleanup.py +0 -0
  63. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/datastore/README.md +0 -0
  64. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/datastore/__init__.py +0 -0
  65. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/datastore/bulk.py +0 -0
  66. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/datastore/collection.py +0 -0
  67. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/datastore/constants.py +0 -0
  68. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/datastore/exceptions.py +0 -0
  69. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/datastore/howler_store.py +0 -0
  70. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/datastore/migrations/fix_process.py +0 -0
  71. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/datastore/operations.py +0 -0
  72. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/datastore/schemas.py +0 -0
  73. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/datastore/store.py +0 -0
  74. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/datastore/support/__init__.py +0 -0
  75. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/datastore/support/build.py +0 -0
  76. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/datastore/support/schemas.py +0 -0
  77. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/datastore/types.py +0 -0
  78. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/error.py +0 -0
  79. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/external/README.md +0 -0
  80. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/external/__init__.py +0 -0
  81. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/external/generate_mitre.py +0 -0
  82. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/external/generate_sigma_rules.py +0 -0
  83. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/external/generate_tlds.py +0 -0
  84. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/external/reindex_data.py +0 -0
  85. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/external/wipe_databases.py +0 -0
  86. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/gunicorn_config.py +0 -0
  87. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/healthz.py +0 -0
  88. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/helper/__init__.py +0 -0
  89. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/helper/azure.py +0 -0
  90. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/helper/hit.py +0 -0
  91. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/helper/oauth.py +0 -0
  92. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/helper/search.py +0 -0
  93. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/helper/workflow.py +0 -0
  94. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/helper/ws.py +0 -0
  95. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/README.md +0 -0
  96. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/__init__.py +0 -0
  97. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/base.py +0 -0
  98. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/charter.txt +0 -0
  99. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/howler_enum.py +0 -0
  100. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/__init__.py +0 -0
  101. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/action.py +0 -0
  102. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/analytic.py +0 -0
  103. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/assemblyline.py +0 -0
  104. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/aws.py +0 -0
  105. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/azure.py +0 -0
  106. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/cbs.py +0 -0
  107. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/clue.py +0 -0
  108. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/dossier.py +0 -0
  109. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/__init__.py +0 -0
  110. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/agent.py +0 -0
  111. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/autonomous_system.py +0 -0
  112. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/client.py +0 -0
  113. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/cloud.py +0 -0
  114. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/code_signature.py +0 -0
  115. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/container.py +0 -0
  116. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/dns.py +0 -0
  117. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/egress.py +0 -0
  118. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/elf.py +0 -0
  119. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/email.py +0 -0
  120. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/error.py +0 -0
  121. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/event.py +0 -0
  122. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/faas.py +0 -0
  123. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/file.py +0 -0
  124. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/geo.py +0 -0
  125. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/group.py +0 -0
  126. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/hash.py +0 -0
  127. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/host.py +0 -0
  128. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/http.py +0 -0
  129. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/ingress.py +0 -0
  130. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/interface.py +0 -0
  131. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/network.py +0 -0
  132. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/observer.py +0 -0
  133. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/organization.py +0 -0
  134. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/os.py +0 -0
  135. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/pe.py +0 -0
  136. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/process.py +0 -0
  137. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/registry.py +0 -0
  138. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/related.py +0 -0
  139. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/rule.py +0 -0
  140. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/server.py +0 -0
  141. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/threat.py +0 -0
  142. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/tls.py +0 -0
  143. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/url.py +0 -0
  144. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/user.py +0 -0
  145. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/user_agent.py +0 -0
  146. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/ecs/vulnerability.py +0 -0
  147. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/gcp.py +0 -0
  148. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/hit.py +0 -0
  149. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/howler_data.py +0 -0
  150. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/lead.py +0 -0
  151. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/localized_label.py +0 -0
  152. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/overview.py +0 -0
  153. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/pivot.py +0 -0
  154. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/template.py +0 -0
  155. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/user.py +0 -0
  156. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/models/view.py +0 -0
  157. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/random_data.py +0 -0
  158. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/odm/randomizer.py +0 -0
  159. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/patched.py +0 -0
  160. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/plugins/__init__.py +0 -0
  161. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/plugins/config.py +0 -0
  162. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/remote/__init__.py +0 -0
  163. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/remote/datatypes/README.md +0 -0
  164. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/remote/datatypes/__init__.py +0 -0
  165. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/remote/datatypes/counters.py +0 -0
  166. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/remote/datatypes/events.py +0 -0
  167. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/remote/datatypes/hash.py +0 -0
  168. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/remote/datatypes/lock.py +0 -0
  169. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/remote/datatypes/queues/__init__.py +0 -0
  170. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/remote/datatypes/queues/comms.py +0 -0
  171. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/remote/datatypes/queues/multi.py +0 -0
  172. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/remote/datatypes/queues/named.py +0 -0
  173. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/remote/datatypes/queues/priority.py +0 -0
  174. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/remote/datatypes/set.py +0 -0
  175. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/remote/datatypes/user_quota_tracker.py +0 -0
  176. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/security/__init__.py +0 -0
  177. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/security/socket.py +0 -0
  178. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/services/__init__.py +0 -0
  179. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/services/analytic_service.py +0 -0
  180. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/services/auth_service.py +0 -0
  181. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/services/config_service.py +0 -0
  182. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/services/dossier_service.py +0 -0
  183. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/services/event_service.py +0 -0
  184. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/services/jwt_service.py +0 -0
  185. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/services/lucene_service.py +0 -0
  186. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/services/notebook_service.py +0 -0
  187. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/services/overview_service.py +0 -0
  188. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/services/template_service.py +0 -0
  189. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/services/user_service.py +0 -0
  190. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/telemetry.py +0 -0
  191. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/utils/__init__.py +0 -0
  192. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/utils/annotations.py +0 -0
  193. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/utils/chunk.py +0 -0
  194. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/utils/compat.py +0 -0
  195. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/utils/constants.py +0 -0
  196. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/utils/dict_utils.py +0 -0
  197. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/utils/isotime.py +0 -0
  198. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/utils/list_utils.py +0 -0
  199. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/utils/lucene.py +0 -0
  200. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/utils/path.py +0 -0
  201. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/utils/socket_utils.py +0 -0
  202. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/utils/str_utils.py +0 -0
  203. {howler_api-3.4.0.dev929 → howler_api-3.4.0.dev947}/howler/utils/uid.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: howler-api
3
- Version: 3.4.0.dev929
3
+ Version: 3.4.0.dev947
4
4
  Summary: Howler - API server
5
5
  License: MIT
6
6
  Keywords: howler,alerting,gc,canada,cse-cst,cse,cst,cyber,cccs
@@ -42,7 +42,7 @@ Requires-Dist: prometheus-client (==0.25.0)
42
42
  Requires-Dist: pydantic (>=2.11.4,<3.0.0)
43
43
  Requires-Dist: pydantic-settings[yaml] (>=2.9.1,<3.0.0)
44
44
  Requires-Dist: pydash (>=8.0.5,<9.0.0)
45
- Requires-Dist: pyjwt (==2.12.1)
45
+ Requires-Dist: pyjwt (==2.13.0)
46
46
  Requires-Dist: pysigma (==0.11.23)
47
47
  Requires-Dist: pysigma-backend-elasticsearch (>=1.1.2,<2.0.0)
48
48
  Requires-Dist: python-baseconv (==1.2.2)
@@ -123,7 +123,7 @@ def create_hits(user: User, **kwargs):
123
123
 
124
124
  datastore().hit.commit()
125
125
 
126
- action_service.bulk_execute_on_query(f"howler.id:({' OR '.join(odm.howler.id for odm in odms)})", user=user)
126
+ action_service.enqueue_action_execution([odm.howler.id for odm in odms], trigger="create", user=user)
127
127
 
128
128
  response_body["warnings"] = warnings
129
129
 
@@ -551,11 +551,7 @@ def add_label(id, label_set, user, **kwargs):
551
551
 
552
552
  datastore().hit.commit()
553
553
 
554
- action_service.bulk_execute_on_query(
555
- f"howler.id:{id}",
556
- trigger="add_label",
557
- user=user,
558
- )
554
+ action_service.enqueue_action_execution([id], trigger="add_label", user=user)
559
555
 
560
556
  hit, version = hit_service.get_hit(id, as_odm=False, version=True)
561
557
 
@@ -609,11 +605,7 @@ def remove_labels(id, label_set, user, **kwargs):
609
605
 
610
606
  datastore().hit.commit()
611
607
 
612
- action_service.bulk_execute_on_query(
613
- f"howler.id:{id}",
614
- trigger="remove_label",
615
- user=user,
616
- )
608
+ action_service.enqueue_action_execution([id], trigger="remove_label", user=user)
617
609
 
618
610
  hit, version = hit_service.get_hit(id, as_odm=False, version=True)
619
611
 
@@ -178,6 +178,6 @@ def create_one_or_many_hits(tool_name: str, user: User, **kwargs): # noqa: C901
178
178
 
179
179
  datastore().hit.commit()
180
180
 
181
- action_service.bulk_execute_on_query(f"howler.id:({' OR '.join(entry['id'] for entry in out)})", user=user)
181
+ action_service.enqueue_action_execution([entry["id"] for entry in out], trigger="create", user=user)
182
182
 
183
183
  return created(out, warnings=warnings)
@@ -0,0 +1,82 @@
1
+ """Action queue worker cronjob — starts per-trigger action queue consumer threads.
2
+
3
+ Auto-discovered by ``howler.cronjobs.setup_jobs`` when ``HWL_USE_JOB_SYSTEM``
4
+ is enabled. Instead of scheduling a periodic APScheduler job, this module
5
+ starts a long-running daemon thread per trigger type that drains its
6
+ dedicated action queue.
7
+ """
8
+
9
+ import threading
10
+
11
+ from apscheduler.schedulers.base import BaseScheduler
12
+
13
+ from howler.common.logging import get_logger
14
+ from howler.config import config
15
+ from howler.odm.models.action import VALID_TRIGGERS
16
+ from howler.services.action_service import _get_action_queue, process_action_batch
17
+
18
+ logger = get_logger(__file__)
19
+
20
+ _threads: dict[str, threading.Thread] = {}
21
+
22
+ BATCH_SIZE: int = config.system.action_queue.batch_size
23
+ BATCH_TIMEOUT: int = config.system.action_queue.batch_timeout
24
+
25
+
26
+ def run_worker(trigger: str) -> None: # pragma: no cover – long-running loop, tested via process_action_batch
27
+ """Block on the action queue for *trigger* and process batches.
28
+
29
+ Accumulates up to ``BATCH_SIZE`` items or flushes after ``BATCH_TIMEOUT``
30
+ seconds, whichever comes first.
31
+ """
32
+ if not config.system.action_queue.enabled:
33
+ logger.info("Action queue worker disabled by configuration, not starting for trigger=%s", trigger)
34
+ return
35
+
36
+ queue = _get_action_queue(trigger)
37
+ logger.info(
38
+ "Action queue worker started for trigger=%s (batch_size=%d, timeout=%ds)",
39
+ trigger,
40
+ BATCH_SIZE,
41
+ BATCH_TIMEOUT,
42
+ )
43
+
44
+ batch: list[dict] = []
45
+
46
+ while True:
47
+ try:
48
+ item = queue.pop(blocking=True, timeout=BATCH_TIMEOUT)
49
+
50
+ if item is not None:
51
+ batch.append(item)
52
+
53
+ if len(batch) >= BATCH_SIZE or (item is None and batch):
54
+ logger.debug("Processing action batch of %d item(s) for trigger=%s", len(batch), trigger)
55
+ try:
56
+ process_action_batch(trigger, batch)
57
+ logger.info("Action batch complete: %d item(s) processed for trigger=%s", len(batch), trigger)
58
+ except Exception:
59
+ logger.exception("Error processing action batch for trigger=%s", trigger)
60
+ finally:
61
+ batch = []
62
+ except Exception:
63
+ logger.exception("Unexpected error in action queue worker loop for trigger=%s", trigger)
64
+
65
+
66
+ def setup_job(sched: BaseScheduler):
67
+ """Start an action queue worker thread per trigger type if the action queue is enabled."""
68
+ global _threads
69
+
70
+ if not config.system.action_queue.enabled:
71
+ logger.info("Action queue worker disabled by configuration")
72
+ return
73
+
74
+ for trigger in VALID_TRIGGERS:
75
+ if trigger in _threads and _threads[trigger].is_alive():
76
+ logger.debug("Action queue worker thread for trigger=%s already running", trigger)
77
+ continue
78
+
79
+ thread = threading.Thread(target=run_worker, args=(trigger,), name=f"action-queue-{trigger}", daemon=True)
80
+ thread.start()
81
+ _threads[trigger] = thread
82
+ logger.info("Action queue worker thread started for trigger=%s", trigger)
@@ -17,6 +17,9 @@ def get_apps_list(discovery_url: Optional[str]) -> list[dict[str, str]]:
17
17
  Returns:
18
18
  list[dict[str, str]]: A list of other apps
19
19
  """
20
+ if not config.discovery.enabled or discovery_url is None:
21
+ return []
22
+
20
23
  if discovery_url not in DISCO_CACHE:
21
24
  apps = []
22
25
 
@@ -25,7 +28,7 @@ def get_apps_list(discovery_url: Optional[str]) -> list[dict[str, str]]:
25
28
 
26
29
  try:
27
30
  resp = requests.get(
28
- typing.cast(str, discovery_url or config.ui.discover_url),
31
+ typing.cast(str, discovery_url or config.discovery.url),
29
32
  headers={"accept": "application/json"},
30
33
  timeout=5,
31
34
  )
@@ -31,7 +31,7 @@ from howler.security.utils import get_password_hash
31
31
  from howler.utils.constants import TESTING
32
32
  from howler.utils.uid import get_random_id
33
33
 
34
- APPS = get_apps_list(discovery_url=config.ui.discover_url)
34
+ APPS = get_apps_list(discovery_url=config.discovery.url)
35
35
  ESCALATIONS = Escalation.list()
36
36
  EXAMPLE_ANALYTICS = ["Password Checker", "Bad Guy Finder", "Exploit Patcher"]
37
37
 
@@ -353,6 +353,19 @@ class ViewCleanup(BaseModel):
353
353
  )
354
354
 
355
355
 
356
+ class ActionQueue(BaseModel):
357
+ """Action queue worker configuration.
358
+
359
+ Controls the background worker that buffers action execution requests
360
+ via a Redis queue, coalescing them into batches to reduce Elasticsearch
361
+ query load during ingestion spikes.
362
+ """
363
+
364
+ enabled: bool = Field(default=True, description="Enable the action queue worker?")
365
+ batch_size: int = Field(default=100, description="Max action items per batch.")
366
+ batch_timeout: int = Field(default=10, description="Seconds to wait before flushing a partial batch.")
367
+
368
+
356
369
  class System(BaseModel):
357
370
  """System-level configuration for Howler.
358
371
 
@@ -366,6 +379,8 @@ class System(BaseModel):
366
379
  "Retention Configuration"
367
380
  view_cleanup: ViewCleanup = ViewCleanup()
368
381
  "View Cleanup Configuration"
382
+ action_queue: ActionQueue = ActionQueue()
383
+ "Action Queue Worker Configuration"
369
384
 
370
385
 
371
386
  class UI(BaseModel):
@@ -456,6 +471,18 @@ class Telemetry(BaseModel):
456
471
  )
457
472
 
458
473
 
474
+ class Discovery(BaseModel):
475
+ """Service discovery configuration for Howler.
476
+
477
+ Defines settings for enabling and configuring service discovery,
478
+ allowing Howler instances to locate and communicate with each other
479
+ in distributed deployments.
480
+ """
481
+
482
+ url: Optional[str] = Field(default=None, description="Discovery URL")
483
+ enabled: bool = Field(default=False, description="Should discovery be enabled?")
484
+
485
+
459
486
  class Core(BaseModel):
460
487
  """Core application configuration for Howler.
461
488
 
@@ -478,6 +505,8 @@ class Core(BaseModel):
478
505
  notebook: Notebook = Notebook()
479
506
  "Configuration for Notebook Integration"
480
507
 
508
+ enable_eureka_discovery: bool = Field(default=True, description="Should Eureka discovery be disabled?")
509
+
481
510
 
482
511
  root_path = Path("/etc") / APP_NAME.replace("-dev", "").replace("-stg", "")
483
512
 
@@ -538,6 +567,8 @@ class Config(BaseSettings):
538
567
  logging: Logging = Logging()
539
568
  system: System = System()
540
569
  ui: UI = UI()
570
+ discovery: Discovery = Discovery()
571
+
541
572
  mapping: dict[str, str] = Field(description="Mapping of alert keys to clue types", default={})
542
573
 
543
574
  model_config = SettingsConfigDict(
@@ -182,6 +182,6 @@ def get_disco_url(host_url: Optional[str]):
182
182
 
183
183
  return f"https://{hostname}/eureka/apps"
184
184
  else:
185
- return config.ui.discover_url
185
+ return config.discovery.url
186
186
  else:
187
- return config.ui.discover_url
187
+ return config.discovery.url
@@ -9,13 +9,108 @@ from howler.common.exceptions import HowlerValueError
9
9
  from howler.common.loader import datastore
10
10
  from howler.common.logging import get_logger
11
11
  from howler.common.logging.audit import audit
12
+ from howler.config import config
12
13
  from howler.odm.models.action import VALID_TRIGGERS, Action
13
14
  from howler.odm.models.user import User
15
+ from howler.remote.datatypes.queues.named import NamedQueue
14
16
  from howler.utils.constants import TESTING
15
17
  from howler.utils.str_utils import sanitize_lucene_query
16
18
 
17
19
  logger = get_logger(__file__)
18
20
 
21
+ # Per-trigger persistent queues for buffering action execution requests.
22
+ _action_queues: dict[str, NamedQueue[dict]] = {}
23
+
24
+
25
+ def _get_action_queue(trigger: str) -> NamedQueue[dict]:
26
+ """Return the action queue for *trigger*, creating it on first use.
27
+
28
+ Raises:
29
+ ValueError: If *trigger* is not in ``VALID_TRIGGERS``.
30
+ """
31
+ if trigger not in VALID_TRIGGERS:
32
+ raise HowlerValueError(f"Invalid trigger {trigger!r}. Must be one of {VALID_TRIGGERS}")
33
+
34
+ if trigger not in _action_queues:
35
+ _action_queues[trigger] = NamedQueue(
36
+ f"howler.action_queue.{trigger}",
37
+ host=config.core.redis.persistent.host,
38
+ port=config.core.redis.persistent.port,
39
+ private=False,
40
+ )
41
+
42
+ return _action_queues[trigger]
43
+
44
+
45
+ def enqueue_action_execution(hit_ids: list[str], trigger: str = "create", user: Optional[User] = None) -> None:
46
+ """Buffer action execution by pushing to a Redis queue.
47
+
48
+ When the action queue is disabled in configuration, falls back to
49
+ calling ``bulk_execute_on_query`` directly for backwards compatibility.
50
+
51
+ Args:
52
+ hit_ids: List of hit IDs to execute actions against.
53
+ trigger: The trigger type (create, promote, demote, add_label, remove_label).
54
+ user: The user initiating the action.
55
+ """
56
+ if not hit_ids:
57
+ return
58
+
59
+ if trigger not in VALID_TRIGGERS:
60
+ raise HowlerValueError(f"Invalid trigger {trigger!r}. Must be one of {VALID_TRIGGERS}")
61
+
62
+ if not config.system.action_queue.enabled:
63
+ query = f"howler.id:({' OR '.join(sanitize_lucene_query(h) for h in hit_ids)})"
64
+ bulk_execute_on_query(query, trigger=trigger, user=user)
65
+ return
66
+
67
+ try:
68
+ _get_action_queue(trigger).push(
69
+ {
70
+ "hit_ids": hit_ids,
71
+ "user": user.as_primitives() if user is not None and hasattr(user, "as_primitives") else user,
72
+ }
73
+ )
74
+ except Exception:
75
+ logger.exception("Failed to enqueue action execution, falling back to direct execution")
76
+ query = f"howler.id:({' OR '.join(sanitize_lucene_query(h) for h in hit_ids)})"
77
+ bulk_execute_on_query(query, trigger=trigger, user=user)
78
+
79
+
80
+ def process_action_batch(trigger: str, items: list[dict]) -> None:
81
+ """Process a batch of queued action execution requests for a single trigger.
82
+
83
+ Groups items by user and issues a single coalesced
84
+ ``bulk_execute_on_query`` call per group, reducing Elasticsearch load.
85
+
86
+ Args:
87
+ trigger: The trigger type this batch belongs to.
88
+ items: List of dicts with keys ``hit_ids`` and ``user``.
89
+ """
90
+ if not items:
91
+ return
92
+
93
+ groups: dict[str | None, tuple[list[str], Any]] = {}
94
+
95
+ for item in items:
96
+ user_data = item.get("user")
97
+ user_key = user_data["uname"] if isinstance(user_data, dict) and "uname" in user_data else None
98
+
99
+ if user_key not in groups:
100
+ groups[user_key] = ([], user_data)
101
+
102
+ groups[user_key][0].extend(item["hit_ids"])
103
+
104
+ for user_key, (hit_ids, user_data) in groups.items():
105
+ # Deduplicate IDs within a batch group
106
+ unique_ids = list(dict.fromkeys(hit_ids))
107
+ query = f"howler.id:({' OR '.join(sanitize_lucene_query(h) for h in unique_ids)})"
108
+
109
+ try:
110
+ bulk_execute_on_query(query, trigger=trigger, user=user_data)
111
+ except Exception:
112
+ logger.exception("Error processing action batch for trigger=%s user=%s", trigger, user_key)
113
+
19
114
 
20
115
  def validate_action(new_action: Any) -> Optional[Response]: # noqa: C901
21
116
  """Validate a new action"""
@@ -736,12 +736,12 @@ def transition_hit(
736
736
  # Commit database changes before executing bulk actions
737
737
  datastore().hit.commit()
738
738
 
739
- # Build query for all processed hits (primary + children)
740
- all_processed_hits = [primary_hit] + child_hits
741
- hit_query = f"howler.id:({' OR '.join(h['howler']['id'] for h in all_processed_hits)})"
739
+ # Build query for all processed hits (primary + children), excluding missing hits
740
+ all_processed_hits = [primary_hit] + [ch for ch in child_hits if ch]
741
+ hit_ids = [h["howler"]["id"] for h in all_processed_hits]
742
742
 
743
- # Execute bulk actions on all hits
744
- action_service.bulk_execute_on_query(hit_query, trigger=trigger, user=user)
743
+ # Enqueue action execution for all hits
744
+ action_service.enqueue_action_execution(hit_ids, trigger=trigger, user=user)
745
745
 
746
746
  # Emit events for all processed hits to notify other systems
747
747
  for processed_hit in all_processed_hits:
@@ -152,7 +152,7 @@ suppress-none-returning = true
152
152
  [tool.poetry]
153
153
  package-mode = true
154
154
  name = "howler-api"
155
- version = "3.4.0.dev929"
155
+ version = "3.4.0.dev947"
156
156
  description = "Howler - API server"
157
157
  authors = [
158
158
  "Canadian Centre for Cyber Security <howler@cyber.gc.ca>",
@@ -207,7 +207,7 @@ gunicorn = "23.0.0"
207
207
  packaging = "<25.0"
208
208
  passlib = "1.7.4"
209
209
  prometheus-client = "0.25.0"
210
- pyjwt = "2.12.1"
210
+ pyjwt = "2.13.0"
211
211
  python-baseconv = "1.2.2"
212
212
  python-datemath = "3.0.3"
213
213
  pyyaml = "6.0.3"