itsi 0.2.26 → 0.2.27.rc1
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 +7 -3
- data/Rakefile +24 -3
- data/crates/itsi_acme/Cargo.toml +2 -1
- data/crates/itsi_acme/src/acceptor.rs +1 -1
- data/crates/itsi_acme/src/acme.rs +31 -3
- data/crates/itsi_acme/src/http_challenge.rs +81 -0
- data/crates/itsi_acme/src/https_helper.rs +3 -1
- data/crates/itsi_acme/src/jose.rs +6 -2
- data/crates/itsi_acme/src/lib.rs +2 -0
- data/crates/itsi_acme/src/resolver.rs +27 -4
- data/crates/itsi_acme/src/state.rs +183 -22
- data/crates/itsi_scheduler/Cargo.toml +1 -1
- data/crates/itsi_scheduler/src/itsi_scheduler.rs +115 -64
- data/crates/itsi_scheduler/src/lib.rs +2 -1
- data/crates/itsi_server/Cargo.toml +2 -1
- data/crates/itsi_server/src/lib.rs +15 -0
- data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +9 -0
- data/crates/itsi_server/src/ruby_types/itsi_http_response.rs +95 -0
- data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +22 -1
- data/crates/itsi_server/src/ruby_types/itsi_server.rs +100 -0
- data/crates/itsi_server/src/server/binds/listener.rs +9 -24
- data/crates/itsi_server/src/server/binds/tls.rs +372 -67
- data/crates/itsi_server/src/services/itsi_http_service.rs +46 -2
- data/gems/scheduler/Cargo.lock +4011 -527
- data/gems/scheduler/Gemfile +8 -2
- data/gems/scheduler/Gemfile.lock +107 -0
- data/gems/scheduler/Rakefile +33 -9
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/scheduler/lib/itsi/scheduler.rb +121 -6
- data/gems/scheduler/test/helpers/test_helper.rb +2 -0
- data/gems/scheduler/test/test_address_resolve.rb +8 -2
- data/gems/scheduler/test/test_itsi_scheduler.rb +80 -0
- data/gems/scheduler/test/test_timeout_after.rb +102 -0
- data/gems/server/Cargo.lock +30 -1
- data/gems/server/Gemfile +2 -0
- data/gems/server/Gemfile.lock +123 -0
- data/gems/server/Rakefile +18 -5
- data/gems/server/lib/itsi/http_request.rb +10 -0
- data/gems/server/lib/itsi/server/rack_interface.rb +45 -2
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/gems/server/lib/itsi/server.rb +24 -0
- data/gems/server/test/acme/local_acme_challenges.rb +190 -0
- data/gems/server/test/helpers/local_acme.rb +218 -0
- data/gems/server/test/helpers/test_helper.rb +7 -9
- data/gems/server/test/middleware/endpoint.rb +9 -6
- data/gems/server/test/rack/test_rack_server.rb +79 -0
- data/lib/itsi/version.rb +1 -1
- metadata +12 -6
|
@@ -23,10 +23,12 @@ use parking_lot::RwLock;
|
|
|
23
23
|
use std::{
|
|
24
24
|
collections::HashMap,
|
|
25
25
|
io,
|
|
26
|
+
io::Read,
|
|
26
27
|
ops::Deref,
|
|
27
28
|
os::{fd::FromRawFd, unix::net::UnixStream},
|
|
28
29
|
str::FromStr,
|
|
29
30
|
sync::Arc,
|
|
31
|
+
thread,
|
|
30
32
|
time::Duration,
|
|
31
33
|
};
|
|
32
34
|
use tokio::{
|
|
@@ -56,6 +58,7 @@ pub struct ResponseInner {
|
|
|
56
58
|
pub frame_writer: RwLock<Option<Sender<Bytes>>>,
|
|
57
59
|
pub response: RwLock<Option<HttpResponse>>,
|
|
58
60
|
pub hijacked_socket: RwLock<Option<UnixStream>>,
|
|
61
|
+
pub partial_hijacked_socket: RwLock<Option<UnixStream>>,
|
|
59
62
|
pub response_sender: RwLock<Option<OneshotSender<ResponseFrame>>>,
|
|
60
63
|
pub shutdown_rx: watch::Receiver<RunningPhase>,
|
|
61
64
|
pub parts: Arc<Parts>,
|
|
@@ -65,6 +68,7 @@ pub struct ResponseInner {
|
|
|
65
68
|
pub enum ResponseFrame {
|
|
66
69
|
HttpResponse(Box<HttpResponse>),
|
|
67
70
|
HijackedResponse(ItsiHttpResponse),
|
|
71
|
+
PartialHijackedResponse(ItsiHttpResponse),
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
impl ItsiHttpResponse {
|
|
@@ -81,6 +85,7 @@ impl ItsiHttpResponse {
|
|
|
81
85
|
frame_writer: RwLock::new(None),
|
|
82
86
|
response: RwLock::new(Some(Response::new(HttpBody::empty()))),
|
|
83
87
|
hijacked_socket: RwLock::new(None),
|
|
88
|
+
partial_hijacked_socket: RwLock::new(None),
|
|
84
89
|
}),
|
|
85
90
|
}
|
|
86
91
|
}
|
|
@@ -219,6 +224,42 @@ impl ItsiHttpResponse {
|
|
|
219
224
|
Ok(response)
|
|
220
225
|
}
|
|
221
226
|
|
|
227
|
+
pub async fn process_partial_hijacked_response(&self) -> Result<HttpResponse> {
|
|
228
|
+
let stream =
|
|
229
|
+
self.partial_hijacked_socket
|
|
230
|
+
.write()
|
|
231
|
+
.take()
|
|
232
|
+
.ok_or(itsi_error::ItsiError::InvalidInput(
|
|
233
|
+
"Couldn't partial hijack stream".to_owned(),
|
|
234
|
+
))?;
|
|
235
|
+
let stream = TokioUnixStream::from_std(stream).map_err(|err| {
|
|
236
|
+
itsi_error::ItsiError::InvalidInput(format!(
|
|
237
|
+
"Couldn't prepare partial hijack stream: {:?}",
|
|
238
|
+
err
|
|
239
|
+
))
|
|
240
|
+
})?;
|
|
241
|
+
|
|
242
|
+
let mut response = self.response.write().take().ok_or(
|
|
243
|
+
itsi_error::ItsiError::InvalidInput("Missing partial hijack response".to_owned()),
|
|
244
|
+
)?;
|
|
245
|
+
let parts = self.parts.clone();
|
|
246
|
+
|
|
247
|
+
tokio::spawn(async move {
|
|
248
|
+
let mut req = Request::from_parts((*parts).clone(), Empty::<Bytes>::new());
|
|
249
|
+
match hyper::upgrade::on(&mut req).await {
|
|
250
|
+
Ok(upgraded) => {
|
|
251
|
+
if let Err(err) = Self::two_way_bridge(upgraded, stream).await {
|
|
252
|
+
warn!("partial hijack bridge error: {:?}", err);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
Err(err) => warn!("partial hijack upgrade error: {:?}", err),
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
*response.body_mut() = HttpBody::empty();
|
|
260
|
+
Ok(response)
|
|
261
|
+
}
|
|
262
|
+
|
|
222
263
|
pub fn internal_server_error(&self, message: String) {
|
|
223
264
|
error!(message);
|
|
224
265
|
self.close_write().ok();
|
|
@@ -304,6 +345,60 @@ impl ItsiHttpResponse {
|
|
|
304
345
|
// not implemented
|
|
305
346
|
}
|
|
306
347
|
|
|
348
|
+
pub fn partial_hijack(&self, fd: i32) -> MagnusResult<()> {
|
|
349
|
+
let mut stream = unsafe { UnixStream::from_raw_fd(fd) };
|
|
350
|
+
let status = self
|
|
351
|
+
.response
|
|
352
|
+
.read()
|
|
353
|
+
.as_ref()
|
|
354
|
+
.map(|response| response.status())
|
|
355
|
+
.unwrap_or(StatusCode::OK);
|
|
356
|
+
|
|
357
|
+
if status == StatusCode::SWITCHING_PROTOCOLS {
|
|
358
|
+
*self.partial_hijacked_socket.write() = Some(stream);
|
|
359
|
+
if let Some(sender) = self.response_sender.write().take() {
|
|
360
|
+
sender
|
|
361
|
+
.send(ResponseFrame::PartialHijackedResponse(self.clone()))
|
|
362
|
+
.ok();
|
|
363
|
+
}
|
|
364
|
+
} else if let Some(mut response) = self.response.write().take() {
|
|
365
|
+
let (writer, reader) = tokio::sync::mpsc::channel::<Bytes>(256);
|
|
366
|
+
let shutdown_rx = self.shutdown_rx.clone();
|
|
367
|
+
let frame_stream = FrameStream::new(reader, shutdown_rx);
|
|
368
|
+
let buffered =
|
|
369
|
+
BufferedStream::new(frame_stream, 32 * 1024, Duration::from_millis(10));
|
|
370
|
+
*response.body_mut() = HttpBody::stream(buffered);
|
|
371
|
+
self.frame_writer.write().replace(writer.clone());
|
|
372
|
+
|
|
373
|
+
thread::spawn(move || {
|
|
374
|
+
let mut buf = [0u8; 16 * 1024];
|
|
375
|
+
loop {
|
|
376
|
+
match stream.read(&mut buf) {
|
|
377
|
+
Ok(0) => break,
|
|
378
|
+
Ok(n) => {
|
|
379
|
+
if writer
|
|
380
|
+
.blocking_send(Bytes::copy_from_slice(&buf[..n]))
|
|
381
|
+
.is_err()
|
|
382
|
+
{
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
|
|
387
|
+
Err(_) => break,
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
if let Some(sender) = self.response_sender.write().take() {
|
|
393
|
+
sender
|
|
394
|
+
.send(ResponseFrame::HttpResponse(Box::new(response)))
|
|
395
|
+
.ok();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
Ok(())
|
|
400
|
+
}
|
|
401
|
+
|
|
307
402
|
pub fn is_closed(&self) -> bool {
|
|
308
403
|
self.response.read().is_none() && self.frame_writer.read().is_none()
|
|
309
404
|
}
|
|
@@ -2,7 +2,7 @@ use super::file_watcher::{self, WatcherCommand};
|
|
|
2
2
|
use crate::{
|
|
3
3
|
ruby_types::ITSI_SERVER_CONFIG,
|
|
4
4
|
server::{
|
|
5
|
-
binds::{bind::Bind, listener::Listener},
|
|
5
|
+
binds::{bind::Bind, listener::Listener, tls::DynamicAcmeManager},
|
|
6
6
|
middleware_stack::MiddlewareSet,
|
|
7
7
|
},
|
|
8
8
|
};
|
|
@@ -81,6 +81,8 @@ pub struct ServerParams {
|
|
|
81
81
|
pub binds: Vec<Bind>,
|
|
82
82
|
#[debug(skip)]
|
|
83
83
|
pub(crate) listeners: Mutex<Vec<Listener>>,
|
|
84
|
+
#[debug(skip)]
|
|
85
|
+
pub acme_managers: RwLock<Vec<(String, DynamicAcmeManager)>>,
|
|
84
86
|
listener_info: Mutex<HashMap<String, i32>>,
|
|
85
87
|
pub itsi_server_token_preference: ItsiServerTokenPreference,
|
|
86
88
|
pub preloaded: AtomicBool,
|
|
@@ -348,6 +350,7 @@ impl ServerParams {
|
|
|
348
350
|
preexisting_listeners,
|
|
349
351
|
listener_info: Mutex::new(HashMap::new()),
|
|
350
352
|
listeners: Mutex::new(Vec::new()),
|
|
353
|
+
acme_managers: RwLock::new(Vec::new()),
|
|
351
354
|
middleware_loader: middleware_loader.into(),
|
|
352
355
|
middleware: OnceLock::new(),
|
|
353
356
|
preloaded: AtomicBool::new(false),
|
|
@@ -401,8 +404,26 @@ impl ServerParams {
|
|
|
401
404
|
})
|
|
402
405
|
.collect::<Result<HashMap<String, i32>>>()?;
|
|
403
406
|
|
|
407
|
+
let has_http_listener = listeners
|
|
408
|
+
.iter()
|
|
409
|
+
.any(|listener| matches!(listener, Listener::Tcp(_)));
|
|
410
|
+
let mut acme_managers = Vec::new();
|
|
411
|
+
for listener in &listeners {
|
|
412
|
+
match listener {
|
|
413
|
+
Listener::TcpTls((_, acceptor)) | Listener::UnixTls((_, acceptor)) => {
|
|
414
|
+
acceptor.set_http01_enabled(has_http_listener);
|
|
415
|
+
acceptor.initialize_domains();
|
|
416
|
+
if let Some(manager) = acceptor.manager() {
|
|
417
|
+
acme_managers.push((listener.handover()?.0, manager));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
Listener::Tcp(_) | Listener::Unix(_) => {}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
404
424
|
*self.listener_info.lock() = listener_info;
|
|
405
425
|
*self.listeners.lock() = listeners;
|
|
426
|
+
*self.acme_managers.write() = acme_managers;
|
|
406
427
|
Ok(())
|
|
407
428
|
}
|
|
408
429
|
}
|
|
@@ -8,6 +8,7 @@ use itsi_server_config::ItsiServerConfig;
|
|
|
8
8
|
use itsi_tracing::{error, run_silently};
|
|
9
9
|
use magnus::{block::Proc, error::Result, RHash, Ruby};
|
|
10
10
|
use parking_lot::Mutex;
|
|
11
|
+
use std::collections::HashMap;
|
|
11
12
|
use std::{path::PathBuf, sync::Arc};
|
|
12
13
|
use tracing::{info, instrument};
|
|
13
14
|
mod file_watcher;
|
|
@@ -46,6 +47,105 @@ impl ItsiServer {
|
|
|
46
47
|
Ok(())
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
fn selected_acme_manager(
|
|
51
|
+
&self,
|
|
52
|
+
listener_id: Option<String>,
|
|
53
|
+
) -> Result<crate::server::binds::tls::DynamicAcmeManager> {
|
|
54
|
+
let config = self.config()?;
|
|
55
|
+
let server_params = config.server_params.read();
|
|
56
|
+
if server_params.workers > 1 {
|
|
57
|
+
return Err(magnus::Error::new(
|
|
58
|
+
magnus::Ruby::get().unwrap().exception_runtime_error(),
|
|
59
|
+
"Dynamic TLS domain management currently only supports single-worker mode",
|
|
60
|
+
));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let managers = server_params.acme_managers.read();
|
|
64
|
+
if managers.is_empty() {
|
|
65
|
+
return Err(magnus::Error::new(
|
|
66
|
+
magnus::Ruby::get().unwrap().exception_runtime_error(),
|
|
67
|
+
"No ACME-managed TLS bindings are configured",
|
|
68
|
+
));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if let Some(listener_id) = listener_id {
|
|
72
|
+
managers
|
|
73
|
+
.iter()
|
|
74
|
+
.find(|(id, _)| id == &listener_id)
|
|
75
|
+
.map(|(_, manager)| manager.clone())
|
|
76
|
+
.ok_or_else(|| {
|
|
77
|
+
magnus::Error::new(
|
|
78
|
+
magnus::Ruby::get().unwrap().exception_runtime_error(),
|
|
79
|
+
format!("Unknown ACME TLS binding: {}", listener_id),
|
|
80
|
+
)
|
|
81
|
+
})
|
|
82
|
+
} else if managers.len() == 1 {
|
|
83
|
+
Ok(managers[0].1.clone())
|
|
84
|
+
} else {
|
|
85
|
+
Err(magnus::Error::new(
|
|
86
|
+
magnus::Ruby::get().unwrap().exception_runtime_error(),
|
|
87
|
+
"Multiple ACME TLS bindings are configured; specify a listener_id",
|
|
88
|
+
))
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
pub fn tls_bindings(&self) -> Result<Vec<String>> {
|
|
93
|
+
let config = self.config()?;
|
|
94
|
+
let server_params = config.server_params.read();
|
|
95
|
+
let bindings = server_params
|
|
96
|
+
.acme_managers
|
|
97
|
+
.read()
|
|
98
|
+
.iter()
|
|
99
|
+
.map(|(listener_id, _)| listener_id.clone())
|
|
100
|
+
.collect();
|
|
101
|
+
Ok(bindings)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
pub fn tls_domains(&self, listener_id: Option<String>) -> Result<Vec<String>> {
|
|
105
|
+
let manager = self.selected_acme_manager(listener_id)?;
|
|
106
|
+
Ok(manager
|
|
107
|
+
.statuses()
|
|
108
|
+
.into_iter()
|
|
109
|
+
.map(|status| status.domain)
|
|
110
|
+
.collect())
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
pub fn tls_domain_statuses(
|
|
114
|
+
&self,
|
|
115
|
+
listener_id: Option<String>,
|
|
116
|
+
) -> Result<Vec<HashMap<String, String>>> {
|
|
117
|
+
let manager = self.selected_acme_manager(listener_id)?;
|
|
118
|
+
Ok(manager
|
|
119
|
+
.statuses()
|
|
120
|
+
.into_iter()
|
|
121
|
+
.map(|status| {
|
|
122
|
+
let mut out = HashMap::new();
|
|
123
|
+
out.insert("domain".to_string(), status.domain);
|
|
124
|
+
out.insert("status".to_string(), status.status);
|
|
125
|
+
if let Some(last_error) = status.last_error {
|
|
126
|
+
out.insert("last_error".to_string(), last_error);
|
|
127
|
+
}
|
|
128
|
+
out
|
|
129
|
+
})
|
|
130
|
+
.collect())
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
pub fn register_tls_domain(&self, domain: String, listener_id: Option<String>) -> Result<()> {
|
|
134
|
+
let manager = self.selected_acme_manager(listener_id)?;
|
|
135
|
+
manager.register_domain(domain);
|
|
136
|
+
Ok(())
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
pub fn unregister_tls_domain(
|
|
140
|
+
&self,
|
|
141
|
+
domain: String,
|
|
142
|
+
listener_id: Option<String>,
|
|
143
|
+
) -> Result<()> {
|
|
144
|
+
let manager = self.selected_acme_manager(listener_id)?;
|
|
145
|
+
manager.unregister_domain(&domain);
|
|
146
|
+
Ok(())
|
|
147
|
+
}
|
|
148
|
+
|
|
49
149
|
fn config(&self) -> Result<Arc<ItsiServerConfig>> {
|
|
50
150
|
self.config.lock().as_ref().cloned().ok_or_else(|| {
|
|
51
151
|
magnus::Error::new(
|
|
@@ -8,7 +8,6 @@ use super::bind_protocol::BindProtocol;
|
|
|
8
8
|
|
|
9
9
|
use super::tls::ItsiTlsAcceptor;
|
|
10
10
|
use itsi_error::{ItsiError, Result};
|
|
11
|
-
use itsi_tracing::info;
|
|
12
11
|
use socket2::{Domain, Protocol, SockRef, Socket, Type};
|
|
13
12
|
use std::fmt::Display;
|
|
14
13
|
use std::net::{IpAddr, SocketAddr, TcpListener};
|
|
@@ -21,7 +20,6 @@ use tokio::net::UnixListener as TokioUnixListener;
|
|
|
21
20
|
use tokio::net::{unix, TcpStream, UnixStream};
|
|
22
21
|
use tokio::sync::watch::Receiver;
|
|
23
22
|
use tokio::time::timeout;
|
|
24
|
-
use tokio_stream::StreamExt;
|
|
25
23
|
|
|
26
24
|
pub(crate) enum Listener {
|
|
27
25
|
Tcp(TcpListener),
|
|
@@ -115,25 +113,8 @@ impl TokioListener {
|
|
|
115
113
|
}
|
|
116
114
|
|
|
117
115
|
pub async fn spawn_acme_event_task(&self, mut shutdown_receiver: Receiver<RunningPhase>) {
|
|
118
|
-
if let TokioListener::TcpTls(
|
|
119
|
-
_
|
|
120
|
-
ItsiTlsAcceptor::Automatic(_acme_acceptor, state, _server_config),
|
|
121
|
-
) = self
|
|
122
|
-
{
|
|
123
|
-
let mut state = state.lock().await;
|
|
124
|
-
loop {
|
|
125
|
-
tokio::select! {
|
|
126
|
-
stream_event = StreamExt::next(&mut *state) => {
|
|
127
|
-
match stream_event {
|
|
128
|
-
Some(event) => info!("ACME Event: {:?}", event),
|
|
129
|
-
None => error!("Received no acme event"),
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
_ = shutdown_receiver.changed() => {
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
116
|
+
if let TokioListener::TcpTls(_, ItsiTlsAcceptor::Automatic { .. }) = self {
|
|
117
|
+
let _ = shutdown_receiver.changed().await;
|
|
137
118
|
}
|
|
138
119
|
}
|
|
139
120
|
|
|
@@ -215,7 +196,11 @@ impl AcceptedStream {
|
|
|
215
196
|
Err(_) => Err(ItsiError::Pass),
|
|
216
197
|
}
|
|
217
198
|
}
|
|
218
|
-
ItsiTlsAcceptor::Automatic
|
|
199
|
+
ItsiTlsAcceptor::Automatic {
|
|
200
|
+
acme_acceptor,
|
|
201
|
+
server_config,
|
|
202
|
+
..
|
|
203
|
+
} => {
|
|
219
204
|
let accept_future = acme_acceptor.accept(stream);
|
|
220
205
|
match timeout(handshake_timeout, accept_future).await {
|
|
221
206
|
Err(_) => Err(ItsiError::Pass),
|
|
@@ -224,7 +209,7 @@ impl AcceptedStream {
|
|
|
224
209
|
Ok(Some(start_handshake)) => {
|
|
225
210
|
match timeout(
|
|
226
211
|
handshake_timeout,
|
|
227
|
-
start_handshake.into_stream(
|
|
212
|
+
start_handshake.into_stream(server_config.clone()),
|
|
228
213
|
)
|
|
229
214
|
.await
|
|
230
215
|
{
|
|
@@ -259,7 +244,7 @@ impl AcceptedStream {
|
|
|
259
244
|
Err(_) => Err(ItsiError::Pass),
|
|
260
245
|
}
|
|
261
246
|
}
|
|
262
|
-
ItsiTlsAcceptor::Automatic
|
|
247
|
+
ItsiTlsAcceptor::Automatic { .. } => {
|
|
263
248
|
error!("Automatic TLS not supported on Unix sockets");
|
|
264
249
|
Err(ItsiError::UnsupportedProtocol(
|
|
265
250
|
"Automatic TLS on Unix Sockets".to_owned(),
|