itsi 0.1.20 → 0.2.2

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 (319) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -8
  3. data/Cargo.lock +2 -2
  4. data/LICENSE.txt +698 -0
  5. data/README.md +15 -4
  6. data/Rakefile +9 -5
  7. data/crates/itsi_acme/.gitignore +4 -0
  8. data/crates/itsi_acme/Cargo.toml +86 -0
  9. data/crates/itsi_acme/LICENSE-APACHE +201 -0
  10. data/crates/itsi_acme/LICENSE-MIT +23 -0
  11. data/crates/itsi_acme/README.md +9 -0
  12. data/crates/itsi_acme/examples/high_level.rs +63 -0
  13. data/crates/itsi_acme/examples/high_level_warp.rs +52 -0
  14. data/crates/itsi_acme/examples/low_level.rs +87 -0
  15. data/crates/itsi_acme/examples/low_level_axum.rs +66 -0
  16. data/crates/itsi_acme/src/acceptor.rs +81 -0
  17. data/crates/itsi_acme/src/acme.rs +354 -0
  18. data/crates/itsi_acme/src/axum.rs +86 -0
  19. data/crates/itsi_acme/src/cache.rs +39 -0
  20. data/crates/itsi_acme/src/caches/boxed.rs +80 -0
  21. data/crates/itsi_acme/src/caches/composite.rs +69 -0
  22. data/crates/itsi_acme/src/caches/dir.rs +106 -0
  23. data/crates/itsi_acme/src/caches/mod.rs +11 -0
  24. data/crates/itsi_acme/src/caches/no.rs +78 -0
  25. data/crates/itsi_acme/src/caches/test.rs +136 -0
  26. data/crates/itsi_acme/src/config.rs +172 -0
  27. data/crates/itsi_acme/src/https_helper.rs +69 -0
  28. data/crates/itsi_acme/src/incoming.rs +142 -0
  29. data/crates/itsi_acme/src/jose.rs +161 -0
  30. data/crates/itsi_acme/src/lib.rs +142 -0
  31. data/crates/itsi_acme/src/resolver.rs +59 -0
  32. data/crates/itsi_acme/src/state.rs +424 -0
  33. data/crates/itsi_rb_helpers/src/lib.rs +4 -3
  34. data/crates/itsi_scheduler/Cargo.toml +1 -1
  35. data/crates/itsi_scheduler/src/itsi_scheduler.rs +8 -2
  36. data/crates/itsi_scheduler/src/lib.rs +1 -0
  37. data/crates/itsi_server/Cargo.toml +1 -1
  38. data/crates/itsi_server/src/lib.rs +2 -1
  39. data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +18 -1
  40. data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +11 -3
  41. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +122 -63
  42. data/crates/itsi_server/src/ruby_types/itsi_server.rs +2 -0
  43. data/crates/itsi_server/src/server/binds/bind.rs +3 -0
  44. data/crates/itsi_server/src/server/binds/listener.rs +12 -5
  45. data/crates/itsi_server/src/server/binds/tls.rs +13 -5
  46. data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +12 -5
  47. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +8 -1
  48. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +9 -1
  49. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +48 -43
  50. data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +11 -2
  51. data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +39 -12
  52. data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +36 -27
  53. data/crates/itsi_server/src/server/middleware_stack/middlewares/csp.rs +25 -11
  54. data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +12 -3
  55. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +74 -72
  56. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +15 -1
  57. data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +11 -8
  58. data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +19 -11
  59. data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +5 -5
  60. data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +2 -2
  61. data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +11 -5
  62. data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +17 -20
  63. data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +19 -8
  64. data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +16 -37
  65. data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +22 -12
  66. data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +26 -11
  67. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +7 -1
  68. data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +14 -4
  69. data/crates/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +19 -0
  70. data/crates/itsi_server/src/server/middleware_stack/mod.rs +49 -13
  71. data/crates/itsi_server/src/server/mod.rs +1 -0
  72. data/crates/itsi_server/src/server/redirect_type.rs +26 -0
  73. data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +22 -16
  74. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +49 -12
  75. data/crates/itsi_server/src/server/signal.rs +1 -0
  76. data/crates/itsi_server/src/server/size_limited_incoming.rs +6 -0
  77. data/crates/itsi_server/src/server/thread_worker.rs +5 -1
  78. data/crates/itsi_server/src/services/itsi_http_service.rs +20 -2
  79. data/crates/itsi_server/src/services/rate_limiter.rs +15 -4
  80. data/crates/itsi_server/src/services/static_file_server.rs +33 -19
  81. data/crates/itsi_tracing/src/lib.rs +42 -22
  82. data/docs/content/_index.md +1 -2
  83. data/docs/content/acknowledgements/_index.md +5 -2
  84. data/docs/content/configuration/_index.md +8 -5
  85. data/docs/content/contact/_index.md +8 -1
  86. data/docs/content/faqs/_index.md +5 -3
  87. data/docs/content/features/_index.md +56 -50
  88. data/docs/content/getting_started/_index.md +8 -5
  89. data/docs/content/getting_started/local_development.md +68 -8
  90. data/docs/content/getting_started/logging.md +16 -9
  91. data/docs/content/getting_started/running_itsi_in_production.md +5 -3
  92. data/docs/content/getting_started/signals.md +38 -0
  93. data/docs/content/itsi_scheduler/_index.md +8 -7
  94. data/docs/content/utilities/_index.md +13 -0
  95. data/docs/content/utilities/config_file_testing.md +17 -0
  96. data/docs/content/utilities/passfile_generator.md +41 -0
  97. data/docs/content/utilities/route_testing.md +27 -0
  98. data/docs/content/utilities/secrets_management.md +30 -0
  99. data/docs/hugo.yaml +1 -1
  100. data/fairytale.txt +3 -4
  101. data/gems/scheduler/Cargo.lock +1 -1
  102. data/gems/scheduler/README.md +4 -5
  103. data/gems/scheduler/Rakefile +0 -4
  104. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  105. data/gems/scheduler/lib/itsi/scheduler.rb +9 -4
  106. data/gems/scheduler/test/test_active_record.rb +12 -7
  107. data/gems/server/Cargo.lock +1 -1
  108. data/gems/server/Rakefile +0 -4
  109. data/gems/server/exe/itsi +13 -2
  110. data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +2 -0
  111. data/gems/server/lib/itsi/http_request.rb +40 -9
  112. data/gems/server/lib/itsi/http_response.rb +2 -1
  113. data/gems/server/lib/itsi/passfile.rb +0 -1
  114. data/gems/server/lib/itsi/server/config/config_helpers.rb +20 -8
  115. data/gems/server/lib/itsi/server/config/dsl.rb +20 -435
  116. data/gems/server/lib/itsi/server/config/known_paths.rb +4 -1
  117. data/gems/server/lib/itsi/server/config/middleware/_index.md +6 -4
  118. data/gems/server/lib/itsi/server/config/middleware/allow_list.md +46 -0
  119. data/gems/server/lib/itsi/server/config/middleware/allow_list.rb +42 -0
  120. data/gems/server/lib/itsi/server/config/middleware/auth_api_key.md +90 -0
  121. data/gems/server/lib/itsi/server/config/middleware/auth_api_key.rb +51 -0
  122. data/gems/server/lib/itsi/server/config/middleware/auth_basic.md +45 -0
  123. data/gems/server/lib/itsi/server/config/middleware/auth_basic.rb +44 -0
  124. data/gems/server/lib/itsi/server/config/middleware/auth_jwt.md +82 -0
  125. data/gems/server/lib/itsi/server/config/middleware/auth_jwt.rb +38 -0
  126. data/gems/server/lib/itsi/server/config/middleware/cache_control.md +78 -0
  127. data/gems/server/lib/itsi/server/config/middleware/cache_control.rb +45 -0
  128. data/gems/server/lib/itsi/server/config/middleware/cidr_to_regex.rb +50 -0
  129. data/gems/server/lib/itsi/server/config/middleware/compression.md +50 -0
  130. data/gems/server/lib/itsi/server/config/middleware/compression.rb +37 -0
  131. data/gems/server/lib/itsi/server/config/middleware/cors.md +93 -0
  132. data/gems/server/lib/itsi/server/config/middleware/cors.rb +32 -0
  133. data/gems/server/lib/itsi/server/config/middleware/csp.md +37 -0
  134. data/gems/server/lib/itsi/server/config/middleware/csp.rb +44 -0
  135. data/gems/server/lib/itsi/server/config/middleware/deny_list.md +45 -0
  136. data/gems/server/lib/itsi/server/config/middleware/deny_list.rb +42 -0
  137. data/gems/server/lib/itsi/server/config/middleware/endpoint/_index.md +159 -0
  138. data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.md +186 -0
  139. data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.rb +33 -0
  140. data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.md +12 -0
  141. data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.rb +42 -0
  142. data/gems/server/lib/itsi/server/config/middleware/endpoint/endpoint.rb +99 -0
  143. data/gems/server/lib/itsi/server/config/middleware/endpoint/get.md +12 -0
  144. data/gems/server/lib/itsi/server/config/middleware/endpoint/get.rb +42 -0
  145. data/gems/server/lib/itsi/server/config/middleware/endpoint/http_request.md +44 -0
  146. data/gems/server/lib/itsi/server/config/middleware/endpoint/http_response.md +39 -0
  147. data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.md +12 -0
  148. data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.rb +42 -0
  149. data/gems/server/lib/itsi/server/config/middleware/endpoint/post.md +12 -0
  150. data/gems/server/lib/itsi/server/config/middleware/endpoint/post.rb +42 -0
  151. data/gems/server/lib/itsi/server/config/middleware/endpoint/put.md +12 -0
  152. data/gems/server/lib/itsi/server/config/middleware/endpoint/put.rb +42 -0
  153. data/gems/server/lib/itsi/server/config/middleware/endpoint/schemas.md +122 -0
  154. data/gems/server/lib/itsi/server/config/middleware/error_response.md +61 -0
  155. data/gems/server/lib/itsi/server/config/middleware/error_response.rb +36 -0
  156. data/gems/server/lib/itsi/server/config/middleware/etag.md +59 -0
  157. data/gems/server/lib/itsi/server/config/middleware/etag.rb +27 -0
  158. data/gems/server/lib/itsi/server/config/middleware/grpc.md +172 -0
  159. data/gems/server/lib/itsi/server/config/middleware/grpc.rb +54 -0
  160. data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.md +124 -0
  161. data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.rb +61 -0
  162. data/gems/server/lib/itsi/server/config/middleware/location.md +107 -0
  163. data/gems/server/lib/itsi/server/config/middleware/location.rb +99 -0
  164. data/gems/server/lib/itsi/server/config/middleware/log_requests.md +13 -11
  165. data/gems/server/lib/itsi/server/config/middleware/log_requests.rb +1 -3
  166. data/gems/server/lib/itsi/server/config/middleware/max_body.md +18 -0
  167. data/gems/server/lib/itsi/server/config/middleware/max_body.rb +21 -0
  168. data/gems/server/lib/itsi/server/config/middleware/proxy.md +62 -0
  169. data/gems/server/lib/itsi/server/config/middleware/proxy.rb +41 -0
  170. data/gems/server/lib/itsi/server/config/middleware/rackup_file.md +54 -0
  171. data/gems/server/lib/itsi/server/config/middleware/rackup_file.rb +44 -0
  172. data/gems/server/lib/itsi/server/config/middleware/rate_limit.md +126 -0
  173. data/gems/server/lib/itsi/server/config/middleware/rate_limit.rb +34 -0
  174. data/gems/server/lib/itsi/server/config/middleware/rate_limit_store.rb +25 -0
  175. data/gems/server/lib/itsi/server/config/middleware/redirect.md +55 -0
  176. data/gems/server/lib/itsi/server/config/middleware/redirect.rb +25 -0
  177. data/gems/server/lib/itsi/server/config/middleware/request_headers.md +34 -0
  178. data/gems/server/lib/itsi/server/config/middleware/request_headers.rb +24 -0
  179. data/gems/server/lib/itsi/server/config/middleware/response_headers.md +33 -0
  180. data/gems/server/lib/itsi/server/config/middleware/response_headers.rb +25 -0
  181. data/gems/server/lib/itsi/server/config/middleware/run.md +60 -0
  182. data/gems/server/lib/itsi/server/config/middleware/run.rb +43 -0
  183. data/gems/server/lib/itsi/server/config/middleware/static_assets.md +73 -0
  184. data/gems/server/lib/itsi/server/config/middleware/static_assets.rb +87 -0
  185. data/gems/server/lib/itsi/server/config/middleware/static_response.md +44 -0
  186. data/gems/server/lib/itsi/server/config/middleware/static_response.rb +29 -0
  187. data/gems/server/lib/itsi/server/config/middleware/string_rewrite.md +67 -0
  188. data/gems/server/lib/itsi/server/config/middleware/token_source.rb +32 -0
  189. data/gems/server/lib/itsi/server/config/middleware.rb +4 -0
  190. data/gems/server/lib/itsi/server/config/option.rb +5 -0
  191. data/gems/server/lib/itsi/server/config/options/_index.md +3 -2
  192. data/gems/server/lib/itsi/server/config/options/auto_reload_config.md +13 -0
  193. data/gems/server/lib/itsi/server/config/options/auto_reload_config.rb +41 -0
  194. data/gems/server/lib/itsi/server/config/options/bind.md +71 -0
  195. data/gems/server/lib/itsi/server/config/options/bind.rb +26 -0
  196. data/gems/server/lib/itsi/server/config/options/certificates.md +65 -0
  197. data/gems/server/lib/itsi/server/config/options/daemonize.md +14 -0
  198. data/gems/server/lib/itsi/server/config/options/daemonize.rb +19 -0
  199. data/gems/server/lib/itsi/server/config/options/fiber_scheduler.md +1 -2
  200. data/gems/server/lib/itsi/server/config/options/fiber_scheduler.rb +6 -3
  201. data/gems/server/lib/itsi/server/config/options/header_read_timeout.md +17 -0
  202. data/gems/server/lib/itsi/server/config/options/header_read_timeout.rb +19 -0
  203. data/gems/server/lib/itsi/server/config/options/hooks/_index.md +11 -0
  204. data/gems/server/lib/itsi/server/config/options/hooks/after_fork.md +13 -0
  205. data/gems/server/lib/itsi/server/config/options/hooks/after_fork.rb +28 -0
  206. data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.md +14 -0
  207. data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.rb +28 -0
  208. data/gems/server/lib/itsi/server/config/options/hooks/after_start.md +12 -0
  209. data/gems/server/lib/itsi/server/config/options/hooks/after_start.rb +28 -0
  210. data/gems/server/lib/itsi/server/config/options/hooks/before_fork.md +13 -0
  211. data/gems/server/lib/itsi/server/config/options/hooks/before_fork.rb +28 -0
  212. data/gems/server/lib/itsi/server/config/options/hooks/before_restart.md +12 -0
  213. data/gems/server/lib/itsi/server/config/options/hooks/before_restart.rb +28 -0
  214. data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.md +12 -0
  215. data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.rb +28 -0
  216. data/gems/server/lib/itsi/server/config/options/include.md +20 -0
  217. data/gems/server/lib/itsi/server/config/options/include.rb +36 -0
  218. data/gems/server/lib/itsi/server/config/options/listen_backlog.md +11 -0
  219. data/gems/server/lib/itsi/server/config/options/listen_backlog.rb +19 -0
  220. data/gems/server/lib/itsi/server/config/options/log_format.md +18 -0
  221. data/gems/server/lib/itsi/server/config/options/log_format.rb +19 -0
  222. data/gems/server/lib/itsi/server/config/options/log_level.md +34 -0
  223. data/gems/server/lib/itsi/server/config/options/log_level.rb +20 -0
  224. data/gems/server/lib/itsi/server/config/options/log_target.md +38 -0
  225. data/gems/server/lib/itsi/server/config/options/log_target.rb +19 -0
  226. data/gems/server/lib/itsi/server/config/options/log_target_filters.md +17 -0
  227. data/gems/server/lib/itsi/server/config/options/log_target_filters.rb +19 -0
  228. data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.md +27 -0
  229. data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.rb +24 -0
  230. data/gems/server/lib/itsi/server/config/options/nodelay.md +16 -0
  231. data/gems/server/lib/itsi/server/config/options/nodelay.rb +19 -0
  232. data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.md +19 -0
  233. data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.rb +18 -0
  234. data/gems/server/lib/itsi/server/config/options/pin_worker_cores.md +17 -0
  235. data/gems/server/lib/itsi/server/config/options/pin_worker_cores.rb +19 -0
  236. data/gems/server/lib/itsi/server/config/options/preload.md +21 -0
  237. data/gems/server/lib/itsi/server/config/options/preload.rb +18 -0
  238. data/gems/server/lib/itsi/server/config/options/recv_buffer_size.md +15 -0
  239. data/gems/server/lib/itsi/server/config/options/recv_buffer_size.rb +19 -0
  240. data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.md +21 -0
  241. data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.rb +30 -0
  242. data/gems/server/lib/itsi/server/config/options/request_timeout.md +23 -0
  243. data/gems/server/lib/itsi/server/config/options/request_timeout.rb +19 -0
  244. data/gems/server/lib/itsi/server/config/options/reuse_address.md +16 -0
  245. data/gems/server/lib/itsi/server/config/options/reuse_address.rb +19 -0
  246. data/gems/server/lib/itsi/server/config/options/reuse_port.md +16 -0
  247. data/gems/server/lib/itsi/server/config/options/reuse_port.rb +19 -0
  248. data/gems/server/lib/itsi/server/config/options/scheduler_threads.md +34 -0
  249. data/gems/server/lib/itsi/server/config/options/scheduler_threads.rb +17 -0
  250. data/gems/server/lib/itsi/server/config/options/shutdown_timeout.md +17 -0
  251. data/gems/server/lib/itsi/server/config/options/shutdown_timeout.rb +19 -0
  252. data/gems/server/lib/itsi/server/config/options/stream_body.md +32 -0
  253. data/gems/server/lib/itsi/server/config/options/stream_body.rb +18 -0
  254. data/gems/server/lib/itsi/server/config/options/threads.md +7 -2
  255. data/gems/server/lib/itsi/server/config/options/threads.rb +1 -1
  256. data/gems/server/lib/itsi/server/config/options/watch.md +16 -0
  257. data/gems/server/lib/itsi/server/config/options/watch.rb +28 -0
  258. data/gems/server/lib/itsi/server/config/options/worker_memory_limit.md +22 -0
  259. data/gems/server/lib/itsi/server/config/options/worker_memory_limit.rb +18 -0
  260. data/gems/server/lib/itsi/server/config/options/workers.md +1 -2
  261. data/gems/server/lib/itsi/server/config/options/workers.rb +1 -1
  262. data/gems/server/lib/itsi/server/config/typed_struct.rb +59 -20
  263. data/gems/server/lib/itsi/server/config.rb +77 -48
  264. data/gems/server/lib/itsi/server/default_config/Itsi.rb +3 -3
  265. data/gems/server/lib/itsi/server/grpc/grpc_call.rb +1 -1
  266. data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +11 -4
  267. data/gems/server/lib/itsi/server/rack/handler/itsi.rb +3 -3
  268. data/gems/server/lib/itsi/server/route_tester.rb +58 -8
  269. data/gems/server/lib/itsi/server/signal_trap.rb +1 -1
  270. data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +14 -18
  271. data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +5 -4
  272. data/gems/server/lib/itsi/server/typed_handlers.rb +12 -4
  273. data/gems/server/lib/itsi/server/version.rb +1 -1
  274. data/gems/server/lib/itsi/server.rb +98 -14
  275. data/gems/server/lib/ruby_lsp/itsi/addon.rb +20 -18
  276. data/gems/server/test/helpers/test_helper.rb +89 -29
  277. data/gems/server/test/middleware/allow_list.rb +128 -0
  278. data/gems/server/test/middleware/auth_api_key.rb +141 -0
  279. data/gems/server/test/middleware/auth_basic.rb +91 -0
  280. data/gems/server/test/middleware/auth_jwt.rb +214 -0
  281. data/gems/server/test/middleware/cache_control.rb +82 -0
  282. data/gems/server/test/middleware/cidr_to_regex.rb +46 -0
  283. data/gems/server/test/middleware/compression.rb +89 -0
  284. data/gems/server/test/middleware/cors.rb +113 -0
  285. data/gems/server/test/middleware/csp.rb +62 -0
  286. data/gems/server/test/middleware/deny_list.rb +131 -0
  287. data/gems/server/test/middleware/endpoint.rb +300 -0
  288. data/gems/server/test/middleware/etag.rb +75 -0
  289. data/gems/server/test/middleware/grpc/grpc.rb +158 -0
  290. data/gems/server/test/middleware/grpc/test_service.proto +32 -0
  291. data/gems/server/test/middleware/grpc/test_service_impl.rb +28 -0
  292. data/gems/server/test/middleware/grpc/test_service_pb.rb +18 -0
  293. data/gems/server/test/middleware/grpc/test_service_services_pb.rb +30 -0
  294. data/gems/server/test/middleware/header_interpolation.rb +35 -0
  295. data/gems/server/test/middleware/intrusion_protection.rb +259 -0
  296. data/gems/server/test/middleware/location.rb +220 -0
  297. data/gems/server/test/middleware/max_body.rb +20 -0
  298. data/gems/server/test/middleware/proxy.rb +415 -0
  299. data/gems/server/test/middleware/rate_limit.rb +211 -0
  300. data/gems/server/test/middleware/redirect.rb +85 -0
  301. data/gems/server/test/middleware/request_headers.rb +50 -0
  302. data/gems/server/test/middleware/response_headers.rb +50 -0
  303. data/gems/server/test/middleware/static_assets.rb +374 -0
  304. data/gems/server/test/middleware/static_response.rb +56 -0
  305. data/gems/server/test/middleware/string_rewrite.rb +112 -0
  306. data/gems/server/test/options/bind.rb +47 -0
  307. data/gems/server/test/options/header_read_timeout.rb +23 -0
  308. data/gems/server/test/options/test_request_timeout.rb +16 -0
  309. data/gems/server/test/options/test_workers.rb +2 -4
  310. data/gems/server/test/{test_itsi_server.rb → rack/test_rack_server.rb} +2 -2
  311. data/grpc_test/Itsi.rb +11 -0
  312. data/grpc_test/echo.proto +14 -0
  313. data/grpc_test/echo_pb.rb +16 -0
  314. data/grpc_test/echo_service_impl.rb +8 -0
  315. data/grpc_test/echo_services_pb.rb +22 -0
  316. data/lib/itsi/version.rb +1 -1
  317. data/tasks.txt +15 -72
  318. metadata +210 -7
  319. data/gems/server/lib/itsi/server/default_config/Itsi-rackup.rb +0 -119
