itsi 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +977 -91
  3. data/crates/itsi_error/src/from.rs +1 -0
  4. data/crates/itsi_error/src/lib.rs +2 -0
  5. data/crates/itsi_server/Cargo.toml +6 -1
  6. data/crates/itsi_server/src/lib.rs +3 -0
  7. data/crates/itsi_server/src/server/bind.rs +5 -4
  8. data/crates/itsi_server/src/server/listener.rs +68 -16
  9. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +6 -0
  10. data/crates/itsi_server/src/server/tls/locked_dir_cache.rs +94 -0
  11. data/crates/itsi_server/src/server/tls.rs +78 -41
  12. data/gems/scheduler/ext/itsi_error/src/from.rs +1 -0
  13. data/gems/scheduler/ext/itsi_error/src/lib.rs +2 -0
  14. data/gems/scheduler/ext/itsi_server/Cargo.toml +6 -1
  15. data/gems/scheduler/ext/itsi_server/src/lib.rs +3 -0
  16. data/gems/scheduler/ext/itsi_server/src/server/bind.rs +5 -4
  17. data/gems/scheduler/ext/itsi_server/src/server/listener.rs +68 -16
  18. data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/single_mode.rs +6 -0
  19. data/gems/scheduler/ext/itsi_server/src/server/tls/locked_dir_cache.rs +94 -0
  20. data/gems/scheduler/ext/itsi_server/src/server/tls.rs +78 -41
  21. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  22. data/gems/server/ext/itsi_error/src/from.rs +1 -0
  23. data/gems/server/ext/itsi_error/src/lib.rs +2 -0
  24. data/gems/server/ext/itsi_server/Cargo.toml +6 -1
  25. data/gems/server/ext/itsi_server/src/lib.rs +3 -0
  26. data/gems/server/ext/itsi_server/src/server/bind.rs +5 -4
  27. data/gems/server/ext/itsi_server/src/server/listener.rs +68 -16
  28. data/gems/server/ext/itsi_server/src/server/serve_strategy/single_mode.rs +6 -0
  29. data/gems/server/ext/itsi_server/src/server/tls/locked_dir_cache.rs +94 -0
  30. data/gems/server/ext/itsi_server/src/server/tls.rs +78 -41
  31. data/gems/server/lib/itsi/server/version.rb +1 -1
  32. data/lib/itsi/version.rb +1 -1
  33. metadata +8 -5
