itsi-server 0.1.1 → 0.1.13
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/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +7 -0
- data/Cargo.lock +4417 -0
- data/Cargo.toml +7 -0
- data/README.md +4 -0
- data/Rakefile +8 -1
- data/_index.md +6 -0
- data/exe/itsi +94 -45
- data/ext/itsi_error/Cargo.toml +2 -0
- data/ext/itsi_error/src/from.rs +68 -0
- data/ext/itsi_error/src/lib.rs +18 -34
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
- 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 +3 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
- data/ext/itsi_rb_helpers/src/lib.rs +140 -10
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_rb_helpers/target/debug/build/rb-sys-eb9ed4ff3a60f995/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
- data/ext/itsi_scheduler/Cargo.toml +24 -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.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +73 -13
- data/ext/itsi_server/extconf.rb +1 -1
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +100 -40
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +141 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_request.rs +147 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response.rs +19 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_stream/mod.rs +216 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +282 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +388 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +355 -0
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +82 -0
- data/ext/itsi_server/src/ruby_types/mod.rs +55 -0
- data/ext/itsi_server/src/server/bind.rs +75 -31
- data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
- data/ext/itsi_server/src/server/byte_frame.rs +32 -0
- data/ext/itsi_server/src/server/cache_store.rs +74 -0
- data/ext/itsi_server/src/server/io_stream.rs +104 -0
- data/ext/itsi_server/src/server/itsi_service.rs +172 -0
- data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
- data/ext/itsi_server/src/server/listener.rs +332 -132
- data/ext/itsi_server/src/server/middleware_stack/middleware.rs +153 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +47 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +58 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +321 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +139 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +300 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +287 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +48 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +127 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +191 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +72 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +85 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +195 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +216 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +124 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +43 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +34 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +93 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +162 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +158 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +315 -0
- data/ext/itsi_server/src/server/mod.rs +15 -2
- data/ext/itsi_server/src/server/process_worker.rs +229 -0
- data/ext/itsi_server/src/server/rate_limiter.rs +565 -0
- data/ext/itsi_server/src/server/request_job.rs +11 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +337 -0
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +421 -0
- data/ext/itsi_server/src/server/signal.rs +93 -0
- data/ext/itsi_server/src/server/static_file_server.rs +984 -0
- data/ext/itsi_server/src/server/thread_worker.rs +444 -0
- data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +132 -0
- data/ext/itsi_server/src/server/tls.rs +187 -60
- data/ext/itsi_server/src/server/types.rs +43 -0
- data/ext/itsi_tracing/Cargo.toml +5 -0
- data/ext/itsi_tracing/src/lib.rs +225 -7
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
- data/lib/itsi/http_request.rb +87 -0
- data/lib/itsi/http_response.rb +39 -0
- data/lib/itsi/server/Itsi.rb +119 -0
- data/lib/itsi/server/config/dsl.rb +506 -0
- data/lib/itsi/server/config.rb +131 -0
- data/lib/itsi/server/default_app/default_app.rb +38 -0
- data/lib/itsi/server/default_app/index.html +91 -0
- data/lib/itsi/server/grpc_interface.rb +213 -0
- data/lib/itsi/server/rack/handler/itsi.rb +27 -0
- data/lib/itsi/server/rack_interface.rb +94 -0
- data/lib/itsi/server/scheduler_interface.rb +21 -0
- data/lib/itsi/server/scheduler_mode.rb +10 -0
- data/lib/itsi/server/signal_trap.rb +29 -0
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +90 -9
- data/lib/itsi/standard_headers.rb +86 -0
- metadata +122 -31
- data/ext/itsi_server/src/request/itsi_request.rs +0 -143
- data/ext/itsi_server/src/request/mod.rs +0 -1
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
- data/ext/itsi_server/src/server/itsi_server.rs +0 -182
- data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
- data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
- data/lib/itsi/request.rb +0 -39
@@ -1,20 +1,115 @@
|
|
1
1
|
use base64::{engine::general_purpose, Engine as _};
|
2
2
|
use itsi_error::Result;
|
3
|
-
use itsi_tracing::
|
4
|
-
use
|
3
|
+
use itsi_tracing::info;
|
4
|
+
use locked_dir_cache::LockedDirCache;
|
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
|
+
};
|
5
12
|
use rustls_pemfile::{certs, pkcs8_private_keys};
|
6
|
-
use std::{
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
use std::{
|
14
|
+
collections::HashMap,
|
15
|
+
fs,
|
16
|
+
io::{BufReader, Error},
|
17
|
+
sync::Arc,
|
18
|
+
};
|
19
|
+
use tokio::sync::Mutex;
|
20
|
+
use tokio_rustls::{rustls::ServerConfig, TlsAcceptor};
|
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
|
+
};
|
27
|
+
mod locked_dir_cache;
|
28
|
+
|
29
|
+
#[derive(Clone)]
|
30
|
+
pub enum ItsiTlsAcceptor {
|
31
|
+
Manual(TlsAcceptor),
|
32
|
+
Automatic(
|
33
|
+
AcmeAcceptor,
|
34
|
+
Arc<Mutex<AcmeState<Error>>>,
|
35
|
+
Arc<ServerConfig>,
|
36
|
+
),
|
37
|
+
}
|
38
|
+
|
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.
|
45
|
+
pub fn configure_tls(
|
46
|
+
host: &str,
|
47
|
+
query_params: &HashMap<String, String>,
|
48
|
+
) -> Result<ItsiTlsAcceptor> {
|
49
|
+
let domains = query_params
|
50
|
+
.get("domains")
|
51
|
+
.map(|v| v.split(',').map(String::from).collect::<Vec<_>>())
|
52
|
+
.or_else(|| query_params.get("domain").map(|v| vec![v.to_string()]));
|
53
|
+
|
54
|
+
if query_params.get("cert").is_some_and(|c| c == "acme") {
|
55
|
+
if let Some(domains) = domains {
|
56
|
+
let directory_url = &*ITSI_ACME_DIRECTORY_URL;
|
57
|
+
info!(
|
58
|
+
domains = format!("{:?}", domains),
|
59
|
+
directory_url, "Requesting acme cert"
|
60
|
+
);
|
61
|
+
let acme_contact_email = query_params
|
62
|
+
.get("acme_email")
|
63
|
+
.map(|s| s.to_string())
|
64
|
+
.or_else(|| (*ITSI_ACME_CONTACT_EMAIL).as_ref().ok().map(|s| s.to_string()))
|
65
|
+
.ok_or_else(|| itsi_error::ItsiError::ArgumentError(
|
66
|
+
"acme_email query param or ITSI_ACME_CONTACT_EMAIL must be set before you can auto-generate let's encrypt certificates".to_string(),
|
67
|
+
))?;
|
68
|
+
|
69
|
+
let acme_config = AcmeConfig::new(domains)
|
70
|
+
.contact([format!("mailto:{}", acme_contact_email)])
|
71
|
+
.cache(LockedDirCache::new(&*ITSI_ACME_CACHE_DIR))
|
72
|
+
.directory(directory_url);
|
73
|
+
|
74
|
+
let acme_state = if let Ok(ca_pem_path) = &*ITSI_ACME_CA_PEM_PATH {
|
75
|
+
let mut root_cert_store = RootCertStore::empty();
|
76
|
+
|
77
|
+
let ca_pem = fs::read(ca_pem_path).expect("failed to read CA pem file");
|
78
|
+
let mut ca_reader = BufReader::new(&ca_pem[..]);
|
79
|
+
let der_certs: Vec<CertificateDer> = certs(&mut ca_reader)
|
80
|
+
.collect::<std::result::Result<Vec<CertificateDer>, _>>()
|
81
|
+
.map_err(|e| {
|
82
|
+
itsi_error::ItsiError::ArgumentError(format!(
|
83
|
+
"Invalid ACME CA Pem path {:?}",
|
84
|
+
e
|
85
|
+
))
|
86
|
+
})?;
|
87
|
+
root_cert_store.add_parsable_certificates(der_certs);
|
88
|
+
|
89
|
+
let client_config = ClientConfig::builder()
|
90
|
+
.with_root_certificates(root_cert_store)
|
91
|
+
.with_no_client_auth();
|
92
|
+
acme_config
|
93
|
+
.client_tls_config(Arc::new(client_config))
|
94
|
+
.state()
|
95
|
+
} else {
|
96
|
+
acme_config.state()
|
97
|
+
};
|
98
|
+
|
99
|
+
let mut rustls_config = ServerConfig::builder()
|
100
|
+
.with_no_client_auth()
|
101
|
+
.with_cert_resolver(acme_state.resolver());
|
102
|
+
|
103
|
+
rustls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
104
|
+
|
105
|
+
let acceptor = acme_state.acceptor();
|
106
|
+
return Ok(ItsiTlsAcceptor::Automatic(
|
107
|
+
acceptor,
|
108
|
+
Arc::new(Mutex::new(acme_state)),
|
109
|
+
Arc::new(rustls_config),
|
110
|
+
));
|
111
|
+
}
|
112
|
+
}
|
18
113
|
let (certs, key) = if let (Some(cert_path), Some(key_path)) =
|
19
114
|
(query_params.get("cert"), query_params.get("key"))
|
20
115
|
{
|
@@ -22,41 +117,20 @@ pub fn configure_tls(host: &str, query_params: &HashMap<String, String>) -> Resu
|
|
22
117
|
let certs = load_certs(cert_path);
|
23
118
|
let key = load_private_key(key_path);
|
24
119
|
(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
120
|
} else {
|
46
|
-
generate_ca_signed_cert(host)?
|
121
|
+
generate_ca_signed_cert(domains.unwrap_or(vec![host.to_owned()]))?
|
47
122
|
};
|
48
123
|
|
49
124
|
let mut config = ServerConfig::builder()
|
50
|
-
.with_safe_defaults()
|
51
125
|
.with_no_client_auth()
|
52
126
|
.with_single_cert(certs, key)
|
53
127
|
.expect("Failed to build TLS config");
|
54
128
|
|
55
129
|
config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
56
|
-
Ok(config)
|
130
|
+
Ok(ItsiTlsAcceptor::Manual(TlsAcceptor::from(Arc::new(config))))
|
57
131
|
}
|
58
132
|
|
59
|
-
pub fn load_certs(path: &str) -> Vec<
|
133
|
+
pub fn load_certs(path: &str) -> Vec<CertificateDer<'static>> {
|
60
134
|
let data = if let Some(stripped) = path.strip_prefix("base64:") {
|
61
135
|
general_purpose::STANDARD
|
62
136
|
.decode(stripped)
|
@@ -74,14 +148,20 @@ pub fn load_certs(path: &str) -> Vec<Certificate> {
|
|
74
148
|
})
|
75
149
|
.collect::<Result<_>>()
|
76
150
|
.expect("Failed to parse certificate file");
|
77
|
-
certs_der
|
151
|
+
certs_der
|
152
|
+
.into_iter()
|
153
|
+
.map(|vec| {
|
154
|
+
// Convert the owned Vec<u8> into a CertificateDer and force 'static.
|
155
|
+
unsafe { std::mem::transmute(CertificateDer::from(vec)) }
|
156
|
+
})
|
157
|
+
.collect()
|
78
158
|
} else {
|
79
|
-
vec![
|
159
|
+
vec![CertificateDer::from(data)]
|
80
160
|
}
|
81
161
|
}
|
82
162
|
|
83
163
|
/// Loads a private key from a file or Base64.
|
84
|
-
pub fn load_private_key(path: &str) ->
|
164
|
+
pub fn load_private_key(path: &str) -> PrivateKeyDer<'static> {
|
85
165
|
let key_data = if let Some(stripped) = path.strip_prefix("base64:") {
|
86
166
|
general_purpose::STANDARD
|
87
167
|
.decode(stripped)
|
@@ -100,39 +180,86 @@ pub fn load_private_key(path: &str) -> PrivateKey {
|
|
100
180
|
.collect::<Result<_>>()
|
101
181
|
.expect("Failed to parse private key");
|
102
182
|
if !keys.is_empty() {
|
103
|
-
return
|
183
|
+
return PrivateKeyDer::try_from(keys[0].clone()).unwrap();
|
104
184
|
}
|
105
185
|
}
|
106
|
-
|
186
|
+
PrivateKeyDer::try_from(key_data).unwrap()
|
107
187
|
}
|
108
188
|
|
109
|
-
pub fn generate_ca_signed_cert(
|
110
|
-
|
189
|
+
pub fn generate_ca_signed_cert(
|
190
|
+
domains: Vec<String>,
|
191
|
+
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
|
192
|
+
info!(
|
193
|
+
domains = format!("{}", domains.join(", ")),
|
194
|
+
"Self signed cert",
|
195
|
+
);
|
196
|
+
info!(
|
197
|
+
"Add {} to your system's trusted cert store to resolve certificate errors.",
|
198
|
+
format!("{}/itsi_dev_ca.crt", ITSI_LOCAL_CA_DIR.to_str().unwrap())
|
199
|
+
);
|
200
|
+
info!("Dev CA path can be overridden by setting env var: `ITSI_LOCAL_CA_DIR`.");
|
201
|
+
let (ca_key_pem, ca_cert_pem) = get_or_create_local_dev_ca()?;
|
111
202
|
|
112
|
-
let ca_kp = KeyPair::from_pem(
|
113
|
-
let
|
203
|
+
let ca_kp = KeyPair::from_pem(&ca_key_pem).expect("Failed to load CA key");
|
204
|
+
let ca_cert = CertificateParams::from_ca_cert_pem(&ca_cert_pem)
|
205
|
+
.expect("Failed to parse embedded CA certificate")
|
206
|
+
.self_signed(&ca_kp)
|
207
|
+
.expect("Failed to self-sign embedded CA cert");
|
114
208
|
|
115
|
-
let
|
116
|
-
let ee_key = KeyPair::generate().unwrap();
|
209
|
+
let ee_key = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap();
|
117
210
|
let mut ee_params = CertificateParams::default();
|
118
211
|
|
119
|
-
|
120
|
-
|
121
|
-
|
212
|
+
use std::net::IpAddr;
|
213
|
+
|
214
|
+
ee_params.subject_alt_names = domains
|
215
|
+
.iter()
|
216
|
+
.map(|domain| {
|
217
|
+
if let Ok(ip) = domain.parse::<IpAddr>() {
|
218
|
+
SanType::IpAddress(ip)
|
219
|
+
} else {
|
220
|
+
SanType::DnsName(domain.clone().try_into().unwrap())
|
221
|
+
}
|
222
|
+
})
|
223
|
+
.collect();
|
224
|
+
|
122
225
|
ee_params
|
123
226
|
.distinguished_name
|
124
|
-
.push(DnType::CommonName,
|
227
|
+
.push(DnType::CommonName, domains[0].clone());
|
125
228
|
|
126
229
|
ee_params.use_authority_key_identifier_extension = true;
|
127
230
|
|
128
|
-
let ee_cert = ee_params.signed_by(&ee_key, &ca_cert, &
|
231
|
+
let ee_cert = ee_params.signed_by(&ee_key, &ca_cert, &ca_kp).unwrap();
|
129
232
|
let ee_cert_der = ee_cert.der().to_vec();
|
130
|
-
let ee_cert =
|
131
|
-
|
233
|
+
let ee_cert = CertificateDer::from(ee_cert_der);
|
234
|
+
let ca_cert = CertificateDer::from(ca_cert.der().to_vec());
|
235
|
+
Ok((
|
236
|
+
vec![ee_cert, ca_cert],
|
237
|
+
PrivateKeyDer::try_from(ee_key.serialize_der()).unwrap(),
|
238
|
+
))
|
132
239
|
}
|
133
240
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
241
|
+
fn get_or_create_local_dev_ca() -> Result<(String, String)> {
|
242
|
+
let ca_dir = &*ITSI_LOCAL_CA_DIR;
|
243
|
+
fs::create_dir_all(ca_dir)?;
|
244
|
+
|
245
|
+
let key_path = ca_dir.join("itsi_dev_ca.key");
|
246
|
+
let cert_path = ca_dir.join("itsi_dev_ca.crt");
|
247
|
+
|
248
|
+
if key_path.exists() && cert_path.exists() {
|
249
|
+
// Already have a local CA
|
250
|
+
let key_pem = fs::read_to_string(&key_path)?;
|
251
|
+
let cert_pem = fs::read_to_string(&cert_path)?;
|
252
|
+
|
253
|
+
Ok((key_pem, cert_pem))
|
254
|
+
} else {
|
255
|
+
let subject_alt_names = vec!["dev.itsi.fyi".to_string(), "localhost".to_string()];
|
256
|
+
|
257
|
+
let CertifiedKey { cert, key_pair } =
|
258
|
+
generate_simple_self_signed(subject_alt_names).unwrap();
|
259
|
+
|
260
|
+
fs::write(&key_path, key_pair.serialize_pem())?;
|
261
|
+
fs::write(&cert_path, cert.pem())?;
|
262
|
+
|
263
|
+
Ok((key_pair.serialize_pem(), cert.pem()))
|
264
|
+
}
|
138
265
|
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
use std::convert::Infallible;
|
2
|
+
|
3
|
+
use bytes::Bytes;
|
4
|
+
use http::{Request, Response};
|
5
|
+
use http_body_util::combinators::BoxBody;
|
6
|
+
use hyper::body::Incoming;
|
7
|
+
|
8
|
+
pub type HttpResponse = Response<BoxBody<Bytes, Infallible>>;
|
9
|
+
pub type HttpRequest = Request<Incoming>;
|
10
|
+
|
11
|
+
pub trait RequestExt {
|
12
|
+
fn content_type(&self) -> Option<&str>;
|
13
|
+
fn accept(&self) -> Option<&str>;
|
14
|
+
fn header(&self, header_name: &str) -> Option<&str>;
|
15
|
+
fn query_param(&self, query_name: &str) -> Option<&str>;
|
16
|
+
}
|
17
|
+
|
18
|
+
impl RequestExt for HttpRequest {
|
19
|
+
fn content_type(&self) -> Option<&str> {
|
20
|
+
self.headers()
|
21
|
+
.get("content-type")
|
22
|
+
.map(|hv| hv.to_str().unwrap_or(""))
|
23
|
+
}
|
24
|
+
|
25
|
+
fn accept(&self) -> Option<&str> {
|
26
|
+
self.headers()
|
27
|
+
.get("accept")
|
28
|
+
.map(|hv| hv.to_str().unwrap_or(""))
|
29
|
+
}
|
30
|
+
|
31
|
+
fn header(&self, header_name: &str) -> Option<&str> {
|
32
|
+
self.headers()
|
33
|
+
.get(header_name)
|
34
|
+
.map(|hv| hv.to_str().unwrap_or(""))
|
35
|
+
}
|
36
|
+
|
37
|
+
fn query_param(&self, query_name: &str) -> Option<&str> {
|
38
|
+
self.uri()
|
39
|
+
.query()
|
40
|
+
.and_then(|query| query.split('&').find(|param| param.starts_with(query_name)))
|
41
|
+
.map(|param| param.split('=').nth(1).unwrap_or(""))
|
42
|
+
}
|
43
|
+
}
|
data/ext/itsi_tracing/Cargo.toml
CHANGED
data/ext/itsi_tracing/src/lib.rs
CHANGED
@@ -1,11 +1,229 @@
|
|
1
|
+
use atty::{Stream, is};
|
2
|
+
use std::{
|
3
|
+
env,
|
4
|
+
sync::{Mutex, OnceLock},
|
5
|
+
};
|
1
6
|
pub use tracing::{debug, error, info, trace, warn};
|
2
|
-
use
|
7
|
+
use tracing_appender::rolling;
|
8
|
+
use tracing_subscriber::Layer;
|
9
|
+
use tracing_subscriber::fmt::writer::BoxMakeWriter;
|
10
|
+
use tracing_subscriber::{EnvFilter, fmt, prelude::*, reload};
|
3
11
|
|
12
|
+
// Global reload handle for changing the level at runtime.
|
13
|
+
static RELOAD_HANDLE: OnceLock<
|
14
|
+
Mutex<Option<reload::Handle<EnvFilter, tracing_subscriber::Registry>>>,
|
15
|
+
> = OnceLock::new();
|
16
|
+
|
17
|
+
/// Log format: Plain or JSON.
|
18
|
+
#[derive(Debug, Clone)]
|
19
|
+
pub enum LogFormat {
|
20
|
+
Plain,
|
21
|
+
Json,
|
22
|
+
}
|
23
|
+
|
24
|
+
/// Log target: STDOUT, File, or Both.
|
25
|
+
#[derive(Debug, Clone)]
|
26
|
+
pub enum LogTarget {
|
27
|
+
Stdout,
|
28
|
+
File(String), // file name (rotated daily)
|
29
|
+
Both(String), // file name (rotated daily) plus STDOUT
|
30
|
+
}
|
31
|
+
|
32
|
+
/// Logger configuration.
|
33
|
+
#[derive(Debug, Clone)]
|
34
|
+
pub struct LogConfig {
|
35
|
+
/// Log level as a string (e.g. "info", "debug").
|
36
|
+
pub level: String,
|
37
|
+
/// Format: Plain (with optional ANSI) or JSON.
|
38
|
+
pub format: LogFormat,
|
39
|
+
/// Target: STDOUT, File, or Both.
|
40
|
+
pub target: LogTarget,
|
41
|
+
/// Whether to enable ANSI coloring (for plain text).
|
42
|
+
pub use_ansi: bool,
|
43
|
+
}
|
44
|
+
|
45
|
+
impl Default for LogConfig {
|
46
|
+
fn default() -> Self {
|
47
|
+
let level = env::var("ITSI_LOG").unwrap_or_else(|_| "info".into());
|
48
|
+
let format = match env::var("ITSI_LOG_FORMAT").as_deref() {
|
49
|
+
Ok("json") => LogFormat::Json,
|
50
|
+
_ => LogFormat::Plain,
|
51
|
+
};
|
52
|
+
let target = match env::var("ITSI_LOG_TARGET").as_deref() {
|
53
|
+
Ok("file") => {
|
54
|
+
let file = env::var("ITSI_LOG_FILE").unwrap_or_else(|_| "app.log".into());
|
55
|
+
LogTarget::File(file)
|
56
|
+
}
|
57
|
+
Ok("both") => {
|
58
|
+
let file = env::var("ITSI_LOG_FILE").unwrap_or_else(|_| "app.log".into());
|
59
|
+
LogTarget::Both(file)
|
60
|
+
}
|
61
|
+
_ => LogTarget::Stdout,
|
62
|
+
};
|
63
|
+
// If ITSI_LOG_ANSI is set, use that; otherwise, use ANSI if stdout is a TTY.
|
64
|
+
let use_ansi = env::var("ITSI_LOG_ANSI")
|
65
|
+
.map(|s| s == "true")
|
66
|
+
.unwrap_or_else(|_| is(Stream::Stdout));
|
67
|
+
Self {
|
68
|
+
level,
|
69
|
+
format,
|
70
|
+
target,
|
71
|
+
use_ansi,
|
72
|
+
}
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
/// Initialize the global tracing subscriber with the default configuration.
|
4
77
|
pub fn init() {
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
78
|
+
init_with_config(LogConfig::default());
|
79
|
+
}
|
80
|
+
|
81
|
+
/// Initialize the global tracing subscriber with a given configuration.
|
82
|
+
pub fn init_with_config(config: LogConfig) {
|
83
|
+
// Build an EnvFilter from the configured level.
|
84
|
+
let env_filter = EnvFilter::new(config.level);
|
85
|
+
|
86
|
+
// Build the formatting layer based on target and format.
|
87
|
+
let fmt_layer = match config.target {
|
88
|
+
LogTarget::Stdout => match config.format {
|
89
|
+
LogFormat::Plain => fmt::layer()
|
90
|
+
.compact()
|
91
|
+
.with_file(false)
|
92
|
+
.with_line_number(false)
|
93
|
+
.with_target(false)
|
94
|
+
.with_thread_ids(false)
|
95
|
+
.with_writer(BoxMakeWriter::new(std::io::stdout))
|
96
|
+
.with_ansi(config.use_ansi)
|
97
|
+
.boxed(),
|
98
|
+
LogFormat::Json => fmt::layer()
|
99
|
+
.compact()
|
100
|
+
.with_file(false)
|
101
|
+
.with_line_number(false)
|
102
|
+
.with_target(false)
|
103
|
+
.with_thread_ids(false)
|
104
|
+
.with_writer(BoxMakeWriter::new(std::io::stdout))
|
105
|
+
.with_ansi(config.use_ansi)
|
106
|
+
.json()
|
107
|
+
.boxed(),
|
108
|
+
},
|
109
|
+
LogTarget::File(file) => match config.format {
|
110
|
+
LogFormat::Plain => fmt::layer()
|
111
|
+
.compact()
|
112
|
+
.with_file(false)
|
113
|
+
.with_line_number(false)
|
114
|
+
.with_target(false)
|
115
|
+
.with_thread_ids(false)
|
116
|
+
.with_writer(BoxMakeWriter::new({
|
117
|
+
let file = file.clone();
|
118
|
+
move || rolling::daily(".", file.clone())
|
119
|
+
}))
|
120
|
+
.with_ansi(false)
|
121
|
+
.boxed(),
|
122
|
+
LogFormat::Json => fmt::layer()
|
123
|
+
.compact()
|
124
|
+
.with_file(false)
|
125
|
+
.with_line_number(false)
|
126
|
+
.with_target(false)
|
127
|
+
.with_thread_ids(false)
|
128
|
+
.with_writer(BoxMakeWriter::new({
|
129
|
+
let file = file.clone();
|
130
|
+
move || rolling::daily(".", file.clone())
|
131
|
+
}))
|
132
|
+
.with_ansi(false)
|
133
|
+
.json()
|
134
|
+
.boxed(),
|
135
|
+
},
|
136
|
+
LogTarget::Both(file) => {
|
137
|
+
// For "Both" target, handle each format separately to avoid type mismatches
|
138
|
+
match config.format {
|
139
|
+
LogFormat::Plain => {
|
140
|
+
let stdout_layer = fmt::layer()
|
141
|
+
.compact()
|
142
|
+
.with_file(false)
|
143
|
+
.with_line_number(false)
|
144
|
+
.with_target(false)
|
145
|
+
.with_thread_ids(false)
|
146
|
+
.with_writer(BoxMakeWriter::new(std::io::stdout))
|
147
|
+
.with_ansi(config.use_ansi);
|
148
|
+
|
149
|
+
let file_layer = fmt::layer()
|
150
|
+
.compact()
|
151
|
+
.with_file(false)
|
152
|
+
.with_line_number(false)
|
153
|
+
.with_target(false)
|
154
|
+
.with_thread_ids(false)
|
155
|
+
.with_writer(BoxMakeWriter::new({
|
156
|
+
let file = file.clone();
|
157
|
+
move || rolling::daily(".", file.clone())
|
158
|
+
}))
|
159
|
+
.with_ansi(false);
|
160
|
+
|
161
|
+
stdout_layer.and_then(file_layer).boxed()
|
162
|
+
}
|
163
|
+
LogFormat::Json => {
|
164
|
+
let stdout_layer = fmt::layer()
|
165
|
+
.compact()
|
166
|
+
.with_file(false)
|
167
|
+
.with_line_number(false)
|
168
|
+
.with_target(false)
|
169
|
+
.with_thread_ids(false)
|
170
|
+
.with_writer(BoxMakeWriter::new(std::io::stdout))
|
171
|
+
.with_ansi(config.use_ansi)
|
172
|
+
.json();
|
173
|
+
|
174
|
+
let file_layer = fmt::layer()
|
175
|
+
.compact()
|
176
|
+
.with_file(false)
|
177
|
+
.with_line_number(false)
|
178
|
+
.with_target(false)
|
179
|
+
.with_thread_ids(false)
|
180
|
+
.with_writer(BoxMakeWriter::new({
|
181
|
+
let file = file.clone();
|
182
|
+
move || rolling::daily(".", file.clone())
|
183
|
+
}))
|
184
|
+
.with_ansi(false)
|
185
|
+
.json();
|
186
|
+
|
187
|
+
stdout_layer.and_then(file_layer).boxed()
|
188
|
+
}
|
189
|
+
}
|
190
|
+
}
|
191
|
+
};
|
192
|
+
|
193
|
+
// Create a reloadable filter layer so we can update the level at runtime.
|
194
|
+
let (filter_layer, handle) = reload::Layer::new(env_filter);
|
195
|
+
|
196
|
+
// Build the subscriber registry
|
197
|
+
let subscriber = tracing_subscriber::registry()
|
198
|
+
.with(filter_layer)
|
199
|
+
.with(fmt_layer);
|
200
|
+
|
201
|
+
tracing::subscriber::set_global_default(subscriber)
|
202
|
+
.expect("Unable to set global tracing subscriber");
|
203
|
+
|
204
|
+
RELOAD_HANDLE.set(Mutex::new(Some(handle))).unwrap();
|
205
|
+
}
|
206
|
+
|
207
|
+
/// Change the log level at runtime.
|
208
|
+
pub fn set_level(new_level: &str) {
|
209
|
+
if let Some(handle) = RELOAD_HANDLE.get().unwrap().lock().unwrap().as_ref() {
|
210
|
+
handle
|
211
|
+
.modify(|filter| *filter = EnvFilter::new(new_level))
|
212
|
+
.expect("Failed to update log level");
|
213
|
+
} else {
|
214
|
+
eprintln!("Reload handle not initialized; call init() first.");
|
215
|
+
}
|
216
|
+
}
|
217
|
+
|
218
|
+
/// Run a function silently by temporarily setting a no-op subscriber.
|
219
|
+
pub fn run_silently<F, R>(f: F) -> R
|
220
|
+
where
|
221
|
+
F: FnOnce() -> R,
|
222
|
+
{
|
223
|
+
let no_op_subscriber = tracing_subscriber::fmt()
|
224
|
+
.with_writer(std::io::sink)
|
225
|
+
.with_max_level(tracing_subscriber::filter::LevelFilter::OFF)
|
226
|
+
.finish();
|
227
|
+
let dispatch = tracing::Dispatch::new(no_op_subscriber);
|
228
|
+
tracing::dispatcher::with_default(&dispatch, f)
|
11
229
|
}
|
data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock
ADDED
File without changes
|
data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock
ADDED
File without changes
|
data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock
ADDED
File without changes
|
data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock
ADDED
File without changes
|
data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock
ADDED
File without changes
|
data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock
ADDED
File without changes
|