@@ -15,6 +15,7 @@ use std::sync::Arc;
15
15
  use std::{path::PathBuf, sync::OnceLock};
16
16
  use tokio::sync::Mutex;
17
17
  use tokio::time::{self, Duration};
18
+ use tracing::debug;
18
19
 
19
20
  #[derive(Debug, Serialize, Deserialize)]
20
21
  pub struct CspReport {
@@ -46,11 +47,11 @@ pub struct CspConfig {
46
47
 
47
48
  #[derive(Debug, Deserialize)]
48
49
  pub struct Csp {
49
- pub policy_input: Option<CspConfig>,
50
+ pub policy: Option<CspConfig>,
50
51
  pub reporting_enabled: bool,
51
52
  pub report_file: Option<PathBuf>,
52
53
  pub report_endpoint: String,
53
- pub flush_interval: u64,
54
+ pub flush_interval: f64,
54
55
 
55
56
  #[serde(skip)]
56
57
  pub computed_policy: OnceLock<String>,
@@ -63,7 +64,7 @@ pub struct Csp {
63
64
  #[async_trait]
64
65
  impl super::MiddlewareLayer for Csp {
65
66
  async fn initialize(&self) -> Result<(), magnus::error::Error> {
66
- if let Some(policy_config) = &self.policy_input {
67
+ if let Some(policy_config) = &self.policy {
67
68
  let mut parts = Vec::new();
68
69
  if !policy_config.default_src.is_empty() {
69
70
  parts.push(format!(
@@ -81,6 +82,7 @@ impl super::MiddlewareLayer for Csp {
81
82
  parts.push(format!("report-uri {}", policy_config.report_uri.join(" ")));
82
83
  }
83
84
  let policy = parts.join("; ");
85
+ debug!(target: "middleware::csp", "Computed CSP policy: {}", policy);
84
86
  self.computed_policy
85
87
  .set(policy)
86
88
  .map_err(|_| ItsiError::new("Failed to set computed CSP policy"))?;
@@ -92,7 +94,7 @@ impl super::MiddlewareLayer for Csp {
92
94
  let report_path = report_file.clone();
93
95
  let pending_reports = Arc::clone(&self.pending_reports);
94
96
  let handle = tokio::spawn(async move {
95
- let mut interval = time::interval(Duration::from_secs(flush_interval));
97
+ let mut interval = time::interval(Duration::from_secs_f64(flush_interval));
96
98
  loop {
97
99
  interval.tick().await;
98
100
 
@@ -106,18 +108,25 @@ impl super::MiddlewareLayer for Csp {
106
108
  }
107
109
  }
108
110
  reports.clear();
109
- if let Err(e) = tokio::fs::OpenOptions::new()
111
+
112
+ debug!("Flushing CSP report to file {:?}", &report_path.display());
113
+
114
+ use tokio::io::AsyncWriteExt;
115
+
116
+ match tokio::fs::OpenOptions::new()
110
117
  .append(true)
111
118
  .create(true)
112
119
  .open(&report_path)
113
120
  .await
114
- .map(|mut file| async move {
115
- use tokio::io::AsyncWriteExt;
116
- file.write_all(lines.as_bytes()).await
117
- })
118
- .map_err(ItsiError::new)
119
121
  {
120
- eprintln!("Error writing CSP reports: {:?}", e);
122
+ Ok(mut file) => {
123
+ if let Err(e) = file.write_all(lines.as_bytes()).await {
124
+ eprintln!("Error writing CSP reports: {:?}", e);
125
+ }
126
+ }
127
+ Err(e) => {
128
+ eprintln!("Error opening CSP report file: {:?}", e);
129
+ }
121
130
  }
122
131
  }
123
132
  }
@@ -136,6 +145,7 @@ impl super::MiddlewareLayer for Csp {
136
145
  _context: &mut HttpRequestContext,
137
146
  ) -> Result<Either<HttpRequest, HttpResponse>, magnus::error::Error> {
138
147
  if self.reporting_enabled && req.uri().path() == self.report_endpoint {
148
+ debug!(target: "middleware::csp", "Received CSP report");
139
149
  let full_bytes: Result<Bytes, _> = req
140
150
  .into_body()
141
151
  .into_data_stream()
@@ -148,6 +158,7 @@ impl super::MiddlewareLayer for Csp {
148
158
 
149
159
  if let Ok(body_bytes) = full_bytes {
150
160
  if let Ok(report) = serde_json::from_slice::<CspReport>(&body_bytes) {
161
+ debug!(target: "middleware::csp", "Report: {:?}", report);
151
162
  let mut pending = self.pending_reports.lock().await;
152
163
  pending.push(report);
153
164
  }
@@ -163,6 +174,7 @@ impl super::MiddlewareLayer for Csp {
163
174
  async fn after(&self, resp: HttpResponse, _context: &mut HttpRequestContext) -> HttpResponse {
164
175
  if let Some(policy) = self.computed_policy.get() {
165
176
  if !resp.headers().contains_key("Content-Security-Policy") {
177
+ debug!(target: "middleware::csp", "Adding CSP header");
166
178
  let (mut parts, body) = resp.into_parts();
167
179
  if let Ok(header_value) = HeaderValue::from_str(policy) {
168
180
  parts
@@ -170,6 +182,8 @@ impl super::MiddlewareLayer for Csp {
170
182
  .insert("Content-Security-Policy", header_value);
171
183
  }
172
184
  return HttpResponse::from_parts(parts, body);
185
+ } else {
186
+ debug!(target: "middleware::csp", "CSP header already present");
173
187
  }
174
188
  }
175
189
  resp
@@ -3,20 +3,22 @@ use crate::{
3
3
  services::itsi_http_service::HttpRequestContext,
4
4
  };
5
5
 
6
- use super::{ErrorResponse, FromValue, MiddlewareLayer};
6
+ use super::{token_source::TokenSource, ErrorResponse, FromValue, MiddlewareLayer};
7
7
  use async_trait::async_trait;
8
8
  use either::Either;
9
9
  use itsi_error::ItsiError;
10
10
  use magnus::error::Result;
11
11
  use regex::RegexSet;
12
12
  use serde::Deserialize;
13
- use std::sync::OnceLock;
13
+ use std::{collections::HashMap, sync::OnceLock};
14
+ use tracing::debug;
14
15
 
15
16
  #[derive(Debug, Clone, Deserialize)]
16
17
  pub struct DenyList {
17
18
  #[serde(skip_deserializing)]
18
19
  pub denied_ips: OnceLock<RegexSet>,
19
20
  pub denied_patterns: Vec<String>,
21
+ pub trusted_proxies: HashMap<String, TokenSource>,
20
22
  #[serde(default = "forbidden_error_response")]
21
23
  pub error_response: ErrorResponse,
22
24
  }
@@ -40,8 +42,15 @@ impl MiddlewareLayer for DenyList {
40
42
  req: HttpRequest,
41
43
  context: &mut HttpRequestContext,
42
44
  ) -> Result<Either<HttpRequest, HttpResponse>> {
45
+ let addr = if self.trusted_proxies.contains_key(&context.addr) {
46
+ let source = self.trusted_proxies.get(&context.addr).unwrap();
47
+ source.extract_token(&req).unwrap_or(&context.addr)
48
+ } else {
49
+ &context.addr
50
+ };
43
51
  if let Some(denied_ips) = self.denied_ips.get() {
44
- if denied_ips.is_match(&context.addr) {
52
+ if denied_ips.is_match(addr) {
53
+ debug!(target: "middleware::deny_list", "IP address {} is not allowed", addr);
45
54
  return Ok(Either::Right(
46
55
  self.error_response
47
56
  .to_http_response(req.accept().into())
@@ -1,86 +1,85 @@
1
- use std::convert::Infallible;
2
-
1
+ use super::{ContentSource, DefaultFormat, ErrorResponse};
2
+ use crate::server::http_message_types::ResponseFormat;
3
3
  use bytes::Bytes;
4
4
  use http_body_util::{combinators::BoxBody, Full};
5
-
6
- use crate::server::http_message_types::ResponseFormat;
7
-
8
- use super::{ContentSource, DefaultFormat, ErrorResponse};
5
+ use std::{convert::Infallible, sync::Arc};
9
6
 
10
7
  impl DefaultFormat {
11
8
  pub fn response_for_code(&self, code: u16) -> ContentSource {
12
9
  match self {
13
10
  DefaultFormat::Plaintext => match code {
14
- 500 => ContentSource::Inline("500 Internal Error".to_owned()),
15
- 404 => ContentSource::Inline("404 Not Found".to_owned()),
16
- 401 => ContentSource::Inline("401 Unauthorized".to_owned()),
17
- 403 => ContentSource::Inline("403 Forbidden".to_owned()),
18
- 413 => ContentSource::Inline("413 Payload Too Large".to_owned()),
19
- 429 => ContentSource::Inline("429 Too Many Requests".to_owned()),
20
- 502 => ContentSource::Inline("502 Bad Gateway".to_owned()),
21
- 503 => ContentSource::Inline("503 Service Unavailable".to_owned()),
22
- 504 => ContentSource::Inline("504 Gateway Timeout".to_owned()),
23
- _ => ContentSource::Inline("Unexpected Error".to_owned()),
11
+ 500 => ContentSource::Static(Arc::new("500 Internal Error".into())),
12
+ 404 => ContentSource::Static(Arc::new("404 Not Found".into())),
13
+ 401 => ContentSource::Static(Arc::new("401 Unauthorized".into())),
14
+ 403 => ContentSource::Static(Arc::new("403 Forbidden".into())),
15
+ 413 => ContentSource::Static(Arc::new("413 Payload Too Large".into())),
16
+ 429 => ContentSource::Static(Arc::new("429 Too Many Requests".into())),
17
+ 502 => ContentSource::Static(Arc::new("502 Bad Gateway".into())),
18
+ 503 => ContentSource::Static(Arc::new("503 Service Unavailable".into())),
19
+ 504 => ContentSource::Static(Arc::new("504 Gateway Timeout".into())),
20
+ _ => ContentSource::Static(Arc::new("Unexpected Error".into())),
24
21
  },
25
22
  DefaultFormat::Html => match code {
26
- 500 => ContentSource::Inline(
27
- include_str!("../../../../default_responses/html/500.html").to_owned(),
28
- ),
29
- 404 => ContentSource::Inline(
30
- include_str!("../../../../default_responses/html/404.html").to_owned(),
31
- ),
32
- 401 => ContentSource::Inline(
33
- include_str!("../../../../default_responses/html/401.html").to_owned(),
34
- ),
35
- 403 => ContentSource::Inline(
36
- include_str!("../../../../default_responses/html/403.html").to_owned(),
37
- ),
38
- 413 => ContentSource::Inline(
39
- include_str!("../../../../default_responses/html/413.html").to_owned(),
40
- ),
41
- 429 => ContentSource::Inline(
42
- include_str!("../../../../default_responses/html/429.html").to_owned(),
43
- ),
44
- 502 => ContentSource::Inline(
45
- include_str!("../../../../default_responses/html/502.html").to_owned(),
46
- ),
47
- 503 => ContentSource::Inline(
48
- include_str!("../../../../default_responses/html/503.html").to_owned(),
49
- ),
50
- 504 => ContentSource::Inline(
51
- include_str!("../../../../default_responses/html/504.html").to_owned(),
52
- ),
53
- _ => ContentSource::Inline("Unexpected Error".to_owned()),
23
+ 500 => ContentSource::Static(Arc::new(
24
+ include_str!("../../../../default_responses/html/500.html").into(),
25
+ )),
26
+ 404 => ContentSource::Static(Arc::new(
27
+ include_str!("../../../../default_responses/html/404.html").into(),
28
+ )),
29
+ 401 => ContentSource::Static(Arc::new(
30
+ include_str!("../../../../default_responses/html/401.html").into(),
31
+ )),
32
+ 403 => ContentSource::Static(Arc::new(
33
+ include_str!("../../../../default_responses/html/403.html").into(),
34
+ )),
35
+ 413 => ContentSource::Static(Arc::new(
36
+ include_str!("../../../../default_responses/html/413.html").into(),
37
+ )),
38
+ 429 => ContentSource::Static(Arc::new(
39
+ include_str!("../../../../default_responses/html/429.html").into(),
40
+ )),
41
+ 502 => ContentSource::Static(Arc::new(
42
+ include_str!("../../../../default_responses/html/502.html").into(),
43
+ )),
44
+ 503 => ContentSource::Static(Arc::new(
45
+ include_str!("../../../../default_responses/html/503.html").into(),
46
+ )),
47
+ 504 => ContentSource::Static(Arc::new(
48
+ include_str!("../../../../default_responses/html/504.html").into(),
49
+ )),
50
+ _ => ContentSource::Static(Arc::new(
51
+ include_str!("../../../../default_responses/html/500.html").into(),
52
+ )),
54
53
  },
55
54
  DefaultFormat::Json => match code {
56
- 500 => ContentSource::Inline(
57
- include_str!("../../../../default_responses/json/500.json").to_owned(),
58
- ),
59
- 404 => ContentSource::Inline(
60
- include_str!("../../../../default_responses/json/404.json").to_owned(),
61
- ),
62
- 401 => ContentSource::Inline(
63
- include_str!("../../../../default_responses/json/401.json").to_owned(),
64
- ),
65
- 403 => ContentSource::Inline(
66
- include_str!("../../../../default_responses/json/403.json").to_owned(),
67
- ),
68
- 413 => ContentSource::Inline(
69
- include_str!("../../../../default_responses/json/413.json").to_owned(),
70
- ),
71
- 429 => ContentSource::Inline(
72
- include_str!("../../../../default_responses/json/429.json").to_owned(),
73
- ),
74
- 502 => ContentSource::Inline(
75
- include_str!("../../../../default_responses/json/502.json").to_owned(),
76
- ),
77
- 503 => ContentSource::Inline(
78
- include_str!("../../../../default_responses/json/503.json").to_owned(),
79
- ),
80
- 504 => ContentSource::Inline(
81
- include_str!("../../../../default_responses/json/504.json").to_owned(),
82
- ),
83
- _ => ContentSource::Inline("Unexpected Error".to_owned()),
55
+ 500 => ContentSource::Static(Arc::new(
56
+ include_str!("../../../../default_responses/json/500.json").into(),
57
+ )),
58
+ 404 => ContentSource::Static(Arc::new(
59
+ include_str!("../../../../default_responses/json/404.json").into(),
60
+ )),
61
+ 401 => ContentSource::Static(Arc::new(
62
+ include_str!("../../../../default_responses/json/401.json").into(),
63
+ )),
64
+ 403 => ContentSource::Static(Arc::new(
65
+ include_str!("../../../../default_responses/json/403.json").into(),
66
+ )),
67
+ 413 => ContentSource::Static(Arc::new(
68
+ include_str!("../../../../default_responses/json/413.json").into(),
69
+ )),
70
+ 429 => ContentSource::Static(Arc::new(
71
+ include_str!("../../../../default_responses/json/429.json").into(),
72
+ )),
73
+ 502 => ContentSource::Static(Arc::new(
74
+ include_str!("../../../../default_responses/json/502.json").into(),
75
+ )),
76
+ 503 => ContentSource::Static(Arc::new(
77
+ include_str!("../../../../default_responses/json/503.json").into(),
78
+ )),
79
+ 504 => ContentSource::Static(Arc::new(
80
+ include_str!("../../../../default_responses/json/504.json").into(),
81
+ )),
82
+ _ => ContentSource::Static(Arc::new("Unexpected Error".into())),
84
83
  },
85
84
  }
86
85
  }
@@ -95,6 +94,9 @@ impl ErrorResponse {
95
94
  };
96
95
  match source {
97
96
  ContentSource::Inline(bytes) => BoxBody::new(Full::new(Bytes::from(bytes))),
97
+ ContentSource::Static(text) => {
98
+ BoxBody::new(Full::new(Bytes::from(String::from(text.as_str()))))
99
+ }
98
100
  ContentSource::File(_) => BoxBody::new(Full::new(Bytes::from("Unexpected error"))),
99
101
  }
100
102
  }
@@ -5,6 +5,8 @@ use http_body_util::{combinators::BoxBody, Full};
5
5
  use serde::{Deserialize, Deserializer};
6
6
  use std::convert::Infallible;
7
7
  use std::path::PathBuf;
8
+ use std::sync::Arc;
9
+ use tracing::warn;
8
10
 
9
11
  use crate::server::http_message_types::{HttpResponse, ResponseFormat};
10
12
  use crate::services::static_file_server::ROOT_STATIC_FILE_SERVER;
@@ -16,6 +18,9 @@ pub enum ContentSource {
16
18
  Inline(String),
17
19
  #[serde(rename(deserialize = "file"))]
18
20
  File(PathBuf),
21
+ #[serde(rename(deserialize = "static"))]
22
+ #[serde(skip_deserializing)]
23
+ Static(Arc<String>),
19
24
  }
20
25
 
21
26
  #[derive(Debug, Clone, Deserialize, Default)]
@@ -90,7 +95,13 @@ impl From<ErrorResponseDef> for ErrorResponse {
90
95
  "bad_gateway" => ErrorResponse::bad_gateway(),
91
96
  "service_unavailable" => ErrorResponse::service_unavailable(),
92
97
  "gateway_timeout" => ErrorResponse::gateway_timeout(),
93
- _ => panic!("Unknown error response name: {}", name),
98
+ _ => {
99
+ warn!(
100
+ "Unknown error response name: {}. Using internal server error.",
101
+ name
102
+ );
103
+ ErrorResponse::internal_server_error()
104
+ }
94
105
  },
95
106
  }
96
107
  }
@@ -139,6 +150,9 @@ impl ErrorResponse {
139
150
  Some(ContentSource::Inline(text)) => {
140
151
  return BoxBody::new(Full::new(Bytes::from(text.clone())));
141
152
  }
153
+ Some(ContentSource::Static(text)) => {
154
+ return BoxBody::new(Full::new(Bytes::from(String::from(text.as_str()))));
155
+ }
142
156
  Some(ContentSource::File(path)) => {
143
157
  // Convert the PathBuf to a &str (assumes valid UTF-8).
144
158
  if let Some(path_str) = path.to_str() {
@@ -15,6 +15,7 @@ use hyper::body::Body;
15
15
  use magnus::error::Result;
16
16
  use serde::Deserialize;
17
17
  use sha2::{Digest, Sha256};
18
+ use tracing::debug;
18
19
 
19
20
  #[derive(Debug, Clone, Copy, Deserialize, Default)]
20
21
  pub enum ETagType {
@@ -60,6 +61,7 @@ impl MiddlewareLayer for ETag {
60
61
  // Store if-none-match header in context if present for later use in after hook
61
62
  if self.handle_if_none_match {
62
63
  if let Some(if_none_match) = req.headers().get(header::IF_NONE_MATCH) {
64
+ debug!(target: "middleware::etag", "Received If-None-Match header: {:?}", if_none_match);
63
65
  if let Ok(etag_value) = if_none_match.to_str() {
64
66
  context.set_if_none_match(Some(etag_value.to_string()));
65
67
  }
@@ -77,33 +79,35 @@ impl MiddlewareLayer for ETag {
77
79
  | StatusCode::NON_AUTHORITATIVE_INFORMATION
78
80
  | StatusCode::NO_CONTENT
79
81
  | StatusCode::PARTIAL_CONTENT => {}
80
- _ => return resp,
82
+ _ => {
83
+ debug!(target: "middleware::etag", "Skipping ETag middleware for ineligible response");
84
+ return resp;
85
+ }
81
86
  }
82
87
 
83
- // Skip if already has an ETag
84
88
  if resp.headers().contains_key(header::ETAG) {
89
+ debug!(target: "middleware::etag", "Forwarding response with existing ETag");
85
90
  return resp;
86
91
  }
87
92
 
88
- // Skip if Cache-Control: no-store is present
89
93
  if let Some(cache_control) = resp.headers().get(header::CACHE_CONTROL) {
90
94
  if let Ok(cache_control_str) = cache_control.to_str() {
91
95
  if cache_control_str.contains("no-store") {
96
+ debug!(target: "middleware::etag", "Skipping ETag for no-store response");
92
97
  return resp;
93
98
  }
94
99
  }
95
100
  }
96
101
 
97
- // Check if body is a stream or fixed size using size_hint (similar to compression.rs)
98
102
  let body_size = resp.size_hint().exact();
99
103
 
100
- // Skip streaming bodies
101
104
  if body_size.is_none() {
105
+ debug!(target: "middleware::etag", "Skipping ETag for streaming response");
102
106
  return resp;
103
107
  }
104
108
 
105
- // Skip if body is too small
106
109
  if body_size.unwrap_or(0) < self.min_body_size as u64 {
110
+ debug!(target: "middleware::etag", "Skipping ETag for small response");
107
111
  return resp;
108
112
  }
109
113
 
@@ -142,6 +146,7 @@ impl MiddlewareLayer for ETag {
142
146
  ETagType::Weak => format!("W/\"{}\"", computed_etag),
143
147
  };
144
148
 
149
+ debug!(target: "middleware::etag", "Computed ETag for response {}", formatted_etag);
145
150
  if let Ok(value) = HeaderValue::from_str(&formatted_etag) {
146
151
  parts.headers.insert(header::ETAG, value);
147
152
  }
@@ -150,7 +155,6 @@ impl MiddlewareLayer for ETag {
150
155
  formatted_etag
151
156
  };
152
157
 
153
- // Handle 304 Not Modified if we have an If-None-Match header and it matches
154
158
  if self.handle_if_none_match {
155
159
  if let Some(if_none_match) = context.get_if_none_match() {
156
160
  if if_none_match == etag_value || if_none_match == "*" {
@@ -176,7 +180,6 @@ impl MiddlewareLayer for ETag {
176
180
  }
177
181
  }
178
182
 
179
- // Recreate response with the original body and the ETag header
180
183
  Response::from_parts(parts, body)
181
184
  }
182
185
  }
@@ -4,6 +4,7 @@ use crate::services::rate_limiter::{
4
4
  get_ban_manager, get_rate_limiter, BanManager, RateLimiter, RateLimiterConfig,
5
5
  };
6
6
 
7
+ use super::token_source::TokenSource;
7
8
  use super::{ErrorResponse, FromValue, MiddlewareLayer};
8
9
 
9
10
  use async_trait::async_trait;
@@ -28,12 +29,13 @@ pub struct IntrusionProtection {
28
29
  pub banned_header_pattern_matchers: OnceLock<HashMap<String, RegexSet>>,
29
30
  #[serde(default)]
30
31
  pub banned_header_patterns: HashMap<String, Vec<String>>,
31
- pub banned_time_seconds: u64,
32
+ pub banned_time_seconds: f64,
32
33
  #[serde(skip_deserializing)]
33
34
  pub rate_limiter: OnceLock<Arc<dyn RateLimiter>>,
34
35
  #[serde(skip_deserializing)]
35
36
  pub ban_manager: OnceLock<BanManager>,
36
37
  pub store_config: RateLimiterConfig,
38
+ pub trusted_proxies: HashMap<String, TokenSource>,
37
39
  #[serde(default = "forbidden_error_response")]
38
40
  pub error_response: ErrorResponse,
39
41
  }
@@ -49,6 +51,7 @@ impl MiddlewareLayer for IntrusionProtection {
49
51
  if !self.banned_url_patterns.is_empty() {
50
52
  match RegexSet::new(&self.banned_url_patterns) {
51
53
  Ok(regex_set) => {
54
+ debug!(target: "middleware::intrusion_protection", "Compiled URL regex patterns: {} items.", regex_set.len());
52
55
  let _ = self.banned_url_pattern_matcher.set(regex_set);
53
56
  }
54
57
  Err(e) => {
@@ -64,6 +67,7 @@ impl MiddlewareLayer for IntrusionProtection {
64
67
  if !patterns.is_empty() {
65
68
  match RegexSet::new(patterns) {
66
69
  Ok(regex_set) => {
70
+ debug!(target: "middleware::intrusion_protection", "Compiled header regex patterns for {}: {} items.", header_name, regex_set.len());
67
71
  header_matchers.insert(header_name.clone(), regex_set);
68
72
  }
69
73
  Err(e) => {
@@ -81,12 +85,14 @@ impl MiddlewareLayer for IntrusionProtection {
81
85
  // Initialize rate limiter (used for tracking bans)
82
86
  // This will automatically fall back to in-memory if Redis fails
83
87
  if let Ok(limiter) = get_rate_limiter(&self.store_config).await {
88
+ debug!(target: "middleware::intrusion_protection", "Initialized rate limiter.");
84
89
  let _ = self.rate_limiter.set(limiter);
85
90
  }
86
91
 
87
92
  // Initialize ban manager
88
93
  // This will automatically fall back to in-memory if Redis fails
89
94
  if let Ok(manager) = get_ban_manager(&self.store_config).await {
95
+ debug!(target: "middleware::intrusion_protection", "Initialized ban manager.");
90
96
  let _ = self.ban_manager.set(manager);
91
97
  }
92
98
 
@@ -99,12 +105,18 @@ impl MiddlewareLayer for IntrusionProtection {
99
105
  context: &mut HttpRequestContext,
100
106
  ) -> Result<Either<HttpRequest, HttpResponse>> {
101
107
  // Get client IP address from context's service
102
- let client_ip = &context.addr;
108
+ let client_ip = if self.trusted_proxies.contains_key(&context.addr) {
109
+ let source = self.trusted_proxies.get(&context.addr).unwrap();
110
+ source.extract_token(&req).unwrap_or(&context.addr)
111
+ } else {
112
+ &context.addr
113
+ };
103
114
 
104
115
  // Check if the IP is already banned
105
116
  if let Some(ban_manager) = self.ban_manager.get() {
106
117
  match ban_manager.is_banned(client_ip).await {
107
118
  Ok(Some(_)) => {
119
+ debug!(target: "middleware::intrusion_protection", "IP {} is banned.", client_ip);
108
120
  return Ok(Either::Right(
109
121
  self.error_response
110
122
  .to_http_response(req.accept().into())
@@ -115,9 +127,7 @@ impl MiddlewareLayer for IntrusionProtection {
115
127
  error!("Error checking IP ban status: {:?}", e);
116
128
  // Continue processing - fail open
117
129
  }
118
- _ => {
119
- // Not banned, continue with intrusion checks
120
- }
130
+ _ => {}
121
131
  }
122
132
  } else {
123
133
  warn!("No ban manager available for intrusion protection");
@@ -128,12 +138,13 @@ impl MiddlewareLayer for IntrusionProtection {
128
138
  let path = req.uri().path_and_query().map(|p| p.as_str()).unwrap_or("");
129
139
 
130
140
  if url_matcher.is_match(path) {
141
+ debug!(target: "middleware::intrusion_protection", "Banned URL pattern detected: {}", path);
131
142
  if let Some(ban_manager) = self.ban_manager.get() {
132
143
  match ban_manager
133
144
  .ban_ip(
134
145
  client_ip,
135
146
  &format!("Banned URL pattern detected: {}", path),
136
- Duration::from_secs(self.banned_time_seconds),
147
+ Duration::from_secs_f64(self.banned_time_seconds),
137
148
  )
138
149
  .await
139
150
  {
@@ -156,10 +167,7 @@ impl MiddlewareLayer for IntrusionProtection {
156
167
  for (header_name, pattern_set) in header_matchers {
157
168
  if let Some(header_value) = req.header(header_name) {
158
169
  if pattern_set.is_match(header_value) {
159
- info!(
160
- "Intrusion detected: Header pattern match for {} in header {}",
161
- header_value, header_name
162
- );
170
+ debug!(target: "middleware::intrusion_protection", "Banned header pattern detected: {} in {}", header_value, header_name);
163
171
 
164
172
  // Ban the IP address if possible
165
173
  if let Some(ban_manager) = self.ban_manager.get() {
@@ -170,7 +178,7 @@ impl MiddlewareLayer for IntrusionProtection {
170
178
  "Banned header pattern detected: {} in {}",
171
179
  header_value, header_name
172
180
  ),
173
- Duration::from_secs(self.banned_time_seconds),
181
+ Duration::from_secs_f64(self.banned_time_seconds),
174
182
  )
175
183
  .await
176
184
  {
@@ -42,11 +42,11 @@ pub enum LogMiddlewareLevel {
42
42
  impl LogMiddlewareLevel {
43
43
  pub fn log(&self, message: String) {
44
44
  match self {
45
- LogMiddlewareLevel::Trace => trace!(message),
46
- LogMiddlewareLevel::Debug => debug!(message),
47
- LogMiddlewareLevel::Info => info!(message),
48
- LogMiddlewareLevel::Warn => warn!(message),
49
- LogMiddlewareLevel::Error => error!(message),
45
+ LogMiddlewareLevel::Trace => trace!(target: "middleware::log_requests", message),
46
+ LogMiddlewareLevel::Debug => debug!(target: "middleware::log_requests", message),
47
+ LogMiddlewareLevel::Info => info!(target: "middleware::log_requests", message),
48
+ LogMiddlewareLevel::Warn => warn!(target: "middleware::log_requests", message),
49
+ LogMiddlewareLevel::Error => error!(target: "middleware::log_requests", message),
50
50
  }
51
51
  }
52
52
  }
@@ -13,7 +13,7 @@ use std::sync::atomic::Ordering;
13
13
 
14
14
  #[derive(Debug, Clone, Deserialize)]
15
15
  pub struct MaxBody {
16
- pub max_size: usize,
16
+ pub limit_bytes: usize,
17
17
  #[serde(default = "payload_too_large_error_response")]
18
18
  pub error_response: ErrorResponse,
19
19
  }
@@ -29,7 +29,7 @@ impl MiddlewareLayer for MaxBody {
29
29
  req: HttpRequest,
30
30
  context: &mut HttpRequestContext,
31
31
  ) -> Result<Either<HttpRequest, HttpResponse>> {
32
- req.body().limit.store(self.max_size, Ordering::Relaxed);
32
+ req.body().limit.store(self.limit_bytes, Ordering::Relaxed);
33
33
  context.set_response_format(req.accept().into());
34
34
  Ok(Either::Left(req))
35
35
  }
@@ -57,22 +57,28 @@ use serde::Deserialize;
57
57
  use serde_magnus::deserialize;
58
58
  pub use static_assets::StaticAssets;
59
59
  pub use static_response::StaticResponse;
60
+ pub use string_rewrite::StringRewrite;
60
61
 
61
62
  use crate::server::http_message_types::HttpRequest;
62
63
  use crate::server::http_message_types::HttpResponse;
63
64
  use crate::services::itsi_http_service::HttpRequestContext;
64
65
 
66
+ use std::collections::HashMap;
67
+ use std::sync::Mutex;
68
+ static CACHE: LazyLock<Mutex<HashMap<u64, Arc<dyn std::any::Any + Send + Sync>>>> =
69
+ LazyLock::new(|| Mutex::new(HashMap::new()));
70
+
71
+ pub fn clear_value_cache() {
72
+ let mut cache = CACHE.lock().unwrap();
73
+ cache.clear();
74
+ }
75
+
65
76
  pub trait FromValue: Sized + Send + Sync + 'static {
66
77
  fn from_value(value: Value) -> Result<Arc<Self>>
67
78
  where
68
79
  Self: Deserialize<'static>,
69
80
  {
70
- use std::collections::HashMap;
71
- use std::sync::Mutex;
72
-
73
81
  let raw = value.as_raw();
74
- static CACHE: LazyLock<Mutex<HashMap<u64, Arc<dyn std::any::Any + Send + Sync>>>> =
75
- LazyLock::new(|| Mutex::new(HashMap::new()));
76
82
 
77
83
  let mut cache = CACHE.lock().unwrap();
78
84