@@ -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 {
@@ -0,0 +1,94 @@
1
+ use async_trait::async_trait;
2
+ use fs2::FileExt; // for lock_exclusive, unlock
3
+ use std::fs::OpenOptions;
4
+ use std::io::Error as IoError;
5
+ use std::path::{Path, PathBuf};
6
+ use tokio_rustls_acme::caches::DirCache;
7
+ use tokio_rustls_acme::{AccountCache, CertCache};
8
+
9
+ /// A wrapper around DirCache that locks a file before writing cert/account data.
10
+ pub struct LockedDirCache<P: AsRef<Path> + Send + Sync> {
11
+ inner: DirCache<P>,
12
+ lock_path: PathBuf,
13
+ }
14
+
15
+ impl<P: AsRef<Path> + Send + Sync> LockedDirCache<P> {
16
+ pub fn new(dir: P) -> Self {
17
+ let dir_path = dir.as_ref().to_path_buf();
18
+ let lock_path = dir_path.join(".acme.lock");
19
+ Self {
20
+ inner: DirCache::new(dir),
21
+ lock_path,
22
+ }
23
+ }
24
+
25
+ fn lock_exclusive(&self) -> Result<std::fs::File, IoError> {
26
+ let lockfile = OpenOptions::new()
27
+ .write(true)
28
+ .truncate(true)
29
+ .open(&self.lock_path)?;
30
+ lockfile.lock_exclusive()?;
31
+ Ok(lockfile)
32
+ }
33
+ }
34
+
35
+ #[async_trait]
36
+ impl<P: AsRef<Path> + Send + Sync> CertCache for LockedDirCache<P> {
37
+ type EC = IoError;
38
+
39
+ async fn load_cert(
40
+ &self,
41
+ domains: &[String],
42
+ directory_url: &str,
43
+ ) -> Result<Option<Vec<u8>>, Self::EC> {
44
+ // Just delegate to the inner DirCache
45
+ self.inner.load_cert(domains, directory_url).await
46
+ }
47
+
48
+ async fn store_cert(
49
+ &self,
50
+ domains: &[String],
51
+ directory_url: &str,
52
+ cert: &[u8],
53
+ ) -> Result<(), Self::EC> {
54
+ // Acquire the lock before storing
55
+ let lockfile = self.lock_exclusive()?;
56
+
57
+ // Perform the store operation
58
+ let result = self.inner.store_cert(domains, directory_url, cert).await;
59
+
60
+ // Unlock and return
61
+ let _ = fs2::FileExt::unlock(&lockfile);
62
+ result
63
+ }
64
+ }
65
+
66
+ #[async_trait]
67
+ impl<P: AsRef<Path> + Send + Sync> AccountCache for LockedDirCache<P> {
68
+ type EA = IoError;
69
+
70
+ async fn load_account(
71
+ &self,
72
+ contact: &[String],
73
+ directory_url: &str,
74
+ ) -> Result<Option<Vec<u8>>, Self::EA> {
75
+ self.inner.load_account(contact, directory_url).await
76
+ }
77
+
78
+ async fn store_account(
79
+ &self,
80
+ contact: &[String],
81
+ directory_url: &str,
82
+ account: &[u8],
83
+ ) -> Result<(), Self::EA> {
84
+ let lockfile = self.lock_exclusive()?;
85
+
86
+ let result = self
87
+ .inner
88
+ .store_account(contact, directory_url, account)
89
+ .await;
90
+
91
+ let _ = fs2::FileExt::unlock(&lockfile);
92
+ result
93
+ }
94
+ }
@@ -1,21 +1,70 @@
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
+ use locked_dir_cache::LockedDirCache;
4
5
  use rcgen::{CertificateParams, DnType, KeyPair, SanType};
6
+ use rustls::pki_types::{CertificateDer, PrivateKeyDer};
5
7
  use rustls_pemfile::{certs, pkcs8_private_keys};
6
- use std::{collections::HashMap, fs, io::BufReader};
7
- use tokio_rustls::rustls::{Certificate, PrivateKey, ServerConfig};
8
-
8
+ use std::{
9
+ collections::HashMap,
10
+ env, fs,
11
+ io::{BufReader, Error},
12
+ sync::Arc,
13
+ };
14
+ use tokio::sync::Mutex;
15
+ use tokio_rustls::{rustls::ServerConfig, TlsAcceptor};
16
+ use tokio_rustls_acme::{AcmeAcceptor, AcmeConfig, AcmeState};
17
+ mod locked_dir_cache;
9
18
  const ITS_CA_CERT: &str = include_str!("./itsi_ca/itsi_ca.crt");
10
19
  const ITS_CA_KEY: &str = include_str!("./itsi_ca/itsi_ca.key");
11
20
 
21
+ #[derive(Clone)]
22
+ pub enum ItsiTlsAcceptor {
23
+ Manual(TlsAcceptor),
24
+ Automatic(
25
+ AcmeAcceptor,
26
+ Arc<Mutex<AcmeState<Error>>>,
27
+ Arc<ServerConfig>,
28
+ ),
29
+ }
30
+
12
31
  // Generates a TLS configuration based on either :
13
32
  // * Input "cert" and "key" options (either paths or Base64-encoded strings) or
14
33
  // * Performs automatic certificate generation/retrieval. Generated certs use an internal self-signed Isti CA.
15
34
  // If a non-local host or optional domain parameter is provided,
16
35
  // 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);
36
+ pub fn configure_tls(
37
+ host: &str,
38
+ query_params: &HashMap<String, String>,
39
+ ) -> Result<ItsiTlsAcceptor> {
40
+ let domains = query_params
41
+ .get("domains")
42
+ .map(|v| v.split(',').map(String::from).collect::<Vec<_>>());
43
+
44
+ if query_params.get("cert").is_none() || query_params.get("key").is_none() {
45
+ if let Some(domains) = domains {
46
+ let directory_url = env::var("ACME_DIRECTORY_URL")
47
+ .unwrap_or_else(|_| "https://acme-v02.api.letsencrypt.org/directory".to_string());
48
+ info!(
49
+ domains = format!("{:?}", domains),
50
+ directory_url, "Requesting acme cert"
51
+ );
52
+ let acme_state = AcmeConfig::new(domains)
53
+ .contact(["mailto:wc@pico.net.nz"])
54
+ .cache(LockedDirCache::new("./rustls_acme_cache"))
55
+ .directory(directory_url)
56
+ .state();
57
+ let rustls_config = ServerConfig::builder()
58
+ .with_no_client_auth()
59
+ .with_cert_resolver(acme_state.resolver());
60
+ let acceptor = acme_state.acceptor();
61
+ return Ok(ItsiTlsAcceptor::Automatic(
62
+ acceptor,
63
+ Arc::new(Mutex::new(acme_state)),
64
+ Arc::new(rustls_config),
65
+ ));
66
+ }
67
+ }
19
68
  let (certs, key) = if let (Some(cert_path), Some(key_path)) =
20
69
  (query_params.get("cert"), query_params.get("key"))
21
70
  {
@@ -24,36 +73,19 @@ pub fn configure_tls(host: &str, query_params: &HashMap<String, String>) -> Resu
24
73
  let key = load_private_key(key_path);
25
74
  (certs, key)
26
75
  } 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
- }
76
+ generate_ca_signed_cert(vec![host.to_owned()])?
44
77
  };
