itsi-server 0.1.2 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a59f65a9b7f82d2e3e4e94eed7b1de48b9579e6a2ee40887f6de7500da9cb391
4
- data.tar.gz: 7e49394fbbef18255d4a15ac6335b7acb74d925f2c026c5b24dcf9032f37bdc9
3
+ metadata.gz: a9738897c1d51717595b2ab829af9a7ed03f14b818f451121abb9fe2dd90ae9a
4
+ data.tar.gz: 5b0cbc6234c01a11f509541b4ca963ff6743f3d3190c1c55f26eecedb5a49351
5
5
  SHA512:
6
- metadata.gz: 5886b25965a3fa73fb3ad3f6d7e915558f5cbf69a6dd62ab8dee23b51d64735b43d90942eadb05b44cb374cd64e00ad6eec078686511ca257f5b3f3b3fe99d48
7
- data.tar.gz: b799d8fe05a30559d593f14b13c8b9c9a9f9569b610703e904177ca177671f171e5493615a48c39dd181fb9c2a67f75945e8c592d84ae15414ca4ebef3929ed4
6
+ metadata.gz: ec4156d473dcbcc34bc9ae98bfb7161afd62b050fa8652630a26d7ca2dc3b226523b268cc21e86179cecd07a0699ce8766edb861a7dc9c729df1580c98ccff0e
7
+ data.tar.gz: cb19f0a410357782a691ba8aba8b27d24b3484d284ec363c46c16e3ead74eb8d4fb29e9f32aed59fe8442b8df8a35768fca39fc687a53bb98d15e589b1147d1b
@@ -65,6 +65,7 @@ impl From<ItsiError> for magnus::Error {
65
65
  ItsiError::ClientConnectionClosed => {
66
66
  magnus::Error::new(magnus::exception::eof_error(), CLIENT_CONNECTION_CLOSED)
67
67
  }
68
+ ItsiError::Pass() => magnus::Error::new(magnus::exception::interrupt(), "Pass"),
68
69
  }
69
70
  }
70
71
  }
@@ -19,4 +19,6 @@ pub enum ItsiError {
19
19
  Jump(String),
20
20
  #[error("Break")]
21
21
  Break(),
22
+ #[error("Pass")]
23
+ Pass(),
22
24
  }
@@ -3,4 +3,4 @@
3
3
  require "mkmf"
4
4
  require "rb_sys/mkmf"
5
5
 
6
- create_rust_makefile("scheduler/itsi_scheduler")
6
+ create_rust_makefile("itsi/scheduler/itsi_scheduler")
@@ -17,7 +17,7 @@ itsi_error = { path = "../itsi_error" }
17
17
  socket2 = "0.5.8"
18
18
  parking_lot = "0.12.3"
19
19
  rustls-pemfile = "2.2.0"
20
- tokio-rustls = "0.23"
20
+ tokio-rustls = "0.26.2"
21
21
  bytes = "1.3"
22
22
  rcgen = { version = "0.13.2", features = ["x509-parser", "pem"] }
23
23
  base64 = "0.22.1"
@@ -39,3 +39,5 @@ httparse = "1.10.1"
39
39
  async-channel = "2.3.1"
40
40
  tempfile = "3.18.0"
41
41
  sysinfo = "0.33.1"
42
+ tokio-rustls-acme = "0.6.0"
43
+ rustls = "0.23.23"
@@ -3,4 +3,4 @@
3
3
  require "mkmf"
4
4
  require "rb_sys/mkmf"
5
5
 
