itsi-server 0.1.1 → 0.1.4
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.
Potentially problematic release.
This version of itsi-server might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/exe/itsi +88 -28
- data/ext/itsi_error/Cargo.toml +2 -0
- data/ext/itsi_error/src/from.rs +71 -0
- data/ext/itsi_error/src/lib.rs +12 -37
- data/ext/itsi_instrument_entry/Cargo.toml +15 -0
- data/ext/itsi_instrument_entry/src/lib.rs +31 -0
- data/ext/itsi_rb_helpers/Cargo.toml +2 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +121 -0
- data/ext/itsi_rb_helpers/src/lib.rs +90 -10
- data/ext/itsi_scheduler/Cargo.toml +24 -0
- data/ext/itsi_scheduler/extconf.rb +6 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
- data/ext/itsi_scheduler/src/lib.rs +38 -0
- data/ext/itsi_server/Cargo.toml +17 -3
- data/ext/itsi_server/extconf.rb +1 -1
- data/ext/itsi_server/src/body_proxy/big_bytes.rs +104 -0
- data/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
- data/ext/itsi_server/src/body_proxy/mod.rs +2 -0
- data/ext/itsi_server/src/lib.rs +61 -7
- data/ext/itsi_server/src/request/itsi_request.rs +238 -104
- data/ext/itsi_server/src/response/itsi_response.rs +347 -0
- data/ext/itsi_server/src/response/mod.rs +1 -0
- data/ext/itsi_server/src/server/bind.rs +54 -23
- data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
- data/ext/itsi_server/src/server/io_stream.rs +104 -0
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +11 -30
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +3 -50
- data/ext/itsi_server/src/server/itsi_server.rs +196 -134
- data/ext/itsi_server/src/server/lifecycle_event.rs +9 -0
- data/ext/itsi_server/src/server/listener.rs +240 -132
- data/ext/itsi_server/src/server/mod.rs +7 -1
- data/ext/itsi_server/src/server/process_worker.rs +196 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +254 -0
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +27 -0
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +247 -0
- data/ext/itsi_server/src/server/signal.rs +70 -0
- data/ext/itsi_server/src/server/thread_worker.rs +368 -0
- data/ext/itsi_server/src/server/tls.rs +101 -51
- data/ext/itsi_tracing/Cargo.toml +4 -0
- data/ext/itsi_tracing/src/lib.rs +36 -6
- data/lib/itsi/request.rb +30 -14
- data/lib/itsi/server/rack/handler/itsi.rb +25 -0
- data/lib/itsi/server/scheduler_mode.rb +6 -0
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +82 -2
- data/lib/itsi/signals.rb +23 -0
- data/lib/itsi/stream_io.rb +38 -0
- metadata +38 -25
- data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
- data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
use super::itsi_server::RequestJob;
|
|
2
|
+
use crate::{request::itsi_request::ItsiRequest, ITSI_SERVER};
|
|
3
|
+
use itsi_rb_helpers::{
|
|
4
|
+
call_with_gvl, call_without_gvl, create_ruby_thread, kill_threads, HeapValue,
|
|
5
|
+
};
|
|
6
|
+
use itsi_tracing::{debug, error, info, warn};
|
|
7
|
+
use magnus::{
|
|
8
|
+
error::Result,
|
|
9
|
+
value::{InnerValue, Lazy, LazyId, Opaque, ReprValue},
|
|
10
|
+
Module, RClass, Ruby, Thread, Value,
|
|
11
|
+
};
|
|
12
|
+
use nix::unistd::Pid;
|
|
13
|
+
use parking_lot::{Mutex, RwLock};
|
|
14
|
+
use std::{
|
|
15
|
+
num::NonZeroU8,
|
|
16
|
+
ops::Deref,
|
|
17
|
+
sync::{
|
|
18
|
+
atomic::{AtomicBool, Ordering},
|
|
19
|
+
Arc,
|
|
20
|
+
},
|
|
21
|
+
thread,
|
|
22
|
+
time::{Duration, Instant},
|
|
23
|
+
};
|
|
24
|
+
use tokio::{runtime::Builder as RuntimeBuilder, sync::watch};
|
|
25
|
+
use tracing::instrument;
|
|
26
|
+
pub struct ThreadWorker {
|
|
27
|
+
pub id: String,
|
|
28
|
+
pub app: Opaque<Value>,
|
|
29
|
+
pub receiver: Arc<async_channel::Receiver<RequestJob>>,
|
|
30
|
+
pub sender: async_channel::Sender<RequestJob>,
|
|
31
|
+
pub thread: RwLock<Option<HeapValue<Thread>>>,
|
|
32
|
+
pub terminated: Arc<AtomicBool>,
|
|
33
|
+
pub scheduler_class: Option<Opaque<Value>>,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static ID_CALL: LazyId = LazyId::new("call");
|
|
37
|
+
static ID_ALIVE: LazyId = LazyId::new("alive?");
|
|
38
|
+
static ID_SCHEDULER: LazyId = LazyId::new("scheduler");
|
|
39
|
+
static ID_SCHEDULE: LazyId = LazyId::new("schedule");
|
|
40
|
+
static ID_BLOCK: LazyId = LazyId::new("block");
|
|
41
|
+
static ID_YIELD: LazyId = LazyId::new("yield");
|
|
42
|
+
static ID_CONST_GET: LazyId = LazyId::new("const_get");
|
|
43
|
+
static CLASS_FIBER: Lazy<RClass> = Lazy::new(|ruby| {
|
|
44
|
+
ruby.module_kernel()
|
|
45
|
+
.const_get::<_, RClass>("Fiber")
|
|
46
|
+
.unwrap()
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
pub struct TerminateWakerSignal(bool);
|
|
50
|
+
|
|
51
|
+
#[instrument(name = "Boot", parent=None, skip(threads, app, pid, scheduler_class))]
|
|
52
|
+
pub fn build_thread_workers(
|
|
53
|
+
pid: Pid,
|
|
54
|
+
threads: NonZeroU8,
|
|
55
|
+
app: Opaque<Value>,
|
|
56
|
+
scheduler_class: Option<String>,
|
|
57
|
+
) -> Result<(Arc<Vec<ThreadWorker>>, async_channel::Sender<RequestJob>)> {
|
|
58
|
+
let (sender, receiver) = async_channel::bounded(20);
|
|
59
|
+
let receiver_ref = Arc::new(receiver);
|
|
60
|
+
let sender_ref = sender;
|
|
61
|
+
let (app, scheduler_class) = load_app(app, scheduler_class)?;
|
|
62
|
+
Ok((
|
|
63
|
+
Arc::new(
|
|
64
|
+
(1..=u8::from(threads))
|
|
65
|
+
.map(|id| {
|
|
66
|
+
info!(pid = pid.as_raw(), id, "Thread");
|
|
67
|
+
ThreadWorker::new(
|
|
68
|
+
format!("{:?}#{:?}", pid, id),
|
|
69
|
+
app,
|
|
70
|
+
receiver_ref.clone(),
|
|
71
|
+
sender_ref.clone(),
|
|
72
|
+
scheduler_class,
|
|
73
|
+
)
|
|
74
|
+
})
|
|
75
|
+
.collect::<Result<Vec<_>>>()?,
|
|
76
|
+
),
|
|
77
|
+
sender_ref,
|
|
78
|
+
))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
pub fn load_app(
|
|
82
|
+
app: Opaque<Value>,
|
|
83
|
+
scheduler_class: Option<String>,
|
|
84
|
+
) -> Result<(Opaque<Value>, Option<Opaque<Value>>)> {
|
|
85
|
+
call_with_gvl(|ruby| {
|
|
86
|
+
let app = app.get_inner_with(&ruby);
|
|
87
|
+
let app = Opaque::from(
|
|
88
|
+
app.funcall::<_, _, Value>(*ID_CALL, ())
|
|
89
|
+
.expect("Couldn't load app"),
|
|
90
|
+
);
|
|
91
|
+
let scheduler_class = if let Some(scheduler_class) = scheduler_class {
|
|
92
|
+
Some(Opaque::from(
|
|
93
|
+
ruby.module_kernel()
|
|
94
|
+
.funcall::<_, _, Value>(*ID_CONST_GET, (scheduler_class,))?,
|
|
95
|
+
))
|
|
96
|
+
} else {
|
|
97
|
+
None
|
|
98
|
+
};
|
|
99
|
+
Ok((app, scheduler_class))
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
impl ThreadWorker {
|
|
103
|
+
pub fn new(
|
|
104
|
+
id: String,
|
|
105
|
+
app: Opaque<Value>,
|
|
106
|
+
receiver: Arc<async_channel::Receiver<RequestJob>>,
|
|
107
|
+
sender: async_channel::Sender<RequestJob>,
|
|
108
|
+
scheduler_class: Option<Opaque<Value>>,
|
|
109
|
+
) -> Result<Self> {
|
|
110
|
+
let mut worker = Self {
|
|
111
|
+
id,
|
|
112
|
+
app,
|
|
113
|
+
receiver,
|
|
114
|
+
sender,
|
|
115
|
+
thread: RwLock::new(None),
|
|
116
|
+
terminated: Arc::new(AtomicBool::new(false)),
|
|
117
|
+
scheduler_class,
|
|
118
|
+
};
|
|
119
|
+
worker.run()?;
|
|
120
|
+
Ok(worker)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#[instrument(skip(self), fields(id = self.id))]
|
|
124
|
+
pub async fn request_shutdown(&self) {
|
|
125
|
+
match self.sender.send(RequestJob::Shutdown).await {
|
|
126
|
+
Ok(_) => {}
|
|
127
|
+
Err(err) => error!("Failed to send shutdown request: {}", err),
|
|
128
|
+
};
|
|
129
|
+
info!("Requesting shutdown");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
#[instrument(skip(self, deadline), fields(id = self.id))]
|
|
133
|
+
pub fn poll_shutdown(&self, deadline: Instant) -> bool {
|
|
134
|
+
call_with_gvl(|_ruby| {
|
|
135
|
+
if let Some(thread) = self.thread.read().deref() {
|
|
136
|
+
if Instant::now() > deadline {
|
|
137
|
+
warn!("Worker shutdown timed out. Killing thread");
|
|
138
|
+
self.terminated.store(true, Ordering::SeqCst);
|
|
139
|
+
kill_threads(vec![thread.as_value()]);
|
|
140
|
+
}
|
|
141
|
+
if thread.funcall::<_, _, bool>(*ID_ALIVE, ()).unwrap_or(false) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
info!("Thread has shut down");
|
|
145
|
+
}
|
|
146
|
+
self.thread.write().take();
|
|
147
|
+
|
|
148
|
+
false
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
pub fn run(&mut self) -> Result<()> {
|
|
153
|
+
let id = self.id.clone();
|
|
154
|
+
let app = self.app;
|
|
155
|
+
let receiver = self.receiver.clone();
|
|
156
|
+
let terminated = self.terminated.clone();
|
|
157
|
+
let scheduler_class = self.scheduler_class;
|
|
158
|
+
call_with_gvl(|_| {
|
|
159
|
+
*self.thread.write() = Some(
|
|
160
|
+
create_ruby_thread(move || {
|
|
161
|
+
if let Some(scheduler_class) = scheduler_class {
|
|
162
|
+
if let Err(err) =
|
|
163
|
+
Self::fiber_accept_loop(id, app, receiver, scheduler_class, terminated)
|
|
164
|
+
{
|
|
165
|
+
error!("Error in fiber_accept_loop: {:?}", err);
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
Self::accept_loop(id, app, receiver, terminated);
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
.into(),
|
|
172
|
+
);
|
|
173
|
+
Ok::<(), magnus::Error>(())
|
|
174
|
+
})?;
|
|
175
|
+
Ok(())
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
pub fn build_scheduler_proc(
|
|
179
|
+
app: Opaque<Value>,
|
|
180
|
+
leader: &Arc<Mutex<Option<RequestJob>>>,
|
|
181
|
+
receiver: &Arc<async_channel::Receiver<RequestJob>>,
|
|
182
|
+
terminated: &Arc<AtomicBool>,
|
|
183
|
+
waker_sender: &watch::Sender<TerminateWakerSignal>,
|
|
184
|
+
) -> magnus::block::Proc {
|
|
185
|
+
let leader = leader.clone();
|
|
186
|
+
let receiver = receiver.clone();
|
|
187
|
+
let terminated = terminated.clone();
|
|
188
|
+
let waker_sender = waker_sender.clone();
|
|
189
|
+
Ruby::get().unwrap().proc_from_fn(move |ruby, _args, _blk| {
|
|
190
|
+
let scheduler = ruby
|
|
191
|
+
.get_inner(&CLASS_FIBER)
|
|
192
|
+
.funcall::<_, _, Value>(*ID_SCHEDULER, ())
|
|
193
|
+
.unwrap();
|
|
194
|
+
let server = ruby.get_inner(&ITSI_SERVER);
|
|
195
|
+
let thread_current = ruby.thread_current();
|
|
196
|
+
let leader_clone = leader.clone();
|
|
197
|
+
let receiver = receiver.clone();
|
|
198
|
+
let terminated = terminated.clone();
|
|
199
|
+
let waker_sender = waker_sender.clone();
|
|
200
|
+
let mut batch = Vec::with_capacity(MAX_BATCH_SIZE as usize);
|
|
201
|
+
|
|
202
|
+
static MAX_BATCH_SIZE: i32 = 25;
|
|
203
|
+
call_without_gvl(move || loop {
|
|
204
|
+
let mut idle_counter = 0;
|
|
205
|
+
if let Some(v) = leader_clone.lock().take() {
|
|
206
|
+
match v {
|
|
207
|
+
RequestJob::ProcessRequest(itsi_request) => {
|
|
208
|
+
batch.push(RequestJob::ProcessRequest(itsi_request))
|
|
209
|
+
}
|
|
210
|
+
RequestJob::Shutdown => {
|
|
211
|
+
waker_sender.send(TerminateWakerSignal(true)).unwrap();
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
for _ in 0..MAX_BATCH_SIZE {
|
|
217
|
+
if let Ok(req) = receiver.try_recv() {
|
|
218
|
+
batch.push(req);
|
|
219
|
+
} else {
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let shutdown_requested = call_with_gvl(|_| {
|
|
225
|
+
for req in batch.drain(..) {
|
|
226
|
+
match req {
|
|
227
|
+
RequestJob::ProcessRequest(request) => {
|
|
228
|
+
let response = request.response.clone();
|
|
229
|
+
if let Err(err) =
|
|
230
|
+
server.funcall::<_, _, Value>(*ID_SCHEDULE, (app, request))
|
|
231
|
+
{
|
|
232
|
+
ItsiRequest::internal_error(ruby, response, err)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
RequestJob::Shutdown => return true,
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
false
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if shutdown_requested || terminated.load(Ordering::Relaxed) {
|
|
242
|
+
waker_sender.send(TerminateWakerSignal(true)).unwrap();
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let yield_result = if receiver.is_empty() {
|
|
247
|
+
waker_sender.send(TerminateWakerSignal(false)).unwrap();
|
|
248
|
+
idle_counter = (idle_counter + 1) % 100;
|
|
249
|
+
call_with_gvl(|ruby| {
|
|
250
|
+
if idle_counter == 0 {
|
|
251
|
+
ruby.gc_start();
|
|
252
|
+
}
|
|
253
|
+
scheduler.funcall::<_, _, Value>(*ID_BLOCK, (thread_current, None::<u8>))
|
|
254
|
+
})
|
|
255
|
+
} else {
|
|
256
|
+
call_with_gvl(|_| scheduler.funcall::<_, _, Value>(*ID_YIELD, ()))
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
if yield_result.is_err() {
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
#[instrument(skip_all, fields(thread_worker=id))]
|
|
267
|
+
pub fn fiber_accept_loop(
|
|
268
|
+
id: String,
|
|
269
|
+
app: Opaque<Value>,
|
|
270
|
+
receiver: Arc<async_channel::Receiver<RequestJob>>,
|
|
271
|
+
scheduler_class: Opaque<Value>,
|
|
272
|
+
terminated: Arc<AtomicBool>,
|
|
273
|
+
) -> Result<()> {
|
|
274
|
+
let ruby = Ruby::get().unwrap();
|
|
275
|
+
let (waker_sender, waker_receiver) = watch::channel(TerminateWakerSignal(false));
|
|
276
|
+
let leader: Arc<Mutex<Option<RequestJob>>> = Arc::new(Mutex::new(None));
|
|
277
|
+
let server = ruby.get_inner(&ITSI_SERVER);
|
|
278
|
+
let scheduler_proc =
|
|
279
|
+
Self::build_scheduler_proc(app, &leader, &receiver, &terminated, &waker_sender);
|
|
280
|
+
let (scheduler, scheduler_fiber) = server.funcall::<_, _, (Value, Value)>(
|
|
281
|
+
"start_scheduler_loop",
|
|
282
|
+
(scheduler_class, scheduler_proc),
|
|
283
|
+
)?;
|
|
284
|
+
Self::start_waker_thread(
|
|
285
|
+
scheduler.into(),
|
|
286
|
+
scheduler_fiber.into(),
|
|
287
|
+
leader,
|
|
288
|
+
receiver,
|
|
289
|
+
waker_receiver,
|
|
290
|
+
);
|
|
291
|
+
Ok(())
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
#[allow(clippy::await_holding_lock)]
|
|
295
|
+
pub fn start_waker_thread(
|
|
296
|
+
scheduler: Opaque<Value>,
|
|
297
|
+
scheduler_fiber: Opaque<Value>,
|
|
298
|
+
leader: Arc<Mutex<Option<RequestJob>>>,
|
|
299
|
+
receiver: Arc<async_channel::Receiver<RequestJob>>,
|
|
300
|
+
mut waker_receiver: watch::Receiver<TerminateWakerSignal>,
|
|
301
|
+
) {
|
|
302
|
+
create_ruby_thread(move || {
|
|
303
|
+
let scheduler = scheduler.get_inner_with(&Ruby::get().unwrap());
|
|
304
|
+
let leader = leader.clone();
|
|
305
|
+
call_without_gvl(|| {
|
|
306
|
+
RuntimeBuilder::new_current_thread()
|
|
307
|
+
.build()
|
|
308
|
+
.expect("Failed to build Tokio runtime")
|
|
309
|
+
.block_on(async {
|
|
310
|
+
loop {
|
|
311
|
+
waker_receiver.changed().await.ok();
|
|
312
|
+
if waker_receiver.borrow().0 {
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
tokio::select! {
|
|
316
|
+
_ = waker_receiver.changed() => {
|
|
317
|
+
if waker_receiver.borrow().0 {
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
next_msg = receiver.recv() => {
|
|
322
|
+
*leader.lock() = next_msg.ok();
|
|
323
|
+
call_with_gvl(|_| {
|
|
324
|
+
scheduler
|
|
325
|
+
.funcall::<_, _, Value>(
|
|
326
|
+
"unblock",
|
|
327
|
+
(None::<u8>, scheduler_fiber),
|
|
328
|
+
)
|
|
329
|
+
.ok();
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
#[instrument(skip_all, fields(thread_worker=id))]
|
|
340
|
+
pub fn accept_loop(
|
|
341
|
+
id: String,
|
|
342
|
+
app: Opaque<Value>,
|
|
343
|
+
receiver: Arc<async_channel::Receiver<RequestJob>>,
|
|
344
|
+
terminated: Arc<AtomicBool>,
|
|
345
|
+
) {
|
|
346
|
+
let ruby = Ruby::get().unwrap();
|
|
347
|
+
let server = ruby.get_inner(&ITSI_SERVER);
|
|
348
|
+
call_without_gvl(|| loop {
|
|
349
|
+
match receiver.recv_blocking() {
|
|
350
|
+
Ok(RequestJob::ProcessRequest(request)) => {
|
|
351
|
+
if terminated.load(Ordering::Relaxed) {
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
call_with_gvl(|_ruby| {
|
|
355
|
+
request.process(&ruby, server, app).ok();
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
Ok(RequestJob::Shutdown) => {
|
|
359
|
+
debug!("Shutting down thread worker");
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
Err(_) => {
|
|
363
|
+
thread::sleep(Duration::from_micros(1));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
@@ -1,20 +1,69 @@
|
|
|
1
1
|
use base64::{engine::general_purpose, Engine as _};
|
|
2
2
|
use itsi_error::Result;
|
|
3
|
-
use itsi_tracing::
|
|
3
|
+
use itsi_tracing::info;
|
|
4
4
|
use rcgen::{CertificateParams, DnType, KeyPair, SanType};
|
|
5
|
+
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
|
|
5
6
|
use rustls_pemfile::{certs, pkcs8_private_keys};
|
|
6
|
-
use std::{
|
|
7
|
-
|
|
7
|
+
use std::{
|
|
8
|
+
collections::HashMap,
|
|
9
|
+
env, fs,
|
|
10
|
+
io::{BufReader, Error},
|
|
11
|
+
sync::Arc,
|
|
12
|
+
};
|
|
13
|
+
use tokio::sync::Mutex;
|
|
14
|
+
use tokio_rustls::{rustls::ServerConfig, TlsAcceptor};
|
|
15
|
+
use tokio_rustls_acme::{caches::DirCache, AcmeAcceptor, AcmeConfig, AcmeState};
|
|
8
16
|
|
|
9
17
|
const ITS_CA_CERT: &str = include_str!("./itsi_ca/itsi_ca.crt");
|
|
10
18
|
const ITS_CA_KEY: &str = include_str!("./itsi_ca/itsi_ca.key");
|
|
11
19
|
|
|
20
|
+
#[derive(Clone)]
|
|
21
|
+
pub enum ItsiTlsAcceptor {
|
|
22
|
+
Manual(TlsAcceptor),
|
|
23
|
+
Automatic(
|
|
24
|
+
AcmeAcceptor,
|
|
25
|
+
Arc<Mutex<AcmeState<Error>>>,
|
|
26
|
+
Arc<ServerConfig>,
|
|
27
|
+
),
|
|
28
|
+
}
|
|
29
|
+
|
|
12
30
|
// Generates a TLS configuration based on either :
|
|
13
31
|
// * Input "cert" and "key" options (either paths or Base64-encoded strings) or
|
|
14
32
|
// * Performs automatic certificate generation/retrieval. Generated certs use an internal self-signed Isti CA.
|
|
15
33
|
// If a non-local host or optional domain parameter is provided,
|
|
16
34
|
// an automated certificate will attempt to be fetched using let's encrypt.
|
|
17
|
-
pub fn configure_tls(
|
|
35
|
+
pub fn configure_tls(
|
|
36
|
+
host: &str,
|
|
37
|
+
query_params: &HashMap<String, String>,
|
|
38
|
+
) -> Result<ItsiTlsAcceptor> {
|
|
39
|
+
let domains = query_params
|
|
40
|
+
.get("domains")
|
|
41
|
+
.map(|v| v.split(',').map(String::from).collect::<Vec<_>>());
|
|
42
|
+
|
|
43
|
+
if query_params.get("cert").is_none() || query_params.get("key").is_none() {
|
|
44
|
+
if let Some(domains) = domains {
|
|
45
|
+
let directory_url = env::var("ACME_DIRECTORY_URL")
|
|
46
|
+
.unwrap_or_else(|_| "https://acme-v02.api.letsencrypt.org/directory".to_string());
|
|
47
|
+
info!(
|
|
48
|
+
domains = format!("{:?}", domains),
|
|
49
|
+
directory_url, "Requesting acme cert"
|
|
50
|
+
);
|
|
51
|
+
let acme_state = AcmeConfig::new(domains)
|
|
52
|
+
.contact(["mailto:wc@pico.net.nz"])
|
|
53
|
+
.cache(DirCache::new("./rustls_acme_cache"))
|
|
54
|
+
.directory(directory_url)
|
|
55
|
+
.state();
|
|
56
|
+
let rustls_config = ServerConfig::builder()
|
|
57
|
+
.with_no_client_auth()
|
|
58
|
+
.with_cert_resolver(acme_state.resolver());
|
|
59
|
+
let acceptor = acme_state.acceptor();
|
|
60
|
+
return Ok(ItsiTlsAcceptor::Automatic(
|
|
61
|
+
acceptor,
|
|
62
|
+
Arc::new(Mutex::new(acme_state)),
|
|
63
|
+
Arc::new(rustls_config),
|
|
64
|
+
));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
18
67
|
let (certs, key) = if let (Some(cert_path), Some(key_path)) =
|
|
19
68
|
(query_params.get("cert"), query_params.get("key"))
|
|
20
69
|
{
|
|
@@ -22,41 +71,20 @@ pub fn configure_tls(host: &str, query_params: &HashMap<String, String>) -> Resu
|
|
|
22
71
|
let certs = load_certs(cert_path);
|
|
23
72
|
let key = load_private_key(key_path);
|
|
24
73
|
(certs, key)
|
|
25
|
-
} else if query_params
|
|
26
|
-
.get("cert")
|
|
27
|
-
.map(|v| v == "auto")
|
|
28
|
-
.unwrap_or(false)
|
|
29
|
-
{
|
|
30
|
-
let domain_param = query_params.get("domain");
|
|
31
|
-
let host_string = host.to_string();
|
|
32
|
-
let domain = domain_param.or_else(|| {
|
|
33
|
-
if host_string != "localhost" {
|
|
34
|
-
Some(&host_string)
|
|
35
|
-
} else {
|
|
36
|
-
None
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
if let Some(domain) = domain {
|
|
41
|
-
retrieve_acme_cert(domain)?
|
|
42
|
-
} else {
|
|
43
|
-
generate_ca_signed_cert(host)?
|
|
44
|
-
}
|
|
45
74
|
} else {
|
|
46
|
-
generate_ca_signed_cert(host)?
|
|
75
|
+
generate_ca_signed_cert(vec![host.to_owned()])?
|
|
47
76
|
};
|
|
48
77
|
|
|
49
78
|
let mut config = ServerConfig::builder()
|
|
50
|
-
.with_safe_defaults()
|
|
51
79
|
.with_no_client_auth()
|
|
52
80
|
.with_single_cert(certs, key)
|
|
53
81
|
.expect("Failed to build TLS config");
|
|
54
82
|
|
|
55
83
|
config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
|
56
|
-
Ok(config)
|
|
84
|
+
Ok(ItsiTlsAcceptor::Manual(TlsAcceptor::from(Arc::new(config))))
|
|
57
85
|
}
|
|
58
86
|
|
|
59
|
-
pub fn load_certs(path: &str) -> Vec<
|
|
87
|
+
pub fn load_certs(path: &str) -> Vec<CertificateDer<'static>> {
|
|
60
88
|
let data = if let Some(stripped) = path.strip_prefix("base64:") {
|
|
61
89
|
general_purpose::STANDARD
|
|
62
90
|
.decode(stripped)
|
|
@@ -74,14 +102,20 @@ pub fn load_certs(path: &str) -> Vec<Certificate> {
|
|
|
74
102
|
})
|
|
75
103
|
.collect::<Result<_>>()
|
|
76
104
|
.expect("Failed to parse certificate file");
|
|
77
|
-
certs_der
|
|
105
|
+
certs_der
|
|
106
|
+
.into_iter()
|
|
107
|
+
.map(|vec| {
|
|
108
|
+
// Convert the owned Vec<u8> into a CertificateDer and force 'static.
|
|
109
|
+
unsafe { std::mem::transmute(CertificateDer::from(vec)) }
|
|
110
|
+
})
|
|
111
|
+
.collect()
|
|
78
112
|
} else {
|
|
79
|
-
vec![
|
|
113
|
+
vec![CertificateDer::from(data)]
|
|
80
114
|
}
|
|
81
115
|
}
|
|
82
116
|
|
|
83
117
|
/// Loads a private key from a file or Base64.
|
|
84
|
-
pub fn load_private_key(path: &str) ->
|
|
118
|
+
pub fn load_private_key(path: &str) -> PrivateKeyDer<'static> {
|
|
85
119
|
let key_data = if let Some(stripped) = path.strip_prefix("base64:") {
|
|
86
120
|
general_purpose::STANDARD
|
|
87
121
|
.decode(stripped)
|
|
@@ -100,39 +134,55 @@ pub fn load_private_key(path: &str) -> PrivateKey {
|
|
|
100
134
|
.collect::<Result<_>>()
|
|
101
135
|
.expect("Failed to parse private key");
|
|
102
136
|
if !keys.is_empty() {
|
|
103
|
-
return
|
|
137
|
+
return PrivateKeyDer::try_from(keys[0].clone()).unwrap();
|
|
104
138
|
}
|
|
105
139
|
}
|
|
106
|
-
|
|
140
|
+
PrivateKeyDer::try_from(key_data).unwrap()
|
|
107
141
|
}
|
|
108
142
|
|
|
109
|
-
pub fn generate_ca_signed_cert(
|
|
143
|
+
pub fn generate_ca_signed_cert(
|
|
144
|
+
domains: Vec<String>,
|
|
145
|
+
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
|
|
110
146
|
info!("Generating New Itsi CA - Self signed Certificate. Use `itsi ca export` to export the CA certificate for import into your local trust store.");
|
|
111
147
|
|
|
112
|
-
let ca_kp = KeyPair::from_pem(ITS_CA_KEY).
|
|
113
|
-
let
|
|
148
|
+
let ca_kp = KeyPair::from_pem(ITS_CA_KEY).expect("Failed to load embedded CA key");
|
|
149
|
+
let ca_cert = CertificateParams::from_ca_cert_pem(ITS_CA_CERT)
|
|
150
|
+
.expect("Failed to parse embedded CA certificate")
|
|
151
|
+
.self_signed(&ca_kp)
|
|
152
|
+
.expect("Failed to self-sign embedded CA cert");
|
|
114
153
|
|
|
115
|
-
let
|
|
116
|
-
let ee_key = KeyPair::generate().unwrap();
|
|
154
|
+
let ee_key = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap();
|
|
117
155
|
let mut ee_params = CertificateParams::default();
|
|
118
156
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
157
|
+
info!(
|
|
158
|
+
"Generated certificate will be valid for domains {:?}",
|
|
159
|
+
domains
|
|
160
|
+
);
|
|
161
|
+
use std::net::IpAddr;
|
|
162
|
+
|
|
163
|
+
ee_params.subject_alt_names = domains
|
|
164
|
+
.iter()
|
|
165
|
+
.map(|domain| {
|
|
166
|
+
if let Ok(ip) = domain.parse::<IpAddr>() {
|
|
167
|
+
SanType::IpAddress(ip)
|
|
168
|
+
} else {
|
|
169
|
+
SanType::DnsName(domain.clone().try_into().unwrap())
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
.collect();
|
|
173
|
+
|
|
122
174
|
ee_params
|
|
123
175
|
.distinguished_name
|
|
124
|
-
.push(DnType::CommonName,
|
|
176
|
+
.push(DnType::CommonName, domains[0].clone());
|
|
125
177
|
|
|
126
178
|
ee_params.use_authority_key_identifier_extension = true;
|
|
127
179
|
|
|
128
|
-
let ee_cert = ee_params.signed_by(&ee_key, &ca_cert, &
|
|
180
|
+
let ee_cert = ee_params.signed_by(&ee_key, &ca_cert, &ca_kp).unwrap();
|
|
129
181
|
let ee_cert_der = ee_cert.der().to_vec();
|
|
130
|
-
let ee_cert =
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
warn!("Retrieving ACME cert for {}", domain);
|
|
137
|
-
generate_ca_signed_cert(domain)
|
|
182
|
+
let ee_cert = CertificateDer::from(ee_cert_der);
|
|
183
|
+
let ca_cert = CertificateDer::from(ca_cert.der().to_vec());
|
|
184
|
+
Ok((
|
|
185
|
+
vec![ee_cert, ca_cert],
|
|
186
|
+
PrivateKeyDer::try_from(ee_key.serialize_der()).unwrap(),
|
|
187
|
+
))
|
|
138
188
|
}
|
data/ext/itsi_tracing/Cargo.toml
CHANGED
data/ext/itsi_tracing/src/lib.rs
CHANGED
|
@@ -1,11 +1,41 @@
|
|
|
1
|
+
use std::env;
|
|
2
|
+
|
|
3
|
+
use atty::{Stream, is};
|
|
1
4
|
pub use tracing::{debug, error, info, trace, warn};
|
|
2
|
-
use
|
|
5
|
+
pub use tracing_attributes::instrument; // Explicitly export from tracing-attributes
|
|
6
|
+
use tracing_subscriber::{
|
|
7
|
+
EnvFilter,
|
|
8
|
+
fmt::{self, format},
|
|
9
|
+
};
|
|
3
10
|
|
|
11
|
+
#[instrument]
|
|
4
12
|
pub fn init() {
|
|
5
|
-
let env_filter = EnvFilter::
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
.
|
|
13
|
+
let env_filter = EnvFilter::builder()
|
|
14
|
+
.with_env_var("ITSI_LOG")
|
|
15
|
+
.try_from_env()
|
|
16
|
+
.unwrap_or_else(|_| EnvFilter::new("info"));
|
|
17
|
+
|
|
18
|
+
let format = fmt::format()
|
|
19
|
+
.compact()
|
|
20
|
+
.with_file(false)
|
|
21
|
+
.with_level(true)
|
|
22
|
+
.with_line_number(false)
|
|
23
|
+
.with_source_location(false)
|
|
24
|
+
.with_target(false)
|
|
25
|
+
.with_thread_ids(false);
|
|
26
|
+
|
|
27
|
+
let is_tty = is(Stream::Stdout);
|
|
28
|
+
|
|
29
|
+
let subscriber = tracing_subscriber::fmt()
|
|
9
30
|
.event_format(format)
|
|
10
|
-
.
|
|
31
|
+
.with_env_filter(env_filter);
|
|
32
|
+
|
|
33
|
+
if (is_tty && env::var("ITSI_LOG_PLAIN").is_err()) || env::var("ITSI_LOG_ANSI").is_ok() {
|
|
34
|
+
subscriber.with_ansi(true).init();
|
|
35
|
+
} else {
|
|
36
|
+
subscriber
|
|
37
|
+
.fmt_fields(format::JsonFields::default())
|
|
38
|
+
.event_format(fmt::format().json())
|
|
39
|
+
.init();
|
|
40
|
+
}
|
|
11
41
|
}
|