45
78
 
46
79
  let mut config = ServerConfig::builder()
47
- .with_safe_defaults()
48
80
  .with_no_client_auth()
49
81
  .with_single_cert(certs, key)
50
82
  .expect("Failed to build TLS config");
51
83
 
52
84
  config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
53
- Ok(config)
85
+ Ok(ItsiTlsAcceptor::Manual(TlsAcceptor::from(Arc::new(config))))
54
86
  }
55
87
 
56
- pub fn load_certs(path: &str) -> Vec<Certificate> {
88
+ pub fn load_certs(path: &str) -> Vec<CertificateDer<'static>> {
57
89
  let data = if let Some(stripped) = path.strip_prefix("base64:") {
58
90
  general_purpose::STANDARD
59
91
  .decode(stripped)
@@ -71,14 +103,20 @@ pub fn load_certs(path: &str) -> Vec<Certificate> {
71
103
  })
72
104
  .collect::<Result<_>>()
73
105
  .expect("Failed to parse certificate file");
74
- certs_der.into_iter().map(Certificate).collect()
106
+ certs_der
107
+ .into_iter()
108
+ .map(|vec| {
109
+ // Convert the owned Vec<u8> into a CertificateDer and force 'static.
110
+ unsafe { std::mem::transmute(CertificateDer::from(vec)) }
111
+ })
112
+ .collect()
75
113
  } else {
76
- vec![Certificate(data)]
114
+ vec![CertificateDer::from(data)]
77
115
  }
78
116
  }
79
117
 
80
118
  /// Loads a private key from a file or Base64.
