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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -8
- data/Cargo.lock +2 -2
- data/LICENSE.txt +698 -0
- data/README.md +15 -4
- data/Rakefile +9 -5
- data/crates/itsi_acme/.gitignore +4 -0
- data/crates/itsi_acme/Cargo.toml +86 -0
- data/crates/itsi_acme/LICENSE-APACHE +201 -0
- data/crates/itsi_acme/LICENSE-MIT +23 -0
- data/crates/itsi_acme/README.md +9 -0
- data/crates/itsi_acme/examples/high_level.rs +63 -0
- data/crates/itsi_acme/examples/high_level_warp.rs +52 -0
- data/crates/itsi_acme/examples/low_level.rs +87 -0
- data/crates/itsi_acme/examples/low_level_axum.rs +66 -0
- data/crates/itsi_acme/src/acceptor.rs +81 -0
- data/crates/itsi_acme/src/acme.rs +354 -0
- data/crates/itsi_acme/src/axum.rs +86 -0
- data/crates/itsi_acme/src/cache.rs +39 -0
- data/crates/itsi_acme/src/caches/boxed.rs +80 -0
- data/crates/itsi_acme/src/caches/composite.rs +69 -0
- data/crates/itsi_acme/src/caches/dir.rs +106 -0
- data/crates/itsi_acme/src/caches/mod.rs +11 -0
- data/crates/itsi_acme/src/caches/no.rs +78 -0
- data/crates/itsi_acme/src/caches/test.rs +136 -0
- data/crates/itsi_acme/src/config.rs +172 -0
- data/crates/itsi_acme/src/https_helper.rs +69 -0
- data/crates/itsi_acme/src/incoming.rs +142 -0
- data/crates/itsi_acme/src/jose.rs +161 -0
- data/crates/itsi_acme/src/lib.rs +142 -0
- data/crates/itsi_acme/src/resolver.rs +59 -0
- data/crates/itsi_acme/src/state.rs +424 -0
- data/crates/itsi_rb_helpers/src/lib.rs +4 -3
- data/crates/itsi_scheduler/Cargo.toml +1 -1
- data/crates/itsi_scheduler/src/itsi_scheduler.rs +8 -2
- data/crates/itsi_scheduler/src/lib.rs +1 -0
- data/crates/itsi_server/Cargo.toml +1 -1
- data/crates/itsi_server/src/lib.rs +2 -1
- data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +18 -1
- data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +11 -3
- data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +122 -63
- data/crates/itsi_server/src/ruby_types/itsi_server.rs +2 -0
- data/crates/itsi_server/src/server/binds/bind.rs +3 -0
- data/crates/itsi_server/src/server/binds/listener.rs +12 -5
- data/crates/itsi_server/src/server/binds/tls.rs +13 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +12 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +8 -1
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +9 -1
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +48 -43
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +11 -2
- data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +39 -12
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +36 -27
- data/crates/itsi_server/src/server/middleware_stack/middlewares/csp.rs +25 -11
- data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +12 -3
- data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +74 -72
- data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +15 -1
- data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +11 -8
- data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +19 -11
- data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +5 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +2 -2
- data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +11 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +17 -20
- data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +19 -8
- data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +16 -37
- data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +22 -12
- data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +26 -11
- data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +7 -1
- data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +14 -4
- data/crates/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +19 -0
- data/crates/itsi_server/src/server/middleware_stack/mod.rs +49 -13
- data/crates/itsi_server/src/server/mod.rs +1 -0
- data/crates/itsi_server/src/server/redirect_type.rs +26 -0
- data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +22 -16
- data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +49 -12
- data/crates/itsi_server/src/server/signal.rs +1 -0
- data/crates/itsi_server/src/server/size_limited_incoming.rs +6 -0
- data/crates/itsi_server/src/server/thread_worker.rs +5 -1
- data/crates/itsi_server/src/services/itsi_http_service.rs +20 -2
- data/crates/itsi_server/src/services/rate_limiter.rs +15 -4
- data/crates/itsi_server/src/services/static_file_server.rs +33 -19
- data/crates/itsi_tracing/src/lib.rs +42 -22
- data/docs/content/_index.md +1 -2
- data/docs/content/acknowledgements/_index.md +5 -2
- data/docs/content/configuration/_index.md +8 -5
- data/docs/content/contact/_index.md +8 -1
- data/docs/content/faqs/_index.md +5 -3
- data/docs/content/features/_index.md +56 -50
- data/docs/content/getting_started/_index.md +8 -5
- data/docs/content/getting_started/local_development.md +68 -8
- data/docs/content/getting_started/logging.md +16 -9
- data/docs/content/getting_started/running_itsi_in_production.md +5 -3
- data/docs/content/getting_started/signals.md +38 -0
- data/docs/content/itsi_scheduler/_index.md +8 -7
- data/docs/content/utilities/_index.md +13 -0
- data/docs/content/utilities/config_file_testing.md +17 -0
- data/docs/content/utilities/passfile_generator.md +41 -0
- data/docs/content/utilities/route_testing.md +27 -0
- data/docs/content/utilities/secrets_management.md +30 -0
- data/docs/hugo.yaml +1 -1
- data/fairytale.txt +3 -4
- data/gems/scheduler/Cargo.lock +1 -1
- data/gems/scheduler/README.md +4 -5
- data/gems/scheduler/Rakefile +0 -4
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/scheduler/lib/itsi/scheduler.rb +9 -4
- data/gems/scheduler/test/test_active_record.rb +12 -7
- data/gems/server/Cargo.lock +1 -1
- data/gems/server/Rakefile +0 -4
- data/gems/server/exe/itsi +13 -2
- data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +2 -0
- data/gems/server/lib/itsi/http_request.rb +40 -9
- data/gems/server/lib/itsi/http_response.rb +2 -1
- data/gems/server/lib/itsi/passfile.rb +0 -1
- data/gems/server/lib/itsi/server/config/config_helpers.rb +20 -8
- data/gems/server/lib/itsi/server/config/dsl.rb +20 -435
- data/gems/server/lib/itsi/server/config/known_paths.rb +4 -1
- data/gems/server/lib/itsi/server/config/middleware/_index.md +6 -4
- data/gems/server/lib/itsi/server/config/middleware/allow_list.md +46 -0
- data/gems/server/lib/itsi/server/config/middleware/allow_list.rb +42 -0
- data/gems/server/lib/itsi/server/config/middleware/auth_api_key.md +90 -0
- data/gems/server/lib/itsi/server/config/middleware/auth_api_key.rb +51 -0
- data/gems/server/lib/itsi/server/config/middleware/auth_basic.md +45 -0
- data/gems/server/lib/itsi/server/config/middleware/auth_basic.rb +44 -0
- data/gems/server/lib/itsi/server/config/middleware/auth_jwt.md +82 -0
- data/gems/server/lib/itsi/server/config/middleware/auth_jwt.rb +38 -0
- data/gems/server/lib/itsi/server/config/middleware/cache_control.md +78 -0
- data/gems/server/lib/itsi/server/config/middleware/cache_control.rb +45 -0
- data/gems/server/lib/itsi/server/config/middleware/cidr_to_regex.rb +50 -0
- data/gems/server/lib/itsi/server/config/middleware/compression.md +50 -0
- data/gems/server/lib/itsi/server/config/middleware/compression.rb +37 -0
- data/gems/server/lib/itsi/server/config/middleware/cors.md +93 -0
- data/gems/server/lib/itsi/server/config/middleware/cors.rb +32 -0
- data/gems/server/lib/itsi/server/config/middleware/csp.md +37 -0
- data/gems/server/lib/itsi/server/config/middleware/csp.rb +44 -0
- data/gems/server/lib/itsi/server/config/middleware/deny_list.md +45 -0
- data/gems/server/lib/itsi/server/config/middleware/deny_list.rb +42 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/_index.md +159 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.md +186 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.rb +33 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.md +12 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.rb +42 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/endpoint.rb +99 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/get.md +12 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/get.rb +42 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/http_request.md +44 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/http_response.md +39 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.md +12 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.rb +42 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/post.md +12 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/post.rb +42 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/put.md +12 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/put.rb +42 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/schemas.md +122 -0
- data/gems/server/lib/itsi/server/config/middleware/error_response.md +61 -0
- data/gems/server/lib/itsi/server/config/middleware/error_response.rb +36 -0
- data/gems/server/lib/itsi/server/config/middleware/etag.md +59 -0
- data/gems/server/lib/itsi/server/config/middleware/etag.rb +27 -0
- data/gems/server/lib/itsi/server/config/middleware/grpc.md +172 -0
- data/gems/server/lib/itsi/server/config/middleware/grpc.rb +54 -0
- data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.md +124 -0
- data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.rb +61 -0
- data/gems/server/lib/itsi/server/config/middleware/location.md +107 -0
- data/gems/server/lib/itsi/server/config/middleware/location.rb +99 -0
- data/gems/server/lib/itsi/server/config/middleware/log_requests.md +13 -11
- data/gems/server/lib/itsi/server/config/middleware/log_requests.rb +1 -3
- data/gems/server/lib/itsi/server/config/middleware/max_body.md +18 -0
- data/gems/server/lib/itsi/server/config/middleware/max_body.rb +21 -0
- data/gems/server/lib/itsi/server/config/middleware/proxy.md +62 -0
- data/gems/server/lib/itsi/server/config/middleware/proxy.rb +41 -0
- data/gems/server/lib/itsi/server/config/middleware/rackup_file.md +54 -0
- data/gems/server/lib/itsi/server/config/middleware/rackup_file.rb +44 -0
- data/gems/server/lib/itsi/server/config/middleware/rate_limit.md +126 -0
- data/gems/server/lib/itsi/server/config/middleware/rate_limit.rb +34 -0
- data/gems/server/lib/itsi/server/config/middleware/rate_limit_store.rb +25 -0
- data/gems/server/lib/itsi/server/config/middleware/redirect.md +55 -0
- data/gems/server/lib/itsi/server/config/middleware/redirect.rb +25 -0
- data/gems/server/lib/itsi/server/config/middleware/request_headers.md +34 -0
- data/gems/server/lib/itsi/server/config/middleware/request_headers.rb +24 -0
- data/gems/server/lib/itsi/server/config/middleware/response_headers.md +33 -0
- data/gems/server/lib/itsi/server/config/middleware/response_headers.rb +25 -0
- data/gems/server/lib/itsi/server/config/middleware/run.md +60 -0
- data/gems/server/lib/itsi/server/config/middleware/run.rb +43 -0
- data/gems/server/lib/itsi/server/config/middleware/static_assets.md +73 -0
- data/gems/server/lib/itsi/server/config/middleware/static_assets.rb +87 -0
- data/gems/server/lib/itsi/server/config/middleware/static_response.md +44 -0
- data/gems/server/lib/itsi/server/config/middleware/static_response.rb +29 -0
- data/gems/server/lib/itsi/server/config/middleware/string_rewrite.md +67 -0
- data/gems/server/lib/itsi/server/config/middleware/token_source.rb +32 -0
- data/gems/server/lib/itsi/server/config/middleware.rb +4 -0
- data/gems/server/lib/itsi/server/config/option.rb +5 -0
- data/gems/server/lib/itsi/server/config/options/_index.md +3 -2
- data/gems/server/lib/itsi/server/config/options/auto_reload_config.md +13 -0
- data/gems/server/lib/itsi/server/config/options/auto_reload_config.rb +41 -0
- data/gems/server/lib/itsi/server/config/options/bind.md +71 -0
- data/gems/server/lib/itsi/server/config/options/bind.rb +26 -0
- data/gems/server/lib/itsi/server/config/options/certificates.md +65 -0
- data/gems/server/lib/itsi/server/config/options/daemonize.md +14 -0
- data/gems/server/lib/itsi/server/config/options/daemonize.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/fiber_scheduler.md +1 -2
- data/gems/server/lib/itsi/server/config/options/fiber_scheduler.rb +6 -3
- data/gems/server/lib/itsi/server/config/options/header_read_timeout.md +17 -0
- data/gems/server/lib/itsi/server/config/options/header_read_timeout.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/hooks/_index.md +11 -0
- data/gems/server/lib/itsi/server/config/options/hooks/after_fork.md +13 -0
- data/gems/server/lib/itsi/server/config/options/hooks/after_fork.rb +28 -0
- data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.md +14 -0
- data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.rb +28 -0
- data/gems/server/lib/itsi/server/config/options/hooks/after_start.md +12 -0
- data/gems/server/lib/itsi/server/config/options/hooks/after_start.rb +28 -0
- data/gems/server/lib/itsi/server/config/options/hooks/before_fork.md +13 -0
- data/gems/server/lib/itsi/server/config/options/hooks/before_fork.rb +28 -0
- data/gems/server/lib/itsi/server/config/options/hooks/before_restart.md +12 -0
- data/gems/server/lib/itsi/server/config/options/hooks/before_restart.rb +28 -0
- data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.md +12 -0
- data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.rb +28 -0
- data/gems/server/lib/itsi/server/config/options/include.md +20 -0
- data/gems/server/lib/itsi/server/config/options/include.rb +36 -0
- data/gems/server/lib/itsi/server/config/options/listen_backlog.md +11 -0
- data/gems/server/lib/itsi/server/config/options/listen_backlog.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/log_format.md +18 -0
- data/gems/server/lib/itsi/server/config/options/log_format.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/log_level.md +34 -0
- data/gems/server/lib/itsi/server/config/options/log_level.rb +20 -0
- data/gems/server/lib/itsi/server/config/options/log_target.md +38 -0
- data/gems/server/lib/itsi/server/config/options/log_target.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/log_target_filters.md +17 -0
- data/gems/server/lib/itsi/server/config/options/log_target_filters.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.md +27 -0
- data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.rb +24 -0
- data/gems/server/lib/itsi/server/config/options/nodelay.md +16 -0
- data/gems/server/lib/itsi/server/config/options/nodelay.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.md +19 -0
- data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.rb +18 -0
- data/gems/server/lib/itsi/server/config/options/pin_worker_cores.md +17 -0
- data/gems/server/lib/itsi/server/config/options/pin_worker_cores.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/preload.md +21 -0
- data/gems/server/lib/itsi/server/config/options/preload.rb +18 -0
- data/gems/server/lib/itsi/server/config/options/recv_buffer_size.md +15 -0
- data/gems/server/lib/itsi/server/config/options/recv_buffer_size.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.md +21 -0
- data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.rb +30 -0
- data/gems/server/lib/itsi/server/config/options/request_timeout.md +23 -0
- data/gems/server/lib/itsi/server/config/options/request_timeout.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/reuse_address.md +16 -0
- data/gems/server/lib/itsi/server/config/options/reuse_address.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/reuse_port.md +16 -0
- data/gems/server/lib/itsi/server/config/options/reuse_port.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/scheduler_threads.md +34 -0
- data/gems/server/lib/itsi/server/config/options/scheduler_threads.rb +17 -0
- data/gems/server/lib/itsi/server/config/options/shutdown_timeout.md +17 -0
- data/gems/server/lib/itsi/server/config/options/shutdown_timeout.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/stream_body.md +32 -0
- data/gems/server/lib/itsi/server/config/options/stream_body.rb +18 -0
- data/gems/server/lib/itsi/server/config/options/threads.md +7 -2
- data/gems/server/lib/itsi/server/config/options/threads.rb +1 -1
- data/gems/server/lib/itsi/server/config/options/watch.md +16 -0
- data/gems/server/lib/itsi/server/config/options/watch.rb +28 -0
- data/gems/server/lib/itsi/server/config/options/worker_memory_limit.md +22 -0
- data/gems/server/lib/itsi/server/config/options/worker_memory_limit.rb +18 -0
- data/gems/server/lib/itsi/server/config/options/workers.md +1 -2
- data/gems/server/lib/itsi/server/config/options/workers.rb +1 -1
- data/gems/server/lib/itsi/server/config/typed_struct.rb +59 -20
- data/gems/server/lib/itsi/server/config.rb +77 -48
- data/gems/server/lib/itsi/server/default_config/Itsi.rb +3 -3
- data/gems/server/lib/itsi/server/grpc/grpc_call.rb +1 -1
- data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +11 -4
- data/gems/server/lib/itsi/server/rack/handler/itsi.rb +3 -3
- data/gems/server/lib/itsi/server/route_tester.rb +58 -8
- data/gems/server/lib/itsi/server/signal_trap.rb +1 -1
- data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +14 -18
- data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +5 -4
- data/gems/server/lib/itsi/server/typed_handlers.rb +12 -4
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/gems/server/lib/itsi/server.rb +98 -14
- data/gems/server/lib/ruby_lsp/itsi/addon.rb +20 -18
- data/gems/server/test/helpers/test_helper.rb +89 -29
- data/gems/server/test/middleware/allow_list.rb +128 -0
- data/gems/server/test/middleware/auth_api_key.rb +141 -0
- data/gems/server/test/middleware/auth_basic.rb +91 -0
- data/gems/server/test/middleware/auth_jwt.rb +214 -0
- data/gems/server/test/middleware/cache_control.rb +82 -0
- data/gems/server/test/middleware/cidr_to_regex.rb +46 -0
- data/gems/server/test/middleware/compression.rb +89 -0
- data/gems/server/test/middleware/cors.rb +113 -0
- data/gems/server/test/middleware/csp.rb +62 -0
- data/gems/server/test/middleware/deny_list.rb +131 -0
- data/gems/server/test/middleware/endpoint.rb +300 -0
- data/gems/server/test/middleware/etag.rb +75 -0
- data/gems/server/test/middleware/grpc/grpc.rb +158 -0
- data/gems/server/test/middleware/grpc/test_service.proto +32 -0
- data/gems/server/test/middleware/grpc/test_service_impl.rb +28 -0
- data/gems/server/test/middleware/grpc/test_service_pb.rb +18 -0
- data/gems/server/test/middleware/grpc/test_service_services_pb.rb +30 -0
- data/gems/server/test/middleware/header_interpolation.rb +35 -0
- data/gems/server/test/middleware/intrusion_protection.rb +259 -0
- data/gems/server/test/middleware/location.rb +220 -0
- data/gems/server/test/middleware/max_body.rb +20 -0
- data/gems/server/test/middleware/proxy.rb +415 -0
- data/gems/server/test/middleware/rate_limit.rb +211 -0
- data/gems/server/test/middleware/redirect.rb +85 -0
- data/gems/server/test/middleware/request_headers.rb +50 -0
- data/gems/server/test/middleware/response_headers.rb +50 -0
- data/gems/server/test/middleware/static_assets.rb +374 -0
- data/gems/server/test/middleware/static_response.rb +56 -0
- data/gems/server/test/middleware/string_rewrite.rb +112 -0
- data/gems/server/test/options/bind.rb +47 -0
- data/gems/server/test/options/header_read_timeout.rb +23 -0
- data/gems/server/test/options/test_request_timeout.rb +16 -0
- data/gems/server/test/options/test_workers.rb +2 -4
- data/gems/server/test/{test_itsi_server.rb → rack/test_rack_server.rb} +2 -2
- data/grpc_test/Itsi.rb +11 -0
- data/grpc_test/echo.proto +14 -0
- data/grpc_test/echo_pb.rb +16 -0
- data/grpc_test/echo_service_impl.rb +8 -0
- data/grpc_test/echo_services_pb.rb +22 -0
- data/lib/itsi/version.rb +1 -1
- data/tasks.txt +15 -72
- metadata +210 -7
- 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
|
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:
|
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.
|
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::
|
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
|
-
|
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
|
-
|
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(
|
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())
|
data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs
CHANGED
@@ -1,86 +1,85 @@
|
|
1
|
-
use
|
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::
|
15
|
-
404 => ContentSource::
|
16
|
-
401 => ContentSource::
|
17
|
-
403 => ContentSource::
|
18
|
-
413 => ContentSource::
|
19
|
-
429 => ContentSource::
|
20
|
-
502 => ContentSource::
|
21
|
-
503 => ContentSource::
|
22
|
-
504 => ContentSource::
|
23
|
-
_ => ContentSource::
|
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::
|
27
|
-
include_str!("../../../../default_responses/html/500.html").
|
28
|
-
),
|
29
|
-
404 => ContentSource::
|
30
|
-
include_str!("../../../../default_responses/html/404.html").
|
31
|
-
),
|
32
|
-
401 => ContentSource::
|
33
|
-
include_str!("../../../../default_responses/html/401.html").
|
34
|
-
),
|
35
|
-
403 => ContentSource::
|
36
|
-
include_str!("../../../../default_responses/html/403.html").
|
37
|
-
),
|
38
|
-
413 => ContentSource::
|
39
|
-
include_str!("../../../../default_responses/html/413.html").
|
40
|
-
),
|
41
|
-
429 => ContentSource::
|
42
|
-
include_str!("../../../../default_responses/html/429.html").
|
43
|
-
),
|
44
|
-
502 => ContentSource::
|
45
|
-
include_str!("../../../../default_responses/html/502.html").
|
46
|
-
),
|
47
|
-
503 => ContentSource::
|
48
|
-
include_str!("../../../../default_responses/html/503.html").
|
49
|
-
),
|
50
|
-
504 => ContentSource::
|
51
|
-
include_str!("../../../../default_responses/html/504.html").
|
52
|
-
),
|
53
|
-
_ => ContentSource::
|
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::
|
57
|
-
include_str!("../../../../default_responses/json/500.json").
|
58
|
-
),
|
59
|
-
404 => ContentSource::
|
60
|
-
include_str!("../../../../default_responses/json/404.json").
|
61
|
-
),
|
62
|
-
401 => ContentSource::
|
63
|
-
include_str!("../../../../default_responses/json/401.json").
|
64
|
-
),
|
65
|
-
403 => ContentSource::
|
66
|
-
include_str!("../../../../default_responses/json/403.json").
|
67
|
-
),
|
68
|
-
413 => ContentSource::
|
69
|
-
include_str!("../../../../default_responses/json/413.json").
|
70
|
-
),
|
71
|
-
429 => ContentSource::
|
72
|
-
include_str!("../../../../default_responses/json/429.json").
|
73
|
-
),
|
74
|
-
502 => ContentSource::
|
75
|
-
include_str!("../../../../default_responses/json/502.json").
|
76
|
-
),
|
77
|
-
503 => ContentSource::
|
78
|
-
include_str!("../../../../default_responses/json/503.json").
|
79
|
-
),
|
80
|
-
504 => ContentSource::
|
81
|
-
include_str!("../../../../default_responses/json/504.json").
|
82
|
-
),
|
83
|
-
_ => ContentSource::
|
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
|
-
_ =>
|
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
|
-
_ =>
|
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:
|
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::
|
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
|
-
|
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::
|
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
|
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.
|
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
|
|