itsi-scheduler 0.2.22-aarch64-linux

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 (149) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +8 -0
  3. data/Cargo.lock +997 -0
  4. data/Cargo.toml +7 -0
  5. data/Rakefile +39 -0
  6. data/ext/itsi_acme/Cargo.toml +86 -0
  7. data/ext/itsi_acme/examples/high_level.rs +63 -0
  8. data/ext/itsi_acme/examples/high_level_warp.rs +52 -0
  9. data/ext/itsi_acme/examples/low_level.rs +87 -0
  10. data/ext/itsi_acme/examples/low_level_axum.rs +66 -0
  11. data/ext/itsi_acme/src/acceptor.rs +81 -0
  12. data/ext/itsi_acme/src/acme.rs +354 -0
  13. data/ext/itsi_acme/src/axum.rs +86 -0
  14. data/ext/itsi_acme/src/cache.rs +39 -0
  15. data/ext/itsi_acme/src/caches/boxed.rs +80 -0
  16. data/ext/itsi_acme/src/caches/composite.rs +69 -0
  17. data/ext/itsi_acme/src/caches/dir.rs +106 -0
  18. data/ext/itsi_acme/src/caches/mod.rs +11 -0
  19. data/ext/itsi_acme/src/caches/no.rs +78 -0
  20. data/ext/itsi_acme/src/caches/test.rs +136 -0
  21. data/ext/itsi_acme/src/config.rs +172 -0
  22. data/ext/itsi_acme/src/https_helper.rs +69 -0
  23. data/ext/itsi_acme/src/incoming.rs +142 -0
  24. data/ext/itsi_acme/src/jose.rs +161 -0
  25. data/ext/itsi_acme/src/lib.rs +142 -0
  26. data/ext/itsi_acme/src/resolver.rs +59 -0
  27. data/ext/itsi_acme/src/state.rs +424 -0
  28. data/ext/itsi_error/Cargo.lock +368 -0
  29. data/ext/itsi_error/Cargo.toml +12 -0
  30. data/ext/itsi_error/src/lib.rs +140 -0
  31. data/ext/itsi_instrument_entry/Cargo.toml +15 -0
  32. data/ext/itsi_instrument_entry/src/lib.rs +31 -0
  33. data/ext/itsi_rb_helpers/Cargo.lock +355 -0
  34. data/ext/itsi_rb_helpers/Cargo.toml +11 -0
  35. data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
  36. data/ext/itsi_rb_helpers/src/lib.rs +232 -0
  37. data/ext/itsi_scheduler/Cargo.toml +24 -0
  38. data/ext/itsi_scheduler/extconf.rb +11 -0
  39. data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  40. data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  41. data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  42. data/ext/itsi_scheduler/src/itsi_scheduler.rs +320 -0
  43. data/ext/itsi_scheduler/src/lib.rs +39 -0
  44. data/ext/itsi_server/Cargo.lock +2956 -0
  45. data/ext/itsi_server/Cargo.toml +94 -0
  46. data/ext/itsi_server/src/default_responses/mod.rs +14 -0
  47. data/ext/itsi_server/src/env.rs +43 -0
  48. data/ext/itsi_server/src/lib.rs +154 -0
  49. data/ext/itsi_server/src/prelude.rs +2 -0
  50. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +116 -0
  51. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +149 -0
  52. data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +346 -0
  53. data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +265 -0
  54. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +399 -0
  55. data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +447 -0
  56. data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +545 -0
  57. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +650 -0
  58. data/ext/itsi_server/src/ruby_types/itsi_server.rs +102 -0
  59. data/ext/itsi_server/src/ruby_types/mod.rs +48 -0
  60. data/ext/itsi_server/src/server/binds/bind.rs +204 -0
  61. data/ext/itsi_server/src/server/binds/bind_protocol.rs +37 -0
  62. data/ext/itsi_server/src/server/binds/listener.rs +485 -0
  63. data/ext/itsi_server/src/server/binds/mod.rs +4 -0
  64. data/ext/itsi_server/src/server/binds/tls/locked_dir_cache.rs +132 -0
  65. data/ext/itsi_server/src/server/binds/tls.rs +278 -0
  66. data/ext/itsi_server/src/server/byte_frame.rs +32 -0
  67. data/ext/itsi_server/src/server/frame_stream.rs +143 -0
  68. data/ext/itsi_server/src/server/http_message_types.rs +230 -0
  69. data/ext/itsi_server/src/server/io_stream.rs +128 -0
  70. data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
  71. data/ext/itsi_server/src/server/middleware_stack/middleware.rs +170 -0
  72. data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +63 -0
  73. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +94 -0
  74. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +93 -0
  75. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +343 -0
  76. data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +151 -0
  77. data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +329 -0
  78. data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +300 -0
  79. data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +193 -0
  80. data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +64 -0
  81. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +188 -0
  82. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +168 -0
  83. data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +183 -0
  84. data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +82 -0
  85. data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +209 -0
  86. data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +133 -0
  87. data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
  88. data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +122 -0
  89. data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +407 -0
  90. data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +155 -0
  91. data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +54 -0
  92. data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +54 -0
  93. data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +51 -0
  94. data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +138 -0
  95. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +269 -0
  96. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +62 -0
  97. data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +218 -0
  98. data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +31 -0
  99. data/ext/itsi_server/src/server/middleware_stack/mod.rs +381 -0
  100. data/ext/itsi_server/src/server/mod.rs +14 -0
  101. data/ext/itsi_server/src/server/process_worker.rs +247 -0
  102. data/ext/itsi_server/src/server/redirect_type.rs +26 -0
  103. data/ext/itsi_server/src/server/request_job.rs +11 -0
  104. data/ext/itsi_server/src/server/serve_strategy/acceptor.rs +100 -0
  105. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +411 -0
  106. data/ext/itsi_server/src/server/serve_strategy/mod.rs +31 -0
  107. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +449 -0
  108. data/ext/itsi_server/src/server/signal.rs +129 -0
  109. data/ext/itsi_server/src/server/size_limited_incoming.rs +107 -0
  110. data/ext/itsi_server/src/server/thread_worker.rs +504 -0
  111. data/ext/itsi_server/src/services/cache_store.rs +74 -0
  112. data/ext/itsi_server/src/services/itsi_http_service.rs +270 -0
  113. data/ext/itsi_server/src/services/mime_types.rs +2896 -0
  114. data/ext/itsi_server/src/services/mod.rs +6 -0
  115. data/ext/itsi_server/src/services/password_hasher.rs +89 -0
  116. data/ext/itsi_server/src/services/rate_limiter.rs +609 -0
  117. data/ext/itsi_server/src/services/static_file_server.rs +1400 -0
  118. data/ext/itsi_tracing/Cargo.lock +274 -0
  119. data/ext/itsi_tracing/Cargo.toml +17 -0
  120. data/ext/itsi_tracing/src/lib.rs +370 -0
  121. data/itsi-scheduler-100.png +0 -0
  122. data/lib/itsi/schedule_refinement.rb +96 -0
  123. data/lib/itsi/scheduler/3.1/itsi_scheduler.so +0 -0
  124. data/lib/itsi/scheduler/3.2/itsi_scheduler.so +0 -0
  125. data/lib/itsi/scheduler/3.3/itsi_scheduler.so +0 -0
  126. data/lib/itsi/scheduler/3.4/itsi_scheduler.so +0 -0
  127. data/lib/itsi/scheduler/4.0/itsi_scheduler.so +0 -0
  128. data/lib/itsi/scheduler/native_extension.rb +34 -0
  129. data/lib/itsi/scheduler/version.rb +7 -0
  130. data/lib/itsi/scheduler.rb +153 -0
  131. data/vendor/rb-sys-build/.cargo-ok +1 -0
  132. data/vendor/rb-sys-build/.cargo_vcs_info.json +6 -0
  133. data/vendor/rb-sys-build/Cargo.lock +294 -0
  134. data/vendor/rb-sys-build/Cargo.toml +71 -0
  135. data/vendor/rb-sys-build/Cargo.toml.orig +32 -0
  136. data/vendor/rb-sys-build/LICENSE-APACHE +190 -0
  137. data/vendor/rb-sys-build/LICENSE-MIT +21 -0
  138. data/vendor/rb-sys-build/src/bindings/sanitizer.rs +185 -0
  139. data/vendor/rb-sys-build/src/bindings/stable_api.rs +247 -0
  140. data/vendor/rb-sys-build/src/bindings/wrapper.h +71 -0
  141. data/vendor/rb-sys-build/src/bindings.rs +280 -0
  142. data/vendor/rb-sys-build/src/cc.rs +421 -0
  143. data/vendor/rb-sys-build/src/lib.rs +12 -0
  144. data/vendor/rb-sys-build/src/rb_config/flags.rs +101 -0
  145. data/vendor/rb-sys-build/src/rb_config/library.rs +132 -0
  146. data/vendor/rb-sys-build/src/rb_config/search_path.rs +57 -0
  147. data/vendor/rb-sys-build/src/rb_config.rs +906 -0
  148. data/vendor/rb-sys-build/src/utils.rs +53 -0
  149. metadata +210 -0
