itsi 0.1.5 → 0.1.7
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 +49 -0
- data/crates/itsi_error/src/lib.rs +1 -1
- data/crates/itsi_server/Cargo.toml +1 -0
- data/crates/itsi_server/src/env.rs +43 -0
- data/crates/itsi_server/src/lib.rs +1 -0
- data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +1 -1
- data/crates/itsi_server/src/server/tls/locked_dir_cache.rs +55 -17
- data/crates/itsi_server/src/server/tls.rs +91 -22
- data/gems/scheduler/ext/itsi_error/src/lib.rs +1 -1
- data/gems/scheduler/ext/itsi_server/Cargo.toml +1 -0
- data/gems/scheduler/ext/itsi_server/src/env.rs +43 -0
- data/gems/scheduler/ext/itsi_server/src/lib.rs +1 -0
- data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/single_mode.rs +1 -1
- data/gems/scheduler/ext/itsi_server/src/server/tls/locked_dir_cache.rs +55 -17
- data/gems/scheduler/ext/itsi_server/src/server/tls.rs +91 -22
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/server/ext/itsi_error/src/lib.rs +1 -1
- data/gems/server/ext/itsi_server/Cargo.toml +1 -0
- data/gems/server/ext/itsi_server/src/env.rs +43 -0
- data/gems/server/ext/itsi_server/src/lib.rs +1 -0
- data/gems/server/ext/itsi_server/src/server/serve_strategy/single_mode.rs +1 -1
- data/gems/server/ext/itsi_server/src/server/tls/locked_dir_cache.rs +55 -17
- data/gems/server/ext/itsi_server/src/server/tls.rs +91 -22
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/version.rb +1 -1
- data/tasks.txt +0 -2
- metadata +9 -12
- data/crates/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -13
- data/crates/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -5
- data/gems/scheduler/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -13
- data/gems/scheduler/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -5
- data/gems/server/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -13
- data/gems/server/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 88879d13925e0738ba948eb707096da6e8b039cb317ce0975333295550725c70
|
4
|
+
data.tar.gz: 0eaf0dc170f15efb83428313c7b2554f1e19fe8c9dc81a8c5c4d553a03b586cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e37c041ee042a7d7e69f4f577864e08890aa3bb9fe855c292fc95950c9fc682d5afa0e2e74fc4bd5fb9c5cb7f60733e6e5fcf250f205742c6caec32a23399c68
|
7
|
+
data.tar.gz: d7c9cdd9bcde2a58192798e601a8b5a3099a76b9e924d4b58e206c11a3f015b094a0d975f4f87fd5338e3cfd05f46cd3f4684f59fb140643be85e8452be84195
|
data/Cargo.lock
CHANGED
@@ -398,6 +398,27 @@ dependencies = [
|
|
398
398
|
"unicode-xid",
|
399
399
|
]
|
400
400
|
|
401
|
+
[[package]]
|
402
|
+
name = "dirs"
|
403
|
+
version = "6.0.0"
|
404
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
405
|
+
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
406
|
+
dependencies = [
|
407
|
+
"dirs-sys",
|
408
|
+
]
|
409
|
+
|
410
|
+
[[package]]
|
411
|
+
name = "dirs-sys"
|
412
|
+
version = "0.5.0"
|
413
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
414
|
+
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
415
|
+
dependencies = [
|
416
|
+
"libc",
|
417
|
+
"option-ext",
|
418
|
+
"redox_users",
|
419
|
+
"windows-sys 0.59.0",
|
420
|
+
]
|
421
|
+
|
401
422
|
[[package]]
|
402
423
|
name = "displaydoc"
|
403
424
|
version = "0.2.5"
|
@@ -989,6 +1010,7 @@ dependencies = [
|
|
989
1010
|
"bytes",
|
990
1011
|
"crossbeam",
|
991
1012
|
"derive_more",
|
1013
|
+
"dirs",
|
992
1014
|
"fs2",
|
993
1015
|
"futures",
|
994
1016
|
"http",
|
@@ -1106,6 +1128,16 @@ dependencies = [
|
|
1106
1128
|
"windows-targets 0.52.6",
|
1107
1129
|
]
|
1108
1130
|
|
1131
|
+
[[package]]
|
1132
|
+
name = "libredox"
|
1133
|
+
version = "0.1.3"
|
1134
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
1135
|
+
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
1136
|
+
dependencies = [
|
1137
|
+
"bitflags",
|
1138
|
+
"libc",
|
1139
|
+
]
|
1140
|
+
|
1109
1141
|
[[package]]
|
1110
1142
|
name = "linux-raw-sys"
|
1111
1143
|
version = "0.4.15"
|
@@ -1321,6 +1353,12 @@ version = "1.20.3"
|
|
1321
1353
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
1322
1354
|
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
1323
1355
|
|
1356
|
+
[[package]]
|
1357
|
+
name = "option-ext"
|
1358
|
+
version = "0.2.0"
|
1359
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
1360
|
+
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
1361
|
+
|
1324
1362
|
[[package]]
|
1325
1363
|
name = "overload"
|
1326
1364
|
version = "0.1.1"
|
@@ -1608,6 +1646,17 @@ dependencies = [
|
|
1608
1646
|
"bitflags",
|
1609
1647
|
]
|
1610
1648
|
|
1649
|
+
[[package]]
|
1650
|
+
name = "redox_users"
|
1651
|
+
version = "0.5.0"
|
1652
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
1653
|
+
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
1654
|
+
dependencies = [
|
1655
|
+
"getrandom 0.2.15",
|
1656
|
+
"libredox",
|
1657
|
+
"thiserror 2.0.11",
|
1658
|
+
]
|
1659
|
+
|
1611
1660
|
[[package]]
|
1612
1661
|
name = "regex"
|
1613
1662
|
version = "1.11.1"
|
@@ -0,0 +1,43 @@
|
|
1
|
+
use std::{
|
2
|
+
env::{var, VarError},
|
3
|
+
path::PathBuf,
|
4
|
+
sync::LazyLock,
|
5
|
+
};
|
6
|
+
|
7
|
+
type StringVar = LazyLock<String>;
|
8
|
+
type MaybeStringVar = LazyLock<Result<String, VarError>>;
|
9
|
+
type PathVar = LazyLock<PathBuf>;
|
10
|
+
|
11
|
+
/// ACME Configuration for auto-generating production certificates
|
12
|
+
/// *ITSI_ACME_CACHE_DIR* - Directory to store cached certificates
|
13
|
+
/// so that these are not regenerated every time the server starts
|
14
|
+
pub static ITSI_ACME_CACHE_DIR: StringVar = LazyLock::new(|| {
|
15
|
+
var("ITSI_ACME_CACHE_DIR").unwrap_or_else(|_| "./.rustls_acme_cache".to_string())
|
16
|
+
});
|
17
|
+
|
18
|
+
/// *ITSI_ACME_CONTACT_EMAIL* - Contact Email address to provide to ACME server during certificate renewal
|
19
|
+
pub static ITSI_ACME_CONTACT_EMAIL: MaybeStringVar =
|
20
|
+
LazyLock::new(|| var("ITSI_ACME_CONTACT_EMAIL"));
|
21
|
+
|
22
|
+
/// *ITSI_ACME_CA_PEM_PATH* - Optional CA Pem path, used for testing with non-trusted CAs for certifcate generation.
|
23
|
+
pub static ITSI_ACME_CA_PEM_PATH: MaybeStringVar = LazyLock::new(|| var("ITSI_ACME_CA_PEM_PATH"));
|
24
|
+
|
25
|
+
/// *ITSI_ACME_DIRECTORY_URL* - Directory URL to use for ACME certificate generation.
|
26
|
+
pub static ITSI_ACME_DIRECTORY_URL: StringVar = LazyLock::new(|| {
|
27
|
+
var("ITSI_ACME_DIRECTORY_URL")
|
28
|
+
.unwrap_or_else(|_| "https://acme-v02.api.letsencrypt.org/directory".to_string())
|
29
|
+
});
|
30
|
+
|
31
|
+
/// *ITSI_ACME_LOCK_FILE_NAME* - Name of the lock file used to prevent concurrent certificate generation.
|
32
|
+
pub static ITSI_ACME_LOCK_FILE_NAME: StringVar =
|
33
|
+
LazyLock::new(|| var("ITSI_ACME_LOCK_FILE_NAME").unwrap_or(".acme.lock".to_string()));
|
34
|
+
|
35
|
+
pub static ITSI_LOCAL_CA_DIR: PathVar = LazyLock::new(|| {
|
36
|
+
var("ITSI_LOCAL_CA_DIR")
|
37
|
+
.map(PathBuf::from)
|
38
|
+
.unwrap_or_else(|_| {
|
39
|
+
dirs::home_dir()
|
40
|
+
.expect("Failed to find HOME directory when initializing ITSI_LOCAL_CA_DIR")
|
41
|
+
.join(".itsi")
|
42
|
+
})
|
43
|
+
});
|
@@ -83,7 +83,7 @@ impl SingleMode {
|
|
83
83
|
Ok(())
|
84
84
|
}
|
85
85
|
|
86
|
-
#[instrument(parent=None, skip(self))]
|
86
|
+
#[instrument(parent=None, skip(self), fields(pid=format!("{}", Pid::this())))]
|
87
87
|
pub fn run(self: Arc<Self>) -> Result<()> {
|
88
88
|
let mut listener_task_set = JoinSet::new();
|
89
89
|
let self_ref = Arc::new(self);
|
@@ -1,34 +1,68 @@
|
|
1
1
|
use async_trait::async_trait;
|
2
|
-
use fs2::FileExt;
|
3
|
-
use
|
2
|
+
use fs2::FileExt;
|
3
|
+
use parking_lot::Mutex;
|
4
|
+
use std::fs::{self, OpenOptions};
|
4
5
|
use std::io::Error as IoError;
|
5
6
|
use std::path::{Path, PathBuf};
|
6
7
|
use tokio_rustls_acme::caches::DirCache;
|
7
8
|
use tokio_rustls_acme::{AccountCache, CertCache};
|
8
9
|
|
10
|
+
use crate::env::ITSI_ACME_LOCK_FILE_NAME;
|
11
|
+
|
9
12
|
/// A wrapper around DirCache that locks a file before writing cert/account data.
|
10
13
|
pub struct LockedDirCache<P: AsRef<Path> + Send + Sync> {
|
11
14
|
inner: DirCache<P>,
|
12
15
|
lock_path: PathBuf,
|
16
|
+
current_lock: Mutex<Option<std::fs::File>>,
|
13
17
|
}
|
14
18
|
|
15
19
|
impl<P: AsRef<Path> + Send + Sync> LockedDirCache<P> {
|
16
20
|
pub fn new(dir: P) -> Self {
|
17
21
|
let dir_path = dir.as_ref().to_path_buf();
|
18
|
-
|
22
|
+
std::fs::create_dir_all(&dir_path).unwrap();
|
23
|
+
let lock_path = dir_path.join(&*ITSI_ACME_LOCK_FILE_NAME);
|
24
|
+
Self::touch_file(&lock_path).expect("Failed to create lock file");
|
25
|
+
|
19
26
|
Self {
|
20
27
|
inner: DirCache::new(dir),
|
21
28
|
lock_path,
|
29
|
+
current_lock: Mutex::new(None),
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
fn touch_file(path: &PathBuf) -> std::io::Result<()> {
|
34
|
+
if let Some(parent) = path.parent() {
|
35
|
+
fs::create_dir_all(parent)?;
|
22
36
|
}
|
37
|
+
fs::OpenOptions::new()
|
38
|
+
.create(true)
|
39
|
+
.write(true)
|
40
|
+
.truncate(true)
|
41
|
+
.open(path)?;
|
42
|
+
Ok(())
|
23
43
|
}
|
24
44
|
|
25
|
-
fn lock_exclusive(&self) -> Result<
|
45
|
+
fn lock_exclusive(&self) -> Result<(), IoError> {
|
46
|
+
if self.current_lock.lock().is_some() {
|
47
|
+
return Ok(());
|
48
|
+
}
|
49
|
+
|
50
|
+
if let Some(parent) = self.lock_path.parent() {
|
51
|
+
std::fs::create_dir_all(parent)?;
|
52
|
+
}
|
26
53
|
let lockfile = OpenOptions::new()
|
54
|
+
.create(true)
|
27
55
|
.write(true)
|
28
56
|
.truncate(true)
|
29
57
|
.open(&self.lock_path)?;
|
30
58
|
lockfile.lock_exclusive()?;
|
31
|
-
|
59
|
+
*self.current_lock.lock() = Some(lockfile);
|
60
|
+
Ok(())
|
61
|
+
}
|
62
|
+
|
63
|
+
fn unlock(&self) -> Result<(), IoError> {
|
64
|
+
self.current_lock.lock().take();
|
65
|
+
Ok(())
|
32
66
|
}
|
33
67
|
}
|
34
68
|
|
@@ -41,8 +75,14 @@ impl<P: AsRef<Path> + Send + Sync> CertCache for LockedDirCache<P> {
|
|
41
75
|
domains: &[String],
|
42
76
|
directory_url: &str,
|
43
77
|
) -> Result<Option<Vec<u8>>, Self::EC> {
|
44
|
-
|
45
|
-
self.inner.load_cert(domains, directory_url).await
|
78
|
+
self.lock_exclusive()?;
|
79
|
+
let result = self.inner.load_cert(domains, directory_url).await;
|
80
|
+
|
81
|
+
if let Ok(Some(_)) = result {
|
82
|
+
self.unlock()?;
|
83
|
+
}
|
84
|
+
|
85
|
+
result
|
46
86
|
}
|
47
87
|
|
48
88
|
async fn store_cert(
|
@@ -52,13 +92,14 @@ impl<P: AsRef<Path> + Send + Sync> CertCache for LockedDirCache<P> {
|
|
52
92
|
cert: &[u8],
|
53
93
|
) -> Result<(), Self::EC> {
|
54
94
|
// Acquire the lock before storing
|
55
|
-
|
95
|
+
self.lock_exclusive()?;
|
56
96
|
|
57
97
|
// Perform the store operation
|
58
98
|
let result = self.inner.store_cert(domains, directory_url, cert).await;
|
59
99
|
|
60
|
-
|
61
|
-
|
100
|
+
if let Ok(()) = result {
|
101
|
+
self.unlock()?;
|
102
|
+
}
|
62
103
|
result
|
63
104
|
}
|
64
105
|
}
|
@@ -72,6 +113,7 @@ impl<P: AsRef<Path> + Send + Sync> AccountCache for LockedDirCache<P> {
|
|
72
113
|
contact: &[String],
|
73
114
|
directory_url: &str,
|
74
115
|
) -> Result<Option<Vec<u8>>, Self::EA> {
|
116
|
+
self.lock_exclusive()?;
|
75
117
|
self.inner.load_account(contact, directory_url).await
|
76
118
|
}
|
77
119
|
|
@@ -81,14 +123,10 @@ impl<P: AsRef<Path> + Send + Sync> AccountCache for LockedDirCache<P> {
|
|
81
123
|
directory_url: &str,
|
82
124
|
account: &[u8],
|
83
125
|
) -> Result<(), Self::EA> {
|
84
|
-
|
126
|
+
self.lock_exclusive()?;
|
85
127
|
|
86
|
-
|
87
|
-
.inner
|
128
|
+
self.inner
|
88
129
|
.store_account(contact, directory_url, account)
|
89
|
-
.await
|
90
|
-
|
91
|
-
let _ = fs2::FileExt::unlock(&lockfile);
|
92
|
-
result
|
130
|
+
.await
|
93
131
|
}
|
94
132
|
}
|
@@ -2,21 +2,29 @@ use base64::{engine::general_purpose, Engine as _};
|
|
2
2
|
use itsi_error::Result;
|
3
3
|
use itsi_tracing::info;
|
4
4
|
use locked_dir_cache::LockedDirCache;
|
5
|
-
use rcgen::{
|
6
|
-
|
5
|
+
use rcgen::{
|
6
|
+
generate_simple_self_signed, CertificateParams, CertifiedKey, DnType, KeyPair, SanType,
|
7
|
+
};
|
8
|
+
use rustls::{
|
9
|
+
pki_types::{CertificateDer, PrivateKeyDer},
|
10
|
+
ClientConfig, RootCertStore,
|
11
|
+
};
|
7
12
|
use rustls_pemfile::{certs, pkcs8_private_keys};
|
8
13
|
use std::{
|
9
14
|
collections::HashMap,
|
10
|
-
|
15
|
+
fs,
|
11
16
|
io::{BufReader, Error},
|
12
17
|
sync::Arc,
|
13
18
|
};
|
14
19
|
use tokio::sync::Mutex;
|
15
20
|
use tokio_rustls::{rustls::ServerConfig, TlsAcceptor};
|
16
21
|
use tokio_rustls_acme::{AcmeAcceptor, AcmeConfig, AcmeState};
|
22
|
+
|
23
|
+
use crate::env::{
|
24
|
+
ITSI_ACME_CACHE_DIR, ITSI_ACME_CA_PEM_PATH, ITSI_ACME_CONTACT_EMAIL, ITSI_ACME_DIRECTORY_URL,
|
25
|
+
ITSI_LOCAL_CA_DIR,
|
26
|
+
};
|
17
27
|
mod locked_dir_cache;
|
18
|
-
const ITS_CA_CERT: &str = include_str!("./itsi_ca/itsi_ca.crt");
|
19
|
-
const ITS_CA_KEY: &str = include_str!("./itsi_ca/itsi_ca.key");
|
20
28
|
|
21
29
|
#[derive(Clone)]
|
22
30
|
pub enum ItsiTlsAcceptor {
|
@@ -28,11 +36,12 @@ pub enum ItsiTlsAcceptor {
|
|
28
36
|
),
|
29
37
|
}
|
30
38
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
39
|
+
/// Generates a TLS configuration based on either :
|
40
|
+
/// * Input "cert" and "key" options (either paths or Base64-encoded strings) or
|
41
|
+
/// * Performs automatic certificate generation/retrieval. Generated certs use an internal self-signed Isti CA.
|
42
|
+
///
|
43
|
+
/// If a non-local host or optional domain parameter is provided,
|
44
|
+
/// an automated certificate will attempt to be fetched using let's encrypt.
|
36
45
|
pub fn configure_tls(
|
37
46
|
host: &str,
|
38
47
|
query_params: &HashMap<String, String>,
|
@@ -41,22 +50,55 @@ pub fn configure_tls(
|
|
41
50
|
.get("domains")
|
42
51
|
.map(|v| v.split(',').map(String::from).collect::<Vec<_>>());
|
43
52
|
|
44
|
-
if query_params.get("cert").
|
53
|
+
if query_params.get("cert").is_some_and(|c| c == "auto") {
|
45
54
|
if let Some(domains) = domains {
|
46
|
-
let directory_url =
|
47
|
-
.unwrap_or_else(|_| "https://acme-v02.api.letsencrypt.org/directory".to_string());
|
55
|
+
let directory_url = &*ITSI_ACME_DIRECTORY_URL;
|
48
56
|
info!(
|
49
57
|
domains = format!("{:?}", domains),
|
50
58
|
directory_url, "Requesting acme cert"
|
51
59
|
);
|
52
|
-
|
53
|
-
|
54
|
-
.
|
55
|
-
|
56
|
-
|
57
|
-
|
60
|
+
|
61
|
+
let acme_config = AcmeConfig::new(domains)
|
62
|
+
.contact([format!("mailto:{}", (*ITSI_ACME_CONTACT_EMAIL).as_ref().map_err(|_| {
|
63
|
+
itsi_error::ItsiError::ArgumentError(
|
64
|
+
"ITSI_ACME_CONTACT_EMAIL must be set before you can auto-generate production certificates"
|
65
|
+
.to_string(),
|
66
|
+
)
|
67
|
+
})?)])
|
68
|
+
.cache(LockedDirCache::new(&*ITSI_ACME_CACHE_DIR))
|
69
|
+
.directory(directory_url);
|
70
|
+
|
71
|
+
let acme_state = if let Ok(ca_pem_path) = &*ITSI_ACME_CA_PEM_PATH {
|
72
|
+
let mut root_cert_store = RootCertStore::empty();
|
73
|
+
|
74
|
+
let ca_pem = fs::read(ca_pem_path).expect("failed to read CA pem file");
|
75
|
+
let mut ca_reader = BufReader::new(&ca_pem[..]);
|
76
|
+
let der_certs: Vec<CertificateDer> = certs(&mut ca_reader)
|
77
|
+
.collect::<std::result::Result<Vec<CertificateDer>, _>>()
|
78
|
+
.map_err(|e| {
|
79
|
+
itsi_error::ItsiError::ArgumentError(format!(
|
80
|
+
"Invalid ACME CA Pem path {:?}",
|
81
|
+
e
|
82
|
+
))
|
83
|
+
})?;
|
84
|
+
root_cert_store.add_parsable_certificates(der_certs);
|
85
|
+
|
86
|
+
let client_config = ClientConfig::builder()
|
87
|
+
.with_root_certificates(root_cert_store)
|
88
|
+
.with_no_client_auth();
|
89
|
+
acme_config
|
90
|
+
.client_tls_config(Arc::new(client_config))
|
91
|
+
.state()
|
92
|
+
} else {
|
93
|
+
acme_config.state()
|
94
|
+
};
|
95
|
+
|
96
|
+
let mut rustls_config = ServerConfig::builder()
|
58
97
|
.with_no_client_auth()
|
59
98
|
.with_cert_resolver(acme_state.resolver());
|
99
|
+
|
100
|
+
rustls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
101
|
+
|
60
102
|
let acceptor = acme_state.acceptor();
|
61
103
|
return Ok(ItsiTlsAcceptor::Automatic(
|
62
104
|
acceptor,
|
@@ -73,7 +115,7 @@ pub fn configure_tls(
|
|
73
115
|
let key = load_private_key(key_path);
|
74
116
|
(certs, key)
|
75
117
|
} else {
|
76
|
-
generate_ca_signed_cert(vec![host.to_owned()])?
|
118
|
+
generate_ca_signed_cert(domains.unwrap_or(vec![host.to_owned()]))?
|
77
119
|
};
|
78
120
|
|
79
121
|
let mut config = ServerConfig::builder()
|
@@ -145,9 +187,10 @@ pub fn generate_ca_signed_cert(
|
|
145
187
|
domains: Vec<String>,
|
146
188
|
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
|
147
189
|
info!("Generating New Itsi CA - Self signed Certificate. Use `itsi ca export` to export the CA certificate for import into your local trust store.");
|
190
|
+
let (ca_key_pem, ca_cert_pem) = get_or_create_local_dev_ca()?;
|
148
191
|
|
149
|
-
let ca_kp = KeyPair::from_pem(
|
150
|
-
let ca_cert = CertificateParams::from_ca_cert_pem(
|
192
|
+
let ca_kp = KeyPair::from_pem(&ca_key_pem).expect("Failed to load CA key");
|
193
|
+
let ca_cert = CertificateParams::from_ca_cert_pem(&ca_cert_pem)
|
151
194
|
.expect("Failed to parse embedded CA certificate")
|
152
195
|
.self_signed(&ca_kp)
|
153
196
|
.expect("Failed to self-sign embedded CA cert");
|
@@ -187,3 +230,29 @@ pub fn generate_ca_signed_cert(
|
|
187
230
|
PrivateKeyDer::try_from(ee_key.serialize_der()).unwrap(),
|
188
231
|
))
|
189
232
|
}
|
233
|
+
|
234
|
+
fn get_or_create_local_dev_ca() -> Result<(String, String)> {
|
235
|
+
let ca_dir = &*ITSI_LOCAL_CA_DIR;
|
236
|
+
fs::create_dir_all(ca_dir)?;
|
237
|
+
|
238
|
+
let key_path = ca_dir.join("itsi_dev_ca.key");
|
239
|
+
let cert_path = ca_dir.join("itsi_dev_ca.crt");
|
240
|
+
|
241
|
+
if key_path.exists() && cert_path.exists() {
|
242
|
+
// Already have a local CA
|
243
|
+
let key_pem = fs::read_to_string(&key_path)?;
|
244
|
+
let cert_pem = fs::read_to_string(&cert_path)?;
|
245
|
+
|
246
|
+
Ok((key_pem, cert_pem))
|
247
|
+
} else {
|
248
|
+
let subject_alt_names = vec!["dev.itsi.fyi".to_string(), "localhost".to_string()];
|
249
|
+
|
250
|
+
let CertifiedKey { cert, key_pair } =
|
251
|
+
generate_simple_self_signed(subject_alt_names).unwrap();
|
252
|
+
|
253
|
+
fs::write(&key_path, key_pair.serialize_pem())?;
|
254
|
+
fs::write(&cert_path, cert.pem())?;
|
255
|
+
|
256
|
+
Ok((key_pair.serialize_pem(), cert.pem()))
|
257
|
+
}
|
258
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
use std::{
|
2
|
+
env::{var, VarError},
|
3
|
+
path::PathBuf,
|
4
|
+
sync::LazyLock,
|
5
|
+
};
|
6
|
+
|
7
|
+
type StringVar = LazyLock<String>;
|
8
|
+
type MaybeStringVar = LazyLock<Result<String, VarError>>;
|
9
|
+
type PathVar = LazyLock<PathBuf>;
|
10
|
+
|
11
|
+
/// ACME Configuration for auto-generating production certificates
|
12
|
+
/// *ITSI_ACME_CACHE_DIR* - Directory to store cached certificates
|
13
|
+
/// so that these are not regenerated every time the server starts
|
14
|
+
pub static ITSI_ACME_CACHE_DIR: StringVar = LazyLock::new(|| {
|
15
|
+
var("ITSI_ACME_CACHE_DIR").unwrap_or_else(|_| "./.rustls_acme_cache".to_string())
|
16
|
+
});
|
17
|
+
|
18
|
+
/// *ITSI_ACME_CONTACT_EMAIL* - Contact Email address to provide to ACME server during certificate renewal
|
19
|
+
pub static ITSI_ACME_CONTACT_EMAIL: MaybeStringVar =
|
20
|
+
LazyLock::new(|| var("ITSI_ACME_CONTACT_EMAIL"));
|
21
|
+
|
22
|
+
/// *ITSI_ACME_CA_PEM_PATH* - Optional CA Pem path, used for testing with non-trusted CAs for certifcate generation.
|
23
|
+
pub static ITSI_ACME_CA_PEM_PATH: MaybeStringVar = LazyLock::new(|| var("ITSI_ACME_CA_PEM_PATH"));
|
24
|
+
|
25
|
+
/// *ITSI_ACME_DIRECTORY_URL* - Directory URL to use for ACME certificate generation.
|
26
|
+
pub static ITSI_ACME_DIRECTORY_URL: StringVar = LazyLock::new(|| {
|
27
|
+
var("ITSI_ACME_DIRECTORY_URL")
|
28
|
+
.unwrap_or_else(|_| "https://acme-v02.api.letsencrypt.org/directory".to_string())
|
29
|
+
});
|
30
|
+
|
31
|
+
/// *ITSI_ACME_LOCK_FILE_NAME* - Name of the lock file used to prevent concurrent certificate generation.
|
32
|
+
pub static ITSI_ACME_LOCK_FILE_NAME: StringVar =
|
33
|
+
LazyLock::new(|| var("ITSI_ACME_LOCK_FILE_NAME").unwrap_or(".acme.lock".to_string()));
|
34
|
+
|
35
|
+
pub static ITSI_LOCAL_CA_DIR: PathVar = LazyLock::new(|| {
|
36
|
+
var("ITSI_LOCAL_CA_DIR")
|
37
|
+
.map(PathBuf::from)
|
38
|
+
.unwrap_or_else(|_| {
|
39
|
+
dirs::home_dir()
|
40
|
+
.expect("Failed to find HOME directory when initializing ITSI_LOCAL_CA_DIR")
|
41
|
+
.join(".itsi")
|
42
|
+
})
|
43
|
+
});
|
@@ -83,7 +83,7 @@ impl SingleMode {
|
|
83
83
|
Ok(())
|
84
84
|
}
|
85
85
|
|
86
|
-
#[instrument(parent=None, skip(self))]
|
86
|
+
#[instrument(parent=None, skip(self), fields(pid=format!("{}", Pid::this())))]
|
87
87
|
pub fn run(self: Arc<Self>) -> Result<()> {
|
88
88
|
let mut listener_task_set = JoinSet::new();
|
89
89
|
let self_ref = Arc::new(self);
|