itsi-server 0.1.19 → 0.1.20
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/Cargo.lock +950 -239
- data/README.md +2 -0
- data/exe/itsi +5 -5
- data/ext/itsi_acme/Cargo.toml +86 -0
- data/ext/itsi_acme/examples/high_level.rs +63 -0
- data/ext/itsi_acme/examples/high_level_warp.rs +52 -0
- data/ext/itsi_acme/examples/low_level.rs +87 -0
- data/ext/itsi_acme/examples/low_level_axum.rs +66 -0
- data/ext/itsi_acme/src/acceptor.rs +81 -0
- data/ext/itsi_acme/src/acme.rs +354 -0
- data/ext/itsi_acme/src/axum.rs +86 -0
- data/ext/itsi_acme/src/cache.rs +39 -0
- data/ext/itsi_acme/src/caches/boxed.rs +80 -0
- data/ext/itsi_acme/src/caches/composite.rs +69 -0
- data/ext/itsi_acme/src/caches/dir.rs +106 -0
- data/ext/itsi_acme/src/caches/mod.rs +11 -0
- data/ext/itsi_acme/src/caches/no.rs +78 -0
- data/ext/itsi_acme/src/caches/test.rs +136 -0
- data/ext/itsi_acme/src/config.rs +172 -0
- data/ext/itsi_acme/src/https_helper.rs +69 -0
- data/ext/itsi_acme/src/incoming.rs +142 -0
- data/ext/itsi_acme/src/jose.rs +161 -0
- data/ext/itsi_acme/src/lib.rs +142 -0
- data/ext/itsi_acme/src/resolver.rs +59 -0
- data/ext/itsi_acme/src/state.rs +424 -0
- data/ext/itsi_server/Cargo.toml +3 -3
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +2 -2
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +150 -19
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +1 -0
- data/ext/itsi_server/src/server/binds/listener.rs +34 -29
- data/ext/itsi_server/src/server/binds/tls/locked_dir_cache.rs +2 -2
- data/ext/itsi_server/src/server/binds/tls.rs +1 -1
- data/ext/itsi_server/src/server/middleware_stack/middleware.rs +33 -28
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +56 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +179 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +25 -2
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +3 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +2 -1
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +32 -34
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +10 -4
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +30 -7
- data/ext/itsi_server/src/server/thread_worker.rs +2 -2
- data/ext/itsi_server/src/services/static_file_server.rs +30 -28
- data/ext/itsi_tracing/src/lib.rs +39 -8
- data/lib/itsi/server/config/config_helpers.rb +93 -0
- data/lib/itsi/server/config/dsl.rb +81 -33
- data/lib/itsi/server/config/known_paths/KitchensinkDirectories.txt +2346 -0
- data/lib/itsi/server/config/known_paths/Randomfiles.txt +24 -0
- data/lib/itsi/server/config/known_paths/UnixDotfiles.txt +52 -0
- data/lib/itsi/server/config/known_paths/backdoors/ASP_CommonBackdoors.txt +29 -0
- data/lib/itsi/server/config/known_paths/backdoors/bot_control_panels.txt +1668 -0
- data/lib/itsi/server/config/known_paths/backdoors/shells.txt +1167 -0
- data/lib/itsi/server/config/known_paths/cgi/CGI_HTTP_POST.txt +7 -0
- data/lib/itsi/server/config/known_paths/cgi/CGI_HTTP_POST_Windows.txt +6 -0
- data/lib/itsi/server/config/known_paths/cgi/CGI_Microsoft.txt +79 -0
- data/lib/itsi/server/config/known_paths/cgi/CGI_XPlatform.txt +3948 -0
- data/lib/itsi/server/config/known_paths/cms/README.md +5 -0
- data/lib/itsi/server/config/known_paths/cms/drupal_plugins.txt +6320 -0
- data/lib/itsi/server/config/known_paths/cms/drupal_themes.txt +828 -0
- data/lib/itsi/server/config/known_paths/cms/joomla_plugins.txt +224 -0
- data/lib/itsi/server/config/known_paths/cms/joomla_themes.txt +30 -0
- data/lib/itsi/server/config/known_paths/cms/php-nuke.txt +2142 -0
- data/lib/itsi/server/config/known_paths/cms/wordpress.txt +1566 -0
- data/lib/itsi/server/config/known_paths/cms/wp_common_theme_files.txt +46 -0
- data/lib/itsi/server/config/known_paths/cms/wp_plugins.txt +13366 -0
- data/lib/itsi/server/config/known_paths/cms/wp_plugins_full.txt +68662 -0
- data/lib/itsi/server/config/known_paths/cms/wp_plugins_top225.txt +225 -0
- data/lib/itsi/server/config/known_paths/cms/wp_themes.readme +12 -0
- data/lib/itsi/server/config/known_paths/cms/wp_themes.txt +7336 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/3CharExtBrute.txt +17576 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/CommonWebExtensions.txt +80 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/Extensions.Backup.txt +14 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/Extensions.Common.txt +865 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/Extensions.Compressed.txt +186 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/Extensions.Mostcommon.txt +30 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/Extensions.Skipfish.txt +93 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/WordlistSkipfish.txt +1918 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/copy_of.txt +8 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-directories-lowercase.txt +56180 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-directories.txt +62290 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-extensions-lowercase.txt +2367 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-extensions.txt +2450 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-files-lowercase.txt +35323 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-files.txt +37037 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-words-lowercase.txt +107982 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-words.txt +119600 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-directories-lowercase.txt +26593 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-directories.txt +30009 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-extensions-lowercase.txt +1233 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-extensions.txt +1289 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-files-lowercase.txt +16243 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-files.txt +17128 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-words-lowercase.txt +56293 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-words.txt +63087 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-directories-lowercase.txt +17776 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-directories.txt +20122 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-extensions-lowercase.txt +914 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-extensions.txt +963 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-files-lowercase.txt +10848 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-files.txt +11424 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-words-lowercase.txt +38267 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-words.txt +43003 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/spanish.txt +445 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/test_demo.txt +36 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/upload_variants.txt +44 -0
- data/lib/itsi/server/config/known_paths/login-file-locations/Logins.txt +71 -0
- data/lib/itsi/server/config/known_paths/login-file-locations/cfm.txt +294 -0
- data/lib/itsi/server/config/known_paths/login-file-locations/html.txt +295 -0
- data/lib/itsi/server/config/known_paths/login-file-locations/jsp.txt +294 -0
- data/lib/itsi/server/config/known_paths/login-file-locations/php.txt +294 -0
- data/lib/itsi/server/config/known_paths/login-file-locations/windows-asp.txt +294 -0
- data/lib/itsi/server/config/known_paths/login-file-locations/windows-aspx.txt +294 -0
- data/lib/itsi/server/config/known_paths/password-file-locations/Passwords.txt +47 -0
- data/lib/itsi/server/config/known_paths/php/PHP.txt +30 -0
- data/lib/itsi/server/config/known_paths/php/PHP_CommonBackdoors.txt +5 -0
- data/lib/itsi/server/config/known_paths/proxy-conf.txt +31 -0
- data/lib/itsi/server/config/known_paths/tftp.txt +79 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/ADFS.txt +86 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/AdobeXML.txt +16 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Apache.txt +101 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/ApacheTomcat.txt +47 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Apache_Axis.txt +16 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/ColdFusion.txt +111 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/FatwireCMS.txt +390 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Frontpage.txt +38 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/HP_System_Mgmt_Homepage.txt +239 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/HTTP_POST_Microsoft.txt +2 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Hyperion.txt +578 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/IIS.txt +187 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/JBoss.txt +5 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/JRun.txt +13 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/JavaServlets_Common.txt +3 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Joomla_exploitable.txt +1937 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/LotusNotes.txt +206 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Netware.txt +18 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Oracle9i.txt +60 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/OracleAppServer.txt +192 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/README.md +6 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Ruby_Rails.txt +121 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/SAP.txt +463 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Sharepoint.txt +1707 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/SiteMinder.txt +19 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/SunAppServerGlassfish.txt +51 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/SuniPlanet.txt +35 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Vignette.txt +73 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Weblogic.txt +160 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Websphere.txt +366 -0
- data/lib/itsi/server/config/known_paths/wellknown-rfc5785.txt +30 -0
- data/lib/itsi/server/config/known_paths.rb +17 -0
- data/lib/itsi/server/config/middleware/_index.md +54 -0
- data/lib/itsi/server/config/middleware/log_requests.md +63 -0
- data/lib/itsi/server/config/middleware/log_requests.rb +33 -0
- data/lib/itsi/server/config/middleware.rb +9 -0
- data/lib/itsi/server/config/option.rb +9 -0
- data/lib/itsi/server/config/options/_index.md +36 -0
- data/lib/itsi/server/config/options/fiber_scheduler.md +35 -0
- data/lib/itsi/server/config/options/fiber_scheduler.rb +18 -0
- data/lib/itsi/server/config/options/threads.md +39 -0
- data/lib/itsi/server/config/options/threads.rb +17 -0
- data/lib/itsi/server/config/options/workers.md +43 -0
- data/lib/itsi/server/config/options/workers.rb +17 -0
- data/lib/itsi/server/config/typed_struct.rb +203 -0
- data/lib/itsi/server/config.rb +124 -30
- data/lib/itsi/server/signal_trap.rb +5 -1
- data/lib/itsi/server/typed_handlers/source_parser.rb +1 -1
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +27 -6
- data/lib/ruby_lsp/itsi/addon.rb +64 -48
- metadata +141 -5
- data/CHANGELOG.md +0 -10
- data/CODE_OF_CONDUCT.md +0 -139
- data/LICENSE.txt +0 -21
- data/_index.md +0 -6
@@ -18,7 +18,7 @@ use std::{
|
|
18
18
|
collections::{HashMap, HashSet},
|
19
19
|
sync::OnceLock,
|
20
20
|
};
|
21
|
-
use tracing::error;
|
21
|
+
use tracing::{debug, error};
|
22
22
|
|
23
23
|
#[derive(Debug, Clone, Deserialize)]
|
24
24
|
pub struct AuthJwt {
|
@@ -137,6 +137,11 @@ struct Claims {
|
|
137
137
|
#[async_trait]
|
138
138
|
impl MiddlewareLayer for AuthJwt {
|
139
139
|
async fn initialize(&self) -> Result<()> {
|
140
|
+
debug!(
|
141
|
+
target: "middleware::auth_jwt",
|
142
|
+
"Instantiating auth_jwt with {} verifiers", self.verifiers.len()
|
143
|
+
);
|
144
|
+
|
140
145
|
let keys: HashMap<JwtAlgorithm, Vec<DecodingKey>> = self
|
141
146
|
.verifiers
|
142
147
|
.iter()
|
@@ -145,10 +150,24 @@ impl MiddlewareLayer for AuthJwt {
|
|
145
150
|
let keys: itsi_error::Result<Vec<DecodingKey>> = key_strings
|
146
151
|
.iter()
|
147
152
|
.map(|key_string| algorithm.key_from(key_string))
|
153
|
+
.inspect(|key_result| {
|
154
|
+
if key_result.is_err() {
|
155
|
+
debug!(
|
156
|
+
target: "middleware::auth_jwt",
|
157
|
+
"Failed to load key for algorithm {:?}", algorithm
|
158
|
+
)
|
159
|
+
} else {
|
160
|
+
debug!(
|
161
|
+
target: "middleware::auth_jwt",
|
162
|
+
"Loaded key for algorithm {:?}", algorithm
|
163
|
+
)
|
164
|
+
}
|
165
|
+
})
|
148
166
|
.collect();
|
149
167
|
keys.map(|keys| (algo, keys))
|
150
168
|
})
|
151
169
|
.collect::<itsi_error::Result<HashMap<JwtAlgorithm, Vec<DecodingKey>>>>()?;
|
170
|
+
|
152
171
|
self.keys
|
153
172
|
.set(keys)
|
154
173
|
.map_err(|_| ItsiError::new("Failed to set keys"))?;
|
@@ -158,11 +177,16 @@ impl MiddlewareLayer for AuthJwt {
|
|
158
177
|
async fn before(
|
159
178
|
&self,
|
160
179
|
req: HttpRequest,
|
161
|
-
|
180
|
+
_: &mut HttpRequestContext,
|
162
181
|
) -> Result<Either<HttpRequest, HttpResponse>> {
|
163
182
|
// Retrieve the JWT token from either a header or a query parameter.
|
164
183
|
let token_str = match &self.token_source {
|
165
184
|
TokenSource::Header { name, prefix } => {
|
185
|
+
debug!(
|
186
|
+
target: "middleware::auth_jwt",
|
187
|
+
"Extracting JWT from header: {}, prefix: {:?}",
|
188
|
+
name, prefix
|
189
|
+
);
|
166
190
|
if let Some(header) = req.header(name) {
|
167
191
|
if let Some(prefix) = prefix {
|
168
192
|
Some(header.strip_prefix(prefix).unwrap_or("").trim_ascii())
|
@@ -173,10 +197,21 @@ impl MiddlewareLayer for AuthJwt {
|
|
173
197
|
None
|
174
198
|
}
|
175
199
|
}
|
176
|
-
TokenSource::Query(query_name) =>
|
200
|
+
TokenSource::Query(query_name) => {
|
201
|
+
debug!(
|
202
|
+
target: "middleware::auth_jwt",
|
203
|
+
"Extracting JWT from query parameter: {}",
|
204
|
+
query_name
|
205
|
+
);
|
206
|
+
req.query_param(query_name)
|
207
|
+
}
|
177
208
|
};
|
178
209
|
|
179
210
|
if token_str.is_none() {
|
211
|
+
debug!(
|
212
|
+
target: "middleware::auth_jwt",
|
213
|
+
"No JWT found in headers or query parameters"
|
214
|
+
);
|
180
215
|
return Ok(Either::Right(
|
181
216
|
self.error_response
|
182
217
|
.to_http_response(req.accept().into())
|
@@ -186,8 +221,13 @@ impl MiddlewareLayer for AuthJwt {
|
|
186
221
|
let token_str = token_str.unwrap();
|
187
222
|
let header =
|
188
223
|
decode_header(token_str).map_err(|_| ItsiError::new("Invalid token header"))?;
|
224
|
+
|
189
225
|
let alg: JwtAlgorithm = header.alg.into();
|
190
226
|
|
227
|
+
debug!(
|
228
|
+
target: "middleware::auth_jwt",
|
229
|
+
"Matched algorithm {:?}", alg
|
230
|
+
);
|
191
231
|
if !self.verifiers.contains_key(&alg) {
|
192
232
|
return Ok(Either::Right(
|
193
233
|
self.error_response
|
@@ -225,6 +265,7 @@ impl MiddlewareLayer for AuthJwt {
|
|
225
265
|
None
|
226
266
|
}
|
227
267
|
});
|
268
|
+
|
228
269
|
let token_data = if let Some(data) = token_data {
|
229
270
|
data
|
230
271
|
} else {
|
@@ -244,6 +285,10 @@ impl MiddlewareLayer for AuthJwt {
|
|
244
285
|
Audience::Multiple(v) => v.iter().cloned().collect(),
|
245
286
|
};
|
246
287
|
if expected_audiences.is_disjoint(&token_auds) {
|
288
|
+
debug!(
|
289
|
+
"AUD check failed, token_auds: {:?}, expected_audiences: {:?}",
|
290
|
+
token_auds, expected_audiences
|
291
|
+
);
|
247
292
|
return Ok(Either::Right(
|
248
293
|
self.error_response
|
249
294
|
.to_http_response(req.accept().into())
|
@@ -256,6 +301,10 @@ impl MiddlewareLayer for AuthJwt {
|
|
256
301
|
if let Some(expected_subjects) = &self.subjects {
|
257
302
|
if let Some(sub) = &claims.sub {
|
258
303
|
if !expected_subjects.contains(sub) {
|
304
|
+
debug!(
|
305
|
+
"SUB check failed, token_sub: {:?}, expected_subjects: {:?}",
|
306
|
+
sub, expected_subjects
|
307
|
+
);
|
259
308
|
return Ok(Either::Right(
|
260
309
|
self.error_response
|
261
310
|
.to_http_response(req.accept().into())
|
@@ -269,6 +318,10 @@ impl MiddlewareLayer for AuthJwt {
|
|
269
318
|
if let Some(expected_issuers) = &self.issuers {
|
270
319
|
if let Some(iss) = &claims.iss {
|
271
320
|
if !expected_issuers.contains(iss) {
|
321
|
+
debug!(
|
322
|
+
"ISS check failed, token_iss: {:?}, expected_issuers: {:?}",
|
323
|
+
iss, expected_issuers
|
324
|
+
);
|
272
325
|
return Ok(Either::Right(
|
273
326
|
self.error_response
|
274
327
|
.to_http_response(req.accept().into())
|
@@ -0,0 +1,179 @@
|
|
1
|
+
use super::FromValue;
|
2
|
+
use crate::{
|
3
|
+
server::http_message_types::{HttpRequest, HttpResponse},
|
4
|
+
services::itsi_http_service::HttpRequestContext,
|
5
|
+
};
|
6
|
+
use async_trait::async_trait;
|
7
|
+
use bytes::{Bytes, BytesMut};
|
8
|
+
use either::Either;
|
9
|
+
use futures::TryStreamExt;
|
10
|
+
use http::{HeaderValue, StatusCode};
|
11
|
+
use http_body_util::{combinators::BoxBody, BodyExt, Empty};
|
12
|
+
use itsi_error::ItsiError;
|
13
|
+
use serde::{Deserialize, Serialize};
|
14
|
+
use std::sync::Arc;
|
15
|
+
use std::{path::PathBuf, sync::OnceLock};
|
16
|
+
use tokio::sync::Mutex;
|
17
|
+
use tokio::time::{self, Duration};
|
18
|
+
|
19
|
+
#[derive(Debug, Serialize, Deserialize)]
|
20
|
+
pub struct CspReport {
|
21
|
+
#[serde(rename = "csp-report")]
|
22
|
+
pub report: ReportDetails,
|
23
|
+
}
|
24
|
+
|
25
|
+
#[derive(Debug, Serialize, Deserialize)]
|
26
|
+
pub struct ReportDetails {
|
27
|
+
#[serde(rename = "document-uri")]
|
28
|
+
pub document_uri: String,
|
29
|
+
#[serde(rename = "referrer")]
|
30
|
+
pub referrer: Option<String>,
|
31
|
+
#[serde(rename = "violated-directive")]
|
32
|
+
pub violated_directive: String,
|
33
|
+
#[serde(rename = "original-policy")]
|
34
|
+
pub original_policy: String,
|
35
|
+
#[serde(rename = "blocked-uri")]
|
36
|
+
pub blocked_uri: String,
|
37
|
+
}
|
38
|
+
|
39
|
+
#[derive(Debug, Deserialize)]
|
40
|
+
pub struct CspConfig {
|
41
|
+
pub default_src: Vec<String>,
|
42
|
+
pub script_src: Vec<String>,
|
43
|
+
pub style_src: Vec<String>,
|
44
|
+
pub report_uri: Vec<String>,
|
45
|
+
}
|
46
|
+
|
47
|
+
#[derive(Debug, Deserialize)]
|
48
|
+
pub struct Csp {
|
49
|
+
pub policy_input: Option<CspConfig>,
|
50
|
+
pub reporting_enabled: bool,
|
51
|
+
pub report_file: Option<PathBuf>,
|
52
|
+
pub report_endpoint: String,
|
53
|
+
pub flush_interval: u64,
|
54
|
+
|
55
|
+
#[serde(skip)]
|
56
|
+
pub computed_policy: OnceLock<String>,
|
57
|
+
#[serde(skip)]
|
58
|
+
pub pending_reports: Arc<Mutex<Vec<CspReport>>>,
|
59
|
+
#[serde(skip)]
|
60
|
+
pub flush_task: OnceLock<tokio::task::JoinHandle<()>>,
|
61
|
+
}
|
62
|
+
|
63
|
+
#[async_trait]
|
64
|
+
impl super::MiddlewareLayer for Csp {
|
65
|
+
async fn initialize(&self) -> Result<(), magnus::error::Error> {
|
66
|
+
if let Some(policy_config) = &self.policy_input {
|
67
|
+
let mut parts = Vec::new();
|
68
|
+
if !policy_config.default_src.is_empty() {
|
69
|
+
parts.push(format!(
|
70
|
+
"default-src {}",
|
71
|
+
policy_config.default_src.join(" ")
|
72
|
+
));
|
73
|
+
}
|
74
|
+
if !policy_config.script_src.is_empty() {
|
75
|
+
parts.push(format!("script-src {}", policy_config.script_src.join(" ")));
|
76
|
+
}
|
77
|
+
if !policy_config.style_src.is_empty() {
|
78
|
+
parts.push(format!("style-src {}", policy_config.style_src.join(" ")));
|
79
|
+
}
|
80
|
+
if !policy_config.report_uri.is_empty() {
|
81
|
+
parts.push(format!("report-uri {}", policy_config.report_uri.join(" ")));
|
82
|
+
}
|
83
|
+
let policy = parts.join("; ");
|
84
|
+
self.computed_policy
|
85
|
+
.set(policy)
|
86
|
+
.map_err(|_| ItsiError::new("Failed to set computed CSP policy"))?;
|
87
|
+
}
|
88
|
+
|
89
|
+
if self.reporting_enabled {
|
90
|
+
if let Some(ref report_file) = self.report_file {
|
91
|
+
let flush_interval = self.flush_interval;
|
92
|
+
let report_path = report_file.clone();
|
93
|
+
let pending_reports = Arc::clone(&self.pending_reports);
|
94
|
+
let handle = tokio::spawn(async move {
|
95
|
+
let mut interval = time::interval(Duration::from_secs(flush_interval));
|
96
|
+
loop {
|
97
|
+
interval.tick().await;
|
98
|
+
|
99
|
+
let mut reports = pending_reports.lock().await;
|
100
|
+
if !reports.is_empty() {
|
101
|
+
let mut lines = String::new();
|
102
|
+
for report in reports.iter() {
|
103
|
+
if let Ok(line) = serde_json::to_string(report) {
|
104
|
+
lines.push_str(&line);
|
105
|
+
lines.push('\n');
|
106
|
+
}
|
107
|
+
}
|
108
|
+
reports.clear();
|
109
|
+
if let Err(e) = tokio::fs::OpenOptions::new()
|
110
|
+
.append(true)
|
111
|
+
.create(true)
|
112
|
+
.open(&report_path)
|
113
|
+
.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
|
+
{
|
120
|
+
eprintln!("Error writing CSP reports: {:?}", e);
|
121
|
+
}
|
122
|
+
}
|
123
|
+
}
|
124
|
+
});
|
125
|
+
self.flush_task
|
126
|
+
.set(handle)
|
127
|
+
.map_err(|_| ItsiError::new("Failed to set flush task handle"))?;
|
128
|
+
}
|
129
|
+
}
|
130
|
+
Ok(())
|
131
|
+
}
|
132
|
+
|
133
|
+
async fn before(
|
134
|
+
&self,
|
135
|
+
req: HttpRequest,
|
136
|
+
_context: &mut HttpRequestContext,
|
137
|
+
) -> Result<Either<HttpRequest, HttpResponse>, magnus::error::Error> {
|
138
|
+
if self.reporting_enabled && req.uri().path() == self.report_endpoint {
|
139
|
+
let full_bytes: Result<Bytes, _> = req
|
140
|
+
.into_body()
|
141
|
+
.into_data_stream()
|
142
|
+
.try_fold(BytesMut::new(), |mut acc, chunk| async move {
|
143
|
+
acc.extend_from_slice(&chunk);
|
144
|
+
Ok(acc)
|
145
|
+
})
|
146
|
+
.await
|
147
|
+
.map(|b| b.freeze());
|
148
|
+
|
149
|
+
if let Ok(body_bytes) = full_bytes {
|
150
|
+
if let Ok(report) = serde_json::from_slice::<CspReport>(&body_bytes) {
|
151
|
+
let mut pending = self.pending_reports.lock().await;
|
152
|
+
pending.push(report);
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
let mut resp = HttpResponse::new(BoxBody::new(Empty::new()));
|
157
|
+
*resp.status_mut() = StatusCode::NO_CONTENT;
|
158
|
+
return Ok(Either::Right(resp));
|
159
|
+
}
|
160
|
+
Ok(Either::Left(req))
|
161
|
+
}
|
162
|
+
|
163
|
+
async fn after(&self, resp: HttpResponse, _context: &mut HttpRequestContext) -> HttpResponse {
|
164
|
+
if let Some(policy) = self.computed_policy.get() {
|
165
|
+
if !resp.headers().contains_key("Content-Security-Policy") {
|
166
|
+
let (mut parts, body) = resp.into_parts();
|
167
|
+
if let Ok(header_value) = HeaderValue::from_str(policy) {
|
168
|
+
parts
|
169
|
+
.headers
|
170
|
+
.insert("Content-Security-Policy", header_value);
|
171
|
+
}
|
172
|
+
return HttpResponse::from_parts(parts, body);
|
173
|
+
}
|
174
|
+
}
|
175
|
+
resp
|
176
|
+
}
|
177
|
+
}
|
178
|
+
|
179
|
+
impl FromValue for Csp {}
|
@@ -5,6 +5,7 @@ mod auth_jwt;
|
|
5
5
|
mod cache_control;
|
6
6
|
mod compression;
|
7
7
|
mod cors;
|
8
|
+
mod csp;
|
8
9
|
mod deny_list;
|
9
10
|
mod error_response;
|
10
11
|
mod etag;
|
@@ -23,6 +24,9 @@ mod static_response;
|
|
23
24
|
mod string_rewrite;
|
24
25
|
mod token_source;
|
25
26
|
|
27
|
+
use std::sync::Arc;
|
28
|
+
use std::sync::LazyLock;
|
29
|
+
|
26
30
|
pub use allow_list::AllowList;
|
27
31
|
use async_trait::async_trait;
|
28
32
|
pub use auth_api_key::AuthAPIKey;
|
@@ -32,6 +36,7 @@ pub use cache_control::CacheControl;
|
|
32
36
|
pub use compression::Compression;
|
33
37
|
pub use compression::CompressionAlgorithm;
|
34
38
|
pub use cors::Cors;
|
39
|
+
pub use csp::Csp;
|
35
40
|
pub use deny_list::DenyList;
|
36
41
|
use either::Either;
|
37
42
|
pub use error_response::ErrorResponse;
|
@@ -39,6 +44,7 @@ pub use etag::ETag;
|
|
39
44
|
pub use intrusion_protection::IntrusionProtection;
|
40
45
|
pub use log_requests::LogRequests;
|
41
46
|
use magnus::error::Result;
|
47
|
+
use magnus::rb_sys::AsRawValue;
|
42
48
|
use magnus::Value;
|
43
49
|
pub use max_body::MaxBody;
|
44
50
|
pub use proxy::Proxy;
|
@@ -57,11 +63,28 @@ use crate::server::http_message_types::HttpResponse;
|
|
57
63
|
use crate::services::itsi_http_service::HttpRequestContext;
|
58
64
|
|
59
65
|
pub trait FromValue: Sized + Send + Sync + 'static {
|
60
|
-
fn from_value(value: Value) -> Result<Self
|
66
|
+
fn from_value(value: Value) -> Result<Arc<Self>>
|
61
67
|
where
|
62
68
|
Self: Deserialize<'static>,
|
63
69
|
{
|
64
|
-
|
70
|
+
use std::collections::HashMap;
|
71
|
+
use std::sync::Mutex;
|
72
|
+
|
73
|
+
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
|
+
|
77
|
+
let mut cache = CACHE.lock().unwrap();
|
78
|
+
|
79
|
+
if let Some(cached) = cache.get(&raw) {
|
80
|
+
if let Some(deserialized) = cached.downcast_ref::<Arc<Self>>() {
|
81
|
+
return Ok(deserialized.clone());
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
let deserialized: Arc<Self> = Arc::new(deserialize(value)?);
|
86
|
+
cache.insert(raw, deserialized.clone());
|
87
|
+
Ok(deserialized)
|
65
88
|
}
|
66
89
|
}
|
67
90
|
|
@@ -42,7 +42,7 @@ impl FromStr for RequestType {
|
|
42
42
|
}
|
43
43
|
|
44
44
|
impl RubyApp {
|
45
|
-
pub fn from_value(params: HeapVal) -> magnus::error::Result<Self
|
45
|
+
pub fn from_value(params: HeapVal) -> magnus::error::Result<Arc<Self>> {
|
46
46
|
let app = params.funcall::<_, _, Proc>(Symbol::new("[]"), ("app_proc",))?;
|
47
47
|
let sendfile = params
|
48
48
|
.funcall::<_, _, bool>(Symbol::new("[]"), ("sendfile",))
|
@@ -61,13 +61,13 @@ impl RubyApp {
|
|
61
61
|
.parse()
|
62
62
|
.unwrap_or(RequestType::Http);
|
63
63
|
|
64
|
-
Ok(RubyApp {
|
64
|
+
Ok(Arc::new(RubyApp {
|
65
65
|
app: Arc::new(app.into()),
|
66
66
|
sendfile,
|
67
67
|
nonblocking,
|
68
68
|
request_type,
|
69
69
|
base_path,
|
70
|
-
})
|
70
|
+
}))
|
71
71
|
}
|
72
72
|
}
|
73
73
|
|
@@ -59,6 +59,7 @@ impl MiddlewareLayer for StaticAssets {
|
|
59
59
|
self.base_path_regex
|
60
60
|
.set(Regex::new(&self.base_path).map_err(ItsiError::new)?)
|
61
61
|
.map_err(ItsiError::new)?;
|
62
|
+
|
62
63
|
self.file_server
|
63
64
|
.set(StaticFileServer::new(StaticFileServerConfig {
|
64
65
|
root_dir: self.root_dir.clone(),
|
@@ -70,7 +71,7 @@ impl MiddlewareLayer for StaticAssets {
|
|
70
71
|
recheck_interval: Duration::from_secs(self.file_check_interval),
|
71
72
|
serve_hidden_files: self.serve_hidden_files,
|
72
73
|
allowed_extensions: self.allowed_extensions.clone(),
|
73
|
-
}))
|
74
|
+
})?)
|
74
75
|
.map_err(ItsiError::new)?;
|
75
76
|
Ok(())
|
76
77
|
}
|
@@ -2,12 +2,14 @@ mod middleware;
|
|
2
2
|
mod middlewares;
|
3
3
|
use http::header::{ACCEPT, CONTENT_TYPE, HOST};
|
4
4
|
use itsi_rb_helpers::HeapVal;
|
5
|
-
use magnus::{
|
5
|
+
use magnus::{
|
6
|
+
error::Result, rb_sys::AsRawValue, value::ReprValue, RArray, RHash, Ruby, TryConvert, Value,
|
7
|
+
};
|
6
8
|
pub use middleware::Middleware;
|
7
9
|
pub use middlewares::*;
|
8
10
|
use regex::{Regex, RegexSet};
|
9
11
|
use std::{collections::HashMap, sync::Arc};
|
10
|
-
use tracing::debug;
|
12
|
+
use tracing::{debug, info};
|
11
13
|
|
12
14
|
use super::http_message_types::HttpRequest;
|
13
15
|
|
@@ -16,6 +18,7 @@ pub struct MiddlewareSet {
|
|
16
18
|
pub route_set: RegexSet,
|
17
19
|
pub patterns: Vec<Arc<Regex>>,
|
18
20
|
pub stacks: HashMap<usize, MiddlewareStack>,
|
21
|
+
unique_middlewares: HashMap<u64, Middleware>,
|
19
22
|
}
|
20
23
|
|
21
24
|
#[derive(Debug)]
|
@@ -128,6 +131,7 @@ impl MiddlewareStack {
|
|
128
131
|
|
129
132
|
impl MiddlewareSet {
|
130
133
|
pub fn new(routes_raw: Option<HeapVal>) -> Result<Self> {
|
134
|
+
let mut unique_middlewares = HashMap::new();
|
131
135
|
if let Some(routes_raw) = routes_raw {
|
132
136
|
let mut stacks = HashMap::new();
|
133
137
|
let mut routes = vec![];
|
@@ -147,19 +151,30 @@ impl MiddlewareSet {
|
|
147
151
|
"Route is missing :route key",
|
148
152
|
))?
|
149
153
|
.funcall::<_, _, String>("source", ())?;
|
154
|
+
|
150
155
|
let middleware =
|
151
|
-
|
156
|
+
RHash::from_value(route_hash.get("middleware").ok_or(magnus::Error::new(
|
152
157
|
magnus::exception::standard_error(),
|
153
158
|
"Route is missing middleware key",
|
154
159
|
))?)
|
155
160
|
.ok_or(magnus::Error::new(
|
156
161
|
magnus::exception::standard_error(),
|
157
|
-
format!("middleware must be
|
162
|
+
format!("middleware must be a hash. Got {:?}", routes_raw),
|
158
163
|
))?;
|
159
164
|
|
160
165
|
let mut layers = middleware
|
161
|
-
.
|
162
|
-
.map(
|
166
|
+
.enumeratorize("each", ())
|
167
|
+
.map(|pair| {
|
168
|
+
let pair = RArray::from_value(pair.unwrap()).unwrap();
|
169
|
+
let middleware_type: String = pair.entry(0).unwrap();
|
170
|
+
let value: Value = pair.entry(1).unwrap();
|
171
|
+
info!("Parsing middleware from value {}", value);
|
172
|
+
let middleware = MiddlewareSet::parse_middleware(middleware_type, value);
|
173
|
+
if let Ok(middleware) = middleware.as_ref() {
|
174
|
+
unique_middlewares.insert(value.as_raw(), middleware.clone());
|
175
|
+
};
|
176
|
+
middleware
|
177
|
+
})
|
163
178
|
.collect::<Result<Vec<_>>>()?;
|
164
179
|
routes.push(route_raw);
|
165
180
|
layers.sort();
|
@@ -184,6 +199,7 @@ impl MiddlewareSet {
|
|
184
199
|
format!("Failed to create route set: {}", e),
|
185
200
|
)
|
186
201
|
})?,
|
202
|
+
unique_middlewares,
|
187
203
|
patterns: routes
|
188
204
|
.into_iter()
|
189
205
|
.map(|r| Regex::new(&r))
|
@@ -241,47 +257,27 @@ impl MiddlewareSet {
|
|
241
257
|
))
|
242
258
|
}
|
243
259
|
|
244
|
-
pub fn parse_middleware(
|
245
|
-
let middleware_hash = RHash::from_value(middleware).ok_or(magnus::Error::new(
|
246
|
-
magnus::exception::standard_error(),
|
247
|
-
format!("Filter must be a hash. Got {:?}", middleware),
|
248
|
-
))?;
|
249
|
-
let middleware_type: String = middleware_hash
|
250
|
-
.get("type")
|
251
|
-
.ok_or(magnus::Error::new(
|
252
|
-
magnus::exception::standard_error(),
|
253
|
-
format!("Filter must have a :type key. Got {:?}", middleware_hash),
|
254
|
-
))?
|
255
|
-
.to_string();
|
260
|
+
pub fn parse_middleware(middleware_type: String, parameters: Value) -> Result<Middleware> {
|
256
261
|
let mw_type = middleware_type.clone();
|
257
262
|
|
258
|
-
let parameters: Value = middleware_hash.get("parameters").ok_or(magnus::Error::new(
|
259
|
-
magnus::exception::standard_error(),
|
260
|
-
format!(
|
261
|
-
"Filter must have a :parameters key. Got {:?}",
|
262
|
-
middleware_hash
|
263
|
-
),
|
264
|
-
))?;
|
265
|
-
|
266
263
|
let result = (move || -> Result<Middleware> {
|
267
264
|
match mw_type.as_str() {
|
268
265
|
"allow_list" => Ok(Middleware::AllowList(AllowList::from_value(parameters)?)),
|
269
266
|
"auth_basic" => Ok(Middleware::AuthBasic(AuthBasic::from_value(parameters)?)),
|
270
|
-
"auth_jwt" => Ok(Middleware::AuthJwt(
|
271
|
-
parameters,
|
272
|
-
)?))),
|
267
|
+
"auth_jwt" => Ok(Middleware::AuthJwt(AuthJwt::from_value(parameters)?)),
|
273
268
|
"auth_api_key" => Ok(Middleware::AuthAPIKey(AuthAPIKey::from_value(parameters)?)),
|
274
269
|
"cache_control" => Ok(Middleware::CacheControl(CacheControl::from_value(
|
275
270
|
parameters,
|
276
271
|
)?)),
|
277
272
|
"deny_list" => Ok(Middleware::DenyList(DenyList::from_value(parameters)?)),
|
278
273
|
"etag" => Ok(Middleware::ETag(ETag::from_value(parameters)?)),
|
274
|
+
"csp" => Ok(Middleware::Csp(Csp::from_value(parameters)?)),
|
279
275
|
"intrusion_protection" => Ok({
|
280
276
|
Middleware::IntrusionProtection(IntrusionProtection::from_value(parameters)?)
|
281
277
|
}),
|
282
278
|
"max_body" => Ok(Middleware::MaxBody(MaxBody::from_value(parameters)?)),
|
283
279
|
"rate_limit" => Ok(Middleware::RateLimit(RateLimit::from_value(parameters)?)),
|
284
|
-
"cors" => Ok(Middleware::Cors(
|
280
|
+
"cors" => Ok(Middleware::Cors(Cors::from_value(parameters)?)),
|
285
281
|
"request_headers" => Ok(Middleware::RequestHeaders(RequestHeaders::from_value(
|
286
282
|
parameters,
|
287
283
|
)?)),
|
@@ -323,10 +319,12 @@ impl MiddlewareSet {
|
|
323
319
|
}
|
324
320
|
|
325
321
|
pub async fn initialize_layers(&self) -> Result<()> {
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
322
|
+
info!(
|
323
|
+
"Unique middleware keys: {:?}",
|
324
|
+
self.unique_middlewares.keys()
|
325
|
+
);
|
326
|
+
for middleware in self.unique_middlewares.values() {
|
327
|
+
middleware.initialize().await?;
|
330
328
|
}
|
331
329
|
Ok(())
|
332
330
|
}
|
@@ -73,13 +73,18 @@ impl ClusterMode {
|
|
73
73
|
Ok(())
|
74
74
|
}
|
75
75
|
LifecycleEvent::Restart => {
|
76
|
-
self.server_config.
|
77
|
-
|
78
|
-
|
79
|
-
|
76
|
+
if self.server_config.check_config() {
|
77
|
+
self.server_config.dup_fds()?;
|
78
|
+
self.shutdown().await.ok();
|
79
|
+
info!("Shutdown complete. Calling reload exec");
|
80
|
+
self.server_config.reload_exec()?;
|
81
|
+
}
|
80
82
|
Ok(())
|
81
83
|
}
|
82
84
|
LifecycleEvent::Reload => {
|
85
|
+
if !self.server_config.check_config() {
|
86
|
+
return Ok(());
|
87
|
+
}
|
83
88
|
let should_reexec = self.server_config.clone().reload(true)?;
|
84
89
|
if should_reexec {
|
85
90
|
self.server_config.dup_fds()?;
|
@@ -286,6 +291,7 @@ impl ClusterMode {
|
|
286
291
|
self.build_runtime().block_on(async {
|
287
292
|
let self_ref = self_ref.clone();
|
288
293
|
let mut memory_check_interval = time::interval(time::Duration::from_secs(2));
|
294
|
+
|
289
295
|
loop {
|
290
296
|
tokio::select! {
|
291
297
|
_ = receiver.changed() => {
|