6
- create_rust_makefile("server/itsi_server")
6
+ create_rust_makefile("itsi/server/itsi_server")
@@ -56,6 +56,9 @@ pub fn log_error(msg: String) {
56
56
  #[magnus::init]
57
57
  fn init(ruby: &Ruby) -> Result<()> {
58
58
  itsi_tracing::init();
59
+ rustls::crypto::aws_lc_rs::default_provider()
60
+ .install_default()
61
+ .ok();
59
62
 
60
63
  let itsi = ruby.get_inner(&ITSI_MODULE);
61
64
  itsi.define_singleton_method("log_debug", function!(log_debug, 1))?;
@@ -1,4 +1,7 @@
1
- use super::{bind_protocol::BindProtocol, tls::configure_tls};
1
+ use super::{
2
+ bind_protocol::BindProtocol,
3
+ tls::{configure_tls, ItsiTlsAcceptor},
4
+ };
2
5
  use itsi_error::ItsiError;
3
6
  use std::{
4
7
  collections::HashMap,
@@ -6,8 +9,6 @@ use std::{
6
9
  path::PathBuf,
7
10
  str::FromStr,
8
11
  };
9
- use tokio_rustls::rustls::ServerConfig;
10
-
11
12
  #[derive(Debug, Clone)]
12
13
  pub enum BindAddress {
13
14
  Ip(IpAddr),
@@ -26,7 +27,7 @@ pub struct Bind {
26
27
  pub address: BindAddress,
27
28
  pub port: Option<u16>, // None for Unix Sockets
28
29
  pub protocol: BindProtocol,
29
- pub tls_config: Option<ServerConfig>,
30
+ pub tls_config: Option<ItsiTlsAcceptor>,
30
31
  }
31
32
 
32
33
  impl std::fmt::Debug for Bind {
@@ -2,7 +2,7 @@ use super::{
2
2
  bind::Bind,
3
3
  listener::Listener,
4
4
  serve_strategy::{cluster_mode::ClusterMode, single_mode::SingleMode},
5
- signal::{reset_signal_handlers, SIGNAL_HANDLER_CHANNEL},
5
+ signal::{clear_signal_handlers, reset_signal_handlers, SIGNAL_HANDLER_CHANNEL},
6
6
  };
7
7
  use crate::{request::itsi_request::ItsiRequest, server::serve_strategy::ServeStrategy};
8
8
  use derive_more::Debug;
@@ -195,9 +195,11 @@ impl Server {
195
195
  Ok(Arc::new(listeners))
196
196
  }
197
197
 
198
- pub(crate) fn build_strategy(self) -> Result<ServeStrategy> {
198
+ pub(crate) fn build_strategy(
199
+ self,
200
+ listeners: Arc<Vec<Arc<Listener>>>,
201
+ ) -> Result<ServeStrategy> {
199
202
  let server = Arc::new(self);
200
- let listeners = server.listeners()?;
201
203
 
202
204
  let strategy = if server.config.workers == 1 {
203
205
  ServeStrategy::Single(Arc::new(SingleMode::new(
@@ -218,13 +220,25 @@ impl Server {
218
220
  pub fn start(&self) -> Result<()> {
219
221
  reset_signal_handlers();
220
222
  let rself = self.clone();
221
- call_without_gvl(move || {
222
- let strategy = rself.build_strategy()?;
223
+ let listeners = self.listeners()?;
224
+ let listeners_clone = listeners.clone();
225
+ call_without_gvl(move || -> Result<()> {
226
+ let strategy = rself.build_strategy(listeners_clone)?;
223
227
  if let Err(e) = strategy.run() {
224
228
  error!("Error running server: {}", e);
225
229
  strategy.stop()?;
226
230
  }
231
+ drop(strategy);
227
232
  Ok(())
228
- })
233
+ })?;
234
+ if let Ok(listeners) = Arc::try_unwrap(listeners) {
235
+ listeners.into_iter().for_each(|listener| {
236
+ if let Ok(listener) = Arc::try_unwrap(listener) {
237
+ listener.unbind()
238
+ };
239
+ });
240
+ }
241
+ clear_signal_handlers();
242
+ Ok(())
229
243
  }
230
244
  }
@@ -1,5 +1,6 @@
1
1
  #[derive(Debug, Clone)]
2
2
  pub enum LifecycleEvent {
3
+ Start,
3
4
  Shutdown,
4
5
  Restart,
5
6
  IncreaseWorkers,
@@ -1,7 +1,8 @@
1
1
  use super::bind::{Bind, BindAddress};
2
2
  use super::bind_protocol::BindProtocol;
3
3
  use super::io_stream::IoStream;
4
- use itsi_error::Result;
4
+ use super::tls::ItsiTlsAcceptor;
5
+ use itsi_error::{ItsiError, Result};
5
6
  use itsi_tracing::info;
6
7
  use socket2::{Domain, Protocol, Socket, Type};
7
8
  use std::net::{IpAddr, SocketAddr, TcpListener};
@@ -11,12 +12,14 @@ use tokio::net::TcpListener as TokioTcpListener;
11
12
  use tokio::net::UnixListener as TokioUnixListener;
12
13
  use tokio::net::{unix, TcpStream, UnixStream};
13
14
  use tokio_rustls::TlsAcceptor;
15
+ use tokio_stream::StreamExt;
16
+ use tracing::error;
14
17
 
15
18
  pub(crate) enum Listener {
16
19
  Tcp(TcpListener),
17
- TcpTls((TcpListener, TlsAcceptor)),
20
+ TcpTls((TcpListener, ItsiTlsAcceptor)),
18
21
  Unix(UnixListener),
19
- UnixTls((UnixListener, TlsAcceptor)),
22
+ UnixTls((UnixListener, ItsiTlsAcceptor)),
20
23
  }
21
24
 
22
25
  pub(crate) enum TokioListener {
@@ -27,7 +30,7 @@ pub(crate) enum TokioListener {
27
30
  },
28
31
  TcpTls {
29
32
  listener: TokioTcpListener,
30
- acceptor: TlsAcceptor,
33
+ acceptor: ItsiTlsAcceptor,
31
34
  host: String,
32
35
  port: u16,
33
36
  },
@@ -36,11 +39,19 @@ pub(crate) enum TokioListener {
36
39
  },
37
40
  UnixTls {
38
41
  listener: TokioUnixListener,
39
- acceptor: TlsAcceptor,
42
+ acceptor: ItsiTlsAcceptor,
40
43
  },
41
44
  }
42
45
 
43
46
  impl TokioListener {
47
+ pub fn unbind(self) {
48
+ match self {
49
+ TokioListener::Tcp { listener, .. } => drop(listener.into_std().unwrap()),
50
+ TokioListener::TcpTls { listener, .. } => drop(listener.into_std().unwrap()),
51
+ TokioListener::Unix { listener } => drop(listener.into_std().unwrap()),
52
+ TokioListener::UnixTls { listener, .. } => drop(listener.into_std().unwrap()),
53
+ };
54
+ }
44
55
  pub(crate) async fn accept(&self) -> Result<IoStream> {
45
56
  match self {
46
57
  TokioListener::Tcp { listener, .. } => TokioListener::accept_tcp(listener).await,
@@ -59,9 +70,48 @@ impl TokioListener {
59
70
  Self::to_tokio_io(Stream::TcpStream(tcp_stream), None).await
60
71
  }
61
72
 
62
- async fn accept_tls(listener: &TokioTcpListener, acceptor: &TlsAcceptor) -> Result<IoStream> {
73
+ pub async fn spawn_state_task(&self) {
74
+ if let TokioListener::TcpTls {
75
+ acceptor: ItsiTlsAcceptor::Automatic(_acme_acceptor, state, _server_config),
76
+ ..
77
+ } = self
78
+ {
79
+ let mut state = state.lock().await;
80
+ loop {
81
+ if let Some(event) = StreamExt::next(&mut *state).await {
82
+ info!("Received acme event: {:?}", event)
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ async fn accept_tls(
89
+ listener: &TokioTcpListener,
90
+ acceptor: &ItsiTlsAcceptor,
91
+ ) -> Result<IoStream> {
63
92
  let tcp_stream = listener.accept().await?;
64
- Self::to_tokio_io(Stream::TcpStream(tcp_stream), Some(acceptor)).await
93
+ match acceptor {
94
+ ItsiTlsAcceptor::Manual(tls_acceptor) => {
95
+ Self::to_tokio_io(Stream::TcpStream(tcp_stream), Some(tls_acceptor)).await
96
+ }
97
+ ItsiTlsAcceptor::Automatic(acme_acceptor, _, rustls_config) => {
98
+ let accept_future = acme_acceptor.accept(tcp_stream.0);
99
+ match accept_future.await.unwrap() {
100
+ None => Err(ItsiError::Pass()),
101
+ Some(start_handshake) => {
102
+ let tls_stream = start_handshake
103
+ .into_stream(rustls_config.clone())
104
+ .await
105
+ .unwrap();
106
+ // Wrap in your IoStream::TcpTls variant
107
+ Ok(IoStream::TcpTls {
108
+ stream: tls_stream,
109
+ addr: SockAddr::Tcp(Arc::new(tcp_stream.1)),
110
+ })
111
+ }
112
+ }
113
+ }
114
+ }
65
115
  }
66
116
 
67
117
  async fn accept_unix(listener: &TokioUnixListener) -> Result<IoStream> {
@@ -71,10 +121,20 @@ impl TokioListener {
71
121
 
72
122
  async fn accept_unix_tls(
73
123
  listener: &TokioUnixListener,
74
- acceptor: &TlsAcceptor,
124
+ acceptor: &ItsiTlsAcceptor,
75
125
  ) -> Result<IoStream> {
76
126
  let unix_stream = listener.accept().await?;
77
- Self::to_tokio_io(Stream::UnixStream(unix_stream), Some(acceptor)).await
127
+ match acceptor {
128
+ ItsiTlsAcceptor::Manual(tls_acceptor) => {
129
+ Self::to_tokio_io(Stream::UnixStream(unix_stream), Some(tls_acceptor)).await
130
+ }
131
+ ItsiTlsAcceptor::Automatic(_, _, _) => {
132
+ error!("Automatic TLS not supported on Unix sockets");
133
+ Err(ItsiError::UnsupportedProtocol(
134
+ "Automatic TLS on Unix Sockets".to_owned(),
135
+ ))
136
+ }
137
+ }
78
138
  }
79
139
 
80
140
  async fn to_tokio_io(
@@ -166,6 +226,14 @@ impl std::fmt::Display for SockAddr {
166
226
  }
167
227
 
168
228
  impl Listener {
229
+ pub fn unbind(self) {
230
+ match self {
231
+ Listener::Tcp(listener) => drop(listener),
232
+ Listener::TcpTls((listener, _)) => drop(listener),
233
+ Listener::Unix(listener) => drop(listener),
234
+ Listener::UnixTls((listener, _)) => drop(listener),
235
+ };
236
+ }
169
237
  pub fn to_tokio_listener(&self) -> TokioListener {
170
238
  match self {
171
239
  Listener::Tcp(listener) => TokioListener::Tcp {
@@ -213,16 +281,12 @@ impl TryFrom<Bind> for Listener {
213
281
  BindProtocol::Http => Listener::Tcp(connect_tcp_socket(addr, bind.port.unwrap())?),
214
282
  BindProtocol::Https => {
215
283
  let tcp_listener = connect_tcp_socket(addr, bind.port.unwrap())?;
216
- let tls_acceptor = TlsAcceptor::from(Arc::new(bind.tls_config.unwrap()));
217
- Listener::TcpTls((tcp_listener, tls_acceptor))
284
+ Listener::TcpTls((tcp_listener, bind.tls_config.unwrap()))
218
285
  }
219
286
  _ => unreachable!(),
220
287
  },
221
288
  BindAddress::UnixSocket(path) => match bind.tls_config {
222
- Some(tls_config) => {
223
- let tls_acceptor = TlsAcceptor::from(Arc::new(tls_config));
224
- Listener::UnixTls((connect_unix_socket(&path)?, tls_acceptor))
225
- }
289
+ Some(tls_config) => Listener::UnixTls((connect_unix_socket(&path)?, tls_config)),
226
290
  None => Listener::Unix(connect_unix_socket(&path)?),
227
291
  },
228
292
  };
@@ -237,9 +301,11 @@ fn connect_tcp_socket(addr: IpAddr, port: u16) -> Result<TcpListener> {
237
301
  };
238
302
  let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?;
239
303
  let socket_address: SocketAddr = SocketAddr::new(addr, port);
304
+ socket.set_reuse_port(true).ok();
305
+ socket.set_reuse_address(true).ok();
240
306
  socket.set_nonblocking(true).ok();
241
307
  socket.set_nodelay(true).ok();
242
- socket.set_recv_buffer_size(1_048_576).ok();
308
+ socket.set_recv_buffer_size(262_144).ok();
243
309
  socket.bind(&socket_address.into())?;
244
310
  socket.listen(1024)?;
245
311
  Ok(socket.into())
@@ -249,6 +315,7 @@ fn connect_unix_socket(path: &PathBuf) -> Result<UnixListener> {
249
315
  let _ = std::fs::remove_file(path);
250
316
  let socket = Socket::new(Domain::UNIX, Type::STREAM, None)?;
251
317
  socket.set_nonblocking(true).ok();
318
+
252
319
  let socket_address = socket2::SockAddr::unix(path)?;
253
320
 
254
321
  info!("Binding to {:?}", path);
@@ -72,6 +72,7 @@ impl ClusterMode {
72
72
  lifecycle_event: LifecycleEvent,
73
73
  ) -> Result<()> {
74
74
  match lifecycle_event {
75
+ LifecycleEvent::Start => Ok(()),
75
76
  LifecycleEvent::Shutdown => {
76
77
  self.shutdown().await?;
77
78
  Ok(())
@@ -95,6 +95,12 @@ impl SingleMode {
95
95
  let self_ref = self_ref.clone();
96
96
  let listener = listener.clone();
97
97
  let (shutdown_sender, mut shutdown_receiver) = tokio::sync::watch::channel::<RunningPhase>(RunningPhase::Running);
98
+ let listener_clone = listener.clone();
99
+
100
+ tokio::spawn(async move {
101
+ listener_clone.spawn_state_task().await;
102
+ });
103
+
98
104
  listener_task_set.spawn(async move {
99
105
  let strategy = self_ref.clone();
100
106
  loop {
@@ -124,6 +130,9 @@ impl SingleMode {
124
130
  }
125
131
  }
126
132
  }
133
+ if let Ok(listener) = Arc::try_unwrap(listener){
134
+ listener.unbind();
135
+ }
127
136
  });
128
137
 
129
138
  }
@@ -45,7 +45,8 @@ fn receive_signal(signum: i32, _: sighandler_t) {
45
45
  }
46
46
  }
47
47
 
48
- pub fn reset_signal_handlers() {
48
+ pub fn reset_signal_handlers() -> bool {
49
+ SIGINT_COUNT.store(0, std::sync::atomic::Ordering::SeqCst);
49
50
  unsafe {
50
51
  libc::signal(libc::SIGTERM, receive_signal as usize);
51
52
  libc::signal(libc::SIGINT, receive_signal as usize);
@@ -54,4 +55,16 @@ pub fn reset_signal_handlers() {
54
55
  libc::signal(libc::SIGTTIN, receive_signal as usize);
55
56
  libc::signal(libc::SIGTTOU, receive_signal as usize);
56
57
  }
58
+ true
59
+ }
60
+
61
+ pub fn clear_signal_handlers() {
62
+ unsafe {
63
+ libc::signal(libc::SIGTERM, libc::SIG_DFL);
64
+ libc::signal(libc::SIGINT, libc::SIG_DFL);
65
+ libc::signal(libc::SIGUSR1, libc::SIG_DFL);
66
+ libc::signal(libc::SIGUSR2, libc::SIG_DFL);
67
+ libc::signal(libc::SIGTTIN, libc::SIG_DFL);
68
+ libc::signal(libc::SIGTTOU, libc::SIG_DFL);
69
+ }
57
70
  }
@@ -1,21 +1,69 @@
1
1
  use base64::{engine::general_purpose, Engine as _};
2
2
  use itsi_error::Result;
3
- use itsi_tracing::{info, warn};
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::{collections::HashMap, fs, io::BufReader};
7
- use tokio_rustls::rustls::{Certificate, PrivateKey, ServerConfig};
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(host: &str, query_params: &HashMap<String, String>) -> Result<ServerConfig> {
18
- info!("TLS Options {:?}", query_params);
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
+ }
19
67
  let (certs, key) = if let (Some(cert_path), Some(key_path)) =
20
68
  (query_params.get("cert"), query_params.get("key"))
21
69
  {
@@ -24,36 +72,19 @@ pub fn configure_tls(host: &str, query_params: &HashMap<String, String>) -> Resu
24
72
  let key = load_private_key(key_path);
25
73
  (certs, key)
26
74
  } else {
27
- let domains_param = query_params
28
- .get("domains")
29
- .map(|v| v.split(',').map(String::from).collect());
30
- let host_string = host.to_string();
31
- let domains = domains_param.or_else(|| {
32
- if host_string != "localhost" {
33
- Some(vec![host_string])
34
- } else {
35
- None
36
- }
37
- });
38
-
39
- if let Some(domains) = domains {
40
- retrieve_acme_cert(domains)?
41
- } else {
42
- generate_ca_signed_cert(vec![host.to_owned()])?
43
- }
75
+ generate_ca_signed_cert(vec![host.to_owned()])?
44
76
  };
45
77
 
46
78
  let mut config = ServerConfig::builder()
47
- .with_safe_defaults()
48
79
  .with_no_client_auth()
49
80
  .with_single_cert(certs, key)
50
81
  .expect("Failed to build TLS config");
51
82
 
52
83
  config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
53
- Ok(config)
84
+ Ok(ItsiTlsAcceptor::Manual(TlsAcceptor::from(Arc::new(config))))
54
85
  }
55
86
 
56
- pub fn load_certs(path: &str) -> Vec<Certificate> {
87
+ pub fn load_certs(path: &str) -> Vec<CertificateDer<'static>> {
57
88
  let data = if let Some(stripped) = path.strip_prefix("base64:") {
58
89
  general_purpose::STANDARD
59
90
  .decode(stripped)
@@ -71,14 +102,20 @@ pub fn load_certs(path: &str) -> Vec<Certificate> {
71
102
  })
72
103
  .collect::<Result<_>>()
73
104
  .expect("Failed to parse certificate file");
74
- certs_der.into_iter().map(Certificate).collect()
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()
75
112
  } else {
76
- vec![Certificate(data)]
113
+ vec![CertificateDer::from(data)]
77
114
  }
78
115
  }
79
116
 
80
117
  /// Loads a private key from a file or Base64.
81
- pub fn load_private_key(path: &str) -> PrivateKey {
118
+ pub fn load_private_key(path: &str) -> PrivateKeyDer<'static> {
82
119
  let key_data = if let Some(stripped) = path.strip_prefix("base64:") {
83
120
  general_purpose::STANDARD
84
121
  .decode(stripped)
@@ -97,13 +134,15 @@ pub fn load_private_key(path: &str) -> PrivateKey {
97
134
  .collect::<Result<_>>()
98
135
  .expect("Failed to parse private key");
99
136
  if !keys.is_empty() {
100
- return PrivateKey(keys[0].clone());
137
+ return PrivateKeyDer::try_from(keys[0].clone()).unwrap();
101
138
  }
102
139
  }
103
- PrivateKey(key_data)
140
+ PrivateKeyDer::try_from(key_data).unwrap()
104
141
  }
105
142
 
106
- pub fn generate_ca_signed_cert(domains: Vec<String>) -> Result<(Vec<Certificate>, PrivateKey)> {
143
+ pub fn generate_ca_signed_cert(
144
+ domains: Vec<String>,
145
+ ) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
107
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.");
108
147
 
109
148
  let ca_kp = KeyPair::from_pem(ITS_CA_KEY).expect("Failed to load embedded CA key");
@@ -140,13 +179,10 @@ pub fn generate_ca_signed_cert(domains: Vec<String>) -> Result<(Vec<Certificate>
140
179
 
141
180
  let ee_cert = ee_params.signed_by(&ee_key, &ca_cert, &ca_kp).unwrap();
142
181
  let ee_cert_der = ee_cert.der().to_vec();
143
- let ee_cert = Certificate(ee_cert_der);
144
- let ca_cert = Certificate(ca_cert.der().to_vec());
145
- Ok((vec![ee_cert, ca_cert], PrivateKey(ee_key.serialize_der())))
146
- }
147
-
148
- /// TODO: Retrieves an ACME certificate for a given domain.
149
- pub fn retrieve_acme_cert(domains: Vec<String>) -> Result<(Vec<Certificate>, PrivateKey)> {
150
- warn!("Retrieving ACME cert for {}", domains.join(", "));
151
- generate_ca_signed_cert(domains)
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
+ ))
152
188
  }
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Itsi
4
4
  class Server
5
- VERSION = "0.1.2"
5
+ VERSION = "0.1.4"
6
6
  end
7
7
  end
data/lib/itsi/server.rb CHANGED
@@ -9,6 +9,20 @@ require_relative "server/rack/handler/itsi"
9
9
 
10
10
  module Itsi
11
11
  class Server
12
+
13
+ def self.running?
14
+ @running ||= false
15
+ end
16
+
17
+ def self.start(app:, **opts)
18
+ server = new(app: ->{app}, **opts)
19
+ @running = true
20
+ Signal.trap('INT', 'DEFAULT')
21
+ server.start
22
+ ensure
23
+ @running = false
24
+ end
25
+
12
26
  def self.call(app, request)
13
27
  respond request, app.call(request.to_env)
14
28
  end
data/lib/itsi/signals.rb CHANGED
@@ -1,18 +1,23 @@
1
1
  module Itsi
2
2
  module Signals
3
3
  DEFAULT_SIGNALS = ["DEFAULT", ""].freeze
4
- module TrapInterceptor
5
- def trap(signal, command = nil, &block)
6
- return super unless DEFAULT_SIGNALS.include?(command.to_s) && block.nil?
7
- Itsi::Server.reset_signal_handlers
4
+ module SignalTrap
5
+ def self.trap(signal, *args, &block)
6
+ if DEFAULT_SIGNALS.include?(command.to_s) && block.nil?
7
+ Itsi::Server.reset_signal_handlers
8
+ nil
9
+ else
10
+ super(signal, *args, &block)
11
+ end
8
12
  end
9
13
  end
10
- [Kernel, Signal].each do |receiver|
11
- receiver.singleton_class.prepend(TrapInterceptor)
12
- end
13
-
14
- [Object].each do |receiver|
15
- receiver.include(TrapInterceptor)
16
- end
17
14
  end
18
15
  end
16
+
17
+ [Kernel, Signal].each do |receiver|
18
+ receiver.singleton_class.prepend(Itsi::Signals::SignalTrap)
19
+ end
20
+
21
+ [Object].each do |receiver|
22
+ receiver.include(Itsi::Signals::SignalTrap)
23
+ end
metadata CHANGED
@@ -1,28 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: itsi-server
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter Coppieters
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-03-13 00:00:00.000000000 Z
10
+ date: 2025-03-14 00:00:00.000000000 Z
11
11
  dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: libclang
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - "~>"
17
- - !ruby/object:Gem::Version
18
- version: '14.0'
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - "~>"
24
- - !ruby/object:Gem::Version
25
- version: '14.0'
26
12
  - !ruby/object:Gem::Dependency
27
13
  name: rack
28
14
  requirement: !ruby/object:Gem::Requirement