81
- pub fn load_private_key(path: &str) -> PrivateKey {
119
+ pub fn load_private_key(path: &str) -> PrivateKeyDer<'static> {
82
120
  let key_data = if let Some(stripped) = path.strip_prefix("base64:") {
83
121
  general_purpose::STANDARD
84
122
  .decode(stripped)
@@ -97,13 +135,15 @@ pub fn load_private_key(path: &str) -> PrivateKey {
97
135
  .collect::<Result<_>>()
98
136
  .expect("Failed to parse private key");
99
137
  if !keys.is_empty() {
100
- return PrivateKey(keys[0].clone());
138
+ return PrivateKeyDer::try_from(keys[0].clone()).unwrap();
101
139
  }
102
140
  }
103
- PrivateKey(key_data)
141
+ PrivateKeyDer::try_from(key_data).unwrap()
104
142
  }
105
143
 
106
- pub fn generate_ca_signed_cert(domains: Vec<String>) -> Result<(Vec<Certificate>, PrivateKey)> {
144
+ pub fn generate_ca_signed_cert(
145
+ domains: Vec<String>,
146
+ ) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
107
147
  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
148
 
109
149
  let ca_kp = KeyPair::from_pem(ITS_CA_KEY).expect("Failed to load embedded CA key");
@@ -140,13 +180,10 @@ pub fn generate_ca_signed_cert(domains: Vec<String>) -> Result<(Vec<Certificate>
140
180
 
141
181
  let ee_cert = ee_params.signed_by(&ee_key, &ca_cert, &ca_kp).unwrap();
142
182
  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)
183
+ let ee_cert = CertificateDer::from(ee_cert_der);
184
+ let ca_cert = CertificateDer::from(ca_cert.der().to_vec());
185
+ Ok((
186
+ vec![ee_cert, ca_cert],
187
+ PrivateKeyDer::try_from(ee_key.serialize_der()).unwrap(),
188
+ ))
152
189
  }
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Itsi
4
4
  class Server
5
- VERSION = "0.1.3"
5
+ VERSION = "0.1.5"
6
6
  end
7
7
  end
data/lib/itsi/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Itsi
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.5"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: itsi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter Coppieters
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.1.2
18
+ version: 0.1.4
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.1.2
25
+ version: 0.1.4
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: itsi-scheduler
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: 0.1.2
32
+ version: 0.1.4
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 0.1.2
39
+ version: 0.1.4
40
40
  description: Wrapper Gem for both the Itsi server and it's Fiber scheduler
41
41
  email:
42
42
  - wc@pico.net.nz
@@ -91,6 +91,7 @@ files:
91
91
  - crates/itsi_server/src/server/signal.rs
92
92
  - crates/itsi_server/src/server/thread_worker.rs
93
93
  - crates/itsi_server/src/server/tls.rs
94
+ - crates/itsi_server/src/server/tls/locked_dir_cache.rs
94
95
  - crates/itsi_tracing/Cargo.lock
95
96
  - crates/itsi_tracing/Cargo.toml
96
97
  - crates/itsi_tracing/src/lib.rs
@@ -148,6 +149,7 @@ files:
148
149
  - gems/scheduler/ext/itsi_server/src/server/signal.rs
149
150
  - gems/scheduler/ext/itsi_server/src/server/thread_worker.rs
150
151
  - gems/scheduler/ext/itsi_server/src/server/tls.rs
152
+ - gems/scheduler/ext/itsi_server/src/server/tls/locked_dir_cache.rs
151
153
  - gems/scheduler/ext/itsi_tracing/Cargo.lock
152
154
  - gems/scheduler/ext/itsi_tracing/Cargo.toml
153
155
  - gems/scheduler/ext/itsi_tracing/src/lib.rs
@@ -218,6 +220,7 @@ files:
218
220
  - gems/server/ext/itsi_server/src/server/signal.rs
219
221
  - gems/server/ext/itsi_server/src/server/thread_worker.rs
220
222
  - gems/server/ext/itsi_server/src/server/tls.rs
223
+ - gems/server/ext/itsi_server/src/server/tls/locked_dir_cache.rs
221
224
  - gems/server/ext/itsi_tracing/Cargo.lock
222
225
  - gems/server/ext/itsi_tracing/Cargo.toml
223
226
  - gems/server/ext/itsi_tracing/src/lib.rs