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.
- checksums.yaml +4 -4
- data/Cargo.lock +977 -91
- data/crates/itsi_error/src/from.rs +1 -0
- data/crates/itsi_error/src/lib.rs +2 -0
- data/crates/itsi_server/Cargo.toml +6 -1
- data/crates/itsi_server/src/lib.rs +3 -0
- data/crates/itsi_server/src/server/bind.rs +5 -4
- data/crates/itsi_server/src/server/listener.rs +68 -16
- data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +6 -0
- data/crates/itsi_server/src/server/tls/locked_dir_cache.rs +94 -0
- data/crates/itsi_server/src/server/tls.rs +78 -41
- data/gems/scheduler/ext/itsi_error/src/from.rs +1 -0
- data/gems/scheduler/ext/itsi_error/src/lib.rs +2 -0
- data/gems/scheduler/ext/itsi_server/Cargo.toml +6 -1
- data/gems/scheduler/ext/itsi_server/src/lib.rs +3 -0
- data/gems/scheduler/ext/itsi_server/src/server/bind.rs +5 -4
- data/gems/scheduler/ext/itsi_server/src/server/listener.rs +68 -16
- data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/single_mode.rs +6 -0
- data/gems/scheduler/ext/itsi_server/src/server/tls/locked_dir_cache.rs +94 -0
- data/gems/scheduler/ext/itsi_server/src/server/tls.rs +78 -41
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/server/ext/itsi_error/src/from.rs +1 -0
- data/gems/server/ext/itsi_error/src/lib.rs +2 -0
- data/gems/server/ext/itsi_server/Cargo.toml +6 -1
- data/gems/server/ext/itsi_server/src/lib.rs +3 -0
- data/gems/server/ext/itsi_server/src/server/bind.rs +5 -4
- data/gems/server/ext/itsi_server/src/server/listener.rs +68 -16
- data/gems/server/ext/itsi_server/src/server/serve_strategy/single_mode.rs +6 -0
- data/gems/server/ext/itsi_server/src/server/tls/locked_dir_cache.rs +94 -0
- data/gems/server/ext/itsi_server/src/server/tls.rs +78 -41
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/version.rb +1 -1
- 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::
|
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::{
|
7
|
-
|
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(
|
18
|
-
|
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
|
-
|
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<
|
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
|
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![
|
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) ->
|
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
|
138
|
+
return PrivateKeyDer::try_from(keys[0].clone()).unwrap();
|
101
139
|
}
|
102
140
|
}
|
103
|
-
|
141
|
+
PrivateKeyDer::try_from(key_data).unwrap()
|
104
142
|
}
|
105
143
|
|
106
|
-
pub fn generate_ca_signed_cert(
|
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 =
|
144
|
-
let ca_cert =
|
145
|
-
Ok((
|
146
|
-
|
147
|
-
|
148
|
-
|
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
|
}
|
data/lib/itsi/version.rb
CHANGED
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.
|
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.
|
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.
|
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.
|
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.
|
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
|