itsi-scheduler 0.1.5 → 0.2.2
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 +120 -52
- data/README.md +57 -24
- data/Rakefile +0 -4
- data/ext/itsi_acme/Cargo.toml +86 -0
- data/ext/itsi_acme/examples/high_level.rs +63 -0
- data/ext/itsi_acme/examples/high_level_warp.rs +52 -0
- data/ext/itsi_acme/examples/low_level.rs +87 -0
- data/ext/itsi_acme/examples/low_level_axum.rs +66 -0
- data/ext/itsi_acme/src/acceptor.rs +81 -0
- data/ext/itsi_acme/src/acme.rs +354 -0
- data/ext/itsi_acme/src/axum.rs +86 -0
- data/ext/itsi_acme/src/cache.rs +39 -0
- data/ext/itsi_acme/src/caches/boxed.rs +80 -0
- data/ext/itsi_acme/src/caches/composite.rs +69 -0
- data/ext/itsi_acme/src/caches/dir.rs +106 -0
- data/ext/itsi_acme/src/caches/mod.rs +11 -0
- data/ext/itsi_acme/src/caches/no.rs +78 -0
- data/ext/itsi_acme/src/caches/test.rs +136 -0
- data/ext/itsi_acme/src/config.rs +172 -0
- data/ext/itsi_acme/src/https_helper.rs +69 -0
- data/ext/itsi_acme/src/incoming.rs +142 -0
- data/ext/itsi_acme/src/jose.rs +161 -0
- data/ext/itsi_acme/src/lib.rs +142 -0
- data/ext/itsi_acme/src/resolver.rs +59 -0
- data/ext/itsi_acme/src/state.rs +424 -0
- data/ext/itsi_error/Cargo.toml +1 -0
- data/ext/itsi_error/src/lib.rs +106 -7
- 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_rb_helpers/Cargo.toml +1 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +18 -0
- data/ext/itsi_rb_helpers/src/lib.rs +63 -12
- 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 +1 -1
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +9 -3
- data/ext/itsi_scheduler/src/lib.rs +1 -0
- data/ext/itsi_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +73 -29
- data/ext/itsi_server/src/default_responses/mod.rs +11 -0
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +114 -75
- data/ext/itsi_server/src/prelude.rs +2 -0
- data/ext/itsi_server/src/{body_proxy → ruby_types/itsi_body_proxy}/big_bytes.rs +10 -5
- data/ext/itsi_server/src/{body_proxy/itsi_body_proxy.rs → ruby_types/itsi_body_proxy/mod.rs} +29 -8
- data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +264 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +362 -0
- data/ext/itsi_server/src/{response/itsi_response.rs → ruby_types/itsi_http_response.rs} +84 -40
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +233 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +565 -0
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +86 -0
- data/ext/itsi_server/src/ruby_types/mod.rs +48 -0
- data/ext/itsi_server/src/server/{bind.rs → binds/bind.rs} +59 -24
- data/ext/itsi_server/src/server/binds/listener.rs +444 -0
- data/ext/itsi_server/src/server/binds/mod.rs +4 -0
- data/ext/itsi_server/src/server/{tls → binds/tls}/locked_dir_cache.rs +57 -19
- data/ext/itsi_server/src/server/{tls.rs → binds/tls.rs} +120 -31
- data/ext/itsi_server/src/server/byte_frame.rs +32 -0
- data/ext/itsi_server/src/server/http_message_types.rs +97 -0
- data/ext/itsi_server/src/server/io_stream.rs +2 -1
- data/ext/itsi_server/src/server/lifecycle_event.rs +3 -0
- data/ext/itsi_server/src/server/middleware_stack/middleware.rs +170 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +63 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +94 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +94 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +343 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +151 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +316 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +301 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +193 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +64 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +192 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +171 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +198 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +209 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +116 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +411 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +142 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +55 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +54 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +51 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +126 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +187 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +173 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +31 -0
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +381 -0
- data/ext/itsi_server/src/server/mod.rs +7 -5
- data/ext/itsi_server/src/server/process_worker.rs +65 -14
- data/ext/itsi_server/src/server/redirect_type.rs +26 -0
- data/ext/itsi_server/src/server/request_job.rs +11 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +150 -50
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +9 -6
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +399 -165
- data/ext/itsi_server/src/server/signal.rs +33 -26
- data/ext/itsi_server/src/server/size_limited_incoming.rs +107 -0
- data/ext/itsi_server/src/server/thread_worker.rs +218 -107
- data/ext/itsi_server/src/services/cache_store.rs +74 -0
- data/ext/itsi_server/src/services/itsi_http_service.rs +257 -0
- data/ext/itsi_server/src/services/mime_types.rs +1416 -0
- data/ext/itsi_server/src/services/mod.rs +6 -0
- data/ext/itsi_server/src/services/password_hasher.rs +83 -0
- data/ext/itsi_server/src/services/rate_limiter.rs +580 -0
- data/ext/itsi_server/src/services/static_file_server.rs +1340 -0
- data/ext/itsi_tracing/Cargo.toml +1 -0
- data/ext/itsi_tracing/src/lib.rs +362 -33
- 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/itsi-scheduler-100.png +0 -0
- data/lib/itsi/scheduler/version.rb +1 -1
- data/lib/itsi/scheduler.rb +11 -6
- metadata +117 -24
- data/CHANGELOG.md +0 -5
- data/CODE_OF_CONDUCT.md +0 -132
- data/LICENSE.txt +0 -21
- data/ext/itsi_error/src/from.rs +0 -71
- data/ext/itsi_server/extconf.rb +0 -6
- data/ext/itsi_server/src/body_proxy/mod.rs +0 -2
- data/ext/itsi_server/src/request/itsi_request.rs +0 -277
- data/ext/itsi_server/src/request/mod.rs +0 -1
- data/ext/itsi_server/src/response/mod.rs +0 -1
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -13
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -5
- data/ext/itsi_server/src/server/itsi_server.rs +0 -244
- data/ext/itsi_server/src/server/listener.rs +0 -327
- /data/ext/itsi_server/src/server/{bind_protocol.rs → binds/bind_protocol.rs} +0 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
use crate::acme::ACME_TLS_ALPN_NAME;
|
2
|
+
use rustls::server::{ClientHello, ResolvesServerCert};
|
3
|
+
use rustls::sign::CertifiedKey;
|
4
|
+
use std::collections::BTreeMap;
|
5
|
+
use std::sync::Arc;
|
6
|
+
use std::sync::Mutex;
|
7
|
+
|
8
|
+
#[derive(Debug)]
|
9
|
+
pub struct ResolvesServerCertAcme {
|
10
|
+
inner: Mutex<Inner>,
|
11
|
+
}
|
12
|
+
|
13
|
+
#[derive(Debug)]
|
14
|
+
struct Inner {
|
15
|
+
cert: Option<Arc<CertifiedKey>>,
|
16
|
+
auth_keys: BTreeMap<String, Arc<CertifiedKey>>,
|
17
|
+
}
|
18
|
+
|
19
|
+
impl ResolvesServerCertAcme {
|
20
|
+
pub(crate) fn new() -> Arc<Self> {
|
21
|
+
Arc::new(Self {
|
22
|
+
inner: Mutex::new(Inner {
|
23
|
+
cert: None,
|
24
|
+
auth_keys: Default::default(),
|
25
|
+
}),
|
26
|
+
})
|
27
|
+
}
|
28
|
+
pub(crate) fn set_cert(&self, cert: Arc<CertifiedKey>) {
|
29
|
+
self.inner.lock().unwrap().cert = Some(cert);
|
30
|
+
}
|
31
|
+
pub(crate) fn set_auth_key(&self, domain: String, cert: Arc<CertifiedKey>) {
|
32
|
+
self.inner.lock().unwrap().auth_keys.insert(domain, cert);
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
impl ResolvesServerCert for ResolvesServerCertAcme {
|
37
|
+
fn resolve(&self, client_hello: ClientHello) -> Option<Arc<CertifiedKey>> {
|
38
|
+
let is_acme_challenge = client_hello
|
39
|
+
.alpn()
|
40
|
+
.into_iter()
|
41
|
+
.flatten()
|
42
|
+
.eq([ACME_TLS_ALPN_NAME]);
|
43
|
+
if is_acme_challenge {
|
44
|
+
match client_hello.server_name() {
|
45
|
+
None => {
|
46
|
+
log::debug!("client did not supply SNI");
|
47
|
+
None
|
48
|
+
}
|
49
|
+
Some(domain) => {
|
50
|
+
let domain = domain.to_owned();
|
51
|
+
let domain: String = AsRef::<str>::as_ref(&domain).into();
|
52
|
+
self.inner.lock().unwrap().auth_keys.get(&domain).cloned()
|
53
|
+
}
|
54
|
+
}
|
55
|
+
} else {
|
56
|
+
self.inner.lock().unwrap().cert.clone()
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
@@ -0,0 +1,424 @@
|
|
1
|
+
use std::convert::Infallible;
|
2
|
+
use std::fmt::Debug;
|
3
|
+
use std::future::Future;
|
4
|
+
use std::pin::Pin;
|
5
|
+
use std::sync::Arc;
|
6
|
+
use std::task::{Context, Poll};
|
7
|
+
use std::time::Duration;
|
8
|
+
|
9
|
+
use chrono::{DateTime, TimeZone, Utc};
|
10
|
+
use futures::future::try_join_all;
|
11
|
+
use futures::{ready, FutureExt, Stream};
|
12
|
+
use rcgen::{CertificateParams, DistinguishedName, Error as RcgenError, PKCS_ECDSA_P256_SHA256};
|
13
|
+
use rustls::crypto::ring::sign::any_ecdsa_type;
|
14
|
+
use rustls::pki_types::{CertificateDer as RustlsCertificate, PrivateKeyDer, PrivatePkcs8KeyDer};
|
15
|
+
use rustls::sign::CertifiedKey;
|
16
|
+
use thiserror::Error;
|
17
|
+
use tokio::io::{AsyncRead, AsyncWrite};
|
18
|
+
use tokio::time::Sleep;
|
19
|
+
use x509_parser::parse_x509_certificate;
|
20
|
+
|
21
|
+
use crate::acceptor::AcmeAcceptor;
|
22
|
+
use crate::acme::{
|
23
|
+
Account, AcmeError, Auth, AuthStatus, Directory, Identifier, Order, OrderStatus,
|
24
|
+
};
|
25
|
+
use crate::{AcmeConfig, Incoming, ResolvesServerCertAcme};
|
26
|
+
|
27
|
+
type Timer = std::pin::Pin<Box<Sleep>>;
|
28
|
+
type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
|
29
|
+
|
30
|
+
pub fn after(d: std::time::Duration) -> Timer {
|
31
|
+
Box::pin(tokio::time::sleep(d))
|
32
|
+
}
|
33
|
+
|
34
|
+
#[allow(clippy::type_complexity)]
|
35
|
+
pub struct AcmeState<EC: Debug = Infallible, EA: Debug = EC> {
|
36
|
+
config: Arc<AcmeConfig<EC, EA>>,
|
37
|
+
resolver: Arc<ResolvesServerCertAcme>,
|
38
|
+
account_key: Option<Vec<u8>>,
|
39
|
+
|
40
|
+
early_action: Option<BoxFuture<Event<EC, EA>>>,
|
41
|
+
load_cert: Option<BoxFuture<Result<Option<Vec<u8>>, EC>>>,
|
42
|
+
load_account: Option<BoxFuture<Result<Option<Vec<u8>>, EA>>>,
|
43
|
+
order: Option<BoxFuture<Result<Vec<u8>, OrderError>>>,
|
44
|
+
backoff_cnt: usize,
|
45
|
+
wait: Option<Timer>,
|
46
|
+
}
|
47
|
+
|
48
|
+
pub type Event<EC, EA> = Result<EventOk, EventError<EC, EA>>;
|
49
|
+
|
50
|
+
#[derive(Debug)]
|
51
|
+
pub enum EventOk {
|
52
|
+
DeployedCachedCert,
|
53
|
+
DeployedNewCert,
|
54
|
+
CertCacheStore,
|
55
|
+
AccountCacheStore,
|
56
|
+
}
|
57
|
+
|
58
|
+
#[derive(Error, Debug)]
|
59
|
+
pub enum EventError<EC: Debug, EA: Debug> {
|
60
|
+
#[error("cert cache load: {0}")]
|
61
|
+
CertCacheLoad(EC),
|
62
|
+
#[error("account cache load: {0}")]
|
63
|
+
AccountCacheLoad(EA),
|
64
|
+
#[error("cert cache store: {0}")]
|
65
|
+
CertCacheStore(EC),
|
66
|
+
#[error("account cache store: {0}")]
|
67
|
+
AccountCacheStore(EA),
|
68
|
+
#[error("cached cert parse: {0}")]
|
69
|
+
CachedCertParse(CertParseError),
|
70
|
+
#[error("order: {0}")]
|
71
|
+
Order(OrderError),
|
72
|
+
#[error("new cert parse: {0}")]
|
73
|
+
NewCertParse(CertParseError),
|
74
|
+
}
|
75
|
+
|
76
|
+
#[derive(Error, Debug)]
|
77
|
+
pub enum OrderError {
|
78
|
+
#[error("acme error: {0}")]
|
79
|
+
Acme(#[from] AcmeError),
|
80
|
+
#[error("certificate generation error: {0}")]
|
81
|
+
Rcgen(#[from] RcgenError),
|
82
|
+
#[error("bad order object: {0:?}")]
|
83
|
+
BadOrder(Order),
|
84
|
+
#[error("bad auth object: {0:?}")]
|
85
|
+
BadAuth(Auth),
|
86
|
+
#[error("authorization for {0} failed too many times")]
|
87
|
+
TooManyAttemptsAuth(String),
|
88
|
+
#[error("order status stayed on processing too long")]
|
89
|
+
ProcessingTimeout(Order),
|
90
|
+
}
|
91
|
+
|
92
|
+
#[derive(Error, Debug)]
|
93
|
+
pub enum CertParseError {
|
94
|
+
#[error("X509 parsing error: {0}")]
|
95
|
+
X509(#[from] x509_parser::nom::Err<x509_parser::error::X509Error>),
|
96
|
+
#[error("expected 2 or more pem, got: {0}")]
|
97
|
+
Pem(#[from] pem::PemError),
|
98
|
+
#[error("expected 2 or more pem, got: {0}")]
|
99
|
+
TooFewPem(usize),
|
100
|
+
#[error("unsupported private key type")]
|
101
|
+
InvalidPrivateKey,
|
102
|
+
}
|
103
|
+
|
104
|
+
impl<EC: 'static + Debug, EA: 'static + Debug> AcmeState<EC, EA> {
|
105
|
+
pub fn incoming<
|
106
|
+
TCP: AsyncRead + AsyncWrite + Unpin,
|
107
|
+
ETCP,
|
108
|
+
ITCP: Stream<Item = Result<TCP, ETCP>> + Unpin,
|
109
|
+
>(
|
110
|
+
self,
|
111
|
+
tcp_incoming: ITCP,
|
112
|
+
alpn_protocols: Vec<Vec<u8>>,
|
113
|
+
) -> Incoming<TCP, ETCP, ITCP, EC, EA> {
|
114
|
+
let acceptor = self.acceptor();
|
115
|
+
Incoming::new(tcp_incoming, self, acceptor, alpn_protocols)
|
116
|
+
}
|
117
|
+
pub fn acceptor(&self) -> AcmeAcceptor {
|
118
|
+
AcmeAcceptor::new(self.resolver())
|
119
|
+
}
|
120
|
+
|
121
|
+
#[cfg(feature = "axum")]
|
122
|
+
pub fn axum_acceptor(
|
123
|
+
&self,
|
124
|
+
rustls_config: Arc<rustls::ServerConfig>,
|
125
|
+
) -> crate::axum::AxumAcceptor {
|
126
|
+
crate::axum::AxumAcceptor::new(self.acceptor(), rustls_config)
|
127
|
+
}
|
128
|
+
pub fn resolver(&self) -> Arc<ResolvesServerCertAcme> {
|
129
|
+
self.resolver.clone()
|
130
|
+
}
|
131
|
+
pub fn new(config: AcmeConfig<EC, EA>) -> Self {
|
132
|
+
let config = Arc::new(config);
|
133
|
+
Self {
|
134
|
+
config: config.clone(),
|
135
|
+
resolver: ResolvesServerCertAcme::new(),
|
136
|
+
account_key: None,
|
137
|
+
early_action: None,
|
138
|
+
load_cert: Some(Box::pin({
|
139
|
+
let config = config.clone();
|
140
|
+
async move {
|
141
|
+
config
|
142
|
+
.cache
|
143
|
+
.load_cert(&config.domains, &config.directory_url)
|
144
|
+
.await
|
145
|
+
}
|
146
|
+
})),
|
147
|
+
load_account: Some(Box::pin({
|
148
|
+
let config = config;
|
149
|
+
async move {
|
150
|
+
config
|
151
|
+
.cache
|
152
|
+
.load_account(&config.contact, &config.directory_url)
|
153
|
+
.await
|
154
|
+
}
|
155
|
+
})),
|
156
|
+
order: None,
|
157
|
+
backoff_cnt: 0,
|
158
|
+
wait: None,
|
159
|
+
}
|
160
|
+
}
|
161
|
+
fn parse_cert(pem: &[u8]) -> Result<(CertifiedKey, [DateTime<Utc>; 2]), CertParseError> {
|
162
|
+
let mut pems = pem::parse_many(pem)?;
|
163
|
+
if pems.len() < 2 {
|
164
|
+
return Err(CertParseError::TooFewPem(pems.len()));
|
165
|
+
}
|
166
|
+
let pk_bytes = pems.remove(0).into_contents();
|
167
|
+
let pk_der: PrivatePkcs8KeyDer = pk_bytes.into();
|
168
|
+
let pk: PrivateKeyDer = pk_der.into();
|
169
|
+
let pk = match any_ecdsa_type(&pk) {
|
170
|
+
Ok(pk) => pk,
|
171
|
+
Err(_) => return Err(CertParseError::InvalidPrivateKey),
|
172
|
+
};
|
173
|
+
let cert_chain: Vec<RustlsCertificate> =
|
174
|
+
pems.into_iter().map(|p| p.into_contents().into()).collect();
|
175
|
+
let validity = match parse_x509_certificate(cert_chain[0].as_ref()) {
|
176
|
+
Ok((_, cert)) => {
|
177
|
+
let validity = cert.validity();
|
178
|
+
[validity.not_before, validity.not_after]
|
179
|
+
.map(|t| Utc.timestamp_opt(t.timestamp(), 0).earliest().unwrap())
|
180
|
+
}
|
181
|
+
Err(err) => return Err(CertParseError::X509(err)),
|
182
|
+
};
|
183
|
+
let cert = CertifiedKey::new(cert_chain, pk);
|
184
|
+
Ok((cert, validity))
|
185
|
+
}
|
186
|
+
|
187
|
+
#[allow(clippy::result_large_err)]
|
188
|
+
fn process_cert(&mut self, pem: Vec<u8>, cached: bool) -> Event<EC, EA> {
|
189
|
+
let (cert, validity) = match (Self::parse_cert(&pem), cached) {
|
190
|
+
(Ok(r), _) => r,
|
191
|
+
(Err(err), cached) => {
|
192
|
+
return match cached {
|
193
|
+
true => Err(EventError::CachedCertParse(err)),
|
194
|
+
false => Err(EventError::NewCertParse(err)),
|
195
|
+
}
|
196
|
+
}
|
197
|
+
};
|
198
|
+
self.resolver.set_cert(Arc::new(cert));
|
199
|
+
let wait_duration = (validity[1] - (validity[1] - validity[0]) / 3 - Utc::now())
|
200
|
+
.max(chrono::Duration::zero())
|
201
|
+
.to_std()
|
202
|
+
.unwrap_or_default();
|
203
|
+
self.wait = Some(after(wait_duration));
|
204
|
+
if cached {
|
205
|
+
return Ok(EventOk::DeployedCachedCert);
|
206
|
+
}
|
207
|
+
let config = self.config.clone();
|
208
|
+
self.early_action = Some(Box::pin(async move {
|
209
|
+
match config
|
210
|
+
.cache
|
211
|
+
.store_cert(&config.domains, &config.directory_url, &pem)
|
212
|
+
.await
|
213
|
+
{
|
214
|
+
Ok(()) => Ok(EventOk::CertCacheStore),
|
215
|
+
Err(err) => Err(EventError::CertCacheStore(err)),
|
216
|
+
}
|
217
|
+
}));
|
218
|
+
Event::Ok(EventOk::DeployedNewCert)
|
219
|
+
}
|
220
|
+
async fn order(
|
221
|
+
config: Arc<AcmeConfig<EC, EA>>,
|
222
|
+
resolver: Arc<ResolvesServerCertAcme>,
|
223
|
+
key_pair: Vec<u8>,
|
224
|
+
) -> Result<Vec<u8>, OrderError> {
|
225
|
+
let directory = Directory::discover(&config.client_config, &config.directory_url).await?;
|
226
|
+
let account = Account::create_with_keypair(
|
227
|
+
&config.client_config,
|
228
|
+
directory,
|
229
|
+
&config.contact,
|
230
|
+
&key_pair,
|
231
|
+
&config.eab,
|
232
|
+
)
|
233
|
+
.await?;
|
234
|
+
|
235
|
+
let mut params = CertificateParams::new(config.domains.clone())?;
|
236
|
+
params.distinguished_name = DistinguishedName::new();
|
237
|
+
let key_pair = rcgen::KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)?;
|
238
|
+
|
239
|
+
let (order_url, mut order) = account
|
240
|
+
.new_order(&config.client_config, config.domains.clone())
|
241
|
+
.await?;
|
242
|
+
loop {
|
243
|
+
match order.status {
|
244
|
+
OrderStatus::Pending => {
|
245
|
+
let auth_futures = order
|
246
|
+
.authorizations
|
247
|
+
.iter()
|
248
|
+
.map(|url| Self::authorize(&config, &resolver, &account, url));
|
249
|
+
try_join_all(auth_futures).await?;
|
250
|
+
log::info!("completed all authorizations");
|
251
|
+
order = account.order(&config.client_config, &order_url).await?;
|
252
|
+
}
|
253
|
+
OrderStatus::Processing => {
|
254
|
+
for i in 0u64..10 {
|
255
|
+
log::info!("order processing");
|
256
|
+
after(Duration::from_secs(1u64 << i)).await;
|
257
|
+
order = account.order(&config.client_config, &order_url).await?;
|
258
|
+
if order.status != OrderStatus::Processing {
|
259
|
+
break;
|
260
|
+
}
|
261
|
+
}
|
262
|
+
if order.status == OrderStatus::Processing {
|
263
|
+
return Err(OrderError::ProcessingTimeout(order));
|
264
|
+
}
|
265
|
+
}
|
266
|
+
OrderStatus::Ready => {
|
267
|
+
log::info!("sending csr");
|
268
|
+
let csr = params.serialize_request(&key_pair)?;
|
269
|
+
order = account
|
270
|
+
.finalize(&config.client_config, order.finalize, csr.der().to_vec())
|
271
|
+
.await?
|
272
|
+
}
|
273
|
+
OrderStatus::Valid { certificate } => {
|
274
|
+
log::info!("download certificate");
|
275
|
+
let pem = [
|
276
|
+
&key_pair.serialize_pem(),
|
277
|
+
"\n",
|
278
|
+
&account
|
279
|
+
.certificate(&config.client_config, certificate)
|
280
|
+
.await?,
|
281
|
+
]
|
282
|
+
.concat();
|
283
|
+
return Ok(pem.into_bytes());
|
284
|
+
}
|
285
|
+
OrderStatus::Invalid => return Err(OrderError::BadOrder(order)),
|
286
|
+
}
|
287
|
+
}
|
288
|
+
}
|
289
|
+
async fn authorize(
|
290
|
+
config: &AcmeConfig<EC, EA>,
|
291
|
+
resolver: &ResolvesServerCertAcme,
|
292
|
+
account: &Account,
|
293
|
+
url: &String,
|
294
|
+
) -> Result<(), OrderError> {
|
295
|
+
let auth = account.auth(&config.client_config, url).await?;
|
296
|
+
let (domain, challenge_url) = match auth.status {
|
297
|
+
AuthStatus::Pending => {
|
298
|
+
let Identifier::Dns(domain) = auth.identifier;
|
299
|
+
log::info!("trigger challenge for {}", &domain);
|
300
|
+
let (challenge, auth_key) =
|
301
|
+
account.tls_alpn_01(&auth.challenges, domain.clone())?;
|
302
|
+
resolver.set_auth_key(domain.clone(), Arc::new(auth_key));
|
303
|
+
account
|
304
|
+
.challenge(&config.client_config, &challenge.url)
|
305
|
+
.await?;
|
306
|
+
(domain, challenge.url.clone())
|
307
|
+
}
|
308
|
+
AuthStatus::Valid => return Ok(()),
|
309
|
+
_ => return Err(OrderError::BadAuth(auth)),
|
310
|
+
};
|
311
|
+
for i in 0u64..5 {
|
312
|
+
after(Duration::from_secs(1u64 << i)).await;
|
313
|
+
let auth = account.auth(&config.client_config, url).await?;
|
314
|
+
match auth.status {
|
315
|
+
AuthStatus::Pending => {
|
316
|
+
log::info!("authorization for {} still pending", &domain);
|
317
|
+
account
|
318
|
+
.challenge(&config.client_config, &challenge_url)
|
319
|
+
.await?
|
320
|
+
}
|
321
|
+
AuthStatus::Valid => return Ok(()),
|
322
|
+
_ => return Err(OrderError::BadAuth(auth)),
|
323
|
+
}
|
324
|
+
}
|
325
|
+
Err(OrderError::TooManyAttemptsAuth(domain))
|
326
|
+
}
|
327
|
+
fn poll_next_infinite(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Event<EC, EA>> {
|
328
|
+
loop {
|
329
|
+
// queued early action
|
330
|
+
if let Some(early_action) = &mut self.early_action {
|
331
|
+
let result = ready!(early_action.poll_unpin(cx));
|
332
|
+
self.early_action.take();
|
333
|
+
return Poll::Ready(result);
|
334
|
+
}
|
335
|
+
|
336
|
+
// sleep
|
337
|
+
if let Some(timer) = &mut self.wait {
|
338
|
+
ready!(timer.poll_unpin(cx));
|
339
|
+
self.wait.take();
|
340
|
+
}
|
341
|
+
|
342
|
+
// load from cert cache
|
343
|
+
if let Some(load_cert) = &mut self.load_cert {
|
344
|
+
let result = ready!(load_cert.poll_unpin(cx));
|
345
|
+
self.load_cert.take();
|
346
|
+
match result {
|
347
|
+
Ok(Some(pem)) => {
|
348
|
+
return Poll::Ready(Self::process_cert(self.get_mut(), pem, true));
|
349
|
+
}
|
350
|
+
Ok(None) => {}
|
351
|
+
Err(err) => return Poll::Ready(Err(EventError::CertCacheLoad(err))),
|
352
|
+
}
|
353
|
+
}
|
354
|
+
|
355
|
+
// load from account cache
|
356
|
+
if let Some(load_account) = &mut self.load_account {
|
357
|
+
let result = ready!(load_account.poll_unpin(cx));
|
358
|
+
self.load_account.take();
|
359
|
+
match result {
|
360
|
+
Ok(Some(key_pair)) => self.account_key = Some(key_pair),
|
361
|
+
Ok(None) => {}
|
362
|
+
Err(err) => return Poll::Ready(Err(EventError::AccountCacheLoad(err))),
|
363
|
+
}
|
364
|
+
}
|
365
|
+
|
366
|
+
// execute order
|
367
|
+
if let Some(order) = &mut self.order {
|
368
|
+
let result = ready!(order.poll_unpin(cx));
|
369
|
+
self.order.take();
|
370
|
+
match result {
|
371
|
+
Ok(pem) => {
|
372
|
+
self.backoff_cnt = 0;
|
373
|
+
return Poll::Ready(Self::process_cert(self.get_mut(), pem, false));
|
374
|
+
}
|
375
|
+
Err(err) => {
|
376
|
+
// TODO: replace key on some errors or high backoff_cnt?
|
377
|
+
self.wait = Some(after(Duration::from_secs(1 << self.backoff_cnt)));
|
378
|
+
self.backoff_cnt = (self.backoff_cnt + 1).min(16);
|
379
|
+
return Poll::Ready(Err(EventError::Order(err)));
|
380
|
+
}
|
381
|
+
}
|
382
|
+
}
|
383
|
+
|
384
|
+
// schedule order
|
385
|
+
let account_key = match &self.account_key {
|
386
|
+
None => {
|
387
|
+
let account_key = Account::generate_key_pair();
|
388
|
+
self.account_key = Some(account_key.clone());
|
389
|
+
let config = self.config.clone();
|
390
|
+
let account_key_clone = account_key.clone();
|
391
|
+
self.early_action = Some(Box::pin(async move {
|
392
|
+
match config
|
393
|
+
.cache
|
394
|
+
.store_account(
|
395
|
+
&config.contact,
|
396
|
+
&config.directory_url,
|
397
|
+
&account_key_clone,
|
398
|
+
)
|
399
|
+
.await
|
400
|
+
{
|
401
|
+
Ok(()) => Ok(EventOk::AccountCacheStore),
|
402
|
+
Err(err) => Err(EventError::AccountCacheStore(err)),
|
403
|
+
}
|
404
|
+
}));
|
405
|
+
account_key
|
406
|
+
}
|
407
|
+
Some(account_key) => account_key.clone(),
|
408
|
+
};
|
409
|
+
let config = self.config.clone();
|
410
|
+
let resolver = self.resolver.clone();
|
411
|
+
self.order = Some(Box::pin({
|
412
|
+
Self::order(config.clone(), resolver.clone(), account_key)
|
413
|
+
}));
|
414
|
+
}
|
415
|
+
}
|
416
|
+
}
|
417
|
+
|
418
|
+
impl<EC: 'static + Debug, EA: 'static + Debug> Stream for AcmeState<EC, EA> {
|
419
|
+
type Item = Event<EC, EA>;
|
420
|
+
|
421
|
+
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
422
|
+
Poll::Ready(Some(ready!(self.poll_next_infinite(cx))))
|
423
|
+
}
|
424
|
+
}
|
data/ext/itsi_error/Cargo.toml
CHANGED
data/ext/itsi_error/src/lib.rs
CHANGED
@@ -1,24 +1,123 @@
|
|
1
|
-
pub
|
1
|
+
pub use anyhow::Context;
|
2
|
+
use magnus::Error as MagnusError;
|
3
|
+
use magnus::{
|
4
|
+
error::ErrorType,
|
5
|
+
exception::{self, arg_error, standard_error},
|
6
|
+
};
|
2
7
|
use thiserror::Error;
|
3
8
|
|
9
|
+
pub static CLIENT_CONNECTION_CLOSED: &str = "Client disconnected";
|
4
10
|
pub type Result<T> = std::result::Result<T, ItsiError>;
|
5
11
|
|
6
12
|
#[derive(Error, Debug)]
|
7
13
|
pub enum ItsiError {
|
8
|
-
#[error("Invalid input {0}")]
|
14
|
+
#[error("Invalid input: {0}")]
|
9
15
|
InvalidInput(String),
|
10
|
-
|
16
|
+
|
17
|
+
#[error("Internal server error: {0}")]
|
11
18
|
InternalServerError(String),
|
12
|
-
|
19
|
+
|
20
|
+
#[error("Unsupported protocol: {0}")]
|
13
21
|
UnsupportedProtocol(String),
|
22
|
+
|
14
23
|
#[error("Argument error: {0}")]
|
15
24
|
ArgumentError(String),
|
25
|
+
|
16
26
|
#[error("Client Connection Closed")]
|
17
27
|
ClientConnectionClosed,
|
18
|
-
|
28
|
+
|
29
|
+
#[error("Internal Error")]
|
30
|
+
InternalError(String),
|
31
|
+
|
32
|
+
#[error(transparent)]
|
33
|
+
Io(#[from] std::io::Error),
|
34
|
+
|
35
|
+
#[error(transparent)]
|
36
|
+
Rcgen(#[from] rcgen::Error),
|
37
|
+
|
38
|
+
#[error(transparent)]
|
39
|
+
HttpParse(#[from] httparse::Error),
|
40
|
+
|
41
|
+
#[error(transparent)]
|
42
|
+
NixErrno(#[from] nix::errno::Errno),
|
43
|
+
|
44
|
+
#[error(transparent)]
|
45
|
+
Nul(#[from] std::ffi::NulError),
|
46
|
+
|
47
|
+
#[error("Jump: {0}")]
|
19
48
|
Jump(String),
|
49
|
+
|
20
50
|
#[error("Break")]
|
21
|
-
Break
|
51
|
+
Break,
|
52
|
+
|
22
53
|
#[error("Pass")]
|
23
|
-
Pass
|
54
|
+
Pass,
|
55
|
+
|
56
|
+
#[error(transparent)]
|
57
|
+
Anyhow(#[from] anyhow::Error),
|
24
58
|
}
|
59
|
+
|
60
|
+
impl From<magnus::Error> for ItsiError {
|
61
|
+
fn from(err: magnus::Error) -> Self {
|
62
|
+
match err.error_type() {
|
63
|
+
ErrorType::Jump(tag) => ItsiError::Jump(tag.to_string()),
|
64
|
+
ErrorType::Error(_exception_class, cow) => ItsiError::ArgumentError(cow.to_string()),
|
65
|
+
ErrorType::Exception(exception) => ItsiError::ArgumentError(exception.to_string()),
|
66
|
+
}
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
pub trait IntoMagnusError {
|
71
|
+
fn into_magnus_error(self) -> MagnusError;
|
72
|
+
}
|
73
|
+
|
74
|
+
impl<T: std::error::Error> IntoMagnusError for T {
|
75
|
+
fn into_magnus_error(self) -> MagnusError {
|
76
|
+
MagnusError::new(standard_error(), self.to_string())
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
impl From<&str> for ItsiError {
|
81
|
+
fn from(s: &str) -> Self {
|
82
|
+
ItsiError::InternalError(s.to_owned())
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
impl From<String> for ItsiError {
|
87
|
+
fn from(s: String) -> Self {
|
88
|
+
ItsiError::InternalError(s)
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
impl From<ItsiError> for magnus::Error {
|
93
|
+
fn from(err: ItsiError) -> Self {
|
94
|
+
match err {
|
95
|
+
ItsiError::InvalidInput(msg) => magnus::Error::new(arg_error(), msg),
|
96
|
+
ItsiError::InternalServerError(msg) => magnus::Error::new(standard_error(), msg),
|
97
|
+
ItsiError::InternalError(msg) => magnus::Error::new(standard_error(), msg),
|
98
|
+
ItsiError::UnsupportedProtocol(msg) => magnus::Error::new(arg_error(), msg),
|
99
|
+
ItsiError::ArgumentError(msg) => magnus::Error::new(arg_error(), msg),
|
100
|
+
ItsiError::Jump(msg) => magnus::Error::new(exception::local_jump_error(), msg),
|
101
|
+
ItsiError::ClientConnectionClosed => {
|
102
|
+
magnus::Error::new(exception::eof_error(), CLIENT_CONNECTION_CLOSED)
|
103
|
+
}
|
104
|
+
ItsiError::Break => magnus::Error::new(exception::interrupt(), "Break"),
|
105
|
+
ItsiError::Pass => magnus::Error::new(exception::interrupt(), "Pass"),
|
106
|
+
ItsiError::Io(err) => err.into_magnus_error(),
|
107
|
+
ItsiError::Rcgen(err) => err.into_magnus_error(),
|
108
|
+
ItsiError::HttpParse(err) => err.into_magnus_error(),
|
109
|
+
ItsiError::NixErrno(err) => err.into_magnus_error(),
|
110
|
+
ItsiError::Nul(err) => err.into_magnus_error(),
|
111
|
+
ItsiError::Anyhow(err) => err.into_magnus_error(),
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
impl ItsiError {
|
117
|
+
pub fn new(error: impl Send + Sync + 'static + std::fmt::Display) -> Self {
|
118
|
+
ItsiError::InternalError(format!("{}", error))
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
unsafe impl Send for ItsiError {}
|
123
|
+
unsafe impl Sync for ItsiError {}
|