@@ -0,0 +1,128 @@
1
+ use pin_project::pin_project;
2
+ use tokio::net::{TcpStream, UnixStream};
3
+ use tokio_rustls::server::TlsStream;
4
+
5
+ use std::io::{self, IoSlice};
6
+ use std::os::unix::io::{AsRawFd, RawFd};
7
+ use std::pin::Pin;
8
+ use std::task::{Context, Poll};
9
+ use tokio::io::{AsyncRead, AsyncWrite};
10
+
11
+ use super::binds::listener::SockAddr;
12
+
13
+ #[pin_project(project = IoStreamEnumProj)]
14
+ pub enum IoStream {
15
+ Tcp {
16
+ #[pin]
17
+ stream: TcpStream,
18
+ addr: SockAddr,
19
+ },
20
+ TcpTls {
21
+ #[pin]
22
+ stream: TlsStream<TcpStream>,
23
+ addr: SockAddr,
24
+ },
25
+ Unix {
26
+ #[pin]
27
+ stream: UnixStream,
28
+ addr: SockAddr,
29
+ },
30
+ UnixTls {
31
+ #[pin]
32
+ stream: TlsStream<UnixStream>,
33
+ addr: SockAddr,
34
+ },
35
+ }
36
+
37
+ impl IoStream {
38
+ pub fn addr(&self) -> String {
39
+ match self {
40
+ IoStream::Tcp { addr, .. } => addr.to_string(),
41
+ IoStream::TcpTls { addr, .. } => addr.to_string(),
42
+ IoStream::Unix { addr, .. } => addr.to_string(),
43
+ IoStream::UnixTls { addr, .. } => addr.to_string(),
44
+ }
45
+ }
46
+ }
47
+
48
+ impl AsyncRead for IoStream {
49
+ fn poll_read(
50
+ self: Pin<&mut Self>,
51
+ cx: &mut Context<'_>,
52
+ buf: &mut tokio::io::ReadBuf<'_>,
53
+ ) -> Poll<std::io::Result<()>> {
54
+ match self.project() {
55
+ IoStreamEnumProj::Tcp { stream, .. } => stream.poll_read(cx, buf),
56
+ IoStreamEnumProj::TcpTls { stream, .. } => stream.poll_read(cx, buf),
57
+ IoStreamEnumProj::Unix { stream, .. } => stream.poll_read(cx, buf),
58
+ IoStreamEnumProj::UnixTls { stream, .. } => stream.poll_read(cx, buf),
59
+ }
60
+ }
61
+ }
62
+
63
+ impl AsyncWrite for IoStream {
64
+ fn poll_write(
65
+ self: Pin<&mut Self>,
66
+ cx: &mut Context<'_>,
67
+ buf: &[u8],
68
+ ) -> Poll<std::io::Result<usize>> {
69
+ match self.project() {
70
+ IoStreamEnumProj::Tcp { stream, .. } => stream.poll_write(cx, buf),
71
+ IoStreamEnumProj::TcpTls { stream, .. } => stream.poll_write(cx, buf),
72
+ IoStreamEnumProj::Unix { stream, .. } => stream.poll_write(cx, buf),
73
+ IoStreamEnumProj::UnixTls { stream, .. } => stream.poll_write(cx, buf),
74
+ }
75
+ }
76
+
77
+ fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
78
+ match self.project() {
79
+ IoStreamEnumProj::Tcp { stream, .. } => stream.poll_flush(cx),
80
+ IoStreamEnumProj::TcpTls { stream, .. } => stream.poll_flush(cx),
81
+ IoStreamEnumProj::Unix { stream, .. } => stream.poll_flush(cx),
82
+ IoStreamEnumProj::UnixTls { stream, .. } => stream.poll_flush(cx),
83
+ }
84
+ }
85
+
86
+ fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
87
+ match self.project() {
88
+ IoStreamEnumProj::Tcp { stream, .. } => stream.poll_shutdown(cx),
89
+ IoStreamEnumProj::TcpTls { stream, .. } => stream.poll_shutdown(cx),
90
+ IoStreamEnumProj::Unix { stream, .. } => stream.poll_shutdown(cx),
91
+ IoStreamEnumProj::UnixTls { stream, .. } => stream.poll_shutdown(cx),
92
+ }
93
+ }
94
+
95
+ fn poll_write_vectored(
96
+ self: Pin<&mut Self>,
97
+ cx: &mut Context<'_>,
98
+ bufs: &[IoSlice<'_>],
99
+ ) -> Poll<io::Result<usize>> {
100
+ match self.project() {
101
+ IoStreamEnumProj::Tcp { stream, .. } => stream.poll_write_vectored(cx, bufs),
102
+ IoStreamEnumProj::TcpTls { stream, .. } => stream.poll_write_vectored(cx, bufs),
103
+ IoStreamEnumProj::Unix { stream, .. } => stream.poll_write_vectored(cx, bufs),
104
+ IoStreamEnumProj::UnixTls { stream, .. } => stream.poll_write_vectored(cx, bufs),
105
+ }
106
+ }
107
+
108
+ fn is_write_vectored(&self) -> bool {
109
+ match self {
110
+ IoStream::Tcp { stream, .. } => stream.is_write_vectored(),
111
+ IoStream::TcpTls { stream, .. } => stream.is_write_vectored(),
112
+ IoStream::Unix { stream, .. } => stream.is_write_vectored(),
113
+ IoStream::UnixTls { stream, .. } => stream.is_write_vectored(),
114
+ }
115
+ }
116
+ }
117
+
118
+ impl AsRawFd for IoStream {
119
+ fn as_raw_fd(&self) -> RawFd {
120
+ // For immutable access, we can simply pattern-match on self.
121
+ match self {
122
+ IoStream::Tcp { stream, .. } => stream.as_raw_fd(),
123
+ IoStream::TcpTls { stream, .. } => stream.get_ref().0.as_raw_fd(),
124
+ IoStream::Unix { stream, .. } => stream.as_raw_fd(),
125
+ IoStream::UnixTls { stream, .. } => stream.get_ref().0.as_raw_fd(),
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,12 @@
1
+ #[derive(Debug, Clone, PartialEq)]
2
+ pub enum LifecycleEvent {
3
+ Start,
4
+ Shutdown,
5
+ Restart,
6
+ Reload,
7
+ IncreaseWorkers,
8
+ DecreaseWorkers,
9
+ ForceShutdown,
10
+ PrintInfo,
11
+ ChildTerminated,
12
+ }
@@ -0,0 +1,170 @@
1
+ use crate::{
2
+ server::http_message_types::{HttpRequest, HttpResponse},
3
+ services::itsi_http_service::HttpRequestContext,
4
+ };
5
+
6
+ use super::middlewares::*;
7
+
8
+ use async_trait::async_trait;
9
+ use either::Either;
10
+ use magnus::error::Result;
11
+ use std::{cmp::Ordering, sync::Arc};
12
+
13
+ #[derive(Debug, Clone)]
14
+ pub enum Middleware {
15
+ AllowList(Arc<AllowList>),
16
+ AuthAPIKey(Arc<AuthAPIKey>),
17
+ AuthBasic(Arc<AuthBasic>),
18
+ AuthJwt(Arc<AuthJwt>),
19
+ CacheControl(Arc<CacheControl>),
20
+ Compression(Arc<Compression>),
21
+ Cors(Arc<Cors>),
22
+ Csp(Arc<Csp>),
23
+ DenyList(Arc<DenyList>),
24
+ ETag(Arc<ETag>),
25
+ IntrusionProtection(Arc<IntrusionProtection>),
26
+ LogRequests(Arc<LogRequests>),
27
+ MaxBody(Arc<MaxBody>),
28
+ Proxy(Arc<Proxy>),
29
+ RateLimit(Arc<RateLimit>),
30
+ Redirect(Arc<Redirect>),
31
+ RequestHeaders(Arc<RequestHeaders>),
32
+ ResponseHeaders(Arc<ResponseHeaders>),
33
+ RubyApp(Arc<RubyApp>),
34
+ StaticAssets(Arc<StaticAssets>),
35
+ StaticResponse(Arc<StaticResponse>),
36
+ }
37
+
38
+ #[async_trait]
39
+ impl MiddlewareLayer for Middleware {
40
+ /// Called just once, to initialize the middleware state.
41
+ async fn initialize(&self) -> Result<()> {
42
+ match self {
43
+ Middleware::DenyList(filter) => filter.initialize().await,
44
+ Middleware::AllowList(filter) => filter.initialize().await,
45
+ Middleware::AuthBasic(filter) => filter.initialize().await,
46
+ Middleware::AuthJwt(filter) => filter.initialize().await,
47
+ Middleware::AuthAPIKey(filter) => filter.initialize().await,
48
+ Middleware::IntrusionProtection(filter) => filter.initialize().await,
49
+ Middleware::MaxBody(filter) => filter.initialize().await,
50
+ Middleware::RateLimit(filter) => filter.initialize().await,
51
+ Middleware::RequestHeaders(filter) => filter.initialize().await,
52
+ Middleware::ResponseHeaders(filter) => filter.initialize().await,
53
+ Middleware::CacheControl(filter) => filter.initialize().await,
54
+ Middleware::Cors(filter) => filter.initialize().await,
55
+ Middleware::Csp(filter) => filter.initialize().await,
56
+ Middleware::ETag(filter) => filter.initialize().await,
57
+ Middleware::StaticAssets(filter) => filter.initialize().await,
58
+ Middleware::StaticResponse(filter) => filter.initialize().await,
59
+ Middleware::Compression(filter) => filter.initialize().await,
60
+ Middleware::LogRequests(filter) => filter.initialize().await,
61
+ Middleware::Redirect(filter) => filter.initialize().await,
62
+ Middleware::Proxy(filter) => filter.initialize().await,
63
+ Middleware::RubyApp(filter) => filter.initialize().await,
64
+ }
65
+ }
66
+
67
+ async fn before(
68
+ &self,
69
+ req: HttpRequest,
70
+ context: &mut HttpRequestContext,
71
+ ) -> Result<Either<HttpRequest, HttpResponse>> {
72
+ match self {
73
+ Middleware::DenyList(filter) => filter.before(req, context).await,
74
+ Middleware::AllowList(filter) => filter.before(req, context).await,
75
+ Middleware::AuthBasic(filter) => filter.before(req, context).await,
76
+ Middleware::AuthJwt(filter) => filter.before(req, context).await,
77
+ Middleware::AuthAPIKey(filter) => filter.before(req, context).await,
78
+ Middleware::IntrusionProtection(filter) => filter.before(req, context).await,
79
+ Middleware::MaxBody(filter) => filter.before(req, context).await,
80
+ Middleware::RequestHeaders(filter) => filter.before(req, context).await,
81
+ Middleware::ResponseHeaders(filter) => filter.before(req, context).await,
82
+ Middleware::RateLimit(filter) => filter.before(req, context).await,
83
+ Middleware::CacheControl(filter) => filter.before(req, context).await,
84
+ Middleware::Cors(filter) => filter.before(req, context).await,
85
+ Middleware::Csp(filter) => filter.before(req, context).await,
86
+ Middleware::ETag(filter) => filter.before(req, context).await,
87
+ Middleware::StaticAssets(filter) => filter.before(req, context).await,
88
+ Middleware::StaticResponse(filter) => filter.before(req, context).await,
89
+ Middleware::Compression(filter) => filter.before(req, context).await,
90
+ Middleware::LogRequests(filter) => filter.before(req, context).await,
91
+ Middleware::Redirect(filter) => filter.before(req, context).await,
92
+ Middleware::Proxy(filter) => filter.before(req, context).await,
93
+ Middleware::RubyApp(filter) => filter.before(req, context).await,
94
+ }
95
+ }
96
+
97
+ async fn after(&self, res: HttpResponse, context: &mut HttpRequestContext) -> HttpResponse {
98
+ match self {
99
+ Middleware::DenyList(filter) => filter.after(res, context).await,
100
+ Middleware::AllowList(filter) => filter.after(res, context).await,
101
+ Middleware::AuthBasic(filter) => filter.after(res, context).await,
102
+ Middleware::AuthJwt(filter) => filter.after(res, context).await,
103
+ Middleware::AuthAPIKey(filter) => filter.after(res, context).await,
104
+ Middleware::IntrusionProtection(filter) => filter.after(res, context).await,
105
+ Middleware::MaxBody(filter) => filter.after(res, context).await,
106
+ Middleware::RateLimit(filter) => filter.after(res, context).await,
107
+ Middleware::RequestHeaders(filter) => filter.after(res, context).await,
108
+ Middleware::ResponseHeaders(filter) => filter.after(res, context).await,
109
+ Middleware::CacheControl(filter) => filter.after(res, context).await,
110
+ Middleware::Csp(filter) => filter.after(res, context).await,
111
+ Middleware::Cors(filter) => filter.after(res, context).await,
112
+ Middleware::ETag(filter) => filter.after(res, context).await,
113
+ Middleware::StaticAssets(filter) => filter.after(res, context).await,
114
+ Middleware::StaticResponse(filter) => filter.after(res, context).await,
115
+ Middleware::Compression(filter) => filter.after(res, context).await,
116
+ Middleware::LogRequests(filter) => filter.after(res, context).await,
117
+ Middleware::Redirect(filter) => filter.after(res, context).await,
118
+ Middleware::Proxy(filter) => filter.after(res, context).await,
119
+ Middleware::RubyApp(filter) => filter.after(res, context).await,
120
+ }
121
+ }
122
+ }
123
+
124
+ impl Middleware {
125
+ fn variant_order(&self) -> usize {
126
+ match self {
127
+ Middleware::DenyList(_) => 0,
128
+ Middleware::AllowList(_) => 1,
129
+ Middleware::IntrusionProtection(_) => 2,
130
+ Middleware::Redirect(_) => 3,
131
+ Middleware::LogRequests(_) => 4,
132
+ Middleware::CacheControl(_) => 5,
133
+ Middleware::RequestHeaders(_) => 6,
134
+ Middleware::ResponseHeaders(_) => 7,
135
+ Middleware::MaxBody(_) => 8,
136
+ Middleware::AuthBasic(_) => 9,
137
+ Middleware::AuthJwt(_) => 10,
138
+ Middleware::AuthAPIKey(_) => 11,
139
+ Middleware::RateLimit(_) => 12,
140
+ Middleware::ETag(_) => 13,
141
+ Middleware::Csp(_) => 14,
142
+ Middleware::Compression(_) => 15,
143
+ Middleware::Proxy(_) => 16,
144
+ Middleware::Cors(_) => 17,
145
+ Middleware::StaticResponse(_) => 18,
146
+ Middleware::StaticAssets(_) => 19,
147
+ Middleware::RubyApp(_) => 20,
148
+ }
149
+ }
150
+ }
151
+
152
+ impl PartialEq for Middleware {
153
+ fn eq(&self, other: &Self) -> bool {
154
+ self.variant_order() == other.variant_order()
155
+ }
156
+ }
157
+
158
+ impl Eq for Middleware {}
159
+
160
+ impl PartialOrd for Middleware {
161
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
162
+ Some(self.cmp(other))
163
+ }
164
+ }
165
+
166
+ impl Ord for Middleware {
167
+ fn cmp(&self, other: &Self) -> Ordering {
168
+ self.variant_order().cmp(&other.variant_order())
169
+ }
170
+ }
@@ -0,0 +1,63 @@
1
+ use super::{token_source::TokenSource, ErrorResponse, FromValue, MiddlewareLayer};
2
+ use crate::{
3
+ server::http_message_types::{HttpRequest, HttpResponse, RequestExt},
4
+ services::itsi_http_service::HttpRequestContext,
5
+ };
6
+ use async_trait::async_trait;
7
+ use either::Either;
8
+ use itsi_error::ItsiError;
9
+ use magnus::error::Result;
10
+ use regex::RegexSet;
11
+ use serde::Deserialize;
12
+ use std::{collections::HashMap, sync::OnceLock};
13
+ use tracing::debug;
14
+
15
+ #[derive(Debug, Clone, Deserialize)]
16
+ pub struct AllowList {
17
+ #[serde(skip_deserializing)]
18
+ pub allowed_ips: OnceLock<RegexSet>,
19
+ pub allowed_patterns: Vec<String>,
20
+ pub trusted_proxies: HashMap<String, TokenSource>,
21
+ #[serde(default = "forbidden_error_response")]
22
+ pub error_response: ErrorResponse,
23
+ }
24
+
25
+ fn forbidden_error_response() -> ErrorResponse {
26
+ ErrorResponse::forbidden()
27
+ }
28
+
29
+ #[async_trait]
30
+ impl MiddlewareLayer for AllowList {
31
+ async fn initialize(&self) -> Result<()> {
32
+ let allowed_ips = RegexSet::new(&self.allowed_patterns).map_err(ItsiError::new)?;
33
+ self.allowed_ips
34
+ .set(allowed_ips)
35
+ .map_err(|e| ItsiError::new(format!("Failed to set allowed IPs: {:?}", e)))?;
36
+ Ok(())
37
+ }
38
+
39
+ async fn before(
40
+ &self,
41
+ req: HttpRequest,
42
+ context: &mut HttpRequestContext,
43
+ ) -> Result<Either<HttpRequest, HttpResponse>> {
44
+ if let Some(allowed_ips) = self.allowed_ips.get() {
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
+ };
51
+ if !allowed_ips.is_match(addr) {
52
+ debug!(target: "middleware::allow_list", "IP address {} is not allowed", addr);
53
+ return Ok(Either::Right(
54
+ self.error_response
55
+ .to_http_response(req.accept().into())
56
+ .await,
57
+ ));
58
+ }
59
+ }
60
+ Ok(Either::Left(req))
61
+ }
62
+ }
63
+ impl FromValue for AllowList {}
@@ -0,0 +1,94 @@
1
+ use std::collections::HashMap;
2
+
3
+ use crate::{
4
+ server::http_message_types::{HttpRequest, HttpResponse, RequestExt},
5
+ services::{itsi_http_service::HttpRequestContext, password_hasher},
6
+ };
7
+
8
+ use super::{error_response::ErrorResponse, token_source::TokenSource, FromValue, MiddlewareLayer};
9
+
10
+ use async_trait::async_trait;
11
+ use either::Either;
12
+ use magnus::error::Result;
13
+ use serde::Deserialize;
14
+ use tracing::debug;
15
+
16
+ type PasswordHash = String;
17
+
18
+ /// A simple API key filter.
19
+ /// The API key can be given inside the header or a query string
20
+ /// Keys are validated against a list of allowed key values (Changing these requires a restart)
21
+ #[derive(Debug, Clone, Deserialize)]
22
+ pub struct AuthAPIKey {
23
+ pub valid_keys: HashMap<String, PasswordHash>,
24
+ pub key_id_source: Option<TokenSource>,
25
+ pub token_source: TokenSource,
26
+ #[serde(default = "unauthorized_error_response")]
27
+ pub error_response: ErrorResponse,
28
+ }
29
+
30
+ fn unauthorized_error_response() -> ErrorResponse {
31
+ ErrorResponse::unauthorized()
32
+ }
33
+
34
+ #[async_trait]
35
+ impl MiddlewareLayer for AuthAPIKey {
36
+ async fn before(
37
+ &self,
38
+ req: HttpRequest,
39
+ _context: &mut HttpRequestContext,
40
+ ) -> Result<Either<HttpRequest, HttpResponse>> {
41
+ if let Some(submitted_key) = match &self.token_source {
42
+ TokenSource::Header { name, prefix } => {
43
+ if let Some(header) = req.header(name) {
44
+ if let Some(prefix) = prefix {
45
+ Some(header.strip_prefix(prefix).unwrap_or("").trim_ascii())
46
+ } else {
47
+ Some(header.trim_ascii())
48
+ }
49
+ } else {
50
+ None
51
+ }
52
+ }
53
+ TokenSource::Query(query_name) => req.query_param(query_name),
54
+ } {
55
+ debug!(target: "middleware::auth_api_key", "API Key Retrieved. Anonymous {}", self.key_id_source.is_none());
56
+
57
+ if let Some(key_id) = self.key_id_source.as_ref() {
58
+ let key_id = match &key_id {
59
+ TokenSource::Header { name, prefix } => {
60
+ if let Some(header) = req.header(name) {
61
+ if let Some(prefix) = prefix {
62
+ Some(header.strip_prefix(prefix).unwrap_or("").trim_ascii())
63
+ } else {
64
+ Some(header.trim_ascii())
65
+ }
66
+ } else {
67
+ None
68
+ }
69
+ }
70
+ TokenSource::Query(query_name) => req.query_param(query_name),
71
+ };
72
+ debug!(target: "middleware::auth_api_key", "Key ID Retrieved");
73
+ if let Some(hash) = key_id.and_then(|kid| self.valid_keys.get(kid)) {
74
+ debug!(target: "middleware::auth_api_key", "Key for ID found");
75
+ if password_hasher::verify_password_hash(submitted_key, hash).is_ok_and(|v| v) {
76
+ return Ok(Either::Left(req));
77
+ }
78
+ }
79
+ } else if self.valid_keys.values().any(|key| {
80
+ password_hasher::verify_password_hash(submitted_key, key).is_ok_and(|v| v)
81
+ }) {
82
+ return Ok(Either::Left(req));
83
+ }
84
+ }
85
+
86
+ debug!(target: "middleware::auth_api_key", "Failed to authenticate API key");
87
+ Ok(Either::Right(
88
+ self.error_response
89
+ .to_http_response(req.accept().into())
90
+ .await,
91
+ ))
92
+ }
93
+ }
94
+ impl FromValue for AuthAPIKey {}
@@ -0,0 +1,93 @@
1
+ use async_trait::async_trait;
2
+ use base64::{engine::general_purpose, Engine};
3
+ use bytes::Bytes;
4
+ use either::Either;
5
+ use http::{Response, StatusCode};
6
+ use magnus::error::Result;
7
+ use serde::{Deserialize, Serialize};
8
+ use std::collections::HashMap;
9
+ use std::str;
10
+ use tracing::debug;
11
+
12
+ use crate::{
13
+ server::http_message_types::{HttpBody, HttpRequest, HttpResponse, RequestExt},
14
+ services::{itsi_http_service::HttpRequestContext, password_hasher::verify_password_hash},
15
+ };
16
+
17
+ use super::{FromValue, MiddlewareLayer};
18
+
19
+ type PasswordHash = String;
20
+
21
+ #[derive(Debug, Clone, Serialize, Deserialize)]
22
+ pub struct AuthBasic {
23
+ pub realm: String,
24
+ /// Maps usernames to passwords.
25
+ pub credential_pairs: HashMap<String, PasswordHash>,
26
+ }
27
+
28
+ impl AuthBasic {
29
+ fn basic_auth_failed_response(&self) -> HttpResponse {
30
+ Response::builder()
31
+ .status(StatusCode::UNAUTHORIZED)
32
+ .header(
33
+ "WWW-Authenticate",
34
+ format!("Basic realm=\"{}\"", self.realm),
35
+ )
36
+ .body(HttpBody::full(Bytes::from("Unauthorized")))
37
+ .unwrap()
38
+ }
39
+ }
40
+ #[async_trait]
41
+ impl MiddlewareLayer for AuthBasic {
42
+ async fn before(
43
+ &self,
44
+ req: HttpRequest,
45
+ _context: &mut HttpRequestContext,
46
+ ) -> Result<Either<HttpRequest, HttpResponse>> {
47
+ // Retrieve the Authorization header.
48
+ let auth_header = req.header("Authorization");
49
+
50
+ if !auth_header.is_some_and(|header| header.starts_with("Basic ")) {
51
+ debug!(target: "middleware::auth_basic", "Basic auth failed. Authorization Header doesn't start with 'Basic '");
52
+ return Ok(Either::Right(self.basic_auth_failed_response()));
53
+ }
54
+
55
+ let auth_header = auth_header.unwrap();
56
+
57
+ let encoded_credentials = &auth_header["Basic ".len()..];
58
+ let decoded = match general_purpose::STANDARD.decode(encoded_credentials) {
59
+ Ok(bytes) => bytes,
60
+ Err(_) => {
61
+ debug!(target: "middleware::auth_basic", "Basic auth failed. Decoding failed");
62
+ return Ok(Either::Right(self.basic_auth_failed_response()));
63
+ }
64
+ };
65
+
66
+ let decoded_str = match str::from_utf8(&decoded) {
67
+ Ok(s) => s,
68
+ Err(_) => {
69
+ debug!(target: "middleware::auth_basic", "Basic auth failed. Decoding failed");
70
+ return Ok(Either::Right(self.basic_auth_failed_response()));
71
+ }
72
+ };
73
+
74
+ let mut parts = decoded_str.splitn(2, ':');
75
+ let username = parts.next().unwrap_or("");
76
+ let password = parts.next().unwrap_or("");
77
+
78
+ match self.credential_pairs.get(username) {
79
+ Some(expected_password_hash) => {
80
+ match verify_password_hash(password, expected_password_hash) {
81
+ Ok(true) => Ok(Either::Left(req)),
82
+ _ => Ok(Either::Right(self.basic_auth_failed_response())),
83
+ }
84
+ }
85
+ None => {
86
+ debug!(target: "middleware::auth_basic", "Basic auth failed. Username {} not found", username);
87
+ Ok(Either::Right(self.basic_auth_failed_response()))
88
+ }
89
+ }
90
+ }
91
+ }
92
+
93
+ impl FromValue for AuthBasic {}